feat: add Gemini AI tracking and fix admin dashboard

- Integrated API tracking into analyze-bottle service
- Added credit balance check before Gemini API calls
- Deduct credits after successful Gemini analysis
- Track both successful and failed Gemini API calls
- Added debug logging to admin dashboard for recent API calls
- Fixed error handling in analyze-bottle

Now tracks both Google Search and Gemini AI API usage!
This commit is contained in:
2025-12-18 15:35:02 +01:00
parent b18f8907a3
commit 1cfa9cab8c
2 changed files with 50 additions and 2 deletions

View File

@@ -30,7 +30,8 @@ export default async function AdminPage() {
const stats = await getGlobalApiStats();
// Fetch recent API usage
const { data: recentUsage } = await supabase
console.log('[Admin Page] Fetching recent API usage...');
const { data: recentUsage, error: recentError } = await supabase
.from('api_usage')
.select(`
*,
@@ -41,6 +42,8 @@ export default async function AdminPage() {
.order('created_at', { ascending: false })
.limit(50);
console.log('[Admin Page] Recent usage - count:', recentUsage?.length, 'error:', recentError);
// Fetch per-user statistics
const { data: userStats } = await supabase
.from('api_usage')

View File

@@ -5,6 +5,8 @@ import { BottleMetadataSchema, AnalysisResponse } from '@/types/whisky';
import { createServerActionClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { createHash } from 'crypto';
import { trackApiUsage } from './track-api-usage';
import { checkCreditBalance, deductCredits } from './credit-service';
export async function analyzeBottle(base64Image: string): Promise<AnalysisResponse> {
const supabase = createServerActionClient({ cookies });
@@ -16,10 +18,21 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
try {
// Ensure user is authenticated for tracking/billing
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
if (!session || !session.user) {
return { success: false, error: 'Nicht autorisiert oder Session abgelaufen.' };
}
const userId = session.user.id;
// Check credit balance before making API call
const creditCheck = await checkCreditBalance(userId, 'gemini_ai');
if (!creditCheck.allowed) {
return {
success: false,
error: `Nicht genügend Credits. Du benötigst ${creditCheck.cost} Credits, hast aber nur ${creditCheck.balance}.`
};
}
// 1. Generate Hash for Caching
const base64Data = base64Image.split(',')[1] || base64Image;
const imageHash = createHash('sha256').update(base64Data).digest('hex');
@@ -66,6 +79,21 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
const validatedData = BottleMetadataSchema.parse(jsonData);
// Track successful API call
await trackApiUsage({
userId: userId,
apiType: 'gemini_ai',
endpoint: 'generateContent',
success: true
});
// Deduct credits after successful API call
const creditDeduction = await deductCredits(userId, 'gemini_ai', 'Bottle analysis');
if (!creditDeduction.success) {
console.error('Failed to deduct credits:', creditDeduction.error);
// Don't fail the analysis if credit deduction fails
}
// 4. Store in Cache
const { error: storeError } = await supabase
.from('vision_cache')
@@ -83,6 +111,23 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
};
} catch (error) {
console.error('Gemini Analysis Error:', error);
// Track failed API call
try {
const { data: { session } } = await supabase.auth.getSession();
if (session?.user) {
await trackApiUsage({
userId: session.user.id,
apiType: 'gemini_ai',
endpoint: 'generateContent',
success: false,
errorMessage: error instanceof Error ? error.message : 'Unknown error'
});
}
} catch (trackError) {
console.error('Failed to track error:', trackError);
}
return {
success: false,
error: error instanceof Error ? error.message : 'An unknown error occurred during analysis.',