mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-16 03:39:29 +02:00
Le projet Stitch porte deux planches que je n'avais pas vues — « Official
Palette for Loki AI v1 » et « Official Dark Mode Palette for Loki AI ». Leurs
couleurs NOMMÉES font désormais foi, et elles corrigent le relevé précédent, que
la compression JPEG des captures avait décalé :
clair Main BG #F5F6F8 · Sidebar BG #ECECEC · Primary Accent #95A899
Typography #333333
sombre Deep Navy #0F172A · Slate Surface #1E293B · Sage Accent #84A98C
Le thème sombre retrouve donc exactement les deux teintes du cahier des charges,
que mon relevé sur capture avait fait dériver vers #1E2834/#253141. Les surfaces
non nommées (carte de réponse, carte de saisie, piste de jauge, bulle sombre)
restent relevées sur les écrans de la maquette, en pleine résolution cette fois.
Géométrie, elle aussi mesurée sur la maquette :
- Bulles et carte de réponse : rayon 10 px (contre 16/18 et un coin rentré), la
carte sans cadre — c'est l'ombre qui la détache.
- Carte de saisie : rayon 14 px et CADRE SAUGE, le seul contour coloré de
l'écran. Il retombait au gris commun, une règle de coque plus bas dans la
feuille reprenant la main sur la couleur.
- Liste des discussions : lignes pleine largeur séparées d'un filet, bande plate
pour l'active — la maquette n'a ni pastille arrondie ni gouttière.
- Moniteur machine : titre en casse normale (le monospace capitales de la charte
précédente y criait), libellé + pourcentage à la même échelle, jauge de 7 px
entièrement arrondie. La sous-ligne chiffrée passe en infobulle et le bouton
de repli disparaît : la maquette n'en a pas.
- Une jauge de VRAM par carte, en plus de la charge : sur une machine à une
seule carte on retombe sur les trois jauges de la maquette (GPU, VRAM, RAM),
et pour du LLM c'est la VRAM qui décide si un modèle tient.
- Échelle typographique des planches : corps 16 px dans le fil et la saisie,
secondaire 14 px medium dans la barre latérale.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd
116 lines
5.3 KiB
JavaScript
116 lines
5.3 KiB
JavaScript
// ─── Coque de l'interface ────────────────────────────────────────────────────
|
|
// Les commandes de la refonte : escamoter la barre latérale, replier le
|
|
// moniteur machine, basculer le thème depuis l'en-tête, changer de modèle sans
|
|
// ouvrir les réglages.
|
|
//
|
|
// Chaque état vit sur <html> (data-side, data-perf, data-theme) et dans
|
|
// localStorage, et il est REPOSÉ AVANT LE RENDU par le script du <head> : sans
|
|
// ça, la barre latérale s'afficherait puis se refermerait sous les yeux à chaque
|
|
// chargement de page.
|
|
|
|
// Escamotage de la barre latérale (grand écran). Sur téléphone elle est déjà un
|
|
// tiroir — c'est toggleSide() qui s'en charge, et le bouton ☰ qui l'ouvre.
|
|
function toggleSideCollapse(){
|
|
if(window.innerWidth <= 720){ toggleSide(); return; }
|
|
const on = document.documentElement.getAttribute('data-side') !== '0';
|
|
document.documentElement.setAttribute('data-side', on ? '0' : '1');
|
|
try{ localStorage.setItem('loki-side', on ? '0' : '1'); }catch(_){}
|
|
// La largeur du fil change sans qu'aucun `resize` ne soit émis : la gouttière
|
|
// de barre de défilement mesurée dans --sbw resterait celle d'avant, et la
|
|
// carte de saisie serait décalée du fil.
|
|
if(typeof syncGutter === 'function') requestAnimationFrame(syncGutter);
|
|
}
|
|
|
|
// Bascule clair/sombre depuis l'en-tête. Elle passe par applyTheme + savePrefs
|
|
// (01-theme.js) pour rester alignée avec l'interrupteur des réglages et avec le
|
|
// serveur — deux chemins vers le même réglage, une seule source de vérité.
|
|
function toggleThemeQuick(){
|
|
const dark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
applyTheme(dark ? 'light' : 'dark');
|
|
if(typeof savePrefs === 'function') savePrefs();
|
|
}
|
|
|
|
// Déplie un <details> ET tous ceux qui le contiennent. Depuis que les réglages
|
|
// vivent sous un repli unique, ouvrir une section par programme (journal du
|
|
// moteur, installation llama.cpp) ne suffit plus : elle s'ouvrait à l'intérieur
|
|
// d'un parent fermé, donc invisible.
|
|
function openDetails(el){
|
|
if(typeof el === 'string') el = document.getElementById(el);
|
|
while(el){
|
|
if(el.tagName === 'DETAILS') el.open = true;
|
|
el = el.parentElement;
|
|
}
|
|
}
|
|
|
|
// Sélecteur de modèle de l'en-tête. Il liste les presets ; en choisir un revient
|
|
// à cliquer la ligne correspondante dans les réglages (switchTo demande
|
|
// confirmation puis relance le moteur).
|
|
function onModelSwitch(sel){
|
|
const n = parseInt(sel.value, 10);
|
|
if(!n) return;
|
|
const name = sel.options[sel.selectedIndex].textContent;
|
|
// La liste est repeinte par loadPresets() une fois la bascule faite (ou
|
|
// annulée) : on ne touche pas à la sélection ici, sinon l'affichage mentirait
|
|
// pendant les quelques secondes du redémarrage.
|
|
switchTo(n, name);
|
|
}
|
|
|
|
// Le sélecteur affiche LE MODÈLE CHARGÉ, pas seulement le preset actif.
|
|
//
|
|
// Les deux ne coïncident pas toujours : un preset n'est « actif » que si son
|
|
// empreinte correspond exactement à la configuration courante. Une configuration
|
|
// écrite à la main, héritée d'une image, ou modifiée d'un cran depuis le dernier
|
|
// preset ne correspond à AUCUN d'entre eux — et l'en-tête n'affichait alors rien
|
|
// alors qu'un modèle était bel et bien chargé. On retombe donc sur le nom du
|
|
// fichier de la configuration active.
|
|
//
|
|
// Deux sources, deux moments : loadPresets() donne la liste, loadCfg() donne le
|
|
// modèle. Chacune dépose ce qu'elle sait et redemande le dessin — elles partent
|
|
// en parallèle (loadAll), aucun ordre n'est garanti.
|
|
let MS_PRESETS = [], MS_ACTIVE = null, MS_MODEL = '';
|
|
|
|
function renderModelSwitch(presets, active){
|
|
MS_PRESETS = presets || []; MS_ACTIVE = active || null;
|
|
paintModelSwitch();
|
|
}
|
|
// Appelé par loadCfg avec la valeur MODEL de la configuration active.
|
|
function setLoadedModel(name){
|
|
MS_MODEL = (name || '').trim();
|
|
paintModelSwitch();
|
|
}
|
|
// Nom lisible d'un fichier de modèle : sans dossier ni extension. Un .gguf
|
|
// s'appelle « Qwen3-14B-Instruct-Q4_K_M.gguf » — le chemin et le suffixe
|
|
// n'apprennent rien et mangent toute la place.
|
|
function modelLabel(path){
|
|
const base = path.split(/[\\/]/).pop();
|
|
return base.replace(/\.gguf$/i, '');
|
|
}
|
|
function paintModelSwitch(){
|
|
const sel = document.getElementById('model-switch');
|
|
if(!sel) return;
|
|
const prev = sel.value;
|
|
sel.textContent = '';
|
|
// Ligne de tête : ce qui est RÉELLEMENT chargé, quand aucun preset ne le
|
|
// revendique. Value vide → onModelSwitch l'ignore, elle ne bascule rien.
|
|
if(!MS_ACTIVE){
|
|
const o = document.createElement('option');
|
|
o.value = '';
|
|
o.textContent = MS_MODEL ? modelLabel(MS_MODEL) : (MS_PRESETS.length ? 'aucun modèle actif' : 'aucun preset');
|
|
o.selected = true;
|
|
sel.appendChild(o);
|
|
}
|
|
MS_PRESETS.forEach((p, i) => {
|
|
const o = document.createElement('option');
|
|
o.value = String(i + 1);
|
|
o.textContent = p.name;
|
|
if(MS_ACTIVE && p.id === MS_ACTIVE.id) o.selected = true;
|
|
sel.appendChild(o);
|
|
});
|
|
sel.disabled = !MS_PRESETS.length;
|
|
sel.title = MS_MODEL
|
|
? 'modèle chargé : ' + MS_MODEL + ' — changer de preset relance le moteur'
|
|
: 'aucun modèle configuré';
|
|
// Une bascule en cours ne doit pas voir sa sélection sauter sous la souris.
|
|
if(prev && [...sel.options].some(o => o.value === prev)) sel.value = prev;
|
|
}
|