feat: 拆分左侧栏、中间聊天区、右侧栏组件, Hooks 提取

This commit is contained in:
zdl
2025-11-24 15:23:22 +08:00
parent 41f1bbab1b
commit 5183473557
6 changed files with 740 additions and 238 deletions

View File

@@ -0,0 +1,289 @@
// src/views/AgentChat/hooks/useAgentChat.ts
// 消息处理 Hook - 发送消息、处理响应、错误处理
import { useState, useCallback } from 'react';
import type { Dispatch, SetStateAction, KeyboardEvent } from 'react';
import axios from 'axios';
import { logger } from '@utils/logger';
import { MessageTypes, type Message } from '../constants/messageTypes';
import type { UploadedFile } from './useFileUpload';
import type { User } from './useAgentSessions';
/**
* Toast 通知函数类型(来自 Chakra UI
*/
export interface ToastFunction {
(options: {
title: string;
description?: string;
status: 'success' | 'error' | 'warning' | 'info';
duration?: number;
isClosable?: boolean;
}): void;
}
/**
* useAgentChat Hook 参数
*/
export interface UseAgentChatParams {
/** 当前用户信息 */
user: User | null;
/** 当前会话 ID */
currentSessionId: string | null;
/** 设置当前会话 ID */
setCurrentSessionId: Dispatch<SetStateAction<string | null>>;
/** 选中的 AI 模型 */
selectedModel: string;
/** 选中的工具列表 */
selectedTools: string[];
/** 已上传文件列表 */
uploadedFiles: UploadedFile[];
/** 清空已上传文件 */
clearFiles: () => void;
/** Toast 通知函数 */
toast: ToastFunction;
/** 重新加载会话列表(发送消息成功后调用) */
loadSessions: () => Promise<void>;
}
/**
* useAgentChat Hook 返回值
*/
export interface UseAgentChatReturn {
/** 消息列表 */
messages: Message[];
/** 设置消息列表 */
setMessages: Dispatch<SetStateAction<Message[]>>;
/** 输入框内容 */
inputValue: string;
/** 设置输入框内容 */
setInputValue: Dispatch<SetStateAction<string>>;
/** 是否正在处理消息 */
isProcessing: boolean;
/** 发送消息 */
handleSendMessage: () => Promise<void>;
/** 键盘事件处理Enter 发送) */
handleKeyPress: (e: KeyboardEvent<HTMLInputElement>) => void;
/** 添加消息到列表 */
addMessage: (message: Partial<Message>) => void;
}
/**
* useAgentChat Hook
*
* 处理消息发送、AI 响应、错误处理逻辑
*
* @param params - UseAgentChatParams
* @returns UseAgentChatReturn
*
* @example
* ```tsx
* const {
* messages,
* inputValue,
* setInputValue,
* isProcessing,
* handleSendMessage,
* handleKeyPress,
* } = useAgentChat({
* user,
* currentSessionId,
* setCurrentSessionId,
* selectedModel,
* selectedTools,
* uploadedFiles,
* clearFiles,
* toast,
* loadSessions,
* });
* ```
*/
export const useAgentChat = ({
user,
currentSessionId,
setCurrentSessionId,
selectedModel,
selectedTools,
uploadedFiles,
clearFiles,
toast,
loadSessions,
}: UseAgentChatParams): UseAgentChatReturn => {
const [messages, setMessages] = useState<Message[]>([]);
const [inputValue, setInputValue] = useState('');
const [isProcessing, setIsProcessing] = useState(false);
/**
* 添加消息到列表
*/
const addMessage = useCallback((message: Partial<Message>) => {
setMessages((prev) => [
...prev,
{
id: Date.now() + Math.random(),
timestamp: new Date().toISOString(),
...message,
} as Message,
]);
}, []);
/**
* 发送消息到后端 API
*/
const handleSendMessage = useCallback(async () => {
if (!inputValue.trim() || isProcessing) return;
// 创建用户消息
const userMessage: Partial<Message> = {
type: MessageTypes.USER,
content: inputValue,
timestamp: new Date().toISOString(),
files: uploadedFiles.length > 0 ? uploadedFiles : undefined,
};
addMessage(userMessage);
const userInput = inputValue;
// 清空输入框和文件
setInputValue('');
clearFiles();
setIsProcessing(true);
try {
// 显示 "思考中" 状态
addMessage({
type: MessageTypes.AGENT_THINKING,
content: '正在分析你的问题...',
});
// 调用后端 API
const response = await axios.post('/mcp/agent/chat', {
message: userInput,
conversation_history: messages
.filter((m) => m.type === MessageTypes.USER || m.type === MessageTypes.AGENT_RESPONSE)
.map((m) => ({
isUser: m.type === MessageTypes.USER,
content: m.content,
})),
user_id: user?.id || 'anonymous',
user_nickname: user?.nickname || '匿名用户',
user_avatar: user?.avatar || '',
subscription_type: user?.subscription_type || 'free',
session_id: currentSessionId,
model: selectedModel,
tools: selectedTools,
files: uploadedFiles.length > 0 ? uploadedFiles : undefined,
});
// 移除 "思考中" 消息
setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_THINKING));
if (response.data.success) {
const data = response.data;
// 更新会话 ID如果是新会话
if (data.session_id && !currentSessionId) {
setCurrentSessionId(data.session_id);
}
// 显示执行计划(如果有)
if (data.plan) {
addMessage({
type: MessageTypes.AGENT_PLAN,
content: '已制定执行计划',
plan: data.plan,
});
}
// 显示执行步骤(如果有)
if (data.steps && data.steps.length > 0) {
addMessage({
type: MessageTypes.AGENT_EXECUTING,
content: '正在执行步骤...',
plan: data.plan,
stepResults: data.steps,
});
}
// 移除 "执行中" 消息
setMessages((prev) => prev.filter((m) => m.type !== MessageTypes.AGENT_EXECUTING));
// 显示最终回复
addMessage({
type: MessageTypes.AGENT_RESPONSE,
content: data.final_answer || data.message || '处理完成',
plan: data.plan,
stepResults: data.steps,
metadata: data.metadata,
});
// 重新加载会话列表
loadSessions();
}
} catch (error: any) {
logger.error('Agent chat error', error);
// 移除 "思考中" 和 "执行中" 消息
setMessages((prev) =>
prev.filter(
(m) => m.type !== MessageTypes.AGENT_THINKING && m.type !== MessageTypes.AGENT_EXECUTING
)
);
// 显示错误消息
const errorMessage = error.response?.data?.error || error.message || '处理失败';
addMessage({
type: MessageTypes.ERROR,
content: `处理失败:${errorMessage}`,
});
// 显示 Toast 通知
toast({
title: '处理失败',
description: errorMessage,
status: 'error',
duration: 5000,
});
} finally {
setIsProcessing(false);
}
}, [
inputValue,
isProcessing,
uploadedFiles,
messages,
user,
currentSessionId,
selectedModel,
selectedTools,
addMessage,
clearFiles,
setCurrentSessionId,
loadSessions,
toast,
]);
/**
* 键盘事件处理Enter 发送Shift+Enter 换行)
*/
const handleKeyPress = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
},
[handleSendMessage]
);
return {
messages,
setMessages,
inputValue,
setInputValue,
isProcessing,
handleSendMessage,
handleKeyPress,
addMessage,
};
};