import React, { useState, useRef, useEffect } from 'react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import { Input, Button, Typography, Spin, message, Layout, Tooltip, Input as AntInput, Modal, Upload, Dropdown, Space, Select, } from 'antd' import { ArrowUpOutlined, LinuxOutlined, RobotOutlined, LoadingOutlined, CopyOutlined, ReloadOutlined, PaperClipOutlined, } from '@ant-design/icons' import { callDeepSeekAPI, handleAPIError } from './models/api' import { conversationStore } from '@/utils/pageConversationStore' import './ChatConversation.less' const { Text } = Typography const { TextArea } = Input const { Sider, Content } = Layout function ChatConversation() { // 会话相关状态 - 使用独立的存储 const [conversations, setConversations] = useState(conversationStore.getConversations()) const [currentConversationId, setCurrentConversationId] = useState(conversationStore.getCurrentConversationId()) const [inputValue, setInputValue] = useState('') const [loading, setLoading] = useState(false) const [sidebarExpanded, setSidebarExpanded] = useState(false) const [editModalVisible, setEditModalVisible] = useState(false) const [editingConversation, setEditingConversation] = useState(null) const [newTitle, setNewTitle] = useState('') const messagesEndRef = useRef(null) const inputRef = useRef(null) // 获取当前会话 const currentConversation = conversations.find(conv => conv.id === currentConversationId) const currentMessages = currentConversation?.messages || [] // 自动滚动到底部 const chatScrollRef = useRef(null); useEffect(() => { if (chatScrollRef.current) { chatScrollRef.current.scrollTop = chatScrollRef.current.scrollHeight; } }, [currentMessages, loading]); // currentMessages 是你的消息数组 const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) } const handleChange = value => { console.log(`selected ${value}`); } // 订阅对话状态变化 useEffect(() => { const unsubscribe = conversationStore.subscribe(({ conversations, currentConversationId }) => { setConversations(conversations) setCurrentConversationId(currentConversationId) }) return unsubscribe }, []) useEffect(() => { scrollToBottom() }, [currentMessages]) // 创建新对话 const createNewConversation = () => { conversationStore.createNewConversation() setInputValue('') } // 切换对话 const switchConversation = (conversationId) => { conversationStore.switchConversation(conversationId) setInputValue('') } // 删除对话 const deleteConversation = (conversationId) => { if (conversations.length <= 1) { message.warning('至少需要保留一个对话') return } conversationStore.deleteConversation(conversationId) } // 编辑对话标题 const editConversationTitle = (conversation) => { setEditingConversation(conversation) setNewTitle(conversation.title) setEditModalVisible(true) } // 保存对话标题 const saveConversationTitle = () => { if (!newTitle.trim()) { message.warning('标题不能为空') return } conversationStore.updateConversationTitle(editingConversation.id, newTitle.trim()) setEditModalVisible(false) setEditingConversation(null) setNewTitle('') } // 发送消息 const sendMessage = async () => { const messageText = inputValue.trim() if (!messageText || loading) return // 添加用户消息到独立存储 conversationStore.addMessage('user', messageText) setInputValue('') setLoading(true) try { // 准备发送给API的消息历史 const apiMessages = currentMessages.map(msg => ({ role: msg.role, content: msg.content })).concat({ role: 'user', content: messageText }) // 调用DeepSeek API const assistantResponse = await callDeepSeekAPI(apiMessages) // 添加助手回复到独立存储 conversationStore.addMessage('assistant', assistantResponse) } catch (error) { const errorMessage = handleAPIError(error) message.error(errorMessage) // 添加错误消息到独立存储 conversationStore.addMessage('assistant', `抱歉,我遇到了一个问题:${errorMessage}`) } finally { setLoading(false) } } // 处理回车键发送 const handleKeyPress = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() sendMessage() } } // 清空当前对话 const clearCurrentChat = () => { conversationStore.clearCurrentConversation() } // 兼容剪贴板 const copyToClipboard = async (text) => { if (!text) return Promise.reject(new Error("exmpty text")) // 现代 API,需 HTTPS 或 localhost(window.isSecureContext) if (navigator.clipboard && window.isSecureContext) { try { await navigator.clipboard.writeText(text) return } catch (e) { // 若失败,继续使用回退方案 console.warn('navigator.clipboard failed, fallback to execCommand', e) } } // 回退方案 return new Promise((resolve, reject) => { try { const ta = document.createElement('textarea') ta.value = text ta.setAttribute('readonly', '') ta.style.position = 'fixed' ta.style.left = '-9999px' document.body.appendChild(ta) ta.select() ta.setSelectionRange(0, ta.value.length) const ok = document.execCommand('copy') document.body.removeChild(ta) if (ok) resolve() else reject(new Error('execCommand failed')) } catch (err) { reject(err) } }) } // 渲染单条消息 ================================== const renderMessage = (msg, index) => { const isUser = msg.role === 'user' return (
提出一个问题,比如:请帮我总结一段文本。