feat: Implement AI-powered estimation system, add comprehensive usage guide, and redesign PDF sitemap module.
Some checks failed
Build & Deploy Mintel Blog / build-and-deploy (push) Failing after 23s
Some checks failed
Build & Deploy Mintel Blog / build-and-deploy (push) Failing after 23s
This commit is contained in:
@@ -23,6 +23,8 @@ async function main() {
|
||||
|
||||
let jsonStatePath: string | null = null;
|
||||
|
||||
const isEstimation = process.argv.includes('--estimation') || process.argv.includes('-E');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
@@ -34,6 +36,8 @@ async function main() {
|
||||
cacheKey = args[++i];
|
||||
} else if (arg === '--json') {
|
||||
jsonStatePath = args[++i];
|
||||
} else if (arg === '--estimation' || arg === '-E') {
|
||||
// Handled above
|
||||
} else if (!arg.startsWith('--')) {
|
||||
briefing = arg;
|
||||
}
|
||||
@@ -140,7 +144,8 @@ async function main() {
|
||||
console.log(`📦 Saved detailed state to: ${finalJsonPath}`);
|
||||
console.log('📄 Generating PDF estimation...');
|
||||
try {
|
||||
execSync(`npx tsx ./scripts/generate-quote.ts --input ${tempJsonPath}`, { stdio: 'inherit' });
|
||||
const genArgs = isEstimation ? '--estimation' : '';
|
||||
execSync(`npx tsx ./scripts/generate-quote.ts --input ${tempJsonPath} ${genArgs}`, { stdio: 'inherit' });
|
||||
} finally {
|
||||
// await fs.unlink(tempJsonPath);
|
||||
}
|
||||
@@ -243,7 +248,17 @@ async function performCrawl(url: string): Promise<string> {
|
||||
return summary + pages.map(p => `--- PAGE: ${p.url} ---\n${p.content}`).join('\n\n');
|
||||
}
|
||||
|
||||
async function getAiEstimation(briefing: string, distilledCrawl: string, comments: string | null, apiKey: string, principles: string, techStandards: string, tone: string) {
|
||||
const cleanJson = (str: string) => {
|
||||
// Remove markdown code blocks if present
|
||||
let cleaned = str.replace(/```json\n?|```/g, '').trim();
|
||||
// Remove potential control characters that break JSON.parse
|
||||
cleaned = cleaned.replace(/[\u0000-\u001F\u007F-\u009F]/g, " ");
|
||||
// Remove trailing commas before closing braces/brackets
|
||||
cleaned = cleaned.replace(/,\s*([\]}])/g, '$1');
|
||||
return cleaned;
|
||||
};
|
||||
|
||||
const getAiEstimation = async (briefing: string, distilledCrawl: string, comments: string | null, apiKey: string, principles: string, techStandards: string, tone: string) => {
|
||||
let usage = { prompt: 0, completion: 0, cost: 0 };
|
||||
const addUsage = (data: any) => {
|
||||
if (data?.usage) {
|
||||
@@ -316,7 +331,7 @@ Focus 100% on the BRIEFING text provided by the user. Use the DISTILLED_CRAWL on
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p1Resp.data);
|
||||
const facts = JSON.parse(p1Resp.data.choices[0].message.content);
|
||||
const facts = JSON.parse(cleanJson(p1Resp.data.choices[0].message.content));
|
||||
|
||||
// 2. PASS 2: Feature Deep-Dive
|
||||
console.log(' ↳ Pass 2: Feature Deep-Dive...');
|
||||
@@ -348,7 +363,7 @@ ${JSON.stringify(facts, null, 2)}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p2Resp.data);
|
||||
const details = JSON.parse(p2Resp.data.choices[0].message.content);
|
||||
const details = JSON.parse(cleanJson(p2Resp.data.choices[0].message.content));
|
||||
|
||||
// 3. PASS 3: Strategic Content (Bespoke Strategy)
|
||||
console.log(' ↳ Pass 3: Strategic Content (Bespoke Strategy)...');
|
||||
@@ -360,19 +375,17 @@ Analyze the BRIEFING and the EXISTING WEBSITE context.
|
||||
${tone}
|
||||
|
||||
### OBJECTIVE:
|
||||
1. **briefingSummary**: A deep, respectful summary of the status quo and the target state.
|
||||
- **LENGTH**: EXACTLY TWO PARAGRAPHS. Minimum 8 sentences total.
|
||||
- **MIRROR TEST**: Acknowledge the EXISTING website specifically. Why does the new project make sense NOW?
|
||||
- **ABSOLUTE RULE**: DO NOT claim "keine digitale Repräsentation", "erstmals abgebildet", "Erstplatzierung" or "kommunikative Lücke" regarding existence if Pass 1 identified this as a RELAUNCH (isRelaunch=true). EXPLICITLY acknowledge the existing context and the NEED FOR EVOLUTION/MODERNIZATION.
|
||||
- **RESPECT**: Explicitly incorporate the customer's expressed wishes.
|
||||
- **ABSOLUTE RULE**: DO NOT INVENT DETAILS. Do not mention specific people (e.g., "Frieder Helmich"), software versions, or internal details NOT present in the briefing.
|
||||
- **TONE**: Natural Ich-Form. Clear, direct, zero marketing fluff.
|
||||
2. **designVision**: A high-density, professional vision of the future execution.
|
||||
- **LENGTH**: EXACTLY TWO PARAGRAPHS. Minimum 6 sentences total.
|
||||
- **INVESTMENT VALUE**: Plant a clear picture of a stable, high-quality system.
|
||||
- **TECHNICAL PRECISION**: Focus on execution (Typografie, Visual Logic, Performance).
|
||||
- **NO FLUFF**: Do NOT focus on "Full-Screen Hero Video" as the main thing. Focus on the FIRM's essence and how we translate it into a professional tool.
|
||||
- **ABSOLUTE RULE**: NO HALLUCINATIONS. Stay general yet precise. No "Verzicht auf Stockmaterial" unless explicitly stated.
|
||||
3. **briefingSummary**: Ein sachlicher, tiefgehender Überblick der Unternehmenslage.
|
||||
- **STIL**: Keine Ich-Form. Keine Marketing-Floskeln. Nutze präzise Fachbegriffe. Sei prägnant und effizient (ca. 70% der vorherigen Länge).
|
||||
- **FORM**: EXAKT ZWEI ABSÄTZE. Insgesamt ca. 6 Sätze.
|
||||
- **INHALT**: Welcher technologische Sprung ist notwendig? Was ist der Status Quo? (Bezug zur URL/Briefing).
|
||||
- **ABSOLUTE REGEL**: Keine Halluzinationen über fehlende Präsenzen bei Relaunches.
|
||||
- **DATENSCHUTZ**: KEINERLEI namentliche Nennungen von Personen (z. B. "Danny Joseph") in diesen Texten.
|
||||
4. **designVision**: Ein abstraktes, strategisches Konzept.
|
||||
- **STIL**: Rein konzeptionell. Keine Umsetzungsschritte. Keinerlei "To-dos". Sei prägnant.
|
||||
- **FORM**: EXAKT ZWEI ABSÄTZE. Insgesamt ca. 4 Sätze.
|
||||
- **DATENSCHUTZ**: KEINERLEI namentliche Nennungen von Personen in diesen Texten.
|
||||
- **FOKUS**: Welche strategische Wirkung soll erzielt werden? (Z. B. "Industrielle Souveränität").
|
||||
|
||||
### OUTPUT FORMAT (Strict JSON):
|
||||
{
|
||||
@@ -389,7 +402,7 @@ ${tone}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p3Resp.data);
|
||||
const strategy = JSON.parse(p3Resp.data.choices[0].message.content);
|
||||
const strategy = JSON.parse(cleanJson(p3Resp.data.choices[0].message.content));
|
||||
|
||||
// 4. PASS 4: Information Architecture (Sitemap)
|
||||
console.log(' ↳ Pass 4: Information Architecture...');
|
||||
@@ -418,7 +431,7 @@ ${JSON.stringify({ facts, strategy }, null, 2)}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p4Resp.data);
|
||||
const ia = JSON.parse(p4Resp.data.choices[0].message.content);
|
||||
const ia = JSON.parse(cleanJson(p4Resp.data.choices[0].message.content));
|
||||
|
||||
// 5. PASS 5: Position Synthesis & Pricing Transparency
|
||||
console.log(' ↳ Pass 5: Position Synthesis...');
|
||||
@@ -443,14 +456,15 @@ Each position in the quote must be perfectly justified and detailed.
|
||||
### RULES FOR positionDescriptions (STRICT):
|
||||
1. **NO "ICH-FORM"**: Do NOT use "Ich" or "Mein". Lead with the action or component.
|
||||
2. **CONCISE & ITEM-BASED**: Use short, technical sentences. Focus on WHAT is delivered.
|
||||
3. **ZERO GENERALIZATION**: Do NOT say "Verschiedene Funktionen".
|
||||
3. **ZERO GENERALIZATION**: Do NOT say "Verschiedene Funktionen" or "Optimierte Darstellung". Name the things.
|
||||
4. **ITEMIZED SYNTHESIS**: Mention EVERY component selected in Pass 1.
|
||||
5. **HARD SPECIFICS**: Preserve technical details from the briefing (e.g., "110 kV", "HDD-Bohrtechnik").
|
||||
6. **STYLE**: Direct, engineering-grade, 100% GERMAN.
|
||||
5. **HARD SPECIFICS**: Preserve technical details from the briefing and CURRENT WEBSITE (distilledCrawl). If they mention "HDD-Bohrtechnik" or "110kV Kabelanlagen", IT MUST BE IN THE DESCRIPTION.
|
||||
6. **INDUSTRIAL AMBITION**: Describe it as a high-end technical solution, not a cheap website.
|
||||
7. **SPECIFIC - PAGES**: For "Individuelle Seiten", list the pages as a comma-separated list.
|
||||
8. **SPECIFIC - HOSTING**: Always append: "Inkl. 20GB Speicher."
|
||||
9. **SPECIFIC - LOGIC**: Describe the ACTUAL logic. NEVER use generic terms.
|
||||
10. **STRICT KEYS**: Keys MUST be EXACTLY the ones defined in POSITION TITLES.
|
||||
11. **AGB BAN (CRITICAL)**: NEVER mention "Allgemeine Geschäftsbedingungen" or "AGGs" in any text. They are NOT part of this offer.
|
||||
|
||||
### EXAMPLES (FEW-SHOT):
|
||||
- **BAD**: "Ich entwickle die Seiten: Startseite, Leistungen, Kontakt."
|
||||
@@ -481,7 +495,7 @@ ${JSON.stringify({ facts, details, strategy, ia }, null, 2)}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p5Resp.data);
|
||||
const positionsData = JSON.parse(p5Resp.data.choices[0].message.content);
|
||||
const positionsData = JSON.parse(cleanJson(p5Resp.data.choices[0].message.content));
|
||||
|
||||
// 6. PASS 6: The Industrial Critic
|
||||
console.log(' ↳ Pass 6: The Industrial Critic (Quality Gate)...');
|
||||
@@ -492,10 +506,13 @@ Analyze the CURRENT_STATE against the BRIEFING_TRUTH.
|
||||
### CRITICAL ERROR CHECKLIST (FAIL IF FOUND):
|
||||
1. **Hallucination Leakage**: FAIL if names of people (e.g., "Frieder Helmich"), specific software versions, or invented details are used unless they appear EXACTLY in the BRIEFING.
|
||||
- **CRITICAL**: Forbid "Sie", "Ansprechpartner" or "Unternehmen" for personName if a name IS in the briefing. If none is in briefing, use empty string.
|
||||
2. **Logic Conflict**: FAIL if isRelaunch is true but briefingSummary claims no website exists or uses phrases like "Da aktuell keine digitale Repräsentation vorliegt", "erstmals abgebildet", "Erstplatzierung" or "Lücke schließen" (regarding existence).
|
||||
2. **Logic Conflict**: FAIL if isRelaunch is true but briefingSummary claims no website exists.
|
||||
- FAIL if the description in positionDescriptions mentions more items than extracted in facts.
|
||||
3. **Implementation Fluff**: FAIL if "React", "Next.js", "TypeScript", "Tailwind" or other tech-stack details are mentioned. Focus on Concept & Result.
|
||||
4. **Length Check**: Briefing and Vision MUST be significantly long (EXACTLY 2 paragraphs each, minimum 8 sentences for briefing, 6 for vision).
|
||||
3. **Implementation Fluff**: FAIL if tech-stack details are mentioned (React, etc.). Focus on Concept & Result.
|
||||
4. **Genericism Check (CRITICAL)**: FAIL if any text sounds like it could apply to ANY company. It MUST mention specific industry details (e.g., "Kabeltiefbau", "Infrastruktur-Zentrum") from the Briefing or Crawl.
|
||||
6. **Namen-Verbot (STRICT)**: FAIL if any personal names (e.g. "Danny Joseph", "Joseph", etc.) appear in 'briefingSummary' or 'designVision'. Use abstract terms like "Unternehmensführung" or "Management" if necessary.
|
||||
7. **AGB BAN**: FAIL if "Allgemeine Geschäftsbedingungen" or "AGB" appear anywhere.
|
||||
8. **Length Check**: Briefing (ca. 6 Sätze) und Vision (ca. 4 Sätze). Kürze Texte, die zu ausschweifend sind, auf das Wesentliche.
|
||||
|
||||
### MISSION:
|
||||
Return updated fields ONLY. Specifically focus on hardening 'positionDescriptions', 'sitemap', 'briefingSummary', and 'designVision'.
|
||||
@@ -509,7 +526,7 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p6Resp.data);
|
||||
const reflection = JSON.parse(p6Resp.data.choices[0].message.content);
|
||||
const reflection = JSON.parse(cleanJson(p6Resp.data.choices[0].message.content));
|
||||
|
||||
// 6. Reflection Merge Utility
|
||||
const mergeReflection = (state: any, reflection: any) => {
|
||||
|
||||
Reference in New Issue
Block a user