Files
2026-11-14 09:56:45 +00:00

66 lines
2.1 KiB
TypeScript

"use client"
// Starting a run: the goal and the plan as JSON. The plan is what the run will do, shown to the
// person before they start it, and admitted by the server against the hard ceilings. The start resolves
// once the live stream has seen the new run, so the page's live run list already shows it.
import type { ProjectSlugs } from "@conusai-kit/ui"
import { useWrite } from "@conusai-kit/ui/data"
import { FormLayout, PageSection, TextField } from "@conusai-kit/ui/patterns"
import type { ReactElement } from "react"
export interface StartFormLabels {
readonly heading: string
readonly goalLabel: string
readonly goalHint: string
readonly planLabel: string
readonly planHint: string
readonly submit: string
readonly starting: string
readonly failed: string
}
const EXAMPLE = JSON.stringify([
{ key: "read", label: "Read the document", tool: "documents.read", args: { fileId: "…" } },
])
const parses = (text: string): boolean => {
try {
JSON.parse(text)
return true
} catch {
return false
}
}
export function StartForm({ slugs, labels }: { slugs: ProjectSlugs; labels: StartFormLabels }): ReactElement {
const startRun = useWrite<ProjectSlugs & { goal: string; plan: unknown }, unknown>("agent.run.start", {
org: slugs.org,
})
return (
<PageSection title={labels.heading} variant="card">
<FormLayout
submitLabel={labels.submit}
busyLabel={labels.starting}
validate={({ plan = "" }) =>
parses(plan) ? undefined : { plan: labels.failed.replace("{detail}", "invalid JSON") }
}
onSubmit={async ({ goal = "", plan = "" }) => {
await startRun.mutate({ ...slugs, goal, plan: JSON.parse(plan) })
}}
>
<TextField name="goal" label={labels.goalLabel} description={labels.goalHint} required maxLength={2_000} />
<TextField
multiline
name="plan"
label={labels.planLabel}
description={labels.planHint.replace("{example}", EXAMPLE)}
required
rows={6}
defaultValue={EXAMPLE}
/>
</FormLayout>
</PageSection>
)
}