feat(gtm-architect): Integrate GTM Architect app with Python backend, DB persistence, and Docker stack
This commit is contained in:
@@ -173,6 +173,106 @@ const App: React.FC = () => {
|
||||
setError(null);
|
||||
};
|
||||
|
||||
// --- IMPORT MARKDOWN ---
|
||||
const handleImportMarkdown = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const content = e.target?.result as string;
|
||||
if (!content) return;
|
||||
|
||||
try {
|
||||
const newAnalysisData: Partial<AnalysisData> = {};
|
||||
|
||||
// Helper to parse table from a section
|
||||
const parseSection = (sectionTitle: string): string[][] => {
|
||||
const sectionRegex = new RegExp(`## ${sectionTitle}[\\s\\S]*?(?=## |$)`, 'i');
|
||||
const match = content.match(sectionRegex);
|
||||
if (!match) return [];
|
||||
|
||||
const lines = match[0].split('\n');
|
||||
const rows: string[][] = [];
|
||||
let inTable = false;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim().startsWith('|') && !line.includes('---')) {
|
||||
const cells = line.split('|').map(c => c.trim()).filter(c => c);
|
||||
if (cells.length > 0) {
|
||||
if (!inTable) {
|
||||
// Skip header row if we are just starting (assuming we have default headers or don't need them for raw data)
|
||||
// Actually, we usually want data rows. The first row in MD table is header.
|
||||
// Let's rely on standard markdown table structure: Header | Separator | Data
|
||||
inTable = true;
|
||||
} else {
|
||||
rows.push(cells);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove the header row if captured (simple heuristic: first row is usually header)
|
||||
if (rows.length > 0) rows.shift();
|
||||
return rows;
|
||||
};
|
||||
|
||||
// Mapping MD titles to keys
|
||||
// Note: flexible matching for DE/EN titles
|
||||
newAnalysisData.offer = {
|
||||
summary: [],
|
||||
headers: t.stepTitles.offer.includes('Schritt') ? ["Produkt/Lösung", "Beschreibung", "Kernfunktionen", "Differenzierung", "Quelle"] : ["Product/Solution", "Description", "Core Features", "Differentiation", "Source"],
|
||||
rows: parseSection("Schritt 1")
|
||||
};
|
||||
if (newAnalysisData.offer.rows.length === 0) newAnalysisData.offer.rows = parseSection("Step 1");
|
||||
|
||||
newAnalysisData.targetGroups = {
|
||||
summary: [],
|
||||
headers: t.stepTitles.targetGroups.includes('Schritt') ? ["Zielbranche", "Merkmale", "Region", "Quelle"] : ["Target Industry", "Characteristics", "Region", "Source"],
|
||||
rows: parseSection("Schritt 2")
|
||||
};
|
||||
if (newAnalysisData.targetGroups.rows.length === 0) newAnalysisData.targetGroups.rows = parseSection("Step 2");
|
||||
|
||||
newAnalysisData.personas = {
|
||||
summary: [],
|
||||
headers: [],
|
||||
rows: parseSection("Schritt 3")
|
||||
};
|
||||
if (newAnalysisData.personas.rows.length === 0) newAnalysisData.personas.rows = parseSection("Step 3");
|
||||
|
||||
newAnalysisData.painPoints = {
|
||||
summary: [],
|
||||
headers: [],
|
||||
rows: parseSection("Schritt 4")
|
||||
};
|
||||
if (newAnalysisData.painPoints.rows.length === 0) newAnalysisData.painPoints.rows = parseSection("Step 4");
|
||||
|
||||
newAnalysisData.gains = {
|
||||
summary: [],
|
||||
headers: [],
|
||||
rows: parseSection("Schritt 5")
|
||||
};
|
||||
if (newAnalysisData.gains.rows.length === 0) newAnalysisData.gains.rows = parseSection("Step 5");
|
||||
|
||||
newAnalysisData.messages = {
|
||||
summary: [],
|
||||
headers: [],
|
||||
rows: parseSection("Schritt 6")
|
||||
};
|
||||
if (newAnalysisData.messages.rows.length === 0) newAnalysisData.messages.rows = parseSection("Step 6");
|
||||
|
||||
|
||||
setAnalysisData(newAnalysisData);
|
||||
setGenerationStep(6); // Jump to end
|
||||
setProjectName(file.name.replace('.md', ' (Imported)'));
|
||||
setProjectId(null); // Treat as new project
|
||||
|
||||
} catch (err) {
|
||||
console.error("Parse error", err);
|
||||
setError("Fehler beim Importieren der Datei.");
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
const handleStartGeneration = useCallback(async () => {
|
||||
if (!inputData.companyUrl) {
|
||||
@@ -357,7 +457,14 @@ const App: React.FC = () => {
|
||||
const step = analysisData[stepKey] as AnalysisStep | undefined;
|
||||
if (!step) return null;
|
||||
|
||||
// Allow manual addition for Offer (Step 1) and Target Groups (Step 2)
|
||||
const canAdd = ['offer', 'targetGroups'].includes(stepKey);
|
||||
const canDelete = ['offer', 'targetGroups', 'personas'].includes(stepKey);
|
||||
|
||||
const handleManualAdd = (newRow: string[]) => {
|
||||
const currentRows = step.rows || [];
|
||||
handleDataChange(stepKey, { ...step, rows: [...currentRows, newRow] });
|
||||
};
|
||||
|
||||
return (
|
||||
<StepDisplay
|
||||
@@ -367,8 +474,8 @@ const App: React.FC = () => {
|
||||
headers={step.headers}
|
||||
rows={step.rows}
|
||||
onDataChange={(newRows) => handleDataChange(stepKey, { ...step, rows: newRows })}
|
||||
canAddRows={false} // Disabled enrich functionality
|
||||
onEnrichRow={undefined}
|
||||
canAddRows={canAdd}
|
||||
onEnrichRow={canAdd ? handleManualAdd : undefined}
|
||||
isEnriching={false}
|
||||
canDeleteRows={canDelete}
|
||||
t={t}
|
||||
@@ -456,6 +563,17 @@ const App: React.FC = () => {
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex justify-center gap-3">
|
||||
<label className="flex items-center px-4 py-2 bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 rounded-lg text-sm font-bold transition-all shadow-sm border border-slate-200 dark:border-slate-700 cursor-pointer">
|
||||
<FolderOpen className="mr-2 h-4 w-4 text-orange-500" />
|
||||
{inputData.language === 'de' ? 'MD Laden' : 'Load MD'}
|
||||
<input
|
||||
type="file"
|
||||
accept=".md"
|
||||
onChange={handleImportMarkdown}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button
|
||||
onClick={() => setShowHistory(true)}
|
||||
className="flex items-center px-4 py-2 bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 rounded-lg text-sm font-bold transition-all shadow-sm border border-slate-200 dark:border-slate-700"
|
||||
|
||||
Reference in New Issue
Block a user