File size: 10,330 Bytes
c6f3061
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
<!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>
<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 masque uniquement le passage sélectionné et le reconstruit avec l’histoire autour. Il classe ensuite les variantes selon leur probabilité locale. Ce classement ne vérifie ni la logique ni la grammaire : lis chaque proposition avant de l’appliquer.</p><p class="meta">Le détail de chaque variante montre les étapes de reconstruction. Le texte original reste disponible et une application 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;
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;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',prompt:text,start,end,seed:Number($('seed').value)})});const data=await response.json();if(!response.ok)throw Error(data.error);
  if($('story').value!==text){$('status').textContent='Le texte a changé : relance l’exploration.';return}
  $('results').append(node('p','Original : '+data.selection.text,'meta'));
  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));card.append(node('p',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'));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=>{$('modelinfo').textContent=(x.parameters/1e6).toFixed(2)+' M paramètres · '+x.manifest.max_length+' tokens de contexte · '+x.manifest.splits.train.documents.toLocaleString('fr')+' récits · poids entraînés depuis zéro'});
example('mood');
</script></html>