ltcai 4.6.0 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,13 +3,21 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import { ImagePlus, MessageSquare, Plus, Send, Trash2 } from "lucide-react";
4
4
  import { latticeApi } from "@/api/client";
5
5
  import { EmptyState, EntityList, SourceBadge, StructuredView } from "@/components/primitives";
6
- import { LivingBrain, type BrainActivity, type BrainVitals } from "@/components/LivingBrain";
6
+ import { type BrainState, LivingBrain } from "@/components/LivingBrain";
7
7
  import { Badge } from "@/components/ui/badge";
8
8
  import { Button } from "@/components/ui/button";
9
9
  import { Textarea } from "@/components/ui/textarea";
10
10
  import { asArray } from "@/lib/utils";
11
11
 
12
12
  type Msg = { role?: string; content?: string; timestamp?: string };
13
+ type BrainActivity = BrainState;
14
+ type BrainVitals = {
15
+ connected: boolean;
16
+ memories: number;
17
+ knowledge: number;
18
+ conversations: number;
19
+ model: string | null;
20
+ };
13
21
 
14
22
  function fileToDataUrl(file: File) {
15
23
  return new Promise<string>((resolve, reject) => {
@@ -139,7 +147,7 @@ export function BrainConversation({ className }: { className?: string }) {
139
147
  <div className={className}>
140
148
  <div className="brain-conversation-grid">
141
149
  <section className="brain-presence-column" aria-label="Living Brain presence">
142
- <LivingBrain activity={activity} vitals={vitals} />
150
+ <LivingBrain state={activity} size="normal" />
143
151
  </section>
144
152
 
145
153
  <section className="brain-chat-panel premium-surface" aria-label="Conversation with Lattice Brain">
@@ -1,121 +1,212 @@
1
1
  import * as React from "react";
2
- import { Activity, Brain, CheckCircle2, CircleDotDashed, Loader2, Sparkles, Waves } from "lucide-react";
3
- import { Badge } from "@/components/ui/badge";
4
- import { cn, fmtNumber } from "@/lib/utils";
5
-
6
- export type BrainActivity = "idle" | "listening" | "recalling" | "thinking" | "planning" | "acting";
7
-
8
- export type BrainVitals = {
9
- connected?: boolean;
10
- memories?: number | string | null;
11
- knowledge?: number | string | null;
12
- conversations?: number | string | null;
13
- model?: string | null;
14
- activityLabel?: string | null;
15
- };
16
-
17
- const activityLabels: Record<BrainActivity, string> = {
18
- idle: "Present",
19
- listening: "Listening",
20
- recalling: "Recalling",
21
- thinking: "Thinking",
22
- planning: "Planning",
23
- acting: "Acting",
24
- };
25
-
26
- function readable(value: BrainVitals[keyof BrainVitals]) {
27
- if (value === null || value === undefined || value === "") return "-";
28
- if (typeof value === "number") return fmtNumber(value);
29
- return String(value);
2
+ import { cn } from "@/lib/utils";
3
+
4
+ export type BrainState = "idle" | "listening" | "thinking" | "recalling" | "synthesizing" | "planning" | "acting" | "resting";
5
+
6
+ export interface LivingBrainProps {
7
+ state?: BrainState;
8
+ intensity?: number; // 0-1 how "alive" it feels right now
9
+ onPulse?: () => void; // allow parent to trigger a memory pulse
10
+ size?: "normal" | "large" | "trace";
11
+ label?: string;
12
+ className?: string;
13
+ showLabel?: boolean;
14
+ depth?: number; // 0-5 progressive exploration depth; higher = more "open" / revealing
15
+ onInteract?: () => void; // called on click to advance exploration (travel deeper)
30
16
  }
31
17
 
18
+ /**
19
+ * The Living Brain — the primary visual and emotional object in the product.
20
+ * It is not decoration. It is the other participant.
21
+ * It breathes. It reacts. It remembers.
22
+ */
32
23
  export function LivingBrain({
33
- activity = "idle",
34
- vitals,
35
- compact = false,
36
- showVitals = false,
24
+ state = "idle",
25
+ intensity = 0.6,
26
+ onPulse,
27
+ size = "large",
28
+ label,
37
29
  className,
38
- }: {
39
- activity?: BrainActivity;
40
- vitals?: BrainVitals;
41
- compact?: boolean;
42
- showVitals?: boolean;
43
- className?: string;
44
- }) {
45
- const state = vitals?.activityLabel || activityLabels[activity];
46
- const connected = vitals?.connected !== false;
30
+ showLabel = true,
31
+ depth = 0,
32
+ onInteract,
33
+ }: LivingBrainProps) {
34
+ const [isPulsing, setIsPulsing] = React.useState(false);
35
+ const organismRef = React.useRef<HTMLButtonElement>(null);
36
+
37
+ // External trigger for memory / important recall moments
38
+ React.useEffect(() => {
39
+ if (onPulse) {
40
+ const handler = () => firePulse();
41
+ // allow global window event too for simplicity across components
42
+ window.addEventListener("brain:recall", handler as EventListener);
43
+ return () => window.removeEventListener("brain:recall", handler as EventListener);
44
+ }
45
+ }, [onPulse]);
46
+
47
+ function firePulse() {
48
+ setIsPulsing(true);
49
+ if (organismRef.current) {
50
+ organismRef.current.classList.add("pulse");
51
+ // clean after animation
52
+ window.setTimeout(() => {
53
+ if (organismRef.current) organismRef.current.classList.remove("pulse");
54
+ setIsPulsing(false);
55
+ }, 1350);
56
+ }
57
+ onPulse?.();
58
+ }
59
+
60
+ // Auto gentle pulse when recalling or high intensity
61
+ React.useEffect(() => {
62
+ if ((state === "recalling" || intensity > 0.82) && !isPulsing) {
63
+ const t = window.setTimeout(() => firePulse(), 180);
64
+ return () => clearTimeout(t);
65
+ }
66
+ }, [state, intensity]);
67
+
68
+ const dataState = state;
69
+ const isLarge = size === "large";
70
+ const isTrace = size === "trace";
71
+
72
+ const dynamicIntensity = Math.max(0.35, Math.min(1, intensity));
73
+ const effectiveDepth = Math.max(0, Math.min(5, depth || 0));
74
+ const canTravel = state !== "thinking";
75
+
76
+ const handleClick = () => {
77
+ if (!canTravel) return;
78
+ firePulse();
79
+ onInteract?.();
80
+ };
47
81
 
48
82
  return (
49
- <section
50
- className={cn("living-brain", compact && "living-brain-compact", className)}
51
- data-activity={activity}
52
- aria-label="Living Brain"
83
+ <div
84
+ className={cn(
85
+ "brain-presence select-none",
86
+ isLarge && "large",
87
+ isTrace && "trace",
88
+ className,
89
+ effectiveDepth > 0 && "is-exploring"
90
+ )}
91
+ aria-label="Your living Brain"
92
+ role="group"
93
+ data-depth={effectiveDepth}
53
94
  >
54
- <div className="brain-presence-head">
55
- <div>
56
- <div className="brain-presence-kicker"><Brain className="h-4 w-4" /> Lattice Brain</div>
57
- <h2>The Brain is awake.</h2>
58
- </div>
59
- <Badge variant={connected ? "success" : "warning"}>{connected ? "online" : "starting"}</Badge>
60
- </div>
61
-
62
- <div className="brain-stage" aria-hidden="true">
63
- <span className="brain-halo brain-halo-a" />
64
- <span className="brain-halo brain-halo-b" />
65
- <span className="brain-wave brain-wave-a" />
66
- <span className="brain-wave brain-wave-b" />
67
- <span className="brain-wave brain-wave-c" />
68
- <svg className="brain-organ" viewBox="0 0 440 360" role="img" aria-label="Animated Brain presence">
69
- <defs>
70
- <filter id="brainGlow" x="-40%" y="-40%" width="180%" height="180%">
71
- <feGaussianBlur stdDeviation="10" result="blur" />
72
- <feColorMatrix in="blur" type="matrix" values="0 0 0 0 0.12 0 0 0 0 0.78 0 0 0 0 0.68 0 0 0 0.52 0" />
73
- <feBlend in="SourceGraphic" />
74
- </filter>
75
- </defs>
76
- <path className="brain-mass brain-mass-left" d="M214 74c-25-34-82-31-103 7-28 1-50 22-51 51-28 16-39 51-25 81-14 32 6 72 42 80 18 35 72 43 101 14 30 17 71 6 87-25 27-11 43-40 36-69 19-25 14-64-11-84 2-31-31-61-76-55Z" />
77
- <path className="brain-mass brain-mass-right" d="M224 74c24-35 83-33 105 5 29 0 52 22 53 52 28 16 39 52 24 82 14 33-8 74-45 80-19 34-73 41-101 11-31 16-72 3-86-29-27-12-41-42-33-71-18-27-10-66 18-85-1-30 31-57 65-45Z" />
78
- <path className="thought-path thought-path-a" d="M106 143c35-30 83-27 113 9 26 31 74 31 110 0" />
79
- <path className="thought-path thought-path-b" d="M93 218c38 25 75 23 112-8 34-29 75-31 121-6" />
80
- <path className="thought-path thought-path-c" d="M144 100c9 38 33 59 72 63 42 5 69 27 83 67" />
81
- <path className="thought-path thought-path-d" d="M149 286c16-44 42-68 78-71 42-4 72-26 89-66" />
82
- <circle className="memory-pulse pulse-a" cx="125" cy="150" r="7" />
83
- <circle className="memory-pulse pulse-b" cx="223" cy="164" r="8" />
84
- <circle className="memory-pulse pulse-c" cx="312" cy="210" r="7" />
85
- <circle className="memory-pulse pulse-d" cx="183" cy="257" r="6" />
86
- </svg>
87
- <div className="brain-state-pill">
88
- {activity === "thinking" ? <Loader2 className="h-4 w-4 animate-spin" /> : <CircleDotDashed className="h-4 w-4" />}
89
- <span>{state}</span>
95
+ <button
96
+ type="button"
97
+ ref={organismRef}
98
+ className={cn("brain-organism", `size-${size}`, `depth-${effectiveDepth}`)}
99
+ data-state={dataState}
100
+ aria-label={effectiveDepth < 5 ? "Travel deeper into your Brain" : "Rest inside the Knowledge Graph"}
101
+ aria-disabled={!canTravel}
102
+ style={{
103
+ transform: `scale(${0.96 + (dynamicIntensity - 0.5) * 0.09 + effectiveDepth * 0.015})`,
104
+ }}
105
+ onClick={handleClick}
106
+ title={effectiveDepth < 5 ? "Travel deeper into your Brain" : "The core of your knowledge"}
107
+ >
108
+ {/* Living anatomical presence. The glow opens with depth; the folds make it unmistakably a Brain. */}
109
+ <div className="brain-core" style={{ transform: `scale(${1 + effectiveDepth * 0.045})` }}>
110
+ <svg className="brain-anatomy" viewBox="0 0 220 174" aria-hidden>
111
+ <path
112
+ className="brain-lobe brain-lobe-left"
113
+ d="M102 30c-13-20-44-19-55 1-18 1-29 16-28 33-13 8-18 25-11 39-9 16-1 36 17 42 5 19 27 26 43 15 13 10 33 8 43-5 5-7 8-16 8-27V52c0-8-6-17-17-22Z"
114
+ />
115
+ <path
116
+ className="brain-lobe brain-lobe-right"
117
+ d="M118 30c13-20 44-19 55 1 18 1 29 16 28 33 13 8 18 25 11 39 9 16 1 36-17 42-5 19-27 26-43 15-13 10-33 8-43-5-5-7-8-16-8-27V52c0-8 6-17 17-22Z"
118
+ />
119
+ <path className="brain-bridge" d="M103 48c9-8 24-8 33 0 7 6 9 16 5 25-5 11-16 15-31 12-15 3-26-1-31-12-4-9-2-19 5-25 5-4 12-6 19 0Z" />
120
+ <path className="brain-stem" d="M92 137c10 9 26 9 36 0 1 14 7 25 20 33H76c12-8 17-19 16-33Z" />
121
+ <path className="brain-fold fold-a" d="M48 50c18-11 38-8 47 8" />
122
+ <path className="brain-fold fold-b" d="M34 82c22-8 45-5 58 8" />
123
+ <path className="brain-fold fold-c" d="M43 119c18 5 35 2 49-11" />
124
+ <path className="brain-fold fold-d" d="M172 50c-18-11-38-8-47 8" />
125
+ <path className="brain-fold fold-e" d="M186 82c-22-8-45-5-58 8" />
126
+ <path className="brain-fold fold-f" d="M177 119c-18 5-35 2-49-11" />
127
+ <path className="brain-fold fold-mid" d="M110 38c-5 30-5 70 0 112" />
128
+ </svg>
90
129
  </div>
91
- </div>
92
-
93
- {showVitals ? (
94
- <div className="brain-vitals">
95
- <div className="brain-vital">
96
- <Sparkles className="h-4 w-4" />
97
- <span>Memories</span>
98
- <strong>{readable(vitals?.memories)}</strong>
99
- </div>
100
- <div className="brain-vital">
101
- <Waves className="h-4 w-4" />
102
- <span>Knowledge</span>
103
- <strong>{readable(vitals?.knowledge)}</strong>
104
- </div>
105
- <div className="brain-vital">
106
- <Activity className="h-4 w-4" />
107
- <span>Activity</span>
108
- <strong>{readable(vitals?.conversations)}</strong>
109
- </div>
130
+
131
+ {/* Breathing field expands as we go deeper. */}
132
+ <div
133
+ className="brain-aura"
134
+ style={{
135
+ animationDuration: state === "thinking" ? "1.65s" : state === "recalling" ? "2.4s" : "6.8s",
136
+ transform: `scale(${1 + effectiveDepth * 0.12})`,
137
+ opacity: 0.65 + effectiveDepth * 0.05
138
+ }}
139
+ />
140
+
141
+ {/* Thought activity — increases and starts to "resolve" into structure at higher depths */}
142
+ <div className="thought-activity" aria-hidden>
143
+ {Array.from({ length: Math.min(12, 5 + effectiveDepth * 2) }).map((_, i) => (
144
+ <div
145
+ key={i}
146
+ className={cn("thought-particle", effectiveDepth >= 3 && "resolving")}
147
+ style={{
148
+ left: `${18 + ((i * 13 + effectiveDepth * 4) % 64)}%`,
149
+ top: `${22 + (i % 5) * 14}%`,
150
+ animationDelay: `-${i * 0.55 + (intensity * 1.1) - effectiveDepth * 0.2}s`,
151
+ animationDuration: `${2.8 + (1 - dynamicIntensity) * 1.6 - effectiveDepth * 0.15}s`,
152
+ }}
153
+ />
154
+ ))}
110
155
  </div>
111
- ) : null}
112
156
 
113
- {compact ? null : (
114
- <div className="brain-presence-foot">
115
- <CheckCircle2 className="h-4 w-4 text-primary" />
116
- <span>{vitals?.model ? readable(vitals.model) : "Waiting for a local model"}</span>
157
+ {/* Memory ripples more and stronger as depth increases (echoes surfacing) */}
158
+ {Array.from({ length: 1 + Math.floor(effectiveDepth / 1.5) }).map((_, i) => (
159
+ <div
160
+ key={i}
161
+ className="memory-ripple"
162
+ aria-hidden
163
+ style={{
164
+ inset: `${18 + i * 6}%`,
165
+ animationDelay: `${180 + i * 220}ms`,
166
+ opacity: 0.55 + effectiveDepth * 0.06
167
+ }}
168
+ />
169
+ ))}
170
+
171
+ {/* Relationship structure appears only near the deepest layers. */}
172
+ {effectiveDepth >= 4 && (
173
+ <svg className="brain-inner-structure" viewBox="0 0 100 100" aria-hidden>
174
+ <g stroke="hsl(var(--brain-core) / 0.35)" strokeWidth="0.6" fill="none">
175
+ <circle cx="50" cy="50" r="18" />
176
+ <circle cx="50" cy="50" r="28" />
177
+ <path d="M32 50 Q50 32 68 50" />
178
+ <path d="M32 50 Q50 68 68 50" />
179
+ </g>
180
+ </svg>
181
+ )}
182
+ </button>
183
+
184
+ {showLabel && !isTrace && (
185
+ <div className="brain-presence-label" data-state={state}>
186
+ <span className="dot" />
187
+ {label || (effectiveDepth > 0 ? `Depth ${effectiveDepth}` : humanState(state))}
117
188
  </div>
118
189
  )}
119
- </section>
190
+ </div>
120
191
  );
121
192
  }
193
+
194
+ function humanState(s: BrainState) {
195
+ switch (s) {
196
+ case "listening": return "Listening";
197
+ case "thinking": return "Thinking with you";
198
+ case "recalling": return "Remembering";
199
+ case "synthesizing": return "Making sense";
200
+ case "planning": return "Planning";
201
+ case "acting": return "Acting";
202
+ case "resting": return "With you";
203
+ default: return "Here";
204
+ }
205
+ }
206
+
207
+ // Helper to broadcast recall pulses from anywhere (conversation, memory surface, etc)
208
+ export function triggerBrainRecall() {
209
+ if (typeof window !== "undefined") {
210
+ window.dispatchEvent(new CustomEvent("brain:recall"));
211
+ }
212
+ }