"use strict"; const { AppError } = require("../../errors"); /** * Парсит и валидирует координаты прямоугольной области референса. * @param {string|object} value * @returns {{ x: number, y: number, w: number, h: number } | null} */ function parseRegion(value) { if (value === undefined || value === null || String(value).trim() === "") { return null; } let parsed = value; if (typeof value === "string") { try { parsed = JSON.parse(value); } catch { throw new AppError(400, "Область референса передана в неверном формате."); } } const nums = ["x", "y", "w", "h"].map((key) => Number(parsed?.[key])); if (nums.some((num) => !Number.isFinite(num) || num < 0)) { throw new AppError( 400, "Область референса должна содержать неотрицательные числовые поля x, y, w, h." ); } const [x, y, w, h] = nums; if (w <= 0 || h <= 0) { throw new AppError(400, "Ширина и высота области должны быть больше 0."); } return { x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h), }; } /** * Масштабирует координаты области при даунскейле референса. * @param {{ x: number, y: number, w: number, h: number }} region * @param {number} factor * @returns {{ x: number, y: number, w: number, h: number }} */ function scaleRegion(region, factor = 1) { if (!region || factor === 1 || !Number.isFinite(factor) || factor <= 0) { return region; } return { x: Math.max(0, Math.round(region.x * factor)), y: Math.max(0, Math.round(region.y * factor)), w: Math.max(1, Math.round(region.w * factor)), h: Math.max(1, Math.round(region.h * factor)), }; } /** * Собирает текст системного промпта для редактирования через Codex. * @param {Object} options * @param {string} options.prompt * @param {{ width?: number, height?: number }|null} [options.dimensions] * @param {string} [options.inputName='input.jpg'] * @param {string|null} [options.referenceName=null] * @param {{ x: number, y: number, w: number, h: number }|null} [options.region=null] * @param {boolean} [options.downscaled=false] * @returns {string} */ function buildEditPrompt({ prompt, dimensions = null, inputName = "input.jpg", referenceName = null, region = null, downscaled = false, }) { const size = dimensions && dimensions.width && dimensions.height ? `${dimensions.width}×${dimensions.height}` : ""; const lines = [ `Перед тобой фотография ${inputName}. Примени к ней следующее редактирование: «${prompt}».`, ]; if (referenceName) { lines.push( `Также дано вспомогательное изображение ${referenceName} — тот же сюжет и ракурс, что и ${inputName}. Красным прямоугольником на ${referenceName} выделена область, в которой нужно внести правку.` ); } if (referenceName && region) { lines.push( `Координаты области на ${referenceName}: x=${region.x}, y=${region.y}, ширина=${region.w}, высота=${region.h} (в пикселях).` ); lines.push( `Примени редактирование к ${inputName} именно в области, соответствующей прямоугольнику на ${referenceName}. Остальную часть ${inputName} не меняй.` ); } if (referenceName && !region) { lines.push( `Используй ${referenceName} как ориентир по сюжету и ракурсу при внесении правки в ${inputName}.` ); } if (downscaled) { lines.push("Исходное фото уменьшено для скорости обработки."); } lines.push( "Сохрани отредактированное фото в файл output.jpg в текущей директории.", "Не перерисовывай фото с нуля и не заменяй его на полностью новое изображение — редактируй исходное фото, сохраняя его сюжет, объекты, лица и пропорции.", size ? `Сохрани исходный размер ${size}.` : "Сохрани исходные пропорции.", "Файл output.jpg должен быть валидным JPEG. По завершении кратко опиши, что сделал." ); return lines.join(" "); } /** * Собирает промпт для подсказок вариантов редактирования. * @param {string} [inputName='input.jpg'] * @returns {string} */ function buildSuggestPrompt(inputName = "input.jpg") { return `Перед тобой фотография ${inputName}. Предложи 3 коротких варианта промпта (по одному на строку, без нумерации) для её редактирования. Только варианты промптов, ничего больше.`; } /** * Парсит предложения Codex в массив строк. * @param {string} text * @returns {string[]} */ function parseSuggestions(text) { return String(text || "") .split(/\r?\n/) .map((line) => line.replace(/^\s*(?:[-*•]|\d+[.)])\s*/, "").trim()) .filter((line) => line.length > 0 && line.length <= 200) .slice(0, 3); } module.exports = { parseRegion, scaleRegion, buildEditPrompt, buildSuggestPrompt, parseSuggestions, };