/* DONA_LEARNING_V93 : feedback explicite, corrections modérées, aucun entraînement implicite. */ (()=>{ 'use strict'; if(window.DonaLearningV93)return; const el=(tag,cls,txt)=>{const e=document.createElement(tag);if(cls)e.className=cls;if(txt!==undefined)e.textContent=String(txt);return e;}; const btn=(label,click,cls='dl93-button')=>{const b=el('button',cls,label);b.type='button';b.addEventListener('click',click);return b;}; const field=(label,input)=>{const x=el('label','dl93-field');x.append(el('span','',label),input);return x;}; const txt=(text,n=120)=>{const s=String(text||'');return s.length>n?s.slice(0,n)+'…':s;}; const api=async(url,body)=>{const o=body===undefined?{cache:'no-store',credentials:'same-origin'}:{method:'POST',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},credentials:'same-origin',body:JSON.stringify(body)};const r=await fetch(url,o);const data=await r.json().catch(()=>({}));if(!r.ok||!data.ok)throw Error(data.error||'Erreur HTTP '+r.status);return data;}; let last=null,dock,panel,dialog,menuTick=0,socketBound=false,submitting=false; function notify(message,bad=false){const n=document.getElementById('dl93Status');if(n){n.textContent=message||'';n.classList.toggle('dl93-error',bad);}} function showDock(){if(!last||!last.token)return;dock.hidden=false;dock.querySelector('.dl93-context').textContent=txt(last.heard||'Dernière demande',90);} function setTurn(d){if(!d||!d.token)return;last={token:d.token,heard:String(d.heard||''),answer:String(d.answer||''),source:d.source||'voice'};showDock();notify('');} function labelBtn(name,fn){return btn(name,fn,'dl93-tool');} function createDock(){dock=el('div','dl93-dock');dock.id='dl93Dock';dock.hidden=true;dock.setAttribute('role','region');dock.setAttribute('aria-label','Évaluer la dernière réponse de Dona'); dock.append(el('span','dl93-context','Dernière réponse de Dona'),labelBtn('👍',()=>rateUp()),labelBtn('👎',()=>openCorrection()),labelBtn('Historique',()=>openPanel())); const close=labelBtn('×',()=>{dock.hidden=true;});close.title='Masquer les évaluations';close.setAttribute('aria-label','Masquer les évaluations');dock.append(close);document.body.append(dock); } async function rateUp(){if(!last||submitting)return;submitting=true;try{await api('/api/dona/v93/learning/feedback',{token:last.token,rating:'up'});dock.hidden=true;last=null;openToast('👍 Réponse enregistrée.');}catch(e){openToast(e.message,true);}finally{submitting=false;}} function openToast(message,bad=false){let box=document.getElementById('dl93Toast');if(!box){box=el('div','dl93-toast');box.id='dl93Toast';document.body.append(box);}box.textContent=message;box.classList.toggle('dl93-error',bad);box.hidden=false;setTimeout(()=>{box.hidden=true;},5500);} function makeDialog(){dialog=el('div','dl93-overlay');dialog.id='dl93Correction';dialog.hidden=true;dialog.setAttribute('role','dialog');dialog.setAttribute('aria-modal','true');dialog.setAttribute('aria-label','Corriger une réponse Dona'); const sheet=el('div','dl93-sheet'); const h=el('div','dl93-top');h.append(el('h3','', 'Corriger Dona'),btn('×',()=>closeCorrection(),'dl93-close'));sheet.append(h); const summary=el('div','dl93-summary');summary.id='dl93Summary';sheet.append(summary); const kind=document.createElement('select');kind.id='dl93Kind';[['transcription','Vosk a mal transcrit'],['comprehension','Hermes a mal compris'],['execution','Mauvaise action / erreur d’outil']].forEach(([v,l])=>{const opt=el('option','',l);opt.value=v;kind.append(opt);}); sheet.append(field('Type d’erreur',kind)); const correction=document.createElement('textarea');correction.id='dl93CorrectionText';correction.maxLength=1800;correction.rows=4; sheet.append(field('Ce que j’ai dit / ce qui était attendu',correction)); const explanation=document.createElement('textarea');explanation.id='dl93Note';explanation.rows=2;explanation.maxLength=800;explanation.placeholder='Précision facultative';sheet.append(field('Commentaire',explanation)); const note=el('p','dl93-hint','La correction restera en attente dans Apprentissage. Après validation : Vosk utilisera la correction pour la même phrase exacte ; Hermes recevra une indication pour cette demande précise. Aucune commande ne sera relancée automatiquement.');sheet.append(note); kind.addEventListener('change',()=>{correction.placeholder=kind.value==='transcription'?'Écris exactement les mots que tu avais prononcés…':kind.value==='comprehension'?'Explique ce que Dona devait comprendre / faire…':'Indique la bonne action ou l’équipement visé…';note.textContent=kind.value==='execution'?'Ce signalement sera conservé dans l’historique. Aucune règle d’exécution ne sera appliquée automatiquement.':'La correction restera en attente jusqu’à sa validation dans Apprentissage. Seules les formulations identiques seront corrigées.';}); const actions=el('div','dl93-actions');actions.append(btn('Annuler',closeCorrection,'dl93-button dl93-neutral'),btn('Enregistrer 👎',submitBad));sheet.append(actions); const state=el('p','dl93-state');state.id='dl93CorrectionStatus';state.setAttribute('role','status');sheet.append(state); dialog.append(sheet);document.body.append(dialog);dialog.addEventListener('pointerdown',e=>{if(e.target===dialog)closeCorrection();}); } function openCorrection(){if(!last){openToast('Aucune réponse récente.');return;}dialog.hidden=false;const raw=last.heard||'Transcription indisponible';document.getElementById('dl93Summary').replaceChildren(el('p','','Dona a entendu : '+txt(raw,500)),el('p','','Réponse : '+txt(last.answer,500)));const kind=document.getElementById('dl93Kind');kind.value='transcription';const c=document.getElementById('dl93CorrectionText');c.value='';c.placeholder='Écris exactement les mots que tu avais prononcés…';document.getElementById('dl93Note').value='';document.getElementById('dl93CorrectionStatus').textContent='';kind.dispatchEvent(new Event('change'));c.focus();} function closeCorrection(){if(dialog)dialog.hidden=true;} async function submitBad(){if(!last||submitting)return;const kind=document.getElementById('dl93Kind').value;const correction=document.getElementById('dl93CorrectionText').value;const note=document.getElementById('dl93Note').value;const error=document.getElementById('dl93CorrectionStatus');submitting=true; try{await api('/api/dona/v93/learning/feedback',{token:last.token,rating:'down',kind,correction,note});closeCorrection();last=null;dock.hidden=true;openToast('👎 Correction enregistrée. Valide-la dans Apprentissage pour l’activer.');} catch(e){error.textContent=e.message;error.classList.add('dl93-error');} finally{submitting=false;}} function makePanel(){panel=el('div','dl93-overlay');panel.id='dl93Panel';panel.hidden=true;panel.setAttribute('role','dialog');panel.setAttribute('aria-modal','true');panel.setAttribute('aria-label','Historique des corrections Dona'); const sheet=el('div','dl93-sheet dl93-history');const top=el('div','dl93-top');top.append(el('h3','','Apprentissage de Dona'),btn('×',closePanel,'dl93-close'));sheet.append(top); const intro=el('p','dl93-hint','Historique local des réponses évaluées. Aucun son enregistré. Les règles doivent être approuvées manuellement et peuvent être retirées à tout moment.');sheet.append(intro); const content=el('div','dl93-panel-body');content.id='dl93PanelContent';sheet.append(content); const foot=el('div','dl93-actions');foot.append(btn('Actualiser',loadPanel,'dl93-button dl93-neutral'),btn('Fermer',closePanel));sheet.append(foot); const state=el('p','dl93-state');state.id='dl93Status';state.setAttribute('role','status');sheet.append(state);panel.append(sheet);document.body.append(panel);panel.addEventListener('pointerdown',e=>{if(e.target===panel)closePanel();}); } async function openPanel(){panel.hidden=false;await loadPanel();} function closePanel(){if(panel)panel.hidden=true;} function prettyKind(k){return({transcription:'Vosk / transcription',comprehension:'Compréhension Hermes',execution:'Exécution'})[k]||'Avis';} function chip(s){const v={approved:'Règle active',pending:'À valider',recorded:'Enregistré',rejected:'Rejeté',revoked:'Règle retirée'};return el('span','dl93-chip',v[s]||s);} function itemRow(item,content){const card=el('article','dl93-record');const top=el('div','dl93-record-head');top.append(el('strong','',item.rating==='up'?'👍 Correct':'👎 '+prettyKind(item.kind)),chip(item.status));card.append(top); card.append(el('p','dl93-small','Entendu : '+txt(item.heard,260)));if(item.answer)card.append(el('p','dl93-small','Réponse : '+txt(item.answer,180))); if(item.correction)card.append(el('p','dl93-small','Correction : '+txt(item.correction,260))); if(item.note)card.append(el('p','dl93-small','Note : '+txt(item.note,180))); if(item.status==='pending'){ const actions=el('div','dl93-actions'); actions.append(btn('✓ Valider la règle',async()=>{if(!confirm('Appliquer cette correction aux prochaines demandes identiques ?'))return;await change('/api/dona/v93/learning/'+item.id+'/approve');},'dl93-button')); actions.append(btn('Rejeter',async()=>{await change('/api/dona/v93/learning/'+item.id+'/reject');},'dl93-button dl93-neutral'));card.append(actions); } const remove=btn('Supprimer cette évaluation',async()=>{if(confirm('Supprimer cette évaluation et sa règle éventuelle ?'))await change('/api/dona/v93/learning/'+item.id+'/delete');},'dl93-button dl93-neutral');card.append(remove); content.append(card);} async function change(url){try{await api(url,{});await loadPanel();}catch(e){notify(e.message,true);}} async function loadPanel(){const content=document.getElementById('dl93PanelContent');if(!content)return;content.replaceChildren(el('p','','Chargement…'));notify('');try{ const data=await api('/api/dona/v93/learning');content.replaceChildren();content.append(el('h4','','Évaluations : '+data.stats.positive+' 👍 · '+data.stats.negative+' 👎')); if(data.rules.length){const rules=el('section','dl93-rules');rules.append(el('h4','','Règles actives ('+data.rules.length+')')); for(const r of data.rules){const row=el('div','dl93-rule');row.append(el('span','',prettyKind(r.kind)+' : '+txt(r.original,90)+' → '+txt(r.replacement,90)),btn('Retirer',async()=>{if(confirm('Supprimer cette règle ?'))await change('/api/dona/v93/learning/rules/'+r.id+'/remove');},'dl93-button dl93-neutral'));rules.append(row);}content.append(rules);} if(!data.history.length)content.append(el('p','dl93-small','Aucune réponse évaluée pour le moment.')); data.history.forEach(f=>itemRow(f,content)); }catch(e){content.replaceChildren(el('p','dl93-error',e.message));}} function attachMenu(){const m=document.getElementById('dv92ToolsMenu');if(!m||m.hidden||m.querySelector('#dl93MenuEntry'))return;const b=btn('🎓 Apprentissage et corrections',()=>{m.hidden=true;document.getElementById('dv92Hamburger')?.setAttribute('aria-expanded','false');openPanel();},'dv92-menu-entry');b.id='dl93MenuEntry';m.append(b);} function init(){if(document.getElementById('dl93Dock'))return;createDock();makeDialog();makePanel();document.addEventListener('click',e=>{if(e.target.closest('#dv92Hamburger'))window.setTimeout(attachMenu,10);});document.addEventListener('keydown',e=>{if(e.key==='Escape'){closeCorrection();closePanel();}}); const waitForSocket=()=>{const sock=window.alfredSocket;if(!sock?.on){if(++menuTick<80)setTimeout(waitForSocket,150);return;}if(socketBound)return;socketBound=true; sock.on('dona_learning_turn',setTurn); sock.on('dona_learning_stt',data=>{const n=document.getElementById('dl93Transcript');if(n){n.textContent='Vosk : '+data.raw+' → Correction : '+data.used;n.hidden=false;}}); sock.on('alfred_state',state=>{if(state?.mode==='thinking'){if(dock)dock.hidden=true;if(document.getElementById('dl93Transcript'))document.getElementById('dl93Transcript').hidden=true;}}); };waitForSocket(); const t=el('div','dl93-transcript');t.id='dl93Transcript';t.hidden=true;document.body.append(t); window.DonaLearningV93={openPanel,openCorrection};} if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init,{once:true});else init(); })();