@cortexmemory/cli 0.26.2 → 0.27.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.
Files changed (58) hide show
  1. package/dist/commands/dev.d.ts.map +1 -1
  2. package/dist/commands/dev.js +121 -10
  3. package/dist/commands/dev.js.map +1 -1
  4. package/dist/commands/init.d.ts.map +1 -1
  5. package/dist/commands/init.js +273 -43
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/setup.d.ts.map +1 -1
  8. package/dist/commands/setup.js +102 -46
  9. package/dist/commands/setup.js.map +1 -1
  10. package/dist/commands/status.d.ts.map +1 -1
  11. package/dist/commands/status.js +94 -7
  12. package/dist/commands/status.js.map +1 -1
  13. package/dist/types.d.ts +23 -0
  14. package/dist/types.d.ts.map +1 -1
  15. package/dist/utils/config.d.ts +11 -0
  16. package/dist/utils/config.d.ts.map +1 -1
  17. package/dist/utils/config.js +20 -0
  18. package/dist/utils/config.js.map +1 -1
  19. package/dist/utils/init/graph-setup.d.ts.map +1 -1
  20. package/dist/utils/init/graph-setup.js +12 -0
  21. package/dist/utils/init/graph-setup.js.map +1 -1
  22. package/dist/utils/init/quickstart-setup.d.ts +87 -0
  23. package/dist/utils/init/quickstart-setup.d.ts.map +1 -0
  24. package/dist/utils/init/quickstart-setup.js +462 -0
  25. package/dist/utils/init/quickstart-setup.js.map +1 -0
  26. package/dist/utils/schema-sync.d.ts.map +1 -1
  27. package/dist/utils/schema-sync.js +27 -21
  28. package/dist/utils/schema-sync.js.map +1 -1
  29. package/package.json +3 -2
  30. package/templates/vercel-ai-quickstart/.env.local.example +45 -0
  31. package/templates/vercel-ai-quickstart/README.md +280 -0
  32. package/templates/vercel-ai-quickstart/app/api/chat/route.ts +196 -0
  33. package/templates/vercel-ai-quickstart/app/api/facts/route.ts +39 -0
  34. package/templates/vercel-ai-quickstart/app/api/health/route.ts +99 -0
  35. package/templates/vercel-ai-quickstart/app/api/memories/route.ts +37 -0
  36. package/templates/vercel-ai-quickstart/app/globals.css +114 -0
  37. package/templates/vercel-ai-quickstart/app/layout.tsx +19 -0
  38. package/templates/vercel-ai-quickstart/app/page.tsx +131 -0
  39. package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +237 -0
  40. package/templates/vercel-ai-quickstart/components/ConvexClientProvider.tsx +21 -0
  41. package/templates/vercel-ai-quickstart/components/DataPreview.tsx +57 -0
  42. package/templates/vercel-ai-quickstart/components/HealthStatus.tsx +214 -0
  43. package/templates/vercel-ai-quickstart/components/LayerCard.tsx +263 -0
  44. package/templates/vercel-ai-quickstart/components/LayerFlowDiagram.tsx +195 -0
  45. package/templates/vercel-ai-quickstart/components/MemorySpaceSwitcher.tsx +93 -0
  46. package/templates/vercel-ai-quickstart/convex/conversations.ts +67 -0
  47. package/templates/vercel-ai-quickstart/convex/facts.ts +131 -0
  48. package/templates/vercel-ai-quickstart/convex/health.ts +15 -0
  49. package/templates/vercel-ai-quickstart/convex/memories.ts +104 -0
  50. package/templates/vercel-ai-quickstart/convex/schema.ts +20 -0
  51. package/templates/vercel-ai-quickstart/convex/users.ts +105 -0
  52. package/templates/vercel-ai-quickstart/lib/animations.ts +146 -0
  53. package/templates/vercel-ai-quickstart/lib/layer-tracking.ts +214 -0
  54. package/templates/vercel-ai-quickstart/next.config.js +7 -0
  55. package/templates/vercel-ai-quickstart/package.json +41 -0
  56. package/templates/vercel-ai-quickstart/postcss.config.js +5 -0
  57. package/templates/vercel-ai-quickstart/tailwind.config.js +37 -0
  58. package/templates/vercel-ai-quickstart/tsconfig.json +33 -0
@@ -0,0 +1,214 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+
5
+ interface HealthCheck {
6
+ status: string;
7
+ latencyMs?: number;
8
+ error?: string;
9
+ }
10
+
11
+ interface HealthResponse {
12
+ status: "healthy" | "degraded" | "unhealthy";
13
+ timestamp: string;
14
+ checks: Record<string, HealthCheck>;
15
+ config: {
16
+ convexUrl: string;
17
+ publicConvexUrl: string;
18
+ openaiKey: string;
19
+ graphSync: string;
20
+ graphBackend: string;
21
+ };
22
+ }
23
+
24
+ export function HealthStatus() {
25
+ const [health, setHealth] = useState<HealthResponse | null>(null);
26
+ const [loading, setLoading] = useState(true);
27
+ const [error, setError] = useState<string | null>(null);
28
+ const [expanded, setExpanded] = useState(false);
29
+
30
+ const checkHealth = useCallback(async () => {
31
+ try {
32
+ setLoading(true);
33
+ setError(null);
34
+ const response = await fetch("/api/health");
35
+ if (!response.ok) {
36
+ throw new Error(`HTTP ${response.status}`);
37
+ }
38
+ const data = await response.json();
39
+ setHealth(data);
40
+ } catch (err) {
41
+ setError(err instanceof Error ? err.message : "Health check failed");
42
+ } finally {
43
+ setLoading(false);
44
+ }
45
+ }, []);
46
+
47
+ useEffect(() => {
48
+ checkHealth();
49
+ // Re-check every 30 seconds
50
+ const interval = setInterval(checkHealth, 30000);
51
+ return () => clearInterval(interval);
52
+ }, [checkHealth]);
53
+
54
+ const statusColor = {
55
+ healthy: "bg-green-500",
56
+ degraded: "bg-yellow-500",
57
+ unhealthy: "bg-red-500",
58
+ };
59
+
60
+ const statusIcon = {
61
+ healthy: "✓",
62
+ degraded: "⚠",
63
+ unhealthy: "✗",
64
+ };
65
+
66
+ if (loading && !health) {
67
+ return (
68
+ <div className="flex items-center gap-2 text-sm text-gray-400">
69
+ <div className="w-2 h-2 rounded-full bg-gray-500 animate-pulse" />
70
+ <span>Checking...</span>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ if (error && !health) {
76
+ return (
77
+ <button
78
+ onClick={checkHealth}
79
+ className="flex items-center gap-2 text-sm text-red-400 hover:text-red-300"
80
+ >
81
+ <div className="w-2 h-2 rounded-full bg-red-500" />
82
+ <span>Connection Error</span>
83
+ </button>
84
+ );
85
+ }
86
+
87
+ if (!health) return null;
88
+
89
+ return (
90
+ <div className="relative">
91
+ <button
92
+ onClick={() => setExpanded(!expanded)}
93
+ className="flex items-center gap-2 text-sm hover:opacity-80 transition-opacity"
94
+ >
95
+ <div
96
+ className={`w-2 h-2 rounded-full ${statusColor[health.status]} ${
97
+ loading ? "animate-pulse" : ""
98
+ }`}
99
+ />
100
+ <span
101
+ className={
102
+ health.status === "healthy"
103
+ ? "text-green-400"
104
+ : health.status === "degraded"
105
+ ? "text-yellow-400"
106
+ : "text-red-400"
107
+ }
108
+ >
109
+ {statusIcon[health.status]} Backend{" "}
110
+ {health.status.charAt(0).toUpperCase() + health.status.slice(1)}
111
+ </span>
112
+ </button>
113
+
114
+ {expanded && (
115
+ <div className="absolute right-0 top-full mt-2 w-80 bg-gray-900 border border-white/10 rounded-lg shadow-xl z-50 p-4">
116
+ <div className="flex items-center justify-between mb-3">
117
+ <h3 className="font-semibold">System Health</h3>
118
+ <button
119
+ onClick={checkHealth}
120
+ disabled={loading}
121
+ className="text-xs text-gray-400 hover:text-white disabled:opacity-50"
122
+ >
123
+ {loading ? "Checking..." : "Refresh"}
124
+ </button>
125
+ </div>
126
+
127
+ <div className="space-y-2 text-sm">
128
+ {/* Individual checks */}
129
+ {Object.entries(health.checks).map(([name, check]) => (
130
+ <div
131
+ key={name}
132
+ className="flex items-center justify-between py-1 border-b border-white/5"
133
+ >
134
+ <span className="text-gray-400 capitalize">
135
+ {name.replace(/([A-Z])/g, " $1").trim()}
136
+ </span>
137
+ <span
138
+ className={`flex items-center gap-1 ${
139
+ check.status === "ok"
140
+ ? "text-green-400"
141
+ : check.status === "warning"
142
+ ? "text-yellow-400"
143
+ : "text-red-400"
144
+ }`}
145
+ >
146
+ {check.status === "ok"
147
+ ? "✓"
148
+ : check.status === "warning"
149
+ ? "⚠"
150
+ : "✗"}
151
+ {check.latencyMs !== undefined && (
152
+ <span className="text-gray-500 text-xs ml-1">
153
+ {check.latencyMs}ms
154
+ </span>
155
+ )}
156
+ </span>
157
+ </div>
158
+ ))}
159
+
160
+ {/* Config summary */}
161
+ <div className="pt-2 mt-2 border-t border-white/10">
162
+ <div className="text-xs text-gray-500 mb-1">Configuration</div>
163
+ <div className="flex flex-wrap gap-1">
164
+ <span
165
+ className={`px-2 py-0.5 rounded text-xs ${
166
+ health.config.convexUrl === "configured"
167
+ ? "bg-green-900/30 text-green-400"
168
+ : "bg-red-900/30 text-red-400"
169
+ }`}
170
+ >
171
+ Convex
172
+ </span>
173
+ <span
174
+ className={`px-2 py-0.5 rounded text-xs ${
175
+ health.config.openaiKey === "configured"
176
+ ? "bg-green-900/30 text-green-400"
177
+ : "bg-red-900/30 text-red-400"
178
+ }`}
179
+ >
180
+ OpenAI
181
+ </span>
182
+ <span
183
+ className={`px-2 py-0.5 rounded text-xs ${
184
+ health.config.graphSync === "enabled"
185
+ ? "bg-blue-900/30 text-blue-400"
186
+ : "bg-gray-900/30 text-gray-500"
187
+ }`}
188
+ >
189
+ Graph: {health.config.graphBackend}
190
+ </span>
191
+ </div>
192
+ </div>
193
+
194
+ {/* Errors */}
195
+ {Object.entries(health.checks)
196
+ .filter(([, check]) => check.error)
197
+ .map(([name, check]) => (
198
+ <div
199
+ key={`error-${name}`}
200
+ className="mt-2 p-2 bg-red-900/20 rounded text-xs text-red-400"
201
+ >
202
+ <strong className="capitalize">{name}:</strong> {check.error}
203
+ </div>
204
+ ))}
205
+ </div>
206
+
207
+ <div className="mt-3 pt-2 border-t border-white/10 text-xs text-gray-500">
208
+ Last checked: {new Date(health.timestamp).toLocaleTimeString()}
209
+ </div>
210
+ </div>
211
+ )}
212
+ </div>
213
+ );
214
+ }
@@ -0,0 +1,263 @@
1
+ "use client";
2
+
3
+ import { motion, AnimatePresence } from "framer-motion";
4
+ import { useState } from "react";
5
+ import { DataPreview } from "./DataPreview";
6
+
7
+ type LayerStatus = "pending" | "in_progress" | "complete" | "error" | "skipped";
8
+ type RevisionAction = "ADD" | "UPDATE" | "SUPERSEDE" | "NONE";
9
+
10
+ interface LayerCardProps {
11
+ name: string;
12
+ icon: string;
13
+ status: LayerStatus;
14
+ latencyMs?: number;
15
+ data?: {
16
+ id?: string;
17
+ preview?: string;
18
+ metadata?: Record<string, unknown>;
19
+ };
20
+ compact?: boolean;
21
+ optional?: boolean;
22
+ /** Revision action taken by belief revision system (v0.24.0+) */
23
+ revisionAction?: RevisionAction;
24
+ /** Facts that were superseded (when revisionAction is "SUPERSEDE") */
25
+ supersededFacts?: string[];
26
+ }
27
+
28
+ /**
29
+ * Get badge styling for revision action
30
+ */
31
+ const revisionBadgeConfig: Record<
32
+ RevisionAction,
33
+ { color: string; bgColor: string; label: string }
34
+ > = {
35
+ ADD: {
36
+ color: "text-green-400",
37
+ bgColor: "bg-green-500/20",
38
+ label: "NEW",
39
+ },
40
+ UPDATE: {
41
+ color: "text-blue-400",
42
+ bgColor: "bg-blue-500/20",
43
+ label: "UPDATED",
44
+ },
45
+ SUPERSEDE: {
46
+ color: "text-orange-400",
47
+ bgColor: "bg-orange-500/20",
48
+ label: "SUPERSEDED",
49
+ },
50
+ NONE: {
51
+ color: "text-gray-400",
52
+ bgColor: "bg-gray-500/20",
53
+ label: "UNCHANGED",
54
+ },
55
+ };
56
+
57
+ const statusConfig: Record<
58
+ LayerStatus,
59
+ { color: string; bgColor: string; text: string }
60
+ > = {
61
+ pending: {
62
+ color: "text-gray-400",
63
+ bgColor: "bg-gray-500/20",
64
+ text: "○",
65
+ },
66
+ in_progress: {
67
+ color: "text-yellow-400",
68
+ bgColor: "bg-yellow-500/20",
69
+ text: "◐",
70
+ },
71
+ complete: {
72
+ color: "text-green-400",
73
+ bgColor: "bg-green-500/20",
74
+ text: "●",
75
+ },
76
+ error: {
77
+ color: "text-red-400",
78
+ bgColor: "bg-red-500/20",
79
+ text: "✕",
80
+ },
81
+ skipped: {
82
+ color: "text-gray-600",
83
+ bgColor: "bg-gray-800/50",
84
+ text: "○",
85
+ },
86
+ };
87
+
88
+ export function LayerCard({
89
+ name,
90
+ icon,
91
+ status,
92
+ latencyMs,
93
+ data,
94
+ compact = false,
95
+ optional = false,
96
+ revisionAction,
97
+ supersededFacts,
98
+ }: LayerCardProps) {
99
+ const [isExpanded, setIsExpanded] = useState(false);
100
+ const config = statusConfig[status];
101
+ const revisionConfig = revisionAction
102
+ ? revisionBadgeConfig[revisionAction]
103
+ : null;
104
+
105
+ if (compact) {
106
+ return (
107
+ <motion.div
108
+ className={`px-3 py-2 rounded-lg border border-white/10 ${config.bgColor} min-w-[100px]`}
109
+ animate={{
110
+ scale: status === "in_progress" ? [1, 1.02, 1] : 1,
111
+ boxShadow:
112
+ status === "complete"
113
+ ? [
114
+ "0 0 0 0 rgba(34, 197, 94, 0.4)",
115
+ "0 0 0 4px rgba(34, 197, 94, 0)",
116
+ ]
117
+ : "none",
118
+ }}
119
+ transition={{
120
+ duration: status === "in_progress" ? 1 : 0.5,
121
+ repeat: status === "in_progress" ? Infinity : 0,
122
+ }}
123
+ >
124
+ <div className="flex items-center gap-2">
125
+ <span className={`text-sm ${config.color}`}>{config.text}</span>
126
+ <span>{icon}</span>
127
+ <span className="text-xs font-medium truncate">{name}</span>
128
+ </div>
129
+ <div className="flex items-center justify-center gap-2 mt-0.5">
130
+ {latencyMs !== undefined && (
131
+ <span className="text-[10px] text-gray-500">{latencyMs}ms</span>
132
+ )}
133
+ {/* Revision action badge (v0.24.0+) */}
134
+ {revisionConfig && revisionAction !== "NONE" && (
135
+ <motion.span
136
+ initial={{ scale: 0.8, opacity: 0 }}
137
+ animate={{ scale: 1, opacity: 1 }}
138
+ className={`text-[9px] px-1.5 py-0.5 rounded ${revisionConfig.bgColor} ${revisionConfig.color} font-medium`}
139
+ >
140
+ {revisionConfig.label}
141
+ </motion.span>
142
+ )}
143
+ </div>
144
+ </motion.div>
145
+ );
146
+ }
147
+
148
+ return (
149
+ <motion.div
150
+ className={`rounded-xl border border-white/10 ${config.bgColor} overflow-hidden ${
151
+ optional && status === "skipped" ? "opacity-50" : ""
152
+ }`}
153
+ animate={{
154
+ scale: status === "in_progress" ? [1, 1.01, 1] : 1,
155
+ }}
156
+ transition={{
157
+ duration: 1,
158
+ repeat: status === "in_progress" ? Infinity : 0,
159
+ }}
160
+ layout
161
+ >
162
+ <button
163
+ onClick={() => data && setIsExpanded(!isExpanded)}
164
+ className={`w-full px-4 py-3 flex items-center gap-3 ${
165
+ data ? "cursor-pointer hover:bg-white/5" : "cursor-default"
166
+ } transition-colors`}
167
+ >
168
+ {/* Status indicator */}
169
+ <motion.div
170
+ className={`w-3 h-3 rounded-full flex-shrink-0 ${
171
+ status === "complete"
172
+ ? "bg-green-500"
173
+ : status === "in_progress"
174
+ ? "bg-yellow-500"
175
+ : status === "error"
176
+ ? "bg-red-500"
177
+ : "bg-gray-500"
178
+ }`}
179
+ animate={{
180
+ opacity: status === "in_progress" ? [0.5, 1, 0.5] : 1,
181
+ scale: status === "complete" ? [1, 1.2, 1] : 1,
182
+ }}
183
+ transition={{
184
+ duration: status === "in_progress" ? 1 : 0.3,
185
+ repeat: status === "in_progress" ? Infinity : 0,
186
+ }}
187
+ />
188
+
189
+ {/* Icon and name */}
190
+ <span className="text-lg">{icon}</span>
191
+ <span className="font-medium flex-1 text-left">{name}</span>
192
+
193
+ {/* Latency */}
194
+ {latencyMs !== undefined && (
195
+ <span className="text-sm text-gray-400">{latencyMs}ms</span>
196
+ )}
197
+
198
+ {/* Revision action badge (v0.24.0+) */}
199
+ {revisionConfig && revisionAction !== "NONE" && (
200
+ <motion.span
201
+ initial={{ scale: 0.8, opacity: 0 }}
202
+ animate={{ scale: 1, opacity: 1 }}
203
+ className={`text-[10px] px-1.5 py-0.5 rounded ${revisionConfig.bgColor} ${revisionConfig.color} font-medium`}
204
+ >
205
+ {revisionConfig.label}
206
+ </motion.span>
207
+ )}
208
+
209
+ {/* Optional badge */}
210
+ {optional && (
211
+ <span className="text-[10px] px-1.5 py-0.5 bg-white/10 rounded text-gray-400">
212
+ optional
213
+ </span>
214
+ )}
215
+
216
+ {/* Expand indicator */}
217
+ {data && (
218
+ <motion.span
219
+ className="text-gray-400"
220
+ animate={{ rotate: isExpanded ? 180 : 0 }}
221
+ >
222
+
223
+ </motion.span>
224
+ )}
225
+ </button>
226
+
227
+ {/* Expandable data preview */}
228
+ <AnimatePresence>
229
+ {isExpanded && data && (
230
+ <motion.div
231
+ initial={{ height: 0, opacity: 0 }}
232
+ animate={{ height: "auto", opacity: 1 }}
233
+ exit={{ height: 0, opacity: 0 }}
234
+ transition={{ duration: 0.2 }}
235
+ className="overflow-hidden"
236
+ >
237
+ <div className="px-4 pb-3 pt-1 border-t border-white/10">
238
+ <DataPreview data={data} />
239
+ {/* Show superseded facts when revisionAction is SUPERSEDE */}
240
+ {revisionAction === "SUPERSEDE" &&
241
+ supersededFacts &&
242
+ supersededFacts.length > 0 && (
243
+ <div className="mt-2 pt-2 border-t border-white/5">
244
+ <div className="text-[10px] text-orange-400 font-medium mb-1">
245
+ Superseded Facts:
246
+ </div>
247
+ {supersededFacts.map((fact, i) => (
248
+ <div
249
+ key={i}
250
+ className="text-[10px] text-gray-500 line-through"
251
+ >
252
+ {fact}
253
+ </div>
254
+ ))}
255
+ </div>
256
+ )}
257
+ </div>
258
+ </motion.div>
259
+ )}
260
+ </AnimatePresence>
261
+ </motion.div>
262
+ );
263
+ }
@@ -0,0 +1,195 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import { LayerCard } from "./LayerCard";
5
+ import type { LayerState } from "@/lib/layer-tracking";
6
+
7
+ interface LayerFlowDiagramProps {
8
+ layers: Record<string, LayerState>;
9
+ isOrchestrating: boolean;
10
+ memorySpaceId: string;
11
+ userId: string;
12
+ }
13
+
14
+ export function LayerFlowDiagram({
15
+ layers,
16
+ isOrchestrating,
17
+ memorySpaceId,
18
+ userId,
19
+ }: LayerFlowDiagramProps) {
20
+ // Define the layer flow order
21
+ const topLayers = [
22
+ { key: "memorySpace", name: "Memory Space", icon: "📦" },
23
+ { key: "user", name: "User", icon: "👤" },
24
+ { key: "agent", name: "Agent", icon: "🤖" },
25
+ ];
26
+
27
+ const flowLayers = [
28
+ { key: "conversation", name: "Conversation", icon: "💬" },
29
+ { key: "vector", name: "Vector Store", icon: "🎯" },
30
+ { key: "facts", name: "Facts", icon: "💡" },
31
+ { key: "graph", name: "Graph", icon: "🕸️", optional: true },
32
+ ];
33
+
34
+ return (
35
+ <div className="space-y-6">
36
+ {/* Title section */}
37
+ <div className="text-center">
38
+ <h3 className="text-lg font-semibold">Memory Orchestration Flow</h3>
39
+ <p className="text-sm text-gray-400 mt-1">
40
+ {isOrchestrating
41
+ ? "Processing your message through Cortex layers..."
42
+ : "Send a message to see data flow through the system"}
43
+ </p>
44
+ </div>
45
+
46
+ {/* Top row: Context layers */}
47
+ <div className="flex justify-center gap-3">
48
+ {topLayers.map((layer, i) => (
49
+ <motion.div
50
+ key={layer.key}
51
+ initial={{ opacity: 0, y: -10 }}
52
+ animate={{ opacity: 1, y: 0 }}
53
+ transition={{ delay: i * 0.1 }}
54
+ >
55
+ <LayerCard
56
+ name={layer.name}
57
+ icon={layer.icon}
58
+ status={layers[layer.key]?.status || "pending"}
59
+ latencyMs={layers[layer.key]?.latencyMs}
60
+ data={layers[layer.key]?.data}
61
+ compact
62
+ />
63
+ </motion.div>
64
+ ))}
65
+ </div>
66
+
67
+ {/* Flow connector */}
68
+ <div className="flex justify-center">
69
+ <motion.div
70
+ className="w-0.5 h-8 bg-gradient-to-b from-white/30 to-white/10"
71
+ animate={{
72
+ opacity: isOrchestrating ? [0.3, 1, 0.3] : 0.3,
73
+ }}
74
+ transition={{
75
+ duration: 1.5,
76
+ repeat: Infinity,
77
+ ease: "easeInOut",
78
+ }}
79
+ />
80
+ </div>
81
+
82
+ {/* Main flow: Processing layers */}
83
+ <div className="space-y-3">
84
+ {flowLayers.map((layer, i) => (
85
+ <motion.div
86
+ key={layer.key}
87
+ initial={{ opacity: 0, x: -20 }}
88
+ animate={{ opacity: 1, x: 0 }}
89
+ transition={{ delay: 0.3 + i * 0.1 }}
90
+ >
91
+ <LayerCard
92
+ name={layer.name}
93
+ icon={layer.icon}
94
+ status={
95
+ layers[layer.key]?.status ||
96
+ (layer.optional ? "skipped" : "pending")
97
+ }
98
+ latencyMs={layers[layer.key]?.latencyMs}
99
+ data={layers[layer.key]?.data}
100
+ optional={layer.optional}
101
+ // Belief revision info (v0.24.0+) - only for facts layer
102
+ revisionAction={
103
+ layer.key === "facts"
104
+ ? layers[layer.key]?.revisionAction
105
+ : undefined
106
+ }
107
+ supersededFacts={
108
+ layer.key === "facts"
109
+ ? layers[layer.key]?.supersededFacts
110
+ : undefined
111
+ }
112
+ />
113
+
114
+ {/* Flow connector between layers */}
115
+ {i < flowLayers.length - 1 && (
116
+ <div className="flex justify-center py-2">
117
+ <motion.div
118
+ className="w-0.5 h-6"
119
+ style={{
120
+ background:
121
+ layers[layer.key]?.status === "complete"
122
+ ? "linear-gradient(to bottom, rgb(34, 197, 94), rgba(255, 255, 255, 0.1))"
123
+ : "linear-gradient(to bottom, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.05))",
124
+ }}
125
+ animate={{
126
+ opacity:
127
+ isOrchestrating &&
128
+ layers[layer.key]?.status === "in_progress"
129
+ ? [0.3, 1, 0.3]
130
+ : 1,
131
+ }}
132
+ transition={{
133
+ duration: 0.8,
134
+ repeat: Infinity,
135
+ ease: "easeInOut",
136
+ }}
137
+ />
138
+ </div>
139
+ )}
140
+ </motion.div>
141
+ ))}
142
+ </div>
143
+
144
+ {/* Legend */}
145
+ <div className="pt-4 border-t border-white/10 space-y-2">
146
+ {/* Status legend */}
147
+ <div className="flex justify-center gap-4 text-xs text-gray-500">
148
+ <div className="flex items-center gap-1.5">
149
+ <span className="w-2 h-2 rounded-full bg-gray-500" />
150
+ <span>Pending</span>
151
+ </div>
152
+ <div className="flex items-center gap-1.5">
153
+ <span className="w-2 h-2 rounded-full bg-yellow-500 animate-pulse" />
154
+ <span>Processing</span>
155
+ </div>
156
+ <div className="flex items-center gap-1.5">
157
+ <span className="w-2 h-2 rounded-full bg-green-500" />
158
+ <span>Complete</span>
159
+ </div>
160
+ <div className="flex items-center gap-1.5">
161
+ <span className="w-2 h-2 rounded-full bg-gray-700" />
162
+ <span>Skipped</span>
163
+ </div>
164
+ </div>
165
+ {/* Belief revision legend (v0.24.0+) */}
166
+ <div className="flex justify-center gap-3 text-xs text-gray-600">
167
+ <span className="text-gray-700">Belief Revision:</span>
168
+ <div className="flex items-center gap-1">
169
+ <span className="px-1 py-0.5 bg-green-500/20 text-green-400 rounded text-[9px]">
170
+ NEW
171
+ </span>
172
+ </div>
173
+ <div className="flex items-center gap-1">
174
+ <span className="px-1 py-0.5 bg-blue-500/20 text-blue-400 rounded text-[9px]">
175
+ UPDATED
176
+ </span>
177
+ </div>
178
+ <div className="flex items-center gap-1">
179
+ <span className="px-1 py-0.5 bg-orange-500/20 text-orange-400 rounded text-[9px]">
180
+ SUPERSEDED
181
+ </span>
182
+ </div>
183
+ </div>
184
+ </div>
185
+
186
+ {/* Context info */}
187
+ <div className="text-center text-xs text-gray-600">
188
+ <p>
189
+ Space: <code className="text-gray-400">{memorySpaceId}</code> • User:{" "}
190
+ <code className="text-gray-400">{userId}</code>
191
+ </p>
192
+ </div>
193
+ </div>
194
+ );
195
+ }