Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ next-env.d.ts
# Sytem Prompt example

systemprompt-example.txt

# Ehnhance Prompt example

enhance-prompt.txt
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
Loading