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:
@@ -30,7 +30,8 @@ export default async function AdminPage() {
|
|||||||
const stats = await getGlobalApiStats();
|
const stats = await getGlobalApiStats();
|
||||||
|
|
||||||
// Fetch recent API usage
|
// 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')
|
.from('api_usage')
|
||||||
.select(`
|
.select(`
|
||||||
*,
|
*,
|
||||||
@@ -41,6 +42,8 @@ export default async function AdminPage() {
|
|||||||
.order('created_at', { ascending: false })
|
.order('created_at', { ascending: false })
|
||||||
.limit(50);
|
.limit(50);
|
||||||
|
|
||||||
|
console.log('[Admin Page] Recent usage - count:', recentUsage?.length, 'error:', recentError);
|
||||||
|
|
||||||
// Fetch per-user statistics
|
// Fetch per-user statistics
|
||||||
const { data: userStats } = await supabase
|
const { data: userStats } = await supabase
|
||||||
.from('api_usage')
|
.from('api_usage')
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { BottleMetadataSchema, AnalysisResponse } from '@/types/whisky';
|
|||||||
import { createServerActionClient } from '@supabase/auth-helpers-nextjs';
|
import { createServerActionClient } from '@supabase/auth-helpers-nextjs';
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
import { trackApiUsage } from './track-api-usage';
|
||||||
|
import { checkCreditBalance, deductCredits } from './credit-service';
|
||||||
|
|
||||||
export async function analyzeBottle(base64Image: string): Promise<AnalysisResponse> {
|
export async function analyzeBottle(base64Image: string): Promise<AnalysisResponse> {
|
||||||
const supabase = createServerActionClient({ cookies });
|
const supabase = createServerActionClient({ cookies });
|
||||||
@@ -16,10 +18,21 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
|
|||||||
try {
|
try {
|
||||||
// Ensure user is authenticated for tracking/billing
|
// Ensure user is authenticated for tracking/billing
|
||||||
const { data: { session } } = await supabase.auth.getSession();
|
const { data: { session } } = await supabase.auth.getSession();
|
||||||
if (!session) {
|
if (!session || !session.user) {
|
||||||
return { success: false, error: 'Nicht autorisiert oder Session abgelaufen.' };
|
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
|
// 1. Generate Hash for Caching
|
||||||
const base64Data = base64Image.split(',')[1] || base64Image;
|
const base64Data = base64Image.split(',')[1] || base64Image;
|
||||||
const imageHash = createHash('sha256').update(base64Data).digest('hex');
|
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);
|
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
|
// 4. Store in Cache
|
||||||
const { error: storeError } = await supabase
|
const { error: storeError } = await supabase
|
||||||
.from('vision_cache')
|
.from('vision_cache')
|
||||||
@@ -83,6 +111,23 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
|
|||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Gemini Analysis Error:', 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 {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : 'An unknown error occurred during analysis.',
|
error: error instanceof Error ? error.message : 'An unknown error occurred during analysis.',
|
||||||
|
|||||||
Reference in New Issue
Block a user