前端(Vite + React)、API/worker(Fastify + BullMQ)、api-contracts 契约包、 迁移脚本、测试与自托管 docker-compose 示例。 开源准备:运维脚本与机器信息已拆到私有仓库;公司名、备案号、隐私邮箱改读 publicCompliance.local.json(仓库只保留占位模板);站点地址与客服二维码改为 VITE_ 环境变量;fixtures 中的真实用户标识已匿名化;三个 package.json 标注 Apache-2.0。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
82 lines
3.0 KiB
JavaScript
82 lines
3.0 KiB
JavaScript
import { createServer } from 'node:http';
|
|
|
|
const port = 4400;
|
|
const maxRequestBytes = 1024 * 1024;
|
|
|
|
const server = createServer(async (request, response) => {
|
|
if (request.method === 'GET' && request.url === '/health') {
|
|
writeJson(response, 200, { ok: true });
|
|
return;
|
|
}
|
|
|
|
if (request.method !== 'POST' || request.url !== '/api/v3/images/generations') {
|
|
writeJson(response, 404, { error: { code: 'NOT_FOUND', message: 'Not found' } });
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const payload = JSON.parse(await readBody(request));
|
|
assertSeedreamPayload(payload);
|
|
response.setHeader('x-tt-logid', 'fullstack-seedream-checkpoint-request');
|
|
writeJson(response, 200, {
|
|
model: 'doubao-seedream-5-0-pro-260628',
|
|
created: Math.floor(Date.now() / 1000),
|
|
data: [{
|
|
url: 'https://ark-project.tos-cn-beijing.volces.com/fullstack-checkpoint.png',
|
|
size: '1024x1024',
|
|
}],
|
|
usage: {
|
|
generated_images: 1,
|
|
output_tokens: 12,
|
|
total_tokens: 12,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
writeJson(response, 400, {
|
|
error: {
|
|
code: 'FULLSTACK_SEEDREAM_REQUEST_INVALID',
|
|
message: error instanceof Error ? error.message : 'Invalid request',
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
server.listen(port, '0.0.0.0');
|
|
|
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
process.once(signal, () => server.close(() => process.exit(0)));
|
|
}
|
|
|
|
async function readBody(request) {
|
|
const chunks = [];
|
|
let bytes = 0;
|
|
for await (const chunk of request) {
|
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
bytes += buffer.length;
|
|
if (bytes > maxRequestBytes) throw new Error('Request body too large');
|
|
chunks.push(buffer);
|
|
}
|
|
return Buffer.concat(chunks).toString('utf8');
|
|
}
|
|
|
|
function assertSeedreamPayload(payload) {
|
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) throw new Error('Payload must be an object');
|
|
if (payload.model !== 'doubao-seedream-5-0-pro-260628') throw new Error('Unexpected model');
|
|
if (payload.size !== '1024x1024') throw new Error('Unexpected size');
|
|
if (payload.output_format !== 'png') throw new Error('Output must be PNG');
|
|
if (payload.response_format !== 'url') throw new Error('Response format must be URL');
|
|
if (payload.watermark !== false) throw new Error('Watermark must be disabled');
|
|
if (payload.optimize_prompt_options?.mode !== 'standard') throw new Error('Unexpected prompt mode');
|
|
if (typeof payload.prompt !== 'string' || !payload.prompt.includes('全栈 checkpoint')) throw new Error('Unexpected prompt');
|
|
if ('image' in payload) throw new Error('Checkpoint regression task must be text-to-image');
|
|
for (const forbidden of ['stream', 'tools', 'sequential_image_generation']) {
|
|
if (forbidden in payload) throw new Error(`Forbidden field: ${forbidden}`);
|
|
}
|
|
}
|
|
|
|
function writeJson(response, statusCode, body) {
|
|
response.statusCode = statusCode;
|
|
response.setHeader('content-type', 'application/json; charset=utf-8');
|
|
response.end(JSON.stringify(body));
|
|
}
|