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
This commit is contained in:
michaelschal committed 2025-10-09 18:39:18 +00:00
1 parent 0c257d991d
commit 97b1a76270
4 files changed
+79 -28

No files matched your search

+4
View File
@@ -34,6 +34,10 @@ externalPort = 5000
localPort = 37659
externalPort = 4200
[[ports]]
localPort = 38631
externalPort = 8080
[[ports]]
localPort = 40537
externalPort = 3000
+35 -23
View File
@@ -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<Message[]>([
@@ -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() {
<div className="flex items-center gap-3">
<label className="text-sm font-medium text-foreground">Modèle IA:</label>
<Select value={selectedModel} onValueChange={setSelectedModel}>
<Select value={selectedModel} onValueChange={setSelectedModel} disabled={modelsLoading}>
<SelectTrigger className="w-[300px] rounded-xl" data-testid="select-ai-model">
<SelectValue />
{modelsLoading ? (
<div className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" />
<span>Chargement des modèles...</span>
</div>
) : (
<SelectValue placeholder="Sélectionner un modèle" />
)}
</SelectTrigger>
<SelectContent>
{OPENROUTER_MODELS.map((model) => (
<SelectContent className="max-h-[300px] overflow-y-auto">
{availableModels.map((model: OpenRouterModel) => (
<SelectItem key={model.id} value={model.id} data-testid={`model-option-${model.id}`}>
<div className="flex flex-col">
<span>{model.name}</span>
<span className="text-xs text-muted-foreground">{model.category}</span>
<span className="font-medium">{model.name}</span>
{model.pricing && (
<span className="text-xs text-muted-foreground">
${model.pricing.prompt} / ${model.pricing.completion}
</span>
)}
</div>
</SelectItem>
))}
+13 -2
View File
@@ -423,14 +423,25 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
});
// 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({
+27 -3
View File
@@ -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<GeneratedText[]> {
async generatePostText(productInfo: ProductInfo, userId: string, modelOverride?: string): Promise<GeneratedText[]> {
// 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<any[]> {
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();