mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-23 06:58:12 +02:00
Add Google Gemini TTS as alternative to Edge TTS
- Store Gemini API key globally in appConfig (single key for all users)
- Add /api/settings/gemini GET/POST/DELETE routes for API key management
- Backend: add tts_engine parameter ("edge" or "gemini") to FFmpeg service
- Backend: add generate_tts_gemini() using Google Cloud TTS REST API
- Frontend: Settings page shows Google Gemini API key input card
- Frontend: new-reel, mobile/new-reel, remotion-video, mobile/remotion-video
pages now have Edge/Gemini engine toggle and French voice selector
- Fix tts-preview route to extract ttsVoice from req.body instead of
undefined voice variable
This commit is contained in:
1 parent
59bc56aedc
commit
72b13b3e42
12 files changed
+605
-29
No files matched your search
@@ -30,6 +30,7 @@ export default function Settings() {
|
||||
const [ffmpegApiUrl, setFfmpegApiUrl] = useState("");
|
||||
const [ffmpegApiKey, setFfmpegApiKey] = useState("");
|
||||
const [externalApiKey, setExternalApiKey] = useState("");
|
||||
const [geminiApiKey, setGeminiApiKey] = useState("");
|
||||
const { toast } = useToast();
|
||||
|
||||
const { data: session } = useQuery<{ id: string; username: string; role: string }>({
|
||||
@@ -60,6 +61,11 @@ export default function Settings() {
|
||||
enabled: isAdmin,
|
||||
});
|
||||
|
||||
const { data: geminiConfig } = useQuery({
|
||||
queryKey: ['/api/settings/gemini'],
|
||||
enabled: isAdmin,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (cloudinaryConfig) {
|
||||
setCloudName((cloudinaryConfig as any).cloudName || "");
|
||||
@@ -86,9 +92,16 @@ export default function Settings() {
|
||||
}
|
||||
}, [ffmpegConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (geminiConfig) {
|
||||
// Don't pre-fill - user must enter key manually
|
||||
}
|
||||
}, [geminiConfig]);
|
||||
|
||||
// Vérifier si une config OpenRouter existe déjà
|
||||
const hasExistingConfig = !!openrouterConfig;
|
||||
const hasExistingFfmpegConfig = !!ffmpegConfig;
|
||||
const hasExistingGeminiConfig = !!(geminiConfig as any)?.configured;
|
||||
|
||||
const saveCloudinaryMutation = useMutation({
|
||||
mutationFn: () => {
|
||||
@@ -157,6 +170,31 @@ export default function Settings() {
|
||||
},
|
||||
});
|
||||
|
||||
const saveGeminiMutation = useMutation({
|
||||
mutationFn: () => {
|
||||
const payload: any = {};
|
||||
if (geminiApiKey && geminiApiKey.trim() !== "") {
|
||||
payload.apiKey = geminiApiKey;
|
||||
}
|
||||
return apiRequest('POST', '/api/settings/gemini', payload);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/settings/gemini'] });
|
||||
toast({
|
||||
title: "Configuration sauvegardée",
|
||||
description: "Votre clé API Gemini a été enregistrée",
|
||||
});
|
||||
setGeminiApiKey("");
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: "Erreur",
|
||||
description: "Impossible de sauvegarder la configuration Gemini",
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const uploadLogoMutation = useMutation({
|
||||
mutationFn: async (file: File) => {
|
||||
const formData = new FormData();
|
||||
@@ -521,6 +559,52 @@ export default function Settings() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Google Gemini TTS */}
|
||||
{isAdmin && (
|
||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||
<CardHeader className="p-6">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Mic className="w-5 h-5" />
|
||||
Google Gemini TTS
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Configurez votre clé API Google pour utiliser Gemini TTS dans les Reels
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="geminiApiKey">Clé API Google Gemini</Label>
|
||||
<Input
|
||||
id="geminiApiKey"
|
||||
type="password"
|
||||
value={geminiApiKey}
|
||||
onChange={(e) => setGeminiApiKey(e.target.value)}
|
||||
placeholder={hasExistingGeminiConfig ? "••••••••••••••••" : "AIza..."}
|
||||
data-testid="input-gemini-api-key"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{hasExistingGeminiConfig ? (
|
||||
<span className="text-green-600 dark:text-green-400">✓ Clé API configurée</span>
|
||||
) : (
|
||||
"Obtenez une clé API sur console.cloud.google.com → APIs & Services → Credentials"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => saveGeminiMutation.mutate()}
|
||||
disabled={
|
||||
saveGeminiMutation.isPending ||
|
||||
(!hasExistingGeminiConfig && !geminiApiKey)
|
||||
}
|
||||
data-testid="button-save-gemini"
|
||||
className="w-full"
|
||||
>
|
||||
{saveGeminiMutation.isPending ? "Enregistrement..." : "Enregistrer Gemini TTS"}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
{isAdmin && (
|
||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||
|
||||
Reference in new issue
Block a user