feat(config): badges statut + suppression par provider dans Clés API IA
CI / lint (push) Failing after 33s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
CI / security (push) Successful in 24s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s

- backend: ajout DELETE /api/config/ai-keys/{provider_env} pour supprimer une clé
- frontend: badge ✓ Configuré / Non configuré sur chaque ligne provider
- frontend: bouton × Supprimer avec confirmation sur les providers configurés
- testAIKeys met à jour les badges selon les résultats du test
This commit is contained in:
2026-09-07 15:43:24 -04:00
parent 2d70be0ae2
commit 72383b1634
2 changed files with 105 additions and 13 deletions
+86 -13
View File
@@ -1340,21 +1340,77 @@ function updateRegexPreview() {
// ── AI Keys management ──
const AI_KEY_MAP = {
"cfg-deepseek-key": "DEEPSEEK_API_KEY",
"cfg-openrouter-key": "OPENROUTER_API_KEY",
"cfg-gemini-key": "GEMINI_API_KEY",
"cfg-nvidia-key": "NVIDIA_API_KEY",
"cfg-qwencloud-key": "QWENCLOUD_API_KEY",
"cfg-xiaomi-key": "XIAOMI_API_KEY",
"cfg-mistral-key": "MISTRAL_API_KEY",
};
const AI_PROVIDER_NAMES = ["deepseek","openrouter","gemini","nvidia","qwencloud","xiaomi","mistral"];
function _ensureAIKeyUI() {
for (const [inputId] of Object.entries(AI_KEY_MAP)) {
const input = document.getElementById(inputId);
if (!input) continue;
const row = input.closest(".config-row");
if (!row || row.dataset.enhanced) continue;
row.dataset.enhanced = "1";
row.style.cssText += "display:flex;align-items:center;gap:8px;flex-wrap:wrap;";
const badge = document.createElement("span");
badge.id = inputId.replace("-key", "-badge");
badge.style.cssText = "font-size:11px;padding:2px 8px;border-radius:10px;white-space:nowrap;";
row.appendChild(badge);
const delBtn = document.createElement("button");
delBtn.type = "button";
delBtn.id = inputId.replace("-key", "-delete");
delBtn.className = "config-btn-secondary";
delBtn.style.cssText = "font-size:11px;padding:4px 10px;color:var(--danger,#e74c3c);border-color:var(--danger,#e74c3c);cursor:pointer;display:none;";
delBtn.textContent = "\u00d7 Supprimer";
delBtn.addEventListener("click", () => deleteAIKey(inputId));
row.appendChild(delBtn);
}
}
function _setAIKeyBadge(inputId, hasKey) {
const badge = document.getElementById(inputId.replace("-key", "-badge"));
const delBtn = document.getElementById(inputId.replace("-key", "-delete"));
if (badge) {
if (hasKey) {
badge.textContent = "\u2713 Configur\u00e9";
badge.style.background = "var(--success-bg, #27ae6022)";
badge.style.color = "var(--success, #27ae60)";
badge.style.border = "1px solid var(--success, #27ae60)";
} else {
badge.textContent = "Non configur\u00e9";
badge.style.background = "var(--muted-bg, #ffffff10)";
badge.style.color = "var(--text-muted, #888)";
badge.style.border = "1px solid var(--border, #444)";
}
}
if (delBtn) delBtn.style.display = hasKey ? "inline-block" : "none";
}
async function loadAIKeys() {
_ensureAIKeyUI();
try {
const data = await api("/api/config/ai-keys");
["DEEPSEEK_API_KEY","OPENROUTER_API_KEY","GEMINI_API_KEY","NVIDIA_API_KEY","QWENCLOUD_API_KEY","XIAOMI_API_KEY","MISTRAL_API_KEY"].forEach(k => {
const id = "cfg-" + k.toLowerCase().replace(/_api_key/g, "") + "-key";
const input = document.getElementById(id);
if (input && data[k]) input.placeholder = data[k];
});
for (const [inputId, envName] of Object.entries(AI_KEY_MAP)) {
const input = document.getElementById(inputId);
const val = data[envName] || "";
if (input) {
input.placeholder = val || (inputId.includes("gemini") ? "AIza..." : inputId.includes("openrouter") ? "sk-or-..." : "sk-...");
}
_setAIKeyBadge(inputId, !!val);
}
} catch(e) {}
}
async function saveAIKeys() {
const keys = {};
const map = { "cfg-deepseek-key": "DEEPSEEK_API_KEY", "cfg-openrouter-key": "OPENROUTER_API_KEY", "cfg-gemini-key": "GEMINI_API_KEY", "cfg-nvidia-key": "NVIDIA_API_KEY", "cfg-qwencloud-key": "QWENCLOUD_API_KEY", "cfg-xiaomi-key": "XIAOMI_API_KEY", "cfg-mistral-key": "MISTRAL_API_KEY" };
for (const [id, name] of Object.entries(map)) {
for (const [id, name] of Object.entries(AI_KEY_MAP)) {
const input = document.getElementById(id);
if (input && input.value.trim()) keys[name] = input.value.trim();
}
@@ -1362,8 +1418,19 @@ async function saveAIKeys() {
try {
await api("/api/config/ai-keys", { method: "POST", body: JSON.stringify(keys) });
showToast(t("config.api_keys_saved"), "success");
// Clear inputs
Object.keys(map).forEach(id => { const el = document.getElementById(id); if (el) el.value = ""; });
Object.keys(AI_KEY_MAP).forEach(id => { const el = document.getElementById(id); if (el) el.value = ""; });
loadAIKeys();
} catch(e) { showToast("Erreur: " + e.message, "error"); }
}
async function deleteAIKey(inputId) {
const envName = AI_KEY_MAP[inputId];
if (!envName) return;
const provider = envName.replace("_API_KEY", "").replace(/_/g, " ");
if (!confirm("Supprimer la cl\u00e9 API " + provider + " ?\nLe fournisseur ne sera plus disponible.")) return;
try {
await api("/api/config/ai-keys/" + envName, { method: "DELETE" });
showToast("Cl\u00e9 " + provider + " supprim\u00e9e", "success");
loadAIKeys();
} catch(e) { showToast("Erreur: " + e.message, "error"); }
}
@@ -1372,12 +1439,18 @@ async function testAIKeys() {
const status = document.getElementById("cfg-ai-status");
if (status) status.textContent = "Test en cours...";
try {
// Test connectivity
const results = await api("/api/config/ai-keys/test", { method: "POST" });
var msg = Object.entries(results).map(([k,v]) => k + ": " + v).join(" | ");
if (status) status.textContent = msg;
// Fetch models for configured providers
for (const p of ["deepseek","openrouter","gemini","nvidia","qwencloud","xiaomi","mistral"]) {
for (const p of AI_PROVIDER_NAMES) {
const inputId = "cfg-" + p + "-key";
if (results[p] === "ok") {
_setAIKeyBadge(inputId, true);
} else if (results[p] && results[p].includes("non")) {
_setAIKeyBadge(inputId, false);
}
}
for (const p of AI_PROVIDER_NAMES) {
if (results[p] === "ok") {
try {
const m = await api("/api/config/ai-models?provider=" + p);
@@ -1394,7 +1467,7 @@ async function testAIKeys() {
} catch(e) {
if (status) status.textContent = "Erreur: " + e.message;
}
}
}}
export {