From d6f7db2a6e4a0841413266eec1a6f9bacb4be1fe Mon Sep 17 00:00:00 2001 From: Floke Date: Sat, 20 Dec 2025 20:58:40 +0000 Subject: [PATCH] Dateien nach "general-market-intelligence/components" hochladen --- .../components/StepInput.tsx | 244 ++++++++++++++ .../components/StepOutreach.tsx | 313 ++++++++++++++++++ .../components/StepProcessing.tsx | 65 ++++ .../components/StepReport.tsx | 204 ++++++++++++ .../components/StepReview.tsx | 122 +++++++ 5 files changed, 948 insertions(+) create mode 100644 general-market-intelligence/components/StepInput.tsx create mode 100644 general-market-intelligence/components/StepOutreach.tsx create mode 100644 general-market-intelligence/components/StepProcessing.tsx create mode 100644 general-market-intelligence/components/StepReport.tsx create mode 100644 general-market-intelligence/components/StepReview.tsx diff --git a/general-market-intelligence/components/StepInput.tsx b/general-market-intelligence/components/StepInput.tsx new file mode 100644 index 00000000..a5a07e45 --- /dev/null +++ b/general-market-intelligence/components/StepInput.tsx @@ -0,0 +1,244 @@ + +import React, { useState } from 'react'; +import { Search, ArrowRight, Loader2, Globe, Link as LinkIcon, Languages, Upload, FileText, X, FolderOpen, FileUp } from 'lucide-react'; +import { Language, AnalysisResult, SearchStrategy } from '../types'; +import { parseMarkdownReport } from '../utils/reportParser'; + +interface StepInputProps { + onSearch: (url: string, context: string, market: string, language: Language) => void; + onLoadReport: (strategy: SearchStrategy, results: AnalysisResult[]) => void; + isLoading: boolean; +} + +const COUNTRIES = [ + "Germany", "Austria", "Switzerland", "United Kingdom", "France", "Spain", "Italy", "Netherlands", "Europe (General)", "USA" +]; + +export const StepInput: React.FC = ({ onSearch, onLoadReport, isLoading }) => { + const [activeMode, setActiveMode] = useState<'new' | 'load'>('new'); + const [url, setUrl] = useState(''); + const [fileContent, setFileContent] = useState(''); + const [fileName, setFileName] = useState(''); + const [market, setMarket] = useState(COUNTRIES[0]); + const [language, setLanguage] = useState('de'); + + const handleFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + setFileContent(event.target?.result as string); + setFileName(file.name); + }; + reader.readAsText(file); + } + }; + + const handleLoadReport = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + const content = event.target?.result as string; + const parsed = parseMarkdownReport(content); + if (parsed) { + onLoadReport(parsed.strategy, parsed.results); + } else { + alert("Could not parse report. Please make sure it's a valid ProspectIntel MD file."); + } + }; + reader.readAsText(file); + } + }; + + const handleRemoveFile = () => { + setFileContent(''); + setFileName(''); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (url.trim() && fileContent.trim()) { + onSearch(url.trim(), fileContent.trim(), market, language); + } + }; + + return ( +
+
+

Market Intelligence Agent

+
+ + +
+ + {activeMode === 'new' ? ( +

+ Upload your Strategy Document to let AI design the perfect market audit. +

+ ) : ( +

+ Select an exported .md Report to continue working on an existing analysis. +

+ )} +
+ +
+ {activeMode === 'new' ? ( +
+
+ + + {!fileContent ? ( +
+ + +
+ ) : ( +
+
+ +
+
+

{fileName}

+

Context loaded successfully

+
+ +
+ )} +
+ +
+ +
+ setUrl(e.target.value)} + placeholder="e.g. https://www.reference-customer.com" + className="w-full pl-12 pr-4 py-3 rounded-xl border border-slate-200 focus:border-indigo-500 focus:ring-4 focus:ring-indigo-500/10 outline-none transition-all text-lg" + required + /> + +
+

Used to calibrate the search and find lookalikes.

+
+ +
+
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+ ) : ( +
+
+ + +
+ +
+

Note:

+

+ Loading an existing audit will take you directly to the Report view. You can then trigger new outreach campaigns for any company in the list. +

+
+
+ )} +
+
+ ); +}; diff --git a/general-market-intelligence/components/StepOutreach.tsx b/general-market-intelligence/components/StepOutreach.tsx new file mode 100644 index 00000000..eb289af3 --- /dev/null +++ b/general-market-intelligence/components/StepOutreach.tsx @@ -0,0 +1,313 @@ + +import React, { useState } from 'react'; +import { AnalysisResult, EmailDraft, Language } from '../types'; +import { generateOutreachCampaign, translateEmailDrafts } from '../services/geminiService'; +import { Upload, FileText, Sparkles, Copy, Check, Loader2, ArrowRight, CheckCircle2, Download, Languages } from 'lucide-react'; + +interface StepOutreachProps { + company: AnalysisResult; + language: Language; + referenceUrl: string; + onBack: () => void; +} + +export const StepOutreach: React.FC = ({ company, language, referenceUrl, onBack }) => { + const [fileContent, setFileContent] = useState(''); + const [fileName, setFileName] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + const [isTranslating, setIsTranslating] = useState(false); + const [emails, setEmails] = useState([]); + const [activeTab, setActiveTab] = useState(0); + const [copied, setCopied] = useState(false); + + const handleFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + setFileContent(event.target?.result as string); + setFileName(file.name); + }; + reader.readAsText(file); + } + }; + + const handleGenerate = async () => { + if (!fileContent) return; + setIsProcessing(true); + try { + const drafts = await generateOutreachCampaign(company, fileContent, language, referenceUrl); + setEmails(drafts); + } catch (e) { + console.error(e); + alert('Failed to generate campaign. Please try again.'); + } finally { + setIsProcessing(false); + } + }; + + const handleTranslate = async (targetLang: Language) => { + if (emails.length === 0) return; + setIsTranslating(true); + try { + const translated = await translateEmailDrafts(emails, targetLang); + setEmails(translated); + } catch (e) { + console.error("Translation failed", e); + alert("Translation failed."); + } finally { + setIsTranslating(false); + } + } + + const handleDownloadMD = () => { + if (emails.length === 0) return; + + let content = `# Hyper-Personalized Campaign: ${company.companyName}\n\n`; + content += `Generated by parcelLab Intel\n\n`; + + emails.forEach((email, index) => { + content += `## Variant ${index + 1}: ${email.persona}\n\n`; + content += `**Subject:** ${email.subject}\n\n`; + content += `**Body:**\n\n${email.body}\n\n`; + content += `**Analysis:**\n`; + email.keyPoints.forEach(kp => content += `- ${kp}\n`); + content += `\n---\n\n`; + }); + + const blob = new Blob([content], { type: 'text/markdown' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `campaign_${company.companyName.replace(/\s+/g, '_')}_variants.md`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + // Helper to render text with bold syntax **text** + const renderBoldText = (text: string) => { + const parts = text.split(/(\*\*.*?\*\*)/g); + return parts.map((part, index) => { + if (part.startsWith('**') && part.endsWith('**')) { + return {part.slice(2, -2)}; + } + return {part}; + }); + }; + + if (emails.length > 0) { + const activeEmail = emails[activeTab]; + return ( +
+
+
+

Hyper-Personalized Campaign

+

Target: {company.companyName}

+
+ +
+ + {/* Toolbar */} +
+
+ + Translate + +
+ + + + {isTranslating && } +
+ + +
+ +
+ {/* Sidebar Tabs */} +
+ {emails.map((email, idx) => ( + + ))} +
+ + {/* Content Area */} +
+ {activeEmail ? ( + <> +
+ +
+ {activeEmail.subject} +
+
+ +
+
+ + +
+
+ {renderBoldText(activeEmail.body)} +
+
+ + {/* Checklist Section */} + {activeEmail.keyPoints && activeEmail.keyPoints.length > 0 && ( +
+

+ + Persona & KPI Analysis +

+
    + {activeEmail.keyPoints.map((point, i) => ( +
  • +
    + +
    + {point} +
  • + ))} +
+
+ )} + + ) : ( +
Select a variant
+ )} +
+
+
+ ); + } + + return ( +
+
+
+ +
+

Create Outreach Campaign

+

+ Generating emails for {company.companyName}. +
Please upload your marketing knowledge base (Markdown/Text) to begin. +

+
+ +
+ {!fileContent ? ( +
+ + +
+ ) : ( +
+
+
+ +
+
+

Knowledge Base Loaded

+

{fileName}

+
+ +
+ + +
+ )} +
+
+ +
+
+ ); +}; diff --git a/general-market-intelligence/components/StepProcessing.tsx b/general-market-intelligence/components/StepProcessing.tsx new file mode 100644 index 00000000..f22cea9b --- /dev/null +++ b/general-market-intelligence/components/StepProcessing.tsx @@ -0,0 +1,65 @@ + +import React, { useEffect, useRef } from 'react'; +import { Loader, TrendingUp, Search, Eye } from 'lucide-react'; +import { AnalysisState } from '../types'; + +interface StepProcessingProps { + state: AnalysisState; +} + +export const StepProcessing: React.FC = ({ state }) => { + const percentage = Math.round((state.completed / state.total) * 100) || 0; + const terminalRef = useRef(null); + + useEffect(() => { + if (terminalRef.current) { + terminalRef.current.scrollTop = terminalRef.current.scrollHeight; + } + }, [state.completed, state.currentCompany]); + + return ( +
+
+
+ +
+

Running Market Audit

+

Analyzing digital traces based on your strategy...

+
+ +
+
+ Progress + {percentage}% ({state.completed}/{state.total}) +
+
+
+
+
+ +
+
+
+
+
+ audit_agent.exe +
+
+
$ load_strategy --context="custom"
+ {state.completed > 0 &&
... {state.completed} companies analyzed.
} + + {state.currentCompany && ( + <> +
{`> Analyzing: ${state.currentCompany}`}
+
+
Fetching Firmographics (Revenue/Size)...
+
Scanning Custom Signals...
+
Validating against ICP...
+
+ + )} +
+
+
+ ); +}; diff --git a/general-market-intelligence/components/StepReport.tsx b/general-market-intelligence/components/StepReport.tsx new file mode 100644 index 00000000..416194fe --- /dev/null +++ b/general-market-intelligence/components/StepReport.tsx @@ -0,0 +1,204 @@ + +import React, { useMemo } from 'react'; +import { LeadStatus, AnalysisResult, Language, Tier, SearchStrategy } from '../types'; +import { ExternalLink, AlertCircle, CheckCircle, XCircle, RefreshCcw, ArrowLeft, Clock, Euro, Download, FileText, ShoppingBag, Star, TrendingUp, Users, Database, Send, Server, Truck, Eye, Search } from 'lucide-react'; + +interface StepReportProps { + results: AnalysisResult[]; + strategy: SearchStrategy; + onRestart: () => void; + onStartOutreach: (company: AnalysisResult) => void; + language: Language; +} + +export const StepReport: React.FC = ({ results, strategy, onRestart, onStartOutreach, language }) => { + + const sortedResults = useMemo(() => { + return [...results].sort((a, b) => { + const tierOrder = { [Tier.TIER_1]: 3, [Tier.TIER_2]: 2, [Tier.TIER_3]: 1 }; + const tierDiff = tierOrder[b.tier] - tierOrder[a.tier]; + if (tierDiff !== 0) return tierDiff; + if (a.status === LeadStatus.POTENTIAL && b.status !== LeadStatus.POTENTIAL) return -1; + if (a.status !== LeadStatus.POTENTIAL && b.status === LeadStatus.POTENTIAL) return 1; + return 0; + }); + }, [results]); + + const getStatusColor = (status: LeadStatus) => { + switch (status) { + case LeadStatus.CUSTOMER: return 'bg-blue-50 text-blue-700 border-blue-200'; + case LeadStatus.COMPETITOR: return 'bg-red-50 text-red-700 border-red-200'; + case LeadStatus.POTENTIAL: return 'bg-emerald-50 text-emerald-700 border-emerald-200'; + default: return 'bg-slate-50 text-slate-700 border-slate-200'; + } + }; + + const getTierColor = (tier: Tier) => { + switch (tier) { + case Tier.TIER_1: return 'bg-purple-100 text-purple-800 border-purple-200'; + case Tier.TIER_2: return 'bg-indigo-50 text-indigo-700 border-indigo-200'; + case Tier.TIER_3: return 'bg-slate-50 text-slate-600 border-slate-200'; + } + }; + + const downloadMarkdown = () => { + const signalHeaders = strategy.signals.map(s => s.name); + const headers = ["Company", "Prio", "Rev/Emp", "Status", ...signalHeaders, "Recommendation"]; + + const rows = sortedResults.map(r => { + const signalValues = strategy.signals.map(s => r.dynamicAnalysis[s.id]?.value || '-'); + return `| ${r.companyName} | ${r.tier} | ${r.revenue} / ${r.employees} | ${r.status} | ${signalValues.join(" | ")} | ${r.recommendation} |`; + }); + + const content = ` +# Market Intelligence Report: ${strategy.productContext} +**Context:** ${strategy.idealCustomerProfile} + +| ${headers.join(" | ")} | +|${headers.map(() => "---").join("|")}| +${rows.join("\n")} + `; + + const blob = new Blob([content], { type: 'text/markdown' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `report_${new Date().toISOString().slice(0,10)}.md`; + a.click(); + URL.revokeObjectURL(url); + }; + + return ( +
+
+
+

Analysis Report

+

Context: {strategy.productContext}

+
+
+ +
+
+ +
+
+ + + + + + + + + + {sortedResults.map((row, idx) => ( + + + {/* Column 1: Firmographics */} + + + {/* Column 2: Intelligence (Stacked) */} + + + {/* Column 3: Action */} + + + ))} + +
Company ProfileStrategic Intelligence & AuditAction
+
+
+
{row.companyName}
+ +
+ + {row.tier} + + + {row.status} + +
+
+ +
+
+ Revenue + {row.revenue} +
+
+ Employees + {row.employees} +
+
+ Source + {row.dataSource} +
+
+
+
+
+ {/* Recommendation Block */} +
+
+ AI Recommendation +
+

+ "{row.recommendation}" +

+
+ + {/* Dynamic Signals Stacked */} +
+ {strategy.signals.map((s, i) => { + const data = row.dynamicAnalysis[s.id]; + if (!data) return null; + return ( +
+
+
+ {i + 1} +
+ {s.name} +
+
+
+ {data.value} +
+ {data.proof && ( +
+ + Evidence: {data.proof} +
+ )} +
+
+ ); + })} +
+
+
+ +
+
+
+ +
+ + +
+
+ ); +}; diff --git a/general-market-intelligence/components/StepReview.tsx b/general-market-intelligence/components/StepReview.tsx new file mode 100644 index 00000000..37af2de5 --- /dev/null +++ b/general-market-intelligence/components/StepReview.tsx @@ -0,0 +1,122 @@ + +import React, { useState } from 'react'; +import { Trash2, Plus, CheckCircle2, ExternalLink, FileText } from 'lucide-react'; +import { Competitor } from '../types'; + +interface StepReviewProps { + competitors: Competitor[]; + onRemove: (id: string) => void; + onAdd: (name: string) => void; + onConfirm: () => void; + hasResults?: boolean; + onShowReport?: () => void; +} + +export const StepReview: React.FC = ({ competitors, onRemove, onAdd, onConfirm, hasResults, onShowReport }) => { + const [newCompany, setNewCompany] = useState(''); + + const handleAdd = (e: React.FormEvent) => { + e.preventDefault(); + if (newCompany.trim()) { + onAdd(newCompany.trim()); + setNewCompany(''); + } + }; + + return ( +
+
+
+

Confirm Target List

+

Review the identified companies before starting the deep tech audit.

+
+
+ + {competitors.length} Companies + +
+
+ +
+
    + {competitors.map((comp) => ( +
  • +
    +
    + {comp.name} + {comp.url && ( + + + + )} +
    + {comp.description && ( +

    {comp.description}

    + )} +
    + + +
  • + ))} + {competitors.length === 0 && ( +
  • + No companies in list. Add some manually below. +
  • + )} +
+ +
+
+ setNewCompany(e.target.value)} + placeholder="Add another company manually..." + className="flex-1 px-4 py-2 rounded-lg border border-slate-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 outline-none" + /> + +
+
+
+ + {/* Sticky Footer CTA */} +
+
+ {hasResults && onShowReport && ( + + )} + +
+
+
+ ); +};