RetrievalAgentCard.tsx 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738
  1. import { useRef } from "react";
  2. import { ArrowRight, Bot, ChevronDown, ChevronUp } from "lucide-react";
  3. import { Handle, Position, type NodeProps } from "@xyflow/react";
  4. import type { CanvasNodeData } from "@/lib/layout";
  5. import { QueryAttemptList } from "@/components/flow/QueryAttemptList";
  6. import { SourceComparisonButton } from "@/components/flow/SourceComparisonButton";
  7. import { useRetrievalHoverPreview } from "@/components/flow/RetrievalHoverPreview";
  8. export function RetrievalAgentCard({ data }: NodeProps) {
  9. const node = data as unknown as CanvasNodeData;
  10. const item = node.item;
  11. if (!item || item.itemType !== "retrieval-agent") return null;
  12. return <RetrievalAgentCardContent node={node} item={item} />;
  13. }
  14. function RetrievalAgentCardContent({ node, item }: { node: CanvasNodeData; item: Extract<NonNullable<CanvasNodeData["item"]>, { itemType: "retrieval-agent" }> }) {
  15. const cardRef = useRef<HTMLElement>(null);
  16. const hoverPreview = useRetrievalHoverPreview({ anchorRef: cardRef, buildId: node.buildId, item, onOpenDetail: () => node.onOpen(item, "business") });
  17. const run = item.run;
  18. const expanded = Boolean(node.expanded);
  19. const query = run.querySummary;
  20. const queryListId = `query-list-${run.eventId}`;
  21. return <><article ref={cardRef} className={`flowCard retrievalCard retrievalAgentCard status-${run.status}${expanded ? " isQueryExpanded" : ""}`} tabIndex={0} data-focus-return={`${item.id}:card`} {...hoverPreview.anchorProps} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); node.onOpen(item, "business"); } }}>
  22. <Handle type="target" position={Position.Left} className="flowHandle" />
  23. <Handle type="source" position={Position.Right} className="flowHandle" />
  24. <header><span className="retrievalKind"><Bot size={14} />Agent 取数</span><span className={`agentRunStatus run-${run.status}`}>{run.status === "completed" ? "已完成" : run.status === "failed" ? "失败" : run.status === "interrupted" ? "构建中断" : "进行中"}</span></header>
  25. <h3>{run.businessLabel}</h3>
  26. <div className="agentTask"><b>取数目标</b><p>{run.taskPreview || "任务未记录"}</p></div>
  27. <div className="agentQuerySummary"><b>查询过程</b><p>{query.total} 次 · <span className="query-hit">{query.hit} 次命中</span> · <span className="query-empty">{query.empty} 次无结果</span>{query.failure ? <> · <span className="query-failure">{query.failure} 次失败</span></> : null}{query.interrupted ? <> · <span className="query-interrupted">{query.interrupted} 次中断</span></> : null}{query.unknown ? <> · <span className="query-unknown">{query.unknown} 次结果待确认</span></> : null}</p></div>
  28. <div className="agentScreening"><b>初筛整理</b><p>{run.screening.preview || (run.screening.state === "running" ? "正在整理查询结果" : "初筛整理未记录")}</p></div>
  29. {expanded ? <QueryAttemptList id={queryListId} attempts={run.attempts} roundIndex={item.roundIndex} branchId={item.branchId} onOpen={node.onOpen} /> : null}
  30. <footer>
  31. {run.attempts.length ? <button type="button" className="nodeAction nodrag nopan" aria-expanded={expanded} aria-controls={queryListId} onClick={(event) => { event.stopPropagation(); node.onToggleRetrievalAgent(item.id); }}>{expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}{expanded ? "收起查询" : `展开 ${run.attempts.length} 次查询`}</button> : null}
  32. <button type="button" className="nodeAction nodrag nopan" data-focus-return={`${item.id}:business`} onClick={(event) => { event.stopPropagation(); node.onOpen(item, "business"); }}>查看 Agent 详情<ArrowRight size={14} /></button>
  33. <SourceComparisonButton item={item} onOpen={node.onOpen} />
  34. </footer>
  35. </article>{hoverPreview.preview}</>;
  36. }