"use client"; import { useEffect, useRef } from "react"; import { EmptyState } from "@/components/shared/StateViews"; import { MessageBubble } from "./MessageBubble"; import { useChatStore } from "@/lib/stores/chat-store"; /** 工具确认回调签名:传 run_id / confirmation_id / tool_call_id 供端点调用 */ export type ToolConfirmationHandler = ( runId: string, confirmationId: string, toolCallId: string, ) => void; /** * 消息列表:自管理滚动到底。 * * 计划 todo(SessionPlanCard)暂不挂载——当前阶段(implementation-status.md §8)不实现 * 自动长期任务规划,AI 单次回复内的推演已由回复气泡的「思考过程」承担。待后端 * plan.* 运行事件落地(planner-design.md §5)后,再于「AI 实际产出计划」时挂载, * 无计划则不渲染(而非用 mock 常驻显示)。 */ export function MessageList({ onApproveToolCall, onDenyToolCall, onRegenerate, characterName, }: { onApproveToolCall?: ToolConfirmationHandler; onDenyToolCall?: ToolConfirmationHandler; onRegenerate?: () => void; characterName?: string; } = {}) { const messages = useChatStore((s) => s.messages); const sending = useChatStore((s) => s.sending); const bottomRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); // 空会话:不展示计划 todo,仅空状态引导 if (messages.length === 0) { return (
); } // 找到最后一条 assistant 消息的索引:用于在该气泡下显示「重新回答」按钮。 // 发送中(sending)时正在生成新的助手消息,此时不显示该按钮——由 MessageBubble // 内部根据 isLastAssistant && !sending 综合判定。 let lastAssistantIdx = -1; for (let i = messages.length - 1; i >= 0; i--) { if (messages[i].role === "assistant") { lastAssistantIdx = i; break; } } return (
{messages.map((message, idx) => ( ))}
); }