⚠ Outil d'aide à la décision clinique — pas un dispositif médical. Les résultats de HAL Diag sont fournis à titre indicatif uniquement et ne remplacent en aucun cas le jugement clinique du médecin. Données traitées localement (HAL-MDGM1.54b sur OVH HDS). Anonymisation automatique avant synthèse HAL-SNT4.6.
Données cliniques
Oréo
🔬
HAL DIAG · PRÊT
Collez vos données cliniques ci-dessus
et lancez l'analyse.
'); w.document.close(); } function clearPhoto() { photoBase64 = null; $('photoPreview').style.display = 'none'; $('cameraInput').value = ''; } async function handlePhoto(input) { const file = input.files[0]; if (!file) return; // PDF → extraction texte via API if (file.type === 'application/pdf') { $('ocrStatus').textContent = '⏳ Extraction du PDF…'; $('ocrStatus').style.color = 'var(--teal)'; $('photoPreview').style.display = 'block'; $('photoImg').style.display = 'none'; try { if (!API_BASE) await detectAPI(); const formData = new FormData(); formData.append('file', file); const r = await fetch(API_BASE + '/api/diag/pdf', { method: 'POST', headers: {'ngrok-skip-browser-warning': 'true'}, body: formData }); if (r.ok) { const {text} = await r.json(); $('clinicalInput').value = text; $('ocrStatus').textContent = `✓ PDF extrait (${text.length} car.) — vérifiez et complétez`; $('ocrStatus').style.color = 'var(--green)'; photoBase64 = null; } else { $('ocrStatus').textContent = '⚠ Extraction échouée — copiez le texte manuellement'; $('ocrStatus').style.color = 'var(--yellow)'; } } catch(e) { $('ocrStatus').textContent = '⚠ ' + e.message; $('ocrStatus').style.color = 'var(--red)'; } return; } // Image → resize + preview photoMediaType = 'image/jpeg'; $('photoImg').style.display = 'block'; // Redimensionner à max 1024px pour alléger l'envoi const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { const MAX = 2560; let w = img.width, h = img.height; if (w > MAX || h > MAX) { if (w > h) { h = Math.round(h * MAX / w); w = MAX; } else { w = Math.round(w * MAX / h); h = MAX; } } const canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; canvas.getContext('2d').drawImage(img, 0, 0, w, h); const dataUrl = canvas.toDataURL('image/jpeg', 0.95); $('photoImg').src = dataUrl; $('photoPreview').style.display = 'block'; photoBase64 = dataUrl.split(',')[1]; const kb = Math.round(photoBase64.length * 0.75 / 1024); $('ocrStatus').textContent = `✓ Image prête (${w}×${h}px · ~${kb}KB) — cliquez Analyser`; $('ocrStatus').style.color = 'var(--teal)'; }; img.src = e.target.result; }; reader.readAsDataURL(file); } const $=id=>document.getElementById(id); let API_BASE=''; async function detectAPI(){ try{ const cfg=await fetch('api-config.json?'+Date.now(),{signal:AbortSignal.timeout(3000),headers:{'ngrok-skip-browser-warning':'true'}}); if(cfg.ok){const{url}=await cfg.json();const h=await fetch(url+'/api/health',{signal:AbortSignal.timeout(4000),headers:{'ngrok-skip-browser-warning':'true'}});if(h.ok){API_BASE=url;return;}} }catch(e){} try{const h=await fetch('http://localhost:8420/api/health',{signal:AbortSignal.timeout(2000)});if(h.ok){API_BASE='http://localhost:8420';return;}}catch(e){} } function setStep(n, status, text){ const el=$('step'+n), st=$('step'+n+'-status'); el.classList.remove('active','done'); if(status==='active'){el.classList.add('active');st.className='step-status running';st.innerHTML=' '+text;} else if(status==='done'){el.classList.add('done');st.className='step-status done';st.textContent='✓ '+text;} else{st.className='step-status wait';st.textContent=text;} } function renderMedgemma(mg, duration){ if(!mg||mg.error) return '
MedGemma indisponible — analyse Sonnet seule
'; let h=''; if(mg.resume_clinique) h+=`
Résumé
${esc(mg.resume_clinique)}
`; if(mg.parametres_anormaux?.length){ h+='
Paramètres anormaux
'; mg.parametres_anormaux.forEach(p=>h+=`
${esc(p.parametre)} : ${esc(p.valeur)} — ${esc(p.interpretation)}
`); h+='
'; } if(mg.hypotheses_diagnostiques?.length){ h+='
Hypothèses diagnostiques
'; mg.hypotheses_diagnostiques.forEach(hd=>{ const pc=hd.probabilite?.toLowerCase().includes('haute')?'haute':hd.probabilite?.toLowerCase().includes('moyenne')?'moyenne':'faible'; h+=`
${esc(hd.diagnostic)}${esc(hd.probabilite)}
${esc(hd.arguments)}
`; }); h+='
'; } if(mg.urgences_identifiees?.length){ h+='
Urgences
'; mg.urgences_identifiees.forEach(u=>h+=`
⚠ ${esc(u)}
`); h+='
'; } return h; } function esc(s){return String(s||'').replace(//g,'>');} async function fetchMedicaments(synthesis, container) { if (!synthesis || !container) return; container.innerHTML = `
⏳ Recherche BDPM…
`; try { if (!API_BASE) await detectAPI(); const r = await fetch(API_BASE + '/api/diag/medicaments', { method: 'POST', headers: {'Content-Type': 'application/json', 'ngrok-skip-browser-warning': 'true'}, body: JSON.stringify({synthesis}) }); if (!r.ok) { container.innerHTML = ''; return; } const {medicaments} = await r.json(); if (!medicaments || !medicaments.length) { container.innerHTML = ''; return; } container.innerHTML = renderBDPM(medicaments); } catch(e) { container.innerHTML = ''; } } function renderBDPM(medicaments) { let h = `
BDPM · Base de Données Publique des Médicaments ansm.sante.fr · officiel · ${medicaments.length} terme(s)
`; medicaments.forEach(med => { const rcp = med.rcp || {}; h += `
🔍 "${med.terme}"
`; // Carte principale med.resultats.forEach((m,i) => { const remb = m.remboursement ? `Remb. ${m.remboursement}` : ''; const amm = m.statut_amm.includes('active') ? `✓ AMM` : `${m.statut_amm}`; h += `
${esc(m.substance||m.nom)}
${remb} ${amm}
${m.dosage?`💊 ${esc(m.dosage)}`:''} ${m.forme?`📋 ${esc(m.forme)}`:''} ${m.voies?.length?`🔀 ${m.voies.join(', ')}`:''}
`; }); // Bloc RCP if (rcp && !rcp.error && Object.keys(rcp).length > 1) { const prescBadge = rcp.prescription ? `${esc(rcp.prescription)}` : ''; const ordoBadge = rcp.ordonnance === 'Oui' ? `Ordonnance obligatoire` : `Sans ordonnance`; h += `
RCP ANSM ${prescBadge} ${ordoBadge} ${rcp.rcp_url?`↗ RCP officiel`:''}
`; if (rcp.posologie_adulte) h += `
POSOLOGIE
${esc(rcp.posologie_adulte)}
`; if (rcp.contre_indications?.length) { h += `
CONTRE-INDICATIONS
    `; rcp.contre_indications.forEach(ci => { h += `
  • ${esc(ci)}
  • `; }); h += `
`; } if (rcp.interactions_principales?.length) { h += `
INTERACTIONS
    `; rcp.interactions_principales.forEach(it => { h += `
  • ${esc(it)}
  • `; }); h += `
`; } if (rcp.precautions_cles?.length) { h += `
PRÉCAUTIONS
    `; rcp.precautions_cles.forEach(p => { h += `
  • ${esc(p)}
  • `; }); h += `
`; } h += `
`; } h += `
`; }); h += `
Source : Base de Données Publique des Médicaments (BDPM) — ANSM/HAS — Mise à jour 2x/jour
`; return h; } function buildMgPDF(mg) { if (!mg || mg.error) return ''; let h = '
'; h += '
HAL-MDGM1.54b · Analyse locale
'; if (mg.resume_clinique) h += '

Résumé : ' + esc(mg.resume_clinique) + '

'; if (mg.hypotheses_diagnostiques?.length) { h += ''; h += ''; mg.hypotheses_diagnostiques.forEach(hd => { h += ``; }); h += '
HypothèseProbabilitéArguments
${esc(hd.diagnostic)}${esc(hd.probabilite)}${esc(hd.arguments)}
'; } if (mg.urgences_identifiees?.length) { h += '

Urgences : ' + mg.urgences_identifiees.map(u => esc(u)).join(' · ') + '

'; } h += '
'; return h; } let lastMgData = null; // pour export PDF function renderSynthesis(text){ // Tableaux markdown → text = text.replace(/(\|.+\|\n?\|[-| :]+\|\n?(?:\|.+\|\n?)+)/g, (match) => { const rows = match.trim().split('\n').filter(r => r.trim()); let html = '
'; rows.forEach((row, i) => { if (/^\|[-| :]+\|$/.test(row.trim())) return; // skip separator const cells = row.split('|').filter((c,idx,a) => idx>0 && idx `<${tag}>${c.trim()}`).join('') + ''; }); return html + '
'; }); return text .replace(/\*\*(.+?)\*\*/g,'$1') .replace(/^### (.+)$/gm,'

$1

') .replace(/^## (.+)$/gm,'

$1

') .replace(/^# (.+)$/gm,'

$1

') .replace(/^- (.+)$/gm,'
  • $1
  • ') .replace(/(
  • .*?<\/li>(\n|$))+/gs, m => '') .replace(/\n\n/g,'

    ') .replace(/\n/g,'
    '); } async function runDiag(){ const input=$('clinicalInput').value.trim(); if(!input && !photoBase64){$('clinicalInput').focus();return;} if(!API_BASE) await detectAPI(); if(!API_BASE){ $('results').innerHTML='

    ❌ Serveur HAL inaccessible
    '; return; } // Reset UI const btn=$('runBtn'); btn.disabled=true; btn.innerHTML=' Analyse en cours…'; $('steps').style.display='grid'; $('results').innerHTML=''; setStep(1,'active','Analyse MedGemma…'); setStep(2,'wait','En attente'); setStep(3,'wait','En attente'); const t0=Date.now(); try{ const body = { clinical_data: input, anonymize: $('anonCheck').checked, lang: currentLang }; if (photoBase64) { body.image_base64 = photoBase64; body.image_media_type = photoMediaType; } const r=await fetch(API_BASE+'/api/diag',{ method:'POST', headers:{'Content-Type':'application/json','ngrok-skip-browser-warning':'true'}, body:JSON.stringify(body) }); // Simuler les étapes visuellement (la requête est bloquante) setTimeout(()=>setStep(1,'done','Extraction OK'),1000); setTimeout(()=>{setStep(2,$('anonCheck').checked?'done':'done',$('anonCheck').checked?'Anonymisé':'Désactivé');},2000); setTimeout(()=>setStep(3,'active','Synthèse Sonnet…'),2500); if(!r.ok){const err=await r.json().catch(()=>({}));throw new Error(err.detail||r.statusText);} const data=await r.json(); const elapsed=Math.round((Date.now()-t0)/1000); lastMgData = data.medgemma; setStep(1,'done',`${data.medgemma_duration_s||'?'}s`); setStep(2,'done',data.anonymized?'Anonymisé':'Désactivé'); setStep(3,'done',`${elapsed}s total`); // Render results const res=$('results'); res.innerHTML=''; // MedGemma card — masquer si indisponible et image seule const mgHasData = data.medgemma && !data.medgemma.error && Object.keys(data.medgemma).length > 0; if (mgHasData) { const mgCard=document.createElement('div'); mgCard.className='result-card'; mgCard.innerHTML=`
    HAL-MDGM1.54b · Local · HDS ${data.medgemma_duration_s||'?'}s · OVH HDS · aucune donnée sortie
    ${renderMedgemma(data.medgemma,data.medgemma_duration_s)}
    `; res.appendChild(mgCard); } // Sonnet card const sonnetCard=document.createElement('div'); sonnetCard.className='result-card'; sonnetCard.innerHTML=`
    HAL-SNT4.6 · Synthèse finale ${data.anonymized?'🔒 données anonymisées':''}
    ${elapsed}s total
    ${renderSynthesis(esc(data.synthesis))}
    `; res.appendChild(sonnetCard); // Bloc médicaments BDPM — chargement asynchrone après affichage synthèse const medContainer = document.createElement('div'); medContainer.id = 'med-bdpm-block'; res.appendChild(medContainer); fetchMedicaments(data.synthesis, medContainer); }catch(e){ setStep(3,'wait','Erreur'); $('results').innerHTML=`
    ❌ ${esc(e.message)}
    `; } btn.disabled=false; btn.innerHTML=' Analyser'; } detectAPI(); // URL params: ?q=texte+clinique&lang=fr&auto=1 (function(){ var p=new URLSearchParams(window.location.search); var q=p.get("q")||p.get("clinical"); var lang=p.get("lang"); var auto=p.get("auto"); if(q){ document.getElementById("clinicalInput").value=decodeURIComponent(q); } if(lang==="en"){ toggleLang(); } if(auto==="1"&&q){ setTimeout(function(){runDiag();},1500); } })();