import React, { useState, useEffect, useMemo } from 'react'; import { motion, AnimatePresence } from 'motion/react'; import { Zap, Search, ArrowRight, History, Plus, Wind, PlusCircle, FileText, ChevronRight, Maximize2 } from 'lucide-react'; import { WaveCanvas } from './WaveCanvas'; import { BrainstormSession, BrainstormIdea, Note } from '../../types'; import { generateBrainstormWave, generateExpansion, getEmbedding, cosineSimilarity } from '../../services/geminiService'; interface BrainstormViewProps { notes: Note[]; onConvertNote: (idea: BrainstormIdea) => void; } export const BrainstormView: React.FC = ({ notes, onConvertNote }) => { const [seedInput, setSeedInput] = useState(''); const [isGenerating, setIsGenerating] = useState(false); const [error, setError] = useState(null); const [sessions, setSessions] = useState([]); const [activeSessionId, setActiveSessionId] = useState(null); const [ideas, setIdeas] = useState([]); const [selectedIdeaId, setSelectedIdeaId] = useState(null); useEffect(() => { fetch('/api/brainstorm/sessions') .then(res => res.json()) .then(data => setSessions(data)) .catch(err => console.error("Failed to load sessions", err)); }, []); useEffect(() => { if (activeSessionId) { fetch(`/api/brainstorm/${activeSessionId}`) .then(res => res.json()) .then(data => { if (data.ideas) { setIdeas(prev => { const filtered = prev.filter(i => i.sessionId !== activeSessionId); return [...filtered, ...data.ideas]; }); } }) .catch(err => console.error("Failed to load ideas", err)); } }, [activeSessionId]); const activeSession = useMemo(() => sessions.find(s => s.id === activeSessionId), [activeSessionId, sessions]); const activeIdeas = useMemo(() => ideas.filter(i => i.sessionId === activeSessionId), [activeSessionId, ideas]); const selectedIdea = useMemo(() => ideas.find(i => i.id === selectedIdeaId), [selectedIdeaId, ideas]); useEffect(() => { const handleRemoteStart = (e: any) => { if (e.detail?.seed) { handleStartBrainstorm(e.detail.seed, e.detail.sourceNoteId); } }; window.addEventListener('start-brainstorm', handleRemoteStart); return () => window.removeEventListener('start-brainstorm', handleRemoteStart); }, [notes]); const handleStartBrainstorm = async (seed: string, sourceNoteId?: string) => { if (!seed.trim()) return; setIsGenerating(true); setError(null); try { // 1. Create session on backend const sessionRes = await fetch('/api/brainstorm/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ seedIdea: seed, sourceNoteId }) }); const session = await sessionRes.json(); if (!sessionRes.ok) throw new Error(session.error || "Failed to create session"); setSessions(prev => [session, ...prev]); setActiveSessionId(session.id); setSeedInput(''); // 2. Generate waves in frontend concurrently const contextSummaries = notes.slice(0, 5).map(n => n.title).join(', '); const wavePromises = [1, 2, 3].map(async (num) => { try { const generated = await generateBrainstormWave(seed, num, contextSummaries); return generated.map(g => ({ ...g, waveNumber: num })); } catch (e) { console.error(`Wave ${num} failed`, e); return []; } }); const wavesResults = await Promise.all(wavePromises); const allNewIdeas = wavesResults.flat(); if (allNewIdeas.length === 0) { throw new Error("No ideas were generated. Gemini might be shy today."); } // 3. Save ideas to backend const ideasRes = await fetch(`/api/brainstorm/${session.id}/ideas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ideas: allNewIdeas }) }); const savedIdeas = await ideasRes.json(); setIdeas(prev => [...prev, ...savedIdeas]); } catch (err: any) { console.error("Brainstorm failed:", err); setError(err.message || "An unexpected error occurred while brainstorming."); } finally { setIsGenerating(false); } }; const updateIdea = async (ideaId: string, updates: Partial) => { try { const res = await fetch(`/api/brainstorm/ideas/${ideaId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }); const updated = await res.json(); setIdeas(prev => prev.map(i => i.id === ideaId ? updated : i)); } catch (err) { console.error("Update failed", err); } }; const handleDeepenIdea = async (idea: BrainstormIdea) => { setIsGenerating(true); try { const generated = await generateExpansion(idea.title, idea.description); const newIdeasData = generated.map(g => ({ ...g, waveNumber: Math.min(idea.waveNumber + 1, 3), parentIdeaId: idea.id })); const res = await fetch(`/api/brainstorm/${idea.sessionId}/ideas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ideas: newIdeasData }) }); const savedIdeas = await res.json(); setIdeas(prev => [...prev, ...savedIdeas]); } catch (err) { console.error("Deepen failed", err); setError("Failed to expand this idea."); } finally { setIsGenerating(false); } }; const handleDismissIdea = (ideaId: string) => { updateIdea(ideaId, { status: 'dismissed' }); setSelectedIdeaId(null); }; const handleConvertToNote = (idea: BrainstormIdea) => { updateIdea(idea.id, { status: 'converted' }); onConvertNote(idea); }; return (
{/* Header / Start area */}
{/* Architectural Grid Background */}

Waves of Thought

Unfold dimensions of potentiality

setSeedInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleStartBrainstorm(seedInput)} placeholder="Enter a concept to unfold..." className={`w-full relative bg-white dark:bg-[#1A1A1A] border-2 rounded-2xl px-8 py-7 pr-20 outline-none transition-all text-2xl font-serif italic text-ink dark:text-dark-ink shadow-sm group-hover:shadow-md ${error ? 'border-rose-400 focus:ring-rose-100 shadow-rose-100' : 'border-border/40 focus:border-ochre/40 focus:ring-4 focus:ring-ochre/5'}`} />
{error && (

Obstruction detected

{error}
)} {isGenerating && !error && (
{[0.2, 0.4, 0.6].map((d, i) => ( ))}
Gemini is harvesting seeds of thought from the digital ether...
)}
{/* Main Canvas Area */}
{activeSession ? ( updateIdea(id, { position: pos })} selectedNodeId={selectedIdeaId} relatedNotes={notes} /> ) : (

The canvas is waiting for your spark...

)} {/* Floating UI overlays */} {activeSession && (
Wave 1
Wave 2
Wave 3
)}
{/* Right Sidebar Detail Panel */} {selectedIdea && (
Vague {selectedIdea.waveNumber}
{selectedIdea.status === 'converted' && ( Note Created )}

{selectedIdea.title}

Novelty: {selectedIdea.noveltyScore}/10

{selectedIdea.description}

Origin connection

"{selectedIdea.connectionToSeed}"

{selectedIdea.relatedNoteIds && selectedIdea.relatedNoteIds.length > 0 && (

Semantic Context

{selectedIdea.relatedNoteIds.map(noteId => { const note = notes.find(n => n.id === noteId); return note ? (
{note.title}
) : null; })}
)}
)}
{/* History Rail */}
{sessions.map(session => ( ))}
); };