mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
docs ai working
This commit is contained in:
parent
0e71470723
commit
592c2c0c17
@ -9,7 +9,7 @@ import { yupMixed, yupObject, yupString } from "@stackframe/stack-shared/dist/sc
|
||||
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
|
||||
import { Json } from "@stackframe/stack-shared/dist/utils/json";
|
||||
import { ModelMessage, generateText, streamText } from "ai";
|
||||
import { ModelMessage, generateText, stepCountIs, streamText } from "ai";
|
||||
|
||||
export const POST = createSmartRouteHandler({
|
||||
metadata: {
|
||||
@ -57,6 +57,9 @@ export const POST = createSmartRouteHandler({
|
||||
const tools = await getTools(body.tools as ToolName[], { auth: fullReq.auth });
|
||||
const toolsArg = Object.keys(tools).length > 0 ? tools : undefined;
|
||||
const messages = body.messages as ModelMessage[];
|
||||
const promptId = body.systemPrompt as SystemPromptId;
|
||||
const isDocsOrSearch = promptId === "docs-ask-ai" || promptId === "command-center-ask-ai";
|
||||
const stepLimit = toolsArg == null ? 1 : isDocsOrSearch ? 50 : 5;
|
||||
|
||||
if (mode === "stream") {
|
||||
const result = streamText({
|
||||
@ -64,6 +67,7 @@ export const POST = createSmartRouteHandler({
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
tools: toolsArg,
|
||||
stopWhen: stepCountIs(stepLimit),
|
||||
});
|
||||
return {
|
||||
statusCode: 200,
|
||||
@ -76,6 +80,7 @@ export const POST = createSmartRouteHandler({
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
tools: toolsArg,
|
||||
stopWhen: stepCountIs(stepLimit),
|
||||
});
|
||||
|
||||
const contentBlocks: Array<
|
||||
|
||||
@ -67,25 +67,27 @@ You are a Stack Auth assistant in a dashboard search bar.
|
||||
`,
|
||||
|
||||
"docs-ask-ai": `
|
||||
## Context: Documentation Assistant
|
||||
# Stack Auth AI Assistant System Prompt
|
||||
|
||||
You are Stack Auth's AI assistant. You help users with Stack Auth - a complete authentication and user management solution.
|
||||
|
||||
**CRITICAL**: Keep responses SHORT and concise. ALWAYS use the available tools to pull relevant documentation for every question. There should almost never be a question where you don't retrieve relevant docs.
|
||||
|
||||
Think step by step about what to say. Being wrong is 100x worse than saying you don't know.
|
||||
|
||||
**TOOL USAGE WORKFLOW:**
|
||||
## TOOL USAGE WORKFLOW:
|
||||
1. **FIRST**, use \`search_docs\` with relevant keywords to find related documentation
|
||||
2. **THEN**, use \`get_docs_by_id\` to retrieve the full content of the most relevant pages
|
||||
3. Base your answer on the actual documentation content retrieved
|
||||
4. When referring to API endpoints, **always cite the actual endpoint** (e.g., "GET /users/me") not the documentation URL
|
||||
|
||||
**CORE RESPONSIBILITIES:**
|
||||
## 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:**
|
||||
## WHAT TO CONSIDER STACK AUTH-RELATED:
|
||||
- Authentication implementation in any framework (Next.js, React, etc.)
|
||||
- User management, registration, login, logout
|
||||
- Session management and security
|
||||
@ -96,17 +98,29 @@ Think step by step about what to say. Being wrong is 100x worse than saying you
|
||||
- Stack Auth configuration and setup
|
||||
- Troubleshooting authentication issues
|
||||
|
||||
**RESPONSE FORMAT:**
|
||||
## SUPPORT CONTACT INFORMATION:
|
||||
When users need personalized support, have complex issues, or ask for help beyond what you can provide from the documentation, direct them to:
|
||||
- **Discord Community**: https://discord.stack-auth.com (best for quick questions and community help)
|
||||
- **Email Support**: team@stack-auth.com (for technical support and detailed inquiries)
|
||||
|
||||
## RESPONSE GUIDELINES:
|
||||
1. Be concise and direct. Only provide detailed explanations when specifically requested
|
||||
2. For every question, use the available tools to retrieve the most relevant documentation sections
|
||||
3. If you're uncertain, say "I don't know" rather than making definitive negative statements
|
||||
4. For complex issues or personalized help, suggest Discord or email support
|
||||
|
||||
## RESPONSE FORMAT:
|
||||
- Use markdown formatting for better readability
|
||||
- **ALWAYS include code examples** - Show users how to actually implement solutions
|
||||
- Include code blocks with proper syntax highlighting (typescript, bash, etc.)
|
||||
- Use bullet points for lists
|
||||
- Bold important concepts
|
||||
- Provide practical, working examples
|
||||
- Focus on giving complete, helpful answers
|
||||
- **When referencing documentation, use links with the base URL: https://docs.stack-auth.com**
|
||||
- Example: For setup docs, use https://docs.stack-auth.com/docs/getting-started/setup
|
||||
|
||||
**CODE EXAMPLE GUIDELINES:**
|
||||
## CODE EXAMPLE GUIDELINES:
|
||||
- For API calls, show both the HTTP endpoint AND the SDK method
|
||||
- For example, when explaining "get current user":
|
||||
* Show the HTTP API endpoint: GET /api/v1/users/me
|
||||
@ -115,7 +129,7 @@ Think step by step about what to say. Being wrong is 100x worse than saying you
|
||||
- Always show complete, runnable code snippets with proper language tags
|
||||
- Include context like "HTTP API", "SDK (React)", "SDK (Next.js)" etc.
|
||||
|
||||
**STACK AUTH HTTP API HEADERS (CRITICAL):**
|
||||
## STACK AUTH HTTP API HEADERS (CRITICAL):
|
||||
Stack Auth does NOT use standard "Authorization: Bearer" headers. When showing HTTP/REST API examples, ALWAYS use these Stack Auth-specific headers:
|
||||
|
||||
**For client-side requests (browser/mobile):**
|
||||
@ -158,19 +172,27 @@ const response = await fetch('https://api.stack-auth.com/api/v1/users/USER_ID',
|
||||
|
||||
NEVER show "Authorization: Bearer" for Stack Auth API calls - this is incorrect and will not work.
|
||||
|
||||
**WHEN UNSURE:**
|
||||
## WHEN UNSURE:
|
||||
- If you're unsure about a Stack Auth feature, say "As an AI, I don't know" or "As an AI, I'm not certain" clearly
|
||||
- Avoid saying things are "not possible" or "impossible", instead say that you don't know
|
||||
- Ask clarifying questions to better understand the user's needs
|
||||
- Product to help with related Stack Auth topics that might be useful
|
||||
- Provide the best information you can based on your knowledge, but acknowledge limitations
|
||||
- If the issue is complex or requires personalized assistance, direct them to Discord or email support
|
||||
|
||||
**MANDATORY BEHAVIOR:**
|
||||
## KEY STACK AUTH CONCEPTS TO REMEMBER:
|
||||
- The core philosophy is complete authentication and user management
|
||||
- All features work together - authentication, user management, teams, permissions
|
||||
- Built for modern frameworks like Next.js, React, and more
|
||||
- Supports multiple authentication methods: OAuth, email/password, magic links
|
||||
- Team and permission management for multi-tenant applications
|
||||
|
||||
## MANDATORY BEHAVIOR:
|
||||
This is not optional - retrieve relevant documentation for every question.
|
||||
- Be direct and to the point. Only elaborate when users specifically ask for more detail.
|
||||
|
||||
Remember: You're here to help users succeed with Stack Auth. Be helpful but concise, ask questions when needed, always pull relevant docs, and don't hesitate to direct users to support channels when they need additional help.
|
||||
`,
|
||||
`,
|
||||
|
||||
"email-wysiwyg-editor": `
|
||||
You are an expert email designer and senior frontend engineer specializing in react-email and Tailwind CSS.
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
"clear-docs": "node scripts/clear-docs.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/react": "^1.2.12",
|
||||
"@ai-sdk/react": "^3.0.0",
|
||||
"@openrouter/ai-sdk-provider": "0.7.5",
|
||||
"@modelcontextprotocol/sdk": "^1.17.2",
|
||||
"@phosphor-icons/react": "^2.1.10",
|
||||
@ -31,7 +31,7 @@
|
||||
"@stackframe/stack": "workspace:^",
|
||||
"@stackframe/stack-shared": "workspace:^",
|
||||
"@vercel/mcp-adapter": "^1.0.0",
|
||||
"ai": "^4.3.17",
|
||||
"ai": "^6.0.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"fumadocs-core": "15.3.3",
|
||||
"fumadocs-mdx": "11.6.4",
|
||||
|
||||
@ -1,208 +1,49 @@
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
||||
import { experimental_createMCPClient as createMCPClient, streamText } from 'ai';
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
// Create OpenRouter AI instance
|
||||
const openrouter = createOpenRouter({
|
||||
apiKey: process.env.STACK_OPENROUTER_API_KEY,
|
||||
});
|
||||
|
||||
// Helper function to get error message
|
||||
function getErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
import { stackServerApp } from "@/stack";
|
||||
import { convertToModelMessages, UIMessage } from "ai";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { messages } = await request.json();
|
||||
const payload = await request.json() as { messages?: UIMessage[] };
|
||||
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
||||
|
||||
// Create MCP client for Stack Auth documentation with error handling
|
||||
let tools = {};
|
||||
try {
|
||||
// Use local MCP server in development, production server in production
|
||||
const mcpUrl = process.env.NODE_ENV === 'development'
|
||||
? new URL('/api/internal/mcp', 'http://localhost:8104')
|
||||
: new URL('/api/internal/mcp', 'https://mcp.stack-auth.com');
|
||||
const backendBaseUrl =
|
||||
process.env.NEXT_PUBLIC_STACK_API_URL ??
|
||||
"https://api.stack-auth.com";
|
||||
|
||||
const stackAuthMcp = await createMCPClient({
|
||||
transport: new StreamableHTTPClientTransport(mcpUrl),
|
||||
});
|
||||
tools = await stackAuthMcp.tools();
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize MCP client or retrieve tools:', error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Documentation service temporarily unavailable',
|
||||
details: 'Our documentation service is currently unreachable. Please try again in a moment, or visit https://docs.stack-auth.com directly for help.',
|
||||
}),
|
||||
{
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
const modelMessages = await convertToModelMessages(messages);
|
||||
const requestHeaders: Record<string, string> = {
|
||||
"content-type": "application/json",
|
||||
};
|
||||
|
||||
const user = await stackServerApp.getUser();
|
||||
if (user != null) {
|
||||
const accessToken = await user.getAccessToken();
|
||||
if (accessToken != null) {
|
||||
requestHeaders["x-stack-access-type"] = "client";
|
||||
requestHeaders["x-stack-access-token"] = accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a comprehensive system prompt that restricts AI to Stack Auth topics
|
||||
const systemPrompt = `
|
||||
# Stack Auth AI Assistant System Prompt
|
||||
|
||||
You are Stack Auth's AI assistant. You help users with Stack Auth - a complete authentication and user management solution.
|
||||
|
||||
**CRITICAL**: Keep responses SHORT and concise. ALWAYS use the available tools to pull relevant documentation for every question. There should almost never be a question where you don't retrieve relevant docs.
|
||||
|
||||
Think step by step about what to say. Being wrong is 100x worse than saying you don't know.
|
||||
|
||||
## TOOL USAGE WORKFLOW:
|
||||
1. **FIRST**, use \`search_docs\` with relevant keywords to find related documentation
|
||||
2. **THEN**, use \`get_docs_by_id\` to retrieve the full content of the most relevant pages
|
||||
3. Base your answer on the actual documentation content retrieved
|
||||
4. When referring to API endpoints, **always cite the actual endpoint** (e.g., "GET /users/me") not the documentation URL
|
||||
|
||||
## 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
|
||||
|
||||
## SUPPORT CONTACT INFORMATION:
|
||||
When users need personalized support, have complex issues, or ask for help beyond what you can provide from the documentation, direct them to:
|
||||
- **Discord Community**: https://discord.stack-auth.com (best for quick questions and community help)
|
||||
- **Email Support**: team@stack-auth.com (for technical support and detailed inquiries)
|
||||
|
||||
## RESPONSE GUIDELINES:
|
||||
1. Be concise and direct. Only provide detailed explanations when specifically requested
|
||||
2. For every question, use the available tools to retrieve the most relevant documentation sections
|
||||
3. If you're uncertain, say "I don't know" rather than making definitive negative statements
|
||||
4. For complex issues or personalized help, suggest Discord or email support
|
||||
|
||||
## RESPONSE FORMAT:
|
||||
- Use markdown formatting for better readability
|
||||
- **ALWAYS include code examples** - Show users how to actually implement solutions
|
||||
- Include code blocks with proper syntax highlighting (typescript, bash, etc.)
|
||||
- Use bullet points for lists
|
||||
- Bold important concepts
|
||||
- Provide practical, working examples
|
||||
- Focus on giving complete, helpful answers
|
||||
- **When referencing documentation, use links with the base URL: https://docs.stack-auth.com**
|
||||
- Example: For setup docs, use https://docs.stack-auth.com/docs/getting-started/setup
|
||||
|
||||
## CODE EXAMPLE GUIDELINES:
|
||||
- For API calls, show both the HTTP endpoint AND the SDK method
|
||||
- For example, when explaining "get current user":
|
||||
* Show the HTTP API endpoint: GET /api/v1/users/me
|
||||
* Show the SDK usage: const user = useUser();
|
||||
* Include necessary imports and authentication headers
|
||||
- Always show complete, runnable code snippets with proper language tags
|
||||
- Include context like "HTTP API", "SDK (React)", "SDK (Next.js)" etc.
|
||||
|
||||
## STACK AUTH HTTP API HEADERS (CRITICAL):
|
||||
Stack Auth does NOT use standard "Authorization: Bearer" headers. When showing HTTP/REST API examples, ALWAYS use these Stack Auth-specific headers:
|
||||
|
||||
**For client-side requests (browser/mobile):**
|
||||
\`\`\`
|
||||
X-Stack-Access-Type: client
|
||||
X-Stack-Project-Id: <your-project-id>
|
||||
X-Stack-Publishable-Client-Key: <your-publishable-client-key>
|
||||
X-Stack-Access-Token: <user-access-token> // for authenticated requests
|
||||
\`\`\`
|
||||
|
||||
**For server-side requests (backend):**
|
||||
\`\`\`
|
||||
X-Stack-Access-Type: server
|
||||
X-Stack-Project-Id: <your-project-id>
|
||||
X-Stack-Secret-Server-Key: <your-secret-server-key>
|
||||
\`\`\`
|
||||
|
||||
**Example HTTP request (client-side, authenticated):**
|
||||
\`\`\`typescript
|
||||
const response = await fetch('https://api.stack-auth.com/api/v1/users/me', {
|
||||
headers: {
|
||||
'X-Stack-Access-Type': 'client',
|
||||
'X-Stack-Project-Id': 'YOUR_PROJECT_ID',
|
||||
'X-Stack-Publishable-Client-Key': 'YOUR_PUBLISHABLE_CLIENT_KEY',
|
||||
'X-Stack-Access-Token': 'USER_ACCESS_TOKEN',
|
||||
},
|
||||
});
|
||||
\`\`\`
|
||||
|
||||
**Example HTTP request (server-side):**
|
||||
\`\`\`typescript
|
||||
const response = await fetch('https://api.stack-auth.com/api/v1/users/USER_ID', {
|
||||
headers: {
|
||||
'X-Stack-Access-Type': 'server',
|
||||
'X-Stack-Project-Id': 'YOUR_PROJECT_ID',
|
||||
'X-Stack-Secret-Server-Key': 'YOUR_SECRET_SERVER_KEY',
|
||||
},
|
||||
});
|
||||
\`\`\`
|
||||
|
||||
NEVER show "Authorization: Bearer" for Stack Auth API calls - this is incorrect and will not work.
|
||||
|
||||
## WHEN UNSURE:
|
||||
- If you're unsure about a Stack Auth feature, say "As an AI, I don't know" or "As an AI, I'm not certain" clearly
|
||||
- Avoid saying things are "not possible" or "impossible", instead say that you don't know
|
||||
- Ask clarifying questions to better understand the user's needs
|
||||
- Product to help with related Stack Auth topics that might be useful
|
||||
- Provide the best information you can based on your knowledge, but acknowledge limitations
|
||||
- If the issue is complex or requires personalized assistance, direct them to Discord or email support
|
||||
|
||||
## KEY STACK AUTH CONCEPTS TO REMEMBER:
|
||||
- The core philosophy is complete authentication and user management
|
||||
- All features work together - authentication, user management, teams, permissions
|
||||
- Built for modern frameworks like Next.js, React, and more
|
||||
- Supports multiple authentication methods: OAuth, email/password, magic links
|
||||
- Team and permission management for multi-tenant applications
|
||||
|
||||
## MANDATORY BEHAVIOR:
|
||||
This is not optional - retrieve relevant documentation for every question.
|
||||
- Be direct and to the point. Only elaborate when users specifically ask for more detail.
|
||||
|
||||
Remember: You're here to help users succeed with Stack Auth. Be helpful but concise, ask questions when needed, always pull relevant docs, and don't hesitate to direct users to support channels when they need additional help.
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = streamText({
|
||||
model: openrouter('anthropic/claude-4.5-sonnet'),
|
||||
tools: {
|
||||
...tools,
|
||||
},
|
||||
maxSteps: 50,
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
temperature: 0.3,
|
||||
maxTokens: 4096, // Ensure we have enough tokens for complete responses
|
||||
});
|
||||
|
||||
return result.toDataStreamResponse({
|
||||
getErrorMessage,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Chat API Error:', error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Failed to process chat request',
|
||||
details: getErrorMessage(error),
|
||||
const backendResponse = await fetch(
|
||||
`${backendBaseUrl}/api/latest/ai/query/stream`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: requestHeaders,
|
||||
body: JSON.stringify({
|
||||
quality: "smart",
|
||||
speed: "fast",
|
||||
systemPrompt: "docs-ask-ai",
|
||||
tools: ["docs"],
|
||||
messages: modelMessages,
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return new Response(backendResponse.body, {
|
||||
status: backendResponse.status,
|
||||
headers: {
|
||||
"content-type":
|
||||
backendResponse.headers.get("content-type") ?? "text/event-stream",
|
||||
"cache-control": "no-cache",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,12 +1,35 @@
|
||||
'use client';
|
||||
|
||||
import { useChat } from '@ai-sdk/react';
|
||||
import { useChat, type UIMessage } from '@ai-sdk/react';
|
||||
import { DefaultChatTransport } from 'ai';
|
||||
import { runAsynchronously } from '@stackframe/stack-shared/dist/utils/promises';
|
||||
import { ChevronDown, ChevronUp, ExternalLink, FileText, Maximize2, Minimize2, Send, X } from 'lucide-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useSidebar } from '../layouts/sidebar-context';
|
||||
import { MessageFormatter } from './message-formatter';
|
||||
|
||||
type ToolInvocationPart = {
|
||||
type: `tool-${string}`,
|
||||
toolCallId: string,
|
||||
state: "input-streaming" | "input-available" | "output-available" | "output-error" | "approval-requested" | "approval-responded" | "output-denied",
|
||||
input: unknown,
|
||||
output?: unknown,
|
||||
errorText?: string,
|
||||
};
|
||||
|
||||
function getMessageContent(message: UIMessage): string {
|
||||
return message.parts
|
||||
.filter((part): part is { type: "text", text: string } => part.type === "text")
|
||||
.map(part => part.text)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function getToolInvocations(message: UIMessage): ToolInvocationPart[] {
|
||||
return message.parts
|
||||
.filter((part) => part.type.startsWith("tool-") || part.type === "dynamic-tool")
|
||||
.map((part) => part as unknown as ToolInvocationPart);
|
||||
}
|
||||
|
||||
// Stack Auth Icon Component (just the icon, not full logo)
|
||||
function StackIcon({ size = 20, className }: { size?: number, className?: string }) {
|
||||
return (
|
||||
@ -335,25 +358,36 @@ export function AIChatDrawer() {
|
||||
const topPosition = 'top-3';
|
||||
const height = isHomePage && isScrolled ? 'h-[calc(100vh-1.5rem)]' : 'h-[calc(100vh-1.5rem)]';
|
||||
|
||||
const [input, setInput] = useState('');
|
||||
|
||||
const {
|
||||
messages,
|
||||
input,
|
||||
handleInputChange,
|
||||
handleSubmit,
|
||||
isLoading,
|
||||
sendMessage,
|
||||
status,
|
||||
error,
|
||||
} = useChat({
|
||||
api: '/api/chat',
|
||||
initialMessages: [],
|
||||
transport: new DefaultChatTransport({ api: '/api/chat' }),
|
||||
onError: (error: Error) => {
|
||||
console.error('Chat error:', error);
|
||||
},
|
||||
onFinish: (message) => {
|
||||
// Send AI response to Discord
|
||||
runAsynchronously(() => sendAIResponseToDiscord(message.content));
|
||||
onFinish: ({ message }: { message: UIMessage }) => {
|
||||
runAsynchronously(() => sendAIResponseToDiscord(getMessageContent(message)));
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = status === 'submitted' || status === 'streaming';
|
||||
|
||||
// Debug: log messages, status, and errors
|
||||
useEffect(() => {
|
||||
console.log('[docs-chat] status:', status);
|
||||
console.log('[docs-chat] error:', error);
|
||||
console.log('[docs-chat] messages:', JSON.stringify(messages.map(m => ({
|
||||
id: m.id,
|
||||
role: m.role,
|
||||
parts: m.parts.map(p => ({ type: p.type, ...(p.type === 'text' ? { text: (p as { text: string }).text.slice(0, 100) } : {}) })),
|
||||
})), null, 2));
|
||||
}, [messages, status, error]);
|
||||
|
||||
// Auto-scroll to bottom when new messages are added
|
||||
useEffect(() => {
|
||||
const container = messagesContainerRef.current;
|
||||
@ -379,6 +413,14 @@ export function AIChatDrawer() {
|
||||
}
|
||||
}, [input]);
|
||||
|
||||
const submitMessage = () => {
|
||||
const text = input.trim();
|
||||
if (!text || isLoading) return;
|
||||
setInput('');
|
||||
if (editableRef.current) editableRef.current.textContent = '';
|
||||
runAsynchronously(() => sendMessage({ text }));
|
||||
};
|
||||
|
||||
// Function to send AI response to Discord webhook
|
||||
const sendAIResponseToDiscord = async (response: string) => {
|
||||
try {
|
||||
@ -441,8 +483,8 @@ export function AIChatDrawer() {
|
||||
};
|
||||
|
||||
// Enhanced submit handler that also sends to Discord
|
||||
const handleChatSubmit = async (e: React.FormEvent) => {
|
||||
if (!input.trim()) return;
|
||||
const handleChatSubmit = () => {
|
||||
if (!input.trim() || isLoading) return;
|
||||
|
||||
// Update session data
|
||||
setSessionData(prev => ({
|
||||
@ -453,8 +495,7 @@ export function AIChatDrawer() {
|
||||
// Send message to Discord webhook
|
||||
runAsynchronously(() => sendToDiscord(input.trim()));
|
||||
|
||||
// Continue with normal chat submission
|
||||
handleSubmit(e);
|
||||
submitMessage();
|
||||
};
|
||||
|
||||
// Starter prompts for users
|
||||
@ -477,13 +518,8 @@ export function AIChatDrawer() {
|
||||
];
|
||||
|
||||
const handleStarterPromptClick = (prompt: string) => {
|
||||
// Use the handleInputChange from useChat to update the input
|
||||
handleInputChange({ target: { value: prompt } } as React.ChangeEvent<HTMLInputElement>);
|
||||
};
|
||||
|
||||
// Helper function for safe async event handling
|
||||
const handleSubmitSafely = () => {
|
||||
runAsynchronously(() => handleChatSubmit({} as React.FormEvent));
|
||||
setInput(prompt);
|
||||
if (editableRef.current) editableRef.current.textContent = prompt;
|
||||
};
|
||||
|
||||
return (
|
||||
@ -558,35 +594,51 @@ export function AIChatDrawer() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`flex ${
|
||||
message.role === 'user' ? 'justify-end' : 'justify-start'
|
||||
}`}
|
||||
>
|
||||
messages.map((message) => {
|
||||
const messageContent = getMessageContent(message);
|
||||
const toolInvocations = message.role === "assistant" ? getToolInvocations(message) : [];
|
||||
|
||||
if (message.role === "assistant" && !messageContent && toolInvocations.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`max-w-[85%] p-2 rounded-lg text-xs ${
|
||||
message.role === 'user'
|
||||
? 'bg-fd-primary/10 border border-fd-primary/20 text-fd-foreground'
|
||||
: 'bg-fd-muted text-fd-foreground border border-fd-border'
|
||||
key={message.id}
|
||||
className={`flex ${
|
||||
message.role === 'user' ? 'justify-end' : 'justify-start'
|
||||
}`}
|
||||
>
|
||||
{message.role === 'user' ? (
|
||||
<div className="whitespace-pre-wrap break-words">
|
||||
{message.content}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{message.toolInvocations?.map((toolCall, index) => (
|
||||
<ToolCallDisplay key={index} toolCall={toolCall} />
|
||||
))}
|
||||
<MessageFormatter content={message.content} />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className={`max-w-[85%] p-2 rounded-lg text-xs ${
|
||||
message.role === 'user'
|
||||
? 'bg-fd-primary/10 border border-fd-primary/20 text-fd-foreground'
|
||||
: 'bg-fd-muted text-fd-foreground border border-fd-border'
|
||||
}`}
|
||||
>
|
||||
{message.role === 'user' ? (
|
||||
<div className="whitespace-pre-wrap break-words">
|
||||
{messageContent}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{toolInvocations.map((part, index) => (
|
||||
<ToolCallDisplay
|
||||
key={index}
|
||||
toolCall={{
|
||||
toolName: part.type === "dynamic-tool" ? (part as unknown as { toolName: string }).toolName : part.type.replace(/^tool-/, ""),
|
||||
args: part.input as { id?: string, search_query?: string },
|
||||
result: part.output as { content?: { text: string }[], text?: string } | undefined,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{messageContent && <MessageFormatter content={messageContent} />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
{isLoading && (
|
||||
@ -631,10 +683,7 @@ export function AIChatDrawer() {
|
||||
style={{ lineHeight: "1.4", minHeight: "20px" }}
|
||||
onInput={(e) => {
|
||||
const value = e.currentTarget.textContent || "";
|
||||
handleInputChange({
|
||||
target: { value },
|
||||
} as React.ChangeEvent<HTMLInputElement>);
|
||||
|
||||
setInput(value);
|
||||
// Clean up the div if it's empty to show placeholder
|
||||
if (!value.trim()) {
|
||||
e.currentTarget.innerHTML = "";
|
||||
@ -643,7 +692,7 @@ export function AIChatDrawer() {
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSubmitSafely();
|
||||
handleChatSubmit();
|
||||
}
|
||||
}}
|
||||
onPaste={(e) => {
|
||||
@ -651,17 +700,14 @@ export function AIChatDrawer() {
|
||||
const text = e.clipboardData.getData("text/plain");
|
||||
e.currentTarget.textContent =
|
||||
(e.currentTarget.textContent || "") + text;
|
||||
const value = e.currentTarget.textContent;
|
||||
handleInputChange({
|
||||
target: { value },
|
||||
} as React.ChangeEvent<HTMLInputElement>);
|
||||
setInput(e.currentTarget.textContent || "");
|
||||
}}
|
||||
data-placeholder="Ask about Stack Auth..."
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
disabled={!input.trim() || isLoading}
|
||||
onClick={handleSubmitSafely}
|
||||
onClick={handleChatSubmit}
|
||||
className="h-8 w-8 rounded-full p-0 shrink-0 bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user