fix: complete image_make asset delivery
This commit is contained in:
@@ -237,7 +237,7 @@ export async function migrateSchema(pool) {
|
||||
|
||||
await pool.query(
|
||||
`ALTER TABLE h5_assets
|
||||
MODIFY source_type ENUM('upload', 'chat', 'agent', 'template', 'generated', 'workspace')
|
||||
MODIFY source_type ENUM('upload', 'chat', 'agent', 'template', 'generated', 'workspace', 'image_make')
|
||||
NOT NULL DEFAULT 'upload'`,
|
||||
);
|
||||
|
||||
|
||||
@@ -123,6 +123,9 @@ export function createImageMakeClient({
|
||||
|
||||
const deadline = Date.now() + overallTimeout;
|
||||
let job = created;
|
||||
if (!ACTIVE_STATUSES.has(String(job?.status ?? '')) && !job?.result && created?.status_url) {
|
||||
job = await safeJson(await request(created.status_url));
|
||||
}
|
||||
while (ACTIVE_STATUSES.has(String(job?.status ?? ''))) {
|
||||
if (Date.now() >= deadline) {
|
||||
throw createClientError('IMAGE_MAKE_TIMEOUT', '图片生成超时', { jobId });
|
||||
|
||||
@@ -80,6 +80,51 @@ test('image_make client fails closed on checksum mismatch', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('image_make client resolves a completed idempotent reuse through status_url', async () => {
|
||||
const image = Buffer.concat([Buffer.from('RIFF'), Buffer.alloc(4), Buffer.from('WEBPreused')]);
|
||||
const sha256 = crypto.createHash('sha256').update(image).digest('hex');
|
||||
const calls = [];
|
||||
const client = createImageMakeClient({
|
||||
baseUrl: 'http://127.0.0.1:8080', token: 'test-token', sleep: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const path = new URL(url).pathname;
|
||||
calls.push(path);
|
||||
if (path === '/v1/generation-jobs') {
|
||||
return Response.json({
|
||||
job_id: 'job-reused',
|
||||
status: 'succeeded',
|
||||
status_url: '/v1/generation-jobs/job-reused',
|
||||
});
|
||||
}
|
||||
if (path === '/v1/generation-jobs/job-reused') {
|
||||
return Response.json({
|
||||
job_id: 'job-reused',
|
||||
status: 'succeeded',
|
||||
result: {
|
||||
download_url: '/v1/generation-jobs/job-reused/result',
|
||||
sha256,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (path.endsWith('/result')) {
|
||||
return new Response(image, { headers: { 'content-type': 'image/webp' } });
|
||||
}
|
||||
throw new Error(`Unexpected request ${path}`);
|
||||
},
|
||||
});
|
||||
const result = await client.generateImage({
|
||||
prompt: 'reuse me',
|
||||
presetId: 'memind_dark_hero',
|
||||
idempotencyKey: 'imgreq-reused',
|
||||
});
|
||||
assert.equal(result.jobId, 'job-reused');
|
||||
assert.deepEqual(calls, [
|
||||
'/v1/generation-jobs',
|
||||
'/v1/generation-jobs/job-reused',
|
||||
'/v1/generation-jobs/job-reused/result',
|
||||
]);
|
||||
});
|
||||
|
||||
test('image_make env client stays disabled until URL and token are both configured', () => {
|
||||
assert.equal(createImageMakeClientFromEnv({}), null);
|
||||
assert.equal(createImageMakeClientFromEnv({ IMAGE_MAKE_BASE_URL: 'http://127.0.0.1:8080' }), null);
|
||||
|
||||
@@ -28,7 +28,7 @@ async function handleGenerate(req, res, { service, userId, consumerPrefix }) {
|
||||
purpose,
|
||||
prompt,
|
||||
negativePrompt: req.body?.negative_prompt,
|
||||
consumerRef: `${consumerPrefix}:${req.requestId ?? 'request'}:${purpose}`,
|
||||
consumerRef: `${consumerPrefix}:${idempotencyKey || req.requestId || 'request'}:${purpose}`,
|
||||
idempotencyKey,
|
||||
});
|
||||
if (!result.ok) {
|
||||
|
||||
@@ -42,7 +42,7 @@ test('image generation route passes authenticated user and idempotency key to th
|
||||
purpose: 'hero',
|
||||
prompt: 'calm library',
|
||||
negativePrompt: undefined,
|
||||
consumerRef: 'mindspace:req-1:hero',
|
||||
consumerRef: 'mindspace:imgreq-1:hero',
|
||||
idempotencyKey: 'imgreq-1',
|
||||
}]);
|
||||
} finally { await fixture.close(); }
|
||||
@@ -84,11 +84,15 @@ test('internal image generation route requires its injected secret guard and exp
|
||||
assert.equal(denied.status, 401);
|
||||
const allowed = await fetch(`${fixture.url}/agent/mindspace_image_generate`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', authorization: 'Bearer internal-secret' },
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
authorization: 'Bearer internal-secret',
|
||||
'idempotency-key': 'page-hero-1',
|
||||
},
|
||||
body: JSON.stringify({ user_id: 'user-2', purpose: 'hero', prompt: 'x' }),
|
||||
});
|
||||
assert.equal(allowed.status, 201);
|
||||
assert.equal(calls[0].userId, 'user-2');
|
||||
assert.equal(calls[0].consumerRef, 'mindspace-agent:req-1:hero');
|
||||
assert.equal(calls[0].consumerRef, 'mindspace-agent:page-hero-1:hero');
|
||||
} finally { await fixture.close(); }
|
||||
});
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import test from 'node:test';
|
||||
import { createMindSpaceImageGenerationService } from './mindspace-image-generation.mjs';
|
||||
|
||||
test('MindSpace asset schema accepts image_make as a source type', () => {
|
||||
const schemaSql = fs.readFileSync(new URL('./schema.sql', import.meta.url), 'utf8');
|
||||
const dbSource = fs.readFileSync(new URL('./db.mjs', import.meta.url), 'utf8');
|
||||
assert.match(schemaSql, /source_type ENUM\([^\n]*'image_make'/);
|
||||
assert.match(dbSource, /MODIFY source_type ENUM\([^\n]*'image_make'/);
|
||||
});
|
||||
|
||||
test('MindSpace image generation stays inert while the admin purpose is disabled', async () => {
|
||||
const service = createMindSpaceImageGenerationService({
|
||||
configService: {
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ CREATE TABLE IF NOT EXISTS h5_assets (
|
||||
risk_level ENUM('none', 'low', 'medium', 'high', 'critical') NOT NULL DEFAULT 'none',
|
||||
visibility ENUM('private', 'internal', 'public_candidate') NOT NULL DEFAULT 'private',
|
||||
status ENUM('uploaded', 'processing', 'ready', 'quarantined', 'archived', 'deleted') NOT NULL DEFAULT 'ready',
|
||||
source_type ENUM('upload', 'chat', 'agent', 'template', 'generated', 'workspace') NOT NULL DEFAULT 'upload',
|
||||
source_type ENUM('upload', 'chat', 'agent', 'template', 'generated', 'workspace', 'image_make') NOT NULL DEFAULT 'upload',
|
||||
created_at BIGINT NOT NULL,
|
||||
updated_at BIGINT NOT NULL,
|
||||
deleted_at BIGINT NULL,
|
||||
|
||||
Reference in New Issue
Block a user