Merge archiving an agent from its profile: owners only, asked twice, never g1t
1 file+56−20/1 viewed
| 1 | − | import { data, redirect, useOutletContext } from "react-router"; | |
| 1 | + | import { useState } from "react"; | |
| 2 | + | import { Form, data, redirect, useNavigation, useOutletContext } from "react-router"; | |
| 2 | 3 | ||
| 3 | 4 | import type { WorkspaceAgent } from "@g1t/contracts"; | |
| 4 | 5 | ||
| ⋯ | |||
| 22 | 23 | const viewer = requireUser(context, request); | |
| 23 | 24 | const slug = params.owner.toLowerCase(); | |
| 24 | 25 | if (!roleIn(viewer, slug)) throw data(null, { status: 404 }); | |
| 25 | − | const read = readAgentForm(await request.formData(), { orchestrator: params.handle.toLowerCase() === "g1t" }); | |
| 26 | + | const form = await request.formData(); | |
| 27 | + | // Archiving keeps the agent's history (runs, replies, spend) but takes it | |
| 28 | + | // out of chat and frees its handle. g1t itself can't be archived. | |
| 29 | + | if (form.get("intent") === "archive") { | |
| 30 | + | const archived = await workspaceAgents.archive(slug, params.handle.toLowerCase(), viewer).catch(() => null); | |
| 31 | + | if (!archived) return { errors: { form: "The agents service didn't answer. Try again in a moment." }, saved: false }; | |
| 32 | + | if (!archived.ok) return { errors: { form: archived.error.message }, saved: false }; | |
| 33 | + | throw redirect(`/${slug}/-/agents`); | |
| 34 | + | } | |
| 35 | + | const read = readAgentForm(form, { orchestrator: params.handle.toLowerCase() === "g1t" }); | |
| 26 | 36 | if (!read.ok) return { errors: read.errors, saved: false }; | |
| 27 | 37 | const saved = await workspaceAgents.update(slug, params.handle.toLowerCase(), viewer, read.input).catch(() => null); | |
| 28 | 38 | if (!saved) return { errors: { form: "The agents service didn't answer. Try again in a moment." }, saved: false }; | |
| ⋯ | |||
| 53 | 63 | teams={loaderData.teams} | |
| 54 | 64 | seed={agent.avatar_seed || agent.id} | |
| 55 | 65 | /> | |
| 66 | + | {!isOrchestrator(agent) && <Archive name={agent.display_name} />} | |
| 56 | 67 | </> | |
| 57 | 68 | ); | |
| 58 | 69 | } | |
| 70 | + | ||
| 71 | + | /** Retiring an agent: asked twice, since it leaves every channel at once. */ | |
| 72 | + | function Archive({ name }: { name: string }) { | |
| 73 | + | const [asking, setAsking] = useState(false); | |
| 74 | + | const busy = useNavigation().formData?.get("intent") === "archive"; | |
| 75 | + | return ( | |
| 76 | + | <section className="mt-12 border-t border-line pt-8 md:grid md:grid-cols-[14rem_1fr] md:gap-10"> | |
| 77 | + | <div className="mb-4 md:mb-0"> | |
| 78 | + | <h2 className="text-sm font-semibold text-fg">Archive</h2> | |
| 79 | + | <p className="mt-1 text-sm text-muted">Retire this agent. Only owners can.</p> | |
| 80 | + | </div> | |
| 81 | + | <div> | |
| 82 | + | <p className="text-sm text-muted"> | |
| 83 | + | {name} stops answering, leaves every channel and DM, and its handle is free to use again. Its runs, replies and spend stay in | |
| 84 | + | the audit log and on Usage. | |
| 85 | + | </p> | |
| 86 | + | {asking ? ( | |
| 87 | + | <Form method="post" className="mt-4 flex flex-wrap items-center gap-3"> | |
| 88 | + | <input type="hidden" name="intent" value="archive" /> | |
| 89 | + | <button | |
| 90 | + | type="submit" | |
| 91 | + | disabled={busy} | |
| 92 | + | className="rounded-lg border border-danger/50 bg-danger/15 px-4 py-2 text-sm font-medium text-danger hover:bg-danger/25 disabled:opacity-60" | |
| 93 | + | > | |
| 94 | + | {busy ? "Archiving…" : `Archive ${name}`} | |
| 95 | + | </button> | |
| 96 | + | <button type="button" onClick={() => setAsking(false)} className="px-2 py-2 text-sm text-muted hover:text-fg"> | |
| 97 | + | Keep {name} | |
| 98 | + | </button> | |
| 99 | + | </Form> | |
| 100 | + | ) : ( | |
| 101 | + | <button | |
| 102 | + | type="button" | |
| 103 | + | onClick={() => setAsking(true)} | |
| 104 | + | className="mt-4 rounded-lg border border-line px-4 py-2 text-sm text-muted hover:border-danger/50 hover:text-danger" | |
| 105 | + | > | |
| 106 | + | Archive agent… | |
| 107 | + | </button> | |
| 108 | + | )} | |
| 109 | + | </div> | |
| 110 | + | </section> | |
| 111 | + | ); | |
| 112 | + | } | |