import React, { useState, useRef } from 'react'; import { Moon, Sun, Upload, FileText, Download, Share2, MessageSquare, AlertCircle, LayoutGrid, List, Grid, ChevronLeft, ArrowRight, X, BrainCircuit } from 'lucide-react'; import Background from './components/Background'; import BentoCard from './components/BentoCard'; import ChatBot from './components/ChatBot'; import { BentoCardData, ChatMessage, AppSettings, ProcessingStatus } from './types'; import { generateBentoCards, expandBentoCard, chatWithDocument } from './services/geminiService'; const App: React.FC = () => { // Settings const [settings, setSettings] = useState({ apiKey: '', model: 'gemini-flash-latest', theme: 'light', layoutMode: 'auto', useThinking: false }); // Inputs const [file, setFile] = useState(null); // State const [view, setView] = useState<'input' | 'results'>('input'); const [cards, setCards] = useState([]); const [status, setStatus] = useState({ state: 'idle' }); const [paperContext, setPaperContext] = useState(''); // Stores the raw text/base64 const [paperTitle, setPaperTitle] = useState(''); // Chat const [isChatOpen, setIsChatOpen] = useState(false); const [chatHistory, setChatHistory] = useState([]); const [isChatProcessing, setIsChatProcessing] = useState(false); const gridRef = useRef(null); const toggleTheme = () => { const newTheme = settings.theme === 'dark' ? 'light' : 'dark'; setSettings(prev => ({ ...prev, theme: newTheme })); if (newTheme === 'dark') { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }; const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { setFile(e.target.files[0]); } }; const handleProcess = async () => { if (!settings.apiKey) { setStatus({ state: 'error', message: "Please enter your Gemini API Key." }); return; } if (!file) { setStatus({ state: 'error', message: "Please upload a PDF file." }); return; } setStatus({ state: 'reading', message: 'Reading PDF file...' }); try { let contentToProcess = ""; const contextTitle = file.name; // PDF UPLOAD FLOW contentToProcess = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const result = reader.result as string; const base64 = result.split(',')[1]; resolve(base64); }; reader.onerror = reject; reader.readAsDataURL(file); }); if (file.type !== 'application/pdf') { throw new Error("File must be a PDF."); } setPaperContext(contentToProcess); setPaperTitle(contextTitle); setStatus({ state: 'analyzing', message: settings.useThinking ? 'Thinking deeply about paper structure...' : 'Analyzing paper structure...' }); const generatedCards = await generateBentoCards( settings.apiKey, settings.model, contentToProcess, true, settings.useThinking ); setCards(generatedCards); setStatus({ state: 'complete', message: 'Visualization ready.' }); setView('results'); } catch (error: any) { console.error(error); setStatus({ state: 'error', message: error.message || "An error occurred processing the paper." }); } }; const handleReset = () => { setView('input'); setCards([]); setStatus({ state: 'idle' }); setChatHistory([]); setIsChatOpen(false); setPaperTitle(''); setPaperContext(''); setFile(null); }; const handleExpandCard = async (card: BentoCardData) => { if (card.expandedContent || card.isLoadingDetails) return; setCards(prev => prev.map(c => c.id === card.id ? { ...c, isLoadingDetails: true } : c)); try { const details = await expandBentoCard( settings.apiKey, settings.model, card.title, card.detailPrompt, paperContext, settings.useThinking ); setCards(prev => prev.map(c => c.id === card.id ? { ...c, expandedContent: details, isLoadingDetails: false } : c)); } catch (error) { setCards(prev => prev.map(c => c.id === card.id ? { ...c, isLoadingDetails: false, expandedContent: "Failed to load details." } : c)); } }; const handleResizeCard = (id: string, deltaCol: number, deltaRow: number) => { setCards(prev => prev.map(c => { if (c.id === id) { const newCol = Math.max(1, Math.min(4, c.colSpan + deltaCol)); const newRow = Math.max(1, Math.min(3, c.rowSpan + deltaRow)); return { ...c, colSpan: newCol, rowSpan: newRow }; } return c; })); }; const handleRateCard = (id: string, rating: number) => { setCards(prev => prev.map(c => c.id === id ? { ...c, rating } : c)); console.log(`Rated card ${id}: ${rating}`); }; const handleExport = async () => { if (!gridRef.current) return; // @ts-ignore if (window.html2canvas) { // @ts-ignore const canvas = await window.html2canvas(gridRef.current, { backgroundColor: settings.theme === 'dark' ? '#0f172a' : '#f8fafc', scale: 2, useCORS: true, logging: false }); const link = document.createElement('a'); link.download = 'bento-summary.png'; link.href = canvas.toDataURL(); link.click(); } else { alert("Export module not loaded yet."); } }; const handleShare = async () => { if (navigator.share) { try { await navigator.share({ title: 'Paper Summary', text: 'Check out this visual summary generated by PaperStack!', url: window.location.href }); } catch (err) { console.error("Share failed", err); } } else { alert("Sharing is not supported on this browser."); } }; const handleSendMessage = async (text: string) => { const newUserMsg: ChatMessage = { id: Date.now().toString(), role: 'user', text, timestamp: Date.now() }; setChatHistory(prev => [...prev, newUserMsg]); setIsChatProcessing(true); try { const responseText = await chatWithDocument(settings.apiKey, settings.model, chatHistory, text, paperContext || JSON.stringify(cards)); const newBotMsg: ChatMessage = { id: (Date.now()+1).toString(), role: 'model', text: responseText, timestamp: Date.now() }; setChatHistory(prev => [...prev, newBotMsg]); } catch (error) { const errorMsg: ChatMessage = { id: (Date.now()+1).toString(), role: 'system', text: "Failed to get response.", timestamp: Date.now() }; setChatHistory(prev => [...prev, errorMsg]); } finally { setIsChatProcessing(false); } }; return (
{/* Navbar */}
{/* Input View */} {view === 'input' && (

Visual Research Summaries

Upload your research paper (PDF) and instantly transform it into a rich, interactive Bento grid.
Powered by Gemini 3.0 Pro.

{/* API Key */}
KEY
setSettings({...settings, apiKey: e.target.value})} className="w-full bg-white dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-xl py-4 pl-16 pr-4 focus:ring-2 focus:ring-brand-500 outline-none transition-all text-sm font-mono" />
{/* Model Select & Thinking Toggle */}
{/* Thinking Toggle Switch */}
Thinking 32k Budget
{/* PDF Upload - Main Action */}
{/* Generate Button */} {status.state === 'error' && (
{status.message}
)}
)} {/* Results View */} {view === 'results' && (
{/* Controls */}

Summary Grid {settings.useThinking && ( Deep Thought )}

{/* Grid */}
{cards.map((card) => ( ))}
{/* Floating Chat Trigger */}
)}
setIsChatOpen(false)} messages={chatHistory} onSendMessage={handleSendMessage} isProcessing={isChatProcessing} />
); }; export default App;