@easybits.cloud/html-tailwind-generator 0.2.82 → 0.2.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dataUrlToImagePart,
3
3
  streamGenerate
4
- } from "./chunk-BZE2DJWW.js";
4
+ } from "./chunk-JRRGQPX3.js";
5
5
 
6
6
  // src/generateDocument.ts
7
7
  var DOCUMENT_SYSTEM_PROMPT = `You are a professional document designer who creates stunning letter-sized (8.5" \xD7 11") document pages using HTML + Tailwind CSS.
@@ -246,4 +246,4 @@ export {
246
246
  DOCUMENT_PROMPT_SUFFIX,
247
247
  generateDocument
248
248
  };
249
- //# sourceMappingURL=chunk-NFAH6S7X.js.map
249
+ //# sourceMappingURL=chunk-5DDQ5ZXR.js.map
@@ -60,19 +60,33 @@ function sanitizeSemanticColors(html) {
60
60
  import { streamText } from "ai";
61
61
  import { createAnthropic } from "@ai-sdk/anthropic";
62
62
  import { nanoid } from "nanoid";
63
+ function isOpenAiModel(id) {
64
+ return /^(gpt-|o[1-9]|dall-e|tts-|whisper|chatgpt-)/.test(id);
65
+ }
63
66
  async function resolveModel(opts) {
67
+ if (opts.modelId && isOpenAiModel(opts.modelId)) {
68
+ const openaiKey2 = opts.openaiApiKey || process.env.OPENAI_API_KEY;
69
+ if (openaiKey2) {
70
+ const { createOpenAI } = await import("@ai-sdk/openai");
71
+ return createOpenAI({ apiKey: openaiKey2 })(opts.modelId);
72
+ }
73
+ }
74
+ if (opts.modelId && !isOpenAiModel(opts.modelId)) {
75
+ const anthropicKey2 = opts.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
76
+ if (anthropicKey2) {
77
+ return createAnthropic({ apiKey: anthropicKey2 })(opts.modelId);
78
+ }
79
+ }
64
80
  const anthropicKey = opts.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
65
81
  if (anthropicKey) {
66
- const anthropic = createAnthropic({ apiKey: anthropicKey });
67
- return anthropic(opts.modelId || opts.defaultAnthropic);
82
+ return createAnthropic({ apiKey: anthropicKey })(opts.defaultAnthropic);
68
83
  }
69
84
  const openaiKey = opts.openaiApiKey || process.env.OPENAI_API_KEY;
70
85
  if (openaiKey) {
71
86
  const { createOpenAI } = await import("@ai-sdk/openai");
72
- const openai = createOpenAI({ apiKey: openaiKey });
73
- return openai(opts.modelId || opts.defaultOpenai);
87
+ return createOpenAI({ apiKey: openaiKey })(opts.defaultOpenai);
74
88
  }
75
- return createAnthropic()(opts.modelId || opts.defaultAnthropic);
89
+ return createAnthropic()(opts.defaultAnthropic);
76
90
  }
77
91
  function dataUrlToImagePart(dataUrl) {
78
92
  const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
@@ -330,4 +344,4 @@ export {
330
344
  extractJsonObjects,
331
345
  streamGenerate
332
346
  };
333
- //# sourceMappingURL=chunk-BZE2DJWW.js.map
347
+ //# sourceMappingURL=chunk-JRRGQPX3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sanitizeColors.ts","../src/streamCore.ts"],"sourcesContent":["/**\n * Replace hardcoded Tailwind color classes with semantic color classes\n * (bg-primary, text-primary, etc.). Gray/white/black/slate/zinc/neutral stay intact.\n *\n * Covers ALL chromatic Tailwind colors the model might generate.\n */\n\n// All chromatic colors Tailwind ships (excluding neutrals: slate, gray, zinc, neutral, stone)\nconst COLORS =\n \"red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose\";\n\nfunction re(prefix: string, shades: string): RegExp {\n return new RegExp(`\\\\b${prefix}-(${COLORS})-(${shades})\\\\b`, \"g\");\n}\n\nconst replacements: [RegExp, string][] = [\n // Background\n [re(\"bg\", \"500|600|700\"), \"bg-primary\"],\n [re(\"bg\", \"50|100\"), \"bg-primary-light\"],\n [re(\"bg\", \"800|900|950\"), \"bg-primary-dark\"],\n [re(\"bg\", \"200|300|400\"), \"bg-primary\"],\n\n // Text\n [re(\"text\", \"500|600|700\"), \"text-primary\"],\n [re(\"text\", \"800|900|950\"), \"text-primary-dark\"],\n [re(\"text\", \"50|100|200|300\"), \"text-on-primary\"],\n [re(\"text\", \"400\"), \"text-primary\"],\n\n // Border\n [re(\"border\", \"\\\\d{2,3}\"), \"border-primary\"],\n\n // Ring\n [re(\"ring\", \"\\\\d{2,3}\"), \"ring-primary\"],\n\n // Gradients\n [re(\"from\", \"\\\\d{2,3}\"), \"from-primary\"],\n [re(\"to\", \"\\\\d{2,3}\"), \"to-primary\"],\n [re(\"via\", \"\\\\d{2,3}\"), \"via-primary\"],\n\n // Hover/focus variants\n [new RegExp(`\\\\bhover:bg-(${COLORS})-(500|600|700|800|900|950)\\\\b`, \"g\"), \"hover:bg-primary-dark\"],\n [new RegExp(`\\\\bhover:bg-(${COLORS})-(50|100|200|300|400)\\\\b`, \"g\"), \"hover:bg-primary-light\"],\n [new RegExp(`\\\\bhover:text-(${COLORS})-\\\\d{2,3}\\\\b`, \"g\"), \"hover:text-primary\"],\n [new RegExp(`\\\\bfocus:ring-(${COLORS})-\\\\d{2,3}\\\\b`, \"g\"), \"focus:ring-primary\"],\n [new RegExp(`\\\\bfocus:border-(${COLORS})-\\\\d{2,3}\\\\b`, \"g\"), \"focus:border-primary\"],\n\n // Divide\n [re(\"divide\", \"\\\\d{2,3}\"), \"divide-primary\"],\n\n // Placeholder\n [re(\"placeholder\", \"\\\\d{2,3}\"), \"placeholder-primary\"],\n\n // Outline\n [re(\"outline\", \"\\\\d{2,3}\"), \"outline-primary\"],\n\n // Shadow colored\n [re(\"shadow\", \"\\\\d{2,3}\"), \"shadow-primary\"],\n\n // Decoration\n [re(\"decoration\", \"\\\\d{2,3}\"), \"decoration-primary\"],\n\n // Accent\n [re(\"accent\", \"\\\\d{2,3}\"), \"accent-primary\"],\n];\n\nexport function sanitizeSemanticColors(html: string): string {\n let result = html;\n for (const [pattern, replacement] of replacements) {\n result = result.replace(pattern, replacement);\n }\n return result;\n}\n","import { streamText } from \"ai\";\nimport { createAnthropic } from \"@ai-sdk/anthropic\";\nimport { nanoid } from \"nanoid\";\nimport { findImageSlots } from \"./images/enrichImages\";\nimport { searchImage } from \"./images/pexels\";\nimport { generateImage } from \"./images/dalleImages\";\nimport { generateSvg } from \"./images/svgGenerator\";\nimport type { Section3 } from \"./types\";\nimport { sanitizeSemanticColors } from \"./sanitizeColors\";\n\n/**\n * Resolve AI model from available keys.\n * Prefers Anthropic, falls back to OpenAI.\n */\nfunction isOpenAiModel(id: string): boolean {\n return /^(gpt-|o[1-9]|dall-e|tts-|whisper|chatgpt-)/.test(id);\n}\n\nexport async function resolveModel(opts: {\n openaiApiKey?: string;\n anthropicApiKey?: string;\n modelId?: string;\n defaultOpenai: string;\n defaultAnthropic: string;\n}) {\n // If modelId explicitly specifies a provider, use that provider\n if (opts.modelId && isOpenAiModel(opts.modelId)) {\n const openaiKey = opts.openaiApiKey || process.env.OPENAI_API_KEY;\n if (openaiKey) {\n const { createOpenAI } = await import(\"@ai-sdk/openai\");\n return createOpenAI({ apiKey: openaiKey })(opts.modelId);\n }\n }\n if (opts.modelId && !isOpenAiModel(opts.modelId)) {\n const anthropicKey = opts.anthropicApiKey || process.env.ANTHROPIC_API_KEY;\n if (anthropicKey) {\n return createAnthropic({ apiKey: anthropicKey })(opts.modelId);\n }\n }\n // No explicit modelId — prefer Anthropic, fallback to OpenAI\n const anthropicKey = opts.anthropicApiKey || process.env.ANTHROPIC_API_KEY;\n if (anthropicKey) {\n return createAnthropic({ apiKey: anthropicKey })(opts.defaultAnthropic);\n }\n const openaiKey = opts.openaiApiKey || process.env.OPENAI_API_KEY;\n if (openaiKey) {\n const { createOpenAI } = await import(\"@ai-sdk/openai\");\n return createOpenAI({ apiKey: openaiKey })(opts.defaultOpenai);\n }\n return createAnthropic()(opts.defaultAnthropic);\n}\n\n/**\n * Convert data URL to Uint8Array for AI SDK vision.\n */\nexport function dataUrlToImagePart(dataUrl: string): { image: Uint8Array; mimeType: string } | null {\n const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);\n if (!match) return null;\n return {\n image: new Uint8Array(Buffer.from(match[2], \"base64\")),\n mimeType: match[1],\n };\n}\n\n/**\n * Extract complete JSON objects from accumulated text using brace-depth tracking.\n */\nexport function extractJsonObjects(text: string): [any[], string] {\n const objects: any[] = [];\n let remaining = text;\n\n while (remaining.length > 0) {\n remaining = remaining.trimStart();\n if (!remaining.startsWith(\"{\")) {\n const nextBrace = remaining.indexOf(\"{\");\n if (nextBrace === -1) break;\n remaining = remaining.slice(nextBrace);\n continue;\n }\n\n let depth = 0;\n let inString = false;\n let escape = false;\n let end = -1;\n\n for (let i = 0; i < remaining.length; i++) {\n const ch = remaining[i];\n if (escape) { escape = false; continue; }\n if (ch === \"\\\\\") { escape = true; continue; }\n if (ch === '\"') { inString = !inString; continue; }\n if (inString) continue;\n if (ch === \"{\") depth++;\n if (ch === \"}\") { depth--; if (depth === 0) { end = i; break; } }\n }\n\n if (end === -1) break;\n\n const candidate = remaining.slice(0, end + 1);\n remaining = remaining.slice(end + 1);\n\n try {\n objects.push(JSON.parse(candidate));\n } catch {\n // malformed, skip\n }\n }\n\n return [objects, remaining];\n}\n\n/** Inline shimmer SVG used as src for loading image placeholders */\nconst LOADING_PLACEHOLDER_SRC = `data:image/svg+xml,${encodeURIComponent('<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"800\" height=\"500\" viewBox=\"0 0 800 500\"><rect fill=\"#f3f4f6\" width=\"800\" height=\"500\" rx=\"12\"/><g opacity=\".4\"><rect x=\"320\" y=\"200\" width=\"160\" height=\"4\" rx=\"2\" fill=\"#d1d5db\"><animate attributeName=\"opacity\" values=\".3;.8;.3\" dur=\"1.5s\" repeatCount=\"indefinite\"/></rect><rect x=\"280\" y=\"215\" width=\"240\" height=\"4\" rx=\"2\" fill=\"#d1d5db\"><animate attributeName=\"opacity\" values=\".3;.8;.3\" dur=\"1.5s\" begin=\".3s\" repeatCount=\"indefinite\"/></rect><rect x=\"340\" y=\"230\" width=\"120\" height=\"4\" rx=\"2\" fill=\"#d1d5db\"><animate attributeName=\"opacity\" values=\".3;.8;.3\" dur=\"1.5s\" begin=\".6s\" repeatCount=\"indefinite\"/></rect></g><g transform=\"translate(376,150)\" opacity=\".3\"><path d=\"M0 28V4a4 4 0 014-4h40a4 4 0 014 4v24a4 4 0 01-4 4H4a4 4 0 01-4-4z\" fill=\"#d1d5db\"/><circle cx=\"14\" cy=\"12\" r=\"4\" fill=\"#9ca3af\"/><path d=\"M4 28l10-10 6 6 8-8 16 16H4z\" fill=\"#9ca3af\" opacity=\".5\"/></g></svg>')}`;\n\n/** Inline SVG placeholder for loading charts */\nconst SVG_LOADING_PLACEHOLDER = `<div class=\"w-full h-48 bg-gray-50 rounded-lg flex items-center justify-center animate-pulse\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#9ca3af\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"4\" height=\"9\" rx=\"1\"/><rect x=\"10\" y=\"7\" width=\"4\" height=\"14\" rx=\"1\"/><rect x=\"17\" y=\"3\" width=\"4\" height=\"18\" rx=\"1\"/></svg></div>`;\n\n/** Replace data-svg-chart divs with loading placeholders */\nexport function addSvgLoadingPlaceholders(html: string): string {\n return html.replace(\n /<div\\s([^>]*?)data-svg-chart=\"([^\"]+)\"([^>]*?)>[\\s\\S]*?<\\/div>/gi,\n (_match, before, chart, after) => {\n return `<div ${before}data-svg-chart=\"${chart}\"${after}>${SVG_LOADING_PLACEHOLDER}</div>`;\n }\n );\n}\n\n/** Replace data-image-query attrs with animated loading placeholders */\nexport function addLoadingPlaceholders(html: string): string {\n return html.replace(\n /(<img\\s[^>]*)data-image-query=\"([^\"]+)\"([^>]*?)(?:\\s*\\/?>)/gi,\n (_match, before, query, after) => {\n if (before.includes('src=') || after.includes('src=')) return _match;\n return `${before}src=\"${LOADING_PLACEHOLDER_SRC}\" data-image-query=\"${query}\" alt=\"${query}\"${after}>`;\n }\n );\n}\n\nexport interface StreamGenerateOptions {\n /** Anthropic API key */\n anthropicApiKey?: string;\n /** OpenAI API key */\n openaiApiKey?: string;\n /** Model ID override */\n model?: string;\n /** System prompt */\n systemPrompt: string;\n /** User message content (text or multimodal parts) */\n userContent: any[];\n /** Pexels API key for image enrichment */\n pexelsApiKey?: string;\n /** Persist DALL-E images to permanent storage */\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n /** Called when a new section is parsed */\n onSection?: (section: Section3) => void;\n /** Called when a section's images are enriched */\n onImageUpdate?: (sectionId: string, html: string) => void;\n /** Called with raw text buffer for real-time partial streaming */\n onRawChunk?: (buffer: string, completedCount: number) => void;\n /** Called when generation is complete */\n onDone?: (sections: Section3[]) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\n/**\n * Core streaming generation: stream AI text → parse NDJSON → emit sections → enrich images.\n * Used by both generateLanding and generateDocument.\n */\nexport async function streamGenerate(options: StreamGenerateOptions): Promise<Section3[]> {\n const {\n anthropicApiKey,\n openaiApiKey: _openaiApiKey,\n model: modelId,\n systemPrompt,\n userContent,\n pexelsApiKey,\n persistImage,\n onSection,\n onImageUpdate,\n onRawChunk,\n onDone,\n onError,\n } = options;\n\n const openaiApiKey = _openaiApiKey || process.env.OPENAI_API_KEY;\n const model = await resolveModel({\n openaiApiKey,\n anthropicApiKey,\n modelId,\n defaultOpenai: \"gpt-4o\",\n defaultAnthropic: \"claude-sonnet-4-6\",\n });\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: [{ role: \"user\", content: userContent }],\n });\n\n const allSections: Section3[] = [];\n const imagePromises: Promise<void>[] = [];\n let sectionOrder = 0;\n let buffer = \"\";\n\n function enrichSvgCharts(sectionRef: Section3) {\n const svgRegex = /<div\\s[^>]*data-svg-chart=\"([^\"]+)\"[^>]*>[\\s\\S]*?<\\/div>/gi;\n const svgMatches: { fullMatch: string; prompt: string }[] = [];\n let svgM: RegExpExecArray | null;\n while ((svgM = svgRegex.exec(sectionRef.html)) !== null) {\n svgMatches.push({ fullMatch: svgM[0], prompt: svgM[1] });\n }\n if (svgMatches.length === 0) return;\n\n const anthropicKey = anthropicApiKey || process.env.ANTHROPIC_API_KEY;\n imagePromises.push(\n (async () => {\n const results = await Promise.allSettled(\n svgMatches.map(async ({ fullMatch, prompt }) => {\n try {\n const svg = await generateSvg(prompt, anthropicKey);\n return { fullMatch, svg };\n } catch (e) {\n console.warn(`[svg] failed for \"${prompt}\":`, e);\n return { fullMatch, svg: `<div class=\"w-full h-48 bg-gray-100 rounded-lg flex items-center justify-center text-gray-400 text-sm\">${prompt}</div>` };\n }\n })\n );\n let html = sectionRef.html;\n for (const r of results) {\n if (r.status === \"fulfilled\" && r.value) {\n html = html.replace(r.value.fullMatch, r.value.svg);\n }\n }\n if (html !== sectionRef.html) {\n sectionRef.html = html;\n onImageUpdate?.(sectionRef.id, html);\n }\n })()\n );\n }\n\n function enrichSection(sectionRef: Section3) {\n const slots = findImageSlots(sectionRef.html);\n if (slots.length === 0) return;\n const slotsSnapshot = slots.map((s) => ({ ...s }));\n imagePromises.push(\n (async () => {\n const results = await Promise.allSettled(\n slotsSnapshot.map(async (slot) => {\n let url: string | null = null;\n // 1. Pexels first (free, fast)\n if (pexelsApiKey) {\n const img = await searchImage(slot.query, pexelsApiKey).catch(() => null);\n url = img?.url || null;\n }\n // 2. DALL-E fallback\n if (!url && openaiApiKey) {\n try {\n const tempUrl = await generateImage(slot.query, openaiApiKey);\n url = persistImage ? await persistImage(tempUrl, slot.query) : tempUrl;\n } catch (e) {\n console.warn(`[dalle] failed for \"${slot.query}\":`, e);\n }\n }\n url ??= `https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(slot.query.slice(0, 30))}`;\n return { slot, url };\n })\n );\n let html = sectionRef.html;\n for (const r of results) {\n if (r.status === \"fulfilled\" && r.value) {\n const { slot, url } = r.value;\n const replacement = slot.replaceStr.replace(\"{url}\", url);\n html = html.replaceAll(slot.searchStr, replacement);\n }\n }\n if (html !== sectionRef.html) {\n sectionRef.html = html;\n onImageUpdate?.(sectionRef.id, html);\n }\n })()\n );\n }\n\n function processObject(obj: any) {\n if (!obj.html || !obj.label) return;\n const section: Section3 = {\n id: nanoid(8),\n order: sectionOrder++,\n html: sanitizeSemanticColors(addSvgLoadingPlaceholders(addLoadingPlaceholders(obj.html))),\n label: obj.label,\n };\n allSections.push(section);\n onSection?.(section);\n enrichSection(section);\n enrichSvgCharts(section);\n }\n\n try {\n let chunkCount = 0;\n for await (const chunk of result.textStream) {\n buffer += chunk;\n chunkCount++;\n\n const [objects, remaining] = extractJsonObjects(buffer);\n buffer = remaining;\n for (const obj of objects) {\n chunkCount = 0;\n processObject(obj);\n }\n\n if (onRawChunk && chunkCount % 5 === 0 && buffer.length > 20) {\n onRawChunk(buffer, allSections.length);\n }\n }\n\n // Parse remaining buffer\n if (buffer.trim()) {\n let cleaned = buffer.trim();\n if (cleaned.startsWith(\"```\")) {\n cleaned = cleaned.replace(/^```(?:json)?\\s*/, \"\").replace(/\\s*```$/, \"\");\n }\n const [lastObjects] = extractJsonObjects(cleaned);\n for (const obj of lastObjects) processObject(obj);\n }\n\n // Wait for image enrichment\n await Promise.allSettled(imagePromises);\n\n // Final fallback for images without src\n for (const section of allSections) {\n const before = section.html;\n section.html = section.html.replace(\n /<img\\s(?![^>]*\\bsrc=)([^>]*?)>/gi,\n (_match, attrs) => {\n const altMatch = attrs.match(/alt=\"([^\"]*?)\"/);\n const query = altMatch?.[1] || \"image\";\n return `<img src=\"https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(query.slice(0, 30))}\" ${attrs}>`;\n }\n );\n section.html = section.html.replace(\n /data-image-query=\"([^\"]+)\"/g,\n (_match, query) => {\n return `src=\"https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(query.slice(0, 30))}\" data-enriched=\"placeholder\"`;\n }\n );\n if (section.html !== before) {\n onImageUpdate?.(section.id, section.html);\n }\n }\n\n onDone?.(allSections);\n return allSections;\n } catch (err: any) {\n const error = err instanceof Error ? err : new Error(err?.message || \"Generation failed\");\n onError?.(error);\n throw error;\n }\n}\n"],"mappings":"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","names":["openaiKey","anthropicKey"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  resolveModel,
3
3
  sanitizeSemanticColors
4
- } from "./chunk-BZE2DJWW.js";
4
+ } from "./chunk-JRRGQPX3.js";
5
5
  import {
6
6
  enrichImages
7
7
  } from "./chunk-G5QRFGPZ.js";
@@ -132,4 +132,4 @@ export {
132
132
  extractSectionDescription,
133
133
  refineLanding
134
134
  };
135
- //# sourceMappingURL=chunk-BSM664ZZ.js.map
135
+ //# sourceMappingURL=chunk-TPFWCQ7E.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dataUrlToImagePart,
3
3
  streamGenerate
4
- } from "./chunk-BZE2DJWW.js";
4
+ } from "./chunk-JRRGQPX3.js";
5
5
 
6
6
  // src/generate.ts
7
7
  var SYSTEM_PROMPT = `You are a world-class web designer who creates AWARD-WINNING landing pages. Your designs win Awwwards, FWA, and CSS Design Awards. You think in terms of visual hierarchy, whitespace, and emotional impact.
@@ -130,4 +130,4 @@ export {
130
130
  PROMPT_SUFFIX,
131
131
  generateLanding
132
132
  };
133
- //# sourceMappingURL=chunk-SMD43TOX.js.map
133
+ //# sourceMappingURL=chunk-VAR4GPNQ.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveModel
3
- } from "./chunk-BZE2DJWW.js";
3
+ } from "./chunk-JRRGQPX3.js";
4
4
  import "./chunk-G5QRFGPZ.js";
5
5
 
6
6
  // src/directions.ts
@@ -38,6 +38,7 @@ RULES:
38
38
  - AT LEAST one direction must be dark-mode
39
39
  - AT LEAST one must use serif headings
40
40
  - AT LEAST one must be bold/editorial with huge typography
41
+ - AT LEAST one layoutHint must include "photo" (e.g. "split-screen-photo", "immersive-gallery") \u2014 this tells the preview generator to use a real image
41
42
  - Colors must be cohesive palettes, not random. Think Dribbble-worthy.
42
43
  - Names should be evocative ("The Chronicle", "Neon Pulse", "Warm Atelier")`;
43
44
  async function generateDirections(options) {
@@ -90,7 +91,8 @@ DESIGN APPROACHES (vary across directions):
90
91
  - Approach B: Split layout \u2014 image on one half, text on solid color half
91
92
  - Approach C: Full-bleed image WITH a solid overlay (bg-black/50 or bg-[primary]/80) AND white text on top
92
93
  - Approach D: Geometric/abstract design with color blocks, no image
93
- - Choose the approach that best fits the mood. NOT every cover needs a full-bleed image.
94
+ - If the layout hint contains "photo", you MUST use Approach B or C (with a real image).
95
+ - Otherwise, choose the approach that best fits the mood. NOT every cover needs a full-bleed image.
94
96
 
95
97
  If using images:
96
98
  - Pattern: <img data-image-query="specific english search query" alt="description" class="absolute inset-0 w-full h-full object-cover"/>
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/directions.ts"],"sourcesContent":["import { generateObject, streamText } from \"ai\";\nimport { z } from \"zod\";\nimport { nanoid } from \"nanoid\";\nimport { resolveModel } from \"./streamCore\";\n\nexport const DesignDirectionSchema = z.object({\n name: z.string().describe(\"Creative direction name, e.g. 'The Editorial'\"),\n tagline: z\n .string()\n .describe(\"One-line vibe description, e.g. 'Bold serif typography meets minimalist space'\"),\n headingFont: z\n .string()\n .describe(\"Google Font name for headings, e.g. 'Playfair Display'\"),\n bodyFont: z\n .string()\n .describe(\"Google Font name for body text, e.g. 'Inter'\"),\n colors: z.object({\n primary: z.string().describe(\"Main brand color as hex, e.g. '#6366f1'\"),\n accent: z.string().describe(\"Accent/CTA color as hex, e.g. '#f59e0b'\"),\n surface: z.string().describe(\"Background surface color as hex, e.g. '#ffffff'\"),\n surfaceAlt: z.string().describe(\"Alt surface (cards, alternating sections) as hex, e.g. '#f8fafc'\"),\n text: z.string().describe(\"Main text color as hex, e.g. '#0f172a'\"),\n }),\n mood: z.enum([\"dark\", \"light\", \"warm\", \"cool\", \"vibrant\"]),\n layoutHint: z\n .string()\n .describe(\"Layout archetype: 'split-screen', 'editorial', 'immersive-gallery', 'community-feed', 'bento-grid', 'magazine'\"),\n});\n\nexport type DesignDirection = z.infer<typeof DesignDirectionSchema>;\n\nexport interface DirectionsOptions {\n anthropicApiKey?: string;\n openaiApiKey?: string;\n prompt: string;\n count?: number;\n /** \"landing\" generates hero sections, \"document\" generates cover pages */\n product?: \"landing\" | \"document\";\n /** Override the model ID (default: gpt-4o-mini) */\n model?: string;\n}\n\nconst DIRECTIONS_SYSTEM = `You are an elite creative director at a top design agency (Pentagram, Sagmeister, Collins).\n\nGiven a project brief, propose design directions that are MAXIMALLY diverse from each other.\n\nRULES:\n- Each direction must feel like a completely DIFFERENT design agency made it\n- Vary these axes: typography style (geometric sans, humanist sans, serif, slab, display/decorative), color mood (dark, light, warm, cool, vibrant), layout approach (editorial, split-screen, immersive, bento-grid, community)\n- Fonts MUST be popular Google Fonts that render well. Good examples:\n SANS: Inter, DM Sans, Space Grotesk, Outfit, Plus Jakarta Sans, Manrope, Sora, Figtree, Urbanist\n SERIF: Playfair Display, Lora, Merriweather, Source Serif 4, Cormorant Garamond, Libre Baskerville, DM Serif Display\n DISPLAY: Bebas Neue, Oswald, Archivo Black, Righteous, Anton, Alfa Slab One\n MONO: JetBrains Mono, Space Mono, IBM Plex Mono\n- NEVER use the same heading font twice\n- NEVER use the same color palette twice\n- AT LEAST one direction must be dark-mode\n- AT LEAST one must use serif headings\n- AT LEAST one must be bold/editorial with huge typography\n- Colors must be cohesive palettes, not random. Think Dribbble-worthy.\n- Names should be evocative (\"The Chronicle\", \"Neon Pulse\", \"Warm Atelier\")`;\n\n/**\n * Generate N design directions for a landing page.\n * Uses generateObject for type-safe structured output.\n */\nexport async function generateDirections(\n options: DirectionsOptions\n): Promise<DesignDirection[]> {\n const { prompt, count = 4, openaiApiKey, anthropicApiKey, model: modelId } = options;\n\n const model = await resolveModel({\n openaiApiKey,\n anthropicApiKey,\n modelId,\n defaultOpenai: \"gpt-4o-mini\",\n defaultAnthropic: \"claude-haiku-4-5-20251001\",\n });\n\n const { object } = await generateObject({\n model,\n schema: z.object({\n directions: z.array(DesignDirectionSchema).length(count),\n }),\n system: DIRECTIONS_SYSTEM,\n prompt: `Project brief: \"${prompt}\"\n\nGenerate ${count} design directions. Make them as visually distinct as possible.`,\n });\n\n return object.directions;\n}\n\n/**\n * Generate a hero section preview for a given design direction.\n * Fast Haiku call, returns raw HTML string with Google Fonts link.\n */\nexport async function generateHeroPreview(options: {\n anthropicApiKey?: string;\n openaiApiKey?: string;\n prompt: string;\n direction: DesignDirection;\n product?: \"landing\" | \"document\";\n /** Override model ID */\n model?: string;\n /** Called with partial HTML as it streams in */\n onChunk?: (partialHtml: string) => void;\n}): Promise<string> {\n const { prompt, direction, anthropicApiKey, openaiApiKey, product = \"landing\", model: modelId, onChunk } = options;\n\n const model = await resolveModel({\n openaiApiKey,\n anthropicApiKey,\n modelId,\n defaultOpenai: \"gpt-4o-mini\",\n defaultAnthropic: \"claude-haiku-4-5-20251001\",\n });\n\n const fontsUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(direction.headingFont).replace(/%20/g, \"+\")}:wght@400;700;900&family=${encodeURIComponent(direction.bodyFont).replace(/%20/g, \"+\")}:wght@400;500;600&display=swap`;\n\n const isDocument = product === \"document\";\n\n const systemPrompt = isDocument\n ? `You create stunning document cover pages with HTML + Tailwind CSS.\nOutput ONLY the raw HTML — no markdown fences, no explanation.\nThe HTML must include a <link> tag for Google Fonts and a <section> tag.\nThis is a LETTER-SIZE document cover page (8.5\" × 11\"), NOT a website hero.\nUse the EXACT fonts and colors provided. The cover must feel premium and print-ready.\nUse real-looking content specific to the brief (Spanish text).\n\nCRITICAL — TEXT MUST BE VISIBLE AND READABLE:\n- The document title MUST be large (text-4xl or bigger), bold, and clearly visible\n- Include: document title, subtitle or description, date, optional author/company name\n- ALL text must have strong contrast against its background — if background is dark, text MUST be white/light\n- NEVER let text disappear into the background\n\nDESIGN APPROACHES (vary across directions):\n- Approach A: Solid color background (bg-[primary]) with large white text — NO image needed\n- Approach B: Split layout — image on one half, text on solid color half\n- Approach C: Full-bleed image WITH a solid overlay (bg-black/50 or bg-[primary]/80) AND white text on top\n- Approach D: Geometric/abstract design with color blocks, no image\n- Choose the approach that best fits the mood. NOT every cover needs a full-bleed image.\n\nIf using images:\n- Pattern: <img data-image-query=\"specific english search query\" alt=\"description\" class=\"absolute inset-0 w-full h-full object-cover\"/>\n- ALWAYS add a dark overlay on top: <div class=\"absolute inset-0 bg-black/50\"></div>\n- Text goes ABOVE the overlay with z-10 and text-white\n- NEVER include a src attribute — ONLY data-image-query\n\nNO buttons or CTAs — this is a print document cover.\nNO emoji — use geometric shapes or SVG icons for decoration.`\n : `You create stunning hero sections with HTML + Tailwind CSS.\nOutput ONLY the raw HTML — no markdown fences, no explanation.\nThe HTML must include a <link> tag for Google Fonts and a <section> tag.\nUse the EXACT fonts and colors provided. The hero must feel premium and polished.\nUse real-looking content specific to the brief (Spanish text).\nInclude: headline (huge), subtitle, 1-2 CTAs, and optionally a hero image via <img data-image-query=\"...\" alt=\"...\" class=\"...\">.\nNEVER use src on img tags. Use data-image-query with English search terms.`;\n\n const sectionInstruction = isDocument\n ? `Generate a document cover page. Use inline style for font-family referencing the Google Fonts.\nStart with: <link href=\"${fontsUrl}\" rel=\"stylesheet\">\nThen a <section class=\"w-[8.5in] h-[11in] relative overflow-hidden\"> sized for letter paper.\nUse the exact hex colors in Tailwind arbitrary values like bg-[${direction.colors.primary}] text-[${direction.colors.text}] etc.\nUse full-bleed backgrounds, geometric accents, elegant typography hierarchy.\nMake it look like a $50K design agency document cover.`\n : `Generate a hero section. Use inline style for font-family referencing the Google Fonts.\nStart with: <link href=\"${fontsUrl}\" rel=\"stylesheet\">\nThen a <section> with min-h-[80vh].\nUse the exact hex colors in Tailwind arbitrary values like bg-[${direction.colors.primary}] text-[${direction.colors.text}] etc.\nMake it look like a $50K agency landing page hero.`;\n\n const result = streamText({\n model,\n system: systemPrompt,\n prompt: `Brief: \"${prompt}\"\n\nDesign direction: \"${direction.name}\" — ${direction.tagline}\nLayout: ${direction.layoutHint}\nHeading font: ${direction.headingFont}\nBody font: ${direction.bodyFont}\nColors: primary=${direction.colors.primary}, accent=${direction.colors.accent}, surface=${direction.colors.surface}, surfaceAlt=${direction.colors.surfaceAlt}, text=${direction.colors.text}\nMood: ${direction.mood}\n\n${sectionInstruction}`,\n });\n\n let html = \"\";\n let chunkCount = 0;\n for await (const chunk of result.textStream) {\n html += chunk;\n chunkCount++;\n if (onChunk && chunkCount % 3 === 0) {\n onChunk(html);\n }\n }\n if (onChunk) onChunk(html);\n\n // Clean markdown fences if present\n html = html.trim();\n if (html.startsWith(\"```\")) {\n html = html.replace(/^```(?:html|xml)?\\s*/, \"\").replace(/\\s*```$/, \"\");\n }\n\n return html;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/directions.ts"],"sourcesContent":["import { generateObject, streamText } from \"ai\";\nimport { z } from \"zod\";\nimport { nanoid } from \"nanoid\";\nimport { resolveModel } from \"./streamCore\";\n\nexport const DesignDirectionSchema = z.object({\n name: z.string().describe(\"Creative direction name, e.g. 'The Editorial'\"),\n tagline: z\n .string()\n .describe(\"One-line vibe description, e.g. 'Bold serif typography meets minimalist space'\"),\n headingFont: z\n .string()\n .describe(\"Google Font name for headings, e.g. 'Playfair Display'\"),\n bodyFont: z\n .string()\n .describe(\"Google Font name for body text, e.g. 'Inter'\"),\n colors: z.object({\n primary: z.string().describe(\"Main brand color as hex, e.g. '#6366f1'\"),\n accent: z.string().describe(\"Accent/CTA color as hex, e.g. '#f59e0b'\"),\n surface: z.string().describe(\"Background surface color as hex, e.g. '#ffffff'\"),\n surfaceAlt: z.string().describe(\"Alt surface (cards, alternating sections) as hex, e.g. '#f8fafc'\"),\n text: z.string().describe(\"Main text color as hex, e.g. '#0f172a'\"),\n }),\n mood: z.enum([\"dark\", \"light\", \"warm\", \"cool\", \"vibrant\"]),\n layoutHint: z\n .string()\n .describe(\"Layout archetype: 'split-screen', 'editorial', 'immersive-gallery', 'community-feed', 'bento-grid', 'magazine'\"),\n});\n\nexport type DesignDirection = z.infer<typeof DesignDirectionSchema>;\n\nexport interface DirectionsOptions {\n anthropicApiKey?: string;\n openaiApiKey?: string;\n prompt: string;\n count?: number;\n /** \"landing\" generates hero sections, \"document\" generates cover pages */\n product?: \"landing\" | \"document\";\n /** Override the model ID (default: gpt-4o-mini) */\n model?: string;\n}\n\nconst DIRECTIONS_SYSTEM = `You are an elite creative director at a top design agency (Pentagram, Sagmeister, Collins).\n\nGiven a project brief, propose design directions that are MAXIMALLY diverse from each other.\n\nRULES:\n- Each direction must feel like a completely DIFFERENT design agency made it\n- Vary these axes: typography style (geometric sans, humanist sans, serif, slab, display/decorative), color mood (dark, light, warm, cool, vibrant), layout approach (editorial, split-screen, immersive, bento-grid, community)\n- Fonts MUST be popular Google Fonts that render well. Good examples:\n SANS: Inter, DM Sans, Space Grotesk, Outfit, Plus Jakarta Sans, Manrope, Sora, Figtree, Urbanist\n SERIF: Playfair Display, Lora, Merriweather, Source Serif 4, Cormorant Garamond, Libre Baskerville, DM Serif Display\n DISPLAY: Bebas Neue, Oswald, Archivo Black, Righteous, Anton, Alfa Slab One\n MONO: JetBrains Mono, Space Mono, IBM Plex Mono\n- NEVER use the same heading font twice\n- NEVER use the same color palette twice\n- AT LEAST one direction must be dark-mode\n- AT LEAST one must use serif headings\n- AT LEAST one must be bold/editorial with huge typography\n- AT LEAST one layoutHint must include \"photo\" (e.g. \"split-screen-photo\", \"immersive-gallery\") — this tells the preview generator to use a real image\n- Colors must be cohesive palettes, not random. Think Dribbble-worthy.\n- Names should be evocative (\"The Chronicle\", \"Neon Pulse\", \"Warm Atelier\")`;\n\n/**\n * Generate N design directions for a landing page.\n * Uses generateObject for type-safe structured output.\n */\nexport async function generateDirections(\n options: DirectionsOptions\n): Promise<DesignDirection[]> {\n const { prompt, count = 4, openaiApiKey, anthropicApiKey, model: modelId } = options;\n\n const model = await resolveModel({\n openaiApiKey,\n anthropicApiKey,\n modelId,\n defaultOpenai: \"gpt-4o-mini\",\n defaultAnthropic: \"claude-haiku-4-5-20251001\",\n });\n\n const { object } = await generateObject({\n model,\n schema: z.object({\n directions: z.array(DesignDirectionSchema).length(count),\n }),\n system: DIRECTIONS_SYSTEM,\n prompt: `Project brief: \"${prompt}\"\n\nGenerate ${count} design directions. Make them as visually distinct as possible.`,\n });\n\n return object.directions;\n}\n\n/**\n * Generate a hero section preview for a given design direction.\n * Fast Haiku call, returns raw HTML string with Google Fonts link.\n */\nexport async function generateHeroPreview(options: {\n anthropicApiKey?: string;\n openaiApiKey?: string;\n prompt: string;\n direction: DesignDirection;\n product?: \"landing\" | \"document\";\n /** Override model ID */\n model?: string;\n /** Called with partial HTML as it streams in */\n onChunk?: (partialHtml: string) => void;\n}): Promise<string> {\n const { prompt, direction, anthropicApiKey, openaiApiKey, product = \"landing\", model: modelId, onChunk } = options;\n\n const model = await resolveModel({\n openaiApiKey,\n anthropicApiKey,\n modelId,\n defaultOpenai: \"gpt-4o-mini\",\n defaultAnthropic: \"claude-haiku-4-5-20251001\",\n });\n\n const fontsUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(direction.headingFont).replace(/%20/g, \"+\")}:wght@400;700;900&family=${encodeURIComponent(direction.bodyFont).replace(/%20/g, \"+\")}:wght@400;500;600&display=swap`;\n\n const isDocument = product === \"document\";\n\n const systemPrompt = isDocument\n ? `You create stunning document cover pages with HTML + Tailwind CSS.\nOutput ONLY the raw HTML — no markdown fences, no explanation.\nThe HTML must include a <link> tag for Google Fonts and a <section> tag.\nThis is a LETTER-SIZE document cover page (8.5\" × 11\"), NOT a website hero.\nUse the EXACT fonts and colors provided. The cover must feel premium and print-ready.\nUse real-looking content specific to the brief (Spanish text).\n\nCRITICAL — TEXT MUST BE VISIBLE AND READABLE:\n- The document title MUST be large (text-4xl or bigger), bold, and clearly visible\n- Include: document title, subtitle or description, date, optional author/company name\n- ALL text must have strong contrast against its background — if background is dark, text MUST be white/light\n- NEVER let text disappear into the background\n\nDESIGN APPROACHES (vary across directions):\n- Approach A: Solid color background (bg-[primary]) with large white text — NO image needed\n- Approach B: Split layout — image on one half, text on solid color half\n- Approach C: Full-bleed image WITH a solid overlay (bg-black/50 or bg-[primary]/80) AND white text on top\n- Approach D: Geometric/abstract design with color blocks, no image\n- If the layout hint contains \"photo\", you MUST use Approach B or C (with a real image).\n- Otherwise, choose the approach that best fits the mood. NOT every cover needs a full-bleed image.\n\nIf using images:\n- Pattern: <img data-image-query=\"specific english search query\" alt=\"description\" class=\"absolute inset-0 w-full h-full object-cover\"/>\n- ALWAYS add a dark overlay on top: <div class=\"absolute inset-0 bg-black/50\"></div>\n- Text goes ABOVE the overlay with z-10 and text-white\n- NEVER include a src attribute — ONLY data-image-query\n\nNO buttons or CTAs — this is a print document cover.\nNO emoji — use geometric shapes or SVG icons for decoration.`\n : `You create stunning hero sections with HTML + Tailwind CSS.\nOutput ONLY the raw HTML — no markdown fences, no explanation.\nThe HTML must include a <link> tag for Google Fonts and a <section> tag.\nUse the EXACT fonts and colors provided. The hero must feel premium and polished.\nUse real-looking content specific to the brief (Spanish text).\nInclude: headline (huge), subtitle, 1-2 CTAs, and optionally a hero image via <img data-image-query=\"...\" alt=\"...\" class=\"...\">.\nNEVER use src on img tags. Use data-image-query with English search terms.`;\n\n const sectionInstruction = isDocument\n ? `Generate a document cover page. Use inline style for font-family referencing the Google Fonts.\nStart with: <link href=\"${fontsUrl}\" rel=\"stylesheet\">\nThen a <section class=\"w-[8.5in] h-[11in] relative overflow-hidden\"> sized for letter paper.\nUse the exact hex colors in Tailwind arbitrary values like bg-[${direction.colors.primary}] text-[${direction.colors.text}] etc.\nUse full-bleed backgrounds, geometric accents, elegant typography hierarchy.\nMake it look like a $50K design agency document cover.`\n : `Generate a hero section. Use inline style for font-family referencing the Google Fonts.\nStart with: <link href=\"${fontsUrl}\" rel=\"stylesheet\">\nThen a <section> with min-h-[80vh].\nUse the exact hex colors in Tailwind arbitrary values like bg-[${direction.colors.primary}] text-[${direction.colors.text}] etc.\nMake it look like a $50K agency landing page hero.`;\n\n const result = streamText({\n model,\n system: systemPrompt,\n prompt: `Brief: \"${prompt}\"\n\nDesign direction: \"${direction.name}\" — ${direction.tagline}\nLayout: ${direction.layoutHint}\nHeading font: ${direction.headingFont}\nBody font: ${direction.bodyFont}\nColors: primary=${direction.colors.primary}, accent=${direction.colors.accent}, surface=${direction.colors.surface}, surfaceAlt=${direction.colors.surfaceAlt}, text=${direction.colors.text}\nMood: ${direction.mood}\n\n${sectionInstruction}`,\n });\n\n let html = \"\";\n let chunkCount = 0;\n for await (const chunk of result.textStream) {\n html += chunk;\n chunkCount++;\n if (onChunk && chunkCount % 3 === 0) {\n onChunk(html);\n }\n }\n if (onChunk) onChunk(html);\n\n // Clean markdown fences if present\n html = html.trim();\n if (html.startsWith(\"```\")) {\n html = html.replace(/^```(?:html|xml)?\\s*/, \"\").replace(/\\s*```$/, \"\");\n }\n\n return html;\n}\n"],"mappings":"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","names":[]}
package/dist/generate.js CHANGED
@@ -2,10 +2,10 @@ import {
2
2
  PROMPT_SUFFIX,
3
3
  SYSTEM_PROMPT,
4
4
  generateLanding
5
- } from "./chunk-SMD43TOX.js";
5
+ } from "./chunk-VAR4GPNQ.js";
6
6
  import {
7
7
  extractJsonObjects
8
- } from "./chunk-BZE2DJWW.js";
8
+ } from "./chunk-JRRGQPX3.js";
9
9
  import "./chunk-G5QRFGPZ.js";
10
10
  export {
11
11
  PROMPT_SUFFIX,
@@ -2,8 +2,8 @@ import {
2
2
  DOCUMENT_PROMPT_SUFFIX,
3
3
  DOCUMENT_SYSTEM_PROMPT,
4
4
  generateDocument
5
- } from "./chunk-NFAH6S7X.js";
6
- import "./chunk-BZE2DJWW.js";
5
+ } from "./chunk-5DDQ5ZXR.js";
6
+ import "./chunk-JRRGQPX3.js";
7
7
  import "./chunk-G5QRFGPZ.js";
8
8
  export {
9
9
  DOCUMENT_PROMPT_SUFFIX,
package/dist/index.js CHANGED
@@ -10,16 +10,16 @@ import {
10
10
  PROMPT_SUFFIX,
11
11
  SYSTEM_PROMPT,
12
12
  generateLanding
13
- } from "./chunk-SMD43TOX.js";
13
+ } from "./chunk-VAR4GPNQ.js";
14
14
  import {
15
15
  DOCUMENT_PROMPT_SUFFIX,
16
16
  DOCUMENT_SYSTEM_PROMPT,
17
17
  generateDocument
18
- } from "./chunk-NFAH6S7X.js";
18
+ } from "./chunk-5DDQ5ZXR.js";
19
19
  import {
20
20
  REFINE_SYSTEM,
21
21
  refineLanding
22
- } from "./chunk-BSM664ZZ.js";
22
+ } from "./chunk-TPFWCQ7E.js";
23
23
  import {
24
24
  deployToEasyBits,
25
25
  deployToS3
@@ -36,7 +36,7 @@ import {
36
36
  } from "./chunk-LHVMUMQS.js";
37
37
  import {
38
38
  extractJsonObjects
39
- } from "./chunk-BZE2DJWW.js";
39
+ } from "./chunk-JRRGQPX3.js";
40
40
  import {
41
41
  enrichImages,
42
42
  findImageSlots,
package/dist/refine.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  REFINE_SYSTEM,
3
3
  extractSectionDescription,
4
4
  refineLanding
5
- } from "./chunk-BSM664ZZ.js";
6
- import "./chunk-BZE2DJWW.js";
5
+ } from "./chunk-TPFWCQ7E.js";
6
+ import "./chunk-JRRGQPX3.js";
7
7
  import "./chunk-G5QRFGPZ.js";
8
8
  export {
9
9
  REFINE_SYSTEM,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easybits.cloud/html-tailwind-generator",
3
- "version": "0.2.82",
3
+ "version": "0.2.84",
4
4
  "description": "AI-powered landing page generator with Tailwind CSS — canvas editor, streaming generation, and one-click deploy",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/sanitizeColors.ts","../src/streamCore.ts"],"sourcesContent":["/**\n * Replace hardcoded Tailwind color classes with semantic color classes\n * (bg-primary, text-primary, etc.). Gray/white/black/slate/zinc/neutral stay intact.\n *\n * Covers ALL chromatic Tailwind colors the model might generate.\n */\n\n// All chromatic colors Tailwind ships (excluding neutrals: slate, gray, zinc, neutral, stone)\nconst COLORS =\n \"red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose\";\n\nfunction re(prefix: string, shades: string): RegExp {\n return new RegExp(`\\\\b${prefix}-(${COLORS})-(${shades})\\\\b`, \"g\");\n}\n\nconst replacements: [RegExp, string][] = [\n // Background\n [re(\"bg\", \"500|600|700\"), \"bg-primary\"],\n [re(\"bg\", \"50|100\"), \"bg-primary-light\"],\n [re(\"bg\", \"800|900|950\"), \"bg-primary-dark\"],\n [re(\"bg\", \"200|300|400\"), \"bg-primary\"],\n\n // Text\n [re(\"text\", \"500|600|700\"), \"text-primary\"],\n [re(\"text\", \"800|900|950\"), \"text-primary-dark\"],\n [re(\"text\", \"50|100|200|300\"), \"text-on-primary\"],\n [re(\"text\", \"400\"), \"text-primary\"],\n\n // Border\n [re(\"border\", \"\\\\d{2,3}\"), \"border-primary\"],\n\n // Ring\n [re(\"ring\", \"\\\\d{2,3}\"), \"ring-primary\"],\n\n // Gradients\n [re(\"from\", \"\\\\d{2,3}\"), \"from-primary\"],\n [re(\"to\", \"\\\\d{2,3}\"), \"to-primary\"],\n [re(\"via\", \"\\\\d{2,3}\"), \"via-primary\"],\n\n // Hover/focus variants\n [new RegExp(`\\\\bhover:bg-(${COLORS})-(500|600|700|800|900|950)\\\\b`, \"g\"), \"hover:bg-primary-dark\"],\n [new RegExp(`\\\\bhover:bg-(${COLORS})-(50|100|200|300|400)\\\\b`, \"g\"), \"hover:bg-primary-light\"],\n [new RegExp(`\\\\bhover:text-(${COLORS})-\\\\d{2,3}\\\\b`, \"g\"), \"hover:text-primary\"],\n [new RegExp(`\\\\bfocus:ring-(${COLORS})-\\\\d{2,3}\\\\b`, \"g\"), \"focus:ring-primary\"],\n [new RegExp(`\\\\bfocus:border-(${COLORS})-\\\\d{2,3}\\\\b`, \"g\"), \"focus:border-primary\"],\n\n // Divide\n [re(\"divide\", \"\\\\d{2,3}\"), \"divide-primary\"],\n\n // Placeholder\n [re(\"placeholder\", \"\\\\d{2,3}\"), \"placeholder-primary\"],\n\n // Outline\n [re(\"outline\", \"\\\\d{2,3}\"), \"outline-primary\"],\n\n // Shadow colored\n [re(\"shadow\", \"\\\\d{2,3}\"), \"shadow-primary\"],\n\n // Decoration\n [re(\"decoration\", \"\\\\d{2,3}\"), \"decoration-primary\"],\n\n // Accent\n [re(\"accent\", \"\\\\d{2,3}\"), \"accent-primary\"],\n];\n\nexport function sanitizeSemanticColors(html: string): string {\n let result = html;\n for (const [pattern, replacement] of replacements) {\n result = result.replace(pattern, replacement);\n }\n return result;\n}\n","import { streamText } from \"ai\";\nimport { createAnthropic } from \"@ai-sdk/anthropic\";\nimport { nanoid } from \"nanoid\";\nimport { findImageSlots } from \"./images/enrichImages\";\nimport { searchImage } from \"./images/pexels\";\nimport { generateImage } from \"./images/dalleImages\";\nimport { generateSvg } from \"./images/svgGenerator\";\nimport type { Section3 } from \"./types\";\nimport { sanitizeSemanticColors } from \"./sanitizeColors\";\n\n/**\n * Resolve AI model from available keys.\n * Prefers Anthropic, falls back to OpenAI.\n */\nexport async function resolveModel(opts: {\n openaiApiKey?: string;\n anthropicApiKey?: string;\n modelId?: string;\n defaultOpenai: string;\n defaultAnthropic: string;\n}) {\n const anthropicKey = opts.anthropicApiKey || process.env.ANTHROPIC_API_KEY;\n if (anthropicKey) {\n const anthropic = createAnthropic({ apiKey: anthropicKey });\n return anthropic(opts.modelId || opts.defaultAnthropic);\n }\n const openaiKey = opts.openaiApiKey || process.env.OPENAI_API_KEY;\n if (openaiKey) {\n const { createOpenAI } = await import(\"@ai-sdk/openai\");\n const openai = createOpenAI({ apiKey: openaiKey });\n return openai(opts.modelId || opts.defaultOpenai);\n }\n return createAnthropic()(opts.modelId || opts.defaultAnthropic);\n}\n\n/**\n * Convert data URL to Uint8Array for AI SDK vision.\n */\nexport function dataUrlToImagePart(dataUrl: string): { image: Uint8Array; mimeType: string } | null {\n const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);\n if (!match) return null;\n return {\n image: new Uint8Array(Buffer.from(match[2], \"base64\")),\n mimeType: match[1],\n };\n}\n\n/**\n * Extract complete JSON objects from accumulated text using brace-depth tracking.\n */\nexport function extractJsonObjects(text: string): [any[], string] {\n const objects: any[] = [];\n let remaining = text;\n\n while (remaining.length > 0) {\n remaining = remaining.trimStart();\n if (!remaining.startsWith(\"{\")) {\n const nextBrace = remaining.indexOf(\"{\");\n if (nextBrace === -1) break;\n remaining = remaining.slice(nextBrace);\n continue;\n }\n\n let depth = 0;\n let inString = false;\n let escape = false;\n let end = -1;\n\n for (let i = 0; i < remaining.length; i++) {\n const ch = remaining[i];\n if (escape) { escape = false; continue; }\n if (ch === \"\\\\\") { escape = true; continue; }\n if (ch === '\"') { inString = !inString; continue; }\n if (inString) continue;\n if (ch === \"{\") depth++;\n if (ch === \"}\") { depth--; if (depth === 0) { end = i; break; } }\n }\n\n if (end === -1) break;\n\n const candidate = remaining.slice(0, end + 1);\n remaining = remaining.slice(end + 1);\n\n try {\n objects.push(JSON.parse(candidate));\n } catch {\n // malformed, skip\n }\n }\n\n return [objects, remaining];\n}\n\n/** Inline shimmer SVG used as src for loading image placeholders */\nconst LOADING_PLACEHOLDER_SRC = `data:image/svg+xml,${encodeURIComponent('<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"800\" height=\"500\" viewBox=\"0 0 800 500\"><rect fill=\"#f3f4f6\" width=\"800\" height=\"500\" rx=\"12\"/><g opacity=\".4\"><rect x=\"320\" y=\"200\" width=\"160\" height=\"4\" rx=\"2\" fill=\"#d1d5db\"><animate attributeName=\"opacity\" values=\".3;.8;.3\" dur=\"1.5s\" repeatCount=\"indefinite\"/></rect><rect x=\"280\" y=\"215\" width=\"240\" height=\"4\" rx=\"2\" fill=\"#d1d5db\"><animate attributeName=\"opacity\" values=\".3;.8;.3\" dur=\"1.5s\" begin=\".3s\" repeatCount=\"indefinite\"/></rect><rect x=\"340\" y=\"230\" width=\"120\" height=\"4\" rx=\"2\" fill=\"#d1d5db\"><animate attributeName=\"opacity\" values=\".3;.8;.3\" dur=\"1.5s\" begin=\".6s\" repeatCount=\"indefinite\"/></rect></g><g transform=\"translate(376,150)\" opacity=\".3\"><path d=\"M0 28V4a4 4 0 014-4h40a4 4 0 014 4v24a4 4 0 01-4 4H4a4 4 0 01-4-4z\" fill=\"#d1d5db\"/><circle cx=\"14\" cy=\"12\" r=\"4\" fill=\"#9ca3af\"/><path d=\"M4 28l10-10 6 6 8-8 16 16H4z\" fill=\"#9ca3af\" opacity=\".5\"/></g></svg>')}`;\n\n/** Inline SVG placeholder for loading charts */\nconst SVG_LOADING_PLACEHOLDER = `<div class=\"w-full h-48 bg-gray-50 rounded-lg flex items-center justify-center animate-pulse\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#9ca3af\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"4\" height=\"9\" rx=\"1\"/><rect x=\"10\" y=\"7\" width=\"4\" height=\"14\" rx=\"1\"/><rect x=\"17\" y=\"3\" width=\"4\" height=\"18\" rx=\"1\"/></svg></div>`;\n\n/** Replace data-svg-chart divs with loading placeholders */\nexport function addSvgLoadingPlaceholders(html: string): string {\n return html.replace(\n /<div\\s([^>]*?)data-svg-chart=\"([^\"]+)\"([^>]*?)>[\\s\\S]*?<\\/div>/gi,\n (_match, before, chart, after) => {\n return `<div ${before}data-svg-chart=\"${chart}\"${after}>${SVG_LOADING_PLACEHOLDER}</div>`;\n }\n );\n}\n\n/** Replace data-image-query attrs with animated loading placeholders */\nexport function addLoadingPlaceholders(html: string): string {\n return html.replace(\n /(<img\\s[^>]*)data-image-query=\"([^\"]+)\"([^>]*?)(?:\\s*\\/?>)/gi,\n (_match, before, query, after) => {\n if (before.includes('src=') || after.includes('src=')) return _match;\n return `${before}src=\"${LOADING_PLACEHOLDER_SRC}\" data-image-query=\"${query}\" alt=\"${query}\"${after}>`;\n }\n );\n}\n\nexport interface StreamGenerateOptions {\n /** Anthropic API key */\n anthropicApiKey?: string;\n /** OpenAI API key */\n openaiApiKey?: string;\n /** Model ID override */\n model?: string;\n /** System prompt */\n systemPrompt: string;\n /** User message content (text or multimodal parts) */\n userContent: any[];\n /** Pexels API key for image enrichment */\n pexelsApiKey?: string;\n /** Persist DALL-E images to permanent storage */\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n /** Called when a new section is parsed */\n onSection?: (section: Section3) => void;\n /** Called when a section's images are enriched */\n onImageUpdate?: (sectionId: string, html: string) => void;\n /** Called with raw text buffer for real-time partial streaming */\n onRawChunk?: (buffer: string, completedCount: number) => void;\n /** Called when generation is complete */\n onDone?: (sections: Section3[]) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\n/**\n * Core streaming generation: stream AI text → parse NDJSON → emit sections → enrich images.\n * Used by both generateLanding and generateDocument.\n */\nexport async function streamGenerate(options: StreamGenerateOptions): Promise<Section3[]> {\n const {\n anthropicApiKey,\n openaiApiKey: _openaiApiKey,\n model: modelId,\n systemPrompt,\n userContent,\n pexelsApiKey,\n persistImage,\n onSection,\n onImageUpdate,\n onRawChunk,\n onDone,\n onError,\n } = options;\n\n const openaiApiKey = _openaiApiKey || process.env.OPENAI_API_KEY;\n const model = await resolveModel({\n openaiApiKey,\n anthropicApiKey,\n modelId,\n defaultOpenai: \"gpt-4o\",\n defaultAnthropic: \"claude-sonnet-4-6\",\n });\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: [{ role: \"user\", content: userContent }],\n });\n\n const allSections: Section3[] = [];\n const imagePromises: Promise<void>[] = [];\n let sectionOrder = 0;\n let buffer = \"\";\n\n function enrichSvgCharts(sectionRef: Section3) {\n const svgRegex = /<div\\s[^>]*data-svg-chart=\"([^\"]+)\"[^>]*>[\\s\\S]*?<\\/div>/gi;\n const svgMatches: { fullMatch: string; prompt: string }[] = [];\n let svgM: RegExpExecArray | null;\n while ((svgM = svgRegex.exec(sectionRef.html)) !== null) {\n svgMatches.push({ fullMatch: svgM[0], prompt: svgM[1] });\n }\n if (svgMatches.length === 0) return;\n\n const anthropicKey = anthropicApiKey || process.env.ANTHROPIC_API_KEY;\n imagePromises.push(\n (async () => {\n const results = await Promise.allSettled(\n svgMatches.map(async ({ fullMatch, prompt }) => {\n try {\n const svg = await generateSvg(prompt, anthropicKey);\n return { fullMatch, svg };\n } catch (e) {\n console.warn(`[svg] failed for \"${prompt}\":`, e);\n return { fullMatch, svg: `<div class=\"w-full h-48 bg-gray-100 rounded-lg flex items-center justify-center text-gray-400 text-sm\">${prompt}</div>` };\n }\n })\n );\n let html = sectionRef.html;\n for (const r of results) {\n if (r.status === \"fulfilled\" && r.value) {\n html = html.replace(r.value.fullMatch, r.value.svg);\n }\n }\n if (html !== sectionRef.html) {\n sectionRef.html = html;\n onImageUpdate?.(sectionRef.id, html);\n }\n })()\n );\n }\n\n function enrichSection(sectionRef: Section3) {\n const slots = findImageSlots(sectionRef.html);\n if (slots.length === 0) return;\n const slotsSnapshot = slots.map((s) => ({ ...s }));\n imagePromises.push(\n (async () => {\n const results = await Promise.allSettled(\n slotsSnapshot.map(async (slot) => {\n let url: string | null = null;\n // 1. Pexels first (free, fast)\n if (pexelsApiKey) {\n const img = await searchImage(slot.query, pexelsApiKey).catch(() => null);\n url = img?.url || null;\n }\n // 2. DALL-E fallback\n if (!url && openaiApiKey) {\n try {\n const tempUrl = await generateImage(slot.query, openaiApiKey);\n url = persistImage ? await persistImage(tempUrl, slot.query) : tempUrl;\n } catch (e) {\n console.warn(`[dalle] failed for \"${slot.query}\":`, e);\n }\n }\n url ??= `https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(slot.query.slice(0, 30))}`;\n return { slot, url };\n })\n );\n let html = sectionRef.html;\n for (const r of results) {\n if (r.status === \"fulfilled\" && r.value) {\n const { slot, url } = r.value;\n const replacement = slot.replaceStr.replace(\"{url}\", url);\n html = html.replaceAll(slot.searchStr, replacement);\n }\n }\n if (html !== sectionRef.html) {\n sectionRef.html = html;\n onImageUpdate?.(sectionRef.id, html);\n }\n })()\n );\n }\n\n function processObject(obj: any) {\n if (!obj.html || !obj.label) return;\n const section: Section3 = {\n id: nanoid(8),\n order: sectionOrder++,\n html: sanitizeSemanticColors(addSvgLoadingPlaceholders(addLoadingPlaceholders(obj.html))),\n label: obj.label,\n };\n allSections.push(section);\n onSection?.(section);\n enrichSection(section);\n enrichSvgCharts(section);\n }\n\n try {\n let chunkCount = 0;\n for await (const chunk of result.textStream) {\n buffer += chunk;\n chunkCount++;\n\n const [objects, remaining] = extractJsonObjects(buffer);\n buffer = remaining;\n for (const obj of objects) {\n chunkCount = 0;\n processObject(obj);\n }\n\n if (onRawChunk && chunkCount % 5 === 0 && buffer.length > 20) {\n onRawChunk(buffer, allSections.length);\n }\n }\n\n // Parse remaining buffer\n if (buffer.trim()) {\n let cleaned = buffer.trim();\n if (cleaned.startsWith(\"```\")) {\n cleaned = cleaned.replace(/^```(?:json)?\\s*/, \"\").replace(/\\s*```$/, \"\");\n }\n const [lastObjects] = extractJsonObjects(cleaned);\n for (const obj of lastObjects) processObject(obj);\n }\n\n // Wait for image enrichment\n await Promise.allSettled(imagePromises);\n\n // Final fallback for images without src\n for (const section of allSections) {\n const before = section.html;\n section.html = section.html.replace(\n /<img\\s(?![^>]*\\bsrc=)([^>]*?)>/gi,\n (_match, attrs) => {\n const altMatch = attrs.match(/alt=\"([^\"]*?)\"/);\n const query = altMatch?.[1] || \"image\";\n return `<img src=\"https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(query.slice(0, 30))}\" ${attrs}>`;\n }\n );\n section.html = section.html.replace(\n /data-image-query=\"([^\"]+)\"/g,\n (_match, query) => {\n return `src=\"https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(query.slice(0, 30))}\" data-enriched=\"placeholder\"`;\n }\n );\n if (section.html !== before) {\n onImageUpdate?.(section.id, section.html);\n }\n }\n\n onDone?.(allSections);\n return allSections;\n } catch (err: any) {\n const error = err instanceof Error ? err : new Error(err?.message || \"Generation failed\");\n onError?.(error);\n throw error;\n }\n}\n"],"mappings":"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","names":[]}