109 lines
4.0 KiB
JavaScript
109 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const root = process.cwd();
|
|
const envPath = path.join(root, '.env.local');
|
|
const outputDir = path.join(root, 'public', 'style-previews');
|
|
|
|
function loadEnvFile(filePath) {
|
|
if (!fs.existsSync(filePath)) return;
|
|
for (const line of fs.readFileSync(filePath, 'utf8').split(/\r?\n/)) {
|
|
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=(.*)\s*$/);
|
|
if (!match) continue;
|
|
const [, key, rawValue] = match;
|
|
if (process.env[key]) continue;
|
|
process.env[key] = rawValue.replace(/^['"]|['"]$/g, '');
|
|
}
|
|
}
|
|
|
|
const styles = [
|
|
{
|
|
id: 'none',
|
|
label: 'neutral toy concept',
|
|
prompt: 'neutral original toy mascot concept preview, small rounded collectible character, clean studio light, no text, no logo, no watermark, square UI thumbnail',
|
|
},
|
|
{
|
|
id: 'plush',
|
|
label: 'plush toy',
|
|
prompt: 'plush toy style preview, soft fuzzy fabric original rounded mascot, stitched details, warm studio light, no text, no logo, no watermark, square UI thumbnail',
|
|
},
|
|
{
|
|
id: 'mecha',
|
|
label: 'mecha toy',
|
|
prompt: 'mecha toy style preview, original rounded robot collectible, polished armor panels, tiny mechanical joints, clean dramatic light, no text, no logo, no watermark, square UI thumbnail',
|
|
},
|
|
{
|
|
id: 'kawaii',
|
|
label: 'kawaii toy',
|
|
prompt: 'kawaii toy style preview, original cute rounded mascot, soft pastel colors, friendly expression, clean studio background, no text, no logo, no watermark, square UI thumbnail',
|
|
},
|
|
{
|
|
id: 'blueprint',
|
|
label: 'patent blueprint',
|
|
prompt: 'patent blueprint style preview, original toy mascot shown as clean white technical line art on deep blue blueprint background, no readable text, no logo, no watermark, square UI thumbnail',
|
|
},
|
|
{
|
|
id: 'cyber',
|
|
label: 'cyberpunk toy',
|
|
prompt: 'cyberpunk toy style preview, original rounded collectible mascot, neon rim light, glossy dark materials, futuristic display glow, no text, no logo, no watermark, square UI thumbnail',
|
|
},
|
|
{
|
|
id: 'minimal',
|
|
label: 'minimal toy',
|
|
prompt: 'minimal toy style preview, original rounded mascot, simple geometric silhouette, restrained colors, premium clean product render, no text, no logo, no watermark, square UI thumbnail',
|
|
},
|
|
];
|
|
|
|
async function imageToBuffer(payload, apiKey) {
|
|
const first = payload?.data?.[0];
|
|
if (!first) throw new Error('missing image data');
|
|
if (first.b64_json) return Buffer.from(first.b64_json, 'base64');
|
|
if (first.url) {
|
|
const res = await fetch(first.url, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
if (!res.ok) throw new Error(`image url fetch ${res.status}: ${await res.text()}`);
|
|
return Buffer.from(await res.arrayBuffer());
|
|
}
|
|
throw new Error('image payload has no b64_json or url');
|
|
}
|
|
|
|
async function main() {
|
|
loadEnvFile(envPath);
|
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
if (!apiKey) throw new Error('OPENAI_API_KEY missing');
|
|
|
|
const apiBase = process.env.GPT_API_BASE || 'https://api.openai.com/v1';
|
|
const model = process.env.GPT_IMAGE_MODEL || 'gpt-image-2';
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
|
|
for (const style of styles) {
|
|
const out = path.join(outputDir, `${style.id}.png`);
|
|
if (fs.existsSync(out) && !process.argv.includes('--force')) {
|
|
console.log(`skip ${style.id}`);
|
|
continue;
|
|
}
|
|
console.log(`generate ${style.id}`);
|
|
const res = await fetch(`${apiBase}/images/generations`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model,
|
|
prompt: style.prompt,
|
|
n: 1,
|
|
size: '1024x1024',
|
|
}),
|
|
});
|
|
if (!res.ok) throw new Error(`GPT image ${style.id} ${res.status}: ${await res.text()}`);
|
|
fs.writeFileSync(out, await imageToBuffer(await res.json(), apiKey));
|
|
}
|
|
}
|
|
|
|
main().catch(error => {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
});
|