Plandex / components /NotesPanel.tsx
AUXteam's picture
Upload folder using huggingface_hub
c59bca4 verified
import React, { useState } from 'react';
import { Plus, Sparkles, MoreVertical, Trash2, Edit2, Check, LayoutGrid, Rocket, Github, Loader2, RefreshCcw, MessageSquarePlus } from 'lucide-react';
import { Note } from '../types';
interface NotesPanelProps {
notes: Note[];
onAddNote: () => void;
onUpdateNote: (note: Note) => void;
onDeleteNote: (id: string) => void;
onUseTemplate: (note: Note) => void;
onGenerateSummary: (note: Note) => void;
queue?: string[];
hfTimerQueue?: { templates: string[], nextExecutionTime: number };
onHuggingFaceCue?: () => void;
onRefreshTemplates?: () => void;
isRefreshing?: boolean;
onOpenNewSession?: () => void;
}
export const NotesPanel: React.FC<NotesPanelProps> = ({
notes,
onAddNote,
onUpdateNote,
onDeleteNote,
onUseTemplate,
onGenerateSummary,
queue = [],
hfTimerQueue,
onHuggingFaceCue,
onRefreshTemplates,
isRefreshing,
onOpenNewSession
}) => {
const [editingId, setEditingId] = useState<string | null>(null);
return (
<div className="flex flex-col h-full bg-gray-50 border-r border-gray-200">
<div className="p-4 border-b border-gray-200 bg-white sticky top-0 z-10">
<div className="flex justify-between items-center mb-4">
<h2 className="font-semibold text-gray-800 text-lg">Message Templates</h2>
<div className="flex items-center gap-2">
{onRefreshTemplates && (
<button
onClick={onRefreshTemplates}
disabled={isRefreshing}
className="p-2 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 rounded-full transition-all"
title="Sync Templates"
>
<RefreshCcw className={`w-4 h-4 ${isRefreshing ? 'animate-spin' : ''}`} />
</button>
)}
{onOpenNewSession && (
<button
onClick={onOpenNewSession}
className="p-2 bg-green-50 text-green-600 hover:bg-green-100 rounded-full transition-all"
title="New Session"
>
<MessageSquarePlus className="w-4 h-4" />
</button>
)}
<button
onClick={onAddNote}
className="p-2 bg-yellow-400 hover:bg-yellow-500 text-yellow-900 rounded-full shadow-sm transition-all"
title="New Template"
>
<Plus className="w-5 h-5" />
</button>
</div>
</div>
{/* HuggingFace Cue Button */}
{onHuggingFaceCue && (
<button
onClick={onHuggingFaceCue}
className="w-full bg-gradient-to-r from-indigo-600 to-violet-600 text-white p-2.5 rounded-xl text-sm font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-md mb-2"
>
<Rocket className="w-4 h-4" />
HF Deployment Cue
</button>
)}
{/* Queue Status */}
{hfTimerQueue && hfTimerQueue.templates.length > 0 && (
<div className="bg-indigo-50 border border-indigo-100 rounded-lg p-2 flex items-center gap-2 mb-2">
<div className="w-2 h-2 rounded-full bg-indigo-500 animate-pulse" />
<span className="text-[10px] font-bold text-indigo-700 uppercase tracking-wider">
Auto-Queue: {hfTimerQueue.templates.length} waiting (runs in {Math.max(0, Math.ceil((hfTimerQueue.nextExecutionTime - Date.now()) / 60000))} mins)
</span>
</div>
)}
{queue.length > 0 && (
<div className="bg-gray-100 rounded-lg p-2 flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
<span className="text-[10px] font-bold text-gray-500 uppercase tracking-wider">
Cue: {queue.length} templates pending
</span>
</div>
)}
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{/* Load Templates Button - Only show if no remote templates exist and handler is provided */}
{onRefreshTemplates && !notes.some(n => n.id.startsWith('github-')) && (
<div className="bg-white rounded-xl border-2 border-dashed border-gray-200 p-8 flex flex-col items-center justify-center text-center space-y-4 hover:border-indigo-300 transition-colors">
<div className="p-3 bg-indigo-50 text-indigo-600 rounded-full">
{isRefreshing ? <Loader2 className="w-8 h-8 animate-spin" /> : <Github className="w-8 h-8" />}
</div>
<div>
<h3 className="font-bold text-gray-800">No Remote Templates</h3>
<p className="text-sm text-gray-500 max-w-[200px] mx-auto">
Connect to GitHub to load your shared message templates.
</p>
</div>
<button
onClick={onRefreshTemplates}
disabled={isRefreshing}
className="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-lg font-semibold text-sm transition-all shadow-sm flex items-center gap-2"
>
{isRefreshing ? 'Loading...' : 'Load Templates'}
</button>
</div>
)}
{notes.map((note) => (
<div
key={note.id}
className={`group relative bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-md transition-all duration-200 ${
editingId === note.id ? 'ring-2 ring-indigo-500 border-transparent' : ''
}`}
>
<div className="p-4">
{editingId === note.id ? (
<div className="space-y-2">
<input
type="text"
value={note.title}
onChange={(e) => onUpdateNote({ ...note, title: e.target.value })}
className="w-full font-bold text-gray-800 bg-transparent outline-none placeholder-gray-400"
placeholder="Title"
autoFocus
/>
<textarea
value={note.content}
onChange={(e) => onUpdateNote({ ...note, content: e.target.value })}
className="w-full text-sm text-gray-600 bg-transparent outline-none resize-none h-24 placeholder-gray-400"
placeholder="Type your message template..."
/>
<div className="flex justify-end gap-2 pt-2">
<button
onClick={() => setEditingId(null)}
className="p-1.5 text-green-600 hover:bg-green-50 rounded-full"
title="Done Editing"
>
<Check className="w-4 h-4" />
</button>
</div>
</div>
) : (
<>
<h3 className="font-bold text-gray-800 mb-2 truncate">{note.title || 'Untitled'}</h3>
<p className="text-sm text-gray-600 line-clamp-3 mb-3 whitespace-pre-wrap">
{note.content || 'Empty note'}
</p>
{/* AI Summary Section */}
{note.summary ? (
<div className="bg-indigo-50 rounded-lg p-2 mb-3">
<div className="flex items-center gap-1.5 text-xs font-semibold text-indigo-700 mb-1">
<Sparkles className="w-3 h-3" />
<span>AI Summary</span>
</div>
<div className="text-xs text-indigo-600 whitespace-pre-wrap leading-relaxed">
{note.summary}
</div>
</div>
) : (
<button
onClick={(e) => {
e.stopPropagation();
onGenerateSummary(note);
}}
className="text-xs flex items-center gap-1 text-gray-400 hover:text-indigo-600 transition-colors mb-3"
>
<Sparkles className="w-3 h-3" />
Generate Summary
</button>
)}
<div className="flex items-center justify-between mt-2 pt-2 border-t border-gray-100 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="flex gap-1">
<button
onClick={(e) => {
e.stopPropagation();
setEditingId(note.id);
}}
className="p-1.5 text-gray-400 hover:text-indigo-600 hover:bg-gray-100 rounded-lg"
title="Edit"
>
<Edit2 className="w-4 h-4" />
</button>
<button
onClick={(e) => {
e.stopPropagation();
onDeleteNote(note.id);
}}
className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-gray-100 rounded-lg"
title="Delete"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<button
onClick={() => onUseTemplate(note)}
className="text-xs font-medium bg-gray-900 text-white px-3 py-1.5 rounded-lg hover:bg-black transition-colors"
>
Use Template
</button>
</div>
</>
)}
</div>
</div>
))}
</div>
</div>
);
};