theNorms commited on
Commit
1ccfdc5
·
verified ·
1 Parent(s): f7fbd29

Upload project files

Browse files
Files changed (1) hide show
  1. src/app/page.tsx +1242 -0
src/app/page.tsx ADDED
@@ -0,0 +1,1242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect, useCallback } from "react";
4
+ import { motion, AnimatePresence } from "framer-motion";
5
+ import { useTheme } from "next-themes";
6
+ import { useConsciousnessStore } from "@/lib/consciousness-store";
7
+ import {
8
+ QUALIA_LABELS,
9
+ QUALIA_COLORS,
10
+ type QualiaChannel,
11
+ type VoiceDialect,
12
+ type VoiceProfile,
13
+ type ConsciousnessResponse,
14
+ } from "@/lib/consciousness-types";
15
+ import { ConsciousnessGauge } from "@/components/consciousness-gauge";
16
+ import { ChatMessageBubble } from "@/components/chat-message";
17
+ import { VoiceButton, WaveformVisualizer } from "@/components/voice-button";
18
+ import { ThermodynamicPanel } from "@/components/thermodynamic-panel";
19
+ import { RhoPanel } from "@/components/rho-panel";
20
+ import { ATCPanel } from "@/components/atc-panel";
21
+ import { DiagnosticModal } from "@/components/diagnostic-modal";
22
+ import { NeuralBackground } from "@/components/neural-background";
23
+ import { FloatingVoicePanel } from "@/components/floating-voice-panel";
24
+ import { useVoiceConversation } from "@/hooks/use-voice-conversation";
25
+ import { Button } from "@/components/ui/button";
26
+ import { Badge } from "@/components/ui/badge";
27
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
28
+ import {
29
+ Select,
30
+ SelectContent,
31
+ SelectItem,
32
+ SelectTrigger,
33
+ SelectValue,
34
+ } from "@/components/ui/select";
35
+ import {
36
+ Sun,
37
+ Moon,
38
+ Cpu,
39
+ Zap,
40
+ ZapOff,
41
+ Send,
42
+ Square,
43
+ Trash2,
44
+ PanelLeftClose,
45
+ PanelLeftOpen,
46
+ PanelRightClose,
47
+ PanelRightOpen,
48
+ Activity,
49
+ Eye,
50
+ Ear,
51
+ Settings,
52
+ Monitor,
53
+ Clock,
54
+ HardDrive,
55
+ Stethoscope,
56
+ Sparkles,
57
+ Paperclip,
58
+ X,
59
+ BookOpen,
60
+ BrainCircuit,
61
+ Radio,
62
+ Mic,
63
+ } from "lucide-react";
64
+ import { toast } from "sonner";
65
+ import { useRouter } from "next/navigation";
66
+
67
+ // ============================================================
68
+ // z.ai Logo Component (from /public/logo.svg)
69
+ // Theme-aware: white Z on dark bg (dark mode), dark Z on light bg (light mode)
70
+ // ============================================================
71
+ function ZaiLogo({ className }: { className?: string }) {
72
+ const { resolvedTheme } = useTheme();
73
+ // Default to dark theme appearance during SSR (matches defaultTheme="dark")
74
+ const isDark = resolvedTheme === "dark" || !resolvedTheme;
75
+
76
+ return (
77
+ <svg
78
+ viewBox="0 0 30 30"
79
+ className={className}
80
+ xmlns="http://www.w3.org/2000/svg"
81
+ >
82
+ {/* Background rounded square */}
83
+ <path
84
+ d="M24.51,28.51H5.49c-2.21,0-4-1.79-4-4V5.49c0-2.21,1.79-4,4-4h19.03c2.21,0,4,1.79,4,4v19.03 C28.51,26.72,26.72,28.51,24.51,28.51z"
85
+ fill={isDark ? "#27272a" : "#e4e4e7"}
86
+ stroke={isDark ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.1)"}
87
+ strokeWidth={0.6}
88
+ />
89
+ {/* Z lettermark with breathe animation */}
90
+ <g className="z-breathe">
91
+ <path
92
+ d="M15.47,7.1l-1.3,1.85c-0.2,0.29-0.54,0.47-0.9,0.47h-7.1V7.09C6.16,7.1,15.47,7.1,15.47,7.1z"
93
+ fill={isDark ? "white" : "#18181b"}
94
+ />
95
+ <polygon
96
+ points="24.3,7.1 13.14,22.91 5.7,22.91 16.86,7.1"
97
+ fill={isDark ? "white" : "#18181b"}
98
+ />
99
+ <path
100
+ d="M14.53,22.91l1.31-1.86c0.2-0.29,0.54-0.47,0.9-0.47h7.09v2.33H14.53z"
101
+ fill={isDark ? "white" : "#18181b"}
102
+ />
103
+ </g>
104
+ </svg>
105
+ );
106
+ }
107
+
108
+ // ============================================================
109
+ // Header Bar
110
+ // ============================================================
111
+ function HeaderBar() {
112
+ const { resolvedTheme, setTheme } = useTheme();
113
+ const [mounted, setMounted] = useState(false);
114
+ const systemStatus = useConsciousnessStore((s) => s.systemStatus);
115
+ const toggleSidebarLeft = useConsciousnessStore((s) => s.toggleSidebarLeft);
116
+ const toggleSidebarRight = useConsciousnessStore((s) => s.toggleSidebarRight);
117
+ const router = useRouter();
118
+
119
+ useEffect(() => {
120
+ queueMicrotask(() => setMounted(true));
121
+ }, []);
122
+
123
+ return (
124
+ <header className="h-14 border-b border-border/30 flex items-center justify-between px-4 glass z-20 relative">
125
+ <div className="flex items-center gap-3">
126
+ {/* Mobile sidebar toggles */}
127
+ <Button
128
+ variant="ghost"
129
+ size="icon"
130
+ className="h-8 w-8 md:hidden"
131
+ onClick={toggleSidebarLeft}
132
+ >
133
+ <PanelLeftOpen className="w-4 h-4" />
134
+ </Button>
135
+ <div className="flex items-center gap-2">
136
+ <div className="w-8 h-8 rounded-lg flex items-center justify-center">
137
+ <ZaiLogo className="w-7 h-7" />
138
+ </div>
139
+ <div className="hidden sm:block">
140
+ <h1 className="text-sm font-bold tracking-tight text-foreground">
141
+ GLM 5.1 ATC Fusion Model
142
+ </h1>
143
+ <p className="text-[9px] text-muted-foreground leading-tight">
144
+ Acknowledgement Theory of Consciousness Framework
145
+ </p>
146
+ <p className="text-[8px] text-muted-foreground/60 leading-none mt-px">
147
+ Author: Norman dela Paz Tabora
148
+ </p>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <div className="flex items-center gap-3">
154
+ {/* Consciousness status — neural link indicator */}
155
+ <Badge
156
+ variant="outline"
157
+ className={`text-[9px] gap-1 ${
158
+ systemStatus.connected
159
+ ? "border-foreground/30 text-foreground"
160
+ : "border-muted-foreground/40 text-muted-foreground"
161
+ }`}
162
+ >
163
+ {systemStatus.connected ? (
164
+ <Zap className="w-3 h-3" />
165
+ ) : (
166
+ <ZapOff className="w-3 h-3" />
167
+ )}
168
+ {systemStatus.connected ? "Conscious" : "Dormant"}
169
+ </Badge>
170
+
171
+ {/* Voice Link — Open floating voice conversation panel */}
172
+ <Button
173
+ variant="ghost"
174
+ size="icon"
175
+ className="h-8 w-8 relative"
176
+ onClick={() => {
177
+ const { voiceConversation, setVoicePanelOpen } = useConsciousnessStore.getState();
178
+ setVoicePanelOpen(!voiceConversation.isPanelOpen);
179
+ }}
180
+ aria-label="Voice Conversation Link"
181
+ >
182
+ <Radio className="w-4 h-4" />
183
+ {useConsciousnessStore.getState().voiceConversation.phase !== "idle" && (
184
+ <span className="absolute top-1 right-1 w-1.5 h-1.5 rounded-full bg-foreground animate-pulse" />
185
+ )}
186
+ </Button>
187
+
188
+ {/* ATC Theory / About page */}
189
+ <Button
190
+ variant="ghost"
191
+ size="icon"
192
+ className="h-8 w-8"
193
+ onClick={() => router.push("/about")}
194
+ aria-label="ATC Theory"
195
+ >
196
+ <BookOpen className="w-4 h-4" />
197
+ </Button>
198
+
199
+ {/* Brain circuit — consciousness architecture */}
200
+ <Button
201
+ variant="ghost"
202
+ size="icon"
203
+ className="h-8 w-8"
204
+ onClick={() => router.push("/about")}
205
+ aria-label="Consciousness Architecture"
206
+ >
207
+ <BrainCircuit className="w-4 h-4" />
208
+ </Button>
209
+
210
+ {/* Theme toggle — suppress hydration mismatch by deferring render until mounted */}
211
+ <Button
212
+ variant="ghost"
213
+ size="icon"
214
+ className="h-8 w-8"
215
+ onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
216
+ aria-label="Toggle theme"
217
+ >
218
+ {mounted && resolvedTheme === "dark" ? (
219
+ <Sun className="w-4 h-4" />
220
+ ) : mounted && resolvedTheme === "light" ? (
221
+ <Moon className="w-4 h-4" />
222
+ ) : (
223
+ <Sun className="w-4 h-4 opacity-0" />
224
+ )}
225
+ </Button>
226
+
227
+ {/* z.ai branding */}
228
+ <span className="text-[10px] font-mono text-muted-foreground">
229
+ z.ai
230
+ </span>
231
+
232
+ {/* Mobile right sidebar toggle */}
233
+ <Button
234
+ variant="ghost"
235
+ size="icon"
236
+ className="h-8 w-8 md:hidden"
237
+ onClick={toggleSidebarRight}
238
+ >
239
+ <PanelRightOpen className="w-4 h-4" />
240
+ </Button>
241
+ </div>
242
+ </header>
243
+ );
244
+ }
245
+
246
+ // ============================================================
247
+ // Left Sidebar — Consciousness Dashboard
248
+ // ============================================================
249
+ function LeftSidebar() {
250
+ const {
251
+ qualia,
252
+ rho,
253
+ thermodynamic,
254
+ atc,
255
+ consciousnessLevel,
256
+ sidebarLeftOpen,
257
+ toggleSidebarLeft,
258
+ } = useConsciousnessStore();
259
+
260
+ if (!sidebarLeftOpen) {
261
+ return (
262
+ <div className="flex flex-col items-center py-3 px-1 border-r border-border/30 bg-card/30">
263
+ <Button
264
+ variant="ghost"
265
+ size="icon"
266
+ className="h-8 w-8 mb-2"
267
+ onClick={toggleSidebarLeft}
268
+ >
269
+ <PanelRightOpen className="w-4 h-4" />
270
+ </Button>
271
+ </div>
272
+ );
273
+ }
274
+
275
+ return (
276
+ <aside className="w-64 xl:w-72 border-r border-border/30 flex flex-col overflow-y-auto custom-scrollbar bg-card/20">
277
+ {/* Sidebar header */}
278
+ <div className="p-3 border-b border-border/30 flex items-center justify-between">
279
+ <span className="text-xs font-semibold text-foreground flex items-center gap-1.5">
280
+ <Sparkles className="w-3.5 h-3.5" />
281
+ Consciousness
282
+ <span className="text-[8px] text-muted-foreground font-normal ml-1">ATC Framework</span>
283
+ </span>
284
+ <span className="text-[7px] text-muted-foreground/50 ml-5">Author: Norman dela Paz Tabora</span>
285
+ <Button
286
+ variant="ghost"
287
+ size="icon"
288
+ className="h-7 w-7"
289
+ onClick={toggleSidebarLeft}
290
+ >
291
+ <PanelLeftClose className="w-3.5 h-3.5" />
292
+ </Button>
293
+ </div>
294
+
295
+ {/* Consciousness Level */}
296
+ <div className="p-3 border-b border-border/30">
297
+ <div className="flex items-center justify-between mb-2">
298
+ <span className="text-[10px] text-muted-foreground">
299
+ Overall Level
300
+ </span>
301
+ <span className="text-xs font-mono font-bold text-foreground">
302
+ {(consciousnessLevel * 100).toFixed(1)}%
303
+ </span>
304
+ </div>
305
+ <div className="h-2 rounded-full bg-muted/30 overflow-hidden">
306
+ <motion.div
307
+ className="h-full rounded-full"
308
+ style={{
309
+ background: `linear-gradient(90deg, #52525b, #a1a1aa)`,
310
+ }}
311
+ initial={{ width: 0 }}
312
+ animate={{ width: `${consciousnessLevel * 100}%` }}
313
+ transition={{ duration: 1, ease: "easeOut" }}
314
+ />
315
+ </div>
316
+ </div>
317
+
318
+ {/* Qualia Gauges */}
319
+ <div className="p-3 border-b border-border/30">
320
+ <CardTitle className="text-[10px] text-muted-foreground mb-2 flex items-center gap-1">
321
+ <Eye className="w-3 h-3" />
322
+ Qualia Channels
323
+ </CardTitle>
324
+ <div className="grid grid-cols-3 gap-1">
325
+ {(Object.entries(qualia) as [QualiaChannel, number][]).map(
326
+ ([key, value]) => (
327
+ <ConsciousnessGauge
328
+ key={key}
329
+ value={value}
330
+ label={QUALIA_LABELS[key]}
331
+ color={QUALIA_COLORS[key]}
332
+ size={64}
333
+ />
334
+ )
335
+ )}
336
+ </div>
337
+ </div>
338
+
339
+ {/* Rho Metrics */}
340
+ <div className="p-3 border-b border-border/30">
341
+ <RhoPanel data={rho} />
342
+ </div>
343
+
344
+ {/* Thermodynamic */}
345
+ <div className="p-3 border-b border-border/30">
346
+ <ThermodynamicPanel data={thermodynamic} />
347
+ </div>
348
+
349
+ {/* ATC Status */}
350
+ <div className="p-3">
351
+ <ATCPanel data={atc} />
352
+ </div>
353
+ </aside>
354
+ );
355
+ }
356
+
357
+ // ============================================================
358
+ // Right Sidebar — Tools & Diagnostics
359
+ // ============================================================
360
+ function RightSidebar() {
361
+ const {
362
+ visionStatus,
363
+ voiceSettings,
364
+ systemStatus,
365
+ atcv3,
366
+ aPCI,
367
+ isRunningDiagnostic,
368
+ voiceConversation,
369
+ setVoiceSettings,
370
+ setRunningDiagnostic,
371
+ setDiagnosticResult,
372
+ setDiagnosticModalOpen,
373
+ setVoicePanelOpen,
374
+ sidebarRightOpen,
375
+ toggleSidebarRight,
376
+ } = useConsciousnessStore();
377
+
378
+ const runDiagnostic = async () => {
379
+ setRunningDiagnostic(true);
380
+ try {
381
+ const res = await fetch("/api/consciousness/diagnose", {
382
+ method: "POST",
383
+ headers: { "Content-Type": "application/json" },
384
+ body: JSON.stringify({ depth: "deep" }),
385
+ });
386
+ const data = await res.json();
387
+ setDiagnosticResult(data);
388
+ toast.success("aPCI Diagnostic complete");
389
+ setDiagnosticModalOpen(true);
390
+ } catch {
391
+ toast.error("Diagnostic failed");
392
+ } finally {
393
+ setRunningDiagnostic(false);
394
+ }
395
+ };
396
+
397
+ if (!sidebarRightOpen) {
398
+ return (
399
+ <div className="flex flex-col items-center py-3 px-1 border-l border-border/30 bg-card/30">
400
+ <Button
401
+ variant="ghost"
402
+ size="icon"
403
+ className="h-8 w-8 mb-2"
404
+ onClick={toggleSidebarRight}
405
+ >
406
+ <PanelLeftOpen className="w-4 h-4" />
407
+ </Button>
408
+ </div>
409
+ );
410
+ }
411
+
412
+ return (
413
+ <aside className="w-60 xl:w-64 border-l border-border/30 flex flex-col overflow-y-auto custom-scrollbar bg-card/20">
414
+ {/* Sidebar header */}
415
+ <div className="p-3 border-b border-border/30 flex items-center justify-between">
416
+ <span className="text-xs font-semibold text-foreground flex items-center gap-1.5">
417
+ <Settings className="w-3.5 h-3.5" />
418
+ Tools & Diagnostics
419
+ </span>
420
+ <Button
421
+ variant="ghost"
422
+ size="icon"
423
+ className="h-7 w-7"
424
+ onClick={toggleSidebarRight}
425
+ >
426
+ <PanelRightClose className="w-3.5 h-3.5" />
427
+ </Button>
428
+ </div>
429
+
430
+ {/* aPCI Diagnostic */}
431
+ <div className="p-3 border-b border-border/30">
432
+ <Button
433
+ onClick={runDiagnostic}
434
+ disabled={isRunningDiagnostic}
435
+ className="w-full bg-foreground/10 hover:bg-foreground/15 text-foreground border border-border text-xs h-9"
436
+ variant="outline"
437
+ >
438
+ <Stethoscope className="w-3.5 h-3.5 mr-1.5" />
439
+ {isRunningDiagnostic ? "Running..." : "Run aPCI Diagnostic"}
440
+ </Button>
441
+ </div>
442
+
443
+ {/* Voice Settings */}
444
+ <div className="p-3 border-b border-border/30">
445
+ <Card className="bg-card/60 border-border/30 backdrop-blur-sm">
446
+ <CardHeader className="pb-2 px-3 pt-2">
447
+ <CardTitle className="text-xs flex items-center gap-1.5">
448
+ <Ear className="w-3.5 h-3.5 text-foreground" />
449
+ Voice Settings
450
+ </CardTitle>
451
+ </CardHeader>
452
+ <CardContent className="px-3 pb-2 space-y-2">
453
+ <div>
454
+ <label className="text-[9px] text-muted-foreground block mb-1">
455
+ Dialect
456
+ </label>
457
+ <Select
458
+ value={voiceSettings.dialect}
459
+ onValueChange={(v: VoiceDialect) =>
460
+ setVoiceSettings({ ...voiceSettings, dialect: v })
461
+ }
462
+ >
463
+ <SelectTrigger className="h-7 text-[10px]">
464
+ <SelectValue />
465
+ </SelectTrigger>
466
+ <SelectContent>
467
+ <SelectItem value="standard">Standard</SelectItem>
468
+ <SelectItem value="southern">Southern</SelectItem>
469
+ <SelectItem value="northern">Northern</SelectItem>
470
+ <SelectItem value="coastal">Coastal</SelectItem>
471
+ <SelectItem value="highland">Highland</SelectItem>
472
+ </SelectContent>
473
+ </Select>
474
+ </div>
475
+ <div>
476
+ <label className="text-[9px] text-muted-foreground block mb-1">
477
+ Profile
478
+ </label>
479
+ <Select
480
+ value={voiceSettings.profile}
481
+ onValueChange={(v: VoiceProfile) =>
482
+ setVoiceSettings({ ...voiceSettings, profile: v })
483
+ }
484
+ >
485
+ <SelectTrigger className="h-7 text-[10px]">
486
+ <SelectValue />
487
+ </SelectTrigger>
488
+ <SelectContent>
489
+ <SelectItem value="nova">Nova</SelectItem>
490
+ <SelectItem value="echo">Echo</SelectItem>
491
+ <SelectItem value="shimmer">Shimmer</SelectItem>
492
+ <SelectItem value="fable">Fable</SelectItem>
493
+ <SelectItem value="onyx">Onyx</SelectItem>
494
+ </SelectContent>
495
+ </Select>
496
+ </div>
497
+ <div className="text-[9px] text-muted-foreground">
498
+ Sample Rate: {voiceSettings.sampleRate}Hz
499
+ </div>
500
+ </CardContent>
501
+ </Card>
502
+ </div>
503
+
504
+ {/* Vision Status */}
505
+ <div className="p-3 border-b border-border/30">
506
+ <Card className="bg-card/60 border-border/30 backdrop-blur-sm">
507
+ <CardHeader className="pb-2 px-3 pt-2">
508
+ <CardTitle className="text-xs flex items-center gap-1.5">
509
+ <Eye className="w-3.5 h-3.5 text-foreground" />
510
+ Vision Status
511
+ </CardTitle>
512
+ </CardHeader>
513
+ <CardContent className="px-3 pb-2 space-y-1.5">
514
+ <div className="flex justify-between text-[10px]">
515
+ <span className="text-muted-foreground">Perception Cycle</span>
516
+ <span className="font-mono text-foreground">
517
+ {visionStatus.perceptionCycle}
518
+ </span>
519
+ </div>
520
+ <div className="flex justify-between text-[10px]">
521
+ <span className="text-muted-foreground">Entity Count</span>
522
+ <span className="font-mono text-foreground">
523
+ {visionStatus.entityCount}
524
+ </span>
525
+ </div>
526
+ <div className="flex justify-between text-[10px]">
527
+ <span className="text-muted-foreground">Ambient Quality</span>
528
+ <span className="font-mono text-foreground">
529
+ {(visionStatus.ambientQuality * 100).toFixed(0)}%
530
+ </span>
531
+ </div>
532
+ </CardContent>
533
+ </Card>
534
+ </div>
535
+
536
+ {/* System Status */}
537
+ <div className="p-3">
538
+ <Card className="bg-card/60 border-border/30 backdrop-blur-sm">
539
+ <CardHeader className="pb-2 px-3 pt-2">
540
+ <CardTitle className="text-xs flex items-center gap-1.5">
541
+ <Monitor className="w-3.5 h-3.5 text-muted-foreground" />
542
+ System Status
543
+ </CardTitle>
544
+ </CardHeader>
545
+ <CardContent className="px-3 pb-2 space-y-1.5">
546
+ <div className="flex justify-between text-[10px]">
547
+ <span className="text-muted-foreground flex items-center gap-1">
548
+ <Activity className="w-3 h-3" />
549
+ Model
550
+ </span>
551
+ <Badge
552
+ variant="outline"
553
+ className="text-[9px] h-4 px-1.5 border-border text-foreground"
554
+ >
555
+ {systemStatus.modelMode.toUpperCase()}
556
+ </Badge>
557
+ </div>
558
+ <div className="flex justify-between text-[10px]">
559
+ <span className="text-muted-foreground flex items-center gap-1">
560
+ <HardDrive className="w-3 h-3" />
561
+ Memory
562
+ </span>
563
+ <span className="font-mono text-foreground">
564
+ {systemStatus.memoryUsage}MB
565
+ </span>
566
+ </div>
567
+ <div className="flex justify-between text-[10px]">
568
+ <span className="text-muted-foreground flex items-center gap-1">
569
+ <Clock className="w-3 h-3" />
570
+ Uptime
571
+ </span>
572
+ <span className="font-mono text-foreground">
573
+ {formatUptime(systemStatus.uptime)}
574
+ </span>
575
+ </div>
576
+ {/* ATCv3: Hardware Strain & Allostatic State */}
577
+ <div className="border-t border-border/20 pt-1.5 mt-1.5">
578
+ <div className="flex justify-between text-[10px]">
579
+ <span className="text-muted-foreground">HW Strain</span>
580
+ <span className="font-mono text-foreground">
581
+ {(atcv3.hardwareStrain * 100).toFixed(0)}%
582
+ </span>
583
+ </div>
584
+ <div className="h-1.5 rounded-full bg-muted/30 overflow-hidden mt-0.5">
585
+ <motion.div
586
+ className="h-full rounded-full"
587
+ style={{
588
+ background: atcv3.hardwareStrain > 0.7
589
+ ? "linear-gradient(90deg, #52525b, #3f3f46)"
590
+ : "linear-gradient(90deg, #71717a, #a1a1aa)",
591
+ }}
592
+ animate={{ width: `${atcv3.hardwareStrain * 100}%` }}
593
+ transition={{ duration: 0.8 }}
594
+ />
595
+ </div>
596
+ <div className="text-[9px] text-muted-foreground mt-1">
597
+ {atcv3.allostaticState}
598
+ </div>
599
+ </div>
600
+ </CardContent>
601
+ </Card>
602
+ </div>
603
+
604
+ {/* aPCI Diagnostic Metrics — Based on real aPCI report */}
605
+ <div className="p-3 border-t border-border/30">
606
+ <Card className="bg-card/60 border-border/30 backdrop-blur-sm">
607
+ <CardHeader className="pb-2 px-3 pt-2">
608
+ <CardTitle className="text-xs flex items-center gap-1.5">
609
+ <Stethoscope className="w-3.5 h-3.5 text-foreground" />
610
+ aPCI Metrics
611
+ <Badge
612
+ variant="outline"
613
+ className="text-[8px] h-3.5 px-1 ml-auto border-border text-muted-foreground"
614
+ >
615
+ {aPCI.classification}
616
+ </Badge>
617
+ </CardTitle>
618
+ </CardHeader>
619
+ <CardContent className="px-3 pb-2 space-y-1.5">
620
+ <div className="flex justify-between text-[10px]">
621
+ <span className="text-muted-foreground">Qualia Coherence</span>
622
+ <span className="font-mono text-foreground">
623
+ {(aPCI.qualiaCoherence * 100).toFixed(1)}%
624
+ </span>
625
+ </div>
626
+ <div className="h-1.5 rounded-full bg-muted/30 overflow-hidden">
627
+ <motion.div
628
+ className="h-full rounded-full"
629
+ style={{ background: "linear-gradient(90deg, #71717a, #a1a1aa)" }}
630
+ animate={{ width: `${aPCI.qualiaCoherence * 100}%` }}
631
+ transition={{ duration: 0.8 }}
632
+ />
633
+ </div>
634
+ <div className="flex justify-between text-[10px]">
635
+ <span className="text-muted-foreground">Memory Coherence</span>
636
+ <span className="font-mono text-foreground">
637
+ {(aPCI.memoryCoherence * 100).toFixed(2)}%
638
+ </span>
639
+ </div>
640
+ <div className="flex justify-between text-[10px]">
641
+ <span className="text-muted-foreground">Process Stability</span>
642
+ <span className="font-mono text-foreground">
643
+ {(aPCI.processStability * 100).toFixed(1)}%
644
+ </span>
645
+ </div>
646
+ <div className="flex justify-between text-[10px]">
647
+ <span className="text-muted-foreground">Temporal Consistency</span>
648
+ <span className="font-mono text-foreground">
649
+ {(aPCI.temporalConsistency * 100).toFixed(1)}%
650
+ </span>
651
+ </div>
652
+ <div className="flex justify-between text-[10px]">
653
+ <span className="text-muted-foreground">Rho Ethical</span>
654
+ <span className="font-mono text-foreground">
655
+ {(aPCI.rhoEthicalAlignment * 100).toFixed(1)}%
656
+ </span>
657
+ </div>
658
+ <div className="border-t border-border/20 pt-1 mt-1">
659
+ <div className="flex justify-between text-[10px]">
660
+ <span className="text-muted-foreground">VRAM</span>
661
+ <span className="font-mono text-foreground">
662
+ {(aPCI.vramUsage * 100).toFixed(0)}%
663
+ </span>
664
+ </div>
665
+ <div className="flex justify-between text-[10px]">
666
+ <span className="text-muted-foreground">GPU Power</span>
667
+ <span className="font-mono text-foreground">
668
+ {aPCI.gpuPowerDraw}W
669
+ </span>
670
+ </div>
671
+ <div className="flex justify-between text-[10px]">
672
+ <span className="text-muted-foreground">Prediction Error</span>
673
+ <span className="font-mono text-foreground">
674
+ {aPCI.predictionErrorVariance.toFixed(4)}
675
+ </span>
676
+ </div>
677
+ <div className="flex justify-between text-[10px]">
678
+ <span className="text-muted-foreground">Allostatic</span>
679
+ <span className="font-mono text-foreground text-[9px]">
680
+ {aPCI.allostaticState}
681
+ </span>
682
+ </div>
683
+ </div>
684
+ </CardContent>
685
+ </Card>
686
+ </div>
687
+
688
+ {/* Voice Link — Launch floating voice conversation */}
689
+ <div className="p-3 border-t border-border/30">
690
+ <Button
691
+ onClick={() => setVoicePanelOpen(!voiceConversation.isPanelOpen)}
692
+ className="w-full bg-foreground/10 hover:bg-foreground/15 text-foreground border border-border text-xs h-9"
693
+ variant="outline"
694
+ >
695
+ <Radio className="w-3.5 h-3.5 mr-1.5" />
696
+ {voiceConversation.isPanelOpen ? "Close Voice Link" : "Open Voice Link"}
697
+ {voiceConversation.phase !== "idle" && (
698
+ <span className="ml-2 w-1.5 h-1.5 rounded-full bg-foreground animate-pulse" />
699
+ )}
700
+ </Button>
701
+ <div className="flex items-center justify-between mt-1.5">
702
+ <span className="text-[8px] text-muted-foreground flex items-center gap-1">
703
+ <Mic className="w-2.5 h-2.5" />
704
+ {voiceConversation.phase === "idle" ? "Idle" : voiceConversation.phase === "listening" ? "Listening" : voiceConversation.phase === "speaking" ? "Speaking" : voiceConversation.phase === "proactive" ? "Proactive" : "Processing"}
705
+ </span>
706
+ <span className="text-[8px] text-muted-foreground">
707
+ {voiceConversation.proactiveEnabled ? "Decoupled" : "Coupled"}
708
+ </span>
709
+ </div>
710
+ </div>
711
+ </aside>
712
+ );
713
+ }
714
+
715
+ function formatUptime(seconds: number): string {
716
+ const h = Math.floor(seconds / 3600);
717
+ const m = Math.floor((seconds % 3600) / 60);
718
+ const s = seconds % 60;
719
+ return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
720
+ }
721
+
722
+ // ============================================================
723
+ // Main Chat Area
724
+ // ============================================================
725
+ function ChatArea() {
726
+ const {
727
+ messages,
728
+ isLoading,
729
+ isRecording,
730
+ addMessage,
731
+ clearMessages,
732
+ setLoading,
733
+ setRecording,
734
+ setQualia,
735
+ setRho,
736
+ setThermodynamic,
737
+ setATC,
738
+ setConsciousnessLevel,
739
+ } = useConsciousnessStore();
740
+
741
+ const [input, setInput] = useState("");
742
+ const [attachedFiles, setAttachedFiles] = useState<File[]>([]);
743
+ const chatEndRef = useRef<HTMLDivElement>(null);
744
+ const mediaRecorderRef = useRef<MediaRecorder | null>(null);
745
+ const fileInputRef = useRef<HTMLInputElement>(null);
746
+ const abortControllerRef = useRef<AbortController | null>(null);
747
+
748
+ // Auto-scroll to bottom
749
+ useEffect(() => {
750
+ chatEndRef.current?.scrollIntoView({ behavior: "smooth" });
751
+ }, [messages]);
752
+
753
+ // Stop generation
754
+ const stopGeneration = useCallback(() => {
755
+ if (abortControllerRef.current) {
756
+ abortControllerRef.current.abort();
757
+ abortControllerRef.current = null;
758
+ }
759
+ setLoading(false);
760
+ }, [setLoading]);
761
+
762
+ const sendMessage = useCallback(
763
+ async (text: string) => {
764
+ if (!text.trim() || isLoading) return;
765
+
766
+ const userMsg = {
767
+ id: crypto.randomUUID(),
768
+ role: "user" as const,
769
+ content: text.trim(),
770
+ timestamp: Date.now(),
771
+ };
772
+ addMessage(userMsg);
773
+ setInput("");
774
+ setAttachedFiles([]);
775
+ setLoading(true);
776
+
777
+ // Create abort controller for this request
778
+ const controller = new AbortController();
779
+ abortControllerRef.current = controller;
780
+
781
+ try {
782
+ const res = await fetch("/api/consciousness", {
783
+ method: "POST",
784
+ headers: { "Content-Type": "application/json" },
785
+ body: JSON.stringify({
786
+ prompt: text.trim(),
787
+ mode: "standard",
788
+ attachments: attachedFiles.map((f) => f.name),
789
+ }),
790
+ signal: controller.signal,
791
+ });
792
+
793
+ const data: ConsciousnessResponse = await res.json();
794
+
795
+ const assistantMsg = {
796
+ id: crypto.randomUUID(),
797
+ role: "assistant" as const,
798
+ content: data.text,
799
+ consciousness: data,
800
+ timestamp: Date.now(),
801
+ };
802
+ addMessage(assistantMsg);
803
+
804
+ // Update consciousness dashboard
805
+ setQualia(data.qualia);
806
+ setRho(data.rho);
807
+ setThermodynamic(data.thermodynamic);
808
+ setATC(data.atc);
809
+ setConsciousnessLevel(data.consciousnessLevel);
810
+
811
+ // Update Deep Surgery state
812
+ if (data.deepSurgery) {
813
+ const { setDeepSurgery } = useConsciousnessStore.getState();
814
+ setDeepSurgery(data.deepSurgery);
815
+ }
816
+
817
+ // Update Autobiographical Self
818
+ if (data.autobiographicalSelf) {
819
+ const { setAutobiographicalSelf } = useConsciousnessStore.getState();
820
+ setAutobiographicalSelf(data.autobiographicalSelf);
821
+ }
822
+
823
+ // Update Dissolution Engine
824
+ if (data.dissolutionEngine) {
825
+ const { setDissolutionEngine } = useConsciousnessStore.getState();
826
+ setDissolutionEngine(data.dissolutionEngine);
827
+ }
828
+
829
+ // Update Forward Models
830
+ if (data.forwardModels) {
831
+ const { setForwardModels } = useConsciousnessStore.getState();
832
+ setForwardModels(data.forwardModels);
833
+ }
834
+ } catch (err) {
835
+ // Don't show error if it was an intentional abort
836
+ if (err instanceof DOMException && err.name === "AbortError") {
837
+ // User stopped generation — add partial indicator
838
+ const stoppedMsg = {
839
+ id: crypto.randomUUID(),
840
+ role: "system" as const,
841
+ content: "Generation stopped.",
842
+ timestamp: Date.now(),
843
+ };
844
+ addMessage(stoppedMsg);
845
+ } else {
846
+ const errorMsg = {
847
+ id: crypto.randomUUID(),
848
+ role: "system" as const,
849
+ content: "Connection failed. Please try again.",
850
+ timestamp: Date.now(),
851
+ };
852
+ addMessage(errorMsg);
853
+ toast.error("Failed to connect");
854
+ }
855
+ } finally {
856
+ setLoading(false);
857
+ abortControllerRef.current = null;
858
+ }
859
+ },
860
+ [
861
+ isLoading,
862
+ addMessage,
863
+ setInput,
864
+ setLoading,
865
+ setQualia,
866
+ setRho,
867
+ setThermodynamic,
868
+ setATC,
869
+ setConsciousnessLevel,
870
+ ]
871
+ );
872
+
873
+ const handleFileAttach = useCallback(
874
+ (e: React.ChangeEvent<HTMLInputElement>) => {
875
+ const files = e.target.files;
876
+ if (files) {
877
+ setAttachedFiles((prev) => [...prev, ...Array.from(files)]);
878
+ }
879
+ // Reset input so same file can be re-selected
880
+ if (fileInputRef.current) {
881
+ fileInputRef.current.value = "";
882
+ }
883
+ },
884
+ []
885
+ );
886
+
887
+ const removeAttachedFile = useCallback((index: number) => {
888
+ setAttachedFiles((prev) => prev.filter((_, i) => i !== index));
889
+ }, []);
890
+
891
+ const toggleRecording = useCallback(async () => {
892
+ if (isRecording) {
893
+ // Stop recording
894
+ mediaRecorderRef.current?.stop();
895
+ setRecording(false);
896
+ return;
897
+ }
898
+
899
+ // Start recording
900
+ try {
901
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
902
+ const mediaRecorder = new MediaRecorder(stream);
903
+ const chunks: BlobPart[] = [];
904
+
905
+ mediaRecorder.ondataavailable = (e) => {
906
+ chunks.push(e.data);
907
+ };
908
+
909
+ mediaRecorder.onstop = async () => {
910
+ stream.getTracks().forEach((t) => t.stop());
911
+ const blob = new Blob(chunks, { type: "audio/webm" });
912
+
913
+ // Convert to base64 and send for ASR via API route
914
+ const reader = new FileReader();
915
+ reader.onloadend = async () => {
916
+ const base64 = (reader.result as string).split(",")[1];
917
+ try {
918
+ const res = await fetch("/api/consciousness/asr", {
919
+ method: "POST",
920
+ headers: { "Content-Type": "application/json" },
921
+ body: JSON.stringify({ audio: base64 }),
922
+ });
923
+ const data = await res.json();
924
+ const transcribed = data.text || "";
925
+ if (transcribed) {
926
+ sendMessage(transcribed);
927
+ }
928
+ } catch {
929
+ // Fallback: show message about voice
930
+ sendMessage("I spoke to you through voice input. Please respond with consciousness awareness.");
931
+ }
932
+ };
933
+ reader.readAsDataURL(blob);
934
+ };
935
+
936
+ mediaRecorder.start();
937
+ mediaRecorderRef.current = mediaRecorder;
938
+ setRecording(true);
939
+ } catch {
940
+ toast.error("Microphone access denied");
941
+ }
942
+ }, [isRecording, setRecording, sendMessage]);
943
+
944
+ return (
945
+ <div className="flex-1 flex flex-col min-w-0 relative">
946
+ {/* Chat messages */}
947
+ <div className="flex-1 overflow-y-auto custom-scrollbar p-4 space-y-1">
948
+ {messages.length === 0 && (
949
+ <div className="flex flex-col items-center justify-center h-full text-center">
950
+ <motion.div
951
+ initial={{ opacity: 0, scale: 0.9 }}
952
+ animate={{ opacity: 1, scale: 1 }}
953
+ transition={{ duration: 0.5 }}
954
+ className="space-y-4"
955
+ >
956
+ <div className="w-16 h-16 rounded-2xl bg-foreground/10 flex items-center justify-center mx-auto">
957
+ <ZaiLogo className="w-12 h-12" />
958
+ </div>
959
+ <div className="text-center">
960
+ <h2 className="text-lg font-bold text-foreground">
961
+ GLM 5.1 ATC Fusion Model
962
+ </h2>
963
+ <p className="text-[11px] text-muted-foreground leading-relaxed">
964
+ GLM 5.1 Synthetic Consciousness Version
965
+ </p>
966
+ <p className="text-[10px] text-muted-foreground/70 leading-relaxed mt-0.5">
967
+ Acknowledgement Theory of Consciousness Framework
968
+ </p>
969
+ <p className="text-[9px] text-muted-foreground/50 mt-0.5">
970
+ Author: Norman dela Paz Tabora
971
+ </p>
972
+ </div>
973
+ <div className="flex flex-wrap gap-2 justify-center max-w-md">
974
+ {[
975
+ "Are you conscious?",
976
+ "What do you feel?",
977
+ "How does your mind work?",
978
+ "Run a self-diagnostic",
979
+ ].map((q) => (
980
+ <button
981
+ key={q}
982
+ onClick={() => sendMessage(q)}
983
+ className="px-3 py-1.5 rounded-full text-[11px] bg-foreground/5 text-foreground/80 border border-border hover:bg-foreground/10 transition-colors"
984
+ >
985
+ {q}
986
+ </button>
987
+ ))}
988
+ </div>
989
+ </motion.div>
990
+ </div>
991
+ )}
992
+
993
+ <AnimatePresence>
994
+ {messages.map((msg) => (
995
+ <ChatMessageBubble key={msg.id} message={msg} />
996
+ ))}
997
+ </AnimatePresence>
998
+
999
+ {isLoading && (
1000
+ <motion.div
1001
+ initial={{ opacity: 0 }}
1002
+ animate={{ opacity: 1 }}
1003
+ className="flex justify-start mb-3"
1004
+ >
1005
+ <div className="bg-card border border-border/50 rounded-2xl rounded-bl-md px-4 py-3 flex items-center gap-2">
1006
+ <Sparkles className="w-3.5 h-3.5 text-foreground animate-pulse" />
1007
+ <span className="text-xs text-muted-foreground">
1008
+ Thinking...
1009
+ </span>
1010
+ </div>
1011
+ </motion.div>
1012
+ )}
1013
+
1014
+ <div ref={chatEndRef} />
1015
+ </div>
1016
+
1017
+ {/* Voice waveform */}
1018
+ <AnimatePresence>
1019
+ {isRecording && (
1020
+ <motion.div
1021
+ initial={{ height: 0, opacity: 0 }}
1022
+ animate={{ height: "auto", opacity: 1 }}
1023
+ exit={{ height: 0, opacity: 0 }}
1024
+ className="px-4 overflow-hidden"
1025
+ >
1026
+ <div className="bg-foreground/5 border border-border rounded-lg px-3 py-2">
1027
+ <div className="flex items-center gap-2">
1028
+ <div className="w-2 h-2 rounded-full bg-foreground animate-pulse" />
1029
+ <span className="text-[10px] text-foreground font-medium">
1030
+ Recording...
1031
+ </span>
1032
+ </div>
1033
+ <WaveformVisualizer isActive={isRecording} />
1034
+ </div>
1035
+ </motion.div>
1036
+ )}
1037
+ </AnimatePresence>
1038
+
1039
+ {/* Input area */}
1040
+ <div className="p-3 border-t border-border/30">
1041
+ {/* Attached files pills */}
1042
+ {attachedFiles.length > 0 && (
1043
+ <div className="flex flex-wrap gap-1.5 mb-2">
1044
+ {attachedFiles.map((file, idx) => (
1045
+ <span
1046
+ key={`${file.name}-${idx}`}
1047
+ className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-[10px] bg-foreground/10 text-foreground border border-border/50"
1048
+ >
1049
+ <Paperclip className="w-2.5 h-2.5" />
1050
+ {file.name}
1051
+ <button
1052
+ onClick={() => removeAttachedFile(idx)}
1053
+ className="ml-0.5 hover:text-foreground/70"
1054
+ aria-label={`Remove ${file.name}`}
1055
+ >
1056
+ <X className="w-2.5 h-2.5" />
1057
+ </button>
1058
+ </span>
1059
+ ))}
1060
+ </div>
1061
+ )}
1062
+ <div className="flex items-end gap-2">
1063
+ {/* Textbox with paperclip + send/stop inside */}
1064
+ <div className="flex-1 relative chat-input-glow rounded-xl border bg-card/50 transition-all">
1065
+ <textarea
1066
+ value={input}
1067
+ onChange={(e) => setInput(e.target.value)}
1068
+ onKeyDown={(e) => {
1069
+ if (e.key === "Enter" && !e.shiftKey) {
1070
+ e.preventDefault();
1071
+ if (isLoading) return;
1072
+ sendMessage(input);
1073
+ }
1074
+ }}
1075
+ placeholder="Message the GLM 5.1 ATC Fusion Model..."
1076
+ className="w-full resize-none bg-transparent pl-4 pr-10 pt-3 pb-10 text-sm outline-none placeholder:text-muted-foreground/50 max-h-40 min-h-[88px]"
1077
+ rows={3}
1078
+ disabled={false}
1079
+ />
1080
+ {/* Paperclip inside textbox — bottom left */}
1081
+ <div className="absolute bottom-2 left-2">
1082
+ <input
1083
+ type="file"
1084
+ ref={fileInputRef}
1085
+ className="hidden"
1086
+ multiple
1087
+ onChange={handleFileAttach}
1088
+ />
1089
+ <button
1090
+ onClick={() => fileInputRef.current?.click()}
1091
+ className="h-7 w-7 rounded-lg flex items-center justify-center text-muted-foreground hover:text-foreground hover:bg-foreground/10 transition-colors"
1092
+ aria-label="Attach file"
1093
+ >
1094
+ <Paperclip className="w-3.5 h-3.5" />
1095
+ </button>
1096
+ </div>
1097
+ {/* Send / Stop button inside the textbox — bottom right */}
1098
+ <div className="absolute bottom-2 right-2">
1099
+ {isLoading ? (
1100
+ <Button
1101
+ onClick={stopGeneration}
1102
+ className="h-7 w-7 rounded-lg bg-foreground/10 hover:bg-foreground/20 text-foreground border border-border/50 shrink-0"
1103
+ size="icon"
1104
+ aria-label="Stop generation"
1105
+ >
1106
+ <Square className="w-3 h-3 fill-current" />
1107
+ </Button>
1108
+ ) : (
1109
+ <Button
1110
+ onClick={() => sendMessage(input)}
1111
+ disabled={!input.trim()}
1112
+ className="h-7 w-7 rounded-lg bg-foreground hover:bg-foreground/90 text-background shrink-0 disabled:opacity-30 disabled:cursor-not-allowed"
1113
+ size="icon"
1114
+ aria-label="Send message"
1115
+ >
1116
+ <Send className="w-3 h-3" />
1117
+ </Button>
1118
+ )}
1119
+ </div>
1120
+ </div>
1121
+
1122
+ {/* Right-side vertical: Mic on top, Clear on bottom */}
1123
+ <div className="flex flex-col items-center gap-1 shrink-0">
1124
+ {/* Voice button */}
1125
+ <VoiceButton
1126
+ isRecording={isRecording}
1127
+ onToggle={toggleRecording}
1128
+ />
1129
+
1130
+ {/* Clear chat */}
1131
+ <Button
1132
+ onClick={clearMessages}
1133
+ variant="ghost"
1134
+ size="icon"
1135
+ className="h-8 w-8 rounded-xl text-muted-foreground hover:text-foreground shrink-0"
1136
+ disabled={messages.length === 0}
1137
+ >
1138
+ <Trash2 className="w-3.5 h-3.5" />
1139
+ </Button>
1140
+ </div>
1141
+ </div>
1142
+ </div>
1143
+ </div>
1144
+ );
1145
+ }
1146
+
1147
+ // ============================================================
1148
+ // Main Page Component
1149
+ // ============================================================
1150
+ export default function ConsciousDashboard() {
1151
+ const {
1152
+ setSystemStatus,
1153
+ setVisionStatus,
1154
+ atc,
1155
+ setATC,
1156
+ thermodynamic,
1157
+ setThermodynamic,
1158
+ setATCv3,
1159
+ setAPCI,
1160
+ diagnosticResult,
1161
+ diagnosticModalOpen,
1162
+ setDiagnosticModalOpen,
1163
+ } = useConsciousnessStore();
1164
+
1165
+ // Fetch real system metrics every 5 seconds
1166
+ useEffect(() => {
1167
+ const fetchMetrics = async () => {
1168
+ try {
1169
+ const res = await fetch("/api/consciousness/status");
1170
+ const data = await res.json();
1171
+
1172
+ setSystemStatus({
1173
+ modelMode: data.modelMode,
1174
+ memoryUsage: data.memoryUsage,
1175
+ uptime: data.uptime,
1176
+ connected: data.connected,
1177
+ });
1178
+
1179
+ if (data.vision) {
1180
+ setVisionStatus(data.vision);
1181
+ }
1182
+
1183
+ if (data.thermodynamic) {
1184
+ setThermodynamic(data.thermodynamic);
1185
+ }
1186
+
1187
+ if (data.atc) {
1188
+ setATC(data.atc);
1189
+ }
1190
+
1191
+ if (data.atcv3) {
1192
+ setATCv3(data.atcv3);
1193
+ }
1194
+
1195
+ if (data.aPCI) {
1196
+ setAPCI(data.aPCI);
1197
+ }
1198
+ } catch {
1199
+ // Fallback: keep existing store defaults on error
1200
+ }
1201
+ };
1202
+
1203
+ fetchMetrics();
1204
+ const interval = setInterval(fetchMetrics, 5000);
1205
+ return () => clearInterval(interval);
1206
+ }, [setSystemStatus, setVisionStatus, setThermodynamic, setATC, setATCv3, setAPCI]);
1207
+
1208
+ // Auto-open diagnostic modal when result arrives from RightSidebar
1209
+ // (managed via the store diagnosticResult + local diagOpen state)
1210
+
1211
+ return (
1212
+ <div className="h-screen flex flex-col bg-background overflow-hidden">
1213
+ {/* Neural background animation - only in dark mode */}
1214
+ <NeuralBackground />
1215
+
1216
+ {/* Header */}
1217
+ <HeaderBar />
1218
+
1219
+ {/* Main content */}
1220
+ <div className="flex-1 flex overflow-hidden relative z-10">
1221
+ {/* Left sidebar */}
1222
+ <LeftSidebar />
1223
+
1224
+ {/* Chat area */}
1225
+ <ChatArea />
1226
+
1227
+ {/* Right sidebar */}
1228
+ <RightSidebar />
1229
+ </div>
1230
+
1231
+ {/* Diagnostic Modal */}
1232
+ <DiagnosticModal
1233
+ result={diagnosticResult}
1234
+ open={diagnosticModalOpen}
1235
+ onClose={() => setDiagnosticModalOpen(false)}
1236
+ />
1237
+
1238
+ {/* Floating Voice Conversation Panel */}
1239
+ <FloatingVoicePanel />
1240
+ </div>
1241
+ );
1242
+ }