@easybits.cloud/html-tailwind-generator 0.1.0 → 0.1.1
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/README.md +64 -16
- package/package.json +3 -1
- package/src/generate.ts +29 -10
- package/src/images/dalleImages.ts +29 -0
- package/src/images/enrichImages.ts +11 -3
- package/src/images/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/refine.ts +21 -10
package/README.md
CHANGED
|
@@ -6,12 +6,12 @@ Built and maintained by [EasyBits](https://easybits.cloud).
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **AI Generation** — Streaming landing page creation with Claude
|
|
9
|
+
- **AI Generation** — Streaming landing page creation with Claude or OpenAI GPT-4o
|
|
10
10
|
- **Canvas Editor** — iframe-based preview with click-to-select, inline text editing, section reorder
|
|
11
11
|
- **Floating Toolbar** — AI prompt input, style presets (Minimal, Cards, Bold, Glass, Dark), reference image support
|
|
12
12
|
- **Code Editor** — CodeMirror 6 with HTML syntax, flash highlight on scroll-to-code, format, Cmd+S
|
|
13
13
|
- **Theme System** — 5 preset themes (Neutral, Dark, Slate, Midnight, Warm) + custom multi-color picker
|
|
14
|
-
- **Image Enrichment** — Auto-replace placeholder images with Pexels stock photos
|
|
14
|
+
- **Image Enrichment** — Auto-replace placeholder images with Pexels stock photos or DALL-E generated images
|
|
15
15
|
- **Deploy** — To EasyBits hosting (`slug.easybits.cloud`) or any S3-compatible storage
|
|
16
16
|
|
|
17
17
|
## Install
|
|
@@ -20,16 +20,40 @@ Built and maintained by [EasyBits](https://easybits.cloud).
|
|
|
20
20
|
npm install @easybits.cloud/html-tailwind-generator
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
## Environment Variables
|
|
24
|
+
|
|
25
|
+
All API keys can be set via environment variables instead of passing them explicitly:
|
|
26
|
+
|
|
27
|
+
| Variable | Used by | Description |
|
|
28
|
+
|----------|---------|-------------|
|
|
29
|
+
| `ANTHROPIC_API_KEY` | `generateLanding`, `refineLanding` | Anthropic API key (auto-read by `@ai-sdk/anthropic`) |
|
|
30
|
+
| `PEXELS_API_KEY` | `enrichImages`, `searchImage` | Pexels stock photo API key |
|
|
31
|
+
|
|
32
|
+
OpenAI keys must be passed explicitly via `openaiApiKey` — there is no env var fallback (to avoid accidentally mixing providers).
|
|
33
|
+
|
|
23
34
|
## Quick Start
|
|
24
35
|
|
|
25
36
|
### Generate a landing page (server-side)
|
|
26
37
|
|
|
38
|
+
The simplest usage — just set `ANTHROPIC_API_KEY` and `PEXELS_API_KEY` in your `.env`:
|
|
39
|
+
|
|
27
40
|
```ts
|
|
28
41
|
import { generateLanding } from "@easybits.cloud/html-tailwind-generator/generate";
|
|
29
42
|
|
|
43
|
+
const sections = await generateLanding({
|
|
44
|
+
prompt: "SaaS de gestión de proyectos para equipos remotos",
|
|
45
|
+
onSection(section) {
|
|
46
|
+
console.log("New section:", section.label);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You can also pass keys explicitly if you prefer:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
30
54
|
const sections = await generateLanding({
|
|
31
55
|
anthropicApiKey: "sk-ant-...",
|
|
32
|
-
pexelsApiKey: "...",
|
|
56
|
+
pexelsApiKey: "...",
|
|
33
57
|
prompt: "SaaS de gestión de proyectos para equipos remotos",
|
|
34
58
|
onSection(section) {
|
|
35
59
|
console.log("New section:", section.label);
|
|
@@ -38,17 +62,33 @@ const sections = await generateLanding({
|
|
|
38
62
|
console.log("Images enriched for", id);
|
|
39
63
|
},
|
|
40
64
|
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Generate with OpenAI + DALL-E
|
|
41
68
|
|
|
42
|
-
|
|
69
|
+
Pass `openaiApiKey` to use GPT-4o for text and DALL-E 3 for images (one key for everything):
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const sections = await generateLanding({
|
|
73
|
+
openaiApiKey: "sk-...",
|
|
74
|
+
prompt: "SaaS de gestión de proyectos para equipos remotos",
|
|
75
|
+
onSection(section) {
|
|
76
|
+
console.log("New section:", section.label);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
43
79
|
```
|
|
44
80
|
|
|
81
|
+
When `openaiApiKey` is provided:
|
|
82
|
+
- **Generation** uses GPT-4o (or custom `model`)
|
|
83
|
+
- **Refinement** uses GPT-4o-mini (GPT-4o when `referenceImage` is provided)
|
|
84
|
+
- **Images** use DALL-E 3 with Pexels as fallback
|
|
85
|
+
|
|
45
86
|
### Refine a section
|
|
46
87
|
|
|
47
88
|
```ts
|
|
48
89
|
import { refineLanding } from "@easybits.cloud/html-tailwind-generator/refine";
|
|
49
90
|
|
|
50
91
|
const html = await refineLanding({
|
|
51
|
-
anthropicApiKey: "sk-ant-...",
|
|
52
92
|
currentHtml: sections[0].html,
|
|
53
93
|
instruction: "Make it more minimal with more whitespace",
|
|
54
94
|
onChunk(accumulated) {
|
|
@@ -132,14 +172,16 @@ const url = await deployToS3({
|
|
|
132
172
|
|
|
133
173
|
## Exports
|
|
134
174
|
|
|
135
|
-
| Path |
|
|
136
|
-
|
|
137
|
-
| `@easybits.cloud/html-tailwind-generator` | Everything (
|
|
138
|
-
| `@easybits.cloud/html-tailwind-generator/generate` | `generateLanding`, `extractJsonObjects`, `SYSTEM_PROMPT` |
|
|
139
|
-
| `@easybits.cloud/html-tailwind-generator/refine` | `refineLanding`, `REFINE_SYSTEM` |
|
|
140
|
-
| `@easybits.cloud/html-tailwind-generator/deploy` | `deployToEasyBits`, `deployToS3` |
|
|
141
|
-
| `@easybits.cloud/html-tailwind-generator/images` | `searchImage`, `enrichImages`, `findImageSlots` |
|
|
142
|
-
| `@easybits.cloud/html-tailwind-generator/components` | `Canvas`, `SectionList`, `FloatingToolbar`, `CodeEditor` |
|
|
175
|
+
| Path | Exports |
|
|
176
|
+
|------|---------|
|
|
177
|
+
| `@easybits.cloud/html-tailwind-generator` | Everything below (re-exported for convenience) |
|
|
178
|
+
| `@easybits.cloud/html-tailwind-generator/generate` | `generateLanding`, `extractJsonObjects`, `SYSTEM_PROMPT`, `PROMPT_SUFFIX`, type `GenerateOptions` |
|
|
179
|
+
| `@easybits.cloud/html-tailwind-generator/refine` | `refineLanding`, `REFINE_SYSTEM`, type `RefineOptions` |
|
|
180
|
+
| `@easybits.cloud/html-tailwind-generator/deploy` | `deployToEasyBits`, `deployToS3`, types `DeployToS3Options`, `DeployToEasyBitsOptions` |
|
|
181
|
+
| `@easybits.cloud/html-tailwind-generator/images` | `searchImage`, `enrichImages`, `findImageSlots`, `generateImage`, type `PexelsResult` |
|
|
182
|
+
| `@easybits.cloud/html-tailwind-generator/components` | `Canvas`, `SectionList`, `FloatingToolbar`, `CodeEditor`, type `CanvasHandle` |
|
|
183
|
+
|
|
184
|
+
Types also exported from the root path: `Section3`, `IframeMessage`, `LandingTheme`, `CustomColors`.
|
|
143
185
|
|
|
144
186
|
## Theme System
|
|
145
187
|
|
|
@@ -156,18 +198,24 @@ The generator uses a semantic color system with CSS custom properties:
|
|
|
156
198
|
**Required:**
|
|
157
199
|
- `react` >= 18
|
|
158
200
|
- `ai` >= 4 (Vercel AI SDK)
|
|
159
|
-
- `@ai-sdk/anthropic` >= 3
|
|
201
|
+
- `@ai-sdk/anthropic` >= 3 (for Claude)
|
|
202
|
+
|
|
203
|
+
**Optional (for OpenAI support):**
|
|
204
|
+
- `@ai-sdk/openai` >= 1
|
|
160
205
|
|
|
161
206
|
**Optional (for editor components):**
|
|
162
207
|
- `react-dom`, `react-icons`
|
|
163
|
-
-
|
|
208
|
+
- CodeMirror packages (required if you use `CodeEditor`):
|
|
209
|
+
```bash
|
|
210
|
+
npm install @codemirror/lang-html @codemirror/state @codemirror/theme-one-dark @codemirror/view @codemirror/commands @codemirror/search @codemirror/language @codemirror/autocomplete
|
|
211
|
+
```
|
|
164
212
|
|
|
165
213
|
## TODO
|
|
166
214
|
|
|
167
215
|
> These are planned improvements — contributions welcome for noncommercial use.
|
|
168
216
|
|
|
169
217
|
- [ ] **Inline Tailwind CSS build** — Replace CDN `<script src="tailwindcss.com">` with `@tailwindcss/standalone` or PostCSS to generate only used CSS as `<style>`. Faster load, no external dependency, production-ready.
|
|
170
|
-
- [
|
|
218
|
+
- [x] **DALL-E image generation** — `openaiApiKey` option generates unique images via DALL-E 3 with Pexels fallback.
|
|
171
219
|
- [ ] **tsup build** — Add a proper build step (ESM + CJS + types) for npm publish. Currently exported as raw TypeScript source, which works for monorepo consumers but not for external npm users.
|
|
172
220
|
- [ ] **i18n** — Component labels are in Spanish. Add a `locale` prop or i18n system for English and other languages.
|
|
173
221
|
- [ ] **Tests** — Unit tests for `extractJsonObjects`, `findImageSlots`, `buildDeployHtml`, `buildCustomTheme`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easybits.cloud/html-tailwind-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"react-dom": ">=18",
|
|
23
23
|
"ai": ">=4",
|
|
24
24
|
"@ai-sdk/anthropic": ">=3",
|
|
25
|
+
"@ai-sdk/openai": ">=1",
|
|
25
26
|
"@codemirror/lang-html": ">=6",
|
|
26
27
|
"@codemirror/state": ">=6",
|
|
27
28
|
"@codemirror/theme-one-dark": ">=6",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"@codemirror/language": { "optional": true },
|
|
43
44
|
"@codemirror/autocomplete": { "optional": true },
|
|
44
45
|
"react-icons": { "optional": true },
|
|
46
|
+
"@ai-sdk/openai": { "optional": true },
|
|
45
47
|
"react-dom": { "optional": true }
|
|
46
48
|
},
|
|
47
49
|
"dependencies": {
|
package/src/generate.ts
CHANGED
|
@@ -3,8 +3,21 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
3
3
|
import { nanoid } from "nanoid";
|
|
4
4
|
import { findImageSlots } from "./images/enrichImages";
|
|
5
5
|
import { searchImage } from "./images/pexels";
|
|
6
|
+
import { generateImage } from "./images/dalleImages";
|
|
6
7
|
import type { Section3 } from "./types";
|
|
7
8
|
|
|
9
|
+
function resolveModel(opts: { openaiApiKey?: string; anthropicApiKey?: string; modelId?: string; defaultOpenai: string; defaultAnthropic: string }) {
|
|
10
|
+
if (opts.openaiApiKey) {
|
|
11
|
+
const { createOpenAI } = require("@ai-sdk/openai");
|
|
12
|
+
const openai = createOpenAI({ apiKey: opts.openaiApiKey });
|
|
13
|
+
return openai(opts.modelId || opts.defaultOpenai);
|
|
14
|
+
}
|
|
15
|
+
const anthropic = opts.anthropicApiKey
|
|
16
|
+
? createAnthropic({ apiKey: opts.anthropicApiKey })
|
|
17
|
+
: createAnthropic();
|
|
18
|
+
return anthropic(opts.modelId || opts.defaultAnthropic);
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
export const 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.
|
|
9
22
|
|
|
10
23
|
RULES:
|
|
@@ -113,6 +126,8 @@ export function extractJsonObjects(text: string): [any[], string] {
|
|
|
113
126
|
export interface GenerateOptions {
|
|
114
127
|
/** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var */
|
|
115
128
|
anthropicApiKey?: string;
|
|
129
|
+
/** OpenAI API key. If provided, uses GPT-4o instead of Claude */
|
|
130
|
+
openaiApiKey?: string;
|
|
116
131
|
/** Landing page description prompt */
|
|
117
132
|
prompt: string;
|
|
118
133
|
/** Reference image (base64 data URI) for vision-based generation */
|
|
@@ -121,7 +136,7 @@ export interface GenerateOptions {
|
|
|
121
136
|
extraInstructions?: string;
|
|
122
137
|
/** Custom system prompt (overrides default SYSTEM_PROMPT) */
|
|
123
138
|
systemPrompt?: string;
|
|
124
|
-
/** Model ID (default: claude-sonnet-4-6) */
|
|
139
|
+
/** Model ID (default: gpt-4o for OpenAI, claude-sonnet-4-6 for Anthropic) */
|
|
125
140
|
model?: string;
|
|
126
141
|
/** Pexels API key for image enrichment. Falls back to PEXELS_API_KEY env var */
|
|
127
142
|
pexelsApiKey?: string;
|
|
@@ -142,11 +157,12 @@ export interface GenerateOptions {
|
|
|
142
157
|
export async function generateLanding(options: GenerateOptions): Promise<Section3[]> {
|
|
143
158
|
const {
|
|
144
159
|
anthropicApiKey,
|
|
160
|
+
openaiApiKey,
|
|
145
161
|
prompt,
|
|
146
162
|
referenceImage,
|
|
147
163
|
extraInstructions,
|
|
148
164
|
systemPrompt = SYSTEM_PROMPT,
|
|
149
|
-
model: modelId
|
|
165
|
+
model: modelId,
|
|
150
166
|
pexelsApiKey,
|
|
151
167
|
onSection,
|
|
152
168
|
onImageUpdate,
|
|
@@ -154,11 +170,7 @@ export async function generateLanding(options: GenerateOptions): Promise<Section
|
|
|
154
170
|
onError,
|
|
155
171
|
} = options;
|
|
156
172
|
|
|
157
|
-
const
|
|
158
|
-
? createAnthropic({ apiKey: anthropicApiKey })
|
|
159
|
-
: createAnthropic();
|
|
160
|
-
|
|
161
|
-
const model = anthropic(modelId);
|
|
173
|
+
const model = resolveModel({ openaiApiKey, anthropicApiKey, modelId, defaultOpenai: "gpt-4o", defaultAnthropic: "claude-sonnet-4-6" });
|
|
162
174
|
|
|
163
175
|
// Build prompt content (supports multimodal with reference image)
|
|
164
176
|
const extra = extraInstructions ? `\nAdditional instructions: ${extraInstructions}` : "";
|
|
@@ -207,7 +219,7 @@ export async function generateLanding(options: GenerateOptions): Promise<Section
|
|
|
207
219
|
allSections.push(section);
|
|
208
220
|
onSection?.(section);
|
|
209
221
|
|
|
210
|
-
// Enrich images
|
|
222
|
+
// Enrich images (DALL-E if openaiApiKey, otherwise Pexels)
|
|
211
223
|
const slots = findImageSlots(section.html);
|
|
212
224
|
if (slots.length > 0) {
|
|
213
225
|
const sectionRef = section;
|
|
@@ -216,8 +228,15 @@ export async function generateLanding(options: GenerateOptions): Promise<Section
|
|
|
216
228
|
(async () => {
|
|
217
229
|
const results = await Promise.allSettled(
|
|
218
230
|
slotsSnapshot.map(async (slot) => {
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
let url: string | null = null;
|
|
232
|
+
if (openaiApiKey) {
|
|
233
|
+
url = await generateImage(slot.query, openaiApiKey).catch(() => null);
|
|
234
|
+
}
|
|
235
|
+
if (!url) {
|
|
236
|
+
const img = await searchImage(slot.query, pexelsApiKey).catch(() => null);
|
|
237
|
+
url = img?.url || null;
|
|
238
|
+
}
|
|
239
|
+
url ??= `https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(slot.query.slice(0, 30))}`;
|
|
221
240
|
return { slot, url };
|
|
222
241
|
})
|
|
223
242
|
);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate an image using DALL-E 3 API.
|
|
3
|
+
*/
|
|
4
|
+
export async function generateImage(
|
|
5
|
+
query: string,
|
|
6
|
+
openaiApiKey: string
|
|
7
|
+
): Promise<string> {
|
|
8
|
+
const res = await fetch("https://api.openai.com/v1/images/generations", {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${openaiApiKey}`,
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify({
|
|
15
|
+
model: "dall-e-3",
|
|
16
|
+
prompt: query,
|
|
17
|
+
n: 1,
|
|
18
|
+
size: "1792x1024",
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
const err = await res.text().catch(() => "Unknown error");
|
|
24
|
+
throw new Error(`DALL-E API error ${res.status}: ${err}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
return data.data[0].url;
|
|
29
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { searchImage } from "./pexels";
|
|
2
|
+
import { generateImage } from "./dalleImages";
|
|
2
3
|
|
|
3
4
|
interface ImageMatch {
|
|
4
5
|
query: string;
|
|
@@ -102,14 +103,21 @@ export function findImageSlots(html: string): ImageMatch[] {
|
|
|
102
103
|
/**
|
|
103
104
|
* Enrich all images in an HTML string with Pexels photos.
|
|
104
105
|
*/
|
|
105
|
-
export async function enrichImages(html: string, pexelsApiKey?: string): Promise<string> {
|
|
106
|
+
export async function enrichImages(html: string, pexelsApiKey?: string, openaiApiKey?: string): Promise<string> {
|
|
106
107
|
const slots = findImageSlots(html);
|
|
107
108
|
if (slots.length === 0) return html;
|
|
108
109
|
|
|
109
110
|
let result = html;
|
|
110
111
|
const promises = slots.map(async (slot) => {
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
let url: string | null = null;
|
|
113
|
+
if (openaiApiKey) {
|
|
114
|
+
url = await generateImage(slot.query, openaiApiKey).catch(() => null);
|
|
115
|
+
}
|
|
116
|
+
if (!url) {
|
|
117
|
+
const img = await searchImage(slot.query, pexelsApiKey).catch(() => null);
|
|
118
|
+
url = img?.url || null;
|
|
119
|
+
}
|
|
120
|
+
url ??= `https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(slot.query.slice(0, 30))}`;
|
|
113
121
|
const replacement = slot.replaceStr.replace("{url}", url);
|
|
114
122
|
result = result.replaceAll(slot.searchStr, replacement);
|
|
115
123
|
});
|
package/src/images/index.ts
CHANGED
package/src/index.ts
CHANGED
package/src/refine.ts
CHANGED
|
@@ -2,6 +2,18 @@ import { streamText } from "ai";
|
|
|
2
2
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3
3
|
import { enrichImages } from "./images/enrichImages";
|
|
4
4
|
|
|
5
|
+
function resolveModel(opts: { openaiApiKey?: string; anthropicApiKey?: string; modelId?: string; defaultOpenai: string; defaultAnthropic: string }) {
|
|
6
|
+
if (opts.openaiApiKey) {
|
|
7
|
+
const { createOpenAI } = require("@ai-sdk/openai");
|
|
8
|
+
const openai = createOpenAI({ apiKey: opts.openaiApiKey });
|
|
9
|
+
return openai(opts.modelId || opts.defaultOpenai);
|
|
10
|
+
}
|
|
11
|
+
const anthropic = opts.anthropicApiKey
|
|
12
|
+
? createAnthropic({ apiKey: opts.anthropicApiKey })
|
|
13
|
+
: createAnthropic();
|
|
14
|
+
return anthropic(opts.modelId || opts.defaultAnthropic);
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
export const REFINE_SYSTEM = `You are an expert HTML/Tailwind CSS developer. You receive the current HTML of a landing page section and a user instruction.
|
|
6
18
|
|
|
7
19
|
RULES:
|
|
@@ -26,6 +38,8 @@ TAILWIND v3 NOTES:
|
|
|
26
38
|
export interface RefineOptions {
|
|
27
39
|
/** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var */
|
|
28
40
|
anthropicApiKey?: string;
|
|
41
|
+
/** OpenAI API key. If provided, uses GPT-4o-mini instead of Claude */
|
|
42
|
+
openaiApiKey?: string;
|
|
29
43
|
/** Current HTML of the section being refined */
|
|
30
44
|
currentHtml: string;
|
|
31
45
|
/** User instruction for refinement */
|
|
@@ -34,7 +48,7 @@ export interface RefineOptions {
|
|
|
34
48
|
referenceImage?: string;
|
|
35
49
|
/** Custom system prompt (overrides default REFINE_SYSTEM) */
|
|
36
50
|
systemPrompt?: string;
|
|
37
|
-
/** Model ID (default:
|
|
51
|
+
/** Model ID (default: gpt-4o-mini/gpt-4o for OpenAI, claude-haiku/claude-sonnet for Anthropic) */
|
|
38
52
|
model?: string;
|
|
39
53
|
/** Pexels API key for image enrichment. Falls back to PEXELS_API_KEY env var */
|
|
40
54
|
pexelsApiKey?: string;
|
|
@@ -53,6 +67,7 @@ export interface RefineOptions {
|
|
|
53
67
|
export async function refineLanding(options: RefineOptions): Promise<string> {
|
|
54
68
|
const {
|
|
55
69
|
anthropicApiKey,
|
|
70
|
+
openaiApiKey,
|
|
56
71
|
currentHtml,
|
|
57
72
|
instruction,
|
|
58
73
|
referenceImage,
|
|
@@ -64,13 +79,9 @@ export async function refineLanding(options: RefineOptions): Promise<string> {
|
|
|
64
79
|
onError,
|
|
65
80
|
} = options;
|
|
66
81
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Use Haiku for speed, Sonnet for vision
|
|
72
|
-
const defaultModel = referenceImage ? "claude-sonnet-4-6" : "claude-haiku-4-5-20251001";
|
|
73
|
-
const model = anthropic(modelId || defaultModel);
|
|
82
|
+
const defaultOpenai = referenceImage ? "gpt-4o" : "gpt-4o-mini";
|
|
83
|
+
const defaultAnthropic = referenceImage ? "claude-sonnet-4-6" : "claude-haiku-4-5-20251001";
|
|
84
|
+
const model = resolveModel({ openaiApiKey, anthropicApiKey, modelId, defaultOpenai, defaultAnthropic });
|
|
74
85
|
|
|
75
86
|
// Build content (supports multimodal with reference image)
|
|
76
87
|
const content: any[] = [];
|
|
@@ -102,8 +113,8 @@ export async function refineLanding(options: RefineOptions): Promise<string> {
|
|
|
102
113
|
html = html.replace(/^```(?:html|xml)?\s*/, "").replace(/\s*```$/, "");
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
// Enrich images
|
|
106
|
-
html = await enrichImages(html, pexelsApiKey);
|
|
116
|
+
// Enrich images (DALL-E if openaiApiKey, otherwise Pexels)
|
|
117
|
+
html = await enrichImages(html, pexelsApiKey, openaiApiKey);
|
|
107
118
|
|
|
108
119
|
onDone?.(html);
|
|
109
120
|
return html;
|