@cortexmemory/cli 0.27.4 → 0.28.0
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 +18 -6
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +74 -34
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +3 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/app-template-sync.d.ts.map +1 -1
- package/dist/utils/app-template-sync.js +3 -1
- package/dist/utils/app-template-sync.js.map +1 -1
- package/dist/utils/init/quickstart-setup.d.ts.map +1 -1
- package/dist/utils/init/quickstart-setup.js.map +1 -1
- package/package.json +4 -4
- package/templates/basic/.env.local.example +23 -0
- package/templates/basic/README.md +181 -56
- package/templates/basic/package-lock.json +2180 -406
- package/templates/basic/package.json +23 -5
- package/templates/basic/src/__tests__/chat.test.ts +340 -0
- package/templates/basic/src/__tests__/cortex.test.ts +260 -0
- package/templates/basic/src/__tests__/display.test.ts +455 -0
- package/templates/basic/src/__tests__/e2e/fact-extraction.test.ts +498 -0
- package/templates/basic/src/__tests__/e2e/memory-flow.test.ts +355 -0
- package/templates/basic/src/__tests__/e2e/server-e2e.test.ts +414 -0
- package/templates/basic/src/__tests__/helpers/test-utils.ts +345 -0
- package/templates/basic/src/__tests__/integration/chat-flow.test.ts +422 -0
- package/templates/basic/src/__tests__/integration/server.test.ts +441 -0
- package/templates/basic/src/__tests__/llm.test.ts +344 -0
- package/templates/basic/src/chat.ts +300 -0
- package/templates/basic/src/cortex.ts +203 -0
- package/templates/basic/src/display.ts +425 -0
- package/templates/basic/src/index.ts +194 -64
- package/templates/basic/src/llm.ts +214 -0
- package/templates/basic/src/server.ts +280 -0
- package/templates/basic/vitest.config.ts +33 -0
- package/templates/basic/vitest.e2e.config.ts +28 -0
- package/templates/basic/vitest.integration.config.ts +25 -0
- package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +1 -1
- package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +6 -9
- package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +14 -18
- package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +4 -7
- package/templates/vercel-ai-quickstart/app/api/chat/route.ts +28 -11
- package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +19 -13
- package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +16 -16
- package/templates/vercel-ai-quickstart/app/globals.css +24 -9
- package/templates/vercel-ai-quickstart/app/page.tsx +25 -13
- package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +3 -1
- package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +6 -6
- package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +19 -8
- package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +41 -14
- package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +10 -5
- package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +3 -3
- package/templates/vercel-ai-quickstart/lib/password.ts +5 -5
- package/templates/vercel-ai-quickstart/next.config.js +10 -2
- package/templates/vercel-ai-quickstart/package.json +18 -11
- package/templates/vercel-ai-quickstart/test-api.mjs +131 -100
- package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +73 -44
- package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +40 -40
- package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +8 -8
- package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +12 -8
- package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +4 -1
|
@@ -13,7 +13,8 @@ export async function GET(req: Request) {
|
|
|
13
13
|
const { searchParams } = new URL(req.url);
|
|
14
14
|
const conversationId = searchParams.get("conversationId");
|
|
15
15
|
const userId = searchParams.get("userId");
|
|
16
|
-
const memorySpaceId =
|
|
16
|
+
const memorySpaceId =
|
|
17
|
+
searchParams.get("memorySpaceId") || "quickstart-demo";
|
|
17
18
|
|
|
18
19
|
const cortex = getCortex();
|
|
19
20
|
|
|
@@ -27,7 +28,7 @@ export async function GET(req: Request) {
|
|
|
27
28
|
if (!conversation) {
|
|
28
29
|
return Response.json(
|
|
29
30
|
{ error: "Conversation not found" },
|
|
30
|
-
{ status: 404 }
|
|
31
|
+
{ status: 404 },
|
|
31
32
|
);
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -42,7 +43,9 @@ export async function GET(req: Request) {
|
|
|
42
43
|
return Response.json({
|
|
43
44
|
conversation: {
|
|
44
45
|
id: conversation.conversationId,
|
|
45
|
-
title:
|
|
46
|
+
title:
|
|
47
|
+
(conversation.metadata?.title as string) ||
|
|
48
|
+
getDefaultTitle(conversation),
|
|
46
49
|
createdAt: conversation.createdAt,
|
|
47
50
|
updatedAt: conversation.updatedAt,
|
|
48
51
|
messageCount: conversation.messageCount || 0,
|
|
@@ -53,10 +56,7 @@ export async function GET(req: Request) {
|
|
|
53
56
|
|
|
54
57
|
// List conversations for user (requires userId)
|
|
55
58
|
if (!userId) {
|
|
56
|
-
return Response.json(
|
|
57
|
-
{ error: "userId is required" },
|
|
58
|
-
{ status: 400 }
|
|
59
|
-
);
|
|
59
|
+
return Response.json({ error: "userId is required" }, { status: 400 });
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Get conversations for the user
|
|
@@ -84,7 +84,7 @@ export async function GET(req: Request) {
|
|
|
84
84
|
|
|
85
85
|
return Response.json(
|
|
86
86
|
{ error: "Failed to fetch conversations" },
|
|
87
|
-
{ status: 500 }
|
|
87
|
+
{ status: 500 },
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -95,10 +95,7 @@ export async function POST(req: Request) {
|
|
|
95
95
|
const { userId, memorySpaceId = "quickstart-demo", title } = body;
|
|
96
96
|
|
|
97
97
|
if (!userId) {
|
|
98
|
-
return Response.json(
|
|
99
|
-
{ error: "userId is required" },
|
|
100
|
-
{ status: 400 }
|
|
101
|
-
);
|
|
98
|
+
return Response.json({ error: "userId is required" }, { status: 400 });
|
|
102
99
|
}
|
|
103
100
|
|
|
104
101
|
const cortex = getCortex();
|
|
@@ -134,7 +131,7 @@ export async function POST(req: Request) {
|
|
|
134
131
|
|
|
135
132
|
return Response.json(
|
|
136
133
|
{ error: "Failed to create conversation" },
|
|
137
|
-
{ status: 500 }
|
|
134
|
+
{ status: 500 },
|
|
138
135
|
);
|
|
139
136
|
}
|
|
140
137
|
}
|
|
@@ -147,7 +144,7 @@ export async function DELETE(req: Request) {
|
|
|
147
144
|
if (!conversationId) {
|
|
148
145
|
return Response.json(
|
|
149
146
|
{ error: "conversationId is required" },
|
|
150
|
-
{ status: 400 }
|
|
147
|
+
{ status: 400 },
|
|
151
148
|
);
|
|
152
149
|
}
|
|
153
150
|
|
|
@@ -161,7 +158,7 @@ export async function DELETE(req: Request) {
|
|
|
161
158
|
|
|
162
159
|
return Response.json(
|
|
163
160
|
{ error: "Failed to delete conversation" },
|
|
164
|
-
{ status: 500 }
|
|
161
|
+
{ status: 500 },
|
|
165
162
|
);
|
|
166
163
|
}
|
|
167
164
|
}
|
|
@@ -169,7 +166,10 @@ export async function DELETE(req: Request) {
|
|
|
169
166
|
/**
|
|
170
167
|
* Generate a default title from conversation data
|
|
171
168
|
*/
|
|
172
|
-
function getDefaultTitle(conv: {
|
|
169
|
+
function getDefaultTitle(conv: {
|
|
170
|
+
createdAt: number;
|
|
171
|
+
messageCount?: number;
|
|
172
|
+
}): string {
|
|
173
173
|
const date = new Date(conv.createdAt);
|
|
174
174
|
const timeStr = date.toLocaleTimeString("en-US", {
|
|
175
175
|
hour: "numeric",
|
|
@@ -129,7 +129,8 @@
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
@keyframes logo-pulse {
|
|
132
|
-
0%,
|
|
132
|
+
0%,
|
|
133
|
+
100% {
|
|
133
134
|
transform: scale(1);
|
|
134
135
|
box-shadow: 0 0 0 0 rgba(12, 140, 230, 0.4);
|
|
135
136
|
}
|
|
@@ -182,12 +183,24 @@
|
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
/* Staggered animation for conversation list */
|
|
185
|
-
.conversation-item:nth-child(1) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
.conversation-item:nth-child(
|
|
189
|
-
|
|
190
|
-
|
|
186
|
+
.conversation-item:nth-child(1) {
|
|
187
|
+
animation-delay: 0ms;
|
|
188
|
+
}
|
|
189
|
+
.conversation-item:nth-child(2) {
|
|
190
|
+
animation-delay: 30ms;
|
|
191
|
+
}
|
|
192
|
+
.conversation-item:nth-child(3) {
|
|
193
|
+
animation-delay: 60ms;
|
|
194
|
+
}
|
|
195
|
+
.conversation-item:nth-child(4) {
|
|
196
|
+
animation-delay: 90ms;
|
|
197
|
+
}
|
|
198
|
+
.conversation-item:nth-child(5) {
|
|
199
|
+
animation-delay: 120ms;
|
|
200
|
+
}
|
|
201
|
+
.conversation-item:nth-child(n + 6) {
|
|
202
|
+
animation-delay: 150ms;
|
|
203
|
+
}
|
|
191
204
|
|
|
192
205
|
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
193
206
|
Input Focus Animations
|
|
@@ -216,7 +229,9 @@ input:focus {
|
|
|
216
229
|
background: rgba(255, 255, 255, 0.1);
|
|
217
230
|
border-radius: 50%;
|
|
218
231
|
transform: translate(-50%, -50%);
|
|
219
|
-
transition:
|
|
232
|
+
transition:
|
|
233
|
+
width 0.4s ease,
|
|
234
|
+
height 0.4s ease;
|
|
220
235
|
}
|
|
221
236
|
|
|
222
237
|
.btn-primary:hover::after {
|
|
@@ -265,7 +280,7 @@ input:focus {
|
|
|
265
280
|
transform: translateX(-100%);
|
|
266
281
|
transition: transform 0.3s ease;
|
|
267
282
|
}
|
|
268
|
-
|
|
283
|
+
|
|
269
284
|
.sidebar-mobile-visible {
|
|
270
285
|
transform: translateX(0);
|
|
271
286
|
position: fixed;
|
|
@@ -52,7 +52,9 @@ const ChatHistorySidebar = dynamic(
|
|
|
52
52
|
function MainContent() {
|
|
53
53
|
const { isLoading, isAdminSetup, isAuthenticated, user } = useAuth();
|
|
54
54
|
const [memorySpaceId, setMemorySpaceId] = useState("quickstart-demo");
|
|
55
|
-
const [currentConversationId, setCurrentConversationId] = useState<
|
|
55
|
+
const [currentConversationId, setCurrentConversationId] = useState<
|
|
56
|
+
string | null
|
|
57
|
+
>(null);
|
|
56
58
|
const [versions, setVersions] = useState<VersionInfo | null>(null);
|
|
57
59
|
const {
|
|
58
60
|
layers,
|
|
@@ -74,18 +76,24 @@ function MainContent() {
|
|
|
74
76
|
}, [resetLayers]);
|
|
75
77
|
|
|
76
78
|
// Handle conversation selection
|
|
77
|
-
const handleSelectConversation = useCallback(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
const handleSelectConversation = useCallback(
|
|
80
|
+
(conversationId: string) => {
|
|
81
|
+
setCurrentConversationId(conversationId);
|
|
82
|
+
resetLayers();
|
|
83
|
+
},
|
|
84
|
+
[resetLayers],
|
|
85
|
+
);
|
|
81
86
|
|
|
82
87
|
// Handle conversation update (e.g., title change after first message)
|
|
83
|
-
const handleConversationUpdate = useCallback(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
const handleConversationUpdate = useCallback(
|
|
89
|
+
(conversationId: string) => {
|
|
90
|
+
// Update current conversation ID if it was null (new chat created)
|
|
91
|
+
if (!currentConversationId) {
|
|
92
|
+
setCurrentConversationId(conversationId);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
[currentConversationId],
|
|
96
|
+
);
|
|
89
97
|
|
|
90
98
|
// Loading state
|
|
91
99
|
if (isLoading) {
|
|
@@ -158,7 +166,9 @@ function MainContent() {
|
|
|
158
166
|
memorySpaceId={memorySpaceId}
|
|
159
167
|
userId={userId}
|
|
160
168
|
conversationId={currentConversationId}
|
|
161
|
-
apiEndpoint={
|
|
169
|
+
apiEndpoint={
|
|
170
|
+
versions?.aiSdkMajor === 6 ? "/api/chat-v6" : "/api/chat"
|
|
171
|
+
}
|
|
162
172
|
onOrchestrationStart={startOrchestration}
|
|
163
173
|
onLayerUpdate={updateLayer}
|
|
164
174
|
onReset={resetLayers}
|
|
@@ -193,7 +203,9 @@ function MainContent() {
|
|
|
193
203
|
<footer className="border-t border-white/10 px-6 py-3">
|
|
194
204
|
<div className="flex items-center justify-between text-sm text-gray-500">
|
|
195
205
|
<div className="flex items-center gap-4">
|
|
196
|
-
<span>
|
|
206
|
+
<span>
|
|
207
|
+
Cortex SDK {versions ? `v${versions.cortexSdk}` : "..."}
|
|
208
|
+
</span>
|
|
197
209
|
<span>•</span>
|
|
198
210
|
<span>Vercel AI SDK {versions?.aiSdk ?? "..."}</span>
|
|
199
211
|
{versions?.aiSdkMajor === 6 && (
|
|
@@ -41,7 +41,9 @@ export function AdminSetup() {
|
|
|
41
41
|
<span className="text-4xl">🧠</span>
|
|
42
42
|
</div>
|
|
43
43
|
<h1 className="text-3xl font-bold mb-2">Welcome to Cortex</h1>
|
|
44
|
-
<p className="text-gray-400">
|
|
44
|
+
<p className="text-gray-400">
|
|
45
|
+
Set up your admin password to get started
|
|
46
|
+
</p>
|
|
45
47
|
</div>
|
|
46
48
|
|
|
47
49
|
{/* Setup Form */}
|
|
@@ -32,7 +32,7 @@ export interface AuthContextValue extends AuthState {
|
|
|
32
32
|
register: (
|
|
33
33
|
username: string,
|
|
34
34
|
password: string,
|
|
35
|
-
displayName?: string
|
|
35
|
+
displayName?: string,
|
|
36
36
|
) => Promise<boolean>;
|
|
37
37
|
logout: () => void;
|
|
38
38
|
clearError: () => void;
|
|
@@ -164,7 +164,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
164
164
|
JSON.stringify({
|
|
165
165
|
user: data.user,
|
|
166
166
|
sessionToken: data.sessionToken,
|
|
167
|
-
})
|
|
167
|
+
}),
|
|
168
168
|
);
|
|
169
169
|
}
|
|
170
170
|
|
|
@@ -184,7 +184,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
184
184
|
return false;
|
|
185
185
|
}
|
|
186
186
|
},
|
|
187
|
-
[]
|
|
187
|
+
[],
|
|
188
188
|
);
|
|
189
189
|
|
|
190
190
|
// Register
|
|
@@ -192,7 +192,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
192
192
|
async (
|
|
193
193
|
username: string,
|
|
194
194
|
password: string,
|
|
195
|
-
displayName?: string
|
|
195
|
+
displayName?: string,
|
|
196
196
|
): Promise<boolean> => {
|
|
197
197
|
setState((prev) => ({ ...prev, error: null }));
|
|
198
198
|
|
|
@@ -217,7 +217,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
217
217
|
JSON.stringify({
|
|
218
218
|
user: data.user,
|
|
219
219
|
sessionToken: data.sessionToken,
|
|
220
|
-
})
|
|
220
|
+
}),
|
|
221
221
|
);
|
|
222
222
|
}
|
|
223
223
|
|
|
@@ -237,7 +237,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
237
237
|
return false;
|
|
238
238
|
}
|
|
239
239
|
},
|
|
240
|
-
[]
|
|
240
|
+
[],
|
|
241
241
|
);
|
|
242
242
|
|
|
243
243
|
// Logout
|
|
@@ -26,9 +26,15 @@ interface ChatHistorySidebarProps {
|
|
|
26
26
|
// Helpers
|
|
27
27
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
28
28
|
|
|
29
|
-
function groupByDate(
|
|
29
|
+
function groupByDate(
|
|
30
|
+
conversations: Conversation[],
|
|
31
|
+
): Record<string, Conversation[]> {
|
|
30
32
|
const now = new Date();
|
|
31
|
-
const today = new Date(
|
|
33
|
+
const today = new Date(
|
|
34
|
+
now.getFullYear(),
|
|
35
|
+
now.getMonth(),
|
|
36
|
+
now.getDate(),
|
|
37
|
+
).getTime();
|
|
32
38
|
const yesterday = today - 86400000;
|
|
33
39
|
const weekAgo = today - 7 * 86400000;
|
|
34
40
|
|
|
@@ -44,7 +50,7 @@ function groupByDate(conversations: Conversation[]): Record<string, Conversation
|
|
|
44
50
|
const convDay = new Date(
|
|
45
51
|
convDate.getFullYear(),
|
|
46
52
|
convDate.getMonth(),
|
|
47
|
-
convDate.getDate()
|
|
53
|
+
convDate.getDate(),
|
|
48
54
|
).getTime();
|
|
49
55
|
|
|
50
56
|
if (convDay >= today) {
|
|
@@ -89,7 +95,7 @@ export function ChatHistorySidebar({
|
|
|
89
95
|
|
|
90
96
|
try {
|
|
91
97
|
const response = await fetch(
|
|
92
|
-
`/api/conversations?userId=${encodeURIComponent(user.id)}&memorySpaceId=${encodeURIComponent(memorySpaceId)}
|
|
98
|
+
`/api/conversations?userId=${encodeURIComponent(user.id)}&memorySpaceId=${encodeURIComponent(memorySpaceId)}`,
|
|
93
99
|
);
|
|
94
100
|
const data = await response.json();
|
|
95
101
|
if (data.conversations) {
|
|
@@ -121,13 +127,18 @@ export function ChatHistorySidebar({
|
|
|
121
127
|
setDeletingId(conversationId);
|
|
122
128
|
|
|
123
129
|
try {
|
|
124
|
-
const response = await fetch(
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
const response = await fetch(
|
|
131
|
+
`/api/conversations?conversationId=${encodeURIComponent(conversationId)}`,
|
|
132
|
+
{
|
|
133
|
+
method: "DELETE",
|
|
134
|
+
},
|
|
135
|
+
);
|
|
127
136
|
|
|
128
137
|
if (!response.ok) {
|
|
129
138
|
const data = await response.json().catch(() => ({}));
|
|
130
|
-
throw new Error(
|
|
139
|
+
throw new Error(
|
|
140
|
+
data.error || `Delete failed with status ${response.status}`,
|
|
141
|
+
);
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
// Remove from local state only after successful deletion
|
|
@@ -69,6 +69,9 @@ export function ChatInterface({
|
|
|
69
69
|
conversationIdRef.current = conversationId;
|
|
70
70
|
}, [conversationId]);
|
|
71
71
|
|
|
72
|
+
// Track if we should skip history load (when we create a new conversation ourselves)
|
|
73
|
+
const skipHistoryLoadRef = useRef<string | null>(null);
|
|
74
|
+
|
|
72
75
|
// Create transport with a function that reads from ref for conversationId
|
|
73
76
|
// This ensures we always send the latest conversationId
|
|
74
77
|
const transport = useMemo(
|
|
@@ -104,6 +107,8 @@ export function ChatInterface({
|
|
|
104
107
|
// Handle conversation title update
|
|
105
108
|
if (part.type === "data-conversation-update") {
|
|
106
109
|
const update = part.data as { conversationId: string; title: string };
|
|
110
|
+
// Mark this conversation ID to skip history load - we just created it
|
|
111
|
+
skipHistoryLoadRef.current = update.conversationId;
|
|
107
112
|
onConversationUpdate?.(update.conversationId, update.title);
|
|
108
113
|
}
|
|
109
114
|
},
|
|
@@ -122,20 +127,28 @@ export function ChatInterface({
|
|
|
122
127
|
|
|
123
128
|
// Load messages when conversation changes
|
|
124
129
|
useEffect(() => {
|
|
125
|
-
//
|
|
126
|
-
setMessages([]);
|
|
127
|
-
|
|
128
|
-
// If no conversation selected, nothing more to do
|
|
130
|
+
// If no conversation selected, just clear messages
|
|
129
131
|
if (!conversationId) {
|
|
132
|
+
setMessages([]);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Skip loading if we just created this conversation ourselves
|
|
137
|
+
// (we already have the messages in state from the current chat session)
|
|
138
|
+
if (skipHistoryLoadRef.current === conversationId) {
|
|
139
|
+
skipHistoryLoadRef.current = null;
|
|
130
140
|
return;
|
|
131
141
|
}
|
|
132
142
|
|
|
143
|
+
// Clear messages before loading new conversation
|
|
144
|
+
setMessages([]);
|
|
145
|
+
|
|
133
146
|
// Fetch conversation history
|
|
134
147
|
const loadConversationHistory = async () => {
|
|
135
148
|
setIsLoadingHistory(true);
|
|
136
149
|
try {
|
|
137
150
|
const response = await fetch(
|
|
138
|
-
`/api/conversations?conversationId=${encodeURIComponent(conversationId)}
|
|
151
|
+
`/api/conversations?conversationId=${encodeURIComponent(conversationId)}`,
|
|
139
152
|
);
|
|
140
153
|
|
|
141
154
|
if (!response.ok) {
|
|
@@ -147,12 +160,19 @@ export function ChatInterface({
|
|
|
147
160
|
|
|
148
161
|
if (data.messages && data.messages.length > 0) {
|
|
149
162
|
// Transform to the format expected by useChat
|
|
150
|
-
const loadedMessages = data.messages.map(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
163
|
+
const loadedMessages = data.messages.map(
|
|
164
|
+
(msg: {
|
|
165
|
+
id: string;
|
|
166
|
+
role: string;
|
|
167
|
+
content: string;
|
|
168
|
+
createdAt: string;
|
|
169
|
+
}) => ({
|
|
170
|
+
id: msg.id,
|
|
171
|
+
role: msg.role,
|
|
172
|
+
content: msg.content,
|
|
173
|
+
createdAt: new Date(msg.createdAt),
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
156
176
|
setMessages(loadedMessages);
|
|
157
177
|
}
|
|
158
178
|
} catch (error) {
|
|
@@ -193,7 +213,10 @@ export function ChatInterface({
|
|
|
193
213
|
};
|
|
194
214
|
|
|
195
215
|
// Extract text content from message parts (AI SDK v5 format)
|
|
196
|
-
const getMessageContent = (message: {
|
|
216
|
+
const getMessageContent = (message: {
|
|
217
|
+
content?: string;
|
|
218
|
+
parts?: Array<{ type: string; text?: string }>;
|
|
219
|
+
}): string => {
|
|
197
220
|
if (typeof message.content === "string") {
|
|
198
221
|
return message.content;
|
|
199
222
|
}
|
|
@@ -233,7 +256,9 @@ export function ChatInterface({
|
|
|
233
256
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
234
257
|
/>
|
|
235
258
|
</svg>
|
|
236
|
-
<span className="text-gray-400 text-sm">
|
|
259
|
+
<span className="text-gray-400 text-sm">
|
|
260
|
+
Loading conversation...
|
|
261
|
+
</span>
|
|
237
262
|
</div>
|
|
238
263
|
</div>
|
|
239
264
|
)}
|
|
@@ -244,7 +269,9 @@ export function ChatInterface({
|
|
|
244
269
|
<span className="text-3xl">🧠</span>
|
|
245
270
|
</div>
|
|
246
271
|
<h2 className="text-xl font-semibold mb-2">
|
|
247
|
-
{conversationId
|
|
272
|
+
{conversationId
|
|
273
|
+
? "Continue your conversation"
|
|
274
|
+
: "Start a new conversation"}
|
|
248
275
|
</h2>
|
|
249
276
|
<p className="text-gray-400 max-w-md mx-auto mb-6">
|
|
250
277
|
This demo shows how Cortex orchestrates memory across multiple
|
|
@@ -57,7 +57,9 @@ export function LoginScreen() {
|
|
|
57
57
|
<span className="text-4xl">🧠</span>
|
|
58
58
|
</div>
|
|
59
59
|
<h1 className="text-3xl font-bold mb-2">Cortex Memory Demo</h1>
|
|
60
|
-
<p className="text-gray-400">
|
|
60
|
+
<p className="text-gray-400">
|
|
61
|
+
Sign in to explore AI with long-term memory
|
|
62
|
+
</p>
|
|
61
63
|
</div>
|
|
62
64
|
|
|
63
65
|
{/* Login/Register Card */}
|
|
@@ -114,8 +116,7 @@ export function LoginScreen() {
|
|
|
114
116
|
htmlFor="displayName"
|
|
115
117
|
className="block text-sm font-medium text-gray-300 mb-2"
|
|
116
118
|
>
|
|
117
|
-
Display Name
|
|
118
|
-
<span className="text-gray-500">(optional)</span>
|
|
119
|
+
Display Name <span className="text-gray-500">(optional)</span>
|
|
119
120
|
</label>
|
|
120
121
|
<input
|
|
121
122
|
id="displayName"
|
|
@@ -144,7 +145,9 @@ export function LoginScreen() {
|
|
|
144
145
|
placeholder="Enter your password"
|
|
145
146
|
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:border-cortex-500 transition-colors"
|
|
146
147
|
disabled={isLoading}
|
|
147
|
-
autoComplete={
|
|
148
|
+
autoComplete={
|
|
149
|
+
activeTab === "login" ? "current-password" : "new-password"
|
|
150
|
+
}
|
|
148
151
|
/>
|
|
149
152
|
</div>
|
|
150
153
|
|
|
@@ -181,7 +184,9 @@ export function LoginScreen() {
|
|
|
181
184
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
182
185
|
/>
|
|
183
186
|
</svg>
|
|
184
|
-
{activeTab === "login"
|
|
187
|
+
{activeTab === "login"
|
|
188
|
+
? "Signing in..."
|
|
189
|
+
: "Creating account..."}
|
|
185
190
|
</>
|
|
186
191
|
) : activeTab === "login" ? (
|
|
187
192
|
"Sign In"
|
|
@@ -99,10 +99,10 @@ export const memoryAgent = new ToolLoopAgent({
|
|
|
99
99
|
// └─────────────────────────────────────────────────────────────────┘
|
|
100
100
|
prepareCall: createMemoryPrepareCall({
|
|
101
101
|
convexUrl: process.env.CONVEX_URL!,
|
|
102
|
-
maxMemories: 20,
|
|
103
|
-
includeFacts: true,
|
|
102
|
+
maxMemories: 20, // Max items to inject from recall
|
|
103
|
+
includeFacts: true, // Include Layer 3 facts
|
|
104
104
|
includeVector: true, // Include Layer 2 vector memories
|
|
105
|
-
includeGraph: true,
|
|
105
|
+
includeGraph: true, // Expand through graph relationships
|
|
106
106
|
}),
|
|
107
107
|
|
|
108
108
|
// Default to 5 steps (sufficient for most chat interactions)
|
|
@@ -28,7 +28,7 @@ export async function hashPassword(password: string): Promise<string> {
|
|
|
28
28
|
passwordBuffer,
|
|
29
29
|
"PBKDF2",
|
|
30
30
|
false,
|
|
31
|
-
["deriveBits"]
|
|
31
|
+
["deriveBits"],
|
|
32
32
|
);
|
|
33
33
|
|
|
34
34
|
// Derive key using PBKDF2
|
|
@@ -40,7 +40,7 @@ export async function hashPassword(password: string): Promise<string> {
|
|
|
40
40
|
hash: "SHA-256",
|
|
41
41
|
},
|
|
42
42
|
keyMaterial,
|
|
43
|
-
KEY_LENGTH
|
|
43
|
+
KEY_LENGTH,
|
|
44
44
|
);
|
|
45
45
|
|
|
46
46
|
// Convert to base64 strings
|
|
@@ -60,7 +60,7 @@ export async function hashPassword(password: string): Promise<string> {
|
|
|
60
60
|
*/
|
|
61
61
|
export async function verifyPassword(
|
|
62
62
|
password: string,
|
|
63
|
-
storedHash: string
|
|
63
|
+
storedHash: string,
|
|
64
64
|
): Promise<boolean> {
|
|
65
65
|
try {
|
|
66
66
|
const [saltB64, expectedHashB64] = storedHash.split(":");
|
|
@@ -84,7 +84,7 @@ export async function verifyPassword(
|
|
|
84
84
|
passwordBuffer,
|
|
85
85
|
"PBKDF2",
|
|
86
86
|
false,
|
|
87
|
-
["deriveBits"]
|
|
87
|
+
["deriveBits"],
|
|
88
88
|
);
|
|
89
89
|
|
|
90
90
|
// Derive key using same parameters
|
|
@@ -96,7 +96,7 @@ export async function verifyPassword(
|
|
|
96
96
|
hash: "SHA-256",
|
|
97
97
|
},
|
|
98
98
|
keyMaterial,
|
|
99
|
-
KEY_LENGTH
|
|
99
|
+
KEY_LENGTH,
|
|
100
100
|
);
|
|
101
101
|
|
|
102
102
|
// Compare hashes
|
|
@@ -4,6 +4,11 @@ const path = require("path");
|
|
|
4
4
|
const nextConfig = {
|
|
5
5
|
transpilePackages: ["@cortexmemory/sdk", "@cortexmemory/vercel-ai-provider"],
|
|
6
6
|
serverExternalPackages: ["convex"],
|
|
7
|
+
// Disable image optimization to avoid sharp dependency (LGPL licensed)
|
|
8
|
+
// This quickstart doesn't use image optimization features
|
|
9
|
+
images: {
|
|
10
|
+
unoptimized: true,
|
|
11
|
+
},
|
|
7
12
|
experimental: {
|
|
8
13
|
// Ensure linked packages resolve dependencies from this project's node_modules
|
|
9
14
|
externalDir: true,
|
|
@@ -16,8 +21,11 @@ const nextConfig = {
|
|
|
16
21
|
webpack: (config) => {
|
|
17
22
|
config.resolve.alias = {
|
|
18
23
|
...config.resolve.alias,
|
|
19
|
-
"@anthropic-ai/sdk": path.resolve(
|
|
20
|
-
|
|
24
|
+
"@anthropic-ai/sdk": path.resolve(
|
|
25
|
+
__dirname,
|
|
26
|
+
"node_modules/@anthropic-ai/sdk",
|
|
27
|
+
),
|
|
28
|
+
openai: path.resolve(__dirname, "node_modules/openai"),
|
|
21
29
|
"neo4j-driver": path.resolve(__dirname, "node_modules/neo4j-driver"),
|
|
22
30
|
};
|
|
23
31
|
return config;
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"description": "Cortex Memory + Vercel AI SDK Quickstart Demo",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"dev": "next dev --webpack",
|
|
8
|
+
"dev": "next dev --webpack -H 0.0.0.0",
|
|
9
9
|
"build": "next build --webpack",
|
|
10
|
-
"start": "next start",
|
|
10
|
+
"start": "next start -H 0.0.0.0",
|
|
11
11
|
"lint": "next lint",
|
|
12
12
|
"test": "jest --selectProjects unit integration",
|
|
13
13
|
"test:unit": "jest --selectProjects unit",
|
|
@@ -19,32 +19,39 @@
|
|
|
19
19
|
"convex:deploy": "convex deploy"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@ai-sdk/openai": "^3.0.
|
|
23
|
-
"@ai-sdk/react": "^3.0.
|
|
22
|
+
"@ai-sdk/openai": "^3.0.4",
|
|
23
|
+
"@ai-sdk/react": "^3.0.11",
|
|
24
24
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
25
25
|
"@cortexmemory/sdk": "file:../../..",
|
|
26
26
|
"@cortexmemory/vercel-ai-provider": "file:..",
|
|
27
|
-
"ai": "^6.0.
|
|
27
|
+
"ai": "^6.0.11",
|
|
28
28
|
"convex": "^1.31.2",
|
|
29
|
-
"framer-motion": "^12.
|
|
29
|
+
"framer-motion": "^12.24.0",
|
|
30
30
|
"neo4j-driver": "^6.0.1",
|
|
31
31
|
"next": "^16.1.1",
|
|
32
32
|
"openai": "^6.15.0",
|
|
33
33
|
"react": "^19.2.3",
|
|
34
34
|
"react-dom": "^19.2.3",
|
|
35
|
-
"zod": "^4.
|
|
35
|
+
"zod": "^4.3.5"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@tailwindcss/postcss": "^4.1.18",
|
|
39
|
-
"@types/jest": "^
|
|
39
|
+
"@types/jest": "^30.0.0",
|
|
40
40
|
"@types/node": "^25.0.3",
|
|
41
41
|
"@types/react": "^19.2.7",
|
|
42
42
|
"@types/react-dom": "^19.2.3",
|
|
43
43
|
"autoprefixer": "^10.4.23",
|
|
44
|
-
"jest": "^
|
|
44
|
+
"jest": "^30.2.0",
|
|
45
45
|
"postcss": "^8.5.6",
|
|
46
46
|
"tailwindcss": "^4.1.18",
|
|
47
|
-
"ts-jest": "^29.
|
|
47
|
+
"ts-jest": "^29.4.6",
|
|
48
48
|
"typescript": "^5.9.3"
|
|
49
|
-
}
|
|
49
|
+
},
|
|
50
|
+
"main": "jest.config.js",
|
|
51
|
+
"directories": {
|
|
52
|
+
"lib": "lib",
|
|
53
|
+
"test": "tests"
|
|
54
|
+
},
|
|
55
|
+
"keywords": [],
|
|
56
|
+
"author": ""
|
|
50
57
|
}
|