mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-24 07:28:11 +02:00
Add ability to configure and select AI models for text generation
Implement OpenRouter API integration for AI-powered text generation, allowing users to configure API keys, select models, and define system prompts for customized content creation. 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/HPCBgsr
This commit is contained in:
1 parent
d63f776af0
commit
68ed90b0c9
6 files changed
+219
-39
No files matched your search
@@ -14,6 +14,10 @@ run = ["npm", "run", "start"]
|
|||||||
localPort = 5000
|
localPort = 5000
|
||||||
externalPort = 80
|
externalPort = 80
|
||||||
|
|
||||||
|
[[ports]]
|
||||||
|
localPort = 33041
|
||||||
|
externalPort = 3000
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
PORT = "5000"
|
PORT = "5000"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Settings as SettingsIcon, Bell, Key, Shield, Cloud } from "lucide-react";
|
import { Settings as SettingsIcon, Bell, Key, Shield, Cloud, Brain } from "lucide-react";
|
||||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||||
import Sidebar from "@/components/sidebar";
|
import Sidebar from "@/components/sidebar";
|
||||||
import TopBar from "@/components/topbar";
|
import TopBar from "@/components/topbar";
|
||||||
@@ -7,6 +7,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
@@ -18,12 +20,19 @@ export default function Settings() {
|
|||||||
const [cloudName, setCloudName] = useState("");
|
const [cloudName, setCloudName] = useState("");
|
||||||
const [apiKey, setApiKey] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
const [apiSecret, setApiSecret] = useState("");
|
const [apiSecret, setApiSecret] = useState("");
|
||||||
|
const [openrouterApiKey, setOpenrouterApiKey] = useState("");
|
||||||
|
const [openrouterModel, setOpenrouterModel] = useState("anthropic/claude-3.5-sonnet");
|
||||||
|
const [openrouterSystemPrompt, setOpenrouterSystemPrompt] = useState("Tu es un expert en marketing des réseaux sociaux. Génère 3 variations de textes engageants pour des publications Facebook et Instagram à partir des informations produit fournies. Chaque variation doit être unique, captivante et optimisée pour l'engagement.");
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { data: cloudinaryConfig } = useQuery({
|
const { data: cloudinaryConfig } = useQuery({
|
||||||
queryKey: ['/api/cloudinary/config'],
|
queryKey: ['/api/cloudinary/config'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: openrouterConfig } = useQuery({
|
||||||
|
queryKey: ['/api/openrouter/config'],
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cloudinaryConfig) {
|
if (cloudinaryConfig) {
|
||||||
setCloudName((cloudinaryConfig as any).cloudName || "");
|
setCloudName((cloudinaryConfig as any).cloudName || "");
|
||||||
@@ -31,6 +40,13 @@ export default function Settings() {
|
|||||||
}
|
}
|
||||||
}, [cloudinaryConfig]);
|
}, [cloudinaryConfig]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (openrouterConfig) {
|
||||||
|
setOpenrouterModel((openrouterConfig as any).model || "anthropic/claude-3.5-sonnet");
|
||||||
|
setOpenrouterSystemPrompt((openrouterConfig as any).systemPrompt || "Tu es un expert en marketing des réseaux sociaux. Génère 3 variations de textes engageants pour des publications Facebook et Instagram à partir des informations produit fournies. Chaque variation doit être unique, captivante et optimisée pour l'engagement.");
|
||||||
|
}
|
||||||
|
}, [openrouterConfig]);
|
||||||
|
|
||||||
const saveCloudinaryMutation = useMutation({
|
const saveCloudinaryMutation = useMutation({
|
||||||
mutationFn: () =>
|
mutationFn: () =>
|
||||||
apiRequest('POST', '/api/cloudinary/config', {
|
apiRequest('POST', '/api/cloudinary/config', {
|
||||||
@@ -55,6 +71,30 @@ export default function Settings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const saveOpenrouterMutation = useMutation({
|
||||||
|
mutationFn: () =>
|
||||||
|
apiRequest('POST', '/api/openrouter/config', {
|
||||||
|
apiKey: openrouterApiKey,
|
||||||
|
model: openrouterModel,
|
||||||
|
systemPrompt: openrouterSystemPrompt,
|
||||||
|
}),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['/api/openrouter/config'] });
|
||||||
|
toast({
|
||||||
|
title: "Configuration sauvegardée",
|
||||||
|
description: "Vos paramètres OpenRouter ont été enregistrés",
|
||||||
|
});
|
||||||
|
setOpenrouterApiKey(""); // Clear the API key after saving
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast({
|
||||||
|
title: "Erreur",
|
||||||
|
description: "Impossible de sauvegarder la configuration OpenRouter",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen overflow-hidden bg-background">
|
<div className="flex h-screen overflow-hidden bg-background">
|
||||||
{sidebarOpen && (
|
{sidebarOpen && (
|
||||||
@@ -128,26 +168,72 @@ export default function Settings() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Key className="w-5 h-5" />
|
<Brain className="w-5 h-5" />
|
||||||
Clés API
|
Configuration OpenRouter (IA)
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Configurez vos clés API pour les intégrations
|
Configurez OpenRouter pour la génération de texte avec l'IA
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="openrouterKey">Clé API OpenRouter</Label>
|
<Label htmlFor="openrouterApiKey">Clé API OpenRouter</Label>
|
||||||
<Input
|
<Input
|
||||||
id="openrouterKey"
|
id="openrouterApiKey"
|
||||||
type="password"
|
type="password"
|
||||||
|
value={openrouterApiKey}
|
||||||
|
onChange={(e) => setOpenrouterApiKey(e.target.value)}
|
||||||
placeholder="sk-or-v1-..."
|
placeholder="sk-or-v1-..."
|
||||||
data-testid="input-openrouter-key"
|
data-testid="input-openrouter-api-key"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Utilisée pour la génération de texte IA
|
Obtenez votre clé API sur{" "}
|
||||||
|
<a href="https://openrouter.ai/keys" target="_blank" rel="noopener noreferrer" className="underline">
|
||||||
|
openrouter.ai
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="openrouterModel">Modèle d'IA</Label>
|
||||||
|
<Select value={openrouterModel} onValueChange={setOpenrouterModel}>
|
||||||
|
<SelectTrigger id="openrouterModel" data-testid="select-openrouter-model">
|
||||||
|
<SelectValue placeholder="Sélectionner un modèle" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="anthropic/claude-3.5-sonnet">Claude 3.5 Sonnet</SelectItem>
|
||||||
|
<SelectItem value="anthropic/claude-3-opus">Claude 3 Opus</SelectItem>
|
||||||
|
<SelectItem value="openai/gpt-4o">GPT-4o</SelectItem>
|
||||||
|
<SelectItem value="openai/gpt-4-turbo">GPT-4 Turbo</SelectItem>
|
||||||
|
<SelectItem value="google/gemini-pro-1.5">Gemini 1.5 Pro</SelectItem>
|
||||||
|
<SelectItem value="meta-llama/llama-3.1-70b-instruct">Llama 3.1 70B</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Choisissez le modèle d'IA pour générer vos publications
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="openrouterSystemPrompt">Prompt système</Label>
|
||||||
|
<Textarea
|
||||||
|
id="openrouterSystemPrompt"
|
||||||
|
value={openrouterSystemPrompt}
|
||||||
|
onChange={(e) => setOpenrouterSystemPrompt(e.target.value)}
|
||||||
|
placeholder="Instructions pour l'IA..."
|
||||||
|
rows={6}
|
||||||
|
data-testid="textarea-openrouter-system-prompt"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Définissez les instructions que l'IA doit suivre pour générer vos textes
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={() => saveOpenrouterMutation.mutate()}
|
||||||
|
disabled={saveOpenrouterMutation.isPending || !openrouterApiKey || !openrouterModel || !openrouterSystemPrompt}
|
||||||
|
data-testid="button-save-openrouter"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{saveOpenrouterMutation.isPending ? "Enregistrement..." : "Enregistrer OpenRouter"}
|
||||||
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
+49
-2
@@ -4,7 +4,7 @@ import { storage } from "./storage";
|
|||||||
import multer from "multer";
|
import multer from "multer";
|
||||||
import { openRouterService } from "./services/openrouter";
|
import { openRouterService } from "./services/openrouter";
|
||||||
import { cloudinaryService } from "./services/cloudinary";
|
import { cloudinaryService } from "./services/cloudinary";
|
||||||
import { insertPostSchema, insertScheduledPostSchema, insertSocialPageSchema, insertAiGenerationSchema, insertCloudinaryConfigSchema } from "@shared/schema";
|
import { insertPostSchema, insertScheduledPostSchema, insertSocialPageSchema, insertAiGenerationSchema, insertCloudinaryConfigSchema, insertOpenrouterConfigSchema } from "@shared/schema";
|
||||||
|
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
const upload = multer({ storage: multer.memoryStorage() });
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
const productInfo = req.body;
|
const productInfo = req.body;
|
||||||
const userId = "demo-user"; // In real app, get from session/auth
|
const userId = "demo-user"; // In real app, get from session/auth
|
||||||
|
|
||||||
const generatedTexts = await openRouterService.generatePostText(productInfo);
|
const generatedTexts = await openRouterService.generatePostText(productInfo, userId);
|
||||||
|
|
||||||
// Save to database
|
// Save to database
|
||||||
await storage.createAiGeneration({
|
await storage.createAiGeneration({
|
||||||
@@ -283,6 +283,53 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// OpenRouter configuration
|
||||||
|
app.get("/api/openrouter/config", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const userId = "demo-user"; // In real app, get from session/auth
|
||||||
|
const config = await storage.getOpenrouterConfig(userId);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return res.json(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't send the API key to the client
|
||||||
|
const { apiKey, ...safeConfig } = config;
|
||||||
|
res.json(safeConfig);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching OpenRouter config:", error);
|
||||||
|
res.status(500).json({ error: "Failed to fetch OpenRouter config" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/api/openrouter/config", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const userId = "demo-user"; // In real app, get from session/auth
|
||||||
|
|
||||||
|
const configData = insertOpenrouterConfigSchema.parse({
|
||||||
|
...req.body,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if config already exists
|
||||||
|
const existingConfig = await storage.getOpenrouterConfig(userId);
|
||||||
|
|
||||||
|
let config;
|
||||||
|
if (existingConfig) {
|
||||||
|
config = await storage.updateOpenrouterConfig(userId, configData);
|
||||||
|
} else {
|
||||||
|
config = await storage.createOpenrouterConfig(configData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't send the API key back
|
||||||
|
const { apiKey, ...safeConfig } = config;
|
||||||
|
res.json(safeConfig);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving OpenRouter config:", error);
|
||||||
|
res.status(500).json({ error: "Failed to save OpenRouter config" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const httpServer = createServer(app);
|
const httpServer = createServer(app);
|
||||||
|
|
||||||
return httpServer;
|
return httpServer;
|
||||||
|
|||||||
@@ -12,35 +12,32 @@ interface GeneratedText {
|
|||||||
characterCount: number;
|
characterCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { storage } from '../storage';
|
||||||
|
|
||||||
export class OpenRouterService {
|
export class OpenRouterService {
|
||||||
private apiKey: string;
|
|
||||||
private baseUrl = "https://openrouter.ai/api/v1/chat/completions";
|
private baseUrl = "https://openrouter.ai/api/v1/chat/completions";
|
||||||
|
|
||||||
constructor() {
|
async generatePostText(productInfo: ProductInfo, userId: string): Promise<GeneratedText[]> {
|
||||||
this.apiKey = process.env.OPENROUTER_API_KEY || "";
|
// Get user's OpenRouter configuration
|
||||||
if (!this.apiKey) {
|
const config = await storage.getOpenrouterConfig(userId);
|
||||||
console.warn("OPENROUTER_API_KEY not set. AI text generation will not work.");
|
|
||||||
}
|
if (!config) {
|
||||||
}
|
throw new Error('Configuration OpenRouter non trouvée. Veuillez configurer OpenRouter dans les paramètres.');
|
||||||
|
|
||||||
async generatePostText(productInfo: ProductInfo): Promise<GeneratedText[]> {
|
|
||||||
if (!this.apiKey) {
|
|
||||||
throw new Error("OpenRouter API key not configured");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const prompt = this.buildPrompt(productInfo);
|
const prompt = this.buildPrompt(productInfo, config.systemPrompt);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(this.baseUrl, {
|
const response = await fetch(this.baseUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${this.apiKey}`,
|
"Authorization": `Bearer ${config.apiKey}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"HTTP-Referer": process.env.APP_URL || "http://localhost:5000",
|
"HTTP-Referer": process.env.APP_URL || "http://localhost:5555",
|
||||||
"X-Title": "Social Flow"
|
"X-Title": "Social Flow"
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: "anthropic/claude-3.5-sonnet",
|
model: config.model,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
@@ -66,26 +63,18 @@ export class OpenRouterService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildPrompt(productInfo: ProductInfo): string {
|
private buildPrompt(productInfo: ProductInfo, systemPrompt: string): string {
|
||||||
const features = productInfo.features?.join(", ") || "";
|
const features = productInfo.features?.join(", ") || "";
|
||||||
|
|
||||||
return `Tu es un expert en marketing sur les réseaux sociaux. Génère 3 variantes de texte pour une publication Facebook/Instagram pour ce produit:
|
const productDetails = `Nom: ${productInfo.name}
|
||||||
|
|
||||||
Nom: ${productInfo.name}
|
|
||||||
${productInfo.description ? `Description: ${productInfo.description}` : ''}
|
${productInfo.description ? `Description: ${productInfo.description}` : ''}
|
||||||
${productInfo.price ? `Prix: ${productInfo.price}` : ''}
|
${productInfo.price ? `Prix: ${productInfo.price}` : ''}
|
||||||
${features ? `Caractéristiques: ${features}` : ''}
|
${features ? `Caractéristiques: ${features}` : ''}`;
|
||||||
|
|
||||||
Crée 3 versions différentes:
|
return `${systemPrompt}
|
||||||
1. Version Dynamique - Énergique et engageante avec des emojis
|
|
||||||
2. Version Informative - Factuelle et professionnelle
|
|
||||||
3. Version Émotionnelle - Axée sur les bénéfices émotionnels
|
|
||||||
|
|
||||||
Chaque version doit:
|
Informations produit:
|
||||||
- Être en français
|
${productDetails}
|
||||||
- Faire entre 150-250 caractères
|
|
||||||
- Être optimisée pour les réseaux sociaux
|
|
||||||
- Inclure des emojis pertinents (sauf la version informative)
|
|
||||||
|
|
||||||
Format de réponse:
|
Format de réponse:
|
||||||
VERSION 1 - DYNAMIQUE:
|
VERSION 1 - DYNAMIQUE:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
scheduledPosts,
|
scheduledPosts,
|
||||||
aiGenerations,
|
aiGenerations,
|
||||||
cloudinaryConfig,
|
cloudinaryConfig,
|
||||||
|
openrouterConfig,
|
||||||
type User,
|
type User,
|
||||||
type InsertUser,
|
type InsertUser,
|
||||||
type SocialPage,
|
type SocialPage,
|
||||||
@@ -20,6 +21,8 @@ import {
|
|||||||
type InsertAiGeneration,
|
type InsertAiGeneration,
|
||||||
type CloudinaryConfig,
|
type CloudinaryConfig,
|
||||||
type InsertCloudinaryConfig,
|
type InsertCloudinaryConfig,
|
||||||
|
type OpenrouterConfig,
|
||||||
|
type InsertOpenrouterConfig,
|
||||||
} from "@shared/schema";
|
} from "@shared/schema";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { eq, and, gte, lte, desc, isNull } from "drizzle-orm";
|
import { eq, and, gte, lte, desc, isNull } from "drizzle-orm";
|
||||||
@@ -66,6 +69,11 @@ export interface IStorage {
|
|||||||
getCloudinaryConfig(userId: string): Promise<CloudinaryConfig | undefined>;
|
getCloudinaryConfig(userId: string): Promise<CloudinaryConfig | undefined>;
|
||||||
createCloudinaryConfig(config: InsertCloudinaryConfig): Promise<CloudinaryConfig>;
|
createCloudinaryConfig(config: InsertCloudinaryConfig): Promise<CloudinaryConfig>;
|
||||||
updateCloudinaryConfig(userId: string, config: Partial<InsertCloudinaryConfig>): Promise<CloudinaryConfig>;
|
updateCloudinaryConfig(userId: string, config: Partial<InsertCloudinaryConfig>): Promise<CloudinaryConfig>;
|
||||||
|
|
||||||
|
// OpenRouter Config
|
||||||
|
getOpenrouterConfig(userId: string): Promise<OpenrouterConfig | undefined>;
|
||||||
|
createOpenrouterConfig(config: InsertOpenrouterConfig): Promise<OpenrouterConfig>;
|
||||||
|
updateOpenrouterConfig(userId: string, config: Partial<InsertOpenrouterConfig>): Promise<OpenrouterConfig>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DatabaseStorage implements IStorage {
|
export class DatabaseStorage implements IStorage {
|
||||||
@@ -235,6 +243,25 @@ export class DatabaseStorage implements IStorage {
|
|||||||
.returning();
|
.returning();
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenRouter Config
|
||||||
|
async getOpenrouterConfig(userId: string): Promise<OpenrouterConfig | undefined> {
|
||||||
|
const [config] = await db.select().from(openrouterConfig).where(eq(openrouterConfig.userId, userId));
|
||||||
|
return config || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOpenrouterConfig(config: InsertOpenrouterConfig): Promise<OpenrouterConfig> {
|
||||||
|
const [newConfig] = await db.insert(openrouterConfig).values(config).returning();
|
||||||
|
return newConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateOpenrouterConfig(userId: string, config: Partial<InsertOpenrouterConfig>): Promise<OpenrouterConfig> {
|
||||||
|
const [updated] = await db.update(openrouterConfig)
|
||||||
|
.set({ ...config, updatedAt: new Date() })
|
||||||
|
.where(eq(openrouterConfig.userId, userId))
|
||||||
|
.returning();
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storage = new DatabaseStorage();
|
export const storage = new DatabaseStorage();
|
||||||
@@ -37,6 +37,16 @@ export const cloudinaryConfig = pgTable("cloudinary_config", {
|
|||||||
updatedAt: timestamp("updated_at").defaultNow(),
|
updatedAt: timestamp("updated_at").defaultNow(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const openrouterConfig = pgTable("openrouter_config", {
|
||||||
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||||
|
userId: varchar("user_id").notNull().unique().references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
apiKey: text("api_key").notNull(),
|
||||||
|
model: text("model").notNull().default("anthropic/claude-3.5-sonnet"),
|
||||||
|
systemPrompt: text("system_prompt").notNull().default("Tu es un expert en marketing des réseaux sociaux. Génère 3 variations de textes engageants pour des publications Facebook et Instagram à partir des informations produit fournies. Chaque variation doit être unique, captivante et optimisée pour l'engagement."),
|
||||||
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
export const media = pgTable("media", {
|
export const media = pgTable("media", {
|
||||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||||
userId: varchar("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
userId: varchar("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||||
@@ -96,6 +106,7 @@ export const usersRelations = relations(users, ({ many, one }) => ({
|
|||||||
posts: many(posts),
|
posts: many(posts),
|
||||||
aiGenerations: many(aiGenerations),
|
aiGenerations: many(aiGenerations),
|
||||||
cloudinaryConfig: one(cloudinaryConfig),
|
cloudinaryConfig: one(cloudinaryConfig),
|
||||||
|
openrouterConfig: one(openrouterConfig),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const cloudinaryConfigRelations = relations(cloudinaryConfig, ({ one }) => ({
|
export const cloudinaryConfigRelations = relations(cloudinaryConfig, ({ one }) => ({
|
||||||
@@ -105,6 +116,13 @@ export const cloudinaryConfigRelations = relations(cloudinaryConfig, ({ one }) =
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const openrouterConfigRelations = relations(openrouterConfig, ({ one }) => ({
|
||||||
|
user: one(users, {
|
||||||
|
fields: [openrouterConfig.userId],
|
||||||
|
references: [users.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
export const socialPagesRelations = relations(socialPages, ({ one, many }) => ({
|
export const socialPagesRelations = relations(socialPages, ({ one, many }) => ({
|
||||||
user: one(users, {
|
user: one(users, {
|
||||||
fields: [socialPages.userId],
|
fields: [socialPages.userId],
|
||||||
@@ -199,6 +217,12 @@ export const insertCloudinaryConfigSchema = createInsertSchema(cloudinaryConfig)
|
|||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const insertOpenrouterConfigSchema = createInsertSchema(openrouterConfig).omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
});
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export type User = typeof users.$inferSelect;
|
export type User = typeof users.$inferSelect;
|
||||||
export type InsertUser = z.infer<typeof insertUserSchema>;
|
export type InsertUser = z.infer<typeof insertUserSchema>;
|
||||||
@@ -220,3 +244,6 @@ export type InsertAiGeneration = z.infer<typeof insertAiGenerationSchema>;
|
|||||||
|
|
||||||
export type CloudinaryConfig = typeof cloudinaryConfig.$inferSelect;
|
export type CloudinaryConfig = typeof cloudinaryConfig.$inferSelect;
|
||||||
export type InsertCloudinaryConfig = z.infer<typeof insertCloudinaryConfigSchema>;
|
export type InsertCloudinaryConfig = z.infer<typeof insertCloudinaryConfigSchema>;
|
||||||
|
|
||||||
|
export type OpenrouterConfig = typeof openrouterConfig.$inferSelect;
|
||||||
|
export type InsertOpenrouterConfig = z.infer<typeof insertOpenrouterConfigSchema>;
|
||||||
Reference in new issue
Block a user