@cortexmemory/cli 0.26.2 → 0.27.3
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/convex.js +1 -1
- package/dist/commands/convex.js.map +1 -1
- package/dist/commands/deploy.d.ts +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +771 -144
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +210 -36
- 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/index.js +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/app-template-sync.d.ts +95 -0
- package/dist/utils/app-template-sync.d.ts.map +1 -0
- package/dist/utils/app-template-sync.js +425 -0
- package/dist/utils/app-template-sync.js.map +1 -0
- 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/deployment-selector.d.ts +21 -0
- package/dist/utils/deployment-selector.d.ts.map +1 -1
- package/dist/utils/deployment-selector.js +32 -0
- package/dist/utils/deployment-selector.js.map +1 -1
- package/dist/utils/init/graph-setup.d.ts.map +1 -1
- package/dist/utils/init/graph-setup.js +25 -2
- 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/auth/check/route.ts +30 -0
- package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +83 -0
- package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +94 -0
- package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +59 -0
- package/templates/vercel-ai-quickstart/app/api/chat/route.ts +277 -0
- package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +179 -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 +275 -0
- package/templates/vercel-ai-quickstart/app/layout.tsx +19 -0
- package/templates/vercel-ai-quickstart/app/page.tsx +216 -0
- package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +139 -0
- package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +283 -0
- package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +323 -0
- package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +334 -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/LoginScreen.tsx +202 -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/jest.config.js +45 -0
- package/templates/vercel-ai-quickstart/lib/animations.ts +146 -0
- package/templates/vercel-ai-quickstart/lib/cortex.ts +27 -0
- package/templates/vercel-ai-quickstart/lib/layer-tracking.ts +214 -0
- package/templates/vercel-ai-quickstart/lib/password.ts +120 -0
- package/templates/vercel-ai-quickstart/next.config.js +27 -0
- package/templates/vercel-ai-quickstart/package.json +46 -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/tests/helpers/mock-cortex.ts +263 -0
- package/templates/vercel-ai-quickstart/tests/helpers/setup.ts +48 -0
- package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +455 -0
- package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +461 -0
- package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +228 -0
- package/templates/vercel-ai-quickstart/tsconfig.json +33 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
interface DataPreviewProps {
|
|
4
|
+
data: {
|
|
5
|
+
id?: string;
|
|
6
|
+
preview?: string;
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function DataPreview({ data }: DataPreviewProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="space-y-2 text-sm">
|
|
14
|
+
{/* ID */}
|
|
15
|
+
{data.id && (
|
|
16
|
+
<div className="flex items-center gap-2">
|
|
17
|
+
<span className="text-gray-500">ID:</span>
|
|
18
|
+
<code className="px-1.5 py-0.5 bg-black/30 rounded text-xs font-mono text-cortex-400">
|
|
19
|
+
{data.id}
|
|
20
|
+
</code>
|
|
21
|
+
</div>
|
|
22
|
+
)}
|
|
23
|
+
|
|
24
|
+
{/* Preview */}
|
|
25
|
+
{data.preview && (
|
|
26
|
+
<div className="mt-2">
|
|
27
|
+
<span className="text-gray-500 text-xs">Data:</span>
|
|
28
|
+
<pre className="mt-1 p-2 bg-black/30 rounded text-xs font-mono text-gray-300 overflow-x-auto whitespace-pre-wrap">
|
|
29
|
+
{data.preview}
|
|
30
|
+
</pre>
|
|
31
|
+
</div>
|
|
32
|
+
)}
|
|
33
|
+
|
|
34
|
+
{/* Metadata */}
|
|
35
|
+
{data.metadata && Object.keys(data.metadata).length > 0 && (
|
|
36
|
+
<div className="mt-2">
|
|
37
|
+
<span className="text-gray-500 text-xs">Metadata:</span>
|
|
38
|
+
<div className="mt-1 flex flex-wrap gap-1.5">
|
|
39
|
+
{Object.entries(data.metadata).map(([key, value]) => (
|
|
40
|
+
<span
|
|
41
|
+
key={key}
|
|
42
|
+
className="px-1.5 py-0.5 bg-white/5 rounded text-xs"
|
|
43
|
+
>
|
|
44
|
+
<span className="text-gray-500">{key}:</span>{" "}
|
|
45
|
+
<span className="text-gray-300">
|
|
46
|
+
{typeof value === "object"
|
|
47
|
+
? JSON.stringify(value)
|
|
48
|
+
: String(value)}
|
|
49
|
+
</span>
|
|
50
|
+
</span>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -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
|
+
}
|