mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-24 07:28:13 +02:00
Export : memes options quel que soit le format, curseur simple, etat vide
Trois choses ne tenaient pas debout dans la fenetre. Les options CHANGEAIENT selon le format : le JSON proposait un obscur « journal d'affichage » a la place des trois cases du Markdown. Personne ne pouvait deviner ce que ca recouvrait, et cocher une case n'avait pas le meme sens selon le format choisi juste au-dessus. Les trois memes options valent desormais pour les deux ; le format ne decide que du contenant. Cote JSON, le journal est filtre par les memes criteres. La taille du fichier annoncee sous le curseur ne servait a rien et justifiait a elle seule une sonde cote serveur. Supprimee, sonde comprise : le nombre d'echanges vient maintenant de /api/chat/state, qui est deja l'instantane leger de la conversation. La fenetre reprend le vocabulaire visuel de l'editeur de preset (modal-card, pe-group, pe-row, interrupteurs pe-switch) au lieu de son balisage bricole, et chaque option porte son sous-titre. Conversation vide : un simple « Rien a exporter » remplace les reglages, plutot que d'offrir de tailler un fichier qui serait vide de toute facon.
This commit is contained in:
1 parent
b7042f1127
commit
4a66b27227
8 files changed
+284
-371
No files matched your search
@@ -252,33 +252,33 @@ function renderApiKey(d){
|
||||
}
|
||||
async function loadApiKey(){ renderApiKey(await jget('/api/apikey')); }
|
||||
// --- Export de la conversation ---------------------------------------------
|
||||
// Taille d'un export, en français et avec une décimale. fmtSize convient aux
|
||||
// modèles (des Go, où l'arrondi ne se voit pas) mais pas ici : un fil de 2585
|
||||
// octets s'y affichait « 3 KB », soit une taille fausse dans le seul endroit de
|
||||
// l'interface qui prétend annoncer la taille réelle.
|
||||
function fmtExportSize(b){
|
||||
b = Number(b) || 0;
|
||||
if(b < 1024) return b + ' o';
|
||||
if(b < 1024*1024) return (b/1024).toFixed(1).replace('.', ',') + ' Ko';
|
||||
return (b/(1024*1024)).toFixed(1).replace('.', ',') + ' Mo';
|
||||
}
|
||||
// xTurnsTotal : nombre d'échanges du fil, borne haute du curseur. Connu
|
||||
// seulement après la sonde ; d'ici là le curseur est désactivé plutôt que faux.
|
||||
// xTurnsTotal : nombre d'échanges du fil, borne haute du curseur. Vient de
|
||||
// /api/chat/state ; 0 = conversation vide, et la fenêtre n'a alors rien à régler.
|
||||
let xTurnsTotal = 0;
|
||||
function openExportModal(){
|
||||
onExportFormat();
|
||||
async function openExportModal(){
|
||||
showModal('export-modal');
|
||||
probeExport(); // remplit la borne du curseur et la taille réelle
|
||||
try{
|
||||
const st = await jget('/api/chat/state');
|
||||
xTurnsTotal = (st && st.turns) || 0;
|
||||
}catch(e){ xTurnsTotal = 0; }
|
||||
// Rien à exporter : on remplace les réglages par un message, plutôt que de
|
||||
// proposer de tailler un fichier qui serait vide de toute façon.
|
||||
const vide = xTurnsTotal === 0;
|
||||
document.getElementById('x-empty').style.display = vide ? '' : 'none';
|
||||
document.getElementById('x-body').style.display = vide ? 'none' : '';
|
||||
document.getElementById('x-go').style.display = vide ? 'none' : '';
|
||||
if(vide) return;
|
||||
const el = document.getElementById('x-turns');
|
||||
el.max = String(xTurnsTotal);
|
||||
el.value = el.max; // par défaut : tout le fil
|
||||
el.disabled = xTurnsTotal < 2; // un seul échange : rien à trancher
|
||||
onExportFormat();
|
||||
}
|
||||
function closeExportModal(){ hideModal('export-modal'); }
|
||||
// Le format décide des options AFFICHÉES : les réglages Markdown n'ont aucun
|
||||
// effet sur un JSON, les montrer grisés ne ferait qu'encombrer.
|
||||
function onExportFormat(){
|
||||
const json = exportFormat() === 'json';
|
||||
document.getElementById('x-md-opts').style.display = json ? 'none' : '';
|
||||
document.getElementById('x-json-opts').style.display = json ? '' : 'none';
|
||||
onExportPreview();
|
||||
}
|
||||
// Le format ne change QUE le contenant : les options de contenu sont les mêmes
|
||||
// des deux côtés. Il n'y a donc plus rien à montrer ou cacher ici, seulement le
|
||||
// résumé à rafraîchir.
|
||||
function onExportFormat(){ onExportPreview(); }
|
||||
function exportFormat(){
|
||||
const r = document.querySelector('input[name=x-fmt]:checked');
|
||||
return r ? r.value : 'md';
|
||||
@@ -288,16 +288,11 @@ function exportFormat(){
|
||||
// simple /api/chat/export.
|
||||
function exportQuery(){
|
||||
const p = new URLSearchParams();
|
||||
const fmt = exportFormat();
|
||||
if(fmt === 'json') p.set('format','json');
|
||||
if(exportFormat() === 'json') p.set('format','json');
|
||||
const on = id => document.getElementById(id).checked;
|
||||
if(fmt === 'json'){
|
||||
if(!on('x-full')) p.set('full','0');
|
||||
} else {
|
||||
if(!on('x-reasoning')) p.set('reasoning','0');
|
||||
if(!on('x-tools')) p.set('tools','0');
|
||||
if(!on('x-results')) p.set('results','0');
|
||||
}
|
||||
if(!on('x-reasoning')) p.set('reasoning','0');
|
||||
if(!on('x-tools')) p.set('tools','0');
|
||||
if(!on('x-results')) p.set('results','0');
|
||||
const t = exportTurns();
|
||||
if(t > 0) p.set('turns', String(t));
|
||||
const q = p.toString();
|
||||
@@ -307,13 +302,10 @@ function exportQuery(){
|
||||
// « toute la conversation », donc on n'envoie rien plutôt qu'un nombre qui se
|
||||
// périmerait à l'échange suivant.
|
||||
function exportTurns(){
|
||||
const el = document.getElementById('x-turns');
|
||||
const v = parseInt(el.value, 10) || 0;
|
||||
const v = parseInt(document.getElementById('x-turns').value, 10) || 0;
|
||||
if(!xTurnsTotal || v >= xTurnsTotal) return 0;
|
||||
return v;
|
||||
}
|
||||
// Dit en une ligne ce que l'export va contenir. Sans ça on découvre en ouvrant
|
||||
// le fichier que le raisonnement qu'on venait chercher n'y est pas.
|
||||
function onExportPreview(){
|
||||
// Sortie d'outil sans bulle d'outil n'a aucun sens : la case suit.
|
||||
const tools = document.getElementById('x-tools');
|
||||
@@ -321,18 +313,13 @@ function onExportPreview(){
|
||||
results.disabled = !tools.checked;
|
||||
if(!tools.checked) results.checked = false;
|
||||
paintExportRange();
|
||||
const parts = [];
|
||||
if(exportFormat() === 'json'){
|
||||
parts.push(document.getElementById('x-full').checked ? 'messages + journal complet' : 'messages seuls');
|
||||
} else {
|
||||
const off = [];
|
||||
if(!document.getElementById('x-reasoning').checked) off.push('raisonnements');
|
||||
if(!tools.checked) off.push('outils');
|
||||
else if(!results.checked) off.push('sorties d\'outils');
|
||||
parts.push(off.length ? 'sans ' + off.join(' ni ') : 'tout le contenu');
|
||||
}
|
||||
document.getElementById('x-note').textContent = parts.join(' · ');
|
||||
probeExportSoon();
|
||||
const off = [];
|
||||
if(!document.getElementById('x-reasoning').checked) off.push('raisonnements');
|
||||
if(!tools.checked) off.push('outils');
|
||||
else if(!results.checked) off.push('sorties d\'outils');
|
||||
document.getElementById('x-note').textContent = off.length
|
||||
? 'Export sans ' + off.join(' ni ') + '.'
|
||||
: '';
|
||||
}
|
||||
// Remplit la piste du curseur et son libellé. Purement local : appelé à chaque
|
||||
// pixel de glissement, il ne doit RIEN demander au serveur.
|
||||
@@ -348,45 +335,9 @@ function paintExportRange(){
|
||||
if(fill) fill.style.width = 'calc(' + (frac*100).toFixed(2) + '% - ' + ((frac - .5) * GPU_THUMB).toFixed(2) + 'px)';
|
||||
const scope = document.getElementById('x-scope');
|
||||
if(!scope) return;
|
||||
if(!xTurnsTotal) scope.textContent = 'conversation vide';
|
||||
else if(n >= xTurnsTotal) scope.textContent = 'toute la conversation (' + xTurnsTotal + ' échange' + (xTurnsTotal>1?'s':'') + ')';
|
||||
else scope.textContent = n + ' dernier' + (n>1?'s':'') + ' échange' + (n>1?'s':'') + ' sur ' + xTurnsTotal;
|
||||
}
|
||||
// Sonde la taille RÉELLE du fichier pour les options courantes. Le serveur rend
|
||||
// l'export puis le jette : c'est peu coûteux, mais pas au point de le refaire à
|
||||
// chaque pixel du curseur, d'où le délai. Un `seq` protège du désordre : sur un
|
||||
// glissement rapide, une réponse en retard ne doit pas écraser la plus récente.
|
||||
let xProbeTimer = null, xProbeSeq = 0;
|
||||
function probeExportSoon(){
|
||||
document.getElementById('x-size').textContent = '…';
|
||||
// ⚠️ On périme TOUT DE SUITE la sonde en vol, sans attendre que la suivante
|
||||
// parte. Sans ça, une réponse partie avant le changement d'options revenait
|
||||
// écraser le « … » par la taille de l'état PRÉCÉDENT : le poids affiché
|
||||
// retardait d'un cran sur le curseur, ce qui est pire que pas de chiffre.
|
||||
xProbeSeq++;
|
||||
clearTimeout(xProbeTimer);
|
||||
xProbeTimer = setTimeout(probeExport, 250);
|
||||
}
|
||||
async function probeExport(){
|
||||
const seq = ++xProbeSeq;
|
||||
const sep = exportQuery().includes('?') ? '&' : '?';
|
||||
try{
|
||||
const r = await jget(exportQuery() + sep + 'probe=1');
|
||||
if(seq !== xProbeSeq) return; // une sonde plus récente a pris la main
|
||||
if(!r || !r.ok) throw new Error('probe');
|
||||
// La borne du curseur n'est connue qu'ici : on la pose (et on la garde en
|
||||
// butée droite) avant de repeindre.
|
||||
const el = document.getElementById('x-turns');
|
||||
const atMax = parseInt(el.value,10) >= (xTurnsTotal || 1);
|
||||
xTurnsTotal = r.turns || 0;
|
||||
el.max = String(Math.max(1, xTurnsTotal));
|
||||
el.disabled = xTurnsTotal < 2; // 0 ou 1 échange : rien à trancher
|
||||
if(atMax || parseInt(el.value,10) > xTurnsTotal) el.value = el.max;
|
||||
document.getElementById('x-size').textContent = fmtExportSize(r.size);
|
||||
paintExportRange();
|
||||
}catch(e){
|
||||
if(seq === xProbeSeq) document.getElementById('x-size').textContent = '';
|
||||
}
|
||||
scope.textContent = n >= xTurnsTotal
|
||||
? 'toute la conversation (' + xTurnsTotal + ' échange' + (xTurnsTotal>1?'s':'') + ')'
|
||||
: n + ' dernier' + (n>1?'s':'') + ' échange' + (n>1?'s':'') + ' sur ' + xTurnsTotal;
|
||||
}
|
||||
async function runExport(){
|
||||
const btn = document.getElementById('x-go');
|
||||
|
||||
Reference in new issue
Block a user