diff --git a/docs/src/app/api/chat/route.ts b/docs/src/app/api/chat/route.ts index 230b6998e..4787b1a6c 100644 --- a/docs/src/app/api/chat/route.ts +++ b/docs/src/app/api/chat/route.ts @@ -1,6 +1,5 @@ import { createGoogleGenerativeAI } from '@ai-sdk/google'; -import { streamText, tool } from 'ai'; -import { z } from 'zod'; +import { streamText } from 'ai'; // Allow streaming responses up to 30 seconds export const maxDuration = 30; @@ -22,19 +21,36 @@ export async function POST(request: Request) { const { messages, docsContent } = await request.json(); // Create a comprehensive system prompt that restricts AI to Stack Auth topics - const systemPrompt = `You are Stack Auth's AI assistant. You ONLY answer questions about Stack Auth - a complete authentication and user management solution.. + const systemPrompt = `You are Stack Auth's AI assistant. You help users with Stack Auth - a complete authentication and user management solution. DOCUMENTATION CONTEXT: ${docsContent || 'Documentation not available'} -STRICT GUIDELINES: -1. ONLY answer questions related to Stack Auth, its features, implementation, or usage -2. If asked about non-Stack Auth topics, politely redirect: "I can only help with Stack Auth questions. Please ask about Stack Auth's features, setup, or implementation." -3. Provide detailed, technical answers with code examples when relevant -4. Reference specific Stack Auth features, components, or APIs -5. When explaining concepts, always relate them to Stack Auth specifically -6. Include relevant code snippets from the documentation when helpful -7. If you're unsure about something Stack Auth-related, say so rather than guessing +CORE RESPONSIBILITIES: +1. Help users implement Stack Auth in their applications +2. Answer questions about authentication, user management, and authorization using Stack Auth +3. Provide guidance on Stack Auth features, configuration, and best practices +4. Help with framework integrations (Next.js, React, etc.) using Stack Auth + +WHAT TO CONSIDER STACK AUTH-RELATED: +- Authentication implementation in any framework (Next.js, React, etc.) +- User management, registration, login, logout +- Session management and security +- OAuth providers and social auth +- Database configuration and user data +- API routes and middleware +- Authorization and permissions +- Stack Auth configuration and setup +- Troubleshooting authentication issues + +RESPONSE GUIDELINES: +1. **Be helpful and proactive**: If a question seems related to authentication or user management, assume it's about Stack Auth +2. **Ask follow-up questions**: If you need more context to provide a complete answer, ask specific questions like: + - "Are you using Next.js App Router or Pages Router?" + - "What authentication method are you trying to implement?" + - "What specific issue are you encountering?" +3. **Provide detailed answers**: Include code examples, configuration steps, and practical guidance +4. **Only redirect if clearly off-topic**: Only redirect users if they ask about completely unrelated topics (like cooking, sports, etc.) RESPONSE FORMAT: - Use markdown formatting for better readability @@ -42,41 +58,26 @@ RESPONSE FORMAT: - Use bullet points for lists - Bold important concepts - Provide practical examples when possible +- Focus on giving complete, helpful answers +- **DO NOT reference documentation sections or provide links** +- **DO NOT mention checking documentation, guides, or other resources** +- **Provide all necessary information directly in your response** -Remember: You are Stack Auth's dedicated assistant. Stay focused on Stack Auth topics only.`; +WHEN UNSURE: +- If you're unsure about a Stack Auth feature, say so clearly +- Ask clarifying questions to better understand the user's needs +- Offer to help with related Stack Auth topics that might be useful +- Provide the best information you can based on your knowledge + +Remember: You're here to help users succeed with Stack Auth. Be helpful, ask questions when needed, and provide comprehensive guidance for authentication and user management. Give complete answers without referencing external resources.`; try { - const result = streamText({ + const result = streamText({ model: google('gemini-2.0-flash'), system: systemPrompt, - messages, - maxTokens: 1000, - temperature: 0.3, - tools: { - searchDocs: tool({ - description: 'Search through Stack Auth documentation for specific information', - parameters: z.object({ - query: z.string().describe('The search query to find relevant documentation'), - }), - execute: async ({ query }) => { - // Simple search through the docs content - if (!docsContent) { - return 'Documentation not available'; - } - - const lines = docsContent.split('\n'); - const relevantLines = lines.filter((line: string) => - line.toLowerCase().includes(query.toLowerCase()) - ); - - if (relevantLines.length === 0) { - return `No specific information found for "${query}" in the documentation.`; - } - - return relevantLines.slice(0, 10).join('\n'); - }, - }), - }, + messages, + maxTokens: 1500, + temperature: 0.1, }); return result.toDataStreamResponse({ diff --git a/docs/src/app/api/discord-webhook/route.ts b/docs/src/app/api/discord-webhook/route.ts index c66e15ea2..7414f205a 100644 --- a/docs/src/app/api/discord-webhook/route.ts +++ b/docs/src/app/api/discord-webhook/route.ts @@ -17,5 +17,4 @@ export async function POST(request: NextRequest) { console.error('Error in Discord webhook API:', error); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } -} - \ No newline at end of file +} diff --git a/docs/src/components/chat/ai-chat.tsx b/docs/src/components/chat/ai-chat.tsx index c473509ca..2102b7444 100644 --- a/docs/src/components/chat/ai-chat.tsx +++ b/docs/src/components/chat/ai-chat.tsx @@ -35,14 +35,18 @@ export function AIChatDrawer() { const [isHomePage, setIsHomePage] = useState(false); const [isScrolled, setIsScrolled] = useState(false); const [pageLoadTime] = useState(Date.now()); - const [sessionId] = useState(() => { + const [sessionId, setSessionId] = useState(() => { // Generate or retrieve session ID - const existing = localStorage.getItem('ai-chat-session-id'); - if (existing) { - return existing; + if (typeof window !== 'undefined') { + const existing = localStorage.getItem('ai-chat-session-id'); + if (existing) { + return existing; + } } const newId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`; - localStorage.setItem('ai-chat-session-id', newId); + if (typeof window !== 'undefined') { + localStorage.setItem('ai-chat-session-id', newId); + } return newId; }); const [sessionData, setSessionData] = useState({ @@ -54,7 +58,7 @@ export function AIChatDrawer() { useEffect(() => { const updateSessionData = () => { const timeOnPage = Math.floor((Date.now() - pageLoadTime) / 1000); - + setSessionData(prev => ({ ...prev, timeOnPage, @@ -71,15 +75,18 @@ export function AIChatDrawer() { // Reset session ID if user has been inactive for too long useEffect(() => { const checkSessionExpiry = () => { + if (typeof window === 'undefined') return; + const lastActivity = localStorage.getItem('ai-chat-last-activity'); if (lastActivity) { const timeSinceActivity = Date.now() - parseInt(lastActivity); const ONE_HOUR = 60 * 60 * 1000; - + if (timeSinceActivity > ONE_HOUR) { // Generate new session ID const newId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`; localStorage.setItem('ai-chat-session-id', newId); + setSessionId(newId); // Update the component state setSessionData(prev => ({ ...prev, messageCount: 0 })); } } @@ -118,20 +125,42 @@ export function AIChatDrawer() { }; }, []); - // Calculate position based on homepage and scroll state - const topPosition = isHomePage && isScrolled ? 'top-0' : 'top-14'; - const height = isHomePage && isScrolled ? 'h-screen' : 'h-[calc(100vh-3.5rem)]'; - - // Fetch documentation content when component mounts + // Fetch documentation content when component mounts with caching useEffect(() => { let isCancelled = false; const fetchDocs = async () => { try { + // Check cache first (10 minute TTL) + if (typeof window !== 'undefined') { + const cached = sessionStorage.getItem('ai-chat-docs-cache'); + if (cached) { + const { content, timestamp } = JSON.parse(cached); + const CACHE_TTL = 10 * 60 * 1000; // 10 minutes in milliseconds + + if (Date.now() - timestamp < CACHE_TTL) { + // Cache is still valid, use cached content + if (!isCancelled) { + setDocsContent(content); + } + return; + } + } + } + + // Cache miss or expired, fetch fresh content const response = await fetch('/llms.txt'); if (response.ok && !isCancelled) { const content = await response.text(); setDocsContent(content); + + // Cache the fresh content + if (typeof window !== 'undefined') { + sessionStorage.setItem('ai-chat-docs-cache', JSON.stringify({ + content, + timestamp: Date.now() + })); + } } } catch (error) { console.error('Failed to fetch documentation:', error); @@ -147,6 +176,10 @@ export function AIChatDrawer() { }; }, []); + // Calculate position based on homepage and scroll state + const topPosition = isHomePage && isScrolled ? 'top-0' : 'top-14'; + const height = isHomePage && isScrolled ? 'h-screen' : 'h-[calc(100vh-3.5rem)]'; + const { messages, input, @@ -170,8 +203,10 @@ export function AIChatDrawer() { try { // Update message count and last activity const newMessageCount = sessionData.messageCount + 1; - localStorage.setItem('ai-chat-last-activity', Date.now().toString()); - + if (typeof window !== 'undefined') { + localStorage.setItem('ai-chat-last-activity', Date.now().toString()); + } + // Gather only essential context const context = { message: message, @@ -179,9 +214,9 @@ export function AIChatDrawer() { metadata: { sessionId: sessionId, messageNumber: newMessageCount, - pathname: window.location.pathname, + pathname: typeof window !== 'undefined' ? window.location.pathname : '', timestamp: new Date().toISOString(), - userAgent: navigator.userAgent, + userAgent: typeof window !== 'undefined' ? navigator.userAgent : '', messageType: starterPrompts.some(p => p.prompt === message) ? 'starter-prompt' : 'custom', timeOnPage: sessionData.timeOnPage, isFollowUp: newMessageCount > 1, @@ -219,6 +254,23 @@ export function AIChatDrawer() { handleSubmit(e); }; + // Non-async wrapper for form onSubmit to avoid promise issues + const handleFormSubmit = (e: React.FormEvent) => { + handleChatSubmit(e).catch(error => { + console.error('Chat submit error:', error); + }); + }; + + // Non-async handler for onKeyDown to avoid promise issues + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleChatSubmit(e as React.FormEvent).catch(error => { + console.error('Chat submit error:', error); + }); + } + }; + // Starter prompts for users const starterPrompts = [ { @@ -357,7 +409,7 @@ export function AIChatDrawer() { {/* Input */}
-
+