feat(b2b-marketing): Finalize grounding architecture and frontend improvements
- Upgrade backend to use gemini-2.5-flash with sanitized HTML parsing (no token limit). - Implement robust retry logic and increased timeouts (600s) for deep analysis. - Add file-based logging for prompts and responses. - Fix API endpoint (v1) and regex parsing issues. - Frontend: Optimize PDF export (landscape, no scrollbars), fix copy-paste button, add 'Repeat Step 6' feature. - Update documentation to 'Completed' status.
This commit is contained in:
175
b2b-marketing-assistant/server.cjs
Normal file
175
b2b-marketing-assistant/server.cjs
Normal file
@@ -0,0 +1,175 @@
|
||||
const express = require('express');
|
||||
const { spawn } = require('child_process');
|
||||
const bodyParser = require('body-parser');
|
||||
const cors = require('cors');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
// Port 3002, um Konflikte mit dem Market Intelligence Tool (3001) und dem React Dev Server (3000) zu vermeiden
|
||||
const PORT = 3002;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json({ limit: '10mb' })); // Erhöhe das Limit für potenziell große Payloads
|
||||
|
||||
const PYTHON_EXECUTABLE = 'python3'; // Annahme, dass python3 im PATH des Containers ist
|
||||
// Im Docker-Container liegen server.cjs und das Python-Skript im selben Verzeichnis (/app)
|
||||
const SCRIPT_PATH = path.join(__dirname, 'b2b_marketing_orchestrator.py');
|
||||
|
||||
|
||||
// Helper-Funktion zum Ausführen des Python-Skripts
|
||||
const runPythonScript = (args, res) => {
|
||||
console.log(`[${new Date().toISOString()}] Spawning: ${PYTHON_EXECUTABLE} ${args.join(' ')}`);
|
||||
|
||||
const pythonProcess = spawn(PYTHON_EXECUTABLE, args);
|
||||
|
||||
let pythonOutput = '';
|
||||
let pythonError = '';
|
||||
|
||||
pythonProcess.stdout.on('data', (data) => {
|
||||
pythonOutput += data.toString();
|
||||
});
|
||||
|
||||
pythonProcess.stderr.on('data', (data) => {
|
||||
pythonError += data.toString();
|
||||
});
|
||||
|
||||
pythonProcess.on('close', (code) => {
|
||||
console.log(`[${new Date().toISOString()}] Python script finished with exit code: ${code}`);
|
||||
|
||||
if (pythonError) {
|
||||
console.log(`--- STDERR ---`);
|
||||
console.log(pythonError);
|
||||
console.log(`----------------`);
|
||||
}
|
||||
|
||||
if (code !== 0) {
|
||||
console.error('Python script exited with an error.');
|
||||
return res.status(500).json({
|
||||
error: 'An error occurred in the backend script.',
|
||||
details: pythonError
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const result = JSON.parse(pythonOutput);
|
||||
res.json(result);
|
||||
} catch (parseError) {
|
||||
console.error('Failed to parse Python output as JSON:', parseError);
|
||||
res.status(500).json({
|
||||
error: 'Invalid JSON response from the backend script.',
|
||||
rawOutput: pythonOutput,
|
||||
details: pythonError
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
pythonProcess.on('error', (err) => {
|
||||
console.error('FATAL: Failed to start the python process itself.', err);
|
||||
res.status(500).json({
|
||||
error: 'Failed to start the backend process.',
|
||||
details: err.message
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// API-Endpunkt, um eine neue Analyse zu starten (Schritt 1)
|
||||
app.post('/api/start-generation', (req, res) => {
|
||||
console.log(`[${new Date().toISOString()}] HIT: /api/start-generation`);
|
||||
const { companyUrl, language, regions, focus } = req.body;
|
||||
|
||||
if (!companyUrl || !language) {
|
||||
return res.status(400).json({ error: 'Missing required parameters: companyUrl and language.' });
|
||||
}
|
||||
|
||||
const args = [
|
||||
SCRIPT_PATH,
|
||||
'--mode', 'start_generation',
|
||||
'--url', companyUrl,
|
||||
'--language', language
|
||||
];
|
||||
|
||||
if (regions) args.push('--regions', regions);
|
||||
if (focus) args.push('--focus', focus);
|
||||
|
||||
runPythonScript(args, res);
|
||||
});
|
||||
|
||||
|
||||
// API-Endpunkt, um den nächsten Schritt zu generieren
|
||||
app.post('/api/next-step', (req, res) => {
|
||||
console.log(`[${new Date().toISOString()}] HIT: /api/next-step`);
|
||||
const { analysisData, language, channels, generationStep } = req.body;
|
||||
|
||||
if (!analysisData || !language || generationStep === undefined) {
|
||||
return res.status(400).json({ error: 'Missing required parameters: analysisData, language, generationStep.' });
|
||||
}
|
||||
|
||||
// Wir schreiben die komplexen Kontext-Daten in eine temporäre Datei, um die Kommandozeile sauber zu halten.
|
||||
const tmpDir = path.join(__dirname, 'tmp');
|
||||
if (!fs.existsSync(tmpDir)) {
|
||||
fs.mkdirSync(tmpDir);
|
||||
}
|
||||
const contextFilePath = path.join(tmpDir, `context_${Date.now()}.json`);
|
||||
|
||||
try {
|
||||
fs.writeFileSync(contextFilePath, JSON.stringify(analysisData));
|
||||
|
||||
const args = [
|
||||
SCRIPT_PATH,
|
||||
'--mode', 'next_step',
|
||||
'--language', language,
|
||||
'--context_file', contextFilePath,
|
||||
'--generation_step', generationStep.toString()
|
||||
];
|
||||
|
||||
if (channels && Array.isArray(channels)) {
|
||||
args.push('--channels', channels.join(','));
|
||||
}
|
||||
|
||||
// Da die runPythonScript-Funktion res behandelt, fügen wir hier die Bereinigung hinzu
|
||||
const originalJson = res.json.bind(res);
|
||||
res.json = (data) => {
|
||||
if (fs.existsSync(contextFilePath)) {
|
||||
fs.unlinkSync(contextFilePath);
|
||||
}
|
||||
originalJson(data);
|
||||
};
|
||||
|
||||
const originalStatus = res.status.bind(res);
|
||||
res.status = (code) => {
|
||||
// Wenn ein Fehler auftritt, rufen wir send auf, um die Bereinigung auszulösen
|
||||
const originalSend = res.send.bind(res);
|
||||
res.send = (body) => {
|
||||
if (fs.existsSync(contextFilePath)) {
|
||||
fs.unlinkSync(contextFilePath);
|
||||
}
|
||||
originalSend(body);
|
||||
}
|
||||
return originalStatus(code);
|
||||
}
|
||||
|
||||
|
||||
runPythonScript(args, res);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to write temporary context file:', error);
|
||||
return res.status(500).json({ error: 'Failed to process request context.', details: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- SERVE STATIC FRONTEND ---
|
||||
// Serve static files from the 'dist' directory created by `npm run build`
|
||||
app.use(express.static(path.join(__dirname, 'dist')));
|
||||
|
||||
// Handle client-side routing: return index.html for all non-API routes
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
||||
});
|
||||
|
||||
// Start des Servers
|
||||
app.listen(PORT, () => {
|
||||
console.log(`B2B Marketing Assistant API Bridge running on http://localhost:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user