Refactor GTM Architect to v2: Python-driven architecture, 9-phase process, new DB and Docker setup

This commit is contained in:
2026-01-02 19:00:05 +00:00
parent d59888c889
commit 8439a7fb10
302 changed files with 68130 additions and 4782 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +1,40 @@
# Stage 1: Build the React frontend
FROM node:20-slim AS frontend-builder
FROM node:18-slim AS builder
WORKDIR /app
# Copy package.json and install dependencies
# We are inside the build context root, so path is gtm-architect/package.json
COPY gtm-architect/package.json ./
RUN npm install
# Copy the source code
COPY gtm-architect/ .
# Build the application
COPY package.json ./
# Use --force to avoid issues with dependency versions
RUN npm install --force
COPY . .
RUN npm run build
# ---
# Stage 2: Final application image
FROM python:3.11-slim
# Stage 2: Setup the production environment
FROM python:3.9-slim
WORKDIR /app
# Install Node.js (minimal runtime)
RUN apt-get update && \
apt-get install -y --no-install-recommends curl ca-certificates && \
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y --no-install-recommends nodejs && \
rm -rf /var/lib/apt/lists/*
# Install Node.js
RUN apt-get update && apt-get install -y curl && \
curl -sL https://deb.nodesource.com/setup_18.x | bash - && \
apt-get install -y nodejs
# Install Python dependencies
# We use the requirements from the subdirectory
COPY gtm-architect/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy built frontend from builder stage
COPY --from=builder /app/dist ./gtm-architect/dist
# Google Generative AI Library installieren
RUN pip install --no-cache-dir google-generativeai
# Copy the Node.js server script and package.json for runtime deps
COPY gtm-architect/server.cjs .
COPY gtm-architect/package.json .
# Install only production Node.js dependencies
RUN npm install --omit=dev
# Copy the built frontend from the builder stage
COPY --from=frontend-builder /app/dist ./dist
# Copy the Python Orchestrator and shared modules
# Copy backend files and application code
COPY gtm-architect/server.cjs ./gtm-architect/
COPY gtm-architect/package.json ./gtm-architect/
COPY gtm_architect_orchestrator.py .
COPY gtm_prompts.json .
COPY helpers.py .
COPY config.py .
COPY market_db_manager.py .
COPY gtm-architect/requirements.txt .
COPY gtm_db_manager.py .
# Copy API Key if available (handled by docker-compose usually, but good for standalone)
# COPY gemini_api_key.txt .
# Expose port
# Install Python and Node.js dependencies
RUN pip install --no-cache-dir -r requirements.txt
RUN cd gtm-architect && npm install --production
# Expose the port the server will run on
EXPOSE 3005
# Start the server
CMD ["node", "server.cjs"]
# Command to run the server
CMD ["node", "gtm-architect/server.cjs"]

View File

@@ -1,21 +1,20 @@
# GTM Architect Frontend
<div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
</div>
Dies ist das React-Frontend für die GTM Architect Engine.
# Run and deploy your AI Studio app
## Tech Stack
* **React 19**
* **Vite**
* **TypeScript**
* **Tailwind CSS**
* **Lucide React** (Icons)
This contains everything you need to run your app locally.
## Entwicklung
Das Frontend ist darauf ausgelegt, im Docker-Container `gtm-app` gebaut und serviert zu werden.
Es kommuniziert ausschließlich mit dem lokalen Backend unter `/api/gtm`.
View your app in AI Studio: https://ai.studio/apps/drive/1bvzSOz-NYMzDph6718RuAy1mnCSjjylz
## Struktur
* `App.tsx`: Hauptlogik und State-Management.
* `components/`: UI-Komponenten (Layout).
* `types.ts`: TypeScript Definitionen für die API-Payloads.
## Run Locally
Für die vollständige Systemdokumentation siehe `../gtm_architect_documentation.md`.
**Prerequisites:** Node.js
1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Phase, Language, Theme } from '../types';
import { Activity, Target, Crosshair, Map, FileText, CheckCircle, Lock, Moon, Sun, Languages, ShieldCheck, Terminal } from 'lucide-react';
import { Activity, Target, Crosshair, Map, FileText, CheckCircle, Lock, Moon, Sun, Languages, ShieldCheck, Terminal, LayoutTemplate, TrendingUp, Shield } from 'lucide-react';
interface LayoutProps {
currentPhase: Phase;
@@ -23,6 +23,9 @@ const getStepIcon = (id: Phase) => {
case Phase.Strategy: return Map;
case Phase.AssetGeneration: return FileText;
case Phase.SalesEnablement: return ShieldCheck;
case Phase.LandingPage: return LayoutTemplate;
case Phase.BusinessCase: return TrendingUp;
case Phase.TechTranslator: return Shield;
default: return Activity;
}
}
@@ -40,13 +43,16 @@ export const Layout: React.FC<LayoutProps> = ({
}) => {
const steps = [
{ id: Phase.Input, label: labels.initTitle ? 'Input' : 'Input' }, // Fallback label
{ id: Phase.Input, label: labels.initTitle ? 'Input' : 'Input' },
{ id: Phase.ProductAnalysis, label: labels.phase1 },
{ id: Phase.ICPDiscovery, label: labels.phase2 },
{ id: Phase.WhaleHunting, label: labels.phase3 },
{ id: Phase.Strategy, label: labels.phase4 },
{ id: Phase.AssetGeneration, label: labels.phase5 },
{ id: Phase.SalesEnablement, label: labels.phase6 },
{ id: Phase.LandingPage, label: labels.phase7 },
{ id: Phase.BusinessCase, label: labels.phase8 },
{ id: Phase.TechTranslator, label: labels.phase9 },
];
return (

View File

@@ -1,404 +1,48 @@
import { GoogleGenAI, Type } from "@google/genai";
import { Phase1Data, Phase2Data, Phase3Data, Phase4Data, Phase6Data, Language } from "./types";
import { Phase1Data, Phase2Data, Phase3Data, Phase4Data, Phase6Data, Phase7Data, Phase8Data, Phase9Data, 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.
const API_BASE_URL = '/api'; // Using relative path to proxy through Vite/NGINX
# 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.
const callApi = async <T>(endpoint: string, mode: string, payload: object): Promise<T> => {
try {
const response = await fetch(`${API_BASE_URL}/${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ mode, ...payload }),
});
# 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."
`;
}
if (!response.ok) {
const errorBody = await response.text();
console.error(`API call failed with status ${response.status}:`, errorBody);
throw new Error(`Request failed: ${response.statusText}`);
}
return await response.json() as T;
} catch (error) {
console.error('An error occurred during the API call:', error);
throw new Error('API call failed. Check the server logs for more details.');
}
};
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 = <T>(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<Phase1Data> => {
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
};
export const analyzeProduct = async (productInput: string, lang: Language): Promise<Phase1Data> => {
return callApi<Phase1Data>('run', 'phase1', { productInput, lang });
};
// --- Phase 2: ICP Discovery ---
export const discoverICPs = async (phase1Data: Phase1Data, lang: Language): Promise<Phase2Data> => {
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<Phase2Data>(response.text);
return callApi<Phase2Data>('run', 'phase2', { phase1Data, lang });
};
// --- Phase 3: Whale Hunting ---
export const huntWhales = async (phase2Data: Phase2Data, lang: Language): Promise<Phase3Data> => {
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<Phase3Data>(response.text);
return callApi<Phase3Data>('run', 'phase3', { phase2Data, lang });
};
// --- Phase 4: Strategy ---
export const developStrategy = async (phase3Data: Phase3Data, phase1Data: Phase1Data, lang: Language): Promise<Phase4Data> => {
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<Phase4Data>(response.text);
return callApi<Phase4Data>('run', 'phase4', { phase3Data, phase1Data, lang });
};
// --- Phase 5: Assets & Report ---
@@ -408,67 +52,8 @@ export const generateAssets = async (
phase2Data: Phase2Data,
phase1Data: Phase1Data,
lang: Language
): Promise<string> => {
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.";
): Promise<{ report: string }> => {
return callApi<{ report: string }>('run', 'phase5', { phase4Data, phase3Data, phase2Data, phase1Data, lang });
};
// --- Phase 6: Sales Enablement & Visuals ---
@@ -478,131 +63,40 @@ export const generateSalesEnablement = async (
phase1Data: Phase1Data,
lang: Language
): Promise<Phase6Data> => {
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<Phase6Data>(response.text);
return callApi<Phase6Data>('run', 'phase6', { phase4Data, phase3Data, phase1Data, lang });
};
export const generateConceptImage = async (prompt: string, referenceImagesBase64?: string[]): Promise<string> => {
const ai = getClient();
try {
const parts: any[] = [];
// --- Phase 7: Vertical Landing Page Copy ---
export const generateLandingPageCopy = async (
phase4Data: Phase4Data,
phase2Data: Phase2Data,
lang: Language
): Promise<Phase7Data> => {
return callApi<Phase7Data>('run', 'phase7', { phase4Data, phase2Data, lang });
};
// 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 });
}
// --- Phase 8: Business Case Builder ---
export const buildBusinessCase = async (
phase2Data: Phase2Data,
phase1Data: Phase1Data,
lang: Language
): Promise<Phase8Data> => {
return callApi<Phase8Data>('run', 'phase8', { phase2Data, phase1Data, lang });
};
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash-image',
contents: { parts },
config: {
imageConfig: {
aspectRatio: "16:9",
}
}
});
// --- Phase 9: Feature-to-Value Translator ---
export const translateTech = async (
phase1Data: Phase1Data,
phase4Data: Phase4Data,
lang: Language
): Promise<Phase9Data> => {
return callApi<Phase9Data>('run', 'phase9', { phase1Data, phase4Data, lang });
};
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");
}
export const translateReportToEnglish = async (reportMarkdown: string): Promise<{ report: string }> => {
return callApi<{ report: string }>('run', 'translate', { reportMarkdown });
};
export const generateConceptImage = async (prompt: string, referenceImagesBase64?: string[]): Promise<{ imageBase64: string }> => {
return callApi<{ imageBase64: string }>('run', 'image', { prompt, referenceImagesBase64 });
};

View File

@@ -82,10 +82,10 @@
}
}
</script>
<link rel="stylesheet" href="./index.css">
<link rel="stylesheet" href="/index.css">
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.tsx"></script>
<script type="module" src="/index.tsx"></script>
</body>
</html>

View File

@@ -9,13 +9,13 @@
"preview": "vite preview"
},
"dependencies": {
"@google/genai": "^1.34.0",
"react": "^19.2.3",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.0",
"react-dom": "^19.2.3",
"lucide-react": "^0.562.0",
"express": "^4.18.2"
"express": "^4.19.2",
"cors": "^2.8.5"
},
"devDependencies": {
"@types/node": "^22.14.0",

View File

@@ -1,11 +1,5 @@
openai==0.28.1
pandas
gspread
oauth2client
beautifulsoup4
google-generativeai
requests
python-dotenv
tiktoken
thefuzz
serpapi
wikipedia
beautifulsoup4
werkzeug
python-dotenv

View File

@@ -1,93 +1,89 @@
console.log("--- GTM Architect Server starting ---");
const express = require('express');
const { spawn } = require('child_process');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
try {
const express = require('express');
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const app = express();
const port = 3005; // Port for the GTM Architect service
const app = express();
const port = 3005;
app.use(express.json({ limit: '50mb' }));
// Enable CORS for all routes
app.use(cors());
// Determine Environment and Paths
const distPath = path.join(__dirname, 'dist');
const isProduction = fs.existsSync(distPath);
const staticDir = isProduction ? distPath : __dirname;
console.log(`[Init] Serving static files from: ${staticDir}`);
app.use(express.static(staticDir));
// Determine Python Script Path
// In Docker (optimized), script is in same dir. Locally, it might be one level up.
let pythonScriptPath = path.join(__dirname, 'gtm_architect_orchestrator.py');
if (!fs.existsSync(pythonScriptPath)) {
pythonScriptPath = path.join(__dirname, '../gtm_architect_orchestrator.py');
}
console.log(`[Init] Using Python script at: ${pythonScriptPath}`);
function callPythonScript(mode, data, res) {
const dataString = JSON.stringify(data);
const pythonProcess = spawn('python3', [pythonScriptPath, '--mode', mode, '--data', dataString]);
let pythonData = '';
let errorData = '';
pythonProcess.stdout.on('data', (data) => {
pythonData += data.toString();
});
pythonProcess.stderr.on('data', (data) => {
errorData += data.toString();
});
pythonProcess.on('close', (code) => {
if (code !== 0) {
console.error(`Python script exited with code ${code}`);
console.error('Stderr:', errorData);
return res.status(500).json({ error: 'Python script execution failed.', details: errorData });
}
try {
const result = JSON.parse(pythonData);
res.json(result);
} catch (e) {
console.error('Failed to parse Python script output:', e);
console.error('Raw output:', pythonData);
res.status(500).json({ error: 'Failed to parse Python script output.', details: pythonData });
}
});
}
// API endpoint to handle requests from the frontend
app.post('/api/gtm', (req, res) => {
const { mode, data } = req.body;
if (!mode || !data) {
return res.status(400).json({ error: 'Missing mode or data in request body' });
}
callPythonScript(mode, data, res);
});
// Serve the main index.html for any other requests to support client-side routing
app.get('*', (req, res) => {
res.sendFile(path.join(staticDir, 'index.html'));
});
const VERSION = "1.1.1_Fix_20260101_1200_FINAL"; // Add a version for debugging
const server = app.listen(port, () => {
console.log(`GTM Architect server listening at http://localhost:${port} (Version: ${VERSION})`);
});
// Prevent 502 Bad Gateway by increasing Node.js server timeouts to match Nginx
server.setTimeout(600000);
server.keepAliveTimeout = 610000;
server.headersTimeout = 620000;
} catch (e) {
console.error("!!! A CRITICAL ERROR OCCURRED ON STARTUP !!!");
console.error(e);
process.exit(1);
// Serve static files from the 'dist' directory
const staticPath = path.join(__dirname, 'dist');
if (fs.existsSync(staticPath)) {
app.use(express.static(staticPath));
} else {
console.warn(`Static path not found: ${staticPath}. The frontend might not be served correctly. Make sure to build the frontend first.`);
}
app.use(express.json({ limit: '50mb' }));
// API endpoint to run the Python script
app.post('/api/run', (req, res) => {
const { mode, ...payload } = req.body;
if (!mode) {
return res.status(400).json({ error: 'Mode is required' });
}
// Convert payload to a JSON string and then to a Base64 string to ensure safe command line passing
const payloadString = JSON.stringify(payload);
const payloadBase64 = Buffer.from(payloadString).toString('base64');
const pythonScriptPath = path.join(__dirname, '../gtm_architect_orchestrator.py');
const pythonProcess = spawn('python3', [
pythonScriptPath,
'--mode', mode,
'--payload_base64', payloadBase64
]);
let stdoutData = '';
let stderrData = '';
pythonProcess.stdout.on('data', (data) => {
stdoutData += data.toString();
});
pythonProcess.stderr.on('data', (data) => {
stderrData += data.toString();
});
pythonProcess.on('close', (code) => {
if (code !== 0) {
console.error(`Python script exited with code ${code}`);
console.error('Stderr:', stderrData);
return res.status(500).json({
error: 'Python script execution failed.',
stderr: stderrData,
});
}
try {
// The Python script is expected to print JSON to stdout
const result = JSON.parse(stdoutData);
res.json(result);
} catch (e) {
console.error('Failed to parse JSON from Python script stdout:', e);
console.error('Stdout:', stdoutData);
res.status(500).json({
error: 'Failed to parse response from Python script.',
stdout: stdoutData,
});
}
});
});
// Serve the main index.html for any other GET request to support client-side routing
if (fs.existsSync(staticPath)) {
app.get('*', (req, res) => {
res.sendFile(path.join(staticPath, 'index.html'));
});
}
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});

View File

@@ -6,6 +6,9 @@ export enum Phase {
Strategy = 4,
AssetGeneration = 5,
SalesEnablement = 6,
LandingPage = 7,
BusinessCase = 8,
TechTranslator = 9,
}
export type Language = 'en' | 'de';
@@ -68,6 +71,33 @@ export interface Phase6Data {
}[];
}
export interface Phase7Data {
landingPages: {
industry: string;
headline: string;
subline: string;
bullets: string[];
cta: string;
}[];
}
export interface Phase8Data {
businessCases: {
industry: string;
costDriver: string;
efficiencyGain: string;
riskArgument: string;
}[];
}
export interface Phase9Data {
techTranslations: {
feature: string;
story: string;
headline: string;
}[];
}
export interface AppState {
currentPhase: Phase;
isLoading: boolean;
@@ -80,6 +110,10 @@ export interface AppState {
phase4Result?: Phase4Data;
phase5Result?: string; // Markdown content
phase6Result?: Phase6Data;
phase7Result?: Phase7Data;
phase8Result?: Phase8Data;
phase9Result?: Phase9Data;
translatedReport?: string; // New field for the English translation
language: Language;
theme: Theme;
}

View File

@@ -1 +0,0 @@
GEMINI_API_KEY=PLACEHOLDER_API_KEY

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +0,0 @@
<div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
</div>
# Run and deploy your AI Studio app
This contains everything you need to run your app locally.
View your app in AI Studio: https://ai.studio/apps/drive/1bvzSOz-NYMzDph6718RuAy1mnCSjjylz
## Run Locally
**Prerequisites:** Node.js
1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`

View File

@@ -1,158 +0,0 @@
import React from 'react';
import { Phase, Language, Theme } from '../types';
import { Activity, Target, Crosshair, Map, FileText, CheckCircle, Lock, Moon, Sun, Languages, ShieldCheck, Terminal, LayoutTemplate, TrendingUp, Shield } from 'lucide-react';
interface LayoutProps {
currentPhase: Phase;
maxAllowedPhase: Phase;
onPhaseSelect: (phase: Phase) => void;
children: React.ReactNode;
theme: Theme;
toggleTheme: () => void;
language: Language;
setLanguage: (lang: Language) => void;
labels: any;
}
const getStepIcon = (id: Phase) => {
switch(id) {
case Phase.Input: return Terminal;
case Phase.ProductAnalysis: return Activity;
case Phase.ICPDiscovery: return Target;
case Phase.WhaleHunting: return Crosshair;
case Phase.Strategy: return Map;
case Phase.AssetGeneration: return FileText;
case Phase.SalesEnablement: return ShieldCheck;
case Phase.LandingPage: return LayoutTemplate;
case Phase.BusinessCase: return TrendingUp;
case Phase.TechTranslator: return Shield;
default: return Activity;
}
}
export const Layout: React.FC<LayoutProps> = ({
currentPhase,
maxAllowedPhase,
onPhaseSelect,
children,
theme,
toggleTheme,
language,
setLanguage,
labels
}) => {
const steps = [
{ id: Phase.Input, label: labels.initTitle ? 'Input' : 'Input' },
{ id: Phase.ProductAnalysis, label: labels.phase1 },
{ id: Phase.ICPDiscovery, label: labels.phase2 },
{ id: Phase.WhaleHunting, label: labels.phase3 },
{ id: Phase.Strategy, label: labels.phase4 },
{ id: Phase.AssetGeneration, label: labels.phase5 },
{ id: Phase.SalesEnablement, label: labels.phase6 },
{ id: Phase.LandingPage, label: labels.phase7 },
{ id: Phase.BusinessCase, label: labels.phase8 },
{ id: Phase.TechTranslator, label: labels.phase9 },
];
return (
<div className={`min-h-screen flex font-sans transition-colors duration-300 ${theme === 'dark' ? 'dark bg-robo-900 text-slate-200' : 'bg-slate-50 text-slate-900'}`}>
{/* Sidebar */}
<div className="w-80 border-r flex-shrink-0 flex flex-col transition-colors duration-300
bg-white border-slate-200
dark:bg-robo-800 dark:border-robo-700
">
<div className="p-6 border-b transition-colors duration-300 border-slate-200 dark:border-robo-700">
<h1 className="text-xl font-bold font-mono tracking-tighter flex items-center gap-2 text-slate-900 dark:text-white">
<div className="w-3 h-3 bg-robo-500 dark:bg-robo-accent rounded-full animate-pulse"></div>
ROBOPLANET
</h1>
<p className="text-xs mt-1 uppercase tracking-widest text-slate-500 dark:text-robo-400">GTM Architect Engine</p>
</div>
<div className="px-4 py-4 flex gap-2">
<button
onClick={toggleTheme}
className="flex-1 flex items-center justify-center gap-2 p-2 rounded-md text-sm font-medium transition-colors
bg-slate-100 hover:bg-slate-200 text-slate-700
dark:bg-robo-900 dark:hover:bg-robo-700 dark:text-slate-300"
>
{theme === 'light' ? <Moon size={16}/> : <Sun size={16}/>}
{theme === 'light' ? 'Dark' : 'Light'}
</button>
<button
onClick={() => setLanguage(language === 'en' ? 'de' : 'en')}
className="flex-1 flex items-center justify-center gap-2 p-2 rounded-md text-sm font-medium transition-colors
bg-slate-100 hover:bg-slate-200 text-slate-700
dark:bg-robo-900 dark:hover:bg-robo-700 dark:text-slate-300"
>
<Languages size={16}/>
{language.toUpperCase()}
</button>
</div>
<nav className="flex-1 p-4 space-y-2 overflow-y-auto">
{steps.map((step) => {
const isActive = currentPhase === step.id;
const isCompleted = currentPhase > step.id;
const isUnlocked = step.id <= maxAllowedPhase;
const Icon = getStepIcon(step.id);
return (
<button
key={step.id}
onClick={() => isUnlocked && onPhaseSelect(step.id)}
disabled={!isUnlocked}
className={`w-full flex items-center gap-3 p-3 rounded-lg transition-all border text-left
${isActive
? 'bg-blue-50 border-blue-200 text-blue-700 shadow-sm dark:bg-robo-700 dark:border-robo-500 dark:text-white dark:shadow-lg'
: isUnlocked
? 'border-transparent text-slate-600 hover:bg-slate-100 cursor-pointer dark:text-slate-300 dark:hover:bg-robo-700/50'
: 'border-transparent text-slate-300 dark:text-slate-600 cursor-not-allowed'
}
${isCompleted && !isActive ? 'text-emerald-600 dark:text-emerald-400' : ''}
`}
>
<div className={`p-2 rounded-md transition-colors ${
isActive
? 'bg-blue-100 dark:bg-robo-500 text-blue-600 dark:text-white'
: isUnlocked && !isActive
? 'bg-slate-200 dark:bg-robo-900 text-slate-600 dark:text-slate-400'
: 'bg-slate-100 dark:bg-robo-900/50'
}`}>
{isCompleted ? <CheckCircle size={16} /> : <Icon size={16} />}
</div>
<div className="flex-1">
<div className="text-xs font-bold uppercase tracking-wider opacity-70">
{step.id === 0 ? 'Start' : `Phase 0${step.id}`}
</div>
<div className="font-medium text-sm truncate">{step.label}</div>
</div>
{!isUnlocked && <Lock size={12} className="opacity-30" />}
</button>
);
})}
</nav>
<div className="p-4 border-t text-xs font-mono transition-colors duration-300
border-slate-200 text-slate-400
dark:border-robo-700 dark:text-slate-500
">
System Status: ONLINE<br/>
Language: {language.toUpperCase()}<br/>
Mode: {theme.toUpperCase()}
</div>
</div>
{/* Main Content */}
<main className="flex-1 overflow-y-auto relative transition-colors duration-300
bg-slate-50
dark:bg-gradient-to-br dark:from-robo-900 dark:to-[#0b1120]
">
<div className="max-w-5xl mx-auto p-8 pb-32">
{children}
</div>
</main>
</div>
);
};

View File

@@ -1,845 +0,0 @@
import { GoogleGenAI, Type } from "@google/genai";
import { Phase1Data, Phase2Data, Phase3Data, Phase4Data, Phase6Data, Phase7Data, Phase8Data, Phase9Data, 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 = <T>(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<Phase1Data> => {
const ai = getClient();
const sysInstr = getSystemInstruction(lang);
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);
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<Phase2Data> => {
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<Phase2Data>(response.text);
};
// --- Phase 3: Whale Hunting ---
export const huntWhales = async (phase2Data: Phase2Data, lang: Language): Promise<Phase3Data> => {
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<Phase3Data>(response.text);
};
// --- Phase 4: Strategy ---
export const developStrategy = async (phase3Data: Phase3Data, phase1Data: Phase1Data, lang: Language): Promise<Phase4Data> => {
const ai = getClient();
const sysInstr = getSystemInstruction(lang);
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<Phase4Data>(response.text);
};
// --- Phase 5: Assets & Report ---
export const generateAssets = async (
phase4Data: Phase4Data,
phase3Data: Phase3Data,
phase2Data: Phase2Data,
phase1Data: Phase1Data,
lang: Language
): Promise<string> => {
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
- 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
- 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<Phase6Data> => {
const ai = getClient();
const sysInstr = getSystemInstruction(lang);
const prompt = lang === 'de' ? `
PHASE 6: SALES ENABLEMENT & VISUALS
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?
2. **Deflect & Solve:** Formuliere eine präzise, faktenbasierte Antwort für den Vertriebler (Battlecard).
3. **Visual Context:** Erstelle präzise "Text-to-Image Prompts" (für Midjourney/DALL-E).
Output JSON format ONLY:
{
"battlecards": [
{
"persona": "Rolle",
"objection": "Einwand",
"responseScript": "Antwort"
}
],
"visualPrompts": [
{
"title": "Titel",
"context": "Kontext",
"prompt": "Prompt..."
}
]
}
` : `
PHASE 6: SALES ENABLEMENT & VISUALS
CONTEXT:
- Product Features: ${JSON.stringify(phase1Data.features)}
- Accounts (Personas): ${JSON.stringify(phase3Data.roles)}
- Strategy: ${JSON.stringify(phase4Data.strategyMatrix)}
TASK:
1. **Anticipate Friction:** Identify hardest objections.
2. **Deflect & Solve:** Formulate response script (Battlecard).
3. **Visual Context:** Create "Text-to-Image Prompts".
Output JSON format ONLY:
{
"battlecards": [
{
"persona": "Role",
"objection": "Objection",
"responseScript": "Response"
}
],
"visualPrompts": [
{
"title": "Title",
"context": "Context",
"prompt": "Prompt..."
}
]
}
`;
const response = await ai.models.generateContent({
model: 'gemini-3-flash-preview',
contents: prompt,
config: {
systemInstruction: sysInstr,
responseMimeType: "application/json",
}
});
return cleanAndParseJson<Phase6Data>(response.text);
};
// --- Phase 7: Vertical Landing Page Copy ---
export const generateLandingPageCopy = async (
phase4Data: Phase4Data,
phase2Data: Phase2Data,
lang: Language
): Promise<Phase7Data> => {
const ai = getClient();
const sysInstr = getSystemInstruction(lang);
const prompt = lang === 'de' ? `
PHASE 7: VERTICAL LANDING PAGE COPY (Conversion Optimization)
ICPs: ${JSON.stringify(phase2Data.icps)}
Strategy: ${JSON.stringify(phase4Data.strategyMatrix)}
AUFGABE:
1. Identifiziere generische Aussagen und transformiere sie in spezifische Nutzenargumente für die Top 2 ICPs.
2. Wende die "Wackler-Symbiose" an: Erwähne Service/Hygiene als Trust-Element.
Erstelle für die Top 2 ICPs jeweils einen Landingpage-Entwurf (Hero Section).
Output JSON format ONLY:
{
"landingPages": [
{
"industry": "Zielbranche",
"headline": "Nutzenorientierte Headline (Kein Produktname)",
"subline": "Tech Enabler + Wackler Service Garantie",
"bullets": ["Vorteil 1", "Vorteil 2", "Vorteil 3"],
"cta": "Branchen-spezifischer CTA"
}
]
}
` : `
PHASE 7: VERTICAL LANDING PAGE COPY (Conversion Optimization)
ICPs: ${JSON.stringify(phase2Data.icps)}
Strategy: ${JSON.stringify(phase4Data.strategyMatrix)}
TASK:
1. Transform generic features into specific benefits for the Top 2 ICPs.
2. Apply "Wackler Symbiosis": Mention service/hygiene as trust element.
Create Landing Page Drafts (Hero Section) for Top 2 ICPs.
Output JSON format ONLY:
{
"landingPages": [
{
"industry": "Target Industry",
"headline": "Benefit-oriented Headline",
"subline": "Tech Enabler + Wackler Service Guarantee",
"bullets": ["Benefit 1", "Benefit 2", "Benefit 3"],
"cta": "Industry-specific CTA"
}
]
}
`;
const response = await ai.models.generateContent({
model: 'gemini-3-flash-preview',
contents: prompt,
config: {
systemInstruction: sysInstr,
responseMimeType: "application/json",
}
});
return cleanAndParseJson<Phase7Data>(response.text);
};
// --- Phase 8: Business Case Builder ---
export const buildBusinessCase = async (
phase2Data: Phase2Data,
phase1Data: Phase1Data,
lang: Language
): Promise<Phase8Data> => {
const ai = getClient();
const sysInstr = getSystemInstruction(lang);
const prompt = lang === 'de' ? `
PHASE 8: BUSINESS CASE BUILDER (The CFO Pitch)
Input:
ICPs: ${JSON.stringify(phase2Data.icps)}
Features: ${JSON.stringify(phase1Data.features)}
AUFGABE:
1. Schätze Lohnkosten/Probleme der Zielgruppe (Personalmangel, Krankheitsstand).
2. Setze den Roboter (Leasing ca. 330-600€/Monat) dagegen.
3. Entwickle eine ROI-Logik.
Erstelle für jeden ICP einen "Financial Argumentation Guide".
Output JSON format ONLY:
{
"businessCases": [
{
"industry": "Branche",
"costDriver": "Was kostet der Status Quo? (z.B. Laufwege)",
"efficiencyGain": "Wie amortisiert sich der Roboter?",
"riskArgument": "Warum Miete + Wackler Service sicherer ist als Kauf"
}
]
}
` : `
PHASE 8: BUSINESS CASE BUILDER (The CFO Pitch)
Input:
ICPs: ${JSON.stringify(phase2Data.icps)}
Features: ${JSON.stringify(phase1Data.features)}
TASK:
1. Estimate labor costs/pain points (shortage, sick leave).
2. Compare against Robot Leasing (approx 330-600€/month).
3. Develop ROI logic.
Create a "Financial Argumentation Guide" for each ICP.
Output JSON format ONLY:
{
"businessCases": [
{
"industry": "Industry",
"costDriver": "Status Quo Cost",
"efficiencyGain": "Amortization Logic",
"riskArgument": "Why Lease + Service is safer than buying"
}
]
}
`;
const response = await ai.models.generateContent({
model: 'gemini-3-flash-preview',
contents: prompt,
config: {
systemInstruction: sysInstr,
responseMimeType: "application/json",
}
});
return cleanAndParseJson<Phase8Data>(response.text);
};
// --- Phase 9: Feature-to-Value Translator ---
export const translateTech = async (
phase1Data: Phase1Data,
phase4Data: Phase4Data,
lang: Language
): Promise<Phase9Data> => {
const ai = getClient();
const sysInstr = getSystemInstruction(lang);
const prompt = lang === 'de' ? `
PHASE 9: THE "FEATURE-TO-VALUE" TRANSLATOR
Input Features: ${JSON.stringify(phase1Data.features)}
Strategy Pains: ${JSON.stringify(phase4Data.strategyMatrix.map(s => s.painPoint))}
AUFGABE:
1. Nimm ein technisches Feature (z.B. "LiDAR").
2. Frage: "So what?" (Was bringt das im Alltag?).
3. Frage nochmal: "So what?" (Was ist der emotionale oder finanzielle Vorteil?).
* Kette: LiDAR -> Erkennt Hindernisse -> Stößt nicht an -> Keine Unfälle/Haftung -> **Sicherheit & Ruhe**.
4. Formuliere den Benefit **ohne** Fachchinesisch. Nutze emotionale Trigger.
Erstelle eine Tabelle für die Website-Kommunikation:
* **feature:** (Klein gedruckt für Techies)
* **story:** (Die Story) Der emotionale/wirtschaftliche Nutzen in der Sprache der Zielgruppe.
* **headline:** (Die Headline) Ein knackiger Slogan für dieses Feature.
Output JSON format ONLY:
{
"techTranslations": [
{
"feature": "Technisches Feature",
"story": "Benefit Story (So what chain)",
"headline": "Knackiger Slogan"
}
]
}
` : `
PHASE 9: THE "FEATURE-TO-VALUE" TRANSLATOR
Input Features: ${JSON.stringify(phase1Data.features)}
Strategy Pains: ${JSON.stringify(phase4Data.strategyMatrix.map(s => s.painPoint))}
TASK:
1. Take a technical feature (e.g. "LiDAR").
2. Ask: "So what?" (What does it do in daily life?).
3. Ask again: "So what?" (What is the emotional or financial benefit?).
* Chain: LiDAR -> See obstacles -> No collision -> No accidents/liability -> **Safety & Peace of Mind**.
4. Formulate the benefit **without** jargon. Use emotional triggers.
Create a table for website communication:
* **feature:** (Small print for techies)
* **story:** (The Story) The emotional/economic benefit in target language.
* **headline:** (The Headline) A punchy slogan for this feature.
Output JSON format ONLY:
{
"techTranslations": [
{
"feature": "Tech Feature",
"story": "Benefit Story (So what chain)",
"headline": "Punchy Slogan"
}
]
}
`;
const response = await ai.models.generateContent({
model: 'gemini-3-flash-preview',
contents: prompt,
config: {
systemInstruction: sysInstr,
responseMimeType: "application/json",
}
});
return cleanAndParseJson<Phase9Data>(response.text);
};
export const translateReportToEnglish = async (reportMarkdown: string): Promise<string> => {
const ai = getClient();
const prompt = `
TASK: Translate the following GTM Strategy Report into professional Business English.
CONTEXT: It is a B2B robotic strategy document.
CONSTRAINT: Keep the Markdown structure (#, ##, -, | tables) EXACTLY as is. Only translate the content.
INPUT:
${reportMarkdown}
`;
const response = await ai.models.generateContent({
model: 'gemini-3-flash-preview',
contents: prompt
});
return response.text || reportMarkdown;
};
export const generateConceptImage = async (prompt: string, referenceImagesBase64?: string[]): Promise<string> => {
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");
}
};

View File

@@ -1,91 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Roboplanet GTM Architect</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
colors: {
robo: {
900: '#0f172a',
800: '#1e293b',
700: '#334155',
500: '#3b82f6',
400: '#60a5fa',
accent: '#06b6d4'
}
}
}
}
}
</script>
<style>
body {
/* Default Light Mode colors */
background-color: #f8fafc;
color: #0f172a;
transition: background-color 0.3s, color 0.3s;
}
/* Dark Mode overrides via class */
.dark body {
background-color: #0f172a;
color: #e2e8f0;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1; /* slate-300 */
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8; /* slate-400 */
}
.dark ::-webkit-scrollbar-track {
background: #1e293b;
}
.dark ::-webkit-scrollbar-thumb {
background: #475569;
border-radius: 4px;
}
.dark ::-webkit-scrollbar-thumb:hover {
background: #64748b;
}
</style>
<script type="importmap">
{
"imports": {
"@google/genai": "https://esm.sh/@google/genai@^1.34.0",
"react/": "https://esm.sh/react@^19.2.3/",
"react": "https://esm.sh/react@^19.2.3",
"react-markdown": "https://esm.sh/react-markdown@^10.1.0",
"remark-gfm": "https://esm.sh/remark-gfm@^4.0.0",
"react-dom/": "https://esm.sh/react-dom@^19.2.3/",
"lucide-react": "https://esm.sh/lucide-react@^0.562.0"
}
}
</script>
<link rel="stylesheet" href="/index.css">
</head>
<body>
<div id="root"></div>
<script type="module" src="/index.tsx"></script>
</body>
</html>

View File

@@ -1,15 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error("Could not find root element to mount to");
}
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@@ -1,5 +0,0 @@
{
"name": "Roboplanet GTM Architect",
"description": "A strategic AI engine for developing Go-to-Market strategies for robotic products, ensuring product-market fit and operational feasibility.",
"requestFramePermissions": []
}

View File

@@ -1,25 +0,0 @@
{
"name": "roboplanet-gtm-architect",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@google/genai": "^1.34.0",
"react": "^19.2.3",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.0",
"react-dom": "^19.2.3",
"lucide-react": "^0.562.0"
},
"devDependencies": {
"@types/node": "^22.14.0",
"@vitejs/plugin-react": "^5.0.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"
}
}

View File

@@ -1,29 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"types": [
"node"
],
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": [
"./*"
]
},
"allowImportingTsExtensions": true,
"noEmit": true
}
}

View File

@@ -1,119 +0,0 @@
export enum Phase {
Input = 0,
ProductAnalysis = 1,
ICPDiscovery = 2,
WhaleHunting = 3,
Strategy = 4,
AssetGeneration = 5,
SalesEnablement = 6,
LandingPage = 7,
BusinessCase = 8,
TechTranslator = 9,
}
export type Language = 'en' | 'de';
export type Theme = 'light' | 'dark';
export interface Message {
role: 'user' | 'model';
content: string;
}
export interface Phase1Data {
features: string[];
constraints: string[];
conflictCheck: {
hasConflict: boolean;
details: string;
relatedProduct?: string;
};
rawAnalysis: string;
}
export interface Phase2Data {
icps: {
name: string;
rationale: string;
}[];
dataProxies: {
target: string;
method: string;
}[];
}
export interface Phase3Data {
whales: {
industry: string;
accounts: string[];
}[];
roles: string[];
}
export interface Phase4Data {
strategyMatrix: {
segment: string;
painPoint: string;
angle: string;
differentiation: string;
}[];
}
export interface Phase6Data {
battlecards: {
persona: string;
objection: string;
responseScript: string;
}[];
visualPrompts: {
title: string;
context: string;
prompt: string;
}[];
}
export interface Phase7Data {
landingPages: {
industry: string;
headline: string;
subline: string;
bullets: string[];
cta: string;
}[];
}
export interface Phase8Data {
businessCases: {
industry: string;
costDriver: string;
efficiencyGain: string;
riskArgument: string;
}[];
}
export interface Phase9Data {
techTranslations: {
feature: string;
story: string;
headline: string;
}[];
}
export interface AppState {
currentPhase: Phase;
isLoading: boolean;
history: Message[];
productInput: string;
productImages: string[]; // Array of Base64 strings
phase1Result?: Phase1Data;
phase2Result?: Phase2Data;
phase3Result?: Phase3Data;
phase4Result?: Phase4Data;
phase5Result?: string; // Markdown content
phase6Result?: Phase6Data;
phase7Result?: Phase7Data;
phase8Result?: Phase8Data;
phase9Result?: Phase9Data;
translatedReport?: string; // New field for the English translation
language: Language;
theme: Theme;
}

View File

@@ -1,23 +0,0 @@
import path from 'path';
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, '.', '');
return {
server: {
port: 3000,
host: '0.0.0.0',
},
plugins: [react()],
define: {
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
}
}
};
});

View File

@@ -5,16 +5,12 @@ import react from '@vitejs/plugin-react';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, '.', '');
return {
base: './', // Fix for subdirectory deployment (White Screen)
base: './',
server: {
port: 3000,
host: '0.0.0.0',
},
plugins: [react()],
define: {
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),