diff --git a/.gitignore b/.gitignore index f6b8c857..7e261396 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,7 @@ next-env.d.ts # Sytem Prompt example systemprompt-example.txt + +# Ehnhance Prompt example + +enhance-prompt.txt 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 ( -
-
-

Chat session not found

- -
-
- ) - } + // 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 (
- {/* 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 */} - +
- -
- - -
-
diff --git a/app/page.tsx b/app/page.tsx index 640f14a3..9d419ffb 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -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: () =>
}); @@ -25,28 +25,19 @@ export default function Home() { animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.5 }} > -
- - - Sign In - - -
-
- - - Sign Up - - -
+ +
+ 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 + + +
+
{/* 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..4dd02ff2 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,10 @@ "": { "name": "my-v0-project", "dependencies": { + "@ai-sdk/azure": "^1.3.23", + "@azure-rest/ai-inference": "^1.0.0-beta.6", + "@azure/core-auth": "^1.9.0", + "@azure/core-sse": "^2.2.0", "@clerk/nextjs": "^6.12.6", "@emotion/is-prop-valid": "latest", "@hookform/resolvers": "^3.9.1", @@ -35,6 +39,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", @@ -76,6 +81,10 @@ }, }, "packages": { + "@ai-sdk/azure": ["@ai-sdk/azure@1.3.23", "", { "dependencies": { "@ai-sdk/openai": "1.3.22", "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-vpsaPtU24RBVk/IMM5UylR/N4RtAuL2NZLWc7LJ3tvMTHu6pI46a7w+1qIwR3F6yO9ehWR8qvfLaBefJNFxaVw=="], + + "@ai-sdk/openai": ["@ai-sdk/openai@1.3.22", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-QwA+2EkG0QyjVR+7h6FE7iOu2ivNqAVMm9UJZkVxxTk5OIq5fFJDTEI/zICEMuHImTTXR2JjsL6EirJ28Jc4cw=="], + "@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="], @@ -86,6 +95,26 @@ "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@azure-rest/ai-inference": ["@azure-rest/ai-inference@1.0.0-beta.6", "", { "dependencies": { "@azure-rest/core-client": "^2.1.0", "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-lro": "^2.7.2", "@azure/core-rest-pipeline": "^1.18.2", "@azure/core-tracing": "^1.2.0", "@azure/logger": "^1.1.4", "tslib": "^2.8.1" } }, "sha512-j5FrJDTHu2P2+zwFVe5j2edasOIhqkFj+VkDjbhGkQuOoIAByF0egRkgs0G1k03HyJ7bOOT9BkRF7MIgr/afhw=="], + + "@azure-rest/core-client": ["@azure-rest/core-client@2.4.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.3.0", "@azure/core-rest-pipeline": "^1.5.0", "@azure/core-tracing": "^1.0.1", "@typespec/ts-http-runtime": "^0.2.2", "tslib": "^2.6.2" } }, "sha512-CjMFBcmnt0YNdRcxSSoZbtZNXudLlicdml7UrPsV03nHiWB+Bq5cu5ctieyaCuRtU7jm7+SOFtiE/g4pBFPKKA=="], + + "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + + "@azure/core-auth": ["@azure/core-auth@1.9.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.11.0", "tslib": "^2.6.2" } }, "sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw=="], + + "@azure/core-lro": ["@azure/core-lro@2.7.2", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", "@azure/logger": "^1.0.0", "tslib": "^2.6.2" } }, "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw=="], + + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.20.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.8.0", "@azure/core-tracing": "^1.0.1", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@typespec/ts-http-runtime": "^0.2.2", "tslib": "^2.6.2" } }, "sha512-ASoP8uqZBS3H/8N8at/XwFr6vYrRP3syTK0EUjDXQy0Y1/AUS+QeIRThKmTNJO2RggvBBxaXDPM7YoIwDGeA0g=="], + + "@azure/core-sse": ["@azure/core-sse@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-6Xg/CeW0jRyMoWt+puw2x6Qqkml3tr76Cn/oA9goIcUXtsi3ngmTwCVbwqkUWfhsOfo4F+78LGgiswSxTHN0sg=="], + + "@azure/core-tracing": ["@azure/core-tracing@1.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg=="], + + "@azure/core-util": ["@azure/core-util@1.12.0", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@typespec/ts-http-runtime": "^0.2.2", "tslib": "^2.6.2" } }, "sha512-13IyjTQgABPARvG90+N2dXpC+hwp466XCdQXPCRlbWHgd3SJd5Q1VvaBGv6k1BIa4MQm6hAF1UBU1m8QUxV8sQ=="], + + "@azure/logger": ["@azure/logger@1.2.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.2.2", "tslib": "^2.6.2" } }, "sha512-0hKEzLhpw+ZTAfNJyRrn6s+V0nDWzXk9OjBr2TiGIu0OfMr5s2V4FpKLTAK3Ca5r5OKLbf4hkOGDPyiRjie/jA=="], + "@babel/runtime": ["@babel/runtime@7.27.4", "", {}, "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA=="], "@clerk/backend": ["@clerk/backend@2.0.0", "", { "dependencies": { "@clerk/shared": "^3.9.6", "@clerk/types": "^4.60.0", "cookie": "1.0.2", "snakecase-keys": "8.0.1", "tslib": "2.8.1" } }, "sha512-1P4o9L464KVYjkMPaFBKmWo0WpUZsSO+57xjkrKPjW0FOTylYRicKCafoixfRlPMlYWzx7ETmj0ZypmrAZniBA=="], @@ -306,6 +335,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,16 +375,24 @@ "@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=="], + + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.2.2", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-Gz/Sm64+Sq/vklJu1tt9t+4R2lvnud8NbTD/ZfpZtMiUX7YeVpCA8j6NSW8ptwcoLL+NmYANwqP8DV0q/bwl2w=="], + "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=="], + "agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="], + "ai": ["ai@4.3.16", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g=="], "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], @@ -442,6 +493,8 @@ "debounce": ["debounce@1.2.1", "", {}, "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="], + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], @@ -540,6 +593,10 @@ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "input-otp": ["input-otp@1.4.1", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw=="], "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], @@ -610,6 +667,8 @@ "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], @@ -776,6 +835,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 +865,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 +885,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/components/animated-ai-chat.tsx b/components/animated-ai-chat.tsx index d15b0bd6..558bbf97 100644 --- a/components/animated-ai-chat.tsx +++ b/components/animated-ai-chat.tsx @@ -6,7 +6,7 @@ import { cn } from "@/lib/utils" import { Paperclip, Command, SendIcon, XIcon, LoaderIcon, Sparkles, ImageIcon, Figma, MonitorIcon } from "lucide-react" import { motion, AnimatePresence } from "framer-motion" import * as React from "react" -import { Message } from "@/lib/gemini" +import { Message } from "@/lib/openrouter" interface UseAutoResizeTextareaProps { minHeight: number @@ -128,6 +128,7 @@ export function AnimatedAIChat({ chatId = "default" }: AnimatedAIChatProps) { const [inputFocused, setInputFocused] = useState(false) const commandPaletteRef = useRef(null) const [messages, setMessages] = useState([]) + const [isSplitScreen, setIsSplitScreen] = useState(false) const commandSuggestions: CommandSuggestion[] = [ { @@ -248,6 +249,9 @@ export function AnimatedAIChat({ chatId = "default" }: AnimatedAIChatProps) { setMessages(prev => [...prev, newUserMessage]); setIsTyping(true); + // Enable split screen mode when message is sent + setIsSplitScreen(true); + try { // Call API to get AI response const response = await fetch("/api/chat", { @@ -262,7 +266,8 @@ export function AnimatedAIChat({ chatId = "default" }: AnimatedAIChatProps) { }); if (!response.ok) { - throw new Error("Failed to get response"); + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.error || `Request failed with status ${response.status}`); } const data = await response.json(); @@ -277,10 +282,10 @@ export function AnimatedAIChat({ chatId = "default" }: AnimatedAIChatProps) { } catch (error) { console.error("Error getting AI response:", error); - // Add error message to chat + // Add error message to chat with more specific info const errorMessage: Message = { role: "model", - content: "Sorry, I encountered an error while processing your request." + content: `Sorry, I encountered an error: ${error instanceof Error ? error.message : "Failed to fetch response"}` }; setMessages(prev => [...prev, errorMessage]); @@ -315,39 +320,55 @@ export function AnimatedAIChat({ chatId = "default" }: AnimatedAIChatProps) {
-
+ +
-
- -

- How can I help today? -

- - - - Type a command or ask a question - -
+ + {!isSplitScreen && ( + + +

+ How can I help today? +

+ + + + Type a command or ask a question + +
+ )} +
{/* Display messages */} {messages.length > 0 && ( @@ -566,6 +587,28 @@ export function AnimatedAIChat({ chatId = "default" }: AnimatedAIChatProps) { ))}
+ + {/* Preview panel */} + {isSplitScreen && ( + +
+
+
+ +
+

Preview Panel

+

+ Visual outputs and generated content will appear here +

+
+
+
+ )}
diff --git a/middleware.ts b/middleware.ts index b6e0c7fb..befe7e50 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,11 +7,19 @@ 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(); } + + // No automatic redirection from home page to chat }); export const config = { diff --git a/package.json b/package.json index de510cf6..4bcb0a55 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,10 @@ "lint": "next lint" }, "dependencies": { + "@ai-sdk/azure": "^1.3.23", + "@azure-rest/ai-inference": "^1.0.0-beta.6", + "@azure/core-auth": "^1.9.0", + "@azure/core-sse": "^2.2.0", "@clerk/nextjs": "^6.12.6", "@emotion/is-prop-valid": "latest", "@hookform/resolvers": "^3.9.1", @@ -41,6 +45,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",