@cortexmemory/cli 0.26.0 → 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/db.d.ts.map +1 -1
- package/dist/commands/db.js +2 -18
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +153 -17
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +373 -70
- 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/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/convex-setup.d.ts +58 -6
- package/dist/utils/init/convex-setup.d.ts.map +1 -1
- package/dist/utils/init/convex-setup.js +261 -57
- package/dist/utils/init/convex-setup.js.map +1 -1
- package/dist/utils/init/env-generator.d.ts.map +1 -1
- package/dist/utils/init/env-generator.js +12 -2
- package/dist/utils/init/env-generator.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/init/types.d.ts +4 -0
- package/dist/utils/init/types.d.ts.map +1 -1
- 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,99 @@
|
|
|
1
|
+
import { Cortex } from "@cortexmemory/sdk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Health check endpoint to verify all backend services
|
|
5
|
+
*/
|
|
6
|
+
export async function GET() {
|
|
7
|
+
const checks: Record<
|
|
8
|
+
string,
|
|
9
|
+
{ status: string; latencyMs?: number; error?: string }
|
|
10
|
+
> = {};
|
|
11
|
+
|
|
12
|
+
// Check 1: Environment variables
|
|
13
|
+
const hasConvexUrl = !!process.env.CONVEX_URL;
|
|
14
|
+
const hasPublicConvexUrl = !!process.env.NEXT_PUBLIC_CONVEX_URL;
|
|
15
|
+
const hasOpenAIKey = !!process.env.OPENAI_API_KEY;
|
|
16
|
+
const hasNeo4jUri = !!process.env.NEO4J_URI;
|
|
17
|
+
const hasMemgraphUri = !!process.env.MEMGRAPH_URI;
|
|
18
|
+
|
|
19
|
+
checks.environment = {
|
|
20
|
+
status: hasConvexUrl && hasOpenAIKey ? "ok" : "warning",
|
|
21
|
+
error: !hasConvexUrl
|
|
22
|
+
? "CONVEX_URL not set"
|
|
23
|
+
: !hasOpenAIKey
|
|
24
|
+
? "OPENAI_API_KEY not set"
|
|
25
|
+
: undefined,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Check 2: Cortex SDK initialization
|
|
29
|
+
try {
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
const cortex = new Cortex({
|
|
32
|
+
convexUrl: process.env.CONVEX_URL!,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Quick test - just initialize, don't actually query
|
|
36
|
+
checks.cortexSdk = {
|
|
37
|
+
status: "ok",
|
|
38
|
+
latencyMs: Date.now() - startTime,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
cortex.close();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
checks.cortexSdk = {
|
|
44
|
+
status: "error",
|
|
45
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check 3: Convex backend connectivity (via HTTP)
|
|
50
|
+
if (process.env.CONVEX_URL) {
|
|
51
|
+
try {
|
|
52
|
+
const startTime = Date.now();
|
|
53
|
+
// Convex URLs are like "https://xxx.convex.cloud"
|
|
54
|
+
// We can ping the HTTP endpoint
|
|
55
|
+
const convexUrl = new URL(process.env.CONVEX_URL);
|
|
56
|
+
const response = await fetch(`${convexUrl.origin}/version`, {
|
|
57
|
+
method: "GET",
|
|
58
|
+
signal: AbortSignal.timeout(5000),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
checks.convexBackend = {
|
|
62
|
+
status: response.ok ? "ok" : "error",
|
|
63
|
+
latencyMs: Date.now() - startTime,
|
|
64
|
+
error: response.ok ? undefined : `HTTP ${response.status}`,
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
checks.convexBackend = {
|
|
68
|
+
status: "error",
|
|
69
|
+
error: error instanceof Error ? error.message : "Connection failed",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
checks.convexBackend = {
|
|
74
|
+
status: "error",
|
|
75
|
+
error: "CONVEX_URL not configured",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Overall status
|
|
80
|
+
const hasErrors = Object.values(checks).some((c) => c.status === "error");
|
|
81
|
+
const hasWarnings = Object.values(checks).some((c) => c.status === "warning");
|
|
82
|
+
|
|
83
|
+
return Response.json({
|
|
84
|
+
status: hasErrors ? "unhealthy" : hasWarnings ? "degraded" : "healthy",
|
|
85
|
+
timestamp: new Date().toISOString(),
|
|
86
|
+
checks,
|
|
87
|
+
config: {
|
|
88
|
+
convexUrl: hasConvexUrl ? "configured" : "missing",
|
|
89
|
+
publicConvexUrl: hasPublicConvexUrl ? "configured" : "missing",
|
|
90
|
+
openaiKey: hasOpenAIKey ? "configured" : "missing",
|
|
91
|
+
graphSync: hasNeo4jUri || hasMemgraphUri ? "enabled" : "disabled",
|
|
92
|
+
graphBackend: hasNeo4jUri
|
|
93
|
+
? "neo4j"
|
|
94
|
+
: hasMemgraphUri
|
|
95
|
+
? "memgraph"
|
|
96
|
+
: "none",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Cortex } from "@cortexmemory/sdk";
|
|
2
|
+
|
|
3
|
+
export const dynamic = "force-dynamic";
|
|
4
|
+
|
|
5
|
+
function getCortex() {
|
|
6
|
+
return new Cortex({ convexUrl: process.env.CONVEX_URL! });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function GET(req: Request) {
|
|
10
|
+
try {
|
|
11
|
+
const { searchParams } = new URL(req.url);
|
|
12
|
+
const memorySpaceId =
|
|
13
|
+
searchParams.get("memorySpaceId") || "quickstart-demo";
|
|
14
|
+
const limit = parseInt(searchParams.get("limit") || "20");
|
|
15
|
+
|
|
16
|
+
const cortex = getCortex();
|
|
17
|
+
|
|
18
|
+
// Fetch recent memories
|
|
19
|
+
const memories = await cortex.memory.list({
|
|
20
|
+
memorySpaceId,
|
|
21
|
+
limit,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return Response.json({
|
|
25
|
+
memories,
|
|
26
|
+
count: memories.length,
|
|
27
|
+
memorySpaceId,
|
|
28
|
+
});
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error("[Memories API Error]", error);
|
|
31
|
+
|
|
32
|
+
return Response.json(
|
|
33
|
+
{ error: error instanceof Error ? error.message : "Unknown error" },
|
|
34
|
+
{ status: 500 },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* Source paths for v4 */
|
|
4
|
+
@source "../components/**/*.{js,ts,jsx,tsx}";
|
|
5
|
+
@source "./**/*.{js,ts,jsx,tsx}";
|
|
6
|
+
|
|
7
|
+
/* Custom theme for v4 */
|
|
8
|
+
@theme {
|
|
9
|
+
--color-cortex-50: #f0f7ff;
|
|
10
|
+
--color-cortex-100: #e0effe;
|
|
11
|
+
--color-cortex-200: #b9dffc;
|
|
12
|
+
--color-cortex-300: #7cc5fa;
|
|
13
|
+
--color-cortex-400: #36a8f5;
|
|
14
|
+
--color-cortex-500: #0c8ce6;
|
|
15
|
+
--color-cortex-600: #006fc4;
|
|
16
|
+
--color-cortex-700: #0159a0;
|
|
17
|
+
--color-cortex-800: #064b84;
|
|
18
|
+
--color-cortex-900: #0b3f6d;
|
|
19
|
+
--color-cortex-950: #072848;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Custom scrollbar */
|
|
23
|
+
::-webkit-scrollbar {
|
|
24
|
+
width: 8px;
|
|
25
|
+
height: 8px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
::-webkit-scrollbar-track {
|
|
29
|
+
background: rgba(255, 255, 255, 0.05);
|
|
30
|
+
border-radius: 4px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
::-webkit-scrollbar-thumb {
|
|
34
|
+
background: rgba(255, 255, 255, 0.2);
|
|
35
|
+
border-radius: 4px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
::-webkit-scrollbar-thumb:hover {
|
|
39
|
+
background: rgba(255, 255, 255, 0.3);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Layer flow animations */
|
|
43
|
+
@keyframes pulse-glow {
|
|
44
|
+
0%,
|
|
45
|
+
100% {
|
|
46
|
+
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.4);
|
|
47
|
+
}
|
|
48
|
+
50% {
|
|
49
|
+
box-shadow: 0 0 0 10px rgba(34, 197, 94, 0);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@keyframes flow-down {
|
|
54
|
+
0% {
|
|
55
|
+
transform: translateY(-100%);
|
|
56
|
+
opacity: 0;
|
|
57
|
+
}
|
|
58
|
+
50% {
|
|
59
|
+
opacity: 1;
|
|
60
|
+
}
|
|
61
|
+
100% {
|
|
62
|
+
transform: translateY(100%);
|
|
63
|
+
opacity: 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.layer-complete {
|
|
68
|
+
animation: pulse-glow 1.5s ease-in-out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.flow-indicator {
|
|
72
|
+
animation: flow-down 1.5s ease-in-out infinite;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Chat message animations */
|
|
76
|
+
@keyframes message-in {
|
|
77
|
+
from {
|
|
78
|
+
opacity: 0;
|
|
79
|
+
transform: translateY(10px);
|
|
80
|
+
}
|
|
81
|
+
to {
|
|
82
|
+
opacity: 1;
|
|
83
|
+
transform: translateY(0);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.message-animate {
|
|
88
|
+
animation: message-in 0.3s ease-out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Gradient backgrounds */
|
|
92
|
+
.gradient-radial {
|
|
93
|
+
background: radial-gradient(
|
|
94
|
+
ellipse at center,
|
|
95
|
+
var(--tw-gradient-from) 0%,
|
|
96
|
+
var(--tw-gradient-to) 70%
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Glass effect */
|
|
101
|
+
.glass {
|
|
102
|
+
background: rgba(255, 255, 255, 0.05);
|
|
103
|
+
backdrop-filter: blur(10px);
|
|
104
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Code block styling */
|
|
108
|
+
.code-block {
|
|
109
|
+
background: rgba(0, 0, 0, 0.3);
|
|
110
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
111
|
+
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
|
|
112
|
+
font-size: 0.875rem;
|
|
113
|
+
line-height: 1.5;
|
|
114
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import "./globals.css";
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: "Cortex Memory Quickstart",
|
|
6
|
+
description: "Interactive demo of Cortex Memory with Vercel AI SDK",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function RootLayout({
|
|
10
|
+
children,
|
|
11
|
+
}: {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<body className="bg-gray-900 text-white min-h-screen">{children}</body>
|
|
17
|
+
</html>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import dynamic from "next/dynamic";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { useLayerTracking } from "@/lib/layer-tracking";
|
|
6
|
+
|
|
7
|
+
// Dynamic imports to avoid SSR issues with framer-motion
|
|
8
|
+
const ChatInterface = dynamic(
|
|
9
|
+
() =>
|
|
10
|
+
import("@/components/ChatInterface").then((m) => ({
|
|
11
|
+
default: m.ChatInterface,
|
|
12
|
+
})),
|
|
13
|
+
{ ssr: false },
|
|
14
|
+
);
|
|
15
|
+
const LayerFlowDiagram = dynamic(
|
|
16
|
+
() =>
|
|
17
|
+
import("@/components/LayerFlowDiagram").then((m) => ({
|
|
18
|
+
default: m.LayerFlowDiagram,
|
|
19
|
+
})),
|
|
20
|
+
{ ssr: false },
|
|
21
|
+
);
|
|
22
|
+
const MemorySpaceSwitcher = dynamic(
|
|
23
|
+
() =>
|
|
24
|
+
import("@/components/MemorySpaceSwitcher").then((m) => ({
|
|
25
|
+
default: m.MemorySpaceSwitcher,
|
|
26
|
+
})),
|
|
27
|
+
{ ssr: false },
|
|
28
|
+
);
|
|
29
|
+
const HealthStatus = dynamic(
|
|
30
|
+
() =>
|
|
31
|
+
import("@/components/HealthStatus").then((m) => ({
|
|
32
|
+
default: m.HealthStatus,
|
|
33
|
+
})),
|
|
34
|
+
{ ssr: false },
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export default function Home() {
|
|
38
|
+
const [memorySpaceId, setMemorySpaceId] = useState("quickstart-demo");
|
|
39
|
+
const [userId] = useState("demo-user");
|
|
40
|
+
const {
|
|
41
|
+
layers,
|
|
42
|
+
isOrchestrating,
|
|
43
|
+
startOrchestration,
|
|
44
|
+
updateLayer,
|
|
45
|
+
resetLayers,
|
|
46
|
+
} = useLayerTracking();
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<main className="min-h-screen flex flex-col">
|
|
50
|
+
{/* Header */}
|
|
51
|
+
<header className="border-b border-white/10 px-6 py-4">
|
|
52
|
+
<div className="max-w-7xl mx-auto flex items-center justify-between">
|
|
53
|
+
<div className="flex items-center gap-3">
|
|
54
|
+
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-cortex-500 to-cortex-700 flex items-center justify-center">
|
|
55
|
+
<span className="text-xl">🧠</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div>
|
|
58
|
+
<h1 className="text-xl font-bold">Cortex Memory Quickstart</h1>
|
|
59
|
+
<p className="text-sm text-gray-400">
|
|
60
|
+
Real-time memory orchestration demo
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="flex items-center gap-4">
|
|
66
|
+
<HealthStatus />
|
|
67
|
+
<MemorySpaceSwitcher
|
|
68
|
+
value={memorySpaceId}
|
|
69
|
+
onChange={setMemorySpaceId}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
{/* Main Content */}
|
|
76
|
+
<div className="flex-1 flex overflow-hidden">
|
|
77
|
+
{/* Chat Section */}
|
|
78
|
+
<div className="flex-1 flex flex-col border-r border-white/10">
|
|
79
|
+
<ChatInterface
|
|
80
|
+
memorySpaceId={memorySpaceId}
|
|
81
|
+
userId={userId}
|
|
82
|
+
onOrchestrationStart={startOrchestration}
|
|
83
|
+
onLayerUpdate={updateLayer}
|
|
84
|
+
onReset={resetLayers}
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{/* Layer Flow Visualization */}
|
|
89
|
+
<div className="w-[480px] flex flex-col bg-black/20">
|
|
90
|
+
<div className="p-4 border-b border-white/10">
|
|
91
|
+
<h2 className="font-semibold flex items-center gap-2">
|
|
92
|
+
<span className="text-lg">📊</span>
|
|
93
|
+
Memory Orchestration Flow
|
|
94
|
+
</h2>
|
|
95
|
+
<p className="text-sm text-gray-400 mt-1">
|
|
96
|
+
Watch data flow through Cortex layers in real-time
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div className="flex-1 overflow-y-auto p-4">
|
|
101
|
+
<LayerFlowDiagram
|
|
102
|
+
layers={layers}
|
|
103
|
+
isOrchestrating={isOrchestrating}
|
|
104
|
+
memorySpaceId={memorySpaceId}
|
|
105
|
+
userId={userId}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* Footer */}
|
|
112
|
+
<footer className="border-t border-white/10 px-6 py-3">
|
|
113
|
+
<div className="max-w-7xl mx-auto flex items-center justify-between text-sm text-gray-500">
|
|
114
|
+
<div className="flex items-center gap-4">
|
|
115
|
+
<span>Cortex SDK v0.24.0</span>
|
|
116
|
+
<span>•</span>
|
|
117
|
+
<span>Vercel AI SDK v5</span>
|
|
118
|
+
</div>
|
|
119
|
+
<a
|
|
120
|
+
href="https://cortexmemory.dev/docs"
|
|
121
|
+
target="_blank"
|
|
122
|
+
rel="noopener noreferrer"
|
|
123
|
+
className="hover:text-white transition-colors"
|
|
124
|
+
>
|
|
125
|
+
Documentation →
|
|
126
|
+
</a>
|
|
127
|
+
</div>
|
|
128
|
+
</footer>
|
|
129
|
+
</main>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useChat } from "@ai-sdk/react";
|
|
4
|
+
import { DefaultChatTransport } from "ai";
|
|
5
|
+
import { useState, useRef, useEffect, useMemo, useCallback } from "react";
|
|
6
|
+
import type {
|
|
7
|
+
LayerStatus,
|
|
8
|
+
MemoryLayer,
|
|
9
|
+
LayerState,
|
|
10
|
+
RevisionAction,
|
|
11
|
+
} from "@/lib/layer-tracking";
|
|
12
|
+
|
|
13
|
+
// Type for layer update data parts from the stream
|
|
14
|
+
interface LayerUpdateData {
|
|
15
|
+
layer: MemoryLayer;
|
|
16
|
+
status: LayerStatus;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
latencyMs?: number;
|
|
19
|
+
data?: LayerState["data"];
|
|
20
|
+
error?: { message: string; code?: string };
|
|
21
|
+
revisionAction?: RevisionAction;
|
|
22
|
+
supersededFacts?: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ChatInterfaceProps {
|
|
26
|
+
memorySpaceId: string;
|
|
27
|
+
userId: string;
|
|
28
|
+
onOrchestrationStart?: () => void;
|
|
29
|
+
onLayerUpdate?: (
|
|
30
|
+
layer: MemoryLayer,
|
|
31
|
+
status: LayerStatus,
|
|
32
|
+
data?: LayerState["data"],
|
|
33
|
+
revisionInfo?: {
|
|
34
|
+
action?: RevisionAction;
|
|
35
|
+
supersededFacts?: string[];
|
|
36
|
+
},
|
|
37
|
+
) => void;
|
|
38
|
+
onReset?: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function ChatInterface({
|
|
42
|
+
memorySpaceId,
|
|
43
|
+
userId,
|
|
44
|
+
onOrchestrationStart,
|
|
45
|
+
onLayerUpdate,
|
|
46
|
+
onReset,
|
|
47
|
+
}: ChatInterfaceProps) {
|
|
48
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
49
|
+
const [input, setInput] = useState("");
|
|
50
|
+
const [suggestedMessages] = useState([
|
|
51
|
+
"Hi! My name is Alex and I work at Acme Corp as a senior engineer.",
|
|
52
|
+
"My favorite color is blue and I love hiking on weekends.",
|
|
53
|
+
"I'm learning Spanish and prefer dark mode interfaces.",
|
|
54
|
+
"What do you remember about me?",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
// Create transport with body parameters - memoized to prevent recreation
|
|
58
|
+
const transport = useMemo(
|
|
59
|
+
() =>
|
|
60
|
+
new DefaultChatTransport({
|
|
61
|
+
api: "/api/chat",
|
|
62
|
+
body: { memorySpaceId, userId },
|
|
63
|
+
}),
|
|
64
|
+
[memorySpaceId, userId],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Handle layer data parts from the stream
|
|
68
|
+
const handleDataPart = useCallback(
|
|
69
|
+
(dataPart: any) => {
|
|
70
|
+
if (dataPart.type === "data-orchestration-start") {
|
|
71
|
+
onOrchestrationStart?.();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (dataPart.type === "data-layer-update") {
|
|
75
|
+
const event = dataPart.data as LayerUpdateData;
|
|
76
|
+
onLayerUpdate?.(event.layer, event.status, event.data, {
|
|
77
|
+
action: event.revisionAction,
|
|
78
|
+
supersededFacts: event.supersededFacts,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// orchestration-complete is informational - layer diagram already updated
|
|
83
|
+
// via individual layer events
|
|
84
|
+
},
|
|
85
|
+
[onOrchestrationStart, onLayerUpdate],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const { messages, sendMessage, status } = useChat({
|
|
89
|
+
transport,
|
|
90
|
+
onData: handleDataPart,
|
|
91
|
+
onError: (error) => {
|
|
92
|
+
console.error("Chat error:", error);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Determine if we're actively streaming (only time to show typing indicator)
|
|
97
|
+
const isStreaming = status === "streaming";
|
|
98
|
+
|
|
99
|
+
// Auto-scroll to latest message
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
102
|
+
}, [messages]);
|
|
103
|
+
|
|
104
|
+
// Handle form submission
|
|
105
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
if (!input.trim() || isStreaming) return;
|
|
108
|
+
|
|
109
|
+
const message = input.trim();
|
|
110
|
+
setInput("");
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await sendMessage({ text: message });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error("Failed to send message:", error);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleSuggestedMessage = (message: string) => {
|
|
120
|
+
setInput(message);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Extract text content from message parts (AI SDK v5 format)
|
|
124
|
+
const getMessageContent = (message: any): string => {
|
|
125
|
+
if (typeof message.content === "string") {
|
|
126
|
+
return message.content;
|
|
127
|
+
}
|
|
128
|
+
if (message.parts) {
|
|
129
|
+
return message.parts
|
|
130
|
+
.filter((part: any) => part.type === "text")
|
|
131
|
+
.map((part: any) => part.text)
|
|
132
|
+
.join("");
|
|
133
|
+
}
|
|
134
|
+
return "";
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div className="flex flex-col h-full">
|
|
139
|
+
{/* Messages */}
|
|
140
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
141
|
+
{messages.length === 0 && (
|
|
142
|
+
<div className="text-center py-12">
|
|
143
|
+
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-cortex-500/20 to-cortex-700/20 flex items-center justify-center">
|
|
144
|
+
<span className="text-3xl">🧠</span>
|
|
145
|
+
</div>
|
|
146
|
+
<h2 className="text-xl font-semibold mb-2">
|
|
147
|
+
Welcome to Cortex Memory Demo
|
|
148
|
+
</h2>
|
|
149
|
+
<p className="text-gray-400 max-w-md mx-auto mb-6">
|
|
150
|
+
This demo shows how Cortex orchestrates memory across multiple
|
|
151
|
+
layers in real-time. Try telling me about yourself!
|
|
152
|
+
</p>
|
|
153
|
+
|
|
154
|
+
{/* Suggested messages */}
|
|
155
|
+
<div className="flex flex-wrap justify-center gap-2 max-w-lg mx-auto">
|
|
156
|
+
{suggestedMessages.map((msg, i) => (
|
|
157
|
+
<button
|
|
158
|
+
key={i}
|
|
159
|
+
onClick={() => handleSuggestedMessage(msg)}
|
|
160
|
+
className="px-3 py-2 text-sm bg-white/5 hover:bg-white/10 rounded-lg border border-white/10 transition-colors text-left"
|
|
161
|
+
>
|
|
162
|
+
{msg.length > 40 ? msg.slice(0, 40) + "..." : msg}
|
|
163
|
+
</button>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
{messages.map((message, i) => (
|
|
170
|
+
<div
|
|
171
|
+
key={message.id || i}
|
|
172
|
+
className={`message-animate flex ${
|
|
173
|
+
message.role === "user" ? "justify-end" : "justify-start"
|
|
174
|
+
}`}
|
|
175
|
+
>
|
|
176
|
+
<div
|
|
177
|
+
className={`max-w-[80%] px-4 py-3 rounded-2xl ${
|
|
178
|
+
message.role === "user"
|
|
179
|
+
? "bg-cortex-600 text-white"
|
|
180
|
+
: "bg-white/10 text-white"
|
|
181
|
+
}`}
|
|
182
|
+
>
|
|
183
|
+
<p className="whitespace-pre-wrap">
|
|
184
|
+
{getMessageContent(message)}
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
))}
|
|
189
|
+
|
|
190
|
+
{/* Typing indicator - only show during active streaming */}
|
|
191
|
+
{isStreaming && (
|
|
192
|
+
<div className="flex justify-start">
|
|
193
|
+
<div className="bg-white/10 px-4 py-3 rounded-2xl">
|
|
194
|
+
<div className="flex gap-1">
|
|
195
|
+
<span className="w-2 h-2 bg-white/50 rounded-full animate-bounce" />
|
|
196
|
+
<span className="w-2 h-2 bg-white/50 rounded-full animate-bounce [animation-delay:0.1s]" />
|
|
197
|
+
<span className="w-2 h-2 bg-white/50 rounded-full animate-bounce [animation-delay:0.2s]" />
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
|
|
203
|
+
<div ref={messagesEndRef} />
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
{/* Input - only disabled during active streaming, NOT during background orchestration */}
|
|
207
|
+
<div className="border-t border-white/10 p-4">
|
|
208
|
+
<form onSubmit={handleSubmit} className="flex gap-2">
|
|
209
|
+
<input
|
|
210
|
+
type="text"
|
|
211
|
+
value={input}
|
|
212
|
+
onChange={(e) => setInput(e.target.value)}
|
|
213
|
+
placeholder="Tell me about yourself..."
|
|
214
|
+
className="flex-1 px-4 py-3 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:border-cortex-500 transition-colors"
|
|
215
|
+
disabled={isStreaming}
|
|
216
|
+
/>
|
|
217
|
+
<button
|
|
218
|
+
type="submit"
|
|
219
|
+
disabled={isStreaming || !input.trim()}
|
|
220
|
+
className="px-6 py-3 bg-cortex-600 hover:bg-cortex-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-xl font-medium transition-colors"
|
|
221
|
+
>
|
|
222
|
+
Send
|
|
223
|
+
</button>
|
|
224
|
+
</form>
|
|
225
|
+
|
|
226
|
+
{messages.length > 0 && (
|
|
227
|
+
<button
|
|
228
|
+
onClick={onReset}
|
|
229
|
+
className="mt-2 text-sm text-gray-500 hover:text-white transition-colors"
|
|
230
|
+
>
|
|
231
|
+
Clear visualization →
|
|
232
|
+
</button>
|
|
233
|
+
)}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
|
4
|
+
import { ReactNode, useMemo } from "react";
|
|
5
|
+
|
|
6
|
+
export function ConvexClientProvider({ children }: { children: ReactNode }) {
|
|
7
|
+
const convex = useMemo(() => {
|
|
8
|
+
if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
|
|
9
|
+
console.warn("NEXT_PUBLIC_CONVEX_URL not set");
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL);
|
|
13
|
+
}, []);
|
|
14
|
+
|
|
15
|
+
if (!convex) {
|
|
16
|
+
// Render without Convex for development without backend
|
|
17
|
+
return <>{children}</>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return <ConvexProvider client={convex}>{children}</ConvexProvider>;
|
|
21
|
+
}
|