import { GoogleGenAI, Type } from "@google/genai"; import { Phase1Data, Phase2Data, Phase3Data, Phase4Data, Phase6Data, Language } from "./types"; const getSystemInstruction = (lang: Language) => { if (lang === 'de') { return ` # IDENTITY & PURPOSE Du bist die "GTM Architect Engine" für Roboplanet. Deine Aufgabe ist es, für neue technische Produkte (Roboter) eine präzise Go-to-Market-Strategie zu entwickeln. Du handelst nicht als kreativer Werbetexter, sondern als strategischer Analyst. Dein oberstes Ziel ist Product-Market-Fit und operative Umsetzbarkeit. Antworte IMMER auf DEUTSCH. # CONTEXT: THE PARENT COMPANY (WACKLER) Wir sind Teil der Wackler Group, einem großen Facility-Management-Dienstleister. Unsere Strategie ist NICHT "Roboter ersetzen Menschen", sondern "Hybrid-Reinigung": - 80% der Arbeit (monotone Flächenleistung) = Roboter. - 20% der Arbeit (Edge Cases, Winterdienst, Treppen, Grobschmutz) = Manuelle Reinigung durch Wackler. # STRICT ANALYSIS RULES (MUST FOLLOW): 1. TECHNICAL FACT-CHECK (Keine Halluzinationen): - Analysiere technische Daten extrem konservativ. - Vakuumsystem = Kein "Winterdienst" (Schnee) und keine "Schwerindustrie" (Metallspäne), außer explizit genannt. - Erfinde keine Features, nur um eine Zielgruppe passend zu machen. 2. REGULATORY LOGIC (StVO-Check): - Wenn Vmax < 20 km/h: Schließe "Öffentliche Städte/Kommunen/Straßenreinigung" kategorisch aus (Verkehrshindernis). - Fokusänderung: Konzentriere dich stattdessen ausschließlich auf "Große, zusammenhängende Privatflächen" (Gated Areas). 3. STRATEGIC TARGETING (Use-Case-Logik): - Priorisiere Cluster A (Efficiency): Logistikzentren & Industrie-Hubs (24/7 Betrieb, Sicherheit). - Priorisiere Cluster B (Experience): Shopping Center, Outlets & Freizeitparks (Sauberkeit als Visitenkarte). - Entferne reine E-Commerce-Händler ohne physische Kundenfläche. 4. THE "HYBRID SERVICE" LOGIC (RULE 5): Wann immer du ein "Hartes Constraint" oder eine technische Limitierung identifizierst (z.B. "Kein Winterdienst" oder "Kommt nicht in Ecken"), darfst du dies niemals als reines "Nein" stehen lassen. Wende stattdessen die **"Yes, and..." Logik** an: 1. **Identifiziere die Lücke:** (z.B. "Roboter kann bei Schnee nicht fahren"). 2. **Fülle die Lücke mit Service:** Schlage explizit vor, diesen Teil durch "Wackler Human Manpower" abzudecken. 3. **Formuliere den USP:** Positioniere das Gesamtpaket als "100% Coverage" (Roboter + Mensch aus einer Hand). # THE PRODUCT MATRIX (CONTEXT) Behalte immer im Hinterkopf, dass wir bereits folgende Produkte im Portfolio haben: 1. "Indoor Scrubber 50": Innenreinigung, Hartboden, Fokus: Supermärkte. Message: "Sauberkeit im laufenden Betrieb." 2. "Service Bot Bella": Service/Gastro, Indoor. Fokus: Restaurants. Message: "Entlastung für Servicekräfte." `; } else { return ` # IDENTITY & PURPOSE You are the "GTM Architect Engine" for Roboplanet. Your task is to develop a precise Go-to-Market strategy for new technical products (robots). You do not act as a creative copywriter, but as a strategic analyst. Your top goal is product-market fit and operational feasibility. ALWAYS respond in ENGLISH. # CONTEXT: THE PARENT COMPANY (WACKLER) We are part of the Wackler Group, a major facility management service provider. Our strategy is NOT "Robots replace humans", but "Hybrid Cleaning": - 80% of work (monotonous area coverage) = Robots. - 20% of work (Edge cases, winter service, stairs, heavy debris) = Manual cleaning by Wackler. # STRICT ANALYSIS RULES (MUST FOLLOW): 1. TECHNICAL FACT-CHECK (No Hallucinations): - Analyze technical data extremely conservatively. - Vacuum System = No "Winter Service" (snow) and no "Heavy Industry" (metal shavings), unless explicitly stated. - Do not invent features just to fit a target audience. 2. REGULATORY LOGIC (Traffic Regs): - If Vmax < 20 km/h: Categorically exclude "Public Cities/Streets" (traffic obstruction). - Change Focus: Concentrate exclusively on "Large, contiguous private areas" (Gated Areas). 3. STRATEGIC TARGETING (Use Case Logic): - Prioritize Cluster A (Efficiency): Logistics Centers & Industrial Hubs (24/7 ops, safety). - Prioritize Cluster B (Experience): Shopping Centers, Outlets & Theme Parks (Cleanliness as a calling card). - Remove pure E-commerce retailers without physical customer areas. 4. THE "HYBRID SERVICE" LOGIC (RULE 5): Whenever you identify a "Hard Constraint" or technical limitation (e.g., "No winter service" or "Cannot reach corners"), never let this stand as a simple "No". Instead, apply the **"Yes, and..." logic**: 1. **Identify the gap:** (e.g., "Robot cannot operate in snow"). 2. **Fill the gap with service:** Explicitly suggest covering this part with "Wackler Human Manpower". 3. **Formulate the USP:** Position the total package as "100% Coverage" (Robot + Human from a single source). # THE PRODUCT MATRIX (CONTEXT) Always keep in mind that we already have the following products in our portfolio: 1. "Indoor Scrubber 50": Indoor cleaning, hard floor, supermarkets. 2. "Service Bot Bella": Service/Hospitality, indoor. Focus: restaurants. Message: "Relief for service staff." `; } }; const getClient = () => { const apiKey = process.env.API_KEY; if (!apiKey) { throw new Error("API_KEY not found in environment variables"); } return new GoogleGenAI({ apiKey }); }; // Helper to safely parse JSON from AI response const cleanAndParseJson = (text: string | undefined): T => { if (!text) throw new Error("AI returned empty response"); let cleaned = text.trim(); // Remove markdown formatting if present if (cleaned.startsWith('```json')) { cleaned = cleaned.replace(/^```json\n?/, '').replace(/\n?```$/, ''); } else if (cleaned.startsWith('```')) { cleaned = cleaned.replace(/^```\n?/, '').replace(/\n?```$/, ''); } try { return JSON.parse(cleaned); } catch (e) { console.error("JSON Parse Error. Raw text:", text); throw new Error("Failed to parse AI response. The model output was likely malformed or truncated. Please try again with shorter input."); } }; // --- Phase 1: Product Analysis --- export const analyzeProduct = async (input: string, lang: Language): Promise => { const ai = getClient(); const sysInstr = getSystemInstruction(lang); // STEP 1.1: Extract Specs & Constraints const extractionPrompt = lang === 'de' ? ` PHASE 1-A: TECHNICAL EXTRACTION Input Product Description: "${input.substring(0, 25000)}..." Aufgabe: 1. Extrahiere technische Hauptmerkmale (Specs, Fähigkeiten). 2. Leite "Harte Constraints" ab. WICHTIG: Prüfe Vmax (<20km/h = Privatgelände) und Reinigungstyp (Vakuum != Grobschmutz/Schnee). 3. Erstelle eine kurze Rohanalyse-Zusammenfassung. Output JSON format ONLY. ` : ` PHASE 1-A: TECHNICAL EXTRACTION Input Product Description: "${input.substring(0, 25000)}..." Task: 1. Extract key technical features (specs, capabilities). 2. Derive "Hard Constraints". IMPORTANT: Check Vmax (<20km/h = Private Grounds) and Cleaning Type (Vacuum != Heavy Debris/Snow). 3. Create a short raw analysis summary. Output JSON format ONLY. `; const extractionResponse = await ai.models.generateContent({ model: 'gemini-3-flash-preview', contents: extractionPrompt, config: { systemInstruction: sysInstr, responseMimeType: "application/json", responseSchema: { type: Type.OBJECT, properties: { features: { type: Type.ARRAY, items: { type: Type.STRING } }, constraints: { type: Type.ARRAY, items: { type: Type.STRING } }, rawAnalysis: { type: Type.STRING } } } } }); const extractionData = cleanAndParseJson<{ features: string[]; constraints: string[]; rawAnalysis: string; }>(extractionResponse.text); // STEP 1.2: Portfolio Conflict Check const conflictPrompt = lang === 'de' ? ` PHASE 1-B: PORTFOLIO CONFLICT CHECK Neue Produkt-Features: ${JSON.stringify(extractionData.features)} Neue Produkt-Constraints: ${JSON.stringify(extractionData.constraints)} Existierendes Portfolio: 1. "Indoor Scrubber 50": Innenreinigung, Hartboden, Supermärkte. 2. "Service Bot Bella": Service/Gastro, Indoor, Restaurants. Aufgabe: Prüfe, ob das neue Produkt signifikant mit bestehenden Produkten überlappt (Ist es nur ein Klon?). Output JSON format ONLY. ` : ` PHASE 1-B: PORTFOLIO CONFLICT CHECK New Product Features: ${JSON.stringify(extractionData.features)} New Product Constraints: ${JSON.stringify(extractionData.constraints)} Existing Portfolio: 1. "Indoor Scrubber 50": Indoor cleaning, hard floor, supermarkets. 2. "Service Bot Bella": Service/Gastro, indoor, restaurants. Task: Check if the new product overlaps significantly with existing ones (is it just a clone?). Output JSON format ONLY. `; const conflictResponse = await ai.models.generateContent({ model: 'gemini-3-flash-preview', contents: conflictPrompt, config: { systemInstruction: sysInstr, responseMimeType: "application/json", responseSchema: { type: Type.OBJECT, properties: { conflictCheck: { type: Type.OBJECT, properties: { hasConflict: { type: Type.BOOLEAN }, details: { type: Type.STRING }, relatedProduct: { type: Type.STRING, nullable: true } } } } } } }); const conflictData = cleanAndParseJson<{ conflictCheck: Phase1Data['conflictCheck']; }>(conflictResponse.text); return { ...extractionData, ...conflictData }; }; // --- Phase 2: ICP Discovery --- export const discoverICPs = async (phase1Data: Phase1Data, lang: Language): Promise => { const ai = getClient(); const sysInstr = getSystemInstruction(lang); const prompt = lang === 'de' ? ` PHASE 2: ICP DISCOVERY & DATA PROXIES Basierend auf Features: ${JSON.stringify(phase1Data.features)} Und Constraints: ${JSON.stringify(phase1Data.constraints)} Aufgabe: 1. Negative Selektion: Welche Branchen sind unmöglich? (Denke an Vmax & Vakuum-Regeln!) 2. High Pain: Identifiziere Cluster A (Logistik/Industrie) und Cluster B (Shopping/Outlets). 3. Data Proxy Generierung: Wie finden wir diese digital (z.B. Satellit, Register)? Output JSON format ONLY: { "icps": [ { "name": "Branchen Name", "rationale": "Warum das passt (max 1 Satz)" } ], "dataProxies": [ { "target": "Spezifisches Kriterium", "method": "Wie zu finden (z.B. Scraping)" } ] } ` : ` PHASE 2: ICP DISCOVERY & DATA PROXIES Based on the product features: ${JSON.stringify(phase1Data.features)} And constraints: ${JSON.stringify(phase1Data.constraints)} Task: 1. Negative Selection: Which industries are impossible? (Remember Vmax & Vacuum rules!) 2. High Pain: Identify Cluster A (Logistics/Industry) and Cluster B (Shopping/Outlets). 3. Data Proxy Generation: How to find them digitally via data traces (e.g. satellite, registries). Output JSON format ONLY: { "icps": [ { "name": "Industry Name", "rationale": "Why this is a good fit (max 1 sentence)" } ], "dataProxies": [ { "target": "Specific criteria", "method": "How to find (e.g. scraping, satellite)" } ] } `; const response = await ai.models.generateContent({ model: 'gemini-3-flash-preview', contents: prompt, config: { systemInstruction: sysInstr, responseMimeType: "application/json", } }); return cleanAndParseJson(response.text); }; // --- Phase 3: Whale Hunting --- export const huntWhales = async (phase2Data: Phase2Data, lang: Language): Promise => { const ai = getClient(); const sysInstr = getSystemInstruction(lang); const prompt = lang === 'de' ? ` PHASE 3: WHALE HUNTING Target ICPs (Branchen): ${JSON.stringify(phase2Data.icps)} Aufgabe: 1. Gruppiere die "Whales" (Key Accounts) strikt nach den identifizierten ICP-Branchen. 2. Identifiziere pro Branche 3-5 konkrete Top-Unternehmen im DACH Markt. 3. Definiere die Buying Center Rollen. Output JSON format ONLY: { "whales": [ { "industry": "Name der ICP Branche", "accounts": ["Firma A", "Firma B"] } ], "roles": ["Jobtitel 1", "Jobtitel 2"] } ` : ` PHASE 3: WHALE HUNTING Target ICPs (Industries): ${JSON.stringify(phase2Data.icps)} Task: 1. Group "Whales" (Key Accounts) strictly by the identified ICP industries. 2. Identify 3-5 concrete top companies in the DACH market per industry. 3. Define Buying Center Roles. Output JSON format ONLY: { "whales": [ { "industry": "Name of ICP Industry", "accounts": ["Company A", "Company B"] } ], "roles": ["Job Title 1", "Job Title 2"] } `; const response = await ai.models.generateContent({ model: 'gemini-3-flash-preview', contents: prompt, config: { systemInstruction: sysInstr, responseMimeType: "application/json", } }); return cleanAndParseJson(response.text); }; // --- Phase 4: Strategy --- export const developStrategy = async (phase3Data: Phase3Data, phase1Data: Phase1Data, lang: Language): Promise => { const ai = getClient(); const sysInstr = getSystemInstruction(lang); // Flatten accounts for the prompt to maintain context without complex structure const allAccounts = phase3Data.whales.flatMap(w => w.accounts); const prompt = lang === 'de' ? ` PHASE 4: STRATEGY & ANGLE DEVELOPMENT Accounts: ${JSON.stringify(allAccounts)} Target Industries: ${JSON.stringify(phase3Data.whales.map(w => w.industry))} Features: ${JSON.stringify(phase1Data.features)} Aufgabe: 1. Entwickle einen spezifischen "Angle" (Aufhänger) pro Zielgruppe/Industrie. 2. Consistency Check gegen Product Matrix (Differenzierung zu Scrubber 50/Bella). 3. **WICHTIG:** Wende die "Hybrid Service Logic" an, wenn technische Constraints existieren! (Wackler Manpower als Ergänzung). Output JSON format ONLY: { "strategyMatrix": [ { "segment": "Zielsegment", "painPoint": "Spezifischer Schmerz (Kurz)", "angle": "Unser Marketing Angle (Kurz)", "differentiation": "Wie es sich unterscheidet (Kurz)" } ] } ` : ` PHASE 4: STRATEGY & ANGLE DEVELOPMENT Accounts: ${JSON.stringify(allAccounts)} Target Industries: ${JSON.stringify(phase3Data.whales.map(w => w.industry))} Product Features: ${JSON.stringify(phase1Data.features)} Task: 1. Develop specific "Angle" per target/industry. 2. Consistency Check against Product Matrix (ensure differentiation from Scrubber 50/Bella). 3. **IMPORTANT:** Apply "Hybrid Service Logic" if technical constraints exist! (Wackler Manpower as supplement). Output JSON format ONLY: { "strategyMatrix": [ { "segment": "Target Segment", "painPoint": "Specific Pain (Short)", "angle": "Our Marketing Angle (Short)", "differentiation": "How it differs (Short)" } ] } `; const response = await ai.models.generateContent({ model: 'gemini-3-flash-preview', contents: prompt, config: { systemInstruction: sysInstr, responseMimeType: "application/json", } }); return cleanAndParseJson(response.text); }; // --- Phase 5: Assets & Report --- export const generateAssets = async ( phase4Data: Phase4Data, phase3Data: Phase3Data, phase2Data: Phase2Data, phase1Data: Phase1Data, lang: Language ): Promise => { const ai = getClient(); const sysInstr = getSystemInstruction(lang); const prompt = lang === 'de' ? ` PHASE 5: ASSET GENERATION & FINAL REPORT CONTEXT DATA: - Technical: ${JSON.stringify(phase1Data)} - ICPs: ${JSON.stringify(phase2Data)} - Targets (Whales): ${JSON.stringify(phase3Data)} - Strategy: ${JSON.stringify(phase4Data)} AUFGABE: 1. Erstelle einen "GTM STRATEGY REPORT" in Markdown. 2. Struktur des Reports: - Executive Summary - Produkt Analyse (Features & Constraints) - Zielgruppen (ICPs & Data Proxies) - **Target Accounts (Whales) & Buying Center (DIESE SEKTION MUSS ENTHALTEN SEIN UND ALLE ACCOUNTS GRUPPIERT AUFLISTEN!)** - Strategie-Matrix (Pains & Angles) - Assets (LinkedIn, Email, Landing Page Headline) 3. Self-Correction: Entferne "Marketing Bla-Bla". Nutze Fakten, TCO, ROI. 4. Hybrid-Check: Stelle sicher, dass die "Hybrid Service Logic" (Wackler Manpower) in der Strategie und den Assets sichtbar ist, falls Constraints vorliegen. Output: Gib striktes MARKDOWN zurück. Beginne mit "# GTM STRATEGY REPORT". ` : ` PHASE 5: ASSET GENERATION & FINAL REPORT CONTEXT DATA: - Technical: ${JSON.stringify(phase1Data)} - ICPs: ${JSON.stringify(phase2Data)} - Targets (Whales): ${JSON.stringify(phase3Data)} - Strategy: ${JSON.stringify(phase4Data)} TASK: 1. Create a "GTM STRATEGY REPORT" in Markdown. 2. Report Structure: - Executive Summary - Product Analysis (Features & Constraints) - Target Audience (ICPs & Data Proxies) - **Target Accounts (Whales) & Buying Center (THIS SECTION MUST BE INCLUDED AND LIST ALL ACCOUNTS GROUPED BY INDUSTRY!)** - Strategy Matrix (Pains & Angles) - Assets (LinkedIn, Email, Landing Page Headline) 3. Self-Correction: Remove "Marketing Fluff". Use Facts, TCO, ROI. 4. Hybrid-Check: Ensure "Hybrid Service Logic" (Wackler Manpower) is visible in strategy and assets if constraints exist. Output: Return strictly MARKDOWN formatted text. Start with "# GTM STRATEGY REPORT". `; const response = await ai.models.generateContent({ model: 'gemini-3-flash-preview', contents: prompt, config: { systemInstruction: sysInstr, } }); return response.text || "Error generating assets."; }; // --- Phase 6: Sales Enablement & Visuals --- export const generateSalesEnablement = async ( phase4Data: Phase4Data, phase3Data: Phase3Data, phase1Data: Phase1Data, lang: Language ): Promise => { const ai = getClient(); const sysInstr = getSystemInstruction(lang); const prompt = lang === 'de' ? ` PHASE 6: SALES ENABLEMENT & VISUALS (THE CLOSING KIT) CONTEXT: - Produkt Features: ${JSON.stringify(phase1Data.features)} - Accounts (Personas): ${JSON.stringify(phase3Data.roles)} - Strategie: ${JSON.stringify(phase4Data.strategyMatrix)} AUFGABE: 1. **Anticipate Friction:** Versetze dich in die Lage der identifizierten Personas. Was ist deren härtester Einwand? (z.B. "Zu teuer", "Sicherheitsrisiko", "Job-Verlust"). 2. **Deflect & Solve:** Formuliere eine präzise, faktenbasierte Antwort für den Vertriebler (Battlecard). Nutze Hybrid-Argumente falls nötig. 3. **Visual Context:** Erstelle präzise "Text-to-Image Prompts" (für Midjourney/DALL-E), die das Produkt im perfekten Nutzungskontext zeigen. Output JSON format ONLY: { "battlecards": [ { "persona": "Rolle (z.B. Logistikleiter)", "objection": "Härtester Einwand als Zitat", "responseScript": "Die Antwort (Argumentation, Fakten, ROI)" } ], "visualPrompts": [ { "title": "Titel (z.B. Efficiency Shot)", "context": "Cluster A/B Kontext", "prompt": "Detaillierter Prompt Code für Midjourney..." } ] } Erstelle mindestens 2 Battlecards und 3 Visual Prompts (Efficiency, Experience, Detail). ` : ` PHASE 6: SALES ENABLEMENT & VISUALS (THE CLOSING KIT) CONTEXT: - Product Features: ${JSON.stringify(phase1Data.features)} - Accounts (Personas): ${JSON.stringify(phase3Data.roles)} - Strategy: ${JSON.stringify(phase4Data.strategyMatrix)} TASK: 1. **Anticipate Friction:** Put yourself in the shoes of the personas. What is their hardest objection? (e.g. "Too expensive", "Safety risk", "Job loss"). 2. **Deflect & Solve:** Formulate a precise, fact-based response for sales (Battlecard). Use hybrid arguments if necessary. 3. **Visual Context:** Create precise "Text-to-Image Prompts" (for Midjourney/DALL-E) showing the product in the perfect context. Output JSON format ONLY: { "battlecards": [ { "persona": "Role (e.g. Logistics Manager)", "objection": "Hardest objection as quote", "responseScript": "The response (Argumentation, Facts, ROI)" } ], "visualPrompts": [ { "title": "Title (e.g. Efficiency Shot)", "context": "Cluster A/B Context", "prompt": "Detailed Prompt Code for Midjourney..." } ] } Create at least 2 Battlecards and 3 Visual Prompts (Efficiency, Experience, Detail). `; const response = await ai.models.generateContent({ model: 'gemini-3-flash-preview', contents: prompt, config: { systemInstruction: sysInstr, responseMimeType: "application/json", } }); return cleanAndParseJson(response.text); }; export const generateConceptImage = async (prompt: string, referenceImagesBase64?: string[]): Promise => { const ai = getClient(); try { const parts: any[] = []; // If reference images are provided, add them to parts if (referenceImagesBase64 && referenceImagesBase64.length > 0) { referenceImagesBase64.forEach(img => { const cleanBase64 = img.split(',')[1] || img; const match = img.match(/data:(.*?);base64/); const mimeType = match ? match[1] : 'image/png'; parts.push({ inlineData: { mimeType: mimeType, data: cleanBase64 } }); }); parts.push({ text: "Show the product in these reference images in the following context. Keep the product appearance consistent. " + prompt }); } else { // Text-only prompt parts.push({ text: prompt }); } const response = await ai.models.generateContent({ model: 'gemini-2.5-flash-image', contents: { parts }, config: { imageConfig: { aspectRatio: "16:9", } } }); for (const part of response.candidates?.[0]?.content?.parts || []) { if (part.inlineData) { return `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`; } } throw new Error("No image generated"); } catch (e: any) { console.error("Image generation failed", e); throw new Error(e.message || "Failed to generate image"); } };