Live AI provider/model switching without restart
Backend: - RuntimeAIConfig singleton: mutable in-memory overrides for provider/model - PATCH /admin/api/ai/config: change active provider/model on the fly - GET /admin/api/ai/config: read current runtime state - ai_vision.py: uses runtime_ai_config before falling back to settings - health/status endpoints reflect runtime overrides Frontend: - 'Définir comme actif' button in AI config section - Live feedback message on save
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { apiGet, apiPost } from './client';
|
||||
import { apiGet, apiPost, apiPatch } from './client';
|
||||
import type { AIStatus, TestModelRequest, TestModelResponse } from '@/types/ai.types';
|
||||
|
||||
export async function fetchAIStatus(): Promise<AIStatus> {
|
||||
@@ -8,3 +8,24 @@ export async function fetchAIStatus(): Promise<AIStatus> {
|
||||
export async function testModel(body: TestModelRequest): Promise<TestModelResponse> {
|
||||
return apiPost<TestModelResponse>('/admin/api/ai/test', body);
|
||||
}
|
||||
|
||||
export interface UpdateAIConfigRequest {
|
||||
provider?: string;
|
||||
model?: string;
|
||||
reset?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateAIConfigResponse {
|
||||
provider: string;
|
||||
model: string;
|
||||
is_overridden: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export async function updateAIConfig(body: UpdateAIConfigRequest): Promise<UpdateAIConfigResponse> {
|
||||
return apiPatch<UpdateAIConfigResponse>('/admin/api/ai/config', body);
|
||||
}
|
||||
|
||||
export async function fetchRuntimeConfig(): Promise<UpdateAIConfigResponse> {
|
||||
return apiGet<UpdateAIConfigResponse>('/admin/api/ai/config');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetchAIStatus, testModel } from '@/api/ai';
|
||||
import { fetchAIStatus, testModel, updateAIConfig } from '@/api/ai';
|
||||
import type { TestModelResponse } from '@/types/ai.types';
|
||||
import { Bot, Zap, AlertTriangle, CheckCircle, Loader2, RefreshCw, Cpu, ChevronDown } from 'lucide-react';
|
||||
import { clsx } from 'clsx';
|
||||
@@ -18,6 +18,8 @@ export function AIConfigSection() {
|
||||
const [testResult, setTestResult] = useState<TestModelResponse | null>(null);
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
const [testError, setTestError] = useState<string | null>(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveMessage, setSaveMessage] = useState<string | null>(null);
|
||||
|
||||
// Sync dropdowns with current config when data loads
|
||||
useEffect(() => {
|
||||
@@ -64,6 +66,26 @@ export function AIConfigSection() {
|
||||
}
|
||||
}, [selectedProvider, selectedModel]);
|
||||
|
||||
const handleSetActive = useCallback(async () => {
|
||||
if (!selectedProvider || !selectedModel) return;
|
||||
|
||||
setIsSaving(true);
|
||||
setSaveMessage(null);
|
||||
try {
|
||||
const result = await updateAIConfig({
|
||||
provider: selectedProvider,
|
||||
model: selectedModel,
|
||||
});
|
||||
setSaveMessage(result.message);
|
||||
// Auto-clear message after 4s
|
||||
setTimeout(() => setSaveMessage(null), 4000);
|
||||
} catch (err: any) {
|
||||
setSaveMessage(err?.message || 'Erreur lors de la mise à jour');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [selectedProvider, selectedModel]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<section className="card mb-6">
|
||||
@@ -240,6 +262,30 @@ export function AIConfigSection() {
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleSetActive}
|
||||
disabled={isSaving || !selectedProvider || !selectedModel}
|
||||
className="btn-secondary flex items-center gap-2 text-sm disabled:opacity-50 ml-3"
|
||||
>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
Application...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle size={14} />
|
||||
Définir comme actif
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{saveMessage && (
|
||||
<span className="ml-3 text-sm text-status-ok animate-fade-in">
|
||||
{saveMessage}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Test result — live, no refresh needed */}
|
||||
{(testResult || testError || isTesting) && (
|
||||
<div className={clsx(
|
||||
|
||||
Reference in New Issue
Block a user