mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-02 19:55:18 +02:00
feat(automations): render live step ticker, defer REST until terminal
Step results now render from the synced Zero row so the panel ticks forward as the run progresses. The REST getRun call is gated on the run reaching a terminal status, since output/artifacts/error are only written at terminal mark.
This commit is contained in:
parent
d8db3159d6
commit
ca66bff02b
1 changed files with 60 additions and 48 deletions
|
|
@ -15,7 +15,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { RunStepResult } from "@/contracts/types/automation.types";
|
import type { RunStatus, RunStepResult } from "@/contracts/types/automation.types";
|
||||||
import { useAutomationRun } from "@/hooks/use-automation-runs";
|
import { useAutomationRun } from "@/hooks/use-automation-runs";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { RunStepResultCard } from "./run-step-result-card";
|
import { RunStepResultCard } from "./run-step-result-card";
|
||||||
|
|
@ -23,44 +23,46 @@ import { RunStepResultCard } from "./run-step-result-card";
|
||||||
interface RunDetailsPanelProps {
|
interface RunDetailsPanelProps {
|
||||||
automationId: number;
|
automationId: number;
|
||||||
runId: number;
|
runId: number;
|
||||||
|
/** Live step entries from Zero; rendered while the run is in-flight and
|
||||||
|
* also kept as the authoritative source once it finishes. */
|
||||||
|
liveSteps: RunStepResult[];
|
||||||
|
/** Live run status from Zero. Used to hide diagnostic sections that
|
||||||
|
* only make sense after the run reaches a terminal state. */
|
||||||
|
liveStatus: RunStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expanded view of a single run. Fetches lazily — the parent only renders
|
* Expanded view of a single run. Steps render immediately from the live
|
||||||
* this once the row is opened, so the list view stays cheap.
|
* Zero row so the panel updates as the run progresses; the heavy REST
|
||||||
|
* payload (output, artifacts, resolved inputs, run-level error) is
|
||||||
|
* fetched lazily and merged in when it arrives.
|
||||||
*
|
*
|
||||||
* We surface the run outcome readably: a run-level error first (when
|
* Surfacing order is outcome-first: a run-level error (when present),
|
||||||
* present), then per-step cards that render the agent's markdown
|
* then per-step cards that render the agent's markdown ``final_message``
|
||||||
* ``final_message`` directly, and finally the structural artifacts/inputs.
|
* directly, and finally the structural artifacts/inputs. The full
|
||||||
* The full ``definition_snapshot`` is omitted because it usually mirrors the
|
* ``definition_snapshot`` is omitted because it usually mirrors the live
|
||||||
* live definition — surfacing it would dominate the panel without informing
|
* definition — surfacing it would dominate the panel without informing
|
||||||
* what the user is trying to learn ("did this work? what did it do?").
|
* what the user is trying to learn ("did this work? what did it do?").
|
||||||
*/
|
*/
|
||||||
export function RunDetailsPanel({ automationId, runId }: RunDetailsPanelProps) {
|
export function RunDetailsPanel({
|
||||||
const { data: run, isLoading, error } = useAutomationRun(automationId, runId);
|
automationId,
|
||||||
|
runId,
|
||||||
|
liveSteps,
|
||||||
|
liveStatus,
|
||||||
|
}: RunDetailsPanelProps) {
|
||||||
|
const isTerminal = liveStatus !== "pending" && liveStatus !== "running";
|
||||||
|
// Defer the REST round-trip until the run can actually carry heavy
|
||||||
|
// fields — output/artifacts/error are only written at terminal mark.
|
||||||
|
const { data: run, isLoading, error } = useAutomationRun(automationId, runId, {
|
||||||
|
enabled: isTerminal,
|
||||||
|
});
|
||||||
|
|
||||||
if (isLoading) {
|
const runError = run?.error && Object.keys(run.error).length > 0 ? run.error : null;
|
||||||
return (
|
const hasOutput = !!run?.output && Object.keys(run.output).length > 0;
|
||||||
<div className="flex flex-col gap-3 border-t border-border/60 bg-muted/20 p-4">
|
const hasInputs = !!run && Object.keys(run.inputs ?? {}).length > 0;
|
||||||
<Skeleton className="h-3 w-32" />
|
const hasDiagnostics = !!run && (run.artifacts.length > 0 || hasInputs);
|
||||||
<Skeleton className="h-24 w-full" />
|
const heavyLoading = isTerminal && isLoading && !run;
|
||||||
</div>
|
const heavyError = isTerminal && !!error;
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error || !run) {
|
|
||||||
return (
|
|
||||||
<div className="border-t border-border/60 bg-muted/20 p-4 text-xs text-muted-foreground">
|
|
||||||
Couldn't load run details{error?.message ? `: ${error.message}` : "."}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const runError = run.error && Object.keys(run.error).length > 0 ? run.error : null;
|
|
||||||
const hasOutput = run.output && Object.keys(run.output).length > 0;
|
|
||||||
const hasInputs = Object.keys(run.inputs ?? {}).length > 0;
|
|
||||||
const steps = run.step_results as RunStepResult[];
|
|
||||||
const hasDiagnostics = run.artifacts.length > 0 || hasInputs;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 border-t border-border/60 bg-muted/20 p-4">
|
<div className="flex flex-col gap-4 border-t border-border/60 bg-muted/20 p-4">
|
||||||
|
|
@ -72,31 +74,41 @@ export function RunDetailsPanel({ automationId, runId }: RunDetailsPanelProps) {
|
||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Section icon={GitCommitHorizontal} label={`Step results · ${steps.length}`}>
|
<Section icon={GitCommitHorizontal} label={`Step results · ${liveSteps.length}`}>
|
||||||
{steps.length === 0 ? (
|
{liveSteps.length === 0 ? (
|
||||||
<p className="text-xs text-muted-foreground">No steps recorded.</p>
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{isTerminal ? "No steps recorded." : "Waiting for first step…"}
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{steps.map((step, index) => (
|
{liveSteps.map((step, index) => (
|
||||||
<RunStepResultCard key={step.step_id ?? index} step={step} />
|
<RunStepResultCard key={step.step_id ?? index} step={step} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{hasDiagnostics ? <Separator className="bg-border/60" /> : null}
|
{heavyLoading ? (
|
||||||
|
<Skeleton className="h-16 w-full" />
|
||||||
{run.artifacts.length > 0 ? (
|
) : heavyError ? (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Couldn't load run details{error?.message ? `: ${error.message}` : "."}
|
||||||
|
</p>
|
||||||
|
) : hasDiagnostics ? (
|
||||||
|
<>
|
||||||
|
<Separator className="bg-border/60" />
|
||||||
|
{run && run.artifacts.length > 0 ? (
|
||||||
<Section icon={Package} label={`Artifacts · ${run.artifacts.length}`}>
|
<Section icon={Package} label={`Artifacts · ${run.artifacts.length}`}>
|
||||||
<JsonBlock value={run.artifacts} />
|
<JsonBlock value={run.artifacts} />
|
||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{hasInputs ? (
|
{hasInputs ? (
|
||||||
<Section icon={Settings2} label="Resolved inputs">
|
<Section icon={Settings2} label="Resolved inputs">
|
||||||
<JsonBlock value={run.inputs} />
|
<JsonBlock value={run?.inputs} />
|
||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue