"use client"; import { CheckCircle2, ChevronDown, HelpCircle, ListChecks, ListTree, LockKeyhole, ShieldCheck, XCircle, type LucideIcon } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { useState, type ReactNode } from "react"; import { cn } from "@/lib/utils"; import { Button } from "@/shared/ui/button"; import { agentEase, agentLayoutTransition, agentPanelItemVariants, agentPanelListVariants, agentPanelSectionVariants } from "./agent-motion"; import type { AgentChatMessage, AgentChatProgress, AgentPermissionReply, AgentPermissionRequest, AgentQuestionInfo, AgentQuestionRequest, AgentTodoUpdate } from "../types"; type ProgressStatus = AgentChatProgress["status"]; type TodoStatus = AgentTodoUpdate["todos"][number]["status"]; type PermissionStatus = AgentPermissionRequest["status"]; type QuestionStatus = AgentQuestionRequest["status"]; const progressStatusMeta: Record = { running: { label: "进行中", className: "bg-blue-100 text-blue-700", dotClassName: "border-blue-500 bg-blue-100" }, completed: { label: "完成", className: "bg-emerald-100 text-emerald-700", dotClassName: "border-emerald-500 bg-emerald-100" }, error: { label: "失败", className: "bg-red-100 text-red-700", dotClassName: "border-red-500 bg-red-100" } }; const todoStatusMeta: Record = { completed: { label: "完成", className: "bg-emerald-100 text-emerald-700", dotClassName: "border-emerald-500 bg-emerald-500" }, in_progress: { label: "进行中", className: "bg-blue-100 text-blue-700", dotClassName: "border-blue-500 bg-blue-100" }, pending: { label: "待办", className: "bg-slate-100 text-slate-600", dotClassName: "border-slate-300 bg-white" }, cancelled: { label: "取消", className: "bg-slate-200 text-slate-500", dotClassName: "border-slate-300 bg-slate-200" } }; const permissionStatusMeta: Record = { approved_always: { label: "已始终允许", className: "bg-emerald-100 text-emerald-700" }, approved_once: { label: "已允许一次", className: "bg-emerald-100 text-emerald-700" }, rejected: { label: "已拒绝", className: "bg-red-100 text-red-700" }, aborted: { label: "已中止", className: "bg-slate-200 text-slate-500" }, error: { label: "失败", className: "bg-red-100 text-red-700" }, submitting: { label: "提交中", className: "bg-blue-100 text-blue-700" }, pending: { label: "待确认", className: "bg-orange-100 text-orange-700" } }; const questionStatusMeta: Record = { answered: { label: "已回答", className: "bg-emerald-100 text-emerald-700" }, rejected: { label: "已跳过", className: "bg-slate-200 text-slate-500" }, error: { label: "失败", className: "bg-red-100 text-red-700" }, submitting: { label: "提交中", className: "bg-blue-100 text-blue-700" }, pending: { label: "待回答", className: "bg-blue-100 text-blue-700" } }; export function AgentMessageDetails({ message, onReplyPermission, onReplyQuestion, onRejectQuestion }: { message: AgentChatMessage; onReplyPermission?: (permission: AgentPermissionRequest, reply: AgentPermissionReply) => Promise | void; onReplyQuestion?: (request: AgentQuestionRequest, answers: string[][]) => Promise | void; onRejectQuestion?: (request: AgentQuestionRequest) => Promise | void; }) { const hasDetails = Boolean( message.todos?.todos.length || message.permissions?.length || message.questions?.length ); if (!hasDetails) { return null; } return ( {message.todos ? ( ) : null} {message.permissions?.length ? ( ) : null} {message.questions?.length ? ( ) : null} ); } export function AgentMessageProgress({ progress, running }: { progress?: AgentChatProgress[]; running: boolean; }) { if (!progress?.length) { return null; } return ; } function AgentNestedBlock({ icon: Icon, iconClassName, title, children, floating = false }: { icon: LucideIcon; iconClassName: string; title: string; children: ReactNode; floating?: boolean; }) { return (
{children}
); } function AnimatedDetailItem({ children }: { children: ReactNode }) { return ( {children} ); } function ProgressList({ progress, running }: { progress: AgentChatProgress[]; running: boolean }) { const [expanded, setExpanded] = useState(false); const visibleProgress = expanded ? progress : progress.slice(-1); const listMode = expanded ? "expanded" : "summary"; return (
{visibleProgress.length ? ( {visibleProgress.map((item) => ( ))} ) : null}
); } function TodoCard({ todoUpdate }: { todoUpdate: AgentTodoUpdate }) { return ( {todoUpdate.todos.slice(0, 6).map((todo) => ( {todo.status === "completed" ? {todo.content} {todoStatusMeta[todo.status].label} ))} ); } function PermissionList({ permissions, onReplyPermission }: { permissions: AgentPermissionRequest[]; onReplyPermission?: (permission: AgentPermissionRequest, reply: AgentPermissionReply) => Promise | void; }) { return ( {permissions.slice(-4).map((permission) => ( ))} ); } function PermissionRequestCard({ permission, onReplyPermission }: { permission: AgentPermissionRequest; onReplyPermission?: (permission: AgentPermissionRequest, reply: AgentPermissionReply) => Promise | void; }) { const actionable = permission.status === "pending" || permission.status === "error"; const submitting = permission.status === "submitting"; const canApproveAlways = permission.always.length > 0; const disabled = !onReplyPermission || submitting; const target = permission.target ?? (permission.patterns.join(" ") || permission.permission); const reply = (nextReply: AgentPermissionReply) => { if (!onReplyPermission || submitting) { return; } void Promise.resolve(onReplyPermission(permission, nextReply)); }; return (

{getPermissionTitle(permission)}

{target}

{permission.error ? (

{permission.error}

) : null}
{permissionStatusMeta[permission.status].label}
{actionable ? ( {canApproveAlways ? ( ) : null} ) : null}
); } function QuestionList({ questions, onReplyQuestion, onRejectQuestion }: { questions: AgentQuestionRequest[]; onReplyQuestion?: (request: AgentQuestionRequest, answers: string[][]) => Promise | void; onRejectQuestion?: (request: AgentQuestionRequest) => Promise | void; }) { return ( {questions.slice(-3).map((request) => ( ))} ); } function QuestionRequestCard({ request, onReplyQuestion, onRejectQuestion }: { request: AgentQuestionRequest; onReplyQuestion?: (request: AgentQuestionRequest, answers: string[][]) => Promise | void; onRejectQuestion?: (request: AgentQuestionRequest) => Promise | void; }) { const [draftAnswers, setDraftAnswers] = useState(() => createInitialQuestionAnswers(request)); const actionable = request.status === "pending" || request.status === "error"; const submitting = request.status === "submitting"; const disabled = submitting || !onReplyQuestion; const answers = normalizeDraftAnswers(draftAnswers); const canSubmit = actionable && answers.length === request.questions.length && answers.every((answer) => answer.length > 0); const submit = () => { if (!onReplyQuestion || !canSubmit) { return; } void Promise.resolve(onReplyQuestion(request, answers)); }; const reject = () => { if (!onRejectQuestion || submitting) { return; } void Promise.resolve(onRejectQuestion(request)); }; return (
{request.questions[0]?.header || "问题"} {questionStatusMeta[request.status].label}
{request.questions.map((question, questionIndex) => ( setDraftAnswers((current) => current.map((item, index) => (index === questionIndex ? nextValue : item))) } /> ))}
{request.error ? (

{request.error}

) : null} {request.answers?.length ? (

answer.join("、")).join(";")}> 已答:{request.answers.map((answer) => answer.join("、")).join(";")}

) : null} {actionable ? ( ) : null}
); } type QuestionDraftAnswer = { selected: string[]; custom: string; }; function QuestionInput({ question, questionIndex, value, disabled, onChange }: { question: AgentQuestionInfo; questionIndex: number; value: QuestionDraftAnswer; disabled: boolean; onChange: (value: QuestionDraftAnswer) => void; }) { const name = `agent-question-${questionIndex}-${question.question}`; const showCustomInput = question.custom || question.options.length === 0; const toggleOption = (label: string, checked: boolean) => { if (question.multiple) { onChange({ ...value, selected: checked ? [...value.selected, label] : value.selected.filter((item) => item !== label) }); return; } onChange({ ...value, selected: checked ? [label] : [] }); }; return (

{question.question}

{question.options.length ? (
{question.options.map((option) => { const checked = value.selected.includes(option.label); return ( ); })}
) : null} {showCustomInput ? (