docs-combiner 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/renderer.js +549 -169
- package/dist/renderer.js.map +1 -1
- package/package.json +1 -1
package/dist/renderer.js
CHANGED
|
@@ -100888,14 +100888,14 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
100888
100888
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/CircularProgress/CircularProgress.js");
|
|
100889
100889
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/FormHelperText/FormHelperText.js");
|
|
100890
100890
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/Alert/Alert.js");
|
|
100891
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
100892
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
100893
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
100894
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
100895
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
100896
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
100897
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
100898
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
100891
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/FormControlLabel/FormControlLabel.js");
|
|
100892
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/Checkbox/Checkbox.js");
|
|
100893
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/InputAdornment/InputAdornment.js");
|
|
100894
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/Tooltip/Tooltip.js");
|
|
100895
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/FormControl/FormControl.js");
|
|
100896
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/InputLabel/InputLabel.js");
|
|
100897
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/Select/Select.js");
|
|
100898
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/MenuItem/MenuItem.js");
|
|
100899
100899
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/ToggleButtonGroup/ToggleButtonGroup.js");
|
|
100900
100900
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/ToggleButton/ToggleButton.js");
|
|
100901
100901
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/Paper/Paper.js");
|
|
@@ -100983,15 +100983,219 @@ const INSTRUCTION_ROW = [
|
|
|
100983
100983
|
"# Обязательно | The URL for the main image of your item. Images must be in a supported format (JPG/GIF/PNG) and at least 500 x 500 pixels.",
|
|
100984
100984
|
"# Обязательно | Фирменное название товара. Не более 100 символов."
|
|
100985
100985
|
];
|
|
100986
|
-
/**
|
|
100987
|
-
const
|
|
100988
|
-
|
|
100989
|
-
|
|
100986
|
+
/** Доп. query-параметры для ссылки каталога (RedTrack / utm / макросы Meta; не URL-encode — подстановка на стороне FB). */
|
|
100987
|
+
const DEFAULT_CATALOG_LINK_EXTRA_MACROS = 'sub1={{ad.id}}&sub2={{adset.id}}&sub3={{campaign.id}}&sub4={{ad.name}}&sub5={{adset.name}}&sub6={{campaign.name}}&sub7={{placement}}&sub8={{site_source_name}}&utm_source=facebook&utm_medium=paid';
|
|
100988
|
+
const DEFAULT_CATALOG_LINK_KEITARO_MACROS = 'utm_campaign={{campaign.name}}&utm_source={{site_source_name}}&utm_placement={{placement}}&campaign_id={{campaign.id}}&adset_id={{adset.id}}&ad_id={{ad.id}}&adset_name={{adset.name}}&ad_name={{ad.name}}';
|
|
100989
|
+
/** 8 случайных hex-символов для уникального суффикса имени файла на Drive. */
|
|
100990
|
+
function randomHex8() {
|
|
100991
|
+
const a = new Uint8Array(4);
|
|
100992
|
+
crypto.getRandomValues(a);
|
|
100993
|
+
return Array.from(a, b => b.toString(16).padStart(2, '0')).join('');
|
|
100994
|
+
}
|
|
100995
|
+
/** Имя файла при загрузке крео: N_xxxxxxxx.png — N = номер строки подхода в UI (1–10). */
|
|
100996
|
+
function creativeImageUploadFilename(creoApproachUiNumber) {
|
|
100997
|
+
return `${creoApproachUiNumber}_${randomHex8()}.png`;
|
|
100998
|
+
}
|
|
100999
|
+
/**
|
|
101000
|
+
* Из имени файла картинки на Drive — номер строки подхода к крео (1–10) для каталога/трекера.
|
|
101001
|
+
* Формат: N_xxxxxxxx; снимаются расширение, суффиксы « (N)» от дубликатов Google.
|
|
101002
|
+
* Старый формат (текст_hex8) — возвращается «лейбл» после снятия hex (не только цифры).
|
|
101003
|
+
*/
|
|
101004
|
+
function parseCreoApproachLabelFromImageFileName(fileName) {
|
|
101005
|
+
let s = fileName.replace(/\.[^/.]+$/i, '').trim();
|
|
101006
|
+
if (!s)
|
|
101007
|
+
return undefined;
|
|
101008
|
+
while (/\s+\(\d+\)$/.test(s)) {
|
|
101009
|
+
s = s.replace(/\s+\(\d+\)$/, '').trim();
|
|
101010
|
+
}
|
|
101011
|
+
const m = /^(\d+)_([0-9a-fA-F]{8})$/i.exec(s);
|
|
101012
|
+
if (m)
|
|
101013
|
+
return m[1];
|
|
101014
|
+
const legacy = s.replace(/_[0-9a-fA-F]{8}$/i, '').trim();
|
|
101015
|
+
return legacy || undefined;
|
|
101016
|
+
}
|
|
101017
|
+
const DEFAULT_CATALOG_TEXT_APPROACH_PARAM = 'sub19';
|
|
101018
|
+
const DEFAULT_CATALOG_CREO_APPROACH_PARAM = 'sub20';
|
|
101019
|
+
/** Имя query-параметра для трекера: латиница, цифры, _, - */
|
|
101020
|
+
function sanitizeCatalogUrlParamKey(raw, fallback) {
|
|
101021
|
+
const t = raw.trim();
|
|
101022
|
+
if (!t)
|
|
101023
|
+
return fallback;
|
|
101024
|
+
const cleaned = t.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 64);
|
|
101025
|
+
return cleaned || fallback;
|
|
101026
|
+
}
|
|
101027
|
+
/** Добавляет к URL лендинга creative_id, опционально кастомные параметры подходов (имена редактируются в UI) и строку доп. макросов. */
|
|
101028
|
+
function appendCreativeIdToCatalogLink(baseUrl, creativeId, catalogAnalytics, extraMacrosQueryString) {
|
|
100990
101029
|
const u = baseUrl.trim();
|
|
100991
101030
|
if (!u)
|
|
100992
101031
|
return u;
|
|
100993
101032
|
const sep = u.includes('?') ? '&' : '?';
|
|
100994
|
-
|
|
101033
|
+
const q = [`creative_id=${encodeURIComponent(creativeId)}`];
|
|
101034
|
+
const textKey = sanitizeCatalogUrlParamKey(catalogAnalytics?.textParamKey ?? '', DEFAULT_CATALOG_TEXT_APPROACH_PARAM);
|
|
101035
|
+
const creoKey = sanitizeCatalogUrlParamKey(catalogAnalytics?.creoParamKey ?? '', DEFAULT_CATALOG_CREO_APPROACH_PARAM);
|
|
101036
|
+
if (catalogAnalytics?.textApproach) {
|
|
101037
|
+
q.push(`${textKey}=${encodeURIComponent(catalogAnalytics.textApproach)}`);
|
|
101038
|
+
}
|
|
101039
|
+
if (catalogAnalytics?.creoApproach) {
|
|
101040
|
+
q.push(`${creoKey}=${encodeURIComponent(catalogAnalytics.creoApproach)}`);
|
|
101041
|
+
}
|
|
101042
|
+
const core = `${u}${sep}${q.join('&')}`;
|
|
101043
|
+
const tail = (extraMacrosQueryString ?? '').trim();
|
|
101044
|
+
return tail ? `${core}&${tail}` : core;
|
|
101045
|
+
}
|
|
101046
|
+
/** Текст ответа из OpenRouter/OpenAI chat completion (string или массив частей). */
|
|
101047
|
+
function extractChatCompletionText(choice) {
|
|
101048
|
+
const msg = choice?.message;
|
|
101049
|
+
if (!msg)
|
|
101050
|
+
return '';
|
|
101051
|
+
const c = msg.content;
|
|
101052
|
+
if (typeof c === 'string')
|
|
101053
|
+
return c;
|
|
101054
|
+
if (Array.isArray(c)) {
|
|
101055
|
+
return c
|
|
101056
|
+
.map((part) => (typeof part === 'string' ? part : part?.text ?? part?.content ?? ''))
|
|
101057
|
+
.filter(Boolean)
|
|
101058
|
+
.join('');
|
|
101059
|
+
}
|
|
101060
|
+
if (c != null && typeof c === 'object' && typeof c.text === 'string') {
|
|
101061
|
+
return c.text;
|
|
101062
|
+
}
|
|
101063
|
+
return '';
|
|
101064
|
+
}
|
|
101065
|
+
function stripMarkdownJsonFence(raw) {
|
|
101066
|
+
let s = raw.trim();
|
|
101067
|
+
if (!s.startsWith('```'))
|
|
101068
|
+
return s;
|
|
101069
|
+
s = s.replace(/^```(?:json)?\s*/i, '');
|
|
101070
|
+
const end = s.lastIndexOf('```');
|
|
101071
|
+
if (end >= 0)
|
|
101072
|
+
s = s.slice(0, end).trim();
|
|
101073
|
+
return s;
|
|
101074
|
+
}
|
|
101075
|
+
/** Парсит JSON-массив переводов из ответа модели (с ```json или мусором вокруг). */
|
|
101076
|
+
function parsePairTranslationsJson(raw) {
|
|
101077
|
+
const cleaned = stripMarkdownJsonFence(raw);
|
|
101078
|
+
try {
|
|
101079
|
+
const parsed = JSON.parse(cleaned);
|
|
101080
|
+
if (Array.isArray(parsed))
|
|
101081
|
+
return parsed;
|
|
101082
|
+
}
|
|
101083
|
+
catch {
|
|
101084
|
+
/* далее — вырезка по скобкам */
|
|
101085
|
+
}
|
|
101086
|
+
const jsonMatch = cleaned.match(/\[[\s\S]*\]/);
|
|
101087
|
+
if (!jsonMatch)
|
|
101088
|
+
return null;
|
|
101089
|
+
try {
|
|
101090
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
101091
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
101092
|
+
}
|
|
101093
|
+
catch {
|
|
101094
|
+
return null;
|
|
101095
|
+
}
|
|
101096
|
+
}
|
|
101097
|
+
/** Убирает повторяющиеся префиксы «ОШИБКА:» и лишние пробелы в строке из ответа валидатора. */
|
|
101098
|
+
function normalizeValidationErrorText(s) {
|
|
101099
|
+
let t = s.trim().replace(/\s+/g, ' ');
|
|
101100
|
+
while (/^ОШИБКА[:\s]+/i.test(t)) {
|
|
101101
|
+
t = t.replace(/^ОШИБКА[:\s]+/i, '').trim();
|
|
101102
|
+
}
|
|
101103
|
+
return t;
|
|
101104
|
+
}
|
|
101105
|
+
/** Одна и та же формулировка из ответа модели не дублируется в UI и в промпте переделки. */
|
|
101106
|
+
function dedupeValidationErrors(errors) {
|
|
101107
|
+
const seen = new Set();
|
|
101108
|
+
const out = [];
|
|
101109
|
+
for (const raw of errors) {
|
|
101110
|
+
const n = normalizeValidationErrorText(raw);
|
|
101111
|
+
if (!n)
|
|
101112
|
+
continue;
|
|
101113
|
+
const key = n.toLowerCase();
|
|
101114
|
+
if (seen.has(key))
|
|
101115
|
+
continue;
|
|
101116
|
+
seen.add(key);
|
|
101117
|
+
out.push(n);
|
|
101118
|
+
}
|
|
101119
|
+
return out;
|
|
101120
|
+
}
|
|
101121
|
+
/** Строка «ОШИБКА: нет» при статусе пересборки — противоречие; не считать содержательной ошибкой. */
|
|
101122
|
+
function isValidationNegationLine(s) {
|
|
101123
|
+
const t = normalizeValidationErrorText(s).toLowerCase();
|
|
101124
|
+
if (!t)
|
|
101125
|
+
return true;
|
|
101126
|
+
if (t === 'нет')
|
|
101127
|
+
return true;
|
|
101128
|
+
if (t.includes('нет ошибок') || t.includes('ошибок нет'))
|
|
101129
|
+
return true;
|
|
101130
|
+
if (t === 'none' || /^no errors?\.?$/i.test(t))
|
|
101131
|
+
return true;
|
|
101132
|
+
return false;
|
|
101133
|
+
}
|
|
101134
|
+
/** Все подписанные строки ОШИБКА:/ERROR: (модель иногда пишет по-английски). */
|
|
101135
|
+
function extractLabeledValidationErrors(content) {
|
|
101136
|
+
const out = [];
|
|
101137
|
+
const patterns = [
|
|
101138
|
+
/(?:^|\n)\s*ОШИБКА\s*[:\s]\s*([^\n]+)/gi,
|
|
101139
|
+
/(?:^|\n)\s*ERROR\s*[:\s]\s*([^\n]+)/gi,
|
|
101140
|
+
];
|
|
101141
|
+
for (const re of patterns) {
|
|
101142
|
+
let m;
|
|
101143
|
+
const r = new RegExp(re.source, re.flags);
|
|
101144
|
+
while ((m = r.exec(content)) !== null) {
|
|
101145
|
+
const t = normalizeValidationErrorText(m[1]);
|
|
101146
|
+
if (t)
|
|
101147
|
+
out.push(t);
|
|
101148
|
+
}
|
|
101149
|
+
}
|
|
101150
|
+
return out;
|
|
101151
|
+
}
|
|
101152
|
+
/**
|
|
101153
|
+
* Если модель поставила НУЖНА ПЕРЕСБОРКА, но не оформила список как ОШИБКА: — забираем осмысленные строки
|
|
101154
|
+
* между статусом и блоком РЕКОМЕНДАЦИЯ (часто там остаётся описание причин).
|
|
101155
|
+
*/
|
|
101156
|
+
function extractFallbackValidationErrorsBetweenStatusAndRecommendations(content) {
|
|
101157
|
+
const statusNeed = /СТАТУС\s*:\s*НУЖНА\s+ПЕРЕСБОРКА/i;
|
|
101158
|
+
const idx = content.search(statusNeed);
|
|
101159
|
+
if (idx < 0)
|
|
101160
|
+
return [];
|
|
101161
|
+
const tail = content.slice(idx);
|
|
101162
|
+
const firstNl = tail.indexOf('\n');
|
|
101163
|
+
const blockStart = firstNl >= 0 ? idx + firstNl + 1 : content.length;
|
|
101164
|
+
const rest = content.slice(blockStart);
|
|
101165
|
+
const recIdx = rest.search(/\nРЕКОМЕНДАЦИЯ\s*:/i);
|
|
101166
|
+
const block = recIdx >= 0 ? rest.slice(0, recIdx) : rest.slice(0, 3500);
|
|
101167
|
+
const lines = block.split('\n').map(l => l.trim()).filter(l => {
|
|
101168
|
+
if (l.length < 12)
|
|
101169
|
+
return false;
|
|
101170
|
+
if (/^(затем|список|каждая)\s/i.test(l))
|
|
101171
|
+
return false;
|
|
101172
|
+
if (/^ОШИБКА\s*[:\s]*нет\s*\.?$/i.test(l))
|
|
101173
|
+
return false;
|
|
101174
|
+
if (/^РЕКОМЕНДАЦИЯ\s*:/i.test(l))
|
|
101175
|
+
return false;
|
|
101176
|
+
if (/^ШАГ\s+[\dP]/i.test(l))
|
|
101177
|
+
return false;
|
|
101178
|
+
if (/^(?:HOOK|HEADLINE|PRICE|DISCOUNT|CTA|OTHER_TEXT)\s*:/i.test(l))
|
|
101179
|
+
return false;
|
|
101180
|
+
if (/^СТАТУС\s*:/i.test(l))
|
|
101181
|
+
return false;
|
|
101182
|
+
return true;
|
|
101183
|
+
});
|
|
101184
|
+
return lines
|
|
101185
|
+
.map(l => normalizeValidationErrorText(l.replace(/^ОШИБКА\s*[:\s]+/i, '')))
|
|
101186
|
+
.filter(Boolean);
|
|
101187
|
+
}
|
|
101188
|
+
/** Короткий фрагмент блока ФИНАЛ для подсказки, если структура ответа нестандартная. */
|
|
101189
|
+
function extractValidationFinalSnippet(content, maxLen) {
|
|
101190
|
+
const fi = content.search(/\bФИНАЛ\s*:/i);
|
|
101191
|
+
if (fi < 0)
|
|
101192
|
+
return null;
|
|
101193
|
+
let sn = content.slice(fi, fi + maxLen).replace(/\s+/g, ' ').trim();
|
|
101194
|
+
if (sn.length < 50)
|
|
101195
|
+
return null;
|
|
101196
|
+
if (sn.length > maxLen)
|
|
101197
|
+
sn = `${sn.slice(0, maxLen - 1)}…`;
|
|
101198
|
+
return sn;
|
|
100995
101199
|
}
|
|
100996
101200
|
/**
|
|
100997
101201
|
* Разбор строк «ЗАГОЛОВОК n:» / «ТЕКСТ n:» из ответа модели.
|
|
@@ -101006,6 +101210,18 @@ function extractLeadingPriceNumber(value) {
|
|
|
101006
101210
|
return m[1].replace(',', '.');
|
|
101007
101211
|
return '99';
|
|
101008
101212
|
}
|
|
101213
|
+
/** Фон карточки крео: после 1-й переделки — светло-жёлтый, далее темнеет с каждой следующей. */
|
|
101214
|
+
function getRemakeHighlightBackground(remakeCount, dark) {
|
|
101215
|
+
if (remakeCount <= 0)
|
|
101216
|
+
return undefined;
|
|
101217
|
+
const step = Math.min(remakeCount - 1, 7);
|
|
101218
|
+
if (dark) {
|
|
101219
|
+
const opacities = [0.11, 0.16, 0.22, 0.28, 0.34, 0.42, 0.5, 0.58];
|
|
101220
|
+
return `rgba(255, 193, 7, ${opacities[step]})`;
|
|
101221
|
+
}
|
|
101222
|
+
const light = ['#fffde7', '#fff9c4', '#fff59d', '#ffee58', '#ffeb3b', '#fdd835', '#fbc02d', '#f9a825'];
|
|
101223
|
+
return light[step];
|
|
101224
|
+
}
|
|
101009
101225
|
function App() {
|
|
101010
101226
|
const [clientId, setClientId] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
101011
101227
|
const [clientSecret, setClientSecret] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
@@ -101022,6 +101238,7 @@ function App() {
|
|
|
101022
101238
|
const [translatingPairs, setTranslatingPairs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
101023
101239
|
const [driveFolderUrl, setDriveFolderUrl] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
101024
101240
|
const [link, setLink] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
101241
|
+
const linkInputRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
|
|
101025
101242
|
const [openaiApiKey, setOpenaiApiKey] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
101026
101243
|
const [openRouterBalance, setOpenRouterBalance] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
101027
101244
|
const [openRouterAccountBalance, setOpenRouterAccountBalance] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
@@ -101242,6 +101459,16 @@ function App() {
|
|
|
101242
101459
|
// const [availability, setAvailability] = useState('in stock');
|
|
101243
101460
|
// const [condition, setCondition] = useState('new');
|
|
101244
101461
|
const [linkError, setLinkError] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
101462
|
+
/** В ссылку каталога: имя подхода к паре текстов (под трекер) */
|
|
101463
|
+
const [catalogUrlIncludeTextApproach, setCatalogUrlIncludeTextApproach] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
101464
|
+
/** В ссылку каталога: подход крео — если крео загружены из приложения (есть file id на Drive) */
|
|
101465
|
+
const [catalogUrlIncludeCreoApproach, setCatalogUrlIncludeCreoApproach] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
101466
|
+
/** Имя query-параметра для подхода к тексту (по умолчанию sub19) */
|
|
101467
|
+
const [catalogUrlTextApproachParam, setCatalogUrlTextApproachParam] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(DEFAULT_CATALOG_TEXT_APPROACH_PARAM);
|
|
101468
|
+
/** Имя query-параметра для подхода к крео (по умолчанию sub20) */
|
|
101469
|
+
const [catalogUrlCreoApproachParam, setCatalogUrlCreoApproachParam] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(DEFAULT_CATALOG_CREO_APPROACH_PARAM);
|
|
101470
|
+
/** Query-хвост после creative_id / sub19 / sub20 (редактируемый; пресет RedTrack). */
|
|
101471
|
+
const [catalogLinkExtraMacros, setCatalogLinkExtraMacros] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(DEFAULT_CATALOG_LINK_EXTRA_MACROS);
|
|
101245
101472
|
const [loading, setLoading] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
101246
101473
|
const [authLoading, setAuthLoading] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
101247
101474
|
const [generatedData, setGeneratedData] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
@@ -101433,7 +101660,21 @@ function App() {
|
|
|
101433
101660
|
}
|
|
101434
101661
|
}, 1000);
|
|
101435
101662
|
return () => clearTimeout(timeoutId);
|
|
101436
|
-
}, [
|
|
101663
|
+
}, [
|
|
101664
|
+
generateProduct,
|
|
101665
|
+
generateGeo,
|
|
101666
|
+
generateAdditionalInfo,
|
|
101667
|
+
generatePriceWithCurrency,
|
|
101668
|
+
brand,
|
|
101669
|
+
link,
|
|
101670
|
+
catalogUrlIncludeTextApproach,
|
|
101671
|
+
catalogUrlIncludeCreoApproach,
|
|
101672
|
+
catalogUrlTextApproachParam,
|
|
101673
|
+
catalogUrlCreoApproachParam,
|
|
101674
|
+
catalogLinkExtraMacros,
|
|
101675
|
+
driveFolderUrl,
|
|
101676
|
+
loadingContentFromDrive
|
|
101677
|
+
]);
|
|
101437
101678
|
// Load generated content from Google Drive when driveFolderUrl changes
|
|
101438
101679
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
101439
101680
|
if (!driveFolderUrl) {
|
|
@@ -101448,6 +101689,8 @@ function App() {
|
|
|
101448
101689
|
setTexts(['']);
|
|
101449
101690
|
setLink('');
|
|
101450
101691
|
setUploadedLink('');
|
|
101692
|
+
setCatalogLinkExtraMacros(DEFAULT_CATALOG_LINK_EXTRA_MACROS);
|
|
101693
|
+
setPairTranslations({});
|
|
101451
101694
|
return;
|
|
101452
101695
|
}
|
|
101453
101696
|
const folderId = extractFolderId(driveFolderUrl);
|
|
@@ -101463,6 +101706,8 @@ function App() {
|
|
|
101463
101706
|
setTexts(['']);
|
|
101464
101707
|
setLink('');
|
|
101465
101708
|
setUploadedLink('');
|
|
101709
|
+
setCatalogLinkExtraMacros(DEFAULT_CATALOG_LINK_EXTRA_MACROS);
|
|
101710
|
+
setPairTranslations({});
|
|
101466
101711
|
return;
|
|
101467
101712
|
}
|
|
101468
101713
|
logToTerminal('log', '[Load] driveFolderUrl changed, clearing old data and loading from folderId:', folderId);
|
|
@@ -101474,6 +101719,8 @@ function App() {
|
|
|
101474
101719
|
setTexts(['']);
|
|
101475
101720
|
setLink('');
|
|
101476
101721
|
setUploadedLink('');
|
|
101722
|
+
setCatalogLinkExtraMacros(DEFAULT_CATALOG_LINK_EXTRA_MACROS);
|
|
101723
|
+
setPairTranslations({});
|
|
101477
101724
|
setLoadingContentFromDrive(true);
|
|
101478
101725
|
setDriveFilesFound({ content: false });
|
|
101479
101726
|
// Load content from Google Drive
|
|
@@ -102802,22 +103049,33 @@ function App() {
|
|
|
102802
103049
|
},
|
|
102803
103050
|
body: JSON.stringify(requestBody)
|
|
102804
103051
|
});
|
|
102805
|
-
if (!response.ok)
|
|
103052
|
+
if (!response.ok) {
|
|
103053
|
+
logToTerminal('warn', '[Translate RU] HTTP', response.status, '— перевод пар пропущен');
|
|
102806
103054
|
return;
|
|
103055
|
+
}
|
|
102807
103056
|
const data = await response.json();
|
|
102808
|
-
const
|
|
102809
|
-
|
|
102810
|
-
|
|
103057
|
+
const choice = data.choices?.[0];
|
|
103058
|
+
let raw = extractChatCompletionText(choice) ||
|
|
103059
|
+
(typeof choice?.text === 'string' ? choice.text : '') ||
|
|
103060
|
+
'';
|
|
103061
|
+
if (!raw.trim()) {
|
|
103062
|
+
logToTerminal('warn', '[Translate RU] Пустой ответ модели (content), перевод не выполнен');
|
|
103063
|
+
return;
|
|
103064
|
+
}
|
|
103065
|
+
const parsed = parsePairTranslationsJson(raw);
|
|
103066
|
+
if (!parsed || parsed.length === 0) {
|
|
103067
|
+
logToTerminal('warn', '[Translate RU] Не удалось разобрать JSON-массив переводов, превью:', raw.substring(0, 280));
|
|
102811
103068
|
return;
|
|
102812
|
-
|
|
103069
|
+
}
|
|
102813
103070
|
const translations = {};
|
|
102814
103071
|
parsed.forEach((item, idx) => {
|
|
102815
103072
|
translations[idx] = { titleRu: item.titleRu || '', textRu: item.textRu || '' };
|
|
102816
103073
|
});
|
|
102817
103074
|
setPairTranslations(translations);
|
|
103075
|
+
logToTerminal('log', '[Translate RU] OK, пар переведено:', parsed.length);
|
|
102818
103076
|
}
|
|
102819
103077
|
catch {
|
|
102820
|
-
|
|
103078
|
+
logToTerminal('warn', '[Translate RU] Ошибка запроса или разбора — см. консоль / лог');
|
|
102821
103079
|
}
|
|
102822
103080
|
finally {
|
|
102823
103081
|
setTranslatingPairs(false);
|
|
@@ -103339,7 +103597,21 @@ function App() {
|
|
|
103339
103597
|
};
|
|
103340
103598
|
const keywords = geoKeywords[geo.toUpperCase()] || ['продукт', 'проблема'];
|
|
103341
103599
|
logMsg('log', `🔑 Ключевые слова: ${keywords.join(', ')}`);
|
|
103342
|
-
const
|
|
103600
|
+
const { price: briefPrice, currency: briefCurrency, currencySymbol } = parsePriceAndCurrency(generatePriceWithCurrency);
|
|
103601
|
+
let priceBriefForValidation = '';
|
|
103602
|
+
if (briefPrice && briefCurrency) {
|
|
103603
|
+
const nv = parseFloat(String(briefPrice).replace(',', '.'));
|
|
103604
|
+
const oldNum = Number.isFinite(nv) && nv > 0 ? nv * 2 : NaN;
|
|
103605
|
+
const oldStr = Number.isFinite(oldNum)
|
|
103606
|
+
? (Number.isInteger(oldNum) ? String(oldNum) : (Math.round(oldNum * 100) / 100).toString().replace(/\.?0+$/, ''))
|
|
103607
|
+
: '';
|
|
103608
|
+
priceBriefForValidation = `Новая цена по брифу (после скидки -50%): ${briefPrice} ${briefCurrency}${currencySymbol ? `, символ валюты: ${currencySymbol}` : ''}. ${Number.isFinite(oldNum) ? `Ожидаемая старая цена до скидки: ${oldStr} ${briefCurrency} (2× новой).` : 'Старая цена на макете должна быть в 2 раза больше новой.'} Любые другие суммы — ошибка «не совпадает с брифом».`;
|
|
103609
|
+
logMsg('log', `💶 Эталон цен для валидации: новая ${briefPrice} ${briefCurrency}${oldStr ? `, старая ${oldStr}` : ''}`);
|
|
103610
|
+
}
|
|
103611
|
+
else {
|
|
103612
|
+
logMsg('log', '💶 Поле цены в приложении пустое — валидатор сверяет только визуал двух цен');
|
|
103613
|
+
}
|
|
103614
|
+
const validationPrompt = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.getValidationPrompt)(product, geo, keywords, approachName, undefined, priceBriefForValidation);
|
|
103343
103615
|
const requestBody = {
|
|
103344
103616
|
model: selectedValidationModel,
|
|
103345
103617
|
messages: [
|
|
@@ -103478,25 +103750,28 @@ function App() {
|
|
|
103478
103750
|
}
|
|
103479
103751
|
// Update balance after successful API call
|
|
103480
103752
|
fetchOpenRouterBalance();
|
|
103481
|
-
// Парсим результат
|
|
103482
|
-
const
|
|
103483
|
-
const
|
|
103484
|
-
|
|
103485
|
-
const
|
|
103486
|
-
|
|
103487
|
-
|
|
103488
|
-
|
|
103489
|
-
|
|
103490
|
-
|
|
103491
|
-
|
|
103492
|
-
|
|
103493
|
-
|
|
103494
|
-
|
|
103495
|
-
|
|
103496
|
-
|
|
103497
|
-
|
|
103753
|
+
// Парсим результат (английский STATUS — на случай ответа модели не по шаблону)
|
|
103754
|
+
const statusMatchRu = content.match(/СТАТУС\s*:\s*(OK|НУЖНА\s+ПЕРЕСБОРКА)/i);
|
|
103755
|
+
const statusMatchEn = content.match(/\bSTATUS\s*:\s*(OK|NEEDS?\s+REBUILD|FAIL|FAILED)\b/i);
|
|
103756
|
+
const statusToken = statusMatchRu?.[1] ?? statusMatchEn?.[1] ?? '';
|
|
103757
|
+
const status = (() => {
|
|
103758
|
+
if (!statusToken)
|
|
103759
|
+
return 'needs_rebuild';
|
|
103760
|
+
const u = statusToken.toUpperCase().replace(/\s+/g, ' ');
|
|
103761
|
+
if (u === 'OK')
|
|
103762
|
+
return 'ok';
|
|
103763
|
+
if (u.includes('NEED') || u === 'FAIL' || u === 'FAILED')
|
|
103764
|
+
return 'needs_rebuild';
|
|
103765
|
+
if (u.includes('НУЖНА') || u.includes('ПЕРЕСБОРКА'))
|
|
103766
|
+
return 'needs_rebuild';
|
|
103767
|
+
return 'needs_rebuild';
|
|
103768
|
+
})();
|
|
103769
|
+
let errors = extractLabeledValidationErrors(content).filter(e => !isValidationNegationLine(e));
|
|
103770
|
+
errors = dedupeValidationErrors(errors);
|
|
103771
|
+
// Противоречие: пересборка, а единственная строка была «ОШИБКА: нет» — тянем текст из хвоста ответа
|
|
103772
|
+
if (status === 'needs_rebuild' && errors.length === 0) {
|
|
103773
|
+
errors = dedupeValidationErrors(extractFallbackValidationErrorsBetweenStatusAndRecommendations(content));
|
|
103498
103774
|
}
|
|
103499
|
-
// Если статус "НУЖНА ПЕРЕСБОРКА", но ошибок нет в формате ОШИБКА, ищем в тексте
|
|
103500
103775
|
if (status === 'needs_rebuild' && errors.length === 0) {
|
|
103501
103776
|
const lines = content.split('\n').filter(line => line.includes('нарушено') ||
|
|
103502
103777
|
line.includes('проблема') ||
|
|
@@ -103504,10 +103779,20 @@ function App() {
|
|
|
103504
103779
|
line.includes('не хватает'));
|
|
103505
103780
|
if (lines.length > 0) {
|
|
103506
103781
|
errors.push(...lines.map(l => l.trim()).filter(l => l.length > 0));
|
|
103782
|
+
errors = dedupeValidationErrors(errors);
|
|
103507
103783
|
}
|
|
103508
103784
|
}
|
|
103509
103785
|
const totalElapsed = Date.now() - validationStartTime;
|
|
103510
|
-
const
|
|
103786
|
+
const snippet = status === 'needs_rebuild' && errors.length === 0
|
|
103787
|
+
? extractValidationFinalSnippet(content, 900)
|
|
103788
|
+
: null;
|
|
103789
|
+
const finalErrors = errors.length > 0
|
|
103790
|
+
? errors
|
|
103791
|
+
: status === 'needs_rebuild'
|
|
103792
|
+
? (snippet
|
|
103793
|
+
? [`Валидатор не выписал строки «ОШИБКА:». Фрагмент ответа: ${snippet}`]
|
|
103794
|
+
: ['Валидатор указал пересборку, но не перечислил причины (нет строк «ОШИБКА:»). Откройте полный текст ответа модели в логе или повторите проверку.'])
|
|
103795
|
+
: [];
|
|
103511
103796
|
logMsg('log', `✅ === Валидация завершена ===`);
|
|
103512
103797
|
logMsg('log', `⏱️ Общее время: ${Math.round(totalElapsed / 1000)}s`);
|
|
103513
103798
|
logMsg('log', `📊 Статус: ${status === 'ok' ? '✅ OK' : '❌ НУЖНА ПЕРЕСБОРКА'}`);
|
|
@@ -104146,8 +104431,8 @@ function App() {
|
|
|
104146
104431
|
}
|
|
104147
104432
|
addLog(formatLogMessage('log', '✅ Product image found'));
|
|
104148
104433
|
// Generate images with different approaches (количество по каждому подходу)
|
|
104149
|
-
const
|
|
104150
|
-
if (
|
|
104434
|
+
const expandedTasks = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.getCreoApproachExpandedTasks)();
|
|
104435
|
+
if (expandedTasks.length === 0) {
|
|
104151
104436
|
addLog(formatLogMessage('error', '❌ Укажите количество изображений (хотя бы 1) в настройках подходов'));
|
|
104152
104437
|
alert('Укажите количество изображений (1–4) хотя бы для одного подхода в настройках');
|
|
104153
104438
|
setGeneratingImages(false);
|
|
@@ -104156,11 +104441,15 @@ function App() {
|
|
|
104156
104441
|
// При "both" — на каждый подход генерируем и 1:1, и 2:3
|
|
104157
104442
|
const useBoth = imageAspectRatio === 'both';
|
|
104158
104443
|
const tasks = useBoth
|
|
104159
|
-
?
|
|
104160
|
-
{ approach:
|
|
104161
|
-
{ approach:
|
|
104444
|
+
? expandedTasks.flatMap(t => [
|
|
104445
|
+
{ approach: t.approach, poolIndex: t.poolIndex, ratio: '1:1' },
|
|
104446
|
+
{ approach: t.approach, poolIndex: t.poolIndex, ratio: '2:3' }
|
|
104162
104447
|
])
|
|
104163
|
-
:
|
|
104448
|
+
: expandedTasks.map(t => ({
|
|
104449
|
+
approach: t.approach,
|
|
104450
|
+
poolIndex: t.poolIndex,
|
|
104451
|
+
ratio: imageAspectRatio
|
|
104452
|
+
}));
|
|
104164
104453
|
const additionalInfoLine = generateAdditionalInfo.trim()
|
|
104165
104454
|
? `\n📋 ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ О ПРОДУКТЕ: ${generateAdditionalInfo.trim()}`
|
|
104166
104455
|
: '';
|
|
@@ -104172,13 +104461,14 @@ function App() {
|
|
|
104172
104461
|
const [b1, b2, b3] = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.pickRandomBullets)(t.approach.name);
|
|
104173
104462
|
return `ОБЯЗАТЕЛЬНЫЕ БУЛЛЕТЫ (используй именно эти 3, в указанном порядке; переведи на язык ${generateGeo}): 1) ${b1} 2) ${b2} 3) ${b3}`;
|
|
104174
104463
|
})();
|
|
104175
|
-
return `🏷️ ПРОДУКТ: ${generateProduct}${additionalInfoLine}\n\n${basePromptStructure}🏷️ ПРОДУКТ (повторение для ясности): ${generateProduct}${additionalInfoLine}\n🎯 ПОДХОД: ${t.approach.name}\n\n${t.approach.prompt}\n\n${t.approach.headlineAngle}\n\n${bulletsLine}\n\nТекст — строго следуй правилам этого подхода (
|
|
104464
|
+
return `🏷️ ПРОДУКТ: ${generateProduct}${additionalInfoLine}\n\n${basePromptStructure}🏷️ ПРОДУКТ (повторение для ясности): ${generateProduct}${additionalInfoLine}\n🎯 ПОДХОД: ${t.approach.name}\n\n${t.approach.prompt}\n\n${t.approach.headlineAngle}\n\n${bulletsLine}\n\nТекст — строго следуй правилам этого подхода (верхний заголовок обязателен; на макете не пиши слова HOOK/CTA/CAPS; буллиты добавляй только если они разрешены для подхода).`;
|
|
104176
104465
|
});
|
|
104177
104466
|
// Initialize placeholders for all images
|
|
104178
104467
|
const initialPlaceholders = tasks.map((t, index) => ({
|
|
104179
104468
|
index: index + 1,
|
|
104180
104469
|
imageUrl: undefined,
|
|
104181
104470
|
approach: t.approach.name + (useBoth ? ` (${t.ratio})` : ''),
|
|
104471
|
+
creoApproachUiNumber: t.poolIndex + 1,
|
|
104182
104472
|
aspectRatio: t.ratio,
|
|
104183
104473
|
uploaded: false,
|
|
104184
104474
|
checking: false,
|
|
@@ -104190,7 +104480,8 @@ function App() {
|
|
|
104190
104480
|
regenerating: false,
|
|
104191
104481
|
customRegeneratePrompt: '',
|
|
104192
104482
|
failed: false,
|
|
104193
|
-
generating: false // In sequential mode, images start queued (not generating)
|
|
104483
|
+
generating: false, // In sequential mode, images start queued (not generating)
|
|
104484
|
+
remakeCount: 0
|
|
104194
104485
|
}));
|
|
104195
104486
|
setGeneratedImagesData(initialPlaceholders);
|
|
104196
104487
|
addLog(formatLogMessage('log', `📝 Generated prompts for ${tasks.length} images${useBoth ? ' (1:1 + 2:3 на каждый подход)' : ''}`));
|
|
@@ -104347,7 +104638,7 @@ function App() {
|
|
|
104347
104638
|
imageUrl: null,
|
|
104348
104639
|
success: false,
|
|
104349
104640
|
error: new Error('Generation did not complete'),
|
|
104350
|
-
approach:
|
|
104641
|
+
approach: tasks[i]?.approach.name || 'Unknown',
|
|
104351
104642
|
originalPrompt: imagePrompts[i],
|
|
104352
104643
|
productImageUrl: productImage.url
|
|
104353
104644
|
});
|
|
@@ -104599,7 +104890,8 @@ ${imageData.originalPrompt}
|
|
|
104599
104890
|
checkErrors: undefined,
|
|
104600
104891
|
uploaded: false, // Reset uploaded status since it's a new image
|
|
104601
104892
|
customRegeneratePrompt: '', // Reset custom prompt after regeneration
|
|
104602
|
-
failed: false // Mark as successful
|
|
104893
|
+
failed: false, // Mark as successful
|
|
104894
|
+
remakeCount: (img.remakeCount ?? 0) + 1
|
|
104603
104895
|
}
|
|
104604
104896
|
: img));
|
|
104605
104897
|
// Run validation on the new image (skip if disabled)
|
|
@@ -104714,7 +105006,8 @@ ${imageData.originalPrompt}
|
|
|
104714
105006
|
checkErrors: undefined,
|
|
104715
105007
|
uploaded: false,
|
|
104716
105008
|
customRegeneratePrompt: '',
|
|
104717
|
-
failed: false
|
|
105009
|
+
failed: false,
|
|
105010
|
+
remakeCount: (img.remakeCount ?? 0) + 1
|
|
104718
105011
|
}
|
|
104719
105012
|
: img));
|
|
104720
105013
|
// Validate new image (skip if disabled)
|
|
@@ -104836,11 +105129,10 @@ ${imageData.originalPrompt}
|
|
|
104836
105129
|
logToTerminal('log', msg.replace(/\[.*?\]\s*/, ''));
|
|
104837
105130
|
};
|
|
104838
105131
|
try {
|
|
104839
|
-
const filename =
|
|
105132
|
+
const filename = creativeImageUploadFilename(imageData.creoApproachUiNumber);
|
|
104840
105133
|
addLog(formatLogMessage('log', `📤 Uploading image ${imageData.index} to Drive: ${filename}`));
|
|
104841
105134
|
const driveUrl = await uploadImageToDrive(imageData.imageUrl, filename, folderId, addLog);
|
|
104842
105135
|
addLog(formatLogMessage('log', `✅ Image ${imageData.index} uploaded: ${driveUrl}`));
|
|
104843
|
-
// Update uploaded status
|
|
104844
105136
|
setGeneratedImagesData(prev => prev.map(img => img.index === imageData.index ? { ...img, uploaded: true } : img));
|
|
104845
105137
|
// (Removed legacy Generated Images links list)
|
|
104846
105138
|
}
|
|
@@ -104880,9 +105172,8 @@ ${imageData.originalPrompt}
|
|
|
104880
105172
|
: img));
|
|
104881
105173
|
try {
|
|
104882
105174
|
addLog(formatLogMessage('log', `📤 Uploading ${notUploaded.length} image(s) to Drive (parallel)...`));
|
|
104883
|
-
const
|
|
104884
|
-
|
|
104885
|
-
const filename = `${generateProduct.replace(/\s+/g, '_')}_${imageData.index}_${baseTs}_${i}.png`;
|
|
105175
|
+
const results = await Promise.allSettled(notUploaded.map((imageData) => {
|
|
105176
|
+
const filename = creativeImageUploadFilename(imageData.creoApproachUiNumber);
|
|
104886
105177
|
addLog(formatLogMessage('log', `📤 Starting upload image ${imageData.index}: ${filename}`));
|
|
104887
105178
|
return uploadImageToDrive(imageData.imageUrl, filename, folderId, addLog).then(driveUrl => ({
|
|
104888
105179
|
index: imageData.index,
|
|
@@ -104948,8 +105239,12 @@ ${imageData.originalPrompt}
|
|
|
104948
105239
|
try {
|
|
104949
105240
|
const parsed = new URL(toParse);
|
|
104950
105241
|
let path = parsed.pathname || '/';
|
|
104951
|
-
|
|
104952
|
-
|
|
105242
|
+
// Корень сайта — оставляем как `/`. Если есть «тело» пути (не один домен), завершающий `/` убираем, не добавляем.
|
|
105243
|
+
if (path !== '/' && path.length > 1) {
|
|
105244
|
+
while (path.length > 1 && path.endsWith('/')) {
|
|
105245
|
+
path = path.slice(0, -1);
|
|
105246
|
+
}
|
|
105247
|
+
}
|
|
104953
105248
|
const result = `https://${parsed.host}${path}${parsed.search}${parsed.hash}`;
|
|
104954
105249
|
setLink(result);
|
|
104955
105250
|
setLinkError('');
|
|
@@ -104958,6 +105253,22 @@ ${imageData.originalPrompt}
|
|
|
104958
105253
|
// invalid — leave as is
|
|
104959
105254
|
}
|
|
104960
105255
|
};
|
|
105256
|
+
const handleLinkPaste = (e) => {
|
|
105257
|
+
e.preventDefault();
|
|
105258
|
+
const pasted = e.clipboardData.getData('text/plain');
|
|
105259
|
+
const cleanedPaste = pasted.split('?')[0] ?? pasted;
|
|
105260
|
+
const input = e.currentTarget;
|
|
105261
|
+
const start = input.selectionStart ?? 0;
|
|
105262
|
+
const end = input.selectionEnd ?? 0;
|
|
105263
|
+
const newVal = link.slice(0, start) + cleanedPaste + link.slice(end);
|
|
105264
|
+
handleLinkChange(newVal);
|
|
105265
|
+
const pos = start + cleanedPaste.length;
|
|
105266
|
+
setTimeout(() => {
|
|
105267
|
+
const el = linkInputRef.current;
|
|
105268
|
+
if (el)
|
|
105269
|
+
el.setSelectionRange(pos, pos);
|
|
105270
|
+
}, 0);
|
|
105271
|
+
};
|
|
104961
105272
|
const extractFolderId = (url) => {
|
|
104962
105273
|
// Try to match format: /folders/([a-zA-Z0-9_-]+)
|
|
104963
105274
|
const foldersMatch = url.match(/folders\/([a-zA-Z0-9_-]+)/);
|
|
@@ -105004,7 +105315,13 @@ ${imageData.originalPrompt}
|
|
|
105004
105315
|
const name = f.name?.toLowerCase() || '';
|
|
105005
105316
|
return name !== 'product.png' && name !== 'product.jpg' && name !== 'product.webp';
|
|
105006
105317
|
});
|
|
105007
|
-
return filteredFiles
|
|
105318
|
+
return filteredFiles
|
|
105319
|
+
.filter((f) => f.id && f.name)
|
|
105320
|
+
.map((f) => ({
|
|
105321
|
+
fileId: f.id,
|
|
105322
|
+
name: f.name,
|
|
105323
|
+
viewUrl: `https://drive.google.com/file/d/${f.id}/view?usp=sharing`
|
|
105324
|
+
}));
|
|
105008
105325
|
};
|
|
105009
105326
|
const fetchProductImage = async (folderId) => {
|
|
105010
105327
|
const validToken = await getValidAccessToken();
|
|
@@ -105137,6 +105454,11 @@ ${imageData.originalPrompt}
|
|
|
105137
105454
|
},
|
|
105138
105455
|
brand: brandValue !== undefined ? brandValue : brand || '',
|
|
105139
105456
|
link: linkValue !== undefined ? linkValue : link || '',
|
|
105457
|
+
catalogUrlIncludeTextApproach,
|
|
105458
|
+
catalogUrlIncludeCreoApproach,
|
|
105459
|
+
catalogUrlTextApproachParam,
|
|
105460
|
+
catalogUrlCreoApproachParam,
|
|
105461
|
+
catalogLinkExtraMacros,
|
|
105140
105462
|
selectedPairApproaches: (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_52__.getSelectedPairApproaches)(),
|
|
105141
105463
|
imageApproachCounts: (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_52__.getImageApproachCounts)(),
|
|
105142
105464
|
savedAt: new Date().toISOString()
|
|
@@ -105287,6 +105609,24 @@ ${imageData.originalPrompt}
|
|
|
105287
105609
|
setLink(loadedData.link || '');
|
|
105288
105610
|
logToTerminal('log', '[Load Content] Loaded link:', loadedData.link || '(empty)');
|
|
105289
105611
|
}
|
|
105612
|
+
if (typeof loadedData.catalogUrlIncludeTextApproach === 'boolean') {
|
|
105613
|
+
setCatalogUrlIncludeTextApproach(loadedData.catalogUrlIncludeTextApproach);
|
|
105614
|
+
}
|
|
105615
|
+
if (typeof loadedData.catalogUrlIncludeCreoApproach === 'boolean') {
|
|
105616
|
+
setCatalogUrlIncludeCreoApproach(loadedData.catalogUrlIncludeCreoApproach);
|
|
105617
|
+
}
|
|
105618
|
+
if (typeof loadedData.catalogUrlTextApproachParam === 'string' && loadedData.catalogUrlTextApproachParam.trim()) {
|
|
105619
|
+
setCatalogUrlTextApproachParam(loadedData.catalogUrlTextApproachParam.trim());
|
|
105620
|
+
}
|
|
105621
|
+
if (typeof loadedData.catalogUrlCreoApproachParam === 'string' && loadedData.catalogUrlCreoApproachParam.trim()) {
|
|
105622
|
+
setCatalogUrlCreoApproachParam(loadedData.catalogUrlCreoApproachParam.trim());
|
|
105623
|
+
}
|
|
105624
|
+
if (typeof loadedData.catalogLinkExtraMacros === 'string') {
|
|
105625
|
+
setCatalogLinkExtraMacros(loadedData.catalogLinkExtraMacros);
|
|
105626
|
+
}
|
|
105627
|
+
else {
|
|
105628
|
+
setCatalogLinkExtraMacros(DEFAULT_CATALOG_LINK_EXTRA_MACROS);
|
|
105629
|
+
}
|
|
105290
105630
|
const mergedApproaches = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_52__.mergePromptApproachesFromDriveFile)(loadedData);
|
|
105291
105631
|
if (mergedApproaches) {
|
|
105292
105632
|
logToTerminal('log', '[Load Content] Applied text + image approach settings from Drive JSON');
|
|
@@ -105375,9 +105715,17 @@ ${imageData.originalPrompt}
|
|
|
105375
105715
|
for (let i = 0; i < pairCount; i++) {
|
|
105376
105716
|
const title = titleList[i];
|
|
105377
105717
|
const text = textList[i];
|
|
105718
|
+
const pairApproachIdx = lastUsedApproachIndices[i] ?? i;
|
|
105719
|
+
const textApproachUiNumber = String(pairApproachIdx + 1);
|
|
105378
105720
|
for (const image of images) {
|
|
105379
105721
|
const id = `${brand}${idCounter++}`;
|
|
105380
|
-
const
|
|
105722
|
+
const creoFromFileName = parseCreoApproachLabelFromImageFileName(image.name);
|
|
105723
|
+
const rowLink = appendCreativeIdToCatalogLink(link, id, {
|
|
105724
|
+
textApproach: catalogUrlIncludeTextApproach ? textApproachUiNumber : undefined,
|
|
105725
|
+
creoApproach: catalogUrlIncludeCreoApproach && creoFromFileName ? creoFromFileName : undefined,
|
|
105726
|
+
textParamKey: catalogUrlTextApproachParam,
|
|
105727
|
+
creoParamKey: catalogUrlCreoApproachParam
|
|
105728
|
+
}, catalogLinkExtraMacros);
|
|
105381
105729
|
rows.push([
|
|
105382
105730
|
id,
|
|
105383
105731
|
title,
|
|
@@ -105386,7 +105734,7 @@ ${imageData.originalPrompt}
|
|
|
105386
105734
|
'new',
|
|
105387
105735
|
'10,00 USD',
|
|
105388
105736
|
rowLink,
|
|
105389
|
-
image,
|
|
105737
|
+
image.viewUrl,
|
|
105390
105738
|
brand
|
|
105391
105739
|
]);
|
|
105392
105740
|
}
|
|
@@ -105722,7 +106070,23 @@ ${imageData.originalPrompt}
|
|
|
105722
106070
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: { xs: 'column', sm: 'row' }, spacing: 2 },
|
|
105723
106071
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "Brand (Short ID)", variant: "outlined", sx: { flex: '0 0 320px', minWidth: 280 }, value: brand, InputProps: { readOnly: true }, placeholder: "\u0410\u0432\u0442\u043E: \u0442\u043E\u0432\u0430\u0440-\u0433\u0435\u043E-\u0446\u0435\u043D\u0430", helperText: "\u0410\u0432\u0442\u043E\u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F \u0438\u0437 \u0442\u043E\u0432\u0430\u0440\u0430, \u0433\u0435\u043E \u0438 \u0446\u0435\u043D\u044B" }),
|
|
105724
106072
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { flex: 1, minWidth: 0 } },
|
|
105725
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "Link", variant: "outlined", fullWidth: true, value: link, onChange: (e) => handleLinkChange(e.target.value), onBlur: handleLinkBlur, error: !!linkError, helperText: linkError, placeholder: "https://example.com/product/" }))),
|
|
106073
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "Link", variant: "outlined", fullWidth: true, value: link, onChange: (e) => handleLinkChange(e.target.value), onBlur: handleLinkBlur, error: !!linkError, helperText: linkError, placeholder: "https://example.com/product/", inputRef: linkInputRef, InputProps: { onPaste: handleLinkPaste } }))),
|
|
106074
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', gap: 1, alignItems: 'flex-start', mt: 1.5 } },
|
|
106075
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "\u0414\u043E\u043F. \u043C\u0430\u043A\u0440\u043E\u0441\u044B (\u043A\u0430\u0442\u0430\u043B\u043E\u0433)", variant: "outlined", size: "small", fullWidth: true, multiline: true, minRows: 2, value: catalogLinkExtraMacros, onChange: (e) => setCatalogLinkExtraMacros(e.target.value), placeholder: DEFAULT_CATALOG_LINK_EXTRA_MACROS, helperText: "\u0414\u043E\u0431\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u043F\u043E\u0441\u043B\u0435 creative_id \u0438 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432 \u043F\u043E\u0434\u0445\u043E\u0434\u043E\u0432. \u0421\u043E\u0445\u0440\u0430\u043D\u044F\u0435\u0442\u0441\u044F \u0432 JSON \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043A \u043F\u0430\u043F\u043A\u0438.", sx: { '& .MuiInputBase-input': { fontFamily: 'monospace', fontSize: 12 } } }),
|
|
106076
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 0.5, sx: { flexShrink: 0, mt: 0.5 } },
|
|
106077
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "outlined", onClick: () => setCatalogLinkExtraMacros(DEFAULT_CATALOG_LINK_EXTRA_MACROS), sx: { textTransform: 'none', whiteSpace: 'nowrap' } }, "redtrack"),
|
|
106078
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "outlined", onClick: () => setCatalogLinkExtraMacros(DEFAULT_CATALOG_LINK_KEITARO_MACROS), sx: { textTransform: 'none', whiteSpace: 'nowrap' } }, "keitaro"))),
|
|
106079
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 1.5, sx: { mt: 1 } },
|
|
106080
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: { xs: 'column', sm: 'row' }, spacing: 2, alignItems: { xs: 'stretch', sm: 'flex-start' } },
|
|
106081
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_21__["default"], { control: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_22__["default"], { checked: catalogUrlIncludeTextApproach, onChange: e => setCatalogUrlIncludeTextApproach(e.target.checked), size: "small" }), label: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
106082
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2" }, "\u041F\u043E\u0434\u0445\u043E\u0434 \u043A \u0442\u0435\u043A\u0441\u0442\u0443 \u0432 URL \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430"),
|
|
106083
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", color: "text.secondary" }, "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u2014 \u043D\u043E\u043C\u0435\u0440 \u0441\u0442\u0440\u043E\u043A\u0438 \u043F\u043E\u0434\u0445\u043E\u0434\u0430 \u0434\u043B\u044F \u043F\u0430\u0440 \u0432 \u0442\u0430\u0431\u043B\u0438\u0446\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043A (1\u201310)")), sx: { alignItems: 'flex-start', mr: 0, flex: { sm: '1 1 200px' }, minWidth: 0 } }),
|
|
106084
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0434\u043B\u044F \u0442\u0435\u043A\u0441\u0442\u0430", size: "small", value: catalogUrlTextApproachParam, onChange: e => setCatalogUrlTextApproachParam(e.target.value), placeholder: DEFAULT_CATALOG_TEXT_APPROACH_PARAM, sx: { width: { xs: '100%', sm: 200 }, flexShrink: 0 }, helperText: `По умолчанию: ${DEFAULT_CATALOG_TEXT_APPROACH_PARAM}` })),
|
|
106085
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: { xs: 'column', sm: 'row' }, spacing: 2, alignItems: { xs: 'stretch', sm: 'flex-start' } },
|
|
106086
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_21__["default"], { control: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_22__["default"], { checked: catalogUrlIncludeCreoApproach, onChange: e => setCatalogUrlIncludeCreoApproach(e.target.checked), size: "small" }), label: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
106087
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2" }, "\u041F\u043E\u0434\u0445\u043E\u0434 \u043A \u043A\u0440\u0435\u043E \u0432 URL \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430"),
|
|
106088
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", color: "text.secondary" }, "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u2014 \u043D\u043E\u043C\u0435\u0440 N \u0438\u0437 \u0438\u043C\u0435\u043D\u0438 \u0444\u0430\u0439\u043B\u0430 N_xxxxxxxx.png \u043D\u0430 Drive (N = \u0441\u0442\u0440\u043E\u043A\u0430 \u043F\u043E\u0434\u0445\u043E\u0434\u0430 \u043A \u043A\u0440\u0435\u043E, 1\u201310); \u00AB (1)\u00BB \u043E\u0442 \u0434\u0443\u0431\u043B\u0438\u043A\u0430\u0442\u0430 Google \u0441\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F")), sx: { alignItems: 'flex-start', mr: 0, flex: { sm: '1 1 200px' }, minWidth: 0 } }),
|
|
106089
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0434\u043B\u044F \u043A\u0440\u0435\u043E", size: "small", value: catalogUrlCreoApproachParam, onChange: e => setCatalogUrlCreoApproachParam(e.target.value), placeholder: DEFAULT_CATALOG_CREO_APPROACH_PARAM, sx: { width: { xs: '100%', sm: 200 }, flexShrink: 0 }, helperText: `По умолчанию: ${DEFAULT_CATALOG_CREO_APPROACH_PARAM}` }))),
|
|
105726
106090
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_16__["default"], { sx: { my: 2 } }),
|
|
105727
106091
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true }, "AI Generation Settings"),
|
|
105728
106092
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
@@ -105730,20 +106094,20 @@ ${imageData.originalPrompt}
|
|
|
105730
106094
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2, sx: { mb: 2 } },
|
|
105731
106095
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "\u0413\u0435\u043E", variant: "outlined", fullWidth: true, value: generateGeo, onChange: (e) => setGenerateGeo(e.target.value), placeholder: "\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: \u0420\u0443\u043C\u044B\u043D\u0438\u044F" }),
|
|
105732
106096
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "\u0426\u0435\u043D\u0430 \u0438 \u0432\u0430\u043B\u044E\u0442\u0430", variant: "outlined", fullWidth: true, value: generatePriceWithCurrency, onChange: (e) => setGeneratePriceWithCurrency(e.target.value), placeholder: "\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: 29 euro, 11400 HUF, 149 RON \u0438\u043B\u0438 \u043B\u044E\u0431\u043E\u0439 \u0434\u0440\u0443\u0433\u043E\u0439 \u0444\u043E\u0440\u043C\u0430\u0442", helperText: "\u041B\u044E\u0431\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043B\u044F \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u044F \u0432 \u043F\u0440\u043E\u043C\u043F\u0442\u0435 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0439. \u041A\u043D\u043E\u043F\u043A\u0438 \u0441\u043F\u0440\u0430\u0432\u0430 \u043F\u043E\u0434\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442 \u0441\u0438\u043C\u0432\u043E\u043B \u0432\u0430\u043B\u044E\u0442\u044B, \u0446\u0438\u0444\u0440\u0443 \u0431\u0435\u0440\u0443\u0442 \u0438\u0437 \u043F\u043E\u043B\u044F (\u0438\u043B\u0438 99).", InputProps: {
|
|
105733
|
-
endAdornment: (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106097
|
+
endAdornment: (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_23__["default"], { position: "end" },
|
|
105734
106098
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 0.25, mr: -0.5 } },
|
|
105735
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106099
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { title: "\u041F\u043E\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044C $ (\u0446\u0438\u0444\u0440\u0430 \u0438\u0437 \u043F\u043E\u043B\u044F \u0438\u043B\u0438 99)" },
|
|
105736
106100
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { size: "small", "aria-label": "\u0411\u044B\u0441\u0442\u0440\u043E \u0434\u043E\u043B\u043B\u0430\u0440", onClick: () => {
|
|
105737
106101
|
const n = extractLeadingPriceNumber(generatePriceWithCurrency);
|
|
105738
106102
|
setGeneratePriceWithCurrency(`$${n}`);
|
|
105739
106103
|
}, edge: "end" },
|
|
105740
106104
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_35__["default"], { sx: { fontSize: 20 } }))),
|
|
105741
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106105
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { title: "\u041F\u043E\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u20AC (\u0446\u0438\u0444\u0440\u0430 \u0438\u0437 \u043F\u043E\u043B\u044F \u0438\u043B\u0438 99)" },
|
|
105742
106106
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { size: "small", "aria-label": "\u0411\u044B\u0441\u0442\u0440\u043E \u0435\u0432\u0440\u043E", onClick: () => {
|
|
105743
106107
|
const n = extractLeadingPriceNumber(generatePriceWithCurrency);
|
|
105744
106108
|
setGeneratePriceWithCurrency(`€${n}`);
|
|
105745
106109
|
}, edge: "end", sx: { minWidth: 34, fontSize: '1rem', fontWeight: 700 } }, "\u20AC")),
|
|
105746
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106110
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { title: "\u041F\u043E\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044C z\u0142 (\u0446\u0438\u0444\u0440\u0430 \u0438\u0437 \u043F\u043E\u043B\u044F \u0438\u043B\u0438 99)" },
|
|
105747
106111
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { size: "small", "aria-label": "\u0411\u044B\u0441\u0442\u0440\u043E \u0437\u043B\u043E\u0442\u044B\u0435", onClick: () => {
|
|
105748
106112
|
const n = extractLeadingPriceNumber(generatePriceWithCurrency);
|
|
105749
106113
|
setGeneratePriceWithCurrency(`${n} zł`);
|
|
@@ -105752,29 +106116,29 @@ ${imageData.originalPrompt}
|
|
|
105752
106116
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
105753
106117
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "\u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F (\u043D\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E)", variant: "outlined", fullWidth: true, multiline: true, minRows: 3, value: generateAdditionalInfo, onChange: (e) => setGenerateAdditionalInfo(e.target.value), placeholder: "\u0418\u043D\u0433\u0440\u0435\u0434\u0438\u0435\u043D\u0442\u044B, \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u044F \u043A \u043F\u0440\u043E\u043C\u043F\u0442\u0443 \u0438 \u0442.\u0434.", sx: { mb: 2 } })),
|
|
105754
106118
|
openaiApiKey && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2, sx: { mb: 2 } },
|
|
105755
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105756
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105757
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106119
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_25__["default"], { fullWidth: true, variant: "outlined" },
|
|
106120
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_26__["default"], null, "\u041C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0439"),
|
|
106121
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_27__["default"], { value: selectedImageModel, onChange: (e) => handleImageModelChange(e.target.value), label: "\u041C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0439", disabled: loadingImageModels || imageModels.length === 0 }, loadingImageModels ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_28__["default"], { disabled: true },
|
|
105758
106122
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1 } },
|
|
105759
106123
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 16 }),
|
|
105760
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043C\u043E\u0434\u0435\u043B\u0435\u0439...")))) : imageModels.length === 0 ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106124
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043C\u043E\u0434\u0435\u043B\u0435\u0439...")))) : imageModels.length === 0 ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_28__["default"], { disabled: true }, "\u041D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0445 \u043C\u043E\u0434\u0435\u043B\u0435\u0439")) : (imageModels.map((model) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_28__["default"], { key: model.id, value: model.id }, model.name))))),
|
|
105761
106125
|
!loadingImageModels && imageModels.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_19__["default"], null, selectedImageModel === _models__WEBPACK_IMPORTED_MODULE_2__.MODELS.imageGeneration
|
|
105762
106126
|
? 'Используется модель по умолчанию'
|
|
105763
106127
|
: 'Выбрана модель: ' + (imageModels.find(m => m.id === selectedImageModel)?.name || selectedImageModel)))),
|
|
105764
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105765
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105766
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106128
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_25__["default"], { fullWidth: true, variant: "outlined" },
|
|
106129
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_26__["default"], null, "\u041C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043A\u0440\u0435\u0430\u0442\u0438\u0432\u043E\u0432"),
|
|
106130
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_27__["default"], { value: selectedValidationModel, onChange: (e) => handleValidationModelChange(e.target.value), label: "\u041C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043A\u0440\u0435\u0430\u0442\u0438\u0432\u043E\u0432", disabled: loadingValidationModels || validationModels.length === 0 }, loadingValidationModels ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_28__["default"], { disabled: true },
|
|
105767
106131
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1 } },
|
|
105768
106132
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 16 }),
|
|
105769
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043C\u043E\u0434\u0435\u043B\u0435\u0439...")))) : validationModels.length === 0 ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106133
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043C\u043E\u0434\u0435\u043B\u0435\u0439...")))) : validationModels.length === 0 ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_28__["default"], { disabled: true }, "\u041D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0445 \u043C\u043E\u0434\u0435\u043B\u0435\u0439")) : (validationModels.map((model) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_28__["default"], { key: model.id, value: model.id }, model.name))))),
|
|
105770
106134
|
!loadingValidationModels && validationModels.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_19__["default"], null, selectedValidationModel === _models__WEBPACK_IMPORTED_MODULE_2__.MODELS.creativeValidation
|
|
105771
106135
|
? 'Используется модель по умолчанию'
|
|
105772
106136
|
: 'Выбрана модель: ' + (validationModels.find(m => m.id === selectedValidationModel)?.name || selectedValidationModel))),
|
|
105773
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106137
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_21__["default"], { control: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_22__["default"], { checked: validationDisabled, onChange: (e) => handleValidationDisabledChange(e.target.checked), color: "primary" }), label: "\u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443", sx: { mt: 1 } })))),
|
|
105774
106138
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2, sx: { mb: 2 } },
|
|
105775
106139
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "primary", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_36__["default"], null), onClick: handleGenerateContent, disabled: generating || loadingContentFromDrive || !openaiApiKey || !generateProduct.trim() || !generateGeo.trim(), sx: { flexGrow: 1 }, size: "large" }, generating ? 'Generating...' : 'Generate Titles & Descriptions'),
|
|
105776
106140
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 1, alignItems: "center", sx: { flexGrow: 1 } },
|
|
105777
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106141
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { title: imageAspectRatio === '1:1'
|
|
105778
106142
|
? '1:1 — квадрат (1024×1024 px)'
|
|
105779
106143
|
: imageAspectRatio === '2:3'
|
|
105780
106144
|
? '2:3 — портрет (1024×1536 px)'
|
|
@@ -105866,8 +106230,20 @@ ${imageData.originalPrompt}
|
|
|
105866
106230
|
} }, generatedImagesData.map((imageData) => {
|
|
105867
106231
|
const imgRatio = imageData.aspectRatio ?? (imageAspectRatio === 'both' ? '1:1' : imageAspectRatio);
|
|
105868
106232
|
const aspectRatioCss = imgRatio === '2:3' ? '2 / 3' : '1 / 1';
|
|
105869
|
-
|
|
105870
|
-
|
|
106233
|
+
const remakeN = imageData.remakeCount ?? 0;
|
|
106234
|
+
const remakeBg = getRemakeHighlightBackground(remakeN, darkMode);
|
|
106235
|
+
return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { key: imageData.index, sx: {
|
|
106236
|
+
position: 'relative',
|
|
106237
|
+
...(remakeBg
|
|
106238
|
+
? {
|
|
106239
|
+
p: 1,
|
|
106240
|
+
borderRadius: 1,
|
|
106241
|
+
bgcolor: remakeBg,
|
|
106242
|
+
boxSizing: 'border-box'
|
|
106243
|
+
}
|
|
106244
|
+
: {})
|
|
106245
|
+
} },
|
|
106246
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { title: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043A\u0440\u0435\u0430\u0442\u0438\u0432" },
|
|
105871
106247
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null,
|
|
105872
106248
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { size: "small", "aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043A\u0440\u0435\u0430\u0442\u0438\u0432", sx: {
|
|
105873
106249
|
position: 'absolute',
|
|
@@ -106083,7 +106459,7 @@ ${imageData.originalPrompt}
|
|
|
106083
106459
|
"/",
|
|
106084
106460
|
Math.max(generatedTitlesData.length, generatedTextsData.length),
|
|
106085
106461
|
")"),
|
|
106086
|
-
(generatedTitlesData.length > 0 || generatedTextsData.length > 0) && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106462
|
+
(generatedTitlesData.length > 0 || generatedTextsData.length > 0) && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { title: pairsJsonCopied ? 'Скопировано' : 'Скопировать все пары как JSON-массив' },
|
|
106087
106463
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { size: "small", "aria-label": "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043F\u0430\u0440\u044B \u043A\u0430\u043A JSON", onClick: () => void copyGeneratedPairsAsJson(), disabled: generating, sx: {
|
|
106088
106464
|
opacity: 0.28,
|
|
106089
106465
|
p: 0.35,
|
|
@@ -106146,11 +106522,11 @@ ${imageData.originalPrompt}
|
|
|
106146
106522
|
i + 1),
|
|
106147
106523
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", color: "text.secondary" }, pairLabel),
|
|
106148
106524
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { flexGrow: 1, minWidth: 8 } }),
|
|
106149
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106525
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { title: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043F\u0430\u0440\u0443" },
|
|
106150
106526
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null,
|
|
106151
106527
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { size: "small", color: "error", "aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043F\u0430\u0440\u0443", onClick: () => handleDeleteGeneratedPair(i), disabled: pairGenerating || generating },
|
|
106152
106528
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_42__["default"], { fontSize: "small" })))),
|
|
106153
|
-
translatingPairs && !pairTranslations[i] ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 13, sx: { color: 'text.disabled', ml: 0.5 } })) : pairTranslations[i] ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
106529
|
+
translatingPairs && !pairTranslations[i] ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 13, sx: { color: 'text.disabled', ml: 0.5 } })) : pairTranslations[i] ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { title: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
106154
106530
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { fontWeight: 700, display: 'block', mb: 0.5 } }, "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A:"),
|
|
106155
106531
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { display: 'block', mb: 1 } }, pairTranslations[i].titleRu),
|
|
106156
106532
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { fontWeight: 700, display: 'block', mb: 0.5 } }, "\u0422\u0435\u043A\u0441\u0442:"),
|
|
@@ -106880,7 +107256,7 @@ function PromptManagerDialog({ open, onClose }) {
|
|
|
106880
107256
|
case 'getImageCheckPrompt':
|
|
106881
107257
|
return (0,_prompts__WEBPACK_IMPORTED_MODULE_36__.getImageCheckPrompt)('${product}', '${geo}', true);
|
|
106882
107258
|
case 'getValidationPrompt':
|
|
106883
|
-
return (0,_prompts__WEBPACK_IMPORTED_MODULE_36__.getValidationPrompt)('${product}', '${geo}', ['keyword1', 'keyword2'], '${approachName}', true);
|
|
107259
|
+
return (0,_prompts__WEBPACK_IMPORTED_MODULE_36__.getValidationPrompt)('${product}', '${geo}', ['keyword1', 'keyword2'], '${approachName}', true, 'Новая по брифу: 29 EUR; старая (2×): 58 EUR — в рантайме подставляется из поля цены; в кастомном промпте плейсхолдер ${priceBrief}');
|
|
106884
107260
|
case 'getImageGenerationBasePrompt':
|
|
106885
107261
|
return (0,_prompts__WEBPACK_IMPORTED_MODULE_36__.getImageGenerationBasePrompt)('${generateGeo}', '${generatePrice}', '${generateCurrency}', true);
|
|
106886
107262
|
case 'getLandingPageSystemPrompt':
|
|
@@ -107060,7 +107436,14 @@ function PromptManagerDialog({ open, onClose }) {
|
|
|
107060
107436
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { type: "number", size: "small", value: count, onChange: (e) => {
|
|
107061
107437
|
const v = parseInt(e.target.value, 10);
|
|
107062
107438
|
handleImageApproachCountChange(i, isNaN(v) ? 0 : v);
|
|
107063
|
-
}, onBlur: handleImageCountBlur, inputProps: {
|
|
107439
|
+
}, onBlur: handleImageCountBlur, inputProps: {
|
|
107440
|
+
min: 0,
|
|
107441
|
+
max: 4,
|
|
107442
|
+
step: 1,
|
|
107443
|
+
onWheel: (e) => {
|
|
107444
|
+
e.preventDefault();
|
|
107445
|
+
},
|
|
107446
|
+
}, sx: { width: 56, '& .MuiInputBase-input': { textAlign: 'center', py: 0.5 } } })),
|
|
107064
107447
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", { style: { padding: '4px 8px', fontWeight: 500, whiteSpace: 'nowrap' } }, approach.name),
|
|
107065
107448
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", { style: { padding: '4px 8px', color: '#777' } }, approach.prompt.split('\n')[0])));
|
|
107066
107449
|
}))))),
|
|
@@ -107674,6 +108057,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
107674
108057
|
/* harmony export */ PAIR_APPROACH_POOL: () => (/* binding */ PAIR_APPROACH_POOL),
|
|
107675
108058
|
/* harmony export */ TEXTS_APPROACH_POOL: () => (/* binding */ TEXTS_APPROACH_POOL),
|
|
107676
108059
|
/* harmony export */ TITLES_APPROACH_POOL: () => (/* binding */ TITLES_APPROACH_POOL),
|
|
108060
|
+
/* harmony export */ getCreoApproachExpandedTasks: () => (/* binding */ getCreoApproachExpandedTasks),
|
|
107677
108061
|
/* harmony export */ getCreoApproaches: () => (/* binding */ getCreoApproaches),
|
|
107678
108062
|
/* harmony export */ getImageCheckPrompt: () => (/* binding */ getImageCheckPrompt),
|
|
107679
108063
|
/* harmony export */ getImageGenerationBasePrompt: () => (/* binding */ getImageGenerationBasePrompt),
|
|
@@ -108117,7 +108501,9 @@ function getImageCheckPrompt(product, geo, noOverride) {
|
|
|
108117
108501
|
/**
|
|
108118
108502
|
* Промпт для валидации рекламных креативов
|
|
108119
108503
|
*/
|
|
108120
|
-
function getValidationPrompt(product, geo, keywords, approachName, noOverride
|
|
108504
|
+
function getValidationPrompt(product, geo, keywords, approachName, noOverride,
|
|
108505
|
+
/** Краткий эталон цен из приложения (новая + ожидаемая старая при -50%); пусто — без сверки чисел */
|
|
108506
|
+
priceBrief) {
|
|
108121
108507
|
if (!noOverride) {
|
|
108122
108508
|
const override = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_0__.getPromptOverride)('getValidationPrompt');
|
|
108123
108509
|
if (override?.enabled && override.customPrompt) {
|
|
@@ -108126,63 +108512,49 @@ function getValidationPrompt(product, geo, keywords, approachName, noOverride) {
|
|
|
108126
108512
|
.replace(/\$\{product\}/g, product)
|
|
108127
108513
|
.replace(/\$\{geo\}/g, geo)
|
|
108128
108514
|
.replace(/\$\{keywords\}/g, keywords.join(', '))
|
|
108129
|
-
.replace(/\$\{approachLine\}/g, approachLine)
|
|
108515
|
+
.replace(/\$\{approachLine\}/g, approachLine)
|
|
108516
|
+
.replace(/\$\{priceBrief\}/g, priceBrief?.trim() || 'В приложении бриф цены не задан — сверяй только визуально две цены и -50%.');
|
|
108130
108517
|
}
|
|
108131
108518
|
}
|
|
108132
108519
|
const approachLine = approachName?.trim() ? `\nПОДХОД: ${approachName.trim()}\n` : '\n';
|
|
108133
|
-
// Resolve no-bullets conditions in code — don't leave "if approach = X" for the LLM
|
|
108134
|
-
const noBulletsApproachNames = CREO_APPROACHES.filter(a => a.noBullets).map(a => a.name);
|
|
108135
|
-
const isNoBullets = noBulletsApproachNames.includes(approachName?.trim() ?? '');
|
|
108136
108520
|
const isScreenshotReviews = (approachName?.trim() ?? '') === 'Скрин отзывов';
|
|
108137
|
-
//
|
|
108138
|
-
const step0BulletsHint =
|
|
108139
|
-
|
|
108140
|
-
|
|
108141
|
-
|
|
108142
|
-
const
|
|
108143
|
-
|
|
108144
|
-
|
|
108145
|
-
|
|
108146
|
-
|
|
108147
|
-
|
|
108148
|
-
|
|
108149
|
-
-
|
|
108150
|
-
-
|
|
108151
|
-
-
|
|
108152
|
-
|
|
108153
|
-
|
|
108154
|
-
-
|
|
108155
|
-
-
|
|
108156
|
-
|
|
108157
|
-
|
|
108158
|
-
|
|
108159
|
-
|
|
108160
|
-
-
|
|
108161
|
-
|
|
108162
|
-
|
|
108163
|
-
|
|
108164
|
-
|
|
108165
|
-
-
|
|
108166
|
-
-
|
|
108167
|
-
|
|
108168
|
-
- По умолчанию любой другой рекламный текст (бейджи/подписи/пояснения) запрещён.
|
|
108169
|
-
- НО допускаются (не обязательно) дополнительные бейджи ВНЕ упаковки — это НЕ ошибка, если ВСЕ элементы OTHER_TEXT подпадают под разрешённые типы:
|
|
108170
|
-
A) URGENCY (0–1 шт): бейдж срочности/дефицита — только ясные формулировки («только сегодня», «последние штуки», «акция до конца дня»). ЗАПРЕЩЕНО: «24h» и подобные — непонятно что означает.
|
|
108171
|
-
B) TRUST (0–3 шт, для «Минимализм / Clean Big Text» — макс 1): печати цветные, яркие, очень похожие на печать FDA. Подчёркивают: натуральность состава, премиальность, безопасность. Допустимо: качество, натуральные ингредиенты, экологичность, контроль качества. Печати — размер как минимум как у буллитов, крупные. Даже одна печать — крупная.
|
|
108172
|
-
Правила для каждого элемента OTHER_TEXT:
|
|
108173
|
-
* строго на языке ${geo} (без английских слов)
|
|
108174
|
-
* без цены/скидки/процентов (кроме обязательного поля DISCOUNT), без доменов/ссылок
|
|
108175
|
-
* TRUST‑печати — про натуральность/премиальность/безопасность (не про доставку/поддержку)
|
|
108176
|
-
Примеры стиля (адаптируй к языку GEO, не требуются):
|
|
108177
|
-
- URGENCY: «Ostatnie sztuki», «Tylko dziś», «Koniec dziś» (НЕ «24h» — непонятно)
|
|
108178
|
-
- TRUST: «Naturalna formuła», «Wysoka jakość» (для PL); «Ingrediente naturale», «Calitate» (для RO). ЗАПРЕЩЕНО: «NATURAL», «QUALITY», «100% NATURAL» — английские слова.
|
|
108179
|
-
- Если OTHER_TEXT содержит что-то вне этих типов, или URGENCY > 1, или TRUST > 3 (для Минимализма — TRUST > 1) — ОШИБКА.`;
|
|
108521
|
+
// Буллеты валидатором не проверяются (ни количество, ни наличие)
|
|
108522
|
+
const step0BulletsHint = 'BULLETS: кратко опиши, что видишь на макете (для справки). Количество и наличие буллетов НЕ валидируются.';
|
|
108523
|
+
const step2Bullets = `ШАГ 2 — БУЛЛЕТЫ:
|
|
108524
|
+
- Не проверяй и не оценивай количество, отсутствие или наличие буллетов. 0, 1, 2, 3 и больше — всё допустимо.
|
|
108525
|
+
- НЕ добавляй в финальный список ошибок ни одного пункта, связанного с буллетами (вертикальность, «ровно 3», иконки, читаемость буллетов и т.п.).`;
|
|
108526
|
+
const priceBriefTrim = priceBrief?.trim() || '';
|
|
108527
|
+
const stepPriceBrief = priceBriefTrim
|
|
108528
|
+
? `ШАГ P — ЦЕНЫ (сначала макет, потом бриф):
|
|
108529
|
+
Эталон из приложения:
|
|
108530
|
+
${priceBriefTrim}
|
|
108531
|
+
|
|
108532
|
+
1) ВНУТРЕННЯЯ ЛОГИКА НА МАКЕТЕ (главное):
|
|
108533
|
+
- Должны быть видны две суммы: новая (акционная) и старая (зачёркнутая). Старая ≈ 2× новой при скидке -50% (допуск до ~2% из‑за округления; «1 180» и «1180» — одно и то же).
|
|
108534
|
+
- Если отношение старая/новая ≈ 2 (в пределах допуска) — это уже доказательство корректной пары -50% на макете. В этом случае ЗАПРЕЩЕНО выводить ОШИБКУ формулировками вроде «выдуманные цены», «неверные суммы», «не соответствуют брифу» — даже если цифры в брифе другие или бриф кажется не тем.
|
|
108535
|
+
- Если пара математически согласована с -50% и процент «-50%» где‑либо читаем в зоне оффера (см. ШАГ 4) — не придумывай расхождение с брифом из‑за символа валюты (€ / EUR / MXN и т.д.) или пробелов.
|
|
108536
|
+
|
|
108537
|
+
2) СВЕРКА С БРИФОМ (только когда сопоставимо):
|
|
108538
|
+
- Сравнивай число НОВОЙ цены с брифом ТОЛЬКО если валюта на макете явно та же, что в брифе (EUR↔EUR, MXN↔MXN и т.д.; символ € и код EUR — одна валюта). Допускается разное оформление числа (пробелы тысяч, запятая/точка).
|
|
108539
|
+
- Если валюта на креативе другая, чем в брифе, или не уверен в коде валюты — не требуй совпадения цифр с брифом: достаточно пункта (1).
|
|
108540
|
+
- Ошибку по ценам ставь только если: (а) на макете нет двух цен / нет согласованности -50%, ИЛИ (б) валюта однозначно совпадает с брифом, а число новой цены явно другое (не в пределах округления).
|
|
108541
|
+
|
|
108542
|
+
3) Цена не должна быть оформлена как строка буллета с галочкой — отдельный блок.`
|
|
108543
|
+
: `ШАГ P — ЦЕНЫ (бриф числом не задан):
|
|
108544
|
+
- На макете должны быть ДВЕ цены, старая зачёркнута толсто, новая выразительна; пара должна визуально соответствовать -50% (старая ≈ 2× новой). Конкретные суммы с приложением не сверяй.`;
|
|
108545
|
+
const step3OtherText = isScreenshotReviews
|
|
108546
|
+
? `ШАГ 3 — Подход «Скрин отзывов»:
|
|
108547
|
+
- Блок отзывов (аватары, имена, звёзды, текст) — это нормальный контент креатива, не считай его лишним OTHER_TEXT.`
|
|
108548
|
+
: `ШАГ 3 — Доп. текст, бейджи, печати:
|
|
108549
|
+
- Не валидируй объём текста и не отклоняй креатив за «слишком много элементов», punch vs не punch, количество плашек или пустоту OTHER_TEXT.
|
|
108550
|
+
- Не требуй отсутствия trust/urgency/подписей — для любого подхода это не ошибка.
|
|
108551
|
+
- Язык рекламного текста (включая бейджи) — по-прежнему ШАГ 5. Неясные срочности вроде «24h» — по-прежнему ШАГ 6.`;
|
|
108180
108552
|
return `Ты — СТРОГИЙ валидатор рекламных креативов (1:1). Не улучшай и не предлагай идеи — только проверка и вердикт.
|
|
108181
108553
|
Продукт: ${product}. GEO/язык: ${geo}.${approachLine}
|
|
108182
108554
|
|
|
108183
108555
|
ВАЖНО:
|
|
108184
|
-
- Проверяй язык ТОЛЬКО для рекламного текста на макете (
|
|
108185
|
-
- Если сомневаешься,
|
|
108556
|
+
- Проверяй язык ТОЛЬКО для рекламного текста на макете (заголовок/CTA/цена/скидка/разрешённые бейджи из OTHER_TEXT; если есть буллеты — и их текст). Текст на самой упаковке/этикетке игнорируй (включая название/бренд вроде "${product}").
|
|
108557
|
+
- Если сомневаешься, мелкий текст на банке — этикетка или нет: для языка считай этикеткой и не валидируй. Исключение: **нижний оффер-блок** макета (две цены, зачёркивание, «-50%», кнопка CTA в одной полосе/ряду под картинкой продукта) — это всегда рекламный слой **вне упаковки**, даже рядом с фото банки; цены и скидку оттуда учитывай в PRICE/DISCOUNT и не считай «на упаковке».
|
|
108186
108558
|
- Если текст плохо читается/микрошрифт — это ошибка.
|
|
108187
108559
|
|
|
108188
108560
|
ШАГ 0 — Сначала выпиши распознанный текст (как ты его видишь на макете):
|
|
@@ -108192,9 +108564,7 @@ PRICE: "..." (если нет — "нет")
|
|
|
108192
108564
|
DISCOUNT: "..." (если нет — "нет")
|
|
108193
108565
|
CTA: "..." (если нет — "нет")
|
|
108194
108566
|
OTHER_TEXT: ["..."] (любой другой рекламный текст на макете ВНЕ упаковки и ВНЕ элементов выше; название/бренд на упаковке НЕ включай)
|
|
108195
|
-
(
|
|
108196
|
-
URGENCY_BADGE: "..." (бейдж срочности, если есть; «24h» — ОШИБКА, непонятно что означает)
|
|
108197
|
-
TRUST_BADGES: ["..."] (печати цветные, яркие, в стиле FDA; про натуральность/премиальность/безопасность; размер как у буллитов — крупные, если есть)
|
|
108567
|
+
(опционально, для ясности): URGENCY_BADGE, TRUST_BADGES — перечисли, если есть
|
|
108198
108568
|
|
|
108199
108569
|
ШАГ 1 — HOOK/HEADLINE (критично):
|
|
108200
108570
|
- Должен быть ВЫШЕ всех элементов и читабелен (хорошо читается на телефоне)
|
|
@@ -108207,35 +108577,38 @@ TRUST_BADGES: ["..."] (печати цветные, яркие, в стиле FD
|
|
|
108207
108577
|
|
|
108208
108578
|
${step2Bullets}
|
|
108209
108579
|
|
|
108580
|
+
${stepPriceBrief}
|
|
108581
|
+
|
|
108210
108582
|
ШАГ 2.5 — CLAIMS CHECK:
|
|
108211
108583
|
- Клеймы по результату (жёсткие/абсолютные или мягкие) — НЕ проверяются и НЕ запрещаются. Не блокируй за формулировки обещаний результата.
|
|
108212
108584
|
|
|
108213
|
-
${
|
|
108585
|
+
${step3OtherText}
|
|
108214
108586
|
|
|
108215
|
-
ШАГ 4 — CTA
|
|
108587
|
+
ШАГ 4 — CTA и визуал цены/скидки (критично):
|
|
108216
108588
|
- CTA: на языке ${geo}, хорошо заметная кнопка (позиция НЕ важна: можно вправо/влево/по центру). Главное — CTA читабельна и выглядит как кнопка.
|
|
108217
|
-
-
|
|
108218
|
-
- Скидка ОБЯЗАТЕЛЬНА: «-50%»
|
|
108589
|
+
- Блок цен: две суммы, старая зачёркнута ТОЛСТОЙ линией, новая выразительно (детали и сверка чисел с брифом — в ШАГ P). Если только одна цена — ОШИБКА.
|
|
108590
|
+
- Скидка ОБЯЗАТЕЛЬНА: читаемое «-50%». **Отдельный видимый бейдж** = любая заметная плашка/круг/прямоугольник с текстом «-50%» в **той же зоне оффера**, что и цены (сбоку от сумм, над/под ценовой строкой, между ценой и CTA) — это ОК, не требуй отдельного «далёкого» элемента. «НЕ на упаковке» означает только: не напечатано на этикетке/банке товара; плашка в нижней рекламной полосе макета **не** считается упаковкой.
|
|
108591
|
+
- Если в ШАГ 0 в DISCOUNT ты указал «-50%» (или эквивалент) — ЗАПРЕЩЕНО ставить ошибку «скидка не отображается» / «нет отдельного бейджа».
|
|
108219
108592
|
- Не считать ошибкой, если скидка/цена конкурируют с CTA по акценту — это допустимо, пока CTA остаётся хорошо заметной и читабельной.
|
|
108220
108593
|
- Если скидка указана и она не равна -50% — ошибка.
|
|
108221
108594
|
|
|
108222
108595
|
ШАГ 5 — ЯЗЫК (критично):
|
|
108223
|
-
- В рекламном тексте (HOOK/HEADLINE
|
|
108596
|
+
- В рекламном тексте (HOOK/HEADLINE, текст на буллетах если есть, CTA/PRICE/DISCOUNT/OTHER_TEXT) НЕТ английских слов. Если есть — ошибка.
|
|
108597
|
+
- ЗАПРЕЩЁННЫЕ служебные подписи на макете (частая ошибка модели): слова HOOK, CTA, CAPS, BULLET, HEADLINE, PRICE, DISCOUNT как видимый текст на плашках или кнопке — ОШИБКА (это метки из брифа, не для читателя).
|
|
108224
108598
|
- Все слова соответствуют GEO/языку ${geo}. Если смешение языков — ошибка.
|
|
108225
108599
|
|
|
108226
108600
|
ШАГ 6 — КОМПОЗИЦИЯ:
|
|
108227
108601
|
- Если креатив без человека (lifestyle/clean), проверь наличие контекста использования (кухня, стол, тумбочка, и т.д.). Продукт на полностью пустом/белом фоне без контекста — РЕКОМЕНДАЦИЯ к улучшению (не критичная ошибка, но слабый визуал)
|
|
108228
|
-
- КОНТРАСТ ПЛАШЕК: если
|
|
108229
|
-
-
|
|
108230
|
-
- TRUST‑печати: если печати мелкие или текст нечитабелен на телефоне — ОШИБКА (
|
|
108231
|
-
- СКИДКА «-50%»:
|
|
108232
|
-
- ЦЕНА: ОБЯЗАТЕЛЬНО ДВЕ — старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Одна цена или тонкое зачёркивание — ОШИБКА.
|
|
108602
|
+
- КОНТРАСТ ПЛАШЕК: если HOOK, CTA или блок цен визуально сливается с фоном и текст плохо читается — это ОШИБКА. (Контраст буллетов не проверяй.)
|
|
108603
|
+
- Две цены и зачёркивание старой — см. ШАГ 4 и ШАГ P. Цена не должна быть оформлена как элемент списка с галочкой в стиле бенефитов.
|
|
108604
|
+
- TRUST‑печати: если печати мелкие или текст нечитабелен на телефоне — ОШИБКА (должны быть крупными).
|
|
108605
|
+
- СКИДКА «-50%»: как в ШАГ 4 — плашка рядом с ценами в оффер-блоке засчитывается; если DISCOUNT в ШАГ 0 заполнен — не дублируй ошибку про бейдж.
|
|
108233
108606
|
- Бейджи срочности: «24h» и подобные неясные формулировки — ОШИБКА (непонятно что означает). Ясные («только сегодня», «последние штуки») — ок.
|
|
108234
108607
|
|
|
108235
108608
|
ФИНАЛ:
|
|
108236
108609
|
Выведи строго:
|
|
108237
108610
|
СТАТУС: OK / НУЖНА ПЕРЕСБОРКА
|
|
108238
|
-
Затем список ошибок, каждая строка начинается с "ОШИБКА:" (кратко, по делу). Если ошибок нет —
|
|
108611
|
+
Затем список ошибок, каждая строка начинается с "ОШИБКА:" (кратко, по делу). Одну и ту же проблему не повторяй — не больше одной строки на один тип нарушения. Если СТАТУС: НУЖНА ПЕРЕСБОРКА — ОБЯЗАТЕЛЬНО минимум одна конкретная строка "ОШИБКА: ..." (что именно не так); нельзя писать только "ОШИБКА: нет". Если ошибок нет — СТАТУС: OK и "ОШИБКА: нет".
|
|
108239
108612
|
Затем список рекомендаций (если есть), каждая строка начинается с "РЕКОМЕНДАЦИЯ:" (кратко, по делу). Если рекомендаций нет — напиши "РЕКОМЕНДАЦИЯ: нет".
|
|
108240
108613
|
Не блокируй за клеймы по результату или формулировки обещаний.`;
|
|
108241
108614
|
}
|
|
@@ -108271,6 +108644,9 @@ function getImageGenerationBasePrompt(generateGeo, generatePrice, generateCurren
|
|
|
108271
108644
|
ВАЖНО: изображение должно быть ${ratioShape} — строго соблюдай пропорции холста.
|
|
108272
108645
|
Язык текста: ${generateGeo}.
|
|
108273
108646
|
|
|
108647
|
+
🚫 СЛУЖЕБНЫЕ СЛОВА НЕ РИСОВАТЬ НА КАРТИНКЕ (КРИТИЧНО):
|
|
108648
|
+
Слова HOOK, CTA, CAPS, BULLET, HEADLINE, PRICE, DISCOUNT, BULLETS, LAYER и любые похожие английские метки из этой инструкции (в т.ч. thumb‑stop) — только для тебя; на макете их НЕТ ни на кнопке, ни на плашках, ни мелким текстом. Кнопка: только реальный призыв на языке ${generateGeo} (1–2 слова), без префикса «CTA». Заголовок: только живой текст оффера, без слова HOOK. Заголовок делай прописными буквами на языке ${generateGeo}, но не пиши на изображении слово CAPS и не подписывай блоки ярлыками.
|
|
108649
|
+
|
|
108274
108650
|
🚨 КРИТИЧНО — РЕЛЕВАНТНОСТЬ ПРОДУКТУ (читай ЭТО ПЕРВЫМ):
|
|
108275
108651
|
- Название продукта передано в начале промпта (строка «🏷️ ПРОДУКТ: ...»). Прочитай его ПРЯМО СЕЙЧАС и определи категорию.
|
|
108276
108652
|
- Категории и их проблемы: суставы/колени → боль в суставах, скованность, тугоподвижность; пищеварение → дискомфорт после еды, тяжесть; сон → бессонница, усталость; похудение → лишний вес; простата → частые позывы, дискомфорт.
|
|
@@ -108301,7 +108677,7 @@ HOOK / HEADLINE (строгое правило):
|
|
|
108301
108677
|
- 1–4 строки; до 12 слов. Без переноса внутри слова
|
|
108302
108678
|
- ОБЯЗАТЕЛЬНО содержит ключевое слово проблемы ЯВНО. Примеры по категориям: простата → простатит, простата; похудение → лишний вес, похудение; суставы → боль в суставах, колени, скованность; пищеварение → дискомфорт, вздутие, тяжесть; сон → бессонница, усталость. Без ключевого слова — ОШИБКА
|
|
108303
108679
|
- HOOK ВЫШЕ всех элементов (продукт, буллеты, CTA, цена). Самый крупный текстовый элемент на креативе
|
|
108304
|
-
- HOOK должен быть ВИЗУАЛЬНО “тяжёлым”: ОЧЕНЬ крупный, ТОЛСТЫЙ/жирный шрифт,
|
|
108680
|
+
- HOOK должен быть ВИЗУАЛЬНО “тяжёлым”: ОЧЕНЬ крупный, ТОЛСТЫЙ/жирный шрифт, весь текст заголовка ПРОПИСНЫМИ буквами на языке ${generateGeo}, на яркой контрастной плашке/подложке. Это верхний главный элемент, цепляющий внимание в ленте
|
|
108305
108681
|
- Можно (не обязательно) включить СРОК прямо в HOOK (например «7 dni», «14 dni») как триггер
|
|
108306
108682
|
- Заголовок должен быть РЕЗКИМ и призывным: короткие рубленые фразы, вопросы и предупреждения допустимы. Можно использовать обращение на «ты» (в языке GEO: «Masz…», «Twój…») и вопросительный знак
|
|
108307
108683
|
- заголовок НЕ должен быть абстрактным слоганом/лозунгом или метафорой без конкретной боли. Допускается «thumb‑stop» стиль (вызов/вопрос/предупреждение), если это конкретно и релевантно категории
|
|
@@ -108352,7 +108728,7 @@ CTA > PRICE:
|
|
|
108352
108728
|
❗ ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → цена и «-50%» могут быть более крупными и заметными, но CTA всё равно должен оставаться очень заметным и выглядеть как кнопка (не теряется на фоне)
|
|
108353
108729
|
|
|
108354
108730
|
ПОКАЗЫВАЙ ТОЛЬКО ЭТИ ТЕКСТОВЫЕ ЭЛЕМЕНТЫ:
|
|
108355
|
-
- HOOK (1–4 строки; выше всех элементов; самый крупный;
|
|
108731
|
+
- HOOK (1–4 строки; выше всех элементов; самый крупный; прописные буквы; жирный; на яркой контрастной подложке; ключевое слово проблемы явно)
|
|
108356
108732
|
- 3 буллета (крупные, с иконками/галочками ✓, на контрастных подложках, НЕ на банке/упаковке)
|
|
108357
108733
|
- Цена: ОБЯЗАТЕЛЬНО ДВЕ — старая (2×${generatePrice} ${generateCurrency}) зачёркнута ТОЛСТОЙ контрастной линией + новая ${generatePrice} ${generateCurrency} выразительно. Одна цена = ОШИБКА. Тонкая линия зачёркивания = ОШИБКА. Без слов. Не на упаковке.
|
|
108358
108734
|
- Скидка: «-50%» ОБЯЗАТЕЛЬНО отдельным видимым бейджем (не только в цене!). Процент скидки должен быть явно читаем. Ярко, заметно, строго НЕ на банке/упаковке, визуально слабее CTA
|
|
@@ -108388,11 +108764,11 @@ ANTI-TEMPLATE DIVERSITY (КРИТИЧНО):
|
|
|
108388
108764
|
* 🎯 ПОДХОД: Визуализация проблемы → Инфографика/схема: слева проблема (иконка/схема зоны тела, релевантной продукту), справа решение + продукт. HOOK сверху по центру, BULLETS справа или снизу (вертикально), CTA снизу справа, PRICE/DISCOUNT возле CTA. Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне). Красный акцент только на проблеме.
|
|
108389
108765
|
* 🎯 ПОДХОД: Power / Сила решения → БЕЗ человека. ДИНАМИЧНЫЙ комикс‑кадр, диагональная композиция. Продукт как “герой” + power‑иконки (щит/молния/бёрст). HOOK сверху слева на яркой плашке, BULLETS слева ниже, CTA снизу справа, PRICE/DISCOUNT возле CTA. Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне).
|
|
108390
108766
|
* 🎯 ПОДХОД: Минимализм / Clean Big Text → Белый/градиентный фон, минимум элементов. ОГРОМНЫЙ HOOK занимает верх/центр, продукт крупно (центр/право), CTA снизу по центру, PRICE/DISCOUNT рядом (слабее CTA). Trust‑печати (если есть) — размер как минимум как у буллитов, крупные и читабельные.
|
|
108391
|
-
* 🎯 ПОДХОД: Problem Visual Punch → БЕЗ человека. Яркий сплошной фон (красный/оранжевый) или агрессивный градиент. В центре КРУПНАЯ иконка/схема зоны тела, релевантной продукту (для суставов — колено; для пищеварения — желудок), с красным свечением. Продукт крупно рядом. HOOK 1–4 строки, до 12 слов, огромный
|
|
108392
|
-
* 🎯 ПОДХОД: Любительский Примитивизм → БЕЗ человека. Чёрный или кислотный сплошной фон. Продукт по центру или справа без декора. HOOK сверху — 1–4 строки, до 12 слов, крупный, грубый bold/рукописный,
|
|
108767
|
+
* 🎯 ПОДХОД: Problem Visual Punch → БЕЗ человека. Яркий сплошной фон (красный/оранжевый) или агрессивный градиент. В центре КРУПНАЯ иконка/схема зоны тела, релевантной продукту (для суставов — колено; для пищеварения — желудок), с красным свечением. Продукт крупно рядом. HOOK 1–4 строки, до 12 слов, огромный прописной текст, выше всех. БЕЗ буллитов. Две цены + «-50%» крупно и заметно. Кнопка призыва контрастная (только текст на языке ${generateGeo}, без слова CTA)
|
|
108768
|
+
* 🎯 ПОДХОД: Любительский Примитивизм → БЕЗ человека. Чёрный или кислотный сплошной фон. Продукт по центру или справа без декора. HOOK сверху — 1–4 строки, до 12 слов, крупный, грубый bold/рукописный, прописные буквы, выше всех. Крупные надписи цены/скидки вокруг продукта. Кнопка призыва — плоская, без градиентов (только короткая фраза на языке ${generateGeo}). БЕЗ буллитов, бейджей, теней, иконок.
|
|
108393
108769
|
|
|
108394
108770
|
AUTO-CHECK (перед финалом):
|
|
108395
|
-
- HOOK: 1–4 строки, до 12 слов,
|
|
108771
|
+
- HOOK: 1–4 строки, до 12 слов, прописные буквы, жирный на контрастной плашке, выше всех элементов, ключевое слово проблемы явно, язык ${generateGeo} без английских слов (включая метки HOOK/CTA/CAPS)
|
|
108396
108772
|
- Буллеты: по умолчанию ровно 3, каждый <= 4 слов, без запятых, не предложения. ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → буллетов 0 (запрещены)
|
|
108397
108773
|
- Нет лишнего текста кроме: HOOK, буллетов, цены, скидки, кнопки (+ опционально 1 бейдж срочности + опционально 1–3 trust‑печати)
|
|
108398
108774
|
- Есть скидка «-50%» (вне упаковки) и она слабее CTA
|
|
@@ -108404,10 +108780,10 @@ AUTO-CHECK (перед финалом):
|
|
|
108404
108780
|
ВАЖНО: Твой ответ — это ИЗОБРАЖЕНИЕ, не текст. Не пиши план, не перечисляй элементы текстом, не рассуждай, не вызывай tools. Сразу генерируй визуальный креатив как картинку.`;
|
|
108405
108781
|
}
|
|
108406
108782
|
/**
|
|
108407
|
-
*
|
|
108408
|
-
*
|
|
108783
|
+
* Развёрнутый список задач крео: каждый подход повторяется N раз (0–4), N = imageApproachCounts[i].
|
|
108784
|
+
* poolIndex — индекс строки в CREO_APPROACHES (0-based); в UI таблице это номер строки poolIndex + 1 (1–10).
|
|
108409
108785
|
*/
|
|
108410
|
-
function
|
|
108786
|
+
function getCreoApproachExpandedTasks() {
|
|
108411
108787
|
const counts = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_0__.getImageApproachCounts)();
|
|
108412
108788
|
const result = [];
|
|
108413
108789
|
for (let i = 0; i < CREO_APPROACHES.length && i < counts.length; i++) {
|
|
@@ -108427,46 +108803,50 @@ function getCreoApproaches() {
|
|
|
108427
108803
|
_debugLog(`✅ Using CUSTOM approach: ${approach.name}`, `hasPrompt=${!!override.customPrompt}`, `hasHeadline=${!!override.customHeadlineAngle}`, `hasBullets=${!!override.customBulletsFocus}`);
|
|
108428
108804
|
}
|
|
108429
108805
|
for (let k = 0; k < counts[i]; k++) {
|
|
108430
|
-
result.push(resolved);
|
|
108806
|
+
result.push({ approach: resolved, poolIndex: i });
|
|
108431
108807
|
}
|
|
108432
108808
|
}
|
|
108433
108809
|
return result;
|
|
108434
108810
|
}
|
|
108811
|
+
/** Плоский список подходов (без индекса строки) — для обратной совместимости. */
|
|
108812
|
+
function getCreoApproaches() {
|
|
108813
|
+
return getCreoApproachExpandedTasks().map(t => t.approach);
|
|
108814
|
+
}
|
|
108435
108815
|
const CREO_APPROACHES = [
|
|
108436
108816
|
{
|
|
108437
108817
|
name: 'Эксперт / Авторитет',
|
|
108438
108818
|
prompt: `ЭКСПЕРТ / АВТОРИТЕТ (без человека): стиль “рекомендация эксперта”, но БЕЗ человека. Профессиональный контекст — аптечные полки на фоне / медицинский планшет / стетоскоп / рецептурный блокнот как реквизит рядом с продуктом. Продукт в центре как “рекомендованное решение”. Чистый профессиональный фон, высокий контраст. Trust‑печати (1–3 шт): цветные, яркие, очень похожие на печать FDA; подчёркивают натуральность состава, премиальность, безопасность. Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная.`,
|
|
108439
|
-
headlineAngle: `HOOK: угол «предупреждение / интрига / специалисты знают». Формат: вопрос‑предупреждение ИЛИ “специалисты знают/используют” (адаптируй под GEO). 1–4 строки, до 12 слов,
|
|
108819
|
+
headlineAngle: `HOOK: угол «предупреждение / интрига / специалисты знают». Формат: вопрос‑предупреждение ИЛИ “специалисты знают/используют” (адаптируй под GEO). 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно.`,
|
|
108440
108820
|
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + соц.доказательство/формула) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор для этого подхода — не повторяй комбинации из других креативов.`
|
|
108441
108821
|
},
|
|
108442
108822
|
{
|
|
108443
108823
|
name: 'Lifestyle / Момент приёма',
|
|
108444
108824
|
prompt: `LIFESTYLE / МОМЕНТ ПРИЁМА: продукт в контексте использования (кухня/стол/спальня/тумбочка). Без человека, но с "историей" (стакан воды, чай, тарелка, будильник/часы/календарь). Срочность: реквизит (часы/таймер‑иконка) и/или опциональный urgency‑бейдж (1–3 слова) в углу кадра — только ясные формулировки («только сегодня», «последние штуки»), ЗАПРЕЩЕНО «24h». Высокий контраст, яркий акцент на продукте. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. ВАЖНО: буллиты располагаются вертикально и всегда читабельны на экране телефона без зума — даже если уходят вбок, минимальный размер шрифта буллитов не уменьшается. Если буллиты не вмещаются сбоку с нужным шрифтом — переставь их вниз.`,
|
|
108445
|
-
headlineAngle: `HOOK: угол «цифры + срочность». Формат: число/процент + ограничение времени/“сейчас/сегодня” (адаптируй под GEO), 1–4 строки, до 12 слов,
|
|
108825
|
+
headlineAngle: `HOOK: угол «цифры + срочность». Формат: число/процент + ограничение времени/“сейчас/сегодня” (адаптируй под GEO), 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно.`,
|
|
108446
108826
|
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (срок + цифра + действие/формула) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
108447
108827
|
},
|
|
108448
108828
|
{
|
|
108449
108829
|
name: 'Эмоция / Портрет',
|
|
108450
108830
|
prompt: `ЭМОЦИЯ / ПОРТРЕТ: один из двух подходов с человеком в серии. Используй максимально: ОЧЕНЬ крупный план лица (thumb‑stop), прямой взгляд в камеру, сильная эмоция облегчения/надежды (без широкой стоковой улыбки). Возраст человека подбери под категорию и ЦА продукта: по умолчанию 50–60, но для категорий с более молодой аудиторией (например похудение/фитнес) допускается 35–55. Продукт в руке на уровне лица или рядом, хорошо виден. Свет "тень → свет" на лице допустим. Минимум отвлекающих деталей, высокий контраст. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. ИЕРАРХИЯ: лицо — главный и доминирующий визуальный элемент (верхние 2/3 кадра). Все текстовые блоки (HOOK исключение — сверху) уходят в нижнюю треть. Буллиты, цена, CTA НЕ перекрывают лицо и НЕ конкурируют с ним по визуальному весу — они заметно меньше и ниже.`,
|
|
108451
|
-
headlineAngle: `HOOK: угол «персонально на “ты” + результат/облегчение». 1–4 строки, до 12 слов,
|
|
108831
|
+
headlineAngle: `HOOK: угол «персонально на “ты” + результат/облегчение». 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно. Тон максимально личный и прямой.`,
|
|
108452
108832
|
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (качество жизни + действие + срок/цифра) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
108453
108833
|
},
|
|
108454
108834
|
{
|
|
108455
108835
|
name: 'Визуализация проблемы',
|
|
108456
108836
|
prompt: `ВИЗУАЛИЗАЦИЯ ПРОБЛЕМЫ (без человека): схема/иконка проблемной зоны тела, строго соответствующей категории продукта (для суставов — колено/сустав/позвоночник; для пищеварения — желудок; для простаты — силуэт мужчины) + мягкий красный акцент на этой зоне (не шок‑контент, без графики). Рядом продукт как решение. Можно добавить простую стрелку/переход “проблема → облегчение” как графику (без лишнего текста). Чистый фон, высокая читабельность. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. БЕЗ человека.`,
|
|
108457
|
-
headlineAngle: `HOOK: прямой вопрос о боли/дискомфорте (релевантно категории). 1–4 строки, до 12 слов,
|
|
108837
|
+
headlineAngle: `HOOK: прямой вопрос о боли/дискомфорте (релевантно категории). 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно, вопросительный знак допустим.`,
|
|
108458
108838
|
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + соц.доказательство) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
108459
108839
|
},
|
|
108460
108840
|
{
|
|
108461
108841
|
name: 'Power / Сила решения',
|
|
108462
108842
|
prompt: `POWER / СИЛА РЕШЕНИЯ (без человека): метафоры силы и победы над проблемой: щит, молния, энергия, взрыв‑бёрст, мощные стикеры/иконки. Продукт как “герой” в центре, высокая энергия, контрастные агрессивные цвета. Можно комикс/anti‑design подачу, но всё должно быть читабельно. Без лишнего текста. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. БЕЗ человека. Только продукт + power‑иконки.`,
|
|
108463
|
-
headlineAngle: `HOOK: угол «было → стало / победа над проблемой». Можно использовать стрелку “→” как часть перехода. 1–4 строки, до 12 слов,
|
|
108843
|
+
headlineAngle: `HOOK: угол «было → стало / победа над проблемой». Можно использовать стрелку “→” как часть перехода. 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно.`,
|
|
108464
108844
|
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + скорость + цифра) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
108465
108845
|
},
|
|
108466
108846
|
{
|
|
108467
108847
|
name: 'Минимализм / Clean Big Text',
|
|
108468
|
-
prompt: `МИНИМАЛИЗМ / CLEAN BIG TEXT (без человека): белый или мягкий градиентный фон, премиальное ощущение. ОГРОМНЫЙ HOOK как главный элемент (
|
|
108469
|
-
headlineAngle: `HOOK: одна максимально простая сильная фраза (коротко и ясно), 1–4 строки, до 12 слов,
|
|
108848
|
+
prompt: `МИНИМАЛИЗМ / CLEAN BIG TEXT (без человека): белый или мягкий градиентный фон, премиальное ощущение. ОГРОМНЫЙ HOOK как главный элемент (прописные буквы, жирный, на контрастной плашке). Продукт крупно, минимум реквизита, минимум шума. Тени/премиальные материалы допустимы, но без лишнего текста. БЕЗ человека. Urgency и trust‑печати в этом подходе НЕ рекомендуются — они нарушают чистоту и ощущение премиальности. Добавляй печати только если это критически необходимо, не более 1, но крупную — размер как у буллитов.`,
|
|
108849
|
+
headlineAngle: `HOOK: одна максимально простая сильная фраза (коротко и ясно), 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно.`,
|
|
108470
108850
|
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + формула/цифра) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
108471
108851
|
},
|
|
108472
108852
|
{
|
|
@@ -108476,14 +108856,14 @@ const CREO_APPROACHES = [
|
|
|
108476
108856
|
- Фон: яркий сплошной цвет (красный/оранжевый) или агрессивный градиент
|
|
108477
108857
|
- Центр: КРУПНАЯ иконка/схема проблемной зоны тела, строго соответствующей категории продукта (для суставов — колено/сустав; для простаты — силуэт мужчины; для пищеварения — желудок), с красным свечением
|
|
108478
108858
|
- Продукт: крупно рядом
|
|
108479
|
-
- HOOK: 1–4 строки, до 12 слов, огромный,
|
|
108859
|
+
- HOOK: 1–4 строки, до 12 слов, огромный, прописные буквы, ключевое слово проблемы явно
|
|
108480
108860
|
- БЕЗ буллитов
|
|
108481
108861
|
- Цена + скидка: крупно, заметно
|
|
108482
108862
|
- CTA: контрастная яркая кнопка
|
|
108483
108863
|
|
|
108484
108864
|
Цель: считывание за 1 секунду. Минимум текста, максимум визуального удара.
|
|
108485
108865
|
БЕЗ человека. Никаких trust‑печатей/urgency бейджей — только HOOK + PRICE + -50% + CTA.`,
|
|
108486
|
-
headlineAngle: `HOOK: «ПРОЩАЙ/КОНЕЦ/СТОП + [проблема]!». Эмоция победы/избавления. 1–4 строки, до 12 слов,
|
|
108866
|
+
headlineAngle: `HOOK: «ПРОЩАЙ/КОНЕЦ/СТОП + [проблема]!». Эмоция победы/избавления. 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно.`,
|
|
108487
108867
|
bulletsFocus: `БУЛЛИТЫ: ЗАПРЕЩЕНЫ. Вся информация — в HOOK и крупных надписях.`
|
|
108488
108868
|
},
|
|
108489
108869
|
{
|
|
@@ -108498,7 +108878,7 @@ const CREO_APPROACHES = [
|
|
|
108498
108878
|
- Крупные текстовые надписи рядом с продуктом: цена, скидка; допустимо одно короткое слово-восклицание («РАБОТАЕТ!», «ПРОВЕРЕНО!») на языке GEO
|
|
108499
108879
|
- Кнопка CTA: плоская, без градиентов, контрастный сплошной цвет
|
|
108500
108880
|
- НИКАКИХ буллитов, trust‑печатей, urgency‑бейджей, иконок — только продукт и текст`,
|
|
108501
|
-
headlineAngle: `HOOK: максимально прямолинейный — короткий, грубый, без украшений. 1–4 строки, до 12 слов,
|
|
108881
|
+
headlineAngle: `HOOK: максимально прямолинейный — короткий, грубый, без украшений. 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно, как объявление на столбе. Никакого маркетингового лоска, никаких абстрактных слоганов.`,
|
|
108502
108882
|
bulletsFocus: `БУЛЛИТЫ: ЗАПРЕЩЕНЫ. Всё — в HOOK и крупных надписях вокруг продукта (цена, скидка, одно слово-восклицание).`
|
|
108503
108883
|
},
|
|
108504
108884
|
{
|
|
@@ -108510,7 +108890,7 @@ const CREO_APPROACHES = [
|
|
|
108510
108890
|
- Фон: профессиональный (кабинет, аптечные полки, медицинский контекст). Высокий контраст, читабельность.
|
|
108511
108891
|
- Опционально: 1–3 trust‑печати по низу (цветные, яркие, в стиле FDA). Размер как минимум как у буллитов.
|
|
108512
108892
|
- ИЕРАРХИЯ: врач + продукт — главные элементы. HOOK сверху, BULLETS сбоку или снизу, CTA и PRICE/DISCOUNT в нижнем блоке.`,
|
|
108513
|
-
headlineAngle: `HOOK: угол «врач/специалист рекомендует» или «эксперты знают». 1–4 строки, до 12 слов,
|
|
108893
|
+
headlineAngle: `HOOK: угол «врач/специалист рекомендует» или «эксперты знают». 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно.`,
|
|
108514
108894
|
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + соц.доказательство) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
108515
108895
|
},
|
|
108516
108896
|
{
|
|
@@ -108519,11 +108899,11 @@ const CREO_APPROACHES = [
|
|
|
108519
108899
|
prompt: `СКРИН ОТЗЫВОВ: имитация реалистичного скриншота с положительными благодарными отзывами и оценками 5 звёзд.
|
|
108520
108900
|
- Визуал: реалистичный стиль — как скриншот приложения/сайта отзывов (App Store, Google Play, или страница отзывов). Высокое качество, читабельный текст.
|
|
108521
108901
|
- Отзывы: 2–4 отзыва с аватарками, именами и возрастом (формат «Имя, 45 лет»), ОБЯЗАТЕЛЬНО 5 звёзд (★★★★★) в каждом, короткий текст благодарности. Текст отзывов СТРОГО на языке GEO — никакого английского. Релевантен категории продукта.
|
|
108522
|
-
- HOOK ОБЯЗАТЕЛЕН: крупно, выше блока отзывов или поверх,
|
|
108902
|
+
- HOOK ОБЯЗАТЕЛЕН: крупно, выше блока отзывов или поверх, прописные буквы, на яркой подложке. Ключевое слово проблемы явно.
|
|
108523
108903
|
- Продукт: виден в кадре (рядом со скрином или интегрирован в композицию).
|
|
108524
108904
|
- Цена, скидка «-50%», CTA — в нижней части. БЕЗ буллитов — отзывы заменяют их.
|
|
108525
108905
|
- Стиль: не мультяшный, не комикс — максимально реалистичный скрин.`,
|
|
108526
|
-
headlineAngle: `HOOK: угол «тысячи довольны» / «9 из 10 рекомендуют» / «реальный результат». 1–4 строки, до 12 слов,
|
|
108906
|
+
headlineAngle: `HOOK: угол «тысячи довольны» / «9 из 10 рекомендуют» / «реальный результат». 1–4 строки, до 12 слов, прописные буквы, ключевое слово проблемы явно.`,
|
|
108527
108907
|
bulletsFocus: `БУЛЛИТЫ: ЗАПРЕЩЕНЫ. Вся информация — в HOOK, отзывах (с 5 звёздами), цене и CTA.`
|
|
108528
108908
|
}
|
|
108529
108909
|
];
|