@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.
Files changed (71) hide show
  1. package/dist/commands/db.d.ts.map +1 -1
  2. package/dist/commands/db.js +2 -18
  3. package/dist/commands/db.js.map +1 -1
  4. package/dist/commands/dev.d.ts.map +1 -1
  5. package/dist/commands/dev.js +153 -17
  6. package/dist/commands/dev.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +373 -70
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/setup.d.ts.map +1 -1
  11. package/dist/commands/setup.js +102 -46
  12. package/dist/commands/setup.js.map +1 -1
  13. package/dist/commands/status.d.ts.map +1 -1
  14. package/dist/commands/status.js +94 -7
  15. package/dist/commands/status.js.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/types.d.ts +23 -0
  18. package/dist/types.d.ts.map +1 -1
  19. package/dist/utils/config.d.ts +11 -0
  20. package/dist/utils/config.d.ts.map +1 -1
  21. package/dist/utils/config.js +20 -0
  22. package/dist/utils/config.js.map +1 -1
  23. package/dist/utils/init/convex-setup.d.ts +58 -6
  24. package/dist/utils/init/convex-setup.d.ts.map +1 -1
  25. package/dist/utils/init/convex-setup.js +261 -57
  26. package/dist/utils/init/convex-setup.js.map +1 -1
  27. package/dist/utils/init/env-generator.d.ts.map +1 -1
  28. package/dist/utils/init/env-generator.js +12 -2
  29. package/dist/utils/init/env-generator.js.map +1 -1
  30. package/dist/utils/init/graph-setup.d.ts.map +1 -1
  31. package/dist/utils/init/graph-setup.js +12 -0
  32. package/dist/utils/init/graph-setup.js.map +1 -1
  33. package/dist/utils/init/quickstart-setup.d.ts +87 -0
  34. package/dist/utils/init/quickstart-setup.d.ts.map +1 -0
  35. package/dist/utils/init/quickstart-setup.js +462 -0
  36. package/dist/utils/init/quickstart-setup.js.map +1 -0
  37. package/dist/utils/init/types.d.ts +4 -0
  38. package/dist/utils/init/types.d.ts.map +1 -1
  39. package/dist/utils/schema-sync.d.ts.map +1 -1
  40. package/dist/utils/schema-sync.js +27 -21
  41. package/dist/utils/schema-sync.js.map +1 -1
  42. package/package.json +3 -2
  43. package/templates/vercel-ai-quickstart/.env.local.example +45 -0
  44. package/templates/vercel-ai-quickstart/README.md +280 -0
  45. package/templates/vercel-ai-quickstart/app/api/chat/route.ts +196 -0
  46. package/templates/vercel-ai-quickstart/app/api/facts/route.ts +39 -0
  47. package/templates/vercel-ai-quickstart/app/api/health/route.ts +99 -0
  48. package/templates/vercel-ai-quickstart/app/api/memories/route.ts +37 -0
  49. package/templates/vercel-ai-quickstart/app/globals.css +114 -0
  50. package/templates/vercel-ai-quickstart/app/layout.tsx +19 -0
  51. package/templates/vercel-ai-quickstart/app/page.tsx +131 -0
  52. package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +237 -0
  53. package/templates/vercel-ai-quickstart/components/ConvexClientProvider.tsx +21 -0
  54. package/templates/vercel-ai-quickstart/components/DataPreview.tsx +57 -0
  55. package/templates/vercel-ai-quickstart/components/HealthStatus.tsx +214 -0
  56. package/templates/vercel-ai-quickstart/components/LayerCard.tsx +263 -0
  57. package/templates/vercel-ai-quickstart/components/LayerFlowDiagram.tsx +195 -0
  58. package/templates/vercel-ai-quickstart/components/MemorySpaceSwitcher.tsx +93 -0
  59. package/templates/vercel-ai-quickstart/convex/conversations.ts +67 -0
  60. package/templates/vercel-ai-quickstart/convex/facts.ts +131 -0
  61. package/templates/vercel-ai-quickstart/convex/health.ts +15 -0
  62. package/templates/vercel-ai-quickstart/convex/memories.ts +104 -0
  63. package/templates/vercel-ai-quickstart/convex/schema.ts +20 -0
  64. package/templates/vercel-ai-quickstart/convex/users.ts +105 -0
  65. package/templates/vercel-ai-quickstart/lib/animations.ts +146 -0
  66. package/templates/vercel-ai-quickstart/lib/layer-tracking.ts +214 -0
  67. package/templates/vercel-ai-quickstart/next.config.js +7 -0
  68. package/templates/vercel-ai-quickstart/package.json +41 -0
  69. package/templates/vercel-ai-quickstart/postcss.config.js +5 -0
  70. package/templates/vercel-ai-quickstart/tailwind.config.js +37 -0
  71. 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
+ }