Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
8 changes: 8 additions & 0 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
99 changes: 99 additions & 0 deletions app/api/validate-chat-session/route.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
}
60 changes: 22 additions & 38 deletions app/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="min-h-screen flex items-center justify-center bg-[#0D0D10] text-white">
<div className="text-center">
<h2 className="text-2xl font-bold mb-4">Chat session not found</h2>
<button
onClick={() => router.push("/chat")}
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors"
>
Return to Chat
</button>
</div>
</div>
)
}
// Ensure user and chatId are available
if (!isLoaded || !chatId) {
return;
}

if (!user) {
router.push('/');
return;
}

// When a new chat is created, we'll assume it's valid
// This skips validation for newly created chat sessions
// The chat system will create the database record when needed

}, [chatId, user, isLoaded, router])

return (
<div className="min-h-screen flex flex-col w-full items-center justify-center bg-[#0D0D10] text-white relative overflow-hidden">
{/* Auth buttons */}
{/* Auth button */}
<motion.div
className="absolute top-4 right-4 flex gap-4 z-50"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
>
<motion.button
onClick={() => 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
</motion.button>
<motion.button
onClick={() => 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
</motion.button>
<SignedIn>
<UserButton afterSignOutUrl="/" />
</SignedIn>
</motion.div>

{/* ZapDev branding */}
Expand Down Expand Up @@ -95,7 +79,7 @@ export default function ChatSessionPage() {

{/* Session ID indicator */}
<motion.div
className="absolute top-6 right-60 z-50"
className="absolute top-6 right-20 z-50"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 }}
Expand Down
15 changes: 5 additions & 10 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { Inter } from 'next/font/google'
import { ThemeProvider } from '@/components/theme-provider';
import {
ClerkProvider,
SignInButton,
SignUpButton,
SignedIn,
SignedOut,
UserButton,
} from '@clerk/nextjs';
import { PostHogProvider } from '@/components/PostHogProvider'
Expand All @@ -32,16 +29,14 @@ export default function RootLayout({
return (
<html lang="en" className={`${inter.variable} font-sans`} suppressHydrationWarning>
<body className="min-h-screen bg-[#0D0D10] text-[#EAEAEA]">
<ClerkProvider>
<ClerkProvider
publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
afterSignInUrl="/chat"
afterSignUpUrl="/chat"
>
<PostHogProvider>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
<header style={{ padding: '1rem', display: 'flex', justifyContent: 'flex-end', gap: '1rem' }}>
<SignedOut>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<SignInButton />
<SignUpButton />
</div>
</SignedOut>
<SignedIn>
<UserButton afterSignOutUrl="/" />
</SignedIn>
Expand Down
39 changes: 15 additions & 24 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useRouter } from "next/navigation"
import { motion } from "framer-motion";
import dynamic from 'next/dynamic';
import Hero from "@/components/hero";
import { SignInButton, SignUpButton } from "@clerk/nextjs";
import { SignedIn, UserButton } from "@clerk/nextjs";
import FinalCTA from "@/components/final-cta";

const FeaturesShowcase = dynamic(() => import('@/components/features-showcase'), { loading: () => <div style={{ minHeight: '50vh' }} /> });
Expand All @@ -25,28 +25,19 @@ export default function Home() {
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
>
<div>
<SignInButton mode="redirect" fallbackRedirectUrl="/chat">
<motion.button
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
</motion.button>
</SignInButton>
</div>
<div>
<SignUpButton mode="redirect" fallbackRedirectUrl="/chat">
<motion.button
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
</motion.button>
</SignUpButton>
</div>
<SignedIn>
<div className="flex items-center gap-4">
<motion.button
onClick={() => router.push("/chat")}
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 }}
>
Go to Chat
</motion.button>
<UserButton afterSignOutUrl="/" />
</div>
</SignedIn>
</motion.div>

{/* Try it now button that navigates to chat */}
Expand All @@ -70,7 +61,7 @@ export default function Home() {
</motion.button>
</motion.div>

{/* Homepage sections (commented out for debugging) */}
{/* Homepage sections */}
<Hero />
<FeaturesShowcase />
<VisualShowcase />
Expand Down
27 changes: 27 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading