Files
claude-mem/src/ui/viewer/components/TerminalPreview.tsx
T
Alex Newman 375dd1c3d6 feat: Add Context Settings Modal with Terminal Preview and UI Enhancements (#161)
* feat: Add Context Injection Settings modal with terminal preview

Adds a new settings modal accessible from the viewer UI header that allows users to configure context injection parameters with a live terminal preview showing how observations will appear.

Changes:
- New ContextSettingsModal component with auto-saving settings
- TerminalPreview component for live context visualization
- useContextPreview hook for fetching preview data
- Modal positioned to left of color mode button
- Settings sync with backend via worker service API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Add demo data and modify contextHook for cm_demo_content project

- Introduced DEMO_OBSERVATIONS and DEMO_SUMMARIES for the cm_demo_content project to provide mock data for testing and demonstration purposes.
- Updated contextHook to utilize demo data when the project is cm_demo_content, filtering observations based on configured types and concepts.
- Adjusted the worker service to use the contextHook with demo data, ensuring ANSI rendering for terminal output.
- Enhanced error handling and ensured proper closure of database connections.

* feat: add GitHub stars button with dynamic star count

- Implemented a new GitHubStarsButton component that fetches and displays the star count for a specified GitHub repository.
- Added useGitHubStars hook to handle API requests and state management for star count.
- Created formatStarCount utility function to format the star count into compact notation (k/M suffixes).
- Styled the GitHub stars button to match existing UI components, including hover and active states.
- Updated Header component to include the new GitHubStarsButton, replacing the static GitHub link.
- Added responsive styles to hide the GitHub stars button on mobile devices.

* feat: add API endpoint to fetch distinct projects and update context settings modal

- Implemented a new API endpoint `/api/projects` in `worker-service.ts` to retrieve a list of distinct projects from the observations.
- Modified `ContextSettingsModal.tsx` to replace the current project display with a dropdown for selecting projects, utilizing the fetched project list.
- Updated `useContextPreview.ts` to fetch projects on mount and manage the selected project state.
- Removed the `currentProject` prop from `ContextSettingsModal` and `App` components as it is now managed internally within the modal.

* Enhance Context Settings Modal and Terminal Preview

- Updated the styling of the Context Settings Modal for a modern clean design, including improved backdrop, header, and body layout.
- Introduced responsive design adjustments for smaller screens.
- Added custom scrollbar styles for better user experience.
- Refactored the TerminalPreview component to utilize `ansi-to-html` for rendering ANSI content, improving text display.
- Implemented new font variables for terminal styling across the application.
- Enhanced checkbox and input styles in the settings panel for better usability and aesthetics.
- Improved the layout and structure of settings groups and chips for a more organized appearance.

* Refactor UI components for compact design and enhance MCP toggle functionality

- Updated grid layout in viewer.html and viewer-template.html for better space utilization.
- Reduced padding and font sizes in settings groups, filter chips, and form controls for a more compact appearance.
- Implemented MCP toggle state management in ContextSettingsModal with API integration for status fetching and toggling.
- Reorganized settings groups for clarity, renaming and consolidating sections for improved user experience.
- Added feedback mechanism for MCP toggle status to inform users of changes and errors.

* feat: add collapsible sections, chip groups, form fields with tooltips, and toggle switches in settings modal

- Implemented collapsible sections for better organization of settings.
- Added chip groups with select all/none functionality for observation types and concepts.
- Enhanced form fields with optional tooltips for better user guidance.
- Introduced toggle switches for various settings, improving user interaction.
- Updated styles for new components to ensure consistency and responsiveness.
- Refactored ContextSettingsModal to utilize new components and improve readability.
- Improved TerminalPreview component styling for better layout and usability.

* Refactor modal header and preview selector styles; enhance terminal preview functionality

- Updated modal header padding and added gap for better spacing.
- Introduced a new header-controls section to include a project preview selector.
- Enhanced the preview selector styles for improved usability and aesthetics.
- Adjusted the preview column styles for a cleaner look.
- Implemented word wrap toggle functionality in the TerminalPreview component, allowing users to switch between wrapped and scrollable text.
- Improved scroll position handling in TerminalPreview to maintain user experience during content updates.

* feat: enhance modal settings with new icon links and update header controls

- Added new modal icon links for documentation and social media in ContextSettingsModal.
- Updated the header to remove sidebar toggle functionality and replaced it with context preview toggle.
- Refactored styles for modal icon links to improve UI/UX.
- Removed sidebar component from App and adjusted related state management.

* chore: remove abandoned cm_demo_content demo data approach

The demo data feature was prototyped but didn't work out. Removes:
- DEMO_OBSERVATIONS and DEMO_SUMMARIES arrays
- Conditional logic that bypassed DB for demo project
- Demo mode check in prior message extraction

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-03 21:34:41 -05:00

137 lines
4.2 KiB
TypeScript

import React, { useMemo, useRef, useLayoutEffect, useState } from 'react';
import AnsiToHtml from 'ansi-to-html';
interface TerminalPreviewProps {
content: string;
isLoading?: boolean;
className?: string;
}
const ansiConverter = new AnsiToHtml({
fg: '#dcd6cc',
bg: '#252320',
newline: false,
escapeXML: true,
stream: false
});
export function TerminalPreview({ content, isLoading = false, className = '' }: TerminalPreviewProps) {
const preRef = useRef<HTMLPreElement>(null);
const scrollTopRef = useRef(0);
const [wordWrap, setWordWrap] = useState(true);
const html = useMemo(() => {
// Save scroll position before content changes
if (preRef.current) {
scrollTopRef.current = preRef.current.scrollTop;
}
if (!content) return '';
return ansiConverter.toHtml(content);
}, [content]);
// Restore scroll position after render
useLayoutEffect(() => {
if (preRef.current && scrollTopRef.current > 0) {
preRef.current.scrollTop = scrollTopRef.current;
}
}, [html]);
const preStyle: React.CSSProperties = {
padding: '16px',
margin: 0,
fontFamily: 'var(--font-terminal)',
fontSize: '12px',
lineHeight: '1.6',
overflow: 'auto',
color: 'var(--color-text-primary)',
backgroundColor: 'var(--color-bg-card)',
whiteSpace: wordWrap ? 'pre-wrap' : 'pre',
wordBreak: wordWrap ? 'break-word' : 'normal',
position: 'absolute',
inset: 0,
};
return (
<div
className={className}
style={{
backgroundColor: 'var(--color-bg-card)',
border: '1px solid var(--color-border-primary)',
borderRadius: '8px',
overflow: 'hidden',
height: '100%',
display: 'flex',
flexDirection: 'column',
boxShadow: '0 10px 40px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3)'
}}
>
{/* Window chrome */}
<div
style={{
padding: '12px',
borderBottom: '1px solid var(--color-border-primary)',
display: 'flex',
gap: '6px',
alignItems: 'center',
backgroundColor: 'var(--color-bg-header)'
}}
>
<div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#ff5f57' }} />
<div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#ffbd2e' }} />
<div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#28c840' }} />
<button
onClick={() => setWordWrap(!wordWrap)}
style={{
marginLeft: 'auto',
padding: '4px 8px',
fontSize: '11px',
fontWeight: 500,
color: wordWrap ? 'var(--color-text-secondary)' : 'var(--color-accent-primary)',
backgroundColor: 'transparent',
border: '1px solid',
borderColor: wordWrap ? 'var(--color-border-primary)' : 'var(--color-accent-primary)',
borderRadius: '4px',
cursor: 'pointer',
transition: 'all 0.2s',
whiteSpace: 'nowrap'
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = 'var(--color-accent-primary)';
e.currentTarget.style.color = 'var(--color-accent-primary)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = wordWrap ? 'var(--color-border-primary)' : 'var(--color-accent-primary)';
e.currentTarget.style.color = wordWrap ? 'var(--color-text-secondary)' : 'var(--color-accent-primary)';
}}
title={wordWrap ? 'Disable word wrap (scroll horizontally)' : 'Enable word wrap'}
>
{wordWrap ? '⤢ Wrap' : '⇄ Scroll'}
</button>
</div>
{/* Content area */}
{isLoading ? (
<div
style={{
padding: '16px',
fontFamily: 'var(--font-terminal)',
fontSize: '12px',
color: 'var(--color-text-secondary)'
}}
>
Loading preview...
</div>
) : (
<div style={{ position: 'relative', flex: 1, overflow: 'hidden' }}>
<pre
ref={preRef}
style={preStyle}
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
)}
</div>
);
}