Ver Fonte

refactor(旧组件): 删除固定流程图与通用字段检查器

移除只服务 fake 固定图的 FieldTree、FlowCard、Inspector 和 PhaseFrameNode。相关能力已由动态 TaskRecord、PlannerAxis 和 TaskInspector 按真实业务语义替代。
SamLee há 19 horas atrás
pai
commit
af7b17ce4e

+ 0 - 29
visualization/frontend/components/FieldTree.tsx

@@ -1,29 +0,0 @@
-import { Fragment } from "react";
-
-function scalar(value: unknown): string {
-  if (value === null) return "null";
-  if (typeof value === "boolean") return value ? "true" : "false";
-  if (typeof value === "string") return value || '""';
-  return String(value);
-}
-
-export function FieldTree({ value, depth = 0 }: { value: unknown; depth?: number }) {
-  if (Array.isArray(value)) {
-    if (!value.length) return <span className="emptyValue">[]</span>;
-    return <div className="nestedValue">{value.map((item, index) => <div className="arrayItem" key={index}>
-      <span className="indexBadge">{index}</span><FieldTree value={item} depth={depth + 1} />
-    </div>)}</div>;
-  }
-  if (value !== null && typeof value === "object") {
-    const entries = Object.entries(value as Record<string, unknown>);
-    if (!entries.length) return <span className="emptyValue">{"{}"}</span>;
-    return <dl className={`fieldTree depth-${Math.min(depth, 3)}`}>
-      {entries.map(([key, item]) => <Fragment key={key}>
-        <dt>{key}</dt>
-        <dd><FieldTree value={item} depth={depth + 1} /></dd>
-      </Fragment>)}
-    </dl>;
-  }
-  return <span className={`scalar scalar-${value === null ? "null" : typeof value}`}>{scalar(value)}</span>;
-}
-

+ 0 - 60
visualization/frontend/components/FlowCard.tsx

@@ -1,60 +0,0 @@
-"use client";
-
-import {
-  BadgeCheck, Bot, Braces, Database, FileCheck2, FileLock2, Flag,
-  GitCommitHorizontal, PackageCheck, RadioTower, RefreshCcw, Rocket,
-  Send, ShieldCheck, Workflow,
-} from "lucide-react";
-import { Handle, Position, type Node, type NodeProps } from "@xyflow/react";
-import type { CardData } from "@/lib/flow";
-import type { NodeType } from "@/lib/types";
-
-const ICONS: Record<NodeType, typeof Database> = {
-  input: Database,
-  contract: FileLock2,
-  operation: RadioTower,
-  worker: Bot,
-  attempt: Send,
-  artifact: PackageCheck,
-  validator: ShieldCheck,
-  validation: FileCheck2,
-  decision: BadgeCheck,
-  checkpoint: Flag,
-  publication: Rocket,
-  transaction: GitCommitHorizontal,
-  readback: RefreshCcw,
-};
-
-export function FlowCard({ data, selected }: NodeProps<Node<CardData>>) {
-  const item = data.item;
-  const Icon = ICONS[item.node_type] ?? Braces;
-  return <article
-    className={`flowCard type-${item.node_type} status-${item.status} ${selected ? "selected" : ""} ${data.dimmed ? "dimmed" : ""}`}
-    data-node-id={item.id}
-  >
-    <Handle type="target" position={Position.Left} className="nodeHandle" />
-    <header className="cardHeader">
-      <span className="nodeIcon"><Icon size={17} strokeWidth={1.9} /></span>
-      <span className="cardKind">{item.node_type}</span>
-      <span className="sequence">{String(item.sequence).padStart(2, "0")}</span>
-    </header>
-    <div className="cardBody">
-      <h3>{item.title}</h3>
-      <p>{item.subtitle}</p>
-      <div className="recordType"><Workflow size={12} /> {item.record.model_name}</div>
-      {item.metrics.length ? <dl className="metricGrid">
-        {item.metrics.slice(0, 3).map((metric) => <div key={`${metric.label}:${metric.value}`}>
-          <dt>{metric.label}</dt><dd>{metric.value}</dd>
-        </div>)}
-      </dl> : null}
-    </div>
-    <footer className="cardFooter">
-      <span className={`statusDot status-${item.status}`} />
-      <strong>{item.status}</strong>
-      {item.agent_role ? <span>{item.agent_role}</span> : null}
-      {item.task_kind ? <span className="taskKind">{item.task_kind}</span> : null}
-    </footer>
-    <Handle type="source" position={Position.Right} className="nodeHandle" />
-  </article>;
-}
-

+ 0 - 75
visualization/frontend/components/Inspector.tsx

@@ -1,75 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import { Braces, ChevronRight, CircleOff, Copy, GitFork, X } from "lucide-react";
-import { FieldTree } from "@/components/FieldTree";
-import type { FlowEdgeRecord, FlowNodeRecord, RunGraph } from "@/lib/types";
-
-type Tab = "fields" | "lineage" | "contract";
-
-function EdgeList({ title, edges, graph, direction }: {
-  title: string;
-  edges: FlowEdgeRecord[];
-  graph: RunGraph;
-  direction: "incoming" | "outgoing";
-}) {
-  return <section className="inspectorSection">
-    <h3>{title}<span>{edges.length}</span></h3>
-    {edges.length ? <div className="edgeList">{edges.map((edge) => {
-      const otherId = direction === "incoming" ? edge.source : edge.target;
-      const other = graph.nodes.find((item) => item.id === otherId);
-      return <div key={edge.id} className={`edgeRow edge-${edge.kind}`}>
-        <GitFork size={14} />
-        <div><strong>{other?.title ?? otherId}</strong><small>{edge.label ?? edge.kind}</small></div>
-        <ChevronRight size={14} />
-      </div>;
-    })}</div> : <p className="emptyState"><CircleOff size={15} /> 无连接</p>}
-  </section>;
-}
-
-export function Inspector({ item, graph, onClose }: {
-  item: FlowNodeRecord;
-  graph: RunGraph;
-  onClose: () => void;
-}) {
-  const [tab, setTab] = useState<Tab>("fields");
-  const incoming = graph.edges.filter((edge) => edge.target === item.id);
-  const outgoing = graph.edges.filter((edge) => edge.source === item.id);
-  const expectedFields = graph.schema_catalog[item.record.model_name] ?? [];
-  const copyJson = () => navigator.clipboard?.writeText(JSON.stringify(item.record.payload, null, 2));
-  return <aside className="inspector">
-    <header className="inspectorHeader">
-      <div className={`inspectorIcon type-${item.node_type}`}><Braces size={19} /></div>
-      <div><span>{item.phase} / {item.lane}</span><h2>{item.title}</h2></div>
-      <button type="button" className="iconButton" onClick={onClose} aria-label="关闭详情"><X size={18} /></button>
-    </header>
-    <div className="inspectorMeta">
-      <span className={`pill status-${item.status}`}>{item.status}</span>
-      <code>{item.id}</code>
-    </div>
-    <nav className="inspectorTabs" aria-label="节点详情页签">
-      <button className={tab === "fields" ? "active" : ""} onClick={() => setTab("fields")}>全部字段</button>
-      <button className={tab === "lineage" ? "active" : ""} onClick={() => setTab("lineage")}>上下游</button>
-      <button className={tab === "contract" ? "active" : ""} onClick={() => setTab("contract")}>字段合同</button>
-    </nav>
-    <div className="inspectorScroll">
-      {tab === "fields" ? <>
-        <section className="modelBanner">
-          <div><small>PROJECT MODEL</small><strong>{item.record.model_name}</strong></div>
-          <button type="button" onClick={copyJson}><Copy size={14} /> 复制 JSON</button>
-        </section>
-        <FieldTree value={item.record.payload} />
-      </> : null}
-      {tab === "lineage" ? <>
-        <EdgeList title="输入" edges={incoming} graph={graph} direction="incoming" />
-        <EdgeList title="输出" edges={outgoing} graph={graph} direction="outgoing" />
-      </> : null}
-      {tab === "contract" ? <section className="inspectorSection contractFields">
-        <h3>当前源码字段<span>{expectedFields.length}</span></h3>
-        <p>fake payload 必须与此清单完全同键;后端测试会拒绝遗漏和额外字段。</p>
-        <ol>{expectedFields.map((field) => <li key={field}><code>{field}</code><span>{field in item.record.payload ? "present" : "missing"}</span></li>)}</ol>
-      </section> : null}
-    </div>
-  </aside>;
-}
-

+ 0 - 15
visualization/frontend/components/PhaseFrameNode.tsx

@@ -1,15 +0,0 @@
-"use client";
-
-import type { Node, NodeProps } from "@xyflow/react";
-import type { FrameData } from "@/lib/flow";
-
-export function PhaseFrameNode({ data }: NodeProps<Node<FrameData>>) {
-  const frame = data.frame;
-  return <section className={`phaseFrame tone-${frame.tone}`} aria-label={frame.title}>
-    <div className="phaseLabel">
-      <span>{frame.title}</span>
-      <small>{frame.subtitle}</small>
-    </div>
-  </section>;
-}
-