| import { useState } from 'react' |
|
|
| interface CortexPanelProps { |
| onLearnText: (text: string) => Promise<any> |
| onDream: (cycles: number) => Promise<any> |
| onReason: (question: string) => Promise<any> |
| loading: boolean |
| } |
|
|
| type Tab = 'learn' | 'dream' | 'reason' |
|
|
| export function CortexPanel({ onLearnText, onDream, onReason, loading }: CortexPanelProps) { |
| const [tab, setTab] = useState<Tab>('learn') |
| const [learnText, setLearnText] = useState('') |
| const [learnResult, setLearnResult] = useState<any>(null) |
| const [dreamCycles, setDreamCycles] = useState(3) |
| const [dreamResult, setDreamResult] = useState<any>(null) |
| const [reasonQuestion, setReasonQuestion] = useState('') |
| const [reasonResult, setReasonResult] = useState<any>(null) |
|
|
| const handleLearn = async () => { |
| if (!learnText.trim() || loading) return |
| try { |
| const r = await onLearnText(learnText.trim()) |
| setLearnResult(r) |
| } catch { |
| setLearnResult({ error: 'API not reachable' }) |
| } |
| } |
|
|
| const handleDream = async () => { |
| if (loading) return |
| try { |
| const r = await onDream(dreamCycles) |
| setDreamResult(r) |
| } catch { |
| setDreamResult({ error: 'API not reachable' }) |
| } |
| } |
|
|
| const handleReason = async () => { |
| if (!reasonQuestion.trim() || loading) return |
| try { |
| const r = await onReason(reasonQuestion.trim()) |
| setReasonResult(r) |
| } catch { |
| setReasonResult({ error: 'API not reachable' }) |
| } |
| } |
|
|
| return ( |
| <div className="flex flex-col h-full glass-card overflow-hidden"> |
| {/* Header */} |
| <div className="px-5 py-3 border-b border-pal-border"> |
| <div className="flex items-center gap-2"> |
| <span className="text-base">๐ง </span> |
| <h2 className="text-sm font-semibold text-pal-text">Cortex</h2> |
| <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-pal-accent/15 text-pal-accent font-semibold"> |
| 7 FEATURES |
| </span> |
| </div> |
| </div> |
| |
| {/* Tabs */} |
| <div className="flex gap-1 p-2 border-b border-pal-border"> |
| {([ |
| ['learn', '๐ Learn', 'from-pal-accent2/20'], |
| ['dream', '๐ Dream', 'from-pal-accent/20'], |
| ['reason', '๐ Reason', 'from-pal-gold/20'], |
| ] as [Tab, string, string][]).map(([t, label, color]) => ( |
| <button |
| key={t} |
| onClick={() => setTab(t)} |
| className={`flex-1 py-1.5 rounded-lg text-xs font-semibold transition-all ${ |
| tab === t ? `bg-gradient-to-r ${color} text-pal-text` : 'text-pal-muted hover:text-pal-text' |
| }`} |
| > |
| {label} |
| </button> |
| ))} |
| </div> |
| |
| {/* Content */} |
| <div className="flex-1 overflow-y-auto p-4"> |
| {/* LEARN TAB */} |
| {tab === 'learn' && ( |
| <div className="space-y-3"> |
| <div> |
| <label className="text-[11px] text-pal-muted font-medium uppercase tracking-wide block mb-1"> |
| Paste any text โ PALIMPSESTE becomes an expert instantly |
| </label> |
| <textarea |
| value={learnText} |
| onChange={e => setLearnText(e.target.value)} |
| placeholder="Paste a Wikipedia article, documentation, book chapter..." |
| rows={6} |
| className="w-full bg-pal-surface border border-pal-border rounded-lg px-3 py-2 text-sm text-pal-text placeholder:text-pal-muted focus:outline-none focus:border-pal-accent2/50 transition-all resize-none" |
| /> |
| </div> |
| <button |
| onClick={handleLearn} |
| disabled={!learnText.trim() || loading} |
| className="w-full py-2 rounded-lg bg-gradient-to-r from-pal-accent2 to-pal-green text-white font-semibold text-sm transition-all hover:scale-[1.02] disabled:opacity-30 disabled:cursor-not-allowed" |
| > |
| {loading ? 'Learning...' : 'โก Learn Instantly (O(1) per token)'} |
| </button> |
| |
| {learnResult && !learnResult.error && ( |
| <div className="p-3 rounded-lg bg-pal-surface/60 border border-pal-accent2/20 animate-slide-up"> |
| <div className="flex items-center gap-3 mb-2"> |
| <div className="text-pal-green text-lg">โ</div> |
| <div> |
| <div className="text-xs font-semibold text-pal-text"> |
| Learned {learnResult.n_facts} facts in {learnResult.n_seconds}s |
| </div> |
| <div className="text-[10px] text-pal-muted"> |
| {learnResult.n_tokens} tokens written ยท tag: {learnResult.document_tag} |
| </div> |
| </div> |
| </div> |
| {learnResult.facts && learnResult.facts.length > 0 && ( |
| <div className="space-y-1 mt-2 pt-2 border-t border-pal-border/50"> |
| <div className="text-[10px] text-pal-muted uppercase font-medium">Extracted facts:</div> |
| {learnResult.facts.slice(0, 8).map((f: any, i: number) => ( |
| <div key={i} className="text-[11px] text-pal-text"> |
| <span className="text-pal-accent2">Q:</span> {f.q} |
| <br /> |
| <span className="text-pal-green">A:</span> {f.a.slice(0, 80)}{f.a.length > 80 ? '...' : ''} |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| )} |
| |
| {learnResult?.error && ( |
| <div className="text-xs text-pal-red text-center">{learnResult.error}</div> |
| )} |
| </div> |
| )} |
| |
| {/* DREAM TAB */} |
| {tab === 'dream' && ( |
| <div className="space-y-3"> |
| <div className="text-center py-4"> |
| <div className="text-3xl mb-2 animate-float">๐</div> |
| <p className="text-xs text-pal-muted max-w-xs mx-auto"> |
| When idle, PALIMPSESTE replays its memory, discovers co-activations, |
| and creates new abstract concepts. It gets smarter without any new data. |
| </p> |
| </div> |
| <div className="flex items-center gap-3"> |
| <label className="text-xs text-pal-muted font-medium whitespace-nowrap"> |
| Cycles: <span className="text-pal-accent font-mono">{dreamCycles}</span> |
| </label> |
| <input |
| type="range" min="1" max="10" step="1" |
| value={dreamCycles} |
| onChange={e => setDreamCycles(parseInt(e.target.value))} |
| className="flex-1 accent-pal-accent" |
| /> |
| </div> |
| <button |
| onClick={handleDream} |
| disabled={loading} |
| className="w-full py-2 rounded-lg bg-gradient-to-r from-pal-accent to-pal-accent2 text-white font-semibold text-sm transition-all hover:scale-[1.02] hover:glow-accent disabled:opacity-30" |
| > |
| {loading ? 'Dreaming...' : '๐ Trigger Dream'} |
| </button> |
| |
| {dreamResult && !dreamResult.error && ( |
| <div className="p-3 rounded-lg bg-pal-surface/60 border border-pal-accent/20 animate-slide-up space-y-2"> |
| <div className="flex items-center gap-3"> |
| <div className="text-pal-accent text-lg">โ</div> |
| <div> |
| <div className="text-xs font-semibold text-pal-text"> |
| {dreamResult.n_concepts_promoted} promoted, {dreamResult.n_concepts_extracted} extracted |
| </div> |
| <div className="text-[10px] text-pal-muted"> |
| {dreamResult.n_seconds}s ยท {dreamResult.n_total_concepts} total concepts |
| </div> |
| </div> |
| </div> |
| {dreamResult.concept_labels && dreamResult.concept_labels.length > 0 && ( |
| <div className="pt-2 border-t border-pal-border/50"> |
| <div className="text-[10px] text-pal-muted uppercase font-medium mb-1">Concepts:</div> |
| <div className="flex flex-wrap gap-1"> |
| {dreamResult.concept_labels.map((label: string, i: number) => ( |
| <span key={i} className="text-[10px] px-2 py-0.5 rounded-full bg-pal-accent/10 text-pal-accent"> |
| {label} |
| </span> |
| ))} |
| </div> |
| </div> |
| )} |
| </div> |
| )} |
| </div> |
| )} |
| |
| {/* REASON TAB */} |
| {tab === 'reason' && ( |
| <div className="space-y-3"> |
| <div> |
| <label className="text-[11px] text-pal-muted font-medium uppercase tracking-wide block mb-1"> |
| Ask a complex question โ PALIMPSESTE decomposes and resolves |
| </label> |
| <input |
| type="text" |
| value={reasonQuestion} |
| onChange={e => setReasonQuestion(e.target.value)} |
| onKeyDown={e => e.key === 'Enter' && handleReason()} |
| placeholder="what is the capital of the country that won the world cup 2018?" |
| className="w-full bg-pal-surface border border-pal-border rounded-lg px-3 py-2 text-sm text-pal-text placeholder:text-pal-muted focus:outline-none focus:border-pal-gold/50" |
| /> |
| </div> |
| <button |
| onClick={handleReason} |
| disabled={!reasonQuestion.trim() || loading} |
| className="w-full py-2 rounded-lg bg-gradient-to-r from-pal-gold to-pal-accent text-white font-semibold text-sm transition-all hover:scale-[1.02] disabled:opacity-30" |
| > |
| {loading ? 'Reasoning...' : '๐ Decompose & Resolve'} |
| </button> |
| |
| {reasonResult && !reasonResult.error && ( |
| <div className="p-3 rounded-lg bg-pal-surface/60 border border-pal-gold/20 animate-slide-up space-y-2"> |
| <div className="flex items-center gap-2"> |
| <div className={`text-lg ${reasonResult.success ? 'text-pal-green' : 'text-pal-red'}`}> |
| {reasonResult.success ? 'โ' : 'โ'} |
| </div> |
| <div className="text-xs font-semibold text-pal-text"> |
| {reasonResult.n_decompositions} decompositions, {reasonResult.n_hops} hops, {reasonResult.n_seconds}s |
| </div> |
| </div> |
| <div className="p-2 rounded-lg bg-pal-bg/50 border border-pal-border/30"> |
| <div className="text-[10px] text-pal-muted uppercase font-medium mb-0.5">Answer:</div> |
| <div className="text-sm text-pal-gold font-medium">{reasonResult.answer}</div> |
| </div> |
| {reasonResult.steps && reasonResult.steps.length > 0 && ( |
| <div className="pt-2 border-t border-pal-border/50 space-y-1"> |
| <div className="text-[10px] text-pal-muted uppercase font-medium">Reasoning trace:</div> |
| {reasonResult.steps.filter((s: any) => s.answer).map((s: any, i: number) => ( |
| <div key={i} className="text-[11px] flex items-start gap-2"> |
| <span className={`px-1 rounded font-mono ${ |
| s.type === 'resolve' ? 'bg-pal-green/10 text-pal-green' : |
| s.type === 'compose' ? 'bg-pal-gold/10 text-pal-gold' : |
| 'bg-pal-muted/10 text-pal-muted' |
| }`}> |
| {s.type} |
| </span> |
| <div> |
| <span className="text-pal-muted">{s.question?.slice(0, 40)}</span> |
| {s.answer && <span className="text-pal-text"> โ <b>{s.answer.slice(0, 50)}</b></span>} |
| </div> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| )} |
| </div> |
| )} |
| </div> |
| </div> |
| ) |
| } |
|
|