Skip to content
160 linesCodeBlameRaw
1/**
2 * One agent template: what an agent started from it is responsible for,
3 * the helpers it works with, how it talks, which models it runs on, the
4 * instructions it starts from, what you configure when you start one, and
5 * the agents in the workspace that started from it.
6 */
7import { ArrowLeft } from "lucide-react";
8import { Link, data } from "react-router";
9
10import type { Route } from "./+types/template";
11import { PixelCreature } from "../../../components/agent-avatar";
12import { StartAction } from "../../../components/agent-templates";
13import { PRESETS } from "../../../lib/agent-form";
14import { routingWords, templateListings, templatesPath } from "../../../lib/agent-templates";
15import { loadTemplates } from "../../../lib/agent-templates.server";
16import { page } from "../../../lib/meta";
17import { requireUser, roleIn } from "../../../lib/session.server";
18
19export function meta({ params, ...args }: Route.MetaArgs) {
20 const template = args.loaderData?.templates?.find((t) => t.id === params.template);
21 return page(args, { title: `${template ? template.title : "Template"} · Agent templates · ${params.owner} · g1t` });
22}
23
24export async function loader({ params, context, request }: Route.LoaderArgs) {
25 const viewer = requireUser(context, request);
26 const slug = params.owner.toLowerCase();
27 const role = roleIn(viewer, slug);
28 if (!role) throw data(null, { status: 404 });
29 const loaded = await loadTemplates(slug, viewer);
30 if (loaded.templates && !loaded.templates.some((t) => t.id === params.template)) throw data(null, { status: 404 });
31 return { slug, owner: role === "owner", ...loaded };
32}
33
34export default function AgentTemplate({ loaderData, params }: Route.ComponentProps) {
35 const { slug, owner } = loaderData;
36 const listing = templateListings(loaderData.templates ?? [], loaderData.agents).find((l) => l.template.id === params.template);
37 const back = (
38 <Link to={templatesPath(slug)} className="inline-flex items-center gap-1.5 text-sm text-muted hover:text-fg">
39 <ArrowLeft size={14} />
40 All templates
41 </Link>
42 );
43 if (!listing) {
44 return (
45 <div>
46 {back}
47 <p className="mt-6 text-sm text-muted">Templates can't be shown right now: the agents service didn't answer. Reload in a minute.</p>
48 </div>
49 );
50 }
51 const { template, agents } = listing;
52 const voice = PRESETS.find((preset) => preset.value === template.personality_preset);
53 const names = [template.display_name, ...template.name_ideas.filter((name) => name !== template.display_name)];
54 return (
55 <div className="pb-4">
56 {back}
57 <header className="mt-5 flex flex-wrap items-center gap-4">
58 <PixelCreature seed={template.handle} size={64} />
59 <div className="min-w-0 grow basis-60">
60 <h1 className="text-2xl font-semibold tracking-tight">{template.title}</h1>
61 <p className="mt-0.5 text-sm text-muted">{template.department ? `${template.department} · ` : ""}A template from g1t</p>
62 </div>
63 <StartAction listing={listing} slug={slug} owner={owner} className="h-9 px-4 text-sm" />
64 </header>
65
66 <div className="mt-8 grid gap-8 lg:grid-cols-[minmax(0,1fr)_18rem]">
67 <div className="min-w-0 space-y-8">
68 <section aria-labelledby="duties">
69 <h2 id="duties" className="text-sm font-semibold">
70 What it's responsible for
71 </h2>
72 <ul className="mt-3 space-y-2">
73 {template.responsibilities.map((duty) => (
74 <li key={duty} className="flex gap-2.5 text-sm text-fg-soft">
75 <span className="mt-2 size-1.5 shrink-0 rounded-full bg-accent" aria-hidden="true" />
76 {duty}
77 </li>
78 ))}
79 </ul>
80 </section>
81
82 {template.subagents.length > 0 && (
83 <section aria-labelledby="helpers">
84 <h2 id="helpers" className="text-sm font-semibold">
85 Helpers it works with
86 </h2>
87 <p className="mt-1 text-sm text-muted">Smaller agents it starts for one piece of its work, paid from its own budget.</p>
88 <ul className="mt-3 divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
89 {template.subagents.map((helper) => (
90 <li key={helper.name} className="px-4 py-3">
91 <p className="font-mono text-[0.8125rem]">{helper.name}</p>
92 <p className="mt-0.5 text-sm text-muted">{helper.description}</p>
93 </li>
94 ))}
95 </ul>
96 </section>
97 )}
98
99 <section aria-labelledby="instructions">
100 <h2 id="instructions" className="text-sm font-semibold">
101 The instructions it starts from
102 </h2>
103 <p className="mt-1 text-sm text-muted">Yours to edit when you start the agent, and on its profile after.</p>
104 <pre className="mt-3 max-h-96 overflow-auto rounded-xl border border-line bg-surface p-4 font-sans text-[0.8125rem] leading-relaxed whitespace-pre-wrap text-fg-soft">{template.instructions}</pre>
105 </section>
106 </div>
107
108 <aside className="space-y-6 text-sm">
109 <section aria-labelledby="configure" className="rounded-xl border border-line bg-surface p-4">
110 <h2 id="configure" className="text-xs text-faint">
111 What you configure
112 </h2>
113 <dl className="mt-3 space-y-4">
114 <div>
115 <dt className="text-xs text-faint">Name</dt>
116 <dd className="mt-0.5">{names.slice(0, 4).join(", ")}, or your own</dd>
117 </div>
118 <div>
119 <dt className="text-xs text-faint">Voice</dt>
120 <dd className="mt-0.5">{voice ? voice.label : template.personality_preset}</dd>
121 {voice && <dd className="text-xs text-muted">{voice.about}</dd>}
122 </div>
123 <div>
124 <dt className="text-xs text-faint">Models</dt>
125 <dd className="mt-0.5">{routingWords(template.routing)}</dd>
126 </div>
127 <div>
128 <dt className="text-xs text-faint">Budget</dt>
129 <dd className="mt-0.5">The workspace's default for a new agent, billed at what it costs</dd>
130 </div>
131 <div>
132 <dt className="text-xs text-faint">Team and access</dt>
133 <dd className="mt-0.5">A team you pick. It works with the access of the person asking, never more.</dd>
134 </div>
135 </dl>
136 </section>
137 <section aria-labelledby="here" className="rounded-xl border border-line bg-surface p-4">
138 <h2 id="here" className="text-xs text-faint">
139 In this workspace
140 </h2>
141 {agents.length === 0 ? (
142 <p className="mt-1 text-muted">No agent has started from it yet.</p>
143 ) : (
144 <ul className="mt-2 space-y-1.5">
145 {agents.map((agent) => (
146 <li key={agent.id} className="flex items-center gap-2">
147 <PixelCreature seed={agent.handle} size={20} />
148 <Link to={`/${slug}/-/agents/${agent.handle}`} className="truncate hover:underline">
149 {agent.display_name} <span className="text-faint">@{agent.handle}</span>
150 </Link>
151 </li>
152 ))}
153 </ul>
154 )}
155 </section>
156 </aside>
157 </div>
158 </div>
159 );
160}