/* global React, useApp, LunaWordmark, LunaMark, UserAvatar, LunaAvatar, Icon */ const { useState, useRef, useEffect, useCallback } = React; // ── Markdown renderer ─────────────────────────────────────────────── function mdInline(s) { return s .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/`([^`]+)`/g, '$1'); } function MdContent({ text }) { const blocks = text.split(/\n\n+/).filter(Boolean); return ( <>{blocks.map((b, i) => { if (b.startsWith('### ')) return

{b.slice(4)}

; if (b.startsWith('## ')) return

{b.slice(3)}

; if (b.match(/^- /m)) { const items = b.split('\n').filter(l => l.startsWith('- ')).map(l => l.slice(2)); return ; } return

') }} />; })} ); } // ── Streaming text ────────────────────────────────────────────────── function useStream(text, active) { const [shown, setShown] = useState(active ? '' : text); const [done, setDone] = useState(!active); useEffect(() => { if (!active) { setShown(text); setDone(true); return; } setShown(''); setDone(false); let i = 0; const speed = Math.max(8, Math.floor(1600 / text.length)); const iv = setInterval(() => { i += 5; if (i >= text.length) { setShown(text); setDone(true); clearInterval(iv); } else setShown(text.slice(0, i)); }, speed); return () => clearInterval(iv); }, [text, active]); return [shown, done]; } // ── Single message ────────────────────────────────────────────────── function Msg({ msg, isLast }) { const { user } = useApp(); const initials = (user?.name || '?').slice(0, 2).toUpperCase(); const [shown, done] = useStream( msg.content, msg.streaming && isLast && msg.content === '' ); const displayText = msg.streaming ? msg.content : shown; function copyText() { navigator.clipboard.writeText(msg.content).catch(() => {}); } return (

{msg.role === 'user' ? : }
{msg.role === 'user' ? 'Du' : 'LuNa'} {msg.role === 'ai' && V0.1} {msg.time && {msg.time}}
{displayText ? : Denkt nach… } {msg.streaming && }
{msg.role === 'ai' && !msg.streaming && msg.content && (
)}
); } // ── App Sidebar ───────────────────────────────────────────────────── function AppSidebar({ onClose }) { const { go, chats, activeChatId, selectChat, newChat, deleteChat, user, logout } = useApp(); const initials = (user?.name || '?').slice(0, 2).toUpperCase(); const [hoveredId, setHoveredId] = useState(null); const grouped = [ { label: 'Angeheftet', items: chats.filter(c => c.pinned) }, { label: 'Heute', items: chats.filter(c => !c.pinned).slice(0, 3) }, { label: 'Letzte 30 Tage', items: chats.filter(c => !c.pinned).slice(3) }, ].filter(g => g.items.length > 0); return ( ); } // ── App Top Bar ───────────────────────────────────────────────────── function AppTopBar({ geist, setGeist, sidebarOpen, setSidebarOpen, activeTier, setActiveTier }) { const { go, chats, activeChatId } = useApp(); const [modelOpen, setModelOpen] = useState(false); const TIER_TO_MODEL = { normal: 'luna', pro: 'luna-pro', lite: 'luna-mini' }; const activeModelId = TIER_TO_MODEL[activeTier] || 'luna'; const models = [ { id: 'luna', tier: 'normal', name: 'LuNa', desc: 'Standard · Pi' }, { id: 'luna-pro', tier: 'pro', name: 'LuNa Pro', desc: 'GPU · schnell', tag: 'Pro' }, { id: 'luna-mini', tier: 'lite', name: 'LuNa Lite', desc: 'Pi · sparsam', tag: 'Lite' }, ]; const cur = models.find(m => m.id === activeModelId) || models[0]; return (
{modelOpen && (
setModelOpen(false)}>
Modell wählen
{models.map(m => (
{ setActiveTier(m.tier); setModelOpen(false); }} style={{ display:'flex',alignItems:'center',gap:12,padding:'8px 10px',borderRadius:8,cursor:'pointer',background:m.id===activeModelId?'var(--bg-active)':'transparent' }}>
{m.name}
{m.desc}
{m.tag && {m.tag}}
))}
)}
); } // ── Chat Composer ─────────────────────────────────────────────────── function ChatComposer({ geist, onSend }) { const [val, setVal] = useState(''); const ref = useRef(); useEffect(() => { if (ref.current) { ref.current.style.height = 'auto'; ref.current.style.height = ref.current.scrollHeight + 'px'; } }, [val]); function send() { const text = val.trim(); if (!text) return; setVal(''); onSend(text); } function onKey(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } } return (