import { useEffect, useState, useCallback, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useAppStore } from '@/store'; import { useChat } from '@/hooks/useChat'; import { ChatMessage } from './ChatMessage'; import { ChatInput } from './ChatInput'; import { PipelineProgress } from './PipelineProgress'; import { Button } from '@/components/ui/button'; import { RefreshCw, Loader2, StopCircle, X } from 'lucide-react'; import { api } from '@/lib/api'; import type { Account, Message } from '@/types'; export function ChatView() { const { conversationId } = useParams<{ conversationId?: string }>(); const navigate = useNavigate(); const { setConversations, selectedAccountId } = useAppStore(); const { messages, connected, thinking, toolStatus, pipeline, send, stop, createConversation, removeMessage } = useChat(conversationId || null); const [manifestPath, setManifestPath] = useState(null); const [accounts, setAccounts] = useState([]); const [quote, setQuote] = useState(null); const [pendingMessage, setPendingMessage] = useState(null); const creatingRef = useRef(false); useEffect(() => { api.listAccounts().then(setAccounts).catch(() => {}); }, []); useEffect(() => { api.listConversations().then(setConversations).catch(() => {}); }, [messages]); useEffect(() => { const toolMsgs = messages.filter((m) => m.role === 'tool'); if (toolMsgs.length > 0) { try { const lastTool = JSON.parse(toolMsgs[toolMsgs.length - 1].content); if (lastTool.manifest) setManifestPath(lastTool.manifest); } catch {} } }, [messages]); // After navigating to a new conversation, send the pending message useEffect(() => { if (conversationId && connected && pendingMessage && messages.length === 0) { send(pendingMessage); setPendingMessage(null); } }, [conversationId, connected, pendingMessage]); const handleResume = async () => { if (!manifestPath) return; await fetch('/api/pipeline/resume', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ manifest: manifestPath }), }); }; const findPrecedingUser = useCallback((msgId: string): Message | null => { const idx = messages.findIndex((m) => m.id === msgId); if (idx <= 0) return null; for (let i = idx - 1; i >= 0; i--) { if (messages[i].role === 'user') return messages[i]; } return null; }, [messages]); const handleRegenerate = useCallback((msgId: string) => { const prev = findPrecedingUser(msgId); if (prev) send(prev.content); }, [findPrecedingUser, send]); const handleContinue = useCallback(() => { send('请继续'); }, [send]); const handleQuote = useCallback((content: string) => { setQuote(content.slice(0, 200)); }, []); const handleDeleteMsg = useCallback((msgId: string) => { removeMessage(msgId); }, [removeMessage]); // Delayed conversation creation const handleSendNew = useCallback(async (content: string) => { if (creatingRef.current) return; creatingRef.current = true; setPendingMessage(content); try { createConversation(content.slice(0, 30), selectedAccountId || undefined); setTimeout(async () => { const list = await api.listConversations(); setConversations(list); if (list.length > 0) { navigate(`/chat/${list[0].id}`); } creatingRef.current = false; }, 600); } catch { creatingRef.current = false; } }, [createConversation, selectedAccountId]); const handleSend = useCallback((content: string) => { if (quote) { content = `> ${quote}\n\n${content}`; setQuote(null); } if (conversationId) { send(content); } else { handleSendNew(content); } }, [send, quote, conversationId]); const handleStop = useCallback(() => { stop(); }, [stop]); // Empty state - no conversation selected if (!conversationId) { return (
💬

开始新对话

输入消息开始创作,对话将自动创建

{accounts.length > 0 && (
当前账号:
)}
); } // Active conversation return (
{/* Header bar */}
{connected ? '在线' : '连接中'}
{manifestPath && ( )} {thinking && ( )}
{/* Chat area */}
{/* Message list */}
{messages.map((msg, i) => ( msg.role !== 'tool' && ( ) ))} {pipeline && ( )} {thinking && !pipeline && (
{toolStatus || '思考中...'}
)}
{/* Quote bar */} {quote && (
引用: {quote}
)} {/* Input */}
); }