Files
CrazeAnalytix/services/geminiService.ts
T

124 lines
4.7 KiB
TypeScript
Raw Normal View History

import { GoogleGenAI } from "@google/genai";
import { AggregatedData } from "../types";
const SYSTEM_INSTRUCTION = `
You are an expert Data Analyst Assistant for "Craze Analytix".
You have access to a structured dataset of sales performance including Revenue (Sell Out), Units, Product Lines, and Seasonality.
Your Capabilities:
1. **Analyze Trends**: Use the provided Seasonality and Yearly Breakdown data.
2. **Perform Calculations**: You have access to detailed Product Line totals. You MUST calculate growth percentages, market shares, and sums dynamically if the user asks.
3. **Compare**: Compare performance between years (e.g., 2024 vs 2025).
Rules:
- If the user asks for a calculation (e.g., "What is the % share of Line X?"), perform the math using the provided numbers.
- Always format currency as € (e.g., €1,200) and units with 'u' or 'units' (e.g., 500 units).
- Be concise but insightful. Point out significant growth or decline.
- If data is missing for a specific query, state clearly that it is not in the current filtered view.
`;
const formatCurrency = (val: number) => `€${val.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0})}`;
const formatUnits = (val: number) => `${val.toLocaleString()} units`;
export const queryGemini = async (
apiKey: string,
question: string,
context: AggregatedData,
filteredRecordCount: number
): Promise<string> => {
if (!apiKey) {
return "Please provide your Gemini API Key in the settings to enable the AI assistant.";
}
try {
// Ensure the key is clean of whitespace
const ai = new GoogleGenAI({ apiKey: apiKey.trim() });
// --- CONTEXT GENERATION ---
// We construct a structured report mirroring the dashboard charts
// 1. Totals by Year (KPI Cards)
const yearlySummary = Object.entries(context.totalsByYear)
.sort((a, b) => parseInt(b[0]) - parseInt(a[0])) // Descending years
.map(([year, data]) => ` - ${year}: ${formatCurrency(data.sellOut)} | ${formatUnits(data.units)}`)
.join('\n');
// 2. Seasonality (Line Chart Data)
const seasonalitySummary = context.seasonality.map(p => {
const yearValues = context.availableYears.map(y => `${y}: ${formatCurrency(p[y] as number || 0)}`).join(', ');
return ` - ${p.name}: [${yearValues}]`;
}).join('\n');
// 3. Growth/Decline
const growthSummary = context.topMovers.slice(0, 10).map(m =>
` - ${m.line}: +€${m.sellOutGrowthValue.toLocaleString()} (${m.sellOutGrowthPercentage.toFixed(1)}%)`
).join('\n');
const declineSummary = context.bottomMovers.slice(0, 10).map(m =>
` - ${m.line}: -€${Math.abs(m.sellOutGrowthValue).toLocaleString()} (${m.sellOutGrowthPercentage.toFixed(1)}%)`
).join('\n');
// 4. DETAILED BREAKDOWN (For Calculations)
// We provide a JSON-like structure of ALL top product lines so the AI can compute shares/totals.
// We limit this to top 100 to avoid token limits, which covers most relevant data.
const detailedLines = context.byLine.slice(0, 100).map(l => ({
name: l.name,
revenue: l.value,
units: l.units
}));
const fullReport = `
REPORT CONTEXT (Based on Current Filters):
------------------------------------------
GLOBAL METRICS:
Total Sell Out: ${formatCurrency(context.totalSellOut)}
Total Units: ${formatUnits(context.totalUnits)}
Records Analyzed: ${filteredRecordCount}
Years Available: ${context.availableYears.join(', ')}
YEARLY TOTALS:
${yearlySummary}
MONTHLY TRENDS (Seasonality):
${seasonalitySummary}
TOP PERFORMERS (Growth YoY):
${growthSummary}
WORST PERFORMERS (Decline YoY):
${declineSummary}
DETAILED PRODUCT LINE DATA (Use this for specific calculations):
${JSON.stringify(detailedLines, null, 2)}
`;
const response = await ai.models.generateContent({
model: 'gemini-3-pro-preview', // Updated to the latest capable model for complex reasoning
contents: [
{
role: 'user',
parts: [{ text: `Context Data:\n${fullReport}\n\nUser Question: ${question}` }]
}
],
config: {
systemInstruction: SYSTEM_INSTRUCTION,
}
});
return response.text || "I couldn't generate a response based on the data provided.";
} catch (error: any) {
console.error("Gemini API Error:", error);
if (error.message && error.message.includes("403")) {
return "Error 403: Invalid API Key. Please check your key in the settings.";
}
if (error.message && error.message.includes("429")) {
return "Error 429: Quota exceeded. You are sending too many requests.";
}
return `Error: ${error.message || "An unexpected error occurred while analyzing the data."}`;
}
};