Compare commits
2 Commits
7be5d47604
...
0db83994d0
| Author | SHA1 | Date | |
|---|---|---|---|
| 0db83994d0 | |||
| d13d3a2f36 |
@@ -1 +1 @@
|
||||
{"task_id": "30988f42-8544-817a-a250-fddb7d72b4c6", "token": "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", "session_start_time": "2026-02-16T14:37:19.780026"}
|
||||
{"task_id": "30a88f42-8544-819a-b5fc-c85cd80f43b7", "token": "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", "session_start_time": "2026-02-17T07:16:11.126812"}
|
||||
@@ -57,6 +57,8 @@ const App: React.FC = () => {
|
||||
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 [isEnriching, setIsEnriching] = useState<boolean>(false);
|
||||
|
||||
|
||||
// Project Persistence
|
||||
const [projectId, setProjectId] = useState<string | null>(null);
|
||||
@@ -69,6 +71,43 @@ const App: React.FC = () => {
|
||||
const STEP_TITLES = t.stepTitles;
|
||||
const STEP_KEYS: (keyof AnalysisData)[] = ['offer', 'targetGroups', 'personas', 'painPoints', 'gains', 'messages', 'customerJourney'];
|
||||
|
||||
const handleEnrichRow = async (productName: string, productUrl?: string) => {
|
||||
setIsEnriching(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/enrich-product`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
productName,
|
||||
productUrl,
|
||||
language: inputData.language
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.details || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const newRow = await response.json();
|
||||
setAnalysisData(prev => {
|
||||
const currentOffer = prev.offer || { headers: [], rows: [], summary: [] };
|
||||
return {
|
||||
...prev,
|
||||
offer: {
|
||||
...currentOffer,
|
||||
rows: [...currentOffer.rows, newRow]
|
||||
}
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setError(e instanceof Error ? `Fehler beim Anreichern: ${e.message}` : 'Unbekannter Fehler beim Anreichern.');
|
||||
} finally {
|
||||
setIsEnriching(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- AUTO-SAVE EFFECT ---
|
||||
useEffect(() => {
|
||||
if (generationStep === 0 || !inputData.companyUrl) return;
|
||||
@@ -507,9 +546,10 @@ const App: React.FC = () => {
|
||||
const canAdd = ['offer', 'targetGroups'].includes(stepKey);
|
||||
const canDelete = ['offer', 'targetGroups', 'personas'].includes(stepKey);
|
||||
|
||||
const handleManualAdd = (newRow: string[]) => {
|
||||
const handleManualAdd = () => {
|
||||
const newEmptyRow = Array(step.headers.length).fill('');
|
||||
const currentRows = step.rows || [];
|
||||
handleDataChange(stepKey, { ...step, rows: [...currentRows, newRow] });
|
||||
handleDataChange(stepKey, { ...step, rows: [...currentRows, newEmptyRow] });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -521,8 +561,8 @@ const App: React.FC = () => {
|
||||
rows={step.rows}
|
||||
onDataChange={(newRows) => handleDataChange(stepKey, { ...step, rows: newRows })}
|
||||
canAddRows={canAdd}
|
||||
onEnrichRow={canAdd ? handleManualAdd : undefined}
|
||||
isEnriching={false}
|
||||
onEnrichRow={stepKey === 'offer' ? handleEnrichRow : handleManualAdd}
|
||||
isEnriching={isEnriching}
|
||||
canDeleteRows={canDelete}
|
||||
onRestart={() => handleStepRestart(stepKey)}
|
||||
t={t}
|
||||
|
||||
@@ -15,6 +15,6 @@ View your app in AI Studio: https://ai.studio/apps/drive/1ZPnGbhaEnyhIyqs2rYhcPX
|
||||
|
||||
1. Install dependencies:
|
||||
`npm install`
|
||||
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||
2. Set the `GEMINI_API_KEY` in the central `.env` file in the project's root directory.
|
||||
3. Run the app:
|
||||
`npm run dev`
|
||||
|
||||
@@ -12,7 +12,7 @@ interface StepDisplayProps {
|
||||
onDataChange: (newRows: string[][]) => void;
|
||||
canAddRows?: boolean;
|
||||
canDeleteRows?: boolean;
|
||||
onEnrichRow?: (productName: string, productUrl?: string) => Promise<void>;
|
||||
onEnrichRow?: (productName: string, productUrl?: string) => void;
|
||||
isEnriching?: boolean;
|
||||
onRestart?: () => void;
|
||||
t: typeof translations.de;
|
||||
@@ -106,12 +106,7 @@ export const StepDisplay: React.FC<StepDisplayProps> = ({ title, summary, header
|
||||
};
|
||||
|
||||
const handleAddRowClick = () => {
|
||||
if (onEnrichRow) {
|
||||
setIsAddingRow(true);
|
||||
} else {
|
||||
const newEmptyRow = Array(headers.length).fill('');
|
||||
onDataChange([...rows, newEmptyRow]);
|
||||
}
|
||||
setIsAddingRow(true);
|
||||
};
|
||||
|
||||
const handleConfirmAddRow = () => {
|
||||
|
||||
@@ -89,6 +89,15 @@ router.post('/next-step', (req, res) => {
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
router.post('/enrich-product', (req, res) => {
|
||||
const { productName, productUrl, language } = req.body;
|
||||
const args = [SCRIPT_PATH, '--mode', 'enrich_product', '--product_name', productName, '--language', language];
|
||||
if (productUrl) {
|
||||
args.push('--product_url', productUrl);
|
||||
}
|
||||
runPythonScript(args, res);
|
||||
});
|
||||
|
||||
router.get('/projects', (req, res) => runPythonScript([dbScript, 'list'], res));
|
||||
router.get('/projects/:id', (req, res) => runPythonScript([dbScript, 'load', req.params.id], res));
|
||||
router.delete('/projects/:id', (req, res) => runPythonScript([dbScript, 'delete', req.params.id], res));
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineConfig, loadEnv } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, '.', '');
|
||||
const env = loadEnv(mode, '../', '');
|
||||
return {
|
||||
base: '/b2b/',
|
||||
server: {
|
||||
|
||||
@@ -622,6 +622,40 @@ def next_step(language, context_file, generation_step, channels, focus_industry=
|
||||
summary = [re.sub(r'^\*\s*|^-\s*|^\d+\.\s*', '', s.strip()) for s in summary_match[1].split('\n') if s.strip()] if summary_match else []
|
||||
return {step_key: {"summary": summary, "headers": table_data['headers'], "rows": table_data['rows']}}
|
||||
|
||||
def enrich_product(product_name, product_url, language):
|
||||
logging.info(f"Enriching product: {product_name} ({product_url})")
|
||||
api_key = load_api_key()
|
||||
if not api_key: raise ValueError("Gemini API key is missing.")
|
||||
|
||||
grounding_text = ""
|
||||
if product_url:
|
||||
grounding_text = get_text_from_url(product_url)
|
||||
|
||||
prompt_text = f"""
|
||||
# ANWEISUNG
|
||||
Du bist ein B2B-Marketing-Analyst. Deine Aufgabe ist es, die Daten für EIN Produkt zu generieren.
|
||||
Basierend auf dem Produktnamen und (optional) dem Inhalt der Produkt-URL, fülle die Spalten einer Markdown-Tabelle aus.
|
||||
Die Ausgabe MUSS eine einzelne, kommaseparierte Zeile sein, die in eine Tabelle passt. KEINE Header, KEIN Markdown, nur die Werte.
|
||||
|
||||
# PRODUKT
|
||||
- Name: "{product_name}"
|
||||
- URL-Inhalt: "{grounding_text[:3000]}..."
|
||||
|
||||
# SPALTEN
|
||||
Produkt/Lösung | Beschreibung (1-2 Sätze) | Kernfunktionen | Differenzierung | Primäre Quelle (URL)
|
||||
|
||||
# BEISPIEL-OUTPUT
|
||||
Saugroboter NR1500,Ein professioneller Saugroboter für große Büroflächen.,Autonome Navigation;Intelligente Kartierung;Lange Akkulaufzeit,Fokus auf B2B-Markt;Datenschutzkonform,https://nexaro.com/products/nr1500
|
||||
|
||||
# DEINE AUFGABE
|
||||
Erstelle jetzt die kommaseparierte Zeile für das Produkt "{product_name}".
|
||||
"""
|
||||
|
||||
response_text = call_gemini_api(prompt_text, api_key)
|
||||
|
||||
# Return as a simple list of strings
|
||||
return [cell.strip() for cell in response_text.split(',')]
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--mode', required=True)
|
||||
@@ -633,10 +667,13 @@ def main():
|
||||
parser.add_argument('--channels')
|
||||
parser.add_argument('--language', required=True)
|
||||
parser.add_argument('--focus_industry') # New argument
|
||||
parser.add_argument('--product_name')
|
||||
parser.add_argument('--product_url')
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
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, args.focus_industry)
|
||||
elif args.mode == 'enrich_product': result = enrich_product(args.product_name, args.product_url, args.language)
|
||||
sys.stdout.write(json.dumps(result, ensure_ascii=False))
|
||||
except Exception as e:
|
||||
logging.error(f"Error: {e}", exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user