Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧹 Lint (push) Failing after 35s
Monorepo Pipeline / 🧪 Test (push) Failing after 35s
Monorepo Pipeline / 🏗️ Build (push) Failing after 12s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
134 lines
5.1 KiB
TypeScript
134 lines
5.1 KiB
TypeScript
// ============================================================================
|
|
// Step 04: Architect — Sitemap & Information Architecture (Gemini Pro)
|
|
// ============================================================================
|
|
|
|
import { llmJsonRequest } from "../llm-client.js";
|
|
import type { ConceptState, StepResult, PipelineConfig } from "../types.js";
|
|
import { DEFAULT_MODELS } from "../types.js";
|
|
|
|
export async function executeArchitect(
|
|
state: ConceptState,
|
|
config: PipelineConfig,
|
|
): Promise<StepResult> {
|
|
const models = { ...DEFAULT_MODELS, ...config.modelsOverride };
|
|
const startTime = Date.now();
|
|
|
|
if (!state.auditedFacts) {
|
|
return { success: false, error: "No audited facts available." };
|
|
}
|
|
|
|
// Build navigation constraint from the real site
|
|
const existingNav = state.siteProfile?.navigation?.map((n) => n.label).join(", ") || "unbekannt";
|
|
const existingServices = state.siteProfile?.services?.join(", ") || "unbekannt";
|
|
const externalDomains = state.siteProfile?.externalDomains?.join(", ") || "keine";
|
|
|
|
const systemPrompt = `
|
|
Du bist ein Senior UX Architekt. Erstelle einen ECHTEN SEITENBAUM für die neue Website.
|
|
Regelwerk für den Output:
|
|
|
|
### SEITENBAUM-REGELN:
|
|
1. KEIN MARKETINGSPRECH als Kategoriename. Gültige Kategorien sind nur die echten Navigationspunkte der Website.
|
|
ERLAUBT: "Startseite", "Leistungen", "Über uns", "Karriere", "Referenzen", "Kontakt", "Rechtliches"
|
|
VERBOTEN: "Kern-Präsenz", "Vertrauen", "Business Areas", "Digitaler Auftritt"
|
|
|
|
2. LEISTUNGEN muss in ECHTE UNTERSEITEN aufgeteilt werden — nicht eine einzige "Leistungen"-Seite.
|
|
Jede Kompetenz aus dem existierenden Leistungsspektrum = eine eigene Seite.
|
|
Beispiel statt:
|
|
{ category: "Leistungen", pages: [{ title: "Leistungen", desc: "..." }] }
|
|
So:
|
|
{ category: "Leistungen", pages: [
|
|
{ title: "Kabeltiefbau", desc: "Mittelspannung, Niederspannung, Kabelpflugarbeiten..." },
|
|
{ title: "Horizontalspülbohrungen", desc: "HDD in allen Bodenklassen..." },
|
|
{ title: "Elektromontagen", desc: "Bis 110 kV, Glasfaserkabelmontagen..." },
|
|
{ title: "Planung & Dokumentation", desc: "Genehmigungs- und Ausführungsplanung, Vermessung..." }
|
|
]}
|
|
|
|
3. SEITENTITEL: Kurz, klar, faktisch. Kein Werbejargon.
|
|
ERLAUBT: "Kabeltiefbau", "Über uns", "Karriere"
|
|
VERBOTEN: "Unsere Expertise", "Kompetenzspektrum", "Community"
|
|
|
|
4. Gruppe die Leistungen nach dem ECHTEN Kompetenzkatalog der bestehenden Site — nicht erfinden.
|
|
|
|
5. Keine doppelten Seiten. Keine Phantomseiten.
|
|
|
|
6. Videos = Content-Assets, keine eigene Seite.
|
|
|
|
7. Entitäten mit eigener Domain (${externalDomains}) = NICHT als Seite. Nur als Teaser/Link wenn nötig.
|
|
|
|
### KONTEXT:
|
|
Bestehende Navigation: ${existingNav}
|
|
Bestehende Services: ${existingServices}
|
|
Externe Domains (haben eigene Website): ${externalDomains}
|
|
Angeforderte zusätzliche Seiten aus Briefing: ${(state.auditedFacts as any)?.pages?.join(", ") || "keine spezifischen"}
|
|
|
|
### OUTPUT FORMAT (JSON):
|
|
{
|
|
"websiteTopic": string, // MAX 3 Wörter, beschreibend
|
|
"sitemap": [
|
|
{
|
|
"category": string, // Echter Nav-Eintrag. KEIN Marketingsprech.
|
|
"pages": [
|
|
{ "title": string, "desc": string } // Echte Unterseite, 1-2 Sätze Zweck
|
|
]
|
|
}
|
|
]
|
|
}
|
|
`;
|
|
|
|
const userPrompt = `
|
|
BRIEFING:
|
|
${state.briefing}
|
|
|
|
FAKTEN (aus Extraktion):
|
|
${JSON.stringify({ facts: state.auditedFacts, strategy: { briefingSummary: state.briefingSummary } }, null, 2)}
|
|
|
|
Erstelle den Seitenbaum. Baue die Leistungen DETAILLIERT aus — echte Unterseiten pro Kompetenzbereich.
|
|
`;
|
|
|
|
try {
|
|
const { data, usage } = await llmJsonRequest({
|
|
model: models.pro,
|
|
systemPrompt,
|
|
userPrompt,
|
|
apiKey: config.openrouterKey,
|
|
});
|
|
|
|
// Normalize sitemap structure
|
|
let sitemap = data.sitemap;
|
|
if (sitemap && !Array.isArray(sitemap)) {
|
|
if (sitemap.categories) sitemap = sitemap.categories;
|
|
else {
|
|
const entries = Object.entries(sitemap);
|
|
if (entries.every(([, v]) => Array.isArray(v))) {
|
|
sitemap = entries.map(([category, pages]) => ({ category, pages }));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(sitemap)) {
|
|
sitemap = sitemap.map((cat: any) => ({
|
|
category: cat.category || cat.kategorie || cat.Kategorie || "Allgemein",
|
|
pages: (cat.pages || cat.seiten || []).map((page: any) => ({
|
|
title: page.title || page.titel || "Seite",
|
|
desc: page.desc || page.beschreibung || page.description || "",
|
|
})),
|
|
}));
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: { websiteTopic: data.websiteTopic, sitemap },
|
|
usage: {
|
|
step: "04-architect",
|
|
model: models.pro,
|
|
promptTokens: usage.promptTokens,
|
|
completionTokens: usage.completionTokens,
|
|
cost: usage.cost,
|
|
durationMs: Date.now() - startTime,
|
|
},
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: `Architect step failed: ${(err as Error).message}` };
|
|
}
|
|
}
|