mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
- Identité : prénom et avatars (emoji) affichés en tête des bulles, à la place des libellés « user » / « assistant ». Stockés dans les préférences SERVEUR, donc partagés entre appareils comme le thème. La liste d'emojis est servie par /api/prefs : pas de seconde copie côté client. Validation stricte — prénom nettoyé de ses caractères de contrôle et borné à 24 runes, avatar refusé s'il n'est pas dans la liste fermée. - Barre latérale : séparateur « Paramètres » qui regroupe Identité, Apparence, Accès OpenAI, Accès distant et Actions, sous les réglages d'IA (Machine, Presets, Mode agent, System prompt, Moteur). Un séparateur plutôt qu'un second niveau de repli, illisible dans une colonne aussi étroite. - Logo : 10 → 17 px, il était minuscule sur un écran dense. Le prénom vient de l'utilisateur : les libellés sont écrits en textContent, jamais en innerHTML.
126 lines
3.8 KiB
Go
126 lines
3.8 KiB
Go
package loki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Préférences d'apparence de l'UI web (thème, mode d'affichage). Stockées côté
|
|
// serveur — sur la machine Loki — pour être partagées entre tous les appareils
|
|
// qui pilotent la même instance (téléphone, laptop, accès distant ajean.link).
|
|
// Avant ça, elles ne vivaient qu'en localStorage, donc par navigateur/appareil.
|
|
|
|
var webPrefsMu sync.Mutex
|
|
|
|
// webPrefsAllowed liste les clés de préférence acceptées et, pour chacune, les
|
|
// valeurs valides. On ne stocke que ce qui est connu (pas de champ libre).
|
|
var webPrefsAllowed = map[string]map[string]bool{
|
|
"theme": {"light": true, "dark": true},
|
|
"hide_reasoning": {"0": true, "1": true},
|
|
"hide_tools": {"0": true, "1": true},
|
|
"fold_tools": {"0": true, "1": true},
|
|
"hide_side": {"0": true, "1": true},
|
|
"hide_stats": {"0": true, "1": true},
|
|
}
|
|
|
|
// avatarChoices : les emojis proposés pour toi et pour Loki. Liste FERMÉE —
|
|
// n'accepter qu'un jeu connu évite de stocker une chaîne arbitraire qui finirait
|
|
// affichée dans chaque bulle du chat.
|
|
var avatarChoices = []string{
|
|
"🙂", "😎", "🧑", "👤", "🦊", "🐱", "🐼", "🦉",
|
|
"🚀", "⚡", "🌙", "🔥", "🌿", "🎯", "🧠", "🤖",
|
|
}
|
|
|
|
// webPrefsText : préférences à valeur libre, avec un nettoyage propre à chacune.
|
|
// Renvoie la valeur retenue et si elle est acceptable.
|
|
var webPrefsText = map[string]func(string) (string, bool){
|
|
// Prénom affiché dans le chat : une ligne, sans caractères de contrôle, et
|
|
// borné en RUNES (24 « é » ne doivent pas compter double).
|
|
"user_name": func(v string) (string, bool) {
|
|
v = strings.TrimSpace(strings.Map(func(r rune) rune {
|
|
if r < 0x20 || r == 0x7f {
|
|
return -1
|
|
}
|
|
return r
|
|
}, v))
|
|
if r := []rune(v); len(r) > 24 {
|
|
v = strings.TrimSpace(string(r[:24]))
|
|
}
|
|
return v, true // vide = revenir au libellé par défaut
|
|
},
|
|
"avatar_user": avatarPref,
|
|
"avatar_ai": avatarPref,
|
|
}
|
|
|
|
// avatarPref n'accepte qu'un emoji de la liste fermée (ou le vide, qui rétablit
|
|
// le défaut).
|
|
func avatarPref(v string) (string, bool) {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
return "", true
|
|
}
|
|
for _, a := range avatarChoices {
|
|
if a == v {
|
|
return v, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// loadWebPrefs lit les préférences enregistrées (map vide si aucune).
|
|
func loadWebPrefs() map[string]string {
|
|
out := map[string]string{}
|
|
for k, v := range allKV(bkPrefs) {
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// saveWebPrefs fusionne les valeurs valides de `in` puis enregistre le tout.
|
|
func saveWebPrefs(in map[string]string) (map[string]string, error) {
|
|
webPrefsMu.Lock()
|
|
defer webPrefsMu.Unlock()
|
|
prefs := loadWebPrefs()
|
|
for k, v := range in {
|
|
if clean, ok := webPrefsText[k]; ok {
|
|
if val, good := clean(v); good {
|
|
prefs[k] = val
|
|
}
|
|
continue
|
|
}
|
|
allowed, ok := webPrefsAllowed[k]
|
|
if !ok {
|
|
continue
|
|
}
|
|
v = strings.ToLower(strings.TrimSpace(v))
|
|
if !allowed[v] {
|
|
continue
|
|
}
|
|
prefs[k] = v
|
|
}
|
|
return prefs, replaceKV(bkPrefs, prefs)
|
|
}
|
|
|
|
// handleWebPrefs expose les préférences d'apparence partagées entre appareils.
|
|
//
|
|
// GET → {ok, prefs:{theme, hide_*, fold_*}}
|
|
// POST {theme?, hide_*?, fold_*?} → fusionne puis renvoie {ok, prefs}
|
|
func handleWebPrefs(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodPost {
|
|
var req map[string]string
|
|
_ = json.NewDecoder(r.Body).Decode(&req)
|
|
prefs, err := saveWebPrefs(req)
|
|
if err != nil {
|
|
sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
|
|
return
|
|
}
|
|
sendJSON(w, 200, map[string]any{"ok": true, "prefs": prefs})
|
|
return
|
|
}
|
|
// avatars : la liste fermée est servie avec les préférences, pour que l'UI
|
|
// n'ait pas sa propre copie (deux listes divergent tôt ou tard).
|
|
sendJSON(w, 200, map[string]any{"ok": true, "prefs": loadWebPrefs(), "avatars": avatarChoices})
|
|
}
|