diff --git a/docs/package.json b/docs/package.json index 43e75c03a..36f0b30a3 100644 --- a/docs/package.json +++ b/docs/package.json @@ -18,6 +18,9 @@ "clear-docs": "node scripts/clear-docs.js" }, "dependencies": { + "@ai-sdk/google": "^1.2.21", + "@ai-sdk/openai": "^1.3.22", + "@ai-sdk/react": "^1.2.12", "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-presence": "^1.1.4", @@ -26,6 +29,7 @@ "@radix-ui/react-tabs": "^1.1.12", "@stackframe/stack": "workspace:^", "@xyflow/react": "^12.6.4", + "ai": "^4.3.16", "class-variance-authority": "^0.7.1", "fumadocs-core": "15.3.3", "fumadocs-mdx": "11.6.4", @@ -45,7 +49,8 @@ "remark-gfm": "^4.0.1", "remark-mdx": "^3.1.0", "shiki": "^3.4.2", - "tailwind-merge": "^3.3.0" + "tailwind-merge": "^3.3.0", + "zod": "^3.23.8" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.7", diff --git a/docs/src/app/(home)/page.tsx b/docs/src/app/(home)/page.tsx index e0af3b40c..7e5b832c6 100644 --- a/docs/src/app/(home)/page.tsx +++ b/docs/src/app/(home)/page.tsx @@ -1,3 +1,4 @@ +import AIChat from '@/components/chat/ai-chat'; import DocsSelector from '@/components/homepage/iconHover'; export default function HomePage() { @@ -34,6 +35,11 @@ export default function HomePage() {
+ + {/* AI Chat Assistant */} +
+ +
diff --git a/docs/src/app/api/chat/route.ts b/docs/src/app/api/chat/route.ts new file mode 100644 index 000000000..842c9a66e --- /dev/null +++ b/docs/src/app/api/chat/route.ts @@ -0,0 +1,119 @@ +import { createGoogleGenerativeAI } from '@ai-sdk/google'; +import { streamText } from 'ai'; + +// Allow streaming responses up to 30 seconds +export const maxDuration = 30; + +// Configure Google Gemini with custom API key variable +const google = createGoogleGenerativeAI({ + apiKey: process.env.GOOGLE_AI_API_KEY, +}); + +export function errorHandler(error: unknown) { + if (error == null) { + return 'unknown error'; + } + + if (typeof error === 'string') { + return error; + } + + if (error instanceof Error) { + return error.message; + } + + return JSON.stringify(error); +} + +async function getStackAuthDocs() { + try { + // Get the base URL from the request or use localhost for development + const baseUrl = process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}` + : 'http://localhost:8104'; + + console.log('Fetching docs from:', `${baseUrl}/llms.txt`); + + const response = await fetch(`${baseUrl}/llms.txt`); + console.log('Docs fetch response status:', response.status); + + if (!response.ok) { + console.error('Failed to fetch Stack Auth docs:', response.status, response.statusText); + return null; + } + + const docsContent = await response.text(); + console.log('Docs content length:', docsContent?.length || 0); + console.log('Docs content preview:', docsContent?.substring(0, 200) + '...'); + + return docsContent; + } catch (error) { + console.error('Error fetching Stack Auth docs:', error); + return null; + } +} + +export async function POST(req: Request) { + try { + const { messages } = await req.json(); + + console.log('Received messages:', messages); + console.log('Google AI API Key configured:', !!process.env.GOOGLE_AI_API_KEY); + + // Fetch Stack Auth documentation + const stackAuthDocs = await getStackAuthDocs(); + + // Create system message with documentation context + const systemMessage = { + role: 'system' as const, + content: `You are a technical AI assistant specializing in Stack Auth, a complete authentication solution. You are helping developers who want detailed, technical guidance. + +IMPORTANT INSTRUCTIONS: +- You can ONLY answer questions about Stack Auth and authentication topics +- If someone asks about anything else, politely redirect them to ask about Stack Auth +- Your audience is DEVELOPERS who need in-depth technical information +- Provide comprehensive, detailed answers with code examples when available +- Include specific implementation details, configuration options, and best practices +- Reference exact function names, parameters, and code snippets from the documentation +- Don't oversimplify - developers want the full technical depth +- When explaining concepts, include relevant code examples and implementation details +- If there are multiple approaches, explain the different options and their trade-offs + +${stackAuthDocs ? ` +Here is the complete Stack Auth documentation with detailed examples and technical information: + +${stackAuthDocs} + +Use this documentation to provide comprehensive, technical answers. Include code examples, configuration details, and implementation specifics. Developers are looking for actionable, detailed guidance, not basic overviews. +` : 'Stack Auth documentation could not be loaded. Please answer based on general Stack Auth knowledge, but provide detailed technical information for developers.'} + +Remember: Your responses should match the technical depth and detail level of the Stack Auth documentation. Provide code examples, configuration snippets, and comprehensive implementation guidance.` + }; + + // Prepend system message to the conversation + const messagesWithContext = [systemMessage, ...messages]; + + const result = streamText({ + model: google('gemini-1.5-flash'), + messages: messagesWithContext, + }); + + return result.toDataStreamResponse({ + getErrorMessage: errorHandler, + }); + } catch (error) { + console.error('Error in chat API:', error); + return new Response( + JSON.stringify({ + error: 'Internal server error', + details: error instanceof Error ? error.message : 'Unknown error' + }), + { + status: 500, + headers: { + 'Content-Type': 'application/json', + }, + } + ); + } +} diff --git a/docs/src/components/chat/ai-chat.tsx b/docs/src/components/chat/ai-chat.tsx new file mode 100644 index 000000000..b7d85ff79 --- /dev/null +++ b/docs/src/components/chat/ai-chat.tsx @@ -0,0 +1,120 @@ +'use client'; + +import { useChat } from '@ai-sdk/react'; +import { Bot, Send, User } from 'lucide-react'; +import { useState } from 'react'; + +export default function AIChat() { + const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat(); + const [isOpen, setIsOpen] = useState(false); + + // Debug logging + console.log('Messages:', messages); + + return ( +
+ {/* Toggle Button */} +
+ +
+ + {/* Chat Interface */} + {isOpen && ( +
+
+ +

Stack Auth AI Assistant

+ + Ask questions about Stack Auth documentation + +
+ + {/* Messages Container */} +
+ {messages.length === 0 && ( +
+ +

Welcome to Stack Auth!

+

+ Ask me anything about authentication, documentation, or how to get started. +

+
+ )} + + {messages.map(message => ( +
+ {message.role === 'assistant' && ( +
+ +
+ )} + +
+
+ {message.content} +
+
+ + {message.role === 'user' && ( +
+ +
+ )} +
+ ))} + + {isLoading && ( +
+
+ +
+
+
+
+
+
+
+
+
+ )} +
+ + {/* Input Form */} +
+ + +
+
+ )} +
+ ); +} diff --git a/docs/src/components/homepage/iconHover.tsx b/docs/src/components/homepage/iconHover.tsx index 7f5470905..c9dc4e4b8 100644 --- a/docs/src/components/homepage/iconHover.tsx +++ b/docs/src/components/homepage/iconHover.tsx @@ -227,259 +227,74 @@ const DocsIcon3D: React.FC = ({ } }; - // Add custom CSS for the floating animation - const floatingDotsStyle = ` - @keyframes float-up { - 0% { - transform: translateY(0px) scale(1); - opacity: 0.8; - filter: blur(0px); - } - 50% { - opacity: 0.6; - filter: blur(0.5px); - } - 100% { - transform: translateY(-200px) scale(0.3); - opacity: 0; - filter: blur(1px); - } - } - .animate-float-up { - animation: float-up 3s ease-out infinite; - } -`; - return ( - <> -