Dateien nach "general-market-intelligence" hochladen
This commit is contained in:
1
general-market-intelligence/.env.local
Normal file
1
general-market-intelligence/.env.local
Normal file
@@ -0,0 +1 @@
|
||||
GEMINI_API_KEY=PLACEHOLDER_API_KEY
|
||||
24
general-market-intelligence/.gitignore
vendored
Normal file
24
general-market-intelligence/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
188
general-market-intelligence/App.tsx
Normal file
188
general-market-intelligence/App.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
|
||||
import React, { useState, useCallback } 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 } from './services/geminiService';
|
||||
|
||||
const generateId = () => Math.random().toString(36).substr(2, 9);
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [step, setStep] = useState<AppStep>(AppStep.INPUT);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [language, setLanguage] = useState<Language>('de');
|
||||
const [referenceUrl, setReferenceUrl] = useState<string>('');
|
||||
const [targetMarket, setTargetMarket] = useState<string>('');
|
||||
|
||||
// Data States
|
||||
const [strategy, setStrategy] = useState<SearchStrategy | null>(null);
|
||||
const [competitors, setCompetitors] = useState<Competitor[]>([]);
|
||||
const [analysisResults, setAnalysisResults] = useState<AnalysisResult[]>([]);
|
||||
const [processingState, setProcessingState] = useState<AnalysisState>({
|
||||
currentCompany: '', progress: 0, total: 0, completed: 0
|
||||
});
|
||||
|
||||
const [selectedCompanyForOutreach, setSelectedCompanyForOutreach] = useState<AnalysisResult | null>(null);
|
||||
|
||||
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, productContext: string, market: string, selectedLang: Language) => {
|
||||
setIsLoading(true);
|
||||
setLanguage(selectedLang);
|
||||
setReferenceUrl(url);
|
||||
setTargetMarket(market);
|
||||
try {
|
||||
// 1. Generate Strategy first
|
||||
const generatedStrategy = await generateSearchStrategy(url, productContext, selectedLang);
|
||||
setStrategy(generatedStrategy);
|
||||
setStep(AppStep.STRATEGY);
|
||||
} catch (error) {
|
||||
alert("Failed to generate strategy. Please try again.");
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadReport = (loadedStrategy: SearchStrategy, loadedResults: AnalysisResult[]) => {
|
||||
setStrategy(loadedStrategy);
|
||||
setAnalysisResults(loadedResults);
|
||||
// Reconstruct competitors list from results for consistency if user goes back
|
||||
const reconstructedCompetitors = loadedResults.map(r => ({
|
||||
id: generateId(),
|
||||
name: r.companyName,
|
||||
dataSource: r.dataSource
|
||||
}));
|
||||
setCompetitors(reconstructedCompetitors);
|
||||
setStep(AppStep.REPORT);
|
||||
};
|
||||
|
||||
const handleStrategyConfirm = async (finalStrategy: SearchStrategy) => {
|
||||
setStrategy(finalStrategy);
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// 2. Identify Competitors based on Reference
|
||||
const results = await identifyCompetitors(referenceUrl, targetMarket, language);
|
||||
const mappedCompetitors: Competitor[] = results.map(c => ({
|
||||
id: generateId(),
|
||||
name: c.name || "Unknown",
|
||||
url: c.url,
|
||||
description: c.description
|
||||
}));
|
||||
setCompetitors(mappedCompetitors);
|
||||
setStep(AppStep.REVIEW_LIST);
|
||||
} catch (e) {
|
||||
alert("Failed to find companies.");
|
||||
} 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 });
|
||||
|
||||
const results: AnalysisResult[] = [];
|
||||
|
||||
for (let i = 0; i < competitors.length; i++) {
|
||||
const comp = competitors[i];
|
||||
setProcessingState(prev => ({ ...prev, currentCompany: comp.name }));
|
||||
|
||||
try {
|
||||
// 3. Analyze using the specific strategy
|
||||
const result = await analyzeCompanyWithStrategy(comp.name, strategy, language);
|
||||
results.push(result);
|
||||
} catch (e) {
|
||||
console.error(`Failed to analyze ${comp.name}`);
|
||||
}
|
||||
setProcessingState(prev => ({ ...prev, completed: i + 1 }));
|
||||
}
|
||||
|
||||
setAnalysisResults(results);
|
||||
setStep(AppStep.REPORT);
|
||||
}, [competitors, language, strategy]);
|
||||
|
||||
const handleRestart = () => {
|
||||
setCompetitors([]);
|
||||
setAnalysisResults([]);
|
||||
setStrategy(null);
|
||||
setStep(AppStep.INPUT);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 text-slate-900 font-sans">
|
||||
<Header showBack={step !== AppStep.INPUT && step !== AppStep.ANALYSIS} onBack={handleBack} />
|
||||
<main>
|
||||
{step === AppStep.INPUT && (
|
||||
<StepInput onSearch={handleInitialInput} onLoadReport={handleLoadReport} isLoading={isLoading} />
|
||||
)}
|
||||
|
||||
{step === AppStep.STRATEGY && strategy && (
|
||||
<StepStrategy strategy={strategy} onConfirm={handleStrategyConfirm} />
|
||||
)}
|
||||
|
||||
{step === AppStep.REVIEW_LIST && (
|
||||
<StepReview
|
||||
competitors={competitors}
|
||||
onAdd={handleAddCompetitor}
|
||||
onRemove={handleRemoveCompetitor}
|
||||
onConfirm={runAnalysis}
|
||||
hasResults={analysisResults.length > 0}
|
||||
onShowReport={() => setStep(AppStep.REPORT)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === AppStep.ANALYSIS && (
|
||||
<StepProcessing state={processingState} />
|
||||
)}
|
||||
|
||||
{step === AppStep.REPORT && strategy && (
|
||||
<StepReport
|
||||
results={analysisResults}
|
||||
strategy={strategy}
|
||||
onRestart={handleRestart}
|
||||
language={language}
|
||||
onStartOutreach={(company) => {
|
||||
setSelectedCompanyForOutreach(company);
|
||||
setStep(AppStep.OUTREACH);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === AppStep.OUTREACH && selectedCompanyForOutreach && (
|
||||
<StepOutreach
|
||||
company={selectedCompanyForOutreach}
|
||||
language={language}
|
||||
referenceUrl={referenceUrl}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
35
general-market-intelligence/index.html
Normal file
35
general-market-intelligence/index.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>parcelLab Market Intelligence</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"react": "https://aistudiocdn.com/react@^19.2.0",
|
||||
"react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
|
||||
"react/": "https://aistudiocdn.com/react@^19.2.0/",
|
||||
"@google/genai": "https://aistudiocdn.com/@google/genai@^1.29.0",
|
||||
"lucide-react": "https://aistudiocdn.com/lucide-react@^0.554.0",
|
||||
"uuid": "https://aistudiocdn.com/uuid@^13.0.0",
|
||||
"nanoid": "https://aistudiocdn.com/nanoid@^5.1.6",
|
||||
"jspdf": "https://aistudiocdn.com/jspdf@^2.5.1",
|
||||
"jspdf-autotable": "https://aistudiocdn.com/jspdf-autotable@^3.8.1"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<link rel="stylesheet" href="/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
15
general-market-intelligence/index.tsx
Normal file
15
general-market-intelligence/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
if (!rootElement) {
|
||||
throw new Error("Could not find root element to mount to");
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
Reference in New Issue
Block a user