Checkpoint: Ajout de Google Gemini (gemini-2.0-flash) comme 3ème moteur IA : schéma DB mis à jour (enum aiProvider + colonne geminiApiKey dans userSettings et importSettings), llm.ts mis à jour avec routing vers l'API Google AI Studio (compatible OpenAI), fenêtre de configuration IA mise à jour avec 3 cartes (Mistral / Gemini / Manus) et champ clé API Gemini.
This commit is contained in:
@@ -677,12 +677,14 @@ export default function Settings() {
|
||||
const [sftpAutoExport, setSftpAutoExport] = useState(false);
|
||||
const [sftpRecipientFilter, setSftpRecipientFilter] = useState("");
|
||||
const [llmLogsRetentionMonths, setLlmLogsRetentionMonths] = useState(3);
|
||||
const [aiProvider, setAiProvider] = useState<"mistral" | "manus">("mistral");
|
||||
const [aiProvider, setAiProvider] = useState<"mistral" | "manus" | "gemini">("mistral");
|
||||
const [mistralApiKey, setMistralApiKey] = useState("");
|
||||
const [manusForgeApiKey, setManusForgeApiKey] = useState("");
|
||||
const [manusForgeApiUrl, setManusForgeApiUrl] = useState("");
|
||||
const [geminiApiKey, setGeminiApiKey] = useState("");
|
||||
const [showMistralKey, setShowMistralKey] = useState(false);
|
||||
const [showManusKey, setShowManusKey] = useState(false);
|
||||
const [showGeminiKey, setShowGeminiKey] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (settings) {
|
||||
@@ -706,6 +708,7 @@ export default function Settings() {
|
||||
setMistralApiKey((settings as any).mistralApiKey || "");
|
||||
setManusForgeApiKey((settings as any).manusForgeApiKey || "");
|
||||
setManusForgeApiUrl((settings as any).manusForgeApiUrl || "");
|
||||
setGeminiApiKey((settings as any).geminiApiKey || "");
|
||||
}
|
||||
}, [settings]);
|
||||
|
||||
@@ -754,6 +757,7 @@ export default function Settings() {
|
||||
mistralApiKey,
|
||||
manusForgeApiKey,
|
||||
manusForgeApiUrl,
|
||||
geminiApiKey,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -853,7 +857,7 @@ export default function Settings() {
|
||||
<CardContent className="space-y-6 pt-6">
|
||||
|
||||
{/* Provider selector */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAiProvider("mistral")}
|
||||
@@ -880,6 +884,36 @@ export default function Settings() {
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Google Gemini */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAiProvider("gemini")}
|
||||
className={`relative flex flex-col items-center gap-3 p-5 rounded-xl border-2 transition-all cursor-pointer ${
|
||||
aiProvider === "gemini"
|
||||
? "border-green-500 bg-green-50 dark:bg-green-950/20 shadow-md"
|
||||
: "border-muted hover:border-green-300 bg-background"
|
||||
}`}
|
||||
>
|
||||
{aiProvider === "gemini" && (
|
||||
<div className="absolute top-2 right-2">
|
||||
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||
</div>
|
||||
)}
|
||||
<div className="p-3 bg-green-100 dark:bg-green-900/30 rounded-xl">
|
||||
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" fill="#4285F4"/>
|
||||
<path d="M12 6l1.5 4.5H18l-3.75 2.75L15.75 18 12 15.25 8.25 18l1.5-4.75L6 10.5h4.5L12 6z" fill="white"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="font-bold text-base">Google Gemini</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">Gemini 2.0 Flash<br/>Gratuit (1500 req/j)</p>
|
||||
</div>
|
||||
{aiProvider === "gemini" && (
|
||||
<Badge className="bg-green-500 text-white text-xs">Actif</Badge>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAiProvider("manus")}
|
||||
@@ -938,6 +972,46 @@ export default function Settings() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Gemini config */}
|
||||
{aiProvider === "gemini" && (
|
||||
<div className="space-y-4 p-4 bg-green-50 dark:bg-green-950/10 rounded-xl border border-green-200 dark:border-green-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-green-700 dark:text-green-400 uppercase tracking-wide">Configuration Google Gemini</p>
|
||||
<Badge className="bg-green-100 text-green-700 border border-green-300 text-xs font-normal">Gratuit — 1 500 req/jour</Badge>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="geminiApiKey" className="font-medium">Clé API Google AI Studio</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="geminiApiKey"
|
||||
type={showGeminiKey ? "text" : "password"}
|
||||
value={geminiApiKey}
|
||||
onChange={(e) => setGeminiApiKey(e.target.value)}
|
||||
placeholder="Votre clé API Google (ex: AIzaSy...)"
|
||||
className="h-11 pr-10"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowGeminiKey(!showGeminiKey)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{showGeminiKey ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
||||
<AlertCircle className="w-3 h-3" />
|
||||
Obtenez votre clé gratuite sur{" "}
|
||||
<a href="https://aistudio.google.com/apikey" target="_blank" rel="noopener noreferrer" className="text-green-600 hover:underline">aistudio.google.com/apikey</a>.
|
||||
Laissez vide pour utiliser la variable d'environnement GEMINI_API_KEY du serveur.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-green-100 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
|
||||
<p className="text-xs text-green-800 dark:text-green-300 font-medium">Modèle utilisé : <span className="font-bold">gemini-2.0-flash</span></p>
|
||||
<p className="text-xs text-green-700 dark:text-green-400 mt-1">Analyse directe des PDF, extraction structurée JSON, compatible avec l'interface OpenAI.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Manus config */}
|
||||
{aiProvider === "manus" && (
|
||||
<div className="space-y-4 p-4 bg-blue-50 dark:bg-blue-950/10 rounded-xl border border-blue-200 dark:border-blue-800">
|
||||
@@ -993,14 +1067,14 @@ export default function Settings() {
|
||||
<div>
|
||||
<CardTitle className="text-xl">Configuration du modèle AI</CardTitle>
|
||||
<CardDescription className="mt-1">
|
||||
Paramètres du modèle d'extraction Mistral AI
|
||||
Paramètres avancés du modèle d'extraction IA
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6 pt-6">
|
||||
<div className="space-y-3">
|
||||
<Label htmlFor="llmModel" className="text-base font-semibold">Modèle Mistral</Label>
|
||||
<Label htmlFor="llmModel" className="text-base font-semibold">Modèle IA (override)</Label>
|
||||
<Input
|
||||
id="llmModel"
|
||||
value={llmModel}
|
||||
|
||||
Reference in New Issue
Block a user