VideoWalkPage.tsx 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. "use client";
  2. import { useCallback, useEffect, useMemo, useState } from "react";
  3. import { AppShell } from "@/components/AppShell";
  4. import { ErrorState, LoadingState } from "@/components/StateBlocks";
  5. import { getVideoWalk } from "@/lib/api/client";
  6. import type { FlowLedgerVideoWalkResponse, RawRecord } from "@/lib/api/types";
  7. import { asRecord, text } from "@/lib/flow-ledger/format";
  8. import { WalkTree } from "@/features/walk/WalkTree";
  9. export function VideoWalkPage({ runId, contentId }: { runId: string; contentId: string }) {
  10. const [data, setData] = useState<FlowLedgerVideoWalkResponse | null>(null);
  11. const [loading, setLoading] = useState(true);
  12. const [error, setError] = useState<string | null>(null);
  13. const load = useCallback(async () => {
  14. setLoading(true);
  15. setError(null);
  16. try {
  17. const debug = new URLSearchParams(window.location.search).get("debug") === "1";
  18. setData(await getVideoWalk(runId, contentId, debug));
  19. } catch (err) {
  20. setError(err instanceof Error ? err.message : String(err));
  21. } finally {
  22. setLoading(false);
  23. }
  24. }, [contentId, runId]);
  25. useEffect(() => {
  26. void load();
  27. }, [load]);
  28. const root = asRecord(data?.root_video);
  29. const rootTitle = cleanTitle(text(root.title, "这条视频"));
  30. const actions = useMemo(
  31. () => (data?.actions || []).filter((a): a is RawRecord => typeof a === "object" && a !== null),
  32. [data?.actions]
  33. );
  34. return (
  35. <AppShell
  36. title="这条视频的扩展过程"
  37. subtitle={`从《${rootTitle}》开始向外游走`}
  38. runId={runId}
  39. showBack
  40. onRefresh={load}
  41. >
  42. {loading ? <LoadingState /> : null}
  43. {error ? <ErrorState message={error} /> : null}
  44. {data && !loading && !error ? (
  45. <WalkTree actions={actions} runId={runId} emptyLabel="这条视频没有继续向外游走" />
  46. ) : null}
  47. </AppShell>
  48. );
  49. }
  50. function cleanTitle(value: string): string {
  51. return value.replace(/#[^\s#]+/g, " ").replace(/\s+/g, " ").trim() || value;
  52. }