'use client' import { useState, useRef, useEffect } from 'react' import { useParams, useRouter } from 'next/navigation' import Link from 'next/link' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' interface Message { id: string role: 'user' | 'assistant' content: string timestamp: Date } interface Conversation { id: number title: string updatedAt: string messages: Message[] } interface Agent { id: string name: string slug: string icon: string | null category: { name: string } | null } export default function AgentChatPage() { const params = useParams() const router = useRouter() const slug = params.slug as string const [agent, setAgent] = useState(null) const [loading, setLoading] = useState(true) const [messages, setMessages] = useState([]) const [input, setInput] = useState('') const [sending, setSending] = useState(false) const [conversations, setConversations] = useState([]) const [currentConversationId, setCurrentConversationId] = useState(null) const [upstreamConversationId, setUpstreamConversationId] = useState('') const [loadingConversations, setLoadingConversations] = useState(false) const messagesEndRef = useRef(null) const CHAT_API_URL = '/api/chat' useEffect(() => { fetch(`/api/agents/${slug}`) .then(res => { if (!res.ok) throw new Error('Not found') return res.json() }) .then(data => setAgent(data)) .catch(() => router.push('/404')) .finally(() => setLoading(false)) }, [slug, router]) useEffect(() => { if (agent) { loadConversations() } }, [agent]) useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages]) const loadConversations = async () => { if (!agent) return setLoadingConversations(true) try { const res = await fetch(`/api/conversations?agentId=${agent.id}`) const data = await res.json() setConversations(data) } catch (error) { console.error('Failed to load conversations:', error) } finally { setLoadingConversations(false) } } const loadConversation = async (conversationId: number) => { try { const res = await fetch(`/api/conversations/${conversationId}`) const data = await res.json() setCurrentConversationId(conversationId) setMessages(data.messages.map((msg: any) => ({ id: msg.id.toString(), role: msg.role as 'user' | 'assistant', content: msg.content, timestamp: new Date(msg.timestamp), }))) } catch (error) { console.error('Failed to load conversation:', error) } } const createNewConversation = async () => { if (!agent) return try { const res = await fetch('/api/conversations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ agentId: agent.id, title: '新对话', }), }) const newConversation = await res.json() setConversations(prev => [newConversation, ...prev]) setCurrentConversationId(newConversation.id) setMessages([]) } catch (error) { console.error('Failed to create conversation:', error) } } const deleteConversation = async (conversationId: number, e: React.MouseEvent) => { e.stopPropagation() if (!confirm('确定要删除这个对话吗?')) return try { await fetch(`/api/conversations/${conversationId}`, { method: 'DELETE', }) setConversations(prev => prev.filter(c => c.id !== conversationId)) if (currentConversationId === conversationId) { setCurrentConversationId(null) setMessages([]) } } catch (error) { console.error('Failed to delete conversation:', error) } } const sendMessage = async () => { if (!input.trim() || !agent) return let conversationId = currentConversationId if (!conversationId) { try { const res = await fetch('/api/conversations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ agentId: agent.id, title: input.slice(0, 20) || '新对话', }), }) const newConversation = await res.json() conversationId = newConversation.id setCurrentConversationId(conversationId) setConversations(prev => [newConversation, ...prev]) } catch (error) { console.error('Failed to create conversation:', error) return } } const userMessage: Message = { id: Date.now().toString(), role: 'user', content: input, timestamp: new Date(), } const currentInput = input setMessages(prev => [...prev, userMessage]) setInput('') setSending(true) try { await fetch(`/api/conversations/${conversationId}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role: 'user', content: currentInput, }), }) const assistantMessageId = (Date.now() + 1).toString() let assistantContent = '' let upstreamConvId = upstreamConversationId setMessages(prev => [...prev, { id: assistantMessageId, role: 'assistant', content: '', timestamp: new Date(), }]) const response = await fetch(CHAT_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inputs: {}, query: currentInput, response_mode: 'streaming', conversation_id: upstreamConversationId, user: 'user-' + Date.now(), }), }) const reader = response.body?.getReader() const decoder = new TextDecoder() if (reader) { let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n') buffer = lines.pop() || '' for (const line of lines) { if (line.startsWith('data: ')) { try { const json = JSON.parse(line.slice(6)) if (json.conversation_id) { upstreamConvId = json.conversation_id } const token = json.answer || json.message || '' if (token) { assistantContent += token setMessages(prev => prev.map(msg => msg.id === assistantMessageId ? { ...msg, content: assistantContent } : msg )) } } catch (e) { // ignore parse errors } } } } } setUpstreamConversationId(upstreamConvId) if (!assistantContent) { setMessages(prev => prev.map(msg => msg.id === assistantMessageId ? { ...msg, content: '抱歉,我暂时无法回答这个问题。' } : msg )) assistantContent = '抱歉,我暂时无法回答这个问题。' } await fetch(`/api/conversations/${conversationId}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role: 'assistant', content: assistantContent, }), }) } catch (error) { const errorMessage: Message = { id: (Date.now() + 2).toString(), role: 'assistant', content: '抱歉,服务暂时不可用,请稍后再试。', timestamp: new Date(), } setMessages(prev => [...prev, errorMessage]) } finally { setSending(false) } } const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() sendMessage() } } const handleQuickQuestion = (question: string) => { setInput(question) } const suggestedQuestions = [ '养老政策有哪些最新变化?', '如何申请养老服务补贴?', '老年人健康管理需要注意什么?', '养老机构如何选择?', ] if (loading) { return (
加载中...
) } if (!agent) { return null } return (
{/* 导航栏 */}
{/* 左侧历史问答 */}

历史问答

{loadingConversations ? (
加载中...
) : conversations.length === 0 ? (
暂无对话历史
) : ( conversations.map((conv) => (
loadConversation(conv.id)} className={`p-3 rounded-lg cursor-pointer group relative ${ currentConversationId === conv.id ? 'bg-blue-50 border border-blue-200' : 'bg-white border border-gray-200 hover:bg-gray-50' }`} >

{conv.title}

{new Date(conv.updatedAt).toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', })}

)) )}
{/* 中间聊天区域 */}
{/* 聊天头部 */}

智慧养老服务助手

{sending ? '正在回复...' : '24小时为您服务'}

智能小养正在为您服务
{/* 消息区域 */}
{messages.length === 0 && !sending && (

开始一个新的对话吧

)} {messages.map((message) => (
{message.role === 'assistant' && (
)}
{message.role === 'user' ? (

{message.content}

) : (
{message.content}
)}

{message.timestamp.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}

{message.role === 'user' && (
)}
))} {sending && (
)}
{/* 输入区域 */}
setInput(e.target.value)} onKeyDown={handleKeyPress} placeholder="请输入您的问题,例如:养老政策、健康咨询..." className="flex-1 border border-gray-300 rounded-l-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" disabled={sending} />
{/* 右侧服务区域 */}
{/* 自助服务 */}

自助服务

留言板

官方电话

政策资讯

{/* 热点问题 */}

热点问题

更多
    {suggestedQuestions.map((question, index) => (
  • ))}
{/* 快捷提问 */}

快捷提问

    {['养老政策咨询', '养老机构推荐', '养老服务申请'].map((question, index) => (
  • ))}
) }