/** * ASPIC — contact_ia_panel.js * * Outil de mise au point interne (4 septembre 2026, soir) — PAS encore un * module utilisateur final, mais la première fois que l'interprétation IA * d'une dictée de contact (voir contact_ia_interprete.php/contact_ia_valide.php) * est branchée directement sur le micro du bureau (#micro-desk), à la place * de micro_test.js — décision explicite de Jean-Marc : la banque de dictées * brutes (micro_test.js/dictees_test.php, ASP_DicteesTest) a rempli son rôle * de constitution de corpus et "ne sert plus à rien" maintenant que * l'interprétation elle-même fonctionne. micro_test.js n'est pas supprimé * (garde son utilité d'archive/référence), juste retiré du script chargé en * bas d'ecran_fond.html. * * Reprend la quasi-totalité de la logique de contact_ia_test.js (identifiants * data-champ, appels aux mêmes endpoints, mêmes fonctions de remplissage) — * seule la construction du DOM change : un panneau flottant construit en JS * (même principe que la fenêtre de micro_test.js), pas une page HTML dédiée. * contact_ia_test.html/.js restent en place tels quels (page autonome, plus * très utile maintenant mais rien ne l'interdit de la garder) : ce fichier * ne les modifie pas. * * Différences de présentation demandées par Jean-Marc (4 septembre 2026, * soir) par rapport à contact_ia_test.html : * - Panneau qui ne prend pas tout l'écran (comme la fenêtre de micro_test.js), * pas une page à part entière. * - Champs "bruts" : nom du champ suivi de sa valeur, police terminal, * plutôt que des blocs/cartes stylées avec grille — effet "outil d'expert" * voulu explicitement. * - Pas de texte d'explication en haut : remplacé par une ligne façon prompt * SSH ("user@ : ", blanc sur fond noir). Le prénom vient de * preferences.php (USR_Prenom, déjà utilisé ailleurs sur cet écran pour le * chevalet) plutôt que d'être écrit en dur — même principe que pour les * noms d'agents (jamais figer un nom précis dans le code). * * Contrairement à micro_test.js, le panneau NE SE FERME PAS quand le micro * s'arrête tout seul (redémarrage automatique compris) : seule une action * explicite de Jean-Marc (Valider, ou Échap — voir troisième vague plus bas) * referme le panneau. * * Deuxième vague de demandes de présentation (4 septembre 2026, encore le * même soir) : * - Le panneau grandit vers le bas, mais jamais au-delà du pied réel du * micro sur le bureau (silhouette complète de #micro-desk, voir * ecran_fond.html/MICRO_ZONE_Y2) — recalculé à chaque ouverture et au * redimensionnement de la fenêtre (voir ajusterHauteurPanneau), en lisant * directement la position réelle de #micro-desk (getBoundingClientRect) * plutôt qu'en dupliquant ici la géométrie de l'image de fond. * - Séquence d'ouverture façon terminal : le prompt "user@ : " * s'affiche d'abord seul avec un petit curseur "_" clignotant (une seconde * et demie), puis le curseur est remplacé par "micro ouvert", et une * deuxième ligne de prompt apparaît en dessous : "user@ : parlez !" * (voir demarrerSequencePrompt/rafraichirTextePrompt). * - Plus de bouton "Interpréter" ni "Réinitialiser" : l'interprétation part * automatiquement dès que le micro s'arrête proprement (clic volontaire de * Jean-Marc, pas un arrêt dû à une erreur bloquante — voir * lancerInterpretation, appelée depuis l'événement "end" de la * reconnaissance). Un statut "Interprétation en cours…" s'affiche dans la * même zone que la dictée brute et les erreurs éventuelles, le temps de * l'appel. * - Plus de croix de fermeture séparée : "Valider" referme le panneau après * confirmation de l'enregistrement (le temps de lire le message). * * Troisième vague (4 septembre 2026, encore le même soir) : * - Bouton "Abandonner" retiré : il faisait double emploi avec la touche * Échap, qui reste l'unique façon d'abandonner l'essai en cours sans rien * enregistrer ("tant pis si on perd des data", voir abandonnerPanneau et * le gestionnaire "keydown" plus bas). "Valider" reste seul, en pleine * largeur. * - 3 zones de texte libre pour les actions (plus la liste dynamique * "+ Ajouter une action") — voir remplirActions/collecterActionsValidees. */ (function(){ "use strict"; const boutonMicro = document.getElementById("micro-desk"); if(!boutonMicro) return; // ce script ne concerne que l'écran de fond const ReconnaissanceVocale = window.SpeechRecognition || window.webkitSpeechRecognition; if(!ReconnaissanceVocale){ boutonMicro.title = "Reconnaissance vocale non disponible sur ce navigateur"; return; } const reconnaissance = new ReconnaissanceVocale(); reconnaissance.lang = "fr-FR"; reconnaissance.continuous = true; reconnaissance.interimResults = true; reconnaissance.maxAlternatives = 1; let ecouteVoulue = false; let erreurBloquante = false; let ligneEnCours = null; let texteComplet = ""; let derniereExtraction = null; const DUREE_INACTIVITE_MS = 10 * 60 * 1000; let minuteurInactivite = null; function reinitialiserMinuteurInactivite(){ if(minuteurInactivite) clearTimeout(minuteurInactivite); minuteurInactivite = setTimeout(function(){ if(!ecouteVoulue) return; ecouteVoulue = false; reconnaissance.stop(); }, DUREE_INACTIVITE_MS); } function arreterMinuteurInactivite(){ if(minuteurInactivite){ clearTimeout(minuteurInactivite); minuteurInactivite = null; } } // Silence prolongé = fin de la dictée (demande de Jean-Marc, 4 septembre // 2026, encore le même soir) : "un silence de 3-4 secondes vaut arrêt des // opérations" — 3,5s de compromis. Armé seulement à partir du premier // résultat reçu (voir l'événement "result" plus bas), pas dès l'ouverture // du micro : sinon un temps de réflexion avant de commencer à parler // couperait le micro tout seul. Un arrêt par silence est traité exactement // comme un clic volontaire sur le micro (ecouteVoulue = false puis // reconnaissance.stop()) : ça déclenche l'interprétation automatique au // même titre (voir l'événement "end"). const DUREE_SILENCE_ARRET_MS = 3500; let minuteurSilence = null; function reinitialiserMinuteurSilence(){ if(minuteurSilence) clearTimeout(minuteurSilence); minuteurSilence = setTimeout(function(){ if(!ecouteVoulue) return; ecouteVoulue = false; reconnaissance.stop(); }, DUREE_SILENCE_ARRET_MS); } function arreterMinuteurSilence(){ if(minuteurSilence){ clearTimeout(minuteurSilence); minuteurSilence = null; } } // -------- Construction du panneau (une seule fois, comme micro_test.js) -------- let panneau = null, lignePrompt1, promptTexte1, curseurClignotant, lignePrompt2, promptTexte2, zoneLignes, zoneResultat, boutonValider, messageCreation, selectStatut, selectProfession, blocNouvelleProfession, champNouvelleProfession, selectAssistant, champsActions; function ligneChamp(conteneur, label, nomChamp, multiligne){ const ligne = document.createElement("div"); ligne.className = "ligne-champ"; const lab = document.createElement("span"); lab.className = "label-champ"; lab.textContent = label + " :"; const input = document.createElement(multiligne ? "textarea" : "input"); input.dataset.champ = nomChamp; ligne.appendChild(lab); ligne.appendChild(input); conteneur.appendChild(ligne); return input; } function construirePanneau(){ if(panneau) return; const style = document.createElement("style"); style.textContent = "#panneau-contact-ia{position:fixed;top:6%;left:50%;transform:translateX(-50%);" + "width:min(480px,86vw);background:#000;border:1px solid #333;" + "border-radius:6px;box-shadow:0 10px 40px rgba(0,0,0,.6);z-index:950;" + "display:none;flex-direction:column;overflow:hidden;" + "font-family:'Courier New',Consolas,monospace;color:#fff;}" + "#panneau-contact-ia.visible{display:flex;}" + "#panneau-contact-ia .entete-prompt{color:#fff;background:#000;font-size:13px;" + "line-height:1.6;padding:8px 10px;border-bottom:1px solid #222;}" + "#panneau-contact-ia #ligne-prompt-2[hidden]{display:none;}" + "@keyframes clignoter-curseur{0%,49%{opacity:1;}50%,100%{opacity:0;}}" + "#panneau-contact-ia #curseur-clignotant{animation:clignoter-curseur 1s steps(1) infinite;}" + "#panneau-contact-ia #curseur-clignotant[hidden]{display:none;}" + // Une seule zone de défilement pour TOUT le contenu, y compris l'en- // tête façon prompt — demande de Jean-Marc (4 septembre 2026, encore le // même soir) : "je veux que l'ascenseur parte du haut de la fenêtre // même si ça fait disparaître les deux premières lignes de prompt". // L'en-tête n'est donc plus fixe (flex:none retiré ci-dessus) : il fait // maintenant partie du flux normal à l'intérieur de .contenu-scrollable, // qui est l'unique enfant du panneau et en occupe toute la hauteur. // overflow-y:auto + min-height:0 sur cet enfant flex : le classique // pour qu'il se réduise réellement au lieu de pousser le panneau à // s'agrandir. "#panneau-contact-ia .contenu-scrollable{flex:1;min-height:0;overflow-y:auto;}" + // Ascenseur personnalisé, plus discret que celui du navigateur par // défaut (demande de Jean-Marc) : fin, sans piste visible, pouce gris // sombre qui se fond dans le panneau plutôt que la barre grise claire // habituelle. scrollbar-width/-color pour Firefox, ::-webkit-scrollbar // pour Chrome/Edge (les deux couvrent déjà toute la hauteur de // .contenu-scrollable, qui occupe elle-même tout l'espace disponible // sous l'en-tête — voir flex:1 juste au-dessus). "#panneau-contact-ia .contenu-scrollable{scrollbar-width:thin;scrollbar-color:#3a3a3a transparent;}" + "#panneau-contact-ia .contenu-scrollable::-webkit-scrollbar{width:6px;}" + "#panneau-contact-ia .contenu-scrollable::-webkit-scrollbar-track{background:transparent;}" + "#panneau-contact-ia .contenu-scrollable::-webkit-scrollbar-thumb{" + "background:#3a3a3a;border-radius:3px;}" + "#panneau-contact-ia .contenu-scrollable::-webkit-scrollbar-thumb:hover{background:#555;}" + "#panneau-contact-ia .lignes{padding:8px 10px;" + "font-size:13px;line-height:1.5;white-space:pre-wrap;border-bottom:1px solid #222;}" + "#panneau-contact-ia .lignes .en-cours{color:#8a8a8a;font-style:italic;}" + "#panneau-contact-ia .lignes .erreur{color:#e14a5c;}" + "#panneau-contact-ia .resultat{padding:8px 10px;display:none;}" + "#panneau-contact-ia .resultat.visible{display:block;}" + "#panneau-contact-ia .ligne-champ{display:flex;align-items:baseline;gap:6px;" + "margin-bottom:5px;font-size:13px;}" + "#panneau-contact-ia .label-champ{flex:0 0 100px;color:#888;text-align:right;}" + "#panneau-contact-ia .ligne-champ input,#panneau-contact-ia .ligne-champ select," + "#panneau-contact-ia .ligne-champ textarea{flex:1;font:inherit;font-size:13px;" + "background:transparent;color:#fff;border:none;border-bottom:1px solid #333;" + "padding:2px 0;}" + "#panneau-contact-ia .ligne-champ input.interprete," + "#panneau-contact-ia .ligne-champ select.interprete{border-bottom-color:#3a6d3a;}" + "#panneau-contact-ia .ligne-champ textarea{min-height:40px;resize:vertical;}" + // Options des listes (Statut, Profession, Assistant) illisibles à // l'ouverture — blanc sur blanc pour les lignes normales, blanc sur // bleu (surlignage par défaut du navigateur) pour celle sous le // pointeur — remonté par Jean-Marc le 4 septembre 2026 (encore le même // soir) après essai réel. "background/color" sur