Files
vendoo/tools/verify-slice30-mock.mjs

138 lines
5.7 KiB
JavaScript

import http from 'http';
import { once } from 'events';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import {
generateWithLocalFlux,
getLocalFluxRuntimeInfo,
interruptLocalFlux,
cancelLocalFluxPrompt,
} from '../lib/ai-local-images.mjs';
let submittedWorkflow = null;
let promptSubmissions = 0;
let interrupted = false;
let deletedQueueIds = [];
const imageBytes = Buffer.from('vendoo-slice-30-mock-image');
const server = http.createServer(async (req, res) => {
const sendJson = (status, payload) => {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(payload));
};
if (req.method === 'GET' && req.url === '/system_stats') {
return sendJson(200, { system: { os: 'mock-os', comfyui_version: 'mock-1' }, devices: [{ name: 'Mock GPU', vram_total: 16 * 1024 ** 3, vram_free: 12 * 1024 ** 3 }] });
}
if (req.method === 'GET' && req.url === '/queue') {
return sendJson(200, {
queue_running: [[1, 'mock-running', {}, {}, []]],
queue_pending: [[2, 'mock-pending', {}, {}, []]],
});
}
if (req.method === 'POST' && req.url === '/queue') {
let body = '';
for await (const chunk of req) body += chunk;
deletedQueueIds = JSON.parse(body || '{}').delete || [];
return sendJson(200, { ok: true });
}
if (req.method === 'GET' && req.url.startsWith('/history?')) {
return sendJson(200, { 'mock-prompt': { outputs: {} } });
}
if (req.method === 'POST' && req.url === '/interrupt') {
interrupted = true;
return sendJson(200, { ok: true });
}
if (req.method === 'POST' && req.url === '/prompt') {
promptSubmissions += 1;
let body = '';
for await (const chunk of req) body += chunk;
submittedWorkflow = JSON.parse(body).prompt;
return sendJson(200, { prompt_id: 'slice30-prompt-id' });
}
if (req.method === 'GET' && req.url === '/history/slice30-prompt-id') {
return sendJson(200, {
'slice30-prompt-id': {
outputs: {
9: { images: [{ filename: 'slice30.png', subfolder: '', type: 'output' }] },
},
},
});
}
if (req.method === 'GET' && req.url.startsWith('/view?')) {
res.writeHead(200, { 'Content-Type': 'image/png' });
return res.end(imageBytes);
}
return sendJson(404, { error: `Unhandled ${req.method} ${req.url}` });
});
server.listen(0, '127.0.0.1');
await once(server, 'listening');
const address = server.address();
const settings = {
ai_model_photo_local_enabled: '1',
ai_model_photo_local_url: `http://127.0.0.1:${address.port}`,
ai_model_photo_local_workflow_path: resolve('local-ai/workflows/vendoo_flux_schnell_api.json'),
ai_model_photo_use_source_reference: '0',
ai_model_photo_local_resolution: '768',
};
try {
const phases = [];
const result = await generateWithLocalFlux({
prompt: 'Studio product photo with soft light',
settings,
mode: 'prompt',
sourceDataUrl: '',
width: 768,
height: 768,
seed: 1234,
steps: 4,
onPhase: (phase) => phases.push(phase),
});
if (result.prompt_id !== 'slice30-prompt-id') throw new Error('Prompt-ID wurde nicht zurückgegeben.');
if (Buffer.from(result.base64, 'base64').compare(imageBytes) !== 0) throw new Error('Mock-Bild stimmt nicht.');
const classTypes = Object.values(submittedWorkflow || {}).map(node => node?.class_type).filter(Boolean);
if (classTypes.includes('LoadImage')) throw new Error('Promptmodus enthält unerlaubten LoadImage-Knoten.');
if (!phases.includes('submitted') || !phases.includes('rendering') || !phases.includes('completed')) throw new Error(`Phasen unvollständig: ${phases.join(', ')}`);
let cancelledBeforeSubmit = false;
try {
await generateWithLocalFlux({
prompt: 'This job must not be submitted', settings, mode: 'prompt', isCancelled: () => true,
});
} catch (error) {
cancelledBeforeSubmit = error?.code === 'FLUX_CANCELLED';
}
if (!cancelledBeforeSubmit || promptSubmissions !== 1) throw new Error('Abbruch vor der ComfyUI-Übergabe ist nicht sicher.');
const runtime = await getLocalFluxRuntimeInfo(settings, { historyLimit: 20 });
if (!runtime.reachable || runtime.queue_summary.running !== 1 || runtime.queue_summary.pending !== 1) throw new Error('Queue/System-Diagnose ist falsch.');
if (runtime.system_stats?.devices?.[0]?.name !== 'Mock GPU') throw new Error('GPU-Systemdaten fehlen.');
const pendingCancel = await cancelLocalFluxPrompt('mock-pending', settings);
if (pendingCancel.mode !== 'dequeued' || !deletedQueueIds.includes('mock-pending')) throw new Error('Gezieltes Entfernen eines wartenden Auftrags ist fehlgeschlagen.');
const runningCancel = await cancelLocalFluxPrompt('mock-running', settings);
if (runningCancel.mode !== 'interrupted' || !interrupted) throw new Error('Laufender Auftrag wurde nicht unterbrochen.');
await interruptLocalFlux(settings);
if (!interrupted) throw new Error('Interrupt-Endpunkt wurde nicht aufgerufen.');
const rawWorkflow = JSON.parse(readFileSync(settings.ai_model_photo_local_workflow_path, 'utf8').replace(/^\uFEFF/, ''));
if (Object.values(rawWorkflow).some(node => node?.class_type === 'LoadImage')) throw new Error('Ausgelieferter freier Promptworkflow enthält LoadImage.');
console.log(JSON.stringify({
ok: true,
prompt_id: result.prompt_id,
phases,
queue_summary: runtime.queue_summary,
gpu: runtime.system_stats.devices[0].name,
interrupt: interrupted,
targeted_pending_cancel: deletedQueueIds.includes('mock-pending'),
targeted_running_cancel: runningCancel.mode === 'interrupted',
cancelled_before_submit: cancelledBeforeSubmit,
prompt_submissions: promptSubmissions,
load_image_in_prompt_workflow: false,
}, null, 2));
} finally {
server.close();
}