mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Cartes raisonnement qui suivent l'écriture ; micro aligné ; moyenne tok/s
- Le défilement interne des cartes bornées ne suivait PAS la génération : le rendu en attente porte la bulle entière (.msg), pas .body — le test « parent = bodywrap » ne passait jamais et l'épinglage bas était mort. On cherche maintenant le bodywrap DANS la bulle : la carte suit le texte au fur et à mesure, et une remontée manuelle est respectée. Les cartes d'outil suivent aussi la ligne en cours, puis remontent en tête une fois l'appel terminé. - Bouton micro : il gardait la bordure des boutons génériques, décalé de ses voisins. Ajouté aux règles communes des boutons de saisie (34 px, sans bordure, fond au survol) avec #attach et #files-btn. - La carte du temps total (au-dessus de la saisie) affiche la vitesse moyenne de la conversation : « en cours — 12 s · moy 21.3 tok/s ». Alimentée par les événements stats journalisés (gen_tokens/gen_ms, cumulatifs par complétion — seul le delta est ajouté), donc rejouée au chargement : la moyenne survit au refresh. Remise à zéro au reset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
86d711e772
commit
481028b023
4 files changed
+72
-20
No files matched your search
+36
-10
@@ -1774,7 +1774,7 @@ button:active:not(:disabled){transform:translateY(1px)}
|
||||
⚠️ Une seule règle pour les quatre. Il y en avait trois générations empilées
|
||||
(pilule large « Envoyer », aplat noir, aplat sauge) : chaque état tirait sa
|
||||
taille, sa forme et son alignement d'une couche différente. */
|
||||
#inputbar #attach,#inputbar #files-btn,#send,#stop{
|
||||
#inputbar #attach,#inputbar #files-btn,#inputbar #mic,#send,#stop{
|
||||
width:34px;height:34px;min-height:34px;flex:none;margin:0;padding:0;
|
||||
display:inline-flex;align-items:center;justify-content:center;align-self:center;
|
||||
border:0;border-radius:4px;background:transparent;color:var(--dim);
|
||||
@@ -1785,8 +1785,8 @@ button:active:not(:disabled){transform:translateY(1px)}
|
||||
#stop{display:none}
|
||||
html[data-busy="1"] #send{display:none}
|
||||
html[data-busy="1"] #stop{display:inline-flex}
|
||||
#inputbar #attach svg,#inputbar #files-btn svg,#send svg,#stop svg{display:block;width:18px;height:18px}
|
||||
#inputbar #attach:hover,#inputbar #files-btn:hover{background:var(--row-bg-on);color:var(--text)}
|
||||
#inputbar #attach svg,#inputbar #files-btn svg,#inputbar #mic svg,#send svg,#stop svg{display:block;width:18px;height:18px}
|
||||
#inputbar #attach:hover,#inputbar #files-btn:hover,#inputbar #mic:hover{background:var(--row-bg-on);color:var(--text)}
|
||||
html[data-files="1"] #files-btn{color:var(--accent)}
|
||||
#send{color:var(--accent)}
|
||||
#send:hover:not(:disabled){background:var(--accent-bg);color:var(--accent);opacity:1;transform:none}
|
||||
@@ -2060,8 +2060,9 @@ html[data-files="1"] #files-btn{color:var(--accent)}
|
||||
.rolebadge{display:inline-block;margin-left:6px;padding:0 6px;border:1px solid var(--accent);border-radius:3px;color:var(--accent);font-size:9px;letter-spacing:.05em;text-transform:uppercase;vertical-align:middle}
|
||||
|
||||
/* Dictée : anneau rouge pulsé pendant l'enregistrement, estompé pendant la
|
||||
transcription. Le bouton garde le gabarit de ses voisins (joindre, fichiers). */
|
||||
#mic.rec{color:var(--err);border-color:var(--err);animation:micpulse 1.2s ease-in-out infinite}
|
||||
transcription. Le gabarit (34 px, fond au survol) vient de la règle commune
|
||||
des boutons de saisie — #mic y est listé avec #attach et #files-btn. */
|
||||
#mic.rec{color:var(--err);animation:micpulse 1.2s ease-in-out infinite}
|
||||
@keyframes micpulse{50%{box-shadow:0 0 0 5px color-mix(in srgb,var(--err) 25%,transparent)}}
|
||||
#mic.busy{opacity:.55}
|
||||
|
||||
@@ -5988,6 +5989,11 @@ function renderToolMsg(el, tu){
|
||||
body.appendChild(wait);
|
||||
}
|
||||
addCopyButtons(body); scrollMaybe();
|
||||
// Carte à hauteur bornée : tant que l'outil TRAVAILLE, on suit le bas (la
|
||||
// ligne en cours d'écriture, la commande qui se tape). Terminé, on remonte en
|
||||
// tête — c'est le résumé qu'on relit, pas la dernière ligne.
|
||||
const bw = el.querySelector('.bodywrap');
|
||||
if(bw){ bw.scrollTop = tu.done ? 0 : bw.scrollHeight; }
|
||||
}
|
||||
// Inject a "copier" button into every <pre> code block (idempotent).
|
||||
function addCopyButtons(root){
|
||||
@@ -6209,9 +6215,28 @@ function paintTurnClock(){
|
||||
}
|
||||
const ms = (T.doneTs || nowServer()) - T.startTs;
|
||||
if(!(ms > 0)){ el.remove(); return; }
|
||||
el.textContent = (T.doneTs ? 'tour terminé en ' : 'en cours — ') + fmtDur(ms);
|
||||
// Vitesse moyenne de la CONVERSATION à côté du temps du tour : le débit
|
||||
// typique de ce modèle sur ce fil, toutes réponses confondues.
|
||||
const avg = (CONV_MS > 1000 && CONV_TOK > 20)
|
||||
? ' · moy ' + (CONV_TOK / (CONV_MS/1000)).toFixed(1) + ' tok/s' : '';
|
||||
el.textContent = (T.doneTs ? 'tour terminé en ' : 'en cours — ') + fmtDur(ms) + avg;
|
||||
el.classList.toggle('done', !!T.doneTs);
|
||||
}
|
||||
// --- Vitesse moyenne de la conversation --------------------------------------
|
||||
// Alimentée par les événements `stats` (journalisés, donc rejoués au
|
||||
// chargement : la moyenne survit au refresh). gen_tokens / gen_ms sont
|
||||
// CUMULATIFS au sein d'une complétion et retombent à la suivante : on n'ajoute
|
||||
// que le delta, et une valeur qui recule signale une nouvelle complétion.
|
||||
let CONV_TOK = 0, CONV_MS = 0, _cgTok = 0, _cgMs = 0;
|
||||
function feedConvSpeed(s){
|
||||
const gt = s.gen_tokens || 0, gms = s.gen_ms || 0;
|
||||
if(!gt) return;
|
||||
if(gt < _cgTok){ _cgTok = 0; _cgMs = 0; }
|
||||
CONV_TOK += gt - _cgTok;
|
||||
CONV_MS += Math.max(0, gms - _cgMs);
|
||||
_cgTok = gt; _cgMs = gms;
|
||||
}
|
||||
function resetConvSpeed(){ CONV_TOK = 0; CONV_MS = 0; _cgTok = 0; _cgMs = 0; }
|
||||
// Le compteur se montre AUSSI sur l'indicateur « … » : pendant un appel d'outil
|
||||
// ou un long prefill il n'y a encore aucune réponse sous laquelle écrire, et
|
||||
// c'est précisément le moment où l'on se demande si ça avance.
|
||||
@@ -6434,8 +6459,9 @@ function flushRender(final){
|
||||
}
|
||||
// Carte bornée (raisonnement/outil) : si on était collé en bas, on y reste —
|
||||
// la carte suit la génération. Un défilement manuel vers le haut est respecté.
|
||||
const bw=p.el.parentElement;
|
||||
const scrollable=bw && bw.classList.contains('bodywrap');
|
||||
// ⚠️ p.el est la BULLE (.msg), pas .body : le bodywrap est dedans, pas au-dessus.
|
||||
const bw=p.el.querySelector ? p.el.querySelector(':scope > .bodywrap') : null;
|
||||
const scrollable=!!bw;
|
||||
const stick=scrollable && bw.scrollTop+bw.clientHeight>=bw.scrollHeight-60;
|
||||
const t0=performance.now();
|
||||
renderBody(p.el, text);
|
||||
@@ -6517,7 +6543,7 @@ function handleDelta(d){
|
||||
// serveur) : on nettoie l'écran et on resynchronise la liste, car la bascule
|
||||
// a pu être déclenchée depuis un autre appareil. Les fichiers suivent : ils
|
||||
// appartiennent à la discussion, le panneau doit changer avec elle.
|
||||
if(d.reset!==undefined){ smoothReset(); cancelRender(); stopWorkTimer(); const tc=document.getElementById('turn-clock'); if(tc) tc.remove(); PENDING=null; document.getElementById('chat').innerHTML=''; newTurn(); setCtxUsed(0); lastSeq=0; setBusy(false); if(typeof loadConversations==='function') loadConversations(); if(typeof filesOnConvChange==='function') filesOnConvChange(); if(typeof modeOnConvChange==='function') modeOnConvChange(); return; }
|
||||
if(d.reset!==undefined){ smoothReset(); cancelRender(); stopWorkTimer(); resetConvSpeed(); const tc=document.getElementById('turn-clock'); if(tc) tc.remove(); PENDING=null; document.getElementById('chat').innerHTML=''; newTurn(); setCtxUsed(0); lastSeq=0; setBusy(false); if(typeof loadConversations==='function') loadConversations(); if(typeof filesOnConvChange==='function') filesOnConvChange(); if(typeof modeOnConvChange==='function') modeOnConvChange(); return; }
|
||||
// --- Mode code (20-mode.js) -----------------------------------------------
|
||||
if(d.mode!==undefined){ if(typeof applyModeDelta==='function') applyModeDelta(d.mode); return; }
|
||||
if(d.code_hint){ if(typeof showCodeHint==='function') showCodeHint(); return; }
|
||||
@@ -6565,7 +6591,7 @@ function handleDelta(d){
|
||||
// une trace du fil et DOIT être rejouée.
|
||||
if(d.compact_noop){ setCompacting(false); if(!REPLAYING) toast('rien à compacter (contexte déjà minimal)'); return; }
|
||||
if(d.ctx_used!==undefined){ setCtxUsed(d.ctx_used); return; }
|
||||
if(d.stats){ T.serverStats=d.stats;
|
||||
if(d.stats){ T.serverStats=d.stats; feedConvSpeed(d.stats);
|
||||
// prompt_tokens_total = comptage exact du moteur (include_usage). Absent sur
|
||||
// certains llama-server récents : le serveur publie alors un `ctx_used`
|
||||
// estimé en fin de tour, traité plus bas — on ne remet donc PAS la jauge à
|
||||
|
||||
@@ -368,6 +368,11 @@ function renderToolMsg(el, tu){
|
||||
body.appendChild(wait);
|
||||
}
|
||||
addCopyButtons(body); scrollMaybe();
|
||||
// Carte à hauteur bornée : tant que l'outil TRAVAILLE, on suit le bas (la
|
||||
// ligne en cours d'écriture, la commande qui se tape). Terminé, on remonte en
|
||||
// tête — c'est le résumé qu'on relit, pas la dernière ligne.
|
||||
const bw = el.querySelector('.bodywrap');
|
||||
if(bw){ bw.scrollTop = tu.done ? 0 : bw.scrollHeight; }
|
||||
}
|
||||
// Inject a "copier" button into every <pre> code block (idempotent).
|
||||
function addCopyButtons(root){
|
||||
|
||||
@@ -89,9 +89,28 @@ function paintTurnClock(){
|
||||
}
|
||||
const ms = (T.doneTs || nowServer()) - T.startTs;
|
||||
if(!(ms > 0)){ el.remove(); return; }
|
||||
el.textContent = (T.doneTs ? 'tour terminé en ' : 'en cours — ') + fmtDur(ms);
|
||||
// Vitesse moyenne de la CONVERSATION à côté du temps du tour : le débit
|
||||
// typique de ce modèle sur ce fil, toutes réponses confondues.
|
||||
const avg = (CONV_MS > 1000 && CONV_TOK > 20)
|
||||
? ' · moy ' + (CONV_TOK / (CONV_MS/1000)).toFixed(1) + ' tok/s' : '';
|
||||
el.textContent = (T.doneTs ? 'tour terminé en ' : 'en cours — ') + fmtDur(ms) + avg;
|
||||
el.classList.toggle('done', !!T.doneTs);
|
||||
}
|
||||
// --- Vitesse moyenne de la conversation --------------------------------------
|
||||
// Alimentée par les événements `stats` (journalisés, donc rejoués au
|
||||
// chargement : la moyenne survit au refresh). gen_tokens / gen_ms sont
|
||||
// CUMULATIFS au sein d'une complétion et retombent à la suivante : on n'ajoute
|
||||
// que le delta, et une valeur qui recule signale une nouvelle complétion.
|
||||
let CONV_TOK = 0, CONV_MS = 0, _cgTok = 0, _cgMs = 0;
|
||||
function feedConvSpeed(s){
|
||||
const gt = s.gen_tokens || 0, gms = s.gen_ms || 0;
|
||||
if(!gt) return;
|
||||
if(gt < _cgTok){ _cgTok = 0; _cgMs = 0; }
|
||||
CONV_TOK += gt - _cgTok;
|
||||
CONV_MS += Math.max(0, gms - _cgMs);
|
||||
_cgTok = gt; _cgMs = gms;
|
||||
}
|
||||
function resetConvSpeed(){ CONV_TOK = 0; CONV_MS = 0; _cgTok = 0; _cgMs = 0; }
|
||||
// Le compteur se montre AUSSI sur l'indicateur « … » : pendant un appel d'outil
|
||||
// ou un long prefill il n'y a encore aucune réponse sous laquelle écrire, et
|
||||
// c'est précisément le moment où l'on se demande si ça avance.
|
||||
@@ -314,8 +333,9 @@ function flushRender(final){
|
||||
}
|
||||
// Carte bornée (raisonnement/outil) : si on était collé en bas, on y reste —
|
||||
// la carte suit la génération. Un défilement manuel vers le haut est respecté.
|
||||
const bw=p.el.parentElement;
|
||||
const scrollable=bw && bw.classList.contains('bodywrap');
|
||||
// ⚠️ p.el est la BULLE (.msg), pas .body : le bodywrap est dedans, pas au-dessus.
|
||||
const bw=p.el.querySelector ? p.el.querySelector(':scope > .bodywrap') : null;
|
||||
const scrollable=!!bw;
|
||||
const stick=scrollable && bw.scrollTop+bw.clientHeight>=bw.scrollHeight-60;
|
||||
const t0=performance.now();
|
||||
renderBody(p.el, text);
|
||||
@@ -397,7 +417,7 @@ function handleDelta(d){
|
||||
// serveur) : on nettoie l'écran et on resynchronise la liste, car la bascule
|
||||
// a pu être déclenchée depuis un autre appareil. Les fichiers suivent : ils
|
||||
// appartiennent à la discussion, le panneau doit changer avec elle.
|
||||
if(d.reset!==undefined){ smoothReset(); cancelRender(); stopWorkTimer(); const tc=document.getElementById('turn-clock'); if(tc) tc.remove(); PENDING=null; document.getElementById('chat').innerHTML=''; newTurn(); setCtxUsed(0); lastSeq=0; setBusy(false); if(typeof loadConversations==='function') loadConversations(); if(typeof filesOnConvChange==='function') filesOnConvChange(); if(typeof modeOnConvChange==='function') modeOnConvChange(); return; }
|
||||
if(d.reset!==undefined){ smoothReset(); cancelRender(); stopWorkTimer(); resetConvSpeed(); const tc=document.getElementById('turn-clock'); if(tc) tc.remove(); PENDING=null; document.getElementById('chat').innerHTML=''; newTurn(); setCtxUsed(0); lastSeq=0; setBusy(false); if(typeof loadConversations==='function') loadConversations(); if(typeof filesOnConvChange==='function') filesOnConvChange(); if(typeof modeOnConvChange==='function') modeOnConvChange(); return; }
|
||||
// --- Mode code (20-mode.js) -----------------------------------------------
|
||||
if(d.mode!==undefined){ if(typeof applyModeDelta==='function') applyModeDelta(d.mode); return; }
|
||||
if(d.code_hint){ if(typeof showCodeHint==='function') showCodeHint(); return; }
|
||||
@@ -445,7 +465,7 @@ function handleDelta(d){
|
||||
// une trace du fil et DOIT être rejouée.
|
||||
if(d.compact_noop){ setCompacting(false); if(!REPLAYING) toast('rien à compacter (contexte déjà minimal)'); return; }
|
||||
if(d.ctx_used!==undefined){ setCtxUsed(d.ctx_used); return; }
|
||||
if(d.stats){ T.serverStats=d.stats;
|
||||
if(d.stats){ T.serverStats=d.stats; feedConvSpeed(d.stats);
|
||||
// prompt_tokens_total = comptage exact du moteur (include_usage). Absent sur
|
||||
// certains llama-server récents : le serveur publie alors un `ctx_used`
|
||||
// estimé en fin de tour, traité plus bas — on ne remet donc PAS la jauge à
|
||||
|
||||
@@ -1743,7 +1743,7 @@ button:active:not(:disabled){transform:translateY(1px)}
|
||||
⚠️ Une seule règle pour les quatre. Il y en avait trois générations empilées
|
||||
(pilule large « Envoyer », aplat noir, aplat sauge) : chaque état tirait sa
|
||||
taille, sa forme et son alignement d'une couche différente. */
|
||||
#inputbar #attach,#inputbar #files-btn,#send,#stop{
|
||||
#inputbar #attach,#inputbar #files-btn,#inputbar #mic,#send,#stop{
|
||||
width:34px;height:34px;min-height:34px;flex:none;margin:0;padding:0;
|
||||
display:inline-flex;align-items:center;justify-content:center;align-self:center;
|
||||
border:0;border-radius:4px;background:transparent;color:var(--dim);
|
||||
@@ -1754,8 +1754,8 @@ button:active:not(:disabled){transform:translateY(1px)}
|
||||
#stop{display:none}
|
||||
html[data-busy="1"] #send{display:none}
|
||||
html[data-busy="1"] #stop{display:inline-flex}
|
||||
#inputbar #attach svg,#inputbar #files-btn svg,#send svg,#stop svg{display:block;width:18px;height:18px}
|
||||
#inputbar #attach:hover,#inputbar #files-btn:hover{background:var(--row-bg-on);color:var(--text)}
|
||||
#inputbar #attach svg,#inputbar #files-btn svg,#inputbar #mic svg,#send svg,#stop svg{display:block;width:18px;height:18px}
|
||||
#inputbar #attach:hover,#inputbar #files-btn:hover,#inputbar #mic:hover{background:var(--row-bg-on);color:var(--text)}
|
||||
html[data-files="1"] #files-btn{color:var(--accent)}
|
||||
#send{color:var(--accent)}
|
||||
#send:hover:not(:disabled){background:var(--accent-bg);color:var(--accent);opacity:1;transform:none}
|
||||
@@ -2029,8 +2029,9 @@ html[data-files="1"] #files-btn{color:var(--accent)}
|
||||
.rolebadge{display:inline-block;margin-left:6px;padding:0 6px;border:1px solid var(--accent);border-radius:3px;color:var(--accent);font-size:9px;letter-spacing:.05em;text-transform:uppercase;vertical-align:middle}
|
||||
|
||||
/* Dictée : anneau rouge pulsé pendant l'enregistrement, estompé pendant la
|
||||
transcription. Le bouton garde le gabarit de ses voisins (joindre, fichiers). */
|
||||
#mic.rec{color:var(--err);border-color:var(--err);animation:micpulse 1.2s ease-in-out infinite}
|
||||
transcription. Le gabarit (34 px, fond au survol) vient de la règle commune
|
||||
des boutons de saisie — #mic y est listé avec #attach et #files-btn. */
|
||||
#mic.rec{color:var(--err);animation:micpulse 1.2s ease-in-out infinite}
|
||||
@keyframes micpulse{50%{box-shadow:0 0 0 5px color-mix(in srgb,var(--err) 25%,transparent)}}
|
||||
#mic.busy{opacity:.55}
|
||||
|
||||
|
||||
Reference in new issue
Block a user