feat: enhanced AI usage logging (model, provider, response) and fixed build blockers
This commit is contained in:
@@ -46,7 +46,7 @@ function sleep(ms: number): Promise<void> {
|
||||
/**
|
||||
* Enrich with OpenRouter
|
||||
*/
|
||||
async function enrichWithOpenRouter(instruction: string): Promise<{ data: any; apiTime: number }> {
|
||||
async function enrichWithOpenRouter(instruction: string): Promise<{ data: any; apiTime: number; responseText: string }> {
|
||||
const client = getOpenRouterClient();
|
||||
const startApi = performance.now();
|
||||
const maxRetries = 3;
|
||||
@@ -86,6 +86,7 @@ async function enrichWithOpenRouter(instruction: string): Promise<{ data: any; a
|
||||
return {
|
||||
data: JSON.parse(jsonStr),
|
||||
apiTime: endApi - startApi,
|
||||
responseText: content
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
@@ -109,7 +110,7 @@ async function enrichWithOpenRouter(instruction: string): Promise<{ data: any; a
|
||||
/**
|
||||
* Enrich with Gemini
|
||||
*/
|
||||
async function enrichWithGemini(instruction: string): Promise<{ data: any; apiTime: number }> {
|
||||
async function enrichWithGemini(instruction: string): Promise<{ data: any; apiTime: number; responseText: string }> {
|
||||
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
|
||||
const model = genAI.getGenerativeModel({
|
||||
model: 'gemini-2.5-flash',
|
||||
@@ -130,9 +131,11 @@ async function enrichWithGemini(instruction: string): Promise<{ data: any; apiTi
|
||||
const result = await model.generateContent(instruction);
|
||||
const endApi = performance.now();
|
||||
|
||||
const responseText = result.response.text();
|
||||
return {
|
||||
data: JSON.parse(result.response.text()),
|
||||
data: JSON.parse(responseText),
|
||||
apiTime: endApi - startApi,
|
||||
responseText: responseText
|
||||
};
|
||||
}
|
||||
|
||||
@@ -200,7 +203,7 @@ Instructions:
|
||||
3. Provide a clean "search_string" for Whiskybase (e.g., "Distillery Name Age").`;
|
||||
|
||||
console.log(`[EnrichData] Using provider: ${provider}`);
|
||||
let result: { data: any; apiTime: number };
|
||||
let result: { data: any; apiTime: number; responseText: string };
|
||||
|
||||
if (provider === 'openrouter') {
|
||||
result = await enrichWithOpenRouter(instruction);
|
||||
@@ -224,7 +227,10 @@ Instructions:
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: `enrichData_${provider}`,
|
||||
success: true
|
||||
success: true,
|
||||
provider: provider,
|
||||
model: provider === 'openrouter' ? ENRICHMENT_MODEL : 'gemini-2.5-flash',
|
||||
responseText: result.responseText
|
||||
});
|
||||
|
||||
await deductCredits(userId, 'gemini_ai', `Data enrichment (${provider})`);
|
||||
|
||||
@@ -83,7 +83,7 @@ function sleep(ms: number): Promise<void> {
|
||||
* Analyze whisky label with OpenRouter (Gemma 3 27B)
|
||||
* Includes retry logic for 429 rate limit errors
|
||||
*/
|
||||
async function analyzeWithOpenRouter(base64Data: string, mimeType: string): Promise<{ data: any; apiTime: number }> {
|
||||
async function analyzeWithOpenRouter(base64Data: string, mimeType: string): Promise<{ data: any; apiTime: number; responseText: string }> {
|
||||
const client = getOpenRouterClient();
|
||||
const startApi = performance.now();
|
||||
const maxRetries = 3;
|
||||
@@ -129,6 +129,7 @@ async function analyzeWithOpenRouter(base64Data: string, mimeType: string): Prom
|
||||
return {
|
||||
data: JSON.parse(jsonStr),
|
||||
apiTime: endApi - startApi,
|
||||
responseText: content
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
@@ -155,7 +156,7 @@ async function analyzeWithOpenRouter(base64Data: string, mimeType: string): Prom
|
||||
/**
|
||||
* Analyze whisky label with Gemini
|
||||
*/
|
||||
async function analyzeWithGemini(base64Data: string, mimeType: string): Promise<{ data: any; apiTime: number }> {
|
||||
async function analyzeWithGemini(base64Data: string, mimeType: string): Promise<{ data: any; apiTime: number; responseText: string }> {
|
||||
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
|
||||
const model = genAI.getGenerativeModel({
|
||||
model: 'gemini-2.5-flash',
|
||||
@@ -179,9 +180,11 @@ async function analyzeWithGemini(base64Data: string, mimeType: string): Promise<
|
||||
]);
|
||||
const endApi = performance.now();
|
||||
|
||||
const responseText = result.response.text();
|
||||
return {
|
||||
data: JSON.parse(result.response.text()),
|
||||
data: JSON.parse(responseText),
|
||||
apiTime: endApi - startApi,
|
||||
responseText: responseText
|
||||
};
|
||||
}
|
||||
|
||||
@@ -243,7 +246,7 @@ export async function analyzeLabelWithGemini(imageBase64: string): Promise<Gemin
|
||||
|
||||
// Call appropriate provider
|
||||
console.log(`[Vision] Using provider: ${provider}`);
|
||||
let result: { data: any; apiTime: number };
|
||||
let result: { data: any; apiTime: number; responseText: string };
|
||||
|
||||
if (provider === 'openrouter') {
|
||||
result = await analyzeWithOpenRouter(base64Data, mimeType);
|
||||
@@ -278,7 +281,10 @@ export async function analyzeLabelWithGemini(imageBase64: string): Promise<Gemin
|
||||
userId: user.id,
|
||||
apiType: 'gemini_ai', // Keep same type for tracking
|
||||
endpoint: `analyzeLabelWith${provider === 'openrouter' ? 'OpenRouter' : 'Gemini'}`,
|
||||
success: true
|
||||
success: true,
|
||||
provider: provider,
|
||||
model: provider === 'openrouter' ? OPENROUTER_VISION_MODEL : 'gemini-2.5-flash',
|
||||
responseText: result.responseText
|
||||
});
|
||||
await deductCredits(user.id, 'gemini_ai', `Vision label analysis (${provider})`);
|
||||
|
||||
|
||||
@@ -186,7 +186,10 @@ export async function scanLabel(input: any): Promise<AnalysisResponse> {
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'scanLabel_openrouter',
|
||||
success: true
|
||||
success: true,
|
||||
provider: 'openrouter',
|
||||
model: 'google/gemma-3-27b-it',
|
||||
responseText: content
|
||||
});
|
||||
await deductCredits(userId, 'gemini_ai', 'Bottle OCR scan (OpenRouter)');
|
||||
|
||||
@@ -248,7 +251,10 @@ export async function scanLabel(input: any): Promise<AnalysisResponse> {
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'scanLabel_gemini',
|
||||
success: true
|
||||
success: true,
|
||||
provider: 'google',
|
||||
model: 'gemini-2.5-flash',
|
||||
responseText: result.response.text()
|
||||
});
|
||||
await deductCredits(userId, 'gemini_ai', 'Bottle OCR scan (Gemini)');
|
||||
|
||||
@@ -279,7 +285,9 @@ export async function scanLabel(input: any): Promise<AnalysisResponse> {
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: `scanLabel_${provider}`,
|
||||
success: false,
|
||||
errorMessage: aiError.message
|
||||
errorMessage: aiError.message,
|
||||
provider: provider,
|
||||
model: provider === 'openrouter' ? 'google/gemma-3-27b-it' : 'gemini-2.5-flash'
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -219,36 +219,66 @@ export default async function AdminPage() {
|
||||
<tr className="border-b border-zinc-200 dark:border-zinc-800">
|
||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Time</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">User</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">API Type</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">API/Provider</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Model</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Endpoint</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{recentUsage.map((call: any) => (
|
||||
<tr key={call.id} className="border-b border-zinc-100 dark:border-zinc-800/50">
|
||||
<td className="py-3 px-4 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{new Date(call.created_at).toLocaleString('de-DE')}
|
||||
<tr key={call.id} className="border-b border-zinc-100 dark:border-zinc-800/50 hover:bg-zinc-50 dark:hover:bg-zinc-900/50 transition-colors">
|
||||
<td className="py-3 px-4 text-[10px] text-zinc-500 font-mono">
|
||||
{new Date(call.created_at).toLocaleString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit', day: '2-digit', month: '2-digit' })}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-zinc-900 dark:text-white">
|
||||
<td className="py-3 px-4 text-sm font-bold text-zinc-900 dark:text-white">
|
||||
{call.profiles?.username || 'Unknown'}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-bold ${call.api_type === 'google_search'
|
||||
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400'
|
||||
: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-400'
|
||||
}`}>
|
||||
{call.api_type === 'google_search' ? 'Google Search' : 'Gemini AI'}
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className={`px-2 py-0.5 rounded-full text-[10px] font-black uppercase w-fit ${call.api_type === 'google_search'
|
||||
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400'
|
||||
: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-400'
|
||||
}`}>
|
||||
{call.api_type === 'google_search' ? 'Google' : 'AI'}
|
||||
</span>
|
||||
{call.provider && (
|
||||
<span className="text-[10px] font-bold text-zinc-500 uppercase tracking-tighter">
|
||||
via {call.provider}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<span className="text-[10px] font-mono text-zinc-600 dark:text-zinc-400 block max-w-[120px] truncate" title={call.model}>
|
||||
{call.model || '-'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{call.endpoint}
|
||||
<td className="py-3 px-4">
|
||||
<div className="space-y-1">
|
||||
<div className="text-[10px] font-bold text-zinc-500 uppercase">{call.endpoint}</div>
|
||||
{call.response_text && (
|
||||
<details className="text-[10px]">
|
||||
<summary className="cursor-pointer text-orange-600 hover:text-orange-700 font-bold uppercase transition-colors">Response</summary>
|
||||
<pre className="mt-2 p-2 bg-zinc-100 dark:bg-zinc-950 rounded border border-zinc-200 dark:border-zinc-800 overflow-x-auto max-w-sm whitespace-pre-wrap font-mono text-[9px] text-zinc-400">
|
||||
{call.response_text}
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
{call.success ? (
|
||||
<span className="text-green-600 dark:text-green-400 font-bold">✓</span>
|
||||
<span className="text-green-600 dark:text-green-400 font-black text-xs">OK</span>
|
||||
) : (
|
||||
<span className="text-red-600 dark:text-red-400 font-bold">✗</span>
|
||||
<div className="group relative">
|
||||
<span className="text-red-600 dark:text-red-400 font-black text-xs cursor-help">ERR</span>
|
||||
{call.error_message && (
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-48 p-2 bg-red-600 text-white text-[9px] rounded shadow-xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50">
|
||||
{call.error_message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user