mirror of
https://github.com/JMP-MS/ASPIC.git
synced 2026-10-27 16:07:10 +00:00
V1.006 Suite de l'intégration de la voix
This commit is contained in:
+180
@@ -0,0 +1,180 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ASPIC — agent_notes.php
|
||||||
|
* (renommé le 2 septembre 2026, ex-lilou_notes.php — n'a plus de raison de
|
||||||
|
* porter le nom d'un seul agent maintenant que ASP_Agents en porte
|
||||||
|
* plusieurs, voir plus bas et journal section 27)
|
||||||
|
*
|
||||||
|
* Étape 1 du pilotage vocal, volontairement "sans interprétation" (1er
|
||||||
|
* septembre 2026, décision de Jean-Marc) : reçoit le texte dicté à un agent
|
||||||
|
* entre une phrase d'ouverture ("Lilou, ajoute une tâche...", "Viktor, un
|
||||||
|
* truc à faire...", "Lilou, note ça dans ma todolist...") et une phrase de
|
||||||
|
* clôture ("c'est tout Lilou") ou d'annulation ("Lilou annule"), et
|
||||||
|
* l'enregistre tel quel, ligne par ligne — aucune tentative de
|
||||||
|
* compréhension du CONTENU ici (demande explicite de Jean-Marc). Objectif :
|
||||||
|
* accumuler un vrai corpus d'essais réels avant de définir des règles
|
||||||
|
* d'interprétation (étape 2, envisagée via n8n, avec des règles modifiables
|
||||||
|
* par l'utilisateur — voir réflexions stratégiques, section 10).
|
||||||
|
*
|
||||||
|
* POST uniquement, corps JSON : { "agent": "...", "lignes": [...] }
|
||||||
|
*
|
||||||
|
* "agent" (ajouté le 2 septembre 2026, suite de ASP_Agents/CVX_AgentTraitant
|
||||||
|
* — voir journal section 27 et réflexions stratégiques) : nom de l'agent
|
||||||
|
* reconnu en tête de la phrase d'ouverture ("Viktor", "Matilda"...), un
|
||||||
|
* seul par lot puisqu'une notation entière s'adresse à un seul assistant.
|
||||||
|
* Copié tel quel dans CVX_AgentTraitant sur chaque ligne du lot (colonne
|
||||||
|
* plate, pas de table de lots séparée — cohérent avec le reste de cette
|
||||||
|
* table). Absent ou vide (compatibilité ascendante avec un micro.js pas
|
||||||
|
* encore au courant des autres agents) : on retombe sur l'agent marqué
|
||||||
|
* ASP_Agents.AGN_Defaut = 'O' — plus de nom d'agent figé en dur ici depuis
|
||||||
|
* le retrait de Lilou le 2 septembre 2026 (voir plus bas et journal
|
||||||
|
* section 27) : l'agent de repli peut changer sans toucher au code.
|
||||||
|
*
|
||||||
|
* Format de chaque entrée de "lignes" (évolution du 2 septembre 2026,
|
||||||
|
* demande de Jean-Marc) : soit un objet {"texte": "...", "type": "..."},
|
||||||
|
* soit une simple chaîne (compatibilité avec un ancien micro.js non
|
||||||
|
* redéployé — traitée alors comme une ligne de type "contenu"). Types
|
||||||
|
* possibles : "ouverture" (phrase déclenchante elle-même), "contenu"
|
||||||
|
* (dictée proprement dite), "cloture" ou "annulation" (phrase de fin,
|
||||||
|
* distinguées cette fois — contrairement au contenu, dont le traitement ne
|
||||||
|
* change pas selon clôture ou annulation, décision du 1er septembre
|
||||||
|
* inchangée). Objectif : qu'un futur robot sache, sans ré-analyser le texte
|
||||||
|
* brut, où commence/finit un lot, s'il doit être exécuté ou écarté, et à
|
||||||
|
* quel assistant le router (CVX_AgentTraitant).
|
||||||
|
*
|
||||||
|
* Stockage (2 septembre 2026, pivot décidé par Jean-Marc) : le fichier
|
||||||
|
* texte global (notes_lilou/dictees.txt) est abandonné — l'essai du matin
|
||||||
|
* même une fois le fichier créé manuellement à la racine n'a rien donné
|
||||||
|
* (voir journal, section 27), sans compter le souci de droits d'écriture
|
||||||
|
* sur notes_lilou/ jamais vraiment résolu. Chaque ligne est désormais
|
||||||
|
* enregistrée directement dans la table ASP_ConsignesVoix (une ligne =
|
||||||
|
* une consigne = une ligne de dictée), même pattern de connexion que les
|
||||||
|
* autres endpoints (todolist.php, contacts.php) via getDbConnection().
|
||||||
|
* Nom de table volontairement générique ("ConsignesVoix"), pas centré sur
|
||||||
|
* "Lilou" — l'assistante n'étant qu'une des interfaces possibles vers ce
|
||||||
|
* mécanisme.
|
||||||
|
*
|
||||||
|
* Session utilisateur : accès réservé aux utilisateurs identifiés, même
|
||||||
|
* principe que les autres endpoints (todolist.php, contacts.php...).
|
||||||
|
*/
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
require __DIR__ . '/db.php';
|
||||||
|
|
||||||
|
if (empty($_SESSION['USR_Code'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'Non identifié — veuillez vous connecter.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['error' => 'Méthode non autorisée']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||||
|
$lignes = $data['lignes'] ?? [];
|
||||||
|
|
||||||
|
if (!is_array($lignes) || count($lignes) === 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Aucune ligne à enregistrer']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPES_VALIDES = ['ouverture', 'contenu', 'cloture', 'annulation'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = getDbConnection();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Connexion base de données impossible']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$agent = trim((string) ($data['agent'] ?? ''));
|
||||||
|
if ($agent === '') {
|
||||||
|
// Agent par défaut piloté par la donnée (2 septembre 2026, décision de
|
||||||
|
// Jean-Marc : plus question de figer un nom d'agent en dur ici — après
|
||||||
|
// Lilou, retirée de ASP_Agents le même jour pour cause de confusions
|
||||||
|
// récurrentes, un prochain agent par défaut pourrait tout aussi bien
|
||||||
|
// changer). AGN_Defaut ('O'/'N') désigne l'agent de repli directement
|
||||||
|
// en base, modifiable par Jean-Marc sans toucher au code. Repli
|
||||||
|
// supplémentaire sur le premier agent de la table si aucun n'est
|
||||||
|
// marqué par défaut (ne devrait pas arriver en usage normal, simple
|
||||||
|
// filet de sécurité pour ne jamais laisser CVX_AgentTraitant vide).
|
||||||
|
try {
|
||||||
|
$stmtDefaut = $pdo->query(
|
||||||
|
"SELECT AGN_Nom FROM ASP_Agents WHERE AGN_Defaut = 'O' ORDER BY AGN_Incr ASC LIMIT 1"
|
||||||
|
);
|
||||||
|
$agent = $stmtDefaut->fetchColumn();
|
||||||
|
if ($agent === false || $agent === '') {
|
||||||
|
$stmtPremier = $pdo->query('SELECT AGN_Nom FROM ASP_Agents ORDER BY AGN_Incr ASC LIMIT 1');
|
||||||
|
$agent = $stmtPremier->fetchColumn();
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$agent = false;
|
||||||
|
}
|
||||||
|
if ($agent === false || $agent === '') {
|
||||||
|
$agent = 'Agent';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mb_strlen($agent) > 20) {
|
||||||
|
$agent = mb_substr($agent, 0, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Un seul horodatage pour tout le lot reçu dans cet appel (même principe
|
||||||
|
// que l'ancien fichier texte, qui datait le bloc entier plutôt que chaque
|
||||||
|
// ligne individuellement — les lignes d'un même lot ont été dictées à
|
||||||
|
// quelques secondes d'écart, la distinction n'aurait pas de sens).
|
||||||
|
$horodatage = date('Y-m-d H:i:s');
|
||||||
|
$codeUser = $_SESSION['USR_Code'];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'INSERT INTO ASP_ConsignesVoix (CVX_Consigne, CVX_Type, CVX_DateHeure, CVX_CodeUser, CVX_AgentTraitant) VALUES (?, ?, ?, ?, ?)'
|
||||||
|
);
|
||||||
|
|
||||||
|
$enregistrees = 0;
|
||||||
|
try {
|
||||||
|
foreach ($lignes as $entree) {
|
||||||
|
// Accepte un objet {texte, type} (nouveau format) ou une simple
|
||||||
|
// chaîne (ancien format, avant l'ajout du type par ligne) —
|
||||||
|
// traitée alors comme une ligne de contenu ordinaire.
|
||||||
|
if (is_array($entree)) {
|
||||||
|
$texte = trim((string) ($entree['texte'] ?? ''));
|
||||||
|
$type = (string) ($entree['type'] ?? 'contenu');
|
||||||
|
} else {
|
||||||
|
$texte = trim((string) $entree);
|
||||||
|
$type = 'contenu';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($texte === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!in_array($type, TYPES_VALIDES, true)) {
|
||||||
|
$type = 'contenu';
|
||||||
|
}
|
||||||
|
// CVX_Consigne est un varchar(200) : une dictée plus longue est
|
||||||
|
// tronquée plutôt que de faire échouer tout le lot (mb_substr pour
|
||||||
|
// ne pas couper un caractère accentué en deux) — pas de perte
|
||||||
|
// totale d'une ligne pour un dépassement de quelques caractères.
|
||||||
|
if (mb_strlen($texte) > 200) {
|
||||||
|
$texte = mb_substr($texte, 0, 200);
|
||||||
|
}
|
||||||
|
$stmt->execute([$texte, $type, $horodatage, $codeUser, $agent]);
|
||||||
|
$enregistrees++;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Erreur base de données : ' . $e->getMessage()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($enregistrees === 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Aucune ligne à enregistrer']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'lignes_enregistrees' => $enregistrees]);
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ASPIC — agents.php
|
||||||
|
* Liste des agents (ASP_Agents) — assistants IA nommés auxquels une
|
||||||
|
* consigne vocale peut être adressée (2 septembre 2026, suite du pilotage
|
||||||
|
* vocal, voir journal section 27 et réflexions stratégiques). Consultée
|
||||||
|
* par micro.js au chargement pour reconnaître dynamiquement, à la place du
|
||||||
|
* seul mot déclencheur "Lilou" d'origine, le nom de l'agent auquel
|
||||||
|
* l'utilisateur s'adresse ("Viktor, ajoute un prospect...", etc.) — et
|
||||||
|
* enregistrer ce nom dans CVX_AgentTraitant sans avoir à modifier le code
|
||||||
|
* à chaque agent ajouté ou retiré.
|
||||||
|
*
|
||||||
|
* GET seul : ASP_Agents est une table gérée à la main (voir journal de
|
||||||
|
* développement), pas de création depuis ce endpoint — même principe que
|
||||||
|
* utilisateurs.php pour ASP_Users.
|
||||||
|
*/
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
require __DIR__ . '/db.php';
|
||||||
|
|
||||||
|
if (empty($_SESSION['USR_Code'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'Non identifié — veuillez vous connecter.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['error' => 'Méthode non autorisée']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = getDbConnection();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Connexion base de données impossible']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// AGN_Variantes (ajoutée le 2 septembre 2026, voir journal section 27) :
|
||||||
|
// graphies alternatives du prénom séparées par une virgule ("Milou,
|
||||||
|
// Loulou"), utilisées par micro.js pour tolérer les confusions
|
||||||
|
// récurrentes de la reconnaissance vocale sans avoir à modifier le
|
||||||
|
// code à chaque fois — colonne éditable directement par Jean-Marc.
|
||||||
|
$stmt = $pdo->query('SELECT AGN_Incr, AGN_Nom, AGN_Fonction, AGN_Variantes FROM ASP_Agents ORDER BY AGN_Incr ASC');
|
||||||
|
echo json_encode(['agents' => $stmt->fetchAll()]);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Erreur base de données : ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
*CRM sur mesure — PHP + MySQL + JavaScript vanilla*
|
*CRM sur mesure — PHP + MySQL + JavaScript vanilla*
|
||||||
|
|
||||||
**Le détail complet du journal est désormais tenu dans le dépôt GitHub `JMP-MS/ASPIC`, dossier `/journal`** (26 fichiers `.md`, un par grande section, protégés en accès web direct par un `.htaccess`). Ce document ne garde plus qu'un sommaire et l'état actuel, pour une vue d'ensemble rapide sans avoir à cloner le dépôt. Pour toute question qui touche à l'historique détaillé d'une section, demander sa lecture — Claude ira la chercher sur GitHub avec le token transmis en session (jamais stocké).
|
**Le détail complet du journal est désormais tenu dans le dépôt GitHub `JMP-MS/ASPIC`, dossier `/journal`** (28 fichiers `.md`, un par grande section, protégés en accès web direct par un `.htaccess`). Ce document ne garde plus qu'un sommaire et l'état actuel, pour une vue d'ensemble rapide sans avoir à cloner le dépôt. Pour toute question qui touche à l'historique détaillé d'une section, demander sa lecture — Claude ira la chercher sur GitHub avec le token transmis en session (jamais stocké).
|
||||||
|
|
||||||
## Consigne de travail (Claude) — à partir du 2 septembre 2026
|
## Consigne de travail (Claude) — à partir du 2 septembre 2026
|
||||||
|
|
||||||
Pour les trois documents de ce projet (ce journal — résumé/index —, les Réflexions stratégiques, la Documentation utilisateur), **ainsi que pour les fichiers de section individuels du journal détaillé** (`/journal` sur GitHub, 28 sections numérotées 00 à 27 au 2 septembre 2026) : les mises à jour de contenu ne sont faites qu'**en toute fin de session**, jamais au fil de l'eau pendant les essais — pour éviter d'enregistrer un état intermédiaire ou instable. À ce moment-là, en plus d'écrire la mise à jour dans le document du projet le cas échéant, Claude fournit systématiquement une **copie téléchargeable** (fichier `.md`) de chaque document ou section modifié(e), pour que Jean-Marc puisse la sauvegarder de son côté et la déposer dans le dépôt GitHub. Pour une section du journal détaillé, le fichier téléchargeable reprend le nom exact utilisé dans `/journal` sur GitHub, pour un dépôt direct sans renommage.
|
Pour les trois documents de ce projet (ce journal — résumé/index —, les Réflexions stratégiques, la Documentation utilisateur), **ainsi que pour les fichiers de section individuels du journal détaillé** (`/journal` sur GitHub, 28 sections numérotées 00 à 27 au 2 septembre 2026) : les mises à jour de contenu ne sont faites qu'**en toute fin de session**, jamais au fil de l'eau pendant les essais — pour éviter d'enregistrer un état intermédiaire ou instable. À ce moment-là, en plus d'écrire la mise à jour dans le document du projet le cas échéant, Claude fournit systématiquement une **copie téléchargeable** (fichier `.md`) de chaque document ou section modifié(e), pour que Jean-Marc puisse la sauvegarder de son côté et la déposer dans le dépôt GitHub. Pour une section du journal détaillé, le fichier téléchargeable reprend le nom exact utilisé dans `/journal` sur GitHub, pour un dépôt direct sans renommage.
|
||||||
|
|
||||||
|
**Cas particulier des fichiers de code livrés en session (2 septembre 2026) :** un fichier `.js` n'est pas téléchargeable tel quel depuis le client utilisé par Jean-Marc. Pour tout fichier JavaScript livré (`micro.js` et les suivants), fournir systématiquement, en plus du fichier `.js` normal, une copie strictement identique au format `.txt` (même nom, extension changée — ex. `micro.js.txt`), pour qu'elle soit récupérable.
|
||||||
|
|
||||||
|
**Précision du 2 septembre 2026 (fin de session) sur les exports SQL transmis par Jean-Marc :** un `CREATE TABLE`/`INSERT` envoyé par Jean-Marc est le compte-rendu d'une modification déjà faite à la main côté base (il travaille en éditant directement, puis exporte pour informer Claude) — jamais une commande à exécuter côté Claude. Claude peut donc lire ces exports comme l'état réel de la base au moment de l'envoi, sans jamais chercher à les "lancer".
|
||||||
|
|
||||||
## Sommaire des sections (`/journal` sur GitHub)
|
## Sommaire des sections (`/journal` sur GitHub)
|
||||||
|
|
||||||
- 00. Présentation
|
- 00. Présentation
|
||||||
@@ -37,20 +41,24 @@ Pour les trois documents de ce projet (ce journal — résumé/index —, les R
|
|||||||
- 24. MIRADOR branché
|
- 24. MIRADOR branché
|
||||||
- 25. MIRADOR validé à deux utilisateurs
|
- 25. MIRADOR validé à deux utilisateurs
|
||||||
- 26. ToDoList
|
- 26. ToDoList
|
||||||
- 27. Pilotage vocal (Lilou)
|
- 27. Pilotage vocal
|
||||||
|
|
||||||
*(Numérotation et titres repris du découpage fait par Jean-Marc le 31 août 2026, légèrement reformulés par lui à cette occasion — se référer aux fichiers eux-mêmes pour le libellé exact. Section 26 confirmée présente dans le dépôt le 1er septembre 2026. Section 27 (Pilotage vocal) n'a en revanche pas encore son fichier dans `/journal` au moment de cette mise à jour — comportement pas encore stabilisé, voir Reste à faire ci-dessous ; fichier à créer une fois le test de bout en bout confirmé.)*
|
*(Numérotation et titres repris du découpage fait par Jean-Marc le 31 août 2026, légèrement reformulés par lui à cette occasion — se référer aux fichiers eux-mêmes pour le libellé exact. Section 27 (Pilotage vocal) validée de bout en bout et son fichier définitif déposé dans `/journal` le 2 septembre 2026 — le nom "(Lilou)" porté un temps par cette section a été abandonné le même jour, l'assistante n'étant plus qu'un des agents disponibles parmi d'autres, voir Statut actuel ci-dessous.)*
|
||||||
|
|
||||||
## Statut actuel (2 septembre 2026)
|
## Statut actuel (2 septembre 2026)
|
||||||
|
|
||||||
**Écrans "Identification" et "Liste des contacts"** (avec panneau détail intégré) : aboutis depuis le 25 août 2026, testés en production. **Prospection** (formulaire externe, vérification mail, envoi de doc) : livrée et testée en conditions réelles le 26 août 2026 (section 18). **Dépôt Git** en place depuis le 27-28 août (section 20), avec circuit de livraison FileZilla → SSH VPS → `git push` (section 23) — l'écriture directe par Claude reste bloquée côté proxy Anthropic, contournement adopté sans gêne. **MIRADOR** (authentification centralisée multi-bases, identifiant = email) branché et validé à deux utilisateurs le 30 août 2026, déployé sur `aspic.mvl-tls.fr` (développement) et `crm.aspic.bzh` (test), chacun avec son propre environnement Docker (sections 21, 24, 25).
|
**Écrans "Identification" et "Liste des contacts"** (avec panneau détail intégré) : aboutis depuis le 25 août 2026, testés en production. **Prospection** (formulaire externe, vérification mail, envoi de doc) : livrée et testée en conditions réelles le 26 août 2026 (section 18). **Dépôt Git** en place depuis le 27-28 août (section 20), avec circuit de livraison FileZilla → SSH VPS → `git push` (section 23) — l'écriture directe par Claude reste bloquée côté proxy Anthropic, contournement adopté sans gêne. **MIRADOR** (authentification centralisée multi-bases, identifiant = email) branché et validé à deux utilisateurs le 30 août 2026, déployé sur `aspic.mvl-tls.fr` (développement) et `crm.aspic.bzh` (test), chacun avec son propre environnement Docker (sections 21, 24, 25).
|
||||||
|
|
||||||
**31 août 2026 : ToDoList.** Nouvel écran, devenu l'écran d'accueil post-connexion (la liste des contacts passe dans le menu hamburger). Table `ASP_ToDoList`, trois indicateurs d'urgence par pastille (rouge = à faire sans date ou en retard, orange = à faire dans les temps, vert = réalisé), tri par urgence calculé côté PHP. `TDL_ToDoDate` (date de création) reste éditable pour permettre l'antidatage d'une tâche oubliée. Nouveaux endpoints `todolist.php` et `utilisateurs.php`, nouvel écran `liste_todolist.html` (même gabarit que `liste_contacts.html`, hauteur de panneau égalisée entre les trois modes). Livré, testé (Playwright) et déployé — version **V1.004**, qui inclut aussi ce découpage du journal.
|
**31 août 2026 : ToDoList.** Nouvel écran, devenu l'écran d'accueil post-connexion (la liste des contacts passe dans le menu hamburger). Table `ASP_ToDoList`, trois indicateurs d'urgence par pastille (rouge = à faire sans date ou en retard, orange = à faire dans les temps, vert = réalisé), tri par urgence calculé côté PHP. Nouveaux endpoints `todolist.php` et `utilisateurs.php`, nouvel écran `liste_todolist.html`. Livré, testé (Playwright) et déployé — version **V1.004**.
|
||||||
|
|
||||||
**1er septembre 2026 : migration du sous-domaine de bêta test.** L'environnement de test bascule de `aspic.bzh` vers `crm.aspic.bzh`. Objectif : libérer la racine du domaine pour y installer, dans quelques mois, un site de promotion de la solution — si le contexte business évolue favorablement dans ce sens (voir réflexions stratégiques, section 7). Aucun changement fonctionnel côté application ; toute mention antérieure d'`aspic.bzh` comme environnement de test (dans ce document comme dans le journal détaillé sur GitHub) désigne donc désormais `crm.aspic.bzh`.
|
**1er septembre 2026 : migration du sous-domaine de bêta test.** L'environnement de test bascule de `aspic.bzh` vers `crm.aspic.bzh`, pour libérer la racine du domaine en vue d'un futur site de promotion de la solution (voir réflexions stratégiques, section 7). Aucun changement fonctionnel côté application.
|
||||||
|
|
||||||
**1er-2 septembre 2026 (nuit) : pilotage vocal — Lilou, phase 1.** Nouveau bouton micro dans le bandeau (positionné après l'avatar, pour une place fixe à l'écran), comportement centralisé dans un fichier partagé `micro.js` chargé sur chaque écran hôte — reconnaissance vocale réelle via l'API Web Speech du navigateur, écoute continue avec redémarrage automatique tant que l'utilisateur n'a pas explicitement refermé le micro, mot déclencheur "Lilou" (assistante vocale d'ASPIC, dans la même veine que "Viktor" côté contacts) pour ignorer la conversation de fond — tolérant aussi "Milou", confusion fréquente du moteur de reconnaissance sur ce prénom. Fonctionnalité livrée ce soir : le "mode notation", qui capture une dictée libre entre une phrase d'ouverture ("Lilou, ajoute une tâche" / "un truc à faire" / "note ça dans ma todolist") et une phrase de clôture ("c'est tout Lilou") ou d'annulation ("Lilou annule") — les deux traitées à l'identique, sans interprétation à ce stade, sur demande explicite de Jean-Marc — puis envoie le texte capté tel quel à un nouvel endpoint `lilou_notes.php`, qui l'ajoute, horodaté et attribué à l'utilisateur, à un fichier texte global (`notes_lilou/dictees.txt`, pensé pour être protégé par un `.htaccess` du même principe que `/journal`). Objectif : accumuler un vrai corpus d'essais réels avant de définir, dans un prochain chantier, des règles d'interprétation (étape 2, envisagée via n8n, avec des règles modifiables par l'utilisateur). Version incrémentée une fois pour cette session, **V1.005**.
|
**1er-2 septembre 2026 : pilotage vocal — de "Lilou" à un système multi-agents (V1.005 puis V1.006).** Détail complet en section 27 du journal (`/journal` sur GitHub), fichier définitif déposé le 2 septembre après validation de bout en bout. Résumé : nouveau bouton micro dans le bandeau, comportement centralisé dans `micro.js`, reconnaissance vocale réelle (API Web Speech, écoute continue). Un "mode notation" capture une dictée libre entre une phrase d'ouverture et une phrase de clôture/annulation, sans interprétation à ce stade (corpus brut, interprétation prévue plus tard via n8n). Stockage pivoté d'un fichier texte défaillant vers une table `ASP_ConsignesVoix`, avec un type par ligne (`CVX_Type` : ouverture/contenu/cloture/annulation) pour qu'un futur automatisme sache exploiter un lot sans ré-analyser le texte brut.
|
||||||
|
|
||||||
Longue session de débogage en conditions réelles cette nuit-là : plusieurs corrections apportées à `micro.js` (la clôture peut désormais porter à la fois la dernière ligne de contenu et la fin du lot dans une seule phrase, sans "ensuite" artificiel juste avant ; tolérance à la confusion "Lilou"/"Milou" ; fenêtre de quelques secondes pour recoller une phrase que le moteur de reconnaissance a coupée juste après la virgule suivant "Lilou"), et côté serveur (le fichier `lilou_notes.php` n'avait pas été déployé au premier essai ; droits d'écriture insuffisants sur `notes_lilou/` une fois déployé — dossier propriété de `1001:users`, `www-data` hors de ce groupe). Contournement provisoire en fin de session : `dictees.txt` écrit directement à la racine de l'application plutôt que dans `notes_lilou/`, donc **temporairement hors de toute protection `.htaccess`** — à corriger dès que possible. **Test de bout en bout non confirmé réussi au moment d'arrêter la session** ; à valider dès demain avec la dernière version livrée de `micro.js`.
|
Le système, initialement à agent unique ("Lilou"), a été généralisé le 2 septembre à plusieurs assistants IA nommés (table `ASP_Agents`, endpoint `agents.php`, `lilou_notes.php` renommé `agent_notes.php`) — chaque agent portant ses propres graphies alternatives tolérées (`AGN_Variantes`, pilotable en base sans code) et un agent par défaut (`AGN_Defaut`) pour ne plus jamais figer un nom précis dans le code. Un incident de confidentialité (bribes d'un appel téléphonique captées par un traçage de débogage resté actif) a conduit au retrait immédiat de ce traçage et à l'ajout d'une coupure automatique du micro après quelques minutes d'inactivité. Après plusieurs essais réels montrant des confusions de reconnaissance récurrentes, Jean-Marc a retiré deux agents (Lilou, Karl) plutôt que d'empiler des tolérances risquant de créer de faux déclenchements ou de mal vieillir face à la diversité des accents/prononciations — liste active : Viktor, Matilda (par défaut), Léon, Nikita. Version incrémentée une fois pour cette session, **V1.006**.
|
||||||
|
|
||||||
**Reste à faire, noté au passage :** vérifier concrètement que le `.htaccess` du dossier `/journal` bloque bien l'accès direct (`curl -I` sur un fichier du dossier doit répondre `403`, pas `200` — risque connu que `AllowOverride None` côté Apache ignore silencieusement le fichier, voir échange du 31 août). Pour le pilotage vocal (section 27) : confirmer demain qu'une dictée complète produit bien `dictees.txt` ; une fois validé, remettre ce fichier sous la protection d'un `.htaccess` dans `notes_lilou/` (en corrigant durablement les droits d'écriture de `www-data` sur ce dossier — `chown`/`chmod`, ou ajout de `www-data` au groupe `users` — plutôt que de rouvrir le dossier en écriture pour tout le monde) ; créer alors le fichier de section 27 dans `/journal` sur GitHub. `notes_lilou/` (avec son contenu, y compris `dictees.txt` au fil de son remplissage) est destiné à être versionné et poussé sur GitHub comme le reste de l'application — décision explicite de Jean-Marc, pour qu'une future session Claude puisse lire ce corpus directement en clonant le dépôt.
|
**Reste à faire, noté au passage :**
|
||||||
|
- Vérifier concrètement que le `.htaccess` du dossier `/journal` bloque bien l'accès direct (`curl -I` sur un fichier du dossier doit répondre `403`, pas `200` — point en suspens depuis le 31 août).
|
||||||
|
- Pilotage vocal, étape 2 : définir les règles d'interprétation du contenu dicté (aujourd'hui stocké tel quel), envisagée via n8n avec des règles modifiables par l'utilisateur.
|
||||||
|
- Continuer à observer les agents actifs (Viktor, Matilda, Léon, Nikita) en usage réel ; ajuster `AGN_Variantes` en base si de nouvelles confusions apparaissent.
|
||||||
|
- Documentation utilisateur du pilotage vocal : pas encore rédigée, fonctionnalité encore en phase d'essais techniques.
|
||||||
|
|||||||
@@ -105,12 +105,24 @@ En marge de sa recherche d'un serveur Hostinger plus puissant, Jean-Marc a déco
|
|||||||
|
|
||||||
## 10. Répondeur téléphonique intelligent — projet distinct, exploration initiale (1er-2 septembre 2026)
|
## 10. Répondeur téléphonique intelligent — projet distinct, exploration initiale (1er-2 septembre 2026)
|
||||||
|
|
||||||
*Section volontairement séparée de tout ce qui précède : ce n'est pas un chantier ASPIC, mais un projet à part que Jean-Marc envisage de développer en parallèle — capturé ici parce que la discussion en est partie d'un point technique abordé côté ASPIC (le pilotage vocal "Lilou", voir journal de développement section 27) mais dépasse largement ce cadre.*
|
*Section volontairement séparée de tout ce qui précède : ce n'est pas un chantier ASPIC, mais un projet à part que Jean-Marc envisage de développer en parallèle — capturé ici parce que la discussion en est partie d'un point technique abordé côté ASPIC (le pilotage vocal, voir journal de développement section 27) mais dépasse largement ce cadre.*
|
||||||
|
|
||||||
**Origine de la discussion :** en travaillant sur le pilotage vocal d'ASPIC, Jean-Marc s'est demandé si la reconnaissance de commandes allait rapidement nécessiter des bases de données vectorielles (PostgreSQL/pgvector), redoutant que ça complique sérieusement le sujet. Réponse apportée : pas pour Lilou dans sa forme actuelle — un ensemble borné et restreint de commandes/intentions relève d'une simple classification par mots-clés/regex, pas d'une recherche sémantique ; une base vectorielle serait de la sur-ingénierie pour ce cas précis. Jean-Marc a alors précisé que ce besoin allait en revanche réellement se poser pour un autre projet qu'il envisage : un **répondeur téléphonique intelligent**, branché sur une ligne téléphonique.
|
**Origine de la discussion :** en travaillant sur le pilotage vocal d'ASPIC, Jean-Marc s'est demandé si la reconnaissance de commandes allait rapidement nécessiter des bases de données vectorielles (PostgreSQL/pgvector), redoutant que ça complique sérieusement le sujet. Réponse apportée : pas pour le pilotage vocal d'ASPIC dans sa forme actuelle — un ensemble borné et restreint de commandes/intentions relève d'une simple classification par mots-clés/regex, pas d'une recherche sémantique ; une base vectorielle serait de la sur-ingénierie pour ce cas précis. Jean-Marc a alors précisé que ce besoin allait en revanche réellement se poser pour un autre projet qu'il envisage : un **répondeur téléphonique intelligent**, branché sur une ligne téléphonique.
|
||||||
|
|
||||||
**Compréhension du principe, confirmée par Jean-Marc :** l'assemblage de plusieurs briques distinctes — transformation de la voix de l'appelant en texte (un peu comme le fait Lilou côté ASPIC ce soir), interrogation d'une base de connaissances via recherche vectorielle (pour retrouver l'information ou la réponse pertinente dans un corpus potentiellement large et ouvert, contrairement au jeu de commandes fermé de Lilou), puis transformation de la réponse en voix de synthèse — le tout avec une contrainte de latence forte et explicitement nommée par Jean-Marc : l'interlocuteur humain au bout du fil ne doit à aucun moment se demander si "la personne en face s'est endormie". C'est cette contrainte de réactivité en temps quasi réel, combinée à un corpus de connaissances potentiellement vaste et évolutif (contrairement à la liste fermée de commandes de Lilou), qui justifie cette fois le recours à une base vectorielle — la nuance posée par Claude étant que le besoin vient de la nature du problème (recherche sémantique sur corpus ouvert) et pas de la voix en tant que telle.
|
**Compréhension du principe, confirmée par Jean-Marc :** l'assemblage de plusieurs briques distinctes — transformation de la voix de l'appelant en texte (un peu comme le fait le pilotage vocal côté ASPIC), interrogation d'une base de connaissances via recherche vectorielle (pour retrouver l'information ou la réponse pertinente dans un corpus potentiellement large et ouvert, contrairement au jeu de commandes fermé d'ASPIC), puis transformation de la réponse en voix de synthèse — le tout avec une contrainte de latence forte et explicitement nommée par Jean-Marc : l'interlocuteur humain au bout du fil ne doit à aucun moment se demander si "la personne en face s'est endormie". C'est cette contrainte de réactivité en temps quasi réel, combinée à un corpus de connaissances potentiellement vaste et évolutif (contrairement à la liste fermée de commandes du pilotage vocal ASPIC), qui justifie cette fois le recours à une base vectorielle — la nuance posée par Claude étant que le besoin vient de la nature du problème (recherche sémantique sur corpus ouvert) et pas de la voix en tant que telle.
|
||||||
|
|
||||||
**Infrastructure déjà disponible sur le VPS de Jean-Marc, repérée en marge du débogage de ce soir** (`docker ps -a`, 2 septembre 2026) : une instance **PostgreSQL avec l'extension pgvector** (`pgvector/pgvector:pg16-trixie`) tourne déjà, aux côtés de **n8n** (déjà utilisé et évoqué comme brique d'orchestration possible pour l'interprétation des dictées Lilou, section 27 du journal) et de **LangFlow** (outil de composition de flux IA, potentiellement pertinent pour assembler les briques voix→texte→recherche→texte→voix de ce futur répondeur). Rien n'indique que ces instances aient été mises en place spécifiquement pour ce projet — probablement des briques déjà expérimentées pour d'autres besoins — mais elles constituent une base technique déjà en place le jour où Jean-Marc voudra vraiment démarrer ce chantier, sans avoir à réinstaller quoi que ce soit.
|
**Infrastructure déjà disponible sur le VPS de Jean-Marc, repérée en marge du débogage du pilotage vocal** (`docker ps -a`, 2 septembre 2026) : une instance **PostgreSQL avec l'extension pgvector** (`pgvector/pgvector:pg16-trixie`) tourne déjà, aux côtés de **n8n** (déjà utilisé et évoqué comme brique d'orchestration possible pour l'interprétation des dictées du pilotage vocal, section 27 du journal) et de **LangFlow** (outil de composition de flux IA, potentiellement pertinent pour assembler les briques voix→texte→recherche→texte→voix de ce futur répondeur). Rien n'indique que ces instances aient été mises en place spécifiquement pour ce projet — probablement des briques déjà expérimentées pour d'autres besoins — mais elles constituent une base technique déjà en place le jour où Jean-Marc voudra vraiment démarrer ce chantier, sans avoir à réinstaller quoi que ce soit.
|
||||||
|
|
||||||
**Statut : simple exploration conceptuelle, aucun développement engagé.** Jean-Marc a explicitement mis ce projet de côté pour revenir au pilotage vocal d'ASPIC ("c'est une autre aventure ! [...] Revenons à notre micro sous aspic."). À reprendre le jour où il voudra s'y consacrer, avec cette base d'infrastructure déjà cadrée.
|
**Statut : simple exploration conceptuelle, aucun développement engagé.** Jean-Marc a explicitement mis ce projet de côté pour revenir au pilotage vocal d'ASPIC. À reprendre le jour où il voudra s'y consacrer, avec cette base d'infrastructure déjà cadrée.
|
||||||
|
|
||||||
|
## 11. Le "bureau à l'ancienne" — délégation à plusieurs assistants IA nommés (réflexion long terme, 2 septembre 2026)
|
||||||
|
|
||||||
|
**Origine.** Née en marge des essais du pilotage vocal multi-agents (voir journal de développement, section 27) : une fois plusieurs assistants nommés distincts (Viktor, Matilda...) capables de recevoir des consignes vocales chacun sur son périmètre, Jean-Marc a partagé une vision plus large pour l'évolution de l'interface d'ASPIC à long terme.
|
||||||
|
|
||||||
|
**Le principe.** Remplacer les écrans CRUD traditionnels par une métaphore de bureau à l'ancienne : un tableau de liège avec des post-its de couleur, un par assistant IA, des dossiers de vérification, un agenda, un téléphone. L'utilisateur délègue oralement à plusieurs assistants nommés, chacun responsable d'un périmètre (voir `AGN_Fonction` dans `ASP_Agents` — Agent Commercial, Agent Administratif...). Différence assumée avec une assistante humaine : un agent IA n'a pas la faculté de procrastiner ou de remettre une tâche à plus tard sans raison — il agit, ou explique pourquoi il n'a pas pu.
|
||||||
|
|
||||||
|
**Interface volontairement basique.** Pas une interface "sexy" ou soignée visuellement — la valeur réelle est vue comme résidant dans la structure des données et la couche de délégation, pas dans l'habillage visuel. Cohérent avec la philosophie déjà en place dans ASPIC (pas de sur-ingénierie, PHP/JS vanilla plutôt que framework).
|
||||||
|
|
||||||
|
**Périmètre et statut.** Vision explicitement limitée à ASPIC lui-même à ce stade, pas un projet d'outillage plus large. Jean-Marc reconnaît que cette réflexion peut évoluer avec l'expérience acquise sur le terrain — voir justement le principe ci-dessous, tiré d'un premier retour d'expérience concret le jour même. Aucun développement engagé — pure réflexion à ce stade, capturée ici pour mémoire.
|
||||||
|
|
||||||
|
**Principe de conception qui en découle, tiré de l'expérience réelle du pilotage vocal (2 septembre 2026) :** mieux vaut choisir des noms d'agents qui passent bien nativement à la reconnaissance vocale que d'empiler des tolérances pour rattraper un nom mal choisi — chaque tolérance ajoutée étant à la fois un risque de faux déclenchement (si elle recoupe un mot français courant) et un pari sur un seul accent/une seule prononciation testée, fragile face à la diversité réelle des utilisateurs. Deux agents (Lilou, Karl) ont été retirés sur cette base après des essais répétés peu concluants, plutôt que d'être maintenus à coups de correctifs successifs. Principe à garder en tête si le "bureau à l'ancienne" se concrétise un jour avec davantage d'assistants nommés.
|
||||||
|
|||||||
@@ -1,50 +1,60 @@
|
|||||||
## 27. Pilotage vocal (Lilou) — phase 1 [BROUILLON, fonctionnalité non encore validée de bout en bout] (1er-2 septembre 2026)
|
## 27. Pilotage vocal — multi-agents (1ᵉʳ-2 septembre 2026)
|
||||||
|
|
||||||
**⚠️ Statut de ce fichier.** Section créée par anticipation le 2 septembre 2026 à la demande explicite de Jean-Marc, alors que le principe initial (voir sommaire du journal) était de ne créer ce fichier qu'une fois le pilotage vocal validé de bout en bout — ce qui n'est pas encore le cas. Contenu à considérer comme un brouillon de travail, à mettre à jour à la fin de chaque session tant que la fonctionnalité n'est pas stabilisée, plutôt que comme un compte-rendu définitif.
|
**Contexte et bouton micro (1er septembre 2026).** Suite de la ToDoList (section 26) : nouveau bouton micro dans le bandeau, positionné après l'avatar pour une place fixe à l'écran. Comportement centralisé dans un fichier partagé `micro.js`, chargé sur chaque écran hôte — reconnaissance vocale réelle via l'API Web Speech du navigateur (mode "cloud" du navigateur, l'audio étant envoyé à un service externe, voir réflexions stratégiques section 3), écoute continue avec redémarrage automatique tant que l'utilisateur n'a pas explicitement refermé le micro. Un mot déclencheur filtre les phrases prises en compte, pour ignorer la conversation de fond non destinée à ASPIC — principe conservé tout au long de cette section ; seul son fonctionnement (un seul nom fixe au départ, puis plusieurs agents) a évolué.
|
||||||
|
|
||||||
**Contexte.** Suite de la ToDoList (section 26) : nouveau bouton micro dans le bandeau, positionné après l'avatar pour une place fixe à l'écran. Comportement centralisé dans un fichier partagé `micro.js`, chargé sur chaque écran hôte — reconnaissance vocale réelle via l'API Web Speech du navigateur, écoute continue avec redémarrage automatique tant que l'utilisateur n'a pas explicitement refermé le micro. Mot déclencheur **« Lilou »** (assistante vocale d'ASPIC, dans la même veine que « Viktor » côté contacts) pour ignorer la conversation de fond — tolérant aussi **« Milou »**, confusion fréquente du moteur de reconnaissance sur ce prénom.
|
**Mode notation.** Une phrase d'ouverture ("[nom], ajoute une tâche" / "un truc à faire" / "note ça dans ma todolist") fait basculer dans un mode où le nom de l'agent n'est plus exigé pour chaque ligne suivante — tout ce qui est capté est ajouté tel quel, jusqu'à une phrase de clôture ("c'est tout [nom]") ou d'annulation ("[nom] annule"). Volontairement **sans interprétation** à ce stade (décision explicite de Jean-Marc, 1er septembre) : le texte part tel quel en base pour constituer un corpus de vrais essais, avant de définir des règles d'interprétation dans un prochain chantier (étape 2, envisagée via n8n, avec des règles modifiables par l'utilisateur).
|
||||||
|
|
||||||
**Mode notation (livré le 1er septembre au soir).** Capture une dictée libre entre une phrase d'ouverture (« Lilou, ajoute une tâche » / « un truc à faire » / « note ça dans ma todolist ») et une phrase de clôture (« c'est tout Lilou ») ou d'annulation (« Lilou annule ») — les deux traitées à l'identique, sans interprétation à ce stade, sur demande explicite de Jean-Marc. Objectif : accumuler un vrai corpus d'essais réels avant de définir, dans un prochain chantier, des règles d'interprétation (étape 2, envisagée via n8n, avec des règles modifiables par l'utilisateur). Version applicative incrémentée une fois pour cette fonctionnalité, **V1.005**.
|
**Abandon du fichier texte, pivot vers une table SQL (2 septembre).** La première implémentation écrivait les dictées dans un fichier texte global (`notes_lilou/dictees.txt`), plombée par des soucis de droits d'écriture (`www-data` hors du groupe propriétaire du dossier), puis par un échec pur et simple constaté au test du lendemain matin (aucune mise à jour du fichier malgré l'appel). Sur proposition de Jean-Marc, pour ne pas perdre plus de temps sur une approche provisoire : chaque ligne de dictée est désormais stockée directement dans une table MySQL, `ASP_ConsignesVoix`, structure finale à ce jour :
|
||||||
|
|
||||||
**Première implémentation (abandonnée le 2 septembre, voir plus bas) : fichier texte.** Le texte capté tel quel était envoyé à un endpoint `lilou_notes.php`, qui l'ajoutait, horodaté et attribué à l'utilisateur, à un fichier texte global (`notes_lilou/dictees.txt`, pensé pour être protégé par un `.htaccess` du même principe que `/journal`).
|
|
||||||
|
|
||||||
**Session de débogage en conditions réelles, nuit du 1er au 2 septembre.** Plusieurs corrections apportées à `micro.js` :
|
|
||||||
- la clôture peut désormais porter à la fois la dernière ligne de contenu et la fin du lot dans une seule phrase, sans « ensuite » artificiel juste avant ;
|
|
||||||
- tolérance à la confusion « Lilou »/« Milou » ;
|
|
||||||
- fenêtre de quelques secondes pour recoller une phrase que le moteur de reconnaissance a coupée juste après la virgule suivant « Lilou ».
|
|
||||||
|
|
||||||
Côté serveur, deux problèmes rencontrés : le fichier `lilou_notes.php` n'avait pas été déployé au premier essai ; puis, une fois déployé, droits d'écriture insuffisants sur `notes_lilou/` (dossier propriété de `1001:users`, `www-data` hors de ce groupe). **Contournement provisoire en fin de session :** `dictees.txt` écrit directement à la racine de l'application plutôt que dans `notes_lilou/`, donc temporairement hors de toute protection `.htaccess`. Test de bout en bout non confirmé réussi au moment d'arrêter la session.
|
|
||||||
|
|
||||||
**Test du matin, 2 septembre — échec confirmé.** Jean-Marc a créé manuellement `dictees.txt` à la racine de l'application et refait un essai : aucune mise à jour du fichier. L'approche fichier ne fonctionne donc toujours pas en pratique, au-delà du problème de droits déjà identifié sur `notes_lilou/`.
|
|
||||||
|
|
||||||
**Décision de pivot, 2 septembre — abandon de l'approche fichier texte.** Sur proposition de Jean-Marc, pour ne pas perdre plus de temps sur une idée qui n'était que provisoire : chaque ligne de dictée sera désormais stockée directement dans une table MySQL simple, `ASP_ConsignesVoix` (nom volontairement générique, pas centré sur « Lilou », l'assistante n'étant qu'une des interfaces possibles vers ce mécanisme).
|
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE `ASP_ConsignesVoix` (
|
CREATE TABLE `ASP_ConsignesVoix` (
|
||||||
`CVX_Incr` int NOT NULL,
|
`CVX_Incr` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`CVX_Consigne` varchar(200) NOT NULL,
|
||||||
|
`CVX_Type` varchar(12) NOT NULL,
|
||||||
`CVX_DateHeure` datetime NOT NULL,
|
`CVX_DateHeure` datetime NOT NULL,
|
||||||
`CVX_CodeUser` varchar(3) NOT NULL,
|
`CVX_CodeUser` varchar(3) NOT NULL,
|
||||||
`CVX_Consigne` varchar(200) NOT NULL
|
`CVX_AgentTraitant` varchar(20) NOT NULL,
|
||||||
|
PRIMARY KEY (`CVX_Incr`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
ALTER TABLE `ASP_ConsignesVoix`
|
|
||||||
ADD PRIMARY KEY (`CVX_Incr`);
|
|
||||||
|
|
||||||
ALTER TABLE `ASP_ConsignesVoix`
|
|
||||||
MODIFY `CVX_Incr` int NOT NULL AUTO_INCREMENT;
|
|
||||||
COMMIT;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`CVX_DateHeure` et `CVX_CodeUser` ajoutés par rapport à la proposition initiale de Jean-Marc (qui ne portait que `CVX_Incr`/`CVX_Consigne`), sur suggestion de Claude, pour préserver l'horodatage et l'attribution utilisateur que l'ancien fichier texte portait déjà — évite un `ALTER TABLE` ultérieur sur des données déjà présentes. `CVX_CodeUser` en `varchar(3)`, même convention que `USR_Code`/`TDL_ToDoUser`.
|
`CVX_Type` (ajoutée en cours de session, voir plus bas) distingue `ouverture` / `contenu` / `cloture` / `annulation` sur chaque ligne, pour qu'un futur automatisme ("robot") sache, sans ré-analyser le texte brut, où commence et finit un lot, et s'il doit être exécuté ou écarté — clôture et annulation distinguées à ce niveau, malgré un traitement du contenu identique dans les deux cas (décision du 1er septembre, maintenue). `CVX_AgentTraitant` (ajoutée plus tard dans la session, voir "Généralisation multi-agents" ci-dessous) porte en clair le nom de l'agent adressé, appelée à devenir une clé étrangère (`CVX_IncrAgent`) une fois le modèle stabilisé.
|
||||||
|
|
||||||
**Accès au code — point bloquant identifié le 2 septembre.** `micro.js` et `lilou_notes.php` n'ont jamais été poussés sur GitHub (dépôt non fait la veille au soir, Jean-Marc étant trop fatigué) — ils n'existent que sur le VPS. Jean-Marc s'en occupe (dépôt en cours au moment de la rédaction de ce brouillon). Une fois le dépôt fait, Claude pourra lire le contenu actuel de ces deux fichiers, notamment le format JSON exact envoyé par `micro.js` vers `lilou_notes.php`, avant de réécrire cet endpoint pour insérer dans `ASP_ConsignesVoix` au lieu d'écrire dans `dictees.txt`.
|
**Corrections apportées en conditions réelles.** Plusieurs bugs identifiés et corrigés au fil des essais de Jean-Marc :
|
||||||
|
- une phrase d'ouverture ou de clôture/annulation dite dans la même phrase que du contenu, sans pause ("Lilou, ajoute une tâche, [contenu]" tout d'une traite), perdait silencieusement le contenu situé après la phrase de contrôle repérée — corrigé symétriquement des deux côtés (ouverture, puis clôture/annulation) ;
|
||||||
|
- couper le micro manuellement pendant une notation en cours abandonnait le contenu déjà dicté — corrigé en traitant la coupure manuelle comme une clôture normale, avec un marqueur explicite ("(coupure manuelle du micro)") ;
|
||||||
|
- une clôture prononcée ("c'est tout Lilou") transcrite par erreur en "c'est tous les loups" par le moteur de reconnaissance passait inaperçue — diagnostiquée après une demande explicite de Claude à Jean-Marc de fournir un export exact plutôt qu'une nouvelle tentative à l'aveugle (une première hypothèse s'étant déjà révélée fausse), puis corrigée par une tolérance dédiée à cet homophone de phrase entière.
|
||||||
|
|
||||||
**Pattern d'implémentation identifié pour la suite** (lecture de `db.php` et `todolist.php`, tous deux déjà dans le dépôt) : connexion via `getDbConnection()` (PDO, routage MIRADOR géré automatiquement par session), vérification de session (`$_SESSION['USR_Code']`, 401 sinon), JSON en entrée (`php://input`) et en sortie, requêtes préparées. `lilou_notes.php` réécrit suivra ce même pattern.
|
**Incident de confidentialité et retrait du traçage de débogage.** Un traçage brut temporaire (toutes les phrases reconnues, trigger ou non), ajouté pour diagnostiquer le bug ci-dessus, a enregistré des bribes d'un appel téléphonique confidentiel reçu pendant que le micro était resté ouvert. Jean-Marc a supprimé les lignes lui-même et signalé l'incident sans gravité particulière ; Claude a retiré l'intégralité de cette fonctionnalité de traçage dès que l'incident a été rapporté, sans attendre. Risque résiduel identifié : même sans traçage, l'écoute continue envoie l'audio ambiant au service de reconnaissance tant que le micro est ouvert, qu'un agent soit reconnu ou non — seul le mode local ("on-device", encore expérimental) éliminerait complètement cet envoi (voir réflexions stratégiques, section 3). Mitigation partielle apportée dans la foulée : voir coupure pour inactivité ci-dessous.
|
||||||
|
|
||||||
|
**Coupure automatique pour inactivité.** Ajoutée en réponse directe à l'incident ci-dessus : après `DUREE_INACTIVITE_MS` (10 minutes par défaut) sans interaction réelle avec un agent, le micro se referme tout seul, comme un clic manuel — notation en cours clôturée proprement plutôt que perdue. Le bruit de fond ignoré (aucun agent reconnu) ne repousse volontairement pas ce délai, puisque c'est justement le cas qu'on veut détecter (une conversation qui ne s'adresse à aucun agent, comme lors de l'incident).
|
||||||
|
|
||||||
|
**Généralisation multi-agents (2 septembre).** Jean-Marc a souhaité que l'information "à quel agent s'adresse la note" soit explicitement stockée plutôt que sous-entendue, en vue d'un futur système où plusieurs assistants IA nommés se partageraient les tâches (voir réflexions stratégiques, section 11, le "bureau à l'ancienne"). Nouvelle table `ASP_Agents` :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `ASP_Agents` (
|
||||||
|
`AGN_Incr` int NOT NULL,
|
||||||
|
`AGN_Nom` varchar(20) NOT NULL,
|
||||||
|
`AGN_Fonction` varchar(40) NOT NULL,
|
||||||
|
`AGN_Variantes` varchar(200) NOT NULL,
|
||||||
|
`AGN_Defaut` varchar(1) NOT NULL DEFAULT 'N'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
`micro.js` charge cette liste au démarrage via un nouvel endpoint `agents.php` (GET, même principe que `utilisateurs.php`), et construit dynamiquement le motif de reconnaissance de chaque agent — plus aucun agent codé en dur dans le script. `lilou_notes.php` a été renommé `agent_notes.php` (il ne sert plus un seul agent) et accepte désormais `{agent, lignes}` en entrée.
|
||||||
|
|
||||||
|
**AGN_Variantes — tolérer les confusions sans coder en dur.** Après trois cas réels indépendants de confusion de reconnaissance le même jour (Lilou/Milou/Loulou, Matilda/Mathilda/Mathilde, Karl/Carl), le mécanisme a été généralisé : chaque agent porte en base une liste de graphies alternatives (`AGN_Variantes`, séparées par une virgule), repliées dans le motif de reconnaissance de son nom — modifiable par Jean-Marc directement en base, sans code. Règle explicitement posée à cette occasion : une variante doit rester un nom propre ou un mot rare en tête de phrase, jamais un mot français courant ("car", "calme"...), qui déclencherait l'agent sur n'importe quelle phrase du quotidien commençant par ce mot. Seule exception qui reste codée en dur : l'homophone "les loups" pour la clôture de Lilou, qui porte sur la phrase entière et non sur le seul prénom (aujourd'hui inerte, Lilou n'étant plus dans `ASP_Agents` — voir plus bas).
|
||||||
|
|
||||||
|
**AGN_Defaut — agent de repli piloté par la donnée.** Le champ `agent`, s'il arrive vide dans `agent_notes.php` (compatibilité avec un client qui ne le renseignerait pas), résout désormais l'agent à utiliser via `ASP_Agents.AGN_Defaut = 'O'`, avec un repli supplémentaire sur le premier agent de la table si aucun n'est marqué par défaut. Plus aucun nom d'agent figé en dur dans le code, y compris côté `micro.js` (titre du panneau de notation, secours en cas d'échec de chargement de `agents.php`).
|
||||||
|
|
||||||
|
**Retrait de Lilou et Karl (décision de Jean-Marc).** Les essais réels ont montré des confusions de reconnaissance récurrentes et non résolues sur ces deux noms — "car"/"calme"/"Carl" pour Karl (une notation entière restée bloquée, jamais refermée, lors d'un essai), "Philou"/"loulou" en plus de "Milou" pour Lilou. Plutôt que d'empiler encore des tolérances, avec le risque à la fois de multiplier les faux déclenchements (sur des mots français courants) et de fragiliser le système face à un accent ou une prononciation différente de celle testée, Jean-Marc a choisi de retirer ces deux agents plutôt que de corriger indéfiniment. Liste active à l'issue de cette session : **Viktor** (Agent Commercial, variante "Victor"), **Matilda** (Agent Administratif, variantes "Mathilda"/"Mathilde", agent par défaut), **Léon** (Agent Commercial Export, aucune variante nécessaire à ce jour), **Nikita** (Agent Financier, aucune variante nécessaire à ce jour) — ces deux derniers n'ayant provoqué aucune confusion observée en essai réel.
|
||||||
|
|
||||||
|
**Méthode de travail à noter pour la suite.** Plusieurs diagnostics de cette session (le bug "les loups", un déploiement resté sur l'ancien `lilou_notes.php` après le renommage, les confusions Matilda/Karl/Viktor) ont été résolus après avoir explicitement demandé à Jean-Marc une preuve concrète (export SQL exact, ligne de console) plutôt qu'en tentant une nouvelle correction à l'aveugle sur simple hypothèse — approche à reconduire systématiquement.
|
||||||
|
|
||||||
|
**État à la fin de cette session.** Fonctionnalité validée de bout en bout sur les quatre agents actifs (ouverture → contenu → clôture, y compris avec variantes de prononciation). Version applicative incrémentée une fois pour cette session, **V1.006**.
|
||||||
|
|
||||||
**Reste à faire.**
|
**Reste à faire.**
|
||||||
- Push GitHub de `micro.js` et `lilou_notes.php` (Jean-Marc, en cours).
|
- Étape 2 : règles d'interprétation du contenu dicté (aujourd'hui stocké tel quel, sans traitement), envisagées via n8n, avec des règles modifiables par l'utilisateur.
|
||||||
- Réécriture de `lilou_notes.php` pour insérer dans `ASP_ConsignesVoix` (Claude, une fois le point précédent fait) — un `INSERT` par ligne de dictée, `au fur et à mesure` comme demandé par Jean-Marc.
|
- Continuer à observer les agents actifs en usage réel ; envisager d'autres retraits, ou l'ajout de nouvelles variantes via `AGN_Variantes`, si de nouvelles confusions apparaissent — sans jamais y ajouter un mot français courant.
|
||||||
- Vérifier que le format envoyé par `micro.js` reste compatible avec le nouvel endpoint, ou adapter `micro.js` si besoin.
|
- Vérifier concrètement que le `.htaccess` du dossier `/journal` bloque bien l'accès direct (`curl -I` doit répondre `403`, pas `200`) — point resté en suspens depuis le 31 août, sans lien avec le pilotage vocal mais toujours ouvert.
|
||||||
- Test de bout en bout à refaire et confirmer.
|
- Documentation utilisateur : pas encore mise à jour pour cette fonctionnalité, encore en phase d'essais techniques (choix des noms d'agents notamment) — à envisager une fois l'usage stabilisé.
|
||||||
- Vérifier concrètement que le `.htaccess` du dossier `/journal` bloque bien l'accès direct (`curl -I` sur un fichier du dossier doit répondre `403`, pas `200`) — point resté en suspens depuis le 31 août, sans lien direct avec Lilou mais noté ici pour mémoire tant que ce brouillon n'est pas remplacé par la version finale.
|
|
||||||
- Une fois la fonctionnalité validée de bout en bout : remplacer ce brouillon par une version définitive de la section 27, avec une numérotation de version applicative si de nouveaux changements de code l'accompagnent.
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ASPIC — lilou_notes.php
|
|
||||||
*
|
|
||||||
* Étape 1 du pilotage vocal, volontairement "sans interprétation" (1er
|
|
||||||
* septembre 2026, décision de Jean-Marc) : reçoit le texte dicté à Lilou
|
|
||||||
* entre une phrase d'ouverture ("Lilou, ajoute une tâche...", "Lilou, un
|
|
||||||
* truc à faire...", "Lilou, note ça dans ma todolist...") et une phrase de
|
|
||||||
* clôture ("c'est tout Lilou") ou d'annulation ("Lilou annule"), et
|
|
||||||
* l'ajoute tel quel à un fichier texte global — aucune tentative de
|
|
||||||
* compréhension ici, y compris pour une annulation : on note ce qui a été
|
|
||||||
* dit, point (demande explicite de Jean-Marc). Objectif : accumuler un
|
|
||||||
* vrai corpus d'essais réels avant de définir des règles d'interprétation
|
|
||||||
* (étape 2, envisagée via n8n, avec des règles modifiables par
|
|
||||||
* l'utilisateur — voir réflexions stratégiques, section à venir sur le
|
|
||||||
* pilotage vocal), plutôt que de deviner ces règles à l'avance.
|
|
||||||
*
|
|
||||||
* POST uniquement, corps JSON : { "lignes": ["...", "..."] }
|
|
||||||
* (un tableau d'une ligne par phrase dictée, "ensuite" déjà retiré côté
|
|
||||||
* micro.js).
|
|
||||||
*
|
|
||||||
* Stockage provisoire (2 septembre 2026) : en plein déblocage d'un souci de
|
|
||||||
* droits d'écriture sur le sous-dossier notes_lilou/ (propriétaire
|
|
||||||
* 1001:users, www-data hors de ce groupe), le fichier de sortie est
|
|
||||||
* temporairement écrit directement à la racine de l'appli
|
|
||||||
* (dictees.txt, à côté de ce script) plutôt que dans notes_lilou/ — le
|
|
||||||
* temps de confirmer que le circuit complet fonctionne. Conséquence
|
|
||||||
* assumée pour ce soir : ce fichier n'est plus protégé par le .htaccess
|
|
||||||
* (qui ne couvre que notes_lilou/), donc techniquement accessible par son
|
|
||||||
* URL directe s'il est deviné. À remettre dans notes_lilou/ (avec les
|
|
||||||
* droits corrigés) une fois le test concluant — ne pas oublier.
|
|
||||||
*
|
|
||||||
* Session utilisateur : accès réservé aux utilisateurs identifiés, même
|
|
||||||
* principe que les autres endpoints (todolist.php, contacts.php...).
|
|
||||||
*/
|
|
||||||
|
|
||||||
session_start();
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
|
||||||
|
|
||||||
if (empty($_SESSION['USR_Code'])) {
|
|
||||||
http_response_code(401);
|
|
||||||
echo json_encode(['error' => 'Non identifié — veuillez vous connecter.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
||||||
http_response_code(405);
|
|
||||||
echo json_encode(['error' => 'Méthode non autorisée']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
||||||
$lignes = $data['lignes'] ?? [];
|
|
||||||
|
|
||||||
if (!is_array($lignes) || count($lignes) === 0) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['error' => 'Aucune ligne à enregistrer']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fichier = __DIR__ . '/dictees.txt';
|
|
||||||
|
|
||||||
$horodatage = date('d/m/Y H:i:s');
|
|
||||||
$bloc = '=== ' . $horodatage . ' — ' . $_SESSION['USR_Code'] . " ===\n";
|
|
||||||
foreach ($lignes as $ligne) {
|
|
||||||
$ligne = trim((string) $ligne);
|
|
||||||
if ($ligne === '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$bloc .= ' ' . $ligne . "\n";
|
|
||||||
}
|
|
||||||
$bloc .= "\n";
|
|
||||||
|
|
||||||
$ok = file_put_contents($fichier, $bloc, FILE_APPEND | LOCK_EX);
|
|
||||||
if ($ok === false) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['error' => "Impossible d'écrire dans le fichier de notes"]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
@@ -60,13 +60,60 @@
|
|||||||
* Jean-Marc : lui analysera ses propres essais après coup). "ensuite" en
|
* Jean-Marc : lui analysera ses propres essais après coup). "ensuite" en
|
||||||
* fin de phrase sert de marqueur oral de fin de ligne, retiré du texte
|
* fin de phrase sert de marqueur oral de fin de ligne, retiré du texte
|
||||||
* conservé. Volontairement SANS interprétation à ce stade — le texte part
|
* conservé. Volontairement SANS interprétation à ce stade — le texte part
|
||||||
* tel quel dans notes_lilou/dictees.txt (via lilou_notes.php) pour
|
* tel quel dans la table ASP_ConsignesVoix (via lilou_notes.php ; le
|
||||||
* constituer un corpus de vrais essais, avant de définir des règles
|
* fichier texte notes_lilou/dictees.txt de la V1.005 est abandonné depuis
|
||||||
* d'interprétation dans un prochain chantier (étape 2, envisagée via n8n).
|
* le 2 septembre 2026, voir journal section 27) pour constituer un corpus
|
||||||
|
* de vrais essais, avant de définir des règles d'interprétation dans un
|
||||||
|
* prochain chantier (étape 2, envisagée via n8n).
|
||||||
* Les phrases déclenchantes ci-dessous sont volontairement en dur et
|
* Les phrases déclenchantes ci-dessous sont volontairement en dur et
|
||||||
* simples (pas de grammaire compliquée) — à ajuster une fois les premiers
|
* simples (pas de grammaire compliquée) — à ajuster une fois les premiers
|
||||||
* essais de Jean-Marc faits.
|
* essais de Jean-Marc faits.
|
||||||
*
|
*
|
||||||
|
* Correctif du 2 septembre 2026 (premier essai réel en table SQL) : quand
|
||||||
|
* l'ouverture ou la clôture/annulation est dite dans la même phrase que du
|
||||||
|
* contenu, sans pause ("Lilou, ajoute une tâche, [contenu]" ou "[contenu],
|
||||||
|
* c'est tout Lilou, [contenu]" tout d'une traite), le contenu qui suit la
|
||||||
|
* phrase de contrôle repérée était perdu silencieusement — seul le contenu
|
||||||
|
* qui la précédait (cas déjà traité pour la clôture depuis le 1er
|
||||||
|
* septembre) était conservé. Corrigé pour capturer aussi ce qui suit,
|
||||||
|
* côté ouverture ET clôture/annulation, symétriquement. Micro coupé
|
||||||
|
* manuellement pendant une notation en cours (clôture orale oubliée ou non
|
||||||
|
* reconnue) : traité désormais comme une clôture normale plutôt que comme
|
||||||
|
* un abandon silencieux du contenu déjà dicté.
|
||||||
|
*
|
||||||
|
* Multi-agents et coupure pour inactivité (2 septembre 2026, suite du
|
||||||
|
* premier essai réel en table SQL) : le mot déclencheur unique "Lilou" est
|
||||||
|
* remplacé par une liste d'agents nommés (ASP_Agents : Lilou, Viktor,
|
||||||
|
* Matilda, Léon, Karl, Nikita), chargée depuis agents.php au démarrage de
|
||||||
|
* ce script — voir agentsDisponibles/chargerAgents plus bas. Le nom
|
||||||
|
* reconnu en tête de la phrase d'ouverture détermine l'agent adressé pour
|
||||||
|
* toute la notation qui suit (agentCourant), et part avec chaque ligne
|
||||||
|
* enregistrée dans CVX_AgentTraitant (voir agent_notes.php, ex-lilou_notes.
|
||||||
|
* php, renommé le même jour puisqu'il ne sert plus un seul agent).
|
||||||
|
*
|
||||||
|
* Variantes de prononciation (2 septembre 2026, généralisé après 3 cas
|
||||||
|
* réels indépendants le même jour — Lilou/Milou/Loulou, Matilda/Mathilda,
|
||||||
|
* Karl/Carl) : plutôt que du code en dur par agent, chaque ligne de
|
||||||
|
* ASP_Agents porte une colonne AGN_Variantes (graphies alternatives
|
||||||
|
* séparées par une virgule) intégrée au motif de reconnaissance — voir
|
||||||
|
* construireAgent. Jean-Marc peut donc ajuster une graphie lui-même en
|
||||||
|
* base, sans repasser par du code. Seule exception qui reste codée en dur :
|
||||||
|
* l'homophone "les loups" pour la clôture de Lilou ("c'est tout Lilou" →
|
||||||
|
* "c'est tous les loups"), qui n'est pas une variante du prénom seul mais
|
||||||
|
* de la phrase de clôture entière. Règle à respecter en choisissant une
|
||||||
|
* variante : uniquement des noms propres ou mots rares en tête de phrase,
|
||||||
|
* jamais un mot français courant ("car", "calme"...) qui déclencherait
|
||||||
|
* l'agent sur n'importe quelle phrase du quotidien commençant par ce mot —
|
||||||
|
* refusé explicitement pour Karl ce jour-là malgré des essais réels ratés
|
||||||
|
* avec "car" et "calme".
|
||||||
|
*
|
||||||
|
* Coupure pour inactivité (même jour) : par ailleurs,
|
||||||
|
* suite à un incident réel où le micro est resté ouvert pendant un appel
|
||||||
|
* téléphonique confidentiel (rien d'enregistré, sinon quelques traces de
|
||||||
|
* debug déjà retirées depuis — voir plus bas), une coupure automatique du
|
||||||
|
* micro après quelques minutes d'inactivité (DUREE_INACTIVITE_MS) a été
|
||||||
|
* ajoutée, sur le même principe qu'une fermeture manuelle.
|
||||||
|
*
|
||||||
* Écoute "tant qu'on ne l'a pas coupée soi-même" (1er septembre 2026,
|
* Écoute "tant qu'on ne l'a pas coupée soi-même" (1er septembre 2026,
|
||||||
* ajusté après un premier essai de Jean-Marc) : `continuous = true` ne
|
* ajusté après un premier essai de Jean-Marc) : `continuous = true` ne
|
||||||
* suffit pas à lui seul — de façon documentée (y compris sur Chrome
|
* suffit pas à lui seul — de façon documentée (y compris sur Chrome
|
||||||
@@ -123,13 +170,57 @@
|
|||||||
// le redémarrage automatique sur l'événement "end" qui suit.
|
// le redémarrage automatique sur l'événement "end" qui suit.
|
||||||
let erreurBloquante = false;
|
let erreurBloquante = false;
|
||||||
|
|
||||||
|
// Coupure automatique après inactivité (2 septembre 2026, demande de
|
||||||
|
// Jean-Marc suite à l'incident du micro resté ouvert pendant un appel
|
||||||
|
// téléphonique confidentiel — voir journal section 27) : si aucune
|
||||||
|
// interaction réelle avec un agent (nom reconnu en tête de phrase, ou
|
||||||
|
// ligne captée en mode notation) n'a eu lieu depuis DUREE_INACTIVITE_MS,
|
||||||
|
// le micro se referme tout seul, comme un clic manuel de fermeture — pour
|
||||||
|
// limiter la fenêtre pendant laquelle un micro oublié ouvert continue
|
||||||
|
// d'envoyer l'audio ambiant au service de reconnaissance. Le bruit de
|
||||||
|
// fond ignoré en silence (aucun agent reconnu) ne repousse volontairement
|
||||||
|
// PAS ce délai : c'est précisément le cas qu'on veut détecter (une
|
||||||
|
// conversation qui ne s'adresse à aucun agent). Durée choisie arbitraire
|
||||||
|
// pour ce premier essai, à ajuster avec Jean-Marc selon l'usage réel.
|
||||||
|
const DUREE_INACTIVITE_MS = 10 * 60 * 1000; // 10 minutes
|
||||||
|
let minuteurInactivite = null;
|
||||||
|
|
||||||
|
function reinitialiserMinuteurInactivite(){
|
||||||
|
if(minuteurInactivite) clearTimeout(minuteurInactivite);
|
||||||
|
minuteurInactivite = setTimeout(couperPourInactivite, DUREE_INACTIVITE_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function arreterMinuteurInactivite(){
|
||||||
|
if(minuteurInactivite){
|
||||||
|
clearTimeout(minuteurInactivite);
|
||||||
|
minuteurInactivite = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function couperPourInactivite(){
|
||||||
|
minuteurInactivite = null;
|
||||||
|
if(!ecouteVoulue) return; // déjà refermé entre-temps, rien à faire
|
||||||
|
// Même traitement que la coupure manuelle (voir le clic sur le bouton
|
||||||
|
// plus bas) : une notation en cours n'est pas perdue silencieusement,
|
||||||
|
// elle est clôturée avec un marqueur explicite avant l'arrêt.
|
||||||
|
if(modeNotation){
|
||||||
|
ajouterAuTampon("(coupure automatique pour inactivité)", "cloture");
|
||||||
|
terminerNotation();
|
||||||
|
}
|
||||||
|
ecouteVoulue = false;
|
||||||
|
notifier("Micro coupé automatiquement après " + Math.round(DUREE_INACTIVITE_MS / 60000) + " minutes d'inactivité.");
|
||||||
|
reconnaissance.stop();
|
||||||
|
}
|
||||||
|
|
||||||
function passerEnEcoute(){
|
function passerEnEcoute(){
|
||||||
bouton.classList.add("ecoute");
|
bouton.classList.add("ecoute");
|
||||||
bouton.title = "Le micro est ouvert";
|
bouton.title = "Le micro est ouvert";
|
||||||
|
reinitialiserMinuteurInactivite();
|
||||||
}
|
}
|
||||||
|
|
||||||
function revenirAuRepos(){
|
function revenirAuRepos(){
|
||||||
ecouteVoulue = false;
|
ecouteVoulue = false;
|
||||||
|
arreterMinuteurInactivite();
|
||||||
bouton.classList.remove("ecoute");
|
bouton.classList.remove("ecoute");
|
||||||
bouton.title = "Le micro est inactif";
|
bouton.title = "Le micro est inactif";
|
||||||
}
|
}
|
||||||
@@ -140,6 +231,18 @@
|
|||||||
// via l'événement "end" qui va suivre, sans redémarrage puisque
|
// via l'événement "end" qui va suivre, sans redémarrage puisque
|
||||||
// ecouteVoulue passe à faux avant l'appel à stop().
|
// ecouteVoulue passe à faux avant l'appel à stop().
|
||||||
ecouteVoulue = false;
|
ecouteVoulue = false;
|
||||||
|
// Coupure manuelle du micro alors qu'une notation était en cours
|
||||||
|
// (clôture orale oubliée, ou "c'est tout Lilou" non reconnu) : on ne
|
||||||
|
// perd pas le contenu déjà dicté — même traitement qu'une clôture
|
||||||
|
// normale, plutôt qu'un abandon silencieux (retour de Jean-Marc, 2
|
||||||
|
// septembre 2026 : du contenu dicté ne s'est pas retrouvé enregistré).
|
||||||
|
// Marqueur de clôture explicite ajouté (type "cloture") pour que le
|
||||||
|
// lot envoyé garde la même forme qu'une clôture orale, même sans
|
||||||
|
// phrase de clôture réellement entendue.
|
||||||
|
if(modeNotation){
|
||||||
|
ajouterAuTampon("(coupure manuelle du micro)", "cloture");
|
||||||
|
terminerNotation();
|
||||||
|
}
|
||||||
reconnaissance.stop();
|
reconnaissance.stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -164,6 +267,7 @@
|
|||||||
// -------- pour être appelable depuis n'importe quel écran sans avoir à
|
// -------- pour être appelable depuis n'importe quel écran sans avoir à
|
||||||
// -------- modifier chaque page hôte — voir commentaire d'en-tête.
|
// -------- modifier chaque page hôte — voir commentaire d'en-tête.
|
||||||
let panneauNotation = null;
|
let panneauNotation = null;
|
||||||
|
let panneauTitre = null;
|
||||||
let listeNotation = null;
|
let listeNotation = null;
|
||||||
|
|
||||||
function construirePanneauNotation(){
|
function construirePanneauNotation(){
|
||||||
@@ -185,13 +289,20 @@
|
|||||||
panneauNotation = document.createElement("div");
|
panneauNotation = document.createElement("div");
|
||||||
panneauNotation.id = "panneau-notation-lilou";
|
panneauNotation.id = "panneau-notation-lilou";
|
||||||
panneauNotation.innerHTML =
|
panneauNotation.innerHTML =
|
||||||
'<div class="titre">Lilou vous écoute…</div><ul class="lignes"></ul>';
|
'<div class="titre"></div><ul class="lignes"></ul>';
|
||||||
document.body.appendChild(panneauNotation);
|
document.body.appendChild(panneauNotation);
|
||||||
|
panneauTitre = panneauNotation.querySelector(".titre");
|
||||||
listeNotation = panneauNotation.querySelector(".lignes");
|
listeNotation = panneauNotation.querySelector(".lignes");
|
||||||
}
|
}
|
||||||
|
|
||||||
function ouvrirPanneauNotation(){
|
// nomAgent : titre du panneau adapté à l'agent adressé (2 septembre
|
||||||
|
// 2026, plusieurs agents désormais possibles — voir ASP_Agents). Repli
|
||||||
|
// générique "L'agent" si absent (ne devrait pas arriver, demarrerNotation
|
||||||
|
// fournit toujours un agent détecté) — plus de nom d'agent précis figé en
|
||||||
|
// dur ici depuis le retrait de Lilou de ASP_Agents le 2 septembre 2026.
|
||||||
|
function ouvrirPanneauNotation(nomAgent){
|
||||||
construirePanneauNotation();
|
construirePanneauNotation();
|
||||||
|
panneauTitre.textContent = (nomAgent || "L'agent") + " vous écoute…";
|
||||||
listeNotation.innerHTML = "";
|
listeNotation.innerHTML = "";
|
||||||
panneauNotation.classList.add("visible");
|
panneauNotation.classList.add("visible");
|
||||||
}
|
}
|
||||||
@@ -223,16 +334,14 @@
|
|||||||
let reveilEnAttente = false;
|
let reveilEnAttente = false;
|
||||||
let expirationReveil = 0;
|
let expirationReveil = 0;
|
||||||
const DUREE_REVEIL_MS = 6000;
|
const DUREE_REVEIL_MS = 6000;
|
||||||
|
// agentReveil : agent dont le nom seul vient d'être entendu, mémorisé
|
||||||
|
// pour attribuer correctement le résultat suivant à l'intérieur de la
|
||||||
|
// fenêtre ci-dessus (2 septembre 2026 — généralisation multi-agents de ce
|
||||||
|
// mécanisme, qui ne connaissait que Lilou jusqu'ici).
|
||||||
|
let agentReveil = null;
|
||||||
|
|
||||||
// Le moteur de reconnaissance confond parfois "Lilou" avec "Milou" (même
|
// Phrases d'ouverture : testées sur le texte après retrait du nom de
|
||||||
// sonorité, et "Milou" est un mot bien plus courant en français...) —
|
// l'agent (indépendantes de l'agent adressé).
|
||||||
// tous les motifs qui doivent repérer "Lilou" ci-dessous acceptent aussi
|
|
||||||
// "Milou", pour ne pas bloquer tout le circuit sur un simple aléa de
|
|
||||||
// transcription (retour de Jean-Marc, 2 septembre 2026 : "il a cru que je
|
|
||||||
// disais c'est tout Milou").
|
|
||||||
const MOT_LILOU = "(?:li|mi)lou";
|
|
||||||
|
|
||||||
// Phrases d'ouverture : testées sur le texte après retrait de "Lilou".
|
|
||||||
const PHRASES_OUVERTURE = [/tâche/i, /truc à faire/i, /note.*todolist/i];
|
const PHRASES_OUVERTURE = [/tâche/i, /truc à faire/i, /note.*todolist/i];
|
||||||
// Phrases de clôture / annulation : recherchées n'importe où dans la
|
// Phrases de clôture / annulation : recherchées n'importe où dans la
|
||||||
// phrase entendue, pas seulement en tête (ajusté le 1er septembre 2026
|
// phrase entendue, pas seulement en tête (ajusté le 1er septembre 2026
|
||||||
@@ -241,20 +350,109 @@
|
|||||||
// naturel à l'oral — "date de réalisation le 12/09 c'est tout Lilou" doit
|
// naturel à l'oral — "date de réalisation le 12/09 c'est tout Lilou" doit
|
||||||
// fonctionner dit d'une traite). Ce qui précède la phrase repérée dans la
|
// fonctionner dit d'une traite). Ce qui précède la phrase repérée dans la
|
||||||
// même phrase entendue est conservé comme dernière ligne de contenu avant
|
// même phrase entendue est conservé comme dernière ligne de contenu avant
|
||||||
// de clore — voir traiterLigneNotation.
|
// de clore — voir traiterLigneNotation. Construites par agent ci-dessous
|
||||||
const PHRASES_CLOTURE = [new RegExp("c'?est tout " + MOT_LILOU, "i")];
|
// (2 septembre 2026, ASP_Agents) plutôt qu'une seule fois pour "Lilou".
|
||||||
const PHRASES_ANNULATION = [new RegExp(MOT_LILOU + "\\s+annule", "i")];
|
|
||||||
|
// -------- Agents (ASP_Agents) — assistants nommés auxquels une consigne
|
||||||
|
// -------- vocale peut être adressée (2 septembre 2026, demande de
|
||||||
|
// -------- Jean-Marc, voir journal section 27 et réflexions stratégiques).
|
||||||
|
// -------- Liste récupérée depuis le serveur au chargement plutôt que
|
||||||
|
// -------- codée en dur, pour qu'ajouter/retirer un agent dans ASP_Agents
|
||||||
|
// -------- n'exige pas de modifier ce fichier.
|
||||||
|
let agentsDisponibles = [];
|
||||||
|
let agentsPrets = false;
|
||||||
|
|
||||||
|
// Échappe un nom d'agent pour un usage sûr en expression régulière (au
|
||||||
|
// cas où un nom contiendrait un jour un caractère spécial).
|
||||||
|
function echapperRegex(texte){
|
||||||
|
return texte.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construit les motifs de reconnaissance pour un agent donné, à partir de
|
||||||
|
// son nom et de ses variantes connues (colonne AGN_Variantes — voir
|
||||||
|
// commentaire d'en-tête, "Variantes de prononciation"). variantesBrutes
|
||||||
|
// est la chaîne brute reçue de agents.php ("Milou,Loulou"), vide ou
|
||||||
|
// absente pour un agent sans variante connue pour l'instant.
|
||||||
|
function construireAgent(nom, variantesBrutes){
|
||||||
|
const variantes = (variantesBrutes || "")
|
||||||
|
.split(",")
|
||||||
|
.map(function(v){ return v.trim(); })
|
||||||
|
.filter(function(v){ return v !== ""; });
|
||||||
|
const formes = [nom].concat(variantes).map(echapperRegex);
|
||||||
|
const motNom = formes.length > 1 ? "(?:" + formes.join("|") + ")" : formes[0];
|
||||||
|
|
||||||
|
// Seul cas qui reste codé en dur (voir commentaire d'en-tête) :
|
||||||
|
// l'homophone "les loups" pour la clôture/annulation de Lilou, qui
|
||||||
|
// porte sur la phrase entière et pas sur le seul prénom.
|
||||||
|
let motifsClotureSupp = [];
|
||||||
|
let motifsAnnulationSupp = [];
|
||||||
|
if(/^lilou$/i.test(nom)){
|
||||||
|
motifsClotureSupp = [/c'?est tou[st] les loups/i];
|
||||||
|
motifsAnnulationSupp = [/les loups\s+annule/i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nom: nom,
|
||||||
|
motifOuverture: new RegExp("^" + motNom + "\\s*[,:]?\\s*", "i"),
|
||||||
|
motifsCloture: [new RegExp("c'?est tout " + motNom, "i")].concat(motifsClotureSupp),
|
||||||
|
motifsAnnulation: [new RegExp(motNom + "\\s+annule", "i")].concat(motifsAnnulationSupp)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repère, en tête du texte donné, le nom d'un agent connu — renvoie
|
||||||
|
// l'agent et le texte restant (commande) une fois le nom retiré, ou null
|
||||||
|
// si aucun agent connu n'est reconnu en tête de phrase.
|
||||||
|
function trouverAgentDeclencheur(texte){
|
||||||
|
for(let i = 0; i < agentsDisponibles.length; i++){
|
||||||
|
const agent = agentsDisponibles[i];
|
||||||
|
if(agent.motifOuverture.test(texte)){
|
||||||
|
return {agent: agent, commande: texte.replace(agent.motifOuverture, "").trim()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function chargerAgents(){
|
||||||
|
fetch("agents.php")
|
||||||
|
.then(function(reponse){
|
||||||
|
if(!reponse.ok) throw new Error("HTTP " + reponse.status);
|
||||||
|
return reponse.json();
|
||||||
|
})
|
||||||
|
.then(function(donnees){
|
||||||
|
const liste = (donnees && donnees.agents) || [];
|
||||||
|
if(liste.length === 0) throw new Error("liste vide");
|
||||||
|
agentsDisponibles = liste.map(function(a){ return construireAgent(a.AGN_Nom, a.AGN_Variantes); });
|
||||||
|
agentsPrets = true;
|
||||||
|
})
|
||||||
|
.catch(function(){
|
||||||
|
// Plus de repli sur un nom d'agent figé en dur ici (2 septembre
|
||||||
|
// 2026, décision de Jean-Marc après le retrait de Lilou de
|
||||||
|
// ASP_Agents — un agent codé en dur ici referait exactement le
|
||||||
|
// problème qu'on vient de corriger côté agent_notes.php). Si
|
||||||
|
// agents.php est injoignable, la liste reste vide : aucune phrase
|
||||||
|
// ne déclenche de notation tant que la page n'est pas rechargée —
|
||||||
|
// même comportement que du bruit de fond ignoré, plutôt que de
|
||||||
|
// prétendre qu'un agent précis est disponible alors que la vraie
|
||||||
|
// liste n'a pas pu être chargée. Seule différence avec le bruit de
|
||||||
|
// fond ordinaire : un avertissement explicite, pour que ça ne
|
||||||
|
// passe pas inaperçu.
|
||||||
|
agentsDisponibles = [];
|
||||||
|
agentsPrets = true;
|
||||||
|
notifier("Liste des agents indisponible — aucun agent ne sera reconnu tant que la page n'est pas rechargée.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
chargerAgents();
|
||||||
|
|
||||||
// Marqueur oral de fin de ligne ("..., ensuite") retiré du texte conservé.
|
// Marqueur oral de fin de ligne ("..., ensuite") retiré du texte conservé.
|
||||||
const MARQUEUR_FIN_LIGNE = /\s*,?\s*ensuite\s*[.,!]?\s*$/i;
|
const MARQUEUR_FIN_LIGNE = /\s*,?\s*ensuite\s*[.,!]?\s*$/i;
|
||||||
|
|
||||||
// Repère la première occurrence (n'importe où dans le texte) d'une phrase
|
// Repère la première occurrence (n'importe où dans le texte) d'une phrase
|
||||||
// de la liste donnée, et renvoie sa position — ou null si aucune ne
|
// de la liste donnée, et renvoie sa position et sa longueur — ou null si
|
||||||
// correspond.
|
// aucune ne correspond.
|
||||||
function trouverPhraseDeControle(texte, motifs){
|
function trouverPhraseDeControle(texte, motifs){
|
||||||
for(let i = 0; i < motifs.length; i++){
|
for(let i = 0; i < motifs.length; i++){
|
||||||
const correspondance = texte.match(motifs[i]);
|
const correspondance = texte.match(motifs[i]);
|
||||||
if(correspondance) return {index: correspondance.index};
|
if(correspondance) return {index: correspondance.index, longueur: correspondance[0].length};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -267,24 +465,66 @@
|
|||||||
return texte.replace(MARQUEUR_FIN_LIGNE, "").replace(/[\s,;:]+$/, "").trim();
|
return texte.replace(MARQUEUR_FIN_LIGNE, "").replace(/[\s,;:]+$/, "").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Même nettoyage, côté début de ligne cette fois : retire la ponctuation
|
||||||
|
// de liaison isolée qui suit souvent une phrase de contrôle repérée en
|
||||||
|
// milieu de phrase ("... tâche : penser à ..." → ": " à retirer avant de
|
||||||
|
// garder "penser à ...").
|
||||||
|
function nettoyerDebutLigne(texte){
|
||||||
|
return texte.replace(/^[\s:,;-]+/, "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empile une ligne dans le tampon de notation avec son type ("ouverture",
|
||||||
|
// "contenu", "cloture" ou "annulation" — demande de Jean-Marc, 2
|
||||||
|
// septembre 2026, pour qu'un futur robot sache à quel assistant/action
|
||||||
|
// router le lot sans avoir à ré-analyser le texte). Seul le contenu
|
||||||
|
// s'affiche dans le panneau visuel — les marqueurs d'ouverture/clôture
|
||||||
|
// sont enregistrés mais ne servent à rien à afficher à l'utilisateur.
|
||||||
|
function ajouterAuTampon(texte, type){
|
||||||
|
if(!texte) return;
|
||||||
|
bufferNotation.push({texte: texte, type: type});
|
||||||
|
if(type === "contenu"){
|
||||||
|
afficherLigneDansPanneau(texte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function envoyerNotation(lignes){
|
function envoyerNotation(lignes){
|
||||||
fetch("lilou_notes.php", {
|
// Renommé le 2 septembre 2026 (ex-lilou_notes.php) — plus de raison de
|
||||||
|
// porter le nom d'un seul agent, voir agent_notes.php. "agent" : nom
|
||||||
|
// de l'assistant adressé pour tout ce lot (agentCourant, capturé à
|
||||||
|
// l'ouverture), copié dans CVX_AgentTraitant pour chaque ligne. Chaîne
|
||||||
|
// vide si agentCourant est absent (ne devrait pas arriver) plutôt qu'un
|
||||||
|
// nom d'agent précis figé en dur ici : agent_notes.php sait désormais
|
||||||
|
// résoudre lui-même l'agent par défaut depuis ASP_Agents.AGN_Defaut
|
||||||
|
// (voir ce fichier, 2 septembre 2026, suite du retrait de Lilou).
|
||||||
|
fetch("agent_notes.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: {"Content-Type": "application/json"},
|
||||||
body: JSON.stringify({lignes: lignes})
|
body: JSON.stringify({agent: agentCourant ? agentCourant.nom : "", lignes: lignes})
|
||||||
}).then(function(reponse){
|
}).then(function(reponse){
|
||||||
if(!reponse.ok) throw new Error("HTTP " + reponse.status);
|
if(!reponse.ok) throw new Error("HTTP " + reponse.status);
|
||||||
notifier("Note enregistrée.");
|
// Message rendu explicite à la demande de Jean-Marc (2 septembre
|
||||||
|
// 2026), pour ne laisser aucun doute sur le fait que l'enregistrement
|
||||||
|
// a bien eu lieu, et où : la table ASP_ConsignesVoix, qualifiée de
|
||||||
|
// "provisoire" (nom générique, avant les règles d'interprétation de
|
||||||
|
// l'étape 2).
|
||||||
|
notifier("Consigne enregistrée dans la table provisoire");
|
||||||
}).catch(function(){
|
}).catch(function(){
|
||||||
notifier("Erreur : la note n'a pas pu être enregistrée.");
|
notifier("Erreur : la note n'a pas pu être enregistrée.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function demarrerNotation(){
|
// agentCourant : agent adressé pour la notation en cours (objet construit
|
||||||
|
// par construireAgent) — capturé à l'ouverture, sert aussi bien pour la
|
||||||
|
// reconnaissance de la clôture/annulation propre à cet agent que pour
|
||||||
|
// l'envoi final (2 septembre 2026, ASP_Agents).
|
||||||
|
let agentCourant = null;
|
||||||
|
|
||||||
|
function demarrerNotation(agent){
|
||||||
modeNotation = true;
|
modeNotation = true;
|
||||||
bufferNotation = [];
|
bufferNotation = [];
|
||||||
ouvrirPanneauNotation();
|
agentCourant = agent;
|
||||||
notifier('Lilou note votre demande — dites "c\'est tout Lilou" pour terminer, ou "Lilou annule" pour annuler.');
|
ouvrirPanneauNotation(agent.nom);
|
||||||
|
notifier(agent.nom + ' note votre demande — dites "c\'est tout ' + agent.nom + '" pour terminer, ou "' + agent.nom + ' annule" pour annuler.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function terminerNotation(){
|
function terminerNotation(){
|
||||||
@@ -297,29 +537,44 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function traiterLigneNotation(texte){
|
function traiterLigneNotation(texte){
|
||||||
const cloture = trouverPhraseDeControle(texte, PHRASES_CLOTURE);
|
// Clôture/annulation propres à l'agent en cours (2 septembre 2026) —
|
||||||
const annulation = cloture ? null : trouverPhraseDeControle(texte, PHRASES_ANNULATION);
|
// "c'est tout Viktor" ne referme pas une notation ouverte avec Lilou,
|
||||||
|
// et réciproquement.
|
||||||
|
const motifsCloture = agentCourant ? agentCourant.motifsCloture : [];
|
||||||
|
const motifsAnnulation = agentCourant ? agentCourant.motifsAnnulation : [];
|
||||||
|
const cloture = trouverPhraseDeControle(texte, motifsCloture);
|
||||||
|
const annulation = cloture ? null : trouverPhraseDeControle(texte, motifsAnnulation);
|
||||||
const repere = cloture || annulation;
|
const repere = cloture || annulation;
|
||||||
if(repere){
|
if(repere){
|
||||||
// Même comportement dans les deux cas : on enregistre ce qui a été
|
|
||||||
// dit, sans traitement différencié (demande explicite de Jean-Marc).
|
|
||||||
// Tout ce qui précède la phrase de clôture/annulation dans cette même
|
// Tout ce qui précède la phrase de clôture/annulation dans cette même
|
||||||
// phrase entendue est la dernière ligne de contenu ("date de
|
// phrase entendue est la dernière ligne de contenu ("date de
|
||||||
// réalisation le 12/09 c'est tout Lilou" dit d'une traite) : on la
|
// réalisation le 12/09 c'est tout Lilou" dit d'une traite) : on la
|
||||||
// capture avant de clore.
|
// capture avant de clore.
|
||||||
const avant = nettoyerLigne(texte.slice(0, repere.index));
|
const avant = nettoyerLigne(texte.slice(0, repere.index));
|
||||||
if(avant){
|
ajouterAuTampon(avant, "contenu");
|
||||||
bufferNotation.push(avant);
|
|
||||||
afficherLigneDansPanneau(avant);
|
// La phrase de contrôle elle-même est maintenant enregistrée aussi
|
||||||
}
|
// (demande de Jean-Marc, 2 septembre 2026), avec son type — cloture
|
||||||
|
// ou annulation distinguées cette fois (alors que le traitement du
|
||||||
|
// contenu, lui, reste identique dans les deux cas, comme décidé le
|
||||||
|
// 1er septembre) : un futur robot doit savoir si le lot est à
|
||||||
|
// exécuter ou à écarter sans avoir à ré-interpréter le texte brut.
|
||||||
|
const phraseControle = texte.slice(repere.index, repere.index + repere.longueur).trim();
|
||||||
|
ajouterAuTampon(phraseControle, cloture ? "cloture" : "annulation");
|
||||||
|
|
||||||
|
// Symétriquement, ce qui suit la phrase de clôture/annulation dans la
|
||||||
|
// même phrase entendue ("c'est tout Lilou, et puis aussi ...") était
|
||||||
|
// jusqu'ici perdu silencieusement. Philosophie du projet : on
|
||||||
|
// n'interprète rien, on note tout ce qui a été dit (retour de
|
||||||
|
// Jean-Marc, 2 septembre 2026 — voir aussi le symétrique côté
|
||||||
|
// ouverture ci-dessous).
|
||||||
|
const apres = nettoyerLigne(nettoyerDebutLigne(texte.slice(repere.index + repere.longueur)));
|
||||||
|
ajouterAuTampon(apres, "contenu");
|
||||||
|
|
||||||
terminerNotation();
|
terminerNotation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ligne = nettoyerLigne(texte);
|
ajouterAuTampon(nettoyerLigne(texte), "contenu");
|
||||||
if(ligne){
|
|
||||||
bufferNotation.push(ligne);
|
|
||||||
afficherLigneDansPanneau(ligne);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reconnaissance.addEventListener("start", passerEnEcoute);
|
reconnaissance.addEventListener("start", passerEnEcoute);
|
||||||
@@ -340,59 +595,95 @@
|
|||||||
revenirAuRepos();
|
revenirAuRepos();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mot déclencheur en tête de phrase, avec ou sans virgule/deux-points
|
// Détection du mot déclencheur (2 septembre 2026, généralisation
|
||||||
// juste après ("Lilou, ajoute une tâche" ou "Lilou ajoute une tâche") —
|
// multi-agents) : plus un seul motifDeclencheur codé en dur pour Lilou,
|
||||||
// "Milou" toléré au même titre que les autres motifs, voir MOT_LILOU.
|
// mais trouverAgentDeclencheur(texte) qui teste chaque agent connu
|
||||||
const motifDeclencheur = new RegExp("^" + MOT_LILOU + "\\s*[,:]?\\s*", "i");
|
// (agentsDisponibles, chargé depuis ASP_Agents — voir plus haut) et
|
||||||
|
// renvoie celui reconnu en tête de phrase, le cas échéant.
|
||||||
|
|
||||||
reconnaissance.addEventListener("result", function(evenement){
|
reconnaissance.addEventListener("result", function(evenement){
|
||||||
const dernier = evenement.results[evenement.results.length - 1];
|
const dernier = evenement.results[evenement.results.length - 1];
|
||||||
const texte = dernier[0].transcript.trim();
|
const texte = dernier[0].transcript.trim();
|
||||||
|
|
||||||
if(modeNotation){
|
if(modeNotation){
|
||||||
// En notation, plus besoin de "Lilou" en tête sur chaque ligne — on
|
// En notation, plus besoin du nom de l'agent en tête sur chaque
|
||||||
// ne teste que la clôture/annulation, tout le reste est du contenu.
|
// ligne — on ne teste que la clôture/annulation propre à
|
||||||
|
// agentCourant, tout le reste est du contenu. Une ligne captée ici
|
||||||
|
// est une vraie interaction : le délai d'inactivité est repoussé.
|
||||||
traiterLigneNotation(texte);
|
traiterLigneNotation(texte);
|
||||||
|
reinitialiserMinuteurInactivite();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let agent;
|
||||||
let commande;
|
let commande;
|
||||||
if(motifDeclencheur.test(texte)){
|
const declenchement = trouverAgentDeclencheur(texte);
|
||||||
commande = texte.replace(motifDeclencheur, "").trim();
|
if(declenchement){
|
||||||
} else if(reveilEnAttente && Date.now() <= expirationReveil){
|
agent = declenchement.agent;
|
||||||
// "Lilou" venait d'être entendu seul : on accepte ce résultat-ci
|
commande = declenchement.commande;
|
||||||
// comme s'il avait été précédé du mot déclencheur (voir commentaire
|
} else if(reveilEnAttente && agentReveil && Date.now() <= expirationReveil){
|
||||||
// sur reveilEnAttente plus haut).
|
// Un nom d'agent venait d'être entendu seul : on accepte ce
|
||||||
|
// résultat-ci comme s'il avait été précédé du nom, pour le même
|
||||||
|
// agent (voir commentaire sur reveilEnAttente/agentReveil plus haut).
|
||||||
|
agent = agentReveil;
|
||||||
commande = texte;
|
commande = texte;
|
||||||
} else {
|
} else {
|
||||||
// Pas adressé à Lilou (conversation de fond, etc.) : ignoré en
|
// Pas adressé à un agent connu (conversation de fond, etc.) : ignoré
|
||||||
// silence, sans notification — c'est tout l'intérêt du mot
|
// en silence, sans notification, et SANS repousser le délai
|
||||||
// déclencheur en écoute continue. Laissé en console pour le debug.
|
// d'inactivité — c'est justement ce que ce délai doit détecter (voir
|
||||||
console.log('[micro.js] ignoré (pas de "Lilou" en tête) :', texte);
|
// couperPourInactivite plus haut). Laissé en console pour le debug.
|
||||||
|
console.log('[micro.js] ignoré (aucun agent reconnu en tête) :', texte);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reveilEnAttente = false;
|
reveilEnAttente = false;
|
||||||
|
agentReveil = null;
|
||||||
|
reinitialiserMinuteurInactivite();
|
||||||
|
|
||||||
if(!commande){
|
if(!commande){
|
||||||
// "Lilou" entendu seul, rien d'exploitable derrière dans ce même
|
// Nom d'agent entendu seul, rien d'exploitable derrière dans ce même
|
||||||
// résultat — probablement coupé par le moteur juste après la
|
// résultat — probablement coupé par le moteur juste après la
|
||||||
// virgule. On laisse une petite fenêtre pour que la suite, dite
|
// virgule. On laisse une petite fenêtre pour que la suite, dite
|
||||||
// juste après, soit acceptée même sans "Lilou" en tête.
|
// juste après, soit acceptée même sans le nom en tête, et on
|
||||||
|
// mémorise l'agent concerné (agentReveil) pour l'attribuer
|
||||||
|
// correctement.
|
||||||
reveilEnAttente = true;
|
reveilEnAttente = true;
|
||||||
|
agentReveil = agent;
|
||||||
expirationReveil = Date.now() + DUREE_REVEIL_MS;
|
expirationReveil = Date.now() + DUREE_REVEIL_MS;
|
||||||
notifier("Lilou vous écoute.");
|
notifier(agent.nom + " vous écoute.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(PHRASES_OUVERTURE.some(function(m){ return m.test(commande); })){
|
const repereOuverture = trouverPhraseDeControle(commande, PHRASES_OUVERTURE);
|
||||||
demarrerNotation();
|
if(repereOuverture){
|
||||||
|
demarrerNotation(agent);
|
||||||
|
// La phrase d'ouverture elle-même est enregistrée (type "ouverture",
|
||||||
|
// demande de Jean-Marc le 2 septembre 2026) — texte complet jusqu'à
|
||||||
|
// la fin de la phrase repérée ("ajoute une tâche", "un truc à
|
||||||
|
// faire"...), pour qu'un futur robot puisse s'en servir pour choisir
|
||||||
|
// le bon assistant, sans que Claude/l'agent n'ait à l'interpréter ici.
|
||||||
|
const phraseOuverture = commande.slice(0, repereOuverture.index + repereOuverture.longueur).trim();
|
||||||
|
ajouterAuTampon(phraseOuverture, "ouverture");
|
||||||
|
|
||||||
|
// Contenu éventuellement dicté dans la même phrase que l'ouverture
|
||||||
|
// ("Lilou, ajoute une tâche, penser à ..." dit d'une traite, sans
|
||||||
|
// pause) : jusqu'ici perdu silencieusement — demarrerNotation() ne
|
||||||
|
// fait que réinitialiser le tampon et ouvrir le panneau, rien
|
||||||
|
// n'était capturé pour ce premier morceau. Corrigé le 2 septembre
|
||||||
|
// 2026 (retour de Jean-Marc : "texte de départ... pas noté, qui
|
||||||
|
// pourtant s'était affiché") en reprenant le même principe que la
|
||||||
|
// clôture juste au-dessus : tout ce qui suit la phrase d'ouverture
|
||||||
|
// repérée devient la première ligne de contenu.
|
||||||
|
const apres = nettoyerLigne(nettoyerDebutLigne(
|
||||||
|
commande.slice(repereOuverture.index + repereOuverture.longueur)
|
||||||
|
));
|
||||||
|
ajouterAuTampon(apres, "contenu");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Étape 1 (hors notation) : simple retour visuel du texte reconnu
|
// Étape 1 (hors notation) : simple retour visuel du texte reconnu
|
||||||
// après le mot déclencheur, pour valider le circuit — aucune autre
|
// après le nom de l'agent, pour valider le circuit — aucune autre
|
||||||
// commande interprétée à ce stade.
|
// commande interprétée à ce stade.
|
||||||
notifier('Lilou a entendu : "' + commande + '"');
|
notifier(agent.nom + ' a entendu : "' + commande + '"');
|
||||||
});
|
});
|
||||||
|
|
||||||
reconnaissance.addEventListener("error", function(evenement){
|
reconnaissance.addEventListener("error", function(evenement){
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
Require all denied
|
|
||||||
+1
-1
@@ -24,7 +24,7 @@
|
|||||||
* d'identification, avant toute connexion.
|
* d'identification, avant toute connexion.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const ASPIC_VERSION = 'V1.005';
|
const ASPIC_VERSION = 'V1.006';
|
||||||
|
|
||||||
if (basename($_SERVER['SCRIPT_NAME']) === 'version.php') {
|
if (basename($_SERVER['SCRIPT_NAME']) === 'version.php') {
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|||||||
Reference in New Issue
Block a user