@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.
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +121 -10
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +273 -43
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +102 -46
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +94 -7
- package/dist/commands/status.js.map +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/config.d.ts +11 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +20 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/init/graph-setup.d.ts.map +1 -1
- package/dist/utils/init/graph-setup.js +12 -0
- package/dist/utils/init/graph-setup.js.map +1 -1
- package/dist/utils/init/quickstart-setup.d.ts +87 -0
- package/dist/utils/init/quickstart-setup.d.ts.map +1 -0
- package/dist/utils/init/quickstart-setup.js +462 -0
- package/dist/utils/init/quickstart-setup.js.map +1 -0
- package/dist/utils/schema-sync.d.ts.map +1 -1
- package/dist/utils/schema-sync.js +27 -21
- package/dist/utils/schema-sync.js.map +1 -1
- package/package.json +3 -2
- package/templates/vercel-ai-quickstart/.env.local.example +45 -0
- package/templates/vercel-ai-quickstart/README.md +280 -0
- package/templates/vercel-ai-quickstart/app/api/chat/route.ts +196 -0
- package/templates/vercel-ai-quickstart/app/api/facts/route.ts +39 -0
- package/templates/vercel-ai-quickstart/app/api/health/route.ts +99 -0
- package/templates/vercel-ai-quickstart/app/api/memories/route.ts +37 -0
- package/templates/vercel-ai-quickstart/app/globals.css +114 -0
- package/templates/vercel-ai-quickstart/app/layout.tsx +19 -0
- package/templates/vercel-ai-quickstart/app/page.tsx +131 -0
- package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +237 -0
- package/templates/vercel-ai-quickstart/components/ConvexClientProvider.tsx +21 -0
- package/templates/vercel-ai-quickstart/components/DataPreview.tsx +57 -0
- package/templates/vercel-ai-quickstart/components/HealthStatus.tsx +214 -0
- package/templates/vercel-ai-quickstart/components/LayerCard.tsx +263 -0
- package/templates/vercel-ai-quickstart/components/LayerFlowDiagram.tsx +195 -0
- package/templates/vercel-ai-quickstart/components/MemorySpaceSwitcher.tsx +93 -0
- package/templates/vercel-ai-quickstart/convex/conversations.ts +67 -0
- package/templates/vercel-ai-quickstart/convex/facts.ts +131 -0
- package/templates/vercel-ai-quickstart/convex/health.ts +15 -0
- package/templates/vercel-ai-quickstart/convex/memories.ts +104 -0
- package/templates/vercel-ai-quickstart/convex/schema.ts +20 -0
- package/templates/vercel-ai-quickstart/convex/users.ts +105 -0
- package/templates/vercel-ai-quickstart/lib/animations.ts +146 -0
- package/templates/vercel-ai-quickstart/lib/layer-tracking.ts +214 -0
- package/templates/vercel-ai-quickstart/next.config.js +7 -0
- package/templates/vercel-ai-quickstart/package.json +41 -0
- package/templates/vercel-ai-quickstart/postcss.config.js +5 -0
- package/templates/vercel-ai-quickstart/tailwind.config.js +37 -0
- 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
|
+
}
|