* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix * fix: track native source modules with root-anchored runtime ignores
136 lines
4.4 KiB
JavaScript
136 lines
4.4 KiB
JavaScript
import {
|
|
createAiModelGeneration,
|
|
updateAiModelGeneration,
|
|
createAiModelPhoto,
|
|
createAiModelJob,
|
|
updateAiModelJob,
|
|
getAiModelJob,
|
|
getPendingAiModelJobs,
|
|
} from './db.mjs';
|
|
import { generateAiModelPhotos } from './ai-model-photos.mjs';
|
|
|
|
let processing = false;
|
|
let loopStarted = false;
|
|
let loopTimer = null;
|
|
|
|
async function processOneJob(job) {
|
|
let lastProgress = -1;
|
|
let lastMessage = '';
|
|
const reportProgress = (progress, message = '') => {
|
|
const normalized = Math.max(1, Math.min(99, Math.round(Number(progress || 0))));
|
|
const cleanMessage = String(message || '').trim();
|
|
if (normalized === lastProgress && cleanMessage === lastMessage) return;
|
|
lastProgress = normalized;
|
|
lastMessage = cleanMessage;
|
|
updateAiModelJob(job.id, {
|
|
status: 'running',
|
|
progress: normalized,
|
|
progress_message: cleanMessage || 'AI-Bild wird erzeugt …',
|
|
error_message: null,
|
|
});
|
|
};
|
|
|
|
reportProgress(4, 'Job wird vorbereitet …');
|
|
try {
|
|
const payload = job.payload_json || {};
|
|
const result = await generateAiModelPhotos({ ...payload, settings: payload.settings || {} }, reportProgress);
|
|
reportProgress(94, 'Ergebnis wird gespeichert …');
|
|
const generation = createAiModelGeneration({
|
|
listing_id: payload.listing_id || null,
|
|
context: payload.listing_id ? 'listing' : 'generator',
|
|
mode: payload.mode || result.mode || 'flatlay',
|
|
preset: result.precheck.preset,
|
|
variants: result.photos.length,
|
|
source_photos: Array.isArray(payload.photos) ? payload.photos : [],
|
|
generated_photos: result.photos.map(photo => photo.filename),
|
|
prompt_summary: result.prompt_summary,
|
|
provider: result.provider?.provider || null,
|
|
provider_model: result.provider?.model || null,
|
|
moderation_notes: result.photos.map(photo => photo.moderation?.notes).filter(Boolean).join(' | ') || null,
|
|
safety_status: 'safe',
|
|
status: 'completed',
|
|
});
|
|
const photos = result.photos.map(photo => createAiModelPhoto({
|
|
generation_id: generation.id,
|
|
listing_id: payload.listing_id || null,
|
|
variant_index: photo.variant_index,
|
|
mode: photo.mode || payload.mode || 'flatlay',
|
|
preset: photo.preset,
|
|
image_path: photo.filename,
|
|
source_photo: photo.source_photo,
|
|
prompt: photo.prompt,
|
|
provider: photo.provider || result.provider?.provider || null,
|
|
provider_model: photo.provider_model || result.provider?.model || null,
|
|
moderation_result: JSON.stringify(photo.moderation || {}),
|
|
safety_status: photo.moderation?.safe === false ? 'blocked' : 'safe',
|
|
}));
|
|
updateAiModelGeneration(generation.id, {
|
|
generated_photos: photos.map(photo => photo.image_path),
|
|
provider: result.provider?.provider || null,
|
|
provider_model: result.provider?.model || null,
|
|
mode: payload.mode || result.mode || 'flatlay',
|
|
});
|
|
updateAiModelJob(job.id, {
|
|
status: 'completed',
|
|
progress: 100,
|
|
progress_message: 'AI-Bilder sind fertig.',
|
|
generation_id: generation.id,
|
|
});
|
|
return { generation, photos, provider: result.provider || null };
|
|
} catch (err) {
|
|
updateAiModelJob(job.id, {
|
|
status: 'failed',
|
|
progress: 100,
|
|
progress_message: 'AI-Bildgenerierung fehlgeschlagen.',
|
|
error_message: err.message || 'Unbekannter Fehler',
|
|
});
|
|
throw err;
|
|
}
|
|
}
|
|
async function processQueue() {
|
|
if (processing) return;
|
|
processing = true;
|
|
try {
|
|
const pending = getPendingAiModelJobs(5);
|
|
for (const job of pending) {
|
|
try {
|
|
await processOneJob(job);
|
|
} catch {}
|
|
}
|
|
} finally {
|
|
processing = false;
|
|
}
|
|
}
|
|
|
|
export function startAiModelJobLoop() {
|
|
if (loopStarted) return;
|
|
loopStarted = true;
|
|
loopTimer = setInterval(() => { processQueue().catch(() => {}); }, 2500);
|
|
loopTimer.unref?.();
|
|
}
|
|
|
|
export function stopAiModelJobLoop() {
|
|
if (loopTimer) clearInterval(loopTimer);
|
|
loopTimer = null;
|
|
loopStarted = false;
|
|
}
|
|
|
|
export function enqueueAiModelJob(payload = {}) {
|
|
const job = createAiModelJob({
|
|
listing_id: payload.listing_id || null,
|
|
context: payload.listing_id ? 'listing' : 'generator',
|
|
mode: payload.mode || 'model',
|
|
preset: payload.preset || 'mixed',
|
|
variants: payload.variants || 1,
|
|
status: 'queued',
|
|
progress: 0,
|
|
payload_json: payload,
|
|
});
|
|
processQueue().catch(() => {});
|
|
return job;
|
|
}
|
|
|
|
export function getAiModelJobWithRelations(id) {
|
|
return getAiModelJob(id);
|
|
}
|