feat(web): 添加全局 Toast 通知系统和资产预览导航功能
- 新增 ToastProvider 和 useToast hook,支持全局成功/错误/信息提示 - 资产预览增加左右导航按钮、键盘快捷键(方向键)和计数器显示 - 资产库增加图片/视频类型筛选标签页及计数 - 对话列表增加最近对话展示、搜索优化和删除确认 - 消息增加删除确认对话框 - 优化聊天自动滚动行为,仅在用户未手动滚动时跟随新内容 - 新增删除消息 API 端点 - 优化消息历史清理逻辑,过滤错误消息和孤儿 tool 消息 - 添加自定义滚动条样式 - 优化账户参考图显示逻辑,支持本地文件显示 - 修复对话创建流程,直接导航到新创建的对话
This commit is contained in:
@@ -16,10 +16,13 @@ export function useChat(conversationId: string | null) {
|
||||
const [thinking, setThinking] = useState(false);
|
||||
const [toolStatus, setToolStatus] = useState<string | null>(null);
|
||||
const [pipeline, setPipeline] = useState<PipelineState | null>(null);
|
||||
const initRef = useRef(false);
|
||||
|
||||
// Track which conversation is currently loaded via WS
|
||||
const activeConvRef = useRef<string | null>(null);
|
||||
// Pending conversation creation resolve
|
||||
const createResolveRef = useRef<((id: string) => void) | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Bind all event handlers once
|
||||
const onConnected = () => setConnected(true);
|
||||
const onHistory = (data: Record<string, unknown>) => {
|
||||
setMessages((data.messages as Message[]) || []);
|
||||
@@ -82,6 +85,14 @@ export function useChat(conversationId: string | null) {
|
||||
status: data.status as string | undefined,
|
||||
});
|
||||
};
|
||||
const onConversationCreated = (data: Record<string, unknown>) => {
|
||||
const id = data.id as string;
|
||||
activeConvRef.current = id;
|
||||
if (createResolveRef.current) {
|
||||
createResolveRef.current(id);
|
||||
createResolveRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
chatSocket.on('connected', onConnected);
|
||||
chatSocket.on('history', onHistory);
|
||||
@@ -95,6 +106,7 @@ export function useChat(conversationId: string | null) {
|
||||
chatSocket.on('tool_result', onToolResult);
|
||||
chatSocket.on('tool_error', onToolError);
|
||||
chatSocket.on('pipeline_progress', onPipelineProgress);
|
||||
chatSocket.on('conversation_created', onConversationCreated);
|
||||
chatSocket.connect();
|
||||
|
||||
return () => {
|
||||
@@ -110,19 +122,30 @@ export function useChat(conversationId: string | null) {
|
||||
chatSocket.off('tool_result', onToolResult);
|
||||
chatSocket.off('tool_error', onToolError);
|
||||
chatSocket.off('pipeline_progress', onPipelineProgress);
|
||||
chatSocket.off('conversation_created', onConversationCreated);
|
||||
chatSocket.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Load conversation when ID changes
|
||||
// Init/re-init when conversationId changes
|
||||
useEffect(() => {
|
||||
if (conversationId && connected && !initRef.current) {
|
||||
initRef.current = true;
|
||||
if (!connected) return;
|
||||
|
||||
if (conversationId && conversationId !== activeConvRef.current) {
|
||||
activeConvRef.current = conversationId;
|
||||
setMessages([]);
|
||||
setThinking(false);
|
||||
setToolStatus(null);
|
||||
setPipeline(null);
|
||||
chatSocket.send('init', { conversationId });
|
||||
}
|
||||
|
||||
if (!conversationId) {
|
||||
initRef.current = false;
|
||||
activeConvRef.current = null;
|
||||
setMessages([]);
|
||||
setThinking(false);
|
||||
setToolStatus(null);
|
||||
setPipeline(null);
|
||||
}
|
||||
}, [conversationId, connected]);
|
||||
|
||||
@@ -134,12 +157,23 @@ export function useChat(conversationId: string | null) {
|
||||
chatSocket.stop();
|
||||
}, []);
|
||||
|
||||
const createConversation = useCallback((title: string, accountId?: string) => {
|
||||
chatSocket.send('create_conversation', { title, accountId });
|
||||
const createConversation = useCallback((title: string, accountId?: string): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
createResolveRef.current = resolve;
|
||||
chatSocket.send('create_conversation', { title, accountId });
|
||||
// Timeout fallback
|
||||
setTimeout(() => {
|
||||
if (createResolveRef.current) {
|
||||
createResolveRef.current = null;
|
||||
resolve('');
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeMessage = useCallback((msgId: string) => {
|
||||
const removeMessage = useCallback(async (msgId: string) => {
|
||||
setMessages((prev) => prev.filter((m) => m.id !== msgId));
|
||||
try { await fetch(`/api/pipeline/messages/${msgId}`, { method: 'DELETE' }); } catch {}
|
||||
}, []);
|
||||
|
||||
return { messages, connected, thinking, toolStatus, pipeline, send, stop, createConversation, removeMessage };
|
||||
|
||||
Reference in New Issue
Block a user