feat: 拆分左侧栏、中间聊天区、右侧栏组件, Hooks 提取
This commit is contained in:
289
src/views/AgentChat/hooks/useAgentChat.ts
Normal file
289
src/views/AgentChat/hooks/useAgentChat.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user