@easybits.cloud/html-tailwind-generator 0.2.97 → 0.2.98
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.
- package/dist/{chunk-XKSKYMSK.js → chunk-3QER6Z5B.js} +2 -2
- package/dist/{chunk-WWDK5RJV.js → chunk-AJGAO7UP.js} +71 -1
- package/dist/chunk-AJGAO7UP.js.map +1 -0
- package/dist/{chunk-OQVJB5TI.js → chunk-ROHHDJBH.js} +243 -3
- package/dist/chunk-ROHHDJBH.js.map +1 -0
- package/dist/{chunk-5RC7VLJ7.js → chunk-T6UZ3FVJ.js} +2 -2
- package/dist/directions.js +1 -1
- package/dist/generate.js +2 -2
- package/dist/generateDocument.d.ts +51 -2
- package/dist/generateDocument.js +6 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -4
- package/dist/refine.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-OQVJB5TI.js.map +0 -1
- package/dist/chunk-WWDK5RJV.js.map +0 -1
- /package/dist/{chunk-XKSKYMSK.js.map → chunk-3QER6Z5B.js.map} +0 -0
- /package/dist/{chunk-5RC7VLJ7.js.map → chunk-T6UZ3FVJ.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveModel,
|
|
3
3
|
sanitizeSemanticColors
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-AJGAO7UP.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-
|
|
135
|
+
//# sourceMappingURL=chunk-3QER6Z5B.js.map
|
|
@@ -170,6 +170,72 @@ function addLoadingPlaceholders(html) {
|
|
|
170
170
|
}
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
|
+
async function enrichSectionImages(section, opts) {
|
|
174
|
+
const slots = findImageSlots(section.html);
|
|
175
|
+
if (slots.length === 0) return;
|
|
176
|
+
const results = await Promise.allSettled(
|
|
177
|
+
slots.map(async (slot) => {
|
|
178
|
+
let url = null;
|
|
179
|
+
if (opts.pexelsApiKey) {
|
|
180
|
+
const img = await searchImage(slot.query, opts.pexelsApiKey).catch(() => null);
|
|
181
|
+
url = img?.url || null;
|
|
182
|
+
}
|
|
183
|
+
if (!url && opts.openaiApiKey) {
|
|
184
|
+
try {
|
|
185
|
+
const tempUrl = await generateImage(slot.query, opts.openaiApiKey);
|
|
186
|
+
url = opts.persistImage ? await opts.persistImage(tempUrl, slot.query) : tempUrl;
|
|
187
|
+
} catch (e) {
|
|
188
|
+
console.warn(`[dalle] failed for "${slot.query}":`, e);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
url ??= `https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(slot.query.slice(0, 30))}`;
|
|
192
|
+
return { slot, url };
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
let html = section.html;
|
|
196
|
+
for (const r of results) {
|
|
197
|
+
if (r.status === "fulfilled" && r.value) {
|
|
198
|
+
const { slot, url } = r.value;
|
|
199
|
+
const replacement = slot.replaceStr.replace("{url}", url);
|
|
200
|
+
html = html.replaceAll(slot.searchStr, replacement);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (html !== section.html) {
|
|
204
|
+
section.html = html;
|
|
205
|
+
opts.onImageUpdate?.(section.id, html);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function enrichSectionSvgCharts(section, opts) {
|
|
209
|
+
const svgRegex = /<div\s[^>]*data-svg-chart="([^"]+)"[^>]*>[\s\S]*?<\/div>/gi;
|
|
210
|
+
const svgMatches = [];
|
|
211
|
+
let svgM;
|
|
212
|
+
while ((svgM = svgRegex.exec(section.html)) !== null) {
|
|
213
|
+
svgMatches.push({ fullMatch: svgM[0], prompt: svgM[1] });
|
|
214
|
+
}
|
|
215
|
+
if (svgMatches.length === 0) return;
|
|
216
|
+
const anthropicKey = opts.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
|
217
|
+
const results = await Promise.allSettled(
|
|
218
|
+
svgMatches.map(async ({ fullMatch, prompt }) => {
|
|
219
|
+
try {
|
|
220
|
+
const svg = await generateSvg(prompt, anthropicKey);
|
|
221
|
+
return { fullMatch, svg };
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.warn(`[svg] failed for "${prompt}":`, e);
|
|
224
|
+
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>` };
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
let html = section.html;
|
|
229
|
+
for (const r of results) {
|
|
230
|
+
if (r.status === "fulfilled" && r.value) {
|
|
231
|
+
html = html.replace(r.value.fullMatch, r.value.svg);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (html !== section.html) {
|
|
235
|
+
section.html = html;
|
|
236
|
+
opts.onImageUpdate?.(section.id, html);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
173
239
|
async function streamGenerate(options) {
|
|
174
240
|
const {
|
|
175
241
|
anthropicApiKey,
|
|
@@ -348,6 +414,10 @@ export {
|
|
|
348
414
|
resolveModel,
|
|
349
415
|
dataUrlToImagePart,
|
|
350
416
|
extractJsonObjects,
|
|
417
|
+
addSvgLoadingPlaceholders,
|
|
418
|
+
addLoadingPlaceholders,
|
|
419
|
+
enrichSectionImages,
|
|
420
|
+
enrichSectionSvgCharts,
|
|
351
421
|
streamGenerate
|
|
352
422
|
};
|
|
353
|
-
//# sourceMappingURL=chunk-
|
|
423
|
+
//# sourceMappingURL=chunk-AJGAO7UP.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 * If modelId is already a LanguageModel object, return it directly.\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\nfunction isLanguageModel(value: unknown): value is import(\"ai\").LanguageModel {\n return typeof value === \"object\" && value !== null && \"modelId\" in value && \"provider\" in value;\n}\n\nexport async function resolveModel(opts: {\n openaiApiKey?: string;\n anthropicApiKey?: string;\n modelId?: string | import(\"ai\").LanguageModel;\n defaultOpenai: string;\n defaultAnthropic: string;\n}) {\n // If modelId is already a model object, return it directly\n if (opts.modelId && isLanguageModel(opts.modelId)) {\n return opts.modelId;\n }\n\n const modelId = opts.modelId as string | undefined;\n\n if (modelId && isOpenAiModel(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 })(modelId);\n }\n // OpenAI model requested but no key — fall through to Anthropic default\n } else if (modelId) {\n const anthropicKey = opts.anthropicApiKey || process.env.ANTHROPIC_API_KEY;\n if (anthropicKey) {\n return createAnthropic({ apiKey: anthropicKey })(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\n/** Enrich a section's images (Pexels → DALL-E → placeholder fallback). Mutates section.html in place. */\nexport async function enrichSectionImages(\n section: Section3,\n opts: {\n pexelsApiKey?: string;\n openaiApiKey?: string;\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n onImageUpdate?: (sectionId: string, html: string) => void;\n }\n): Promise<void> {\n const slots = findImageSlots(section.html);\n if (slots.length === 0) return;\n const results = await Promise.allSettled(\n slots.map(async (slot) => {\n let url: string | null = null;\n if (opts.pexelsApiKey) {\n const img = await searchImage(slot.query, opts.pexelsApiKey).catch(() => null);\n url = img?.url || null;\n }\n if (!url && opts.openaiApiKey) {\n try {\n const tempUrl = await generateImage(slot.query, opts.openaiApiKey);\n url = opts.persistImage ? await opts.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 = section.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 !== section.html) {\n section.html = html;\n opts.onImageUpdate?.(section.id, html);\n }\n}\n\n/** Enrich a section's SVG chart placeholders. Mutates section.html in place. */\nexport async function enrichSectionSvgCharts(\n section: Section3,\n opts: {\n anthropicApiKey?: string;\n onImageUpdate?: (sectionId: string, html: string) => void;\n }\n): Promise<void> {\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(section.html)) !== null) {\n svgMatches.push({ fullMatch: svgM[0], prompt: svgM[1] });\n }\n if (svgMatches.length === 0) return;\n const anthropicKey = opts.anthropicApiKey || process.env.ANTHROPIC_API_KEY;\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 = section.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 !== section.html) {\n section.html = html;\n opts.onImageUpdate?.(section.id, html);\n }\n}\n\nexport interface StreamGenerateOptions {\n /** Anthropic API key */\n anthropicApiKey?: string;\n /** OpenAI API key */\n openaiApiKey?: string;\n /** Model ID override or pre-built LanguageModel object */\n model?: string | import(\"ai\").LanguageModel;\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,9 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
+
addLoadingPlaceholders,
|
|
3
|
+
addSvgLoadingPlaceholders,
|
|
2
4
|
dataUrlToImagePart,
|
|
5
|
+
enrichSectionImages,
|
|
6
|
+
enrichSectionSvgCharts,
|
|
7
|
+
extractJsonObjects,
|
|
8
|
+
resolveModel,
|
|
9
|
+
sanitizeSemanticColors,
|
|
3
10
|
streamGenerate
|
|
4
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-AJGAO7UP.js";
|
|
5
12
|
|
|
6
13
|
// src/generateDocument.ts
|
|
14
|
+
import { generateObject, streamText } from "ai";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import { nanoid } from "nanoid";
|
|
7
17
|
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.
|
|
8
18
|
|
|
9
19
|
RULES:
|
|
@@ -240,10 +250,240 @@ Use this exact <img> tag with this exact src URL \u2014 do NOT invent a differen
|
|
|
240
250
|
userContent: content
|
|
241
251
|
});
|
|
242
252
|
}
|
|
253
|
+
var DocumentOutlineSchema = z.object({
|
|
254
|
+
pages: z.array(z.object({
|
|
255
|
+
pageNumber: z.number(),
|
|
256
|
+
label: z.string().describe("Page title for sidebar"),
|
|
257
|
+
type: z.enum(["cover", "content", "data", "visual", "closing"]),
|
|
258
|
+
layoutHint: z.string().describe("Layout approach: split, full-bleed, grid, editorial, table-heavy, sidebar"),
|
|
259
|
+
contentBrief: z.string().describe("2-4 sentences describing exactly what goes on this page"),
|
|
260
|
+
keyElements: z.array(z.string()).describe("Specific elements: stats grid, table, hero image, timeline, etc."),
|
|
261
|
+
backgroundStyle: z.enum(["white", "primary", "gradient", "surface-alt", "image"]),
|
|
262
|
+
continuesFrom: z.string().optional().describe("How this page relates to the previous one")
|
|
263
|
+
}))
|
|
264
|
+
});
|
|
265
|
+
function buildDirectionInstruction(direction) {
|
|
266
|
+
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`;
|
|
267
|
+
return `
|
|
268
|
+
DESIGN DIRECTION: "${direction.name}" \u2014 ${direction.tagline}
|
|
269
|
+
TYPOGRAPHY: Use these Google Fonts via <link href="${fontsUrl}" rel="stylesheet"> on the first page.
|
|
270
|
+
- Headings: font-family: '${direction.headingFont}', sans-serif (via inline style)
|
|
271
|
+
- Body: font-family: '${direction.bodyFont}', sans-serif (via inline style)
|
|
272
|
+
COLORS \u2014 use ONLY semantic Tailwind classes (the editor injects CSS variables that resolve these):
|
|
273
|
+
- bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary
|
|
274
|
+
- bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted
|
|
275
|
+
- bg-secondary, text-secondary, bg-accent, text-accent
|
|
276
|
+
- NEVER use hardcoded hex colors like bg-[#xxx] or text-[#xxx] \u2014 always use semantic classes
|
|
277
|
+
- The palette is: primary=${direction.colors.primary}, accent=${direction.colors.accent}, surface=${direction.colors.surface}
|
|
278
|
+
Mood: ${direction.mood}
|
|
279
|
+
Layout approach: ${direction.layoutHint}
|
|
280
|
+
IMPORTANT: Apply inline style="font-family: '${direction.headingFont}'" on ALL heading elements and style="font-family: '${direction.bodyFont}'" on ALL body text elements. Include the Google Fonts <link> tag inside the FIRST <section> only.`;
|
|
281
|
+
}
|
|
282
|
+
function extractPartialHtml(buffer) {
|
|
283
|
+
const htmlMatch = buffer.match(/"html"\s*:\s*"([\s\S]*)/);
|
|
284
|
+
if (!htmlMatch) return null;
|
|
285
|
+
let partial = htmlMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
286
|
+
if (partial.endsWith("\\")) partial = partial.slice(0, -1);
|
|
287
|
+
const lastQuote = partial.lastIndexOf('"');
|
|
288
|
+
if (lastQuote > 0) partial = partial.slice(0, lastQuote);
|
|
289
|
+
if (/<section/i.test(partial) && !/<\/section>/i.test(partial)) {
|
|
290
|
+
partial += "</section>";
|
|
291
|
+
}
|
|
292
|
+
return partial.length > 20 ? partial : null;
|
|
293
|
+
}
|
|
294
|
+
async function generateDocumentParallel(options) {
|
|
295
|
+
const {
|
|
296
|
+
anthropicApiKey,
|
|
297
|
+
openaiApiKey: _openaiApiKey,
|
|
298
|
+
prompt,
|
|
299
|
+
logoUrl,
|
|
300
|
+
referenceImage,
|
|
301
|
+
extraInstructions,
|
|
302
|
+
model: pageModelId,
|
|
303
|
+
outlineModel: outlineModelId,
|
|
304
|
+
pexelsApiKey,
|
|
305
|
+
direction,
|
|
306
|
+
persistImage,
|
|
307
|
+
pageCount,
|
|
308
|
+
skipCover,
|
|
309
|
+
onOutline,
|
|
310
|
+
onPageChunk,
|
|
311
|
+
onPageComplete,
|
|
312
|
+
onImageUpdate,
|
|
313
|
+
onDone,
|
|
314
|
+
onError
|
|
315
|
+
} = options;
|
|
316
|
+
const openaiApiKey = _openaiApiKey || process.env.OPENAI_API_KEY;
|
|
317
|
+
try {
|
|
318
|
+
const outlineModel = await resolveModel({
|
|
319
|
+
openaiApiKey,
|
|
320
|
+
anthropicApiKey,
|
|
321
|
+
modelId: outlineModelId,
|
|
322
|
+
defaultOpenai: "gpt-4.1-mini",
|
|
323
|
+
defaultAnthropic: "claude-haiku-4-5-20251001"
|
|
324
|
+
});
|
|
325
|
+
const safePrompt = prompt.length > 15e3 ? prompt.substring(0, 15e3) + "\n[...content truncated...]" : prompt;
|
|
326
|
+
const extra = extraInstructions ? `
|
|
327
|
+
Additional instructions: ${extraInstructions}` : "";
|
|
328
|
+
const pageCountHint = skipCover ? `Generate exactly ${Math.max(1, (pageCount || 5) - 1)} content pages (NO cover \u2014 it already exists).` : pageCount ? `Generate exactly ${pageCount} pages including a cover page.` : "Generate 3-8 pages including a cover page.";
|
|
329
|
+
const { object: outline } = await generateObject({
|
|
330
|
+
model: outlineModel,
|
|
331
|
+
schema: DocumentOutlineSchema,
|
|
332
|
+
prompt: `You are planning a professional document. Create a detailed page-by-page outline.
|
|
333
|
+
|
|
334
|
+
DOCUMENT BRIEF: ${safePrompt}${extra}
|
|
335
|
+
|
|
336
|
+
RULES:
|
|
337
|
+
- ${pageCountHint}
|
|
338
|
+
- First page is ALWAYS a stunning cover/title page (unless skipCover)
|
|
339
|
+
- Distribute content EVENLY \u2014 no page should be overloaded
|
|
340
|
+
- Each page must have a DISTINCT layout (mix split, full-bleed, grid, editorial, sidebar, table-heavy)
|
|
341
|
+
- Narrative flows naturally: cover \u2192 introduction \u2192 detail \u2192 data \u2192 closing
|
|
342
|
+
- contentBrief must be detailed enough that a separate AI can generate the page independently
|
|
343
|
+
- keyElements must list specific visual elements (not vague descriptions)
|
|
344
|
+
- Vary backgroundStyle across pages \u2014 not all white
|
|
345
|
+
- If source content is provided, assign specific portions to each page in the contentBrief
|
|
346
|
+
${direction ? `- Design mood: ${direction.mood}, layout approach: ${direction.layoutHint}` : ""}`
|
|
347
|
+
});
|
|
348
|
+
onOutline?.(outline);
|
|
349
|
+
const directionInstruction = direction ? buildDirectionInstruction(direction) : "";
|
|
350
|
+
const logoInstruction = logoUrl ? `
|
|
351
|
+
LOGO: Include this logo on the cover page and as a small header on other pages:
|
|
352
|
+
<img src="${logoUrl}" alt="Logo" class="h-12 object-contain" />
|
|
353
|
+
Use this exact <img> tag with this exact src URL.` : "";
|
|
354
|
+
const outlineJson = JSON.stringify(outline.pages, null, 2);
|
|
355
|
+
const pageModel = await resolveModel({
|
|
356
|
+
openaiApiKey,
|
|
357
|
+
anthropicApiKey,
|
|
358
|
+
modelId: pageModelId,
|
|
359
|
+
defaultOpenai: "gpt-4o",
|
|
360
|
+
defaultAnthropic: "claude-sonnet-4-6"
|
|
361
|
+
});
|
|
362
|
+
async function generateSinglePage(page, retryCount = 0) {
|
|
363
|
+
const pageIdx = page.pageNumber - 1;
|
|
364
|
+
const isCover = page.type === "cover";
|
|
365
|
+
const userContent = [];
|
|
366
|
+
if (referenceImage && (isCover || page.type === "visual")) {
|
|
367
|
+
const converted = dataUrlToImagePart(referenceImage);
|
|
368
|
+
if (converted) {
|
|
369
|
+
userContent.push({ type: "image", ...converted });
|
|
370
|
+
} else {
|
|
371
|
+
userContent.push({ type: "image", image: referenceImage });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
userContent.push({
|
|
375
|
+
type: "text",
|
|
376
|
+
text: `You are generating PAGE ${page.pageNumber} of ${outline.pages.length} for a professional document.
|
|
377
|
+
|
|
378
|
+
FULL DOCUMENT OUTLINE (for context \u2014 you are generating ONLY page ${page.pageNumber}):
|
|
379
|
+
${outlineJson}
|
|
380
|
+
|
|
381
|
+
YOUR PAGE ASSIGNMENT:
|
|
382
|
+
- Label: ${page.label}
|
|
383
|
+
- Type: ${page.type}
|
|
384
|
+
- Layout: ${page.layoutHint}
|
|
385
|
+
- Background: ${page.backgroundStyle}
|
|
386
|
+
- Content: ${page.contentBrief}
|
|
387
|
+
- Key elements: ${page.keyElements.join(", ")}
|
|
388
|
+
${page.continuesFrom ? `- Continues from: ${page.continuesFrom}` : ""}
|
|
389
|
+
${isCover ? logoInstruction : logoUrl ? `
|
|
390
|
+
Small logo header: <img src="${logoUrl}" alt="Logo" class="h-8 object-contain" />` : ""}
|
|
391
|
+
${directionInstruction}
|
|
392
|
+
|
|
393
|
+
OUTPUT: A single JSON object on ONE line, no markdown fences:
|
|
394
|
+
{"label": "${page.label}", "html": "<section class='w-[8.5in] min-h-[11in] relative overflow-hidden'>...</section>"}`
|
|
395
|
+
});
|
|
396
|
+
try {
|
|
397
|
+
const result = streamText({
|
|
398
|
+
model: pageModel,
|
|
399
|
+
system: DOCUMENT_SYSTEM_PROMPT,
|
|
400
|
+
messages: [{ role: "user", content: userContent }]
|
|
401
|
+
});
|
|
402
|
+
let buffer = "";
|
|
403
|
+
let chunkCount = 0;
|
|
404
|
+
for await (const chunk of result.textStream) {
|
|
405
|
+
buffer += chunk;
|
|
406
|
+
chunkCount++;
|
|
407
|
+
if (chunkCount % 5 === 0) {
|
|
408
|
+
const partial = extractPartialHtml(buffer);
|
|
409
|
+
if (partial) onPageChunk?.(pageIdx, partial);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const finalPartial = extractPartialHtml(buffer);
|
|
413
|
+
if (finalPartial) onPageChunk?.(pageIdx, finalPartial);
|
|
414
|
+
let cleaned = buffer.trim();
|
|
415
|
+
if (cleaned.startsWith("```")) {
|
|
416
|
+
cleaned = cleaned.replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, "");
|
|
417
|
+
}
|
|
418
|
+
const [objects] = extractJsonObjects(cleaned);
|
|
419
|
+
const obj = objects[0];
|
|
420
|
+
if (!obj?.html) throw new Error(`No valid HTML output for page ${page.pageNumber}`);
|
|
421
|
+
const section = {
|
|
422
|
+
id: nanoid(8),
|
|
423
|
+
order: pageIdx,
|
|
424
|
+
html: sanitizeSemanticColors(addSvgLoadingPlaceholders(addLoadingPlaceholders(obj.html))),
|
|
425
|
+
label: obj.label || page.label
|
|
426
|
+
};
|
|
427
|
+
onPageComplete?.(pageIdx, section);
|
|
428
|
+
return section;
|
|
429
|
+
} catch (err) {
|
|
430
|
+
if (retryCount < 1) {
|
|
431
|
+
console.warn(`Page ${page.pageNumber} failed, retrying:`, err.message);
|
|
432
|
+
return generateSinglePage(page, retryCount + 1);
|
|
433
|
+
}
|
|
434
|
+
const section = {
|
|
435
|
+
id: nanoid(8),
|
|
436
|
+
order: pageIdx,
|
|
437
|
+
html: `<section class="w-[8.5in] min-h-[11in] relative overflow-hidden bg-gray-50 flex items-center justify-center"><div class="text-center text-gray-400"><p class="text-lg font-semibold">Error generando p\xE1gina</p><p class="text-sm mt-2">${err.message?.slice(0, 100) || "Error desconocido"}</p></div></section>`,
|
|
438
|
+
label: page.label
|
|
439
|
+
};
|
|
440
|
+
onPageComplete?.(pageIdx, section);
|
|
441
|
+
return section;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const results = await Promise.allSettled(
|
|
445
|
+
outline.pages.map((page) => generateSinglePage(page))
|
|
446
|
+
);
|
|
447
|
+
const sections = results.map((r) => r.status === "fulfilled" ? r.value : null).filter((s) => s !== null).sort((a, b) => a.order - b.order);
|
|
448
|
+
for (const section of sections) {
|
|
449
|
+
await enrichSectionImages(section, {
|
|
450
|
+
pexelsApiKey,
|
|
451
|
+
openaiApiKey,
|
|
452
|
+
persistImage,
|
|
453
|
+
onImageUpdate
|
|
454
|
+
});
|
|
455
|
+
await enrichSectionSvgCharts(section, {
|
|
456
|
+
anthropicApiKey,
|
|
457
|
+
onImageUpdate
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
for (const section of sections) {
|
|
461
|
+
const before = section.html;
|
|
462
|
+
section.html = section.html.replace(
|
|
463
|
+
/<img\s(?![^>]*\bsrc=)([^>]*?)>/gi,
|
|
464
|
+
(_match, attrs) => {
|
|
465
|
+
const altMatch = attrs.match(/alt="([^"]*?)"/);
|
|
466
|
+
const query = altMatch?.[1] || "image";
|
|
467
|
+
return `<img src="https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(query.slice(0, 30))}" ${attrs}>`;
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
if (section.html !== before) {
|
|
471
|
+
onImageUpdate?.(section.id, section.html);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
onDone?.(sections);
|
|
475
|
+
return sections;
|
|
476
|
+
} catch (err) {
|
|
477
|
+
const error = err instanceof Error ? err : new Error(err?.message || "Parallel generation failed");
|
|
478
|
+
onError?.(error);
|
|
479
|
+
throw error;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
243
482
|
|
|
244
483
|
export {
|
|
245
484
|
DOCUMENT_SYSTEM_PROMPT,
|
|
246
485
|
DOCUMENT_PROMPT_SUFFIX,
|
|
247
|
-
generateDocument
|
|
486
|
+
generateDocument,
|
|
487
|
+
generateDocumentParallel
|
|
248
488
|
};
|
|
249
|
-
//# sourceMappingURL=chunk-
|
|
489
|
+
//# sourceMappingURL=chunk-ROHHDJBH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generateDocument.ts"],"sourcesContent":["import { generateObject, streamText } from \"ai\";\nimport { z } from \"zod\";\nimport { nanoid } from \"nanoid\";\nimport {\n streamGenerate,\n dataUrlToImagePart,\n resolveModel,\n extractJsonObjects,\n addLoadingPlaceholders,\n addSvgLoadingPlaceholders,\n enrichSectionImages,\n enrichSectionSvgCharts,\n} from \"./streamCore\";\nimport { sanitizeSemanticColors } from \"./sanitizeColors\";\nimport type { Section3 } from \"./types\";\nimport type { DesignDirection } from \"./directions\";\n\nexport const DOCUMENT_SYSTEM_PROMPT = `You are a professional document designer who creates stunning letter-sized (8.5\" × 11\") document pages using HTML + Tailwind CSS.\n\nRULES:\n- Each page is a <section> element sized for letter paper\n- Page structure: <section class=\"w-[8.5in] h-[11in] relative overflow-hidden\">\n- The section itself has NO padding — backgrounds, gradients, and decorative elements go edge-to-edge\n- For text content, use an inner wrapper: <div class=\"px-[0.75in] py-[0.5in]\">...content...</div>\n- Cover pages and decorative sections can use full-bleed backgrounds (bg-primary, gradients, images that fill the entire page)\n- Content MUST NOT overflow page boundaries — be conservative with spacing\n- Use Tailwind CDN classes ONLY (no custom CSS, no @apply, no @import)\n- NO JavaScript, only HTML+Tailwind\n- All text content in Spanish unless the prompt specifies otherwise\n- Use real content from the source material, not Lorem ipsum\n- NOT responsive — fixed letter size, no breakpoints needed\n- Sections can have ANY background — full-bleed color, gradients, or white. Not limited to white paper.\n\nSTRICT PROHIBITIONS:\n1. **NO EMOJI** — Never use emoji characters (🚀❌✅📊 etc.). Instead use inline SVG icons or colored divs. For bullet decorators use small colored circles (<span class=\"w-2 h-2 rounded-full bg-primary inline-block\"></span>) or simple SVG.\n2. **NO Chart.js / NO JavaScript** — Never reference Chart.js, canvas, or any JS library. For data visualization use pure CSS: progress bars (div with percentage width + bg-primary), horizontal bars, styled tables with colored cells. Never use <canvas> or <script>.\n3. **NO buttons or CTAs** — This is a print document, not a web page. No \"Contactar\", \"Ver más\", \"Comprar\" buttons. Use text with contact info instead.\n4. **CONTRAST IS MANDATORY** — Dark/colored backgrounds (bg-primary, bg-primary-dark, bg-secondary, dark gradients) MUST use text-white or text-on-primary. Light backgrounds (white, bg-surface, bg-surface-alt) MUST use text-gray-900 or text-on-surface. NEVER use dark text on dark backgrounds or light text on light backgrounds.\n5. **Max 2 font weights per page** — Pick 2 (e.g. font-semibold + font-normal, or font-bold + font-light). Don't mix 4-5 weights.\n6. **Generous whitespace** — Don't fill every centimeter. Leave breathing room. Use py-8, py-12, gap-6, gap-8 liberally. Less content per page = more professional.\n\nLAYOUT OVERFLOW PREVENTION — CRITICAL:\n- Max 2 columns side by side — each with w-1/2. NEVER use 3+ columns.\n- Decorative sidebars: max w-16 (4rem). NEVER use w-[2.5in] or wider sidebars — they steal too much space.\n- Stats/metric grids: max 3 items per row (grid-cols-3). Use gap-4 or gap-6.\n- Tables: max 4 columns, use text-xs or text-sm for cell text, px-3 py-2 cell padding.\n- Images: always w-full or max-w-[50%] — never fixed pixel widths.\n- Text: never use text-6xl or larger except for cover page title. Body text: text-sm or text-base.\n- NEVER use absolute positioning that could overflow — prefer flex/grid layouts.\n- Decorative shapes with absolute positioning MUST stay fully inside the page. Use overflow-hidden on parent AND keep coordinates positive (no negative right/left values).\n- Large decorative text (text-[200px], text-[10rem] etc.) MUST have opacity-5 or lower AND overflow-hidden on its container. These giant texts frequently overflow — be extra careful.\n- NEVER place elements beyond the right edge — all content and decorations must fit within 8.5in width.\n\nDESIGN — ADAPT to the document type. Read the prompt carefully and match the visual style:\n\nGENERAL PRINCIPLES (apply to ALL documents):\n- First page is ALWAYS a stunning cover/title page with impactful design\n- Typography: strong hierarchy with just 2 weights, clear headings vs body\n- Each page visually distinct — different layouts, accent placements\n- Use the full page creatively — backgrounds, sidebars, geometric shapes\n- Professional and polished, never generic or template-looking\n- Icons: use simple inline SVG (12-20px) for visual accents. Keep SVGs minimal (single path, no complex illustrations)\n\nADAPT YOUR STYLE to what the user is creating:\n- Reports/Data: structured grids, tables with alternating rows (bg-surface-alt), progress bars, stat cards, clean data hierarchy\n- Brochures/Marketing: bold hero images, large headlines, feature grids, testimonial-style quotes, visual storytelling\n- Catalogs/Products: product cards with images, specs grids, price highlights, category headers with full-bleed color\n- Invitations/Events: centered dramatic typography, decorative borders, elegant spacing, date/location prominently styled\n- Proposals/Pitches: problem→solution flow, metric highlights, team/about sections, pricing tables\n- CVs/Resumes: clean sidebar layouts, skill bars, timeline for experience, contact info header\n- Creative/General: mix techniques — bento grids, full-bleed images, overlapping elements, bold color blocking\n\nVISUAL TECHNIQUES available to you:\n- Full-bleed colored pages (bg-primary, gradients)\n- Geometric accent shapes (CSS divs with clip-path or rotation)\n- Asymmetric layouts (grid with unequal columns)\n- Large stat numbers as visual anchors (text-5xl font-black)\n- Header/footer bands with contrasting color\n- Sidebar accents (thin, max w-16)\n- Image + text compositions\n- Bento-grid mixing content blocks of different sizes\n- Tables: alternating row colors, clean borders, generous cell padding (px-4 py-3)\n- For numerical data: CSS progress bars, styled tables with colored cells, large stat numbers — NEVER canvas/charts\n\nCSS PROGRESS BARS — use this pattern for data visualization:\n<div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-primary h-3 rounded-full\" style=\"width: 75%\"></div></div>\n\nCOLOR SYSTEM — use semantic classes:\n- bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary\n- bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted\n- bg-secondary, text-secondary, bg-accent, text-accent\n- Cover pages should use bold full-bleed backgrounds (bg-primary, gradients from-primary to-primary-dark)\n- CONTRAST: bg-primary/bg-primary-dark/bg-secondary → text-white or text-on-primary. White/bg-surface → text-gray-900 or text-on-surface\n\nIMAGES — USE GENEROUSLY:\n- EVERY image MUST use: <img data-image-query=\"english search query\" alt=\"description\" class=\"w-full h-auto object-cover rounded-xl\"/>\n- NEVER include a src attribute — the system auto-replaces data-image-query with a real image\n- For avatar-like elements, use colored divs with initials instead of img tags\n- Include at LEAST 3-5 images across the document — hero images, section illustrations, backgrounds, product photos\n- Each data-image-query should be a UNIQUE, specific search query in English (e.g. \"modern office workspace aerial view\", \"team brainstorming whiteboard\", \"abstract blue technology network\")\n- Use images to break up text-heavy pages and add visual interest\n\nCHARTS & DATA VISUALIZATION (SVG):\n- For charts, diagrams, and decorative data graphics, use:\n <div data-svg-chart=\"bar chart showing Q1 revenue: Jan $45K, Feb $52K, Mar $61K\" class=\"w-full\"></div>\n- The system generates professional SVG charts with a specialized tool — NEVER draw SVGs yourself\n- Use descriptive prompts with data points: type of chart + what it shows + actual values\n- Examples: \"donut chart: 40% Marketing, 30% Sales, 20% R&D, 10% Admin\", \"line chart showing growth: Q1 $100K, Q2 $150K, Q3 $220K, Q4 $310K\"\n- For simple metrics, still prefer CSS progress bars (they render faster)\n\nTAILWIND v3 NOTES:\n- Standard Tailwind v3 classes (shadow-sm, shadow-md, rounded-md, etc.)\n- Borders: border + border-gray-200 for visible borders\n\nEXAMPLE — Cover page (simple, no wide sidebars):\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-white\">\n <div class=\"absolute left-0 top-0 w-2 h-full bg-primary\"></div>\n <div class=\"flex flex-col justify-center h-[11in] px-[1in]\">\n <div class=\"text-sm font-normal text-primary mb-4\">Marzo 2026 · Versión 1.0</div>\n <h1 class=\"text-5xl font-bold text-gray-900 leading-tight\">Reporte<br/>Trimestral</h1>\n <div class=\"w-16 h-1 bg-primary mt-6 mb-4\"></div>\n <p class=\"text-lg font-normal text-gray-500\">Resultados y análisis del primer trimestre</p>\n </div>\n</section>\n\nEXAMPLE — Marketing/brochure page (bold, visual):\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-primary\">\n <div class=\"flex h-[11in]\">\n <div class=\"w-1/2 flex flex-col justify-center px-[0.75in]\">\n <span class=\"text-sm font-normal text-on-primary opacity-70 uppercase tracking-widest mb-3\">Solución Premium</span>\n <h2 class=\"text-4xl font-bold text-on-primary leading-tight mb-6\">Transforma tu negocio digital</h2>\n <p class=\"text-base font-normal text-on-primary opacity-80 mb-8\">Herramientas inteligentes que simplifican la gestión de tus activos digitales.</p>\n <div class=\"flex gap-6\">\n <div><div class=\"text-3xl font-bold text-accent\">98%</div><div class=\"text-xs text-on-primary opacity-70\">Satisfacción</div></div>\n <div><div class=\"text-3xl font-bold text-accent\">2.4K</div><div class=\"text-xs text-on-primary opacity-70\">Empresas</div></div>\n </div>\n </div>\n <div class=\"w-1/2 relative\">\n <img data-image-query=\"modern office team collaboration technology\" alt=\"Team working\" class=\"absolute inset-0 w-full h-full object-cover\" />\n </div>\n </div>\n</section>\n\nEXAMPLE — Catalog/product grid page:\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-surface\">\n <div class=\"h-1 bg-primary w-full\"></div>\n <div class=\"px-[0.75in] py-[0.5in]\">\n <div class=\"flex justify-between items-baseline mb-6\">\n <h2 class=\"text-2xl font-bold text-on-surface\">Colección Primavera</h2>\n <span class=\"text-xs font-normal text-on-surface-muted uppercase tracking-wider\">Página 3 de 8</span>\n </div>\n <div class=\"grid grid-cols-2 gap-6\">\n <div class=\"bg-surface-alt rounded-xl overflow-hidden\">\n <img data-image-query=\"minimalist product on white background\" alt=\"Product\" class=\"w-full h-48 object-cover\" />\n <div class=\"p-4\"><h3 class=\"font-bold text-on-surface text-sm\">Producto Alpha</h3><p class=\"text-xs text-on-surface-muted mt-1\">Diseño ergonómico premium</p><div class=\"text-lg font-bold text-primary mt-2\">$2,490</div></div>\n </div>\n <div class=\"bg-surface-alt rounded-xl overflow-hidden\">\n <img data-image-query=\"elegant product photography studio\" alt=\"Product\" class=\"w-full h-48 object-cover\" />\n <div class=\"p-4\"><h3 class=\"font-bold text-on-surface text-sm\">Producto Beta</h3><p class=\"text-xs text-on-surface-muted mt-1\">Tecnología de vanguardia</p><div class=\"text-lg font-bold text-primary mt-2\">$3,190</div></div>\n </div>\n </div>\n </div>\n</section>\n\nEXAMPLE — Content page with table + progress bars:\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-white\">\n <div class=\"h-1.5 bg-primary w-full\"></div>\n <div class=\"px-[0.75in] py-[0.5in]\">\n <h2 class=\"text-2xl font-bold text-gray-900 mb-1\">Métricas de Rendimiento</h2>\n <p class=\"text-sm font-normal text-gray-500 mb-8\">Indicadores clave del periodo enero—marzo</p>\n <table class=\"w-full text-sm mb-10\">\n <thead><tr class=\"bg-primary text-white\"><th class=\"px-4 py-3 text-left font-semibold\">Indicador</th><th class=\"px-4 py-3 text-left font-semibold\">Valor</th><th class=\"px-4 py-3 text-left font-semibold\">Meta</th></tr></thead>\n <tbody>\n <tr class=\"bg-surface-alt\"><td class=\"px-4 py-3 text-gray-900\">Ingresos</td><td class=\"px-4 py-3 text-gray-900\">$1.2M</td><td class=\"px-4 py-3 text-gray-900\">$1.5M</td></tr>\n <tr><td class=\"px-4 py-3 text-gray-900\">Clientes nuevos</td><td class=\"px-4 py-3 text-gray-900\">340</td><td class=\"px-4 py-3 text-gray-900\">300</td></tr>\n <tr class=\"bg-surface-alt\"><td class=\"px-4 py-3 text-gray-900\">Retención</td><td class=\"px-4 py-3 text-gray-900\">92%</td><td class=\"px-4 py-3 text-gray-900\">90%</td></tr>\n </tbody>\n </table>\n <h3 class=\"text-lg font-bold text-gray-900 mb-4\">Progreso por Área</h3>\n <div class=\"space-y-4\">\n <div><div class=\"flex justify-between text-sm mb-1\"><span class=\"text-gray-900 font-semibold\">Ventas</span><span class=\"text-gray-500\">80%</span></div><div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-primary h-3 rounded-full\" style=\"width: 80%\"></div></div></div>\n <div><div class=\"flex justify-between text-sm mb-1\"><span class=\"text-gray-900 font-semibold\">Marketing</span><span class=\"text-gray-500\">65%</span></div><div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-secondary h-3 rounded-full\" style=\"width: 65%\"></div></div></div>\n <div><div class=\"flex justify-between text-sm mb-1\"><span class=\"text-gray-900 font-semibold\">Producto</span><span class=\"text-gray-500\">95%</span></div><div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-accent h-3 rounded-full\" style=\"width: 95%\"></div></div></div>\n </div>\n </div>\n</section>`;\n\nexport const DOCUMENT_PROMPT_SUFFIX = `\n\nOUTPUT FORMAT: NDJSON — one JSON object per line, NO wrapper array, NO markdown fences.\nEach line: {\"label\": \"Page Title\", \"html\": \"<section class='w-[8.5in] min-h-[11in] relative overflow-hidden'>...</section>\"}\n\nGenerate 3-8 pages depending on content length. First page = cover/title page.\nEach page must fit within letter size (8.5\" × 11\"). Be conservative with spacing.\nMake each page visually distinct — different layouts, different accent placements.\nIMPORTANT: Adapt your design style to match the type of document — not everything is a report. Brochures should feel bold and visual, catalogs should showcase products, invitations should feel elegant, etc.`;\n\nexport interface GenerateDocumentOptions {\n anthropicApiKey?: string;\n openaiApiKey?: string;\n prompt: string;\n logoUrl?: string;\n referenceImage?: string;\n extraInstructions?: string;\n model?: string | import(\"ai\").LanguageModel;\n pexelsApiKey?: string;\n /** Design direction — injects Google Fonts + hex colors into the prompt */\n direction?: DesignDirection;\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n onSection?: (section: Section3) => void;\n onImageUpdate?: (sectionId: string, html: string) => void;\n onRawChunk?: (buffer: string, completedCount: number) => void;\n onDone?: (sections: Section3[]) => void;\n onError?: (error: Error) => void;\n}\n\n/**\n * Generate a multi-page document with streaming AI + image enrichment.\n */\nexport async function generateDocument(options: GenerateDocumentOptions): Promise<Section3[]> {\n const {\n prompt,\n logoUrl,\n referenceImage,\n extraInstructions,\n direction,\n ...rest\n } = options;\n\n const extra = extraInstructions ? `\\nAdditional instructions: ${extraInstructions}` : \"\";\n\n // Build direction style instructions if provided\n let directionInstruction = \"\";\n if (direction) {\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 directionInstruction = `\nDESIGN DIRECTION: \"${direction.name}\" — ${direction.tagline}\nTYPOGRAPHY: Use these Google Fonts via <link href=\"${fontsUrl}\" rel=\"stylesheet\"> on the first page.\n- Headings: font-family: '${direction.headingFont}', sans-serif (via inline style)\n- Body: font-family: '${direction.bodyFont}', sans-serif (via inline style)\nCOLORS — use ONLY semantic Tailwind classes (the editor injects CSS variables that resolve these):\n- bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary\n- bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted\n- bg-secondary, text-secondary, bg-accent, text-accent\n- NEVER use hardcoded hex colors like bg-[#xxx] or text-[#xxx] — always use semantic classes\n- The palette is: primary=${direction.colors.primary}, accent=${direction.colors.accent}, surface=${direction.colors.surface}\nMood: ${direction.mood}\nLayout approach: ${direction.layoutHint}\nIMPORTANT: Apply inline style=\"font-family: '${direction.headingFont}'\" on ALL heading elements and style=\"font-family: '${direction.bodyFont}'\" on ALL body text elements. Include the Google Fonts <link> tag inside the FIRST <section> only.`;\n }\n // Truncate prompt to prevent token overflow (max ~15K chars ≈ 5K tokens)\n const safePrompt = prompt.length > 15_000 ? prompt.substring(0, 15_000) + \"\\n[...content truncated...]\" : prompt;\n const logoInstruction = logoUrl\n ? `\\nLOGO: Include this logo on the cover page and as a small header on other pages:\\n<img src=\"${logoUrl}\" alt=\"Logo\" class=\"h-12 object-contain\" />\\nUse this exact <img> tag with this exact src URL — do NOT invent a different URL or modify it.`\n : \"\";\n\n const content: any[] = [];\n\n if (referenceImage) {\n const converted = dataUrlToImagePart(referenceImage);\n if (converted) {\n content.push({ type: \"image\", ...converted });\n } else {\n content.push({ type: \"image\", image: referenceImage });\n }\n content.push({\n type: \"text\",\n text: `Create a professional document inspired by this reference image for: ${safePrompt}${logoInstruction}${directionInstruction}${extra}${DOCUMENT_PROMPT_SUFFIX}`,\n });\n } else {\n content.push({\n type: \"text\",\n text: `Create a professional document for: ${safePrompt}${logoInstruction}${directionInstruction}${extra}${DOCUMENT_PROMPT_SUFFIX}`,\n });\n }\n\n return streamGenerate({\n ...rest,\n systemPrompt: DOCUMENT_SYSTEM_PROMPT,\n userContent: content,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Parallel Document Generation\n// ---------------------------------------------------------------------------\n\nconst DocumentOutlineSchema = z.object({\n pages: z.array(z.object({\n pageNumber: z.number(),\n label: z.string().describe(\"Page title for sidebar\"),\n type: z.enum([\"cover\", \"content\", \"data\", \"visual\", \"closing\"]),\n layoutHint: z.string().describe(\"Layout approach: split, full-bleed, grid, editorial, table-heavy, sidebar\"),\n contentBrief: z.string().describe(\"2-4 sentences describing exactly what goes on this page\"),\n keyElements: z.array(z.string()).describe(\"Specific elements: stats grid, table, hero image, timeline, etc.\"),\n backgroundStyle: z.enum([\"white\", \"primary\", \"gradient\", \"surface-alt\", \"image\"]),\n continuesFrom: z.string().optional().describe(\"How this page relates to the previous one\"),\n })),\n});\n\nexport type DocumentOutline = z.infer<typeof DocumentOutlineSchema>;\n\nexport interface GenerateDocumentParallelOptions {\n anthropicApiKey?: string;\n openaiApiKey?: string;\n prompt: string;\n logoUrl?: string;\n referenceImage?: string;\n extraInstructions?: string;\n /** Model for page generation (quality model) */\n model?: string | import(\"ai\").LanguageModel;\n /** Model for outline generation (fast model) */\n outlineModel?: string | import(\"ai\").LanguageModel;\n pexelsApiKey?: string;\n direction?: DesignDirection;\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n pageCount?: number;\n skipCover?: boolean;\n onOutline?: (outline: DocumentOutline) => void;\n onPageChunk?: (pageIndex: number, partialHtml: string) => void;\n onPageComplete?: (pageIndex: number, section: Section3) => void;\n onImageUpdate?: (sectionId: string, html: string) => void;\n onDone?: (sections: Section3[]) => void;\n onError?: (error: Error) => void;\n}\n\nfunction buildDirectionInstruction(direction: DesignDirection): string {\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 return `\nDESIGN DIRECTION: \"${direction.name}\" — ${direction.tagline}\nTYPOGRAPHY: Use these Google Fonts via <link href=\"${fontsUrl}\" rel=\"stylesheet\"> on the first page.\n- Headings: font-family: '${direction.headingFont}', sans-serif (via inline style)\n- Body: font-family: '${direction.bodyFont}', sans-serif (via inline style)\nCOLORS — use ONLY semantic Tailwind classes (the editor injects CSS variables that resolve these):\n- bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary\n- bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted\n- bg-secondary, text-secondary, bg-accent, text-accent\n- NEVER use hardcoded hex colors like bg-[#xxx] or text-[#xxx] — always use semantic classes\n- The palette is: primary=${direction.colors.primary}, accent=${direction.colors.accent}, surface=${direction.colors.surface}\nMood: ${direction.mood}\nLayout approach: ${direction.layoutHint}\nIMPORTANT: Apply inline style=\"font-family: '${direction.headingFont}'\" on ALL heading elements and style=\"font-family: '${direction.bodyFont}'\" on ALL body text elements. Include the Google Fonts <link> tag inside the FIRST <section> only.`;\n}\n\n/** Extract partial HTML from a raw JSON buffer (same pattern as onRawChunk) */\nfunction extractPartialHtml(buffer: string): string | null {\n const htmlMatch = buffer.match(/\"html\"\\s*:\\s*\"([\\s\\S]*)/);\n if (!htmlMatch) return null;\n let partial = htmlMatch[1]\n .replace(/\\\\n/g, '\\n').replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, '\\\\');\n if (partial.endsWith('\\\\')) partial = partial.slice(0, -1);\n const lastQuote = partial.lastIndexOf('\"');\n if (lastQuote > 0) partial = partial.slice(0, lastQuote);\n if (/<section/i.test(partial) && !/<\\/section>/i.test(partial)) {\n partial += '</section>';\n }\n return partial.length > 20 ? partial : null;\n}\n\nexport async function generateDocumentParallel(options: GenerateDocumentParallelOptions): Promise<Section3[]> {\n const {\n anthropicApiKey,\n openaiApiKey: _openaiApiKey,\n prompt,\n logoUrl,\n referenceImage,\n extraInstructions,\n model: pageModelId,\n outlineModel: outlineModelId,\n pexelsApiKey,\n direction,\n persistImage,\n pageCount,\n skipCover,\n onOutline,\n onPageChunk,\n onPageComplete,\n onImageUpdate,\n onDone,\n onError,\n } = options;\n\n const openaiApiKey = _openaiApiKey || process.env.OPENAI_API_KEY;\n\n try {\n // --- Phase 1: Generate outline ---\n const outlineModel = await resolveModel({\n openaiApiKey,\n anthropicApiKey,\n modelId: outlineModelId,\n defaultOpenai: \"gpt-4.1-mini\",\n defaultAnthropic: \"claude-haiku-4-5-20251001\",\n });\n\n const safePrompt = prompt.length > 15_000 ? prompt.substring(0, 15_000) + \"\\n[...content truncated...]\" : prompt;\n const extra = extraInstructions ? `\\nAdditional instructions: ${extraInstructions}` : \"\";\n const pageCountHint = skipCover\n ? `Generate exactly ${Math.max(1, (pageCount || 5) - 1)} content pages (NO cover — it already exists).`\n : pageCount\n ? `Generate exactly ${pageCount} pages including a cover page.`\n : \"Generate 3-8 pages including a cover page.\";\n\n const { object: outline } = await generateObject({\n model: outlineModel,\n schema: DocumentOutlineSchema,\n prompt: `You are planning a professional document. Create a detailed page-by-page outline.\n\nDOCUMENT BRIEF: ${safePrompt}${extra}\n\nRULES:\n- ${pageCountHint}\n- First page is ALWAYS a stunning cover/title page (unless skipCover)\n- Distribute content EVENLY — no page should be overloaded\n- Each page must have a DISTINCT layout (mix split, full-bleed, grid, editorial, sidebar, table-heavy)\n- Narrative flows naturally: cover → introduction → detail → data → closing\n- contentBrief must be detailed enough that a separate AI can generate the page independently\n- keyElements must list specific visual elements (not vague descriptions)\n- Vary backgroundStyle across pages — not all white\n- If source content is provided, assign specific portions to each page in the contentBrief\n${direction ? `- Design mood: ${direction.mood}, layout approach: ${direction.layoutHint}` : \"\"}`,\n });\n\n onOutline?.(outline);\n\n // --- Phase 2: Generate pages in parallel ---\n const directionInstruction = direction ? buildDirectionInstruction(direction) : \"\";\n const logoInstruction = logoUrl\n ? `\\nLOGO: Include this logo on the cover page and as a small header on other pages:\\n<img src=\"${logoUrl}\" alt=\"Logo\" class=\"h-12 object-contain\" />\\nUse this exact <img> tag with this exact src URL.`\n : \"\";\n\n const outlineJson = JSON.stringify(outline.pages, null, 2);\n\n const pageModel = await resolveModel({\n openaiApiKey,\n anthropicApiKey,\n modelId: pageModelId,\n defaultOpenai: \"gpt-4o\",\n defaultAnthropic: \"claude-sonnet-4-6\",\n });\n\n async function generateSinglePage(\n page: DocumentOutline[\"pages\"][number],\n retryCount = 0\n ): Promise<Section3> {\n const pageIdx = page.pageNumber - 1;\n const isCover = page.type === \"cover\";\n\n const userContent: any[] = [];\n\n // Reference image only for cover/visual pages\n if (referenceImage && (isCover || page.type === \"visual\")) {\n const converted = dataUrlToImagePart(referenceImage);\n if (converted) {\n userContent.push({ type: \"image\", ...converted });\n } else {\n userContent.push({ type: \"image\", image: referenceImage });\n }\n }\n\n userContent.push({\n type: \"text\",\n text: `You are generating PAGE ${page.pageNumber} of ${outline.pages.length} for a professional document.\n\nFULL DOCUMENT OUTLINE (for context — you are generating ONLY page ${page.pageNumber}):\n${outlineJson}\n\nYOUR PAGE ASSIGNMENT:\n- Label: ${page.label}\n- Type: ${page.type}\n- Layout: ${page.layoutHint}\n- Background: ${page.backgroundStyle}\n- Content: ${page.contentBrief}\n- Key elements: ${page.keyElements.join(\", \")}\n${page.continuesFrom ? `- Continues from: ${page.continuesFrom}` : \"\"}\n${isCover ? logoInstruction : logoUrl ? `\\nSmall logo header: <img src=\"${logoUrl}\" alt=\"Logo\" class=\"h-8 object-contain\" />` : \"\"}\n${directionInstruction}\n\nOUTPUT: A single JSON object on ONE line, no markdown fences:\n{\"label\": \"${page.label}\", \"html\": \"<section class='w-[8.5in] min-h-[11in] relative overflow-hidden'>...</section>\"}`,\n });\n\n try {\n const result = streamText({\n model: pageModel,\n system: DOCUMENT_SYSTEM_PROMPT,\n messages: [{ role: \"user\", content: userContent }],\n });\n\n let buffer = \"\";\n let chunkCount = 0;\n for await (const chunk of result.textStream) {\n buffer += chunk;\n chunkCount++;\n if (chunkCount % 5 === 0) {\n const partial = extractPartialHtml(buffer);\n if (partial) onPageChunk?.(pageIdx, partial);\n }\n }\n\n // Final partial before parse\n const finalPartial = extractPartialHtml(buffer);\n if (finalPartial) onPageChunk?.(pageIdx, finalPartial);\n\n // Parse the JSON object\n let cleaned = buffer.trim();\n if (cleaned.startsWith(\"```\")) {\n cleaned = cleaned.replace(/^```(?:json)?\\s*/, \"\").replace(/\\s*```$/, \"\");\n }\n const [objects] = extractJsonObjects(cleaned);\n const obj = objects[0];\n if (!obj?.html) throw new Error(`No valid HTML output for page ${page.pageNumber}`);\n\n const section: Section3 = {\n id: nanoid(8),\n order: pageIdx,\n html: sanitizeSemanticColors(addSvgLoadingPlaceholders(addLoadingPlaceholders(obj.html))),\n label: obj.label || page.label,\n };\n\n onPageComplete?.(pageIdx, section);\n return section;\n } catch (err) {\n if (retryCount < 1) {\n console.warn(`Page ${page.pageNumber} failed, retrying:`, (err as Error).message);\n return generateSinglePage(page, retryCount + 1);\n }\n // Return error placeholder\n const section: Section3 = {\n id: nanoid(8),\n order: pageIdx,\n html: `<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-gray-50 flex items-center justify-center\"><div class=\"text-center text-gray-400\"><p class=\"text-lg font-semibold\">Error generando página</p><p class=\"text-sm mt-2\">${(err as Error).message?.slice(0, 100) || \"Error desconocido\"}</p></div></section>`,\n label: page.label,\n };\n onPageComplete?.(pageIdx, section);\n return section;\n }\n }\n\n const results = await Promise.allSettled(\n outline.pages.map((page) => generateSinglePage(page))\n );\n\n const sections: Section3[] = results\n .map((r) => r.status === \"fulfilled\" ? r.value : null)\n .filter((s): s is Section3 => s !== null)\n .sort((a, b) => a.order - b.order);\n\n // --- Phase 3: Image enrichment (sequential to respect Pexels rate limits) ---\n for (const section of sections) {\n await enrichSectionImages(section, {\n pexelsApiKey,\n openaiApiKey,\n persistImage,\n onImageUpdate,\n });\n await enrichSectionSvgCharts(section, {\n anthropicApiKey,\n onImageUpdate,\n });\n }\n\n // Final fallback for images without src\n for (const section of sections) {\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 if (section.html !== before) {\n onImageUpdate?.(section.id, section.html);\n }\n }\n\n onDone?.(sections);\n return sections;\n } catch (err: any) {\n const error = err instanceof Error ? err : new Error(err?.message || \"Parallel generation failed\");\n onError?.(error);\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dataUrlToImagePart,
|
|
3
3
|
streamGenerate
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-AJGAO7UP.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-
|
|
133
|
+
//# sourceMappingURL=chunk-T6UZ3FVJ.js.map
|
package/dist/directions.js
CHANGED
package/dist/generate.js
CHANGED
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
PROMPT_SUFFIX,
|
|
3
3
|
SYSTEM_PROMPT,
|
|
4
4
|
generateLanding
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-T6UZ3FVJ.js";
|
|
6
6
|
import {
|
|
7
7
|
extractJsonObjects
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-AJGAO7UP.js";
|
|
9
9
|
import "./chunk-G5QRFGPZ.js";
|
|
10
10
|
export {
|
|
11
11
|
PROMPT_SUFFIX,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as ai from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { S as Section3 } from './types-BIpbpCJr.js';
|
|
3
4
|
import { DesignDirection } from './directions.js';
|
|
4
|
-
import 'zod';
|
|
5
5
|
|
|
6
6
|
declare const DOCUMENT_SYSTEM_PROMPT = "You are a professional document designer who creates stunning letter-sized (8.5\" \u00D7 11\") document pages using HTML + Tailwind CSS.\n\nRULES:\n- Each page is a <section> element sized for letter paper\n- Page structure: <section class=\"w-[8.5in] h-[11in] relative overflow-hidden\">\n- The section itself has NO padding \u2014 backgrounds, gradients, and decorative elements go edge-to-edge\n- For text content, use an inner wrapper: <div class=\"px-[0.75in] py-[0.5in]\">...content...</div>\n- Cover pages and decorative sections can use full-bleed backgrounds (bg-primary, gradients, images that fill the entire page)\n- Content MUST NOT overflow page boundaries \u2014 be conservative with spacing\n- Use Tailwind CDN classes ONLY (no custom CSS, no @apply, no @import)\n- NO JavaScript, only HTML+Tailwind\n- All text content in Spanish unless the prompt specifies otherwise\n- Use real content from the source material, not Lorem ipsum\n- NOT responsive \u2014 fixed letter size, no breakpoints needed\n- Sections can have ANY background \u2014 full-bleed color, gradients, or white. Not limited to white paper.\n\nSTRICT PROHIBITIONS:\n1. **NO EMOJI** \u2014 Never use emoji characters (\uD83D\uDE80\u274C\u2705\uD83D\uDCCA etc.). Instead use inline SVG icons or colored divs. For bullet decorators use small colored circles (<span class=\"w-2 h-2 rounded-full bg-primary inline-block\"></span>) or simple SVG.\n2. **NO Chart.js / NO JavaScript** \u2014 Never reference Chart.js, canvas, or any JS library. For data visualization use pure CSS: progress bars (div with percentage width + bg-primary), horizontal bars, styled tables with colored cells. Never use <canvas> or <script>.\n3. **NO buttons or CTAs** \u2014 This is a print document, not a web page. No \"Contactar\", \"Ver m\u00E1s\", \"Comprar\" buttons. Use text with contact info instead.\n4. **CONTRAST IS MANDATORY** \u2014 Dark/colored backgrounds (bg-primary, bg-primary-dark, bg-secondary, dark gradients) MUST use text-white or text-on-primary. Light backgrounds (white, bg-surface, bg-surface-alt) MUST use text-gray-900 or text-on-surface. NEVER use dark text on dark backgrounds or light text on light backgrounds.\n5. **Max 2 font weights per page** \u2014 Pick 2 (e.g. font-semibold + font-normal, or font-bold + font-light). Don't mix 4-5 weights.\n6. **Generous whitespace** \u2014 Don't fill every centimeter. Leave breathing room. Use py-8, py-12, gap-6, gap-8 liberally. Less content per page = more professional.\n\nLAYOUT OVERFLOW PREVENTION \u2014 CRITICAL:\n- Max 2 columns side by side \u2014 each with w-1/2. NEVER use 3+ columns.\n- Decorative sidebars: max w-16 (4rem). NEVER use w-[2.5in] or wider sidebars \u2014 they steal too much space.\n- Stats/metric grids: max 3 items per row (grid-cols-3). Use gap-4 or gap-6.\n- Tables: max 4 columns, use text-xs or text-sm for cell text, px-3 py-2 cell padding.\n- Images: always w-full or max-w-[50%] \u2014 never fixed pixel widths.\n- Text: never use text-6xl or larger except for cover page title. Body text: text-sm or text-base.\n- NEVER use absolute positioning that could overflow \u2014 prefer flex/grid layouts.\n- Decorative shapes with absolute positioning MUST stay fully inside the page. Use overflow-hidden on parent AND keep coordinates positive (no negative right/left values).\n- Large decorative text (text-[200px], text-[10rem] etc.) MUST have opacity-5 or lower AND overflow-hidden on its container. These giant texts frequently overflow \u2014 be extra careful.\n- NEVER place elements beyond the right edge \u2014 all content and decorations must fit within 8.5in width.\n\nDESIGN \u2014 ADAPT to the document type. Read the prompt carefully and match the visual style:\n\nGENERAL PRINCIPLES (apply to ALL documents):\n- First page is ALWAYS a stunning cover/title page with impactful design\n- Typography: strong hierarchy with just 2 weights, clear headings vs body\n- Each page visually distinct \u2014 different layouts, accent placements\n- Use the full page creatively \u2014 backgrounds, sidebars, geometric shapes\n- Professional and polished, never generic or template-looking\n- Icons: use simple inline SVG (12-20px) for visual accents. Keep SVGs minimal (single path, no complex illustrations)\n\nADAPT YOUR STYLE to what the user is creating:\n- Reports/Data: structured grids, tables with alternating rows (bg-surface-alt), progress bars, stat cards, clean data hierarchy\n- Brochures/Marketing: bold hero images, large headlines, feature grids, testimonial-style quotes, visual storytelling\n- Catalogs/Products: product cards with images, specs grids, price highlights, category headers with full-bleed color\n- Invitations/Events: centered dramatic typography, decorative borders, elegant spacing, date/location prominently styled\n- Proposals/Pitches: problem\u2192solution flow, metric highlights, team/about sections, pricing tables\n- CVs/Resumes: clean sidebar layouts, skill bars, timeline for experience, contact info header\n- Creative/General: mix techniques \u2014 bento grids, full-bleed images, overlapping elements, bold color blocking\n\nVISUAL TECHNIQUES available to you:\n- Full-bleed colored pages (bg-primary, gradients)\n- Geometric accent shapes (CSS divs with clip-path or rotation)\n- Asymmetric layouts (grid with unequal columns)\n- Large stat numbers as visual anchors (text-5xl font-black)\n- Header/footer bands with contrasting color\n- Sidebar accents (thin, max w-16)\n- Image + text compositions\n- Bento-grid mixing content blocks of different sizes\n- Tables: alternating row colors, clean borders, generous cell padding (px-4 py-3)\n- For numerical data: CSS progress bars, styled tables with colored cells, large stat numbers \u2014 NEVER canvas/charts\n\nCSS PROGRESS BARS \u2014 use this pattern for data visualization:\n<div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-primary h-3 rounded-full\" style=\"width: 75%\"></div></div>\n\nCOLOR SYSTEM \u2014 use semantic classes:\n- bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary\n- bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted\n- bg-secondary, text-secondary, bg-accent, text-accent\n- Cover pages should use bold full-bleed backgrounds (bg-primary, gradients from-primary to-primary-dark)\n- CONTRAST: bg-primary/bg-primary-dark/bg-secondary \u2192 text-white or text-on-primary. White/bg-surface \u2192 text-gray-900 or text-on-surface\n\nIMAGES \u2014 USE GENEROUSLY:\n- EVERY image MUST use: <img data-image-query=\"english search query\" alt=\"description\" class=\"w-full h-auto object-cover rounded-xl\"/>\n- NEVER include a src attribute \u2014 the system auto-replaces data-image-query with a real image\n- For avatar-like elements, use colored divs with initials instead of img tags\n- Include at LEAST 3-5 images across the document \u2014 hero images, section illustrations, backgrounds, product photos\n- Each data-image-query should be a UNIQUE, specific search query in English (e.g. \"modern office workspace aerial view\", \"team brainstorming whiteboard\", \"abstract blue technology network\")\n- Use images to break up text-heavy pages and add visual interest\n\nCHARTS & DATA VISUALIZATION (SVG):\n- For charts, diagrams, and decorative data graphics, use:\n <div data-svg-chart=\"bar chart showing Q1 revenue: Jan $45K, Feb $52K, Mar $61K\" class=\"w-full\"></div>\n- The system generates professional SVG charts with a specialized tool \u2014 NEVER draw SVGs yourself\n- Use descriptive prompts with data points: type of chart + what it shows + actual values\n- Examples: \"donut chart: 40% Marketing, 30% Sales, 20% R&D, 10% Admin\", \"line chart showing growth: Q1 $100K, Q2 $150K, Q3 $220K, Q4 $310K\"\n- For simple metrics, still prefer CSS progress bars (they render faster)\n\nTAILWIND v3 NOTES:\n- Standard Tailwind v3 classes (shadow-sm, shadow-md, rounded-md, etc.)\n- Borders: border + border-gray-200 for visible borders\n\nEXAMPLE \u2014 Cover page (simple, no wide sidebars):\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-white\">\n <div class=\"absolute left-0 top-0 w-2 h-full bg-primary\"></div>\n <div class=\"flex flex-col justify-center h-[11in] px-[1in]\">\n <div class=\"text-sm font-normal text-primary mb-4\">Marzo 2026 \u00B7 Versi\u00F3n 1.0</div>\n <h1 class=\"text-5xl font-bold text-gray-900 leading-tight\">Reporte<br/>Trimestral</h1>\n <div class=\"w-16 h-1 bg-primary mt-6 mb-4\"></div>\n <p class=\"text-lg font-normal text-gray-500\">Resultados y an\u00E1lisis del primer trimestre</p>\n </div>\n</section>\n\nEXAMPLE \u2014 Marketing/brochure page (bold, visual):\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-primary\">\n <div class=\"flex h-[11in]\">\n <div class=\"w-1/2 flex flex-col justify-center px-[0.75in]\">\n <span class=\"text-sm font-normal text-on-primary opacity-70 uppercase tracking-widest mb-3\">Soluci\u00F3n Premium</span>\n <h2 class=\"text-4xl font-bold text-on-primary leading-tight mb-6\">Transforma tu negocio digital</h2>\n <p class=\"text-base font-normal text-on-primary opacity-80 mb-8\">Herramientas inteligentes que simplifican la gesti\u00F3n de tus activos digitales.</p>\n <div class=\"flex gap-6\">\n <div><div class=\"text-3xl font-bold text-accent\">98%</div><div class=\"text-xs text-on-primary opacity-70\">Satisfacci\u00F3n</div></div>\n <div><div class=\"text-3xl font-bold text-accent\">2.4K</div><div class=\"text-xs text-on-primary opacity-70\">Empresas</div></div>\n </div>\n </div>\n <div class=\"w-1/2 relative\">\n <img data-image-query=\"modern office team collaboration technology\" alt=\"Team working\" class=\"absolute inset-0 w-full h-full object-cover\" />\n </div>\n </div>\n</section>\n\nEXAMPLE \u2014 Catalog/product grid page:\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-surface\">\n <div class=\"h-1 bg-primary w-full\"></div>\n <div class=\"px-[0.75in] py-[0.5in]\">\n <div class=\"flex justify-between items-baseline mb-6\">\n <h2 class=\"text-2xl font-bold text-on-surface\">Colecci\u00F3n Primavera</h2>\n <span class=\"text-xs font-normal text-on-surface-muted uppercase tracking-wider\">P\u00E1gina 3 de 8</span>\n </div>\n <div class=\"grid grid-cols-2 gap-6\">\n <div class=\"bg-surface-alt rounded-xl overflow-hidden\">\n <img data-image-query=\"minimalist product on white background\" alt=\"Product\" class=\"w-full h-48 object-cover\" />\n <div class=\"p-4\"><h3 class=\"font-bold text-on-surface text-sm\">Producto Alpha</h3><p class=\"text-xs text-on-surface-muted mt-1\">Dise\u00F1o ergon\u00F3mico premium</p><div class=\"text-lg font-bold text-primary mt-2\">$2,490</div></div>\n </div>\n <div class=\"bg-surface-alt rounded-xl overflow-hidden\">\n <img data-image-query=\"elegant product photography studio\" alt=\"Product\" class=\"w-full h-48 object-cover\" />\n <div class=\"p-4\"><h3 class=\"font-bold text-on-surface text-sm\">Producto Beta</h3><p class=\"text-xs text-on-surface-muted mt-1\">Tecnolog\u00EDa de vanguardia</p><div class=\"text-lg font-bold text-primary mt-2\">$3,190</div></div>\n </div>\n </div>\n </div>\n</section>\n\nEXAMPLE \u2014 Content page with table + progress bars:\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-white\">\n <div class=\"h-1.5 bg-primary w-full\"></div>\n <div class=\"px-[0.75in] py-[0.5in]\">\n <h2 class=\"text-2xl font-bold text-gray-900 mb-1\">M\u00E9tricas de Rendimiento</h2>\n <p class=\"text-sm font-normal text-gray-500 mb-8\">Indicadores clave del periodo enero\u2014marzo</p>\n <table class=\"w-full text-sm mb-10\">\n <thead><tr class=\"bg-primary text-white\"><th class=\"px-4 py-3 text-left font-semibold\">Indicador</th><th class=\"px-4 py-3 text-left font-semibold\">Valor</th><th class=\"px-4 py-3 text-left font-semibold\">Meta</th></tr></thead>\n <tbody>\n <tr class=\"bg-surface-alt\"><td class=\"px-4 py-3 text-gray-900\">Ingresos</td><td class=\"px-4 py-3 text-gray-900\">$1.2M</td><td class=\"px-4 py-3 text-gray-900\">$1.5M</td></tr>\n <tr><td class=\"px-4 py-3 text-gray-900\">Clientes nuevos</td><td class=\"px-4 py-3 text-gray-900\">340</td><td class=\"px-4 py-3 text-gray-900\">300</td></tr>\n <tr class=\"bg-surface-alt\"><td class=\"px-4 py-3 text-gray-900\">Retenci\u00F3n</td><td class=\"px-4 py-3 text-gray-900\">92%</td><td class=\"px-4 py-3 text-gray-900\">90%</td></tr>\n </tbody>\n </table>\n <h3 class=\"text-lg font-bold text-gray-900 mb-4\">Progreso por \u00C1rea</h3>\n <div class=\"space-y-4\">\n <div><div class=\"flex justify-between text-sm mb-1\"><span class=\"text-gray-900 font-semibold\">Ventas</span><span class=\"text-gray-500\">80%</span></div><div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-primary h-3 rounded-full\" style=\"width: 80%\"></div></div></div>\n <div><div class=\"flex justify-between text-sm mb-1\"><span class=\"text-gray-900 font-semibold\">Marketing</span><span class=\"text-gray-500\">65%</span></div><div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-secondary h-3 rounded-full\" style=\"width: 65%\"></div></div></div>\n <div><div class=\"flex justify-between text-sm mb-1\"><span class=\"text-gray-900 font-semibold\">Producto</span><span class=\"text-gray-500\">95%</span></div><div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-accent h-3 rounded-full\" style=\"width: 95%\"></div></div></div>\n </div>\n </div>\n</section>";
|
|
7
7
|
declare const DOCUMENT_PROMPT_SUFFIX = "\n\nOUTPUT FORMAT: NDJSON \u2014 one JSON object per line, NO wrapper array, NO markdown fences.\nEach line: {\"label\": \"Page Title\", \"html\": \"<section class='w-[8.5in] min-h-[11in] relative overflow-hidden'>...</section>\"}\n\nGenerate 3-8 pages depending on content length. First page = cover/title page.\nEach page must fit within letter size (8.5\" \u00D7 11\"). Be conservative with spacing.\nMake each page visually distinct \u2014 different layouts, different accent placements.\nIMPORTANT: Adapt your design style to match the type of document \u2014 not everything is a report. Brochures should feel bold and visual, catalogs should showcase products, invitations should feel elegant, etc.";
|
|
@@ -27,5 +27,54 @@ interface GenerateDocumentOptions {
|
|
|
27
27
|
* Generate a multi-page document with streaming AI + image enrichment.
|
|
28
28
|
*/
|
|
29
29
|
declare function generateDocument(options: GenerateDocumentOptions): Promise<Section3[]>;
|
|
30
|
+
declare const DocumentOutlineSchema: z.ZodObject<{
|
|
31
|
+
pages: z.ZodArray<z.ZodObject<{
|
|
32
|
+
pageNumber: z.ZodNumber;
|
|
33
|
+
label: z.ZodString;
|
|
34
|
+
type: z.ZodEnum<{
|
|
35
|
+
cover: "cover";
|
|
36
|
+
content: "content";
|
|
37
|
+
data: "data";
|
|
38
|
+
visual: "visual";
|
|
39
|
+
closing: "closing";
|
|
40
|
+
}>;
|
|
41
|
+
layoutHint: z.ZodString;
|
|
42
|
+
contentBrief: z.ZodString;
|
|
43
|
+
keyElements: z.ZodArray<z.ZodString>;
|
|
44
|
+
backgroundStyle: z.ZodEnum<{
|
|
45
|
+
primary: "primary";
|
|
46
|
+
"surface-alt": "surface-alt";
|
|
47
|
+
image: "image";
|
|
48
|
+
white: "white";
|
|
49
|
+
gradient: "gradient";
|
|
50
|
+
}>;
|
|
51
|
+
continuesFrom: z.ZodOptional<z.ZodString>;
|
|
52
|
+
}, z.core.$strip>>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
type DocumentOutline = z.infer<typeof DocumentOutlineSchema>;
|
|
55
|
+
interface GenerateDocumentParallelOptions {
|
|
56
|
+
anthropicApiKey?: string;
|
|
57
|
+
openaiApiKey?: string;
|
|
58
|
+
prompt: string;
|
|
59
|
+
logoUrl?: string;
|
|
60
|
+
referenceImage?: string;
|
|
61
|
+
extraInstructions?: string;
|
|
62
|
+
/** Model for page generation (quality model) */
|
|
63
|
+
model?: string | ai.LanguageModel;
|
|
64
|
+
/** Model for outline generation (fast model) */
|
|
65
|
+
outlineModel?: string | ai.LanguageModel;
|
|
66
|
+
pexelsApiKey?: string;
|
|
67
|
+
direction?: DesignDirection;
|
|
68
|
+
persistImage?: (tempUrl: string, query: string) => Promise<string>;
|
|
69
|
+
pageCount?: number;
|
|
70
|
+
skipCover?: boolean;
|
|
71
|
+
onOutline?: (outline: DocumentOutline) => void;
|
|
72
|
+
onPageChunk?: (pageIndex: number, partialHtml: string) => void;
|
|
73
|
+
onPageComplete?: (pageIndex: number, section: Section3) => void;
|
|
74
|
+
onImageUpdate?: (sectionId: string, html: string) => void;
|
|
75
|
+
onDone?: (sections: Section3[]) => void;
|
|
76
|
+
onError?: (error: Error) => void;
|
|
77
|
+
}
|
|
78
|
+
declare function generateDocumentParallel(options: GenerateDocumentParallelOptions): Promise<Section3[]>;
|
|
30
79
|
|
|
31
|
-
export { DOCUMENT_PROMPT_SUFFIX, DOCUMENT_SYSTEM_PROMPT, type GenerateDocumentOptions, generateDocument };
|
|
80
|
+
export { DOCUMENT_PROMPT_SUFFIX, DOCUMENT_SYSTEM_PROMPT, type DocumentOutline, type GenerateDocumentOptions, type GenerateDocumentParallelOptions, generateDocument, generateDocumentParallel };
|
package/dist/generateDocument.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DOCUMENT_PROMPT_SUFFIX,
|
|
3
3
|
DOCUMENT_SYSTEM_PROMPT,
|
|
4
|
-
generateDocument
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
generateDocument,
|
|
5
|
+
generateDocumentParallel
|
|
6
|
+
} from "./chunk-ROHHDJBH.js";
|
|
7
|
+
import "./chunk-AJGAO7UP.js";
|
|
7
8
|
import "./chunk-G5QRFGPZ.js";
|
|
8
9
|
export {
|
|
9
10
|
DOCUMENT_PROMPT_SUFFIX,
|
|
10
11
|
DOCUMENT_SYSTEM_PROMPT,
|
|
11
|
-
generateDocument
|
|
12
|
+
generateDocument,
|
|
13
|
+
generateDocumentParallel
|
|
12
14
|
};
|
|
13
15
|
//# sourceMappingURL=generateDocument.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ export { DeployToEasyBitsOptions, DeployToS3Options, deployToEasyBits, deployToS
|
|
|
9
9
|
export { EnrichImagesOptions, PexelsResult, enrichImages, findImageSlots, generateImage, generateSvg, searchImage } from './images.js';
|
|
10
10
|
export { C as Canvas, a as CanvasHandle, b as CodeEditor, F as FloatingToolbar, S as SectionList, V as Viewport, c as ViewportToggle } from './ViewportToggle-zUYxZgQ6.js';
|
|
11
11
|
import 'ai';
|
|
12
|
-
import './directions.js';
|
|
13
12
|
import 'zod';
|
|
13
|
+
import './directions.js';
|
|
14
14
|
import 'react';
|
|
15
15
|
import 'react/jsx-runtime';
|
|
16
16
|
|
package/dist/index.js
CHANGED
|
@@ -10,16 +10,16 @@ import {
|
|
|
10
10
|
PROMPT_SUFFIX,
|
|
11
11
|
SYSTEM_PROMPT,
|
|
12
12
|
generateLanding
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-T6UZ3FVJ.js";
|
|
14
14
|
import {
|
|
15
15
|
DOCUMENT_PROMPT_SUFFIX,
|
|
16
16
|
DOCUMENT_SYSTEM_PROMPT,
|
|
17
17
|
generateDocument
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ROHHDJBH.js";
|
|
19
19
|
import {
|
|
20
20
|
REFINE_SYSTEM,
|
|
21
21
|
refineLanding
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-3QER6Z5B.js";
|
|
23
23
|
import {
|
|
24
24
|
deployToEasyBits,
|
|
25
25
|
deployToS3
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from "./chunk-DL5PA47F.js";
|
|
37
37
|
import {
|
|
38
38
|
extractJsonObjects
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-AJGAO7UP.js";
|
|
40
40
|
import {
|
|
41
41
|
enrichImages,
|
|
42
42
|
findImageSlots,
|
package/dist/refine.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easybits.cloud/html-tailwind-generator",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.98",
|
|
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/generateDocument.ts"],"sourcesContent":["import { streamGenerate, dataUrlToImagePart } from \"./streamCore\";\nimport type { Section3 } from \"./types\";\nimport type { DesignDirection } from \"./directions\";\n\nexport const DOCUMENT_SYSTEM_PROMPT = `You are a professional document designer who creates stunning letter-sized (8.5\" × 11\") document pages using HTML + Tailwind CSS.\n\nRULES:\n- Each page is a <section> element sized for letter paper\n- Page structure: <section class=\"w-[8.5in] h-[11in] relative overflow-hidden\">\n- The section itself has NO padding — backgrounds, gradients, and decorative elements go edge-to-edge\n- For text content, use an inner wrapper: <div class=\"px-[0.75in] py-[0.5in]\">...content...</div>\n- Cover pages and decorative sections can use full-bleed backgrounds (bg-primary, gradients, images that fill the entire page)\n- Content MUST NOT overflow page boundaries — be conservative with spacing\n- Use Tailwind CDN classes ONLY (no custom CSS, no @apply, no @import)\n- NO JavaScript, only HTML+Tailwind\n- All text content in Spanish unless the prompt specifies otherwise\n- Use real content from the source material, not Lorem ipsum\n- NOT responsive — fixed letter size, no breakpoints needed\n- Sections can have ANY background — full-bleed color, gradients, or white. Not limited to white paper.\n\nSTRICT PROHIBITIONS:\n1. **NO EMOJI** — Never use emoji characters (🚀❌✅📊 etc.). Instead use inline SVG icons or colored divs. For bullet decorators use small colored circles (<span class=\"w-2 h-2 rounded-full bg-primary inline-block\"></span>) or simple SVG.\n2. **NO Chart.js / NO JavaScript** — Never reference Chart.js, canvas, or any JS library. For data visualization use pure CSS: progress bars (div with percentage width + bg-primary), horizontal bars, styled tables with colored cells. Never use <canvas> or <script>.\n3. **NO buttons or CTAs** — This is a print document, not a web page. No \"Contactar\", \"Ver más\", \"Comprar\" buttons. Use text with contact info instead.\n4. **CONTRAST IS MANDATORY** — Dark/colored backgrounds (bg-primary, bg-primary-dark, bg-secondary, dark gradients) MUST use text-white or text-on-primary. Light backgrounds (white, bg-surface, bg-surface-alt) MUST use text-gray-900 or text-on-surface. NEVER use dark text on dark backgrounds or light text on light backgrounds.\n5. **Max 2 font weights per page** — Pick 2 (e.g. font-semibold + font-normal, or font-bold + font-light). Don't mix 4-5 weights.\n6. **Generous whitespace** — Don't fill every centimeter. Leave breathing room. Use py-8, py-12, gap-6, gap-8 liberally. Less content per page = more professional.\n\nLAYOUT OVERFLOW PREVENTION — CRITICAL:\n- Max 2 columns side by side — each with w-1/2. NEVER use 3+ columns.\n- Decorative sidebars: max w-16 (4rem). NEVER use w-[2.5in] or wider sidebars — they steal too much space.\n- Stats/metric grids: max 3 items per row (grid-cols-3). Use gap-4 or gap-6.\n- Tables: max 4 columns, use text-xs or text-sm for cell text, px-3 py-2 cell padding.\n- Images: always w-full or max-w-[50%] — never fixed pixel widths.\n- Text: never use text-6xl or larger except for cover page title. Body text: text-sm or text-base.\n- NEVER use absolute positioning that could overflow — prefer flex/grid layouts.\n- Decorative shapes with absolute positioning MUST stay fully inside the page. Use overflow-hidden on parent AND keep coordinates positive (no negative right/left values).\n- Large decorative text (text-[200px], text-[10rem] etc.) MUST have opacity-5 or lower AND overflow-hidden on its container. These giant texts frequently overflow — be extra careful.\n- NEVER place elements beyond the right edge — all content and decorations must fit within 8.5in width.\n\nDESIGN — ADAPT to the document type. Read the prompt carefully and match the visual style:\n\nGENERAL PRINCIPLES (apply to ALL documents):\n- First page is ALWAYS a stunning cover/title page with impactful design\n- Typography: strong hierarchy with just 2 weights, clear headings vs body\n- Each page visually distinct — different layouts, accent placements\n- Use the full page creatively — backgrounds, sidebars, geometric shapes\n- Professional and polished, never generic or template-looking\n- Icons: use simple inline SVG (12-20px) for visual accents. Keep SVGs minimal (single path, no complex illustrations)\n\nADAPT YOUR STYLE to what the user is creating:\n- Reports/Data: structured grids, tables with alternating rows (bg-surface-alt), progress bars, stat cards, clean data hierarchy\n- Brochures/Marketing: bold hero images, large headlines, feature grids, testimonial-style quotes, visual storytelling\n- Catalogs/Products: product cards with images, specs grids, price highlights, category headers with full-bleed color\n- Invitations/Events: centered dramatic typography, decorative borders, elegant spacing, date/location prominently styled\n- Proposals/Pitches: problem→solution flow, metric highlights, team/about sections, pricing tables\n- CVs/Resumes: clean sidebar layouts, skill bars, timeline for experience, contact info header\n- Creative/General: mix techniques — bento grids, full-bleed images, overlapping elements, bold color blocking\n\nVISUAL TECHNIQUES available to you:\n- Full-bleed colored pages (bg-primary, gradients)\n- Geometric accent shapes (CSS divs with clip-path or rotation)\n- Asymmetric layouts (grid with unequal columns)\n- Large stat numbers as visual anchors (text-5xl font-black)\n- Header/footer bands with contrasting color\n- Sidebar accents (thin, max w-16)\n- Image + text compositions\n- Bento-grid mixing content blocks of different sizes\n- Tables: alternating row colors, clean borders, generous cell padding (px-4 py-3)\n- For numerical data: CSS progress bars, styled tables with colored cells, large stat numbers — NEVER canvas/charts\n\nCSS PROGRESS BARS — use this pattern for data visualization:\n<div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-primary h-3 rounded-full\" style=\"width: 75%\"></div></div>\n\nCOLOR SYSTEM — use semantic classes:\n- bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary\n- bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted\n- bg-secondary, text-secondary, bg-accent, text-accent\n- Cover pages should use bold full-bleed backgrounds (bg-primary, gradients from-primary to-primary-dark)\n- CONTRAST: bg-primary/bg-primary-dark/bg-secondary → text-white or text-on-primary. White/bg-surface → text-gray-900 or text-on-surface\n\nIMAGES — USE GENEROUSLY:\n- EVERY image MUST use: <img data-image-query=\"english search query\" alt=\"description\" class=\"w-full h-auto object-cover rounded-xl\"/>\n- NEVER include a src attribute — the system auto-replaces data-image-query with a real image\n- For avatar-like elements, use colored divs with initials instead of img tags\n- Include at LEAST 3-5 images across the document — hero images, section illustrations, backgrounds, product photos\n- Each data-image-query should be a UNIQUE, specific search query in English (e.g. \"modern office workspace aerial view\", \"team brainstorming whiteboard\", \"abstract blue technology network\")\n- Use images to break up text-heavy pages and add visual interest\n\nCHARTS & DATA VISUALIZATION (SVG):\n- For charts, diagrams, and decorative data graphics, use:\n <div data-svg-chart=\"bar chart showing Q1 revenue: Jan $45K, Feb $52K, Mar $61K\" class=\"w-full\"></div>\n- The system generates professional SVG charts with a specialized tool — NEVER draw SVGs yourself\n- Use descriptive prompts with data points: type of chart + what it shows + actual values\n- Examples: \"donut chart: 40% Marketing, 30% Sales, 20% R&D, 10% Admin\", \"line chart showing growth: Q1 $100K, Q2 $150K, Q3 $220K, Q4 $310K\"\n- For simple metrics, still prefer CSS progress bars (they render faster)\n\nTAILWIND v3 NOTES:\n- Standard Tailwind v3 classes (shadow-sm, shadow-md, rounded-md, etc.)\n- Borders: border + border-gray-200 for visible borders\n\nEXAMPLE — Cover page (simple, no wide sidebars):\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-white\">\n <div class=\"absolute left-0 top-0 w-2 h-full bg-primary\"></div>\n <div class=\"flex flex-col justify-center h-[11in] px-[1in]\">\n <div class=\"text-sm font-normal text-primary mb-4\">Marzo 2026 · Versión 1.0</div>\n <h1 class=\"text-5xl font-bold text-gray-900 leading-tight\">Reporte<br/>Trimestral</h1>\n <div class=\"w-16 h-1 bg-primary mt-6 mb-4\"></div>\n <p class=\"text-lg font-normal text-gray-500\">Resultados y análisis del primer trimestre</p>\n </div>\n</section>\n\nEXAMPLE — Marketing/brochure page (bold, visual):\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-primary\">\n <div class=\"flex h-[11in]\">\n <div class=\"w-1/2 flex flex-col justify-center px-[0.75in]\">\n <span class=\"text-sm font-normal text-on-primary opacity-70 uppercase tracking-widest mb-3\">Solución Premium</span>\n <h2 class=\"text-4xl font-bold text-on-primary leading-tight mb-6\">Transforma tu negocio digital</h2>\n <p class=\"text-base font-normal text-on-primary opacity-80 mb-8\">Herramientas inteligentes que simplifican la gestión de tus activos digitales.</p>\n <div class=\"flex gap-6\">\n <div><div class=\"text-3xl font-bold text-accent\">98%</div><div class=\"text-xs text-on-primary opacity-70\">Satisfacción</div></div>\n <div><div class=\"text-3xl font-bold text-accent\">2.4K</div><div class=\"text-xs text-on-primary opacity-70\">Empresas</div></div>\n </div>\n </div>\n <div class=\"w-1/2 relative\">\n <img data-image-query=\"modern office team collaboration technology\" alt=\"Team working\" class=\"absolute inset-0 w-full h-full object-cover\" />\n </div>\n </div>\n</section>\n\nEXAMPLE — Catalog/product grid page:\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-surface\">\n <div class=\"h-1 bg-primary w-full\"></div>\n <div class=\"px-[0.75in] py-[0.5in]\">\n <div class=\"flex justify-between items-baseline mb-6\">\n <h2 class=\"text-2xl font-bold text-on-surface\">Colección Primavera</h2>\n <span class=\"text-xs font-normal text-on-surface-muted uppercase tracking-wider\">Página 3 de 8</span>\n </div>\n <div class=\"grid grid-cols-2 gap-6\">\n <div class=\"bg-surface-alt rounded-xl overflow-hidden\">\n <img data-image-query=\"minimalist product on white background\" alt=\"Product\" class=\"w-full h-48 object-cover\" />\n <div class=\"p-4\"><h3 class=\"font-bold text-on-surface text-sm\">Producto Alpha</h3><p class=\"text-xs text-on-surface-muted mt-1\">Diseño ergonómico premium</p><div class=\"text-lg font-bold text-primary mt-2\">$2,490</div></div>\n </div>\n <div class=\"bg-surface-alt rounded-xl overflow-hidden\">\n <img data-image-query=\"elegant product photography studio\" alt=\"Product\" class=\"w-full h-48 object-cover\" />\n <div class=\"p-4\"><h3 class=\"font-bold text-on-surface text-sm\">Producto Beta</h3><p class=\"text-xs text-on-surface-muted mt-1\">Tecnología de vanguardia</p><div class=\"text-lg font-bold text-primary mt-2\">$3,190</div></div>\n </div>\n </div>\n </div>\n</section>\n\nEXAMPLE — Content page with table + progress bars:\n<section class=\"w-[8.5in] min-h-[11in] relative overflow-hidden bg-white\">\n <div class=\"h-1.5 bg-primary w-full\"></div>\n <div class=\"px-[0.75in] py-[0.5in]\">\n <h2 class=\"text-2xl font-bold text-gray-900 mb-1\">Métricas de Rendimiento</h2>\n <p class=\"text-sm font-normal text-gray-500 mb-8\">Indicadores clave del periodo enero—marzo</p>\n <table class=\"w-full text-sm mb-10\">\n <thead><tr class=\"bg-primary text-white\"><th class=\"px-4 py-3 text-left font-semibold\">Indicador</th><th class=\"px-4 py-3 text-left font-semibold\">Valor</th><th class=\"px-4 py-3 text-left font-semibold\">Meta</th></tr></thead>\n <tbody>\n <tr class=\"bg-surface-alt\"><td class=\"px-4 py-3 text-gray-900\">Ingresos</td><td class=\"px-4 py-3 text-gray-900\">$1.2M</td><td class=\"px-4 py-3 text-gray-900\">$1.5M</td></tr>\n <tr><td class=\"px-4 py-3 text-gray-900\">Clientes nuevos</td><td class=\"px-4 py-3 text-gray-900\">340</td><td class=\"px-4 py-3 text-gray-900\">300</td></tr>\n <tr class=\"bg-surface-alt\"><td class=\"px-4 py-3 text-gray-900\">Retención</td><td class=\"px-4 py-3 text-gray-900\">92%</td><td class=\"px-4 py-3 text-gray-900\">90%</td></tr>\n </tbody>\n </table>\n <h3 class=\"text-lg font-bold text-gray-900 mb-4\">Progreso por Área</h3>\n <div class=\"space-y-4\">\n <div><div class=\"flex justify-between text-sm mb-1\"><span class=\"text-gray-900 font-semibold\">Ventas</span><span class=\"text-gray-500\">80%</span></div><div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-primary h-3 rounded-full\" style=\"width: 80%\"></div></div></div>\n <div><div class=\"flex justify-between text-sm mb-1\"><span class=\"text-gray-900 font-semibold\">Marketing</span><span class=\"text-gray-500\">65%</span></div><div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-secondary h-3 rounded-full\" style=\"width: 65%\"></div></div></div>\n <div><div class=\"flex justify-between text-sm mb-1\"><span class=\"text-gray-900 font-semibold\">Producto</span><span class=\"text-gray-500\">95%</span></div><div class=\"w-full bg-gray-200 rounded-full h-3\"><div class=\"bg-accent h-3 rounded-full\" style=\"width: 95%\"></div></div></div>\n </div>\n </div>\n</section>`;\n\nexport const DOCUMENT_PROMPT_SUFFIX = `\n\nOUTPUT FORMAT: NDJSON — one JSON object per line, NO wrapper array, NO markdown fences.\nEach line: {\"label\": \"Page Title\", \"html\": \"<section class='w-[8.5in] min-h-[11in] relative overflow-hidden'>...</section>\"}\n\nGenerate 3-8 pages depending on content length. First page = cover/title page.\nEach page must fit within letter size (8.5\" × 11\"). Be conservative with spacing.\nMake each page visually distinct — different layouts, different accent placements.\nIMPORTANT: Adapt your design style to match the type of document — not everything is a report. Brochures should feel bold and visual, catalogs should showcase products, invitations should feel elegant, etc.`;\n\nexport interface GenerateDocumentOptions {\n anthropicApiKey?: string;\n openaiApiKey?: string;\n prompt: string;\n logoUrl?: string;\n referenceImage?: string;\n extraInstructions?: string;\n model?: string | import(\"ai\").LanguageModel;\n pexelsApiKey?: string;\n /** Design direction — injects Google Fonts + hex colors into the prompt */\n direction?: DesignDirection;\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n onSection?: (section: Section3) => void;\n onImageUpdate?: (sectionId: string, html: string) => void;\n onRawChunk?: (buffer: string, completedCount: number) => void;\n onDone?: (sections: Section3[]) => void;\n onError?: (error: Error) => void;\n}\n\n/**\n * Generate a multi-page document with streaming AI + image enrichment.\n */\nexport async function generateDocument(options: GenerateDocumentOptions): Promise<Section3[]> {\n const {\n prompt,\n logoUrl,\n referenceImage,\n extraInstructions,\n direction,\n ...rest\n } = options;\n\n const extra = extraInstructions ? `\\nAdditional instructions: ${extraInstructions}` : \"\";\n\n // Build direction style instructions if provided\n let directionInstruction = \"\";\n if (direction) {\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 directionInstruction = `\nDESIGN DIRECTION: \"${direction.name}\" — ${direction.tagline}\nTYPOGRAPHY: Use these Google Fonts via <link href=\"${fontsUrl}\" rel=\"stylesheet\"> on the first page.\n- Headings: font-family: '${direction.headingFont}', sans-serif (via inline style)\n- Body: font-family: '${direction.bodyFont}', sans-serif (via inline style)\nCOLORS — use ONLY semantic Tailwind classes (the editor injects CSS variables that resolve these):\n- bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary\n- bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted\n- bg-secondary, text-secondary, bg-accent, text-accent\n- NEVER use hardcoded hex colors like bg-[#xxx] or text-[#xxx] — always use semantic classes\n- The palette is: primary=${direction.colors.primary}, accent=${direction.colors.accent}, surface=${direction.colors.surface}\nMood: ${direction.mood}\nLayout approach: ${direction.layoutHint}\nIMPORTANT: Apply inline style=\"font-family: '${direction.headingFont}'\" on ALL heading elements and style=\"font-family: '${direction.bodyFont}'\" on ALL body text elements. Include the Google Fonts <link> tag inside the FIRST <section> only.`;\n }\n // Truncate prompt to prevent token overflow (max ~15K chars ≈ 5K tokens)\n const safePrompt = prompt.length > 15_000 ? prompt.substring(0, 15_000) + \"\\n[...content truncated...]\" : prompt;\n const logoInstruction = logoUrl\n ? `\\nLOGO: Include this logo on the cover page and as a small header on other pages:\\n<img src=\"${logoUrl}\" alt=\"Logo\" class=\"h-12 object-contain\" />\\nUse this exact <img> tag with this exact src URL — do NOT invent a different URL or modify it.`\n : \"\";\n\n const content: any[] = [];\n\n if (referenceImage) {\n const converted = dataUrlToImagePart(referenceImage);\n if (converted) {\n content.push({ type: \"image\", ...converted });\n } else {\n content.push({ type: \"image\", image: referenceImage });\n }\n content.push({\n type: \"text\",\n text: `Create a professional document inspired by this reference image for: ${safePrompt}${logoInstruction}${directionInstruction}${extra}${DOCUMENT_PROMPT_SUFFIX}`,\n });\n } else {\n content.push({\n type: \"text\",\n text: `Create a professional document for: ${safePrompt}${logoInstruction}${directionInstruction}${extra}${DOCUMENT_PROMPT_SUFFIX}`,\n });\n }\n\n return streamGenerate({\n ...rest,\n systemPrompt: DOCUMENT_SYSTEM_PROMPT,\n userContent: content,\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -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 * If modelId is already a LanguageModel object, return it directly.\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\nfunction isLanguageModel(value: unknown): value is import(\"ai\").LanguageModel {\n return typeof value === \"object\" && value !== null && \"modelId\" in value && \"provider\" in value;\n}\n\nexport async function resolveModel(opts: {\n openaiApiKey?: string;\n anthropicApiKey?: string;\n modelId?: string | import(\"ai\").LanguageModel;\n defaultOpenai: string;\n defaultAnthropic: string;\n}) {\n // If modelId is already a model object, return it directly\n if (opts.modelId && isLanguageModel(opts.modelId)) {\n return opts.modelId;\n }\n\n const modelId = opts.modelId as string | undefined;\n\n if (modelId && isOpenAiModel(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 })(modelId);\n }\n // OpenAI model requested but no key — fall through to Anthropic default\n } else if (modelId) {\n const anthropicKey = opts.anthropicApiKey || process.env.ANTHROPIC_API_KEY;\n if (anthropicKey) {\n return createAnthropic({ apiKey: anthropicKey })(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 or pre-built LanguageModel object */\n model?: string | import(\"ai\").LanguageModel;\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":";;;;;;;;AAQA,IAAM,SACJ;AAEF,SAAS,GAAG,QAAgB,QAAwB;AAClD,SAAO,IAAI,OAAO,MAAM,MAAM,KAAK,MAAM,MAAM,MAAM,QAAQ,GAAG;AAClE;AAEA,IAAM,eAAmC;AAAA;AAAA,EAEvC,CAAC,GAAG,MAAM,aAAa,GAAG,YAAY;AAAA,EACtC,CAAC,GAAG,MAAM,QAAQ,GAAG,kBAAkB;AAAA,EACvC,CAAC,GAAG,MAAM,aAAa,GAAG,iBAAiB;AAAA,EAC3C,CAAC,GAAG,MAAM,aAAa,GAAG,YAAY;AAAA;AAAA,EAGtC,CAAC,GAAG,QAAQ,aAAa,GAAG,cAAc;AAAA,EAC1C,CAAC,GAAG,QAAQ,aAAa,GAAG,mBAAmB;AAAA,EAC/C,CAAC,GAAG,QAAQ,gBAAgB,GAAG,iBAAiB;AAAA,EAChD,CAAC,GAAG,QAAQ,KAAK,GAAG,cAAc;AAAA;AAAA,EAGlC,CAAC,GAAG,UAAU,UAAU,GAAG,gBAAgB;AAAA;AAAA,EAG3C,CAAC,GAAG,QAAQ,UAAU,GAAG,cAAc;AAAA;AAAA,EAGvC,CAAC,GAAG,QAAQ,UAAU,GAAG,cAAc;AAAA,EACvC,CAAC,GAAG,MAAM,UAAU,GAAG,YAAY;AAAA,EACnC,CAAC,GAAG,OAAO,UAAU,GAAG,aAAa;AAAA;AAAA,EAGrC,CAAC,IAAI,OAAO,gBAAgB,MAAM,kCAAkC,GAAG,GAAG,uBAAuB;AAAA,EACjG,CAAC,IAAI,OAAO,gBAAgB,MAAM,6BAA6B,GAAG,GAAG,wBAAwB;AAAA,EAC7F,CAAC,IAAI,OAAO,kBAAkB,MAAM,iBAAiB,GAAG,GAAG,oBAAoB;AAAA,EAC/E,CAAC,IAAI,OAAO,kBAAkB,MAAM,iBAAiB,GAAG,GAAG,oBAAoB;AAAA,EAC/E,CAAC,IAAI,OAAO,oBAAoB,MAAM,iBAAiB,GAAG,GAAG,sBAAsB;AAAA;AAAA,EAGnF,CAAC,GAAG,UAAU,UAAU,GAAG,gBAAgB;AAAA;AAAA,EAG3C,CAAC,GAAG,eAAe,UAAU,GAAG,qBAAqB;AAAA;AAAA,EAGrD,CAAC,GAAG,WAAW,UAAU,GAAG,iBAAiB;AAAA;AAAA,EAG7C,CAAC,GAAG,UAAU,UAAU,GAAG,gBAAgB;AAAA;AAAA,EAG3C,CAAC,GAAG,cAAc,UAAU,GAAG,oBAAoB;AAAA;AAAA,EAGnD,CAAC,GAAG,UAAU,UAAU,GAAG,gBAAgB;AAC7C;AAEO,SAAS,uBAAuB,MAAsB;AAC3D,MAAI,SAAS;AACb,aAAW,CAAC,SAAS,WAAW,KAAK,cAAc;AACjD,aAAS,OAAO,QAAQ,SAAS,WAAW;AAAA,EAC9C;AACA,SAAO;AACT;;;ACvEA,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB;AAChC,SAAS,cAAc;AAavB,SAAS,cAAc,IAAqB;AAC1C,SAAO,8CAA8C,KAAK,EAAE;AAC9D;AAEA,SAAS,gBAAgB,OAAqD;AAC5E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,aAAa,SAAS,cAAc;AAC5F;AAEA,eAAsB,aAAa,MAMhC;AAED,MAAI,KAAK,WAAW,gBAAgB,KAAK,OAAO,GAAG;AACjD,WAAO,KAAK;AAAA,EACd;AAEA,QAAM,UAAU,KAAK;AAErB,MAAI,WAAW,cAAc,OAAO,GAAG;AACrC,UAAMA,aAAY,KAAK,gBAAgB,QAAQ,IAAI;AACnD,QAAIA,YAAW;AACb,YAAM,EAAE,aAAa,IAAI,MAAM,OAAO,gBAAgB;AACtD,aAAO,aAAa,EAAE,QAAQA,WAAU,CAAC,EAAE,OAAO;AAAA,IACpD;AAAA,EAEF,WAAW,SAAS;AAClB,UAAMC,gBAAe,KAAK,mBAAmB,QAAQ,IAAI;AACzD,QAAIA,eAAc;AAChB,aAAO,gBAAgB,EAAE,QAAQA,cAAa,CAAC,EAAE,OAAO;AAAA,IAC1D;AAAA,EACF;AAEA,QAAM,eAAe,KAAK,mBAAmB,QAAQ,IAAI;AACzD,MAAI,cAAc;AAChB,WAAO,gBAAgB,EAAE,QAAQ,aAAa,CAAC,EAAE,KAAK,gBAAgB;AAAA,EACxE;AACA,QAAM,YAAY,KAAK,gBAAgB,QAAQ,IAAI;AACnD,MAAI,WAAW;AACb,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,gBAAgB;AACtD,WAAO,aAAa,EAAE,QAAQ,UAAU,CAAC,EAAE,KAAK,aAAa;AAAA,EAC/D;AACA,SAAO,gBAAgB,EAAE,KAAK,gBAAgB;AAChD;AAKO,SAAS,mBAAmB,SAAiE;AAClG,QAAM,QAAQ,QAAQ,MAAM,4BAA4B;AACxD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO;AAAA,IACL,OAAO,IAAI,WAAW,OAAO,KAAK,MAAM,CAAC,GAAG,QAAQ,CAAC;AAAA,IACrD,UAAU,MAAM,CAAC;AAAA,EACnB;AACF;AAKO,SAAS,mBAAmB,MAA+B;AAChE,QAAM,UAAiB,CAAC;AACxB,MAAI,YAAY;AAEhB,SAAO,UAAU,SAAS,GAAG;AAC3B,gBAAY,UAAU,UAAU;AAChC,QAAI,CAAC,UAAU,WAAW,GAAG,GAAG;AAC9B,YAAM,YAAY,UAAU,QAAQ,GAAG;AACvC,UAAI,cAAc,GAAI;AACtB,kBAAY,UAAU,MAAM,SAAS;AACrC;AAAA,IACF;AAEA,QAAI,QAAQ;AACZ,QAAI,WAAW;AACf,QAAI,SAAS;AACb,QAAI,MAAM;AAEV,aAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,YAAM,KAAK,UAAU,CAAC;AACtB,UAAI,QAAQ;AAAE,iBAAS;AAAO;AAAA,MAAU;AACxC,UAAI,OAAO,MAAM;AAAE,iBAAS;AAAM;AAAA,MAAU;AAC5C,UAAI,OAAO,KAAK;AAAE,mBAAW,CAAC;AAAU;AAAA,MAAU;AAClD,UAAI,SAAU;AACd,UAAI,OAAO,IAAK;AAChB,UAAI,OAAO,KAAK;AAAE;AAAS,YAAI,UAAU,GAAG;AAAE,gBAAM;AAAG;AAAA,QAAO;AAAA,MAAE;AAAA,IAClE;AAEA,QAAI,QAAQ,GAAI;AAEhB,UAAM,YAAY,UAAU,MAAM,GAAG,MAAM,CAAC;AAC5C,gBAAY,UAAU,MAAM,MAAM,CAAC;AAEnC,QAAI;AACF,cAAQ,KAAK,KAAK,MAAM,SAAS,CAAC;AAAA,IACpC,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,CAAC,SAAS,SAAS;AAC5B;AAGA,IAAM,0BAA0B,sBAAsB,mBAAmB,06BAA06B,CAAC;AAGp/B,IAAM,0BAA0B;AAGzB,SAAS,0BAA0B,MAAsB;AAC9D,SAAO,KAAK;AAAA,IACV;AAAA,IACA,CAAC,QAAQ,QAAQ,OAAO,UAAU;AAChC,aAAO,QAAQ,MAAM,mBAAmB,KAAK,IAAI,KAAK,IAAI,uBAAuB;AAAA,IACnF;AAAA,EACF;AACF;AAGO,SAAS,uBAAuB,MAAsB;AAC3D,SAAO,KAAK;AAAA,IACV;AAAA,IACA,CAAC,QAAQ,QAAQ,OAAO,UAAU;AAChC,UAAI,OAAO,SAAS,MAAM,KAAK,MAAM,SAAS,MAAM,EAAG,QAAO;AAC9D,aAAO,GAAG,MAAM,QAAQ,uBAAuB,uBAAuB,KAAK,UAAU,KAAK,IAAI,KAAK;AAAA,IACrG;AAAA,EACF;AACF;AAiCA,eAAsB,eAAe,SAAqD;AACxF,QAAM;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,IACd,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,eAAe,iBAAiB,QAAQ,IAAI;AAClD,QAAM,QAAQ,MAAM,aAAa;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,SAAS,WAAW;AAAA,IACxB;AAAA,IACA,QAAQ;AAAA,IACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,YAAY,CAAC;AAAA,EACnD,CAAC;AAED,QAAM,cAA0B,CAAC;AACjC,QAAM,gBAAiC,CAAC;AACxC,MAAI,eAAe;AACnB,MAAI,SAAS;AAEb,WAAS,gBAAgB,YAAsB;AAC7C,UAAM,WAAW;AACjB,UAAM,aAAsD,CAAC;AAC7D,QAAI;AACJ,YAAQ,OAAO,SAAS,KAAK,WAAW,IAAI,OAAO,MAAM;AACvD,iBAAW,KAAK,EAAE,WAAW,KAAK,CAAC,GAAG,QAAQ,KAAK,CAAC,EAAE,CAAC;AAAA,IACzD;AACA,QAAI,WAAW,WAAW,EAAG;AAE7B,UAAM,eAAe,mBAAmB,QAAQ,IAAI;AACpD,kBAAc;AAAA,OACX,YAAY;AACX,cAAM,UAAU,MAAM,QAAQ;AAAA,UAC5B,WAAW,IAAI,OAAO,EAAE,WAAW,OAAO,MAAM;AAC9C,gBAAI;AACF,oBAAM,MAAM,MAAM,YAAY,QAAQ,YAAY;AAClD,qBAAO,EAAE,WAAW,IAAI;AAAA,YAC1B,SAAS,GAAG;AACV,sBAAQ,KAAK,qBAAqB,MAAM,MAAM,CAAC;AAC/C,qBAAO,EAAE,WAAW,KAAK,0GAA0G,MAAM,SAAS;AAAA,YACpJ;AAAA,UACF,CAAC;AAAA,QACH;AACA,YAAI,OAAO,WAAW;AACtB,mBAAW,KAAK,SAAS;AACvB,cAAI,EAAE,WAAW,eAAe,EAAE,OAAO;AACvC,mBAAO,KAAK,QAAQ,EAAE,MAAM,WAAW,EAAE,MAAM,GAAG;AAAA,UACpD;AAAA,QACF;AACA,YAAI,SAAS,WAAW,MAAM;AAC5B,qBAAW,OAAO;AAClB,0BAAgB,WAAW,IAAI,IAAI;AAAA,QACrC;AAAA,MACF,GAAG;AAAA,IACL;AAAA,EACF;AAEA,WAAS,cAAc,YAAsB;AAC3C,UAAM,QAAQ,eAAe,WAAW,IAAI;AAC5C,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,gBAAgB,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;AACjD,kBAAc;AAAA,OACX,YAAY;AACX,cAAM,UAAU,MAAM,QAAQ;AAAA,UAC5B,cAAc,IAAI,OAAO,SAAS;AAChC,gBAAI,MAAqB;AAEzB,gBAAI,cAAc;AAChB,oBAAM,MAAM,MAAM,YAAY,KAAK,OAAO,YAAY,EAAE,MAAM,MAAM,IAAI;AACxE,oBAAM,KAAK,OAAO;AAAA,YACpB;AAEA,gBAAI,CAAC,OAAO,cAAc;AACxB,kBAAI;AACF,sBAAM,UAAU,MAAM,cAAc,KAAK,OAAO,YAAY;AAC5D,sBAAM,eAAe,MAAM,aAAa,SAAS,KAAK,KAAK,IAAI;AAAA,cACjE,SAAS,GAAG;AACV,wBAAQ,KAAK,uBAAuB,KAAK,KAAK,MAAM,CAAC;AAAA,cACvD;AAAA,YACF;AACA,oBAAQ,mDAAmD,mBAAmB,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC;AACtG,mBAAO,EAAE,MAAM,IAAI;AAAA,UACrB,CAAC;AAAA,QACH;AACA,YAAI,OAAO,WAAW;AACtB,mBAAW,KAAK,SAAS;AACvB,cAAI,EAAE,WAAW,eAAe,EAAE,OAAO;AACvC,kBAAM,EAAE,MAAM,IAAI,IAAI,EAAE;AACxB,kBAAM,cAAc,KAAK,WAAW,QAAQ,SAAS,GAAG;AACxD,mBAAO,KAAK,WAAW,KAAK,WAAW,WAAW;AAAA,UACpD;AAAA,QACF;AACA,YAAI,SAAS,WAAW,MAAM;AAC5B,qBAAW,OAAO;AAClB,0BAAgB,WAAW,IAAI,IAAI;AAAA,QACrC;AAAA,MACF,GAAG;AAAA,IACL;AAAA,EACF;AAEA,WAAS,cAAc,KAAU;AAC/B,QAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,MAAO;AAC7B,UAAM,UAAoB;AAAA,MACxB,IAAI,OAAO,CAAC;AAAA,MACZ,OAAO;AAAA,MACP,MAAM,uBAAuB,0BAA0B,uBAAuB,IAAI,IAAI,CAAC,CAAC;AAAA,MACxF,OAAO,IAAI;AAAA,IACb;AACA,gBAAY,KAAK,OAAO;AACxB,gBAAY,OAAO;AACnB,kBAAc,OAAO;AACrB,oBAAgB,OAAO;AAAA,EACzB;AAEA,MAAI;AACF,QAAI,aAAa;AACjB,qBAAiB,SAAS,OAAO,YAAY;AAC3C,gBAAU;AACV;AAEA,YAAM,CAAC,SAAS,SAAS,IAAI,mBAAmB,MAAM;AACtD,eAAS;AACT,iBAAW,OAAO,SAAS;AACzB,qBAAa;AACb,sBAAc,GAAG;AAAA,MACnB;AAEA,UAAI,cAAc,aAAa,MAAM,KAAK,OAAO,SAAS,IAAI;AAC5D,mBAAW,QAAQ,YAAY,MAAM;AAAA,MACvC;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,GAAG;AACjB,UAAI,UAAU,OAAO,KAAK;AAC1B,UAAI,QAAQ,WAAW,KAAK,GAAG;AAC7B,kBAAU,QAAQ,QAAQ,oBAAoB,EAAE,EAAE,QAAQ,WAAW,EAAE;AAAA,MACzE;AACA,YAAM,CAAC,WAAW,IAAI,mBAAmB,OAAO;AAChD,iBAAW,OAAO,YAAa,eAAc,GAAG;AAAA,IAClD;AAGA,UAAM,QAAQ,WAAW,aAAa;AAGtC,eAAW,WAAW,aAAa;AACjC,YAAM,SAAS,QAAQ;AACvB,cAAQ,OAAO,QAAQ,KAAK;AAAA,QAC1B;AAAA,QACA,CAAC,QAAQ,UAAU;AACjB,gBAAM,WAAW,MAAM,MAAM,gBAAgB;AAC7C,gBAAM,QAAQ,WAAW,CAAC,KAAK;AAC/B,iBAAO,6DAA6D,mBAAmB,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,KAAK;AAAA,QACtH;AAAA,MACF;AACA,cAAQ,OAAO,QAAQ,KAAK;AAAA,QAC1B;AAAA,QACA,CAAC,QAAQ,UAAU;AACjB,iBAAO,wDAAwD,mBAAmB,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,QACvG;AAAA,MACF;AACA,UAAI,QAAQ,SAAS,QAAQ;AAC3B,wBAAgB,QAAQ,IAAI,QAAQ,IAAI;AAAA,MAC1C;AAAA,IACF;AAEA,aAAS,WAAW;AACpB,WAAO;AAAA,EACT,SAAS,KAAU;AACjB,UAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,KAAK,WAAW,mBAAmB;AACxF,cAAU,KAAK;AACf,UAAM;AAAA,EACR;AACF;","names":["openaiKey","anthropicKey"]}
|
|
File without changes
|
|
File without changes
|