({ node, locked, nodeCount, edgeCount, onUnlock }: Props)
| 18 | } |
| 19 | |
| 20 | export function FlowDetailPanel({ node, locked, nodeCount, edgeCount, onUnlock }: Props) { |
| 21 | const t = useT(); |
| 22 | if (!node) { |
| 23 | return ( |
| 24 | <div className="pointer-events-none absolute bottom-3 right-3 w-80 rounded-lg border border-slate-700 bg-slate-900/80 p-3 text-xs text-slate-500 shadow-xl backdrop-blur"> |
| 25 | <div className="mb-1 text-slate-400">{t("flow.detail_ready")}</div> |
| 26 | {(nodeCount || edgeCount) && ( |
| 27 | <div className="text-slate-500"> |
| 28 | {t("flow.detail_counts", { n: nodeCount ?? 0, edge: edgeCount ?? 0 })} |
| 29 | </div> |
| 30 | )} |
| 31 | <div className="mt-1 text-slate-600">{t("flow.detail_hint")}</div> |
| 32 | </div> |
| 33 | ); |
| 34 | } |
| 35 | |
| 36 | return ( |
| 37 | <div className="pointer-events-auto absolute bottom-3 right-3 max-h-[70%] w-[28rem] overflow-auto rounded-lg border border-cyan-700 bg-slate-900/95 p-3 text-xs shadow-2xl backdrop-blur"> |
| 38 | {/* 顶部:kind 标签 + title + 锁定指示 */} |
| 39 | <div className="mb-2 flex items-center gap-2"> |
| 40 | <span className={kindBadgeClass(node.kind)}>{kindLabel(node, t)}</span> |
| 41 | <span className="flex-1 truncate text-sm font-semibold text-slate-100">{node.title}</span> |
| 42 | {locked && ( |
| 43 | <button |
| 44 | onClick={onUnlock} |
| 45 | className="rounded bg-cyan-700 px-1.5 py-0.5 text-[10px] text-cyan-100 hover:bg-cyan-600" |
| 46 | title={t("flow.detail_lock_tip")} |
| 47 | > |
| 48 | {t("flow.detail_locked")} |
| 49 | </button> |
| 50 | )} |
| 51 | </div> |
| 52 | |
| 53 | {/* path / subtitle */} |
| 54 | {node.path && ( |
| 55 | <div className="mb-2 break-all rounded bg-slate-950/50 p-1.5 font-mono text-[10px] text-cyan-300"> |
| 56 | {node.path} |
| 57 | {node.anchor && ( |
| 58 | <span className="text-slate-500"> |
| 59 | {node.isBlock ? "#^" : "#"} |
| 60 | <span className="text-emerald-400">{node.anchor}</span> |
| 61 | </span> |
| 62 | )} |
| 63 | </div> |
| 64 | )} |
| 65 | {!node.path && node.subtitle && ( |
| 66 | <div className="mb-2 break-words text-slate-300">{node.subtitle}</div> |
| 67 | )} |
| 68 | |
| 69 | {/* tool 节点:args + result */} |
| 70 | {node.kind === "file" || node.kind === "search" || node.kind === "list" || node.kind === "other" ? ( |
| 71 | <> |
| 72 | {node.toolName && ( |
| 73 | <div className="mb-1 text-slate-500"> |
| 74 | {t("flow.detail_tool")}<span className="text-slate-300">{node.toolName}</span> |
| 75 | {node.hitCount > 1 && ( |
| 76 | <span className="ml-2 text-amber-400">×{node.hitCount}</span> |
| 77 | )} |
nothing calls this directly
no test coverage detected