fix linter errors, adjusted ai instructions, added llms.txt caching

This commit is contained in:
Madison 2025-07-08 13:15:27 -05:00
parent a2dc15cf10
commit 8089571dae
3 changed files with 113 additions and 66 deletions

View File

@ -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({

View File

@ -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 });
}
}
}

View File

@ -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 */}
<div className="border-t border-fd-border p-3">
<form onSubmit={handleChatSubmit} className="flex gap-2">
<form onSubmit={handleFormSubmit} className="flex gap-2">
<textarea
value={input}
onChange={handleInputChange}
@ -365,12 +417,7 @@ export function AIChatDrawer() {
className="flex-1 resize-none border border-fd-border rounded-md px-2 py-1 text-xs bg-fd-background text-fd-foreground placeholder:text-fd-muted-foreground focus:outline-none focus:ring-1 focus:ring-fd-primary focus:border-fd-primary"
rows={1}
style={{ minHeight: '32px', maxHeight: '96px' }}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleChatSubmit(e as React.FormEvent);
}
}}
onKeyDown={handleKeyDown}
/>
<button
type="submit"