fix(xiaomi): URL MiMo + header api-key + fallback polling admin SSE
CI / lint (push) Successful in 37s
CI / security (push) Successful in 32s
CI / test (push) Successful in 48s
CI / build (push) Successful in 22s
CI / e2e (push) Successful in 6m1s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s

1. **fix(xiaomi): la clé Xiaomi échouait avec 'Name or service not known'**
   - URL précédente api.xiaomi.com n'existe pas (DNS fail)
   - Vraie URL: https://api.xiaomimimo.com/v1/models
   - Xiaomi MiMo utilise un header custom 'api-key:' (PAS 'Authorization: Bearer')
   - Modèles par défaut mis à jour: mimo-v2.5-pro, mimo-v2.5, mimo-v2.5-asr, etc.
   - Le chat dans ai.py supporte maintenant un header custom via auth_header_name

2. **fix(admin): EventSource 503 → fallback polling**
   - Ajout d'un fallback: si EventSource ne reçoit jamais le premier event
     (cookie expiré, réseau bloqué, proxy timeout), on bascule sur du polling
     /api/admin/stats toutes les 5s. Le UI continue de se mettre à jour.
   - Cleanup correct du timer dans disconnectSSE

3. **fix(test_ai_models)**: 2 nouveaux tests de régression
   - test_xiaomi_uses_api_key_header_not_bearer: vérifie URL + header
   - test_xiaomi_test_endpoint_uses_real_url: vérifie /api/config/ai-keys/test
   - Patche backend.ai ET backend.main (import local)
   - Header case-insensitive (urllib normalise à 'Api-key', HTTP est insensible)

Vérifié :
- pytest : 504 passed (502 + 2 nouveaux Xiaomi)
- frontend unit : 7 passed
- validate-imports : 30 modules / 204 exports
- pane-manager JSDOM : 9/9
- ruff check backend/ : All checks passed
This commit is contained in:
2026-09-07 12:47:24 -04:00
parent 7ff9b854b6
commit d441481930
4 changed files with 205 additions and 27 deletions
+40 -21
View File
@@ -4024,16 +4024,25 @@ async def api_set_ai_keys(body: dict = Body(...), current_user=Depends(require_a
@app.post("/api/config/ai-keys/test")
async def api_test_ai_keys(current_user=Depends(require_admin)):
"""Test which AI providers are configured."""
"""Test which AI providers are configured.
Each provider has a dedicated (URL, header-name) test pair.
- Most OpenAI-compatible APIs use `Authorization: Bearer KEY`
- Xiaomi MiMo uses `api-key: KEY`
- Gemini uses a query-string key
"""
results = {}
for key_name, label, test_url, test_header in [
("DEEPSEEK_API_KEY", "deepseek", "https://api.deepseek.com/v1/models", "Authorization"),
("OPENROUTER_API_KEY", "openrouter", "https://openrouter.ai/api/v1/models", "Authorization"),
("GEMINI_API_KEY", "gemini", "https://generativelanguage.googleapis.com/v1beta/models?key={key}", None),
("NVIDIA_API_KEY", "nvidia", "https://integrate.api.nvidia.com/v1/models", "Authorization"),
("QWENCLOUD_API_KEY", "qwencloud", "https://dashscope.aliyuncs.com/compatible-mode/v1/models", "Authorization"),
("XIAOMI_API_KEY", "xiaomi", "https://api.xiaomi.com/v1/models", "Authorization"),
("MISTRAL_API_KEY", "mistral", "https://api.mistral.ai/v1/models", "Authorization"),
for key_name, label, test_url_tmpl, header_name in [
# OpenAI-compatible — Authorization: Bearer
("DEEPSEEK_API_KEY", "deepseek", "https://api.deepseek.com/v1/models", "Authorization"),
("OPENROUTER_API_KEY","openrouter", "https://openrouter.ai/api/v1/models", "Authorization"),
("NVIDIA_API_KEY", "nvidia", "https://integrate.api.nvidia.com/v1/models", "Authorization"),
("QWENCLOUD_API_KEY", "qwencloud", "https://dashscope.aliyuncs.com/compatible-mode/v1/models", "Authorization"),
("MISTRAL_API_KEY", "mistral", "https://api.mistral.ai/v1/models", "Authorization"),
# Xiaomi MiMo — dedicated api-key header (NOT Authorization: Bearer)
("XIAOMI_API_KEY", "xiaomi", "https://api.xiaomimimo.com/v1/models", "api-key"),
# Gemini — key in query string
("GEMINI_API_KEY", "gemini", "https://generativelanguage.googleapis.com/v1beta/models?key={key}", None),
]:
key = get_ai_key(key_name)
if not key:
@@ -4041,13 +4050,15 @@ async def api_test_ai_keys(current_user=Depends(require_admin)):
continue
try:
import urllib.request
if test_header:
req = urllib.request.Request(test_url, headers={test_header: "Bearer " + key})
url = test_url_tmpl.replace("{key}", key) if "{key}" in test_url_tmpl else test_url_tmpl
if header_name:
req = urllib.request.Request(url, headers={header_name: key})
else:
req = urllib.request.Request(test_url.replace("{key}", key))
req = urllib.request.Request(url)
urllib.request.urlopen(req, timeout=5)
results[label] = "ok"
except Exception as e:
# Truncate the error to keep the response small.
results[label] = "erreur: " + str(e)[:80]
return results
@@ -4093,15 +4104,19 @@ async def api_list_ai_models(provider: str = Query(...), current_user=Depends(re
elif provider == "qwencloud":
url = "https://dashscope.aliyuncs.com/compatible-mode/v1/models"
elif provider == "xiaomi":
# Xiaomi's public /v1/models endpoint is not stable — fetch if reachable,
# otherwise fall back to a curated list of mimo models.
url = "https://api.xiaomi.com/v1/models"
# Xiaomi MiMo — dedicated api-key header (NOT Authorization: Bearer).
# Endpoint: https://api.xiaomimimo.com/v1/models
url = "https://api.xiaomimimo.com/v1/models"
models = [] # parsed below with the custom header
elif provider == "mistral":
url = "https://api.mistral.ai/v1/models"
try:
if provider == "gemini":
req = urllib.request.Request(url)
elif provider == "xiaomi":
# Xiaomi MiMo uses a dedicated api-key header.
req = urllib.request.Request(url, headers={"api-key": key})
else:
req = urllib.request.Request(url, headers={"Authorization": "Bearer " + key})
@@ -4173,12 +4188,16 @@ _FALLBACK_MODELS: dict[str, list[str]] = {
"qwen-vl-plus",
],
"xiaomi": [
# Xiaomi MiMo models — the public /v1/models endpoint is unreliable,
# so we ship a known-good list as fallback.
"mimo-v2-pro",
"mimo-v2-flash",
"mimo-v2-vl",
"mimo-v2-tts",
# Xiaomi MiMo models — the public /v1/models endpoint requires the
# `api-key` custom header (NOT Authorization: Bearer), so the live
# call often fails with 401 even with the right key. We ship a
# known-good list as fallback. See https://mimo.mi.com/docs/
"mimo-v2.5-pro",
"mimo-v2.5",
"mimo-v2.5-asr",
"mimo-v2.5-tts",
"mimo-v2.5-tts-voiceclone",
"mimo-v2.5-tts-voicedesign",
],
"mistral": [
"mistral-large-latest",