feat(ai): selection fournisseur/modele par defaut + guide architecture
- Persistance ai_default_provider / ai_default_models dans data/config.json - Lecture + rechargement a chaud dans backend/ai.py (get_default_provider, reload_ai_config) - UI: selecteurs Fournisseur/Modele par defaut dans la section Cles API IA (i18n FR/EN) - docs/AI_ARCHITECTURE_GUIDE.md: architecture cible, catalogue d'outils, decisions MCP
This commit is contained in:
@@ -1975,6 +1975,36 @@
|
||||
<strong data-i18n="config.test">Tester</strong> pour charger les
|
||||
modeles.
|
||||
</p>
|
||||
<div class="config-row">
|
||||
<label
|
||||
class="config-label"
|
||||
for="cfg-ai-default-provider"
|
||||
data-i18n="config.ai_default_provider"
|
||||
>Fournisseur par defaut</label
|
||||
>
|
||||
<select
|
||||
id="cfg-ai-default-provider"
|
||||
class="config-select"
|
||||
style="width: 200px"
|
||||
>
|
||||
<option value="">--</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label
|
||||
class="config-label"
|
||||
for="cfg-ai-default-model"
|
||||
data-i18n="config.ai_default_model"
|
||||
>Modele par defaut</label
|
||||
>
|
||||
<select
|
||||
id="cfg-ai-default-model"
|
||||
class="config-select"
|
||||
style="width: 220px"
|
||||
>
|
||||
<option value="">--</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label
|
||||
class="config-label"
|
||||
|
||||
+73
-3
@@ -615,8 +615,19 @@ function initConfigModal() {
|
||||
if (saveAIKeysBtn) saveAIKeysBtn.addEventListener("click", saveAIKeys);
|
||||
const testAIKeysBtn = document.getElementById("cfg-test-ai-keys");
|
||||
if (testAIKeysBtn) testAIKeysBtn.addEventListener("click", testAIKeys);
|
||||
// Load existing keys on modal open
|
||||
// Default provider/model selection
|
||||
const aiDefaultProviderSel = document.getElementById("cfg-ai-default-provider");
|
||||
if (aiDefaultProviderSel) {
|
||||
aiDefaultProviderSel.addEventListener("change", async () => {
|
||||
let cfg = {};
|
||||
try { cfg = await api("/api/config"); } catch(e) {}
|
||||
const models = cfg.ai_default_models || {};
|
||||
await _populateAIModelOptions(aiDefaultProviderSel.value, models[aiDefaultProviderSel.value] || "");
|
||||
});
|
||||
}
|
||||
// Load existing keys/defaults on modal open
|
||||
document.getElementById("config-open-btn").addEventListener("click", loadAIKeys);
|
||||
document.getElementById("config-open-btn").addEventListener("click", loadAIDefaults);
|
||||
|
||||
// Profile menu button: open config modal and scroll to profile section
|
||||
var profileBtn = document.getElementById("profile-open-btn");
|
||||
@@ -1407,18 +1418,77 @@ async function loadAIKeys() {
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
async function loadAIDefaults() {
|
||||
const providerSel = document.getElementById("cfg-ai-default-provider");
|
||||
if (!providerSel) return;
|
||||
if (!providerSel.dataset.populated) {
|
||||
providerSel.innerHTML = '<option value="">--</option>';
|
||||
AI_PROVIDER_NAMES.forEach(function(p) {
|
||||
providerSel.innerHTML += '<option value="' + p + '">' + p + '</option>';
|
||||
});
|
||||
providerSel.dataset.populated = "1";
|
||||
}
|
||||
let cfg = {};
|
||||
try { cfg = await api("/api/config"); } catch(e) {}
|
||||
const provider = cfg.ai_default_provider || "deepseek";
|
||||
providerSel.value = provider;
|
||||
const models = cfg.ai_default_models || {};
|
||||
await _populateAIModelOptions(provider, models[provider] || "");
|
||||
}
|
||||
|
||||
async function _populateAIModelOptions(provider, selected) {
|
||||
const modelSel = document.getElementById("cfg-ai-default-model");
|
||||
if (!modelSel) return;
|
||||
modelSel.innerHTML = '<option value="">--</option>';
|
||||
if (!provider) return;
|
||||
try {
|
||||
const m = await api("/api/config/ai-models?provider=" + encodeURIComponent(provider));
|
||||
const list = (m && m.models) || [];
|
||||
list.forEach(function(md) {
|
||||
modelSel.innerHTML += '<option value="' + md + '">' + md + '</option>';
|
||||
});
|
||||
if (selected && list.indexOf(selected) === -1) {
|
||||
modelSel.innerHTML += '<option value="' + selected + '">' + selected + '</option>';
|
||||
}
|
||||
modelSel.value = selected || "";
|
||||
} catch(e) { console.warn("Failed to load default models for", provider, e); }
|
||||
}
|
||||
|
||||
async function saveAIKeys() {
|
||||
const keys = {};
|
||||
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();
|
||||
}
|
||||
if (!Object.keys(keys).length) { showToast(t("config.no_keys"), "warning"); return; }
|
||||
// Collect default provider + per-provider default models
|
||||
const providerSel = document.getElementById("cfg-ai-default-provider");
|
||||
const modelSel = document.getElementById("cfg-ai-default-model");
|
||||
const defaults = {};
|
||||
const models = {};
|
||||
for (const p of AI_PROVIDER_NAMES) {
|
||||
const sel = document.getElementById("cfg-" + p + "-model");
|
||||
if (sel && sel.value) models[p] = sel.value;
|
||||
}
|
||||
if (providerSel && providerSel.value) {
|
||||
defaults.ai_default_provider = providerSel.value;
|
||||
if (modelSel && modelSel.value) models[providerSel.value] = modelSel.value;
|
||||
}
|
||||
if (Object.keys(models).length) defaults.ai_default_models = models;
|
||||
|
||||
if (!Object.keys(keys).length && !Object.keys(defaults).length) {
|
||||
showToast(t("config.no_keys"), "warning"); return;
|
||||
}
|
||||
try {
|
||||
await api("/api/config/ai-keys", { method: "POST", body: JSON.stringify(keys) });
|
||||
if (Object.keys(keys).length) {
|
||||
await api("/api/config/ai-keys", { method: "POST", body: JSON.stringify(keys) });
|
||||
}
|
||||
if (Object.keys(defaults).length) {
|
||||
await api("/api/config", { method: "POST", body: JSON.stringify(defaults) });
|
||||
}
|
||||
showToast(t("config.api_keys_saved"), "success");
|
||||
Object.keys(AI_KEY_MAP).forEach(id => { const el = document.getElementById(id); if (el) el.value = ""; });
|
||||
loadAIKeys();
|
||||
loadAIDefaults();
|
||||
} catch(e) { showToast("Erreur: " + e.message, "error"); }
|
||||
}
|
||||
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
"config.about_loading": "Loading...",
|
||||
"config.add_webhook": "Add",
|
||||
"config.ai_deepseek_label": "DeepSeek API Key",
|
||||
"config.ai_default_model": "Default model",
|
||||
"config.ai_default_provider": "Default provider",
|
||||
"config.ai_gemini_label": "Gemini API Key",
|
||||
"config.ai_keys": "AI API keys",
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
"config.about_loading": "Chargement...",
|
||||
"config.add_webhook": "Ajouter",
|
||||
"config.ai_deepseek_label": "DeepSeek API Key",
|
||||
"config.ai_default_model": "Modèle par défaut",
|
||||
"config.ai_default_provider": "Fournisseur par défaut",
|
||||
"config.ai_gemini_label": "Gemini API Key",
|
||||
"config.ai_keys": "Clés API IA",
|
||||
|
||||
Reference in New Issue
Block a user