feat(b2b): Add batch processing, industry selection, optimized PDF export, and update docs
This commit is contained in:
@@ -22,6 +22,8 @@ const App: React.FC = () => {
|
|||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [generationStep, setGenerationStep] = useState<number>(0); // 0: idle, 1-6: step X is complete
|
const [generationStep, setGenerationStep] = useState<number>(0); // 0: idle, 1-6: step X is complete
|
||||||
|
const [selectedIndustry, setSelectedIndustry] = useState<string>('');
|
||||||
|
const [batchStatus, setBatchStatus] = useState<{ current: number; total: number; industry: string } | null>(null);
|
||||||
|
|
||||||
const t = translations[inputData.language];
|
const t = translations[inputData.language];
|
||||||
const STEP_TITLES = t.stepTitles;
|
const STEP_TITLES = t.stepTitles;
|
||||||
@@ -37,6 +39,8 @@ const App: React.FC = () => {
|
|||||||
setError(null);
|
setError(null);
|
||||||
setAnalysisData({});
|
setAnalysisData({});
|
||||||
setGenerationStep(0);
|
setGenerationStep(0);
|
||||||
|
setSelectedIndustry('');
|
||||||
|
setBatchStatus(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/start-generation`, {
|
const response = await fetch(`${API_BASE_URL}/start-generation`, {
|
||||||
@@ -75,6 +79,11 @@ const App: React.FC = () => {
|
|||||||
const handleGenerateNextStep = useCallback(async () => {
|
const handleGenerateNextStep = useCallback(async () => {
|
||||||
if (generationStep >= 6) return;
|
if (generationStep >= 6) return;
|
||||||
|
|
||||||
|
if (generationStep === 5 && !selectedIndustry) {
|
||||||
|
setError('Bitte wählen Sie eine Fokus-Branche aus.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
@@ -87,6 +96,7 @@ const App: React.FC = () => {
|
|||||||
language: inputData.language,
|
language: inputData.language,
|
||||||
channels: inputData.channels,
|
channels: inputData.channels,
|
||||||
generationStep: generationStep + 1, // Pass the step we want to generate
|
generationStep: generationStep + 1, // Pass the step we want to generate
|
||||||
|
focusIndustry: generationStep === 5 ? selectedIndustry : undefined,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,7 +121,77 @@ const App: React.FC = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [analysisData, generationStep, inputData.channels, inputData.language]);
|
}, [analysisData, generationStep, inputData.channels, inputData.language, selectedIndustry]);
|
||||||
|
|
||||||
|
const handleBatchGenerate = useCallback(async () => {
|
||||||
|
if (!analysisData.targetGroups?.rows) return;
|
||||||
|
|
||||||
|
const industries = analysisData.targetGroups.rows.map(row => row[0]);
|
||||||
|
if (industries.length === 0) return;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setGenerationStep(6); // Show the Step 6 container (will be filled incrementally)
|
||||||
|
|
||||||
|
// Initialize Step 6 data container
|
||||||
|
setAnalysisData(prev => ({
|
||||||
|
...prev,
|
||||||
|
messages: {
|
||||||
|
summary: ["Batch-Analyse aller Branchen läuft..."],
|
||||||
|
headers: ["Fokus-Branche", "Rolle", "Kernbotschaft", "Kanäle"], // Default headers, will be overwritten/verified
|
||||||
|
rows: []
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
let aggregatedRows: string[][] = [];
|
||||||
|
let capturedHeaders: string[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < industries.length; i++) {
|
||||||
|
const industry = industries[i];
|
||||||
|
setBatchStatus({ current: i + 1, total: industries.length, industry });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/next-step`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
analysisData, // Pass full context
|
||||||
|
language: inputData.language,
|
||||||
|
channels: inputData.channels,
|
||||||
|
generationStep: 6,
|
||||||
|
focusIndustry: industry,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`HTTP error for ${industry}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.messages && data.messages.rows) {
|
||||||
|
if (capturedHeaders.length === 0 && data.messages.headers) {
|
||||||
|
capturedHeaders = data.messages.headers;
|
||||||
|
}
|
||||||
|
aggregatedRows = [...aggregatedRows, ...data.messages.rows];
|
||||||
|
|
||||||
|
// Update state incrementally so user sees results growing
|
||||||
|
setAnalysisData(prev => ({
|
||||||
|
...prev,
|
||||||
|
messages: {
|
||||||
|
summary: ["Vollständige Analyse über alle identifizierten Branchen."],
|
||||||
|
headers: capturedHeaders.length > 0 ? capturedHeaders : (prev?.messages?.headers || []),
|
||||||
|
rows: aggregatedRows
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error processing industry ${industry}:`, e);
|
||||||
|
// We continue with next industry even if one fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
setBatchStatus(null);
|
||||||
|
}, [analysisData, inputData]);
|
||||||
|
|
||||||
const handleDataChange = <K extends keyof AnalysisData>(step: K, newData: AnalysisData[K]) => {
|
const handleDataChange = <K extends keyof AnalysisData>(step: K, newData: AnalysisData[K]) => {
|
||||||
if (analysisData[step]) {
|
if (analysisData[step]) {
|
||||||
@@ -154,6 +234,59 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const renderContinueButton = (stepNumber: number) => {
|
const renderContinueButton = (stepNumber: number) => {
|
||||||
if (isLoading || generationStep !== stepNumber - 1) return null;
|
if (isLoading || generationStep !== stepNumber - 1) return null;
|
||||||
|
|
||||||
|
// Industry Selector Logic for Step 6
|
||||||
|
if (stepNumber === 6 && analysisData.targetGroups?.rows) {
|
||||||
|
const industries = analysisData.targetGroups.rows.map(row => row[0]); // Assume Col 0 is Industry
|
||||||
|
return (
|
||||||
|
<div className="my-8 max-w-2xl mx-auto bg-white dark:bg-slate-800 p-6 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 print:hidden">
|
||||||
|
<h3 className="text-lg font-semibold text-slate-900 dark:text-white mb-4 text-center">
|
||||||
|
Wählen Sie eine Fokus-Branche für Schritt 6 (Botschaften):
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div className="flex justify-center mb-6">
|
||||||
|
<button
|
||||||
|
onClick={handleBatchGenerate}
|
||||||
|
className="flex items-center px-4 py-2 bg-slate-800 hover:bg-slate-700 text-white rounded-md text-sm font-medium transition-colors shadow-sm"
|
||||||
|
>
|
||||||
|
<SparklesIcon className="mr-2 h-4 w-4 text-yellow-400" />
|
||||||
|
Alle {industries.length} Branchen analysieren (Batch)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center text-xs text-slate-500 mb-2 uppercase font-semibold tracking-wider">- ODER EINZELN -</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 mb-6 max-h-60 overflow-y-auto pr-2">
|
||||||
|
{industries.map((ind, idx) => (
|
||||||
|
<label key={idx} className={`flex items-center p-3 rounded-lg border cursor-pointer transition-all ${selectedIndustry === ind ? 'bg-sky-50 border-sky-500 ring-1 ring-sky-500 dark:bg-sky-900/20 dark:border-sky-400' : 'border-slate-200 hover:border-slate-300 dark:border-slate-700 dark:hover:border-slate-600'}`}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="industry"
|
||||||
|
value={ind}
|
||||||
|
checked={selectedIndustry === ind}
|
||||||
|
onChange={(e) => { setSelectedIndustry(e.target.value); setError(null); }}
|
||||||
|
className="h-4 w-4 text-sky-600 focus:ring-sky-500 border-gray-300 dark:bg-slate-700 dark:border-slate-600"
|
||||||
|
/>
|
||||||
|
<span className="ml-3 block text-sm font-medium text-slate-700 dark:text-slate-200 break-words">
|
||||||
|
{ind}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<button
|
||||||
|
onClick={handleGenerateNextStep}
|
||||||
|
disabled={!selectedIndustry}
|
||||||
|
className="flex items-center justify-center w-full sm:w-auto mx-auto px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:bg-slate-400 dark:disabled:bg-slate-600 disabled:cursor-not-allowed transition-colors duration-200"
|
||||||
|
>
|
||||||
|
<SparklesIcon className="mr-2 h-5 w-5" />
|
||||||
|
{t.continueButton.replace('{{step}}', (stepNumber - 1).toString()).replace('{{nextStep}}', stepNumber.toString())}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="my-8 text-center print:hidden">
|
<div className="my-8 text-center print:hidden">
|
||||||
<button
|
<button
|
||||||
@@ -201,8 +334,18 @@ const App: React.FC = () => {
|
|||||||
<div className="flex flex-col items-center justify-center text-center p-8">
|
<div className="flex flex-col items-center justify-center text-center p-8">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
<p className="mt-4 text-lg text-slate-600 dark:text-slate-400 animate-pulse">
|
<p className="mt-4 text-lg text-slate-600 dark:text-slate-400 animate-pulse">
|
||||||
{t.generatingStep.replace('{{stepTitle}}', STEP_TITLES[STEP_KEYS[generationStep]])}
|
{batchStatus
|
||||||
|
? `Analysiere Branche ${batchStatus.current} von ${batchStatus.total}: ${batchStatus.industry}...`
|
||||||
|
: t.generatingStep.replace('{{stepTitle}}', STEP_TITLES[STEP_KEYS[generationStep]])}
|
||||||
</p>
|
</p>
|
||||||
|
{batchStatus && (
|
||||||
|
<div className="w-64 h-2 bg-slate-200 rounded-full mt-4 overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full bg-sky-500 transition-all duration-500 ease-out"
|
||||||
|
style={{ width: `${(batchStatus.current / batchStatus.total) * 100}%` }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ export const StepDisplay: React.FC<StepDisplayProps> = ({ title, summary, header
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-x-auto print:overflow-visible print:block">
|
<div className="overflow-x-auto print:hidden">
|
||||||
<table className="w-full table-fixed text-sm text-left text-slate-500 dark:text-slate-400">
|
<table className="w-full table-fixed text-sm text-left text-slate-500 dark:text-slate-400">
|
||||||
<thead className="text-xs text-slate-700 dark:text-slate-300 uppercase bg-slate-100 dark:bg-slate-700">
|
<thead className="text-xs text-slate-700 dark:text-slate-300 uppercase bg-slate-100 dark:bg-slate-700">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -275,6 +275,22 @@ export const StepDisplay: React.FC<StepDisplayProps> = ({ title, summary, header
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Print-Only Block View for readable PDFs */}
|
||||||
|
<div className="hidden print:block space-y-8">
|
||||||
|
{filteredRows.map((row, rowIndex) => (
|
||||||
|
<div key={rowIndex} className="border-b border-slate-300 pb-6 mb-6 break-inside-avoid">
|
||||||
|
{headers.map((header, colIndex) => (
|
||||||
|
<div key={colIndex} className="mb-4">
|
||||||
|
<h4 className="font-bold text-xs uppercase text-slate-500 mb-1">{header}</h4>
|
||||||
|
<div className="text-sm text-slate-900 whitespace-pre-wrap leading-relaxed font-serif">
|
||||||
|
{row[colIndex]}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
{canAddRows && (
|
{canAddRows && (
|
||||||
<div className="mt-6 text-left print:hidden">
|
<div className="mt-6 text-left print:hidden">
|
||||||
{isAddingRow ? (
|
{isAddingRow ? (
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ app.post('/api/start-generation', (req, res) => {
|
|||||||
// API-Endpunkt, um den nächsten Schritt zu generieren
|
// API-Endpunkt, um den nächsten Schritt zu generieren
|
||||||
app.post('/api/next-step', (req, res) => {
|
app.post('/api/next-step', (req, res) => {
|
||||||
console.log(`[${new Date().toISOString()}] HIT: /api/next-step`);
|
console.log(`[${new Date().toISOString()}] HIT: /api/next-step`);
|
||||||
const { analysisData, language, channels, generationStep } = req.body;
|
const { analysisData, language, channels, generationStep, focusIndustry } = req.body;
|
||||||
|
|
||||||
if (!analysisData || !language || generationStep === undefined) {
|
if (!analysisData || !language || generationStep === undefined) {
|
||||||
return res.status(400).json({ error: 'Missing required parameters: analysisData, language, generationStep.' });
|
return res.status(400).json({ error: 'Missing required parameters: analysisData, language, generationStep.' });
|
||||||
@@ -128,6 +128,10 @@ app.post('/api/next-step', (req, res) => {
|
|||||||
if (channels && Array.isArray(channels)) {
|
if (channels && Array.isArray(channels)) {
|
||||||
args.push('--channels', channels.join(','));
|
args.push('--channels', channels.join(','));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (focusIndustry) {
|
||||||
|
args.push('--focus_industry', focusIndustry);
|
||||||
|
}
|
||||||
|
|
||||||
// Da die runPythonScript-Funktion res behandelt, fügen wir hier die Bereinigung hinzu
|
// Da die runPythonScript-Funktion res behandelt, fügen wir hier die Bereinigung hinzu
|
||||||
const originalJson = res.json.bind(res);
|
const originalJson = res.json.bind(res);
|
||||||
|
|||||||
@@ -87,16 +87,220 @@ Dieses Projekt ist der erste Schritt zur Schaffung eines einheitlichen "Strategy
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 5. Status: Produktionsbereit
|
## 5. Deployment & Betrieb
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Das System liefert nun hochqualitative, faktenbasierte Analysen ("Grounding"), die weit über die ursprüngliche Online-Version hinausgehen. Alle bekannten Fehler (Timeouts, API 404, Copy-Paste) sind behoben.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Nächste Schritte (Optional)
|
|
||||||
|
|
||||||
- Erweiterung auf Multi-Language Support im Frontend (aktuell DE fokussiert).
|
Da das Frontend (`App.tsx`) in das Docker-Image kompiliert wird, müssen Änderungen am Code durch einen **Rebuild** aktiviert werden.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Standard-Start (für Nutzung)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Wenn das Image bereits aktuell ist:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
docker run -d -p 3004:3002 --name b2b-assistant-instance \
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-v "$(pwd)/b2b_marketing_orchestrator.py:/app/b2b_marketing_orchestrator.py" \
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-v "$(pwd)/b2b-marketing-assistant/server.cjs:/app/server.cjs" \
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-v "$(pwd)/gemini_api_key.txt:/app/gemini_api_key.txt" \
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-v "$(pwd)/Log_from_docker:/app/Log_from_docker" \
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
b2b-marketing-assistant
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Das Tool ist dann unter `http://localhost:3004` erreichbar. Logs finden Sie im Ordner `Log_from_docker`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Update & Rebuild (nach Code-Änderungen)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Wenn Sie `App.tsx`, `index.html` oder `package.json` geändert haben, **müssen** Sie neu bauen:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. **Alten Container entfernen:**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
docker stop b2b-assistant-instance
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
docker rm b2b-assistant-instance
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2. **Image neu bauen:**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
docker build -t b2b-marketing-assistant -f b2b-marketing-assistant/Dockerfile .
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3. **Neu starten:** (siehe Befehl oben).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 6. Roadmap: Nächste Erweiterungen
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Priorität 1: Persistenz & Dashboard (SQLite)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Um Datenverlust zu vermeiden und Analysen wiederaufnehmbar zu machen.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* **Backend:** Integration einer SQLite-Datenbank (`projects.db`).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* **API:** Endpunkte für `save_project`, `load_project`, `list_projects`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* **Frontend:** Dashboard-Ansicht ("Letzte Analysen") und Speicher-Automatik nach jedem Schritt.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* **Nutzen:** Ermöglicht Batch-Runs über Nacht und das spätere Verfeinern von Analysen ohne Neustart.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Priorität 2: Asset Factory (Schritt 7)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Umwandlung der strategischen Botschaften in operative Marketing-Texte.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* **UI:** Neuer Bereich "Assets generieren" nach Abschluss von Schritt 6.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* **Funktion:** Auswahl einer Persona und eines Formats (z.B. "LinkedIn Vernetzungsanfrage", "Cold Mail Sequenz").
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* **Output:** Generierung von Copy-Paste-fertigen Texten basierend auf den Painpoints/Gains der Analyse.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* **Export:** Als separater "Marketing Kit" Download oder Anhang im Markdown.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Status: Produktionsbereit (Version 1.1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Das System liefert nun hochqualitative, faktenbasierte Analysen ("Grounding") mit HTML-Struktur-Erkennung.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [x] Grounding (HTML-Parsing) & Gemini 2.5 Flash
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [x] Robustheit (Retries, Timeouts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [x] Frontend-Optimierung (PDF, Copy-Paste, Batch-Analyse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [x] Logging (File-basiert)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- Integration von SerpAPI für noch breitere Marktrecherchen (analog Market Intel).
|
|
||||||
|
|||||||
@@ -127,16 +127,16 @@ Fuehre nun **Schritt 6 - Marketingbotschaft (WIE sprechen)** durch.
|
|||||||
|
|
||||||
# Anweisungen fuer Schritt 6: Chain-of-Thought-Analyse & Texterstellung
|
# Anweisungen fuer Schritt 6: Chain-of-Thought-Analyse & Texterstellung
|
||||||
|
|
||||||
**FOKUS:** Um die Analyse handhabbar zu machen, waehle aus Schritt 2 die **eine (1) relevanteste und vielversprechendste Zielbranche** (Primary Industry) aus.
|
**FOKUS:** Erstelle die Botschaften **AUSSCHLIESSLICH** fuer die vorgegebene **Fokus-Branche: {{focus_industry}}**.
|
||||||
Dein Ziel ist es, NUR fuer diese EINE Fokus-Branche eine spezifische Botschaft fuer JEDE Rolle aus Schritt 3 zu erstellen.
|
Ignoriere alle anderen Branchen. Dein Ziel ist es, fuer JEDE Rolle innerhalb dieser EINEN Branche eine spezifische Botschaft zu entwickeln.
|
||||||
|
|
||||||
Fuehre fuer jede **[Rolle]** innerhalb der ausgewaehlten **[Fokus-Branche]** den folgenden Denkprozess durch:
|
Fuehre fuer jede **[Rolle]** innerhalb der **[Fokus-Branche: {{focus_industry}}]** den folgenden Denkprozess durch:
|
||||||
|
|
||||||
1. **Schritt 6.1 (Analyse): Produkt-Rollen-Fit.**
|
1. **Schritt 6.1 (Analyse): Produkt-Rollen-Fit.**
|
||||||
* Welches Produkt/welche Loesung aus der "Angebot"-Tabelle (Schritt 1) ist fuer die **[Rolle]** am relevantesten?
|
* Welches Produkt/welche Loesung aus der "Angebot"-Tabelle (Schritt 1) ist fuer die **[Rolle]** am relevantesten?
|
||||||
|
|
||||||
2. **Schritt 6.2 (Analyse): Branchen-Use-Case.**
|
2. **Schritt 6.2 (Analyse): Branchen-Use-Case.**
|
||||||
* Was sind 1-2 typische Anwendungsfaelle fuer das ausgewaehlte Produkt in der **[Fokus-Branche]**? Was macht die **[Rolle]** damit konkret?
|
* Was sind 1-2 typische Anwendungsfaelle fuer das ausgewaehlte Produkt in der **{{focus_industry}}**? Was macht die **[Rolle]** damit konkret?
|
||||||
|
|
||||||
3. **Schritt 6.3 (Analyse): Nutzen-Quantifizierung.**
|
3. **Schritt 6.3 (Analyse): Nutzen-Quantifizierung.**
|
||||||
* Betrachte die Painpoints (Schritt 4) und Gains (Schritt 5) fuer die **[Rolle]**.
|
* Betrachte die Painpoints (Schritt 4) und Gains (Schritt 5) fuer die **[Rolle]**.
|
||||||
@@ -234,10 +234,10 @@ Now perform **Step 6 - Marketing Message (HOW to speak)**.
|
|||||||
|
|
||||||
# Instructions for Step 6: Chain-of-Thought Analysis & Copywriting
|
# Instructions for Step 6: Chain-of-Thought Analysis & Copywriting
|
||||||
|
|
||||||
**FOCUS:** To make the analysis manageable, select the **one (1) most relevant and promising target industry** (Primary Industry) from Step 2.
|
**FOCUS:** Create messages **EXCLUSIVELY** for the provided **Focus Industry: {{focus_industry}}**.
|
||||||
Your goal is to create a specific message for EACH role from Step 3 ONLY for this ONE focus industry.
|
Ignore all other industries. Your goal is to create a specific message for EACH role within this ONE industry.
|
||||||
|
|
||||||
For each **[Role]** within the selected **[Focus Industry]**, perform the following thought process:
|
For each **[Role]** within the **[Focus Industry: {{focus_industry}}]**, perform the following thought process:
|
||||||
|
|
||||||
1. **Step 6.1 (Analysis): Product-Role Fit.**
|
1. **Step 6.1 (Analysis): Product-Role Fit.**
|
||||||
* Which product/solution from the "Offer" table (Step 1) is most relevant for the **[Role]**?
|
* Which product/solution from the "Offer" table (Step 1) is most relevant for the **[Role]**?
|
||||||
@@ -496,7 +496,7 @@ def start_generation(url, language, regions, focus):
|
|||||||
"offer": {"summary": current_prompts['SUMMARY_TEXT_FOR_STEP1'], "headers": table_data['headers'], "rows": table_data['rows']}
|
"offer": {"summary": current_prompts['SUMMARY_TEXT_FOR_STEP1'], "headers": table_data['headers'], "rows": table_data['rows']}
|
||||||
}
|
}
|
||||||
|
|
||||||
def next_step(language, context_file, generation_step, channels):
|
def next_step(language, context_file, generation_step, channels, focus_industry=None):
|
||||||
logging.info(f"Starting Step {generation_step} in language: {language}")
|
logging.info(f"Starting Step {generation_step} in language: {language}")
|
||||||
api_key = load_api_key()
|
api_key = load_api_key()
|
||||||
if not api_key: raise ValueError("Gemini API key is missing.")
|
if not api_key: raise ValueError("Gemini API key is missing.")
|
||||||
@@ -507,6 +507,11 @@ def next_step(language, context_file, generation_step, channels):
|
|||||||
previous_steps_markdown = format_context_for_prompt(analysis_data, language)
|
previous_steps_markdown = format_context_for_prompt(analysis_data, language)
|
||||||
prompt = step_prompt_template.replace('{{previous_steps_data}}', previous_steps_markdown)
|
prompt = step_prompt_template.replace('{{previous_steps_data}}', previous_steps_markdown)
|
||||||
if '{{channels}}' in prompt: prompt = prompt.replace('{{channels}}', channels or 'LinkedIn, Kaltmail, Landingpage')
|
if '{{channels}}' in prompt: prompt = prompt.replace('{{channels}}', channels or 'LinkedIn, Kaltmail, Landingpage')
|
||||||
|
|
||||||
|
# Inject focus industry if provided (for Step 6)
|
||||||
|
if '{{focus_industry}}' in prompt:
|
||||||
|
prompt = prompt.replace('{{focus_industry}}', focus_industry or 'Primary Industry')
|
||||||
|
|
||||||
initial_inputs = analysis_data.get('_initial_inputs', {})
|
initial_inputs = analysis_data.get('_initial_inputs', {})
|
||||||
|
|
||||||
# Helper to safely get string values even if they are None/null in the JSON
|
# Helper to safely get string values even if they are None/null in the JSON
|
||||||
@@ -546,10 +551,11 @@ def main():
|
|||||||
parser.add_argument('--generation_step', type=int)
|
parser.add_argument('--generation_step', type=int)
|
||||||
parser.add_argument('--channels')
|
parser.add_argument('--channels')
|
||||||
parser.add_argument('--language', required=True)
|
parser.add_argument('--language', required=True)
|
||||||
|
parser.add_argument('--focus_industry') # New argument
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
try:
|
try:
|
||||||
if args.mode == 'start_generation': result = start_generation(args.url, args.language, args.regions, args.focus)
|
if args.mode == 'start_generation': result = start_generation(args.url, args.language, args.regions, args.focus)
|
||||||
elif args.mode == 'next_step': result = next_step(args.language, args.context_file, args.generation_step, args.channels)
|
elif args.mode == 'next_step': result = next_step(args.language, args.context_file, args.generation_step, args.channels, args.focus_industry)
|
||||||
sys.stdout.write(json.dumps(result, ensure_ascii=False))
|
sys.stdout.write(json.dumps(result, ensure_ascii=False))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error: {e}", exc_info=True)
|
logging.error(f"Error: {e}", exc_info=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user