"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { AlertTriangle, RefreshCw } from "lucide-react"; import { fetchJourney, fetchRuns } from "@/lib/api"; import { statusLabel } from "@/lib/journey"; import type { JourneyStep, JourneyView, RunSummary, ZoomLevel } from "@/lib/types"; import { EvidencePanel } from "@/components/EvidencePanel"; import { JourneyCanvas } from "@/components/JourneyCanvas"; const ZOOM_OPTIONS: Array<{ value: ZoomLevel; label: string; hint: string }> = [ { value: "journey", label: "全局", hint: "看创作回合" }, { value: "steps", label: "步骤", hint: "看规划与返工" }, { value: "evidence", label: "证据", hint: "看原始记录" }, ]; const TERMINAL_STATUSES = new Set(["completed", "stopped", "failed", "published"]); export function JourneyApp() { const [runs, setRuns] = useState([]); const [selectedRunId, setSelectedRunId] = useState(null); const [journey, setJourney] = useState(null); const [zoom, setZoom] = useState("journey"); const [selectedStepId, setSelectedStepId] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [refreshTick, setRefreshTick] = useState(0); useEffect(() => { const controller = new AbortController(); fetchRuns(controller.signal) .then((values) => { setRuns(values); setSelectedRunId((current) => current ?? values[0]?.script_build_id ?? null); }) .catch((reason: Error) => setError(reason.message)) .finally(() => setLoading(false)); return () => controller.abort(); }, []); const loadJourney = useCallback( async (signal?: AbortSignal) => { if (selectedRunId === null) return null; setError(null); try { const value = await fetchJourney(selectedRunId, signal); setJourney(value); setSelectedStepId((current) => current && value.steps.some((step) => step.step_id === current) ? current : value.steps[0]?.step_id ?? null, ); return value; } catch (reason) { if (!(reason instanceof DOMException && reason.name === "AbortError")) { setError(reason instanceof Error ? reason.message : "无法读取创作旅程"); } return null; } }, [selectedRunId], ); useEffect(() => { const controller = new AbortController(); let timer: number | null = null; async function refresh() { const value = await loadJourney(controller.signal); if ( !controller.signal.aborted && value && !TERMINAL_STATUSES.has(value.status.toLowerCase()) ) { timer = window.setTimeout(refresh, 12_000); } } setLoading(true); refresh().finally(() => setLoading(false)); return () => { controller.abort(); if (timer !== null) window.clearTimeout(timer); }; }, [loadJourney, refreshTick]); const selectedStep = useMemo( () => journey?.steps.find((step) => step.step_id === selectedStepId) ?? null, [journey, selectedStepId], ); const selectedRun = useMemo( () => runs.find((run) => run.script_build_id === selectedRunId) ?? null, [runs, selectedRunId], ); if (!loading && runs.length === 0 && !error) { return (

还没有可展示的创作记录

完成一次 Script Build 后,这里会按真实执行顺序展示它的规划、执行、验收和返工。

); } return (
{ZOOM_OPTIONS.map((option) => ( ))}
{error ? (
{error}
) : null} {journey ? ( <>
{statusLabel(journey.status)}

{journey.objective}

DATA IN {journey.input_summary.topic} {journey.input_summary.account_name ?? "未指定账号"} {journey.input_summary.persona_point_count} 条人设信息 {journey.input_summary.strategy_names.length ? journey.input_summary.strategy_names.join("、") : "无额外策略"}
{ setSelectedStepId(stepId); if (zoom === "journey") setZoom("steps"); }} /> {zoom === "evidence" ? ( setZoom("steps")} /> ) : null}
) : (
)}
); }