import { useState, useEffect, useRef } from 'react'; import { produce } from 'immer'; import { useBoolean, useGetState } from 'ahooks'; import { Layout, theme } from 'antd'; import ChatMessage from './chat-message'; import ChatInput from './chat-input'; import ChatSidebar, { type ChatSidebarRef } from './sidebar'; // import Header from '../layout/Header'; import useConversation from '../../hooks/use-conversation'; import useChatMessage from '../../hooks/use-chat-message'; import type { ChatItem, ConversationItem } from '../../types/dify_chat'; import { CHAT_CONFIG } from '../../config/chat'; import { fetchConversations, fetchAppParams, fetchChatList } from '../../services/api.client'; import '../../styles/components/chat-with-llm/index.css'; const { Content } = Layout; /** * 主聊天组件 * 实现单页面应用模式,参考webapp-conversation的初始化逻辑 */ export default function Chat() { // 侧边栏状态 const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const { token: { colorBgContainer, borderRadiusLG }, } = theme.useToken(); // 会话管理 const { conversationList, setConversationList, currConversationId, getCurrConversationId, setCurrConversationId, getConversationIdFromStorage, isNewConversation, currInputs, newConversationInputs, resetNewConversationInputs, setCurrInputs, currConversationInfo, setNewConversationInfo, setExistConversationInfo, addConversationToList, updateConversationInList, removeConversationFromList, } = useConversation(); // 消息管理 const { chatList, setChatList, getChatList, isResponding, handleSend, stopResponding, handleFeedback, } = useChatMessage({ onUpdateConversationList: updateConversationInList, onConversationIdChange: async (conversationId: string) => { console.log('🔄 收到会话ID变更通知:', conversationId); // 设置当前会话ID(这会触发localStorage更新) setCurrConversationId(conversationId, CHAT_CONFIG.APP_ID); // 如果是新会话,添加到会话列表 const existingConversation = conversationList.find(item => item.id === conversationId); if (!existingConversation) { console.log('🆕 添加新会话到列表:', conversationId); const newConversation = { id: conversationId, name: '新对话', inputs: currInputs || {}, introduction: '', }; addConversationToList(newConversation); // 检查是否需要自动重命名(新对话的第一条消息) if (!newConversationFirstMessageSent.has(conversationId)) { console.log('🏷️ 新对话第一条消息,准备自动重命名:', conversationId); // 标记该对话已发送第一条消息 setNewConversationFirstMessageSent(prev => new Set(prev).add(conversationId)); // 延迟一下确保组件状态已更新 setTimeout(async () => { try { if (sidebarRef.current) { await sidebarRef.current.autoRename(conversationId); console.log('✅ 新对话自动重命名完成:', conversationId); } else { console.warn('⚠️ 侧边栏引用不可用,无法自动重命名'); } } catch (error) { console.error('❌ 新对话自动重命名失败:', error); } }, 1000); } } }, }); // 应用状态 const [appUnavailable, setAppUnavailable] = useState(false); const [isUnknownReason, setIsUnknownReason] = useState(false); const [inited, setInited] = useState(false); const [promptConfig, setPromptConfig] = useState(null); // 会话状态管理 const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false); const [isChatStarted, { setTrue: setChatStarted, setFalse: setChatNotStarted }] = useBoolean(false); // 聊天列表容器引用 const chatListDomRef = useRef(null); // 侧边栏组件引用 const sidebarRef = useRef(null); // 跟踪新对话是否已发送第一条消息 const [newConversationFirstMessageSent, setNewConversationFirstMessageSent] = useState>(new Set()); // 检查应用配置 const hasSetAppConfig = CHAT_CONFIG.APP_ID && CHAT_CONFIG.API_KEY; /** * 处理开始聊天 */ const handleStartChat = (inputs: Record) => { createNewChat(); setConversationIdChangeBecauseOfNew(true); setCurrInputs(inputs); setChatStarted(); // 解析变量并生成开场白 setChatList(generateNewChatListWithOpenStatement('', inputs)); }; /** * 创建新聊天 */ const createNewChat = () => { setChatList([]); setChatNotStarted(); resetNewConversationInputs(); }; /** * 生成带开场白的新聊天列表 */ const generateNewChatListWithOpenStatement = (introduction?: string, inputs?: Record | null): ChatItem[] => { const newChatList: ChatItem[] = []; if (introduction) { newChatList.push({ id: `opening-statement-${Date.now()}`, content: introduction, isAnswer: true, isOpeningStatement: true, }); } return newChatList; }; /** * 处理会话切换 */ const handleConversationSwitch = async () => { if (!inited) { return; } console.log('🔄 处理会话切换:', { currConversationId, isNewConversation }); // 更新当前会话的输入 let notSyncToStateIntroduction = ''; let notSyncToStateInputs: Record | undefined | null = {}; if (!isNewConversation) { const item = conversationList.find(item => item.id === currConversationId); notSyncToStateInputs = item?.inputs || {}; setCurrInputs(notSyncToStateInputs as any); notSyncToStateIntroduction = item?.introduction || ''; setExistConversationInfo({ name: item?.name || '', introduction: notSyncToStateIntroduction, }); } else { notSyncToStateInputs = newConversationInputs; setCurrInputs(notSyncToStateInputs); } // 更新当前会话的聊天列表 if (!isNewConversation && !conversationIdChangeBecauseOfNew && !isResponding) { try { console.log('📨 获取会话历史消息:', currConversationId); // 调用API获取历史消息 const response = await fetchChatList(currConversationId); console.log('📋 历史消息响应:', response); if (response && (response as any).data) { const { data: historyMessages } = response as any; // 生成新的聊天列表,包含开场白 const newChatList: ChatItem[] = generateNewChatListWithOpenStatement(notSyncToStateIntroduction, notSyncToStateInputs); // 添加历史消息 historyMessages.forEach((item: any) => { // 添加用户问题 newChatList.push({ id: `question-${item.id}`, content: item.query, isAnswer: false, message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [], }); // 添加AI回答 newChatList.push({ id: item.id, content: item.answer, agent_thoughts: item.agent_thoughts || [], feedback: item.feedback, isAnswer: true, message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], }); }); console.log('✅ 设置历史聊天列表:', newChatList.length, '条消息'); setChatList(newChatList); } else { console.warn('⚠️ 获取历史消息失败或无数据'); // 如果获取失败,至少显示开场白 const newChatList: ChatItem[] = generateNewChatListWithOpenStatement(notSyncToStateIntroduction, notSyncToStateInputs); setChatList(newChatList); } } catch (error) { console.error('❌ 获取历史消息失败:', error); // 如果获取失败,至少显示开场白 const newChatList: ChatItem[] = generateNewChatListWithOpenStatement(notSyncToStateIntroduction, notSyncToStateInputs); setChatList(newChatList); } } if (isNewConversation && isChatStarted) { setChatList(generateNewChatListWithOpenStatement()); } }; /** * 处理会话ID变化 */ const handleConversationIdChange = (id: string) => { console.log('🔄 会话ID变化:', { id, currentId: currConversationId }); if (id === '-1') { createNewChat(); setConversationIdChangeBecauseOfNew(true); } else { setConversationIdChangeBecauseOfNew(false); } // 触发会话切换 setCurrConversationId(id, CHAT_CONFIG.APP_ID); }; /** * 检查是否可以发送消息 */ const checkCanSend = () => { if (currConversationId !== '-1') { return true; } if (!currInputs || !promptConfig?.prompt_variables) { return true; } const inputLens = Object.values(currInputs).length; const promptVariablesLens = promptConfig.prompt_variables.length; const emptyInput = inputLens < promptVariablesLens || Object.values(currInputs).find(v => !v); if (emptyInput) { console.error('必填变量值不能为空'); return false; } return true; }; /** * 处理发送消息 */ const handleSendMessage = async (message: string, files?: any[]) => { if (isResponding) { console.log('正在响应中,请等待...'); return; } if (!checkCanSend()) { return; } // console.log('📤 发送消息:', { message, conversationId: currConversationId }); try { // 准备输入数据 const toServerInputs: Record = {}; if (currInputs) { Object.keys(currInputs).forEach((key) => { toServerInputs[key] = currInputs[key]; }); } // 使用 useChatMessage 钩子的 handleSend 方法 await handleSend( message, isNewConversation ? null : currConversationId, files, toServerInputs ); } catch (error) { console.error('发送消息失败:', error); } }; /** * 处理侧边栏切换 */ const handleSidebarToggle = () => { setSidebarCollapsed(!sidebarCollapsed); }; /** * 处理会话选择 */ const handleConversationSelect = (conversationId: string) => { if (conversationId !== currConversationId) { setCurrConversationId(conversationId, CHAT_CONFIG.APP_ID); } }; /** * 处理新建会话 */ const handleNewConversation = () => { setCurrConversationId('-1', CHAT_CONFIG.APP_ID, false); createNewChat(); }; /** * 处理会话删除后的状态更新 */ const handleConversationDeleted = (conversationId: string) => { console.log('🗑️ 处理会话删除后的状态更新:', conversationId); // 如果删除的是当前会话,切换到新会话 if (conversationId === currConversationId) { handleNewConversation(); } // 从列表中移除会话 removeConversationFromList(conversationId); console.log('✅ 会话删除状态更新完成:', conversationId); }; /** * 处理会话重命名后的状态更新 */ const handleConversationRenamed = (conversationId: string, newName: string) => { console.log('✏️ 处理会话重命名后的状态更新:', { conversationId, newName }); // 更新本地会话列表中的名称 updateConversationInList(conversationId, { name: newName }); console.log('✅ 会话重命名状态更新完成:', conversationId, '->', newName); }; /** * 组件初始化 - 参考webapp-conversation的逻辑 */ useEffect(() => { if (!hasSetAppConfig) { console.error('应用配置不完整'); setAppUnavailable(true); return; } (async () => { try { console.log('🚀 开始初始化聊天应用...'); // 并行获取会话列表和应用参数 const [conversationData, appParams] = await Promise.all([ fetchConversations(), fetchAppParams() ]); console.log('📋 获取到的数据:', { conversationData, appParams }); // 处理会话数据 const conversations = (conversationData as any).data || []; if ((conversationData as any).error) { console.error('获取会话列表失败:', (conversationData as any).error); throw new Error((conversationData as any).error); } // 处理当前会话ID const _conversationId = getConversationIdFromStorage(CHAT_CONFIG.APP_ID); const isNotNewConversation = conversations.some((item: ConversationItem) => item.id === _conversationId); console.log('💾 本地存储的会话ID:', _conversationId); console.log('🔍 是否为已存在的会话:', isNotNewConversation); // 获取新会话信息 const { user_input_form, opening_statement: introduction } = (appParams as any).data || {}; setNewConversationInfo({ name: '新对话', introduction: introduction || '', }); // 设置提示配置 setPromptConfig({ prompt_template: '', prompt_variables: user_input_form || [], }); // 设置会话列表 setConversationList(conversations); // 如果存在有效的会话ID,则设置为当前会话 if (isNotNewConversation) { console.log('🎯 设置当前会话ID:', _conversationId); setCurrConversationId(_conversationId, CHAT_CONFIG.APP_ID, false); } else { // 如果localStorage为空或会话不存在,自动创建新会话 console.log('🆕 localStorage为空或会话不存在,创建新会话'); setCurrConversationId('-1', CHAT_CONFIG.APP_ID, false); } setInited(true); console.log('✅ 聊天应用初始化完成'); } catch (e: any) { console.error('❌ 初始化失败:', e); if (e.status === 404) { setAppUnavailable(true); } else { setIsUnknownReason(true); setAppUnavailable(true); } } })(); }, []); // 监听会话切换 useEffect(() => { handleConversationSwitch(); }, [currConversationId, inited]); // 自动滚动到底部 useEffect(() => { if (chatListDomRef.current) { chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight; } }, [chatList]); // 如果应用不可用,显示错误页面 if (appUnavailable) { return (

应用暂时不可用

{isUnknownReason ? '发生了未知错误,请稍后重试' : '应用配置不正确,请检查配置'}

); } // 如果未初始化完成,显示加载状态 if (!inited) { return (

正在加载...

); } // 判断是否已设置输入 const hasSetInputs = (() => { if (!isNewConversation) { return true; } return isChatStarted; })(); const conversationName = currConversationInfo?.name || '新对话'; const conversationIntroduction = currConversationInfo?.introduction || ''; return ( {/* 侧边栏 */} {/* 主内容区域 */} {/* 聊天区域 */}
{/* 如果是新会话且未开始聊天,显示欢迎信息 */} {isNewConversation && !isChatStarted && (

开始新的对话

请在下方输入您的问题

)} {/* 聊天消息列表 */} {chatList.map((item) => ( ))}
{/* 输入区域 */}
); }