From fb1d1505542de440f39711d8d93a515fa820f815 Mon Sep 17 00:00:00 2001 From: kimjaehyeon0101 <47347352-kimjaehyeon0101@users.noreply.replit.com> Date: Wed, 15 Oct 2025 02:06:25 +0000 Subject: [PATCH] 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 --- .replit | 6 +- client/src/components/ChatbotModal.tsx | 134 +++++++++++++++++++++++++ client/src/pages/MediaOutlet.tsx | 23 ++++- package-lock.json | 29 +++++- package.json | 1 + server/chatbot.ts | 54 ++++++++++ server/routes.ts | 74 +++++++++++++- server/storage.ts | 21 ++++ shared/schema.ts | 17 ++++ 9 files changed, 353 insertions(+), 6 deletions(-) create mode 100644 client/src/components/ChatbotModal.tsx create mode 100644 server/chatbot.ts diff --git a/.replit b/.replit index 48ea545..e8a22e7 100644 --- a/.replit +++ b/.replit @@ -38,6 +38,10 @@ externalPort = 3003 localPort = 41425 externalPort = 6000 +[[ports]] +localPort = 41825 +externalPort = 6800 + [[ports]] localPort = 43349 externalPort = 3000 @@ -50,7 +54,7 @@ externalPort = 4200 PORT = "5000" [agent] -integrations = ["javascript_object_storage:1.0.0", "javascript_log_in_with_replit:1.0.0", "javascript_database:1.0.0"] +integrations = ["javascript_object_storage:1.0.0", "javascript_log_in_with_replit:1.0.0", "javascript_database:1.0.0", "javascript_openai_ai_integrations:1.0.0"] [workflows] runButton = "Project" diff --git a/client/src/components/ChatbotModal.tsx b/client/src/components/ChatbotModal.tsx new file mode 100644 index 0000000..5bc5f77 --- /dev/null +++ b/client/src/components/ChatbotModal.tsx @@ -0,0 +1,134 @@ +import { useState } from "react"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { MessageCircle, Send } from "lucide-react"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import type { ChatMessage, MediaOutlet } from "@shared/schema"; + +interface ChatbotModalProps { + outlet: MediaOutlet; + isOpen: boolean; + onClose: () => void; +} + +export default function ChatbotModal({ outlet, isOpen, onClose }: ChatbotModalProps) { + const [message, setMessage] = useState(""); + + const { data: messages = [], isLoading } = useQuery({ + queryKey: [`/api/media-outlets/${outlet.slug}/chat`], + enabled: isOpen + }); + + const sendMessageMutation = useMutation({ + mutationFn: async (content: string) => { + return await apiRequest("POST", `/api/media-outlets/${outlet.slug}/chat`, { content }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [`/api/media-outlets/${outlet.slug}/chat`] }); + setMessage(""); + } + }); + + const handleSend = () => { + if (message.trim()) { + sendMessageMutation.mutate(message.trim()); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }; + + const getChatbotName = () => { + if (outlet.category === 'people') { + return outlet.name; + } else if (outlet.category === 'topics') { + return `${outlet.name} 전문가`; + } else { + return `${outlet.name} 대표`; + } + }; + + return ( + + + + + + {getChatbotName()}와 대화하기 + + + + +
+ {isLoading ? ( +
+ 대화 내용을 불러오는 중... +
+ ) : messages.length === 0 ? ( +
+ +

대화를 시작해보세요!

+

+ {getChatbotName()}에게 궁금한 것을 물어보세요. +

+
+ ) : ( + messages.map((msg) => ( +
+
+

{msg.content}

+
+
+ )) + )} + {sendMessageMutation.isPending && ( +
+
+

입력 중...

+
+
+ )} +
+
+ +
+