Spaces:
Paused
Paused
| 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> | |
| ); | |
| }; | |