mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
v0.5.3 : détection du preset actif robuste + retouches UI
- Presets : la détection du preset actif compare désormais l'ENSEMBLE des KEY=VALUE effectifs (ordre, commentaires, lignes vides et clés appareil ignorés) au lieu d'un hash ligne à ligne. Fini le preset qui n'apparaît plus en surbrillance après un reformatage de config.env (toggle mémoire/internet…). Couvert par un test unitaire. - UI : toggles « Paramètres » du mode agent dans des cartes bordées ; champs de la modale MCP correctement espacés ; section MOTEUR sans contours colorés ; liens « mettre à jour » / « vérifier la version » sans soulignement.
This commit is contained in:
1 parent
142dd87299
commit
2f1f40c604
8 files changed
+76
-24
No files matched your search
Binary file not shown.
Binary file not shown.
@@ -3,13 +3,13 @@
|
||||
"FileVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 5,
|
||||
"Patch": 2,
|
||||
"Patch": 3,
|
||||
"Build": 0
|
||||
},
|
||||
"ProductVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 5,
|
||||
"Patch": 2,
|
||||
"Patch": 3,
|
||||
"Build": 0
|
||||
},
|
||||
"FileFlagsMask": "3f",
|
||||
@@ -25,7 +25,7 @@
|
||||
"LegalCopyright": "Copyright (c) 2026 Jean contributors. MIT License.",
|
||||
"OriginalFilename": "jean.exe",
|
||||
"ProductName": "Jean",
|
||||
"ProductVersion": "0.5.2",
|
||||
"ProductVersion": "0.5.3",
|
||||
"Comments": "https://github.com/nathaninline/jean — projet open source (MIT)"
|
||||
},
|
||||
"VarFileInfo": {
|
||||
|
||||
@@ -59,27 +59,38 @@ func withDisplayName(content, name string) string {
|
||||
// assignment lines symmetrically from both sides before hashing so a plain
|
||||
// content match still identifies the active preset.
|
||||
func presetFingerprint(content []byte) string {
|
||||
var b strings.Builder
|
||||
// On réduit le fichier à l'ENSEMBLE de ses affectations KEY=VALUE effectives,
|
||||
// puis on hache cet ensemble TRIÉ. On ignore : commentaires (dont `# NAME=`),
|
||||
// lignes vides, l'ordre des lignes, et les clés « appareil » (preservedKeys,
|
||||
// réappliquées par SwitchToPreset). Ainsi un simple reformatage de config.env
|
||||
// — réécriture par un toggle mémoire/internet, réordonnancement, commentaires —
|
||||
// ne « perd » plus le preset actif : seule la config effective est comparée.
|
||||
// (Avant : hachage ligne à ligne, cassé par le moindre changement cosmétique →
|
||||
// aucun preset détecté comme actif, souvent après un changement de réglage.)
|
||||
pairs := []string{}
|
||||
for _, line := range strings.Split(string(content), "\n") {
|
||||
if isPreservedAssignment(line) {
|
||||
t := strings.TrimSpace(line)
|
||||
if t == "" || strings.HasPrefix(t, "#") {
|
||||
continue
|
||||
}
|
||||
b.WriteString(line)
|
||||
b.WriteByte('\n')
|
||||
t = strings.TrimPrefix(t, "export ")
|
||||
eq := strings.IndexByte(t, '=')
|
||||
if eq < 0 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(t[:eq])
|
||||
if isPreservedKey(key) {
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, key+"="+strings.TrimSpace(t[eq+1:]))
|
||||
}
|
||||
h := sha1.Sum([]byte(b.String()))
|
||||
sort.Strings(pairs)
|
||||
h := sha1.Sum([]byte(strings.Join(pairs, "\n")))
|
||||
return hex.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
// isPreservedAssignment reports whether a line assigns one of the preservedKeys
|
||||
// (commented or not), e.g. `MEM_MODE=off` or `# CRAWL4AI_URL=...`.
|
||||
func isPreservedAssignment(line string) bool {
|
||||
t := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "#"))
|
||||
eq := strings.IndexByte(t, '=')
|
||||
if eq < 0 {
|
||||
return false
|
||||
}
|
||||
key := strings.TrimSpace(t[:eq])
|
||||
// isPreservedKey reports whether key is one of the device-level preservedKeys.
|
||||
func isPreservedKey(key string) bool {
|
||||
for _, k := range preservedKeys {
|
||||
if key == k {
|
||||
return true
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package jean
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestPresetFingerprintIgnoresCosmetics vérifie que la détection du preset actif
|
||||
// ne dépend QUE de la config effective (ensemble KEY=VALUE), pas de la mise en
|
||||
// forme : ordre des lignes, commentaires (dont `# NAME=`), lignes vides, `export`,
|
||||
// espaces, ni des clés « appareil » (preservedKeys) réappliquées par SwitchToPreset.
|
||||
// C'est la régression « aucun preset en surbrillance après un reformatage de
|
||||
// config.env » (toggle mémoire/internet, réordonnancement…).
|
||||
func TestPresetFingerprintIgnoresCosmetics(t *testing.T) {
|
||||
base := []byte("# NAME=Mon preset\nMODEL=foo.gguf\nCTX=4096\nNGL=999\n")
|
||||
// Même config effective, présentée autrement.
|
||||
variant := []byte("export NGL=999\nMEM_MODE=off\n\nCTX=4096\n# un commentaire\nMODEL=foo.gguf\n")
|
||||
if presetFingerprint(base) != presetFingerprint(variant) {
|
||||
t.Fatal("empreintes différentes alors que la config effective est identique")
|
||||
}
|
||||
// Un vrai changement de valeur doit, lui, produire une empreinte différente.
|
||||
changed := []byte("MODEL=foo.gguf\nCTX=8192\nNGL=999\n")
|
||||
if presetFingerprint(base) == presetFingerprint(changed) {
|
||||
t.Fatal("empreintes identiques alors que CTX diffère")
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const Version = "0.5.2"
|
||||
const Version = "0.5.3"
|
||||
|
||||
// Main est le vrai main() du binaire (cmd/jean ne fait que l'appeler).
|
||||
func Main() {
|
||||
|
||||
@@ -297,15 +297,14 @@ button:disabled{opacity:.5;cursor:not-allowed}
|
||||
.lc-modes{display:flex;flex-direction:column;gap:8px}
|
||||
.lc-modes.busy{opacity:.5;pointer-events:none}
|
||||
.lc-mode{border:1px solid var(--border);border-radius:8px;padding:10px 12px;cursor:pointer;transition:border-color .12s,background .12s}
|
||||
.lc-mode:hover{border-color:var(--accent)}
|
||||
.lc-mode.installed{border-color:var(--accent)}
|
||||
.lc-mode:hover{border-color:var(--dim)}
|
||||
.lc-mode-t{font-size:13px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:6px}
|
||||
.lc-reco{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--accent);border:1px solid var(--accent);border-radius:8px;padding:1px 6px}
|
||||
.lc-mode-d{font-size:11px;color:var(--dim);margin-top:3px}
|
||||
.lc-mode-state{font-size:11px;margin-top:7px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
|
||||
.lc-mode-go{color:var(--accent)}
|
||||
.lc-mode-active-tag{color:var(--accent);font-weight:600}
|
||||
.lc-update-link{color:var(--dim);text-decoration:underline;cursor:pointer}
|
||||
.lc-update-link{color:var(--dim);text-decoration:none;cursor:pointer}
|
||||
.lc-update-link:hover{color:var(--accent)}
|
||||
/* Choix du moteur dans l'éditeur de modèle (3 options : rapide/optimisé/perso) */
|
||||
.be-opts{display:flex;flex-direction:column;gap:7px;margin:6px 0 8px}
|
||||
@@ -384,6 +383,16 @@ button:disabled{opacity:.5;cursor:not-allowed}
|
||||
.io-cap{color:var(--dim);font-size:11px;margin:0 0 3px}
|
||||
.io-input{flex:1;min-width:0;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px;font:inherit;font-size:12px}
|
||||
.io-group{margin-top:12px}
|
||||
|
||||
/* Toggles de la sous-section « Paramètres » du mode agent : chacun dans sa
|
||||
propre carte bordée et arrondie (comme les serveurs MCP), plus lisible. */
|
||||
#param-block .switchrow{border:1px solid var(--border);border-radius:8px;padding:9px 11px;background:var(--bg)}
|
||||
#param-block .switchrow+.switchrow{margin-top:8px}
|
||||
|
||||
/* Modale MCP (ajout/édition) : les champs des groupes stdio/http n'avaient aucun
|
||||
espacement vertical (le conteneur n'est pas un flex à gap) → ils se touchaient.
|
||||
On aligne leur rythme sur celui du reste du formulaire (.mform). */
|
||||
#mcp-stdio-fields,#mcp-http-fields{display:flex;flex-direction:column;gap:14px}
|
||||
</style></head><body>
|
||||
<button id="menubtn" onclick="toggleSide()">☰</button>
|
||||
<div id="backdrop" onclick="toggleSide()"></div>
|
||||
|
||||
@@ -278,15 +278,14 @@ button:disabled{opacity:.5;cursor:not-allowed}
|
||||
.lc-modes{display:flex;flex-direction:column;gap:8px}
|
||||
.lc-modes.busy{opacity:.5;pointer-events:none}
|
||||
.lc-mode{border:1px solid var(--border);border-radius:8px;padding:10px 12px;cursor:pointer;transition:border-color .12s,background .12s}
|
||||
.lc-mode:hover{border-color:var(--accent)}
|
||||
.lc-mode.installed{border-color:var(--accent)}
|
||||
.lc-mode:hover{border-color:var(--dim)}
|
||||
.lc-mode-t{font-size:13px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:6px}
|
||||
.lc-reco{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--accent);border:1px solid var(--accent);border-radius:8px;padding:1px 6px}
|
||||
.lc-mode-d{font-size:11px;color:var(--dim);margin-top:3px}
|
||||
.lc-mode-state{font-size:11px;margin-top:7px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
|
||||
.lc-mode-go{color:var(--accent)}
|
||||
.lc-mode-active-tag{color:var(--accent);font-weight:600}
|
||||
.lc-update-link{color:var(--dim);text-decoration:underline;cursor:pointer}
|
||||
.lc-update-link{color:var(--dim);text-decoration:none;cursor:pointer}
|
||||
.lc-update-link:hover{color:var(--accent)}
|
||||
/* Choix du moteur dans l'éditeur de modèle (3 options : rapide/optimisé/perso) */
|
||||
.be-opts{display:flex;flex-direction:column;gap:7px;margin:6px 0 8px}
|
||||
@@ -365,3 +364,13 @@ button:disabled{opacity:.5;cursor:not-allowed}
|
||||
.io-cap{color:var(--dim);font-size:11px;margin:0 0 3px}
|
||||
.io-input{flex:1;min-width:0;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px;font:inherit;font-size:12px}
|
||||
.io-group{margin-top:12px}
|
||||
|
||||
/* Toggles de la sous-section « Paramètres » du mode agent : chacun dans sa
|
||||
propre carte bordée et arrondie (comme les serveurs MCP), plus lisible. */
|
||||
#param-block .switchrow{border:1px solid var(--border);border-radius:8px;padding:9px 11px;background:var(--bg)}
|
||||
#param-block .switchrow+.switchrow{margin-top:8px}
|
||||
|
||||
/* Modale MCP (ajout/édition) : les champs des groupes stdio/http n'avaient aucun
|
||||
espacement vertical (le conteneur n'est pas un flex à gap) → ils se touchaient.
|
||||
On aligne leur rythme sur celui du reste du formulaire (.mform). */
|
||||
#mcp-stdio-fields,#mcp-http-fields{display:flex;flex-direction:column;gap:14px}
|
||||
Reference in new issue
Block a user