diff --git a/README.md b/README.md
index cf1e9b77..a868f7bb 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Animated AI Chat
+# ZapDev
-Welcome to Animated AI Chat! This is a web application that provides an engaging and interactive chat experience powered by various AI models through OpenRouter, featuring smooth animations and user authentication.
+Welcome to ZapDev! This is a web application that provides an engaging and interactive chat experience powered by various AI models through OpenRouter, featuring smooth animations and user authentication.
## Features
diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts
index 17816eaf..a7367ffa 100644
--- a/app/api/chat/route.ts
+++ b/app/api/chat/route.ts
@@ -3,6 +3,14 @@ import { generateOpenRouterResponse, ChatHistory } from "@/lib/openrouter";
import { getSequentialThinkingSteps } from "@/lib/sequential-thinker";
import { systemPrompt as zapDevSystemPrompt } from "@/lib/systemprompt";
+ /**
+ * API route for generating AI chat responses.
+ * @param req The incoming request, expected to contain a JSON body with the following properties:
+ * - `messages`: An array of messages in the chat, where each message is an object with `role` and `content` properties.
+ * - `chatId`: The ID of the chat, used to generate a unique system prompt.
+ * - `modelId`: The ID of the AI model to use for generating responses, if specified.
+ * @returns A JSON response containing the generated AI response.
+ */
export async function POST(req: Request) {
try {
// Parse request body
diff --git a/app/api/validate-chat-session/route.ts b/app/api/validate-chat-session/route.ts
new file mode 100644
index 00000000..afab33c0
--- /dev/null
+++ b/app/api/validate-chat-session/route.ts
@@ -0,0 +1,99 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getAuth } from '@clerk/nextjs/server';
+import { createClient } from '@supabase/supabase-js';
+
+// Initialize Supabase client
+const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
+const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
+
+// Helper function to create Supabase client
+const getSupabase = () => {
+ if (!supabaseUrl || !supabaseAnonKey) {
+ console.error('Missing Supabase environment variables');
+ return null;
+ }
+ return createClient(supabaseUrl, supabaseAnonKey);
+};
+
+export async function POST(req: NextRequest) {
+ const supabase = getSupabase();
+ if (!supabase) {
+ // If Supabase client can't be initialized, we'll still validate the user
+ // and return a successful response - this allows the application to work
+ // even when database is not available
+ return NextResponse.json({ isValid: true });
+ }
+
+ try {
+ const { userId: authUserId } = getAuth(req);
+ const { chatId, userId: clientUserId } = await req.json();
+
+ if (!authUserId) {
+ return NextResponse.json({ error: 'User not authenticated.' }, { status: 401 });
+ }
+
+ // Ensure the userId from the client matches the authenticated user's ID
+ if (authUserId !== clientUserId) {
+ return NextResponse.json({ error: 'User ID mismatch.' }, { status: 403 });
+ }
+
+ if (!chatId) {
+ return NextResponse.json({ error: 'Chat ID is required.' }, { status: 400 });
+ }
+
+ try {
+ // Check if chat exists
+ const { data: existingChat, error: selectError } = await supabase
+ .from('chats')
+ .select('id')
+ .eq('id', chatId)
+ .maybeSingle();
+
+ if (selectError) {
+ console.error('Error checking chat:', selectError);
+ // Return valid to allow chat to proceed even with DB errors
+ return NextResponse.json({ isValid: true });
+ }
+
+ // If chat doesn't exist, we'll create it
+ if (!existingChat) {
+ // We need to first get the user's internal UUID from the users table
+ const { data: userData, error: userError } = await supabase
+ .from('users')
+ .select('id')
+ .eq('clerk_user_id', authUserId)
+ .maybeSingle();
+
+ if (userError || !userData) {
+ console.warn('User not found in database, returning valid=true anyway');
+ return NextResponse.json({ isValid: true });
+ }
+
+ // Create chat record
+ const { error: insertError } = await supabase
+ .from('chats')
+ .insert({
+ id: chatId,
+ user_id: userData.id,
+ title: 'New Chat'
+ });
+
+ if (insertError) {
+ console.error('Error creating chat:', insertError);
+ // Still return valid=true to allow application to function
+ return NextResponse.json({ isValid: true });
+ }
+ }
+
+ return NextResponse.json({ isValid: true });
+ } catch (dbError) {
+ console.error('Database error:', dbError);
+ // Return valid=true to allow the application to function even with DB errors
+ return NextResponse.json({ isValid: true });
+ }
+ } catch (error) {
+ console.error('General error in /api/validate-chat-session:', error);
+ // For general errors, we'll still allow the chat to proceed
+ return NextResponse.json({ isValid: true });
+ }
+}
diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx
index 5c5418d0..4cff0b05 100644
--- a/app/chat/[id]/page.tsx
+++ b/app/chat/[id]/page.tsx
@@ -3,61 +3,45 @@
import { AnimatedAIChat } from "@/components/animated-ai-chat"
import { motion } from "framer-motion"
import { useRouter, useParams } from "next/navigation"
+import { useUser, UserButton, SignedIn } from "@clerk/nextjs"
import { useEffect, useState } from "react"
export default function ChatSessionPage() {
const router = useRouter()
const params = useParams()
const chatId = params.id as string
+ const { user, isLoaded } = useUser()
const [isValidSession, setIsValidSession] = useState(true)
useEffect(() => {
- // In a real app, validate if this chat session exists/belongs to user
- // For now, we'll assume all sessions are valid
- setIsValidSession(true)
- }, [chatId])
-
- if (!isValidSession) {
- return (
-
- {/* Auth buttons */}
+ {/* Auth button */}
- router.push("/auth?tab=login")}
- className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors text-sm font-medium"
- whileHover={{ scale: 1.05 }}
- whileTap={{ scale: 0.95 }}
- >
- Sign In
-
- router.push("/auth?tab=signup")}
- className="px-4 py-2 rounded-lg bg-gradient-to-r from-[#6C52A0] to-[#A0527C] hover:from-[#7C62B0] hover:to-[#B0627C] transition-all text-sm font-medium"
- whileHover={{ scale: 1.05 }}
- whileTap={{ scale: 0.95 }}
- >
- Sign Up
-
+
+
+
{/* ZapDev branding */}
@@ -95,7 +79,7 @@ export default function ChatSessionPage() {
{/* Session ID indicator */}
-
+
{/* Try it now button that navigates to chat */}
@@ -70,7 +61,7 @@ export default function Home() {
- {/* Homepage sections (commented out for debugging) */}
+ {/* Homepage sections */}
diff --git a/bun.lock b/bun.lock
index 90c97e81..ce452c5b 100644
--- a/bun.lock
+++ b/bun.lock
@@ -35,6 +35,7 @@
"@radix-ui/react-toggle": "1.1.1",
"@radix-ui/react-toggle-group": "1.1.1",
"@radix-ui/react-tooltip": "1.1.6",
+ "@supabase/supabase-js": "^2.49.10",
"ai": "^4.3.16",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
@@ -306,6 +307,20 @@
"@radix-ui/rect": ["@radix-ui/rect@1.1.0", "", {}, "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="],
+ "@supabase/auth-js": ["@supabase/auth-js@2.69.1", "", { "dependencies": { "@supabase/node-fetch": "^2.6.14" } }, "sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ=="],
+
+ "@supabase/functions-js": ["@supabase/functions-js@2.4.4", "", { "dependencies": { "@supabase/node-fetch": "^2.6.14" } }, "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA=="],
+
+ "@supabase/node-fetch": ["@supabase/node-fetch@2.6.15", "", { "dependencies": { "whatwg-url": "^5.0.0" } }, "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ=="],
+
+ "@supabase/postgrest-js": ["@supabase/postgrest-js@1.19.4", "", { "dependencies": { "@supabase/node-fetch": "^2.6.14" } }, "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw=="],
+
+ "@supabase/realtime-js": ["@supabase/realtime-js@2.11.10", "", { "dependencies": { "@supabase/node-fetch": "^2.6.13", "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "ws": "^8.18.2" } }, "sha512-SJKVa7EejnuyfImrbzx+HaD9i6T784khuw1zP+MBD7BmJYChegGxYigPzkKX8CK8nGuDntmeSD3fvriaH0EGZA=="],
+
+ "@supabase/storage-js": ["@supabase/storage-js@2.7.1", "", { "dependencies": { "@supabase/node-fetch": "^2.6.14" } }, "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA=="],
+
+ "@supabase/supabase-js": ["@supabase/supabase-js@2.49.10", "", { "dependencies": { "@supabase/auth-js": "2.69.1", "@supabase/functions-js": "2.4.4", "@supabase/node-fetch": "2.6.15", "@supabase/postgrest-js": "1.19.4", "@supabase/realtime-js": "2.11.10", "@supabase/storage-js": "2.7.1" } }, "sha512-IRPcIdncuhD2m1eZ2Fkg0S1fq9SXlHfmAetBxPN66kVFtTucR8b01xKuVmKqcIJokB17umMf1bmqyS8yboXGsw=="],
+
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
@@ -332,12 +347,16 @@
"@types/node": ["@types/node@22.15.29", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ=="],
+ "@types/phoenix": ["@types/phoenix@1.6.6", "", {}, "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A=="],
+
"@types/react": ["@types/react@19.1.6", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q=="],
"@types/react-dom": ["@types/react-dom@19.1.5", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg=="],
"@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="],
+ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
+
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
"acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="],
@@ -776,6 +795,8 @@
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
+ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
+
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -804,8 +825,12 @@
"web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="],
+ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
+
"webpack-bundle-analyzer": ["webpack-bundle-analyzer@4.10.1", "", { "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", "commander": "^7.2.0", "debounce": "^1.2.1", "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", "is-plain-object": "^5.0.0", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", "ws": "^7.3.1" }, "bin": { "webpack-bundle-analyzer": "lib/bin/analyzer.js" } }, "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ=="],
+ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
@@ -820,6 +845,8 @@
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
+ "@supabase/realtime-js/ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
+
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
diff --git a/middleware.ts b/middleware.ts
index b6e0c7fb..26d5e60b 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -1,4 +1,5 @@
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
+import { NextResponse } from 'next/server';
// Define routes that require authentication
const isProtectedRoute = createRouteMatcher([
@@ -6,10 +7,26 @@ const isProtectedRoute = createRouteMatcher([
// Add any other routes you want to protect here
]);
-export default clerkMiddleware((auth, req) => {
+const publicRoutes = [
+ '/',
+ '/sign-in(.*)',
+ '/sign-up(.*)',
+];
+
+export default clerkMiddleware(async (auth, req) => {
// For routes that require authentication, protect them
if (isProtectedRoute(req)) {
- auth.protect();
+ await auth.protect();
+ }
+
+ // Handle auth redirection
+ const { userId } = await auth();
+ const isHomePage = req.nextUrl.pathname === '/';
+
+ // If user is signed in and on the homepage, redirect to chat
+ if (userId && isHomePage) {
+ const url = new URL('/chat', req.url);
+ return NextResponse.redirect(url);
}
});
diff --git a/package.json b/package.json
index de510cf6..b06e9dc8 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
"@radix-ui/react-toggle": "1.1.1",
"@radix-ui/react-toggle-group": "1.1.1",
"@radix-ui/react-tooltip": "1.1.6",
+ "@supabase/supabase-js": "^2.49.10",
"ai": "^4.3.16",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",