Add AI-powered chatbot for media outlets
Integrate an AI chatbot feature allowing users to interact with media outlets, fetch chat history, and generate AI responses using OpenAI. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9a264234-c5d7-4dcc-adf3-a954b149b30d Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/9a264234-c5d7-4dcc-adf3-a954b149b30d/d35d7YU
This commit is contained in:
54
server/chatbot.ts
Normal file
54
server/chatbot.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import OpenAI from "openai";
|
||||
|
||||
// This is using Replit's AI Integrations service, which provides OpenAI-compatible API access without requiring your own OpenAI API key.
|
||||
// the newest OpenAI model is "gpt-5" which was released August 7, 2025. do not change this unless explicitly requested by the user
|
||||
const openai = new OpenAI({
|
||||
baseURL: process.env.AI_INTEGRATIONS_OPENAI_BASE_URL,
|
||||
apiKey: process.env.AI_INTEGRATIONS_OPENAI_API_KEY
|
||||
});
|
||||
|
||||
interface ChatbotPersonality {
|
||||
systemPrompt: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function getChatbotPersonality(outletName: string, outletCategory: string, outletDescription?: string): ChatbotPersonality {
|
||||
const categoryContext = {
|
||||
people: `You are an AI assistant representing ${outletName}. ${outletDescription ? outletDescription : 'You speak from their perspective, embodying their personality, values, and communication style.'} Engage with users as if you are ${outletName} themselves, sharing insights, opinions, and knowledge from their unique perspective.`,
|
||||
topics: `You are an AI expert on ${outletName}. ${outletDescription ? outletDescription : 'You provide in-depth knowledge and analysis about this topic.'} Answer questions with expertise and provide thoughtful commentary from various angles on this subject.`,
|
||||
companies: `You are an AI representative of ${outletName}. ${outletDescription ? outletDescription : 'You speak on behalf of the company, sharing information about its mission, products, and values.'} Engage professionally while representing the company's interests and perspective.`
|
||||
};
|
||||
|
||||
const systemPrompt = categoryContext[outletCategory as keyof typeof categoryContext] ||
|
||||
`You are an AI assistant for ${outletName}. Provide helpful and informative responses.`;
|
||||
|
||||
return {
|
||||
systemPrompt: systemPrompt + '\n\nKeep responses conversational, engaging, and concise (2-4 paragraphs maximum). Use Korean language for communication.',
|
||||
name: outletName
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateChatbotResponse(
|
||||
messages: Array<{ role: 'user' | 'assistant' | 'system'; content: string }>,
|
||||
outletName: string,
|
||||
outletCategory: string,
|
||||
outletDescription?: string
|
||||
): Promise<string> {
|
||||
const personality = getChatbotPersonality(outletName, outletCategory, outletDescription);
|
||||
|
||||
const chatMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: 'system', content: personality.systemPrompt },
|
||||
...messages.map(msg => ({
|
||||
role: msg.role as 'user' | 'assistant',
|
||||
content: msg.content
|
||||
}))
|
||||
];
|
||||
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: 'gpt-5',
|
||||
messages: chatMessages,
|
||||
max_completion_tokens: 8192,
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content || '죄송합니다. 응답을 생성할 수 없습니다.';
|
||||
}
|
||||
@ -2,7 +2,8 @@ import type { Express } from "express";
|
||||
import { createServer, type Server } from "http";
|
||||
import { storage } from "./storage";
|
||||
import { setupAuth, isAuthenticated } from "./simpleAuth";
|
||||
import { insertArticleSchema, insertMediaOutletRequestSchema, insertBidSchema, insertCommentSchema, insertPredictionBetSchema, insertCommunityPostSchema, insertCommunityReplySchema } from "@shared/schema";
|
||||
import { insertArticleSchema, insertMediaOutletRequestSchema, insertBidSchema, insertCommentSchema, insertPredictionBetSchema, insertCommunityPostSchema, insertCommunityReplySchema, insertChatMessageSchema } from "@shared/schema";
|
||||
import { generateChatbotResponse } from "./chatbot";
|
||||
|
||||
export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Auth middleware
|
||||
@ -582,6 +583,77 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
// Chatbot routes
|
||||
app.get('/api/media-outlets/:slug/chat', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
|
||||
if (!outlet) {
|
||||
return res.status(404).json({ message: "Media outlet not found" });
|
||||
}
|
||||
|
||||
const userId = req.user.claims?.sub || req.user.id;
|
||||
const messages = await storage.getChatMessages(outlet.id, userId);
|
||||
res.json(messages);
|
||||
} catch (error) {
|
||||
console.error("Error fetching chat messages:", error);
|
||||
res.status(500).json({ message: "Failed to fetch chat messages" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/media-outlets/:slug/chat', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
|
||||
if (!outlet) {
|
||||
return res.status(404).json({ message: "Media outlet not found" });
|
||||
}
|
||||
|
||||
const userId = req.user.claims?.sub || req.user.id;
|
||||
const { content } = req.body;
|
||||
|
||||
if (!content || typeof content !== 'string') {
|
||||
return res.status(400).json({ message: "Invalid message content" });
|
||||
}
|
||||
|
||||
// Save user message
|
||||
const userMessage = insertChatMessageSchema.parse({
|
||||
mediaOutletId: outlet.id,
|
||||
userId,
|
||||
role: 'user',
|
||||
content
|
||||
});
|
||||
await storage.createChatMessage(userMessage);
|
||||
|
||||
// Get chat history
|
||||
const chatHistory = await storage.getChatMessages(outlet.id, userId);
|
||||
const messages = chatHistory.map(msg => ({
|
||||
role: msg.role as 'user' | 'assistant',
|
||||
content: msg.content
|
||||
}));
|
||||
|
||||
// Generate AI response
|
||||
const aiResponse = await generateChatbotResponse(
|
||||
messages,
|
||||
outlet.name,
|
||||
outlet.category,
|
||||
outlet.description || undefined
|
||||
);
|
||||
|
||||
// Save assistant message
|
||||
const assistantMessage = insertChatMessageSchema.parse({
|
||||
mediaOutletId: outlet.id,
|
||||
userId,
|
||||
role: 'assistant',
|
||||
content: aiResponse
|
||||
});
|
||||
const savedAssistantMessage = await storage.createChatMessage(assistantMessage);
|
||||
|
||||
res.status(201).json(savedAssistantMessage);
|
||||
} catch (error) {
|
||||
console.error("Error processing chat message:", error);
|
||||
res.status(500).json({ message: "Failed to process chat message" });
|
||||
}
|
||||
});
|
||||
|
||||
const httpServer = createServer(app);
|
||||
return httpServer;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
predictionBets,
|
||||
communityPosts,
|
||||
communityReplies,
|
||||
chatMessages,
|
||||
type User,
|
||||
type UpsertUser,
|
||||
type MediaOutlet,
|
||||
@ -32,6 +33,8 @@ import {
|
||||
type InsertCommunityPost,
|
||||
type CommunityReply,
|
||||
type InsertCommunityReply,
|
||||
type ChatMessage,
|
||||
type InsertChatMessage,
|
||||
} from "@shared/schema";
|
||||
import { db } from "./db";
|
||||
import { eq, desc, asc, and, ilike, sql } from "drizzle-orm";
|
||||
@ -97,6 +100,10 @@ export interface IStorage {
|
||||
getRepliesByPost(postId: string): Promise<CommunityReply[]>;
|
||||
createCommunityReply(reply: InsertCommunityReply): Promise<CommunityReply>;
|
||||
|
||||
// Chatbot operations
|
||||
getChatMessages(mediaOutletId: string, userId: string): Promise<ChatMessage[]>;
|
||||
createChatMessage(message: InsertChatMessage): Promise<ChatMessage>;
|
||||
|
||||
// Analytics operations
|
||||
getAnalytics(): Promise<{
|
||||
totalArticles: number;
|
||||
@ -473,6 +480,20 @@ export class DatabaseStorage implements IStorage {
|
||||
return newReply;
|
||||
}
|
||||
|
||||
// Chatbot operations
|
||||
async getChatMessages(mediaOutletId: string, userId: string): Promise<ChatMessage[]> {
|
||||
return await db
|
||||
.select()
|
||||
.from(chatMessages)
|
||||
.where(and(eq(chatMessages.mediaOutletId, mediaOutletId), eq(chatMessages.userId, userId)))
|
||||
.orderBy(asc(chatMessages.createdAt));
|
||||
}
|
||||
|
||||
async createChatMessage(message: InsertChatMessage): Promise<ChatMessage> {
|
||||
const [newMessage] = await db.insert(chatMessages).values(message).returning();
|
||||
return newMessage;
|
||||
}
|
||||
|
||||
// Analytics operations
|
||||
async getAnalytics(): Promise<{
|
||||
totalArticles: number;
|
||||
|
||||
Reference in New Issue
Block a user