import React, { useState, useCallback, useEffect } from 'react'; import { Header } from './components/Header'; import { StepInput } from './components/StepInput'; import { StepStrategy } from './components/StepStrategy'; import { StepReview } from './components/StepReview'; import { StepProcessing } from './components/StepProcessing'; import { StepReport } from './components/StepReport'; import { StepOutreach } from './components/StepOutreach'; import { AppStep, Competitor, AnalysisResult, AnalysisState, Language, SearchStrategy } from './types'; import { identifyCompetitors, analyzeCompanyWithStrategy, generateSearchStrategy, saveProject } from './services/geminiService'; const generateId = () => Math.random().toString(36).substr(2, 9); const App: React.FC = () => { const [step, setStep] = useState(AppStep.INPUT); const [isLoading, setIsLoading] = useState(false); const [language, setLanguage] = useState('de'); // Project State const [projectId, setProjectId] = useState(null); const [projectName, setProjectName] = useState(''); // Core Data const [referenceUrl, setReferenceUrl] = useState(''); const [targetMarket, setTargetMarket] = useState(''); const [productContext, setProductContext] = useState(''); const [referenceCity, setReferenceCity] = useState(''); const [referenceCountry, setReferenceCountry] = useState(''); const [strategy, setStrategy] = useState(null); const [competitors, setCompetitors] = useState([]); const [categorizedCompetitors, setCategorizedCompetitors] = useState<{ localCompetitors: Competitor[], nationalCompetitors: Competitor[], internationalCompetitors: Competitor[] } | null>(null); const [analysisResults, setAnalysisResults] = useState([]); const [processingState, setProcessingState] = useState({ currentCompany: '', progress: 0, total: 0, completed: 0 }); const [selectedCompanyForOutreach, setSelectedCompanyForOutreach] = useState(null); // --- Auto-Save Effect --- useEffect(() => { // Don't save on initial load or reset if (step === AppStep.INPUT) return; const saveData = async () => { if (!referenceUrl) return; let dynamicName = projectName; try { const refHost = new URL(referenceUrl).hostname.replace('www.', ''); if (analysisResults && analysisResults.length > 0) { // Use the first analyzed company as the title anchor dynamicName = `${analysisResults[0].companyName} (Ref: ${refHost})`; } else if (competitors && competitors.length > 0) { dynamicName = `Search: ${refHost} Lookalikes`; } else if (!projectName || projectName === "New Project") { dynamicName = `Draft: ${refHost}`; } } catch (e) { dynamicName = projectName || "Untitled Project"; } const dataToSave = { id: projectId, name: dynamicName, created_at: new Date().toISOString(), // DB updates updated_at automatically currentStep: step, language, referenceUrl, targetMarket, productContext, strategy, competitors, categorizedCompetitors, analysisResults }; try { const result = await saveProject(dataToSave); if (result.id && !projectId) { setProjectId(result.id); console.log("Project created with ID:", result.id); } } catch (e) { console.error("Auto-save failed", e); } }; // Simple debounce to avoid spamming save on every keystroke/state change const timer = setTimeout(saveData, 1000); return () => clearTimeout(timer); }, [step, strategy, competitors, analysisResults, referenceUrl, projectName]); const handleBack = () => { if (step === AppStep.STRATEGY) setStep(AppStep.INPUT); else if (step === AppStep.REVIEW_LIST) setStep(AppStep.STRATEGY); else if (step === AppStep.REPORT) setStep(AppStep.REVIEW_LIST); else if (step === AppStep.OUTREACH) { setStep(AppStep.REPORT); setSelectedCompanyForOutreach(null); } }; const handleInitialInput = async (url: string, productCtx: string, market: string, selectedLang: Language) => { setIsLoading(true); setLanguage(selectedLang); setReferenceUrl(url); setTargetMarket(market); setProductContext(productCtx); // Set explicit name for new project try { const hostname = new URL(url).hostname.replace('www.', ''); setProjectName(hostname); } catch (e) { setProjectName("New Project"); } try { const generatedStrategy = await generateSearchStrategy(url, productCtx, selectedLang); setStrategy(generatedStrategy); setStep(AppStep.STRATEGY); } catch (error) { alert("Failed to generate strategy. Please try again."); console.error(error); } finally { setIsLoading(false); } }; // Hydrate state from loaded project (DB or File) const handleLoadReport = (loadedStrategy: SearchStrategy, loadedResults: AnalysisResult[]) => { // NOTE: This signature is from the old StepInput prop. // Ideally StepInput should pass the FULL project object if loaded from DB. // But for backward compatibility with file load, we keep it. // If loaded from DB via StepInput -> handleProjectSelect, we need a new handler. // See below 'handleLoadProjectData' setStrategy(loadedStrategy); setAnalysisResults(loadedResults); // Reconstruct competitors list from results for consistency const reconstructedCompetitors = loadedResults.map(r => ({ id: generateId(), name: r.companyName, dataSource: r.dataSource })); setCompetitors(reconstructedCompetitors); setStep(AppStep.REPORT); }; // NEW: Full Project Hydration const handleLoadProjectData = (data: any) => { setProjectId(data.id); setProjectName(data.name); setReferenceUrl(data.referenceUrl || ''); setTargetMarket(data.targetMarket || ''); setProductContext(data.productContext || ''); setLanguage(data.language || 'de'); if (data.strategy) setStrategy(data.strategy); if (data.competitors) setCompetitors(data.competitors); if (data.categorizedCompetitors) setCategorizedCompetitors(data.categorizedCompetitors); if (data.analysisResults) setAnalysisResults(data.analysisResults); // Jump to the last relevant step // If results exist -> Report. Else if competitors -> Review. Else -> Strategy. if (data.analysisResults && data.analysisResults.length > 0) { setStep(AppStep.REPORT); } else if (data.competitors && data.competitors.length > 0) { setStep(AppStep.REVIEW_LIST); } else if (data.strategy) { setStep(AppStep.STRATEGY); } else { setStep(AppStep.INPUT); } }; const handleStrategyConfirm = async (finalStrategy: SearchStrategy) => { setStrategy(finalStrategy); setIsLoading(true); try { const idealCustomerProfile = finalStrategy.idealCustomerProfile; const identifiedCompetitors = await identifyCompetitors(referenceUrl, targetMarket, productContext, referenceCity, referenceCountry, idealCustomerProfile); setCategorizedCompetitors(identifiedCompetitors); const flatCompetitors: Competitor[] = [ ...(identifiedCompetitors.localCompetitors || []), ...(identifiedCompetitors.nationalCompetitors || []), ...(identifiedCompetitors.internationalCompetitors || []), ]; setCompetitors(flatCompetitors); setStep(AppStep.REVIEW_LIST); } catch (e) { alert("Failed to find companies."); console.error(e); } finally { setIsLoading(false); } }; const handleRemoveCompetitor = (id: string) => { setCompetitors(prev => prev.filter(c => c.id !== id)); }; const handleAddCompetitor = (name: string) => { setCompetitors(prev => [...prev, { id: generateId(), name }]); }; const runAnalysis = useCallback(async () => { if (!strategy) return; setStep(AppStep.ANALYSIS); setAnalysisResults([]); setProcessingState({ currentCompany: '', progress: 0, total: competitors.length, completed: 0, terminalLogs: ['🚀 Starting Deep Tech Audit session...'] }); const results: AnalysisResult[] = []; const addLog = (msg: string) => { setProcessingState(prev => ({ ...prev, terminalLogs: [...(prev.terminalLogs || []), msg] })); }; for (let i = 0; i < competitors.length; i++) { const comp = competitors[i]; setProcessingState(prev => ({ ...prev, currentCompany: comp.name })); addLog(`> Analyzing ${comp.name} (${i + 1}/${competitors.length})`); try { addLog(` 🔍 Searching official website for ${comp.name}...`); const result = await analyzeCompanyWithStrategy(comp.name, strategy, language); if (result.dataSource === "Error") { addLog(` ❌ Error: Could not process ${comp.name}.`); } else { const websiteStatus = result.dataSource === "Digital Trace Audit (Deep Dive)" ? "Verified" : (result.dataSource || "Unknown"); addLog(` ✓ Found website: ${websiteStatus}`); addLog(` ✅ Analysis complete for ${comp.name}.`); } results.push(result); } catch (e) { console.error(`Failed to analyze ${comp.name}`); addLog(` ❌ Fatal error analyzing ${comp.name}.`); } setProcessingState(prev => ({ ...prev, completed: i + 1 })); } addLog(`✨ Audit session finished. Generating final report...`); setAnalysisResults(results); setStep(AppStep.REPORT); }, [competitors, language, strategy]); const handleRestart = () => { setProjectId(null); // Reset Project ID to start fresh setCompetitors([]); setAnalysisResults([]); setStrategy(null); setStep(AppStep.INPUT); }; return (
{step === AppStep.INPUT && ( { if ('id' in stratOrFullProject) { handleLoadProjectData(stratOrFullProject); } else { handleLoadReport(stratOrFullProject as SearchStrategy, results); } }} isLoading={isLoading} /> )} {step === AppStep.STRATEGY && strategy && ( )} {step === AppStep.REVIEW_LIST && ( 0} onShowReport={() => setStep(AppStep.REPORT)} /> )} {step === AppStep.ANALYSIS && ( )} {step === AppStep.REPORT && strategy && ( { setSelectedCompanyForOutreach(company); setStep(AppStep.OUTREACH); }} /> )} {step === AppStep.OUTREACH && selectedCompanyForOutreach && ( )}
); }; export default App;