g1t/apps/web/app/routes/repo/intent.tsx

314 lines11,977 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Initial g1t: services, event bus, intents and attempts1import { env } from "cloudflare:workers";
Hosted agents: sandboxes on Cloudflare Containers started from an intent2import { Bot, GitCommitHorizontal, Play, Sparkles, Terminal } from "lucide-react";
3import { useEffect } from "react";
4import {
5 Form,
6 Link,
7 redirect,
8 useNavigation,
9 useRevalidator,
10} from "react-router";
Initial g1t: services, event bus, intents and attempts11
12import type { Route } from "./+types/intent";
API and MCP server, Rust identity service, registration, site redesign13import { Markdown } from "../../components/markdown";
14import {
15 Avatar,
16 Button,
17 CopyLine,
18 EmptyState,
19 ErrorText,
20 Input,
21 Status,
Hosted agents: sandboxes on Cloudflare Containers started from an intent22 Textarea,
API and MCP server, Rust identity service, registration, site redesign23 TimeAgo,
24} from "../../components/ui";
Initial g1t: services, event bus, intents and attempts25import {
26 assertSameOrigin,
27 getViewer,
28 requireUser,
29 unwrap,
30} from "../../lib/session.server";
31
Hosted agents: sandboxes on Cloudflare Containers started from an intent32const REFRESH_MS = 4000;
33
API and MCP server, Rust identity service, registration, site redesign34export function meta({ loaderData, params }: Route.MetaArgs) {
35 const title = loaderData ? `${loaderData.intent.title} · ` : "";
Initial g1t: services, event bus, intents and attempts36 return [{ title: `${title}${params.owner}/${params.repo} · g1t` }];
37}
38
39export async function loader({ params, context }: Route.LoaderArgs) {
40 const viewer = getViewer(context);
41 const detail = unwrap(
42 await env.WORK.getIntent(
43 { namespace: params.owner, name: params.repo },
44 Number(params.number),
45 viewer,
46 ),
47 );
Hosted agents: sandboxes on Cloudflare Containers started from an intent48 return {
49 ...detail,
50 viewer,
g1t agents: model menu and optional AI Gateway routing51 agentModels: await env.RUNNER.models(viewer),
Hosted agents: sandboxes on Cloudflare Containers started from an intent52 };
Initial g1t: services, event bus, intents and attempts53}
54
55export async function action({ request, params, context }: Route.ActionArgs) {
56 assertSameOrigin(request);
57 const user = requireUser(context, request);
58 const form = await request.formData();
59 const intentId = String(form.get("intentId") ?? "");
60
Hosted agents: sandboxes on Cloudflare Containers started from an intent61 if (form.get("action") === "run-hosted") {
62 const result = await env.RUNNER.run(user, intentId, {
63 count: Number(form.get("count")),
64 instructions: String(form.get("instructions") ?? ""),
g1t agents: model menu and optional AI Gateway routing65 model: String(form.get("model") ?? "") || undefined,
Hosted agents: sandboxes on Cloudflare Containers started from an intent66 });
67 return result.ok ? null : { error: result.error.message };
68 }
Initial g1t: services, event bus, intents and attempts69 if (form.get("action") === "withdraw") {
70 const result = await env.WORK.withdrawIntent(user, intentId);
71 return result.ok ? null : { error: result.error.message };
72 }
73 const result = await env.WORK.startAttempt(user, intentId, {
74 agent: String(form.get("agent") ?? ""),
75 runtime: "external",
76 });
77 if (!result.ok) return { error: result.error.message };
API and MCP server, Rust identity service, registration, site redesign78 throw redirect(
79 `/${params.owner}/${params.repo}/attempts/${result.value.id}`,
80 );
Initial g1t: services, event bus, intents and attempts81}
82
83export default function IntentPage({
84 loaderData,
85 actionData,
86 params,
87}: Route.ComponentProps) {
g1t agents: model menu and optional AI Gateway routing88 const { intent, attempts, viewer, agentModels } = loaderData;
Hosted agents: sandboxes on Cloudflare Containers started from an intent89
90 // Follow running attempts without a manual reload.
91 const revalidator = useRevalidator();
92 const navigation = useNavigation();
93 const running = attempts.some((attempt) => attempt.status === "working");
94 useEffect(() => {
95 if (!running) return;
96 const timer = setInterval(() => {
97 if (document.visibilityState === "visible") revalidator.revalidate();
98 }, REFRESH_MS);
99 return () => clearInterval(timer);
100 }, [running, revalidator]);
101 const starting = navigation.formData?.get("action") === "run-hosted";
Initial g1t: services, event bus, intents and attempts102 const base = `/${params.owner}/${params.repo}`;
103 const open = intent.status === "open";
104 return (
API and MCP server, Rust identity service, registration, site redesign105 <div className="grid gap-8 lg:grid-cols-[1fr_19rem]">
Initial g1t: services, event bus, intents and attempts106 <div className="min-w-0">
107 <div className="flex items-start gap-3">
API and MCP server, Rust identity service, registration, site redesign108 <h2 className="grow text-2xl font-semibold tracking-tight text-balance">
Initial g1t: services, event bus, intents and attempts109 {intent.title}{" "}
API and MCP server, Rust identity service, registration, site redesign110 <span className="font-normal text-faint">#{intent.number}</span>
Initial g1t: services, event bus, intents and attempts111 </h2>
112 <Status value={intent.status} />
113 </div>
API and MCP server, Rust identity service, registration, site redesign114 <p className="mt-2 flex items-center gap-2 text-sm text-muted">
115 <Avatar name={intent.author.username} size={18} />
116 <span>
117 <span className="font-medium text-fg">{intent.author.username}</span>{" "}
118 opened this <TimeAgo at={intent.createdAt} />
119 </span>
Initial g1t: services, event bus, intents and attempts120 </p>
121
122 {intent.brief && (
API and MCP server, Rust identity service, registration, site redesign123 <div className="mt-6 rounded-xl border border-line bg-surface p-5">
124 <Markdown source={intent.brief} />
125 </div>
Initial g1t: services, event bus, intents and attempts126 )}
127
API and MCP server, Rust identity service, registration, site redesign128 <div className="mt-10 flex items-baseline justify-between">
129 <h3 className="font-semibold tracking-tight">Attempts</h3>
130 <p className="text-sm text-muted">
131 {attempts.length === 0
132 ? "None yet"
133 : `${attempts.length} in the arena`}
Initial g1t: services, event bus, intents and attempts134 </p>
API and MCP server, Rust identity service, registration, site redesign135 </div>
136 <div className="mt-3">
137 {attempts.length === 0 ? (
138 <EmptyState title="No agent has attempted this yet">
139 Start an attempt here, or point an agent at intent{" "}
140 <code className="font-mono">{intent.id}</code>.
141 </EmptyState>
142 ) : (
143 // Each attempt is a lane hanging off a shared rail.
144 <ol className="relative space-y-3 pl-6 before:absolute before:top-2 before:bottom-2 before:left-1.75 before:w-px before:bg-line">
145 {attempts.map((attempt) => (
146 <li key={attempt.id} className="relative">
147 <span
148 className={`absolute top-5 -left-6 size-3.75 rounded-full border-2 border-bg ${
149 attempt.status === "working"
150 ? "bg-accent"
151 : attempt.status === "submitted"
152 ? "bg-info"
153 : attempt.status === "shipped"
154 ? "bg-shipped"
155 : "bg-line-strong"
156 }`}
157 />
158 <Link
159 to={`${base}/attempts/${attempt.id}`}
160 className="block rounded-xl border border-line bg-surface p-4 transition-colors hover:border-line-strong"
161 >
162 <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
163 <span className="flex items-center gap-1.5 font-mono text-sm font-medium">
164 <Bot size={15} className="text-faint" />
165 {attempt.agent}
166 </span>
Hosted agents: sandboxes on Cloudflare Containers started from an intent167 {attempt.runtime === "hosted" && (
168 <span className="rounded-full border border-line px-1.5 py-px text-[0.6875rem] text-faint">
169 hosted
170 </span>
171 )}
API and MCP server, Rust identity service, registration, site redesign172 <span className="text-sm text-faint">
173 attempt {attempt.number}
174 </span>
175 <Status value={attempt.status} />
176 <span className="ml-auto flex items-center gap-3 text-xs text-faint">
177 <span className="flex items-center gap-1 font-mono">
178 <GitCommitHorizontal size={13} />
179 {attempt.headCommit?.slice(0, 7) ?? "no commits"}
180 </span>
181 <TimeAgo at={attempt.updatedAt} />
182 </span>
183 </div>
184 {attempt.summary && (
185 <p className="mt-2.5 line-clamp-2 text-sm text-muted">
186 {attempt.summary}
187 </p>
188 )}
189 </Link>
190 </li>
191 ))}
192 </ol>
193 )}
194 </div>
Initial g1t: services, event bus, intents and attempts195 </div>
196
197 <aside className="space-y-6">
198 {intent.checks.length > 0 && (
199 <section>
API and MCP server, Rust identity service, registration, site redesign200 <h3 className="text-sm font-medium">Acceptance checks</h3>
201 <ul className="mt-2 space-y-1.5">
Initial g1t: services, event bus, intents and attempts202 {intent.checks.map((check) => (
API and MCP server, Rust identity service, registration, site redesign203 <li
204 key={check}
205 className="flex items-center gap-2 rounded-md border border-line bg-surface px-2.5 py-1.5 font-mono text-xs"
206 >
207 <Terminal size={13} className="shrink-0 text-faint" />
208 <span className="truncate">{check}</span>
Initial g1t: services, event bus, intents and attempts209 </li>
210 ))}
211 </ul>
212 </section>
213 )}
API and MCP server, Rust identity service, registration, site redesign214
g1t agents: model menu and optional AI Gateway routing215 {open && agentModels.length > 0 && (
Hosted agents: sandboxes on Cloudflare Containers started from an intent216 <section className="rounded-xl border border-accent/30 bg-accent/5 p-4">
217 <h3 className="flex items-center gap-2 text-sm font-medium">
218 <Sparkles size={15} className="text-accent" />
Diffs on attempts; hosted agent presented as the g1t agent219 Run g1t agents
Hosted agents: sandboxes on Cloudflare Containers started from an intent220 </h3>
221 <p className="mt-1 text-xs text-muted">
Diffs on attempts; hosted agent presented as the g1t agent222 Each agent works in its own sandbox and its own fork, in
223 parallel. Compare what they produce and ship the best.
Hosted agents: sandboxes on Cloudflare Containers started from an intent224 </p>
225 <Form method="post" className="mt-3 space-y-2">
226 <input type="hidden" name="intentId" value={intent.id} />
227 <input type="hidden" name="action" value="run-hosted" />
228 <label className="flex items-center justify-between gap-3 text-sm">
229 <span className="text-muted">Agents</span>
230 <select
231 name="count"
232 defaultValue="1"
233 className="rounded-md border border-line bg-bg px-2 py-1 text-sm"
234 >
235 {[1, 2, 3, 4, 5].map((count) => (
236 <option key={count} value={count}>
237 {count}
238 </option>
239 ))}
240 </select>
241 </label>
g1t agents: model menu and optional AI Gateway routing242 <label className="flex items-center justify-between gap-3 text-sm">
243 <span className="text-muted">Model</span>
244 <select
245 name="model"
246 className="rounded-md border border-line bg-bg px-2 py-1 text-sm"
247 >
248 {agentModels.map((model) => (
249 <option key={model.id} value={model.id} title={model.description}>
250 {model.label}
251 </option>
252 ))}
253 </select>
254 </label>
Hosted agents: sandboxes on Cloudflare Containers started from an intent255 <Textarea
256 name="instructions"
257 rows={2}
258 placeholder="Extra guidance (optional)"
259 />
260 <div className="*:w-full">
261 <Button variant="accent" type="submit" disabled={starting}>
262 <Play size={14} />
263 {starting ? "Starting sandboxes…" : "Run"}
264 </Button>
265 </div>
266 </Form>
267 </section>
268 )}
269
API and MCP server, Rust identity service, registration, site redesign270 {open && (
271 <section className="rounded-xl border border-line bg-surface p-4">
Hosted agents: sandboxes on Cloudflare Containers started from an intent272 <h3 className="text-sm font-medium">Bring your own agent</h3>
API and MCP server, Rust identity service, registration, site redesign273 <p className="mt-1 text-xs text-muted">
274 With g1t connected to your agent, give it this intent id.
Initial g1t: services, event bus, intents and attempts275 </p>
API and MCP server, Rust identity service, registration, site redesign276 <div className="mt-3">
277 <CopyLine text={intent.id} />
278 </div>
279 {viewer ? (
280 <Form method="post" className="mt-4 space-y-2 border-t border-line pt-4">
281 <input type="hidden" name="intentId" value={intent.id} />
282 <p className="text-xs text-muted">
283 Or start an attempt yourself and get a fork to push to.
284 </p>
285 <Input name="agent" placeholder="Agent name, e.g. claude-code" maxLength={60} />
286 <div className="*:w-full">
287 <Button type="submit">Start attempt</Button>
288 </div>
289 </Form>
290 ) : (
291 <p className="mt-4 border-t border-line pt-4 text-sm text-muted">
292 <Link to="/login" className="text-fg underline underline-offset-4">
293 Sign in
294 </Link>{" "}
295 to start an attempt.
296 </p>
297 )}
298 <ErrorText>{actionData?.error}</ErrorText>
Initial g1t: services, event bus, intents and attempts299 </section>
300 )}
API and MCP server, Rust identity service, registration, site redesign301
Initial g1t: services, event bus, intents and attempts302 {open && viewer?.id === intent.author.id && (
303 <Form method="post">
304 <input type="hidden" name="intentId" value={intent.id} />
305 <input type="hidden" name="action" value="withdraw" />
306 <Button variant="quiet" type="submit">
307 Withdraw intent
308 </Button>
309 </Form>
310 )}
311 </aside>
312 </div>
313 );
314}