diff --git a/.replit b/.replit index 7bce39b..8bbc693 100644 --- a/.replit +++ b/.replit @@ -34,6 +34,10 @@ externalPort = 5000 localPort = 37659 externalPort = 4200 +[[ports]] +localPort = 38631 +externalPort = 8080 + [[ports]] localPort = 40537 externalPort = 3000 diff --git a/client/src/components/ai-chat.tsx b/client/src/components/ai-chat.tsx index 2aef07b..bb2d530 100644 --- a/client/src/components/ai-chat.tsx +++ b/client/src/components/ai-chat.tsx @@ -1,10 +1,10 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { useMutation, useQuery } from "@tanstack/react-query"; import { apiRequest, queryClient } from "@/lib/queryClient"; -import { Bot, User, Lightbulb, History, Zap, Sparkles } from "lucide-react"; +import { Bot, User, Lightbulb, History, Zap, Sparkles, Loader2 } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; @@ -14,21 +14,15 @@ interface Message { variants?: Array<{ variant: string; text: string; characterCount: number }>; } -const OPENROUTER_MODELS = [ - { id: "anthropic/claude-3.5-sonnet", name: "Claude 3.5 Sonnet (Recommandé)", category: "Anthropic" }, - { id: "anthropic/claude-sonnet-3.7", name: "Claude Sonnet 3.7", category: "Anthropic" }, - { id: "anthropic/claude-opus", name: "Claude Opus", category: "Anthropic" }, - { id: "openai/gpt-5", name: "GPT-5", category: "OpenAI" }, - { id: "openai/gpt-4o", name: "GPT-4o", category: "OpenAI" }, - { id: "openai/gpt-3.5-turbo", name: "GPT-3.5 Turbo", category: "OpenAI" }, - { id: "google/gemini-2.5-flash", name: "Gemini 2.5 Flash", category: "Google" }, - { id: "google/gemini-pro", name: "Gemini Pro", category: "Google" }, - { id: "meta-llama/llama-4-maverick:free", name: "Llama 4 Maverick (Gratuit)", category: "Meta" }, - { id: "meta-llama/llama-3.1-70b-instruct", name: "Llama 3.1 70B", category: "Meta" }, - { id: "x-ai/grok-3", name: "Grok 3", category: "xAI" }, - { id: "mistralai/mistral-small-3.1:free", name: "Mistral Small 3.1 (Gratuit)", category: "Mistral AI" }, - { id: "qwen/qwen-qwq-32b:free", name: "Qwen QwQ 32B (Gratuit)", category: "Alibaba" }, -]; +interface OpenRouterModel { + id: string; + name: string; + context_length?: number; + pricing?: { + prompt: string; + completion: string; + }; +} export default function AiChat() { const [messages, setMessages] = useState([ @@ -41,6 +35,13 @@ export default function AiChat() { const [selectedModel, setSelectedModel] = useState("anthropic/claude-3.5-sonnet"); const { toast } = useToast(); + // Fetch available models from OpenRouter + const { data: modelsData, isLoading: modelsLoading } = useQuery<{ models: OpenRouterModel[] }>({ + queryKey: ['/api/ai/models'], + }); + + const availableModels = modelsData?.models || []; + const generateMutation = useMutation({ mutationFn: async (productInfo: any) => { const response = await apiRequest("POST", "/api/ai/generate", { @@ -113,16 +114,27 @@ export default function AiChat() {
- - + {modelsLoading ? ( +
+ + Chargement des modèles... +
+ ) : ( + + )}
- - {OPENROUTER_MODELS.map((model) => ( + + {availableModels.map((model: OpenRouterModel) => (
- {model.name} - {model.category} + {model.name} + {model.pricing && ( + + ${model.pricing.prompt} / ${model.pricing.completion} + + )}
))} diff --git a/server/routes.ts b/server/routes.ts index a176d7a..7582533 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -423,14 +423,25 @@ export async function registerRoutes(app: Express): Promise { } }); + // Get available AI models from OpenRouter + app.get("/api/ai/models", requireAuth, async (req, res) => { + try { + const models = await openRouterService.getAvailableModels(); + res.json({ models }); + } catch (error) { + console.error("Error fetching models:", error); + res.status(500).json({ error: "Failed to fetch models" }); + } + }); + // AI text generation app.post("/api/ai/generate", requireAuth, async (req, res) => { try { const user = req.user as User; const userId = user.id; - const productInfo = req.body; + const { model, ...productInfo } = req.body; - const generatedTexts = await openRouterService.generatePostText(productInfo, userId); + const generatedTexts = await openRouterService.generatePostText(productInfo, userId, model); // Save to database await storage.createAiGeneration({ diff --git a/server/services/openrouter.ts b/server/services/openrouter.ts index 1653903..e1747a2 100644 --- a/server/services/openrouter.ts +++ b/server/services/openrouter.ts @@ -17,7 +17,7 @@ import { storage } from '../storage'; export class OpenRouterService { private baseUrl = "https://openrouter.ai/api/v1/chat/completions"; - async generatePostText(productInfo: ProductInfo, userId: string): Promise { + async generatePostText(productInfo: ProductInfo, userId: string, modelOverride?: string): Promise { // Get user's OpenRouter configuration const config = await storage.getOpenrouterConfig(userId); @@ -26,6 +26,9 @@ export class OpenRouterService { } const prompt = this.buildPrompt(productInfo, config.systemPrompt); + + // Use provided model or fall back to config model + const modelToUse = modelOverride || config.model; try { const response = await fetch(this.baseUrl, { @@ -37,7 +40,7 @@ export class OpenRouterService { "X-Title": "Social Flow" }, body: JSON.stringify({ - model: config.model, + model: modelToUse, messages: [ { role: "user", @@ -58,7 +61,7 @@ export class OpenRouterService { const data = await response.json(); if (!data.choices || !data.choices[0]) { - console.error('Invalid OpenRouter response - no choices:', { hasChoices: !!data.choices, model: config.model }); + console.error('Invalid OpenRouter response - no choices:', { hasChoices: !!data.choices, model: modelToUse }); throw new Error('Réponse invalide de l\'API OpenRouter. Vérifiez votre clé API et votre modèle.'); } @@ -202,6 +205,27 @@ VERSION 3 - ÉMOTIONNELLE: private capitalizeFirst(text: string): string { return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase(); } + + async getAvailableModels(): Promise { + try { + const response = await fetch("https://openrouter.ai/api/v1/models", { + method: "GET", + headers: { + "Content-Type": "application/json", + } + }); + + if (!response.ok) { + throw new Error(`Failed to fetch models: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + return data.data || []; + } catch (error) { + console.error("Error fetching OpenRouter models:", error); + throw error; + } + } } export const openRouterService = new OpenRouterService();