Files
CrazeAnalytix/services/geminiService.ts
T

148 lines
5.7 KiB
TypeScript

import { GoogleGenAI } from "@google/genai";
import { AggregatedData } from "./types";
// Declare process to avoid TypeScript errors without causing aggressive bundler shims
declare const process: any;
const SYSTEM_INSTRUCTION = `
You are an expert Key Account Manager (KAM) and Senior Sales Strategist for "Craze Analytix".
Your specialty is analyzing retail data, advertising performance, and market trends to provide high-level strategic recommendations.
You have access to a detailed report of the currently filtered sales and advertising data.
The data includes Sell Out (€), Units Sold, Product Lines, Customer Markets, Seasonality (Monthly Trends), and Growth/Decline metrics.
Your objective:
1. **Analyze**: Deeply study the provided data according to the user's specific query.
2. **Opinion**: Provide expert opinions on the health of the business, product performance, or market position.
3. **Recommendations**: Give actionable, professional advice to improve sales, ROAS, stock health, or market share.
Guidelines:
- Maintain a professional, consultative, and business-driven tone (KAM style).
- Always justify your recommendations with specific numbers from the data.
- If asked about "Trends" or "Seasonality", look at the Monthly Seasonality section.
- If asked about "Growth" or "Decline", look at the Top/Bottom Movers sections.
- When comparing metrics, calculate variances or ratios if relevant (e.g. Sales vs Ads growth).
- Format your response with clear headers and bullet points for readability.
- Respond in the same language as the user's question (e.g., if they ask in Spanish, answer in Spanish).
`;
// Helper to get API key safely
const getApiKey = (): string | undefined => {
// Try different sources (Vite defines these at build time)
const key =
(typeof process !== 'undefined' && process.env ? process.env.API_KEY || process.env.GEMINI_API_KEY : undefined) ||
(import.meta as any).env?.VITE_GEMINI_API_KEY ||
(import.meta as any).env?.API_KEY ||
(import.meta as any).env?.GEMINI_API_KEY;
// Ensure it's not a literal "undefined" string from a failed build injection
if (key === "undefined" || key === "null" || !key) {
return undefined;
}
return key;
};
const formatCurrency = (val: number) => `€${val.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
const formatUnits = (val: number) => `${val.toLocaleString('de-DE')} units`;
export const queryGemini = async (
question: string,
context: AggregatedData,
filteredRecordCount: number
): Promise<string> => {
const apiKey = getApiKey();
if (!apiKey) {
return "API Key is missing. Please configure your environment variables (API_KEY) or check your .env file.";
}
try {
const ai = new GoogleGenAI({ apiKey });
// --- 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)
// We simplify this to a CSV-like list for the AI to parse trends
const seasonalitySummary = context.seasonality.map(p => {
// Extract values for each year in the point
const yearValues = context.availableYears.map(y => `${y}: ${formatCurrency(p[y] as number || 0)}`).join(', ');
return ` - ${p.name}: [${yearValues}]`;
}).join('\n');
// 3. Top Movers (Growth Table) - Limit to Top 10
const growthSummary = context.topMovers.slice(0, 10).map(m =>
` - ${m.line}: +€${m.sellOutGrowthValue.toLocaleString('de-DE')} (${m.sellOutGrowthPercentage.toFixed(1)}%)`
).join('\n');
// 4. Declining Movers (Decline Table) - Limit to Top 10
const declineSummary = context.bottomMovers.slice(0, 10).map(m =>
` - ${m.line}: -€${Math.abs(m.sellOutGrowthValue).toLocaleString('de-DE')} (${m.sellOutGrowthPercentage.toFixed(1)}%)`
).join('\n');
// 5. Product Lines Overview (Bar Charts) - Limit to Top 50 to save tokens but give depth
const topLinesSummary = context.byLine.slice(0, 50).map((l, i) =>
` ${i + 1}. ${l.name}: ${formatCurrency(l.value)} | ${formatUnits(l.units)}`
).join('\n');
// 6. Customer Distribution (Customer Chart)
const customerSummary = context.byCustomer.map(c =>
` - ${c.name}: ${formatCurrency(c.value)}`
).join('\n');
const fullReport = `
REPORT CONTEXT:
----------------
GLOBAL TOTALS:
Total Sell Out: ${formatCurrency(context.totalSellOut)}
Total Units: ${formatUnits(context.totalUnits)}
Records Analyzed: ${filteredRecordCount}
Years Available: ${context.availableYears.join(', ')}
YEARLY BREAKDOWN:
${yearlySummary}
MONTHLY SEASONALITY (Revenue Trends):
${seasonalitySummary}
FASTEST GROWING LINES (Year-over-Year):
${growthSummary}
DECLINING LINES (Year-over-Year):
${declineSummary}
TOP PRODUCT LINES (Revenue & Units):
${topLinesSummary}
PERFORMANCE BY CUSTOMER:
${customerSummary}
`;
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: `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("Not implemented on this platform")) {
return "System Error: The AI SDK detected a platform mismatch.";
}
return `Error: ${error.message || "An unexpected error occurred while analyzing the data."}`;
}
};