This commit is contained in:
2026-05-09 18:24:08 +08:00
parent a0ed128bde
commit bc704d0c22
38 changed files with 481 additions and 378 deletions

View File

@@ -14,7 +14,6 @@ const promptsPath = path.join(
);
const defaultOutDir = path.join(repoRoot, 'public', 'puzzle-creation-templates');
const defaultTimeoutMs = 180000;
const pollDelayMs = 3000;
const args = new Map();
for (let index = 2; index < process.argv.length; index += 1) {
@@ -66,15 +65,23 @@ function resolveEnv() {
...process.env,
};
return {
baseUrl: String(loaded.APIMART_BASE_URL || '').trim().replace(/\/+$/u, ''),
apiKey: String(loaded.APIMART_API_KEY || '').trim(),
baseUrl: String(loaded.VECTOR_ENGINE_BASE_URL || '')
.trim()
.replace(/\/+$/u, ''),
apiKey: String(loaded.VECTOR_ENGINE_API_KEY || '').trim(),
timeoutMs: Number.parseInt(
String(loaded.APIMART_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs),
String(loaded.VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs),
10,
),
};
}
function buildVectorEngineImagesGenerationUrl(baseUrl) {
return baseUrl.endsWith('/v1')
? `${baseUrl}/images/generations`
: `${baseUrl}/v1/images/generations`;
}
function buildPrompt(template) {
return [
'请生成一张高清 1:1 方形插画,用作拼图创作模板样例图。',
@@ -124,14 +131,6 @@ function extractBase64Images(payload) {
return values;
}
function extractTaskId(payload) {
const ids = [];
collectStringsByKey(payload, 'task_id', ids);
collectStringsByKey(payload, 'taskId', ids);
collectStringsByKey(payload, 'id', ids);
return ids[0] || null;
}
function inferExtensionFromContentType(contentType) {
const normalized = contentType.split(';')[0]?.trim().toLowerCase();
if (normalized === 'image/png') {
@@ -172,7 +171,7 @@ async function fetchJson(url, options, timeoutMs) {
});
const text = await response.text();
if (!response.ok) {
throw new Error(`APIMart ${response.status}: ${text.slice(0, 600)}`);
throw new Error(`VectorEngine ${response.status}: ${text.slice(0, 600)}`);
}
return JSON.parse(text);
} finally {
@@ -200,52 +199,20 @@ async function downloadUrl(url, timeoutMs) {
}
}
async function waitForTask(env, taskId) {
const deadline = Date.now() + env.timeoutMs;
await new Promise((resolve) => setTimeout(resolve, 10000));
while (Date.now() < deadline) {
const payload = await fetchJson(
`${env.baseUrl}/tasks/${encodeURIComponent(taskId)}`,
{
headers: {
Authorization: `Bearer ${env.apiKey}`,
},
},
env.timeoutMs,
);
const statuses = [];
collectStringsByKey(payload, 'status', statuses);
collectStringsByKey(payload, 'task_status', statuses);
const status = String(statuses[0] || '').trim().toLowerCase();
if (['completed', 'succeeded', 'success'].includes(status)) {
return payload;
}
if (['failed', 'error', 'canceled', 'cancelled', 'unknown'].includes(status)) {
throw new Error(`APIMart task ${taskId} failed: ${JSON.stringify(payload).slice(0, 600)}`);
}
await new Promise((resolve) => setTimeout(resolve, pollDelayMs));
}
throw new Error(`APIMart task ${taskId} timed out`);
}
async function generateOne(env, template, outDir) {
const requestBody = {
model: 'gpt-image-2',
model: 'gpt-image-2-all',
prompt: buildPrompt(template),
n: 1,
official_fallback: true,
size: '1:1',
size: '1024x1024',
};
const payload = await fetchJson(
`${env.baseUrl}/images/generations`,
buildVectorEngineImagesGenerationUrl(env.baseUrl),
{
method: 'POST',
headers: {
Authorization: `Bearer ${env.apiKey}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
@@ -253,12 +220,8 @@ async function generateOne(env, template, outDir) {
env.timeoutMs,
);
const resolvedPayload =
extractImageUrls(payload).length || extractBase64Images(payload).length
? payload
: await waitForTask(env, extractTaskId(payload));
const urls = extractImageUrls(resolvedPayload);
const b64Images = extractBase64Images(resolvedPayload);
const urls = extractImageUrls(payload);
const b64Images = extractBase64Images(payload);
let image;
if (urls[0]) {
@@ -270,7 +233,7 @@ async function generateOne(env, template, outDir) {
extension: inferExtensionFromBytes(bytes),
};
} else {
throw new Error(`APIMart returned no image for ${template.id}`);
throw new Error(`VectorEngine returned no image for ${template.id}`);
}
mkdirSync(outDir, { recursive: true });
@@ -302,11 +265,10 @@ if (dryRun) {
id: template.id,
title: template.title,
body: {
model: 'gpt-image-2',
model: 'gpt-image-2-all',
prompt: buildPrompt(template),
n: 1,
official_fallback: true,
size: '1:1',
size: '1024x1024',
},
})),
},
@@ -322,7 +284,7 @@ if (!env.baseUrl || !env.apiKey) {
console.error(
JSON.stringify({
ok: false,
error: 'Missing APIMART_BASE_URL or APIMART_API_KEY',
error: 'Missing VECTOR_ENGINE_BASE_URL or VECTOR_ENGINE_API_KEY',
hasBaseUrl: Boolean(env.baseUrl),
hasApiKey: Boolean(env.apiKey),
}),