import React, { useState, useCallback, useEffect, useRef } from 'react'; import type { AppState, CompetitorCandidate, Product, TargetIndustry, Keyword, SilverBullet, Battlecard, ReferenceAnalysis } from './types'; import { fetchStep1Data, fetchStep2Data, fetchStep3Data, fetchStep4Data, fetchStep5Data_SilverBullets, fetchStep6Data_Conclusion, fetchStep7Data_Battlecards, fetchStep8Data_ReferenceAnalysis } from './services/geminiService'; import { generatePdfReport } from './services/pdfService'; import InputForm from './components/InputForm'; import StepIndicator from './components/StepIndicator'; import Step1Extraction from './components/Step1_Extraction'; import Step2Keywords from './components/Step2_Keywords'; import Step3Competitors from './components/Step3_Competitors'; import Step4Analysis from './components/Step4_Analysis'; import Step5SilverBullets from './components/Step5_SilverBullets'; import Step6Conclusion from './components/Step6_Conclusion'; import Step7_Battlecards from './components/Step7_Battlecards'; import Step8_References from './components/Step8_References'; import LoadingSpinner from './components/LoadingSpinner'; import { translations } from './translations'; const SunIcon = () => (); const MoonIcon = () => (); const RestartIcon = () => (); const DownloadIcon = () => (); const ChevronDownIcon = () => (); const App: React.FC = () => { const [appState, setAppState] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [theme, setTheme] = useState<'light' | 'dark'>('dark'); const [highestStep, setHighestStep] = useState(0); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const dropdownRef = useRef(null); const t = translations[appState?.initial_params?.language || 'de']; useEffect(() => { if (theme === 'dark') { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }, [theme]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setIsDropdownOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); const handleRestart = () => { setAppState(null); setIsLoading(false); setError(null); setHighestStep(0); }; const handleStartAnalysis = useCallback(async (startUrl: string, maxCompetitors: number, marketScope: string, language: 'de' | 'en') => { setIsLoading(true); setError(null); setAppState(null); setHighestStep(0); const currentT = translations[language]; try { const { products, target_industries } = await fetchStep1Data(startUrl, language); setAppState({ step: 1, initial_params: { start_url: startUrl, max_competitors: maxCompetitors, market_scope: marketScope, language }, company: { name: new URL(startUrl).hostname.replace('www.', ''), start_url: startUrl }, products: products, target_industries: target_industries, keywords: [], competitor_candidates: [], competitors_shortlist: [], analyses: [], silver_bullets: [], conclusion: null, battlecards: [], reference_analysis: [], reference_analysis_grounding: [], }); setHighestStep(1); } catch (e) { console.error("Error in Step 1:", e); setError(currentT.errors.step1); } finally { setIsLoading(false); } }, []); const handleConfirmStep = useCallback(async () => { if (!appState) return; setIsLoading(true); setError(null); const nextStep = appState.step + 1; const lang = appState.initial_params.language; try { let newState: Partial = {}; switch (appState.step) { case 1: const { keywords } = await fetchStep2Data(appState.products, appState.target_industries, lang); newState = { keywords, step: 2 }; break; case 2: const { competitor_candidates } = await fetchStep3Data(appState.keywords, appState.initial_params.market_scope, lang); newState = { competitor_candidates, step: 3 }; break; case 3: const shortlist = [...appState.competitor_candidates] .sort((a, b) => b.confidence - a.confidence) .slice(0, appState.initial_params.max_competitors); const { analyses } = await fetchStep4Data(appState.company, shortlist, lang); newState = { competitors_shortlist: shortlist, analyses, step: 4 }; break; case 4: const { silver_bullets } = await fetchStep5Data_SilverBullets(appState.company, appState.analyses, lang); newState = { silver_bullets, step: 5 }; break; case 5: const { conclusion } = await fetchStep6Data_Conclusion(appState.company, appState.products, appState.target_industries, appState.analyses, appState.silver_bullets, lang); newState = { conclusion, step: 6 }; break; case 6: const { battlecards } = await fetchStep7Data_Battlecards(appState.company, appState.analyses, appState.silver_bullets, lang); newState = { battlecards, step: 7 }; break; case 7: const { reference_analysis, groundingMetadata } = await fetchStep8Data_ReferenceAnalysis(appState.competitors_shortlist, lang); newState = { reference_analysis, reference_analysis_grounding: groundingMetadata, step: 8 }; break; } setAppState(prevState => ({ ...prevState!, ...newState })); if (nextStep > highestStep) { setHighestStep(nextStep); } } catch (e) { console.error(`Error in Step ${appState.step + 1}:`, e); setError(translations[lang].errors.generic(appState.step + 1)); } finally { setIsLoading(false); } }, [appState, highestStep]); const handleUpdateState = useCallback((key: keyof AppState, value: any) => { setAppState(prevState => { if (!prevState) return null; return { ...prevState, [key]: value }; }); }, []); const renderStepContent = () => { if (!appState) return null; switch (appState.step) { case 1: return handleUpdateState('products', p)} onIndustriesChange={(i) => handleUpdateState('target_industries', i)} t={t.step1} lang={appState.initial_params.language} />; case 2: return handleUpdateState('keywords', k)} t={t.step2} />; case 3: return handleUpdateState('competitor_candidates', c)} maxCompetitors={appState.initial_params.max_competitors} t={t.step3} />; case 4: return ; case 5: return ; case 6: return ; case 7: return ; case 8: return ; default: return null; } }; // Download logic const handleDownloadJson = () => { if (!appState) return; const content = JSON.stringify(appState, null, 2); const blob = new Blob([content], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `analysis_${appState.company.name}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); setIsDropdownOpen(false); }; const transformMatrixData = (matrixData: any[] | undefined): { [row: string]: { [col: string]: string } } => { const tableData: { [row: string]: { [col: string]: string } } = {}; if (!matrixData || !Array.isArray(matrixData) || matrixData.length === 0) { return tableData; } const allCompetitors = new Set(); matrixData.forEach(row => { if (row && Array.isArray(row.availability)) { row.availability.forEach(item => { if (item && typeof item.competitor === 'string') { allCompetitors.add(item.competitor); } }); } }); const competitorList = Array.from(allCompetitors).sort(); matrixData.forEach(row => { if (!row) return; const rowKey = 'product' in row ? row.product : ('industry' in row ? row.industry : undefined); if (typeof rowKey !== 'string' || !rowKey) { return; } const availabilityMap = new Map(); if (Array.isArray(row.availability)) { row.availability.forEach(item => { if (item && typeof item.competitor === 'string') { availabilityMap.set(item.competitor, item.has_offering); } }); } const rowObject: { [col: string]: string } = {}; competitorList.forEach(competitor => { rowObject[competitor] = availabilityMap.get(competitor) ? '✓' : ' '; }); tableData[rowKey] = rowObject; }); return tableData; }; const generateMarkdownReport = (): string => { if (!appState) return ""; const langT = t.step6.markdown; let md = `# ${langT.title}: ${appState.company.name}\n\n`; md += `**${langT.startUrl}:** ${appState.initial_params.start_url}\n`; md += `**${langT.marketScope}:** ${appState.initial_params.market_scope}\n\n`; md += `## ${langT.step1_title}\n`; md += `### ${langT.step1_products}\n`; appState.products.forEach(p => md += `- **${p.name}:** ${p.purpose}\n`); md += `\n### ${langT.step1_industries}\n`; appState.target_industries.forEach(i => md += `- ${i.name}\n`); md += `\n## ${langT.step4_title}\n`; appState.analyses.forEach(a => { md += `### ${a.competitor.name} (Overlap: ${a.overlap_score}%)\n`; md += `- **${langT.portfolio}:** ${a.portfolio.map(p => p.product).join(', ')}\n`; md += `- **${langT.targetIndustries}:** ${a.target_industries.join(', ')}\n`; md += `- **${langT.differentiators}:**\n`; a.differentiators.forEach(d => md += ` - ${d}\n`); md += `\n`; }); md += `## ${langT.step5_title}\n`; appState.silver_bullets.forEach(b => { md += `- **${langT.against} ${b.competitor_name}:** "${b.statement}"\n`; }); if (appState.battlecards && appState.battlecards.length > 0) { md += `\n## ${langT.step7_title}\n\n`; appState.battlecards.forEach(card => { md += `### ${langT.against} ${card.competitor_name}\n\n`; md += `**${langT.profile}:**\n`; md += `- **${langT.focus}:** ${card.competitor_profile.focus}\n`; md += `- **${langT.positioning}:** ${card.competitor_profile.positioning}\n\n`; md += `**${langT.strengthsVsWeaknesses}:**\n`; (card.strengths_vs_weaknesses || []).forEach(s => md += `- ${s}\n`); md += `\n`; md += `**${langT.landmineQuestions}:**\n`; (card.landmine_questions || []).forEach(q => md += `- ${q}\n`); md += `\n`; md += `**${langT.silverBullet}:**\n`; md += `> "${card.silver_bullet}"\n\n`; }); } if (appState.reference_analysis && appState.reference_analysis.length > 0) { md += `\n## ${langT.step8_title}\n`; appState.reference_analysis.forEach(analysis => { md += `### ${analysis.competitor_name}\n`; if ((analysis.references || []).length > 0) { (analysis.references || []).forEach(ref => { md += `- **${ref.name}** (${ref.industry || 'N/A'})\n`; if(ref.testimonial_snippet) md += ` - *"${ref.testimonial_snippet}"*\n`; if(ref.case_study_url) md += ` - [${langT.caseStudyLink}](${ref.case_study_url})\n`; }); } else { md += ` - ${langT.noReferencesFound}\n`; } md += `\n`; }); if (appState.reference_analysis_grounding && appState.reference_analysis_grounding.length > 0) { md += `\n#### ${langT.sources}\n`; appState.reference_analysis_grounding .filter(chunk => chunk.web && chunk.web.uri) .forEach(chunk => { md += `- [${chunk.web.title || chunk.web.uri}](${chunk.web.uri})\n`; }); } } if(appState.conclusion) { md += `\n## ${langT.step6_title}\n`; const transformForMdTable = (data: { [key: string]: { [key: string]: string } }) => { if (Object.keys(data).length === 0) return { head: '', body: '' }; const headers = Object.keys(Object.values(data)[0] || {}); const head = `| | ${headers.join(' | ')} |\n`; const separator = `|---|${headers.map(() => '---').join('|')}|\n`; const body = Object.entries(data).map(([row, cols]) => `| **${row}** | ${headers.map(h => cols[h] || ' ').join(' | ')} |`).join('\n'); return { head, body: separator + body }; }; const productMatrixForTable = transformMatrixData(appState.conclusion.product_matrix); const industryMatrixForTable = transformMatrixData(appState.conclusion.industry_matrix); md += `### ${langT.productMatrix}\n`; const prodMd = transformForMdTable(productMatrixForTable); md += prodMd.head + prodMd.body + '\n\n'; md += `### ${langT.industryMatrix}\n`; const indMd = transformForMdTable(industryMatrixForTable); md += indMd.head + indMd.body + '\n\n'; md += `### ${langT.summary}\n${appState.conclusion.summary}\n\n`; md += `### ${langT.opportunities}\n${appState.conclusion.opportunities}\n\n`; md += `### ${langT.nextQuestions}\n`; (appState.conclusion.next_questions || []).forEach(q => md += `- ${q}\n`); } return md; }; const handleDownloadMd = () => { const mdContent = generateMarkdownReport(); const blob = new Blob([mdContent], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; if (appState) { link.download = `analysis_${appState.company.name}.md`; } document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); setIsDropdownOpen(false); }; const handleDownloadPdf = async () => { if (!appState) return; await generatePdfReport(appState, t.step6.markdown); setIsDropdownOpen(false); }; return (

{t.appTitle}

{appState && highestStep >= 6 && (
{isDropdownOpen && ( )}
)} {appState && ( )}
{!appState && !isLoading && } {isLoading && !appState && } {appState && (
{error && (

{t.errors.title}

{error}

)} {isLoading ? : renderStepContent()}
{appState.step < 8 && !isLoading && (
)}
)}
); }; export default App;