mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-16 19:59:42 +02:00
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:
1 parent
0c257d991d
commit
97b1a76270
4 files changed
+79
-28
No files matched your search
@@ -34,6 +34,10 @@ externalPort = 5000
|
||||
localPort = 37659
|
||||
externalPort = 4200
|
||||
|
||||
[[ports]]
|
||||
localPort = 38631
|
||||
externalPort = 8080
|
||||
|
||||
[[ports]]
|
||||
localPort = 40537
|
||||
externalPort = 3000
|
||||
|
||||
@@ -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
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
Reference in new issue
Block a user