Menu

Change language

GitHub repo

Agent Memory Inspector

blockai

Autonomous AI agent memory inspector featuring short-term context buffer utilization, long-term episodic vector memories with confidence scoring, and Neo4j-style entity relationship knowledge graph.

Installation

$npx uipkge-ng@latest add agent-memory-inspector
Manifest:https://uipkge.dev/r/angular/agent-memory-inspector.jsonInstalls to:src/app/components/blocks/agent-memory-inspector/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

FactForm
interface FactForm {
  content: string
  category: MemoryCategory
  confidence: number
}
EpisodicMemory
interface EpisodicMemory {
  id: string
  content: string
  category: MemoryCategory
  confidence: number
  lastRecalled: string
  recallsCount: number
  sourceTurn: string
  tags: string[]
  isPinned: boolean
}
MessageTurn
interface MessageTurn {
  id: string
  turnNumber: number
  role: 'user' | 'assistant'
  authorName: string
  avatarText: string
  timestamp: string
  importanceScore: number
  importanceLevel: 'Critical' | 'High' | 'Standard' | 'Action Item'
  tokens: number
  content: string
  extractedFacts: string[]
  embeddingHash: string
}
EntityNode
interface EntityNode {
  id: string
  label: string
  entityType: 'Person' | 'Repository' | 'CSS Engine' | 'Design System' | 'Framework' | 'Standard'
  description: string
  degree: number
  status: 'active' | 'synced' | 'cached'
  embeddingId: string
}
GraphTriplet
interface GraphTriplet {
  id: string
  subject: string
  subjectType: string
  predicate: 'MAINTAINS' | 'USES' | 'CONFIGURES' | 'IMPLEMENTS' | 'MIRRORS' | 'ENFORCES'
  object: string
  objectType: string
  confidence: number
  weight: string
  lastTraversed: string
}

Files installed (6)

  • src/app/components/blocks/agent-memory-inspector/agent-memory-inspector.component.ts27.8 kB
    import { Component, DestroyRef, Input, OnInit, computed, inject, signal, ChangeDetectionStrategy } from '@angular/core'
    import {
      Activity,
      AlertCircle,
      Brain,
      Check,
      Clock,
      Cpu,
      Database,
      LucideAngularModule,
      Network,
      Pencil,
      Plus,
      RefreshCw,
      Search,
      Star,
      Trash2,
      X,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent, type BadgeVariant } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardFooterComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiProgressComponent } from '@/ui/progress/progress.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    import {
      UiTabsComponent,
      UiTabsContentComponent,
      UiTabsListComponent,
      UiTabsTriggerComponent,
    } from '@/ui/tabs/tabs.component'
    import { UiAgentContextBufferComponent } from './agent-context-buffer.component'
    import { UiAgentEntityGraphComponent } from './agent-entity-graph.component'
    import {
      INITIAL_ENTITY_NODES,
      INITIAL_GRAPH_TRIPLETS,
      INITIAL_MEMORY_FACTS,
      INITIAL_MESSAGE_TURNS,
    } from './agent-memory-data'
    import type { EntityNode, EpisodicMemory, GraphTriplet, MemoryCategory, MessageTurn } from './agent-memory-types'
    
    export type { EpisodicMemory, MessageTurn, EntityNode, GraphTriplet, MemoryCategory } from './agent-memory-types'
    
    type CategoryFilter = 'all' | MemoryCategory
    
    interface FactForm {
      content: string
      category: MemoryCategory
      confidence: number
    }
    
    const EMPTY_FORM: FactForm = { content: '', category: 'User Preference', confidence: 0.95 }
    
    const CATEGORY_FILTERS: { value: CategoryFilter; label: string }[] = [
      { value: 'User Preference', label: 'Preference' },
      { value: 'Architecture Rule', label: 'Architecture' },
      { value: 'System Constraint', label: 'Constraint' },
    ]
    
    /**
     * AgentMemoryInspector -- the React block, 1:1: an agent header (wipe / add actions), an
     * inline add/edit fact form, and three tabs: the short-term context buffer, the long-term
     * episodic memory table (search, category filter, pin, edit, delete) and the entity graph.
     * `initialMemories` seeds the episodic facts; edits after that live in the component.
     */
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-agent-memory-inspector, [ui-agent-memory-inspector]',
      standalone: true,
      host: { class: 'contents' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardFooterComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiProgressComponent,
        UiTableComponent,
        UiTableHeaderComponent,
        UiTableBodyComponent,
        UiTableRowComponent,
        UiTableHeadComponent,
        UiTableCellComponent,
        UiTabsComponent,
        UiTabsListComponent,
        UiTabsTriggerComponent,
        UiTabsContentComponent,
        UiAgentContextBufferComponent,
        UiAgentEntityGraphComponent,
      ],
      template: `
        <div [class]="rootClass">
          <!-- Header Block -->
          <div ui-card class="border-border shadow-xs">
            <div ui-card-header class="p-6 pb-4">
              <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
                <!-- Left: Agent Identity & Engine Details -->
                <div class="flex items-start gap-4">
                  <div class="relative">
                    <span ui-avatar class="border-border bg-muted/60 h-12 w-12 rounded-xl border p-1">
                      <span ui-avatar-fallback class="bg-primary/10 text-primary rounded-lg font-mono text-sm font-bold">
                        <lucide-icon [img]="Brain" class="text-primary h-6 w-6" />
                      </span>
                    </span>
                    <span
                      class="bg-background absolute -right-0.5 -bottom-0.5 flex h-3.5 w-3.5 items-center justify-center rounded-full"
                    >
                      <span class="bg-success ring-success/20 h-2 w-2 rounded-full ring-2"></span>
                    </span>
                  </div>
    
                  <div class="space-y-1">
                    <div class="flex flex-wrap items-center gap-2">
                      <h2 class="text-foreground text-lg font-semibold tracking-tight">
                        Senior Engineering Co-Pilot Agent
                      </h2>
                      <span ui-badge variant="outline" class="gap-1 px-2 py-0.5 text-xs font-medium">
                        <lucide-icon [img]="Cpu" class="text-warning h-3 w-3" />
                        Autonomous Sync
                      </span>
                    </div>
    
                    <!-- makes the svg itself the flex item (as in React), so it can
                         shrink the same way when the label wraps on narrow screens. -->
                    <div class="text-muted-foreground flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
                      <span class="text-foreground flex items-center gap-1.5 font-medium">
                        <lucide-icon [img]="Database" class="text-primary h-3.5 w-3.5" />
                        Mem0 Vector + Neo4j Entity Graph
                      </span>
                      <span class="flex items-center gap-1">
                        <lucide-icon [img]="Network" class="h-3.5 w-3.5" />
                        142 Episodic Memories · 28 Entity Nodes
                      </span>
                      <span class="text-muted-foreground flex items-center gap-1 font-mono">
                        <lucide-icon [img]="Activity" class="text-success h-3 w-3" />
                        Recall: 12ms
                      </span>
                    </div>
                  </div>
                </div>
    
                <!-- Right: Actions -->
                <div class="flex flex-wrap items-center gap-2">
                  <button
                    ui-button
                    variant="ghost"
                    size="sm"
                    class="text-muted-foreground hover:text-destructive h-9 gap-1.5 text-xs font-medium"
                    (click)="wipeWorkingMemory()"
                  >
                    <lucide-icon [img]="Trash2" class="h-3.5 w-3.5" />
                    Wipe Working Memory
                  </button>
    
                  <button ui-button size="sm" class="h-9 gap-1.5 text-xs font-medium shadow-xs" (click)="startAddFact()">
                    <lucide-icon [img]="Plus" class="h-3.5 w-3.5" />
                    Add Memory Fact
                  </button>
                </div>
              </div>
            </div>
    
            <!-- Wipe State Feedback Banner -->
            @if (wipeToastVisible()) {
              <div class="border-destructive/20 bg-destructive/5 border-t px-6 py-2.5">
                <div class="text-destructive flex items-center justify-between gap-2 text-xs">
                  <div class="flex items-center gap-2">
                    <lucide-icon [img]="AlertCircle" class="h-4 w-4 shrink-0" />
                    <span>Working context buffer cache cleared. Persistent episodic facts remain intact.</span>
                  </div>
                  <button
                    ui-button
                    variant="outline"
                    size="sm"
                    class="border-destructive/30 text-destructive hover:bg-destructive/10 h-7 text-xs"
                    (click)="restoreWorkingMemory()"
                  >
                    <!-- [class] (not class=) feeds only the svg, so mr-1 is not applied twice (host + svg). -->
                    <lucide-icon [img]="RefreshCw" [class]="'mr-1 h-3 w-3'" />
                    Restore
                  </button>
                </div>
              </div>
            }
          </div>
    
          <!-- Inline Add/Edit Memory Fact Modal / Drawer -->
          @if (isAddingFact()) {
            <div ui-card class="border-primary/30 bg-card ring-primary/20 shadow-sm ring-1">
              <div ui-card-header class="p-4 pb-2">
                <div class="flex items-center justify-between">
                  <div class="flex items-center gap-2">
                    <lucide-icon [img]="Brain" class="text-primary h-4 w-4" />
                    <h3 ui-card-title class="text-sm font-semibold">
                      {{ editingFactId() ? 'Edit Memory Fact' : 'Add Episodic Memory Fact' }}
                    </h3>
                  </div>
                  <button
                    ui-button
                    variant="ghost"
                    size="sm"
                    class="h-7 w-7 p-0"
                    aria-label="Cancel adding fact"
                    (click)="cancelAddFact()"
                  >
                    <lucide-icon [img]="X" class="h-4 w-4" />
                  </button>
                </div>
                <p ui-card-description class="text-xs">
                  Inject a verified truth into the persistent Mem0 vector embedding space.
                </p>
              </div>
    
              <div ui-card-content class="space-y-3 p-4 pt-2">
                <div class="space-y-1.5">
                  <label class="text-foreground text-xs font-medium">Fact Content</label>
                  <textarea
                    [value]="form().content"
                    (input)="onContentInput($event)"
                    placeholder="e.g. User enforces strict 120-character printWidth in Prettier..."
                    rows="2"
                    class="border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-xs focus-visible:ring-1 focus-visible:outline-none"
                  ></textarea>
                </div>
    
                <div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
                  <div class="space-y-1.5">
                    <label class="text-foreground text-xs font-medium">Memory Category</label>
                    <select
                      [value]="form().category"
                      (change)="onCategoryChange($event)"
                      class="border-input bg-background text-foreground focus-visible:ring-ring w-full rounded-md border px-2.5 py-1.5 text-xs focus-visible:ring-1 focus-visible:outline-none"
                    >
                      <option value="User Preference">User Preference</option>
                      <option value="Architecture Rule">Architecture Rule</option>
                      <option value="System Constraint">System Constraint</option>
                    </select>
                  </div>
    
                  <div class="space-y-1.5">
                    <label class="text-foreground text-xs font-medium">Confidence Score (0.0 - 1.0)</label>
                    <input
                      type="number"
                      step="0.01"
                      min="0"
                      max="1"
                      [value]="form().confidence"
                      (input)="onConfidenceInput($event)"
                      class="border-input bg-background text-foreground focus-visible:ring-ring w-full rounded-md border px-3 py-1.5 font-mono text-xs focus-visible:ring-1 focus-visible:outline-none"
                    />
                  </div>
                </div>
              </div>
    
              <div ui-card-footer class="border-border flex justify-end gap-2 border-t p-3">
                <button
                  ui-button
                  aria-label="Cancel adding fact"
                  variant="ghost"
                  size="sm"
                  class="h-8 text-xs"
                  (click)="cancelAddFact()"
                >
                  Cancel
                </button>
                <button ui-button size="sm" class="h-8 text-xs font-medium" (click)="saveNewFact()">
                  <lucide-icon [img]="Check" [class]="'mr-1 h-3.5 w-3.5'" />
                  {{ editingFactId() ? 'Save Changes' : 'Store Fact' }}
                </button>
              </div>
            </div>
          }
    
          <!-- Main Tabs Section -->
          <div ui-tabs [value]="activeTab()" (valueChange)="activeTab.set($event)" class="w-full space-y-4">
            <div ui-tabs-list class="border-border bg-muted/40 grid h-10 w-full grid-cols-3 rounded-lg border p-1">
              <button ui-tabs-trigger value="context" class="gap-2 text-xs font-medium">
                <lucide-icon [img]="Cpu" class="h-3.5 w-3.5" />
                <span>Short-Term Context Buffer</span>
                <span ui-badge variant="secondary" class="ml-1 px-1.5 py-0 font-mono text-xs font-normal">
                  {{ isWiped() ? '0 turns' : '4 turns' }}
                </span>
              </button>
    
              <button ui-tabs-trigger value="episodic" class="gap-2 text-xs font-medium">
                <lucide-icon [img]="Database" class="h-3.5 w-3.5" />
                <span>Long-Term Episodic Memory</span>
                <span ui-badge variant="secondary" class="ml-1 px-1.5 py-0 font-mono text-xs font-normal">
                  {{ memoryFacts().length }} Facts
                </span>
              </button>
    
              <button ui-tabs-trigger value="graph" class="gap-2 text-xs font-medium">
                <lucide-icon [img]="Network" class="h-3.5 w-3.5" />
                <span>Entity Knowledge Graph</span>
                <span ui-badge variant="secondary" class="ml-1 px-1.5 py-0 font-mono text-xs font-normal">
                  {{ entityNodes.length }} Nodes
                </span>
              </button>
            </div>
    
            <!-- TAB 1: SHORT-TERM CONTEXT BUFFER -->
            <div ui-tabs-content value="context" class="space-y-4 outline-none">
              <ui-agent-context-buffer
                [isWiped]="isWiped()"
                [turns]="messageTurns"
                [selectedTurnId]="selectedTurnId()"
                (selectTurn)="selectedTurnId.set($event)"
                (restore)="restoreWorkingMemory()"
              />
            </div>
    
            <!-- TAB 2: LONG-TERM EPISODIC MEMORY TABLE -->
            <div ui-tabs-content value="episodic" class="space-y-4 outline-none">
              <div ui-card class="border-border shadow-xs">
                <!-- Filter and Search Header -->
                <div ui-card-header class="p-5 pb-3">
                  <div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
                    <div>
                      <h3 ui-card-title class="text-sm font-semibold">Persistent Episodic Facts</h3>
                      <p ui-card-description class="text-xs">
                        Distilled knowledge facts retrieved via Mem0 vector cosine similarity search.
                      </p>
                    </div>
    
                    <!-- Search & Category Filters -->
                    <div class="flex flex-wrap items-center gap-2">
                      <div class="relative w-full sm:w-64">
                        <!-- [class] (not class=) keeps absolute/top/left on the svg only, not doubled on the host. -->
                        <lucide-icon
                          [img]="Search"
                          [class]="'text-muted-foreground absolute top-2.5 left-2.5 h-3.5 w-3.5'"
                        />
                        <input
                          type="text"
                          [value]="searchQuery()"
                          (input)="onSearchInput($event)"
                          placeholder="Search memory facts or tags..."
                          class="border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:ring-ring h-8.5 w-full rounded-md border pr-3 pl-8 text-xs focus-visible:ring-1 focus-visible:outline-none"
                        />
                      </div>
    
                      <div class="border-border bg-muted/40 flex items-center gap-1 rounded-lg border p-0.5">
                        <button type="button" [class]="filterClass('all')" (click)="selectedCategory.set('all')">
                          All ({{ memoryFacts().length }})
                        </button>
                        @for (f of categoryFilters; track f.value) {
                          <button type="button" [class]="filterClass(f.value)" (click)="selectedCategory.set(f.value)">
                            {{ f.label }}
                          </button>
                        }
                      </div>
                    </div>
                  </div>
                </div>
    
                <!-- Memory Table -->
                <div ui-card-content class="p-0">
                  <div class="overflow-x-auto">
                    <div ui-table>
                      <thead ui-table-header>
                        <tr ui-table-row class="hover:bg-transparent">
                          <th ui-table-head class="w-8"></th>
                          <th ui-table-head class="min-w-[280px]">Memory Fact Content</th>
                          <th ui-table-head class="w-[160px]">Category</th>
                          <th ui-table-head class="w-[130px]">Confidence</th>
                          <th ui-table-head class="w-[140px]">Last Recalled</th>
                          <th ui-table-head class="w-[100px] text-right">Actions</th>
                        </tr>
                      </thead>
    
                      <tbody ui-table-body>
                        @for (fact of filteredMemories(); track fact.id) {
                          <tr ui-table-row class="group hover:bg-muted/40">
                            <!-- Pin / Star Icon -->
                            <td ui-table-cell class="py-3 pr-1 pl-4">
                              <button
                                type="button"
                                class="text-muted-foreground hover:text-warning focus-visible:outline-none"
                                [title]="fact.isPinned ? 'Unpin fact' : 'Pin fact'"
                                (click)="togglePinFact(fact.id)"
                              >
                                <lucide-icon [img]="Star" [class]="starClass(fact)" />
                              </button>
                            </td>
    
                            <!-- Content & Source Details -->
                            <td ui-table-cell class="py-3">
                              <div class="space-y-1">
                                <div class="text-foreground text-xs leading-relaxed font-medium">{{ fact.content }}</div>
                                <div class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-xs">
                                  <span class="text-muted-foreground font-mono">{{ fact.sourceTurn }}</span>
                                  <span>·</span>
                                  <div class="flex items-center gap-1">
                                    @for (tag of fact.tags; track tag) {
                                      <span class="bg-muted py-0.2 text-muted-foreground rounded px-1.5 font-mono text-xs">
                                        #{{ tag }}
                                      </span>
                                    }
                                  </div>
                                </div>
                              </div>
                            </td>
    
                            <!-- Category Badge -->
                            <td ui-table-cell class="py-3">
                              <span ui-badge [variant]="getCategoryVariant(fact.category)" class="text-xs font-normal">
                                {{ fact.category }}
                              </span>
                            </td>
    
                            <!-- Confidence Meter -->
                            <td ui-table-cell class="py-3">
                              <div class="space-y-1">
                                <div class="flex items-center justify-between font-mono text-xs">
                                  <span class="text-foreground font-semibold"
                                    >{{ confidencePercent(fact.confidence) }}%</span
                                  >
                                  <span class="text-muted-foreground">({{ fact.confidence }})</span>
                                </div>
                                <div ui-progress [value]="fact.confidence * 100" class="bg-muted h-1.5 w-20"></div>
                              </div>
                            </td>
    
                            <!-- Last Recalled & Recalls Count -->
                            <td ui-table-cell class="py-3">
                              <div class="space-y-0.5 text-xs">
                                <div class="text-foreground flex items-center gap-1 font-medium">
                                  <lucide-icon [img]="Clock" class="text-muted-foreground h-3 w-3" />
                                  <span>{{ fact.lastRecalled }}</span>
                                </div>
                                <div class="text-muted-foreground font-mono text-xs">Recalled {{ fact.recallsCount }}×</div>
                              </div>
                            </td>
    
                            <!-- Actions -->
                            <td ui-table-cell class="py-3 pr-4 text-right">
                              <div class="flex items-center justify-end gap-1">
                                <button
                                  ui-button
                                  variant="ghost"
                                  size="sm"
                                  class="text-muted-foreground hover:text-foreground h-7 w-7 p-0"
                                  title="Edit fact"
                                  (click)="editFact(fact)"
                                >
                                  <lucide-icon [img]="Pencil" class="h-3.5 w-3.5" />
                                </button>
    
                                <button
                                  ui-button
                                  variant="ghost"
                                  size="sm"
                                  class="text-muted-foreground hover:text-destructive h-7 w-7 p-0"
                                  title="Delete fact"
                                  (click)="deleteFact(fact.id)"
                                >
                                  <lucide-icon [img]="Trash2" class="h-3.5 w-3.5" />
                                </button>
                              </div>
                            </td>
                          </tr>
                        } @empty {
                          <tr ui-table-row>
                            <td ui-table-cell colspan="6" class="text-muted-foreground h-28 text-center text-xs">
                              No matching memory facts found for the current query.
                            </td>
                          </tr>
                        }
                      </tbody>
                    </div>
                  </div>
                </div>
              </div>
            </div>
    
            <!-- TAB 3: SEMANTIC ENTITY GRAPH -->
            <div ui-tabs-content value="graph" class="space-y-4 outline-none">
              <ui-agent-entity-graph
                [triplets]="graphTriplets"
                [nodes]="entityNodes"
                [selectedNodeId]="selectedNodeId()"
                (selectNode)="selectedNodeId.set($event)"
              />
            </div>
          </div>
        </div>
      `,
    })
    export class UiAgentMemoryInspectorComponent implements OnInit {
      protected readonly Activity = Activity
      protected readonly AlertCircle = AlertCircle
      protected readonly Brain = Brain
      protected readonly Check = Check
      protected readonly Clock = Clock
      protected readonly Cpu = Cpu
      protected readonly Database = Database
      protected readonly Network = Network
      protected readonly Pencil = Pencil
      protected readonly Plus = Plus
      protected readonly RefreshCw = RefreshCw
      protected readonly Search = Search
      protected readonly Star = Star
      protected readonly Trash2 = Trash2
      protected readonly X = X
      protected readonly categoryFilters = CATEGORY_FILTERS
    
      @Input('class') className?: string
      /** Seeds the episodic facts (React `initialMemories`); later changes to the input are not re-applied. */
      @Input() initialMemories: EpisodicMemory[] = INITIAL_MEMORY_FACTS
    
      readonly memoryFacts = signal<EpisodicMemory[]>(INITIAL_MEMORY_FACTS)
      readonly messageTurns: MessageTurn[] = INITIAL_MESSAGE_TURNS
      readonly entityNodes: EntityNode[] = INITIAL_ENTITY_NODES
      readonly graphTriplets: GraphTriplet[] = INITIAL_GRAPH_TRIPLETS
      readonly activeTab = signal('context')
      readonly selectedCategory = signal<CategoryFilter>('all')
      readonly searchQuery = signal('')
      readonly selectedTurnId = signal('turn-4')
      readonly selectedNodeId = signal('node-2')
      readonly isWiped = signal(false)
      readonly wipeToastVisible = signal(false)
      readonly isAddingFact = signal(false)
      readonly editingFactId = signal<string | null>(null)
      readonly form = signal<FactForm>({ ...EMPTY_FORM })
    
      private wipeTimer?: ReturnType<typeof setTimeout>
    
      readonly filteredMemories = computed(() => {
        const category = this.selectedCategory()
        const query = this.searchQuery()
        return this.memoryFacts().filter((fact) => {
          const matchesCategory = category === 'all' || fact.category === category
          const matchesSearch =
            query.trim() === '' ||
            fact.content.toLowerCase().includes(query.toLowerCase()) ||
            fact.tags.some((t) => t.toLowerCase().includes(query.toLowerCase()))
          return matchesCategory && matchesSearch
        })
      })
    
      constructor() {
        inject(DestroyRef).onDestroy(() => clearTimeout(this.wipeTimer))
      }
    
      ngOnInit(): void {
        this.memoryFacts.set(this.initialMemories)
      }
    
      get rootClass(): string {
        return cn('w-full space-y-6 font-sans antialiased', this.className)
      }
    
      wipeWorkingMemory(): void {
        this.isWiped.set(true)
        this.wipeToastVisible.set(true)
        clearTimeout(this.wipeTimer)
        this.wipeTimer = setTimeout(() => this.wipeToastVisible.set(false), 4000)
      }
    
      restoreWorkingMemory(): void {
        this.isWiped.set(false)
        this.wipeToastVisible.set(false)
      }
    
      deleteFact(id: string): void {
        this.memoryFacts.update((prev) => prev.filter((f) => f.id !== id))
      }
    
      togglePinFact(id: string): void {
        this.memoryFacts.update((prev) => prev.map((f) => (f.id === id ? { ...f, isPinned: !f.isPinned } : f)))
      }
    
      startAddFact(): void {
        this.isAddingFact.set(true)
        this.editingFactId.set(null)
        this.form.set({ ...EMPTY_FORM })
      }
    
      editFact(fact: EpisodicMemory): void {
        this.editingFactId.set(fact.id)
        this.form.set({ content: fact.content, category: fact.category, confidence: fact.confidence })
        this.isAddingFact.set(true)
      }
    
      saveNewFact(): void {
        const form = this.form()
        if (!form.content.trim()) return
    
        const editingId = this.editingFactId()
        if (editingId) {
          this.memoryFacts.update((prev) =>
            prev.map((f) =>
              f.id === editingId
                ? {
                    ...f,
                    content: form.content,
                    category: form.category,
                    confidence: Number(form.confidence),
                    lastRecalled: 'Just now',
                  }
                : f,
            ),
          )
        } else {
          const newId = `fact-${Date.now()}`
          this.memoryFacts.update((prev) => [
            {
              id: newId,
              content: form.content,
              category: form.category,
              confidence: Number(form.confidence),
              lastRecalled: 'Just now',
              recallsCount: 1,
              sourceTurn: 'Manual Injection',
              tags: [form.category.toLowerCase().replace(/\s+/g, '-')],
              isPinned: false,
            },
            ...prev,
          ])
        }
    
        this.isAddingFact.set(false)
        this.editingFactId.set(null)
      }
    
      cancelAddFact(): void {
        this.isAddingFact.set(false)
        this.editingFactId.set(null)
      }
    
      onContentInput(event: Event): void {
        const content = (event.target as HTMLTextAreaElement).value
        this.form.update((f) => ({ ...f, content }))
      }
    
      onCategoryChange(event: Event): void {
        const category = (event.target as HTMLSelectElement).value as MemoryCategory
        this.form.update((f) => ({ ...f, category }))
      }
    
      onConfidenceInput(event: Event): void {
        const confidence = parseFloat((event.target as HTMLInputElement).value) || 0
        this.form.update((f) => ({ ...f, confidence }))
      }
    
      onSearchInput(event: Event): void {
        this.searchQuery.set((event.target as HTMLInputElement).value)
      }
    
      getCategoryVariant(category: MemoryCategory): BadgeVariant {
        switch (category) {
          case 'User Preference':
            return 'default'
          case 'Architecture Rule':
            return 'secondary'
          case 'System Constraint':
            return 'outline'
        }
      }
    
      filterClass(value: CategoryFilter): string {
        return cn(
          'rounded px-2.5 py-1 text-xs font-medium transition-colors',
          this.selectedCategory() === value
            ? 'bg-background text-foreground shadow-xs'
            : 'text-muted-foreground hover:text-foreground',
        )
      }
    
      starClass(fact: EpisodicMemory): string {
        return cn('h-4 w-4', fact.isPinned ? 'fill-warning text-warning' : 'text-muted-foreground/40')
      }
    
      confidencePercent(confidence: number): string {
        return (confidence * 100).toFixed(0)
      }
    }
    
  • src/app/components/blocks/agent-memory-inspector/agent-context-buffer.component.ts10.6 kB
  • src/app/components/blocks/agent-memory-inspector/agent-entity-graph.component.ts10.5 kB
  • src/app/components/blocks/agent-memory-inspector/agent-memory-types.ts1.2 kB
  • src/app/components/blocks/agent-memory-inspector/agent-memory-data.ts7.2 kB
  • src/app/components/blocks/agent-memory-inspector/index.ts0.5 kB

Raw manifest:https://uipkge.dev/r/angular/agent-memory-inspector.json