315 lines
9.5 KiB
TypeScript
315 lines
9.5 KiB
TypeScript
import { useParams } from 'react-router-dom'
|
||
import { useState, useEffect } from 'react'
|
||
import { Space, Alert, Spin, message } from 'antd'
|
||
import { RobotOutlined } from '@ant-design/icons'
|
||
import { agentApi, type AgentConfig } from '../api/agentApi'
|
||
import conversationApi, { type Conversation, type Message } from '../api/conversationApi'
|
||
import MessageList from '../components/chat/MessageList'
|
||
import MessageInput from '../components/chat/MessageInput'
|
||
import ModelSelector, { type ModelType } from '../components/chat/ModelSelector'
|
||
import { useProjectStore } from '../stores/useProjectStore'
|
||
import { useKnowledgeBaseStore } from '../stores/useKnowledgeBaseStore'
|
||
|
||
const AgentChatPage = () => {
|
||
const { agentId } = useParams()
|
||
const { currentProject } = useProjectStore()
|
||
const { knowledgeBases, fetchKnowledgeBases } = useKnowledgeBaseStore()
|
||
|
||
// 智能体相关状态
|
||
const [agent, setAgent] = useState<AgentConfig | null>(null)
|
||
const [agentLoading, setAgentLoading] = useState(true)
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
// 对话相关状态
|
||
const [conversation, setConversation] = useState<Conversation | null>(null)
|
||
const [messages, setMessages] = useState<Message[]>([])
|
||
const [selectedModel, setSelectedModel] = useState<ModelType>('deepseek-v3')
|
||
|
||
// 消息发送状态
|
||
const [sending, setSending] = useState(false)
|
||
const [streamingContent, setStreamingContent] = useState('')
|
||
|
||
// 加载智能体配置
|
||
useEffect(() => {
|
||
const fetchAgent = async () => {
|
||
if (!agentId) return
|
||
|
||
try {
|
||
setAgentLoading(true)
|
||
const response = await agentApi.getById(agentId)
|
||
|
||
if (response.success && response.data) {
|
||
setAgent(response.data)
|
||
} else {
|
||
setError(response.message || '智能体不存在')
|
||
}
|
||
} catch (err) {
|
||
console.error('Failed to load agent:', err)
|
||
setError('加载智能体配置失败')
|
||
message.error('加载智能体配置失败')
|
||
} finally {
|
||
setAgentLoading(false)
|
||
}
|
||
}
|
||
|
||
fetchAgent()
|
||
}, [agentId])
|
||
|
||
// 加载知识库列表
|
||
useEffect(() => {
|
||
fetchKnowledgeBases()
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [])
|
||
|
||
// 创建或加载对话
|
||
useEffect(() => {
|
||
const initConversation = async () => {
|
||
console.log('🔷 [initConversation] 开始', { agent, currentProject })
|
||
|
||
if (!agent || !currentProject) {
|
||
console.warn('⚠️ [initConversation] agent或currentProject为空,跳过')
|
||
return
|
||
}
|
||
|
||
try {
|
||
console.log('📞 [initConversation] 调用API创建对话')
|
||
// 创建新对话
|
||
const response = await conversationApi.createConversation({
|
||
projectId: currentProject.id,
|
||
agentId: agent.id,
|
||
title: `与${agent.name}的对话`,
|
||
})
|
||
|
||
console.log('✅ [initConversation] 对话创建成功', response)
|
||
console.log('📦 [initConversation] response:', response)
|
||
|
||
// response 本身就是 ApiResponse<Conversation>
|
||
// response.data 是 Conversation | undefined
|
||
if (response.data) {
|
||
console.log('💾 [initConversation] 设置conversation为:', response.data)
|
||
setConversation(response.data as any)
|
||
setMessages([])
|
||
} else {
|
||
console.error('❌ [initConversation] response.data为空')
|
||
message.error('创建对话失败:无数据返回')
|
||
}
|
||
} catch (err) {
|
||
console.error('❌ [initConversation] 创建对话失败:', err)
|
||
message.error('创建对话失败')
|
||
}
|
||
}
|
||
|
||
initConversation()
|
||
}, [agent, currentProject])
|
||
|
||
// 发送消息(流式)
|
||
const handleSendMessage = async (content: string, knowledgeBaseIds: string[]) => {
|
||
console.log('🔵 [handleSendMessage] 开始', { content, knowledgeBaseIds, conversation, sending })
|
||
|
||
if (!conversation) {
|
||
console.error('❌ [handleSendMessage] conversation为空!')
|
||
message.error('对话未初始化,请刷新页面')
|
||
return
|
||
}
|
||
|
||
if (sending) {
|
||
console.warn('⚠️ [handleSendMessage] 正在发送中,忽略本次请求')
|
||
return
|
||
}
|
||
|
||
setSending(true)
|
||
setStreamingContent('')
|
||
|
||
// 添加用户消息到列表
|
||
const userMessage: Message = {
|
||
id: `temp-${Date.now()}`,
|
||
conversationId: conversation.id,
|
||
role: 'user',
|
||
content,
|
||
createdAt: new Date().toISOString(),
|
||
}
|
||
console.log('📝 [handleSendMessage] 添加用户消息', userMessage)
|
||
setMessages(prev => {
|
||
const newMessages = [...prev, userMessage]
|
||
console.log('📋 [handleSendMessage] 更新后的消息列表', newMessages)
|
||
return newMessages
|
||
})
|
||
|
||
try {
|
||
let fullContent = ''
|
||
|
||
await conversationApi.sendMessageStream(
|
||
{
|
||
conversationId: conversation.id,
|
||
content,
|
||
modelType: selectedModel,
|
||
knowledgeBaseIds,
|
||
},
|
||
// onChunk
|
||
(chunk) => {
|
||
fullContent += chunk
|
||
setStreamingContent(fullContent)
|
||
},
|
||
// onComplete
|
||
() => {
|
||
// 流式完成后,添加完整的助手消息
|
||
const assistantMessage: Message = {
|
||
id: `temp-assistant-${Date.now()}`,
|
||
conversationId: conversation.id,
|
||
role: 'assistant',
|
||
content: fullContent,
|
||
model: selectedModel,
|
||
createdAt: new Date().toISOString(),
|
||
}
|
||
setMessages(prev => [...prev, assistantMessage])
|
||
setStreamingContent('')
|
||
setSending(false)
|
||
},
|
||
// onError
|
||
(error) => {
|
||
console.error('Stream error:', error)
|
||
message.error('发送消息失败:' + error.message)
|
||
setStreamingContent('')
|
||
setSending(false)
|
||
}
|
||
)
|
||
} catch (err) {
|
||
console.error('Failed to send message:', err)
|
||
message.error('发送消息失败')
|
||
setStreamingContent('')
|
||
setSending(false)
|
||
}
|
||
}
|
||
|
||
if (agentLoading) {
|
||
return (
|
||
<div style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
height: '100vh'
|
||
}}>
|
||
<Spin size="large" tip="加载智能体配置中...">
|
||
<div style={{ width: 200, height: 100 }} />
|
||
</Spin>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (!currentProject) {
|
||
return (
|
||
<Alert
|
||
message="请先选择项目"
|
||
description="请在侧边栏选择一个项目后再开始对话"
|
||
type="info"
|
||
showIcon
|
||
/>
|
||
)
|
||
}
|
||
|
||
if (error || !agent) {
|
||
return (
|
||
<Alert
|
||
message="智能体不存在"
|
||
description={error || "请从首页选择一个智能体"}
|
||
type="error"
|
||
showIcon
|
||
/>
|
||
)
|
||
}
|
||
|
||
if (!agent.enabled) {
|
||
return (
|
||
<Alert
|
||
message="该智能体正在开发中"
|
||
description="敬请期待后续版本..."
|
||
type="warning"
|
||
showIcon
|
||
/>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||
{/* 顶部工具栏 - 紧凑设计 */}
|
||
<div style={{
|
||
padding: '12px 24px',
|
||
background: '#fff',
|
||
borderBottom: '1px solid #e8e8e8',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
flexShrink: 0,
|
||
}}>
|
||
<Space align="center" size="middle">
|
||
<span style={{ fontSize: 24 }}>{agent.icon}</span>
|
||
<div>
|
||
<div style={{ fontSize: 16, fontWeight: 600, lineHeight: '24px' }}>
|
||
{agent.name}
|
||
</div>
|
||
<div style={{ fontSize: 12, color: '#8c8c8c', lineHeight: '20px' }}>
|
||
{agent.category} · {currentProject?.name}
|
||
</div>
|
||
</div>
|
||
</Space>
|
||
|
||
{/* 模型选择器 */}
|
||
<ModelSelector
|
||
value={selectedModel}
|
||
onChange={setSelectedModel}
|
||
disabled={sending}
|
||
/>
|
||
</div>
|
||
|
||
{/* 聊天区域 - 去掉Card,直接使用div */}
|
||
<div
|
||
style={{
|
||
flex: 1,
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
overflow: 'hidden',
|
||
minHeight: 0,
|
||
background: '#fff',
|
||
}}
|
||
>
|
||
{/* 消息列表区域 */}
|
||
<div style={{
|
||
flex: 1,
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
minHeight: 0, // 重要:确保可以滚动
|
||
overflow: 'hidden',
|
||
}}>
|
||
{messages.length === 0 && !sending ? (
|
||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#fafafa' }}>
|
||
<div style={{ textAlign: 'center', color: '#999' }}>
|
||
<RobotOutlined style={{ fontSize: 64, marginBottom: 16 }} />
|
||
<div style={{ fontSize: 16 }}>开始对话,我将为您提供专业的研究建议</div>
|
||
<div style={{ fontSize: 14, marginTop: 8 }}>
|
||
您可以直接输入问题,或使用@知识库功能引用文献
|
||
</div>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<MessageList
|
||
messages={messages}
|
||
loading={sending}
|
||
streamingContent={streamingContent}
|
||
/>
|
||
)}
|
||
</div>
|
||
|
||
{/* 消息输入 */}
|
||
<MessageInput
|
||
onSend={handleSendMessage}
|
||
loading={sending}
|
||
knowledgeBases={knowledgeBases}
|
||
placeholder={`向${agent.name}提问...(Shift+Enter换行,Enter发送)`}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default AgentChatPage
|