@easybits.cloud/html-tailwind-generator 0.2.144 → 0.2.146

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,261 +1,3 @@
1
- // src/images/pexels.ts
2
- async function searchImage(query, apiKey) {
3
- const key = apiKey || process.env.PEXELS_API_KEY;
4
- if (!key) return null;
5
- try {
6
- const res = await fetch(
7
- `https://api.pexels.com/v1/search?query=${encodeURIComponent(query)}&per_page=5&orientation=landscape&locale=en-US`,
8
- { headers: { Authorization: key } }
9
- );
10
- if (!res.ok) {
11
- console.warn(`[pexels] ${res.status} for "${query}", trying unsplash fallback`);
12
- return searchUnsplash(query);
13
- }
14
- const data = await res.json();
15
- const photos = data.photos;
16
- if (!photos || photos.length === 0) {
17
- console.warn(`[pexels] 0 results for "${query}"`);
18
- return null;
19
- }
20
- const photo = photos[Math.floor(Math.random() * photos.length)];
21
- return {
22
- url: photo.src.large,
23
- photographer: photo.photographer,
24
- alt: photo.alt || query
25
- };
26
- } catch {
27
- return searchUnsplash(query);
28
- }
29
- }
30
- async function searchUnsplash(query) {
31
- try {
32
- const res = await fetch(
33
- `https://unsplash.com/napi/search/photos?query=${encodeURIComponent(query)}&per_page=5&orientation=landscape`
34
- );
35
- if (!res.ok) return null;
36
- const data = await res.json();
37
- const results = data.results;
38
- if (!results || results.length === 0) return null;
39
- const photo = results[Math.floor(Math.random() * results.length)];
40
- return {
41
- url: photo.urls?.regular || photo.urls?.small,
42
- photographer: photo.user?.name || "Unsplash",
43
- alt: photo.alt_description || query
44
- };
45
- } catch {
46
- return null;
47
- }
48
- }
49
-
50
- // src/images/dalleImages.ts
51
- async function generateImage(query, openaiApiKey) {
52
- const res = await fetch("https://api.openai.com/v1/images/generations", {
53
- method: "POST",
54
- headers: {
55
- Authorization: `Bearer ${openaiApiKey}`,
56
- "Content-Type": "application/json"
57
- },
58
- body: JSON.stringify({
59
- model: "dall-e-3",
60
- prompt: query,
61
- n: 1,
62
- size: "1792x1024"
63
- })
64
- });
65
- if (!res.ok) {
66
- const err = await res.text().catch(() => "Unknown error");
67
- throw new Error(`DALL-E API error ${res.status}: ${err}`);
68
- }
69
- const data = await res.json();
70
- return data.data[0].url;
71
- }
72
-
73
- // src/images/enrichImages.ts
74
- var FAKE_DOMAINS = [
75
- "images.unsplash.com",
76
- "unsplash.com",
77
- "via.placeholder.com",
78
- "placeholder.com",
79
- "placehold.co",
80
- "placehold.it",
81
- "placekitten.com",
82
- "picsum.photos",
83
- "loremflickr.com",
84
- "source.unsplash.com",
85
- "dummyimage.com",
86
- "fakeimg.pl",
87
- "example.com",
88
- "img.freepik.com",
89
- "cdn.pixabay.com"
90
- ];
91
- function findImageSlots(html) {
92
- const matches = [];
93
- const seen = /* @__PURE__ */ new Set();
94
- const diqRegex = /<img\s[^>]*data-image-query=["']([^"']+)["'][^>]*>/gi;
95
- let m;
96
- while ((m = diqRegex.exec(html)) !== null) {
97
- const fullTag = m[0];
98
- const query = m[1];
99
- if (seen.has(query)) continue;
100
- seen.add(query);
101
- const cleanedTag = fullTag.replace(/\ssrc=["'][^"']*["']/, "").replace(/\sdata-image-query=["'][^"']*["']/, "");
102
- const replaceTag = cleanedTag.replace(
103
- /^<img/,
104
- `<img src="{url}" data-enriched="true"`
105
- );
106
- matches.push({
107
- query,
108
- searchStr: fullTag,
109
- replaceStr: replaceTag
110
- });
111
- }
112
- const imgRegex = /<img\s[^>]*src="(https?:\/\/[^"]+)"[^>]*>/gi;
113
- while ((m = imgRegex.exec(html)) !== null) {
114
- const fullTag = m[0];
115
- const srcUrl = m[1];
116
- if (fullTag.includes("data-enriched")) continue;
117
- if (srcUrl.includes("pexels.com")) continue;
118
- if (seen.has(srcUrl)) continue;
119
- let isFake = false;
120
- try {
121
- const domain = new URL(srcUrl).hostname;
122
- isFake = FAKE_DOMAINS.some((d) => domain.includes(d));
123
- } catch {
124
- isFake = true;
125
- }
126
- if (!isFake) continue;
127
- const altMatch = fullTag.match(/alt="([^"]*?)"/);
128
- let query = altMatch?.[1]?.trim() || "";
129
- if (!query) {
130
- try {
131
- const path = new URL(srcUrl).pathname;
132
- const words = path.replace(/[^a-zA-Z]/g, " ").split(/\s+/).filter((w) => w.length > 2).slice(0, 4).join(" ");
133
- if (words.length > 3) query = words;
134
- } catch {
135
- }
136
- }
137
- if (!query) query = "professional website hero image";
138
- seen.add(srcUrl);
139
- matches.push({
140
- query,
141
- searchStr: `src="${srcUrl}"`,
142
- replaceStr: `src="{url}" data-enriched="true"`
143
- });
144
- }
145
- return matches;
146
- }
147
- async function enrichImages(html, pexelsApiKeyOrOpts, openaiApiKey) {
148
- let opts;
149
- if (typeof pexelsApiKeyOrOpts === "object" && pexelsApiKeyOrOpts !== null) {
150
- opts = pexelsApiKeyOrOpts;
151
- } else {
152
- opts = { pexelsApiKey: pexelsApiKeyOrOpts, openaiApiKey };
153
- }
154
- const slots = findImageSlots(html);
155
- if (slots.length === 0) return html;
156
- const resolved = await Promise.allSettled(
157
- slots.map(async (slot) => {
158
- let url = null;
159
- if (opts.pexelsApiKey) {
160
- const img = await searchImage(slot.query, opts.pexelsApiKey).catch(() => null);
161
- url = img?.url || null;
162
- }
163
- if (!url && opts.openaiApiKey) {
164
- try {
165
- const tempUrl = await generateImage(slot.query, opts.openaiApiKey);
166
- url = opts.persistImage ? await opts.persistImage(tempUrl, slot.query) : tempUrl;
167
- } catch (e) {
168
- console.warn(`[dalle] failed for "${slot.query}":`, e);
169
- }
170
- }
171
- url ??= `https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(slot.query.slice(0, 30))}`;
172
- return { slot, url };
173
- })
174
- );
175
- let result = html;
176
- for (const r of resolved) {
177
- if (r.status === "fulfilled" && r.value) {
178
- const { slot, url } = r.value;
179
- const replacement = slot.replaceStr.replace("{url}", url);
180
- result = result.replaceAll(slot.searchStr, replacement);
181
- }
182
- }
183
- result = result.replace(/<img\s(?![^>]*\bsrc=)([^>]*?)>/gi, (_match, attrs) => {
184
- const altMatch = attrs.match(/alt="([^"]*?)"/);
185
- const query = altMatch?.[1] || "professional image";
186
- return `<img src="https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(query.slice(0, 30))}" ${attrs}>`;
187
- });
188
- return result;
189
- }
190
-
191
- // src/images/svgGenerator.ts
192
- import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
193
- import { generateText } from "ai";
194
-
195
- // src/streamCore.ts
196
- import { streamText } from "ai";
197
- import { createAnthropic } from "@ai-sdk/anthropic";
198
- import { nanoid } from "nanoid";
199
-
200
- // src/images/enrichIcons.ts
201
- var ICON_PREFIXES = ["lucide", "heroicons", "material-symbols"];
202
- var iconCache = /* @__PURE__ */ new Map();
203
- function findIconSlots(html) {
204
- const matches = [];
205
- const regex = /<span\s[^>]*data-icon-query="([^"]+)"[^>]*><\/span>/gi;
206
- let m;
207
- while ((m = regex.exec(html)) !== null) {
208
- matches.push({ query: m[1], fullMatch: m[0] });
209
- }
210
- return matches;
211
- }
212
- async function fetchIcon(name) {
213
- if (iconCache.has(name)) return iconCache.get(name);
214
- for (const prefix of ICON_PREFIXES) {
215
- try {
216
- const url = `https://api.iconify.design/${prefix}/${name}.svg?height=1em&color=currentColor`;
217
- const res = await fetch(url);
218
- if (res.ok) {
219
- const svg = await res.text();
220
- if (svg.startsWith("<svg")) {
221
- iconCache.set(name, svg);
222
- return svg;
223
- }
224
- }
225
- } catch {
226
- }
227
- }
228
- iconCache.set(name, null);
229
- return null;
230
- }
231
- async function enrichSectionIcons(html) {
232
- const slots = findIconSlots(html);
233
- if (slots.length === 0) return html;
234
- const uniqueQueries = [...new Set(slots.map((s) => s.query))];
235
- const resolved = await Promise.allSettled(
236
- uniqueQueries.map(async (query) => {
237
- const svg = await fetchIcon(query);
238
- return { query, svg };
239
- })
240
- );
241
- const svgMap = /* @__PURE__ */ new Map();
242
- for (const r of resolved) {
243
- if (r.status === "fulfilled" && r.value.svg) {
244
- svgMap.set(r.value.query, r.value.svg);
245
- }
246
- }
247
- let result = html;
248
- for (const slot of slots) {
249
- const svg = svgMap.get(slot.query);
250
- if (!svg) continue;
251
- const classMatch = slot.fullMatch.match(/class="([^"]*)"/);
252
- const classes = classMatch?.[1] || "inline-block w-5 h-5";
253
- const svgWithClasses = svg.replace("<svg", `<svg class="${classes}"`);
254
- result = result.replace(slot.fullMatch, svgWithClasses);
255
- }
256
- return result;
257
- }
258
-
259
1
  // src/sanitizeColors.ts
260
2
  var COLORS = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose";
261
3
  var SECONDARY_COLORS = "blue|indigo|violet";
@@ -440,142 +182,400 @@ function detectBgFamily(classStr) {
440
182
  else if (/^bg-surface-deep(?:\/\d+)?$/.test(t)) found = "surface-deep";
441
183
  else if (/^bg-surface(?:-alt)?(?:\/\d+)?$/.test(t)) found = "surface";
442
184
  }
443
- if (found) return found;
444
- if (tokens.some((t) => /^bg-gradient-/.test(t))) {
445
- for (const t of tokens) {
446
- const m = t.match(/^from-(primary|secondary|accent|surface)(?:-light|-dark|-alt)?(?:\/\d+)?$/);
447
- if (m) return m[1];
185
+ if (found) return found;
186
+ if (tokens.some((t) => /^bg-gradient-/.test(t))) {
187
+ for (const t of tokens) {
188
+ const m = t.match(/^from-(primary|secondary|accent|surface)(?:-light|-dark|-alt)?(?:\/\d+)?$/);
189
+ if (m) return m[1];
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ function effectiveBg(stack) {
195
+ for (let i = stack.length - 1; i >= 0; i--) {
196
+ const v = stack[i];
197
+ if (v !== null) return v;
198
+ }
199
+ return "surface";
200
+ }
201
+ function onClass(bg) {
202
+ return `text-on-${bg}`;
203
+ }
204
+ function onMutedClass(bg) {
205
+ if (bg === "surface") return "text-on-surface-muted";
206
+ if (bg === "surface-deep") return "text-on-surface-deep";
207
+ return `text-on-${bg}`;
208
+ }
209
+ function fixTextClassesForBg(classStr, bg) {
210
+ let s = classStr;
211
+ const on = onClass(bg);
212
+ const onMuted = onMutedClass(bg);
213
+ s = s.replace(/\btext-on-surface-LIGHT\b/g, on);
214
+ s = s.replace(/\btext-on-surface-DARK\b/g, on);
215
+ s = s.replace(/\btext-on-surface-MUTED\b/g, onMuted);
216
+ if (bg === "primary") {
217
+ s = s.replace(/\btext-on-surface(?:-muted)?\b/g, "text-on-primary");
218
+ s = s.replace(/\btext-on-secondary\b/g, "text-on-primary");
219
+ s = s.replace(/\btext-on-accent\b/g, "text-on-primary");
220
+ s = s.replace(/\btext-primary(?!-(?:light|dark))\b/g, "text-on-primary");
221
+ } else if (bg === "secondary") {
222
+ s = s.replace(/\btext-on-surface(?:-muted)?\b/g, "text-on-secondary");
223
+ s = s.replace(/\btext-on-primary\b/g, "text-on-secondary");
224
+ s = s.replace(/\btext-on-accent\b/g, "text-on-secondary");
225
+ s = s.replace(/\btext-secondary\b/g, "text-on-secondary");
226
+ } else if (bg === "accent") {
227
+ s = s.replace(/\btext-on-surface(?:-muted)?\b/g, "text-on-accent");
228
+ s = s.replace(/\btext-on-primary\b/g, "text-on-accent");
229
+ s = s.replace(/\btext-on-secondary\b/g, "text-on-accent");
230
+ s = s.replace(/\btext-accent\b/g, "text-on-accent");
231
+ } else if (bg === "surface-deep") {
232
+ s = s.replace(/\btext-on-surface(?!-deep)(?:-muted)?\b/g, "text-on-surface-deep");
233
+ s = s.replace(/\btext-on-primary\b/g, "text-on-surface-deep");
234
+ s = s.replace(/\btext-on-secondary\b/g, "text-on-surface-deep");
235
+ s = s.replace(/\btext-on-accent\b/g, "text-on-surface-deep");
236
+ } else {
237
+ s = s.replace(/\btext-on-primary\b/g, "text-on-surface");
238
+ s = s.replace(/\btext-on-secondary\b/g, "text-on-surface");
239
+ s = s.replace(/\btext-on-accent\b/g, "text-on-surface");
240
+ }
241
+ return s;
242
+ }
243
+ function ancestorAwareTextPass(html) {
244
+ try {
245
+ const tagRe = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)\b([^>]*?)(\/?)>/g;
246
+ const stack = [];
247
+ let out = "";
248
+ let lastIdx = 0;
249
+ let m;
250
+ while ((m = tagRe.exec(html)) !== null) {
251
+ const [full, slash, tagName, attrs, selfCloseSlash] = m;
252
+ out += html.slice(lastIdx, m.index);
253
+ lastIdx = m.index + full.length;
254
+ if (slash === "/") {
255
+ stack.pop();
256
+ out += full;
257
+ continue;
258
+ }
259
+ const classMatch = attrs.match(/\bclass="([^"]*)"/);
260
+ const ownBg = classMatch ? detectBgFamily(classMatch[1]) : null;
261
+ const effective = ownBg ?? effectiveBg(stack);
262
+ let newAttrs = attrs;
263
+ if (classMatch) {
264
+ const fixed = fixTextClassesForBg(classMatch[1], effective);
265
+ if (fixed !== classMatch[1]) {
266
+ newAttrs = attrs.replace(/\bclass="[^"]*"/, `class="${fixed}"`);
267
+ }
268
+ }
269
+ out += `<${tagName}${newAttrs}${selfCloseSlash}>`;
270
+ const isVoid = VOID_TAGS.has(tagName.toLowerCase()) || selfCloseSlash === "/";
271
+ if (!isVoid) stack.push(ownBg);
272
+ }
273
+ out += html.slice(lastIdx);
274
+ out = out.replace(/\btext-on-surface-LIGHT\b/g, "text-on-surface").replace(/\btext-on-surface-DARK\b/g, "text-on-surface").replace(/\btext-on-surface-MUTED\b/g, "text-on-surface-muted");
275
+ return out;
276
+ } catch {
277
+ return html.replace(/\btext-on-surface-LIGHT\b/g, "text-on-surface").replace(/\btext-on-surface-DARK\b/g, "text-on-surface").replace(/\btext-on-surface-MUTED\b/g, "text-on-surface-muted");
278
+ }
279
+ }
280
+ function arbitraryValueReplacements(html, themeColors) {
281
+ let s = html;
282
+ const bgUtilGroup = BG_LIKE_UTILS.join("|");
283
+ const bgPattern = new RegExp(
284
+ `(?<![A-Za-z0-9_-])((?:[a-z-]+:)*)(${bgUtilGroup})-\\[#([0-9a-fA-F]{3,8})\\](\\/\\d{1,3})?(?![A-Za-z0-9_-])`,
285
+ "g"
286
+ );
287
+ s = s.replace(bgPattern, (_m, variants, util, hex, opacity) => {
288
+ const role = hexToRole(hex, themeColors);
289
+ const op = opacity || "";
290
+ return `${variants}${util}-${role}${op}`;
291
+ });
292
+ const textPattern = /(?<![A-Za-z0-9_-])((?:[a-z-]+:)*)text-\[#([0-9a-fA-F]{3,8})\](\/\d{1,3})?(?![A-Za-z0-9_-])/g;
293
+ s = s.replace(textPattern, (_m, variants, hex, opacity) => {
294
+ const op = opacity || "";
295
+ if (themeColors) {
296
+ const role = hexToRole(hex, themeColors);
297
+ if (role === "primary" || role === "secondary" || role === "accent") {
298
+ return `${variants}text-${role}${op}`;
299
+ }
300
+ }
301
+ return `${variants}text-on-surface-MUTED${op}`;
302
+ });
303
+ return s;
304
+ }
305
+ function sanitizeSemanticColors(html, themeColors) {
306
+ let result = html;
307
+ for (const [pattern, replacement] of neutralReplacements) {
308
+ result = result.replace(pattern, replacement);
309
+ }
310
+ for (const [pattern, replacement] of textSeedReplacements) {
311
+ result = result.replace(pattern, replacement);
312
+ }
313
+ result = arbitraryValueReplacements(result, themeColors);
314
+ const hasSemanticClasses = /\b(?:bg-primary|bg-secondary|bg-accent|bg-surface)\b/.test(result);
315
+ if (!hasSemanticClasses) {
316
+ for (const [pattern, replacer] of chromaticReplacements) {
317
+ result = result.replace(pattern, replacer);
318
+ }
319
+ }
320
+ result = ancestorAwareTextPass(result);
321
+ return result;
322
+ }
323
+
324
+ // src/images/pexels.ts
325
+ async function searchImage(query, apiKey) {
326
+ const key = apiKey || process.env.PEXELS_API_KEY;
327
+ if (!key) return null;
328
+ try {
329
+ const res = await fetch(
330
+ `https://api.pexels.com/v1/search?query=${encodeURIComponent(query)}&per_page=5&orientation=landscape&locale=en-US`,
331
+ { headers: { Authorization: key } }
332
+ );
333
+ if (!res.ok) {
334
+ console.warn(`[pexels] ${res.status} for "${query}", trying unsplash fallback`);
335
+ return searchUnsplash(query);
336
+ }
337
+ const data = await res.json();
338
+ const photos = data.photos;
339
+ if (!photos || photos.length === 0) {
340
+ console.warn(`[pexels] 0 results for "${query}"`);
341
+ return null;
342
+ }
343
+ const photo = photos[Math.floor(Math.random() * photos.length)];
344
+ return {
345
+ url: photo.src.large,
346
+ photographer: photo.photographer,
347
+ alt: photo.alt || query
348
+ };
349
+ } catch {
350
+ return searchUnsplash(query);
351
+ }
352
+ }
353
+ async function searchUnsplash(query) {
354
+ try {
355
+ const res = await fetch(
356
+ `https://unsplash.com/napi/search/photos?query=${encodeURIComponent(query)}&per_page=5&orientation=landscape`
357
+ );
358
+ if (!res.ok) return null;
359
+ const data = await res.json();
360
+ const results = data.results;
361
+ if (!results || results.length === 0) return null;
362
+ const photo = results[Math.floor(Math.random() * results.length)];
363
+ return {
364
+ url: photo.urls?.regular || photo.urls?.small,
365
+ photographer: photo.user?.name || "Unsplash",
366
+ alt: photo.alt_description || query
367
+ };
368
+ } catch {
369
+ return null;
370
+ }
371
+ }
372
+
373
+ // src/images/dalleImages.ts
374
+ async function generateImage(query, openaiApiKey) {
375
+ const res = await fetch("https://api.openai.com/v1/images/generations", {
376
+ method: "POST",
377
+ headers: {
378
+ Authorization: `Bearer ${openaiApiKey}`,
379
+ "Content-Type": "application/json"
380
+ },
381
+ body: JSON.stringify({
382
+ model: "dall-e-3",
383
+ prompt: query,
384
+ n: 1,
385
+ size: "1792x1024"
386
+ })
387
+ });
388
+ if (!res.ok) {
389
+ const err = await res.text().catch(() => "Unknown error");
390
+ throw new Error(`DALL-E API error ${res.status}: ${err}`);
391
+ }
392
+ const data = await res.json();
393
+ return data.data[0].url;
394
+ }
395
+
396
+ // src/images/enrichImages.ts
397
+ var FAKE_DOMAINS = [
398
+ "images.unsplash.com",
399
+ "unsplash.com",
400
+ "via.placeholder.com",
401
+ "placeholder.com",
402
+ "placehold.co",
403
+ "placehold.it",
404
+ "placekitten.com",
405
+ "picsum.photos",
406
+ "loremflickr.com",
407
+ "source.unsplash.com",
408
+ "dummyimage.com",
409
+ "fakeimg.pl",
410
+ "example.com",
411
+ "img.freepik.com",
412
+ "cdn.pixabay.com"
413
+ ];
414
+ function findImageSlots(html) {
415
+ const matches = [];
416
+ const seen = /* @__PURE__ */ new Set();
417
+ const diqRegex = /<img\s[^>]*data-image-query=["']([^"']+)["'][^>]*>/gi;
418
+ let m;
419
+ while ((m = diqRegex.exec(html)) !== null) {
420
+ const fullTag = m[0];
421
+ const query = m[1];
422
+ if (seen.has(query)) continue;
423
+ seen.add(query);
424
+ const cleanedTag = fullTag.replace(/\ssrc=["'][^"']*["']/, "").replace(/\sdata-image-query=["'][^"']*["']/, "");
425
+ const replaceTag = cleanedTag.replace(
426
+ /^<img/,
427
+ `<img src="{url}" data-enriched="true"`
428
+ );
429
+ matches.push({
430
+ query,
431
+ searchStr: fullTag,
432
+ replaceStr: replaceTag
433
+ });
434
+ }
435
+ const imgRegex = /<img\s[^>]*src="(https?:\/\/[^"]+)"[^>]*>/gi;
436
+ while ((m = imgRegex.exec(html)) !== null) {
437
+ const fullTag = m[0];
438
+ const srcUrl = m[1];
439
+ if (fullTag.includes("data-enriched")) continue;
440
+ if (srcUrl.includes("pexels.com")) continue;
441
+ if (seen.has(srcUrl)) continue;
442
+ let isFake = false;
443
+ try {
444
+ const domain = new URL(srcUrl).hostname;
445
+ isFake = FAKE_DOMAINS.some((d) => domain.includes(d));
446
+ } catch {
447
+ isFake = true;
448
448
  }
449
+ if (!isFake) continue;
450
+ const altMatch = fullTag.match(/alt="([^"]*?)"/);
451
+ let query = altMatch?.[1]?.trim() || "";
452
+ if (!query) {
453
+ try {
454
+ const path = new URL(srcUrl).pathname;
455
+ const words = path.replace(/[^a-zA-Z]/g, " ").split(/\s+/).filter((w) => w.length > 2).slice(0, 4).join(" ");
456
+ if (words.length > 3) query = words;
457
+ } catch {
458
+ }
459
+ }
460
+ if (!query) query = "professional website hero image";
461
+ seen.add(srcUrl);
462
+ matches.push({
463
+ query,
464
+ searchStr: `src="${srcUrl}"`,
465
+ replaceStr: `src="{url}" data-enriched="true"`
466
+ });
449
467
  }
450
- return null;
451
- }
452
- function effectiveBg(stack) {
453
- for (let i = stack.length - 1; i >= 0; i--) {
454
- const v = stack[i];
455
- if (v !== null) return v;
456
- }
457
- return "surface";
458
- }
459
- function onClass(bg) {
460
- return `text-on-${bg}`;
461
- }
462
- function onMutedClass(bg) {
463
- if (bg === "surface") return "text-on-surface-muted";
464
- if (bg === "surface-deep") return "text-on-surface-deep";
465
- return `text-on-${bg}`;
468
+ return matches;
466
469
  }
467
- function fixTextClassesForBg(classStr, bg) {
468
- let s = classStr;
469
- const on = onClass(bg);
470
- const onMuted = onMutedClass(bg);
471
- s = s.replace(/\btext-on-surface-LIGHT\b/g, on);
472
- s = s.replace(/\btext-on-surface-DARK\b/g, on);
473
- s = s.replace(/\btext-on-surface-MUTED\b/g, onMuted);
474
- if (bg === "primary") {
475
- s = s.replace(/\btext-on-surface(?:-muted)?\b/g, "text-on-primary");
476
- s = s.replace(/\btext-on-secondary\b/g, "text-on-primary");
477
- s = s.replace(/\btext-on-accent\b/g, "text-on-primary");
478
- s = s.replace(/\btext-primary(?!-(?:light|dark))\b/g, "text-on-primary");
479
- } else if (bg === "secondary") {
480
- s = s.replace(/\btext-on-surface(?:-muted)?\b/g, "text-on-secondary");
481
- s = s.replace(/\btext-on-primary\b/g, "text-on-secondary");
482
- s = s.replace(/\btext-on-accent\b/g, "text-on-secondary");
483
- s = s.replace(/\btext-secondary\b/g, "text-on-secondary");
484
- } else if (bg === "accent") {
485
- s = s.replace(/\btext-on-surface(?:-muted)?\b/g, "text-on-accent");
486
- s = s.replace(/\btext-on-primary\b/g, "text-on-accent");
487
- s = s.replace(/\btext-on-secondary\b/g, "text-on-accent");
488
- s = s.replace(/\btext-accent\b/g, "text-on-accent");
489
- } else if (bg === "surface-deep") {
490
- s = s.replace(/\btext-on-surface(?!-deep)(?:-muted)?\b/g, "text-on-surface-deep");
491
- s = s.replace(/\btext-on-primary\b/g, "text-on-surface-deep");
492
- s = s.replace(/\btext-on-secondary\b/g, "text-on-surface-deep");
493
- s = s.replace(/\btext-on-accent\b/g, "text-on-surface-deep");
470
+ async function enrichImages(html, pexelsApiKeyOrOpts, openaiApiKey) {
471
+ let opts;
472
+ if (typeof pexelsApiKeyOrOpts === "object" && pexelsApiKeyOrOpts !== null) {
473
+ opts = pexelsApiKeyOrOpts;
494
474
  } else {
495
- s = s.replace(/\btext-on-primary\b/g, "text-on-surface");
496
- s = s.replace(/\btext-on-secondary\b/g, "text-on-surface");
497
- s = s.replace(/\btext-on-accent\b/g, "text-on-surface");
475
+ opts = { pexelsApiKey: pexelsApiKeyOrOpts, openaiApiKey };
498
476
  }
499
- return s;
500
- }
501
- function ancestorAwareTextPass(html) {
502
- try {
503
- const tagRe = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)\b([^>]*?)(\/?)>/g;
504
- const stack = [];
505
- let out = "";
506
- let lastIdx = 0;
507
- let m;
508
- while ((m = tagRe.exec(html)) !== null) {
509
- const [full, slash, tagName, attrs, selfCloseSlash] = m;
510
- out += html.slice(lastIdx, m.index);
511
- lastIdx = m.index + full.length;
512
- if (slash === "/") {
513
- stack.pop();
514
- out += full;
515
- continue;
477
+ const slots = findImageSlots(html);
478
+ if (slots.length === 0) return html;
479
+ const resolved = await Promise.allSettled(
480
+ slots.map(async (slot) => {
481
+ let url = null;
482
+ if (opts.pexelsApiKey) {
483
+ const img = await searchImage(slot.query, opts.pexelsApiKey).catch(() => null);
484
+ url = img?.url || null;
516
485
  }
517
- const classMatch = attrs.match(/\bclass="([^"]*)"/);
518
- const ownBg = classMatch ? detectBgFamily(classMatch[1]) : null;
519
- const effective = ownBg ?? effectiveBg(stack);
520
- let newAttrs = attrs;
521
- if (classMatch) {
522
- const fixed = fixTextClassesForBg(classMatch[1], effective);
523
- if (fixed !== classMatch[1]) {
524
- newAttrs = attrs.replace(/\bclass="[^"]*"/, `class="${fixed}"`);
486
+ if (!url && opts.openaiApiKey) {
487
+ try {
488
+ const tempUrl = await generateImage(slot.query, opts.openaiApiKey);
489
+ url = opts.persistImage ? await opts.persistImage(tempUrl, slot.query) : tempUrl;
490
+ } catch (e) {
491
+ console.warn(`[dalle] failed for "${slot.query}":`, e);
525
492
  }
526
493
  }
527
- out += `<${tagName}${newAttrs}${selfCloseSlash}>`;
528
- const isVoid = VOID_TAGS.has(tagName.toLowerCase()) || selfCloseSlash === "/";
529
- if (!isVoid) stack.push(ownBg);
530
- }
531
- out += html.slice(lastIdx);
532
- out = out.replace(/\btext-on-surface-LIGHT\b/g, "text-on-surface").replace(/\btext-on-surface-DARK\b/g, "text-on-surface").replace(/\btext-on-surface-MUTED\b/g, "text-on-surface-muted");
533
- return out;
534
- } catch {
535
- return html.replace(/\btext-on-surface-LIGHT\b/g, "text-on-surface").replace(/\btext-on-surface-DARK\b/g, "text-on-surface").replace(/\btext-on-surface-MUTED\b/g, "text-on-surface-muted");
536
- }
537
- }
538
- function arbitraryValueReplacements(html, themeColors) {
539
- let s = html;
540
- const bgUtilGroup = BG_LIKE_UTILS.join("|");
541
- const bgPattern = new RegExp(
542
- `(?<![A-Za-z0-9_-])((?:[a-z-]+:)*)(${bgUtilGroup})-\\[#([0-9a-fA-F]{3,8})\\](\\/\\d{1,3})?(?![A-Za-z0-9_-])`,
543
- "g"
494
+ url ??= `https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(slot.query.slice(0, 30))}`;
495
+ return { slot, url };
496
+ })
544
497
  );
545
- s = s.replace(bgPattern, (_m, variants, util, hex, opacity) => {
546
- const role = hexToRole(hex, themeColors);
547
- const op = opacity || "";
548
- return `${variants}${util}-${role}${op}`;
549
- });
550
- const textPattern = /(?<![A-Za-z0-9_-])((?:[a-z-]+:)*)text-\[#([0-9a-fA-F]{3,8})\](\/\d{1,3})?(?![A-Za-z0-9_-])/g;
551
- s = s.replace(textPattern, (_m, variants, hex, opacity) => {
552
- const op = opacity || "";
553
- if (themeColors) {
554
- const role = hexToRole(hex, themeColors);
555
- if (role === "primary" || role === "secondary" || role === "accent") {
556
- return `${variants}text-${role}${op}`;
557
- }
498
+ let result = html;
499
+ for (const r of resolved) {
500
+ if (r.status === "fulfilled" && r.value) {
501
+ const { slot, url } = r.value;
502
+ const replacement = slot.replaceStr.replace("{url}", url);
503
+ result = result.replaceAll(slot.searchStr, replacement);
558
504
  }
559
- return `${variants}text-on-surface-MUTED${op}`;
505
+ }
506
+ result = result.replace(/<img\s(?![^>]*\bsrc=)([^>]*?)>/gi, (_match, attrs) => {
507
+ const altMatch = attrs.match(/alt="([^"]*?)"/);
508
+ const query = altMatch?.[1] || "professional image";
509
+ return `<img src="https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(query.slice(0, 30))}" ${attrs}>`;
560
510
  });
561
- return s;
511
+ return result;
562
512
  }
563
- function sanitizeSemanticColors(html, themeColors) {
564
- let result = html;
565
- for (const [pattern, replacement] of neutralReplacements) {
566
- result = result.replace(pattern, replacement);
513
+
514
+ // src/images/svgGenerator.ts
515
+ import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
516
+ import { generateText } from "ai";
517
+
518
+ // src/streamCore.ts
519
+ import { streamText } from "ai";
520
+ import { createAnthropic } from "@ai-sdk/anthropic";
521
+ import { nanoid } from "nanoid";
522
+
523
+ // src/images/enrichIcons.ts
524
+ var ICON_PREFIXES = ["lucide", "heroicons", "material-symbols"];
525
+ var iconCache = /* @__PURE__ */ new Map();
526
+ function findIconSlots(html) {
527
+ const matches = [];
528
+ const regex = /<span\s[^>]*data-icon-query="([^"]+)"[^>]*><\/span>/gi;
529
+ let m;
530
+ while ((m = regex.exec(html)) !== null) {
531
+ matches.push({ query: m[1], fullMatch: m[0] });
567
532
  }
568
- for (const [pattern, replacement] of textSeedReplacements) {
569
- result = result.replace(pattern, replacement);
533
+ return matches;
534
+ }
535
+ async function fetchIcon(name) {
536
+ if (iconCache.has(name)) return iconCache.get(name);
537
+ for (const prefix of ICON_PREFIXES) {
538
+ try {
539
+ const url = `https://api.iconify.design/${prefix}/${name}.svg?height=1em&color=currentColor`;
540
+ const res = await fetch(url);
541
+ if (res.ok) {
542
+ const svg = await res.text();
543
+ if (svg.startsWith("<svg")) {
544
+ iconCache.set(name, svg);
545
+ return svg;
546
+ }
547
+ }
548
+ } catch {
549
+ }
570
550
  }
571
- result = arbitraryValueReplacements(result, themeColors);
572
- const hasSemanticClasses = /\b(?:bg-primary|bg-secondary|bg-accent|bg-surface)\b/.test(result);
573
- if (!hasSemanticClasses) {
574
- for (const [pattern, replacer] of chromaticReplacements) {
575
- result = result.replace(pattern, replacer);
551
+ iconCache.set(name, null);
552
+ return null;
553
+ }
554
+ async function enrichSectionIcons(html) {
555
+ const slots = findIconSlots(html);
556
+ if (slots.length === 0) return html;
557
+ const uniqueQueries = [...new Set(slots.map((s) => s.query))];
558
+ const resolved = await Promise.allSettled(
559
+ uniqueQueries.map(async (query) => {
560
+ const svg = await fetchIcon(query);
561
+ return { query, svg };
562
+ })
563
+ );
564
+ const svgMap = /* @__PURE__ */ new Map();
565
+ for (const r of resolved) {
566
+ if (r.status === "fulfilled" && r.value.svg) {
567
+ svgMap.set(r.value.query, r.value.svg);
576
568
  }
577
569
  }
578
- result = ancestorAwareTextPass(result);
570
+ let result = html;
571
+ for (const slot of slots) {
572
+ const svg = svgMap.get(slot.query);
573
+ if (!svg) continue;
574
+ const classMatch = slot.fullMatch.match(/class="([^"]*)"/);
575
+ const classes = classMatch?.[1] || "inline-block w-5 h-5";
576
+ const svgWithClasses = svg.replace("<svg", `<svg class="${classes}"`);
577
+ result = result.replace(slot.fullMatch, svgWithClasses);
578
+ }
579
579
  return result;
580
580
  }
581
581
 
@@ -1037,6 +1037,7 @@ async function generateSvg(prompt, anthropicApiKey, options) {
1037
1037
  }
1038
1038
 
1039
1039
  export {
1040
+ sanitizeSemanticColors,
1040
1041
  searchImage,
1041
1042
  generateImage,
1042
1043
  findImageSlots,
@@ -1044,7 +1045,6 @@ export {
1044
1045
  generateSvg,
1045
1046
  findIconSlots,
1046
1047
  enrichSectionIcons,
1047
- sanitizeSemanticColors,
1048
1048
  currentDateLine,
1049
1049
  resolveModel,
1050
1050
  dataUrlToImagePart,
@@ -1056,4 +1056,4 @@ export {
1056
1056
  enrichSectionIconSlots,
1057
1057
  streamGenerate
1058
1058
  };
1059
- //# sourceMappingURL=chunk-DCLDOBYZ.js.map
1059
+ //# sourceMappingURL=chunk-QJZQ56GM.js.map