/** * AskView.jsx - AI Chat Interface (Complete Rebuild + Responsive) * Features: Collapsible sidebars, mobile-friendly, smooth transitions */ const { useState, useEffect } = React; const { SessionSidebar, ChatArea, MediaPanel, NoteSelectionModal } = window; function AskView({ user }) { const { t } = useTranslation(); const token = localStorage.getItem('token'); const API_URL = window.API_URL || '/api'; // State const [sessions, setSessions] = useState([]); const [activeSessionId, setActiveSessionId] = useState(null); const [messages, setMessages] = useState([]); const [noteContext, setNoteContext] = useState([]); const [mediaList, setMediaList] = useState([]); const [loading, setLoading] = useState(false); const [showNoteModal, setShowNoteModal] = useState(false); const [isSending, setIsSending] = useState(false); const [pendingFiles, setPendingFiles] = useState([]); // Files selected before chat creation const [showSessionSidebar, setShowSessionSidebar] = useState(true); const [showContextPanel, setShowContextPanel] = useState(true); // Responsive: Hide sidebars on mobile by default useEffect(() => { const handleResize = () => { if (window.innerWidth < 768) { setShowSessionSidebar(false); setShowContextPanel(false); } else if (window.innerWidth < 1024) { setShowSessionSidebar(true); setShowContextPanel(false); } else { setShowSessionSidebar(true); setShowContextPanel(true); } }; handleResize(); // Initial check window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // Load sessions on mount useEffect(() => { fetchSessions(); }, []); // Load session details when active session changes useEffect(() => { if (activeSessionId) { fetchSessionDetails(activeSessionId); } else { setMessages([]); setNoteContext([]); setMediaList([]); } }, [activeSessionId]); // ==================== API FUNCTIONS ==================== const fetchSessions = async () => { try { const response = await fetch(`${API_URL}/ask/sessions`, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('Failed to load sessions'); const data = await response.json(); setSessions(data); } catch (error) { console.error('Error loading sessions:', error); alert('Failed to load chat sessions'); } }; const fetchSessionDetails = async (sessionId) => { try { const response = await fetch(`${API_URL}/ask/sessions/${sessionId}`, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('Failed to load session'); const data = await response.json(); setMessages(data.messages || []); setNoteContext(data.notes || []); setMediaList(data.media || []); } catch (error) { console.error('Error loading session:', error); alert('Failed to load session details'); } }; const uploadFilesToSession = async (sessionId, files) => { const uploadPromises = files.map(file => { const formData = new FormData(); formData.append('file', file); formData.append('session_id', sessionId); return fetch(`${API_URL}/ask/upload?session_id=${sessionId}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }).then(response => { if (!response.ok) throw new Error(`Upload failed for ${file.name}`); return response.json(); }).then(data => { setMediaList(prev => [...prev, { id: data.media_id, type: data.media_type, filename: data.filename, filepath: data.filepath }]); return data; }); }); try { await Promise.all(uploadPromises); } catch (error) { console.error('Error uploading pending files:', error); alert('Failed to upload some files'); } }; const handleSendMessage = async (messageText, noteIds = []) => { if (!messageText.trim()) return; try { setIsSending(true); let sessionId = activeSessionId; // If no active session, create one first if (!sessionId) { const response = await fetch(`${API_URL}/ask/sessions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ title: messageText.substring(0, 50), note_ids: [] }) }); if (response.ok) { const data = await response.json(); sessionId = data.id; // Backend returns "id", not "session_id" setActiveSessionId(sessionId); // Upload pending files to the new session if (pendingFiles.length > 0) { await uploadFilesToSession(sessionId, pendingFiles); setPendingFiles([]); // Clear pending files } await fetchSessions(); // Refresh sessions list } else { throw new Error('Failed to create new session'); } } // Send the message const response = await fetch(`${API_URL}/ask/send`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ session_id: sessionId, message: messageText, note_ids: noteIds.length > 0 ? noteIds : noteContext.map(n => n.id) }) }); if (response.ok) { const data = await response.json(); setMessages(data.messages || []); } else { throw new Error('Failed to send message'); } } catch (error) { console.error('Send message error:', error); alert('Failed to send message'); } finally { setIsSending(false); } }; const handleDeleteSession = async (sessionId) => { if (!confirm('Delete this chat session?')) return; try { const response = await fetch(`${API_URL}/ask/sessions/${sessionId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('Failed to delete'); // If deleting active session, clear it if (activeSessionId === sessionId) { setActiveSessionId(null); setMessages([]); setNoteContext([]); setPendingFiles([]); } // Refresh sessions list await fetchSessions(); } catch (error) { console.error('Error deleting session:', error); alert('Failed to delete session'); } }; const handleNotesSelected = async (noteIds) => { if (!noteIds || noteIds.length === 0) { alert('Please select at least one note to start a chat'); return; } // Fetch full note details for the selected IDs try { const response = await fetch(`${API_URL}/notes`, { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); const selectedNotes = (data.notes || []).filter(note => noteIds.includes(note.id)); setNoteContext(selectedNotes); setShowNoteModal(false); // Start fresh chat with selected notes setActiveSessionId(null); setMessages([]); } catch (error) { console.error('Error fetching note details:', error); alert('Failed to load note details'); } }; const handleAddNotes = () => { setShowNoteModal(true); }; const handleNewChat = () => { // Clear active session and messages to start fresh setActiveSessionId(null); setMessages([]); setNoteContext([]); setMediaList([]); }; const handleRenameSession = (sessionId, newTitle) => { // Update session title in local state setSessions(prevSessions => prevSessions.map(s => s.id === sessionId ? { ...s, title: newTitle } : s ) ); }; const handleFileUpload = async (files) => { // If session exists, upload immediately if (activeSessionId) { await uploadFilesToSession(activeSessionId, files); } else { // No session yet, store files temporarily setPendingFiles(prev => [...prev, ...files]); } }; // ==================== RENDER ==================== return (
{/* Header Bar - Simplified */}

💬 {t('navigation.ask')}

{t('ask.subtitle')}

{/* Main Content Row (Flex, Overflow Hidden) */}
{/* Left Toggle Button - Always Visible */} {/* Right Toggle Button - Always Visible */} {/* Left: Session Sidebar */}
{/* Center: Chat Area */}
setShowNoteModal(true)} onFileUpload={handleFileUpload} pendingFiles={pendingFiles} onRemovePendingFile={(index) => setPendingFiles(prev => prev.filter((_, i) => i !== index))} t={t} />
{/* Right: Media Panel (replaces Context Panel) */}
{showContextPanel && ( )}
{/* Note Selection Modal */} {showNoteModal && ( setShowNoteModal(false)} t={t} /> )}
); } window.AskView = AskView;