import { useState, useEffect } from "react"; import { Settings as SettingsIcon, Bell, Key, Shield, Cloud, Brain, Image, Upload, X, Video } from "lucide-react"; import { useQuery, useMutation } from "@tanstack/react-query"; import Sidebar from "@/components/sidebar"; import TopBar from "@/components/topbar"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { ModelCombobox } from "@/components/model-combobox"; export default function Settings() { const [sidebarOpen, setSidebarOpen] = useState(false); const [notifications, setNotifications] = useState(true); const [autoPublish, setAutoPublish] = useState(true); const [cloudName, setCloudName] = useState(""); const [apiKey, setApiKey] = useState(""); const [apiSecret, setApiSecret] = useState(""); const [openrouterApiKey, setOpenrouterApiKey] = useState(""); const [openrouterModel, setOpenrouterModel] = useState("anthropic/claude-3.5-sonnet"); const [openrouterSystemPrompt, setOpenrouterSystemPrompt] = useState("Tu es un expert en marketing des réseaux sociaux. Génère 3 variations de textes engageants pour des publications Facebook et Instagram à partir des informations produit fournies. Chaque variation doit être unique, captivante et optimisée pour l'engagement."); const [logoFile, setLogoFile] = useState(null); const [logoPreview, setLogoPreview] = useState(null); const [ffmpegApiUrl, setFfmpegApiUrl] = useState(""); const [ffmpegApiKey, setFfmpegApiKey] = useState(""); const { toast } = useToast(); const { data: session } = useQuery<{ id: string; username: string; role: string }>({ queryKey: ["/api/auth/session"], retry: false, }); const isAdmin = session?.role === "admin"; const { data: cloudinaryConfig } = useQuery({ queryKey: ['/api/cloudinary/config'], }); const { data: openrouterConfig } = useQuery({ queryKey: ['/api/openrouter/config'], }); const { data: openrouterModels, isLoading: modelsLoading } = useQuery({ queryKey: ['/api/openrouter/models'], }); const { data: ffmpegConfig } = useQuery({ queryKey: ['/api/ffmpeg/config'], }); useEffect(() => { if (cloudinaryConfig) { setCloudName((cloudinaryConfig as any).cloudName || ""); setApiKey((cloudinaryConfig as any).apiKey || ""); // Set logo preview if exists if ((cloudinaryConfig as any).logoPublicId) { const cloudName = (cloudinaryConfig as any).cloudName; const logoPublicId = (cloudinaryConfig as any).logoPublicId; setLogoPreview(`https://res.cloudinary.com/${cloudName}/image/upload/${logoPublicId}`); } if ((cloudinaryConfig as any).logoPublicId) { const cloudName = (cloudinaryConfig as any).cloudName; const logoPublicId = (cloudinaryConfig as any).logoPublicId; setLogoPreview(`https://res.cloudinary.com/${cloudName}/image/upload/${logoPublicId}`); } } }, [cloudinaryConfig]); const { data: freesoundConfig } = useQuery({ queryKey: ['/api/freesound/config'], }); const [freesoundClientId, setFreesoundClientId] = useState(""); const [freesoundClientSecret, setFreesoundClientSecret] = useState(""); useEffect(() => { if (freesoundConfig) { setFreesoundClientId((freesoundConfig as any).clientId || ""); setFreesoundClientSecret((freesoundConfig as any).clientSecret || ""); // Usually masked } }, [freesoundConfig]); const hasExistingFreesoundConfig = !!freesoundConfig; const saveFreesoundMutation = useMutation({ mutationFn: () => { const payload: any = { clientId: freesoundClientId, }; if (freesoundClientSecret && freesoundClientSecret.trim() !== "" && freesoundClientSecret !== "********") { payload.clientSecret = freesoundClientSecret; } return apiRequest('POST', '/api/freesound/config', payload); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/freesound/config'] }); toast({ title: "Configuration sauvegardée", description: "Vos paramètres Freesound ont été enregistrés", }); // On garde l'affichage tel quel ou on remet masked si retourné }, onError: () => { toast({ title: "Erreur", description: "Impossible de sauvegarder la configuration Freesound", variant: "destructive", }); }, }); useEffect(() => { if (openrouterConfig) { setOpenrouterModel((openrouterConfig as any).model || "anthropic/claude-3.5-sonnet"); setOpenrouterSystemPrompt((openrouterConfig as any).systemPrompt || "Tu es un expert en marketing des réseaux sociaux. Génère 3 variations de textes engageants pour des publications Facebook et Instagram à partir des informations produit fournies. Chaque variation doit être unique, captivante et optimisée pour l'engagement."); } }, [openrouterConfig]); useEffect(() => { if (ffmpegConfig) { setFfmpegApiUrl((ffmpegConfig as any).apiUrl || ""); } }, [ffmpegConfig]); // Vérifier si une config OpenRouter existe déjà const hasExistingConfig = !!openrouterConfig; const hasExistingFfmpegConfig = !!ffmpegConfig; const saveCloudinaryMutation = useMutation({ mutationFn: () => { const payload: any = { cloudName, }; // N'inclure apiKey et apiSecret que s'ils ne sont pas vides if (apiKey && apiKey.trim() !== "") { payload.apiKey = apiKey; } if (apiSecret && apiSecret.trim() !== "") { payload.apiSecret = apiSecret; } return apiRequest('POST', '/api/cloudinary/config', payload); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/cloudinary/config'] }); toast({ title: "Configuration sauvegardée", description: "Vos paramètres Cloudinary ont été enregistrés", }); setApiKey(""); // Clear the API key after saving setApiSecret(""); // Clear the secret after saving }, onError: () => { toast({ title: "Erreur", description: "Impossible de sauvegarder la configuration", variant: "destructive", }); }, }); const saveOpenrouterMutation = useMutation({ mutationFn: () => { const payload: any = { model: openrouterModel, systemPrompt: openrouterSystemPrompt, }; // N'inclure apiKey que s'il n'est pas vide if (openrouterApiKey && openrouterApiKey.trim() !== "") { payload.apiKey = openrouterApiKey; } return apiRequest('POST', '/api/openrouter/config', payload); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/openrouter/config'] }); toast({ title: "Configuration sauvegardée", description: "Vos paramètres OpenRouter ont été enregistrés", }); setOpenrouterApiKey(""); // Clear the API key after saving }, onError: () => { toast({ title: "Erreur", description: "Impossible de sauvegarder la configuration OpenRouter", variant: "destructive", }); }, }); const uploadLogoMutation = useMutation({ mutationFn: async (file: File) => { const formData = new FormData(); formData.append('logo', file); const response = await fetch('/api/cloudinary/logo', { method: 'POST', credentials: 'include', body: formData, }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to upload logo'); } return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/cloudinary/config'] }); toast({ title: "Logo uploadé", description: "Votre logo a été enregistré avec succès", }); setLogoFile(null); }, onError: (error: Error) => { toast({ title: "Erreur", description: error.message || "Impossible d'uploader le logo", variant: "destructive", }); }, }); const deleteLogoMutation = useMutation({ mutationFn: () => apiRequest('DELETE', '/api/cloudinary/logo', {}), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/cloudinary/config'] }); setLogoPreview(null); setLogoFile(null); toast({ title: "Logo supprimé", description: "Votre logo a été supprimé avec succès", }); }, onError: () => { toast({ title: "Erreur", description: "Impossible de supprimer le logo", variant: "destructive", }); }, }); const saveFfmpegMutation = useMutation({ mutationFn: () => { const payload: any = { apiUrl: ffmpegApiUrl, }; // N'inclure apiKey que s'il n'est pas vide if (ffmpegApiKey && ffmpegApiKey.trim() !== "") { payload.apiKey = ffmpegApiKey; } return apiRequest('POST', '/api/ffmpeg/config', payload); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/ffmpeg/config'] }); toast({ title: "Configuration sauvegardée", description: "Vos paramètres FFmpeg ont été enregistrés", }); setFfmpegApiKey(""); // Clear the API key after saving }, onError: () => { toast({ title: "Erreur", description: "Impossible de sauvegarder la configuration FFmpeg", variant: "destructive", }); }, }); const handleLogoChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { if (!file.type.startsWith('image/')) { toast({ title: "Erreur", description: "Veuillez sélectionner une image", variant: "destructive", }); return; } setLogoFile(file); const reader = new FileReader(); reader.onloadend = () => { setLogoPreview(reader.result as string); }; reader.readAsDataURL(file); } }; return (
{sidebarOpen && (
setSidebarOpen(false)} /> )}
setSidebarOpen(false)} />
setSidebarOpen(!sidebarOpen)} />

Paramètres

Configurez votre application

Notifications Gérez vos préférences de notification

Recevez des notifications pour les publications

Publier automatiquement à l'heure programmée

Configuration OpenRouter (IA) Configurez OpenRouter pour la génération de texte avec l'IA
setOpenrouterApiKey(e.target.value)} placeholder={hasExistingConfig ? "••••••••••••••••" : "sk-or-v1-..."} data-testid="input-openrouter-api-key" />

{hasExistingConfig ? ( <> ✓ Clé API configurée - Laissez vide pour garder la clé actuelle ) : ( <> Obtenez votre clé API sur{" "} openrouter.ai )}

Choisissez le modèle d'IA pour générer vos publications