diff --git a/gtm-architect/v2/App.tsx b/gtm-architect/v2/App.tsx new file mode 100644 index 00000000..8a129b31 --- /dev/null +++ b/gtm-architect/v2/App.tsx @@ -0,0 +1,1952 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Layout } from './components/Layout'; +import { Phase, AppState, Phase1Data, Phase2Data, Phase3Data, Phase4Data, Phase6Data, Phase7Data, Phase8Data, Phase9Data, Language, Theme } from './types'; +import * as Gemini from './geminiService'; +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, LayoutTemplate, TrendingUp, Shield, Languages } from 'lucide-react'; + +const TRANSLATIONS = { + en: { + phase1: 'Product & Constraints', + phase2: 'ICP Discovery', + phase3: 'Whale Hunting', + phase4: 'Strategy Matrix', + phase5: 'Asset Generation', + phase6: 'Sales Enablement', + phase7: 'Vertical Landing Page', + phase8: 'Business Case (ROI)', + phase9: 'Feature-to-Value Translator', + 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', + translateBtn: 'Translate Report to English', + downloadEn: 'Download English Report (.md)', + translating: 'Translating...', + toPhase6: 'Proceed to Sales Enablement (Phase 6)', + toPhase7: 'Proceed to Landing Pages (Phase 7)', + toPhase8: 'Proceed to Business Case (Phase 8)', + toPhase9: 'Proceed to Feature-to-Value (Phase 9)', + phase6Title: 'Sales Enablement & Visuals', + phase7Title: 'Vertical Landing Pages', + phase8Title: 'Business Case Builder', + phase9Title: 'Feature-to-Value Translator', + 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', + // New placeholders + addName: 'Name...', + addRationale: 'Rationale...', + addTarget: 'Target criteria...', + addMethod: 'Method...', + addAccount: 'Account name...', + addRole: 'Job title...', + 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', + phase7: 'Vertical Landing Page', + phase8: 'Business Case (ROI)', + phase9: 'Feature-to-Value Translator', + 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', + translateBtn: 'Bericht ins Englische übersetzen', + downloadEn: 'Englischen Bericht herunterladen (.md)', + translating: 'Übersetze...', + toPhase6: 'Weiter zu Sales Enablement (Phase 6)', + toPhase7: 'Weiter zu Landing Pages (Phase 7)', + toPhase8: 'Weiter zu Business Case (Phase 8)', + toPhase9: 'Weiter zu Feature-to-Value (Phase 9)', + phase6Title: 'Sales Enablement & Visuals', + phase7Title: 'Vertical Landing Pages', + phase8Title: 'Business Case Builder', + phase9Title: 'Feature-to-Value Translator', + 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', + // New placeholders + addName: 'Name...', + addRationale: 'Begründung...', + addTarget: 'Zielkriterium...', + addMethod: 'Suchmethode...', + addAccount: 'Firmenname...', + addRole: 'Jobtitel...', + 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: [], // Now an array + language: 'light', // temporary init, fix in useEffect + theme: 'light' // Default to light + }); + + // Fix initial state type matching + const [language, setLanguage] = useState('de'); // Default to German per prompt + const [theme, setTheme] = useState('light'); + + const [error, setError] = useState(null); + const [loadingMessage, setLoadingMessage] = useState(""); + const [isTranslating, setIsTranslating] = useState(false); + + // Local state for adding new items (Human in the Loop inputs) + // Phase 1 + const [newFeatureInput, setNewFeatureInput] = useState(""); + const [newConstraintInput, setNewConstraintInput] = useState(""); + + // Phase 2 + const [newICPName, setNewICPName] = useState(""); + const [newICPRationale, setNewICPRationale] = useState(""); + const [newProxyTarget, setNewProxyTarget] = useState(""); + const [newProxyMethod, setNewProxyMethod] = useState(""); + // Phase 3 + // Changed to use a map to track input per group index + const [newAccountInputs, setNewAccountInputs] = useState>({}); + const [newRoleInput, setNewRoleInput] = useState(""); + + const [copiedPromptIndex, setCopiedPromptIndex] = useState(null); + + // Phase 6 Image Generation State + const [generatingImages, setGeneratingImages] = useState>({}); + const [generatedImages, setGeneratedImages] = useState>({}); + + // Phase 6 Editing State + const [editingIndex, setEditingIndex] = useState(null); + const canvasRef = useRef(null); + const [isDrawing, setIsDrawing] = useState(false); + const [brushColor, setBrushColor] = useState('#ef4444'); // Red for annotations + const [brushSize, setBrushSize] = useState(4); + + const labels = TRANSLATIONS[language]; + + // Apply theme to body + useEffect(() => { + if (theme === 'dark') { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + }, [theme]); + + useEffect(() => { + if (!state.isLoading && !isTranslating) 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, isTranslating, labels.loading]); + + // Canvas Initialization for Editing + 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 })); + } + }; + + // Determine the highest phase the user has completed data for. + // This allows navigation back and forth. + const getMaxAllowedPhase = (): Phase => { + if (state.phase9Result) return Phase.TechTranslator; + if (state.phase8Result) return Phase.BusinessCase; + if (state.phase7Result) return Phase.LandingPage; + 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; // Prevent navigation while generating + setState(s => ({ ...s, currentPhase: phase })); + }; + + const generateFullReportMarkdown = (): string => { + if (!state.phase5Result) return ""; + + let fullReport = state.phase5Result; + + if (state.phase6Result) { + 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 += `\`\`\`\n${prompt.prompt}\n\`\`\`\n\n`; + }); + } + + if (state.phase7Result) { + fullReport += `\n\n# VERTICAL LANDING PAGES (PHASE 7)\n\n`; + state.phase7Result.landingPages.forEach(lp => { + fullReport += `## ${lp.industry}\n`; + fullReport += `**Headline:** ${lp.headline}\n\n`; + fullReport += `**Subline:** ${lp.subline}\n\n`; + fullReport += `**Benefits:**\n`; + lp.bullets.forEach(b => fullReport += `- ${b}\n`); + fullReport += `\n**CTA:** ${lp.cta}\n\n---\n\n`; + }); + } + + if (state.phase8Result) { + fullReport += `\n\n# BUSINESS CASE & ROI (PHASE 8)\n\n`; + state.phase8Result.businessCases.forEach(bc => { + fullReport += `## ${bc.industry}\n`; + fullReport += `**Cost Driver:** ${bc.costDriver}\n\n`; + fullReport += `**Efficiency Gain:** ${bc.efficiencyGain}\n\n`; + fullReport += `**Risk Argument:** ${bc.riskArgument}\n\n---\n\n`; + }); + } + + if (state.phase9Result) { + fullReport += `\n\n# FEATURE-TO-VALUE TRANSLATOR (PHASE 9)\n\n`; + fullReport += `| Feature | The Story (Benefit) | Headline |\n`; + fullReport += `| :--- | :--- | :--- |\n`; + state.phase9Result.techTranslations.forEach(tt => { + fullReport += `| ${tt.feature} | ${tt.story} | ${tt.headline} |\n`; + }); + } + return fullReport; + }; + + const downloadReport = (content?: string, filenamePrefix: string = "") => { + const reportContent = content || generateFullReportMarkdown(); + if (!reportContent) return; + + const element = document.createElement("a"); + const file = new Blob([reportContent], {type: 'text/markdown'}); + element.href = URL.createObjectURL(file); + element.download = `roboplanet-gtm-strategy-${filenamePrefix}${new Date().toISOString().slice(0,10)}.md`; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + }; + + const handleTranslateReport = async () => { + const fullReport = generateFullReportMarkdown(); + if (!fullReport) return; + + setIsTranslating(true); + setError(null); + try { + const translated = await Gemini.translateReportToEnglish(fullReport); + setState(s => ({ ...s, translatedReport: translated })); + } catch (e: any) { + setError("Translation failed: " + e.message); + } finally { + setIsTranslating(false); + } + }; + + 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); + }; + + // --- Image Generation Handlers --- + + const handleGenerateImage = async (prompt: string, index: number, overrideReference?: string) => { + setGeneratingImages(prev => ({ ...prev, [index]: true })); + try { + // If sketching, use only the sketch as the strong reference + // If not sketching, use the array of uploaded product images + const refImages = overrideReference ? [overrideReference] : state.productImages; + + const imageUrl = await Gemini.generateConceptImage(prompt, refImages); + setGeneratedImages(prev => ({ ...prev, [index]: imageUrl })); + + // If we were editing, close edit mode + if (editingIndex === index) { + setEditingIndex(null); + } + } catch (e: any) { + console.error("Failed to generate image", e); + setError("Image generation failed: " + e.message); + } finally { + setGeneratingImages(prev => ({ ...prev, [index]: false })); + } + }; + + const handleImageUpload = (event: React.ChangeEvent) => { + const files = event.target.files; + if (files && files.length > 0) { + Array.from(files).forEach((file: 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) + })); + }; + + // --- Drawing / Editing Handlers --- + + const startDrawing = (e: React.MouseEvent | React.TouchEvent) => { + 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: React.MouseEvent | React.TouchEvent) => { + 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); + } + }; + + // --- Handlers --- + + const handlePhase1Submit = async () => { + if (!state.productInput.trim()) return; + setState(s => ({ ...s, isLoading: true })); + setError(null); + try { + const result = await Gemini.analyzeProduct(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 Gemini.discoverICPs(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 Gemini.huntWhales(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 Gemini.developStrategy(state.phase3Result, 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 () => { + // Requires all previous data for the final report + if (!state.phase4Result || !state.phase3Result || !state.phase2Result || !state.phase1Result) return; + setState(s => ({ ...s, isLoading: true })); + try { + const result = await Gemini.generateAssets( + state.phase4Result, + state.phase3Result, + state.phase2Result, + 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 Gemini.generateSalesEnablement( + state.phase4Result, + state.phase3Result, + 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 handlePhase7Submit = async () => { + if (!state.phase4Result || !state.phase2Result) return; + setState(s => ({ ...s, isLoading: true })); + try { + const result = await Gemini.generateLandingPageCopy( + state.phase4Result, + state.phase2Result, + language + ); + setState(s => ({ ...s, currentPhase: Phase.LandingPage, phase7Result: result, isLoading: false })); + } catch (e: any) { + setError(e.message); + setState(s => ({ ...s, isLoading: false })); + } + }; + + const handlePhase8Submit = async () => { + if (!state.phase2Result || !state.phase1Result) return; + setState(s => ({ ...s, isLoading: true })); + try { + const result = await Gemini.buildBusinessCase( + state.phase2Result, + state.phase1Result, + language + ); + setState(s => ({ ...s, currentPhase: Phase.BusinessCase, phase8Result: result, isLoading: false })); + } catch (e: any) { + setError(e.message); + setState(s => ({ ...s, isLoading: false })); + } + }; + + const handlePhase9Submit = async () => { + if (!state.phase1Result || !state.phase4Result) return; + setState(s => ({ ...s, isLoading: true })); + try { + const result = await Gemini.translateTech( + state.phase1Result, + state.phase4Result, + language + ); + setState(s => ({ ...s, currentPhase: Phase.TechTranslator, phase9Result: result, isLoading: false })); + } catch (e: any) { + setError(e.message); + setState(s => ({ ...s, isLoading: false })); + } + }; + + // --- List Mutation Handlers (Phase 1) --- + + const addFeature = () => { + if (!newFeatureInput.trim() || !state.phase1Result) return; + const newItem = newFeatureInput.trim(); + setState(s => ({ + ...s, + phase1Result: s.phase1Result ? { + ...s.phase1Result, + features: [...s.phase1Result.features, newItem] + } : 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; + const newItem = newConstraintInput.trim(); + setState(s => ({ + ...s, + phase1Result: s.phase1Result ? { + ...s.phase1Result, + constraints: [...s.phase1Result.constraints, newItem] + } : undefined + })); + setNewConstraintInput(""); + }; + + const removeConstraint = (index: number) => { + setState(s => ({ + ...s, + phase1Result: s.phase1Result ? { + ...s.phase1Result, + constraints: s.phase1Result.constraints.filter((_, i) => i !== index) + } : undefined + })); + }; + + // --- List Mutation Handlers (Phase 2) --- + + const addICP = () => { + if (!newICPName.trim() || !newICPRationale.trim() || !state.phase2Result) return; + const newItem = { name: newICPName.trim(), rationale: newICPRationale.trim() }; + setState(s => ({ + ...s, + phase2Result: s.phase2Result ? { + ...s.phase2Result, + icps: [...s.phase2Result.icps, newItem] + } : 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; + const newItem = { target: newProxyTarget.trim(), method: newProxyMethod.trim() }; + setState(s => ({ + ...s, + phase2Result: s.phase2Result ? { + ...s.phase2Result, + dataProxies: [...s.phase2Result.dataProxies, newItem] + } : undefined + })); + setNewProxyTarget(""); + setNewProxyMethod(""); + }; + + const removeProxy = (index: number) => { + setState(s => ({ + ...s, + phase2Result: s.phase2Result ? { + ...s.phase2Result, + dataProxies: s.phase2Result.dataProxies.filter((_, i) => i !== index) + } : undefined + })); + }; + + // --- List Mutation Handlers (Phase 3) --- + + const addAccount = (groupIndex: number) => { + const inputValue = newAccountInputs[groupIndex]; + if (!inputValue?.trim() || !state.phase3Result) return; + + const newItem = inputValue.trim(); + + const newWhales = [...state.phase3Result.whales]; + newWhales[groupIndex] = { + ...newWhales[groupIndex], + accounts: [...newWhales[groupIndex].accounts, newItem] + }; + + 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; + const newItem = newRoleInput.trim(); + setState(s => ({ + ...s, + phase3Result: s.phase3Result ? { + ...s.phase3Result, + roles: [...s.phase3Result.roles, newItem] + } : undefined + })); + setNewRoleInput(""); + }; + + const removeRole = (index: number) => { + setState(s => ({ + ...s, + phase3Result: s.phase3Result ? { + ...s.phase3Result, + roles: s.phase3Result.roles.filter((_, i) => i !== index) + } : undefined + })); + }; + + const handlePromptChange = (index: number, newText: string) => { + if (!state.phase6Result) return; + + const newPrompts = [...state.phase6Result.visualPrompts]; + newPrompts[index] = { ...newPrompts[index], prompt: newText }; + + setState(s => ({ + ...s, + phase6Result: s.phase6Result ? { + ...s.phase6Result, + visualPrompts: newPrompts + } : undefined + })); + }; + + // --- Render Helpers --- + + const renderInputPhase = () => ( +
+
+

+ + {labels.initTitle} +

+ +
+
+ +