From 0c257d991d39ea7e03190db761756a924ef7bf58 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Thu, 9 Oct 2025 18:10:32 +0000 Subject: [PATCH 1/7] Add option to select available AI models for text generation Adds a dropdown menu to the AI assistant page allowing users to choose from a list of OpenRouter AI models for text generation. The selected model is now passed in the API request. 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/XaovM3F --- client/src/components/ai-chat.tsx | 44 +++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/client/src/components/ai-chat.tsx b/client/src/components/ai-chat.tsx index 212eb35..2aef07b 100644 --- a/client/src/components/ai-chat.tsx +++ b/client/src/components/ai-chat.tsx @@ -6,6 +6,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { Bot, User, Lightbulb, History, Zap, Sparkles } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; interface Message { role: "user" | "assistant"; @@ -13,6 +14,22 @@ 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" }, +]; + export default function AiChat() { const [messages, setMessages] = useState([ { @@ -21,11 +38,15 @@ export default function AiChat() { }, ]); const [input, setInput] = useState(""); + const [selectedModel, setSelectedModel] = useState("anthropic/claude-3.5-sonnet"); const { toast } = useToast(); const generateMutation = useMutation({ mutationFn: async (productInfo: any) => { - const response = await apiRequest("POST", "/api/ai/generate", productInfo); + const response = await apiRequest("POST", "/api/ai/generate", { + ...productInfo, + model: selectedModel, + }); return response.json(); }, onSuccess: (data) => { @@ -68,7 +89,7 @@ export default function AiChat() { return (
-
+
@@ -89,6 +110,25 @@ export default function AiChat() { Nouveau chat
+ +
+ + +
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 2/7] 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(); From 032d5e4936831b50a6166e63f6235537900b9ba2 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Thu, 9 Oct 2025 18:39:55 +0000 Subject: [PATCH 3/7] Display all available AI models from OpenRouter API dynamically Fetch and display all AI models from the OpenRouter API, replacing a hardcoded list, and enabling selection of any model for AI-generated content. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda Replit-Commit-Checkpoint-Type: full_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.md | 1 + 1 file changed, 1 insertion(+) diff --git a/replit.md b/replit.md index cdadfc8..d1b997d 100644 --- a/replit.md +++ b/replit.md @@ -15,6 +15,7 @@ Preferred communication style: Simple, everyday language. - **Calendar Auto-Refresh**: Fixed calendar not updating after creating a new scheduled post. Added `queryClient.invalidateQueries` with `refetchType: 'all'` in `createPostMutation.onSuccess` to force cache invalidation and immediate refetch - **Dashboard Scheduled Posts Count**: Fixed scheduled posts count showing 0 in dashboard. Problem was that posts with `scheduledFor` were created with status "draft" instead of "scheduled". Solution: POST /api/posts now sets `status = "scheduled"` when `scheduledFor` is provided. Legacy posts updated with SQL: `UPDATE posts SET status = 'scheduled' WHERE scheduled_for IS NOT NULL AND status = 'draft'` - **Preview in Calendar & History**: Added eye icon (👁️) buttons to preview posts across calendar (desktop/mobile) and history sections. Clicking the eye icon fetches full post data via GET /api/posts/:id and opens PreviewModal with post text and all associated media. Desktop calendar shows icon on hover; mobile and history show icon always. Preview available in calendar grid, popover ("voir plus"), list view, and recent publications history. +- **Dynamic AI Model Selector**: Replaced hardcoded list of 13 models with dynamic loading from OpenRouter API. Added GET /api/ai/models endpoint that fetches complete catalog (326+ models) from https://openrouter.ai/api/v1/models. AI Assistant page now displays all available models with pricing info in scrollable dropdown. Backend accepts optional model parameter to override config model. Loading state shows spinner while fetching models. ## System Architecture From 078301d353f27ead268ccecf461ff9181d5ea405 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Thu, 9 Oct 2025 18:43:31 +0000 Subject: [PATCH 4/7] Transitioned from Plan to Build mode Replit-Commit-Author: Agent Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/h6F2mtY From 212ef049f7e60a9f4eb27f9021f51134d1ccb80d Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Thu, 9 Oct 2025 18:47:18 +0000 Subject: [PATCH 5/7] Update calendar and preview to show past scheduled posts Refactor `RecentPublications` to filter and sort attempted posts using `scheduledAt` timestamp. Update `PreviewModal` and `CalendarView` to include a `readOnly` prop for the preview modal. 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/oa3970R --- .replit | 4 ++ client/src/components/calendar-view.tsx | 1 + client/src/components/preview-modal.tsx | 47 ++++++++++++------- client/src/components/recent-publications.tsx | 10 ++-- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/.replit b/.replit index 8bbc693..e11f87b 100644 --- a/.replit +++ b/.replit @@ -14,6 +14,10 @@ run = ["npm", "run", "start"] localPort = 5000 externalPort = 80 +[[ports]] +localPort = 33027 +externalPort = 8008 + [[ports]] localPort = 34045 externalPort = 3003 diff --git a/client/src/components/calendar-view.tsx b/client/src/components/calendar-view.tsx index 1466388..5d9fdfe 100644 --- a/client/src/components/calendar-view.tsx +++ b/client/src/components/calendar-view.tsx @@ -417,6 +417,7 @@ export default function CalendarView() { mediaList={previewData.allMedia} onPublish={() => {}} isPublishing={false} + readOnly={true} />
); diff --git a/client/src/components/preview-modal.tsx b/client/src/components/preview-modal.tsx index 5e1f55a..9b796f9 100644 --- a/client/src/components/preview-modal.tsx +++ b/client/src/components/preview-modal.tsx @@ -13,6 +13,7 @@ interface PreviewModalProps { mediaList: Media[]; onPublish: () => void; isPublishing: boolean; + readOnly?: boolean; } type PreviewFormat = 'facebook-feed' | 'instagram-feed' | 'instagram-story'; @@ -24,7 +25,8 @@ export function PreviewModal({ selectedMedia, mediaList, onPublish, - isPublishing + isPublishing, + readOnly = false }: PreviewModalProps) { const [previewFormat, setPreviewFormat] = useState('facebook-feed'); const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0); @@ -311,22 +313,33 @@ export function PreviewModal({ {/* Actions */}
- - + {readOnly ? ( + + ) : ( + <> + + + + )}
diff --git a/client/src/components/recent-publications.tsx b/client/src/components/recent-publications.tsx index 86808b4..550d0ab 100644 --- a/client/src/components/recent-publications.tsx +++ b/client/src/components/recent-publications.tsx @@ -23,10 +23,11 @@ export default function RecentPublications() { queryKey: ['/api/scheduled-posts'], }); - // Filter and sort published posts + // Filter and sort attempted posts (scheduled in the past) + const now = new Date(); const recentPublished = scheduledPosts - .filter(sp => sp.publishedAt) - .sort((a, b) => new Date(b.publishedAt!).getTime() - new Date(a.publishedAt!).getTime()) + .filter(sp => sp.scheduledAt && new Date(sp.scheduledAt) <= now) + .sort((a, b) => new Date(b.scheduledAt!).getTime() - new Date(a.scheduledAt!).getTime()) .slice(0, 10); const handlePreviewPost = async (scheduledPost: ScheduledPostWithRelations) => { @@ -141,7 +142,7 @@ export default function RecentPublications() { • - {format(new Date(scheduledPost.publishedAt!), "d MMM yyyy 'à' HH:mm", { locale: fr })} + {format(new Date(scheduledPost.scheduledAt!), "d MMM yyyy 'à' HH:mm", { locale: fr })} {scheduledPost.error && ( <> @@ -165,6 +166,7 @@ export default function RecentPublications() { mediaList={previewData.allMedia} onPublish={() => {}} isPublishing={false} + readOnly={true} /> ); From 1056d905874e8c2541e7ebb600f790e949444105 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Thu, 9 Oct 2025 18:54:11 +0000 Subject: [PATCH 6/7] Add AI text generation to help create engaging social media posts Implement OpenAI API integration for dynamic content creation in social media posts. 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/oa3970R --- .replit | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.replit b/.replit index e11f87b..8bbc693 100644 --- a/.replit +++ b/.replit @@ -14,10 +14,6 @@ run = ["npm", "run", "start"] localPort = 5000 externalPort = 80 -[[ports]] -localPort = 33027 -externalPort = 8008 - [[ports]] localPort = 34045 externalPort = 3003 From 48680b2842ede488191eb0a1d9fd19d8ca5c5112 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Thu, 9 Oct 2025 18:54:55 +0000 Subject: [PATCH 7/7] Improve post preview functionality and AI model selection Enhance PreviewModal with read-only mode, update history section logic, and dynamically load AI models from OpenRouter API. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/oa3970R --- replit.md | 1 + 1 file changed, 1 insertion(+) diff --git a/replit.md b/replit.md index d1b997d..22b6fd7 100644 --- a/replit.md +++ b/replit.md @@ -16,6 +16,7 @@ Preferred communication style: Simple, everyday language. - **Dashboard Scheduled Posts Count**: Fixed scheduled posts count showing 0 in dashboard. Problem was that posts with `scheduledFor` were created with status "draft" instead of "scheduled". Solution: POST /api/posts now sets `status = "scheduled"` when `scheduledFor` is provided. Legacy posts updated with SQL: `UPDATE posts SET status = 'scheduled' WHERE scheduled_for IS NOT NULL AND status = 'draft'` - **Preview in Calendar & History**: Added eye icon (👁️) buttons to preview posts across calendar (desktop/mobile) and history sections. Clicking the eye icon fetches full post data via GET /api/posts/:id and opens PreviewModal with post text and all associated media. Desktop calendar shows icon on hover; mobile and history show icon always. Preview available in calendar grid, popover ("voir plus"), list view, and recent publications history. - **Dynamic AI Model Selector**: Replaced hardcoded list of 13 models with dynamic loading from OpenRouter API. Added GET /api/ai/models endpoint that fetches complete catalog (326+ models) from https://openrouter.ai/api/v1/models. AI Assistant page now displays all available models with pricing info in scrollable dropdown. Backend accepts optional model parameter to override config model. Loading state shows spinner while fetching models. +- **Read-Only Preview Mode**: Enhanced PreviewModal with optional `readOnly` prop that hides the "Publier" button when viewing posts from calendar or history. Calendar and history now pass `readOnly={true}` to PreviewModal, showing only "Fermer" button. History section updated to display all attempted posts (where `scheduledAt` is in the past) instead of only successfully published posts, and displays `scheduledAt` date instead of `publishedAt`. ## System Architecture