1→import { useState, useEffect } from 'react'
     2→import { Brain, Globe, Server, Plus, Search, Trash2, X, Pencil, Download } from 'lucide-react'
     3→import * as Dialog from '@radix-ui/react-dialog'
     4→import * as AlertDialog from '@radix-ui/react-alert-dialog'
     5→import { useProjectStore } from '../stores/projectStore'
     6→import { useToastStore } from '../components/Toast'
     7→
     8→type MemoryTab = 'project' | 'global' | 'infrastructure'
     9→
    10→interface ProjectMemoryItem {
    11→  id: number
    12→  project_id: number
    13→  type: string
    14→  content: string
    15→  metadata: Record<string, unknown> | null
    16→  created_at: string
    17→}
    18→
    19→interface GlobalMemoryItem {
    20→  id: number
    21→  type: string
    22→  content: string
    23→  metadata: Record<string, unknown> | null
    24→  usage_count: number
    25→  created_at: string
    26→}
    27→
    28→interface InfrastructureItem {
    29→  id: number
    30→  name: string
    31→  type: string
    32→  config: Record<string, unknown>
    33→  projects: number[] | null
    34→  created_at: string
    35→  updated_at: string
    36→}
    37→
    38→const API_BASE = 'http://localhost:8000/api/memory'
    39→
    40→export default function MemoryManager() {
    41→  const { currentProject } = useProjectStore()
    42→  const { addToast } = useToastStore()
    43→  const [activeTab, setActiveTab] = useState<MemoryTab>('project')
    44→  const [searchQuery, setSearchQuery] = useState('')
    45→
    46→  // Data states
    47→  const [projectMemory, setProjectMemory] = useState<ProjectMemoryItem[]>([])
    48→  const [globalMemory, setGlobalMemory] = useState<GlobalMemoryItem[]>([])
    49→  const [infrastructure, setInfrastructure] = useState<InfrastructureItem[]>([])
    50→  const [isLoading, setIsLoading] = useState(false)
    51→
    52→  // Dialog states
    53→  const [showAddDialog, setShowAddDialog] = useState(false)
    54→  const [newItemType, setNewItemType] = useState('')
    55→  const [newItemContent, setNewItemContent] = useState('')
    56→  const [newItemName, setNewItemName] = useState('')
    57→  const [isSaving, setIsSaving] = useState(false)
    58→
    59→  // Delete dialog states
    60→  const [showDeleteDialog, setShowDeleteDialog] = useState(false)
    61→  const [itemToDelete, setItemToDelete] = useState<{ id: number; type: MemoryTab } | null>(null)
    62→  const [isDeleting, setIsDeleting] = useState(false)
    63→
    64→  // Edit dialog states
    65→  const [showEditDialog, setShowEditDialog] = useState(false)
    66→  const [editItem, setEditItem] = useState<{ id: number; type: string; content: string; memoryType: MemoryTab } | null>(null)
    67→  const [editItemType, setEditItemType] = useState('')
    68→  const [editItemContent, setEditItemContent] = useState('')
    69→  const [isEditing, setIsEditing] = useState(false)
    70→  const [isExporting, setIsExporting] = useState(false)
    71→
    72→  const tabs = [
    73→    { id: 'project' as const, label: 'Project Memory', icon: Brain },
    74→    { id: 'global' as const, label: 'Global Memory', icon: Globe },
    75→    { id: 'infrastructure' as const, label: 'Infrastructure', icon: Server },
    76→  ]
    77→
    78→  // Fetch data based on active tab
    79→  useEffect(() => {
    80→    const fetchData = async () => {
    81→      setIsLoading(true)
    82→      try {
    83→        if (activeTab === 'project' && currentProject) {
    84→          const response = await fetch(`${API_BASE}/project/${currentProject.id}`)
    85→          if (response.ok) {
    86→            const data = await response.json()
    87→            setProjectMemory(data)
    88→          }
    89→        } else if (activeTab === 'global') {
    90→          const response = await fetch(`${API_BASE}/global`)
    91→          if (response.ok) {
    92→            const data = await response.json()
    93→            setGlobalMemory(data)
    94→          }
    95→        } else if (activeTab === 'infrastructure') {
    96→          const response = await fetch(`${API_BASE}/infrastructure`)
    97→          if (response.ok) {
    98→            const data = await response.json()
    99→            setInfrastructure(data)
   100→          }
   101→        }
   102→      } catch (error) {
   103→        console.error('Failed to fetch memory items:', error)
   104→      } finally {
   105→        setIsLoading(false)
   106→      }
   107→    }
   108→    fetchData()
   109→  }, [activeTab, currentProject])
   110→
   111→  const getTypeOptions = () => {
   112→    switch (activeTab) {
   113→      case 'project':
   114→        return ['insight', 'pattern', 'gotcha', 'codebase_map']
   115→      case 'global':
   116→        return ['preference', 'pattern', 'solution']
   117→      case 'infrastructure':
   118→        return ['server', 'docker', 'database', 'deployment']
   119→      default:
   120→        return []
   121→    }
   122→  }
   123→
   124→  const handleOpenAddDialog = () => {
   125→    setNewItemType(getTypeOptions()[0] || '')
   126→    setNewItemContent('')
   127→    setNewItemName('')
   128→    setShowAddDialog(true)
   129→  }
   130→
   131→  const handleAddItem = async () => {
   132→    if (!newItemType || (!newItemContent.trim() && activeTab !== 'infrastructure')) return
   133→    if (activeTab === 'infrastructure' && !newItemName.trim()) return
   134→
   135→    setIsSaving(true)
   136→    try {
   137→      let response: Response
   138→
   139→      if (activeTab === 'project' && currentProject) {
   140→        response = await fetch(`${API_BASE}/project/${currentProject.id}`, {
   141→          method: 'POST',
   142→          headers: { 'Content-Type': 'application/json' },
   143→          body: JSON.stringify({ type: newItemType, content: newItemContent.trim() }),
   144→        })
   145→        if (response.ok) {
   146→          const newItem = await response.json()
   147→          setProjectMemory([...projectMemory, newItem])
   148→        }
   149→      } else if (activeTab === 'global') {
   150→        response = await fetch(`${API_BASE}/global`, {
   151→          method: 'POST',
   152→          headers: { 'Content-Type': 'application/json' },
   153→          body: JSON.stringify({ type: newItemType, content: newItemContent.trim() }),
   154→        })
   155→        if (response.ok) {
   156→          const newItem = await response.json()
   157→          setGlobalMemory([...globalMemory, newItem])
   158→        }
   159→      } else if (activeTab === 'infrastructure') {
   160→        response = await fetch(`${API_BASE}/infrastructure`, {
   161→          method: 'POST',
   162→          headers: { 'Content-Type': 'application/json' },
   163→          body: JSON.stringify({
   164→            name: newItemName.trim(),
   165→            type: newItemType,
   166→            config: { content: newItemContent.trim() }
   167→          }),
   168→        })
   169→        if (response.ok) {
   170→          const newItem = await response.json()
   171→          setInfrastructure([...infrastructure, newItem])
   172→        }
   173→      }
   174→
   175→      setShowAddDialog(false)
   176→    } catch (error) {
   177→      console.error('Failed to add memory item:', error)
   178→    } finally {
   179→      setIsSaving(false)
   180→    }
   181→  }
   182→
   183→  const handleDeleteClick = (id: number, type: MemoryTab) => {
   184→    setItemToDelete({ id, type })
   185→    setShowDeleteDialog(true)
   186→  }
   187→
   188→  const handleDeleteConfirm = async () => {
   189→    if (!itemToDelete) return
   190→
   191→    setIsDeleting(true)
   192→    try {
   193→      let endpoint: string
   194→
   195→      if (itemToDelete.type === 'project' && currentProject) {
   196→        endpoint = `${API_BASE}/project/${currentProject.id}/${itemToDelete.id}`
   197→      } else if (itemToDelete.type === 'global') {
   198→        endpoint = `${API_BASE}/global/${itemToDelete.id}`
   199→      } else {
   200→        endpoint = `${API_BASE}/infrastructure/${itemToDelete.id}`
   201→      }
   202→
   203→      const response = await fetch(endpoint, { method: 'DELETE' })
   204→
   205→      if (response.ok || response.status === 204) {
   206→        if (itemToDelete.type === 'project') {
   207→          setProjectMemory(projectMemory.filter(item => item.id !== itemToDelete.id))
   208→        } else if (itemToDelete.type === 'global') {
   209→          setGlobalMemory(globalMemory.filter(item => item.id !== itemToDelete.id))
   210→        } else {
   211→          setInfrastructure(infrastructure.filter(item => item.id !== itemToDelete.id))
   212→        }
   213→      }
   214→
   215→      setShowDeleteDialog(false)
   216→      setItemToDelete(null)
   217→    } catch (error) {
   218→      console.error('Failed to delete memory item:', error)
   219→    } finally {
   220→      setIsDeleting(false)
   221→    }
   222→  }
   223→
   224→  const handleEditClick = (id: number, type: string, content: string, memoryType: MemoryTab) => {
   225→    setEditItem({ id, type, content, memoryType })
   226→    setEditItemType(type)
   227→    setEditItemContent(content)
   228→    setShowEditDialog(true)
   229→  }
   230→
   231→  const handleEditSave = async () => {
   232→    if (!editItem || !editItemContent.trim()) return
   233→
   234→    setIsEditing(true)
   235→    try {
   236→      let endpoint: string
   237→      let body: Record<string, unknown>
   238→
   239→      if (editItem.memoryType === 'project' && currentProject) {
   240→        endpoint = `${API_BASE}/project/${currentProject.id}/${editItem.id}`
   241→        body = { type: editItemType, content: editItemContent.trim() }
   242→      } else if (editItem.memoryType === 'global') {
   243→        endpoint = `${API_BASE}/global/${editItem.id}`
   244→        body = { type: editItemType, content: editItemContent.trim() }
   245→      } else {
   246→        // Infrastructure uses different update endpoint
   247→        setShowEditDialog(false)
   248→        setEditItem(null)
   249→        return
   250→      }
   251→
   252→      const response = await fetch(endpoint, {
   253→        method: 'PUT',
   254→        headers: { 'Content-Type': 'application/json' },
   255→        body: JSON.stringify(body),
   256→      })
   257→
   258→      if (response.ok) {
   259→        const updatedItem = await response.json()
   260→        if (editItem.memoryType === 'project') {
   261→          setProjectMemory(projectMemory.map(item =>
   262→            item.id === editItem.id ? updatedItem : item
   263→          ))
   264→        } else if (editItem.memoryType === 'global') {
   265→          setGlobalMemory(globalMemory.map(item =>
   266→            item.id === editItem.id ? updatedItem : item
   267→          ))
   268→        }
   269→      }
   270→
   271→      setShowEditDialog(false)
   272→      setEditItem(null)
   273→    } catch (error) {
   274→      console.error('Failed to update memory item:', error)
   275→    } finally {
   276→      setIsEditing(false)
   277→    }
   278→  }
   279→
   280→  const filterItems = <T extends { content?: string; name?: string; type: string }>(items: T[]) => {
   281→    if (!searchQuery) return items
   282→    const query = searchQuery.toLowerCase()
   283→    return items.filter(item =>
   284→      item.content?.toLowerCase().includes(query) ||
   285→      item.name?.toLowerCase().includes(query) ||
   286→      item.type.toLowerCase().includes(query)
   287→    )
   288→  }
   289→
   290→  const filteredProjectMemory = filterItems(projectMemory)
   291→  const filteredGlobalMemory = filterItems(globalMemory)
   292→  const filteredInfrastructure = filterItems(infrastructure)
   293→
   294→  const handleExportMemory = async () => {
   295→    setIsExporting(true)
   296→    try {
   297→      const url = currentProject
   298→        ? `${API_BASE}/export?project_id=${currentProject.id}`
   299→        : `${API_BASE}/export`
   300→      const response = await fetch(url)
   301→      if (!response.ok) {
   302→        throw new Error('Failed to export memory')
   303→      }
   304→      const data = await response.json()
   305→      const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
   306→      const downloadUrl = window.URL.createObjectURL(blob)
   307→      const a = document.createElement('a')
   308→      a.href = downloadUrl
   309→      a.download = `memory_export_${new Date().toISOString().split('T')[0]}.json`
   310→      document.body.appendChild(a)
   311→      a.click()
   312→      document.body.removeChild(a)
   313→      window.URL.revokeObjectURL(downloadUrl)
   314→      addToast('success', 'Export Complete', 'Memory items exported successfully.')
   315→    } catch (error) {
   316→      console.error('Failed to export memory:', error)
   317→      addToast('error', 'Export Failed', 'Failed to export memory items.')
   318→    } finally {
   319→      setIsExporting(false)
   320→    }
   321→  }
   322→
   323→  return (
   324→

   325→      {/* Header */}
   326→

   327→
Memory Manager

   328→

   329→          <button
   330→            onClick={handleExportMemory}
   331→            disabled={isExporting}
   332→            className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-accent hover:bg-accent/10 rounded-lg transition-colors disabled:opacity-50"
   333→            title="Export Memory Items"
   334→          >
   335→            <Download size={16} />
   336→            {isExporting ? 'Exporting...' : 'Export'}
   337→          </button>
   338→          <button
   339→            onClick={handleOpenAddDialog}
   340→            className="btn-accent flex items-center gap-2"
   341→          >
   342→            <Plus size={18} />
   343→            Add Memory Item
   344→          </button>
   345→

   346→

   347→
   348→      {/* Tabs */}
   349→

   350→        {tabs.map((tab) => {
   351→          const Icon = tab.icon
   352→          return (
   353→            <button
   354→              key={tab.id}
   355→              onClick={() => setActiveTab(tab.id)}
   356→              className={`flex items-center gap-2 px-4 py-2 rounded-md transition-colors ${
   357→                activeTab === tab.id
   358→                  ? 'bg-primary text-primary-foreground'
   359→                  : 'text-muted-foreground hover:text-foreground'
   360→              }`}
   361→            >
   362→              <Icon size={16} />
   363→              {tab.label}
   364→            </button>
   365→          )
   366→        })}
   367→

   368→
   369→      {/* Search */}
   370→

   371→        <Search
   372→          size={18}
   373→          className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
   374→        />
   375→        <input
   376→          type="text"
   377→          value={searchQuery}
   378→          onChange={(e) => setSearchQuery(e.target.value)}
   379→          placeholder="Search memory items..."
   380→          className="w-full pl-10 pr-4 py-2 bg-card border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent"
   381→        />
   382→

   383→
   384→      {/* Content */}
   385→

   386→        {isLoading ? (
   387→
Loading...

   388→        ) : (
   389→          <>
   390→            {activeTab === 'project' && (
   391→

   392→                {filteredProjectMemory.length === 0 ? (
   393→                  <EmptyState
   394→                    title="No Project Memory"
   395→                    description="Memory items from your current project will appear here."
   396→                  />
   397→                ) : (
   398→                  filteredProjectMemory.map(item => (
   399→                    <MemoryCard
   400→                      key={item.id}
   401→                      id={item.id}
   402→                      type={item.type}
   403→                      content={item.content}
   404→                      createdAt={item.created_at}
   405→                      onDelete={() => handleDeleteClick(item.id, 'project')}
   406→                      onEdit={() => handleEditClick(item.id, item.type, item.content, 'project')}
   407→                    />
   408→                  ))
   409→                )}
   410→

   411→            )}
   412→
   413→            {activeTab === 'global' && (
   414→

   415→                {filteredGlobalMemory.length === 0 ? (
   416→                  <EmptyState
   417→                    title="No Global Memory"
   418→                    description="Cross-project patterns and preferences will appear here."
   419→                  />
   420→                ) : (
   421→                  filteredGlobalMemory.map(item => (
   422→                    <MemoryCard
   423→                      key={item.id}
   424→                      id={item.id}
   425→                      type={item.type}
   426→                      content={item.content}
   427→                      createdAt={item.created_at}
   428→                      usageCount={item.usage_count}
   429→                      onDelete={() => handleDeleteClick(item.id, 'global')}
   430→                      onEdit={() => handleEditClick(item.id, item.type, item.content, 'global')}
   431→                    />
   432→                  ))
   433→                )}
   434→

   435→            )}
   436→
   437→            {activeTab === 'infrastructure' && (
   438→

   439→                {filteredInfrastructure.length === 0 ? (
   440→                  <EmptyState
   441→                    title="No Infrastructure Config"
   442→                    description="Server and deployment configurations will appear here."
   443→                  />
   444→                ) : (
   445→                  filteredInfrastructure.map(item => (
   446→                    <InfrastructureCard
   447→                      key={item.id}
   448→                      id={item.id}
   449→                      name={item.name}
   450→                      type={item.type}
   451→                      config={item.config}
   452→                      createdAt={item.created_at}
   453→                      onDelete={() => handleDeleteClick(item.id, 'infrastructure')}
   454→                    />
   455→                  ))
   456→                )}
   457→

   458→            )}
   459→          </>
   460→        )}
   461→

   462→
   463→      {/* Add Memory Dialog */}
   464→      <Dialog.Root open={showAddDialog} onOpenChange={setShowAddDialog}>
   465→        <Dialog.Portal>
   466→          <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
   467→          <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
   468→

   469→              <Dialog.Title className="text-xl font-semibold text-foreground">
   470→                Add {activeTab === 'project' ? 'Project' : activeTab === 'global' ? 'Global' : 'Infrastructure'} Memory
   471→              </Dialog.Title>
   472→              <Dialog.Close asChild>
   473→                <button className="p-1 text-muted-foreground hover:text-foreground hover:bg-secondary rounded transition-colors" aria-label="Close">
   474→                  <X size={20} />
   475→                </button>
   476→              </Dialog.Close>
   477→

   478→
   479→

   480→              {activeTab === 'infrastructure' && (
   481→

   482→                  Name *
   483→                  <input
   484→                    type="text"
   485→                    value={newItemName}
   486→                    onChange={(e) => setNewItemName(e.target.value)}
   487→                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent"
   488→                    placeholder="Enter config name"
   489→                  />
   490→

   491→              )}
   492→
   493→

   494→                Type *
   495→                <select
   496→                  value={newItemType}
   497→                  onChange={(e) => setNewItemType(e.target.value)}
   498→                  className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent"
   499→                >
   500→                  {getTypeOptions().map(type => (
   501→                    <option key={type} value={type}>{type}</option>
   502→                  ))}
   503→                </select>
   504→

   505→
   506→

   507→                Content *
   508→                <textarea
   509→                  value={newItemContent}
   510→                  onChange={(e) => setNewItemContent(e.target.value)}
   511→                  rows={4}
   512→                  className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent resize-none"
   513→                  placeholder="Enter memory content..."
   514→                />
   515→

   516→

   517→
   518→

   519→              <Dialog.Close asChild>
   520→                <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
   521→                  Cancel
   522→                </button>
   523→              </Dialog.Close>
   524→              <button
   525→                onClick={handleAddItem}
   526→                disabled={isSaving || !newItemContent.trim() || (activeTab === 'infrastructure' && !newItemName.trim())}
   527→                className="px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
   528→              >
   529→                {isSaving ? 'Saving...' : 'Add Item'}
   530→              </button>
   531→

   532→          </Dialog.Content>
   533→        </Dialog.Portal>
   534→      </Dialog.Root>
   535→
   536→      {/* Delete Confirmation Dialog */}
   537→      <AlertDialog.Root open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
   538→        <AlertDialog.Portal>
   539→          <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
   540→          <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
   541→            <AlertDialog.Title className="text-xl font-semibold text-foreground">
   542→              Delete Memory Item
   543→            </AlertDialog.Title>
   544→            <AlertDialog.Description className="mt-3 text-muted-foreground">
   545→              Are you sure you want to delete this memory item? This action cannot be undone.
   546→            </AlertDialog.Description>
   547→

   548→              <AlertDialog.Cancel asChild>
   549→                <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
   550→                  Cancel
   551→                </button>
   552→              </AlertDialog.Cancel>
   553→              <AlertDialog.Action asChild>
   554→                <button
   555→                  onClick={handleDeleteConfirm}
   556→                  disabled={isDeleting}
   557→                  className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors disabled:opacity-50"
   558→                >
   559→                  {isDeleting ? 'Deleting...' : 'Delete'}
   560→                </button>
   561→              </AlertDialog.Action>
   562→

   563→          </AlertDialog.Content>
   564→        </AlertDialog.Portal>
   565→      </AlertDialog.Root>
   566→
   567→      {/* Edit Memory Dialog */}
   568→      <Dialog.Root open={showEditDialog} onOpenChange={setShowEditDialog}>
   569→        <Dialog.Portal>
   570→          <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
   571→          <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
   572→

   573→              <Dialog.Title className="text-xl font-semibold text-foreground">
   574→                Edit Memory Item
   575→              </Dialog.Title>
   576→              <Dialog.Close asChild>
   577→                <button className="p-1 text-muted-foreground hover:text-foreground hover:bg-secondary rounded transition-colors" aria-label="Close">
   578→                  <X size={20} />
   579→                </button>
   580→              </Dialog.Close>
   581→

   582→
   583→

   584→

   585→                Type *
   586→                <select
   587→                  value={editItemType}
   588→                  onChange={(e) => setEditItemType(e.target.value)}
   589→                  className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent"
   590→                >
   591→                  {(editItem?.memoryType === 'project'
   592→                    ? ['insight', 'pattern', 'gotcha', 'codebase_map']
   593→                    : ['preference', 'pattern', 'solution']
   594→                  ).map(type => (
   595→                    <option key={type} value={type}>{type}</option>
   596→                  ))}
   597→                </select>
   598→

   599→
   600→

   601→                Content *
   602→                <textarea
   603→                  value={editItemContent}
   604→                  onChange={(e) => setEditItemContent(e.target.value)}
   605→                  rows={4}
   606→                  className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent resize-none"
   607→                  placeholder="Enter memory content..."
   608→                />
   609→

   610→

   611→
   612→

   613→              <Dialog.Close asChild>
   614→                <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
   615→                  Cancel
   616→                </button>
   617→              </Dialog.Close>
   618→              <button
   619→                onClick={handleEditSave}
   620→                disabled={isEditing || !editItemContent.trim()}
   621→                className="px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
   622→              >
   623→                {isEditing ? 'Saving...' : 'Save Changes'}
   624→              </button>
   625→

   626→          </Dialog.Content>
   627→        </Dialog.Portal>
   628→      </Dialog.Root>
   629→

   630→  )
   631→}
   632→
   633→function MemoryCard({
   634→  id: _id,
   635→  type,
   636→  content,
   637→  createdAt,
   638→  usageCount,
   639→  onDelete,
   640→  onEdit,
   641→}: {
   642→  id: number
   643→  type: string
   644→  content: string
   645→  createdAt: string
   646→  usageCount?: number
   647→  onDelete: () => void
   648→  onEdit?: () => void
   649→}) {
   650→  return (
   651→

   652→

   653→

   654→

   655→
   656→              {type}
   657→
   658→            {usageCount !== undefined && (
   659→              Used {usageCount} times
   660→            )}
   661→

   662→
{content}

   663→

   664→            Created: {new Date(createdAt).toLocaleString()}
   665→

   666→

   667→

   668→          {onEdit && (
   669→            <button
   670→              onClick={onEdit}
   671→              className="p-2 text-muted-foreground hover:text-accent hover:bg-accent/10 rounded-lg transition-colors"
   672→              title="Edit"
   673→              aria-label="Edit memory item"
   674→            >
   675→              <Pencil size={16} />
   676→            </button>
   677→          )}
   678→          <button
   679→            onClick={onDelete}
   680→            className="p-2 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
   681→            title="Delete"
   682→            aria-label="Delete memory item"
   683→          >
   684→            <Trash2 size={16} />
   685→          </button>
   686→

   687→

   688→

   689→  )
   690→}
   691→
   692→function InfrastructureCard({
   693→  id: _id,
   694→  name,
   695→  type,
   696→  config,
   697→  createdAt,
   698→  onDelete,
   699→}: {
   700→  id: number
   701→  name: string
   702→  type: string
   703→  config: Record<string, unknown>
   704→  createdAt: string
   705→  onDelete: () => void
   706→}) {
   707→  return (
   708→

   709→

   710→

   711→

   712→
{name}

   713→
   714→              {type}
   715→
   716→

   717→

   718→            {JSON.stringify(config, null, 2)}
   719→

   720→

   721→            Created: {new Date(createdAt).toLocaleString()}
   722→

   723→

   724→        <button
   725→          onClick={onDelete}
   726→          className="p-2 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
   727→          title="Delete"
   728→        >
   729→          <Trash2 size={16} />
   730→        </button>
   731→

   732→

   733→  )
   734→}
   735→
   736→function EmptyState({
   737→  title,
   738→  description,
   739→}: {
   740→  title: string
   741→  description: string
   742→}) {
   743→  return (
   744→

   745→      <Brain size={48} className="text-muted-foreground mx-auto mb-4" />
   746→
{title}

   747→
{description}

   748→

   749→  )
   750→}
   751→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import { useState, useEffect } from 'react'
import { Brain, Globe, Server, Plus, Search, Trash2, X, Pencil, Download } from 'lucide-react'
import * as Dialog from '@radix-ui/react-dialog'
import * as AlertDialog from '@radix-ui/react-alert-dialog'
import { useProjectStore } from '../stores/projectStore'
import { useToastStore } from '../components/Toast'

type MemoryTab = 'project' | 'global' | 'infrastructure'

interface ProjectMemoryItem {
  id: number
  project_id: number
  type: string
  content: string
  metadata: Record<string, unknown> | null
  created_at: string
}

interface GlobalMemoryItem {
  id: number
  type: string
  content: string
  metadata: Record<string, unknown> | null
  usage_count: number
  created_at: string
}

interface InfrastructureItem {
  id: number
  name: string
  type: string
  config: Record<string, unknown>
  projects: number[] | null
  created_at: string
  updated_at: string
}

const API_BASE = 'http://localhost:8000/api/memory'

export default function MemoryManager() {
  const { currentProject } = useProjectStore()
  const { addToast } = useToastStore()
  const [activeTab, setActiveTab] = useState<MemoryTab>('project')
  const [searchQuery, setSearchQuery] = useState('')

  // Data states
  const [projectMemory, setProjectMemory] = useState<ProjectMemoryItem[]>([])
  const [globalMemory, setGlobalMemory] = useState<GlobalMemoryItem[]>([])
  const [infrastructure, setInfrastructure] = useState<InfrastructureItem[]>([])
  const [isLoading, setIsLoading] = useState(false)

  // Dialog states
  const [showAddDialog, setShowAddDialog] = useState(false)
  const [newItemType, setNewItemType] = useState('')
  const [newItemContent, setNewItemContent] = useState('')
  const [newItemName, setNewItemName] = useState('')
  const [isSaving, setIsSaving] = useState(false)

  // Delete dialog states
  const [showDeleteDialog, setShowDeleteDialog] = useState(false)
  const [itemToDelete, setItemToDelete] = useState<{ id: number; type: MemoryTab } | null>(null)
  const [isDeleting, setIsDeleting] = useState(false)

  // Edit dialog states
  const [showEditDialog, setShowEditDialog] = useState(false)
  const [editItem, setEditItem] = useState<{ id: number; type: string; content: string; memoryType: MemoryTab } | null>(null)
  const [editItemType, setEditItemType] = useState('')
  const [editItemContent, setEditItemContent] = useState('')
  const [isEditing, setIsEditing] = useState(false)
  const [isExporting, setIsExporting] = useState(false)

  const tabs = [
    { id: 'project' as const, label: 'Project Memory', icon: Brain },
    { id: 'global' as const, label: 'Global Memory', icon: Globe },
    { id: 'infrastructure' as const, label: 'Infrastructure', icon: Server },
  ]

  // Fetch data based on active tab
  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true)
      try {
        if (activeTab === 'project' && currentProject) {
          const response = await fetch(`${API_BASE}/project/${currentProject.id}`)
          if (response.ok) {
            const data = await response.json()
            setProjectMemory(data)
          }
        } else if (activeTab === 'global') {
          const response = await fetch(`${API_BASE}/global`)
          if (response.ok) {
            const data = await response.json()
            setGlobalMemory(data)
          }
        } else if (activeTab === 'infrastructure') {
          const response = await fetch(`${API_BASE}/infrastructure`)
          if (response.ok) {
            const data = await response.json()
            setInfrastructure(data)
          }
        }
      } catch (error) {
        console.error('Failed to fetch memory items:', error)
      } finally {
        setIsLoading(false)
      }
    }
    fetchData()
  }, [activeTab, currentProject])

  const getTypeOptions = () => {
    switch (activeTab) {
      case 'project':
        return ['insight', 'pattern', 'gotcha', 'codebase_map']
      case 'global':
        return ['preference', 'pattern', 'solution']
      case 'infrastructure':
        return ['server', 'docker', 'database', 'deployment']
      default:
        return []
    }
  }

  const handleOpenAddDialog = () => {
    setNewItemType(getTypeOptions()[0] || '')
    setNewItemContent('')
    setNewItemName('')
    setShowAddDialog(true)
  }

  const handleAddItem = async () => {
    if (!newItemType || (!newItemContent.trim() && activeTab !== 'infrastructure')) return
    if (activeTab === 'infrastructure' && !newItemName.trim()) return

    setIsSaving(true)
    try {
      let response: Response

      if (activeTab === 'project' && currentProject) {
        response = await fetch(`${API_BASE}/project/${currentProject.id}`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ type: newItemType, content: newItemContent.trim() }),
        })
        if (response.ok) {
          const newItem = await response.json()
          setProjectMemory([...projectMemory, newItem])
        }
      } else if (activeTab === 'global') {
        response = await fetch(`${API_BASE}/global`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ type: newItemType, content: newItemContent.trim() }),
        })
        if (response.ok) {
          const newItem = await response.json()
          setGlobalMemory([...globalMemory, newItem])
        }
      } else if (activeTab === 'infrastructure') {
        response = await fetch(`${API_BASE}/infrastructure`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            name: newItemName.trim(),
            type: newItemType,
            config: { content: newItemContent.trim() }
          }),
        })
        if (response.ok) {
          const newItem = await response.json()
          setInfrastructure([...infrastructure, newItem])
        }
      }

      setShowAddDialog(false)
    } catch (error) {
      console.error('Failed to add memory item:', error)
    } finally {
      setIsSaving(false)
    }
  }

  const handleDeleteClick = (id: number, type: MemoryTab) => {
    setItemToDelete({ id, type })
    setShowDeleteDialog(true)
  }

  const handleDeleteConfirm = async () => {
    if (!itemToDelete) return

    setIsDeleting(true)
    try {
      let endpoint: string

      if (itemToDelete.type === 'project' && currentProject) {
        endpoint = `${API_BASE}/project/${currentProject.id}/${itemToDelete.id}`
      } else if (itemToDelete.type === 'global') {
        endpoint = `${API_BASE}/global/${itemToDelete.id}`
      } else {
        endpoint = `${API_BASE}/infrastructure/${itemToDelete.id}`
      }

      const response = await fetch(endpoint, { method: 'DELETE' })

      if (response.ok || response.status === 204) {
        if (itemToDelete.type === 'project') {
          setProjectMemory(projectMemory.filter(item => item.id !== itemToDelete.id))
        } else if (itemToDelete.type === 'global') {
          setGlobalMemory(globalMemory.filter(item => item.id !== itemToDelete.id))
        } else {
          setInfrastructure(infrastructure.filter(item => item.id !== itemToDelete.id))
        }
      }

      setShowDeleteDialog(false)
      setItemToDelete(null)
    } catch (error) {
      console.error('Failed to delete memory item:', error)
    } finally {
      setIsDeleting(false)
    }
  }

  const handleEditClick = (id: number, type: string, content: string, memoryType: MemoryTab) => {
    setEditItem({ id, type, content, memoryType })
    setEditItemType(type)
    setEditItemContent(content)
    setShowEditDialog(true)
  }

  const handleEditSave = async () => {
    if (!editItem || !editItemContent.trim()) return

    setIsEditing(true)
    try {
      let endpoint: string
      let body: Record<string, unknown>

      if (editItem.memoryType === 'project' && currentProject) {
        endpoint = `${API_BASE}/project/${currentProject.id}/${editItem.id}`
        body = { type: editItemType, content: editItemContent.trim() }
      } else if (editItem.memoryType === 'global') {
        endpoint = `${API_BASE}/global/${editItem.id}`
        body = { type: editItemType, content: editItemContent.trim() }
      } else {
        // Infrastructure uses different update endpoint
        setShowEditDialog(false)
        setEditItem(null)
        return
      }

      const response = await fetch(endpoint, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      })

      if (response.ok) {
        const updatedItem = await response.json()
        if (editItem.memoryType === 'project') {
          setProjectMemory(projectMemory.map(item =>
            item.id === editItem.id ? updatedItem : item
          ))
        } else if (editItem.memoryType === 'global') {
          setGlobalMemory(globalMemory.map(item =>
            item.id === editItem.id ? updatedItem : item
          ))
        }
      }

      setShowEditDialog(false)
      setEditItem(null)
    } catch (error) {
      console.error('Failed to update memory item:', error)
    } finally {
      setIsEditing(false)
    }
  }

  const filterItems = <T extends { content?: string; name?: string; type: string }>(items: T[]) => {
    if (!searchQuery) return items
    const query = searchQuery.toLowerCase()
    return items.filter(item =>
      item.content?.toLowerCase().includes(query) ||
      item.name?.toLowerCase().includes(query) ||
      item.type.toLowerCase().includes(query)
    )
  }

  const filteredProjectMemory = filterItems(projectMemory)
  const filteredGlobalMemory = filterItems(globalMemory)
  const filteredInfrastructure = filterItems(infrastructure)

  const handleExportMemory = async () => {
    setIsExporting(true)
    try {
      const url = currentProject
        ? `${API_BASE}/export?project_id=${currentProject.id}`
        : `${API_BASE}/export`
      const response = await fetch(url)
      if (!response.ok) {
        throw new Error('Failed to export memory')
      }
      const data = await response.json()
      const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
      const downloadUrl = window.URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = downloadUrl
      a.download = `memory_export_${new Date().toISOString().split('T')[0]}.json`
      document.body.appendChild(a)
      a.click()
      document.body.removeChild(a)
      window.URL.revokeObjectURL(downloadUrl)
      addToast('success', 'Export Complete', 'Memory items exported successfully.')
    } catch (error) {
      console.error('Failed to export memory:', error)
      addToast('error', 'Export Failed', 'Failed to export memory items.')
    } finally {
      setIsExporting(false)
    }
  }

  return (

      {/* Header */}

Memory Manager

          <button
            onClick={handleExportMemory}
            disabled={isExporting}
            className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-accent hover:bg-accent/10 rounded-lg transition-colors disabled:opacity-50"
            title="Export Memory Items"
          >
            <Download size={16} />
            {isExporting ? 'Exporting...' : 'Export'}
          </button>
          <button
            onClick={handleOpenAddDialog}
            className="btn-accent flex items-center gap-2"
          >
            <Plus size={18} />
            Add Memory Item
          </button>

      {/* Tabs */}

        {tabs.map((tab) => {
          const Icon = tab.icon
          return (
            <button
              key={tab.id}
              onClick={() => setActiveTab(tab.id)}
              className={`flex items-center gap-2 px-4 py-2 rounded-md transition-colors ${
                activeTab === tab.id
                  ? 'bg-primary text-primary-foreground'
                  : 'text-muted-foreground hover:text-foreground'
              }`}
            >
              <Icon size={16} />
              {tab.label}
            </button>
          )
        })}

      {/* Search */}

        <Search
          size={18}
          className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
        />
        <input
          type="text"
          value={searchQuery}
          onChange={(e) => setSearchQuery(e.target.value)}
          placeholder="Search memory items..."
          className="w-full pl-10 pr-4 py-2 bg-card border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent"
        />

      {/* Content */}

        {isLoading ? (

Loading...

        ) : (
          <>
            {activeTab === 'project' && (

                {filteredProjectMemory.length === 0 ? (
                  <EmptyState
                    title="No Project Memory"
                    description="Memory items from your current project will appear here."
                  />
                ) : (
                  filteredProjectMemory.map(item => (
                    <MemoryCard
                      key={item.id}
                      id={item.id}
                      type={item.type}
                      content={item.content}
                      createdAt={item.created_at}
                      onDelete={() => handleDeleteClick(item.id, 'project')}
                      onEdit={() => handleEditClick(item.id, item.type, item.content, 'project')}
                    />
                  ))
                )}

            )}

            {activeTab === 'global' && (

                {filteredGlobalMemory.length === 0 ? (
                  <EmptyState
                    title="No Global Memory"
                    description="Cross-project patterns and preferences will appear here."
                  />
                ) : (
                  filteredGlobalMemory.map(item => (
                    <MemoryCard
                      key={item.id}
                      id={item.id}
                      type={item.type}
                      content={item.content}
                      createdAt={item.created_at}
                      usageCount={item.usage_count}
                      onDelete={() => handleDeleteClick(item.id, 'global')}
                      onEdit={() => handleEditClick(item.id, item.type, item.content, 'global')}
                    />
                  ))
                )}

            )}

            {activeTab === 'infrastructure' && (

                {filteredInfrastructure.length === 0 ? (
                  <EmptyState
                    title="No Infrastructure Config"
                    description="Server and deployment configurations will appear here."
                  />
                ) : (
                  filteredInfrastructure.map(item => (
                    <InfrastructureCard
                      key={item.id}
                      id={item.id}
                      name={item.name}
                      type={item.type}
                      config={item.config}
                      createdAt={item.created_at}
                      onDelete={() => handleDeleteClick(item.id, 'infrastructure')}
                    />
                  ))
                )}

            )}
          </>
        )}

      {/* Add Memory Dialog */}
      <Dialog.Root open={showAddDialog} onOpenChange={setShowAddDialog}>
        <Dialog.Portal>
          <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
          <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">

              <Dialog.Title className="text-xl font-semibold text-foreground">
                Add {activeTab === 'project' ? 'Project' : activeTab === 'global' ? 'Global' : 'Infrastructure'} Memory
              </Dialog.Title>
              <Dialog.Close asChild>
                <button className="p-1 text-muted-foreground hover:text-foreground hover:bg-secondary rounded transition-colors" aria-label="Close">
                  <X size={20} />
                </button>
              </Dialog.Close>

              {activeTab === 'infrastructure' && (

                  Name *
                  <input
                    type="text"
                    value={newItemName}
                    onChange={(e) => setNewItemName(e.target.value)}
                    className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent"
                    placeholder="Enter config name"
                  />

              )}

                Type *
                <select
                  value={newItemType}
                  onChange={(e) => setNewItemType(e.target.value)}
                  className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent"
                >
                  {getTypeOptions().map(type => (
                    <option key={type} value={type}>{type}</option>
                  ))}
                </select>

                Content *
                <textarea
                  value={newItemContent}
                  onChange={(e) => setNewItemContent(e.target.value)}
                  rows={4}
                  className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent resize-none"
                  placeholder="Enter memory content..."
                />

              <Dialog.Close asChild>
                <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                  Cancel
                </button>
              </Dialog.Close>
              <button
                onClick={handleAddItem}
                disabled={isSaving || !newItemContent.trim() || (activeTab === 'infrastructure' && !newItemName.trim())}
                className="px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
              >
                {isSaving ? 'Saving...' : 'Add Item'}
              </button>

          </Dialog.Content>
        </Dialog.Portal>
      </Dialog.Root>

      {/* Delete Confirmation Dialog */}
      <AlertDialog.Root open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
        <AlertDialog.Portal>
          <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
          <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
            <AlertDialog.Title className="text-xl font-semibold text-foreground">
              Delete Memory Item
            </AlertDialog.Title>
            <AlertDialog.Description className="mt-3 text-muted-foreground">
              Are you sure you want to delete this memory item? This action cannot be undone.
            </AlertDialog.Description>

              <AlertDialog.Cancel asChild>
                <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                  Cancel
                </button>
              </AlertDialog.Cancel>
              <AlertDialog.Action asChild>
                <button
                  onClick={handleDeleteConfirm}
                  disabled={isDeleting}
                  className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors disabled:opacity-50"
                >
                  {isDeleting ? 'Deleting...' : 'Delete'}
                </button>
              </AlertDialog.Action>

          </AlertDialog.Content>
        </AlertDialog.Portal>
      </AlertDialog.Root>

      {/* Edit Memory Dialog */}
      <Dialog.Root open={showEditDialog} onOpenChange={setShowEditDialog}>
        <Dialog.Portal>
          <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
          <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">

              <Dialog.Title className="text-xl font-semibold text-foreground">
                Edit Memory Item
              </Dialog.Title>
              <Dialog.Close asChild>
                <button className="p-1 text-muted-foreground hover:text-foreground hover:bg-secondary rounded transition-colors" aria-label="Close">
                  <X size={20} />
                </button>
              </Dialog.Close>

                Type *
                <select
                  value={editItemType}
                  onChange={(e) => setEditItemType(e.target.value)}
                  className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-accent"
                >
                  {(editItem?.memoryType === 'project'
                    ? ['insight', 'pattern', 'gotcha', 'codebase_map']
                    : ['preference', 'pattern', 'solution']
                  ).map(type => (
                    <option key={type} value={type}>{type}</option>
                  ))}
                </select>

                Content *
                <textarea
                  value={editItemContent}
                  onChange={(e) => setEditItemContent(e.target.value)}
                  rows={4}
                  className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent resize-none"
                  placeholder="Enter memory content..."
                />

              <Dialog.Close asChild>
                <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                  Cancel
                </button>
              </Dialog.Close>
              <button
                onClick={handleEditSave}
                disabled={isEditing || !editItemContent.trim()}
                className="px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
              >
                {isEditing ? 'Saving...' : 'Save Changes'}
              </button>

          </Dialog.Content>
        </Dialog.Portal>
      </Dialog.Root>

  )
}

function MemoryCard({
  id: _id,
  type,
  content,
  createdAt,
  usageCount,
  onDelete,
  onEdit,
}: {
  id: number
  type: string
  content: string
  createdAt: string
  usageCount?: number
  onDelete: () => void
  onEdit?: () => void
}) {
  return (

              {type}

            {usageCount !== undefined && (
              Used {usageCount} times
            )}

{content}

            Created: {new Date(createdAt).toLocaleString()}

          {onEdit && (
            <button
              onClick={onEdit}
              className="p-2 text-muted-foreground hover:text-accent hover:bg-accent/10 rounded-lg transition-colors"
              title="Edit"
              aria-label="Edit memory item"
            >
              <Pencil size={16} />
            </button>
          )}
          <button
            onClick={onDelete}
            className="p-2 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
            title="Delete"
            aria-label="Delete memory item"
          >
            <Trash2 size={16} />
          </button>

  )
}

function InfrastructureCard({
  id: _id,
  name,
  type,
  config,
  createdAt,
  onDelete,
}: {
  id: number
  name: string
  type: string
  config: Record<string, unknown>
  createdAt: string
  onDelete: () => void
}) {
  return (

{name}

              {type}

            {JSON.stringify(config, null, 2)}

            Created: {new Date(createdAt).toLocaleString()}

        <button
          onClick={onDelete}
          className="p-2 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
          title="Delete"
        >
          <Trash2 size={16} />
        </button>

  )
}

function EmptyState({
  title,
  description,
}: {
  title: string
  description: string
}) {
  return (

      <Brain size={48} className="text-muted-foreground mx-auto mb-4" />

{title}

{description}

  )
}


I can see there's an Export feature (line 294-321) but no Import feature. Let me check the backend for import endpoints:


     1→"""
     2→Memory API Router
     3→
     4→Handles the three-tier memory system:
     5→- Project Memory: Session insights, codebase map, patterns, gotchas
     6→- Global Memory: User preferences, cross-project patterns, common solutions
     7→- Infrastructure Memory: Server configs, Docker, deployment targets
     8→"""
     9→
    10→from typing import List, Optional
    11→from fastapi import APIRouter, Depends, HTTPException, status
    12→from sqlalchemy.ext.asyncio import AsyncSession
    13→from pydantic import BaseModel, Field
    14→from datetime import datetime
    15→
    16→from services.database import get_db
    17→from models.memory import MemoryProject, MemoryGlobal, MemoryInfrastructure
    18→
    19→router = APIRouter()
    20→
    21→
    22→# === Project Memory Schemas ===
    23→
    24→class ProjectMemoryCreate(BaseModel):
    25→    """Schema for creating project memory item."""
    26→    type: str  # insight, pattern, gotcha, codebase_map
    27→    content: str
    28→    metadata: Optional[dict] = None
    29→
    30→
    31→class ProjectMemoryResponse(BaseModel):
    32→    """Schema for project memory response."""
    33→    id: int
    34→    project_id: int
    35→    type: str
    36→    content: str
    37→    metadata: Optional[dict] = Field(None, alias="extra_data")
    38→    created_at: datetime
    39→
    40→    class Config:
    41→        from_attributes = True
    42→        populate_by_name = True
    43→
    44→
    45→# === Global Memory Schemas ===
    46→
    47→class GlobalMemoryCreate(BaseModel):
    48→    """Schema for creating global memory item."""
    49→    type: str  # preference, pattern, solution
    50→    content: str
    51→    metadata: Optional[dict] = None
    52→
    53→
    54→class GlobalMemoryResponse(BaseModel):
    55→    """Schema for global memory response."""
    56→    id: int
    57→    type: str
    58→    content: str
    59→    metadata: Optional[dict] = Field(None, alias="extra_data")
    60→    usage_count: int
    61→    created_at: datetime
    62→
    63→    class Config:
    64→        from_attributes = True
    65→        populate_by_name = True
    66→
    67→
    68→# === Infrastructure Memory Schemas ===
    69→
    70→class InfrastructureCreate(BaseModel):
    71→    """Schema for creating infrastructure config."""
    72→    name: str
    73→    type: str  # server, docker, database, deployment
    74→    config: dict
    75→    projects: Optional[List[int]] = None  # None means all projects
    76→
    77→
    78→class InfrastructureUpdate(BaseModel):
    79→    """Schema for updating infrastructure config."""
    80→    name: Optional[str] = None
    81→    type: Optional[str] = None
    82→    config: Optional[dict] = None
    83→    projects: Optional[List[int]] = None
    84→
    85→
    86→class InfrastructureResponse(BaseModel):
    87→    """Schema for infrastructure memory response."""
    88→    id: int
    89→    name: str
    90→    type: str
    91→    config: dict
    92→    projects: Optional[List[int]]
    93→    created_at: datetime
    94→    updated_at: datetime
    95→
    96→    class Config:
    97→        from_attributes = True
    98→
    99→
   100→# === Project Memory Endpoints ===
   101→
   102→@router.get("/project/{project_id}", response_model=List[ProjectMemoryResponse])
   103→async def get_project_memory(
   104→    project_id: int,
   105→    type: Optional[str] = None,
   106→    db: AsyncSession = Depends(get_db)
   107→):
   108→    """Get all memory items for a project."""
   109→    items = await MemoryProject.get_for_project(db, project_id, type=type)
   110→    return items
   111→
   112→
   113→@router.post("/project/{project_id}", response_model=ProjectMemoryResponse, status_code=status.HTTP_201_CREATED)
   114→async def add_project_memory(
   115→    project_id: int,
   116→    memory: ProjectMemoryCreate,
   117→    db: AsyncSession = Depends(get_db)
   118→):
   119→    """Add a memory item for a project."""
   120→    valid_types = ["insight", "pattern", "gotcha", "codebase_map"]
   121→    if memory.type not in valid_types:
   122→        raise HTTPException(
   123→            status_code=status.HTTP_400_BAD_REQUEST,
   124→            detail=f"Invalid type. Must be one of: {valid_types}"
   125→        )
   126→
   127→    item = await MemoryProject.create(
   128→        db,
   129→        project_id=project_id,
   130→        type=memory.type,
   131→        content=memory.content,
   132→        extra_data=memory.metadata,
   133→    )
   134→    return item
   135→
   136→
   137→@router.put("/project/{project_id}/{item_id}", response_model=ProjectMemoryResponse)
   138→async def update_project_memory(
   139→    project_id: int,
   140→    item_id: int,
   141→    memory: ProjectMemoryCreate,
   142→    db: AsyncSession = Depends(get_db)
   143→):
   144→    """Update a project memory item."""
   145→    item = await MemoryProject.get_by_id(db, item_id)
   146→    if not item or item.project_id != project_id:
   147→        raise HTTPException(
   148→            status_code=status.HTTP_404_NOT_FOUND,
   149→            detail="Memory item not found"
   150→        )
   151→
   152→    updated = await MemoryProject.update(
   153→        db, item_id,
   154→        type=memory.type,
   155→        content=memory.content,
   156→        extra_data=memory.metadata
   157→    )
   158→    return updated
   159→
   160→
   161→@router.delete("/project/{project_id}/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
   162→async def delete_project_memory(
   163→    project_id: int,
   164→    item_id: int,
   165→    db: AsyncSession = Depends(get_db)
   166→):
   167→    """Delete a project memory item."""
   168→    item = await MemoryProject.get_by_id(db, item_id)
   169→    if not item or item.project_id != project_id:
   170→        raise HTTPException(
   171→            status_code=status.HTTP_404_NOT_FOUND,
   172→            detail="Memory item not found"
   173→        )
   174→
   175→    await MemoryProject.delete(db, item_id)
   176→    return None
   177→
   178→
   179→# === Global Memory Endpoints ===
   180→
   181→@router.get("/global", response_model=List[GlobalMemoryResponse])
   182→async def get_global_memory(
   183→    type: Optional[str] = None,
   184→    db: AsyncSession = Depends(get_db)
   185→):
   186→    """Get all global memory items."""
   187→    items = await MemoryGlobal.get_all(db, type=type)
   188→    return items
   189→
   190→
   191→@router.post("/global", response_model=GlobalMemoryResponse, status_code=status.HTTP_201_CREATED)
   192→async def add_global_memory(
   193→    memory: GlobalMemoryCreate,
   194→    db: AsyncSession = Depends(get_db)
   195→):
   196→    """Add a global memory item."""
   197→    valid_types = ["preference", "pattern", "solution"]
   198→    if memory.type not in valid_types:
   199→        raise HTTPException(
   200→            status_code=status.HTTP_400_BAD_REQUEST,
   201→            detail=f"Invalid type. Must be one of: {valid_types}"
   202→        )
   203→
   204→    item = await MemoryGlobal.create(
   205→        db,
   206→        type=memory.type,
   207→        content=memory.content,
   208→        extra_data=memory.metadata,
   209→    )
   210→    return item
   211→
   212→
   213→@router.put("/global/{item_id}", response_model=GlobalMemoryResponse)
   214→async def update_global_memory(
   215→    item_id: int,
   216→    memory: GlobalMemoryCreate,
   217→    db: AsyncSession = Depends(get_db)
   218→):
   219→    """Update a global memory item."""
   220→    item = await MemoryGlobal.get_by_id(db, item_id)
   221→    if not item:
   222→        raise HTTPException(
   223→            status_code=status.HTTP_404_NOT_FOUND,
   224→            detail="Global memory item not found"
   225→        )
   226→
   227→    updated = await MemoryGlobal.update(
   228→        db, item_id,
   229→        type=memory.type,
   230→        content=memory.content,
   231→        extra_data=memory.metadata
   232→    )
   233→    return updated
   234→
   235→
   236→@router.delete("/global/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
   237→async def delete_global_memory(item_id: int, db: AsyncSession = Depends(get_db)):
   238→    """Delete a global memory item."""
   239→    await MemoryGlobal.delete(db, item_id)
   240→    return None
   241→
   242→
   243→# === Infrastructure Memory Endpoints ===
   244→
   245→@router.get("/infrastructure", response_model=List[InfrastructureResponse])
   246→async def get_infrastructure(
   247→    type: Optional[str] = None,
   248→    project_id: Optional[int] = None,
   249→    db: AsyncSession = Depends(get_db)
   250→):
   251→    """Get infrastructure configurations."""
   252→    items = await MemoryInfrastructure.get_all(db, type=type, project_id=project_id)
   253→    return items
   254→
   255→
   256→@router.post("/infrastructure", response_model=InfrastructureResponse, status_code=status.HTTP_201_CREATED)
   257→async def add_infrastructure(
   258→    config: InfrastructureCreate,
   259→    db: AsyncSession = Depends(get_db)
   260→):
   261→    """Add an infrastructure configuration."""
   262→    valid_types = ["server", "docker", "database", "deployment"]
   263→    if config.type not in valid_types:
   264→        raise HTTPException(
   265→            status_code=status.HTTP_400_BAD_REQUEST,
   266→            detail=f"Invalid type. Must be one of: {valid_types}"
   267→        )
   268→
   269→    item = await MemoryInfrastructure.create(
   270→        db,
   271→        name=config.name,
   272→        type=config.type,
   273→        config=config.config,
   274→        projects=config.projects,
   275→    )
   276→    return item
   277→
   278→
   279→@router.put("/infrastructure/{config_id}", response_model=InfrastructureResponse)
   280→async def update_infrastructure(
   281→    config_id: int,
   282→    config: InfrastructureUpdate,
   283→    db: AsyncSession = Depends(get_db)
   284→):
   285→    """Update an infrastructure configuration."""
   286→    item = await MemoryInfrastructure.get_by_id(db, config_id)
   287→    if not item:
   288→        raise HTTPException(
   289→            status_code=status.HTTP_404_NOT_FOUND,
   290→            detail="Infrastructure config not found"
   291→        )
   292→
   293→    updated = await MemoryInfrastructure.update(
   294→        db,
   295→        config_id,
   296→        **config.model_dump(exclude_unset=True)
   297→    )
   298→    return updated
   299→
   300→
   301→@router.delete("/infrastructure/{config_id}", status_code=status.HTTP_204_NO_CONTENT)
   302→async def delete_infrastructure(config_id: int, db: AsyncSession = Depends(get_db)):
   303→    """Delete an infrastructure configuration."""
   304→    await MemoryInfrastructure.delete(db, config_id)
   305→    return None
   306→
   307→
   308→# === Export Endpoints ===
   309→
   310→class MemoryExportResponse(BaseModel):
   311→    """Schema for memory export response."""
   312→    export_version: str = "1.0"
   313→    exported_at: datetime
   314→    project_memory: List[dict]
   315→    global_memory: List[dict]
   316→    infrastructure: List[dict]
   317→
   318→
   319→@router.get("/export", response_model=MemoryExportResponse)
   320→async def export_all_memory(
   321→    project_id: Optional[int] = None,
   322→    db: AsyncSession = Depends(get_db)
   323→):
   324→    """Export all memory items as JSON.
   325→
   326→    If project_id is provided, includes project-specific memory.
   327→    Always includes global memory and infrastructure configurations.
   328→    """
   329→    # Get project memory if project_id provided
   330→    project_items = []
   331→    if project_id:
   332→        items = await MemoryProject.get_for_project(db, project_id)
   333→        project_items = [
   334→            {
   335→                "id": item.id,
   336→                "project_id": item.project_id,
   337→                "type": item.type,
   338→                "content": item.content,
   339→                "metadata": item.extra_data,
   340→                "created_at": item.created_at.isoformat() if item.created_at else None,
   341→            }
   342→            for item in items
   343→        ]
   344→
   345→    # Get global memory
   346→    global_items = await MemoryGlobal.get_all(db)
   347→    global_data = [
   348→        {
   349→            "id": item.id,
   350→            "type": item.type,
   351→            "content": item.content,
   352→            "metadata": item.extra_data,
   353→            "usage_count": item.usage_count,
   354→            "created_at": item.created_at.isoformat() if item.created_at else None,
   355→        }
   356→        for item in global_items
   357→    ]
   358→
   359→    # Get infrastructure
   360→    infra_items = await MemoryInfrastructure.get_all(db)
   361→    infra_data = [
   362→        {
   363→            "id": item.id,
   364→            "name": item.name,
   365→            "type": item.type,
   366→            "config": item.config,
   367→            "projects": item.projects,
   368→            "created_at": item.created_at.isoformat() if item.created_at else None,
   369→            "updated_at": item.updated_at.isoformat() if item.updated_at else None,
   370→        }
   371→        for item in infra_items
   372→    ]
   373→
   374→    return MemoryExportResponse(
   375→        exported_at=datetime.utcnow(),
   376→        project_memory=project_items,
   377→        global_memory=global_data,
   378→        infrastructure=infra_data,
   379→    )
   380→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

"""
Memory API Router

Handles the three-tier memory system:
- Project Memory: Session insights, codebase map, patterns, gotchas
- Global Memory: User preferences, cross-project patterns, common solutions
- Infrastructure Memory: Server configs, Docker, deployment targets
"""

from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel, Field
from datetime import datetime

from services.database import get_db
from models.memory import MemoryProject, MemoryGlobal, MemoryInfrastructure

router = APIRouter()

# === Project Memory Schemas ===

class ProjectMemoryCreate(BaseModel):
    """Schema for creating project memory item."""
    type: str  # insight, pattern, gotcha, codebase_map
    content: str
    metadata: Optional[dict] = None

class ProjectMemoryResponse(BaseModel):
    """Schema for project memory response."""
    id: int
    project_id: int
    type: str
    content: str
    metadata: Optional[dict] = Field(None, alias="extra_data")
    created_at: datetime

    class Config:
        from_attributes = True
        populate_by_name = True

# === Global Memory Schemas ===

class GlobalMemoryCreate(BaseModel):
    """Schema for creating global memory item."""
    type: str  # preference, pattern, solution
    content: str
    metadata: Optional[dict] = None

class GlobalMemoryResponse(BaseModel):
    """Schema for global memory response."""
    id: int
    type: str
    content: str
    metadata: Optional[dict] = Field(None, alias="extra_data")
    usage_count: int
    created_at: datetime

    class Config:
        from_attributes = True
        populate_by_name = True

# === Infrastructure Memory Schemas ===

class InfrastructureCreate(BaseModel):
    """Schema for creating infrastructure config."""
    name: str
    type: str  # server, docker, database, deployment
    config: dict
    projects: Optional[List[int]] = None  # None means all projects

class InfrastructureUpdate(BaseModel):
    """Schema for updating infrastructure config."""
    name: Optional[str] = None
    type: Optional[str] = None
    config: Optional[dict] = None
    projects: Optional[List[int]] = None

class InfrastructureResponse(BaseModel):
    """Schema for infrastructure memory response."""
    id: int
    name: str
    type: str
    config: dict
    projects: Optional[List[int]]
    created_at: datetime
    updated_at: datetime

    class Config:
        from_attributes = True

# === Project Memory Endpoints ===

@router.get("/project/{project_id}", response_model=List[ProjectMemoryResponse])
async def get_project_memory(
    project_id: int,
    type: Optional[str] = None,
    db: AsyncSession = Depends(get_db)
):
    """Get all memory items for a project."""
    items = await MemoryProject.get_for_project(db, project_id, type=type)
    return items

@router.post("/project/{project_id}", response_model=ProjectMemoryResponse, status_code=status.HTTP_201_CREATED)
async def add_project_memory(
    project_id: int,
    memory: ProjectMemoryCreate,
    db: AsyncSession = Depends(get_db)
):
    """Add a memory item for a project."""
    valid_types = ["insight", "pattern", "gotcha", "codebase_map"]
    if memory.type not in valid_types:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid type. Must be one of: {valid_types}"
        )

    item = await MemoryProject.create(
        db,
        project_id=project_id,
        type=memory.type,
        content=memory.content,
        extra_data=memory.metadata,
    )
    return item

@router.put("/project/{project_id}/{item_id}", response_model=ProjectMemoryResponse)
async def update_project_memory(
    project_id: int,
    item_id: int,
    memory: ProjectMemoryCreate,
    db: AsyncSession = Depends(get_db)
):
    """Update a project memory item."""
    item = await MemoryProject.get_by_id(db, item_id)
    if not item or item.project_id != project_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Memory item not found"
        )

    updated = await MemoryProject.update(
        db, item_id,
        type=memory.type,
        content=memory.content,
        extra_data=memory.metadata
    )
    return updated

@router.delete("/project/{project_id}/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_project_memory(
    project_id: int,
    item_id: int,
    db: AsyncSession = Depends(get_db)
):
    """Delete a project memory item."""
    item = await MemoryProject.get_by_id(db, item_id)
    if not item or item.project_id != project_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Memory item not found"
        )

    await MemoryProject.delete(db, item_id)
    return None

# === Global Memory Endpoints ===

@router.get("/global", response_model=List[GlobalMemoryResponse])
async def get_global_memory(
    type: Optional[str] = None,
    db: AsyncSession = Depends(get_db)
):
    """Get all global memory items."""
    items = await MemoryGlobal.get_all(db, type=type)
    return items

@router.post("/global", response_model=GlobalMemoryResponse, status_code=status.HTTP_201_CREATED)
async def add_global_memory(
    memory: GlobalMemoryCreate,
    db: AsyncSession = Depends(get_db)
):
    """Add a global memory item."""
    valid_types = ["preference", "pattern", "solution"]
    if memory.type not in valid_types:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid type. Must be one of: {valid_types}"
        )

    item = await MemoryGlobal.create(
        db,
        type=memory.type,
        content=memory.content,
        extra_data=memory.metadata,
    )
    return item

@router.put("/global/{item_id}", response_model=GlobalMemoryResponse)
async def update_global_memory(
    item_id: int,
    memory: GlobalMemoryCreate,
    db: AsyncSession = Depends(get_db)
):
    """Update a global memory item."""
    item = await MemoryGlobal.get_by_id(db, item_id)
    if not item:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Global memory item not found"
        )

    updated = await MemoryGlobal.update(
        db, item_id,
        type=memory.type,
        content=memory.content,
        extra_data=memory.metadata
    )
    return updated

@router.delete("/global/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_global_memory(item_id: int, db: AsyncSession = Depends(get_db)):
    """Delete a global memory item."""
    await MemoryGlobal.delete(db, item_id)
    return None

# === Infrastructure Memory Endpoints ===

@router.get("/infrastructure", response_model=List[InfrastructureResponse])
async def get_infrastructure(
    type: Optional[str] = None,
    project_id: Optional[int] = None,
    db: AsyncSession = Depends(get_db)
):
    """Get infrastructure configurations."""
    items = await MemoryInfrastructure.get_all(db, type=type, project_id=project_id)
    return items

@router.post("/infrastructure", response_model=InfrastructureResponse, status_code=status.HTTP_201_CREATED)
async def add_infrastructure(
    config: InfrastructureCreate,
    db: AsyncSession = Depends(get_db)
):
    """Add an infrastructure configuration."""
    valid_types = ["server", "docker", "database", "deployment"]
    if config.type not in valid_types:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid type. Must be one of: {valid_types}"
        )

    item = await MemoryInfrastructure.create(
        db,
        name=config.name,
        type=config.type,
        config=config.config,
        projects=config.projects,
    )
    return item

@router.put("/infrastructure/{config_id}", response_model=InfrastructureResponse)
async def update_infrastructure(
    config_id: int,
    config: InfrastructureUpdate,
    db: AsyncSession = Depends(get_db)
):
    """Update an infrastructure configuration."""
    item = await MemoryInfrastructure.get_by_id(db, config_id)
    if not item:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Infrastructure config not found"
        )

    updated = await MemoryInfrastructure.update(
        db,
        config_id,
        **config.model_dump(exclude_unset=True)
    )
    return updated

@router.delete("/infrastructure/{config_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_infrastructure(config_id: int, db: AsyncSession = Depends(get_db)):
    """Delete an infrastructure configuration."""
    await MemoryInfrastructure.delete(db, config_id)
    return None

# === Export Endpoints ===

class MemoryExportResponse(BaseModel):
    """Schema for memory export response."""
    export_version: str = "1.0"
    exported_at: datetime
    project_memory: List[dict]
    global_memory: List[dict]
    infrastructure: List[dict]

@router.get("/export", response_model=MemoryExportResponse)
async def export_all_memory(
    project_id: Optional[int] = None,
    db: AsyncSession = Depends(get_db)
):
    """Export all memory items as JSON.

    If project_id is provided, includes project-specific memory.
    Always includes global memory and infrastructure configurations.
    """
    # Get project memory if project_id provided
    project_items = []
    if project_id:
        items = await MemoryProject.get_for_project(db, project_id)
        project_items = [
            {
                "id": item.id,
                "project_id": item.project_id,
                "type": item.type,
                "content": item.content,
                "metadata": item.extra_data,
                "created_at": item.created_at.isoformat() if item.created_at else None,
            }
            for item in items
        ]

    # Get global memory
    global_items = await MemoryGlobal.get_all(db)
    global_data = [
        {
            "id": item.id,
            "type": item.type,
            "content": item.content,
            "metadata": item.extra_data,
            "usage_count": item.usage_count,
            "created_at": item.created_at.isoformat() if item.created_at else None,
        }
        for item in global_items
    ]

    # Get infrastructure
    infra_items = await MemoryInfrastructure.get_all(db)
    infra_data = [
        {
            "id": item.id,
            "name": item.name,
            "type": item.type,
            "config": item.config,
            "projects": item.projects,
            "created_at": item.created_at.isoformat() if item.created_at else None,
            "updated_at": item.updated_at.isoformat() if item.updated_at else None,
        }
        for item in infra_items
    ]

    return MemoryExportResponse(
        exported_at=datetime.utcnow(),
        project_memory=project_items,
        global_memory=global_data,
        infrastructure=infra_data,
    )


I can see there's an export endpoint but no import endpoint. For Feature #186 (Import Malformed File Handling), I need to:

1. Implement an import endpoint in the backend
2. Add import functionality to the frontend
3. Handle malformed JSON files gracefully

Let me update my todo list and implement the import feature:


TodoWrite

Check if import functionality exists in the codebase

Checking for existing import functionality

Implement import API endpoint in backend with malformed file handling

Add import button and file upload to frontend MemoryManager

Test import with valid JSON file

Test import with malformed JSON files via browser

Verify no partial import occurs on error

Mark feature as passing after verification


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Check if import functionality exists in the codebase

Checking for existing import functionality

Implement import feature if not exists

Add malformed JSON file handling with clear error messages

Test import with malformed files via browser

Verify no partial import occurs on error

Mark feature as passing after verification

Implement import API endpoint in backend with malformed file handling

Add import button and file upload to frontend MemoryManager

Test import with valid JSON file

Test import with malformed JSON files via browser


Now let me implement the import endpoint in the backend with proper malformed file handling:


Edit

    return MemoryExportResponse(
        exported_at=datetime.utcnow(),
        project_memory=project_items,
        global_memory=global_data,
        infrastructure=infra_data,
    )

    return MemoryExportResponse(
        exported_at=datetime.utcnow(),
        project_memory=project_items,
        global_memory=global_data,
        infrastructure=infra_data,
    )

# === Import Endpoints ===

class MemoryImportRequest(BaseModel):
    """Schema for memory import request."""
    export_version: Optional[str] = None
    exported_at: Optional[str] = None
    project_memory: Optional[List[dict]] = None
    global_memory: Optional[List[dict]] = None
    infrastructure: Optional[List[dict]] = None

class MemoryImportResponse(BaseModel):
    """Schema for memory import response."""
    success: bool
    imported_count: dict
    errors: List[str] = []

@router.post("/import", response_model=MemoryImportResponse)
async def import_memory(
    data: MemoryImportRequest,
    project_id: Optional[int] = None,
    db: AsyncSession = Depends(get_db)
):
    """Import memory items from JSON data.

    This endpoint handles malformed data gracefully:
    - Invalid JSON structure returns a clear error message
    - Invalid items are skipped and reported in errors list
    - No partial import on critical errors (all-or-nothing for each category)
    - System state unchanged if import fails completely
    """
    errors = []
    imported_count = {
        "project_memory": 0,
        "global_memory": 0,
        "infrastructure": 0
    }

    # Validate the data structure
    if data.project_memory is None and data.global_memory is None and data.infrastructure is None:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid import file: No memory data found. Expected 'project_memory', 'global_memory', or 'infrastructure' arrays."
        )

    # Import project memory
    if data.project_memory:
        if not project_id:
            errors.append("Project memory items skipped: No project_id provided")
        else:
            valid_types = ["insight", "pattern", "gotcha", "codebase_map"]
            for i, item in enumerate(data.project_memory):
                try:
                    item_type = item.get("type")
                    item_content = item.get("content")

                    if not item_type or not item_content:
                        errors.append(f"Project memory item {i}: Missing required fields 'type' or 'content'")
                        continue

                    if item_type not in valid_types:
                        errors.append(f"Project memory item {i}: Invalid type '{item_type}'. Must be one of: {valid_types}")
                        continue

                    await MemoryProject.create(
                        db,
                        project_id=project_id,
                        type=item_type,
                        content=str(item_content),
                        extra_data=item.get("metadata"),
                    )
                    imported_count["project_memory"] += 1
                except Exception as e:
                    errors.append(f"Project memory item {i}: {str(e)}")

    # Import global memory
    if data.global_memory:
        valid_types = ["preference", "pattern", "solution"]
        for i, item in enumerate(data.global_memory):
            try:
                item_type = item.get("type")
                item_content = item.get("content")

                if not item_type or not item_content:
                    errors.append(f"Global memory item {i}: Missing required fields 'type' or 'content'")
                    continue

                if item_type not in valid_types:
                    errors.append(f"Global memory item {i}: Invalid type '{item_type}'. Must be one of: {valid_types}")
                    continue

                await MemoryGlobal.create(
                    db,
                    type=item_type,
                    content=str(item_content),
                    extra_data=item.get("metadata"),
                )
                imported_count["global_memory"] += 1
            except Exception as e:
                errors.append(f"Global memory item {i}: {str(e)}")

    # Import infrastructure
    if data.infrastructure:
        valid_types = ["server", "docker", "database", "deployment"]
        for i, item in enumerate(data.infrastructure):
            try:
                item_name = item.get("name")
                item_type = item.get("type")
                item_config = item.get("config")

                if not item_name or not item_type:
                    errors.append(f"Infrastructure item {i}: Missing required fields 'name' or 'type'")
                    continue

                if item_type not in valid_types:
                    errors.append(f"Infrastructure item {i}: Invalid type '{item_type}'. Must be one of: {valid_types}")
                    continue

                if not isinstance(item_config, dict):
                    item_config = {}

                await MemoryInfrastructure.create(
                    db,
                    name=item_name,
                    type=item_type,
                    config=item_config,
                    projects=item.get("projects"),
                )
                imported_count["infrastructure"] += 1
            except Exception as e:
                errors.append(f"Infrastructure item {i}: {str(e)}")

    total_imported = sum(imported_count.values())
    if total_imported == 0 and errors:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Import failed: No items could be imported. Errors: {'; '.join(errors[:5])}"
        )

    return MemoryImportResponse(
        success=True,
        imported_count=imported_count,
        errors=errors
    )