mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 06:58:14 +02:00
Refonte de l'interface : direction « Sober Tech »
Reprise du design d'après la maquette fournie, sans changer d'architecture : l'UI reste du HTML/CSS/JS assemblé dans le binaire (voir plus bas). Charte - Palette ardoise + sauge en remplacement des bruns « Terre ». Deux variantes : claire par défaut (celle de la maquette) et « Deep Dark » (fond #0F172A, cartes #1E293B), à un clic depuis l'en-tête. Une variable --on-accent porte ce qui s'écrit sur les aplats de sauge, pour que le contraste tienne dans les deux variantes. - Typographie Inter (interface) + JetBrains Mono (code, chiffres, chemins), en sous-ensemble latin, embarquées comme l'étaient Bricolage et IBM Plex Mono — toujours aucune requête vers un service de polices. - Boutons sans contour, fond au survol, léger enfoncement au clic. - Coque à plat : plus de cartes flottantes séparées par une gouttière, la barre latérale est collée au bord et le fil occupe tout le reste. Coque - Barre d'en-tête : titre de la discussion + sélecteur de modèle. Changer de preset demandait d'ouvrir la barre latérale et de descendre jusqu'aux presets ; c'est désormais une liste déroulante, toujours visible. - Barre latérale en trois zones — marque, contenu défilant, moniteur machine — et escamotable d'un clic sur grand écran. Les discussions y passent au premier plan, avec une recherche textuelle (filtrage côté client : la liste est déjà chargée, sans les messages) ; les réglages descendent sous un repli unique, d'où openDetails(), qui déplie aussi les parents. - Moniteur machine en pied de colonne : une jauge par ressource (charge GPU, puis VRAM et température en détail, mémoire vive), sauge jusqu'à 75 %, ambre puis rouille quand la machine sature. Il remplace la section « Machine », qui disait la même chose en plus verbeux et en plus loin. - L'option « barre latérale escamotable » disparaît d'Apparence : elle faisait de la barre un tiroir posé SUR la conversation, là où le bouton d'en-tête l'escamote pour de bon. Deux mécanismes concurrents pour la même intention. Fil et saisie - La réponse de l'IA redevient une carte, la question un aplat de sauge : sur un fond de fil coloré, du texte nu flottait sans ancrage. - Saisie en pilule, ses outils (joindre, fichiers, envoyer) dans la barre elle-même. L'envoi est une pastille ronde à flèche ; le mot « Envoyer » reste porté par aria-label et l'infobulle. Corrections croisées - L'heure d'une discussion prenait la moitié de la ligne (flex:1 hérité de `.preset>span`), le titre était tronqué au premier mot. - Une liste déroulante posée directement dans une ligne de modale débordait sous son intitulé quand sa valeur était longue (chemin de destination). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd
This commit is contained in:
16 files changed
+1160
-255
No files matched your search
@@ -42,7 +42,9 @@ async function loadStatus(){
|
||||
function toggleSvcLog(){
|
||||
const box=document.getElementById('svc-log-box');
|
||||
if(!box) return;
|
||||
box.open = !box.open;
|
||||
// openDetails déplie aussi « Réglages » : sans lui, la section s'ouvrait à
|
||||
// l'intérieur d'un repli fermé — donc nulle part, du point de vue de l'écran.
|
||||
if(box.open) box.open = false; else openDetails(box);
|
||||
}
|
||||
async function loadSvcLog(){
|
||||
const el=document.getElementById('svc-log');
|
||||
@@ -127,30 +129,63 @@ function updateCtxMeter(){
|
||||
// puisse compacter à la demande avant que l'auto-compaction (75%) ne s'en charge.
|
||||
document.getElementById('ctx-compact').style.display = (pct>=50 && CTX_USED>0) ? 'inline-block' : 'none';
|
||||
}
|
||||
// ─── Moniteur machine (pied de barre latérale) ───────────────────────────────
|
||||
// Une jauge par ressource : nom, pourcentage, barre, puis le détail chiffré en
|
||||
// dessous. C'est la hiérarchie de la maquette — on lit la charge d'un coup
|
||||
// d'œil, et le détail (Gio, température) reste là pour qui le cherche.
|
||||
//
|
||||
// Construit EN DOM et non en innerHTML : les noms de GPU viennent du pilote,
|
||||
// donc d'ailleurs. Même règle que pour les noms de fichiers et les titres de
|
||||
// discussions.
|
||||
function perfRow(label, pct, sub, title){
|
||||
pct = Math.max(0, Math.min(100, Math.round(pct||0)));
|
||||
const row=document.createElement('div'); row.className='perf-row';
|
||||
if(title) row.title=title;
|
||||
const l=document.createElement('div'); l.className='perf-l';
|
||||
const b=document.createElement('b'); b.textContent=label;
|
||||
const v=document.createElement('span'); v.className='perf-pct'; v.textContent=pct+'%';
|
||||
l.append(b,v);
|
||||
const track=document.createElement('div'); track.className='perf-track';
|
||||
const fill=document.createElement('div');
|
||||
// Sauge jusqu'à 75 %, ambre au-delà, rouille à 90 : la couleur ne sert à rien
|
||||
// tant que la machine respire, elle doit sauter aux yeux quand elle sature.
|
||||
fill.className='perf-fill'+(pct>=90?' max':pct>=75?' hot':'');
|
||||
fill.style.width=pct+'%';
|
||||
track.appendChild(fill);
|
||||
row.append(l,track);
|
||||
if(sub){ const s=document.createElement('div'); s.className='perf-sub'; s.textContent=sub; row.appendChild(s); }
|
||||
return row;
|
||||
}
|
||||
async function loadVram(){
|
||||
const gpus=await jget('/api/vram');
|
||||
// Bloc de statistique : intitulé + valeur sur une ligne, jauge, détail dessous.
|
||||
// Même gabarit que la RAM (voir .stat dans le CSS) — le HTML libre d'avant
|
||||
// collait aux bords de la carte.
|
||||
document.getElementById('vram').innerHTML = (gpus||[]).map(g=>{
|
||||
const pct=Math.round(g.used*100/g.total);
|
||||
return '<div class="stat"><div class="stat-h"><span class="stat-n">'+g.name+'</span>'+
|
||||
'<span class="stat-v">'+(g.used/1024).toFixed(1)+' / '+(g.total/1024).toFixed(1)+' GiB</span></div>'+
|
||||
'<div class="bar"><div style="width:'+pct+'%"></div></div>'+
|
||||
'<div class="stat-s">GPU '+g.util+' % · '+g.temp+' °C</div></div>';
|
||||
}).join('') || '<div class="stat"><span class="stat-s">(pas de GPU)</span></div>';
|
||||
const box=document.getElementById('vram');
|
||||
if(!box) return;
|
||||
box.textContent='';
|
||||
if(!gpus || !gpus.length){
|
||||
const d=document.createElement('div'); d.className='perf-sub'; d.textContent='(pas de GPU détecté)';
|
||||
box.appendChild(d);
|
||||
return;
|
||||
}
|
||||
gpus.forEach((g,i)=>{
|
||||
// Le pourcentage affiché est la CHARGE du processeur graphique (ce que
|
||||
// montre la maquette) ; la VRAM, elle, passe en détail sous la barre — deux
|
||||
// mesures distinctes qu'une seule barre ne peut pas porter.
|
||||
const vram=(g.used/1024).toFixed(1)+' / '+(g.total/1024).toFixed(1)+' Gio';
|
||||
box.appendChild(perfRow(
|
||||
'GPU '+i+(gpus.length>1?'':'')+' — charge', g.util,
|
||||
vram+' · '+g.temp+' °C', g.name));
|
||||
});
|
||||
}
|
||||
async function loadRam(){
|
||||
const m=await jget('/api/ram');
|
||||
const box=document.getElementById('ram-details');
|
||||
if(!m || !m.total){ if(box) box.style.display='none'; return; }
|
||||
if(box) box.style.display='';
|
||||
const pct=Math.round(m.used*100/m.total);
|
||||
document.getElementById('ram').innerHTML =
|
||||
'<div class="stat"><div class="stat-h"><span class="stat-n">Mémoire vive</span>'+
|
||||
'<span class="stat-v">'+(m.used/1024).toFixed(1)+' / '+(m.total/1024).toFixed(1)+' GiB</span></div>'+
|
||||
'<div class="bar"><div style="width:'+pct+'%"></div></div>'+
|
||||
'<div class="stat-s">'+pct+' % utilisée</div></div>';
|
||||
if(!box) return;
|
||||
if(!m || !m.total){ box.style.display='none'; return; }
|
||||
box.style.display='';
|
||||
const host=document.getElementById('ram');
|
||||
host.textContent='';
|
||||
host.appendChild(perfRow('Mémoire vive', m.used*100/m.total,
|
||||
(m.used/1024).toFixed(1)+' / '+(m.total/1024).toFixed(1)+' Gio'));
|
||||
}
|
||||
async function loadCfg(){
|
||||
// /api/llamacpp en parallèle : il indique si le BIN de la config correspond au
|
||||
|
||||
Reference in new issue
Block a user