- Add ComparePane.tsx: side-by-side AI response comparison - Add Memory.tsx: memory management page with CRUD UI - Add McpPermissionDialog.tsx: MCP tool permission approval dialog - Add McpResponseDisplay.tsx: MCP response visualization - Add MessageBoundary.tsx + MessageListErrorBoundary.tsx: error resilience - Add EmptyState.tsx: contextual empty state component - Add KeyboardShortcutsDialog.tsx: keyboard shortcut reference - Add message-parts/: ActionRow, CompactCard, MistakeRecoverySentinel, ReasoningBlock, SendToTerminalMenu, StatsLine, SummaryCard - Add useDraftPersistence.ts: draft message persistence hook - Add useTerminals.ts: terminal session management hook - Add keyboard-shortcuts.ts + tool-utils.ts: shared utilities - Extend components: ChatInput, MessageBubble, MessageList, Workspace, panes - Extend hooks: useTerminalSocket, useSessionStream test suite - Update pages: Home, Project — workspace layout and session flow
63 lines
2.4 KiB
TypeScript
63 lines
2.4 KiB
TypeScript
import { useState } from 'react';
|
|
import { ChevronDown, ChevronRight, Copy, Check } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import type { Message } from '@/api/types';
|
|
import { MarkdownRenderer } from '@/components/MarkdownRenderer';
|
|
|
|
// v1.11 anchored rolling summary. Inserted by services/compaction.ts as a
|
|
// role='assistant', summary=true row. Distinct from legacy CompactCard
|
|
// (which renders the kind='compact' system rows produced by v1.10 /compact).
|
|
// Collapsed by default; header shows the timestamp; body renders the
|
|
// summary markdown when expanded. Copy button matches CompactCard's affordance.
|
|
export function SummaryCard({ message }: { message: Message }) {
|
|
const [expanded, setExpanded] = useState(false);
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
// Use finished_at when available (that's when the summary actually landed);
|
|
// fall back to created_at for any row missing it. Both are ISO strings.
|
|
const ts = message.finished_at ?? message.created_at;
|
|
const headerTs = ts ? new Date(ts).toLocaleString() : '';
|
|
|
|
async function handleCopy() {
|
|
try {
|
|
await navigator.clipboard.writeText(message.content);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 1200);
|
|
toast.success('Summary copied to clipboard');
|
|
} catch {
|
|
toast.error('Copy failed');
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-lg border border-primary/30 bg-primary/5 text-sm">
|
|
<div className="flex items-center gap-2 px-3 py-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => setExpanded(!expanded)}
|
|
className="flex items-center gap-1.5 flex-1 min-w-0 text-left text-muted-foreground hover:text-foreground"
|
|
>
|
|
{expanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
<span className="text-xs font-medium truncate">
|
|
Compacted summary — {headerTs}
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => void handleCopy()}
|
|
className="p-1 rounded hover:bg-muted text-muted-foreground"
|
|
aria-label="Copy summary"
|
|
title="Copy summary"
|
|
>
|
|
{copied ? <Check size={12} /> : <Copy size={12} />}
|
|
</button>
|
|
</div>
|
|
{expanded && (
|
|
<div className="px-3 pb-3 text-xs leading-relaxed border-t pt-2">
|
|
<MarkdownRenderer content={message.content} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|