From 97b1a76270190a09973482e5d06c748a63fe34b9 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Thu, 9 Oct 2025 18:39:18 +0000 Subject: [PATCH] Fetch and display all available AI models from OpenRouter's API Integrate OpenRouter API to dynamically fetch and display available AI models in the client, and update the AI generation endpoint to accept a model override. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/h6F2mtY --- .replit | 4 +++ client/src/components/ai-chat.tsx | 58 +++++++++++++++++++------------ server/routes.ts | 15 ++++++-- server/services/openrouter.ts | 30 ++++++++++++++-- 4 files changed, 79 insertions(+), 28 deletions(-) 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();