@easybits.cloud/html-tailwind-generator 0.2.144 → 0.2.145
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-UQ6THF6E.js → chunk-4NPOG74J.js} +2 -2
- package/dist/{chunk-DJUYMGSU.js → chunk-A63UO3W2.js} +2 -2
- package/dist/{chunk-4EYTBL6J.js → chunk-COOMU3PD.js} +3 -3
- package/dist/{chunk-DCLDOBYZ.js → chunk-QJZQ56GM.js} +380 -380
- package/dist/chunk-QJZQ56GM.js.map +1 -0
- package/dist/{chunk-JNSVHTFN.js → chunk-XH22BH4A.js} +2 -2
- package/dist/directions.js +2 -2
- package/dist/generate.js +2 -2
- package/dist/generateDocument.js +3 -3
- package/dist/images.js +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +7 -5
- package/dist/refine.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-DCLDOBYZ.js.map +0 -1
- /package/dist/{chunk-UQ6THF6E.js.map → chunk-4NPOG74J.js.map} +0 -0
- /package/dist/{chunk-DJUYMGSU.js.map → chunk-A63UO3W2.js.map} +0 -0
- /package/dist/{chunk-4EYTBL6J.js.map → chunk-COOMU3PD.js.map} +0 -0
- /package/dist/{chunk-JNSVHTFN.js.map → chunk-XH22BH4A.js.map} +0 -0
|
@@ -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
|
|
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
|
|
468
|
-
let
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
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
|
|
511
|
+
return result;
|
|
562
512
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
569
|
-
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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 =
|
|
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-
|
|
1059
|
+
//# sourceMappingURL=chunk-QJZQ56GM.js.map
|