Skip to content
45 linesCodeBlameRaw
1import { Outlet, data, type ShouldRevalidateFunctionArgs, useMatch, useRouteLoaderData } from "react-router";
2
3import { type WorkspaceAgent, hasCodeAccess } from "@g1t/contracts";
4
5import { AgentsSidebar } from "../../../components/agents-mode";
6
7import type { Route } from "./+types/layout";
8import { workspaceAgents } from "../../../lib/services.server";
9import { getViewer, roleIn } from "../../../lib/session.server";
10
11/** Agents mode: the workspace's agents, for its sidebar; null when the agents service does not answer. */
12export type AgentsLayoutData = { slug: string; agents: WorkspaceAgent[] | null };
13
14export async function loader({ params, context }: Route.LoaderArgs): Promise<AgentsLayoutData> {
15 const viewer = getViewer(context);
16 if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 });
17 const listed = await workspaceAgents.list(params.owner.toLowerCase(), viewer!).catch(() => null);
18 return {
19 slug: params.owner.toLowerCase(),
20 agents: listed?.ok ? listed.value.filter((agent) => !agent.archived_at).sort((a, b) => a.display_name.localeCompare(b.display_name)) : null,
21 };
22}
23
24/** The list changes when an agent is made or changed, not when moving between them. */
25export function shouldRevalidate({ currentParams, nextParams, formMethod, defaultShouldRevalidate }: ShouldRevalidateFunctionArgs) {
26 if (formMethod && formMethod !== "GET") return defaultShouldRevalidate;
27 return currentParams.owner !== nextParams.owner;
28}
29
30export default function AgentsLayout({ loaderData }: Route.ComponentProps) {
31 const root = useRouteLoaderData("root") as { user?: { workspaces?: { slug: string; role: string; code_access?: boolean }[] } | null } | undefined;
32 const membership = root?.user?.workspaces?.find((m) => m.slug === loaderData.slug) ?? null;
33 // A phone: the agents first, as a list; the fleet's numbers under it.
34 const index = useMatch("/:owner/-/agents");
35 return (
36 <>
37 {index && (
38 <div className="-mx-4 -mt-6 mb-8 md:hidden">
39 <AgentsSidebar slug={loaderData.slug} shellAgents={null} code={hasCodeAccess(membership)} owner={membership?.role === "owner"} phone />
40 </div>
41 )}
42 <Outlet />
43 </>
44 );
45}