This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"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.
|
||||
|
||||
import { type ProjectSlugs, useTransport } from "@conusai-kit/ui"
|
||||
import { type FormEvent, type ReactElement, useId, useState } 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
|
||||
}
|
||||
|
||||
type State = { kind: "idle" } | { kind: "busy" } | { kind: "failed"; detail: string }
|
||||
|
||||
const EXAMPLE = JSON.stringify([
|
||||
{ key: "read", label: "Read the document", tool: "documents.read", args: { fileId: "…" } },
|
||||
])
|
||||
|
||||
export function StartForm({ slugs, labels }: { slugs: ProjectSlugs; labels: StartFormLabels }): ReactElement {
|
||||
const { call, describe } = useTransport()
|
||||
const goalId = useId()
|
||||
const planId = useId()
|
||||
const [state, setState] = useState<State>({ kind: "idle" })
|
||||
|
||||
const start = async (event: FormEvent<HTMLFormElement>): Promise<void> => {
|
||||
event.preventDefault()
|
||||
const form = new FormData(event.currentTarget)
|
||||
const goal = String(form.get("goal") ?? "").trim()
|
||||
let plan: unknown
|
||||
try {
|
||||
plan = JSON.parse(String(form.get("plan") ?? ""))
|
||||
} catch {
|
||||
setState({ kind: "failed", detail: "invalid JSON" })
|
||||
return
|
||||
}
|
||||
setState({ kind: "busy" })
|
||||
try {
|
||||
await call("agent.run.start", { ...slugs, goal, plan })
|
||||
window.location.reload()
|
||||
} catch (error) {
|
||||
setState({ kind: "failed", detail: describe(error) })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section aria-labelledby={`${goalId}-heading`}>
|
||||
<h2 id={`${goalId}-heading`}>{labels.heading}</h2>
|
||||
<form onSubmit={(event) => void start(event)}>
|
||||
<label htmlFor={goalId}>{labels.goalLabel}</label>
|
||||
<input id={goalId} name="goal" required maxLength={2_000} aria-describedby={`${goalId}-hint`} />
|
||||
<p id={`${goalId}-hint`}>{labels.goalHint}</p>
|
||||
<label htmlFor={planId}>{labels.planLabel}</label>
|
||||
<textarea
|
||||
id={planId}
|
||||
name="plan"
|
||||
required
|
||||
rows={6}
|
||||
defaultValue={EXAMPLE}
|
||||
aria-describedby={`${planId}-hint`}
|
||||
/>
|
||||
<p id={`${planId}-hint`}>{labels.planHint.replace("{example}", EXAMPLE)}</p>
|
||||
<button type="submit" disabled={state.kind === "busy"}>
|
||||
{state.kind === "busy" ? labels.starting : labels.submit}
|
||||
</button>
|
||||
</form>
|
||||
{state.kind === "failed" ? <p role="alert">{labels.failed.replace("{detail}", state.detail)}</p> : null}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user