pr_01m47d15m3e54sn21z27rpy5n9/apps/web/app/routes/repo/intent-new.tsx

60 lines1,949 bytesCodeBlame
1import { env } from "cloudflare:workers";
2import { Form, redirect } from "react-router";
3
4import type { Route } from "./+types/intent-new";
5import { Button, ErrorText, Field, Input, Textarea } from "../../components/ui";
6import { assertSameOrigin, requireUser } from "../../lib/session.server";
7
8export function loader({ request, context }: Route.LoaderArgs) {
9 requireUser(context, request);
10 return null;
11}
12
13export async function action({ request, params, context }: Route.ActionArgs) {
14 assertSameOrigin(request);
15 const user = requireUser(context, request);
16 const form = await request.formData();
17 const result = await env.WORK.openIntent(
18 user,
19 { namespace: params.owner, name: params.repo },
20 {
21 title: String(form.get("title") ?? ""),
22 brief: String(form.get("brief") ?? ""),
23 checks: String(form.get("checks") ?? "").split("\n"),
24 },
25 );
26 if (!result.ok) return { error: result.error.message };
27 throw redirect(
28 `/${params.owner}/${params.repo}/intents/${result.value.number}`,
29 );
30}
31
32export default function NewIntent({ actionData }: Route.ComponentProps) {
33 return (
34 <Form method="post" className="max-w-2xl space-y-4">
35 <Field label="Goal">
36 <Input
37 name="title"
38 required
39 autoFocus
40 maxLength={200}
41 placeholder="Make the parser streaming"
42 />
43 </Field>
44 <Field
45 label="Brief"
46 hint="What an agent is given to work from: the outcome you want, constraints, and anything it should know."
47 >
48 <Textarea name="brief" rows={8} />
49 </Field>
50 <Field
51 label="Acceptance checks (optional)"
52 hint="One command per line. An attempt is accepted when they all pass."
53 >
54 <Textarea name="checks" rows={3} placeholder="cargo test" />
55 </Field>
56 <ErrorText>{actionData?.error}</ErrorText>
57 <Button type="submit">Open intent</Button>
58 </Form>
59 );
60}