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