Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | 3x 3x 3x 2x | import React from 'react';
import { useLabState } from '../useLabState';
import { BookOpen, X, PenLine, PlayCircle, Trash2 } from 'lucide-react';
import { api } from '../api';
import { vscodeNative } from '../vscodeNative';
import MarkdownText from './MarkdownText';
export default function JournalSidebar() {
const {
journalEntries, setJournalEntries, journalInput, setJournalInput,
setJournalOpen, selectedArtifact, setSelectedArtifact, messages,
} = useLabState();
const confirmDeleteEntry = async (entry) => {
const result = await vscodeNative.confirm('Delete journal entry?', 'Delete', 'Cancel');
if (result === 'Delete') {
await api.deleteJournalEntry(entry.id);
setJournalEntries(prev => prev.filter(e => e.id !== entry.id));
}
};
return (
<div className="w-80 bg-lab-bg-secondary/95 border-l border-lab-border flex flex-col overflow-hidden">
<div className="flex items-center justify-between p-4 border-b border-lab-border bg-lab-bg-secondary/80">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-lab-warning" />
<h3 className="text-xs font-bold tracking-tight text-lab-fg">Research Journal</h3>
<span className="text-[9px] text-lab-fg-muted font-mono">{journalEntries.length}</span>
</div>
<button onClick={() => setJournalOpen(false)} className="p-1 text-lab-fg-muted hover:text-lab-fg"><X className="w-3.5 h-3.5" /></button>
</div>
<div className="flex-1 overflow-y-auto p-3 space-y-2 custom-scrollbar">
{journalEntries.length === 0 && (
<div className="flex flex-col items-center justify-center py-12 text-center">
<PenLine className="w-8 h-8 text-lab-fg-muted mb-3" />
<p className="text-[10px] text-lab-fg-muted">No entries yet.</p>
<p className="text-[9px] text-lab-fg-muted mt-1">The AI will log observations as it works, or add your own notes below.</p>
</div>
)}
{journalEntries.map(entry => (
<div key={entry.id} className="group p-3 bg-lab-bg/50 border border-lab-border rounded-xl space-y-1.5 hover:border-lab-border transition-colors">
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-1.5 min-w-0">
<span className={`shrink-0 px-1.5 py-0.5 rounded text-[7px] font-bold uppercase ${entry.author === 'agent' ? 'bg-lab-warning/10 text-lab-warning border border-amber-500/20' : 'bg-lab-accent/10 text-lab-accent border border-lab-accent/20'}`}>
{entry.author === 'agent' ? 'AI' : 'You'}
</span>
<div className="text-[11px] font-bold text-lab-fg truncate">
<MarkdownText content={entry.title || 'Untitled'} />
</div>
</div>
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
{entry.messageId && (
<button onClick={() => { const msg = messages.find(m => m.id === entry.messageId); if (msg) setSelectedArtifact(msg); }} className="p-0.5 text-lab-fg-muted hover:text-lab-warning" title="Jump to simulation">
<PlayCircle className="w-3 h-3" />
</button>
)}
<button onClick={() => confirmDeleteEntry(entry)} className="p-0.5 text-lab-fg-muted hover:text-lab-error" title="Delete">
<Trash2 className="w-3 h-3" />
</button>
</div>
</div>
{entry.phase && (
<span className="inline-block px-1.5 py-0.5 bg-lab-bg-elevated text-[8px] font-bold text-lab-fg-secondary rounded uppercase">{entry.phase}</span>
)}
<div className="text-[10px] text-lab-fg-secondary leading-relaxed">
<MarkdownText content={entry.note || ''} />
</div>
<span className="text-[8px] text-lab-fg-muted">{new Date(entry.timestamp).toLocaleString()}</span>
</div>
))}
</div>
<div className="p-3 border-t border-lab-border bg-lab-bg-secondary/80">
<textarea value={journalInput} onChange={(e) => setJournalInput(e.target.value)} placeholder="Add a note..." className="w-full bg-lab-bg border border-lab-border rounded-lg py-2 px-3 text-[11px] focus:ring-1 focus:ring-amber-500/50 focus:border-amber-500/50 outline-none h-16 resize-none placeholder:text-lab-fg-muted" />
<button onClick={async () => {
if (!journalInput.trim()) return;
const saved = await api.addJournalEntry({ title: 'Note', note: journalInput.trim(), author: 'user', messageId: selectedArtifact?.id || null, timestamp: Date.now() });
setJournalEntries(prev => [...prev, saved]);
setJournalInput('');
}} disabled={!journalInput.trim()} className="mt-2 w-full py-2 bg-amber-600 hover:bg-lab-warning disabled:opacity-30 rounded-lg text-[10px] font-bold text-white flex items-center justify-center gap-1.5 transition-all active:scale-95">
<PenLine className="w-3 h-3" /> Add Note
</button>
</div>
</div>
);
}
|