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.
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 1 | /** |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 2 | * Memory, as a project's and a workspace's pages show it: what coding agents |
| 3 | * (with the MCP server's remember) and people saved, each with where it came | |
| 4 | * from, to pin, edit or forget. | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 5 | * Both pages post to their own action with the intents in `memoryAction`. |
| 6 | */ | |
| 7 | import { Bot, Brain, Pencil, Pin, PinOff, Trash2, User } from "lucide-react"; | |
| 8 | import { useEffect, useRef, useState } from "react"; | |
| 9 | import { Link, useFetcher } from "react-router"; | |
| 10 | ||
| 11 | import { MEMORY_KINDS, type Memory, type MemoryKind, type MemoryScope, type Result, type User as Actor } from "@g1t/contracts"; | |
| 12 | ||
| Merge branch 'worktree-agent-a985c01f43538af01' | 13 | import { SubmitButton, TimeAgo } from "./ui"; |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 14 | import { |
| 15 | AlertDialog, | |
| 16 | AlertDialogAction, | |
| 17 | AlertDialogCancel, | |
| 18 | AlertDialogContent, | |
| 19 | AlertDialogDescription, | |
| 20 | AlertDialogFooter, | |
| 21 | AlertDialogHeader, | |
| 22 | AlertDialogTitle, | |
| 23 | AlertDialogTrigger, | |
| 24 | } from "./ui/alert-dialog"; | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 25 | import { Button } from "./ui/button"; |
| 26 | import { Card } from "./ui/card"; | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 27 | import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog"; |
| 28 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 29 | import { Hint } from "./ui/hint"; |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 30 | import { Switch } from "./ui/switch"; |
| 31 | ||
| 32 | const KIND: Record<MemoryKind, { label: string; about: string; tone: string }> = { | |
| 33 | fact: { label: "Fact", about: "How something is", tone: "text-info border-info/40" }, | |
| 34 | convention: { label: "Convention", about: "How things are done here", tone: "text-accent border-accent/40" }, | |
| 35 | decision: { label: "Decision", about: "Something chosen, and why", tone: "text-merged border-merged/40" }, | |
| 36 | gotcha: { label: "Gotcha", about: "A trap to avoid", tone: "text-warn border-warn/40" }, | |
| 37 | }; | |
| 38 | ||
| 39 | const TEXTAREA = | |
| 40 | "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim"; | |
| 41 | ||
| 42 | type Done = { ok: boolean; error?: string } | undefined; | |
| 43 | ||
| 44 | function KindBadge({ kind }: { kind: MemoryKind }) { | |
| 45 | return ( | |
| 46 | <span className={`inline-flex shrink-0 items-center rounded-full border px-2 py-px text-[0.6875rem] ${KIND[kind].tone}`}> | |
| 47 | {KIND[kind].label} | |
| 48 | </span> | |
| 49 | ); | |
| 50 | } | |
| 51 | ||
| 52 | function KindSelect({ value }: { value?: MemoryKind }) { | |
| 53 | return ( | |
| 54 | <Select name="kind" defaultValue={value ?? "fact"}> | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 55 | <SelectTrigger size="sm" className="w-40" aria-label="Kind of memory"> |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 56 | <SelectValue /> |
| 57 | </SelectTrigger> | |
| 58 | <SelectContent> | |
| 59 | {MEMORY_KINDS.map((kind) => ( | |
| 60 | <SelectItem key={kind} value={kind} description={KIND[kind].about}> | |
| 61 | {KIND[kind].label} | |
| 62 | </SelectItem> | |
| 63 | ))} | |
| 64 | </SelectContent> | |
| 65 | </Select> | |
| 66 | ); | |
| 67 | } | |
| 68 | ||
| 69 | /** Where a memory came from, linked where it can be. */ | |
| 70 | function Source({ memory }: { memory: Memory }) { | |
| 71 | const { source } = memory; | |
| 72 | const repo = source.repo; | |
| 73 | const where = | |
| 74 | repo && source.number != null ? ( | |
| 75 | <> | |
| 76 | {" "} | |
| 77 | on{" "} | |
| 78 | <Link to={`/${repo.namespace}/${repo.name}/pull/${source.number}`} className="hover:text-fg"> | |
| 79 | {memory.scope === "workspace" && `${repo.name}`}#{source.number} | |
| 80 | </Link> | |
| 81 | </> | |
| 82 | ) : repo && memory.scope === "workspace" ? ( | |
| 83 | <> | |
| 84 | {" "} | |
| 85 | in <Link to={`/${repo.namespace}/${repo.name}`} className="hover:text-fg">{repo.name}</Link> | |
| 86 | </> | |
| 87 | ) : null; | |
| 88 | if (source.kind === "person") { | |
| 89 | return ( | |
| 90 | <span className="inline-flex items-center gap-1"> | |
| 91 | <User size={11} /> | |
| 92 | {memory.createdBy} | |
| 93 | {where} | |
| 94 | </span> | |
| 95 | ); | |
| 96 | } | |
| 97 | return ( | |
| 98 | <span className="inline-flex items-center gap-1"> | |
| 99 | <Bot size={11} /> | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 100 | {memory.createdBy} |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 101 | {where} |
| 102 | </span> | |
| 103 | ); | |
| 104 | } | |
| 105 | ||
| 106 | function EditMemory({ memory, action }: { memory: Memory; action: string }) { | |
| 107 | const fetcher = useFetcher<Done>(); | |
| 108 | const [open, setOpen] = useState(false); | |
| 109 | useEffect(() => { | |
| 110 | if (fetcher.state === "idle" && fetcher.data?.ok) setOpen(false); | |
| 111 | }, [fetcher.state, fetcher.data]); | |
| 112 | return ( | |
| 113 | <Dialog open={open} onOpenChange={setOpen}> | |
| Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar | 114 | <DialogTrigger aria-label="Edit" className="rounded p-1 text-faint max-sm:p-2.5 transition-colors hover:bg-raised hover:text-fg"> |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 115 | <Pencil size={13} /> |
| 116 | </DialogTrigger> | |
| 117 | <DialogContent> | |
| 118 | <DialogHeader> | |
| 119 | <DialogTitle>Edit memory</DialogTitle> | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 120 | <DialogDescription>Agents that recall it get the new wording from now on.</DialogDescription> |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 121 | </DialogHeader> |
| 122 | <fetcher.Form method="post" action={action} className="space-y-3"> | |
| 123 | <input type="hidden" name="intent" value="update" /> | |
| 124 | <input type="hidden" name="id" value={memory.id} /> | |
| 125 | <textarea name="text" required rows={4} maxLength={1000} defaultValue={memory.text} className={TEXTAREA} /> | |
| 126 | <div className="flex items-center justify-between gap-3"> | |
| 127 | <KindSelect value={memory.kind} /> | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 128 | <SubmitButton fetcher={fetcher} pending="Saving…"> |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 129 | Save |
| Merge branch 'worktree-agent-a985c01f43538af01' | 130 | </SubmitButton> |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 131 | </div> |
| 132 | {fetcher.data?.error && <p className="text-sm text-danger">{fetcher.data.error}</p>} | |
| 133 | </fetcher.Form> | |
| 134 | </DialogContent> | |
| 135 | </Dialog> | |
| 136 | ); | |
| 137 | } | |
| 138 | ||
| 139 | function MemoryItem({ memory, action, editable }: { memory: Memory; action: string; editable: boolean }) { | |
| 140 | const fetcher = useFetcher<Done>(); | |
| 141 | const pinned = fetcher.formData ? fetcher.formData.get("pinned") === "true" : memory.pinned; | |
| 142 | const gone = fetcher.formData?.get("intent") === "delete"; | |
| 143 | if (gone) return null; | |
| 144 | return ( | |
| 145 | <li className="group flex gap-3 px-4 py-3"> | |
| 146 | <div className="min-w-0 grow"> | |
| 147 | <div className="flex flex-wrap items-center gap-2"> | |
| 148 | <KindBadge kind={memory.kind} /> | |
| 149 | {pinned && ( | |
| 150 | <span className="inline-flex items-center gap-1 text-[0.6875rem] text-accent"> | |
| 151 | <Pin size={11} /> | |
| 152 | pinned | |
| 153 | </span> | |
| 154 | )} | |
| 155 | </div> | |
| Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar | 156 | <p className="mt-1.5 text-sm whitespace-pre-wrap wrap-break-word">{memory.text}</p> |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 157 | <p className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-faint"> |
| 158 | <Source memory={memory} /> | |
| 159 | <span> | |
| 160 | added <TimeAgo at={memory.createdAt} /> | |
| 161 | </span> | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 162 | <span>{memory.lastUsedAt ? <>recalled <TimeAgo at={memory.lastUsedAt} /></> : "not recalled yet"}</span> |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 163 | </p> |
| 164 | {fetcher.data?.error && <p className="mt-1.5 text-xs text-danger">{fetcher.data.error}</p>} | |
| 165 | </div> | |
| 166 | {editable && ( | |
| 167 | <div className="flex shrink-0 items-start gap-0.5"> | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 168 | <Hint label={pinned ? "Unpin" : "Pin: recalled first"}> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 169 | <Button |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 170 | type="button" |
| 171 | aria-label={pinned ? "Unpin" : "Pin"} | |
| 172 | onClick={() => fetcher.submit({ intent: "update", id: memory.id, pinned: String(!pinned) }, { method: "post", action })} | |
| 173 | disabled={fetcher.state !== "idle"} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 174 | variant="ghost" |
| 175 | size="inline" | |
| 176 | className="rounded p-1 text-faint max-sm:p-2.5" | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 177 | > |
| 178 | {pinned ? <PinOff size={13} /> : <Pin size={13} />} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 179 | </Button> |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 180 | </Hint> |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 181 | <EditMemory memory={memory} action={action} /> |
| 182 | <AlertDialog> | |
| Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar | 183 | <AlertDialogTrigger aria-label="Forget" className="rounded p-1 text-faint max-sm:p-2.5 transition-colors hover:bg-raised hover:text-danger"> |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 184 | <Trash2 size={13} /> |
| 185 | </AlertDialogTrigger> | |
| 186 | <AlertDialogContent> | |
| 187 | <AlertDialogHeader> | |
| 188 | <AlertDialogTitle>Forget this?</AlertDialogTitle> | |
| 189 | <AlertDialogDescription> | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 190 | No agent recalls it again. This cannot be undone. |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 191 | </AlertDialogDescription> |
| 192 | </AlertDialogHeader> | |
| 193 | <AlertDialogFooter> | |
| 194 | <AlertDialogCancel>Keep it</AlertDialogCancel> | |
| 195 | <AlertDialogAction onClick={() => fetcher.submit({ intent: "delete", id: memory.id }, { method: "post", action })}> | |
| 196 | Forget | |
| 197 | </AlertDialogAction> | |
| 198 | </AlertDialogFooter> | |
| 199 | </AlertDialogContent> | |
| 200 | </AlertDialog> | |
| 201 | </div> | |
| 202 | )} | |
| 203 | </li> | |
| 204 | ); | |
| 205 | } | |
| 206 | ||
| 207 | export function MemoryList({ | |
| 208 | memories, | |
| 209 | action, | |
| 210 | editable, | |
| 211 | empty, | |
| 212 | }: { | |
| 213 | memories: Memory[]; | |
| 214 | action: string; | |
| 215 | editable: boolean; | |
| 216 | empty: string; | |
| 217 | }) { | |
| 218 | if (memories.length === 0) { | |
| 219 | return <p className="rounded-xl border border-dashed border-line px-4 py-6 text-sm text-muted">{empty}</p>; | |
| 220 | } | |
| 221 | return ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 222 | <Card asChild divided> |
| 223 | <ul> | |
| 224 | {memories.map((memory) => ( | |
| 225 | <MemoryItem key={memory.id} memory={memory} action={action} editable={editable} /> | |
| 226 | ))} | |
| 227 | </ul> | |
| 228 | </Card> | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 229 | ); |
| 230 | } | |
| 231 | ||
| 232 | /** A form to add a memory at one level. */ | |
| 233 | export function AddMemory({ scope, action, placeholder }: { scope: MemoryScope; action: string; placeholder: string }) { | |
| 234 | const fetcher = useFetcher<Done>(); | |
| 235 | const form = useRef<HTMLFormElement>(null); | |
| 236 | useEffect(() => { | |
| 237 | if (fetcher.state === "idle" && fetcher.data?.ok) form.current?.reset(); | |
| 238 | }, [fetcher.state, fetcher.data]); | |
| 239 | return ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 240 | <Card asChild className="p-4"> |
| 241 | <fetcher.Form ref={form} method="post" action={action}> | |
| 242 | <input type="hidden" name="intent" value="add" /> | |
| 243 | <input type="hidden" name="scope" value={scope} /> | |
| 244 | <label className="flex items-center gap-2 text-sm font-medium"> | |
| 245 | <Brain size={15} className="text-accent" /> | |
| 246 | Add to {scope === "workspace" ? "the workspace's" : "this project's"} memory | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 247 | </label> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 248 | <textarea name="text" required rows={2} maxLength={1000} placeholder={placeholder} className={`${TEXTAREA} mt-3`} /> |
| 249 | <div className="mt-3 flex flex-wrap items-center gap-3"> | |
| 250 | <KindSelect /> | |
| 251 | <label className="flex items-center gap-2 text-xs text-muted"> | |
| 252 | <Switch name="pinned" value="true" size="sm" /> | |
| 253 | Pin it | |
| 254 | </label> | |
| 255 | <span className="grow" /> | |
| 256 | <SubmitButton fetcher={fetcher} pending="Adding…"> | |
| 257 | Add | |
| 258 | </SubmitButton> | |
| 259 | </div> | |
| 260 | {fetcher.data?.error && <p className="mt-2 text-sm text-danger">{fetcher.data.error}</p>} | |
| 261 | <p className="mt-2 text-xs text-faint"> | |
| 262 | One fact, convention, decision or gotcha each. Never a secret: say where it lives instead, and | |
| 263 | g1t refuses anything that looks like a key or a token. | |
| 264 | </p> | |
| 265 | </fetcher.Form> | |
| 266 | </Card> | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 267 | ); |
| 268 | } | |
| 269 | ||
| 270 | type MemoryApi = { | |
| 271 | addMemory(actor: Actor, workspace: string, memory: { scope: MemoryScope; repo?: { namespace: string; name: string } | null; text: string; kind?: MemoryKind; pinned?: boolean }): Promise<Result<Memory>>; | |
| 272 | updateMemory(actor: Actor, workspace: string, id: string, change: { text?: string; kind?: MemoryKind; pinned?: boolean }): Promise<Result<Memory>>; | |
| 273 | deleteMemory(actor: Actor, workspace: string, id: string): Promise<Result<boolean>>; | |
| 274 | }; | |
| 275 | ||
| 276 | /** The action both memory pages share: add, update and delete. */ | |
| 277 | export async function memoryAction( | |
| 278 | api: MemoryApi, | |
| 279 | actor: Actor, | |
| 280 | workspace: string, | |
| 281 | repo: { namespace: string; name: string } | null, | |
| 282 | form: FormData, | |
| 283 | ): Promise<Done> { | |
| 284 | const intent = String(form.get("intent") ?? ""); | |
| 285 | const kindValue = String(form.get("kind") ?? ""); | |
| 286 | const kind = MEMORY_KINDS.includes(kindValue as MemoryKind) ? (kindValue as MemoryKind) : undefined; | |
| 287 | if (intent === "add") { | |
| 288 | const scope: MemoryScope = form.get("scope") === "workspace" ? "workspace" : "project"; | |
| 289 | const added = await api.addMemory(actor, workspace, { | |
| 290 | scope, | |
| 291 | repo, | |
| 292 | text: String(form.get("text") ?? ""), | |
| 293 | kind, | |
| 294 | pinned: form.get("pinned") === "true", | |
| 295 | }); | |
| 296 | return added.ok ? { ok: true } : { ok: false, error: added.error.message }; | |
| 297 | } | |
| 298 | if (intent === "update") { | |
| 299 | const pinned = form.get("pinned"); | |
| 300 | const text = form.get("text"); | |
| 301 | const changed = await api.updateMemory(actor, workspace, String(form.get("id") ?? ""), { | |
| 302 | text: text == null ? undefined : String(text), | |
| 303 | kind, | |
| 304 | pinned: pinned == null ? undefined : pinned === "true", | |
| 305 | }); | |
| 306 | return changed.ok ? { ok: true } : { ok: false, error: changed.error.message }; | |
| 307 | } | |
| 308 | if (intent === "delete") { | |
| 309 | const gone = await api.deleteMemory(actor, workspace, String(form.get("id") ?? "")); | |
| 310 | return gone.ok ? { ok: true } : { ok: false, error: gone.error.message }; | |
| 311 | } | |
| 312 | return { ok: false, error: "Unknown action." }; | |
| 313 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.