{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-6",
  "title": "Agent 6: assistant memory",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "What an AI assistant remembers about someone: a ring splitting memory by kind, a switch to pause it, each memory with its source and use count, edited in place, pinned or forgotten with undo, and Forget everything.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/alert-dialog.json",
    "https://ui.ballmac.com/r/blur-fade.json",
    "https://ui.ballmac.com/r/button.json",
    "https://ui.ballmac.com/r/circular-progress.json",
    "https://ui.ballmac.com/r/segmented-control.json",
    "https://ui.ballmac.com/r/skeleton.json",
    "https://ui.ballmac.com/r/state-view.json",
    "https://ui.ballmac.com/r/switch.json",
    "https://ui.ballmac.com/r/textarea.json",
    "https://ui.ballmac.com/r/return-focus.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/agent-6/agent-6.tsx",
      "content": "// Ballmac UI: Agent 6. https://ui.ballmac.com/blocks/agent-6\n\"use client\"\n\nimport * as React from \"react\"\nimport { Brain, Briefcase, Check, Loader2, Pause, Pencil, Pin, Search, SlidersHorizontal, Trash2, Undo2, User } from \"lucide-react\"\n\nimport { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from \"@/components/ballmac/alert-dialog\"\nimport { BlurFadeGroup } from \"@/components/ballmac/blur-fade\"\nimport { Button } from \"@/components/ballmac/button\"\nimport { CircularProgress } from \"@/components/ballmac/circular-progress\"\nimport { SegmentedControl, SegmentedControlItem } from \"@/components/ballmac/segmented-control\"\nimport { Skeleton } from \"@/components/ballmac/skeleton\"\nimport { useReturnFocus } from \"@/lib/ballmac/return-focus\"\nimport { frameProps, StateView, type StateProps } from \"@/components/ballmac/state-view\"\nimport { Switch } from \"@/components/ballmac/switch\"\nimport { Textarea } from \"@/components/ballmac/textarea\"\nimport { cn } from \"@/lib/utils\"\n\ntype Agent6Scope = \"you\" | \"work\" | \"preferences\"\n\ntype Agent6Memory = {\n  id: string\n  /** What is remembered, written the way the assistant would say it: \"Prefers short answers with the conclusion first.\" */\n  text: string\n  scope: Agent6Scope\n  /** Where it was learned (\"Planning the Q4 launch\"). */\n  source?: string\n  /** Already written for people (\"3 weeks ago\"). */\n  learned?: string\n  /** How many times it has been used in an answer. */\n  uses?: number\n  pinned?: boolean\n}\n\ntype Agent6Props = Omit<React.ComponentProps<\"section\">, \"title\"> &\n  StateProps & {\n    memories?: Agent6Memory[]\n    /** The assistant's name, used in the copy. */\n    assistantName?: string\n    /** How many memories fit, for the ring. */\n    capacity?: number\n    /** Chats this week that used memory, and when it last changed (\"2 hours ago\"). */\n    activity?: { chats: number; updated: string }\n    /** Whether memory is paused (controlled). */\n    paused?: boolean\n    defaultPaused?: boolean\n    onPausedChange?: (paused: boolean) => void\n    /** Called with the new text. Throw to keep the old text and show the error. */\n    onEdit?: (id: string, text: string) => void | Promise<void>\n    /** Called when a memory is pinned or unpinned. */\n    onPin?: (id: string, pinned: boolean) => void\n    /** Called straight away when a memory is forgotten. The block offers Undo for a few seconds. */\n    onForget?: (id: string) => void\n    /** Called when Undo is pressed, with the memory to put back. */\n    onRestore?: (memory: Agent6Memory) => void\n    /** The id of a memory that is open for editing at first. */\n    defaultEditingId?: string\n    /** Called after \"Forget everything\" is confirmed. */\n    onForgetAll?: () => void | Promise<void>\n  }\n\nconst scopes: Record<Agent6Scope, { title: string; icon: typeof User; tone: \"chart-1\" | \"chart-2\" | \"chart-4\"; bar: string; soft: string; hint: string }> = {\n  you: { title: \"About you\", icon: User, tone: \"chart-1\", bar: \"bg-chart-1\", soft: \"bg-chart-1/20\", hint: \"Who you are and what you are working toward\" },\n  work: { title: \"Your work\", icon: Briefcase, tone: \"chart-2\", bar: \"bg-chart-2\", soft: \"bg-chart-2/20\", hint: \"Projects, tools and people you mention\" },\n  preferences: { title: \"How you like answers\", icon: SlidersHorizontal, tone: \"chart-4\", bar: \"bg-chart-4\", soft: \"bg-chart-4/20\", hint: \"Tone, format and things to avoid\" },\n}\n\nconst sample: Agent6Memory[] = [\n  { id: \"m1\", scope: \"you\", text: \"Is a product designer based in Lisbon and works in the Western European time zone.\", source: \"Getting set up\", learned: \"2 months ago\", uses: 31, pinned: true },\n  { id: \"m2\", scope: \"you\", text: \"Is learning Rust in the evenings and prefers examples to long explanations.\", source: \"Ownership and borrowing, explained\", learned: \"3 weeks ago\", uses: 9 },\n  { id: \"m3\", scope: \"work\", text: \"Leads the Quiet writing app: a React and Tauri project with a release every second Thursday.\", source: \"Planning the Q4 launch\", learned: \"3 weeks ago\", uses: 27 },\n  { id: \"m4\", scope: \"work\", text: \"Works with Priya (engineering) and Alex (support) on launches.\", source: \"Release checklist\", learned: \"2 weeks ago\", uses: 14 },\n  { id: \"m5\", scope: \"work\", text: \"Uses pnpm, Vitest and Playwright. Does not use Jest.\", source: \"Fixing a flaky test\", learned: \"last week\", uses: 6 },\n  { id: \"m6\", scope: \"preferences\", text: \"Prefers short answers with the conclusion first, then the reasoning.\", source: \"Feedback on a long reply\", learned: \"2 months ago\", uses: 52, pinned: true },\n  { id: \"m7\", scope: \"preferences\", text: \"Wants British spelling in anything written for the website.\", source: \"Editing the changelog\", learned: \"5 weeks ago\", uses: 11 },\n  { id: \"m8\", scope: \"preferences\", text: \"Dislikes emoji in replies and in generated copy.\", source: \"Drafting release notes\", learned: \"last month\", uses: 18 },\n]\n\nfunction MemorySkeleton() {\n  return (\n    <div className=\"grid gap-6\" aria-hidden=\"true\">\n      <Skeleton className=\"h-44 rounded-2xl\" />\n      {[0, 1, 2].map((i) => (\n        <Skeleton key={i} className=\"h-24 rounded-2xl\" />\n      ))}\n    </div>\n  )\n}\n\n/**\n * What an assistant remembers about someone: a ring that splits memory by kind, a switch to pause it, every memory with where it was\n * learned and how often it was used, edited in place, pinned, or forgotten with a few seconds to undo, and one button to forget it all.\n */\nfunction Agent6({\n  memories: memoriesProp = sample,\n  assistantName = \"Sage\",\n  capacity = 40,\n  activity = { chats: 38, updated: \"2 hours ago\" },\n  paused: pausedProp,\n  defaultPaused = false,\n  onPausedChange,\n  onEdit,\n  onPin,\n  onForget,\n  onRestore,\n  onForgetAll,\n  defaultEditingId,\n  state = \"ready\",\n  onRetry,\n  emptyState,\n  errorState,\n  className,\n  ...props\n}: Agent6Props) {\n  const id = React.useId()\n  const [list, setList] = React.useState(memoriesProp)\n  React.useEffect(() => setList(memoriesProp), [memoriesProp])\n  const [pausedLocal, setPausedLocal] = React.useState(defaultPaused)\n  const paused = pausedProp ?? pausedLocal\n  const [scope, setScope] = React.useState<\"all\" | Agent6Scope>(\"all\")\n  const [query, setQuery] = React.useState(\"\")\n  const [editing, setEditing] = React.useState<string | null>(defaultEditingId ?? null)\n  const [draft, setDraft] = React.useState(() => memoriesProp.find((m) => m.id === defaultEditingId)?.text ?? \"\")\n  const [saving, setSaving] = React.useState(false)\n  const [editError, setEditError] = React.useState<string>()\n  const [undo, setUndo] = React.useState<{ memory: Agent6Memory; index: number } | null>(null)\n  const [confirmAll, setConfirmAll] = React.useState(false)\n  const returnFocus = useReturnFocus(confirmAll)\n  const [announce, setAnnounce] = React.useState(\"\")\n  const ready = state === \"ready\" || state === \"empty\"\n  const empty = state === \"empty\" || (state === \"ready\" && !list.length)\n\n  React.useEffect(() => {\n    if (!undo) return\n    const t = setTimeout(() => setUndo(null), 7000)\n    return () => clearTimeout(t)\n  }, [undo])\n\n  const q = query.trim().toLowerCase()\n  const visible = list\n    .filter((m) => (scope === \"all\" || m.scope === scope) && (!q || `${m.text} ${m.source ?? \"\"}`.toLowerCase().includes(q)))\n    .sort((a, b) => Number(!!b.pinned) - Number(!!a.pinned))\n  const grouped = (Object.keys(scopes) as Agent6Scope[]).map((s) => ({ scope: s, items: visible.filter((m) => m.scope === s) })).filter((g) => g.items.length)\n  const counts = (Object.keys(scopes) as Agent6Scope[]).map((s) => ({ scope: s, n: list.filter((m) => m.scope === s).length }))\n  const maxUses = Math.max(1, ...list.map((m) => m.uses ?? 0))\n\n  async function save(m: Agent6Memory) {\n    const text = draft.trim()\n    if (!text) return setEditError(\"A memory cannot be empty. Forget it instead.\")\n    if (text === m.text) return setEditing(null)\n    setSaving(true)\n    setEditError(undefined)\n    try {\n      await onEdit?.(m.id, text)\n      setList((l) => l.map((x) => (x.id === m.id ? { ...x, text } : x)))\n      setAnnounce(\"Memory updated\")\n      setEditing(null)\n    } catch (err) {\n      setEditError(err instanceof Error && err.message ? err.message : \"Could not save. Your change was not kept.\")\n    } finally {\n      setSaving(false)\n    }\n  }\n\n  function forget(m: Agent6Memory) {\n    const index = list.findIndex((x) => x.id === m.id)\n    setList((l) => l.filter((x) => x.id !== m.id))\n    onForget?.(m.id)\n    setUndo({ memory: m, index })\n    setAnnounce(\"Forgotten. Undo is available for a few seconds.\")\n  }\n\n  function restore() {\n    if (!undo) return\n    const { memory, index } = undo\n    setList((l) => [...l.slice(0, index), memory, ...l.slice(index)])\n    onRestore?.(memory)\n    setUndo(null)\n    setAnnounce(\"Memory restored\")\n  }\n\n  const used = Math.min(1, list.length / capacity)\n  const content = (\n    <div className=\"grid grid-cols-1 gap-8\">\n      <div className=\"bg-card relative overflow-hidden rounded-2xl border\">\n        <div className=\"grid items-center gap-6 p-6 sm:grid-cols-[auto_1fr] sm:gap-10 sm:p-8\">\n          <div className=\"justify-self-center\">\n            <CircularProgress\n              size={168}\n              thickness={11}\n              gap={5}\n              duration={0.9}\n              legend={false}\n              rings={counts.map((c) => ({ label: scopes[c.scope].title, value: c.n, max: Math.max(c.n, Math.ceil(list.length * 0.6)), tone: scopes[c.scope].tone, display: String(c.n) }))}\n              aria-label={`${list.length} of ${capacity} memories, ${counts.map((c) => `${c.n} ${scopes[c.scope].title.toLowerCase()}`).join(\", \")}`}\n            >\n              <span className=\"text-3xl font-semibold tracking-tight tabular-nums\">{list.length}</span>\n              <span className=\"text-muted-foreground text-xs\">memories</span>\n            </CircularProgress>\n          </div>\n          <div className=\"grid gap-5\">\n            <ul className=\"grid gap-3\">\n              {counts.map((c) => {\n                const S = scopes[c.scope]\n                return (\n                  <li key={c.scope} className=\"flex items-center gap-3\">\n                    <span aria-hidden=\"true\" className={cn(\"size-2.5 shrink-0 rounded-full\", S.bar)} />\n                    <span className=\"min-w-0 flex-1\">\n                      <span className=\"text-sm font-medium\">{S.title}</span>\n                      <span className=\"text-muted-foreground hidden text-xs sm:ms-2 sm:inline\">{S.hint}</span>\n                    </span>\n                    <span className=\"text-sm font-medium tabular-nums\">{c.n}</span>\n                  </li>\n                )\n              })}\n            </ul>\n            <div className=\"bg-muted/60 flex flex-wrap items-center gap-x-6 gap-y-2 rounded-xl px-4 py-3 text-xs\">\n              <p>\n                <span className=\"text-foreground font-medium tabular-nums\">{Math.round(used * 100)}%</span> <span className=\"text-muted-foreground\">of {capacity} used</span>\n              </p>\n              <p>\n                <span className=\"text-foreground font-medium tabular-nums\">{activity.chats}</span> <span className=\"text-muted-foreground\">chats this week used memory</span>\n              </p>\n              <p className=\"text-muted-foreground\">Updated {activity.updated}</p>\n            </div>\n          </div>\n        </div>\n        <div className=\"flex flex-wrap items-center gap-4 border-t px-6 py-4 sm:px-8\">\n          <div className=\"min-w-0 flex-1 basis-60\">\n            <label htmlFor={`${id}-pause`} className=\"text-sm font-medium\">\n              {paused ? \"Memory is paused\" : \"Memory is on\"}\n            </label>\n            <p className=\"text-muted-foreground text-xs text-pretty\">\n              {paused ? `${assistantName} is not learning anything new and is not using what is below.` : `${assistantName} learns from your chats and uses it to answer better.`}\n            </p>\n          </div>\n          <Switch\n            id={`${id}-pause`}\n            checked={!paused}\n            onCheckedChange={(on) => {\n              setPausedLocal(!on)\n              onPausedChange?.(!on)\n              setAnnounce(on ? \"Memory turned on\" : \"Memory paused\")\n            }}\n            aria-describedby={`${id}-pause-desc`}\n          />\n          <span id={`${id}-pause-desc`} className=\"sr-only\">\n            Turns off learning and using memories. Nothing is deleted.\n          </span>\n        </div>\n      </div>\n\n      <div className=\"flex flex-wrap items-center gap-3\">\n        <div className=\"bg-background focus-within:ring-ring/50 flex h-9 min-w-0 flex-1 basis-56 items-center gap-2 rounded-md border px-2.5 focus-within:ring-[3px]\">\n          <Search aria-hidden=\"true\" className=\"text-muted-foreground size-4\" />\n          <input type=\"search\" aria-label=\"Search memories\" placeholder=\"Search memories\" value={query} onChange={(e) => setQuery(e.target.value)} className=\"placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent text-sm outline-none\" />\n        </div>\n        <SegmentedControl value={scope} onValueChange={(v) => setScope(v as typeof scope)} aria-label=\"Kind\" size=\"sm\">\n          <SegmentedControlItem value=\"all\">All</SegmentedControlItem>\n          <SegmentedControlItem value=\"you\">You</SegmentedControlItem>\n          <SegmentedControlItem value=\"work\">Work</SegmentedControlItem>\n          <SegmentedControlItem value=\"preferences\">Style</SegmentedControlItem>\n        </SegmentedControl>\n      </div>\n\n      <StateView\n        state={empty ? \"empty\" : \"ready\"}\n        bare\n        empty={{\n          title: emptyState?.title ?? \"Nothing remembered yet\",\n          description: emptyState?.description ?? `As you chat, ${assistantName} will note useful things here, like your role, your projects and how you like answers. You decide what stays.`,\n          icon: emptyState?.icon ?? <Brain />,\n          action: emptyState?.action,\n        }}\n      >\n        <div className=\"grid grid-cols-1 gap-8\">\n          {paused ? (\n            <p role=\"note\" className=\"bg-muted/60 flex items-start gap-3 rounded-xl border px-4 py-3 text-sm text-pretty\">\n              <Pause aria-hidden=\"true\" className=\"mt-0.5 size-4 shrink-0\" />\n              Memory is paused. Everything below is kept, but {assistantName} is not using it or adding to it until you turn it back on.\n            </p>\n          ) : null}\n          {grouped.map((g) => {\n            const S = scopes[g.scope]\n            return (\n              <section key={g.scope} aria-labelledby={`${id}-${g.scope}`} className=\"grid grid-cols-1 gap-3\">\n                <h2 id={`${id}-${g.scope}`} className=\"flex items-center gap-2.5 text-sm font-semibold\">\n                  <span aria-hidden=\"true\" className={cn(\"flex size-6 items-center justify-center rounded-md [&_svg]:size-3.5\", S.soft)}>\n                    <S.icon />\n                  </span>\n                  {S.title}\n                  <span className=\"text-muted-foreground text-xs font-normal tabular-nums\">{g.items.length}</span>\n                </h2>\n                <BlurFadeGroup stagger={0.05} item={{ direction: \"up\", offset: 8, blur: 4 }} className=\"grid grid-cols-1 gap-2.5\">\n                  {g.items.map((m) => {\n                    const isEditing = editing === m.id\n                    return (\n                      <article key={m.id} aria-label={m.text} className={cn(\"bg-card group/mem relative flex gap-4 rounded-xl border p-4 pe-3 transition-colors duration-200 hover:border-foreground/20 focus-within:border-foreground/20\", isEditing && \"border-ring ring-ring/20 ring-2\")}>\n                        <span aria-hidden=\"true\" className={cn(\"absolute inset-y-3 start-0 w-1 rounded-full\", S.bar, \"opacity-70\")} />\n                        <div className=\"min-w-0 flex-1 ps-2\">\n                          {isEditing ? (\n                            <div className=\"grid gap-2\">\n                              <label htmlFor={`${id}-edit-${m.id}`} className=\"sr-only\">\n                                Edit this memory\n                              </label>\n                              <Textarea\n                                id={`${id}-edit-${m.id}`}\n                                autoFocus\n                                rows={2}\n                                value={draft}\n                                disabled={saving}\n                                aria-invalid={!!editError || undefined}\n                                aria-describedby={editError ? `${id}-edit-error` : undefined}\n                                onChange={(e) => setDraft(e.target.value)}\n                                onKeyDown={(e) => {\n                                  if (e.key === \"Escape\") setEditing(null)\n                                  else if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n                                    e.preventDefault()\n                                    save(m)\n                                  }\n                                }}\n                                className=\"text-sm\"\n                              />\n                              {editError ? (\n                                <p id={`${id}-edit-error`} role=\"alert\" className=\"text-destructive text-xs\">\n                                  {editError}\n                                </p>\n                              ) : null}\n                              <div className=\"flex items-center gap-2\">\n                                <Button size=\"sm\" onClick={() => save(m)} disabled={saving}>\n                                  {saving ? <Loader2 aria-hidden=\"true\" className=\"animate-spin motion-reduce:animate-none\" /> : <Check aria-hidden=\"true\" />}\n                                  Save\n                                </Button>\n                                <Button variant=\"ghost\" size=\"sm\" onClick={() => setEditing(null)} disabled={saving}>\n                                  Cancel\n                                </Button>\n                                <span className=\"text-muted-foreground ms-auto hidden text-xs sm:block\">Ctrl+Enter to save</span>\n                              </div>\n                            </div>\n                          ) : (\n                            <>\n                              <p className=\"text-sm leading-relaxed text-pretty\">\n                                {m.pinned ? (\n                                  <span className=\"inline-flex align-text-bottom\">\n                                    <Pin aria-label=\"Pinned\" className=\"text-foreground me-1.5 size-3.5 fill-current\" />\n                                  </span>\n                                ) : null}\n                                {m.text}\n                              </p>\n                              <div className=\"text-muted-foreground mt-2.5 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs\">\n                                {m.source ? (\n                                  <span className=\"min-w-0 truncate\">\n                                    From “{m.source}”{m.learned ? ` · ${m.learned}` : \"\"}\n                                  </span>\n                                ) : m.learned ? (\n                                  <span>{m.learned}</span>\n                                ) : null}\n                                {m.uses !== undefined ? (\n                                  <span className=\"flex items-center gap-2 tabular-nums\">\n                                    <span aria-hidden=\"true\" className=\"bg-muted relative h-1 w-14 overflow-hidden rounded-full\">\n                                      <span className={cn(\"absolute inset-y-0 start-0 rounded-full\", S.bar)} style={{ width: `${Math.max(6, (m.uses / maxUses) * 100)}%` }} />\n                                    </span>\n                                    Used {m.uses} {m.uses === 1 ? \"time\" : \"times\"}\n                                  </span>\n                                ) : null}\n                              </div>\n                            </>\n                          )}\n                        </div>\n                        {!isEditing ? (\n                          <div className=\"flex shrink-0 items-start gap-0.5 opacity-100 transition-opacity sm:opacity-0 sm:group-focus-within/mem:opacity-100 sm:group-hover/mem:opacity-100\">\n                            <Button\n                              variant=\"ghost\"\n                              size=\"icon-sm\"\n                              aria-label={m.pinned ? \"Unpin this memory\" : \"Pin this memory\"}\n                              aria-pressed={!!m.pinned}\n                              onClick={() => {\n                                setList((l) => l.map((x) => (x.id === m.id ? { ...x, pinned: !x.pinned } : x)))\n                                onPin?.(m.id, !m.pinned)\n                              }}\n                            >\n                              <Pin aria-hidden=\"true\" className={cn(m.pinned && \"fill-current\")} />\n                            </Button>\n                            <Button\n                              variant=\"ghost\"\n                              size=\"icon-sm\"\n                              aria-label=\"Edit this memory\"\n                              onClick={() => {\n                                setEditing(m.id)\n                                setDraft(m.text)\n                                setEditError(undefined)\n                              }}\n                            >\n                              <Pencil aria-hidden=\"true\" />\n                            </Button>\n                            <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Forget this memory\" onClick={() => forget(m)} className=\"hover:text-destructive\">\n                              <Trash2 aria-hidden=\"true\" />\n                            </Button>\n                          </div>\n                        ) : null}\n                      </article>\n                    )\n                  })}\n                </BlurFadeGroup>\n              </section>\n            )\n          })}\n          {!grouped.length ? <p className=\"text-muted-foreground py-10 text-center text-sm\">No memories match “{query || scopes[scope as Agent6Scope]?.title}”.</p> : null}\n        </div>\n        {list.length ? (\n          <div className=\"mt-10 flex flex-wrap items-center gap-4 border-t pt-6\">\n            <p className=\"text-muted-foreground min-w-0 flex-1 basis-60 text-xs text-pretty\">Memories stay on your account. You can also ask {assistantName} to remember or forget something in any chat.</p>\n            <Button variant=\"outline\" size=\"sm\" onClick={() => setConfirmAll(true)} className=\"text-destructive hover:text-destructive\">\n              <Trash2 aria-hidden=\"true\" />\n              Forget everything\n            </Button>\n          </div>\n        ) : null}\n      </StateView>\n    </div>\n  )\n\n  return (\n    <section data-slot=\"agent-6\" data-state={state} aria-labelledby={`${id}-title`} className={cn(\"mx-auto w-full max-w-3xl px-4 py-8 pb-24 sm:px-6\", className)} {...frameProps(props)}>\n      <header className=\"mb-8\">\n        <h1 id={`${id}-title`} className=\"flex items-center gap-3 text-3xl font-semibold tracking-[-0.035em]\">\n          <span aria-hidden=\"true\" className=\"bg-foreground text-background flex size-9 items-center justify-center rounded-xl [&_svg]:size-5\">\n            {paused ? <Pause /> : <Brain />}\n          </span>\n          Memory\n        </h1>\n        <p className=\"text-muted-foreground mt-2 max-w-xl text-sm text-pretty\">What {assistantName} remembers about you, so you do not have to repeat yourself. Edit, pin or forget anything.</p>\n      </header>\n\n      <StateView state={state === \"loading\" || state === \"error\" ? state : \"ready\"} onRetry={onRetry} label=\"Loading your memories\" loading={<MemorySkeleton />} error={{ title: errorState?.title ?? \"Your memories could not be loaded\", description: errorState?.description ?? \"Nothing was changed. Check your connection and try again.\", ...errorState }}>\n        {content}\n      </StateView>\n\n      {undo ? (\n        <div role=\"status\" className=\"bg-popover text-popover-foreground fixed inset-x-4 bottom-4 z-30 mx-auto flex max-w-md items-center gap-3 rounded-xl border p-3 ps-4 shadow-lg\">\n          <p className=\"min-w-0 flex-1 truncate text-sm\">Forgot “{undo.memory.text}”</p>\n          <Button variant=\"outline\" size=\"sm\" onClick={restore}>\n            <Undo2 aria-hidden=\"true\" className=\"rtl:-scale-x-100\" />\n            Undo\n          </Button>\n        </div>\n      ) : null}\n\n      <AlertDialog open={confirmAll} onOpenChange={setConfirmAll}>\n        <AlertDialogContent {...returnFocus}>\n          <AlertDialogHeader>\n            <AlertDialogTitle>Forget everything?</AlertDialogTitle>\n            <AlertDialogDescription>\n              {assistantName} will lose all {list.length} memories and start fresh. This cannot be undone.\n            </AlertDialogDescription>\n          </AlertDialogHeader>\n          <AlertDialogFooter>\n            <AlertDialogCancel>Cancel</AlertDialogCancel>\n            <AlertDialogAction\n              onClick={async () => {\n                await onForgetAll?.()\n                setList([])\n                setUndo(null)\n                setAnnounce(\"All memories forgotten\")\n              }}\n            >\n              Forget everything\n            </AlertDialogAction>\n          </AlertDialogFooter>\n        </AlertDialogContent>\n      </AlertDialog>\n      <p aria-live=\"polite\" className=\"sr-only\">\n        {announce}\n      </p>\n    </section>\n  )\n}\n\nexport { Agent6, type Agent6Props, type Agent6Memory, type Agent6Scope }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/agent-6/agent-6.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "ai",
      "agent",
      "memory",
      "personalization",
      "privacy",
      "assistant",
      "context",
      "preferences",
      "controls"
    ],
    "version": "1.0.0",
    "updated": "2026-10-17",
    "tests": true,
    "ai": {
      "summary": "A memory control page for an AI assistant. <Agent6 assistantName memories={[{ id, text, scope: 'you' | 'work' | 'preferences', source, learned, uses, pinned }]} capacity activity={{ chats, updated }} paused onPausedChange onEdit={async (id, text) => …} onPin onForget onRestore onForgetAll />. Forgetting calls onForget at once and offers Undo for seven seconds (onRestore); throw from onEdit to keep the old text and show the error.",
      "whenToUse": [
        "Giving people control over what an AI assistant or agent remembers (a trust and privacy requirement in many products)",
        "A settings page for personalization"
      ],
      "whenNotToUse": [
        "A knowledge base the assistant searches (use sources-list)",
        "Chat history (use ai-chat-1)"
      ],
      "composesWith": [
        "settings-3",
        "agent-1",
        "agent-3",
        "app-shell-1"
      ],
      "a11y": [
        {
          "keys": "Tab to a memory",
          "action": "Pin, Edit and Forget appear (always visible on touch screens); each says what it acts on"
        },
        {
          "keys": "Ctrl+Enter or Escape in the edit box",
          "action": "Saves or cancels; errors are announced as alerts"
        },
        {
          "keys": "Forget",
          "action": "An Undo bar appears as a status message; every change is announced politely"
        }
      ],
      "customization": [
        "assistantName, capacity, activity",
        "paused + onPausedChange to control the switch; defaultPaused for the first value",
        "Memories are grouped by scope; write the text the way the assistant would say it"
      ]
    },
    "states": [
      {
        "id": "loading",
        "title": "Loading",
        "example": "agent-6-loading",
        "trigger": "state=\"loading\"",
        "note": "A skeleton of the summary and memory cards under the real heading."
      },
      {
        "id": "empty",
        "title": "Nothing remembered",
        "example": "agent-6-empty",
        "trigger": "state=\"empty\" (or memories={[]})",
        "note": "Explains how the assistant learns and that the person decides what stays; emptyState sets the copy."
      },
      {
        "id": "error",
        "title": "Could not load",
        "example": "agent-6-error",
        "trigger": "state=\"error\"",
        "note": "An alert saying nothing was changed, with Try again (onRetry)."
      }
    ],
    "examples": [
      "agent-6-demo",
      "agent-6-editing",
      "agent-6-paused",
      "agent-6-loading",
      "agent-6-empty",
      "agent-6-error"
    ],
    "url": "https://ui.ballmac.com/components/agent-6"
  },
  "categories": [
    "blocks",
    "agents"
  ],
  "type": "registry:block"
}