700e3253fa
- Updated card styles in viewer.html and viewer-template.html to enhance padding, margins, and overall layout. - Introduced new header structure with left-aligned type and project name, and added view mode toggle buttons for facts and narrative. - Simplified content rendering logic in ObservationCard, allowing for toggling between facts and narrative. - Updated metadata display in ObservationCard, PromptCard, and SummaryCard to include formatted date and improved layout. - Removed unnecessary verbose content sections and streamlined the presentation of facts and narrative.
137 lines
5.1 KiB
TypeScript
137 lines
5.1 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Observation } from '../types';
|
|
import { formatDate } from '../utils/formatters';
|
|
|
|
interface ObservationCardProps {
|
|
observation: Observation;
|
|
}
|
|
|
|
// Helper to strip project root from file paths
|
|
function stripProjectRoot(filePath: string): string {
|
|
// Try to extract relative path by finding common project markers
|
|
const markers = ['/Scripts/', '/src/', '/plugin/', '/docs/'];
|
|
|
|
for (const marker of markers) {
|
|
const index = filePath.indexOf(marker);
|
|
if (index !== -1) {
|
|
// Keep the marker and everything after it
|
|
return filePath.substring(index + 1);
|
|
}
|
|
}
|
|
|
|
// Fallback: if path contains project name, strip everything before it
|
|
const projectIndex = filePath.indexOf('claude-mem/');
|
|
if (projectIndex !== -1) {
|
|
return filePath.substring(projectIndex + 'claude-mem/'.length);
|
|
}
|
|
|
|
// If no markers found, return basename or original path
|
|
const parts = filePath.split('/');
|
|
return parts.length > 3 ? parts.slice(-3).join('/') : filePath;
|
|
}
|
|
|
|
export function ObservationCard({ observation }: ObservationCardProps) {
|
|
const [showFacts, setShowFacts] = useState(false);
|
|
const [showNarrative, setShowNarrative] = useState(false);
|
|
const date = formatDate(observation.created_at_epoch);
|
|
|
|
// Parse JSON fields
|
|
const facts = observation.facts ? JSON.parse(observation.facts) : [];
|
|
const concepts = observation.concepts ? JSON.parse(observation.concepts) : [];
|
|
const filesRead = observation.files_read ? JSON.parse(observation.files_read).map(stripProjectRoot) : [];
|
|
const filesModified = observation.files_modified ? JSON.parse(observation.files_modified).map(stripProjectRoot) : [];
|
|
|
|
// Show summary when both are off
|
|
const showSummary = !showFacts && !showNarrative;
|
|
|
|
return (
|
|
<div className="card">
|
|
{/* Header with toggle buttons in top right */}
|
|
<div className="card-header">
|
|
<div className="card-header-left">
|
|
<span className={`card-type type-${observation.type}`}>
|
|
{observation.type}
|
|
</span>
|
|
<span className="card-project">{observation.project}</span>
|
|
</div>
|
|
<div className="view-mode-toggles">
|
|
{facts.length > 0 && (
|
|
<button
|
|
className={`view-mode-toggle ${showFacts ? 'active' : ''}`}
|
|
onClick={() => {
|
|
setShowFacts(!showFacts);
|
|
if (!showFacts) setShowNarrative(false); // Turn off narrative when turning on facts
|
|
}}
|
|
>
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<polyline points="9 11 12 14 22 4"></polyline>
|
|
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
|
</svg>
|
|
<span>facts</span>
|
|
</button>
|
|
)}
|
|
{observation.narrative && (
|
|
<button
|
|
className={`view-mode-toggle ${showNarrative ? 'active' : ''}`}
|
|
onClick={() => {
|
|
setShowNarrative(!showNarrative);
|
|
if (!showNarrative) setShowFacts(false); // Turn off facts when turning on narrative
|
|
}}
|
|
>
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
<polyline points="14 2 14 8 20 8"></polyline>
|
|
<line x1="16" y1="13" x2="8" y2="13"></line>
|
|
<line x1="16" y1="17" x2="8" y2="17"></line>
|
|
</svg>
|
|
<span>narrative</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Title */}
|
|
<div className="card-title">{observation.title || 'Untitled'}</div>
|
|
|
|
{/* Content based on toggle state */}
|
|
<div className="view-mode-content">
|
|
{showSummary && observation.subtitle && (
|
|
<div className="card-subtitle">{observation.subtitle}</div>
|
|
)}
|
|
{showFacts && facts.length > 0 && (
|
|
<ul className="facts-list">
|
|
{facts.map((fact: string, i: number) => (
|
|
<li key={i}>{fact}</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
{showNarrative && observation.narrative && (
|
|
<div className="narrative">
|
|
{observation.narrative}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Metadata with concepts and files inline */}
|
|
<div className="card-meta">
|
|
<span className="meta-date">#{observation.id} • {date}</span>
|
|
{concepts.length > 0 && (
|
|
<span className="meta-concepts">
|
|
• {concepts.join(', ')}
|
|
</span>
|
|
)}
|
|
{filesRead.length > 0 && (
|
|
<span className="meta-files">
|
|
• <span className="file-label">read:</span> {filesRead.join(', ')}
|
|
</span>
|
|
)}
|
|
{filesModified.length > 0 && (
|
|
<span className="meta-files">
|
|
• <span className="file-label">modified:</span> {filesModified.join(', ')}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|