Agent Memory Inspector
blockaiAutonomous 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
$pnpm dlx uipkge-ng@latest add agent-memory-inspector$npx uipkge-ng@latest add agent-memory-inspector$yarn dlx uipkge-ng@latest add agent-memory-inspector$bunx uipkge-ng@latest add agent-memory-inspectorManifest:
https://uipkge.dev/r/angular/agent-memory-inspector.jsonInstalls to:src/app/components/blocks/agent-memory-inspector/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
FactForminterface FactForm {
content: string
category: MemoryCategory
confidence: number
}EpisodicMemoryinterface EpisodicMemory {
id: string
content: string
category: MemoryCategory
confidence: number
lastRecalled: string
recallsCount: number
sourceTurn: string
tags: string[]
isPinned: boolean
}MessageTurninterface 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
}EntityNodeinterface EntityNode {
id: string
label: string
entityType: 'Person' | 'Repository' | 'CSS Engine' | 'Design System' | 'Framework' | 'Standard'
description: string
degree: number
status: 'active' | 'synced' | 'cached'
embeddingId: string
}GraphTripletinterface 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
import { Component, EventEmitter, Input, Output, booleanAttribute, ChangeDetectionStrategy } from '@angular/core' import { AlertCircle, CornerDownRight, LucideAngularModule } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiProgressComponent } from '@/ui/progress/progress.component' import type { MessageTurn } from './agent-memory-types' /** * Short-term context buffer tab of the agent memory inspector (React `AgentContextBuffer`): * token utilisation overview plus the sliding window of recent turns. Selecting a turn emits * `selectTurn`; the "Restore Message Turns" button of the wiped state emits `restore`. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-agent-context-buffer, [ui-agent-context-buffer]', standalone: true, host: { class: 'contents' }, imports: [ LucideAngularModule, UiAvatarComponent, UiAvatarFallbackComponent, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiProgressComponent, ], template: ` <div class="space-y-4"> <!-- Token Utilization Overview Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="p-5 pb-3"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-0.5"> <div class="flex items-center gap-2"> <h3 ui-card-title class="text-sm font-semibold">Session Context Window Utilization</h3> <span ui-badge variant="outline" class="text-muted-foreground font-mono text-xs"> 128k Model Window </span> </div> <p ui-card-description class="text-xs"> Active in-memory token allocation for the current autonomous execution session. </p> </div> <div class="flex items-baseline gap-1.5 font-mono"> <span class="text-foreground text-base font-bold">{{ isWiped ? '0' : '6,420' }}</span> <span class="text-muted-foreground text-xs">/ 128,000 tokens</span> <span ui-badge variant="secondary" class="ml-1 font-mono text-xs font-semibold"> {{ isWiped ? '0.0%' : '5.0%' }} </span> </div> </div> </div> <div ui-card-content class="space-y-4 p-5 pt-0"> <!-- Progress Bar --> <div ui-progress [value]="isWiped ? 0 : 5.0" class="bg-muted h-2 w-full"></div> <!-- Detailed Token Allocation Breakdown Grid --> <div class="grid grid-cols-2 gap-3 sm:grid-cols-4"> <div class="border-border/80 bg-card/60 rounded-lg border p-3"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>System Prompt</span> <span class="text-foreground font-mono">{{ isWiped ? '0' : '2,140' }}</span> </div> <div class="text-foreground mt-1 text-xs font-medium">33.3% allocated</div> </div> <div class="border-border/80 bg-card/60 rounded-lg border p-3"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Episodic Injections</span> <span class="text-foreground font-mono">{{ isWiped ? '0' : '1,850' }}</span> </div> <div class="text-foreground mt-1 text-xs font-medium">28.8% allocated</div> </div> <div class="border-border/80 bg-card/60 rounded-lg border p-3"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Message History</span> <span class="text-foreground font-mono">{{ isWiped ? '0' : '2,430' }}</span> </div> <div class="text-foreground mt-1 text-xs font-medium">37.9% allocated</div> </div> <div class="border-border/80 bg-card/60 rounded-lg border p-3"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Available Headroom</span> <span class="text-success font-mono">{{ isWiped ? '128,000' : '121,580' }}</span> </div> <div class="text-success mt-1 text-xs font-medium">{{ isWiped ? '100% free' : '95.0% free' }}</div> </div> </div> </div> </div> <!-- Sliding Message Window Buffer (Recent Conversation Turns) --> <div class="space-y-3"> <div class="flex items-center justify-between px-1"> <div class="flex items-center gap-2"> <h3 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Sliding Message Window Buffer </h3> <span ui-badge variant="outline" class="font-mono text-xs" >FIFO Window: {{ turns.length }} Turns Active</span > </div> <span class="text-muted-foreground text-xs">Click turn to inspect vector payload</span> </div> @if (isWiped) { <div class="border-border rounded-xl border border-dashed p-8 text-center"> <lucide-icon [img]="AlertCircle" class="text-muted-foreground/60 mx-auto h-8 w-8" /> <h4 class="text-foreground mt-2 text-sm font-semibold">Working Buffer Cleared</h4> <p class="text-muted-foreground mt-1 text-xs"> Working memory is empty. Start typing or restore the test buffer. </p> <button ui-button size="sm" variant="outline" class="mt-3 text-xs" (click)="restore.emit()"> Restore Message Turns </button> </div> } @else { <div class="space-y-3"> @for (turn of turns; track turn.id) { <div role="button" tabindex="0" [attr.aria-pressed]="selectedTurnId === turn.id" [class]="turnClass(turn)" (click)="selectTurn.emit(turn.id)" (keydown)="onTurnKeydown($event, turn)" > <!-- Turn Header --> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="flex items-center gap-3"> <span ui-avatar class="border-border bg-muted h-7 w-7 rounded-md border"> <span ui-avatar-fallback [class]="avatarFallbackClass(turn)">{{ turn.avatarText }}</span> </span> <div> <div class="flex items-center gap-2"> <span class="text-foreground text-xs font-semibold">{{ turn.authorName }}</span> <span ui-badge variant="outline" class="text-muted-foreground font-mono text-xs"> Turn #{{ turn.turnNumber }} </span> <span class="text-muted-foreground text-xs">{{ turn.timestamp }}</span> </div> </div> </div> <div class="flex items-center gap-2"> <span ui-badge variant="secondary" [class]="importanceClass(turn)"> {{ turn.importanceLevel }} ({{ percent(turn.importanceScore) }}%) </span> <span ui-badge variant="outline" class="font-mono text-xs">{{ turn.tokens }} tok</span> </div> </div> <!-- Message Text --> <p class="text-foreground/90 mt-2.5 font-mono text-xs leading-relaxed">{{ turn.content }}</p> <!-- Extracted Facts & Embedding Preview --> @if (turn.extractedFacts.length > 0) { <div class="border-border/60 bg-muted/40 mt-3 rounded-lg border p-2.5"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="CornerDownRight" class="h-3 w-3" /> <span>Extracted Persistent Declarations:</span> </div> <ul class="mt-1 space-y-1"> @for (f of turn.extractedFacts; track $index) { <li class="text-foreground flex items-center gap-2 text-xs"> <span class="bg-primary inline-block h-1 w-1 rounded-full"></span> <span>{{ f }}</span> </li> } </ul> </div> } </div> } </div> } </div> </div> `, }) export class UiAgentContextBufferComponent { protected readonly AlertCircle = AlertCircle protected readonly CornerDownRight = CornerDownRight @Input({ transform: booleanAttribute }) isWiped = false @Input() turns: MessageTurn[] = [] @Input() selectedTurnId = '' @Output() readonly selectTurn = new EventEmitter<string>() @Output() readonly restore = new EventEmitter<void>() turnClass(turn: MessageTurn): string { return cn( 'group bg-card hover:border-primary/40 focus-visible:ring-ring relative cursor-pointer rounded-xl border p-4.5 transition-colors hover:shadow-xs focus-visible:ring-2 focus-visible:outline-none', this.selectedTurnId === turn.id ? 'border-primary/60 bg-accent/20 ring-primary/30 ring-1' : 'border-border', ) } avatarFallbackClass(turn: MessageTurn): string { return cn( 'text-xs font-bold', turn.role === 'assistant' ? 'bg-primary/10 text-primary' : 'bg-success/10 text-success', ) } importanceClass(turn: MessageTurn): string { return cn( 'font-mono text-xs', turn.importanceLevel === 'Critical' && 'bg-destructive/10 text-destructive', turn.importanceLevel === 'High' && 'bg-warning/10 text-warning', turn.importanceLevel === 'Action Item' && 'bg-info/10 text-info', ) } percent(score: number): number { return Math.round(score * 100) } onTurnKeydown(event: KeyboardEvent, turn: MessageTurn): void { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() this.selectTurn.emit(turn.id) } } }src/app/components/blocks/agent-memory-inspector/agent-entity-graph.component.ts10.5 kB
import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core' import { ArrowRight, Database, LucideAngularModule, Network } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, 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 type { EntityNode, GraphTriplet } from './agent-memory-types' /** * Entity knowledge graph tab of the agent memory inspector (React `AgentEntityGraph`): * node selector, the selected node's connected triplets, and the full triplet table. * Clicking a node emits `selectNode` with its id. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-agent-entity-graph, [ui-agent-entity-graph]', standalone: true, host: { class: 'contents' }, imports: [ LucideAngularModule, UiBadgeComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiProgressComponent, UiTableComponent, UiTableHeaderComponent, UiTableBodyComponent, UiTableRowComponent, UiTableHeadComponent, UiTableCellComponent, ], template: ` <div class="space-y-4"> <!-- Top Row: Active Graph Topology Summary --> <div class="grid grid-cols-1 gap-4 lg:grid-cols-3"> <!-- Node Selector Card --> <div ui-card class="border-border shadow-xs lg:col-span-1"> <div ui-card-header class="p-4 pb-2"> <div class="flex items-center justify-between"> <h3 ui-card-title class="text-xs font-semibold">Entity Nodes ({{ nodes.length }})</h3> <span ui-badge variant="outline" class="text-xs font-normal">Active Subgraph</span> </div> <p ui-card-description class="text-xs">Select node to inspect relational edges</p> </div> <div ui-card-content class="space-y-1.5 p-4 pt-0"> @for (node of nodes; track node.id) { <button type="button" [class]="nodeClass(node)" (click)="selectNode.emit(node.id)"> <div class="flex items-center justify-between"> <span class="text-foreground font-semibold">{{ node.label }}</span> <span ui-badge variant="secondary" class="h-4.5 px-1.5 text-xs">{{ node.entityType }}</span> </div> <p class="text-muted-foreground mt-1 line-clamp-1 text-xs">{{ node.description }}</p> <div class="text-muted-foreground mt-1.5 flex items-center gap-3 text-xs"> <span>Degree: {{ node.degree }} edges</span> <span>·</span> <span class="text-success font-mono">Status: {{ node.status }}</span> </div> </button> } </div> </div> <!-- Selected Node Semantic Deep Dive --> @if (selectedNode; as node) { <div ui-card class="border-border shadow-xs lg:col-span-2"> <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]="Network" class="text-primary h-4 w-4" /> <h3 ui-card-title class="text-sm font-semibold">{{ node.label }}</h3> <span ui-badge variant="outline" class="font-mono text-xs">{{ node.entityType }}</span> </div> <span ui-badge variant="secondary" class="font-mono text-xs">Degree {{ node.degree }}</span> </div> <p ui-card-description class="text-xs">{{ node.description }}</p> </div> <div ui-card-content class="space-y-4 p-4 pt-2"> <!-- Embedding Reference --> <div class="border-border/60 bg-muted/40 flex items-center justify-between rounded-lg border p-2.5 text-xs" > <div class="flex items-center gap-2"> <lucide-icon [img]="Database" class="text-muted-foreground h-3.5 w-3.5" /> <span class="text-muted-foreground">Vector Index ID:</span> <span class="text-foreground font-mono font-medium">{{ node.embeddingId }}</span> </div> <span class="text-success font-mono text-xs">Cosine Cos: 0.962</span> </div> <!-- Associated Triplet Edges --> <div class="space-y-2"> <span class="text-foreground text-xs font-semibold"> Connected Graph Triplets ({{ filteredTriplets.length }}) </span> <div class="space-y-2"> @for (trip of filteredTriplets; track trip.id) { <div class="border-border bg-card flex flex-col gap-2 rounded-lg border p-3 sm:flex-row sm:items-center sm:justify-between" > <div class="flex flex-wrap items-center gap-2 text-xs"> <span class="text-foreground font-semibold">{{ trip.subject }}</span> <span ui-badge variant="outline" class="text-warning h-5 px-1.5 font-mono text-xs font-bold"> {{ trip.predicate }} </span> <lucide-icon [img]="ArrowRight" class="text-muted-foreground h-3 w-3" /> <span class="text-foreground font-semibold">{{ trip.object }}</span> </div> <div class="flex items-center gap-3 text-xs"> <span class="text-muted-foreground font-mono text-xs">Weight: {{ trip.weight }}</span> <span ui-badge variant="secondary" class="font-mono text-xs"> {{ percent(trip.confidence) }}% conf </span> <span class="text-muted-foreground text-xs">{{ trip.lastTraversed }}</span> </div> </div> } </div> </div> </div> </div> } </div> <!-- Complete Graph Relations Table --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="p-5 pb-3"> <div class="flex items-center justify-between"> <div> <h3 ui-card-title class="text-sm font-semibold">Semantic Graph Triplets</h3> <p ui-card-description class="text-xs"> Subject-Predicate-Object directional triples stored in Neo4j / GraphRAG engine. </p> </div> <span ui-badge variant="outline" class="font-mono text-xs">{{ triplets.length }} Total Triplets</span> </div> </div> <div ui-card-content class="p-0"> <div ui-table> <thead ui-table-header> <tr ui-table-row class="hover:bg-transparent"> <th ui-table-head class="text-xs font-semibold">Subject</th> <th ui-table-head class="text-xs font-semibold">Predicate</th> <th ui-table-head class="text-xs font-semibold">Object</th> <th ui-table-head class="text-xs font-semibold">Confidence</th> <th ui-table-head class="text-xs font-semibold">Weight</th> <th ui-table-head class="text-right text-xs font-semibold">Last Traversed</th> </tr> </thead> <tbody ui-table-body> @for (trip of triplets; track trip.id) { <tr ui-table-row class="text-xs"> <td ui-table-cell class="font-medium"> <div class="flex items-center gap-1.5"> <span class="text-foreground font-semibold">{{ trip.subject }}</span> <span class="text-muted-foreground text-xs">({{ trip.subjectType }})</span> </div> </td> <td ui-table-cell> <span ui-badge variant="outline" class="text-warning font-mono text-xs font-bold"> {{ trip.predicate }} </span> </td> <td ui-table-cell class="font-medium"> <div class="flex items-center gap-1.5"> <span class="text-foreground font-semibold">{{ trip.object }}</span> <span class="text-muted-foreground text-xs">({{ trip.objectType }})</span> </div> </td> <td ui-table-cell> <div class="flex items-center gap-2"> <div ui-progress [value]="trip.confidence * 100" class="h-1.5 w-12"></div> <span class="font-mono text-xs">{{ percent(trip.confidence) }}%</span> </div> </td> <td ui-table-cell class="font-mono text-xs">{{ trip.weight }}</td> <td ui-table-cell class="text-muted-foreground text-right text-xs">{{ trip.lastTraversed }}</td> </tr> } </tbody> </div> </div> </div> </div> `, }) export class UiAgentEntityGraphComponent { protected readonly ArrowRight = ArrowRight protected readonly Database = Database protected readonly Network = Network @Input() triplets: GraphTriplet[] = [] @Input() nodes: EntityNode[] = [] @Input() selectedNodeId = '' @Output() readonly selectNode = new EventEmitter<string>() get selectedNode(): EntityNode | undefined { return this.nodes.find((n) => n.id === this.selectedNodeId) || this.nodes[0] } get filteredTriplets(): GraphTriplet[] { const node = this.selectedNode if (!node) return this.triplets return this.triplets.filter((t) => t.subject === node.label || t.object === node.label) } nodeClass(node: EntityNode): string { return cn( 'w-full cursor-pointer rounded-lg border p-2.5 text-left text-xs transition-colors', this.selectedNodeId === node.id ? 'bg-primary/10 border-primary/30 text-foreground font-medium shadow-xs' : 'bg-muted/30 hover:bg-muted/60 text-muted-foreground hover:text-foreground border-transparent', ) } percent(value: number): number { return Math.round(value * 100) } }src/app/components/blocks/agent-memory-inspector/agent-memory-types.ts1.2 kB
export type MemoryCategory = 'User Preference' | 'Architecture Rule' | 'System Constraint' export interface EpisodicMemory { id: string content: string category: MemoryCategory confidence: number lastRecalled: string recallsCount: number sourceTurn: string tags: string[] isPinned: boolean } export 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 } export 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 } export 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 }src/app/components/blocks/agent-memory-inspector/agent-memory-data.ts7.2 kB
import type { EpisodicMemory, MessageTurn, EntityNode, GraphTriplet } from './agent-memory-types' export const INITIAL_MEMORY_FACTS: EpisodicMemory[] = [ { id: 'fact-1', content: 'User prefers Vue 3.5 SFC with <script setup lang="ts">', category: 'User Preference', confidence: 0.98, lastRecalled: '2m ago', recallsCount: 42, sourceTurn: 'Turn #1 (Workspace Init)', tags: ['vue3', 'sfc', 'typescript'], isPinned: true, }, { id: 'fact-2', content: 'User repository uses Tailwind v4 with OKLCH theme tokens', category: 'Architecture Rule', confidence: 0.95, lastRecalled: '14m ago', recallsCount: 89, sourceTurn: 'Turn #3 (CSS System)', tags: ['tailwind4', 'oklch', 'tokens'], isPinned: true, }, { id: 'fact-3', content: 'User enforces strict 120-character printWidth in Prettier', category: 'System Constraint', confidence: 0.99, lastRecalled: '1h ago', recallsCount: 126, sourceTurn: 'Turn #1 (Code Standards)', tags: ['prettier', 'formatting'], isPinned: false, }, { id: 'fact-4', content: 'Company domain is uipkge.dev', category: 'System Constraint', confidence: 0.97, lastRecalled: '3h ago', recallsCount: 31, sourceTurn: 'Turn #3 (Domain Policy)', tags: ['domain', 'hosting'], isPinned: false, }, { id: 'fact-5', content: 'Never publish to npm; components distributed as unbundled raw source registries', category: 'Architecture Rule', confidence: 0.96, lastRecalled: '4h ago', recallsCount: 67, sourceTurn: 'Turn #2 (Architecture Rules)', tags: ['no-npm', 'shadcn', 'registry'], isPinned: true, }, ] // Message Window Buffer export const INITIAL_MESSAGE_TURNS: MessageTurn[] = [ { id: 'turn-1', turnNumber: 1, role: 'user', authorName: 'Elena Rostova (Lead Architect)', avatarText: 'ER', timestamp: '12m ago', importanceScore: 0.94, importanceLevel: 'Critical', tokens: 142, content: 'We are configuring the UIPKGE monorepo for Vue 3.5 and React 19. All Vue components must use <script setup lang="ts"> and strict 120-character printWidth in Prettier. Do not output semicolons.', extractedFacts: ['User prefers Vue 3.5 SFC', '120-char printWidth enforced'], embeddingHash: '0x7f8a91c4', }, { id: 'turn-2', turnNumber: 2, role: 'assistant', authorName: 'Senior Engineering Co-Pilot', avatarText: 'AI', timestamp: '11m ago', importanceScore: 0.78, importanceLevel: 'Standard', tokens: 512, content: 'Understood. I have recorded the formatting rules (no semicolons, single quotes, 120 width) and dual-framework targets. All component scaffolding will adhere strictly to Vue 3.5 script setup standards and OKLCH color token architecture.', extractedFacts: ['Single-quote formatting indexed', 'Vue 3.5 + React dual pipeline'], embeddingHash: '0x3b1d84e2', }, { id: 'turn-3', turnNumber: 3, role: 'user', authorName: 'Elena Rostova (Lead Architect)', avatarText: 'ER', timestamp: '4m ago', importanceScore: 0.98, importanceLevel: 'High', tokens: 286, content: 'Remember that company domain is uipkge.dev and we do NOT publish npm packages. Components are distributed directly as unbundled registry JSON files via shadcn CLI. Tailwind v4 with OKLCH theme tokens must be used across all blocks.', extractedFacts: ['Company domain is uipkge.dev', 'Zero npm publishing policy', 'Tailwind v4 OKLCH token model'], embeddingHash: '0x92e4ca10', }, { id: 'turn-4', turnNumber: 4, role: 'assistant', authorName: 'Senior Engineering Co-Pilot', avatarText: 'AI', timestamp: 'Just now', importanceScore: 0.91, importanceLevel: 'Action Item', tokens: 1490, content: 'Memory updated. Synced "uipkge.dev" and registry-first unbundled distribution architecture into Mem0 vector collection and Neo4j dependency graph. Short-term context buffer is currently utilizing 6,420 / 128,000 tokens (5.0%). Ready for task execution.', extractedFacts: ['Neo4j triplets linked', 'Mem0 vector index synced'], embeddingHash: '0x48c71bf9', }, ] // Entity Knowledge Graph Nodes & Triplets export const INITIAL_ENTITY_NODES: EntityNode[] = [ { id: 'node-1', label: 'Elena Rostova', entityType: 'Person', description: 'Lead Maintainer & Core Architect', degree: 2, status: 'active', embeddingId: 'emb_981a', }, { id: 'node-2', label: 'uipkge-ui', entityType: 'Repository', description: 'Dual-Framework UI Registry Monorepo', degree: 5, status: 'active', embeddingId: 'emb_432b', }, { id: 'node-3', label: 'Tailwind v4', entityType: 'CSS Engine', description: 'CSS-first @theme inline styling layer', degree: 2, status: 'synced', embeddingId: 'emb_771c', }, { id: 'node-4', label: 'OKLCH Tokens', entityType: 'Design System', description: 'Perceptually uniform color model palette', degree: 1, status: 'synced', embeddingId: 'emb_118d', }, { id: 'node-5', label: 'Vue 3.5 SFC', entityType: 'Framework', description: 'Primary component authoring standard', degree: 1, status: 'active', embeddingId: 'emb_554e', }, { id: 'node-6', label: 'React 19 Components', entityType: 'Framework', description: 'Headless mirror registry implementation', degree: 1, status: 'active', embeddingId: 'emb_229f', }, { id: 'node-7', label: '120-char printWidth', entityType: 'Standard', description: 'Prettier wrap limit constraint', degree: 1, status: 'cached', embeddingId: 'emb_883g', }, ] export const INITIAL_GRAPH_TRIPLETS: GraphTriplet[] = [ { id: 'trip-1', subject: 'Elena Rostova', subjectType: 'Person', predicate: 'MAINTAINS', object: 'uipkge-ui', objectType: 'Repository', confidence: 0.99, weight: '1.00', lastTraversed: '12s ago', }, { id: 'trip-2', subject: 'uipkge-ui', subjectType: 'Repository', predicate: 'USES', object: 'Tailwind v4', objectType: 'CSS Engine', confidence: 0.98, weight: '0.96', lastTraversed: '45s ago', }, { id: 'trip-3', subject: 'Tailwind v4', subjectType: 'CSS Engine', predicate: 'CONFIGURES', object: 'OKLCH Tokens', objectType: 'Design System', confidence: 0.97, weight: '0.94', lastTraversed: '2m ago', }, { id: 'trip-4', subject: 'uipkge-ui', subjectType: 'Repository', predicate: 'IMPLEMENTS', object: 'Vue 3.5 SFC', objectType: 'Framework', confidence: 0.99, weight: '0.98', lastTraversed: '1m ago', }, { id: 'trip-5', subject: 'uipkge-ui', subjectType: 'Repository', predicate: 'MIRRORS', object: 'React 19 Components', objectType: 'Framework', confidence: 0.96, weight: '0.91', lastTraversed: '3m ago', }, { id: 'trip-6', subject: 'Elena Rostova', subjectType: 'Person', predicate: 'ENFORCES', object: '120-char printWidth', objectType: 'Standard', confidence: 0.95, weight: '0.89', lastTraversed: '5m ago', }, ] // UI Controls Statesrc/app/components/blocks/agent-memory-inspector/index.ts0.5 kB
export { UiAgentMemoryInspectorComponent, type EpisodicMemory, type MessageTurn, type EntityNode, type GraphTriplet, type MemoryCategory, } from './agent-memory-inspector.component' export { UiAgentContextBufferComponent } from './agent-context-buffer.component' export { UiAgentEntityGraphComponent } from './agent-entity-graph.component' export { INITIAL_MEMORY_FACTS, INITIAL_MESSAGE_TURNS, INITIAL_ENTITY_NODES, INITIAL_GRAPH_TRIPLETS, } from './agent-memory-data'
Raw manifest:https://uipkge.dev/r/angular/agent-memory-inspector.json