Files
ai-toy-patent-workflow/scripts/generate-style-previews.mjs

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);
});