Presets : glisser-déposer, pastilles contexte et capacités, prompt système dans l'éditeur

Repris d'AJEAN et adapté à Loki :
- liste réordonnable par glisser-déposer (SortableJS 1.15.6, MIT), ordre
  gardé côté serveur (/api/presets/order) et suivi par le sélecteur de
  l'en-tête ; le clic de fin de glissement ne bascule pas de preset
- pastille de contexte (32K, 128K, 40K…) et pastille « capacités » : œil
  quand le preset lit les images (MMPROJ, API externe déclarée multimodale,
  Strata), ampoule + niveau quand il raisonne
- éditeur : prompt système DE CE preset (rangé en base, sysprompt:<id>),
  projecteur vision sur le CPU (--no-mmproj-offload, visible seulement avec
  un mmproj)
- les projecteurs mmproj ne sont plus proposés comme modèle (éditeur et
  sélecteur de l'en-tête)
- l'aide « ? » reste survolable sur un réglage grisé

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-07 09:36:59 +02:00
1 parent 127013b32b
commit ecff7de5a3
11 files changed
+412 -27

No files matched your search

+63 -1
View File
@@ -113,7 +113,69 @@ func ListPresets() ([]Preset, error) {
})
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out, nil
return applyPresetOrder(out), nil
}
// presetOrderKey : ordre d'affichage choisi par glisser-déposer dans l'UI
// (repris d'AJEAN v0.10.9), une liste d'IDs persistée en base.
const presetOrderKey = "preset_order"
func loadPresetOrder() []string {
var ids []string
getJSON(bkState, presetOrderKey, &ids)
return ids
}
func savePresetOrder(ids []string) error { return putJSON(bkState, presetOrderKey, ids) }
// applyPresetOrder réordonne `list` (déjà trié par nom) selon l'ordre stocké :
// d'abord les presets classés dans l'ordre choisi, puis les non classés
// (fraîchement créés) dans leur ordre alphabétique. Tri STABLE.
func applyPresetOrder(list []Preset) []Preset {
order := loadPresetOrder()
if len(order) == 0 {
return list
}
pos := map[string]int{}
for i, id := range order {
pos[id] = i
}
sort.SliceStable(list, func(i, j int) bool {
pi, oki := pos[list[i].ID]
pj, okj := pos[list[j].ID]
if oki && okj {
return pi < pj
}
if oki != okj {
return oki // un preset classé passe avant un non classé
}
return false // deux non classés : l'ordre alphabétique reste
})
return list
}
// presetCtx : la taille de contexte d'un preset, formatée pour la pastille de
// la liste (« 32K », « 128K », « 1M »). CTX absent = 32768, le défaut de serve.
func presetCtx(content string) string {
raw := strings.TrimSpace(parseEnv(content)["CTX"])
if raw == "" {
raw = "32768"
}
n, err := strconv.Atoi(raw)
if err != nil || n <= 0 {
return ""
}
// Multiple de 1024 (32768, 131072…) : écrit comme on le pense, « 32K »,
// « 128K » ; sinon décimal, comme on le tape (40000 → « 40K »).
switch {
case n >= 1024 && n%1024 == 0 && n < 1<<20:
return strconv.Itoa(n/1024) + "K"
case n >= 1000000:
return strings.TrimSuffix(strconv.FormatFloat(float64(n)/1000000, 'f', 1, 64), ".0") + "M"
case n >= 1000:
return strconv.Itoa((n+500)/1000) + "K"
}
return strconv.Itoa(n)
}
// uniquePresetID derives a unique filename id from a display name, appending