88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
"use client"
|
|
|
|
import { Button } from "@conusai-kit/ui/primitives"
|
|
|
|
// One run's controls: cancel while it runs, and recovery from its terminal states. The run page is
|
|
// live (live-run.tsx), so a cancel shows up there without a reload. The recovery
|
|
// copy tells the person what the state means and what they can do — failed runs can be started
|
|
// again from the same plan, cancelled runs are simply over, and an uncertain run (a timeout after
|
|
// an external effect) is reconciled by a person, not retried blindly.
|
|
|
|
import type { ProjectSlugs } from "@conusai-kit/ui"
|
|
import { useWrite } from "@conusai-kit/ui/data"
|
|
import { type ReactElement, useState } from "react"
|
|
import type { RunView } from "../contract/index.js"
|
|
|
|
export interface RunControlsLabels {
|
|
readonly cancel: string
|
|
readonly cancelling: string
|
|
readonly failedHint: string
|
|
readonly cancelledHint: string
|
|
readonly uncertainHint: string
|
|
readonly budgetExhausted: string
|
|
readonly approvalUnavailable: string
|
|
readonly toolError: string
|
|
readonly planRefused: string
|
|
readonly internal: string
|
|
}
|
|
|
|
export function RunControls({
|
|
slugs,
|
|
run,
|
|
labels,
|
|
}: {
|
|
slugs: ProjectSlugs
|
|
run: Pick<RunView, "runId" | "status" | "failureCode">
|
|
labels: RunControlsLabels
|
|
}): ReactElement {
|
|
const cancelRun = useWrite<ProjectSlugs & { runId: string }, unknown>("agent.run.cancel")
|
|
const [problem, setProblem] = useState(false)
|
|
const [busy, setBusy] = useState(false)
|
|
|
|
const cancel = async (): Promise<void> => {
|
|
setBusy(true)
|
|
setProblem(false)
|
|
try {
|
|
await cancelRun.mutate({ ...slugs, runId: run.runId })
|
|
} catch {
|
|
setProblem(true)
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
const active = run.status === "queued" || run.status === "running" || run.status === "awaiting_approval"
|
|
const failureText = (code: RunView["failureCode"]): string => {
|
|
switch (code) {
|
|
case "budget_exhausted":
|
|
return labels.budgetExhausted
|
|
case "approval_unavailable":
|
|
return labels.approvalUnavailable
|
|
case "tool_error":
|
|
return labels.toolError
|
|
case "plan_refused":
|
|
return labels.planRefused
|
|
default:
|
|
return labels.internal
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
{problem ? <p role="alert">{labels.internal}</p> : null}
|
|
{active ? (
|
|
<Button type="button" variant="outline" disabled={busy} onClick={() => void cancel()}>
|
|
{busy ? labels.cancelling : labels.cancel}
|
|
</Button>
|
|
) : null}
|
|
{run.status === "failed" ? (
|
|
<p role="alert">
|
|
{failureText(run.failureCode)} {labels.failedHint}
|
|
</p>
|
|
) : null}
|
|
{run.status === "cancelled" ? <p role="status">{labels.cancelledHint}</p> : null}
|
|
{run.status === "uncertain" ? <p role="alert">{labels.uncertainHint}</p> : null}
|
|
</div>
|
|
)
|
|
}
|