{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "memory-viewer",
  "title": "AI Memory Viewer",
  "description": "Display and manage working memory and conversation history with source badges, timestamps, search/filter, and add/remove functionality for agent memory systems.",
  "dependencies": [
    "@radix-ui/react-collapsible",
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/ai/ai-memory-viewer/components/elements/ai-memory-viewer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\";\nimport {\n  Brain,\n  ChevronDown,\n  Clock,\n  History,\n  MessageSquare,\n  Plus,\n  Search,\n  Settings,\n  Trash2,\n  User,\n  X,\n} from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype MemoryType = \"working\" | \"history\" | \"system\";\ntype MemorySource = \"user\" | \"system\" | \"conversation\";\n\ninterface MemoryItem {\n  id: string;\n  content: string;\n  type: MemoryType;\n  source?: MemorySource;\n  createdAt: Date;\n  metadata?: Record<string, unknown>;\n}\n\ninterface AiMemoryViewerContextValue {\n  workingMemory: MemoryItem[];\n  conversationHistory: MemoryItem[];\n  searchQuery: string;\n  setSearchQuery: (query: string) => void;\n  onAdd?: (content: string, type: string) => void;\n  onRemove?: (id: string) => void;\n  maxItems?: number;\n}\n\nconst AiMemoryViewerContext =\n  React.createContext<AiMemoryViewerContextValue | null>(null);\n\nfunction useMemoryViewerContext() {\n  const context = React.useContext(AiMemoryViewerContext);\n  if (!context) {\n    throw new Error(\n      \"AiMemoryViewer components must be used within <AiMemoryViewer>\",\n    );\n  }\n  return context;\n}\n\ninterface AiMemoryViewerProps {\n  workingMemory?: MemoryItem[];\n  conversationHistory?: MemoryItem[];\n  onAdd?: (content: string, type: string) => void;\n  onRemove?: (id: string) => void;\n  maxItems?: number;\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiMemoryViewer({\n  workingMemory = [],\n  conversationHistory = [],\n  onAdd,\n  onRemove,\n  maxItems,\n  children,\n  className,\n}: AiMemoryViewerProps) {\n  const [searchQuery, setSearchQuery] = React.useState(\"\");\n\n  const contextValue = React.useMemo(\n    () => ({\n      workingMemory,\n      conversationHistory,\n      searchQuery,\n      setSearchQuery,\n      onAdd,\n      onRemove,\n      maxItems,\n    }),\n    [\n      workingMemory,\n      conversationHistory,\n      searchQuery,\n      onAdd,\n      onRemove,\n      maxItems,\n    ],\n  );\n\n  return (\n    <AiMemoryViewerContext.Provider value={contextValue}>\n      <div\n        data-slot=\"ai-memory-viewer\"\n        className={cn(\n          \"rounded-lg border border-border bg-card text-card-foreground overflow-hidden\",\n          className,\n        )}\n      >\n        {children ?? (\n          <>\n            <AiMemoryViewerHeader />\n            <AiMemoryViewerSearch />\n            <AiMemoryViewerContent />\n          </>\n        )}\n      </div>\n    </AiMemoryViewerContext.Provider>\n  );\n}\n\ninterface AiMemoryViewerHeaderProps {\n  title?: string;\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiMemoryViewerHeader({\n  title = \"Memory\",\n  children,\n  className,\n}: AiMemoryViewerHeaderProps) {\n  const { workingMemory, conversationHistory, maxItems } =\n    useMemoryViewerContext();\n\n  const totalItems = workingMemory.length + conversationHistory.length;\n\n  return (\n    <div\n      data-slot=\"ai-memory-viewer-header\"\n      className={cn(\n        \"flex items-center justify-between gap-3 px-4 py-3 border-b border-border\",\n        className,\n      )}\n    >\n      <div className=\"flex items-center gap-3\">\n        <div className=\"flex size-8 shrink-0 items-center justify-center rounded-md bg-purple-100 dark:bg-purple-950\">\n          <Brain className=\"size-4 text-purple-600 dark:text-purple-400\" />\n        </div>\n        <div>\n          <h3 className=\"font-semibold text-sm\">{title}</h3>\n          <span className=\"text-xs text-muted-foreground\">\n            {totalItems} items\n            {maxItems && ` / ${maxItems} max`}\n          </span>\n        </div>\n      </div>\n      {children}\n    </div>\n  );\n}\n\ninterface AiMemoryViewerSearchProps {\n  placeholder?: string;\n  className?: string;\n}\n\nfunction AiMemoryViewerSearch({\n  placeholder = \"Search memory...\",\n  className,\n}: AiMemoryViewerSearchProps) {\n  const { searchQuery, setSearchQuery } = useMemoryViewerContext();\n\n  return (\n    <div\n      data-slot=\"ai-memory-viewer-search\"\n      className={cn(\"px-4 py-2 border-b border-border\", className)}\n    >\n      <div className=\"relative\">\n        <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n        <input\n          type=\"text\"\n          value={searchQuery}\n          onChange={(e) => setSearchQuery(e.target.value)}\n          placeholder={placeholder}\n          className={cn(\n            \"w-full rounded-md border border-input bg-background px-9 py-2 text-sm\",\n            \"placeholder:text-muted-foreground\",\n            \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n          )}\n        />\n        {searchQuery && (\n          <button\n            type=\"button\"\n            onClick={() => setSearchQuery(\"\")}\n            className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n          >\n            <X className=\"size-4\" />\n          </button>\n        )}\n      </div>\n    </div>\n  );\n}\n\ninterface AiMemoryViewerContentProps {\n  className?: string;\n}\n\nfunction AiMemoryViewerContent({ className }: AiMemoryViewerContentProps) {\n  return (\n    <div\n      data-slot=\"ai-memory-viewer-content\"\n      className={cn(\"p-4 space-y-4\", className)}\n    >\n      <AiMemoryViewerSection type=\"working\" />\n      <AiMemoryViewerSection type=\"history\" />\n    </div>\n  );\n}\n\ninterface AiMemoryViewerSectionProps {\n  type: \"working\" | \"history\";\n  defaultOpen?: boolean;\n  className?: string;\n}\n\nfunction AiMemoryViewerSection({\n  type,\n  defaultOpen = true,\n  className,\n}: AiMemoryViewerSectionProps) {\n  const { workingMemory, conversationHistory, searchQuery, onAdd } =\n    useMemoryViewerContext();\n  const [isOpen, setIsOpen] = React.useState(defaultOpen);\n\n  const items = type === \"working\" ? workingMemory : conversationHistory;\n\n  const filteredItems = React.useMemo(() => {\n    if (!searchQuery) return items;\n    const query = searchQuery.toLowerCase();\n    return items.filter((item) => item.content.toLowerCase().includes(query));\n  }, [items, searchQuery]);\n\n  const sectionConfig = React.useMemo(() => {\n    if (type === \"working\") {\n      return {\n        title: \"Working Memory\",\n        description: \"Active context for current conversation\",\n        icon: <Brain className=\"size-4\" />,\n        iconBg: \"bg-purple-100 dark:bg-purple-950\",\n        iconColor: \"text-purple-600 dark:text-purple-400\",\n      };\n    }\n    return {\n      title: \"Conversation History\",\n      description: \"Past interactions and context\",\n      icon: <History className=\"size-4\" />,\n      iconBg: \"bg-blue-100 dark:bg-blue-950\",\n      iconColor: \"text-blue-600 dark:text-blue-400\",\n    };\n  }, [type]);\n\n  return (\n    <CollapsiblePrimitive.Root open={isOpen} onOpenChange={setIsOpen}>\n      <div\n        data-slot=\"ai-memory-viewer-section\"\n        data-type={type}\n        className={cn(\n          \"rounded-lg border border-border overflow-hidden\",\n          className,\n        )}\n      >\n        <CollapsiblePrimitive.Trigger\n          className={cn(\n            \"flex w-full items-center gap-3 px-4 py-3 text-sm font-medium transition-colors\",\n            \"hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n          )}\n        >\n          <div\n            className={cn(\n              \"flex size-8 shrink-0 items-center justify-center rounded-md\",\n              sectionConfig.iconBg,\n            )}\n          >\n            <span className={sectionConfig.iconColor}>\n              {sectionConfig.icon}\n            </span>\n          </div>\n          <div className=\"flex-1 text-left\">\n            <div className=\"flex items-center gap-2\">\n              <span className=\"font-medium\">{sectionConfig.title}</span>\n              <span className=\"inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground\">\n                {filteredItems.length}\n              </span>\n            </div>\n            <p className=\"text-xs text-muted-foreground\">\n              {sectionConfig.description}\n            </p>\n          </div>\n          <ChevronDown\n            className={cn(\n              \"size-4 shrink-0 text-muted-foreground transition-transform duration-200\",\n              isOpen && \"rotate-180\",\n            )}\n          />\n        </CollapsiblePrimitive.Trigger>\n        <CollapsiblePrimitive.Content className=\"border-t border-border data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down\">\n          <div className=\"p-4 space-y-2\">\n            {filteredItems.length === 0 ? (\n              <div className=\"text-center py-4 text-sm text-muted-foreground\">\n                {searchQuery ? \"No matching items\" : \"No items yet\"}\n              </div>\n            ) : (\n              filteredItems.map((item) => (\n                <AiMemoryViewerItem key={item.id} item={item} />\n              ))\n            )}\n            {onAdd && type === \"working\" && (\n              <AiMemoryViewerAddButton type={type} />\n            )}\n          </div>\n        </CollapsiblePrimitive.Content>\n      </div>\n    </CollapsiblePrimitive.Root>\n  );\n}\n\ninterface AiMemoryViewerItemProps {\n  item: MemoryItem;\n  className?: string;\n}\n\nfunction AiMemoryViewerItem({ item, className }: AiMemoryViewerItemProps) {\n  const { onRemove } = useMemoryViewerContext();\n\n  const sourceConfig = React.useMemo(() => {\n    const configs: Record<\n      MemorySource,\n      { icon: React.ReactNode; label: string; className: string }\n    > = {\n      user: {\n        icon: <User className=\"size-3\" />,\n        label: \"User\",\n        className:\n          \"bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300\",\n      },\n      system: {\n        icon: <Settings className=\"size-3\" />,\n        label: \"System\",\n        className:\n          \"bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300\",\n      },\n      conversation: {\n        icon: <MessageSquare className=\"size-3\" />,\n        label: \"Conversation\",\n        className:\n          \"bg-green-100 text-green-700 dark:bg-green-950 dark:text-green-300\",\n      },\n    };\n    return item.source ? configs[item.source] : null;\n  }, [item.source]);\n\n  const formatTime = (date: Date) => {\n    return new Intl.DateTimeFormat(\"en-US\", {\n      hour: \"2-digit\",\n      minute: \"2-digit\",\n    }).format(date);\n  };\n\n  return (\n    <div\n      data-slot=\"ai-memory-viewer-item\"\n      data-type={item.type}\n      className={cn(\n        \"group relative rounded-md border border-border bg-muted/30 p-3 transition-colors hover:bg-muted/50\",\n        className,\n      )}\n    >\n      <div className=\"flex items-start justify-between gap-2\">\n        <p className=\"text-sm text-foreground flex-1 min-w-0 pr-8\">\n          {item.content}\n        </p>\n        {onRemove && (\n          <button\n            type=\"button\"\n            onClick={() => onRemove(item.id)}\n            className={cn(\n              \"absolute right-2 top-2 flex size-6 items-center justify-center rounded-md transition-colors\",\n              \"text-muted-foreground hover:bg-red-100 hover:text-red-600 dark:hover:bg-red-950 dark:hover:text-red-400\",\n              \"opacity-0 group-hover:opacity-100 focus:opacity-100\",\n            )}\n          >\n            <Trash2 className=\"size-3.5\" />\n          </button>\n        )}\n      </div>\n      <div className=\"mt-2 flex items-center gap-2 text-xs text-muted-foreground\">\n        {sourceConfig && (\n          <span\n            className={cn(\n              \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-medium\",\n              sourceConfig.className,\n            )}\n          >\n            {sourceConfig.icon}\n            {sourceConfig.label}\n          </span>\n        )}\n        <span className=\"inline-flex items-center gap-1\">\n          <Clock className=\"size-3\" />\n          {formatTime(item.createdAt)}\n        </span>\n      </div>\n    </div>\n  );\n}\n\ninterface AiMemoryViewerAddButtonProps {\n  type: MemoryType;\n  className?: string;\n}\n\nfunction AiMemoryViewerAddButton({\n  type,\n  className,\n}: AiMemoryViewerAddButtonProps) {\n  const { onAdd } = useMemoryViewerContext();\n  const [isAdding, setIsAdding] = React.useState(false);\n  const [content, setContent] = React.useState(\"\");\n  const inputRef = React.useRef<HTMLTextAreaElement>(null);\n\n  const handleSubmit = React.useCallback(\n    (e: React.FormEvent) => {\n      e.preventDefault();\n      if (content.trim() && onAdd) {\n        onAdd(content.trim(), type);\n        setContent(\"\");\n        setIsAdding(false);\n      }\n    },\n    [content, onAdd, type],\n  );\n\n  const handleCancel = React.useCallback(() => {\n    setContent(\"\");\n    setIsAdding(false);\n  }, []);\n\n  React.useEffect(() => {\n    if (isAdding && inputRef.current) {\n      inputRef.current.focus();\n    }\n  }, [isAdding]);\n\n  if (!isAdding) {\n    return (\n      <button\n        type=\"button\"\n        onClick={() => setIsAdding(true)}\n        className={cn(\n          \"flex w-full items-center justify-center gap-2 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground transition-colors\",\n          \"hover:border-primary hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n          className,\n        )}\n      >\n        <Plus className=\"size-4\" />\n        Add memory\n      </button>\n    );\n  }\n\n  return (\n    <form onSubmit={handleSubmit} className={cn(\"space-y-2\", className)}>\n      <textarea\n        ref={inputRef}\n        value={content}\n        onChange={(e) => setContent(e.target.value)}\n        placeholder=\"Enter memory content...\"\n        rows={3}\n        className={cn(\n          \"w-full resize-none rounded-md border border-input bg-background px-3 py-2 text-sm\",\n          \"placeholder:text-muted-foreground\",\n          \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n        )}\n      />\n      <div className=\"flex items-center justify-end gap-2\">\n        <button\n          type=\"button\"\n          onClick={handleCancel}\n          className={cn(\n            \"rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors\",\n            \"hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n          )}\n        >\n          Cancel\n        </button>\n        <button\n          type=\"submit\"\n          disabled={!content.trim()}\n          className={cn(\n            \"rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors\",\n            \"hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n            \"disabled:opacity-50 disabled:cursor-not-allowed\",\n          )}\n        >\n          Add\n        </button>\n      </div>\n    </form>\n  );\n}\n\ninterface AiMemoryViewerCompactProps {\n  workingMemory?: MemoryItem[];\n  conversationHistory?: MemoryItem[];\n  className?: string;\n}\n\nfunction AiMemoryViewerCompact({\n  workingMemory = [],\n  conversationHistory = [],\n  className,\n}: AiMemoryViewerCompactProps) {\n  const totalItems = workingMemory.length + conversationHistory.length;\n\n  return (\n    <div\n      data-slot=\"ai-memory-viewer-compact\"\n      className={cn(\n        \"inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm\",\n        className,\n      )}\n    >\n      <Brain className=\"size-4 text-purple-600 dark:text-purple-400\" />\n      <span className=\"text-xs\">\n        <span className=\"font-medium\">{workingMemory.length}</span> working\n      </span>\n      <span className=\"text-muted-foreground\">/</span>\n      <span className=\"text-xs\">\n        <span className=\"font-medium\">{conversationHistory.length}</span>{\" \"}\n        history\n      </span>\n      <span className=\"text-xs text-muted-foreground\">\n        ({totalItems} total)\n      </span>\n    </div>\n  );\n}\n\nexport {\n  AiMemoryViewer,\n  AiMemoryViewerHeader,\n  AiMemoryViewerSearch,\n  AiMemoryViewerContent,\n  AiMemoryViewerSection,\n  AiMemoryViewerItem,\n  AiMemoryViewerAddButton,\n  AiMemoryViewerCompact,\n};\nexport type { AiMemoryViewerProps, MemoryItem, MemoryType, MemorySource };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Compound component for memory management. Memory types: working, history, system. Sources: user, system, conversation. Features: collapsible sections, search/filter, add/remove items, timestamps, and compact variant.",
  "categories": [
    "ai"
  ],
  "type": "registry:ui"
}