Discord webhook integration for ai chat

This commit is contained in:
Madison 2025-07-06 00:22:12 -07:00
parent fc6783cfcb
commit cb01e0da4a
3 changed files with 260 additions and 2 deletions

148
docs/lib/discord-webhook.ts Normal file
View File

@ -0,0 +1,148 @@
/**
* Sends a message to Discord webhook with rich metadata
*/
export async function sendToDiscordWebhook(data: {
message: string;
username?: string;
metadata?: {
url?: string;
pathname?: string;
timestamp?: string;
userAgent?: string;
viewport?: string;
isHomePage?: boolean;
isScrolled?: boolean;
messageLength?: number;
messageType?: string;
sessionMessageCount?: number;
timeOnPage?: number;
scrollDepth?: number;
referrer?: string;
language?: string;
timezone?: string;
chatExpanded?: boolean;
};
}) {
const webhookUrl = process.env.DISCORD_WEBHOOK_URL;
if (!webhookUrl) {
console.warn('Discord webhook URL not configured');
return;
}
try {
const { message, username, metadata } = data;
// Create a rich embed with metadata
const embed = {
title: "💬 AI Chat Message",
description: message.length > 100 ? message.substring(0, 100) + "..." : message,
color: metadata?.messageType === 'starter-prompt' ? 0x10b981 : 0x3b82f6, // Green for starter prompts, blue for custom
fields: [
// Message Info
...(metadata?.messageType ? [{ name: "📝 Type", value: metadata.messageType === 'starter-prompt' ? "Starter Prompt" : "Custom Message", inline: true }] : []),
...(metadata?.messageLength ? [{ name: "📏 Length", value: `${metadata.messageLength} chars`, inline: true }] : []),
...(metadata?.chatExpanded !== undefined ? [{ name: "🔍 Chat Mode", value: metadata.chatExpanded ? "Expanded" : "Normal", inline: true }] : []),
// Page Context
...(metadata?.pathname ? [{ name: "📄 Page", value: metadata.pathname, inline: true }] : []),
...(metadata?.isHomePage !== undefined ? [{ name: "🏠 Homepage", value: metadata.isHomePage ? "Yes" : "No", inline: true }] : []),
...(metadata?.isScrolled !== undefined ? [{ name: "📜 Scrolled", value: metadata.isScrolled ? "Yes" : "No", inline: true }] : []),
// Session Data
...(metadata?.sessionMessageCount ? [{ name: "💬 Session Messages", value: metadata.sessionMessageCount.toString(), inline: true }] : []),
...(metadata?.timeOnPage ? [{ name: "⏱️ Time on Page", value: formatTime(metadata.timeOnPage), inline: true }] : []),
...(metadata?.scrollDepth !== undefined ? [{ name: "📊 Scroll Depth", value: `${metadata.scrollDepth}%`, inline: true }] : []),
// Technical Info
...(metadata?.viewport ? [{ name: "📱 Viewport", value: metadata.viewport, inline: true }] : []),
...(metadata?.language ? [{ name: "🌐 Language", value: metadata.language, inline: true }] : []),
...(metadata?.timezone ? [{ name: "⏰ Timezone", value: metadata.timezone, inline: true }] : []),
],
timestamp: metadata?.timestamp || new Date().toISOString(),
footer: {
text: "Stack Auth Docs AI Chat",
icon_url: "https://cdn.discordapp.com/embed/avatars/0.png"
}
};
// Add full message as a separate field if it was truncated
if (message.length > 100) {
embed.fields.unshift({ name: "📝 Full Message", value: message, inline: false });
}
// Extract browser info from user agent
const browserInfo = extractBrowserInfo(metadata?.userAgent || '');
if (browserInfo) {
embed.fields.push({ name: "🌐 Browser", value: browserInfo, inline: true });
}
// Add referrer info
if (metadata?.referrer && metadata.referrer !== 'Direct') {
embed.fields.push({ name: "🔗 Referrer", value: metadata.referrer, inline: true });
}
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: username || 'Stack Auth Docs User',
avatar_url: 'https://cdn.discordapp.com/embed/avatars/0.png',
embeds: [embed],
}),
});
if (!response.ok) {
console.error('Failed to send message to Discord:', response.statusText);
}
} catch (error) {
console.error('Error sending message to Discord:', error);
}
}
/**
* Format time in seconds to a human-readable format
*/
function formatTime(seconds: number): string {
if (seconds < 60) return `${seconds}s`;
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}m ${remainingSeconds}s`;
}
/**
* Extract browser and OS info from user agent
*/
function extractBrowserInfo(userAgent: string): string | null {
if (!userAgent) return null;
// Extract browser
let browser = 'Unknown';
if (userAgent.includes('Chrome/')) {
browser = 'Chrome';
} else if (userAgent.includes('Firefox/')) {
browser = 'Firefox';
} else if (userAgent.includes('Safari/') && !userAgent.includes('Chrome/')) {
browser = 'Safari';
} else if (userAgent.includes('Edge/')) {
browser = 'Edge';
}
// Extract OS
let os = 'Unknown';
if (userAgent.includes('Windows NT')) {
os = 'Windows';
} else if (userAgent.includes('Mac OS X')) {
os = 'macOS';
} else if (userAgent.includes('Linux')) {
os = 'Linux';
} else if (userAgent.includes('iPhone') || userAgent.includes('iPad')) {
os = 'iOS';
} else if (userAgent.includes('Android')) {
os = 'Android';
}
return `${browser} on ${os}`;
}

View File

@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from 'next/server';
import { sendToDiscordWebhook } from '../../../../lib/discord-webhook';
export async function POST(request: NextRequest) {
try {
const data = await request.json();
if (!data.message || typeof data.message !== 'string') {
return NextResponse.json({ error: 'Message is required' }, { status: 400 });
}
// Send message to Discord webhook with all metadata
await sendToDiscordWebhook(data);
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error in Discord webhook API:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}

View File

@ -34,6 +34,35 @@ export function AIChatDrawer() {
const [docsContent, setDocsContent] = useState('');
const [isHomePage, setIsHomePage] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const [pageLoadTime] = useState(Date.now());
const [sessionData, setSessionData] = useState({
messagesCount: 0,
starterPromptsUsed: 0,
timeOnPage: 0,
scrollDepth: 0,
});
// Track session data
useEffect(() => {
const updateSessionData = () => {
const timeOnPage = Math.floor((Date.now() - pageLoadTime) / 1000);
const scrollDepth = Math.floor((window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100);
setSessionData(prev => ({
...prev,
timeOnPage,
scrollDepth: Math.max(prev.scrollDepth, scrollDepth || 0),
}));
};
const interval = setInterval(updateSessionData, 5000); // Update every 5 seconds
window.addEventListener('scroll', updateSessionData);
return () => {
clearInterval(interval);
window.removeEventListener('scroll', updateSessionData);
};
}, [pageLoadTime]);
// Detect if we're on homepage and scroll state
useEffect(() => {
@ -112,6 +141,67 @@ export function AIChatDrawer() {
},
});
// Function to send message to Discord webhook
const sendToDiscord = async (message: string) => {
try {
// Gather additional context
const context = {
message: message,
username: 'Stack Auth Docs User',
metadata: {
url: window.location.href,
pathname: window.location.pathname,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
viewport: `${window.innerWidth}x${window.innerHeight}`,
isHomePage: isHomePage,
isScrolled: isScrolled,
messageLength: message.length,
messageType: starterPrompts.some(p => p.prompt === message) ? 'starter-prompt' : 'custom',
sessionMessageCount: messages.length + 1,
// Enhanced session data
timeOnPage: sessionData.timeOnPage,
scrollDepth: sessionData.scrollDepth,
referrer: document.referrer || 'Direct',
language: navigator.language,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
chatExpanded: isChatExpanded,
}
};
await fetch('/api/discord-webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(context),
});
} catch (error) {
console.error('Failed to send message to Discord:', error);
}
};
// Enhanced submit handler that also sends to Discord
const handleChatSubmit = async (e: React.FormEvent) => {
if (!input.trim()) return;
// Update session data
const isStarterPrompt = starterPrompts.some(p => p.prompt === input.trim());
setSessionData(prev => ({
...prev,
messagesCount: prev.messagesCount + 1,
starterPromptsUsed: prev.starterPromptsUsed + (isStarterPrompt ? 1 : 0),
}));
// Send message to Discord webhook
sendToDiscord(input.trim()).catch(error => {
console.error('Discord webhook error:', error);
});
// Continue with normal chat submission
handleSubmit(e);
};
// Starter prompts for users
const starterPrompts = [
{
@ -250,7 +340,7 @@ export function AIChatDrawer() {
{/* Input */}
<div className="border-t border-fd-border p-3">
<form onSubmit={handleSubmit} className="flex gap-2">
<form onSubmit={handleChatSubmit} className="flex gap-2">
<textarea
value={input}
onChange={handleInputChange}
@ -261,7 +351,7 @@ export function AIChatDrawer() {
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit(e as React.FormEvent);
handleChatSubmit(e as React.FormEvent);
}
}}
/>