Skip to Content

Monitoring & Analytics

Comprehensive monitoring and analytics for Earna AI Console. Track chat performance, GPT-4o API usage, alternative model metrics, Supabase database health, and user engagement.

Overview

Earna AI Console monitoring provides visibility into:

  • Chat Analytics: Message volume, response times, and user satisfaction
  • GPT-4o Performance: API latency, token usage, and cost tracking
  • Alternative Models: Claude, Gemini, and other model performance
  • Supabase Health: Database connections, query performance, real-time subscriptions
  • Avatar & Voice: HeyGen sessions and GPT-4o Realtime voice metrics
  • User Engagement: Daily limits, subscription tiers, and feature usage

Analytics Dashboard

Real-time Metrics

Monitor critical platform metrics in real-time:

// lib/monitoring/platform-metrics.ts export interface PlatformMetrics { chat: { activeSessions: number; messagesProcessed: number; avgResponseTime: number; // ms timeToFirstToken: number; // ms for streaming satisfaction: number; // 0-5 rating successRate: number; // 0-100 }; gpt4o: { requestsPerHour: number; avgLatency: number; // ms tokenUsage: number; costPerHour: number; // USD streamingActive: number; // active streams errorRate: number; // percentage }; alternativeModels: { claude: ModelMetrics; gemini: ModelMetrics; mistral: ModelMetrics; activeModel: string; // currently selected fallbackCount: number; // fallback to GPT-4o }; supabase: { databaseHealth: 'healthy' | 'degraded' | 'down'; activeConnections: number; queryLatency: number; // ms realtimeSubscriptions: number; storageUsage: number; // MB }; features: { avatarSessions: number; // HeyGen active sessions voiceSessions: number; // GPT-4o Realtime voice fileUploads: number; dailyLimits: { free: { used: number; limit: number }; pro: { used: number; limit: number }; }; }; system: { apiLatency: number; // ms errorRate: number; // percentage uptime: number; // percentage activeUsers: number; edgeFunctionCalls: number; cacheHitRate: number; // percentage }; } export interface ModelMetrics { requests: number; avgLatency: number; tokenUsage: number; cost: number; errorRate: number; availability: boolean; } export class PlatformAnalytics { private static instance: PlatformAnalytics; private metrics: PlatformMetrics; private subscribers: ((metrics: PlatformMetrics) => void)[] = []; static getInstance(): PlatformAnalytics { if (!PlatformAnalytics.instance) { PlatformAnalytics.instance = new PlatformAnalytics(); } return PlatformAnalytics.instance; } async collectMetrics(): Promise<PlatformMetrics> { this.metrics = { chat: await this.collectChatMetrics(), gpt4o: await this.collectGPT4oMetrics(), alternativeModels: await this.collectAlternativeModelMetrics(), supabase: await this.collectSupabaseMetrics(), features: await this.collectFeatureMetrics(), system: await this.collectSystemMetrics() }; // Notify subscribers this.subscribers.forEach(callback => callback(this.metrics)); return this.metrics; } subscribe(callback: (metrics: PlatformMetrics) => void): () => void { this.subscribers.push(callback); return () => { this.subscribers = this.subscribers.filter(cb => cb !== callback); }; } private async collectChatMetrics() { const timeWindow = new Date(Date.now() - 60 * 60 * 1000); // Last hour const [sessions, messages, responseTime, ttft, satisfaction] = await Promise.all([ this.getActiveChatSessions(), this.getMessagesProcessed(timeWindow), this.getAverageResponseTime(timeWindow), this.getTimeToFirstToken(timeWindow), this.getAverageSatisfaction(timeWindow) ]); return { activeSessions: sessions, messagesProcessed: messages, avgResponseTime: responseTime, timeToFirstToken: ttft, satisfaction, successRate: this.calculateChatSuccessRate(messages) }; } private async collectGPT4oMetrics() { const hourAgo = new Date(Date.now() - 60 * 60 * 1000); return { requestsPerHour: await this.getGPT4oRequests(hourAgo), avgLatency: await this.getGPT4oLatency(hourAgo), tokenUsage: await this.getTokenUsage(hourAgo), costPerHour: await this.calculateGPT4oCost(hourAgo), streamingActive: await this.getActiveStreams(), errorRate: await this.getGPT4oErrorRate(hourAgo) }; } private async collectSupabaseMetrics() { const [health, connections, latency, subscriptions, storage] = await Promise.all([ this.checkSupabaseHealth(), this.getActiveConnections(), this.getQueryLatency(), this.getRealtimeSubscriptions(), this.getStorageUsage() ]); return { databaseHealth: health, activeConnections: connections, queryLatency: latency, realtimeSubscriptions: subscriptions, storageUsage: storage }; } }

Monitoring Dashboard Component

// components/monitoring/Dashboard.tsx 'use client'; import { useState, useEffect } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Progress } from '@/components/ui/progress'; import { PlatformAnalytics, PlatformMetrics } from '@/lib/monitoring/platform-metrics'; export function MonitoringDashboard() { const [metrics, setMetrics] = useState<PlatformMetrics | null>(null); const [loading, setLoading] = useState(true); const analytics = PlatformAnalytics.getInstance(); useEffect(() => { const loadMetrics = async () => { const currentMetrics = await analytics.collectMetrics(); setMetrics(currentMetrics); setLoading(false); }; loadMetrics(); // Subscribe to real-time updates const unsubscribe = analytics.subscribe(setMetrics); // Refresh every 30 seconds const interval = setInterval(loadMetrics, 30000); return () => { unsubscribe(); clearInterval(interval); }; }, []); if (loading) return <div>Loading metrics...</div>; if (!metrics) return <div>Failed to load metrics</div>; return ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> {/* Chat Metrics */} <Card> <CardHeader> <CardTitle>Chat Activity</CardTitle> </CardHeader> <CardContent> <div className="text-3xl font-bold">{metrics.chat.activeSessions}</div> <div className="text-sm text-muted-foreground"> {metrics.chat.messagesProcessed} messages today </div> <div className="mt-2 space-y-1"> <Badge variant={metrics.chat.timeToFirstToken < 200 ? "default" : "secondary"}> {metrics.chat.timeToFirstToken}ms TTFT </Badge> <Badge variant={metrics.chat.avgResponseTime < 500 ? "default" : "secondary"}> {metrics.chat.avgResponseTime}ms avg response </Badge> </div> </CardContent> </Card> {/* GPT-4o Performance */} <Card> <CardHeader> <CardTitle>GPT-4o API</CardTitle> </CardHeader> <CardContent> <div className="space-y-3"> <div> <div className="flex justify-between text-sm"> <span>Requests/hour</span> <span>{metrics.gpt4o.requestsPerHour}</span> </div> <Progress value={Math.min(metrics.gpt4o.requestsPerHour / 100, 100)} className="mt-1" /> </div> <div> <div className="flex justify-between text-sm"> <span>Active streams</span> <span>{metrics.gpt4o.streamingActive}</span> </div> </div> <div className="text-sm text-muted-foreground"> Latency: {metrics.gpt4o.avgLatency}ms </div> <div className="text-sm font-semibold text-green-600"> ${metrics.gpt4o.costPerHour.toFixed(2)} USD/hour </div> </div> </CardContent> </Card> {/* Supabase Status */} <Card> <CardHeader> <CardTitle>Supabase</CardTitle> </CardHeader> <CardContent> <div className="space-y-2"> <div className="flex justify-between items-center"> <span className="text-sm">Database</span> <Badge variant={metrics.supabase.databaseHealth === 'healthy' ? "default" : "destructive"}> {metrics.supabase.databaseHealth} </Badge> </div> <div className="flex justify-between items-center"> <span className="text-sm">Connections</span> <span className="font-semibold">{metrics.supabase.activeConnections}</span> </div> <div className="flex justify-between items-center"> <span className="text-sm">Realtime subs</span> <span className="font-semibold">{metrics.supabase.realtimeSubscriptions}</span> </div> <div className="pt-2 border-t"> <div className="text-sm text-muted-foreground">Query latency</div> <div className="text-lg font-bold">{metrics.supabase.queryLatency}ms</div> </div> </div> </CardContent> </Card> {/* Feature Usage */} <Card> <CardHeader> <CardTitle>Feature Usage</CardTitle> </CardHeader> <CardContent> <div className="space-y-2"> <div className="flex justify-between"> <span className="text-sm">Avatar Sessions</span> <span className="font-semibold">{metrics.features.avatarSessions}</span> </div> <div className="flex justify-between"> <span className="text-sm">Voice Sessions</span> <span className="font-semibold">{metrics.features.voiceSessions}</span> </div> <div className="flex justify-between"> <span className="text-sm">File Uploads</span> <span className="font-semibold">{metrics.features.fileUploads}</span> </div> <div className="pt-2 border-t"> <div className="text-sm text-muted-foreground">Daily Limits</div> <div className="text-xs space-y-1"> <div>Free: {metrics.features.dailyLimits.free.used}/{metrics.features.dailyLimits.free.limit}</div> <div>Pro: {metrics.features.dailyLimits.pro.used}/{metrics.features.dailyLimits.pro.limit}</div> </div> </div> </div> </CardContent> </Card> </div> ); }

Alert Configuration

Set up intelligent alerts for critical platform events:

// lib/monitoring/alerts.ts export interface ChatAlert { type: 'chat_performance'; conditions: { successRate: { min: number }; satisfaction: { min: number }; errorRate: { max: number }; timeToFirstToken: { max: number }; }; notifications: NotificationChannel[]; } export const chatPerformanceAlerts: ChatAlert = { type: 'chat_performance', conditions: { successRate: { min: 95 }, // Alert if below 95% satisfaction: { min: 4.0 }, // Alert if below 4.0/5 errorRate: { max: 2 }, // Alert if above 2% timeToFirstToken: { max: 500 } // Alert if TTFT > 500ms }, notifications: [ { type: 'slack', channel: '#alerts' }, { type: 'email', recipients: ['team@earna.sh'] }, { type: 'webhook', url: process.env.ALERT_WEBHOOK_URL } ] }; export class AlertManager { async checkChatPerformance(metrics: ChatMetrics): Promise<void> { const alerts = []; if (metrics.chat.successRate < chatPerformanceAlerts.conditions.successRate.min) { alerts.push({ type: 'low_success_rate', message: `Chat success rate dropped to ${metrics.chat.successRate}%`, severity: 'high', value: metrics.chat.successRate }); } if (metrics.chat.timeToFirstToken > chatPerformanceAlerts.conditions.timeToFirstToken.max) { alerts.push({ type: 'slow_streaming', message: `Time to first token increased to ${metrics.chat.timeToFirstToken}ms`, severity: 'medium', value: metrics.chat.timeToFirstToken }); } if (alerts.length > 0) { await this.sendAlerts(alerts, chatPerformanceAlerts.notifications); } } }

User Analytics

Chat Analytics

Track detailed chat patterns and user behavior:

// lib/analytics/chat-analytics.ts export interface ChatAnalytics { sessionMetrics: { duration: number; messageCount: number; modelSwitches: number; // Times user switched models userSatisfaction: number; completionReason: 'completed' | 'abandoned' | 'limit_reached' | 'error'; }; modelUsage: { primary: string; // Most used model models: Array<{ model: string; messages: number; tokens: number; cost: number; }>; }; featureUsage: { avatarUsed: boolean; voiceUsed: boolean; filesUploaded: number; codeExecuted: boolean; }; performance: { avgResponseTime: number; avgTimeToFirstToken: number; streamingQuality: number; // 0-100 score }; } export class ChatTracker { async trackChat( chatId: string, analytics: ChatAnalytics ): Promise<void> { await supabase.from('chat_analytics').insert({ chat_id: chatId, session_duration: analytics.sessionMetrics.duration, message_count: analytics.sessionMetrics.messageCount, model_switches: analytics.sessionMetrics.modelSwitches, user_satisfaction: analytics.sessionMetrics.userSatisfaction, completion_reason: analytics.sessionMetrics.completionReason, model_usage: analytics.modelUsage, feature_usage: analytics.featureUsage, performance_metrics: analytics.performance, created_at: new Date() }); // Update real-time analytics await this.updateRealTimeAnalytics(analytics); } async generateInsights(timeframe: 'day' | 'week' | 'month'): Promise<any> { const { data } = await supabase .from('chat_analytics') .select('*') .gte('created_at', this.getTimeframeStart(timeframe)); return { totalChats: data.length, avgDuration: this.average(data.map(d => d.session_duration)), avgSatisfaction: this.average(data.map(d => d.user_satisfaction)), modelPreference: this.getMostUsedModel(data), featureAdoption: this.calculateFeatureAdoption(data), performanceTrends: this.calculatePerformanceTrends(data) }; } }

User Journey Tracking

Monitor user progression through the platform:

// lib/analytics/user-journey.ts export interface UserJourney { stages: { signup: Date | null; // Account created firstChat: Date | null; // First message sent modelExploration: Date | null; // Tried alternative models featureAdoption: Date | null; // Used advanced features subscription: Date | null; // Upgraded to pro retention: Date | null; // Active after 30 days }; metrics: { timeToFirstChat: number; // Minutes from signup timeToSubscription: number; // Days to upgrade engagementScore: number; // 0-100 retentionProbability: number; // 0-1 }; } export class UserJourneyTracker { async updateUserStage(userId: string, stage: keyof UserJourney['stages']): Promise<void> { const { error } = await supabase .from('user_journeys') .upsert({ user_id: userId, [stage]: new Date(), updated_at: new Date() }); if (!error) { await this.calculateJourneyMetrics(userId); } } async getJourneyInsights(): Promise<any> { const { data } = await supabase .from('user_journeys') .select('*') .not('retention', 'is', null); // Only users who reached retention return { avgTimeToFirstChat: this.calculateAverageTime(data, 'firstChat'), conversionRates: { signupToFirstChat: this.calculateConversionRate(data, 'signup', 'firstChat'), firstChatToExploration: this.calculateConversionRate(data, 'firstChat', 'modelExploration'), explorationToSubscription: this.calculateConversionRate(data, 'modelExploration', 'subscription') }, retentionRate: this.calculateRetentionRate(data) }; } }

Cost Monitoring

AI Model Cost Optimization

Track and optimize AI model costs:

// lib/monitoring/cost-monitoring.ts export class CostMonitor { private readonly MODEL_COSTS = { 'gpt-4o': { input: 0.005, output: 0.015 }, // per 1K tokens 'claude-3-opus': { input: 0.015, output: 0.075 }, 'gemini-1.5-pro': { input: 0.00125, output: 0.005 }, 'mistral-large': { input: 0.004, output: 0.012 } }; async trackModelUsage( model: string, inputTokens: number, outputTokens: number, responseTime: number ): Promise<void> { const cost = this.calculateCost(model, inputTokens, outputTokens); await supabase.from('model_usage').insert({ model, input_tokens: inputTokens, output_tokens: outputTokens, cost, response_time: responseTime, created_at: new Date() }); // Check cost limits await this.checkCostLimits(); } private calculateCost(model: string, inputTokens: number, outputTokens: number): number { const modelCost = this.MODEL_COSTS[model as keyof typeof this.MODEL_COSTS]; if (!modelCost) return 0; return (inputTokens * modelCost.input + outputTokens * modelCost.output) / 1000; } async getCostSummary(period: 'hour' | 'day' | 'week' | 'month'): Promise<any> { const timeWindow = this.getTimeWindow(period); const { data } = await supabase .from('model_usage') .select('model, cost, input_tokens, output_tokens') .gte('created_at', timeWindow); const summary = data.reduce((acc, usage) => { if (!acc[usage.model]) { acc[usage.model] = { cost: 0, requests: 0, tokens: 0 }; } acc[usage.model].cost += usage.cost; acc[usage.model].requests++; acc[usage.model].tokens += usage.input_tokens + usage.output_tokens; return acc; }, {}); return { totalCost: Object.values(summary).reduce((sum: number, m: any) => sum + m.cost, 0), costByModel: summary, projectedMonthlyCost: this.projectMonthlyCost(summary, period) }; } private async checkCostLimits(): Promise<void> { const hourlyCost = await this.getCostSummary('hour'); const dailyCost = await this.getCostSummary('day'); const COST_LIMITS = { hourly: 50, // $50/hour daily: 500, // $500/day monthly: 10000 // $10,000/month }; if (hourlyCost.totalCost > COST_LIMITS.hourly) { await this.sendCostAlert('hourly', hourlyCost.totalCost, COST_LIMITS.hourly); } if (dailyCost.totalCost > COST_LIMITS.daily) { await this.sendCostAlert('daily', dailyCost.totalCost, COST_LIMITS.daily); } } }

Performance Optimization

Model Selection Optimizer

Intelligently select the best model based on current conditions:

// lib/monitoring/performance-optimizer.ts export class PerformanceOptimizer { private cache = new Map<string, any>(); async selectOptimalModel(query: string, context: any): Promise<string> { // Check cache first const cacheKey = this.generateCacheKey(query, context); const cached = this.cache.get(cacheKey); if (cached && this.isCacheValid(cached)) { return cached.model; } // Get current model performance const modelPerformance = await this.getModelPerformanceMetrics(); // Select optimal model const optimalModel = this.selectBestModel(query, context, modelPerformance); // Cache the decision this.cache.set(cacheKey, { model: optimalModel, timestamp: Date.now(), performance: modelPerformance }); return optimalModel; } private async getModelPerformanceMetrics(): Promise<any> { const timeWindow = 15 * 60 * 1000; // Last 15 minutes return { 'gpt-4o': { avgLatency: await this.getModelLatency('gpt-4o', timeWindow), errorRate: await this.getModelErrorRate('gpt-4o', timeWindow), availability: await this.checkModelAvailability('gpt-4o'), cost: 0.005 // per 1K tokens }, 'claude-3-opus': { avgLatency: await this.getModelLatency('claude-3-opus', timeWindow), errorRate: await this.getModelErrorRate('claude-3-opus', timeWindow), availability: await this.checkModelAvailability('claude-3-opus'), cost: 0.015 }, 'gemini-1.5-pro': { avgLatency: await this.getModelLatency('gemini-1.5-pro', timeWindow), errorRate: await this.getModelErrorRate('gemini-1.5-pro', timeWindow), availability: await this.checkModelAvailability('gemini-1.5-pro'), cost: 0.00125 } }; } private selectBestModel(query: string, context: any, performance: any): string { // Default to GPT-4o if no alternatives available if (!performance['claude-3-opus'].availability && !performance['gemini-1.5-pro'].availability) { return 'gpt-4o'; } // Score each model const scores = Object.entries(performance).map(([model, metrics]: [string, any]) => { if (!metrics.availability) return { model, score: -1 }; let score = 100; // Penalize high latency score -= metrics.avgLatency / 100; // Penalize high error rates score -= metrics.errorRate * 10; // Consider cost vs performance if (context.prioritizeCost) { score -= metrics.cost * 100; } // Model-specific optimizations if (query.length > 10000 && model === 'gemini-1.5-pro') { score += 20; // Gemini handles long context well } if (context.requiresVision && model === 'gpt-4o') { score += 25; // GPT-4o has best vision capabilities } return { model, score }; }); // Return highest scoring available model return scores .filter(s => s.score > 0) .sort((a, b) => b.score - a.score)[0]?.model || 'gpt-4o'; } }

Observability Tools

Integration with External Services

// lib/monitoring/vercel-analytics.ts import { track } from '@vercel/analytics'; export class VercelAnalytics { trackChatStarted(userId: string, model: string): void { track('chat_started', { userId, model, timestamp: new Date().toISOString() }); } trackMessageSent(userId: string, model: string, tokens: number): void { track('message_sent', { userId, model, tokens, timestamp: new Date().toISOString() }); } trackModelSwitched(userId: string, fromModel: string, toModel: string): void { track('model_switched', { userId, fromModel, toModel, timestamp: new Date().toISOString() }); } trackFeatureUsed(userId: string, feature: string): void { track('feature_used', { userId, feature, timestamp: new Date().toISOString() }); } trackSubscriptionUpgrade(userId: string, tier: string): void { track('subscription_upgraded', { userId, tier, timestamp: new Date().toISOString() }); } }

Comprehensive monitoring helps ensure optimal performance and user experience. Set up alerts for critical metrics and regularly review analytics to identify improvement opportunities.

Best Practices

Monitoring Checklist

  • Real-time Dashboards: Monitor key metrics continuously
  • Intelligent Alerts: Set up proactive alerting for critical issues
  • Cost Tracking: Monitor AI model costs and optimize usage
  • User Analytics: Track user journeys and satisfaction
  • Performance Monitoring: Optimize response times and streaming quality
  • Error Tracking: Comprehensive error monitoring with Sentry
  • Database Health: Monitor Supabase connections and performance
  • Feature Adoption: Track usage of avatars, voice, and file uploads

Monitoring Strategy

  1. Proactive Monitoring: Detect issues before users are affected
  2. User-Centric Metrics: Focus on metrics that impact user experience
  3. Cost Optimization: Balance performance with cost efficiency
  4. Continuous Improvement: Use analytics to drive product improvements
  5. Privacy-First: Ensure all monitoring respects user privacy

For monitoring support and custom dashboards, contact support@earna.sh.

Last updated on