83 lines
12 KiB
JavaScript
83 lines
12 KiB
JavaScript
/* 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();
|
||
})();
|