Checkpoint: Ajout de la section "Moteur IA" dans les paramètres : sélecteur Mistral/Manus, champs clé API avec affichage masqué, persistance en DB, migration VPS appliquée
This commit is contained in:
@@ -280,6 +280,93 @@ const normalizeResponseFormat = ({
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Résout l'URL API en tenant compte des paramètres utilisateur (DB) en priorité sur les variables d'environnement.
|
||||
*/
|
||||
function resolveApiUrlWithSettings(settings?: { aiProvider?: string | null; mistralApiKey?: string | null; manusForgeApiKey?: string | null; manusForgeApiUrl?: string | null }): string {
|
||||
const provider = settings?.aiProvider || "mistral";
|
||||
if (provider === "mistral") {
|
||||
return "https://api.mistral.ai/v1/chat/completions";
|
||||
}
|
||||
// Manus provider
|
||||
const forgeUrl = settings?.manusForgeApiUrl || ENV.forgeApiUrl || "https://forge.manus.im";
|
||||
return `${forgeUrl.replace(/\/$/, "")}/v1/chat/completions`;
|
||||
}
|
||||
|
||||
function getApiKeyWithSettings(settings?: { aiProvider?: string | null; mistralApiKey?: string | null; manusForgeApiKey?: string | null }): string {
|
||||
const provider = settings?.aiProvider || "mistral";
|
||||
if (provider === "mistral") {
|
||||
return settings?.mistralApiKey || ENV.mistralApiKey || "";
|
||||
}
|
||||
return settings?.manusForgeApiKey || ENV.forgeApiKey || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Version de invokeLLM qui accepte les paramètres utilisateur depuis la DB
|
||||
* pour choisir dynamiquement le moteur IA (Mistral ou Manus).
|
||||
*/
|
||||
export async function invokeLLMWithUserSettings(
|
||||
params: InvokeParams,
|
||||
userSettings?: { aiProvider?: string | null; mistralApiKey?: string | null; manusForgeApiKey?: string | null; manusForgeApiUrl?: string | null }
|
||||
): Promise<InvokeResult> {
|
||||
const apiKey = getApiKeyWithSettings(userSettings);
|
||||
if (!apiKey || apiKey.trim().length === 0) {
|
||||
throw new Error("Aucune clé API configurée pour le moteur IA sélectionné");
|
||||
}
|
||||
|
||||
const provider = userSettings?.aiProvider || "mistral";
|
||||
const isMistral = provider === "mistral";
|
||||
|
||||
const {
|
||||
messages,
|
||||
tools,
|
||||
toolChoice,
|
||||
tool_choice,
|
||||
outputSchema,
|
||||
output_schema,
|
||||
responseFormat,
|
||||
response_format,
|
||||
} = params;
|
||||
|
||||
const model = isMistral ? "mistral-large-latest" : "gemini-2.5-flash";
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
model,
|
||||
messages: messages.map(normalizeMessage),
|
||||
};
|
||||
|
||||
if (tools && tools.length > 0) payload.tools = tools;
|
||||
|
||||
const normalizedToolChoice = normalizeToolChoice(toolChoice || tool_choice, tools);
|
||||
if (normalizedToolChoice) payload.tool_choice = normalizedToolChoice;
|
||||
|
||||
payload.max_tokens = 32768;
|
||||
|
||||
if (!isMistral) {
|
||||
payload.thinking = { budget_tokens: 128 };
|
||||
}
|
||||
|
||||
const normalizedResponseFormat = normalizeResponseFormat({ responseFormat, response_format, outputSchema, output_schema });
|
||||
if (normalizedResponseFormat) payload.response_format = normalizedResponseFormat;
|
||||
|
||||
const apiUrl = resolveApiUrlWithSettings(userSettings);
|
||||
const response = await fetch(apiUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`LLM invoke failed: ${response.status} ${response.statusText} – ${errorText}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as InvokeResult;
|
||||
}
|
||||
|
||||
export async function invokeLLM(params: InvokeParams): Promise<InvokeResult> {
|
||||
assertApiKey();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user