import { useState, useEffect, useRef } from 'react' import { Layout, Select, Button, Input, Empty, Card, Space, Spin, Message, Divider, Tag, Modal, Form } from 'antd' import { SendOutlined, DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons' import { getMyProjects } from '@/api/project' import { getLLMModelConfigs } from '@/api/llmModelConfigs' import { createChatSession, getChatSessions, getChatMessages, sendChatMessage, deleteChatSession, updateChatSessionTitle, } from '@/api/chat' import './Chat.css' const { Sider, Content } = Layout function Chat() { const [projects, setProjects] = useState([]) const [llmConfigs, setLlmConfigs] = useState([]) const [selectedProject, setSelectedProject] = useState(null) const [selectedModel, setSelectedModel] = useState(null) const [sessions, setSessions] = useState([]) const [currentSession, setCurrentSession] = useState(null) const [messages, setMessages] = useState([]) const [loading, setLoading] = useState(false) const [sessionLoading, setSessionLoading] = useState(false) const [messageLoading, setMessageLoading] = useState(false) const [inputValue, setInputValue] = useState('') const messagesEndRef = useRef(null) const [editingSessionId, setEditingSessionId] = useState(null) const [editingTitle, setEditingTitle] = useState('') useEffect(() => { fetchProjects() fetchLlmConfigs() }, []) const fetchProjects = async () => { try { const res = await getMyProjects() setProjects(res.data?.data || []) } catch (error) { console.error('Failed to fetch projects:', error) } } const fetchLlmConfigs = async () => { try { const res = await getLLMModelConfigs() setLlmConfigs(res.data?.data || []) if (res.data?.data?.length > 0) { setSelectedModel(res.data.data[0].config_id) } } catch (error) { console.error('Failed to fetch LLM configs:', error) } } const fetchSessions = async (projectId) => { if (!projectId) return setSessionLoading(true) try { const res = await getChatSessions(projectId) setSessions(res.data?.data || []) if (res.data?.data?.length > 0) { setCurrentSession(res.data.data[0]) fetchMessages(res.data.data[0].session_id) } else { setMessages([]) setCurrentSession(null) } } catch (error) { console.error('Failed to fetch sessions:', error) } finally { setSessionLoading(false) } } const fetchMessages = async (sessionId) => { setMessageLoading(true) try { const res = await getChatMessages(sessionId) setMessages(res.data?.data || []) setTimeout(scrollToBottom, 100) } catch (error) { console.error('Failed to fetch messages:', error) } finally { setMessageLoading(false) } } const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) } const handleProjectChange = (projectId) => { setSelectedProject(projectId) setCurrentSession(null) setMessages([]) fetchSessions(projectId) } const handleCreateSession = async () => { if (!selectedProject || !selectedModel) { Message.warning('请先选择项目和模型') return } setLoading(true) try { const res = await createChatSession(selectedProject, selectedModel) const newSession = res.data?.data setSessions([newSession, ...sessions]) setCurrentSession(newSession) setMessages([]) } catch (error) { Message.error('创建会话失败') console.error(error) } finally { setLoading(false) } } const handleSendMessage = async () => { if (!inputValue.trim() || !currentSession) { return } const userMsg = inputValue setInputValue('') setMessageLoading(true) try { const res = await sendChatMessage(currentSession.session_id, userMsg) const { user_message, assistant_message } = res.data?.data || {} const newMessages = [ ...messages, { id: messages.length + 1, role: 'user', content: user_message, created_at: new Date().toISOString(), }, { id: messages.length + 2, role: 'assistant', content: assistant_message, created_at: new Date().toISOString(), }, ] setMessages(newMessages) setTimeout(scrollToBottom, 100) } catch (error) { Message.error('发送消息失败') setInputValue(userMsg) console.error(error) } finally { setMessageLoading(false) } } const handleDeleteSession = (sessionId) => { Modal.confirm({ title: '确认删除', content: '确定要删除这个对话会话吗?', onOk: async () => { try { await deleteChatSession(sessionId) setSessions(sessions.filter(s => s.session_id !== sessionId)) if (currentSession?.session_id === sessionId) { setCurrentSession(null) setMessages([]) } Message.success('会话已删除') } catch (error) { Message.error('删除失败') console.error(error) } }, }) } const handleEditTitle = (session) => { setEditingSessionId(session.session_id) setEditingTitle(session.title || `对话 ${session.session_id}`) } const handleSaveTitle = async () => { if (!editingSessionId || !editingTitle.trim()) { return } try { await updateChatSessionTitle(editingSessionId, editingTitle) setSessions(sessions.map(s => s.session_id === editingSessionId ? { ...s, title: editingTitle } : s )) setEditingSessionId(null) Message.success('标题已更新') } catch (error) { Message.error('更新失败') console.error(error) } } const currentModel = llmConfigs.find(m => m.config_id === selectedModel) return (
项目
({ label: m.model_name, value: m.config_id }))} />
{sessionLoading ? ( ) : sessions.length === 0 ? ( ) : ( sessions.map(session => ( { setCurrentSession(session) fetchMessages(session.session_id) }} style={{ marginBottom: '8px', cursor: 'pointer', background: currentSession?.session_id === session.session_id ? '#e6f7ff' : '#fff', border: currentSession?.session_id === session.session_id ? '1px solid #1890ff' : '1px solid #d9d9d9', }} >
{editingSessionId === session.session_id ? ( setEditingTitle(e.target.value)} size="small" onBlur={handleSaveTitle} onPressEnter={handleSaveTitle} autoFocus /> ) : (
{session.title || `对话 ${session.session_id}`}
)}
{new Date(session.created_at).toLocaleDateString()}
{ e.stopPropagation() handleEditTitle(session) }} /> { e.stopPropagation() handleDeleteSession(session.session_id) }} />
)) )}
{!currentSession ? ( ) : ( <>
{currentSession.title || `对话 ${currentSession.session_id}`}
{currentModel && ( {currentModel.model_name} )}
{messageLoading ? ( ) : messages.length === 0 ? ( ) : ( messages.map((msg) => (
{msg.content}
)) )}
setInputValue(e.target.value)} onPressEnter={handleSendMessage} disabled={messageLoading} autoFocus />
)} ) } export default Chat