Jeton Hugging Face réglable dans l'interface

Le jeton ne vivait que dans la variable d'environnement HF_TOKEN : découvrir
depuis l'interface qu'un dépôt est verrouillé, c'était devoir éditer un
docker-compose et recréer le conteneur pour y répondre.

- Éditeur de preset → Modèle → « Jeton Hugging Face » : ligne repliée comme
  « Dossiers de modèles », qui affiche l'état (aucun / masqué / fourni par
  l'environnement). Le bandeau d'un dépôt verrouillé y mène d'un clic.
- Le jeton est VÉRIFIÉ auprès de /api/whoami-v2 avant d'être enregistré (le
  compte s'affiche) : un jeton mal collé accepté en silence rendrait le 401
  qu'on cherchait à expliquer. Il est rangé avec les secrets en base d'état, pas
  dans config.env que le changement de preset réécrit en bloc, et n'est jamais
  renvoyé en clair — seulement masqué.
- Priorité : jeton enregistré, puis HF_TOKEN. Rien d'enregistré = comportement
  d'avant à l'identique. L'enregistrement vide le cache des réponses obtenues
  sans jeton.
- Le jeton n'est envoyé qu'aux adresses Hugging Face : un lien collé vers un
  autre hébergeur n'a aucune raison de recevoir un secret.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hz1QWmvZqW3t53YC5SJBKC
This commit is contained in:
Claude committed 2026-08-20 10:32:44 +00:00
1 parent b339cced0a
commit 7f5765160d
10 files changed
+498 -16

No files matched your search

+51
View File
@@ -4,12 +4,14 @@ package loki
//
// GET /api/hf/search?q=… dépôts GGUF correspondants
// GET /api/hf/files?repo=auteur/dépôt modèles, projecteurs et drafts du dépôt
// GET|POST /api/hf/token jeton Hugging Face (dépôts verrouillés)
//
// Les deux renvoient aussi le matériel local, pour que l'interface n'ait pas à
// le demander séparément et n'affiche jamais une liste de tailles sans le
// budget en face.
import (
"encoding/json"
"net/http"
"strconv"
"strings"
@@ -80,3 +82,52 @@ func handleHFFiles(w http.ResponseWriter, r *http.Request) {
"hf_token": hfTokenSet(),
})
}
// handleHFToken lit et enregistre le jeton Hugging Face.
//
// GET → {ok, set, source:"config"|"env"|"", masked, account}
// POST {token:"hf_…"} → vérifie le jeton PUIS l'enregistre
// POST {token:""} → efface le jeton enregistré
//
// Le jeton n'est jamais renvoyé en clair : il ne sert qu'ici, personne n'a
// besoin de le recopier depuis Loki. Et il est vérifié AVANT d'être enregistré —
// un jeton mal collé qu'on accepte silencieusement redonne exactement le 401
// qu'on cherchait à expliquer.
func handleHFToken(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var req struct {
Token string `json:"token"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
sendJSON(w, 400, map[string]any{"ok": false, "error": "requête illisible"})
return
}
tok := strings.TrimSpace(req.Token)
account := ""
if tok != "" {
var err error
if account, err = hfWhoAmI(r.Context(), tok); err != nil {
sendJSON(w, 400, map[string]any{"ok": false, "error": err.Error()})
return
}
}
if err := writeHFToken(tok); err != nil {
sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
return
}
sendJSON(w, 200, hfTokenState(account))
return
}
sendJSON(w, 200, hfTokenState(""))
}
// hfTokenState décrit le jeton pour l'interface. account n'est renseigné qu'au
// retour d'un enregistrement : le vérifier à chaque affichage ferait un appel
// sortant à l'ouverture de la fenêtre des réglages.
func hfTokenState(account string) map[string]any {
src := hfTokenSource()
return map[string]any{
"ok": true, "set": src != "", "source": src,
"masked": maskHFToken(hfToken()), "account": account,
}
}