import { useState, useEffect, useRef } from 'react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import { Upload, Send, X, FileText, MessageCircle, Key, CheckCircle, AlertCircle, Loader2, Zap } from 'lucide-react' // Generate session ID once const SESSION_ID = crypto.randomUUID() // Star Background Component function StarField() { return (
{Array.from({ length: 100 }, (_, i) => (
))}
) } // Main App export default function App() { // Load initial state from sessionStorage const [documents, setDocuments] = useState(() => { const saved = sessionStorage.getItem('pa_documents') return saved ? JSON.parse(saved) : [] }) const [messages, setMessages] = useState(() => { const saved = sessionStorage.getItem('pa_messages') return saved ? JSON.parse(saved) : [] }) const [input, setInput] = useState('') const [loading, setLoading] = useState(false) const [uploading, setUploading] = useState(false) const [dragOver, setDragOver] = useState(false) const [provider, setProvider] = useState(() => { return sessionStorage.getItem('pa_provider') || 'groq' }) const [models, setModels] = useState([]) const [selectedModel, setSelectedModel] = useState(() => { return sessionStorage.getItem('pa_model') || '' }) const [loadingModels, setLoadingModels] = useState(false) const [apiKey, setApiKey] = useState(() => { return sessionStorage.getItem('pa_apikey') || '' }) const [connectionStatus, setConnectionStatus] = useState(null) const [apiKeyError, setApiKeyError] = useState(null) const messagesEndRef = useRef(null) const fileInputRef = useRef(null) // Test connection handler const testConnection = async () => { if (!apiKey) return setConnectionStatus('testing') setApiKeyError(null) try { const res = await fetch('/assistant/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider, api_key: apiKey }) }) const data = await res.json() if (data.models?.length > 0) { setModels(data.models) setSelectedModel(data.models[0].id) setConnectionStatus('success') localStorage.setItem(`${provider}_api_key`, apiKey) } else { setConnectionStatus('error') setApiKeyError('No models found. Check your API key.') } } catch (err) { setConnectionStatus('error') setApiKeyError('Connection failed: ' + err.message) } } // Persist to sessionStorage useEffect(() => { sessionStorage.setItem('pa_messages', JSON.stringify(messages)) }, [messages]) useEffect(() => { sessionStorage.setItem('pa_documents', JSON.stringify(documents)) }, [documents]) useEffect(() => { sessionStorage.setItem('pa_provider', provider) }, [provider]) useEffect(() => { if (selectedModel) sessionStorage.setItem('pa_model', selectedModel) }, [selectedModel]) useEffect(() => { if (apiKey) sessionStorage.setItem('pa_apikey', apiKey) }, [apiKey]) // Reset models when provider changes useEffect(() => { setModels([]) setSelectedModel('') setConnectionStatus(null) setApiKeyError(null) }, [provider]) // Scroll to bottom on new messages useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages]) // API helpers const headers = { 'X-Session-ID': SESSION_ID, 'Content-Type': 'application/json' } // Upload document const handleUpload = async (file) => { const formData = new FormData() formData.append('file', file) setUploading(true) try { const res = await fetch('/assistant/upload', { method: 'POST', headers: { 'X-Session-ID': SESSION_ID }, body: formData }) if (!res.ok) { const error = await res.json() alert(error.detail || 'Upload failed') return } const doc = await res.json() setDocuments(prev => [...prev, doc]) } catch (err) { console.error('Upload error:', err) alert('Failed to upload file') } finally { setUploading(false) } } // Delete document const handleDelete = async (docId) => { try { await fetch(`/assistant/documents/${docId}`, { method: 'DELETE', headers }) setDocuments(prev => prev.filter(d => d.id !== docId)) } catch (err) { console.error('Delete error:', err) } } // Send message const handleSend = async () => { if (!input.trim() || loading) return const userMessage = input.trim() setInput('') setMessages(prev => [...prev, { role: 'user', content: userMessage }]) setLoading(true) try { const res = await fetch('/assistant/chat', { method: 'POST', headers, body: JSON.stringify({ message: userMessage, provider: provider, model: selectedModel || null, api_key: apiKey || null }) }) const data = await res.json() setMessages(prev => [...prev, { role: 'assistant', content: data.answer, sources: data.sources }]) } catch (err) { console.error('Chat error:', err) setMessages(prev => [...prev, { role: 'assistant', content: 'Sorry, something went wrong. Please try again.' }]) } finally { setLoading(false) } } // Handle key press const handleKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSend() } } // Drag and drop handlers const handleDragOver = (e) => { e.preventDefault() setDragOver(true) } const handleDragLeave = () => setDragOver(false) const handleDrop = (e) => { e.preventDefault() setDragOver(false) const file = e.dataTransfer.files[0] if (file) handleUpload(file) } const handleFileSelect = (e) => { const file = e.target.files[0] if (file) handleUpload(file) } return ( <>
{/* Header */}

🤖 Portfolio Assistant

Upload your documents and ask questions

{/* Settings Panel */}
{/* Provider Card */}
Provider
{/* API Key Card */}
API Key {connectionStatus === 'success' && ( Connected )}
{ setApiKey(e.target.value) setConnectionStatus(null) }} placeholder={`Paste your ${provider === 'groq' ? 'Groq' : 'HuggingFace'} API key`} /> {apiKeyError ? ( {apiKeyError} ) : ( 0 ? 'success' : ''}`}> {models.length > 0 ? `✓ ${models.length} models loaded` : 'Models will load when you test connection'} )} 🔑 Get your {provider === 'groq' ? 'Groq' : 'HuggingFace'} API key →
{/* Upload Zone */}
fileInputRef.current?.click()} >

{uploading ? 'Uploading...' : 'Drag & drop files here or click to browse'}

Supported: PDF, DOCX, TXT, Markdown, JSON

{/* Document List */} {documents.length > 0 && (
{documents.map(doc => (
{doc.type} {doc.filename}
))}
)} {/* Chat Container */}
{messages.length === 0 ? (

Start a conversation

Upload documents above, then ask questions about them.

) : ( messages.map((msg, idx) => (
{msg.role === 'assistant' ? ( <> {msg.content} {msg.sources?.length > 0 && (
Sources: {[...new Set(msg.sources.map(s => s.source))].map((source, i) => ( {source} ))}
)} ) : ( msg.content )}
)) )} {loading && (
)}
{/* Input */}