CandidateCard.tsx 3.0 KB

12345678910111213141516171819202122232425262728293031
  1. import { ArrowRight, BookOpenCheck, ChevronDown, ChevronUp, FileText, GitBranch } from "lucide-react";
  2. import { Handle, Position, type NodeProps } from "@xyflow/react";
  3. import type { CanvasNodeData } from "@/lib/layout";
  4. import { SourceNoticeBadge, StatusBadge } from "@/components/ui/Badges";
  5. import { SourceComparisonButton } from "@/components/flow/SourceComparisonButton";
  6. export function CandidateCard({ data }: NodeProps) {
  7. const { item, onOpen, onToggleBranch } = data as unknown as CanvasNodeData;
  8. if (!item || item.itemType !== "branch-summary") return null;
  9. const summary = item.branch.summary;
  10. return <article className="flowCard candidateCard" data-focus-return={`${item.id}:card`} aria-label={`候选方案:${item.branch.title}`} tabIndex={0} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); onOpen(item, "business"); } }}>
  11. <Handle type="target" position={Position.Left} className="flowHandle" /><Handle type="source" position={Position.Right} className="flowHandle" />
  12. <header className="cardHeader"><span className="candidateLabel">{item.branch.pathType === "领域信息" ? <BookOpenCheck size={14} /> : <GitBranch size={14} />}实现 Agent · 方案 {item.branchId} · {item.branch.pathType === "unknown" ? "类型未记录" : item.branch.pathType}</span><StatusBadge status={item.branch.status} /></header>
  13. {item.branch.title !== `方案 ${item.branchId}` ? <h3>{item.branch.title}</h3> : null}
  14. <div className="candidateSummary">
  15. <SummaryRow label="任务" value={summary.task} />
  16. <SummaryRow label={item.branch.pathType === "领域信息" ? "领域事实" : "候选产出"} value={summary.output} />
  17. </div>
  18. <footer className="cardFooter">
  19. <button type="button" className="nodeAction nodrag nopan" onClick={(event) => { event.stopPropagation(); onToggleBranch(item.roundIndex, item.branchId); }}>{data.expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}{data.expanded ? "收起方案过程" : "展开方案过程"}</button>
  20. {item.detailRef ? <button type="button" className="nodeAction nodrag nopan" data-focus-return={`${item.id}:business`} onClick={(event) => { event.stopPropagation(); onOpen(item, "business"); }}>查看方案汇总<ArrowRight size={14} /></button> : null}
  21. <SourceComparisonButton item={item} onOpen={onOpen} />
  22. {item.branch.output.type === "script-artifact" && item.branch.output.snapshotRef ? <button type="button" className="nodeAction nodrag nopan artifactAction" data-focus-return={`${item.id}:artifact`} onClick={(event) => { event.stopPropagation(); onOpen(item, "artifact"); }}><FileText size={14} />查看完整候选脚本</button> : null}
  23. </footer>
  24. <SourceNoticeBadge notice={item.branch.outcome.sourceNotice || item.branch.task.sourceNotice || item.branch.output.node.sourceNotice} />
  25. </article>;
  26. }
  27. function SummaryRow({ label, value }: { label: string; value?: string | null }) {
  28. return <div><b>{label}</b><p>{value || "未记录"}</p></div>;
  29. }