import type { BubbleItemType } from "@ant-design/x"; import { useChat } from "@ai-sdk/react"; import { Bubble, Sender } from "@ant-design/x"; import { DefaultChatTransport } from "ai"; import { App, Empty, Spin } from "antd"; import { useCallback, useEffect, useRef, useState } from "react"; import { fetchMessages } from "../../../../hooks/use-conversations"; import { MessageBubble } from "./MessageBubble"; interface ChatPanelProps { conversationId: null | string; onConversationCreated: (id: string) => void; projectId: string; } export function ChatPanel({ conversationId, projectId }: ChatPanelProps) { const { message } = App.useApp(); const [input, setInput] = useState(""); const [loadingHistory, setLoadingHistory] = useState(false); const fetchRef = useRef(fetchMessages); const conversationIdRef = useRef(conversationId); useEffect(() => { conversationIdRef.current = conversationId; }); const { messages, sendMessage, setMessages, status } = useChat({ onError: (err) => { void message.error(`发送失败:${err.message}`); }, transport: new DefaultChatTransport({ api: `/api/projects/${projectId}/chat`, }), }); const isLoading = status === "submitted" || status === "streaming"; useEffect(() => { if (!conversationId) { setMessages([]); return; } let cancelled = false; const load = async () => { setLoadingHistory(true); setMessages([]); try { const data = await fetchRef.current(projectId, conversationId); if (cancelled) return; const history = data.items .filter((m: { role: string }) => m.role === "user" || m.role === "assistant") .reverse() .map((m: { content: string; id: string; role: string }) => ({ id: m.id, parts: [{ text: m.content, type: "text" as const }], role: m.role as "assistant" | "user", })); setMessages(history); } catch (err: unknown) { if (!cancelled) { const msg = err instanceof Error ? err.message : String(err); void message.error(`加载历史失败:${msg}`); } } finally { if (!cancelled) setLoadingHistory(false); } }; void load(); return () => { cancelled = true; }; }, [conversationId, projectId, setMessages, message]); const bubbleItems: BubbleItemType[] = messages.map((msg) => ({ content: msg.parts .filter((p): p is { text: string; type: "text" } => p.type === "text") .map((p) => p.text) .join(""), key: msg.id, role: msg.role === "user" ? "user" : "ai", })); const onSubmit = useCallback( (nextInput: string) => { if (!nextInput.trim()) return; setInput(""); void sendMessage({ text: nextInput }, { body: { conversationId: conversationIdRef.current } }); }, [sendMessage], ); if (!conversationId) { return (