import { useState, useEffect } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { useAuth } from '@clerk/clerk-react' import { API_BASE } from '../config.js' async function* parseSseStream(response) { const reader = response.body.getReader() const decoder = new TextDecoder() let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n') buffer = lines.pop() ?? '' let eventType = null for (const line of lines) { if (line.startsWith('event: ')) eventType = line.slice(7).trim() else if (line.startsWith('data: ')) { const raw = line.slice(6).trim() if (raw) { try { yield { type: eventType, data: JSON.parse(raw) } } catch {} } eventType = null } } } } export default function NotebookGuide({ initialGuide, onGuideReady }) { const { getToken } = useAuth() const [guide, setGuide] = useState(initialGuide || null) const [streaming, setStreaming] = useState('') const [status, setStatus] = useState('idle') const [error, setError] = useState('') useEffect(() => { if (initialGuide) setGuide(initialGuide) }, [initialGuide]) const generate = async () => { setStatus('generating') setStreaming('') setError('') try { const token = await getToken() const res = await fetch(`${API_BASE}/notebook/generate`, { method: 'POST', headers: token ? { Authorization: `Bearer ${token}` } : {}, }) if (!res.ok) throw new Error(`Server error ${res.status}`) for await (const event of parseSseStream(res)) { if (event.type === 'token') setStreaming(prev => prev + event.data.content) else if (event.type === 'done') { setGuide(event.data.guide) setStreaming('') setStatus('done') onGuideReady?.(event.data.guide) } else if (event.type === 'error') throw new Error(event.data.message) } } catch (err) { setError(err.message) setStatus('error') } } const isGenerating = status === 'generating' return (

Notebook Guide

{isGenerating && ( Generating… )} {isGenerating ? ( ) : ( )} {guide ? 'Regenerate' : 'Generate'}
{!guide && !streaming && !isGenerating && (

Click Generate to create an AI-powered overview of all your documents.

)} {isGenerating && streaming && (

{streaming}

)} {guide && !isGenerating && ( {guide.overview && (

Overview

{guide.overview}

)} {guide.themes?.length > 0 && (

Key Themes

{guide.themes.map((t, i) => ( {t} ))}
)} {guide.doc_onelines && Object.keys(guide.doc_onelines).length > 0 && (

Documents

{Object.entries(guide.doc_onelines).map(([src, desc]) => (
{src} — {desc}
))}
)}
)}
{error &&

{error}

}
) }