import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import React, { useEffect, useState, useRef, useCallback } from "react"; import { getNoteContentUrl } from "../api/notesService"; import SecurePdfViewer from "../components/SecurePdfViewer"; // <--- Imported new component import { Upload, Menu, X, Send, Loader2, FileText, MessageSquare, GripVertical, Trash2, Edit2, Check, Brain, } from "lucide-react"; import { fetchNotes, uploadNote, // fetchNoteBlob, // Not needed as the component handles the fetch createChatSession, streamChatRequest, fetchChatHistory, fetchSessions, deleteNote, renameNote, type Note, type ChatMessage, } from "../api/notesService"; const Notes: React.FC = () => { // --- UI State --- const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [isChatOpen, setIsChatOpen] = useState(true); const [isUploading, setIsUploading] = useState(false); const fileInputRef = useRef(null); // --- Resizable Chat State --- const [chatWidth, setChatWidth] = useState(450); const [isResizing, setIsResizing] = useState(false); // --- Data State --- const [notes, setNotes] = useState([]); const [currentNote, setCurrentNote] = useState(null); // const [pdfUrl, setPdfUrl] = useState(null); // <--- REMOVED // --- Edit/Rename State --- const [editingNoteId, setEditingNoteId] = useState(null); const [editName, setEditName] = useState(""); // --- Chat State --- const [messages, setMessages] = useState([]); const [inputMessage, setInputMessage] = useState(""); const [sessionId, setSessionId] = useState(null); const [isChatLoading, setIsChatLoading] = useState(false); // --- Auto Scroll Ref --- const messagesEndRef = useRef(null); useEffect(() => { loadNotes(); }, []); // --- Robust Auto-Scroll --- const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "instant", block: "end", }); }; useEffect(() => { scrollToBottom(); }, [messages, isChatLoading]); // --- Resizing Logic --- const startResizing = useCallback(() => setIsResizing(true), []); const stopResizing = useCallback(() => setIsResizing(false), []); const resize = useCallback( (mouseMoveEvent: MouseEvent) => { if (isResizing) { const newWidth = document.body.clientWidth - mouseMoveEvent.clientX; if (newWidth > 300 && newWidth < 800) setChatWidth(newWidth); } }, [isResizing] ); useEffect(() => { window.addEventListener("mousemove", resize); window.addEventListener("mouseup", stopResizing); return () => { window.removeEventListener("mousemove", resize); window.removeEventListener("mouseup", stopResizing); }; }, [resize, stopResizing]); const loadNotes = async () => { try { const data = await fetchNotes(); setNotes(data); } catch (error) { console.error("Failed to load notes", error); } }; const handleUploadClick = () => fileInputRef.current?.click(); const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setIsUploading(true); try { const newNote = await uploadNote(file); setNotes([newNote, ...notes]); handleNoteSelect(newNote); } catch (error) { console.error("Upload failed", error); alert("Failed to upload PDF"); } finally { setIsUploading(false); } }; // --- Handle Delete --- const handleDeleteNote = async (e: React.MouseEvent, noteId: number) => { e.stopPropagation(); if ( !window.confirm( "Are you sure you want to delete this note and its chat history?" ) ) return; try { await deleteNote(noteId); setNotes((prev) => prev.filter((n) => n.id !== noteId)); if (currentNote?.id === noteId) { setCurrentNote(null); // setPdfUrl(null); // <--- REMOVED setMessages([]); setSessionId(null); } } catch (error) { console.error("Failed to delete note", error); alert("Error deleting note"); } }; // --- Handle Rename --- const startEditing = (e: React.MouseEvent, note: Note) => { e.stopPropagation(); setEditingNoteId(note.id); setEditName(note.filename); }; const saveRename = async (e: React.MouseEvent) => { e.stopPropagation(); if (!editingNoteId || !editName.trim()) return; try { await renameNote(editingNoteId, editName); setNotes((prev) => prev.map((n) => n.id === editingNoteId ? { ...n, filename: editName } : n ) ); if (currentNote?.id === editingNoteId) { setCurrentNote((prev) => prev ? { ...prev, filename: editName } : null ); } setEditingNoteId(null); } catch (error) { console.error("Failed to rename", error); alert("Error renaming note"); } }; const cancelRename = (e: React.MouseEvent) => { e.stopPropagation(); setEditingNoteId(null); }; const handleNoteSelect = async (note: Note) => { if (editingNoteId === note.id) return; setCurrentNote(note); setMessages([]); setSessionId(null); setIsChatOpen(true); // --- PDF VIEWER LOGIC --- // We no longer set pdfUrl state or append the token here. // The SecurePdfViewer component handles the authenticated fetch internally // using the currentNote.id prop. try { const existingSessions = await fetchSessions(note.id); if (existingSessions.length > 0) { const lastSession = existingSessions[0]; setSessionId(lastSession.id); const history = await fetchChatHistory(lastSession.id); const formattedHistory: ChatMessage[] = history.map((msg: any) => ({ role: msg.role, content: msg.content, })); setMessages(formattedHistory); } else { const session = await createChatSession( note.id, `Chat - ${note.filename}` ); setSessionId(session.id); setMessages([ { role: "assistant", content: `Ready to chat about ${note.filename}!`, }, ]); } } catch (error) { console.error("Failed to init chat", error); } }; const handleSendMessage = async () => { if (!inputMessage.trim() || !sessionId) return; const userMsg = inputMessage; setInputMessage(""); setMessages((prev) => [...prev, { role: "user", content: userMsg }]); setIsChatLoading(true); setMessages((prev) => [...prev, { role: "assistant", content: "" }]); try { await streamChatRequest( sessionId, userMsg, (chunk) => { setMessages((prev) => { const newArr = [...prev]; const lastIndex = newArr.length - 1; newArr[lastIndex] = { ...newArr[lastIndex], content: newArr[lastIndex].content + chunk, }; return newArr; }); }, (err) => console.error("Stream error", err) ); } catch (e) { console.error("Chat Request Error", e); } finally { setIsChatLoading(false); } }; return ( // 1. Root Container: #434E78 Background, No Scrollbars
{/* --- Left Sidebar (#607B8F) --- */}
{isSidebarOpen && ( <>

My Notes

{/* Upload Button: #F7E396 (Highlight) */}

History

{notes.map((note) => (
handleNoteSelect(note)} className={`group relative p-3 rounded-lg cursor-pointer flex items-center justify-between transition-all border border-transparent ${ currentNote?.id === note.id ? "bg-[#434E78] border-[#F7E396] shadow-md" : "hover:bg-[#434E78]/50 text-gray-100" }`} > {editingNoteId === note.id ? (
setEditName(e.target.value)} onClick={(e) => e.stopPropagation()} className="flex-1 bg-[#434E78] text-white text-xs px-2 py-1 rounded border border-[#F7E396] outline-none" autoFocus />
) : ( <>
{note.filename}
)}
))}
)}
{/* --- Center: PDF Viewer (#434E78 Background) --- */}

{currentNote ? currentNote.filename : "Select a Note"}

{!isChatOpen && currentNote && ( )}
{currentNote ? (
{/* FIX: Use the SecurePdfViewer component, passing the noteId */}
) : (

Select a PDF from the sidebar to view

)}
{/* --- Resizable Chat Panel (#607B8F) --- */} {isChatOpen && (
)}
{isChatOpen && ( <>

AI Chat

{messages.length === 0 && (

Ask a question about this document...

)} {messages.map((msg, i) => (
{msg.role === "assistant" ? ( {msg.content} ) : ( msg.content )}
))} {isChatLoading && (
)} {/* Auto Scroll Anchor */}
setInputMessage(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleSendMessage()} placeholder="Type your question..." // Input: Dark Blue (#434E78) for contrast on Light Blue (#607B8F) panel className="flex-1 bg-[#434E78] text-white rounded-xl px-4 py-3 border border-transparent focus:border-[#F7E396] focus:ring-0 outline-none placeholder-gray-400 shadow-inner" disabled={!sessionId || isChatLoading} />
)}
); }; export default Notes;