feat: Add web-based viewer UI for real-time memory stream (#58)
* Add viewer HTML for claude-mem with live stream and settings interface - Implemented a responsive layout with left and right columns for observations and settings. - Added status indicators for connection state. - Integrated server-sent events (SSE) for real-time updates on observations and summaries. - Created dynamic project filter dropdown based on available observations. - Developed settings section for environment variables and worker stats. - Included functionality to save settings and load current stats from the server. - Enhanced UI with custom styles for better user experience. * Remove draft implementation plan for v5.1 web UI * feat: Implement viewer UI with sidebar, feed, and settings management - Add main viewer template (HTML) with styling for dark mode. - Create App component to manage state and render Header, Feed, and Sidebar. - Implement Feed component to display observations and summaries with filtering. - Develop Header component for project selection and connection status. - Create ObservationCard and SummaryCard components for displaying individual items. - Implement Sidebar for settings management and displaying worker/database stats. - Add hooks for managing SSE connections, settings, and stats fetching. - Define types for observations, summaries, settings, and stats. * Enhance UI components and improve layout - Updated padding and layout for the feed and card components in viewer.html, viewer-template.html, and viewer.html to improve visual spacing and alignment. - Increased card margins and padding for better readability and aesthetics. - Adjusted font sizes, weights, and line heights for card titles and subtitles to enhance text clarity and hierarchy. - Added a new feed-content class to center the feed items and limit their maximum width. - Modified the Header component to improve the settings icon's SVG structure for better rendering. - Enhanced the Sidebar component by adding a close button with an SVG icon, improving user experience for closing settings. - Updated the Sidebar component's props to include an onClose function for handling sidebar closure. * feat: Add user prompts feature with UI integration - Implemented a new method in SessionStore to retrieve recent user prompts. - Updated WorkerService to fetch and broadcast user prompts to clients. - Enhanced the Feed component to display user prompts alongside observations and summaries. - Created a new PromptCard component for rendering individual user prompts. - Modified useSSE hook to handle new prompt events and processing status. - Updated viewer templates and styles to accommodate the new prompts feature. * feat: Add project filtering and pagination for observations - Implemented `getAllProjects` method in `SessionStore` to retrieve unique projects from the database. - Added `/api/observations` endpoint in `WorkerService` for paginated observations fetching. - Enhanced `App` component to manage paginated observations and integrate with the new API. - Updated `Feed` component to support infinite scrolling and loading more observations. - Modified `Header` to display processing status. - Refactored `PromptCard` to remove unnecessary processing indicator. - Introduced `usePagination` hook to handle pagination logic for observations. - Updated `useSSE` hook to include projects in the state. - Adjusted types to accommodate new project data. * Refactor viewer build process and remove deprecated HTML template - Updated build-viewer.js to copy HTML template to build output with improved logging. - Removed src/ui/viewer.html as it is no longer needed. - Enhanced App component to merge observations while removing duplicates using useMemo. - Improved Feed component to utilize a ref for onLoadMore callback and adjusted infinite scroll logic. - Updated Sidebar component to use default settings from constants and removed redundant formatting functions. - Refactored usePagination hook to streamline loading logic and prevent concurrent requests. - Updated useSSE hook to use centralized API endpoints and improved reconnection logic. - Refactored useSettings and useStats hooks to utilize constants for API endpoints and timing. - Introduced ErrorBoundary component for better error handling in the viewer. - Centralized API endpoint paths, default settings, timing constants, and UI-related constants into dedicated files. - Added utility functions for formatting uptime and bytes for consistent display across components. * feat: Enhance session management and pagination for user prompts, summaries, and observations - Added project field to user prompts in the database and API responses. - Implemented new API endpoints for fetching summaries and prompts with pagination. - Updated WorkerService to handle new endpoints and filter results by project. - Modified App component to manage paginated data for prompts and summaries. - Refactored Feed component to remove unnecessary filtering and handle combined data. - Improved usePagination hook to support multiple data types and project filtering. - Adjusted useSSE hook to only load projects initially, with data fetched via pagination. - Updated types to include project information for user prompts. * feat: add SummarySkeleton component and data utility for merging items - Introduced SummarySkeleton component for displaying loading state in the UI. - Implemented mergeAndDeduplicateByProject utility function to merge real-time and paginated data while removing duplicates based on project filtering. * Enhance UI and functionality of the viewer component - Updated sidebar transition effects to use translate3d for improved performance. - Added a sidebar header with title and connection status indicators. - Modified the PromptCard to display project name instead of prompt number. - Introduced a GitHub and X (Twitter) link in the header for easy access. - Improved styling for setting descriptions and card hover effects. - Enhanced Sidebar component to include connection status and updated layout. * fix: reduce timeout for worker health checks and ensure proper responsiveness
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Settings, Stats } from '../types';
|
||||
import { DEFAULT_SETTINGS } from '../constants/settings';
|
||||
import { formatUptime, formatBytes } from '../utils/formatters';
|
||||
|
||||
interface SidebarProps {
|
||||
isOpen: boolean;
|
||||
settings: Settings;
|
||||
stats: Stats;
|
||||
isSaving: boolean;
|
||||
saveStatus: string;
|
||||
isConnected: boolean;
|
||||
onSave: (settings: Settings) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function Sidebar({ isOpen, settings, stats, isSaving, saveStatus, isConnected, onSave, onClose }: SidebarProps) {
|
||||
const [model, setModel] = useState(settings.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL);
|
||||
const [contextObs, setContextObs] = useState(settings.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS);
|
||||
const [workerPort, setWorkerPort] = useState(settings.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT);
|
||||
|
||||
// Update local state when settings change
|
||||
useEffect(() => {
|
||||
setModel(settings.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL);
|
||||
setContextObs(settings.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS);
|
||||
setWorkerPort(settings.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT);
|
||||
}, [settings]);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave({
|
||||
CLAUDE_MEM_MODEL: model,
|
||||
CLAUDE_MEM_CONTEXT_OBSERVATIONS: contextObs,
|
||||
CLAUDE_MEM_WORKER_PORT: workerPort
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`sidebar ${isOpen ? 'open' : ''}`}>
|
||||
<div className="sidebar-header">
|
||||
<h1>Settings</h1>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||
<span className={`status-dot ${isConnected ? 'connected' : ''}`} />
|
||||
<span style={{ fontSize: '11px', opacity: 0.5, fontWeight: 300 }}>{isConnected ? 'Connected' : 'Disconnected'}</span>
|
||||
</div>
|
||||
<button onClick={handleSave} disabled={isSaving}>
|
||||
{isSaving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
title="Close settings"
|
||||
style={{
|
||||
background: 'transparent',
|
||||
border: '1px solid #404040',
|
||||
padding: '8px',
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stats-scroll">
|
||||
|
||||
<div className="settings-section">
|
||||
<h3>Environment Variables</h3>
|
||||
<div className="form-group">
|
||||
<label htmlFor="model">CLAUDE_MEM_MODEL</label>
|
||||
<div className="setting-description">
|
||||
Model used for AI compression of tool observations. Haiku is fast and cheap, Sonnet offers better quality, Opus is most capable but expensive.
|
||||
</div>
|
||||
<select
|
||||
id="model"
|
||||
value={model}
|
||||
onChange={e => setModel(e.target.value)}
|
||||
>
|
||||
<option value="claude-haiku-4-5">claude-haiku-4-5</option>
|
||||
<option value="claude-sonnet-4-5">claude-sonnet-4-5</option>
|
||||
<option value="claude-opus-4">claude-opus-4</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="contextObs">CLAUDE_MEM_CONTEXT_OBSERVATIONS</label>
|
||||
<div className="setting-description">
|
||||
Number of recent observations to inject at session start. Higher values provide more context but increase token usage. Default: 50
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
id="contextObs"
|
||||
min="1"
|
||||
max="200"
|
||||
value={contextObs}
|
||||
onChange={e => setContextObs(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="workerPort">CLAUDE_MEM_WORKER_PORT</label>
|
||||
<div className="setting-description">
|
||||
Port number for the background worker service. Change only if port 37777 conflicts with another service.
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
id="workerPort"
|
||||
min="1024"
|
||||
max="65535"
|
||||
value={workerPort}
|
||||
onChange={e => setWorkerPort(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{saveStatus && (
|
||||
<div className="save-status">{saveStatus}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="settings-section">
|
||||
<h3>Worker Stats</h3>
|
||||
<div className="stats-grid">
|
||||
<div className="stat">
|
||||
<div className="stat-label">Version</div>
|
||||
<div className="stat-value">{stats.worker?.version || '-'}</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-label">Uptime</div>
|
||||
<div className="stat-value">{formatUptime(stats.worker?.uptime)}</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-label">Active Sessions</div>
|
||||
<div className="stat-value">{stats.worker?.activeSessions || '0'}</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-label">SSE Clients</div>
|
||||
<div className="stat-value">{stats.worker?.sseClients || '0'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-section">
|
||||
<h3>Database Stats</h3>
|
||||
<div className="stats-grid">
|
||||
<div className="stat">
|
||||
<div className="stat-label">DB Size</div>
|
||||
<div className="stat-value">{formatBytes(stats.database?.size)}</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-label">Observations</div>
|
||||
<div className="stat-value">{stats.database?.observations || '0'}</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-label">Sessions</div>
|
||||
<div className="stat-value">{stats.database?.sessions || '0'}</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-label">Summaries</div>
|
||||
<div className="stat-value">{stats.database?.summaries || '0'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user