import { useState, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { ChevronLeft, ChevronRight, Settings2, Sparkles, Send, Bot, Filter, ArrowDownUp } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu'; import rawEntries from './entries.json'; // Persona mappings & Chameleon Colors const PERSONAS: Record = { van_gogh: { name: 'Vincent Van Gogh', color: 'text-yellow-400/80', glow: 'shadow-yellow-400/10' }, mansfield: { name: 'Katherine Mansfield', color: 'text-emerald-400/80', glow: 'shadow-emerald-400/10' }, pepys: { name: 'Samuel Pepys', color: 'text-blue-400/80', glow: 'shadow-blue-400/10' }, maclane: { name: 'Mary MacLane', color: 'text-rose-400/80', glow: 'shadow-rose-400/10' } }; export default function App() { const [isLeftOpen, setIsLeftOpen] = useState(true); const [isRightOpen, setIsRightOpen] = useState(true); const [currentIndex, setCurrentIndex] = useState(100); const [activeFilter, setActiveFilter] = useState(null); const [activeVector, setActiveVector] = useState(null); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); const [chatHistory, setChatHistory] = useState<{role: string, content: string}[]>([]); const [chatInput, setChatInput] = useState(""); const [isTyping, setIsTyping] = useState(false); const entries = useMemo(() => { let filtered = activeFilter ? rawEntries.filter((e: any) => e.persona === activeFilter) : [...rawEntries]; if (sortOrder === 'desc') { filtered.reverse(); } return filtered; }, [activeFilter, sortOrder]); const currentEntry = entries[currentIndex] || entries[0]; const personaData = PERSONAS[currentEntry?.persona] || PERSONAS['maclane']; const activeColor = personaData?.color || 'text-purple-400/80'; const activeGlow = personaData?.glow || 'shadow-purple-400/10'; const chatPersona = activeVector ? PERSONAS[activeVector] : personaData; const chatColor = chatPersona?.color || activeColor; const chatGlow = chatPersona?.glow || activeGlow; const handleNext = () => setCurrentIndex(i => Math.min(i + 1, entries.length - 1)); const handlePrev = () => setCurrentIndex(i => Math.max(i - 1, 0)); return (
{/* Subtle ambient background tied to persona */}
{/* LEFT SIDEBAR: Timeline & Filters */} {isLeftOpen && (

Chronology

Filter Persona
{Object.entries(PERSONAS).map(([key, data]) => ( ))}
{entries.slice(Math.max(0, currentIndex - 20), currentIndex + 20).map((entry: any, i: number) => { const actualIndex = Math.max(0, currentIndex - 20) + i; const isActive = actualIndex === currentIndex; return ( ); })}
)}
{!isLeftOpen && ( setIsLeftOpen(true)} className="absolute left-0 top-1/2 -translate-y-1/2 h-24 w-6 bg-white/5 hover:bg-white/10 border border-white/10 rounded-r-xl z-30 flex items-center justify-center backdrop-blur" > )} {/* CENTER PANE: Reading Area */}

Ghostwriter

{currentEntry?.date || 'Undated'} {personaData?.name || 'Blended Voice'}

Resonance Vector

{Object.entries(PERSONAS).map(([key, data]) => { const isActive = key === (activeVector || currentEntry?.persona); const pct = isActive ? 85 : 5; return (
{data.name} {pct}%
); })}
{currentEntry?.text}
{Object.values(PERSONAS).map((data) => ( Regenerate as {data.name.split(' ').pop()} ))}
Previous Entry Next Entry
{!isRightOpen && ( setIsRightOpen(true)} className="absolute right-0 top-1/2 -translate-y-1/2 h-24 w-6 bg-white/5 hover:bg-white/10 border border-white/10 rounded-l-xl z-30 flex items-center justify-center backdrop-blur" > )} {/* RIGHT SIDEBAR: Chat / Séance */} {isRightOpen && (

The Séance

{Object.entries(PERSONAS).map(([key, data]) => ( ))}

I am the resonance of {chatPersona?.name || 'the spirits'}. Ask me of the thoughts penned on this day, or what shadows danced at the edge of my vision...

{chatHistory.map((msg, idx) => (

{msg.content}

))} {isTyping && (
)}
{ e.preventDefault(); if (!chatInput.trim() || isTyping) return; const userMsg = chatInput; setChatInput(""); setChatHistory(prev => [...prev, { role: 'user', content: userMsg }]); setIsTyping(true); try { // Determine which persona to hit. If activeVector is set, use it. // Otherwise, use the current entry's persona. // If the entry is "Blended Voice" (no persona), fallback to Van Gogh or someone. const targetPersona = activeVector || currentEntry?.persona || 'van_gogh'; const res = await fetch("https://brick-factorial-ghostwriter-api.hf.space/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ persona: targetPersona, message: userMsg, history: chatHistory }) }); const data = await res.json(); setChatHistory(prev => [...prev, { role: 'assistant', content: data.response }]); } catch (err) { console.error("Error communicating with spirits:", err); setChatHistory(prev => [...prev, { role: 'assistant', content: "The connection to the other side was lost..." }]); } finally { setIsTyping(false); } }} > setChatInput(e.target.value)} placeholder="Whisper into the void..." className="w-full bg-white/5 border border-white/10 rounded-full py-3 pl-5 pr-12 text-sm font-sans text-white placeholder:text-white/30 focus:outline-none focus:ring-1 focus:ring-white/20" />
)}
); }