feat: implement database and AI provider configuration settings with Google GenAI integration

This commit is contained in:
Michael committed 2026-04-10 15:00:47 +02:00
1 parent 7372885e48
commit d0ca0c3602
7 files changed
+639 -108

No files matched your search

+37 -5
View File
@@ -145,9 +145,18 @@ function MessageBubble({ msg }: { msg: Message }) {
);
}
const GOOGLE_MODELS: ModelInfo[] = [
{ id: "gemini-2.5-pro-preview-05-06", name: "Gemini 2.5 Pro Preview", free: false, promptPrice: 0, completionPrice: 0 },
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash", free: false, promptPrice: 0, completionPrice: 0 },
{ id: "gemini-2.0-flash-thinking-exp", name: "Gemini 2.0 Flash Thinking", free: false, promptPrice: 0, completionPrice: 0 },
{ id: "gemini-1.5-pro", name: "Gemini 1.5 Pro", free: false, promptPrice: 0, completionPrice: 0 },
{ id: "gemini-1.5-flash", name: "Gemini 1.5 Flash", free: false, promptPrice: 0, completionPrice: 0 },
];
export default function AdminAiChatPage() {
const [models, setModels] = useState<ModelInfo[]>([]);
const [selectedModel, setSelectedModel] = useState<string>(DEFAULT_MODEL);
const [aiProvider, setAiProviderState] = useState<"openrouter" | "google">("openrouter");
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
@@ -161,15 +170,31 @@ export default function AdminAiChatPage() {
const selectedModelInfo = models.find(m => m.id === selectedModel) ?? null;
// Fetch models
// Fetch config + models
useEffect(() => {
setLoadingModels(true);
fetch("/api/openrouter/models")
fetch("/api/admin/ai-config")
.then(r => r.json())
.then(d => {
if (d.models) setModels(d.models);
const provider: "openrouter" | "google" = d.provider ?? "openrouter";
setAiProviderState(provider);
if (provider === "google") {
setModels(GOOGLE_MODELS);
setSelectedModel(d.googleAiModel ?? "gemini-2.0-flash");
} else {
fetch("/api/openrouter/models")
.then(r => r.json())
.then(od => { if (od.models) setModels(od.models); })
.catch(() => { });
if (d.openRouterModel) setSelectedModel(d.openRouterModel);
}
})
.catch(() => {
fetch("/api/openrouter/models")
.then(r => r.json())
.then(od => { if (od.models) setModels(od.models); })
.catch(() => { });
})
.catch(() => { })
.finally(() => setLoadingModels(false));
}, []);
@@ -335,7 +360,14 @@ export default function AdminAiChatPage() {
{/* Model selector */}
<div className="mb-4 shrink-0">
<div className="flex items-center gap-3 p-3 rounded-xl bg-[var(--bg-elevated)] border border-[var(--border)]">
<span className="text-xs font-medium text-[var(--text-muted)] shrink-0">Modèle</span>
<span className={cn(
"text-[10px] font-semibold px-1.5 py-0.5 rounded shrink-0",
aiProvider === "google"
? "bg-blue-500/10 text-blue-500"
: "bg-[var(--accent-bg)] text-[var(--accent)]"
)}>
{aiProvider === "google" ? "Google AI" : "OpenRouter"}
</span>
{loadingModels ? (
<div className="flex items-center gap-2 text-xs text-[var(--text-muted)]">
<Loader2 className="w-3.5 h-3.5 animate-spin" />
+69 -1
View File
@@ -121,6 +121,12 @@ export default function SettingsPage() {
const [selectedModel, setSelectedModel] = useState("google/gemini-2.0-flash-001");
const [modelsStatus, setModelsStatus] = useState<"idle" | "loading" | "ok" | "error">("idle");
// AI Provider
const [aiProvider, setAiProvider] = useState<"openrouter" | "google">("openrouter");
const [googleAiKey, setGoogleAiKey] = useState("");
const [showGoogleKey, setShowGoogleKey] = useState(false);
const [googleAiModel, setGoogleAiModel] = useState("gemini-2.5-pro-preview-05-06");
const [isMounted, setIsMounted] = useState(false);
const {
@@ -171,6 +177,9 @@ export default function SettingsPage() {
if (config.openRouterModel) {
setSelectedModel(config.openRouterModel);
}
if (config.aiProvider) setAiProvider(config.aiProvider);
if (config.googleAiKey) setGoogleAiKey(config.googleAiKey);
if (config.googleAiModel) setGoogleAiModel(config.googleAiModel);
}
}, [fetchModels, setApiKey, setDatabase, setHost, setPassword, setPort, setSelectedModel, setSsl, setUser]);
@@ -195,7 +204,7 @@ export default function SettingsPage() {
const handleSave = async () => {
setSaveStatus("saving");
const url = getDatabaseUrl();
const res = await saveDatabaseSettings(url, apiKey, selectedModel);
const res = await saveDatabaseSettings(url, apiKey, selectedModel, aiProvider, googleAiKey, googleAiModel);
if (res.success) {
setSaveStatus("saved");
setTimeout(() => setSaveStatus("idle"), 2500);
@@ -308,6 +317,65 @@ export default function SettingsPage() {
{/* API FF Nancy */}
<FfApiStatusSection />
{/* AI Provider selector */}
<Section title="Admin AI Chat — Fournisseur" subtitle="Choisissez le fournisseur IA utilisé dans l'onglet Admin AI Chat">
<div className="flex gap-2">
<button
onClick={() => setAiProvider("openrouter")}
className={`flex-1 py-2.5 px-4 rounded-lg border text-[13px] font-medium transition-colors ${aiProvider === "openrouter" ? "border-[var(--accent)] bg-[var(--accent-bg)] text-[var(--accent)]" : "border-[var(--border)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)]"}`}
>
OpenRouter
</button>
<button
onClick={() => setAiProvider("google")}
className={`flex-1 py-2.5 px-4 rounded-lg border text-[13px] font-medium transition-colors ${aiProvider === "google" ? "border-[var(--accent)] bg-[var(--accent-bg)] text-[var(--accent)]" : "border-[var(--border)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)]"}`}
>
Google AI Studio
</button>
</div>
{aiProvider === "google" && (
<>
<Field label="Clé API Google AI Studio" hint="Disponible sur aistudio.google.com/apikey — commence par AIza...">
<div className="relative">
<input
type={showGoogleKey ? "text" : "password"}
placeholder="AIzaSy..."
value={googleAiKey}
onChange={(e) => setGoogleAiKey(e.target.value)}
className="apple-input font-mono pr-10"
/>
<button
onClick={() => setShowGoogleKey((v) => !v)}
className="absolute right-3 top-1/2 -translate-y-1/2 opacity-50 hover:opacity-100 transition-opacity"
>
{showGoogleKey ? <EyeOff className="w-4 h-4" style={{ color: "var(--text-primary)" }} /> : <Eye className="w-4 h-4" style={{ color: "var(--text-primary)" }} />}
</button>
</div>
</Field>
<Field label="Modèle Google" hint="ex: gemini-2.5-pro-preview-05-06, gemini-2.0-flash, gemini-1.5-pro">
<select
value={googleAiModel}
onChange={(e) => setGoogleAiModel(e.target.value)}
className="apple-input"
>
<option value="gemini-2.5-pro-preview-05-06">Gemini 2.5 Pro Preview</option>
<option value="gemini-2.0-flash">Gemini 2.0 Flash</option>
<option value="gemini-2.0-flash-thinking-exp">Gemini 2.0 Flash Thinking</option>
<option value="gemini-1.5-pro">Gemini 1.5 Pro</option>
<option value="gemini-1.5-flash">Gemini 1.5 Flash</option>
</select>
</Field>
</>
)}
{aiProvider === "openrouter" && (
<p className="text-[12px] text-[var(--text-muted)]">
Utilise la clé OpenRouter configurée ci-dessous et le modèle sélectionné.
</p>
)}
</Section>
{/* OpenRouter */}
<Section title="IA Copilot — OpenRouter" subtitle="Clé API pour les analyses de gammes par intelligence artificielle">
<Field label="Clé API" hint="Disponible sur openrouter.ai/keys — commence par sk-or-...">