File size: 13,190 Bytes
f91d419
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!doctype html>
<html lang="fr"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>StoryPatch · DiffuThink</title>
<style>
:root{color-scheme:dark;--bg:#10151d;--panel:#19222e;--line:#344354;--text:#eaf0f7;--sub:#a9b7c8;--accent:#a9edd4}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:16px/1.6 system-ui}main{max-width:1160px;margin:auto;padding:38px 24px}nav{display:flex;justify-content:space-between;align-items:center;gap:16px}a{color:var(--accent)}.brand{font-size:22px;font-weight:750}.eyebrow{font-size:12px;letter-spacing:2px;color:var(--accent)}h1{font-size:clamp(32px,5vw,56px);line-height:1.15;max-width:820px;margin:20px 0}h2{font-size:20px;margin:0 0 14px}.sub{color:var(--sub)}.meta{font-size:13px;color:var(--sub)}.grid{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin:28px 0}.panel{background:var(--panel);border:1px solid var(--line);padding:24px;border-radius:16px}textarea{width:100%;min-height:240px;resize:vertical;color:var(--text);background:#0d141e;border:1px solid #63768e;border-radius:9px;padding:16px;font:18px/1.7 system-ui}button,input{font:inherit}button{border:1px solid var(--line);border-radius:8px;padding:10px 15px;cursor:pointer;background:#243348;color:var(--text)}button.primary{background:var(--accent);color:#112d22;font-weight:700;border:0}button:disabled{opacity:.45;cursor:default}.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.examples{margin:22px 0}.examples button{font-size:13px}.selection{margin:14px 0;padding:12px;background:#111c29;border-radius:8px;overflow-wrap:anywhere}mark{background:#e5c885;color:#1b241f;padding:1px 4px;border-radius:3px}.candidate{margin:14px 0;padding:18px;border:1px solid var(--line);border-radius:10px;background:#111b27}.candidate h3{font-size:22px;color:var(--accent);margin:0 0 6px;overflow-wrap:anywhere}.preview{white-space:pre-wrap;overflow-wrap:anywhere;font-size:14px;margin:12px 0}.trace{font:12px/1.6 monospace;white-space:pre-wrap;max-height:240px;overflow:auto}summary{cursor:pointer;color:var(--sub);font-size:13px}#status{min-height:28px;color:var(--accent)}input{width:100px;padding:7px;background:#0d141e;color:white;border:1px solid var(--line);border-radius:6px}footer{border-top:1px solid var(--line);padding-top:20px;font-size:13px;color:var(--sub)}@media(max-width:800px){.grid{grid-template-columns:1fr}main{padding:24px 16px}.panel{padding:18px}nav{align-items:flex-start}}
</style>
<main><nav><div class="brand">StoryPatch <span class="meta">par DiffuThink</span></div><a href="/lab">Ouvrir le laboratoire de génération →</a></nav>
<p class="eyebrow">UN PETIT PASSAGE. PLUSIEURS POSSIBILITÉS.</p>
<h1>Change un passage.<br>Garde le reste de ton histoire.</h1>
<p class="sub">Sélectionne quelques mots dans un texte anglais. Explore des variantes proposées avec le contexte des deux côtés, compare-les, puis choisis ce que tu gardes.</p>
<p id="modelinfo" class="meta">Modèle local entraîné depuis zéro · aucun service de génération externe</p>
<div class="row examples"><span class="meta">Essayer :</span><button id="example-mood">Une émotion incohérente</button><button id="example-adjective">Un adjectif différent</button><button id="example-warmth">Un détail à revoir</button></div>
<div class="grid"><section class="panel"><h2>1. Choisis le passage</h2><label for="story" class="meta">Ton mini-récit · anglais simple</label><textarea id="story" spellcheck="false" maxlength="3000"></textarea>
<div class="selection">Passage sélectionné : <mark id="selection">…</mark></div>
<label for="ranking" class="meta">Classement des variantes</label> <select id="ranking" style="font:inherit;background:#0d141e;color:var(--text);padding:8px;border:1px solid var(--line);border-radius:6px;margin-bottom:16px;max-width:100%"><option value="nll">Exploration · probabilité locale</option><option id="learned-option" value="learned" disabled>Correction ciblée · expérimental</option></select><p id="ranking-help" class="meta">Explore des variantes selon la probabilité locale du générateur.</p>
<div class="row"><button id="explore" class="primary">Explorer ce passage</button><button id="undo" disabled>Annuler la modification</button><button id="download">Exporter le texte</button></div>
<p id="status" role="status"></p><p class="meta">Sélectionne 1 à 12 mots entiers. Les caractères hors de la sélection sont conservés à l’identique.</p>
<details><summary>Réglage reproductible</summary><label for="seed">Seed</label> <input id="seed" type="number" min="0" value="42"><p class="meta">Six essais déterminés par cette seed, avec quelques longueurs voisines. Les doublons sont regroupés.</p></details></section>
<section class="panel"><h2>2. Compare et choisis</h2><div id="results"><p class="sub">Les variantes apparaîtront ici. Tu gardes toujours le dernier mot.</p></div></section></div>
<section class="panel"><h2>Une suggestion visible, une modification limitée</h2><p class="sub">Le modèle reconstruit le passage avec l’histoire autour. Si le modèle de classement est activé, il compare les variantes au texte original et peut conseiller de le garder. Ses préférences sont apprises sur de petits récits synthétiques : lis chaque proposition avant de l’appliquer.</p><p class="meta">Le détail de chaque variante montre les étapes de reconstruction et son classement. Une modification peut être annulée. Les textes soumis ne sont pas enregistrés par cette application ; aucun appel à un autre modèle n’est effectué.</p></section>
<footer><p>DiffuThink : petit modèle spécialisé dans les récits anglais synthétiques TinyStories. Poids appris dans ce projet, développement assisté par IA. StoryPatch est un outil exploratoire, pas un correcteur certifié. Le débruitage est utilisé ici ; la continuation du laboratoire est autorégressive.</p></footer></main>
<script>
const $=id=>document.getElementById(id);let selection=null,previous=null,busy=false;
let rankerDescription='Classeur spécialisé dans six formes de récits synthétiques. Il peut conseiller de garder l’original, même si une erreur subsiste. Le classement peut être moins bon sur d’autres textes.';
$('ranking').onchange=()=>{$('ranking-help').textContent=$('ranking').value==='learned'?rankerDescription:'Explore des variantes selon la probabilité locale du générateur.';$('results').replaceChildren();$('status').textContent='Relance l’exploration pour comparer ce classement.'};
const examples={mood:['Tom was happy when he lost his favorite toy. He began to cry.','happy'],adjective:['Lily found a blue ball. She rolled it across the grass.','blue'],warmth:['Tom gave the puppy a blanket. Soon it felt cold and fell asleep.','cold']};
function updateSelection(){const a=$('story').selectionStart,b=$('story').selectionEnd;if(a!==b){selection={a,b};$('selection').textContent=$('story').value.slice(a,b)}else{selection=null;$('selection').textContent='Sélectionne des mots dans le texte'}$('explore').disabled=busy||!selection}
function example(name){const [text,part]=examples[name];$('story').value=text;const a=text.indexOf(part);$('story').focus();$('story').setSelectionRange(a,a+part.length);updateSelection();$('results').replaceChildren();$('status').textContent='';previous=null;$('undo').disabled=true}
for(const name of Object.keys(examples))$('example-'+name).onclick=()=>example(name);
$('story').addEventListener('mouseup',updateSelection);$('story').addEventListener('keyup',updateSelection);
$('story').addEventListener('input',()=>{selection=null;$('selection').textContent='Sélectionne un passage';$('results').replaceChildren();$('explore').disabled=true});
function node(tag,text,className){const x=document.createElement(tag);x.textContent=text;if(className)x.className=className;return x}
$('explore').onclick=async()=>{if(!selection||busy)return;busy=true;$('explore').disabled=true;$('status').textContent='Le modèle explore le passage…';$('results').replaceChildren();
 const text=$('story').value,ranking=$('ranking').value;const start=Array.from(text.slice(0,selection.a)).length,end=Array.from(text.slice(0,selection.b)).length;
 try{const response=await fetch('/api/generate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mode:'rewrite',ranking,prompt:text,start,end,seed:Number($('seed').value)})});const data=await response.json();if(!response.ok)throw Error(data.error);
  if($('story').value!==text||$('ranking').value!==ranking){$('status').textContent='Le texte ou le classement a changé : relance l’exploration.';return}
  const keepCard=node('article','','candidate');keepCard.append(node('h3',data.selection.text),node('p',data.recommendation?.action==='keep'?(data.blending?'Le modèle s’abstient de recommander un remplacement. Compare les variantes ci-dessous.':'Conseil : garder le passage original'):'Passage original · toujours disponible','meta'));
  const keep=node('button','Garder l’original');keep.onclick=()=>{if($('story').value!==text){$('status').textContent='Le texte a changé : relance l’exploration.';return}$('results').replaceChildren();$('status').textContent='Passage original conservé. Aucun changement appliqué.'};keepCard.append(keep);$('results').append(keepCard);
  if(!data.candidates.length)$('results').append(node('p','Aucune variante distincte obtenue. Essaie une sélection différente.','sub'));
  for(const candidate of data.candidates){const card=node('article','','candidate');card.append(node('h3',candidate.replacement));const recommended=data.recommendation?.action==='replace'&&data.recommendation.replacement===candidate.replacement;card.append(node('p',recommended?'Variante conseillée · à relire':candidate.ranker_score!==undefined?'Variante classée selon sa compatibilité apprise avec le contexte':candidate.delta_nll<0?'Plus probable localement selon ce modèle':'Le modèle préfère localement la formulation originale','meta'));
   const preview=node('div','','preview');preview.append(document.createTextNode(data.prefix),node('mark',candidate.replacement),document.createTextNode(data.suffix));card.append(preview);
   const apply=node('button','Appliquer cette variante');apply.onclick=()=>{if($('story').value!==text){$('status').textContent='Le texte a changé : relance l’exploration.';return}previous=text;$('story').value=candidate.text;$('undo').disabled=false;$('results').replaceChildren();selection=null;$('selection').textContent=candidate.replacement;$('explore').disabled=true;$('status').textContent='Passage remplacé. Tout le reste du texte est conservé.'};card.append(apply);
   const details=node('details','');details.append(node('summary','Voir le débruitage et le classement'));if(candidate.ranker_score!==undefined)details.append(node('p',(data.blending?'Score combiné (probabilité locale + compatibilité) : ':'Score de compatibilité : ')+candidate.ranker_score.toFixed(3)+' ; original : '+data.original_ranker_score.toFixed(3)+'. Plus haut indique une préférence apprise, pas une probabilité de correction.','meta'));details.append(node('p','NLL locale : '+candidate.local_nll.toFixed(3)+' ; original : '+data.original_local_nll.toFixed(3)+'. Plus bas signifie plus probable, pas forcément plus correct. Seed '+candidate.seed+'.','meta'));details.append(node('div',candidate.trace.map(t=>'Étape '+t.step+' · '+t.remaining+' masques\n'+t.model_text).join('\n\n'),'trace'));card.append(details);$('results').append(card)}
  $('status').textContent=data.candidates.length+' variante(s) distincte(s) sur '+data.attempts+' essais. Aucun changement appliqué.';
 }catch(e){$('status').textContent=e.message}finally{busy=false;$('explore').disabled=!selection}};
$('undo').onclick=()=>{if(previous===null)return;$('story').value=previous;previous=null;$('undo').disabled=true;$('results').replaceChildren();selection=null;$('selection').textContent='Sélectionne un passage';$('explore').disabled=true;$('status').textContent='Texte précédent restauré.'};
$('download').onclick=()=>{const url=URL.createObjectURL(new Blob([$('story').value],{type:'text/plain;charset=utf-8'}));const a=document.createElement('a');a.href=url;a.download='storypatch.txt';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000)};
fetch('/api/info').then(r=>r.json()).then(x=>{$('learned-option').disabled=!x.ranker;if(x.ranker?.blending){$('learned-option').textContent='Classement contextuel · expérimental';rankerDescription='Compare les variantes en combinant leur probabilité locale et un score appris sur des récits variés. Le modèle peut conseiller de garder l’original ; relis chaque suggestion.';if($('ranking').value==='learned')$('ranking-help').textContent=rankerDescription}$('modelinfo').textContent=(x.ranker?.blending?'StoryPatch v3 · ':x.editing_manifest?'StoryPatch v2 · ':'')+(x.parameters/1e6).toFixed(2)+' M paramètres'+(x.ranker?' + '+(x.ranker.parameters/1e6).toFixed(2)+' M pour le classement expérimental':'')+' · '+x.manifest.max_length+' tokens de contexte · poids appris dans ce projet'});
example('mood');
</script></html>