mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 11:55:22 +02:00
124 lines
4.7 KiB
TypeScript
124 lines
4.7 KiB
TypeScript
|
|||
|
|
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."}`;
|
||
|
|
}
|
||
|
|
};
|