Skip to main content
Glama

Chicken Business Management MCP Server

by PSYGER02
geminiService.ts13.6 kB
import { GoogleGenAI, GenerateContentResponse, Type } from "@google/genai"; import type { Sale, ForecastDataPoint, AIInsights, Expense, Product, ParsedSaleFromAI } from '../types'; import { rateLimitService } from './rateLimitService'; const API_KEY = import.meta.env.VITE_GEMINI_API_KEY || import.meta.env.GEMINI_API_KEY; if (!API_KEY) { console.warn("Gemini API_KEY environment variable not set. AI features will be disabled."); } const ai = API_KEY ? new GoogleGenAI({ apiKey: API_KEY }) : null; const generatePromptForForecast = (sales: Sale[]): string => { const historicalData = sales.slice(0, 30).map(s => ({ date: new Date(s.date).toLocaleDateString(), total: s.total })); const dataSummary = historicalData.reduce((acc, curr) => { const date = curr.date; acc[date] = (acc[date] || 0) + curr.total; return acc; }, {} as Record<string, number>); return ` You are a financial analyst for a small restaurant. Based on the following daily sales data for the last 30 days, please provide a 7-day sales forecast. Historical Data (date: total sales): ${JSON.stringify(dataSummary, null, 2)} `; }; const generatePromptForInsights = (sales: Sale[], expenses: Expense[]): string => { const totalSales = sales.reduce((sum, s) => sum + s.total, 0); const totalExpenses = expenses.reduce((sum, e) => sum + e.amount, 0); return ` You are an expert business consultant for a small fried chicken restaurant. Here is a snapshot of their recent performance: - Total Sales (last 90 days): ₱${totalSales.toFixed(2)} - Total Expenses (last 90 days): ₱${totalExpenses.toFixed(2)} - Net Profit (last 90 days): ₱${(totalSales - totalExpenses).toFixed(2)} Based on this data, provide smart business insights. - "insights": Provide 2-3 general observations. - "risks": Identify 2-3 potential risks or areas of concern. - "opportunities": Suggest 2-3 growth opportunities or areas for improvement. `; }; export const getSalesForecast = async (sales: Sale[]): Promise<ForecastDataPoint[]> => { if (!ai) return Promise.reject(new Error("API key not configured.")); const prompt = generatePromptForForecast(sales); try { const response: GenerateContentResponse = await rateLimitService.execute(() => ai.models.generateContent({ model: "gemini-2.0-flash", contents: prompt, config: { responseMimeType: "application/json", responseSchema: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { day: { type: Type.STRING, description: "The forecasted day, e.g., 'Day 1'" }, predictedSales: { type: Type.NUMBER, description: 'The predicted sales amount for that day.' }, }, required: ["day", "predictedSales"], } }, temperature: 0.5, } }) ); if (!response.text()) { throw new Error("AI response was empty."); } const jsonStr = response.text().trim(); const parsedData = JSON.parse(jsonStr); if (Array.isArray(parsedData) && parsedData.every(item => 'day' in item && 'predictedSales' in item)) { return parsedData; } else { throw new Error("AI response is not in the expected format."); } } catch (error) { console.error("Error fetching sales forecast:", error); throw new Error("Failed to get sales forecast from AI."); } }; export const getBusinessInsights = async (sales: Sale[], expenses: Expense[]): Promise<AIInsights> => { if (!ai) return Promise.reject(new Error("API key not configured.")); const prompt = generatePromptForInsights(sales, expenses); try { const response: GenerateContentResponse = await ai.models.generateContent({ model: "gemini-2.0-flash", contents: prompt, config: { responseMimeType: "application/json", responseSchema: { type: Type.OBJECT, properties: { insights: { type: Type.ARRAY, items: { type: Type.STRING }, description: "General observations about the business." }, risks: { type: Type.ARRAY, items: { type: Type.STRING }, description: "Potential risks or areas of concern." }, opportunities: { type: Type.ARRAY, items: { type: Type.STRING }, description: "Growth opportunities or areas for improvement." } }, required: ["insights", "risks", "opportunities"], }, temperature: 0.7 } }); if (!response.text()) { throw new Error("AI response was empty."); } const jsonStr = response.text().trim(); const parsedData = JSON.parse(jsonStr); if (parsedData && 'insights' in parsedData && 'risks' in parsedData && 'opportunities' in parsedData) { return parsedData; } else { throw new Error("AI response is not in the expected format for insights."); } } catch (error) { console.error("Error fetching business insights:", error); throw new Error("Failed to get business insights from AI."); } }; const generatePromptForVoiceSale = (transcript: string, products: Product[]): string => { const productNames = products.map(p => p.name).join(', '); return ` You are an intelligent Point of Sale assistant. Your task is to parse a spoken sales order into a structured JSON format. The user will provide a string of text. You must identify product names, their quantities, and the payment amount mentioned. - Match the product names from the text to the available product list. - If a product is mentioned without a quantity, assume the quantity is 1. - The payment is usually mentioned at the end with a currency word like "pesos", "dollars", or just a number. - If no payment is mentioned, the payment value should be 0. Available Products: ${productNames} Transcript to parse: "${transcript}" `; } // Part C: ChatGPT Plan - Strict JSON parsing for stock operations export const parseStockNote = async (noteContent: string): Promise<any> => { // Fallback parsing if no AI const fallbackParse = (text: string) => { const result: any = {}; const lower = text.toLowerCase(); // Extract purchases const buyMatch = lower.match(/(bought?|buy)\s+(.+?)\s+(\d+)\s+bags?/); if (buyMatch) { result.purchases = [{ product: buyMatch[2], bags: parseInt(buyMatch[3]) }]; } // Extract cooking const cookMatch = lower.match(/cook(ed)?\s+(\d+)\s+bags?/); if (cookMatch) { result.cooking = [{ bags: parseInt(cookMatch[2]), branch: "branch1" }]; } // Extract sales const saleMatch = lower.match(/sold?\s+(\d+)\s+pieces?\s+at\s+(\d+)/); if (saleMatch) { result.sales = [{ pieces: parseInt(saleMatch[1]), price: parseInt(saleMatch[2]), branch: "branch1" }]; } // Extract transfers const transferMatch = lower.match(/sent?\s+(\d+)\s+bags?\s+to\s+(\w+)/); if (transferMatch) { result.transfers = [{ to_branch: transferMatch[2], bags: parseInt(transferMatch[1]) }]; } return result; }; // ChatGPT Plan: Strict JSON-only extraction if (ai) { try { const prompt = `You are a strict JSON-only extractor. For every input note, output valid JSON only. The JSON must follow the schema keys: purchases[], productions[], transfers[], branch_operations[], leftovers[]. If any field is unknown, omit it. Do not add commentary.\n\nParse this note: "${noteContent}"`; const response = await rateLimitService.execute(() => ai.models.generateContent({ model: "gemini-2.0-flash", contents: prompt, config: { responseMimeType: "application/json", temperature: 0.1, responseSchema: { type: Type.OBJECT, properties: { purchases: { type: Type.ARRAY, items: { type: Type.OBJECT } }, productions: { type: Type.ARRAY, items: { type: Type.OBJECT } }, transfers: { type: Type.ARRAY, items: { type: Type.OBJECT } }, branch_operations: { type: Type.ARRAY, items: { type: Type.OBJECT } }, leftovers: { type: Type.ARRAY, items: { type: Type.OBJECT } } } } } }) ); if (response.text()) { return JSON.parse(response.text()); } } catch (error) { console.warn('AI parsing failed, using fallback:', error); } } // Always return something useful return fallbackParse(noteContent); }; export const parseSaleFromVoice = async (transcript: string, products: Product[]): Promise<ParsedSaleFromAI> => { if (!ai) return Promise.reject(new Error("API key not configured.")); const prompt = generatePromptForVoiceSale(transcript, products); try { const response: GenerateContentResponse = await ai.models.generateContent({ model: "gemini-2.5-flash", contents: prompt, config: { responseMimeType: "application/json", responseSchema: { type: Type.OBJECT, properties: { items: { type: Type.ARRAY, description: "List of products and their quantities.", items: { type: Type.OBJECT, properties: { productName: { type: Type.STRING, description: "The name of the product mentioned. Must exactly match one of the available product names.", }, quantity: { type: Type.INTEGER, description: "The quantity of the product.", } }, required: ["productName", "quantity"] } }, payment: { type: Type.NUMBER, description: "The payment amount in numbers, e.g., 100. If not mentioned, return 0." } }, required: ["items", "payment"] }, temperature: 0.1, } }); if (!response.text()) { throw new Error("AI response was empty."); } const jsonStr = response.text().trim(); const parsedData = JSON.parse(jsonStr) as ParsedSaleFromAI; // Basic validation if (parsedData && Array.isArray(parsedData.items) && typeof parsedData.payment === 'number') { return parsedData; } else { throw new Error("AI response is not in the expected format for a sale."); } } catch (error) { console.error("Error parsing sale from voice:", error); throw new Error("Failed to parse sale from AI."); } }; export const getAIAssistantResponse = async ( query: string, history: { text: string; sender: 'user' | 'ai' }[], sales: Sale[], expenses: Expense[], products: Product[] ): Promise<string> => { if (!ai) return Promise.reject(new Error("API key not configured.")); // Prepare a compact version of the data for the prompt const latestSales = sales.slice(0, 50).map(s => ({ date: s.date, total: s.total, items: s.items.length })); const latestExpenses = expenses.slice(0, 20).map(e => ({ date: e.date, amount: e.amount, description: e.description.slice(0, 20) })); const productList = products.map(p => ({ name: p.name, price: p.price, stock: p.stock, category: p.category })); const systemInstruction = ` You are a helpful business assistant for a Point of Sale (POS) system called "Charnoks". Your role is to analyze the provided business data and answer the user's questions in a clear, concise, and friendly manner. You should provide actionable insights, summaries, and answers based ONLY on the data given. When asked for suggestions, be creative but realistic for a small business. Format your answers using markdown for better readability (e.g., use lists, bold text). If you are asked a question you cannot answer with the given data, politely say so. Here is the current business data: - Products: ${JSON.stringify(productList)} - Recent Sales (up to 50): ${JSON.stringify(latestSales)} - Recent Expenses (up to 20): ${JSON.stringify(latestExpenses)} `; // Format chat history for Gemini const geminiHistory = history.map(msg => ({ role: msg.sender === 'user' ? 'user' : 'model', parts: [{ text: msg.text }] })); try { const response: GenerateContentResponse = await ai.models.generateContent({ model: "gemini-2.0-flash", contents: [...geminiHistory, { role: 'user', parts: [{ text: query }] }], config: { systemInstruction: systemInstruction, temperature: 0.7, } }); if (!response.text()) { throw new Error("AI assistant did not provide a response."); } return response.text(); } catch (error) { console.error("Error fetching AI assistant response:", error); throw new Error("Failed to get response from AI assistant."); } };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PSYGER02/mcpserver'

If you have feedback or need assistance with the MCP directory API, please join our Discord server