Skip to content
89 linesCodeBlameRaw
1/**
2 * Agent templates: the starting points g1t provides for a new agent, by
3 * department, with the agents in the workspace that started from each. A
4 * template isn't installed: an owner starts an agent from one (a member,
5 * a personal agent) and configures it, and the agent is its own from then on.
6 */
7import { ArrowLeft, Plus } from "lucide-react";
8import { Link, data } from "react-router";
9
10import type { Route } from "./+types/templates";
11import { TemplateCard } from "../../../components/agent-templates";
12import { EmptyState } from "../../../components/ui";
13import { byDepartment, startPath, templateListings } from "../../../lib/agent-templates";
14import { loadTemplates } from "../../../lib/agent-templates.server";
15import { page } from "../../../lib/meta";
16import { requireUser, roleIn } from "../../../lib/session.server";
17
18export function meta({ params, ...args }: Route.MetaArgs) {
19 return page(args, { title: `Agent templates · ${params.owner} · g1t` });
20}
21
22export async function loader({ params, context, request }: Route.LoaderArgs) {
23 const viewer = requireUser(context, request);
24 const slug = params.owner.toLowerCase();
25 const role = roleIn(viewer, slug);
26 if (!role) throw data(null, { status: 404 });
27 const loaded = await loadTemplates(slug, viewer);
28 // Owners start agents; members start personal ones, unless owners turned that off.
29 return { slug, owner: role === "owner" || loaded.members_create_agents, ...loaded };
30}
31
32export default function AgentTemplates({ loaderData }: Route.ComponentProps) {
33 const { slug, owner, templates, agents } = loaderData;
34 const listings = templateListings(templates ?? [], agents);
35 return (
36 <div className="pb-4">
37 <Link to={`/${slug}/-/agents`} className="inline-flex items-center gap-1.5 text-sm text-muted hover:text-fg">
38 <ArrowLeft size={14} />
39 Agents
40 </Link>
41 <header className="mt-4 mb-8 flex flex-wrap items-end justify-between gap-4">
42 <div className="min-w-0 grow basis-lg">
43 <h1 className="text-2xl font-semibold tracking-tight">Templates</h1>
44 <p className="mt-1.5 max-w-2xl text-sm text-muted">
45 Starting points for a new agent, each a role with responsibilities, helpers, a voice, model limits and instructions. Start an agent from one and make it yours: its name, instructions, voice, models and budget are all
46 yours to change, then and later. A template is never updated under an agent started from it.
47 </p>
48 </div>
49 {owner && (
50 <Link to={`/${slug}/-/agents/new`} className="inline-flex h-9 shrink-0 items-center gap-1.5 rounded-md bg-fg px-4 text-sm font-medium text-bg hover:bg-fg-hover">
51 <Plus size={15} />
52 New agent
53 </Link>
54 )}
55 </header>
56 {templates == null ? (
57 <EmptyState title="Templates can't be shown right now">The agents service didn't answer. Reload in a minute.</EmptyState>
58 ) : (
59 <div className="space-y-10">
60 {/* One grid in department order: most departments have a single template. */}
61 <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
62 {byDepartment(listings)
63 .flatMap(([, list]) => list)
64 .map((listing) => (
65 <TemplateCard key={listing.template.id} listing={listing} slug={slug} owner={owner} />
66 ))}
67 </div>
68 {owner && (
69 <section aria-label="Your own">
70 <h2 className="mb-3 text-xs font-semibold tracking-wide text-faint uppercase">Your own</h2>
71 <Link
72 to={`/${slug}/-/agents/new`}
73 className="flex max-w-md items-center gap-3 rounded-xl border border-dashed border-line-strong p-4 text-sm transition-colors hover:border-fg/40 hover:bg-raised/40"
74 >
75 <span className="flex size-11 shrink-0 items-center justify-center rounded-lg bg-raised text-muted">
76 <Plus size={18} />
77 </span>
78 <span className="min-w-0">
79 <span className="block font-medium">Describe it</span>
80 <span className="block text-xs text-muted">Say what it should do, and g1t drafts the rest for you to change and try.</span>
81 </span>
82 </Link>
83 </section>
84 )}
85 </div>
86 )}
87 </div>
88 );
89}