| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798 |
- "use client";
- import { useCallback, useEffect, useMemo, useState } from "react";
- import { AppShell } from "@/components/AppShell";
- import { ErrorState, LoadingState } from "@/components/StateBlocks";
- import { getRunWalk } from "@/lib/api/client";
- import type { FlowLedgerWalkResponse, RawRecord } from "@/lib/api/types";
- import { asRecord, text } from "@/lib/flow-ledger/format";
- import { WalkTree } from "@/features/walk/WalkTree";
- export function RunWalkPage({ runId }: { runId: string }) {
- const [data, setData] = useState<FlowLedgerWalkResponse | null>(null);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState<string | null>(null);
- const load = useCallback(async () => {
- setLoading(true);
- setError(null);
- try {
- const debug = new URLSearchParams(window.location.search).get("debug") === "1";
- setData(await getRunWalk(runId, debug));
- } catch (err) {
- setError(err instanceof Error ? err.message : String(err));
- } finally {
- setLoading(false);
- }
- }, [runId]);
- useEffect(() => {
- void load();
- }, [load]);
- const summary = asRecord(data?.summary);
- const actions = useMemo(
- () => (data?.actions || []).filter((a): a is RawRecord => typeof a === "object" && a !== null),
- [data?.actions]
- );
- const queryLabels = useMemo(() => {
- const raw = asRecord(summary.query_labels);
- const out: Record<string, string> = {};
- for (const key of Object.keys(raw)) out[key] = text(raw[key], "");
- return out;
- }, [summary]);
- const budget = useMemo(() => budgetStats(summary), [summary]);
- return (
- <AppShell
- title="全 run 游走图"
- subtitle="整条 run 从命中视频向外游走的全部路径"
- runId={runId}
- showBack
- onRefresh={load}
- >
- {loading ? <LoadingState /> : null}
- {error ? <ErrorState message={error} /> : null}
- {data && !loading && !error ? (
- <>
- <header className="wt-overview">
- <div className="wt-overview-head">
- <span className="wt-root-chip">游走预算</span>
- <strong>本 run 总览</strong>
- </div>
- <div className="wt-overview-stats">
- {budget.map((stat) => (
- <span className={`wt-stat ${stat.tone || ""}`} key={stat.label}>
- <b>{stat.value}</b>
- {stat.label}
- </span>
- ))}
- </div>
- </header>
- <WalkTree
- actions={actions}
- runId={runId}
- groupByQuery
- queryLabels={queryLabels}
- emptyLabel="本 run 没有任何向外游走"
- />
- </>
- ) : null}
- </AppShell>
- );
- }
- function fmtUsed(used: unknown, cap: unknown): string {
- const u = Number(used) || 0;
- if (cap === null || cap === undefined || cap === "") return String(u);
- return `${u} / ${Number(cap) || 0}`;
- }
- function budgetStats(summary: RawRecord): { label: string; value: string; tone?: string }[] {
- return [
- { label: "游走到的视频", value: String(Number(summary.walk_video_total) || 0), tone: "go" },
- { label: "标签→新搜索", value: fmtUsed(summary.tag_used, summary.tag_cap) },
- { label: "作者→作品", value: fmtUsed(summary.author_used, summary.author_cap) },
- { label: "最深层数", value: fmtUsed(summary.max_depth_reached, summary.max_depth_cap) }
- ];
- }
|