mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-21 05:58:13 +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
@@ -0,0 +1,105 @@
|
||||
// ─── 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);
|
||||
}
|
||||
|
||||
// Moniteur machine replié : le pied de barre latérale ne garde que son titre.
|
||||
function togglePerf(){
|
||||
const on = document.documentElement.getAttribute('data-perf') !== '0';
|
||||
document.documentElement.setAttribute('data-perf', on ? '0' : '1');
|
||||
try{ localStorage.setItem('loki-perf', on ? '0' : '1'); }catch(_){}
|
||||
const b = document.querySelector('#side-perf .perf-toggle');
|
||||
if(b){
|
||||
b.textContent = on ? '+' : '–';
|
||||
b.title = on ? 'déplier le moniteur' : 'replier le moniteur';
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Remplit le sélecteur à partir de la liste de presets déjà chargée par
|
||||
// loadPresets() — aucun appel réseau supplémentaire.
|
||||
function renderModelSwitch(presets, active){
|
||||
const sel = document.getElementById('model-switch');
|
||||
if(!sel) return;
|
||||
sel.textContent = '';
|
||||
if(!presets || !presets.length){
|
||||
const o = document.createElement('option');
|
||||
o.textContent = 'aucun preset'; o.value = '';
|
||||
sel.appendChild(o); sel.disabled = true;
|
||||
return;
|
||||
}
|
||||
sel.disabled = false;
|
||||
presets.forEach((p, i) => {
|
||||
const o = document.createElement('option');
|
||||
o.value = String(i + 1);
|
||||
o.textContent = p.name;
|
||||
if(active && p.id === active.id) o.selected = true;
|
||||
sel.appendChild(o);
|
||||
});
|
||||
// Aucun preset actif (configuration modifiée à la main) : on le dit, plutôt
|
||||
// que de laisser le premier de la liste passer pour le modèle chargé.
|
||||
if(!active){
|
||||
const o = document.createElement('option');
|
||||
o.value = ''; o.textContent = 'configuration hors preset'; o.selected = true;
|
||||
sel.insertBefore(o, sel.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// État initial des libellés (le CSS, lui, est déjà appliqué par le <head>).
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if(document.documentElement.getAttribute('data-perf') === '0'){
|
||||
const b = document.querySelector('#side-perf .perf-toggle');
|
||||
if(b){ b.textContent = '+'; b.title = 'déplier le moniteur'; }
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user