import type { Execution, ExecutionEnvelope,} from "@executor/react";import { useExecution, useExecutions,} from "@executor/react";import { Alert, Badge, Button, Card, DocumentPanel, EmptyState, LoadableBlock, useExecutorPluginNavigation, useExecutorPluginRouteParams,} from "@executor/react/plugins"; const formatTimestamp = (value: number | null): string => { if (value === null) { return "—"; } return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short", }).format(value);}; const formatDuration = (execution: Execution): string | null => { if (execution.startedAt === null || execution.completedAt === null) { return null; } const durationMs = Math.max(0, execution.completedAt - execution.startedAt); if (durationMs < 1_000) { return `${durationMs} ms`; } if (durationMs < 60_000) { return `${(durationMs / 1_000).toFixed(1)} s`; } return `${(durationMs / 60_000).toFixed(1)} min`;}; const summarizeCode = (code: string): string => code .trim() .replace(/\s+/g, " ") .slice(0, 220); const formatJsonDocument = (value: string | null): string | null => { if (value === null) { return null; } try { return JSON.stringify(JSON.parse(value), null, 2); } catch { return value; }}; const statusVariant = ( status: Execution["status"],): "default" | "muted" | "outline" | "destructive" => { switch (status) { case "completed": return "default"; case "failed": return "destructive"; case "running": case "waiting_for_interaction": return "outline"; default: return "muted"; }}; const MetadataItem = (props: { label: string; value: string;}) => ( <Card className="bg-card/60 px-3 py-2"> <div className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70"> {props.label} </div> <div className="mt-1 text-sm text-foreground">{props.value}</div> </Card>); const ExecutionCard = (props: { execution: Execution; onOpen: () => void;}) => { const { execution } = props; return ( <Button variant="ghost" type="button" onClick={props.onOpen} className="h-auto w-full rounded-2xl border border-border bg-card px-5 py-4 text-left transition-colors hover:border-primary/25 hover:bg-card/90" > <div className="flex items-start justify-between gap-3"> <div className="min-w-0 flex-1"> <div className="flex items-center gap-2"> <div className="truncate text-sm font-semibold text-foreground"> {execution.id} </div> <Badge variant={statusVariant(execution.status)}> {execution.status.replaceAll("_", " ")} </Badge> </div> <div className="mt-1 text-xs text-muted-foreground"> Created {formatTimestamp(execution.createdAt)} {formatDuration(execution) ? ` • ${formatDuration(execution)}` : ""} </div> <p className="mt-3 line-clamp-3 text-sm leading-6 text-muted-foreground"> {summarizeCode(execution.code) || "No code captured."} </p> </div> </div> {execution.errorText && ( <Alert variant="destructive" className="mt-3 text-xs"> {execution.errorText} </Alert> )} </Button> );}; export function ExecutionHistoryPage() { const executions = useExecutions(); const navigation = useExecutorPluginNavigation(); return ( <div className="h-full overflow-y-auto"> <div className="mx-auto max-w-5xl px-6 py-10 lg:px-10 lg:py-14"> <div className="mb-8"> <h1 className="font-display text-3xl tracking-tight text-foreground lg:text-4xl"> Execution history </h1> <p className="mt-1.5 text-[14px] text-muted-foreground"> Every execution recorded for this workspace, newest first. </p> </div> <LoadableBlock loadable={executions} loading="Loading executions..."> {(items) => items.length === 0 ? ( <div className="rounded-2xl border border-dashed border-border py-20"> <EmptyState title="No executions yet" description="Run something and it will show up here." /> </div> ) : ( <div className="grid gap-3"> {items.map((execution) => ( <ExecutionCard key={execution.id} execution={execution} onOpen={() => { void navigation.route(execution.id); }} /> ))} </div> ) } </LoadableBlock> </div> </div> );} const ExecutionDetailSections = (props: { envelope: ExecutionEnvelope;}) => { const { execution, pendingInteraction } = props.envelope; return ( <div className="space-y-6"> <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4"> <MetadataItem label="Status" value={execution.status.replaceAll("_", " ")} /> <MetadataItem label="Created" value={formatTimestamp(execution.createdAt)} /> <MetadataItem label="Started" value={formatTimestamp(execution.startedAt)} /> <MetadataItem label="Completed" value={formatTimestamp(execution.completedAt)} /> </div> <DocumentPanel title="Code" body={execution.code} lang="ts" empty="No code captured." /> <div className="grid gap-4 xl:grid-cols-2"> <DocumentPanel title="Result" body={formatJsonDocument(execution.resultJson)} lang="json" empty="No result recorded." /> <DocumentPanel title="Logs" body={formatJsonDocument(execution.logsJson)} lang="json" empty="No logs recorded." /> </div> {execution.errorText && ( <DocumentPanel title="Error" body={execution.errorText} lang="text" empty="No error recorded." /> )} {pendingInteraction && ( <div className="space-y-4"> <div> <h2 className="text-sm font-semibold text-foreground"> Pending interaction </h2> <p className="mt-1 text-sm text-muted-foreground"> {pendingInteraction.kind} • {pendingInteraction.purpose} </p> </div> <div className="grid gap-3 sm:grid-cols-3"> <MetadataItem label="Kind" value={pendingInteraction.kind} /> <MetadataItem label="Purpose" value={pendingInteraction.purpose} /> <MetadataItem label="Status" value={pendingInteraction.status.replaceAll("_", " ")} /> </div> <div className="grid gap-4 xl:grid-cols-2"> <DocumentPanel title="Request" body={formatJsonDocument(pendingInteraction.payloadJson)} lang="json" empty="No interaction request recorded." /> <DocumentPanel title="Response" body={formatJsonDocument(pendingInteraction.responseJson)} lang="json" empty="No interaction response recorded." /> </div> </div> )} </div> );}; export function ExecutionHistoryDetailPage() { const { executionId } = useExecutorPluginRouteParams<{ executionId: string; }>(); const navigation = useExecutorPluginNavigation(); const execution = useExecution(executionId); return ( <div className="h-full overflow-y-auto"> <div className="mx-auto max-w-6xl px-6 py-10 lg:px-10 lg:py-14"> <div className="mb-8 flex flex-wrap items-center justify-between gap-4"> <div> <Button variant="ghost" type="button" onClick={() => { void navigation.route(); }} className="mb-3 h-auto p-0 text-xs font-medium uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground" > Back to history </Button> <div className="flex items-center gap-3"> <h1 className="font-display text-2xl tracking-tight text-foreground lg:text-3xl"> {executionId} </h1> </div> </div> </div> <LoadableBlock loadable={execution} loading="Loading execution..."> {(envelope) => <ExecutionDetailSections envelope={envelope} />} </LoadableBlock> </div> </div> );}
import type {
Execution,
ExecutionEnvelope,
} from "@executor/react";
import {
useExecution,
useExecutions,
} from "@executor/react";
import {
Alert,
Badge,
Button,
Card,
DocumentPanel,
EmptyState,
LoadableBlock,
useExecutorPluginNavigation,
useExecutorPluginRouteParams,
} from "@executor/react/plugins";
const formatTimestamp = (value: number | null): string => {
if (value === null) {
return "—";
}
return new Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
timeStyle: "short",
}).format(value);
};
const formatDuration = (execution: Execution): string | null => {
if (execution.startedAt === null || execution.completedAt === null) {
return null;
}
const durationMs = Math.max(0, execution.completedAt - execution.startedAt);
if (durationMs < 1_000) {
return `${durationMs} ms`;
}
if (durationMs < 60_000) {
return `${(durationMs / 1_000).toFixed(1)} s`;
}
return `${(durationMs / 60_000).toFixed(1)} min`;
};
const summarizeCode = (code: string): string =>
code
.trim()
.replace(/\s+/g, " ")
.slice(0, 220);
const formatJsonDocument = (value: string | null): string | null => {
if (value === null) {
return null;
}
try {
return JSON.stringify(JSON.parse(value), null, 2);
} catch {
return value;
}
};
const statusVariant = (
status: Execution["status"],
): "default" | "muted" | "outline" | "destructive" => {
switch (status) {
case "completed":
return "default";
case "failed":
return "destructive";
case "running":
case "waiting_for_interaction":
return "outline";
default:
return "muted";
}
};
const MetadataItem = (props: {
label: string;
value: string;
}) => (
<Card className="bg-card/60 px-3 py-2">
<div className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70">
{props.label}
</div>
<div className="mt-1 text-sm text-foreground">{props.value}</div>
</Card>
);
const ExecutionCard = (props: {
execution: Execution;
onOpen: () => void;
}) => {
const { execution } = props;
return (
<Button
variant="ghost"
type="button"
onClick={props.onOpen}
className="h-auto w-full rounded-2xl border border-border bg-card px-5 py-4 text-left transition-colors hover:border-primary/25 hover:bg-card/90"
>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<div className="truncate text-sm font-semibold text-foreground">
{execution.id}
</div>
<Badge variant={statusVariant(execution.status)}>
{execution.status.replaceAll("_", " ")}
</Badge>
</div>
<div className="mt-1 text-xs text-muted-foreground">
Created {formatTimestamp(execution.createdAt)}
{formatDuration(execution) ? ` • ${formatDuration(execution)}` : ""}
</div>
<p className="mt-3 line-clamp-3 text-sm leading-6 text-muted-foreground">
{summarizeCode(execution.code) || "No code captured."}
</p>
</div>
</div>
{execution.errorText && (
<Alert variant="destructive" className="mt-3 text-xs">
{execution.errorText}
</Alert>
)}
</Button>
);
};
export function ExecutionHistoryPage() {
const executions = useExecutions();
const navigation = useExecutorPluginNavigation();
return (
<div className="h-full overflow-y-auto">
<div className="mx-auto max-w-5xl px-6 py-10 lg:px-10 lg:py-14">
<div className="mb-8">
<h1 className="font-display text-3xl tracking-tight text-foreground lg:text-4xl">
Execution history
</h1>
<p className="mt-1.5 text-[14px] text-muted-foreground">
Every execution recorded for this workspace, newest first.
</p>
</div>
<LoadableBlock loadable={executions} loading="Loading executions...">
{(items) =>
items.length === 0 ? (
<div className="rounded-2xl border border-dashed border-border py-20">
<EmptyState
title="No executions yet"
description="Run something and it will show up here."
/>
</div>
) : (
<div className="grid gap-3">
{items.map((execution) => (
<ExecutionCard
key={execution.id}
execution={execution}
onOpen={() => {
void navigation.route(execution.id);
}}
/>
))}
</div>
)
}
</LoadableBlock>
</div>
</div>
);
}
const ExecutionDetailSections = (props: {
envelope: ExecutionEnvelope;
}) => {
const { execution, pendingInteraction } = props.envelope;
return (
<div className="space-y-6">
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
<MetadataItem label="Status" value={execution.status.replaceAll("_", " ")} />
<MetadataItem label="Created" value={formatTimestamp(execution.createdAt)} />
<MetadataItem label="Started" value={formatTimestamp(execution.startedAt)} />
<MetadataItem
label="Completed"
value={formatTimestamp(execution.completedAt)}
/>
</div>
<DocumentPanel
title="Code"
body={execution.code}
lang="ts"
empty="No code captured."
/>
<div className="grid gap-4 xl:grid-cols-2">
<DocumentPanel
title="Result"
body={formatJsonDocument(execution.resultJson)}
lang="json"
empty="No result recorded."
/>
<DocumentPanel
title="Logs"
body={formatJsonDocument(execution.logsJson)}
lang="json"
empty="No logs recorded."
/>
</div>
{execution.errorText && (
<DocumentPanel
title="Error"
body={execution.errorText}
lang="text"
empty="No error recorded."
/>
)}
{pendingInteraction && (
<div className="space-y-4">
<div>
<h2 className="text-sm font-semibold text-foreground">
Pending interaction
</h2>
<p className="mt-1 text-sm text-muted-foreground">
{pendingInteraction.kind} • {pendingInteraction.purpose}
</p>
</div>
<div className="grid gap-3 sm:grid-cols-3">
<MetadataItem label="Kind" value={pendingInteraction.kind} />
<MetadataItem label="Purpose" value={pendingInteraction.purpose} />
<MetadataItem
label="Status"
value={pendingInteraction.status.replaceAll("_", " ")}
/>
</div>
<div className="grid gap-4 xl:grid-cols-2">
<DocumentPanel
title="Request"
body={formatJsonDocument(pendingInteraction.payloadJson)}
lang="json"
empty="No interaction request recorded."
/>
<DocumentPanel
title="Response"
body={formatJsonDocument(pendingInteraction.responseJson)}
lang="json"
empty="No interaction response recorded."
/>
</div>
</div>
)}
</div>
);
};
export function ExecutionHistoryDetailPage() {
const { executionId } = useExecutorPluginRouteParams<{
executionId: string;
}>();
const navigation = useExecutorPluginNavigation();
const execution = useExecution(executionId);
return (
<div className="h-full overflow-y-auto">
<div className="mx-auto max-w-6xl px-6 py-10 lg:px-10 lg:py-14">
<div className="mb-8 flex flex-wrap items-center justify-between gap-4">
<div>
<Button
variant="ghost"
type="button"
onClick={() => {
void navigation.route();
}}
className="mb-3 h-auto p-0 text-xs font-medium uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground"
>
Back to history
</Button>
<div className="flex items-center gap-3">
<h1 className="font-display text-2xl tracking-tight text-foreground lg:text-3xl">
{executionId}
</h1>
</div>
</div>
</div>
<LoadableBlock loadable={execution} loading="Loading execution...">
{(envelope) => <ExecutionDetailSections envelope={envelope} />}
</LoadableBlock>
</div>
</div>
);
}