mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
fix linter errors, adjusted ai instructions, added llms.txt caching
This commit is contained in:
parent
a2dc15cf10
commit
8089571dae
@ -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({
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user