import React, { useState } from 'react'; import { MemoryTaggingEvent } from '../utils/metaMemoryEventProcessor'; import type { TopicTagMetadata, MessageMetadata } from '@just-every/task'; import type { TaskState } from '../hooks/useTaskState'; import { formatDuration } from '../utils/formatters'; import './style.scss'; import './MemoryView.scss'; import './MemoryView.compaction.scss'; import { NoContent } from './NoContent'; export interface MemoryViewProps { taskState: TaskState; className?: string; } export const MemoryView: React.FC = ({ taskState, className = '' }) => { const [activeTab, setActiveTab] = useState<'topics' | 'messages' | 'events' | 'compaction'>('topics'); const [expandedMessages, setExpandedMessages] = useState>(new Set()); const [selectedTopic, setSelectedTopic] = useState(null); const [expandedCompactions, setExpandedCompactions] = useState>(new Set()); const toggleMessageExpanded = (messageId: string) => { const newExpanded = new Set(expandedMessages); if (newExpanded.has(messageId)) { newExpanded.delete(messageId); } else { newExpanded.add(messageId); } setExpandedMessages(newExpanded); }; const toggleCompactionExpanded = (topicTag: string) => { const newExpanded = new Set(expandedCompactions); if (newExpanded.has(topicTag)) { newExpanded.delete(topicTag); } else { newExpanded.add(topicTag); } setExpandedCompactions(newExpanded); }; const findOriginalMessage = (messageId: string) => { // The messageId parameter here is actually the key from taggedMessages map // which should match the 'id' field of messages return taskState.messages.find(event => { // Check the message.id field from ResponseOutputEvent return event.message.id === messageId; }); }; const renderTaggingEvent = (event: MemoryTaggingEvent) => (
{event.isRunning ? ( <>⟳ Processing ) : ( <>✓ Completed )} {new Date(event.startedAt).toLocaleTimeString()} {event.processingTime && ( {formatDuration(event.processingTime)} )}
{event.messageCount} messages
{event.completedAt && (
{event.newTopicCount !== undefined && event.newTopicCount > 0 && ( { if (event.affectedTopics?.length) { setSelectedTopic(event.affectedTopics[0]); setActiveTab('topics'); } }} title={event.affectedTopics?.join(', ')} > +{event.newTopicCount} new topics )} {event.updatedTopicCount !== undefined && event.updatedTopicCount > 0 && ( { if (event.affectedTopics?.length) { setSelectedTopic(event.affectedTopics[0]); setActiveTab('topics'); } }} title={event.affectedTopics?.join(', ')} > ↻{event.updatedTopicCount} updated topics )}
{event.newMessageCount !== undefined && event.newMessageCount > 0 && ( { if (event.affectedMessages?.length) { // Find first affected message's topic to filter by const firstMsgId = event.affectedMessages[0]; const msgMetadata = taggedMessages.get(firstMsgId); if (msgMetadata && msgMetadata.topic_tags.length > 0) { setSelectedTopic(msgMetadata.topic_tags[0]); } setActiveTab('messages'); } }} title="View tagged messages" > +{event.newMessageCount} new tags )} {event.updatedMessageCount !== undefined && event.updatedMessageCount > 0 && ( { if (event.affectedMessages?.length) { // Find first affected message's topic to filter by const firstMsgId = event.affectedMessages[0]; const msgMetadata = taggedMessages.get(firstMsgId); if (msgMetadata && msgMetadata.topic_tags.length > 0) { setSelectedTopic(msgMetadata.topic_tags[0]); } setActiveTab('messages'); } }} title="View updated messages" > ↻{event.updatedMessageCount} updated tags )}
)}
); const renderTopic = (topic: string, metadata: TopicTagMetadata) => (
{ setSelectedTopic(topic); setActiveTab('messages'); }} >
{topic} {metadata.type}
{metadata.description}
Last updated: {new Date(metadata.last_update).toLocaleString()}
); const renderMessage = (messageId: string, metadata: MessageMetadata) => { // Try finding by the map key first, then by the metadata.message_id const originalMessage = findOriginalMessage(messageId) || findOriginalMessage(metadata.message_id); const isExpanded = expandedMessages.has(messageId); return (
toggleMessageExpanded(messageId)}>
{isExpanded ? '▼' : '▶'} {messageId.substring(0, 8)}... {metadata.summary}
{metadata.topic_tags.map(tag => ( { e.stopPropagation(); setSelectedTopic(selectedTopic === tag ? null : tag); }} > {tag} ))}
{isExpanded && (
{originalMessage ? (
Type: {originalMessage.message.type}
{'role' in originalMessage.message && (
Role: {originalMessage.message.role}
)}
Status: {originalMessage.message.status}
{originalMessage.request_id && (
Request ID: {originalMessage.request_id}
)}
Content: {'content' in originalMessage.message ? (
{typeof originalMessage.message.content === 'string' 
                                            ? originalMessage.message.content 
                                            : JSON.stringify(originalMessage.message.content, null, 2)}
) : (
{JSON.stringify(originalMessage.message, null, 2)}
)}
) : (
Original message not found. Message ID: {messageId} {metadata.message_id !== messageId && ` (or ${metadata.message_id})`}
)}
)}
Last updated: {new Date(metadata.last_update).toLocaleString()}
); }; // Render compaction visualization for a topic const renderCompactionVisualization = (topic: string, metadata: TopicTagMetadata) => { const isExpanded = expandedCompactions.has(topic); const targetCompaction = metadata.target_compaction_percent || 0; // Get messages for this topic to calculate stats const topicMessages = Array.from(taggedMessages.entries()).filter(([_, msgMeta]) => msgMeta.topic_tags.includes(topic) ); const totalMessages = topicMessages.length; const totalChars = topicMessages.reduce((sum, [_, msgMeta]) => sum + (msgMeta.summary?.length || 0), 0); const estimatedTokens = Math.ceil(totalChars / 4); // Calculate visual metrics const compactionPercentage = targetCompaction; const retainedPercentage = 100 - compactionPercentage; // Determine compaction status color const getCompactionColor = (percent: number) => { if (percent === 0) return '#6c757d'; // gray - no compaction if (percent <= 20) return '#28a745'; // green - light compaction if (percent <= 50) return '#ffc107'; // yellow - moderate compaction if (percent <= 80) return '#fd7e14'; // orange - heavy compaction return '#dc3545'; // red - maximum compaction }; const compactionColor = getCompactionColor(targetCompaction); return (
toggleCompactionExpanded(topic)}>
{isExpanded ? '▼' : '▶'} {topic} {metadata.type}
{totalMessages} messages ~{estimatedTokens} tokens
Retained: {retainedPercentage}% Target: {targetCompaction}%
📁
{totalMessages} msgs
Original
📦
{Math.ceil(totalMessages * retainedPercentage / 100)} msgs
After Compaction
{isExpanded && (

Compaction Strategy

{metadata.type === 'core' && 'Core topics receive minimal compaction to preserve important context.'} {metadata.type === 'active' && 'Active topics are moderately compacted while maintaining recent context.'} {metadata.type === 'idle' && 'Idle topics are aggressively compacted to save space.'} {metadata.type === 'archived' && 'Archived topics are fully compacted into summaries.'} {metadata.type === 'ephemeral' && 'Ephemeral topics are quickly compacted as they age.'}

Last Updated: {new Date(metadata.last_update).toLocaleString()}
Age: {formatDuration(Date.now() - metadata.last_update)}
Estimated Savings: ~{Math.floor(estimatedTokens * compactionPercentage / 100)} tokens

Compaction Preview

{targetCompaction === 0 ? (

No compaction scheduled for this topic.

) : targetCompaction === 100 ? (

All messages will be compacted into a single comprehensive summary.

) : (

The oldest {targetCompaction}% of messages will be compacted into a summary, preserving recent context.

)}
)}
); }; // Convert objects to Maps if needed (handles JSON serialization) const topicTags: Map = taskState.memoryData.currentState?.topicTags instanceof Map ? taskState.memoryData.currentState.topicTags : new Map(Object.entries(taskState.memoryData.currentState?.topicTags || {}) as [string, TopicTagMetadata][]); const taggedMessages: Map = taskState.memoryData.currentState?.taggedMessages instanceof Map ? taskState.memoryData.currentState.taggedMessages : new Map(Object.entries(taskState.memoryData.currentState?.taggedMessages || {}) as [string, MessageMetadata][]); // Filter messages by selected topic const filteredMessages = selectedTopic ? Array.from(taggedMessages.entries()).filter(([_, metadata]) => metadata.topic_tags.includes(selectedTopic) ) : Array.from(taggedMessages.entries()); return (

Meta-Memory

{taskState.memoryData.stats.totalTopics} Topics
{taskState.memoryData.stats.totalTaggedMessages} Tagged
{taskState.memoryData.stats.totalNewTopics} New Topics
{taskState.memoryData.stats.averageProcessingTime > 0 && (
{formatDuration(taskState.memoryData.stats.averageProcessingTime)} Avg Time
)}
{selectedTopic && activeTab === 'messages' && (
Filtering by topic: {selectedTopic}
)}
{activeTab === 'topics' && (<> {topicTags.size > 0 ? (
{Array.from(topicTags.entries()) .sort((a, b) => b[1].last_update - a[1].last_update) .map(([topic, metadata]) => renderTopic(topic, metadata))}
) : ( )} )} {activeTab === 'messages' && (<> {filteredMessages.length > 0 ? (
{filteredMessages .sort((a, b) => b[1].last_update - a[1].last_update) .map(([messageId, metadata]) => renderMessage(messageId, metadata))}
) : ( )} )} {activeTab === 'events' && (<> {taskState.memoryData.taggingEvents.length > 0 ? (
{taskState.memoryData.taggingEvents .sort((a, b) => b.startedAt - a.startedAt) .map(renderTaggingEvent)}
) : ( )} )} {activeTab === 'compaction' && (
{topicTags.size > 0 ? (
{Array.from(topicTags.entries()) .sort((a, b) => { // Sort by compaction percentage (descending), then by last update const compA = a[1].target_compaction_percent || 0; const compB = b[1].target_compaction_percent || 0; if (compA !== compB) return compB - compA; return b[1].last_update - a[1].last_update; }) .map(([topic, metadata]) => renderCompactionVisualization(topic, metadata))}
) : ( )}
)}
); };