"use strict"; const express = require("express"); const { parseRegion } = require("../../core/codex/prompts"); const { isHeic, convertHeicToJpeg } = require("../../core/images/heic"); const { sniffImageType } = require("../../core/images/sniff"); const { BadRequestError } = require("../../errors"); const { JOB_ID_PATTERN } = require("./history.routes"); /** * @param {Object} deps * @param {import('../../core/jobs/job-service').JobService} deps.jobService * @param {import('../../core/history/history-service').HistoryService} deps.historyService * @param {import('multer').Multer} deps.upload * @param {import('../../logger').Logger} deps.logger */ function createEditRoutes({ jobService, historyService, upload, logger }) { const router = express.Router(); router.post( "/edit", upload.fields([ { name: "photo", maxCount: 1 }, { name: "reference", maxCount: 1 }, ]), async (req, res, next) => { try { const photoFile = req.files?.photo?.[0]; const referenceFile = req.files?.reference?.[0]; let sourceId = req.body?.sourceId ? String(req.body.sourceId).trim() : null; if (sourceId) { if (!JOB_ID_PATTERN.test(sourceId)) { throw new BadRequestError("Некорректный идентификатор исходной версии (sourceId)."); } const version = historyService.getVersion(sourceId); if (!version) { throw new BadRequestError("Исходная версия не найдена в истории."); } } else if (!photoFile || !photoFile.buffer) { throw new BadRequestError("Выберите фотографию или укажите sourceId из истории."); } const prompt = String(req.body?.prompt || "").trim(); if (!prompt) { throw new BadRequestError("Введите промпт для редактирования фотографии."); } if (prompt.length > 1000) { throw new BadRequestError("Промпт не должен превышать 1000 символов."); } let referenceId = req.body?.referenceId ? String(req.body.referenceId).trim() : null; if (referenceId) { if (!JOB_ID_PATTERN.test(referenceId)) { throw new BadRequestError("Некорректный идентификатор референса (referenceId)."); } const refVersion = historyService.getVersion(referenceId); if (!refVersion) { throw new BadRequestError("Референс не найден в истории."); } } const region = parseRegion(req.body?.region); if (region && !referenceFile && !referenceId) { throw new BadRequestError("Область указана без изображения референса."); } // Process photo file buffer if uploaded let inputBuffer = photoFile ? photoFile.buffer : undefined; let inputMimetype = photoFile ? photoFile.mimetype : undefined; let inputOriginalname = photoFile ? photoFile.originalname : undefined; if (photoFile) { if (isHeic(photoFile) || sniffImageType(inputBuffer) === "heic") { inputBuffer = await convertHeicToJpeg(inputBuffer); inputMimetype = "image/jpeg"; } else { const sniffed = sniffImageType(inputBuffer); if (!sniffed) { throw new BadRequestError( "Загруженная фотография не является поддерживаемым изображением." ); } } } // Process reference file buffer if uploaded let referenceBuffer = referenceFile ? referenceFile.buffer : undefined; let referenceMimetype = referenceFile ? referenceFile.mimetype : undefined; let referenceOriginalname = referenceFile ? referenceFile.originalname : undefined; if (referenceFile) { if (isHeic(referenceFile) || sniffImageType(referenceBuffer) === "heic") { referenceBuffer = await convertHeicToJpeg(referenceBuffer); referenceMimetype = "image/jpeg"; } else { const sniffed = sniffImageType(referenceBuffer); if (!sniffed) { throw new BadRequestError( "Загруженный референс не является поддерживаемым изображением." ); } } } const payload = { sourceId, inputBuffer, inputMimetype, inputOriginalname, prompt, referenceId, referenceBuffer, referenceMimetype, referenceOriginalname, region, }; const result = await jobService.createJob({ type: "edit", payload, requestId: req.id, }); logger.info("Постановка задания edit в очередь", { jobId: result.jobId, sourceId, prompt: prompt.slice(0, 80), queuePosition: result.queuePosition, }); res.json({ ok: true, jobId: result.jobId, status: result.status, queuePosition: result.queuePosition, }); } catch (err) { next(err); } } ); return router; } module.exports = { createEditRoutes, };