初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,85 @@
|
||||
"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<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [messages]);
|
||||
|
||||
// 空会话:不展示计划 todo,仅空状态引导
|
||||
if (messages.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center p-6">
|
||||
<EmptyState
|
||||
title="镜面空明"
|
||||
description="发送一条文本消息,开启与当前人物的对话。"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 找到最后一条 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 (
|
||||
<div className="flex-1 overflow-y-auto px-4 py-6 sm:px-6">
|
||||
<div className="space-y-5">
|
||||
{messages.map((message, idx) => (
|
||||
<MessageBubble
|
||||
key={message.message_id}
|
||||
message={message}
|
||||
isLastAssistant={idx === lastAssistantIdx}
|
||||
canRegenerate={idx === lastAssistantIdx && !sending}
|
||||
onApproveToolCall={onApproveToolCall}
|
||||
onDenyToolCall={onDenyToolCall}
|
||||
onRegenerate={onRegenerate}
|
||||
characterName={characterName}
|
||||
/>
|
||||
))}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user