'use client' import { useState, useEffect, useRef, useCallback } from 'react' const MarkdownItCjs = require("markdown-it/dist/markdown-it.js"); import { ComponentCard } from '@/components/ComponentCard'; import { Component } from '@prisma/client'; import { nextTick } from 'process'; const md = MarkdownItCjs() interface Conversation { id: string title: string | null createdAt: string updatedAt: string } type Message = { role: 'user' | 'assistant' content: string id?: string // 添加唯一标识符 } | { role: 'card' components: Component[] id?: string // 添加唯一标识符 } export default function AIAssistantPage() { const [messages, setMessages] = useState([]) const [input, setInput] = useState('') const [isLoading, setIsLoading] = useState(false) const [conversations, setConversations] = useState([]) const [currentConversationId, setCurrentConversationId] = useState(null) const [isLoadingConversations, setIsLoadingConversations] = useState(true) const messagesEndRef = useRef(null) const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) } useEffect(() => { scrollToBottom() }, [messages]) useEffect(() => { loadConversations() }, []) const loadConversations = async () => { try { setIsLoadingConversations(true) const token = localStorage.getItem('token') const response = await fetch('/api/ai/conversations', { headers: { 'Authorization': `Bearer ${token}` } }) if (response.ok) { const data = await response.json() setConversations(data.conversations) } } catch (error) { console.error('加载对话历史失败:', error) } finally { setIsLoadingConversations(false) } } const loadConversation = async (conversationId: string) => { try { setIsLoading(true) const token = localStorage.getItem('token') const response = await fetch(`/api/ai/conversations/${conversationId}`, { headers: { 'Authorization': `Bearer ${token}` } }) if (response.ok) { const conversation = await response.json() setCurrentConversationId(conversationId) setMessages(conversation) } } catch (error) { console.error('加载对话失败:', error) } finally { setIsLoading(false) } } const startNewConversation = () => { setMessages([]) setCurrentConversationId(null) } const deleteConversation = async (conversationId: string) => { if (!confirm('确定要删除这个对话吗?')) return try { const token = localStorage.getItem('token') const response = await fetch(`/api/ai/conversations/${conversationId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }) if (response.ok) { setConversations(prev => prev.filter(c => c.id !== conversationId)) if (currentConversationId === conversationId) { startNewConversation() } } } catch (error) { console.error('删除对话失败:', error) } } const sendMessage = async () => { if (!input.trim() || isLoading) return const userInput = input.trim() // 保存并清理输入内容 const userMessage: Message = { role: 'user', content: userInput, id: Date.now().toString() + Math.random().toString(36).substr(2, 9) } setMessages(prev => [...prev, userMessage]) setInput('') setIsLoading(true) try { const token = localStorage.getItem('token') const response = await fetch('/api/ai', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ prompt: userInput, // 使用保存的输入内容 conversationId: currentConversationId }) }) if (!response.ok) { throw new Error('AI服务请求失败') } const reader = response.body?.getReader() const decoder = new TextDecoder() if (!reader) { throw new Error('无法读取响应流') } setMessages(prev => [...prev, { role: 'assistant', content: '', id: Date.now().toString() + Math.random().toString(36).substr(2, 9) }]) while (true) { const { done, value } = await reader.read() if (done) break const chunk = decoder.decode(value) const lines = chunk.split('\n') for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6) if (data === '[DONE]') { // 重新加载对话列表以显示新对话 loadConversations() return } try { const parsed = JSON.parse(data) if (parsed.conversationId) { // 设置新对话ID setCurrentConversationId(parsed.conversationId) } else if (parsed.content) { console.log(parsed.content); if (parsed.content == 'next_block') { console.log("next_block"); setMessages(prev => [...prev, { role: 'assistant', content: '', id: Date.now().toString() + Math.random().toString(36).substr(2, 9) }]) continue } if (parsed.content.startsWith("show_card")) { console.log("show_card"); let comps = JSON.parse(parsed.content.split("show_card:")[1].trim()) setMessages(prev => [...prev, { role: 'card', components: comps, id: Date.now().toString() + Math.random().toString(36).substr(2, 9) }]) continue } // 使用函数式更新,确保状态更新的原子性 setMessages(prev => { const newMessages = [...prev] const lastMessageIndex = newMessages.length - 1 const lastMessage = newMessages[lastMessageIndex] if (lastMessage && lastMessage.role === 'assistant') { // 创建新的消息对象而不是直接修改 newMessages[lastMessageIndex] = { ...lastMessage, content: lastMessage.content + parsed.content } } else { console.warn('No assistant message to update') } return newMessages }) } else if (parsed.error) { throw new Error(parsed.error) } } catch (e) { console.error(e); } } } } } catch (error) { console.error('发送消息失败:', error) setMessages(prev => [ ...prev, { role: 'assistant', content: '抱歉,发生了错误,请稍后再试。', } ]) } finally { setIsLoading(false) } } const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() sendMessage() } } return (
{/* 对话历史侧边栏 */}

对话历史

{isLoadingConversations ? (
加载中...
) : conversations.length === 0 ? (
暂无对话历史
) : ( conversations.map((conversation) => (
loadConversation(conversation.id)} className="flex-1" >
{conversation.title || '新对话'}
{new Date(conversation.updatedAt).toLocaleDateString()}
)) )}
{/* 主聊天区域 */}
{/* 聊天标题 */}

PC DIY 智能助手

我可以帮助您选择和了解PC配件

{/* 消息列表 */}
{messages.length === 0 ? (
👋 您好!我是PC DIY商店的智能助手
您可以询问关于PC配件的任何问题,比如:
  • • "推荐一些价格在1000-2000元的显卡"
  • • "查找华硕品牌的主板"
  • • "什么是最新的CPU配件"
) : (messages.map((message, index) => (
{message.role == "card" ?
{message.components.map((component) => ( )) }
:
}
)) )} {isLoading && (
AI正在思考...
)}
{/* 输入区域 */}