import React, { useState, useEffect, useRef } from 'react'; import { Layout } from './components/Layout'; import { Phase, AppState, Phase1Data, Phase2Data, Phase3Data, Phase4Data, Phase6Data, Language, Theme } from './types'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { AlertTriangle, ArrowRight, ArrowLeft, Check, Database, Globe, Search, ShieldAlert, Cpu, Building2, UserCircle, Briefcase, Zap, Terminal, Target, Crosshair, Loader2, Plus, X, Download, ShieldCheck, Image as ImageIcon, Copy, Sparkles, Upload, CheckCircle, PenTool, Eraser, Undo, Save, RefreshCw, Pencil, Trash2, History } from 'lucide-react'; const TRANSLATIONS = { en: { phase1: 'Product & Constraints', phase2: 'ICP Discovery', phase3: 'Whale Hunting', phase4: 'Strategy Matrix', phase5: 'Asset Generation', phase6: 'Sales Enablement', initTitle: 'Initialize New Product Sequence', inputLabel: 'Product URL or Technical Specification', inputPlaceholder: 'Paste raw technical data, brochure text, or product URL here...', startBtn: 'Start Analysis', backBtn: 'Back', processing: 'Processing...', features: 'Detected Features', constraints: 'Hard Constraints', conflictTitle: 'Portfolio Conflict Detected', conflictPass: 'Portfolio Conflict Check Passed: Unique Value Proposition Identified.', confirmICP: 'Confirm & Proceed to ICP', targetId: 'Target Identification', dataProxies: 'Data Proxies (Digital Footprints)', method: 'Method', huntWhales: 'Lock Targets & Hunt Whales', region: 'Region: DACH (Germany, Austria, Switzerland)', whales: 'Key Accounts (Whales)', roles: 'Buying Center Roles', confirmStrat: 'Confirm Accounts & Strategize', stratAngles: 'Strategic Angles', segment: 'Segment', pain: 'Pain Point', angle: 'Our Angle', diff: 'Differentiation', writeCopy: 'Generate Assets', genAssets: 'Generated Strategy Report & Assets', complete: 'Process Complete. Strategy locked.', restart: 'Start New Session', addPlaceholder: 'Add item...', download: 'Download Full Report (.md)', downloadAsset: 'Download Asset', toPhase6: 'Proceed to Sales Enablement (Phase 6)', phase6Title: 'Sales Enablement & Visuals', battlecards: 'Kill-Critique Battlecards', visuals: 'Visual Briefings (Midjourney Prompts)', objection: 'Objection', response: 'Response Script', copyPrompt: 'Copy Prompt', copied: 'Copied!', genImage: 'Generate Concept', editImage: 'Edit & Iterate', regenerateSketch: 'Regenerate with Sketch', cancelEdit: 'Cancel Edit', generating: 'Generating...', uploadImage: 'Product Reference Images', uploadHint: 'Upload multiple photos (Front, Side, Detail) for better 3D understanding.', imageUploaded: 'Reference Images', canvasClear: 'Clear', canvasMode: 'Sketch Mode', addName: 'Name...', addRationale: 'Rationale...', addTarget: 'Target criteria...', addMethod: 'Method...', addAccount: 'Account name...', addRole: 'Job title...', save: 'Save Project', load: 'Load Project', history: 'Project History', noProjects: 'No saved projects found.', delete: 'Delete', loading: [ "Initializing quantum analysis engines...", "Parsing raw technical specifications...", "Simulating physical constraints & boundary conditions...", "Cross-referencing internal product matrix...", "Detecting potential portfolio conflicts...", "Synthesizing GTM parameters..." ] }, de: { phase1: 'Produkt & Constraints', phase2: 'ICP Entdeckung', phase3: 'Whale Hunting', phase4: 'Strategie-Matrix', phase5: 'Asset Generierung', phase6: 'Sales Enablement', initTitle: 'Neue Produktsequenz initialisieren', inputLabel: 'Produkt-URL oder Technische Spezifikation', inputPlaceholder: 'Füge hier rohe technische Daten, Broschürentexte oder Produkt-URL ein...', startBtn: 'Analyse starten', backBtn: 'Zurück', processing: 'Verarbeite...', features: 'Erkannte Features', constraints: 'Harte Constraints', conflictTitle: 'Portfolio-Konflikt erkannt', conflictPass: 'Portfolio-Konfliktprüfung bestanden: Alleinstellungsmerkmal identifiziert.', confirmICP: 'Bestätigen & weiter zu ICP', targetId: 'Zielgruppen-Identifikation', dataProxies: 'Data Proxies (Digitale Spuren)', method: 'Methode', huntWhales: 'Ziele fixieren & Whales jagen', region: 'Region: DACH (Deutschland, Österreich, Schweiz)', whales: 'Key Accounts (Whales)', roles: 'Buying Center Rollen', confirmStrat: 'Accounts bestätigen & Strategie', stratAngles: 'Strategische Angles', segment: 'Segment', pain: 'Pain Point', angle: 'Unser Angle', diff: 'Differenzierung', writeCopy: 'Report & Assets generieren', genAssets: 'Generierter Strategie-Report & Assets', complete: 'Prozess abgeschlossen. Strategie fixiert.', restart: 'Neue Session starten', addPlaceholder: 'Punkt hinzufügen...', download: 'Gesamtbericht herunterladen (.md)', downloadAsset: 'Bild speichern', toPhase6: 'Weiter zu Sales Enablement (Phase 6)', phase6Title: 'Sales Enablement & Visuals', battlecards: 'Kill-Critique Battlecards', visuals: 'Visual Briefings (Midjourney Prompts)', objection: 'Einwand', response: 'Antwort-Skript', copyPrompt: 'Prompt kopieren', copied: 'Kopiert!', genImage: 'Konzept erstellen', editImage: 'Zeichnen & Iterieren', regenerateSketch: 'Mit Skizze neu generieren', cancelEdit: 'Abbrechen', generating: 'Generiere...', uploadImage: 'Produkt-Referenzbilder', uploadHint: 'Lade mehrere Fotos hoch (Front, Seite, Detail), um das 3D-Verständnis zu verbessern.', imageUploaded: 'Referenzbilder', canvasClear: 'Leeren', canvasMode: 'Zeichenmodus', addName: 'Name...', addRationale: 'Begründung...', addTarget: 'Zielkriterium...', addMethod: 'Suchmethode...', addAccount: 'Firmenname...', addRole: 'Jobtitel...', save: 'Projekt speichern', load: 'Projekt laden', history: 'Projekt Historie', noProjects: 'Keine gespeicherten Projekte gefunden.', delete: 'Löschen', loading: [ "Initialisiere Quanten-Analyse-Engines...", "Analysiere technische Spezifikationen...", "Simuliere physikalische Constraints...", "Prüfe interne Produkt-Matrix...", "Erkenne potenzielle Portfolio-Konflikte...", "Synthetisiere GTM-Parameter..." ] } }; const App: React.FC = () => { const [state, setState] = useState({ currentPhase: Phase.Input, isLoading: false, history: [], productInput: '', productImages: [], language: 'de', theme: 'light' }); const [language, setLanguage] = useState('de'); const [theme, setTheme] = useState('light'); const [error, setError] = useState(null); const [loadingMessage, setLoadingMessage] = useState(""); // History / DB State const [showHistory, setShowHistory] = useState(false); const [savedProjects, setSavedProjects] = useState([]); // Input State const [newFeatureInput, setNewFeatureInput] = useState(""); const [newConstraintInput, setNewConstraintInput] = useState(""); const [newICPName, setNewICPName] = useState(""); const [newICPRationale, setNewICPRationale] = useState(""); const [newProxyTarget, setNewProxyTarget] = useState(""); const [newProxyMethod, setNewProxyMethod] = useState(""); const [newAccountInputs, setNewAccountInputs] = useState>({}); const [newRoleInput, setNewRoleInput] = useState(""); const [copiedPromptIndex, setCopiedPromptIndex] = useState(null); // Image Gen State const [generatingImages, setGeneratingImages] = useState>({}); const [generatedImages, setGeneratedImages] = useState>({}); const [editingIndex, setEditingIndex] = useState(null); const canvasRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); const [brushColor, setBrushColor] = useState('#ef4444'); const [brushSize, setBrushSize] = useState(4); const labels = TRANSLATIONS[language]; useEffect(() => { if (theme === 'dark') { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }, [theme]); useEffect(() => { if (!state.isLoading) return; const messages = labels.loading; let i = 0; setLoadingMessage(messages[0]); const interval = setInterval(() => { i = (i + 1) % messages.length; setLoadingMessage(messages[i]); }, 2500); return () => clearInterval(interval); }, [state.isLoading, labels.loading]); useEffect(() => { if (editingIndex !== null && canvasRef.current && generatedImages[editingIndex]) { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const img = new Image(); img.crossOrigin = "anonymous"; img.onload = () => { canvas.width = img.width; canvas.height = img.height; ctx?.drawImage(img, 0, 0); }; img.src = generatedImages[editingIndex]; } }, [editingIndex, generatedImages]); const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light'); const goBack = () => { if (state.currentPhase > Phase.Input) { setState(s => ({ ...s, currentPhase: s.currentPhase - 1 })); } }; const getMaxAllowedPhase = (): Phase => { if (state.phase6Result) return Phase.SalesEnablement; if (state.phase5Result) return Phase.AssetGeneration; if (state.phase4Result) return Phase.Strategy; if (state.phase3Result) return Phase.WhaleHunting; if (state.phase2Result) return Phase.ICPDiscovery; if (state.phase1Result) return Phase.ProductAnalysis; return Phase.Input; }; const handlePhaseSelect = (phase: Phase) => { if (state.isLoading) return; setState(s => ({ ...s, currentPhase: phase })); }; const callBackend = async (mode: string, data: any) => { const response = await fetch('./api/gtm', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode, data }) }); if (!response.ok) { const err = await response.json(); throw new Error(err.details || 'Backend error'); } return await response.json(); }; // --- DB Handlers --- const fetchProjects = async () => { try { const projects = await callBackend('list_projects', {}); setSavedProjects(projects); } catch (e) { console.error("Failed to list projects", e); } }; const handleSaveProject = async () => { try { // Clone state to avoid mutation issues const projectData = { ...state, language, theme }; await callBackend('save_project', projectData); // Optional: Show success notification const btn = document.getElementById('save-btn'); if(btn) { const originalText = btn.innerHTML; btn.innerHTML = ` Saved!`; setTimeout(() => btn.innerHTML = originalText, 2000); } } catch (e: any) { setError("Save failed: " + e.message); } }; const handleLoadProject = async (id: string) => { try { const project = await callBackend('load_project', { id }); if (project) { setState(project); if (project.language) setLanguage(project.language); if (project.theme) setTheme(project.theme); setShowHistory(false); } } catch (e: any) { setError("Load failed: " + e.message); } }; const handleDeleteProject = async (id: string, e: React.MouseEvent) => { e.stopPropagation(); if (!confirm("Are you sure you want to delete this project?")) return; try { await callBackend('delete_project', { id }); setSavedProjects(prev => prev.filter(p => p.id !== id)); } catch (e: any) { setError("Delete failed: " + e.message); } }; const toggleHistory = () => { if (!showHistory) { fetchProjects(); } setShowHistory(!showHistory); }; // --- Phase Handlers --- const handlePhase1Submit = async () => { if (!state.productInput.trim()) return; setState(s => ({ ...s, isLoading: true })); setError(null); try { const result = await callBackend('analyze_product', { productInput: state.productInput, language }); setState(s => ({ ...s, currentPhase: Phase.ProductAnalysis, phase1Result: result, isLoading: false })); } catch (e: any) { setError(e.message || "Analysis failed"); setState(s => ({ ...s, isLoading: false })); } }; const handlePhase2Submit = async () => { if (!state.phase1Result) return; setState(s => ({ ...s, isLoading: true })); try { const result = await callBackend('discover_icps', { phase1Result: state.phase1Result, language }); setState(s => ({ ...s, currentPhase: Phase.ICPDiscovery, phase2Result: result, isLoading: false })); } catch (e: any) { setError(e.message); setState(s => ({ ...s, isLoading: false })); } }; const handlePhase3Submit = async () => { if (!state.phase2Result) return; setState(s => ({ ...s, isLoading: true })); try { const result = await callBackend('hunt_whales', { phase2Result: state.phase2Result, language }); setState(s => ({ ...s, currentPhase: Phase.WhaleHunting, phase3Result: result, isLoading: false })); } catch (e: any) { setError(e.message); setState(s => ({ ...s, isLoading: false })); } }; const handlePhase4Submit = async () => { if (!state.phase3Result || !state.phase1Result) return; setState(s => ({ ...s, isLoading: true })); try { const result = await callBackend('develop_strategy', { phase3Result: state.phase3Result, phase1Result: state.phase1Result, language }); setState(s => ({ ...s, currentPhase: Phase.Strategy, phase4Result: result, isLoading: false })); } catch (e: any) { setError(e.message); setState(s => ({ ...s, isLoading: false })); } }; const handlePhase5Submit = async () => { if (!state.phase4Result || !state.phase3Result || !state.phase2Result || !state.phase1Result) return; setState(s => ({ ...s, isLoading: true })); try { const result = await callBackend('generate_assets', { phase4Result: state.phase4Result, phase3Result: state.phase3Result, phase2Result: state.phase2Result, phase1Result: state.phase1Result, language }); setState(s => ({ ...s, currentPhase: Phase.AssetGeneration, phase5Result: result, isLoading: false })); } catch (e: any) { setError(e.message); setState(s => ({ ...s, isLoading: false })); } }; const handlePhase6Submit = async () => { if (!state.phase4Result || !state.phase3Result || !state.phase1Result) return; setState(s => ({ ...s, isLoading: true })); try { const result = await callBackend('generate_sales_enablement', { phase4Result: state.phase4Result, phase3Result: state.phase3Result, phase1Result: state.phase1Result, language }); setState(s => ({ ...s, currentPhase: Phase.SalesEnablement, phase6Result: result, isLoading: false })); } catch (e: any) { setError(e.message); setState(s => ({ ...s, isLoading: false })); } }; const downloadReport = () => { if (!state.phase5Result || !state.phase6Result) return; let fullReport = state.phase5Result; fullReport += `\n\n# SALES ENABLEMENT & VISUALS (PHASE 6)\n\n`; fullReport += `## Kill-Critique Battlecards\n\n`; state.phase6Result.battlecards.forEach(card => { fullReport += `### Persona: ${card.persona}\n`; fullReport += `> **Objection:** "${card.objection}"\n\n`; fullReport += `**Response:** ${card.responseScript}\n\n---\n\n`; }); fullReport += `## Visual Briefings (Prompts)\n\n`; state.phase6Result.visualPrompts.forEach(prompt => { fullReport += `### ${prompt.title}\n`; fullReport += `*Context: ${prompt.context}*\n\n`; fullReport += `\ \ ${prompt.prompt}\n\ \ `; }); const element = document.createElement("a"); const file = new Blob([fullReport], {type: 'text/markdown'}); element.href = URL.createObjectURL(file); element.download = `roboplanet-gtm-strategy-${new Date().toISOString().slice(0,10)}.md`; document.body.appendChild(element); element.click(); document.body.removeChild(element); }; const downloadAsset = (dataUrl: string, index: number) => { const element = document.createElement("a"); element.href = dataUrl; element.download = `roboplanet-visual-${index + 1}.png`; document.body.appendChild(element); element.click(); document.body.removeChild(element); }; const copyToClipboard = (text: string, index: number) => { navigator.clipboard.writeText(text); setCopiedPromptIndex(index); setTimeout(() => setCopiedPromptIndex(null), 2000); }; const handleGenerateImage = async (prompt: string, index: number, overrideReference?: string) => { setError("Bildgenerierung ist in der lokalen Version aktuell deaktiviert. Bitte nutzen Sie die Prompts direkt in Midjourney/DALL-E."); }; const handleImageUpload = (event: React.ChangeEvent) => { const files = event.target.files; if (files && files.length > 0) { Array.from(files).forEach(file => { const reader = new FileReader(); reader.onloadend = () => { if (reader.result) { setState(s => ({ ...s, productImages: [...s.productImages, reader.result as string] })); } }; reader.readAsDataURL(file); }); } }; const removeUploadedImage = (index: number) => { setState(s => ({ ...s, productImages: s.productImages.filter((_, i) => i !== index) })); }; const startDrawing = (e: any) => { setIsDrawing(true); const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const x = (('touches' in e ? e.touches[0].clientX : e.clientX) - rect.left) * scaleX; const y = (('touches' in e ? e.touches[0].clientY : e.clientY) - rect.top) * scaleY; ctx.beginPath(); ctx.moveTo(x, y); }; const draw = (e: any) => { if (!isDrawing) return; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const x = (('touches' in e ? e.touches[0].clientX : e.clientX) - rect.left) * scaleX; const y = (('touches' in e ? e.touches[0].clientY : e.clientY) - rect.top) * scaleY; ctx.lineTo(x, y); ctx.strokeStyle = brushColor; ctx.lineWidth = brushSize; ctx.lineCap = 'round'; ctx.stroke(); }; const stopDrawing = () => setIsDrawing(false); const clearCanvas = () => { const canvas = canvasRef.current; if (canvas && editingIndex !== null) { const ctx = canvas.getContext('2d'); if (ctx) { const img = new Image(); img.onload = () => { ctx.clearRect(0,0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); } img.src = generatedImages[editingIndex]; } } }; const handleRegenerateWithSketch = (index: number, prompt: string) => { const canvas = canvasRef.current; if (canvas) { const sketchData = canvas.toDataURL('image/png'); handleGenerateImage(prompt, index, sketchData); } }; // --- List Mutation Handlers --- const addFeature = () => { if (!newFeatureInput.trim() || !state.phase1Result) return; setState(s => ({ ...s, phase1Result: s.phase1Result ? { ...s.phase1Result, features: [...s.phase1Result.features, newFeatureInput.trim()] } : undefined })); setNewFeatureInput(""); }; const removeFeature = (index: number) => setState(s => ({ ...s, phase1Result: s.phase1Result ? { ...s.phase1Result, features: s.phase1Result.features.filter((_, i) => i !== index) } : undefined })); const addConstraint = () => { if (!newConstraintInput.trim() || !state.phase1Result) return; setState(s => ({ ...s, phase1Result: s.phase1Result ? { ...s.phase1Result, constraints: [...s.phase1Result.constraints, newConstraintInput.trim()] } : undefined })); setNewConstraintInput(""); }; const removeConstraint = (index: number) => setState(s => ({ ...s, phase1Result: s.phase1Result ? { ...s.phase1Result, constraints: s.phase1Result.constraints.filter((_, i) => i !== index) } : undefined })); const addICP = () => { if (!newICPName.trim() || !newICPRationale.trim() || !state.phase2Result) return; setState(s => ({ ...s, phase2Result: s.phase2Result ? { ...s.phase2Result, icps: [...s.phase2Result.icps, { name: newICPName.trim(), rationale: newICPRationale.trim() }] } : undefined })); setNewICPName(""); setNewICPRationale(""); }; const removeICP = (index: number) => setState(s => ({ ...s, phase2Result: s.phase2Result ? { ...s.phase2Result, icps: s.phase2Result.icps.filter((_, i) => i !== index) } : undefined })); const addProxy = () => { if (!newProxyTarget.trim() || !newProxyMethod.trim() || !state.phase2Result) return; setState(s => ({ ...s, phase2Result: s.phase2Result ? { ...s.phase2Result, dataProxies: [...s.phase2Result.dataProxies, { target: newProxyTarget.trim(), method: newProxyMethod.trim() }] } : undefined })); setNewProxyTarget(""); setNewProxyMethod(""); }; const removeProxy = (index: number) => setState(s => ({ ...s, phase2Result: s.phase2Result ? { ...s.phase2Result, dataProxies: s.phase2Result.dataProxies.filter((_, i) => i !== index) } : undefined })); const addAccount = (groupIndex: number) => { const val = newAccountInputs[groupIndex]; if (!val?.trim() || !state.phase3Result) return; const newWhales = [...state.phase3Result.whales]; newWhales[groupIndex] = { ...newWhales[groupIndex], accounts: [...newWhales[groupIndex].accounts, val.trim()] }; setState(s => ({ ...s, phase3Result: s.phase3Result ? { ...s.phase3Result, whales: newWhales } : undefined })); setNewAccountInputs(prev => ({...prev, [groupIndex]: ""})); }; const removeAccount = (groupIndex: number, accountIndex: number) => { if (!state.phase3Result) return; const newWhales = [...state.phase3Result.whales]; newWhales[groupIndex] = { ...newWhales[groupIndex], accounts: newWhales[groupIndex].accounts.filter((_, i) => i !== accountIndex) }; setState(s => ({ ...s, phase3Result: s.phase3Result ? { ...s.phase3Result, whales: newWhales } : undefined })); }; const addRole = () => { if (!newRoleInput.trim() || !state.phase3Result) return; setState(s => ({ ...s, phase3Result: s.phase3Result ? { ...s.phase3Result, roles: [...s.phase3Result.roles, newRoleInput.trim()] } : undefined })); setNewRoleInput(""); }; const removeRole = (index: number) => setState(s => ({ ...s, phase3Result: s.phase3Result ? { ...s.phase3Result, roles: s.phase3Result.roles.filter((_, i) => i !== index) } : undefined })); // --- Render Functions --- const renderInputPhase = () => (

{labels.initTitle}