mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-22 22:48:11 +02:00
UI : anneau d'activité dans la liste des discussions, temps de travail sous les réponses
Deux manques dans la présentation d'un tour : 1. Rien ne disait sur QUELLE discussion un agent était en train de travailler. La ligne concernée porte maintenant un anneau qui tourne, à gauche du titre — et l'en-tête aussi, puisque la barre latérale est escamotée sur téléphone. L'état vient de deux sources : le flux SSE (instantané) et /api/conversations, qui expose désormais `busy` pour que la page ouverte en cours de génération s'anime sans attendre le rejeu du journal. L'indicateur est posé et retiré SANS redessiner la liste : un redessin relancerait l'animation à chaque événement. 2. Le pied des réponses ne montrait que la vitesse du moteur, jamais le temps que le tour avait pris. Il porte maintenant « travail 1 min 04 s » — de la question envoyée à la fin du tour, raisonnement, appels d'outils et attentes compris. La durée avance à la seconde pendant le tour et se fige au turn_done ; elle apparaît aussi sur l'indicateur « … », là où il n'y a pas encore de réponse sous laquelle écrire. La bulle de raisonnement gagne au passage sa propre durée. La mesure est prise sur les HORODATAGES SERVEUR (événements `user` et `turn_done`) : elle est donc juste en direct comme au rejeu, où tout arrive d'un bloc côté client. Le compteur vivant se recale sur l'écart entre l'horloge du navigateur et celle du serveur, réévalué à chaque événement reçu en direct — un téléphone n'est pas à la même heure que la machine. « Masquer la vitesse de génération » ne masque plus que la vitesse : la durée n'est pas une mesure de moteur, c'est ce que la réponse a coûté. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J5UndZ9DedRXPuAoRXbmDb
This commit is contained in:
9 files changed
+488
-35
No files matched your search
@@ -14,12 +14,18 @@
|
||||
// titre, dates, nombre d'échanges — jamais les messages.
|
||||
|
||||
let CONVS = [], CONV_ACTIVE = '';
|
||||
// Vrai quand le SERVEUR dit qu'un tour tourne sur la discussion active. Sert au
|
||||
// premier affichage : le flux SSE n'a pas encore rejoué son journal, mais la
|
||||
// ligne doit déjà s'animer si un agent travaille (page rechargée en cours de
|
||||
// route, ou ouverte depuis un autre appareil).
|
||||
let CONV_BUSY = false;
|
||||
|
||||
async function loadConversations(){
|
||||
let r;
|
||||
try{ r = await jget('/api/conversations'); }catch(_){ return; }
|
||||
CONVS = r.conversations || [];
|
||||
CONV_ACTIVE = r.active || '';
|
||||
CONV_BUSY = !!r.busy;
|
||||
renderConversations();
|
||||
}
|
||||
|
||||
@@ -37,11 +43,16 @@ function renderConversations(){
|
||||
e.textContent = q ? 'aucune discussion ne correspond' : '(aucune)';
|
||||
cont.appendChild(e);
|
||||
syncTopbarTitle();
|
||||
convSyncBusy();
|
||||
return;
|
||||
}
|
||||
for(const c of list){
|
||||
const row = document.createElement('div');
|
||||
row.className = 'preset' + (c.id === CONV_ACTIVE ? ' active' : '');
|
||||
// L'identifiant est porté par la ligne : convSyncBusy retrouve ainsi CELLE
|
||||
// qui travaille sans redessiner la liste (un redessin relancerait
|
||||
// l'animation de l'anneau à chaque événement du flux).
|
||||
row.dataset.conv = c.id;
|
||||
row.onclick = () => convSwitch(c.id);
|
||||
|
||||
const info = document.createElement('div'); info.className = 'preset-info';
|
||||
@@ -74,6 +85,57 @@ function renderConversations(){
|
||||
cont.appendChild(row);
|
||||
}
|
||||
syncTopbarTitle();
|
||||
convSyncBusy();
|
||||
}
|
||||
|
||||
// --- Discussion au travail : anneau qui tourne ------------------------------
|
||||
// Une seule génération tourne à la fois, et c'est forcément sur la discussion
|
||||
// OUVERTE (le serveur n'a qu'un fil en mémoire) : l'indicateur va donc sur la
|
||||
// ligne active. Deux sources le renseignent — le flux SSE (`busy`, instantané)
|
||||
// et la liste (`CONV_BUSY`, utile avant que le rejeu ait rattrapé). Une fois le
|
||||
// rattrapage fini, l'état local fait foi : il est plus frais que la liste, qui
|
||||
// n'est rechargée qu'en fin de tour.
|
||||
function convBusyNow(){
|
||||
const live = (typeof busy !== 'undefined') && busy;
|
||||
if(typeof REPLAYING !== 'undefined' && !REPLAYING) return !!live;
|
||||
return CONV_BUSY || !!live;
|
||||
}
|
||||
|
||||
// Pose ou retire l'anneau dans un conteneur, SANS toucher au reste de son
|
||||
// contenu : réécrire l'hôte remettrait l'animation à zéro.
|
||||
function convSpinToggle(host, on, before){
|
||||
if(!host) return;
|
||||
let sp = host.querySelector(':scope > .conv-spin');
|
||||
if(on && !sp){
|
||||
sp = document.createElement('span');
|
||||
sp.className = 'conv-spin';
|
||||
sp.title = 'l\u2019agent travaille sur cette discussion\u2026';
|
||||
sp.setAttribute('role', 'img');
|
||||
sp.setAttribute('aria-label', 'génération en cours');
|
||||
sp.appendChild(icon('spinner', 14));
|
||||
if(before) host.insertBefore(sp, before); else host.appendChild(sp);
|
||||
} else if(!on && sp){
|
||||
sp.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Rafraîchit l'indicateur partout où il se montre : la ligne de la discussion
|
||||
// concernée, et l'en-tête (la barre latérale est escamotée sur téléphone — sans
|
||||
// ça, rien ne dirait que ça travaille).
|
||||
function convSyncBusy(){
|
||||
const on = convBusyNow();
|
||||
const cont = document.getElementById('conv-list');
|
||||
if(cont){
|
||||
for(const row of cont.querySelectorAll('.preset')){
|
||||
const mine = on && row.dataset.conv === CONV_ACTIVE;
|
||||
row.classList.toggle('working', mine);
|
||||
convSpinToggle(row, mine, row.firstChild);
|
||||
}
|
||||
}
|
||||
// Dans l'en-tête, l'anneau est un FRÈRE du titre : celui-ci tronque son texte
|
||||
// à l'ellipse, un enfant y serait rogné avec lui sur un titre long.
|
||||
const tb = document.getElementById('topbar-title');
|
||||
convSpinToggle(tb && tb.parentNode, on, tb);
|
||||
}
|
||||
|
||||
// Titre de l'en-tête : celui de la discussion ouverte. Il dit de quoi on parle
|
||||
|
||||
Reference in new issue
Block a user