// ============================================================================ // 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 { 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}` }; } }