docs-combiner 0.1.10 → 0.1.11
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 +318 -659
- package/dist/renderer.js.map +1 -1
- package/package.json +1 -1
package/dist/renderer.js
CHANGED
|
@@ -99622,33 +99622,28 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
99622
99622
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/ToggleButtonGroup/ToggleButtonGroup.js");
|
|
99623
99623
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/ToggleButton/ToggleButton.js");
|
|
99624
99624
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/Paper/Paper.js");
|
|
99625
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
99626
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
99627
|
-
/* harmony import */ var
|
|
99628
|
-
/* harmony import */ var
|
|
99629
|
-
/* harmony import */ var
|
|
99630
|
-
/* harmony import */ var
|
|
99631
|
-
/* harmony import */ var
|
|
99632
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99633
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99634
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99635
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99636
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99637
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99638
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99639
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99640
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99641
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99642
|
-
/* harmony import */ var
|
|
99643
|
-
/* harmony import */ var
|
|
99644
|
-
/* harmony import */ var
|
|
99645
|
-
/* harmony import */ var
|
|
99646
|
-
/* harmony import */ var
|
|
99647
|
-
/* harmony import */ var _PromptManagerDialog__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(/*! ./PromptManagerDialog */ "./src/PromptManagerDialog.tsx");
|
|
99648
|
-
/* harmony import */ var _promptOverrides__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(/*! ./promptOverrides */ "./src/promptOverrides.ts");
|
|
99649
|
-
/* harmony import */ var xlsx__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(/*! xlsx */ "./node_modules/xlsx/xlsx.mjs");
|
|
99650
|
-
/* harmony import */ var jszip__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(/*! jszip */ "./node_modules/jszip/dist/jszip.min.js");
|
|
99651
|
-
/* harmony import */ var jszip__WEBPACK_IMPORTED_MODULE_56___default = /*#__PURE__*/__webpack_require__.n(jszip__WEBPACK_IMPORTED_MODULE_56__);
|
|
99625
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/styles/createTheme.js");
|
|
99626
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/styles/ThemeProvider.js");
|
|
99627
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/AccountBalance.js");
|
|
99628
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/AutoAwesome.js");
|
|
99629
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/Brightness4.js");
|
|
99630
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/Brightness7.js");
|
|
99631
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/CheckCircle.js");
|
|
99632
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/CloudDownload.js");
|
|
99633
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/ContentCopy.js");
|
|
99634
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/ExpandMore.js");
|
|
99635
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/InfoOutlined.js");
|
|
99636
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/Login.js");
|
|
99637
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/Logout.js");
|
|
99638
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/NoteAdd.js");
|
|
99639
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/Replay.js");
|
|
99640
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/Settings.js");
|
|
99641
|
+
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/Visibility.js");
|
|
99642
|
+
/* harmony import */ var _PromptManagerDialog__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(/*! ./PromptManagerDialog */ "./src/PromptManagerDialog.tsx");
|
|
99643
|
+
/* harmony import */ var _promptOverrides__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(/*! ./promptOverrides */ "./src/promptOverrides.ts");
|
|
99644
|
+
/* harmony import */ var xlsx__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(/*! xlsx */ "./node_modules/xlsx/xlsx.mjs");
|
|
99645
|
+
/* harmony import */ var jszip__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(/*! jszip */ "./node_modules/jszip/dist/jszip.min.js");
|
|
99646
|
+
/* harmony import */ var jszip__WEBPACK_IMPORTED_MODULE_51___default = /*#__PURE__*/__webpack_require__.n(jszip__WEBPACK_IMPORTED_MODULE_51__);
|
|
99652
99647
|
|
|
99653
99648
|
|
|
99654
99649
|
|
|
@@ -99724,7 +99719,6 @@ function App() {
|
|
|
99724
99719
|
const [pairTranslations, setPairTranslations] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
|
|
99725
99720
|
const [translatingPairs, setTranslatingPairs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99726
99721
|
const [driveFolderUrl, setDriveFolderUrl] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99727
|
-
const [brand, setBrand] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99728
99722
|
const [link, setLink] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99729
99723
|
const [openaiApiKey, setOpenaiApiKey] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99730
99724
|
const [openRouterBalance, setOpenRouterBalance] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
@@ -99876,28 +99870,66 @@ function App() {
|
|
|
99876
99870
|
const { price, currency } = parsePriceAndCurrency(value);
|
|
99877
99871
|
return price.trim() !== '' && currency.trim() !== '';
|
|
99878
99872
|
};
|
|
99873
|
+
// Transliterate to Latin and slugify (lowercase, dashes)
|
|
99874
|
+
const transliterateToSlug = (text) => {
|
|
99875
|
+
const cyrillicMap = {
|
|
99876
|
+
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'e', 'ж': 'zh', 'з': 'z',
|
|
99877
|
+
'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r',
|
|
99878
|
+
'с': 's', 'т': 't', 'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'shch',
|
|
99879
|
+
'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya',
|
|
99880
|
+
'А': 'a', 'Б': 'b', 'В': 'v', 'Г': 'g', 'Д': 'd', 'Е': 'e', 'Ё': 'e', 'Ж': 'zh', 'З': 'z',
|
|
99881
|
+
'И': 'i', 'Й': 'y', 'К': 'k', 'Л': 'l', 'М': 'm', 'Н': 'n', 'О': 'o', 'П': 'p', 'Р': 'r',
|
|
99882
|
+
'С': 's', 'Т': 't', 'У': 'u', 'Ф': 'f', 'Х': 'kh', 'Ц': 'ts', 'Ч': 'ch', 'Ш': 'sh', 'Щ': 'shch',
|
|
99883
|
+
'Ъ': '', 'Ы': 'y', 'Ь': '', 'Э': 'e', 'Ю': 'yu', 'Я': 'ya'
|
|
99884
|
+
};
|
|
99885
|
+
let result = '';
|
|
99886
|
+
for (const char of text) {
|
|
99887
|
+
result += cyrillicMap[char] ?? char;
|
|
99888
|
+
}
|
|
99889
|
+
return result
|
|
99890
|
+
.toLowerCase()
|
|
99891
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
99892
|
+
.replace(/\s+/g, '-')
|
|
99893
|
+
.replace(/-+/g, '-')
|
|
99894
|
+
.replace(/^-|-$/g, '');
|
|
99895
|
+
};
|
|
99896
|
+
// Auto-generate brand from product, geo, price
|
|
99897
|
+
const brand = react__WEBPACK_IMPORTED_MODULE_0___default().useMemo(() => {
|
|
99898
|
+
const productPart = (() => {
|
|
99899
|
+
const p = generateProduct.trim();
|
|
99900
|
+
if (!p)
|
|
99901
|
+
return '';
|
|
99902
|
+
const dashIdx = p.search(/[-–—]/);
|
|
99903
|
+
const base = dashIdx >= 0 ? p.slice(0, dashIdx).trim() : p.split(/\s+/)[0] || p;
|
|
99904
|
+
return transliterateToSlug(base);
|
|
99905
|
+
})();
|
|
99906
|
+
const geoPart = generateGeo.trim() ? transliterateToSlug(generateGeo.trim()) : '';
|
|
99907
|
+
const pricePart = (() => {
|
|
99908
|
+
const { price, currency } = parsePriceAndCurrency(generatePriceWithCurrency);
|
|
99909
|
+
if (!price && !currency)
|
|
99910
|
+
return '';
|
|
99911
|
+
const p = price ? transliterateToSlug(price) : '';
|
|
99912
|
+
const c = currency ? transliterateToSlug(currency) : '';
|
|
99913
|
+
return [p, c].filter(Boolean).join('-');
|
|
99914
|
+
})();
|
|
99915
|
+
return [productPart, geoPart, pricePart].filter(Boolean).join('-');
|
|
99916
|
+
}, [generateProduct, generateGeo, generatePriceWithCurrency]);
|
|
99879
99917
|
const [generating, setGenerating] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99880
99918
|
const [generatingImages, setGeneratingImages] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99881
99919
|
const [imagesGenerationLogs, setImagesGenerationLogs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99882
99920
|
const [generatedImagesData, setGeneratedImagesData] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99883
99921
|
const [checkingImages, setCheckingImages] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99884
99922
|
const [uploadingImages, setUploadingImages] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99885
|
-
const [generatingProduct, setGeneratingProduct] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99886
99923
|
const [uploadingProduct, setUploadingProduct] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99887
|
-
const [productModalOpen, setProductModalOpen] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99888
|
-
const [productSourceImages, setProductSourceImages] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99889
|
-
const [productGeneratedImage, setProductGeneratedImage] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99890
|
-
const [productRegeneratePrompt, setProductRegeneratePrompt] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99891
|
-
const [productGenerationLogs, setProductGenerationLogs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99892
99924
|
const [folderFilesInfo, setFolderFilesInfo] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99925
|
+
const productFileInputRef = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(null);
|
|
99893
99926
|
const [checkingFolderFiles, setCheckingFolderFiles] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99894
99927
|
const permissionCheckedFoldersRef = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(new Set());
|
|
99895
99928
|
const folderCheckRunningRef = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(false);
|
|
99896
|
-
const [productProcessStartTime, setProductProcessStartTime] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99897
99929
|
const [imagesProcessStartTime, setImagesProcessStartTime] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99898
99930
|
const [contentProcessStartTime, setContentProcessStartTime] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99899
99931
|
const [contentGenerationLogs, setContentGenerationLogs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99900
|
-
const [elapsedTime, setElapsedTime] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({
|
|
99932
|
+
const [elapsedTime, setElapsedTime] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({ images: 0, content: 0, landing: 0 });
|
|
99901
99933
|
const [uiNow, setUiNow] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(() => Date.now());
|
|
99902
99934
|
const [generatingLanding, setGeneratingLanding] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99903
99935
|
const [landingGenerationLogs, setLandingGenerationLogs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
@@ -99964,7 +99996,7 @@ function App() {
|
|
|
99964
99996
|
}
|
|
99965
99997
|
};
|
|
99966
99998
|
// Create theme based on mode
|
|
99967
|
-
const theme = react__WEBPACK_IMPORTED_MODULE_0___default().useMemo(() => (0,
|
|
99999
|
+
const theme = react__WEBPACK_IMPORTED_MODULE_0___default().useMemo(() => (0,_mui_material__WEBPACK_IMPORTED_MODULE_31__["default"])({
|
|
99968
100000
|
palette: {
|
|
99969
100001
|
mode: darkMode ? 'dark' : 'light',
|
|
99970
100002
|
...(darkMode
|
|
@@ -100056,7 +100088,7 @@ function App() {
|
|
|
100056
100088
|
};
|
|
100057
100089
|
loadKey();
|
|
100058
100090
|
// Load prompt overrides from Electron config
|
|
100059
|
-
(0,
|
|
100091
|
+
(0,_promptOverrides__WEBPACK_IMPORTED_MODULE_49__.loadOverridesFromElectron)();
|
|
100060
100092
|
}, []);
|
|
100061
100093
|
// Save form fields to localStorage whenever they change
|
|
100062
100094
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
@@ -100112,7 +100144,6 @@ function App() {
|
|
|
100112
100144
|
setGeneratedImagesData([]);
|
|
100113
100145
|
setTitles('');
|
|
100114
100146
|
setTexts(['']);
|
|
100115
|
-
setBrand('');
|
|
100116
100147
|
setLink('');
|
|
100117
100148
|
return;
|
|
100118
100149
|
}
|
|
@@ -100128,7 +100159,6 @@ function App() {
|
|
|
100128
100159
|
setGeneratedImagesData([]);
|
|
100129
100160
|
setTitles('');
|
|
100130
100161
|
setTexts(['']);
|
|
100131
|
-
setBrand('');
|
|
100132
100162
|
setLink('');
|
|
100133
100163
|
return;
|
|
100134
100164
|
}
|
|
@@ -100139,7 +100169,6 @@ function App() {
|
|
|
100139
100169
|
setGeneratedImagesData([]);
|
|
100140
100170
|
setTitles('');
|
|
100141
100171
|
setTexts(['']);
|
|
100142
|
-
setBrand('');
|
|
100143
100172
|
setLink('');
|
|
100144
100173
|
setLoadingContentFromDrive(true);
|
|
100145
100174
|
setLoadingImagesFromDrive(true);
|
|
@@ -100179,12 +100208,6 @@ function App() {
|
|
|
100179
100208
|
}, [titles]); // Only depend on titles, not generatedTitlesData to avoid loops
|
|
100180
100209
|
// Auto-scroll logs when new logs are added
|
|
100181
100210
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
100182
|
-
if (productGenerationLogs.length > 0 && (generatingProduct || uploadingProduct)) {
|
|
100183
|
-
const logContainer = document.getElementById('product-generation-logs');
|
|
100184
|
-
if (logContainer) {
|
|
100185
|
-
logContainer.scrollTop = logContainer.scrollHeight;
|
|
100186
|
-
}
|
|
100187
|
-
}
|
|
100188
100211
|
if (imagesGenerationLogs.length > 0 && (generatingImages || uploadingImages)) {
|
|
100189
100212
|
const logContainer = document.getElementById('images-generation-logs');
|
|
100190
100213
|
if (logContainer) {
|
|
@@ -100197,30 +100220,7 @@ function App() {
|
|
|
100197
100220
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
100198
100221
|
}
|
|
100199
100222
|
}
|
|
100200
|
-
}, [
|
|
100201
|
-
// Timer for product generation
|
|
100202
|
-
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
100203
|
-
let interval = null;
|
|
100204
|
-
if (generatingProduct || uploadingProduct) {
|
|
100205
|
-
interval = setInterval(() => {
|
|
100206
|
-
setElapsedTime(prev => {
|
|
100207
|
-
if (productProcessStartTime) {
|
|
100208
|
-
const elapsed = Math.floor((Date.now() - productProcessStartTime) / 1000);
|
|
100209
|
-
return { ...prev, product: elapsed };
|
|
100210
|
-
}
|
|
100211
|
-
return prev;
|
|
100212
|
-
});
|
|
100213
|
-
}, 1000);
|
|
100214
|
-
}
|
|
100215
|
-
else {
|
|
100216
|
-
setProductProcessStartTime(null);
|
|
100217
|
-
setElapsedTime(prev => ({ ...prev, product: 0 }));
|
|
100218
|
-
}
|
|
100219
|
-
return () => {
|
|
100220
|
-
if (interval)
|
|
100221
|
-
clearInterval(interval);
|
|
100222
|
-
};
|
|
100223
|
-
}, [generatingProduct, uploadingProduct, productProcessStartTime]);
|
|
100223
|
+
}, [imagesGenerationLogs, generatingImages, uploadingImages, contentGenerationLogs, generating]);
|
|
100224
100224
|
// Timer for images generation
|
|
100225
100225
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
100226
100226
|
let interval = null;
|
|
@@ -100441,19 +100441,12 @@ function App() {
|
|
|
100441
100441
|
return;
|
|
100442
100442
|
if (response.ok) {
|
|
100443
100443
|
const data = await response.json();
|
|
100444
|
-
const bankaFiles = data.files.filter((f) => {
|
|
100445
|
-
const name = f.name?.toLowerCase() || '';
|
|
100446
|
-
return name.startsWith('banka') && (name.endsWith('.png') || name.endsWith('.jpg') || name.endsWith('.jpeg'));
|
|
100447
|
-
});
|
|
100448
100444
|
const hasProduct = data.files.some((f) => {
|
|
100449
100445
|
const name = f.name?.toLowerCase() || '';
|
|
100450
100446
|
return name === 'product.png' || name === 'product.jpg';
|
|
100451
100447
|
});
|
|
100452
100448
|
if (!cancelled) {
|
|
100453
|
-
setFolderFilesInfo({
|
|
100454
|
-
bankaCount: bankaFiles.length,
|
|
100455
|
-
hasProduct
|
|
100456
|
-
});
|
|
100449
|
+
setFolderFilesInfo({ hasProduct });
|
|
100457
100450
|
// If product not found, poll every 10 seconds until found
|
|
100458
100451
|
if (!hasProduct) {
|
|
100459
100452
|
pollTimeoutId = setTimeout(() => {
|
|
@@ -101540,7 +101533,7 @@ function App() {
|
|
|
101540
101533
|
setTexts(['']);
|
|
101541
101534
|
setPairTranslations({});
|
|
101542
101535
|
// Read pairs count from settings (3–10, default 3)
|
|
101543
|
-
const pairsCountInit = (0,
|
|
101536
|
+
const pairsCountInit = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_49__.getPairsCount)();
|
|
101544
101537
|
// Initialize placeholders
|
|
101545
101538
|
const initialTitles = Array.from({ length: pairsCountInit }, (_, index) => ({
|
|
101546
101539
|
index: index + 1,
|
|
@@ -101569,7 +101562,7 @@ function App() {
|
|
|
101569
101562
|
}
|
|
101570
101563
|
// Generate all pairs (title + text) in a single request
|
|
101571
101564
|
addLog(formatLogMessage('log', '📋 Generating title+text pairs in a single request...'));
|
|
101572
|
-
const selectedIndices = (0,
|
|
101565
|
+
const selectedIndices = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_49__.getSelectedPairApproaches)();
|
|
101573
101566
|
setLastUsedApproachIndices(selectedIndices);
|
|
101574
101567
|
const pairsCount = selectedIndices.length;
|
|
101575
101568
|
addLog(formatLogMessage('log', `⚙️ Generating ${pairsCount} pairs (approaches: ${selectedIndices.join(', ')})`));
|
|
@@ -102202,362 +102195,52 @@ function App() {
|
|
|
102202
102195
|
errors: finalErrors
|
|
102203
102196
|
};
|
|
102204
102197
|
};
|
|
102205
|
-
const
|
|
102206
|
-
if (!
|
|
102207
|
-
|
|
102208
|
-
|
|
102209
|
-
if (addLog)
|
|
102210
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102211
|
-
throw new Error(errorMsg);
|
|
102212
|
-
}
|
|
102213
|
-
const logMsg = (level, ...args) => {
|
|
102214
|
-
logToTerminal(level, ...args);
|
|
102215
|
-
if (addLog)
|
|
102216
|
-
addLog(formatLogMessage(level, ...args));
|
|
102217
|
-
};
|
|
102218
|
-
const isRegeneration = !!currentProductImageUrl;
|
|
102219
|
-
logMsg('log', `=== Starting product image ${isRegeneration ? 'regeneration' : 'generation'} from source images ===`);
|
|
102220
|
-
logMsg('log', '📸 Source images count:', bankaImageUrls.length);
|
|
102221
|
-
if (isRegeneration) {
|
|
102222
|
-
logMsg('log', '🔄 Regenerating with current product image as reference');
|
|
102223
|
-
}
|
|
102224
|
-
// Ensure we have a valid access token before proceeding
|
|
102225
|
-
const validToken = await getValidAccessToken();
|
|
102226
|
-
if (!validToken) {
|
|
102227
|
-
const errorMsg = 'Not logged in to Google Drive or token expired';
|
|
102228
|
-
logMsg('error', errorMsg);
|
|
102229
|
-
throw new Error(errorMsg);
|
|
102230
|
-
}
|
|
102231
|
-
logMsg('log', '✅ Valid access token confirmed');
|
|
102232
|
-
// Base prompt for product generation
|
|
102233
|
-
let basePrompt = `Прикрепил фото упаковки продукта. Сгенерируй пожалуйста её упрощенное изображение для креативов для рекламы в Facebook.
|
|
102234
|
-
ВАЖНО: Сохрани точно такую же форму упаковки, как на исходном фото (если это упаковка таблеток - оставь упаковку таблеток, если банка - оставь банку, если тюбик - оставь тюбик).
|
|
102235
|
-
Убери мелкий текст. Нужна только сама упаковка продукта, не коробка. Оставь логотип, название, примерно тот же тон.`;
|
|
102236
|
-
// If regenerating, update prompt to mention current image
|
|
102237
|
-
if (isRegeneration) {
|
|
102238
|
-
basePrompt = `${basePrompt}
|
|
102239
|
-
|
|
102240
|
-
🚨 ПЕРЕГЕНЕРАЦИЯ: Прикреплены два типа референсов:
|
|
102241
|
-
1. Исходные фото упаковки продукта (используй их как основу для формы и деталей)
|
|
102242
|
-
2. Текущий вариант сгенерированного продукта (НЕПРАВИЛЬНЫЙ, требует ОБЯЗАТЕЛЬНЫХ изменений согласно требованиям ниже)
|
|
102243
|
-
|
|
102244
|
-
═══════════════════════════════════════════════════════════════
|
|
102245
|
-
🔥 ОБЯЗАТЕЛЬНЫЕ ТРЕБОВАНИЯ ПОЛЬЗОВАТЕЛЯ (ПРИМЕНИ ВСЕ БЕЗ ИСКЛЮЧЕНИЯ):
|
|
102246
|
-
═══════════════════════════════════════════════════════════════`;
|
|
102247
|
-
}
|
|
102248
|
-
const fullPrompt = additionalPrompt.trim()
|
|
102249
|
-
? `${basePrompt}${isRegeneration ? '' : '\nДополнительные требования: '}${additionalPrompt.split('\n').filter(line => line.trim()).map(line => `• ${line.trim()}`).join('\n')}${isRegeneration ? '\n\n⚠️ ВАЖНО: ОБЯЗАТЕЛЬНО примени ВСЕ требования выше в новом варианте изображения. НЕ копируй предыдущий вариант - создай НОВОЕ изображение с учетом всех требований.' : ''}`
|
|
102250
|
-
: basePrompt;
|
|
102251
|
-
logMsg('log', '📝 Prompt:', fullPrompt);
|
|
102252
|
-
// Convert Drive URLs to direct image URLs for API
|
|
102253
|
-
// For Google Drive, convert view URLs to direct download URLs
|
|
102254
|
-
// Note: Token is refreshed above to ensure files are accessible
|
|
102255
|
-
const convertImageUrl = (url) => {
|
|
102256
|
-
const fileIdMatch = url.match(/\/file\/d\/([a-zA-Z0-9_-]+)/);
|
|
102257
|
-
if (fileIdMatch) {
|
|
102258
|
-
const fileId = fileIdMatch[1];
|
|
102259
|
-
return `https://drive.google.com/uc?export=view&id=${fileId}`;
|
|
102260
|
-
}
|
|
102261
|
-
return url;
|
|
102262
|
-
};
|
|
102263
|
-
const sourceImageUrls = bankaImageUrls.map(convertImageUrl);
|
|
102264
|
-
// Build image URLs array: current product image first (if regenerating), then source images
|
|
102265
|
-
const imageUrls = [];
|
|
102266
|
-
if (currentProductImageUrl) {
|
|
102267
|
-
imageUrls.push(convertImageUrl(currentProductImageUrl));
|
|
102268
|
-
}
|
|
102269
|
-
imageUrls.push(...sourceImageUrls);
|
|
102270
|
-
logMsg('log', '🖼️ Total reference images:', imageUrls.length, isRegeneration ? '(current product + source images)' : '(source images only)');
|
|
102271
|
-
const requestBody = {
|
|
102272
|
-
model: _models__WEBPACK_IMPORTED_MODULE_2__.MODELS.imageGeneration,
|
|
102273
|
-
messages: [
|
|
102274
|
-
{
|
|
102275
|
-
role: 'user',
|
|
102276
|
-
content: [
|
|
102277
|
-
{ type: 'text', text: fullPrompt },
|
|
102278
|
-
...imageUrls.map(url => ({ type: 'image_url', image_url: { url } }))
|
|
102279
|
-
]
|
|
102280
|
-
}
|
|
102281
|
-
],
|
|
102282
|
-
modalities: ['image', 'text'],
|
|
102283
|
-
};
|
|
102284
|
-
logMsg('log', '📦 Request body prepared with', imageUrls.length, 'source images');
|
|
102285
|
-
logMsg('log', '🚀 Sending request to OpenRouter API...');
|
|
102286
|
-
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
102287
|
-
method: 'POST',
|
|
102288
|
-
headers: {
|
|
102289
|
-
'Content-Type': 'application/json',
|
|
102290
|
-
'Authorization': `Bearer ${openaiApiKey}`,
|
|
102291
|
-
'HTTP-Referer': window.location.origin || 'https://docs-combiner.app',
|
|
102292
|
-
'X-Title': 'Docs Combiner'
|
|
102293
|
-
},
|
|
102294
|
-
body: JSON.stringify(requestBody)
|
|
102295
|
-
});
|
|
102296
|
-
const responseText = await response.text();
|
|
102297
|
-
logMsg('log', '📊 Response status:', response.status, response.statusText);
|
|
102298
|
-
if (!response.ok) {
|
|
102299
|
-
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
102300
|
-
try {
|
|
102301
|
-
const error = JSON.parse(responseText);
|
|
102302
|
-
errorMessage = error.error?.message || error.message || errorMessage;
|
|
102303
|
-
logMsg('error', '🔍 Parsed error:', errorMessage);
|
|
102304
|
-
}
|
|
102305
|
-
catch (e) {
|
|
102306
|
-
logMsg('error', '❌ Failed to parse error response as JSON');
|
|
102307
|
-
}
|
|
102308
|
-
throw new Error(errorMessage);
|
|
102309
|
-
}
|
|
102310
|
-
logMsg('log', '📥 Parsing response...');
|
|
102311
|
-
let data;
|
|
102312
|
-
try {
|
|
102313
|
-
data = JSON.parse(responseText);
|
|
102314
|
-
logMsg('log', '✅ JSON parsed successfully');
|
|
102315
|
-
}
|
|
102316
|
-
catch (e) {
|
|
102317
|
-
logMsg('error', '❌ Failed to parse response as JSON');
|
|
102318
|
-
throw new Error(`Invalid JSON response: ${responseText.substring(0, 200)}`);
|
|
102319
|
-
}
|
|
102320
|
-
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
|
|
102321
|
-
logMsg('error', '❌ Invalid response structure');
|
|
102322
|
-
throw new Error(`Invalid response structure. Expected choices[0].message`);
|
|
102323
|
-
}
|
|
102324
|
-
const message = data.choices[0].message;
|
|
102325
|
-
if (!message.images || !message.images[0] || !message.images[0].image_url) {
|
|
102326
|
-
logMsg('error', '❌ No images in response');
|
|
102327
|
-
throw new Error(`No images found in response`);
|
|
102198
|
+
const handleUploadProductFile = () => {
|
|
102199
|
+
if (!driveFolderUrl.trim()) {
|
|
102200
|
+
alert('Укажите папку Google Drive');
|
|
102201
|
+
return;
|
|
102328
102202
|
}
|
|
102329
|
-
|
|
102330
|
-
logMsg('log', '✅ Product image generated successfully');
|
|
102331
|
-
logMsg('log', '=== End product image generation ===');
|
|
102332
|
-
// Update balance after successful API call
|
|
102333
|
-
fetchOpenRouterBalance();
|
|
102334
|
-
return imageUrl;
|
|
102203
|
+
productFileInputRef.current?.click();
|
|
102335
102204
|
};
|
|
102336
|
-
const
|
|
102205
|
+
const handleProductFileSelected = async (e) => {
|
|
102206
|
+
const file = e.target.files?.[0];
|
|
102207
|
+
if (!file)
|
|
102208
|
+
return;
|
|
102209
|
+
e.target.value = '';
|
|
102337
102210
|
const validToken = await getValidAccessToken();
|
|
102338
102211
|
if (!validToken) {
|
|
102339
|
-
alert('
|
|
102340
|
-
return;
|
|
102341
|
-
}
|
|
102342
|
-
if (!driveFolderUrl.trim()) {
|
|
102343
|
-
alert('Please fill in Google Drive Folder URL');
|
|
102212
|
+
alert('Войдите в Google Drive');
|
|
102344
102213
|
return;
|
|
102345
102214
|
}
|
|
102346
|
-
|
|
102347
|
-
|
|
102348
|
-
|
|
102349
|
-
throw new Error('Invalid Google Drive Folder URL');
|
|
102350
|
-
}
|
|
102351
|
-
// Check if product.png exists in the folder
|
|
102352
|
-
logToTerminal('log', '🔍 Checking for product.png in folder...');
|
|
102353
|
-
const existingProductImage = await fetchProductImage(folderId);
|
|
102354
|
-
if (existingProductImage) {
|
|
102355
|
-
// Product.png exists - open regeneration modal without generating
|
|
102356
|
-
logToTerminal('log', '✅ product.png found in folder, opening regeneration modal');
|
|
102357
|
-
// Load product image as blob and convert to data URL for display
|
|
102358
|
-
if (!productGeneratedImage) {
|
|
102359
|
-
try {
|
|
102360
|
-
logToTerminal('log', '📥 Downloading product.png for display...');
|
|
102361
|
-
let token = await getValidAccessToken();
|
|
102362
|
-
if (!token) {
|
|
102363
|
-
throw new Error('Not logged in to Google Drive');
|
|
102364
|
-
}
|
|
102365
|
-
let imageResponse = await fetch(`https://www.googleapis.com/drive/v3/files/${existingProductImage.id}?alt=media`, {
|
|
102366
|
-
headers: {
|
|
102367
|
-
'Authorization': `Bearer ${token}`
|
|
102368
|
-
}
|
|
102369
|
-
});
|
|
102370
|
-
// Handle 401 - try to refresh token and retry
|
|
102371
|
-
if (imageResponse.status === 401 && refreshToken) {
|
|
102372
|
-
logToTerminal('log', '🔄 Got 401, refreshing token...');
|
|
102373
|
-
const newToken = await refreshAccessToken(refreshToken);
|
|
102374
|
-
if (newToken) {
|
|
102375
|
-
token = newToken;
|
|
102376
|
-
logToTerminal('log', '✅ Token refreshed, retrying download...');
|
|
102377
|
-
imageResponse = await fetch(`https://www.googleapis.com/drive/v3/files/${existingProductImage.id}?alt=media`, {
|
|
102378
|
-
headers: {
|
|
102379
|
-
'Authorization': `Bearer ${newToken}`
|
|
102380
|
-
}
|
|
102381
|
-
});
|
|
102382
|
-
}
|
|
102383
|
-
}
|
|
102384
|
-
if (!imageResponse.ok) {
|
|
102385
|
-
throw new Error(`Failed to download product image: ${imageResponse.status} ${imageResponse.statusText}`);
|
|
102386
|
-
}
|
|
102387
|
-
const blob = await imageResponse.blob();
|
|
102388
|
-
// Convert blob to data URL using Promise
|
|
102389
|
-
const dataUrl = await new Promise((resolve, reject) => {
|
|
102390
|
-
const reader = new FileReader();
|
|
102391
|
-
reader.onloadend = () => resolve(reader.result);
|
|
102392
|
-
reader.onerror = reject;
|
|
102393
|
-
reader.readAsDataURL(blob);
|
|
102394
|
-
});
|
|
102395
|
-
setProductGeneratedImage(dataUrl);
|
|
102396
|
-
logToTerminal('log', '✅ Product image loaded and displayed');
|
|
102397
|
-
}
|
|
102398
|
-
catch (err) {
|
|
102399
|
-
logToTerminal('error', '❌ Failed to load product image:', err.message);
|
|
102400
|
-
// Fallback to direct URL if blob download fails
|
|
102401
|
-
const productImageUrl = `https://drive.google.com/uc?export=view&id=${existingProductImage.id}`;
|
|
102402
|
-
setProductGeneratedImage(productImageUrl);
|
|
102403
|
-
}
|
|
102404
|
-
}
|
|
102405
|
-
// If we have source images, just open modal
|
|
102406
|
-
if (productSourceImages.length > 0) {
|
|
102407
|
-
logToTerminal('log', '✅ Source images available, opening modal');
|
|
102408
|
-
setProductModalOpen(true);
|
|
102409
|
-
return;
|
|
102410
|
-
}
|
|
102411
|
-
// If no source images, load them without generating
|
|
102412
|
-
logToTerminal('log', '📥 Loading source images without generating');
|
|
102413
|
-
setProductModalOpen(true);
|
|
102414
|
-
setProductGenerationLogs([]);
|
|
102415
|
-
const addLog = (msg) => {
|
|
102416
|
-
setProductGenerationLogs(prev => [...prev, msg]);
|
|
102417
|
-
};
|
|
102418
|
-
try {
|
|
102419
|
-
addLog(formatLogMessage('log', '🔍 Загрузка исходных изображений...'));
|
|
102420
|
-
logToTerminal('log', '🔍 Loading source images...');
|
|
102421
|
-
const bankaImages = await fetchBankaImages(folderId);
|
|
102422
|
-
if (bankaImages.length === 0) {
|
|
102423
|
-
addLog(formatLogMessage('warn', '⚠️ Исходные изображения не найдены, но есть product.png'));
|
|
102424
|
-
logToTerminal('warn', '⚠️ No source images found, but product.png exists');
|
|
102425
|
-
}
|
|
102426
|
-
else {
|
|
102427
|
-
addLog(formatLogMessage('log', `✅ Загружено ${bankaImages.length} исходных изображений`));
|
|
102428
|
-
logToTerminal('log', `✅ Loaded ${bankaImages.length} source image(s)`);
|
|
102429
|
-
setProductSourceImages(bankaImages.map(img => img.url));
|
|
102430
|
-
}
|
|
102431
|
-
}
|
|
102432
|
-
catch (err) {
|
|
102433
|
-
const errorMsg = 'Error loading source images: ' + err.message;
|
|
102434
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102435
|
-
logToTerminal('error', '❌ Error loading source images:', err);
|
|
102436
|
-
// Don't alert, just log - user can still regenerate with existing product image
|
|
102437
|
-
}
|
|
102438
|
-
return; // IMPORTANT: Return here to prevent generation
|
|
102439
|
-
}
|
|
102440
|
-
// Product.png doesn't exist - proceed with full generation
|
|
102441
|
-
logToTerminal('log', '🎨 product.png not found, starting full generation');
|
|
102442
|
-
}
|
|
102443
|
-
catch (err) {
|
|
102444
|
-
// If check fails, proceed with generation (better to try than to block)
|
|
102445
|
-
logToTerminal('warn', '⚠️ Failed to check for product.png, proceeding with generation:', err.message);
|
|
102446
|
-
}
|
|
102447
|
-
// Full generation flow - only when product image doesn't exist
|
|
102448
|
-
setGeneratingProduct(true);
|
|
102449
|
-
setProductGenerationLogs([]);
|
|
102450
|
-
setProductProcessStartTime(Date.now());
|
|
102451
|
-
setProductModalOpen(true);
|
|
102452
|
-
const addLog = (msg) => {
|
|
102453
|
-
setProductGenerationLogs(prev => [...prev, msg]);
|
|
102454
|
-
};
|
|
102455
|
-
try {
|
|
102456
|
-
const folderId = extractFolderId(driveFolderUrl);
|
|
102457
|
-
if (!folderId) {
|
|
102458
|
-
throw new Error('Invalid Google Drive Folder URL');
|
|
102459
|
-
}
|
|
102460
|
-
addLog(formatLogMessage('log', '🔍 Searching for source images (banka*.png/jpg)...'));
|
|
102461
|
-
logToTerminal('log', '🔍 Searching for banka images...');
|
|
102462
|
-
const bankaImages = await fetchBankaImages(folderId);
|
|
102463
|
-
if (bankaImages.length === 0) {
|
|
102464
|
-
addLog(formatLogMessage('error', 'No banka*.png/jpg files found in the folder'));
|
|
102465
|
-
alert('No banka*.png/jpg files found in the folder');
|
|
102466
|
-
return;
|
|
102467
|
-
}
|
|
102468
|
-
addLog(formatLogMessage('log', `✅ Found ${bankaImages.length} source image(s)`));
|
|
102469
|
-
logToTerminal('log', `✅ Found ${bankaImages.length} banka image(s)`);
|
|
102470
|
-
setProductSourceImages(bankaImages.map(img => img.url));
|
|
102471
|
-
// Generate product image
|
|
102472
|
-
addLog(formatLogMessage('log', '🎨 Generating product image...'));
|
|
102473
|
-
logToTerminal('log', '🎨 Generating product image...');
|
|
102474
|
-
const generatedImageUrl = await generateProductFromBanka(bankaImages.map(img => img.url), '', addLog);
|
|
102475
|
-
setProductGeneratedImage(generatedImageUrl);
|
|
102476
|
-
}
|
|
102477
|
-
catch (err) {
|
|
102478
|
-
const errorMsg = 'Error generating product: ' + err.message;
|
|
102479
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102480
|
-
logToTerminal('error', '❌ Error generating product:', err);
|
|
102481
|
-
alert(errorMsg);
|
|
102482
|
-
}
|
|
102483
|
-
finally {
|
|
102484
|
-
setGeneratingProduct(false);
|
|
102485
|
-
}
|
|
102486
|
-
};
|
|
102487
|
-
const handleRegenerateProduct = async () => {
|
|
102488
|
-
if (!productSourceImages.length || !productGeneratedImage)
|
|
102215
|
+
const folderId = extractFolderId(driveFolderUrl);
|
|
102216
|
+
if (!folderId) {
|
|
102217
|
+
alert('Некорректная ссылка на папку Google Drive');
|
|
102489
102218
|
return;
|
|
102490
|
-
setGeneratingProduct(true);
|
|
102491
|
-
setProductGenerationLogs([]);
|
|
102492
|
-
if (!productProcessStartTime) {
|
|
102493
|
-
setProductProcessStartTime(Date.now());
|
|
102494
|
-
}
|
|
102495
|
-
const addLog = (msg) => {
|
|
102496
|
-
setProductGenerationLogs(prev => [...prev, msg]);
|
|
102497
|
-
};
|
|
102498
|
-
try {
|
|
102499
|
-
if (productRegeneratePrompt.trim()) {
|
|
102500
|
-
addLog(formatLogMessage('log', '🔄 Переделка изображения продукта с пользовательскими требованиями:'));
|
|
102501
|
-
productRegeneratePrompt.split('\n').filter(line => line.trim()).forEach((line, idx) => {
|
|
102502
|
-
addLog(formatLogMessage('log', ` ${idx + 1}. ${line.trim()}`));
|
|
102503
|
-
});
|
|
102504
|
-
}
|
|
102505
|
-
else {
|
|
102506
|
-
addLog(formatLogMessage('log', '🔄 Переделка изображения продукта (без дополнительных требований)'));
|
|
102507
|
-
}
|
|
102508
|
-
logToTerminal('log', '🔄 Regenerating product image with additional prompt:', productRegeneratePrompt);
|
|
102509
|
-
// Pass current product image as reference for regeneration
|
|
102510
|
-
const generatedImageUrl = await generateProductFromBanka(productSourceImages, productRegeneratePrompt, addLog, productGeneratedImage || undefined);
|
|
102511
|
-
setProductGeneratedImage(generatedImageUrl);
|
|
102512
|
-
setProductRegeneratePrompt(''); // Clear prompt after regeneration
|
|
102513
|
-
}
|
|
102514
|
-
catch (err) {
|
|
102515
|
-
const errorMsg = 'Error regenerating product: ' + err.message;
|
|
102516
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102517
|
-
logToTerminal('error', '❌ Error regenerating product:', err);
|
|
102518
|
-
alert(errorMsg);
|
|
102519
|
-
}
|
|
102520
|
-
finally {
|
|
102521
|
-
setGeneratingProduct(false);
|
|
102522
102219
|
}
|
|
102523
|
-
|
|
102524
|
-
|
|
102525
|
-
|
|
102526
|
-
alert('No product image to upload');
|
|
102220
|
+
const ext = file.name.toLowerCase().split('.').pop();
|
|
102221
|
+
if (ext !== 'png' && ext !== 'jpg' && ext !== 'jpeg') {
|
|
102222
|
+
alert('Выберите изображение PNG или JPG');
|
|
102527
102223
|
return;
|
|
102528
102224
|
}
|
|
102529
102225
|
setUploadingProduct(true);
|
|
102530
|
-
|
|
102531
|
-
if (!productProcessStartTime) {
|
|
102532
|
-
setProductProcessStartTime(Date.now());
|
|
102533
|
-
}
|
|
102534
|
-
const addLog = (msg) => {
|
|
102535
|
-
setProductGenerationLogs(prev => [...prev, msg]);
|
|
102536
|
-
};
|
|
102226
|
+
const addLog = () => { };
|
|
102537
102227
|
try {
|
|
102538
|
-
const
|
|
102539
|
-
|
|
102540
|
-
|
|
102541
|
-
|
|
102542
|
-
|
|
102543
|
-
|
|
102544
|
-
|
|
102545
|
-
|
|
102546
|
-
|
|
102547
|
-
|
|
102548
|
-
|
|
102549
|
-
|
|
102550
|
-
...folderFilesInfo,
|
|
102551
|
-
hasProduct: true
|
|
102552
|
-
});
|
|
102553
|
-
}
|
|
102554
|
-
alert('product.png uploaded successfully!');
|
|
102555
|
-
setProductModalOpen(false);
|
|
102228
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
102229
|
+
const reader = new FileReader();
|
|
102230
|
+
reader.onloadend = () => resolve(reader.result);
|
|
102231
|
+
reader.onerror = reject;
|
|
102232
|
+
reader.readAsDataURL(file);
|
|
102233
|
+
});
|
|
102234
|
+
const filename = ext === 'png' ? 'product.png' : 'product.jpg';
|
|
102235
|
+
logToTerminal('log', '📤 Загрузка product.png/jpg...');
|
|
102236
|
+
await uploadImageToDrive(dataUrl, filename, folderId, addLog);
|
|
102237
|
+
logToTerminal('log', '✅ product.png/jpg загружен');
|
|
102238
|
+
setFolderFilesInfo({ hasProduct: true });
|
|
102239
|
+
alert('product.png/jpg успешно загружен в папку!');
|
|
102556
102240
|
}
|
|
102557
102241
|
catch (err) {
|
|
102558
|
-
const errorMsg = '
|
|
102559
|
-
|
|
102560
|
-
logToTerminal('error', '❌ Error uploading product:', err);
|
|
102242
|
+
const errorMsg = 'Ошибка загрузки: ' + err.message;
|
|
102243
|
+
logToTerminal('error', '❌', errorMsg);
|
|
102561
102244
|
alert(errorMsg);
|
|
102562
102245
|
}
|
|
102563
102246
|
finally {
|
|
@@ -102801,7 +102484,7 @@ function App() {
|
|
|
102801
102484
|
addLog(formatLogMessage(level, ...args));
|
|
102802
102485
|
};
|
|
102803
102486
|
logMsg('log', '📦 Creating ZIP archive with HTML and product image...');
|
|
102804
|
-
const zip = new (
|
|
102487
|
+
const zip = new (jszip__WEBPACK_IMPORTED_MODULE_51___default())();
|
|
102805
102488
|
zip.file('index.html', htmlContent);
|
|
102806
102489
|
zip.file('product.png', productImageBlob);
|
|
102807
102490
|
logMsg('log', '✅ Files added to archive: index.html, product.png');
|
|
@@ -103141,7 +102824,13 @@ function App() {
|
|
|
103141
102824
|
: '';
|
|
103142
102825
|
const imagePrompts = tasks.map(t => {
|
|
103143
102826
|
const basePromptStructure = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.getImageGenerationBasePrompt)(generateGeo, generatePrice, currencyForPrompt, undefined, t.ratio);
|
|
103144
|
-
|
|
102827
|
+
const bulletsLine = t.approach.noBullets
|
|
102828
|
+
? t.approach.bulletsFocus
|
|
102829
|
+
: (() => {
|
|
102830
|
+
const [b1, b2, b3] = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.pickRandomBullets)(t.approach.name);
|
|
102831
|
+
return `ОБЯЗАТЕЛЬНЫЕ БУЛЛЕТЫ (используй именно эти 3, в указанном порядке; переведи на язык ${generateGeo}): 1) ${b1} 2) ${b2} 3) ${b3}`;
|
|
102832
|
+
})();
|
|
102833
|
+
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 обязателен; буллиты добавляй только если они разрешены для подхода).`;
|
|
103145
102834
|
});
|
|
103146
102835
|
// Initialize placeholders for all images
|
|
103147
102836
|
const initialPlaceholders = tasks.map((t, index) => ({
|
|
@@ -104006,41 +103695,13 @@ ${imageData.originalPrompt}
|
|
|
104006
103695
|
throw new Error(err.error?.message || 'Failed to fetch Drive files');
|
|
104007
103696
|
}
|
|
104008
103697
|
const data = await response.json();
|
|
104009
|
-
// Filter out
|
|
103698
|
+
// Filter out product.png/jpg (used for product reference, not creative images)
|
|
104010
103699
|
const filteredFiles = data.files.filter((f) => {
|
|
104011
103700
|
const name = f.name?.toLowerCase() || '';
|
|
104012
|
-
return
|
|
103701
|
+
return name !== 'product.png' && name !== 'product.jpg';
|
|
104013
103702
|
});
|
|
104014
103703
|
return filteredFiles.map((f) => f.id ? `https://drive.google.com/file/d/${f.id}/view?usp=sharing` : '');
|
|
104015
103704
|
};
|
|
104016
|
-
const fetchBankaImages = async (folderId) => {
|
|
104017
|
-
const validToken = await getValidAccessToken();
|
|
104018
|
-
if (!validToken)
|
|
104019
|
-
throw new Error('Not logged in');
|
|
104020
|
-
const q = `'${folderId}' in parents and trashed = false and mimeType contains 'image/'`;
|
|
104021
|
-
const fields = 'files(id, name)';
|
|
104022
|
-
const url = `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(q)}&fields=${encodeURIComponent(fields)}`;
|
|
104023
|
-
const response = await fetch(url, {
|
|
104024
|
-
headers: {
|
|
104025
|
-
'Authorization': `Bearer ${validToken}`
|
|
104026
|
-
}
|
|
104027
|
-
});
|
|
104028
|
-
if (!response.ok) {
|
|
104029
|
-
const err = await response.json();
|
|
104030
|
-
throw new Error(err.error?.message || 'Failed to fetch Drive files');
|
|
104031
|
-
}
|
|
104032
|
-
const data = await response.json();
|
|
104033
|
-
// Filter only banka* files (case insensitive)
|
|
104034
|
-
const bankaFiles = data.files.filter((f) => {
|
|
104035
|
-
const name = f.name?.toLowerCase() || '';
|
|
104036
|
-
return name.startsWith('banka') && (name.endsWith('.png') || name.endsWith('.jpg') || name.endsWith('.jpeg'));
|
|
104037
|
-
});
|
|
104038
|
-
return bankaFiles.map((f) => ({
|
|
104039
|
-
id: f.id,
|
|
104040
|
-
name: f.name,
|
|
104041
|
-
url: `https://drive.google.com/file/d/${f.id}/view?usp=sharing`
|
|
104042
|
-
}));
|
|
104043
|
-
};
|
|
104044
103705
|
const fetchProductImage = async (folderId) => {
|
|
104045
103706
|
const validToken = await getValidAccessToken();
|
|
104046
103707
|
if (!validToken)
|
|
@@ -104417,11 +104078,7 @@ ${imageData.originalPrompt}
|
|
|
104417
104078
|
logToTerminal('log', '[Load Content] Loaded generatePriceWithCurrency:', settings.generatePriceWithCurrency);
|
|
104418
104079
|
}
|
|
104419
104080
|
}
|
|
104420
|
-
// Load brand
|
|
104421
|
-
if (loadedData.brand !== undefined) {
|
|
104422
|
-
setBrand(loadedData.brand || '');
|
|
104423
|
-
logToTerminal('log', '[Load Content] Loaded brand:', loadedData.brand || '(empty)');
|
|
104424
|
-
}
|
|
104081
|
+
// Load link (brand is auto-generated from product, geo, price)
|
|
104425
104082
|
if (loadedData.link !== undefined) {
|
|
104426
104083
|
setLink(loadedData.link || '');
|
|
104427
104084
|
logToTerminal('log', '[Load Content] Loaded link:', loadedData.link || '(empty)');
|
|
@@ -104739,8 +104396,8 @@ ${imageData.originalPrompt}
|
|
|
104739
104396
|
}
|
|
104740
104397
|
setGeneratedData(rows);
|
|
104741
104398
|
// Create workbook
|
|
104742
|
-
const wb =
|
|
104743
|
-
const ws =
|
|
104399
|
+
const wb = xlsx__WEBPACK_IMPORTED_MODULE_50__.utils.book_new();
|
|
104400
|
+
const ws = xlsx__WEBPACK_IMPORTED_MODULE_50__.utils.aoa_to_sheet(rows);
|
|
104744
104401
|
// Set column widths (approximate pixel width / 7)
|
|
104745
104402
|
ws['!cols'] = [
|
|
104746
104403
|
{ wch: 20 }, // id
|
|
@@ -104753,10 +104410,10 @@ ${imageData.originalPrompt}
|
|
|
104753
104410
|
{ wch: 40 }, // image_link
|
|
104754
104411
|
{ wch: 20 } // brand
|
|
104755
104412
|
];
|
|
104756
|
-
|
|
104413
|
+
xlsx__WEBPACK_IMPORTED_MODULE_50__.utils.book_append_sheet(wb, ws, "Products");
|
|
104757
104414
|
// Generate buffer
|
|
104758
|
-
const wbout =
|
|
104759
|
-
// Upload to Drive
|
|
104415
|
+
const wbout = xlsx__WEBPACK_IMPORTED_MODULE_50__.write(wb, { bookType: 'xlsx', type: 'array' });
|
|
104416
|
+
// Upload to Drive (имя файла по бренду)
|
|
104760
104417
|
const dateStr = new Date().toISOString().split('T')[0];
|
|
104761
104418
|
const fileName = `${brand}-${dateStr}.xlsx`;
|
|
104762
104419
|
const result = await uploadFileToDrive(wbout, fileName, folderId || undefined);
|
|
@@ -104877,13 +104534,13 @@ ${imageData.originalPrompt}
|
|
|
104877
104534
|
setTestLoading(true);
|
|
104878
104535
|
try {
|
|
104879
104536
|
// Create simple test workbook with structure
|
|
104880
|
-
const wb =
|
|
104537
|
+
const wb = xlsx__WEBPACK_IMPORTED_MODULE_50__.utils.book_new();
|
|
104881
104538
|
const rows = [
|
|
104882
104539
|
INSTRUCTION_ROW,
|
|
104883
104540
|
['id', 'title', 'description', 'availability', 'condition', 'price', 'link', 'image_link', 'brand'],
|
|
104884
104541
|
['test1', 'Test Title', 'Test Description', 'in stock', 'new', '10.00 USD', 'http://test.com', 'http://test.com/img.jpg', 'TestBrand']
|
|
104885
104542
|
];
|
|
104886
|
-
const ws =
|
|
104543
|
+
const ws = xlsx__WEBPACK_IMPORTED_MODULE_50__.utils.aoa_to_sheet(rows);
|
|
104887
104544
|
// Set column widths
|
|
104888
104545
|
ws['!cols'] = [
|
|
104889
104546
|
{ wch: 20 }, // id
|
|
@@ -104896,8 +104553,8 @@ ${imageData.originalPrompt}
|
|
|
104896
104553
|
{ wch: 40 }, // image_link
|
|
104897
104554
|
{ wch: 20 } // brand
|
|
104898
104555
|
];
|
|
104899
|
-
|
|
104900
|
-
const wbout =
|
|
104556
|
+
xlsx__WEBPACK_IMPORTED_MODULE_50__.utils.book_append_sheet(wb, ws, "Test");
|
|
104557
|
+
const wbout = xlsx__WEBPACK_IMPORTED_MODULE_50__.write(wb, { bookType: 'xlsx', type: 'array' });
|
|
104901
104558
|
// Try to extract folder ID if available, otherwise upload to root
|
|
104902
104559
|
const folderId = driveFolderUrl ? extractFolderId(driveFolderUrl) : undefined;
|
|
104903
104560
|
const result = await uploadFileToDrive(wbout, 'test_table.xlsx', folderId || undefined);
|
|
@@ -104931,7 +104588,7 @@ ${imageData.originalPrompt}
|
|
|
104931
104588
|
};
|
|
104932
104589
|
// Show lock screen if not unlocked
|
|
104933
104590
|
if (!unlocked) {
|
|
104934
|
-
return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104591
|
+
return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_32__["default"], { theme: theme },
|
|
104935
104592
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_4__["default"], null),
|
|
104936
104593
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { onClick: handleSecretClick, sx: {
|
|
104937
104594
|
width: '100vw',
|
|
@@ -104971,24 +104628,24 @@ ${imageData.originalPrompt}
|
|
|
104971
104628
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("br", null),
|
|
104972
104629
|
"Please contact system administrator"))));
|
|
104973
104630
|
}
|
|
104974
|
-
return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104631
|
+
return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_32__["default"], { theme: theme },
|
|
104975
104632
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_4__["default"], null),
|
|
104976
104633
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_7__["default"], { maxWidth: "lg", sx: { py: 4 } },
|
|
104977
104634
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 } },
|
|
104978
104635
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h4", component: "h1", sx: { fontWeight: 'bold', color: 'primary.main' } }, "Docs Combiner"),
|
|
104979
104636
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
104980
104637
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { onClick: () => setPromptManagerOpen(true), color: "inherit", "aria-label": "manage prompts", sx: { mr: 1 } },
|
|
104981
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104982
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { onClick: toggleTheme, color: "inherit", "aria-label": "toggle theme" }, darkMode ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104638
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_46__["default"], null)),
|
|
104639
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { onClick: toggleTheme, color: "inherit", "aria-label": "toggle theme" }, darkMode ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_36__["default"], null) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_35__["default"], null)))),
|
|
104983
104640
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_9__["default"], { variant: "outlined", sx: { mb: 4 } },
|
|
104984
104641
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_10__["default"], null,
|
|
104985
104642
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 3 },
|
|
104986
104643
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
104987
104644
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true }, "Google Drive Authentication"),
|
|
104988
104645
|
accessToken ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_12__["default"], null,
|
|
104989
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_13__["default"], { expandIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104646
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_13__["default"], { expandIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_40__["default"], null) },
|
|
104990
104647
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { sx: { display: 'flex', alignItems: 'center', color: 'success.main' } },
|
|
104991
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104648
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_37__["default"], { sx: { mr: 1 } }),
|
|
104992
104649
|
" Logged In (Credentials Hidden)")),
|
|
104993
104650
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_14__["default"], null,
|
|
104994
104651
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 2 },
|
|
@@ -104999,7 +104656,7 @@ ${imageData.originalPrompt}
|
|
|
104999
104656
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "Client ID", variant: "outlined", fullWidth: true, value: clientId, onChange: (e) => handleClientIdChange(e.target.value), helperText: "From Google Cloud Console (OAuth 2.0 Client ID)" }),
|
|
105000
104657
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "Client Secret", variant: "outlined", fullWidth: true, value: clientSecret, onChange: (e) => handleClientSecretChange(e.target.value) }))),
|
|
105001
104658
|
openaiApiKey && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 2, mt: 2 } },
|
|
105002
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104659
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_33__["default"], { color: openRouterAccountBalance !== null ? 'primary' : 'disabled' }),
|
|
105003
104660
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2", color: openRouterAccountBalance !== null ? 'text.primary' : 'text.secondary' },
|
|
105004
104661
|
"\u0411\u0430\u043B\u0430\u043D\u0441: ",
|
|
105005
104662
|
openRouterBalanceLoading ? 'Loading...' : (openRouterAccountBalance !== null ? `$${openRouterAccountBalance.toFixed(2)}` : 'N/A')),
|
|
@@ -105008,8 +104665,8 @@ ${imageData.originalPrompt}
|
|
|
105008
104665
|
"\u041B\u0438\u043C\u0438\u0442 \u043A\u043B\u044E\u0447\u0430: ",
|
|
105009
104666
|
openRouterBalanceLoading ? 'Loading...' : (openRouterBalance === -1 ? 'Без лимита' : (openRouterBalance !== null ? `${openRouterBalance.toFixed(4)}` : 'N/A'))))),
|
|
105010
104667
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2, alignItems: "center", sx: { mt: 2 } },
|
|
105011
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: accessToken ? "success" : "primary", onClick: handleLogin, disabled: authLoading || !clientId || !clientSecret, startIcon: authLoading ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20, color: "inherit" }) : (accessToken ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105012
|
-
accessToken && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "outlined", color: "error", onClick: handleLogout, startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104668
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: accessToken ? "success" : "primary", onClick: handleLogin, disabled: authLoading || !clientId || !clientSecret, startIcon: authLoading ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20, color: "inherit" }) : (accessToken ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_37__["default"], null) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_42__["default"], null)), sx: { flexGrow: 1 } }, authLoading ? 'Logging in...' : (accessToken ? 'Logged In' : 'Login with Google')),
|
|
104669
|
+
accessToken && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "outlined", color: "error", onClick: handleLogout, startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_43__["default"], null) }, "Logout")))),
|
|
105013
104670
|
!accessToken && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
|
|
105014
104671
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_16__["default"], null),
|
|
105015
104672
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
@@ -105025,16 +104682,13 @@ ${imageData.originalPrompt}
|
|
|
105025
104682
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 14 }),
|
|
105026
104683
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0444\u0430\u0439\u043B\u043E\u0432...")))),
|
|
105027
104684
|
!checkingFolderFiles && folderFilesInfo && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_19__["default"], null,
|
|
105028
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { component: "span", sx: { display: 'block' } }, folderFilesInfo.
|
|
105029
|
-
"\u2713 \u041D\u0430\u0439\u0434\u0435\u043D\u043E \u0438\u0441\u0445\u043E\u0434\u043D\u044B\u0445 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0439: ",
|
|
105030
|
-
folderFilesInfo.bankaCount)) : (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { component: "span", sx: { color: 'warning.main' } }, "\u26A0 \u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F (banka*.png/jpg) \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B"))),
|
|
105031
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { component: "span", sx: { display: 'block', mt: 0.5 } }, folderFilesInfo.hasProduct ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { component: "span", sx: { color: 'success.main' } }, "\u2713 product.png/jpg \u043D\u0430\u0439\u0434\u0435\u043D")) : (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { component: "span", sx: { color: 'info.main' } }, "\u2139 product.png/jpg \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D"))))),
|
|
104685
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { component: "span", sx: { display: 'block' } }, folderFilesInfo.hasProduct ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { component: "span", sx: { color: 'success.main' } }, "\u2713 product.png/jpg \u043D\u0430\u0439\u0434\u0435\u043D")) : (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { component: "span", sx: { color: 'info.main' } }, "\u2139 product.png/jpg \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u2014 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0438\u0436\u0435"))))),
|
|
105032
104686
|
!checkingFolderFiles && !folderFilesInfo && driveFolderUrl.trim() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_19__["default"], null, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u0443\u044E \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u043F\u0430\u043F\u043A\u0443 Google Drive"))))),
|
|
105033
104687
|
!driveFolderUrl.trim() ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "info", sx: { mt: 2 } },
|
|
105034
104688
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body1", sx: { fontWeight: 'bold', mb: 1 } }, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u0430\u043F\u043A\u0443 Google Drive \u0434\u043B\u044F \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u044F"),
|
|
105035
104689
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2" }, "\u0414\u043B\u044F \u0440\u0430\u0431\u043E\u0442\u044B \u0441 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0435\u0439 \u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430 \u0438 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0439 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u0430\u043F\u043A\u0443 Google Drive. \u0412\u0441\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u0431\u0443\u0434\u0443\u0442 \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0442\u044C\u0441\u044F \u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044C\u0441\u044F \u0438\u0437 \u044D\u0442\u043E\u0439 \u043F\u0430\u043F\u043A\u0438."))) : (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
|
|
105036
104690
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: { xs: 'column', sm: 'row' }, spacing: 2 },
|
|
105037
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "Brand (Short ID)", variant: "outlined", sx: { flex: '0 0
|
|
104691
|
+
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" }),
|
|
105038
104692
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { flex: 1, minWidth: 0 } },
|
|
105039
104693
|
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/" }))),
|
|
105040
104694
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_16__["default"], { sx: { my: 2 } }),
|
|
@@ -105067,7 +104721,7 @@ ${imageData.originalPrompt}
|
|
|
105067
104721
|
: 'Выбрана модель: ' + (validationModels.find(m => m.id === selectedValidationModel)?.name || selectedValidationModel))),
|
|
105068
104722
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_25__["default"], { control: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_26__["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 } })))),
|
|
105069
104723
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2, sx: { mb: 2 } },
|
|
105070
|
-
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(
|
|
104724
|
+
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_34__["default"], null), onClick: handleGenerateContent, disabled: generating || loadingContentFromDrive || !openaiApiKey || !generateProduct.trim() || !generateGeo.trim(), sx: { flexGrow: 1 }, size: "large" }, generating ? 'Generating...' : 'Generate Titles & Descriptions'),
|
|
105071
104725
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 1, alignItems: "center", sx: { flexGrow: 1 } },
|
|
105072
104726
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_27__["default"], { title: imageAspectRatio === '1:1'
|
|
105073
104727
|
? '1:1 — квадрат (1024×1024 px)'
|
|
@@ -105094,7 +104748,7 @@ ${imageData.originalPrompt}
|
|
|
105094
104748
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_29__["default"], { value: "1:1", sx: { px: 1.5, fontWeight: 600, fontSize: '0.8rem' } }, "1:1"),
|
|
105095
104749
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_29__["default"], { value: "2:3", sx: { px: 1.5, fontWeight: 600, fontSize: '0.8rem' } }, "2:3"),
|
|
105096
104750
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_29__["default"], { value: "both", sx: { px: 1.5, fontWeight: 600, fontSize: '0.8rem' } }, "\u041E\u0431\u0430")))),
|
|
105097
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "secondary", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104751
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "secondary", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_34__["default"], null), onClick: handleGenerateImages, disabled: generatingImages ||
|
|
105098
104752
|
loadingImagesFromDrive ||
|
|
105099
104753
|
!openaiApiKey ||
|
|
105100
104754
|
(!accessToken && !refreshToken) ||
|
|
@@ -105120,16 +104774,15 @@ ${imageData.originalPrompt}
|
|
|
105120
104774
|
openaiApiKey && (accessToken || refreshToken) && !generateProduct.trim() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "error", sx: { mt: 1 } }, "\u0417\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u0435 \u043F\u043E\u043B\u0435 \u0422\u043E\u0432\u0430\u0440")),
|
|
105121
104775
|
openaiApiKey && (accessToken || refreshToken) && generateProduct.trim() && !generateGeo.trim() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "error", sx: { mt: 1 } }, "\u0417\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u0435 \u043F\u043E\u043B\u0435 \u0413\u0435\u043E")),
|
|
105122
104776
|
openaiApiKey && (accessToken || refreshToken) && generateProduct.trim() && generateGeo.trim() && !driveFolderUrl.trim() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "error", sx: { mt: 1 } }, "\u0417\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u0435 URL \u043F\u0430\u043F\u043A\u0438 Google Drive"))))),
|
|
104777
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "file", ref: productFileInputRef, accept: "image/png,image/jpeg,image/jpg", style: { display: 'none' }, onChange: handleProductFileSelected }),
|
|
105123
104778
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2, sx: { mb: 2 } },
|
|
105124
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "success", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105125
|
-
|
|
105126
|
-
: (productGeneratedImage ? 'Перегенерировать Product' : 'Generate Product from Banka')),
|
|
105127
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "info", startIcon: generatingLanding ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20, color: "inherit" }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_49__["default"], null), onClick: handleCreateLanding, disabled: generatingLanding ||
|
|
104779
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "success", startIcon: uploadingProduct ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20, color: "inherit" }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_38__["default"], null), onClick: handleUploadProductFile, disabled: uploadingProduct || !accessToken || !driveFolderUrl.trim(), sx: { flexGrow: 1 }, size: "large" }, uploadingProduct ? 'Загрузка...' : 'Загрузить product.png/jpg'),
|
|
104780
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "info", startIcon: generatingLanding ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20, color: "inherit" }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_44__["default"], null), onClick: handleCreateLanding, disabled: generatingLanding ||
|
|
105128
104781
|
!openaiApiKey ||
|
|
105129
104782
|
!accessToken ||
|
|
105130
104783
|
!driveFolderUrl.trim() ||
|
|
105131
104784
|
(folderFilesInfo !== null && !folderFilesInfo.hasProduct), sx: { flexGrow: 1 }, size: "large", title: folderFilesInfo !== null && !folderFilesInfo.hasProduct
|
|
105132
|
-
? 'product.png/jpg не найден в папке.
|
|
104785
|
+
? 'product.png/jpg не найден в папке. Загрузите его с помощью кнопки «Загрузить product.png/jpg»'
|
|
105133
104786
|
: undefined }, generatingLanding ? 'Creating Landing...' : 'Создать лендинг')),
|
|
105134
104787
|
(generatedImagesData.length > 0 || loadingImagesFromDrive) && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { mb: 2, mt: 3 } },
|
|
105135
104788
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true, sx: { fontWeight: 'bold' } },
|
|
@@ -105281,7 +104934,7 @@ ${imageData.originalPrompt}
|
|
|
105281
104934
|
"+",
|
|
105282
104935
|
imageData.checkErrors.length - 2,
|
|
105283
104936
|
" \u0435\u0449\u0451")))),
|
|
105284
|
-
imageData.checkFailed && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "outlined", color: "warning", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104937
|
+
imageData.checkFailed && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "outlined", color: "warning", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_45__["default"], null), onClick: () => handleRetryCheck(imageData), disabled: imageData.checking, sx: { mt: 0.5, fontSize: '0.7rem', py: 0.25, px: 1 } }, "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443")))),
|
|
105285
104938
|
imageData.checkStatus === 'checking' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'warning.main', display: 'block' } }, "\uD83D\uDD0D \u041F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442\u0441\u044F...")),
|
|
105286
104939
|
imageData.checkStatus === 'pending' && !imageData.failed && !imageData.generating && imageData.imageUrl && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'text.secondary', display: 'block' } }, "\u23F3 \u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438")),
|
|
105287
104940
|
imageData.failed && !imageData.generating && !imageData.imageUrl && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'error.main', display: 'block', fontWeight: 'bold' } }, "\u274C \u0413\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C")),
|
|
@@ -105304,7 +104957,7 @@ ${imageData.originalPrompt}
|
|
|
105304
104957
|
: 'transparent'
|
|
105305
104958
|
}
|
|
105306
104959
|
} }),
|
|
105307
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "contained", color: imageData.failed ? 'error' : imageData.checkStatus === 'needs_rebuild' ? 'warning' : 'primary', startIcon: imageData.regenerating ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 16 }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104960
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "contained", color: imageData.failed ? 'error' : imageData.checkStatus === 'needs_rebuild' ? 'warning' : 'primary', startIcon: imageData.regenerating ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 16 }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_34__["default"], null), onClick: () => handleRegenerateImage(imageData), disabled: imageData.regenerating ||
|
|
105308
104961
|
imageData.uploading ||
|
|
105309
104962
|
imageData.checkStatus === 'checking' ||
|
|
105310
104963
|
!imageData.originalPrompt ||
|
|
@@ -105314,12 +104967,12 @@ ${imageData.originalPrompt}
|
|
|
105314
104967
|
: ((generatingImages && !imageData.imageUrl && !imageData.failed && !imageData.generating)
|
|
105315
104968
|
? 'В очереди'
|
|
105316
104969
|
: (!imageData.imageUrl ? 'Сгенерировать' : 'Переделать'))),
|
|
105317
|
-
imageData.imageUrl && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "outlined", color: "secondary", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104970
|
+
imageData.imageUrl && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "outlined", color: "secondary", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_45__["default"], null), onClick: () => handleRegenerateImageFresh(imageData), disabled: imageData.regenerating ||
|
|
105318
104971
|
imageData.uploading ||
|
|
105319
104972
|
imageData.checkStatus === 'checking' ||
|
|
105320
104973
|
!imageData.originalPrompt ||
|
|
105321
104974
|
!imageData.productImageUrl, fullWidth: true }, "\u041F\u0435\u0440\u0435\u0434\u0435\u043B\u0430\u0442\u044C \u0437\u0430\u043D\u043E\u0432\u043E")),
|
|
105322
|
-
!imageData.uploaded && imageData.imageUrl && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "outlined", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104975
|
+
!imageData.uploaded && imageData.imageUrl && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { size: "small", variant: "outlined", startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_38__["default"], null), onClick: () => {
|
|
105323
104976
|
const folderId = extractFolderId(driveFolderUrl);
|
|
105324
104977
|
if (folderId) {
|
|
105325
104978
|
handleUploadImage(imageData, folderId);
|
|
@@ -105327,7 +104980,7 @@ ${imageData.originalPrompt}
|
|
|
105327
104980
|
}, disabled: imageData.uploading || imageData.regenerating || !driveFolderUrl.trim(), fullWidth: true }, imageData.uploading ? 'Загрузка...' : 'Загрузить'))))));
|
|
105328
104981
|
}))),
|
|
105329
104982
|
generatedImagesData.length > 0 && generatedImagesData.some(img => !img.uploaded && img.imageUrl) && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { mt: 2 } },
|
|
105330
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "primary", onClick: handleUploadAllImages, disabled: uploadingImages || generatingImages || !driveFolderUrl.trim(), startIcon: uploadingImages ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20 }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104983
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "primary", onClick: handleUploadAllImages, disabled: uploadingImages || generatingImages || !driveFolderUrl.trim(), startIcon: uploadingImages ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20 }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_38__["default"], null), fullWidth: true }, uploadingImages ? 'Загрузка...' : `Загрузить все (${generatedImagesData.filter(img => !img.uploaded && img.imageUrl).length})`))))),
|
|
105331
104984
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_16__["default"], { sx: { my: 2 } }),
|
|
105332
104985
|
(generatedTitlesData.length > 0 || generatedTextsData.length > 0 || loadingContentFromDrive) && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { mb: 2 } },
|
|
105333
104986
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true, sx: { fontWeight: 'bold' } },
|
|
@@ -105398,7 +105051,7 @@ ${imageData.originalPrompt}
|
|
|
105398
105051
|
tooltip: { sx: { maxWidth: 360, fontSize: 12, lineHeight: 1.6, p: '10px 14px' } },
|
|
105399
105052
|
} },
|
|
105400
105053
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { size: "small", sx: { p: 0.25, color: 'text.disabled', '&:hover': { color: 'primary.main' } } },
|
|
105401
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105054
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_41__["default"], { sx: { fontSize: 14 } })))) : null),
|
|
105402
105055
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 1.5 },
|
|
105403
105056
|
titleData && (titleData.generating ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1 } },
|
|
105404
105057
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 16, sx: { color: 'primary.main' } }),
|
|
@@ -105465,11 +105118,11 @@ ${imageData.originalPrompt}
|
|
|
105465
105118
|
generatingLanding && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 } },
|
|
105466
105119
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20 }),
|
|
105467
105120
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2", sx: { color: 'text.secondary' } }, formatElapsedTime(elapsedTime.landing)))),
|
|
105468
|
-
!generatingLanding && generatedLandingHTML && generatedLandingImageBlob && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "outlined", color: "primary", onClick: handlePreviewLanding, startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105121
|
+
!generatingLanding && generatedLandingHTML && generatedLandingImageBlob && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "outlined", color: "primary", onClick: handlePreviewLanding, startIcon: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_47__["default"], null), fullWidth: true }, "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043B\u0435\u043D\u0434\u0438\u043D\u0433\u0430")))),
|
|
105469
105122
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2 },
|
|
105470
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", size: "large", onClick: handleGenerate, disabled: loading || !accessToken, startIcon: loading ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20, color: "inherit" }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105123
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", size: "large", onClick: handleGenerate, disabled: loading || !accessToken, startIcon: loading ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20, color: "inherit" }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_38__["default"], null), sx: { py: 1.5, fontSize: '1.1rem', flexGrow: 1 } }, loading ? 'Generating...' : 'Generate Table')),
|
|
105471
105124
|
uploadedLink && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "success", sx: { mt: 2 }, action: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_8__["default"], { "aria-label": "copy link", color: "inherit", size: "small", onClick: handleCopyLink, sx: { ml: 1 } },
|
|
105472
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105125
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_39__["default"], { fontSize: "small" })) },
|
|
105473
105126
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1 } },
|
|
105474
105127
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null,
|
|
105475
105128
|
"File Uploaded!",
|
|
@@ -105479,88 +105132,7 @@ ${imageData.originalPrompt}
|
|
|
105479
105132
|
getElectronAPI().openExternal(uploadedLink);
|
|
105480
105133
|
}, style: { cursor: 'pointer', textDecoration: 'underline', color: 'inherit' } }, "Click here to open in Google Drive")),
|
|
105481
105134
|
linkCopied && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'success.dark', fontWeight: 'bold' } }, "Copied!")))))))))),
|
|
105482
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105483
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_32__["default"], { open: generatingProduct || uploadingProduct, sx: {
|
|
105484
|
-
position: 'absolute',
|
|
105485
|
-
zIndex: (theme) => theme.zIndex.modal + 1,
|
|
105486
|
-
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
105487
|
-
display: 'flex',
|
|
105488
|
-
flexDirection: 'column',
|
|
105489
|
-
gap: 3
|
|
105490
|
-
} },
|
|
105491
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 } },
|
|
105492
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 60, sx: { color: 'white' } }),
|
|
105493
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", sx: { color: 'white', fontWeight: 'bold' } }, formatElapsedTime(elapsedTime.product))),
|
|
105494
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_30__["default"], { sx: {
|
|
105495
|
-
p: 3,
|
|
105496
|
-
maxWidth: '80%',
|
|
105497
|
-
maxHeight: '60%',
|
|
105498
|
-
overflow: 'auto',
|
|
105499
|
-
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
105500
|
-
? 'rgba(30, 30, 30, 0.95)'
|
|
105501
|
-
: 'rgba(255, 255, 255, 0.95)'
|
|
105502
|
-
} },
|
|
105503
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true, sx: { mb: 2, fontWeight: 'bold' } }, uploadingProduct ? 'Процесс загрузки' : 'Процесс генерации'),
|
|
105504
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { id: "product-generation-logs", sx: {
|
|
105505
|
-
fontFamily: 'monospace',
|
|
105506
|
-
fontSize: '0.875rem',
|
|
105507
|
-
lineHeight: 1.6,
|
|
105508
|
-
whiteSpace: 'pre-wrap',
|
|
105509
|
-
wordBreak: 'break-word',
|
|
105510
|
-
maxHeight: '400px',
|
|
105511
|
-
overflowY: 'auto'
|
|
105512
|
-
} }, productGenerationLogs.length === 0 ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { color: "text.secondary" }, uploadingProduct ? 'Начало загрузки...' : 'Инициализация...')) : (productGenerationLogs.map((log, idx) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { key: idx, sx: {
|
|
105513
|
-
mb: 0.5,
|
|
105514
|
-
color: log.includes('❌') || log.toLowerCase().includes('error') ? 'error.main' :
|
|
105515
|
-
log.includes('⚠️') || log.toLowerCase().includes('warn') ? 'warning.main' :
|
|
105516
|
-
'text.primary'
|
|
105517
|
-
} }, log))))))),
|
|
105518
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_33__["default"], null,
|
|
105519
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6" }, "Product Image Generation")),
|
|
105520
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_34__["default"], null,
|
|
105521
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 3, sx: { mt: 1 } },
|
|
105522
|
-
productSourceImages.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
105523
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "subtitle1", gutterBottom: true, sx: { fontWeight: 'bold' } },
|
|
105524
|
-
"\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F (",
|
|
105525
|
-
productSourceImages.length,
|
|
105526
|
-
"):"),
|
|
105527
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
105528
|
-
display: 'grid',
|
|
105529
|
-
gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' },
|
|
105530
|
-
gap: 2,
|
|
105531
|
-
mt: 1
|
|
105532
|
-
} }, productSourceImages.map((url, idx) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { key: idx, component: "img", src: convertDriveUrlToImageUrl(url), alt: `Source ${idx + 1}`, sx: {
|
|
105533
|
-
width: '100%',
|
|
105534
|
-
height: 'auto',
|
|
105535
|
-
maxHeight: 300,
|
|
105536
|
-
objectFit: 'contain',
|
|
105537
|
-
border: (theme) => `1px solid ${theme.palette.mode === 'dark' ? '#444' : '#ddd'}`,
|
|
105538
|
-
borderRadius: 1
|
|
105539
|
-
}, onError: (e) => {
|
|
105540
|
-
// Fallback: try using thumbnail URL if direct view fails
|
|
105541
|
-
const fileIdMatch = url.match(/\/file\/d\/([a-zA-Z0-9_-]+)/);
|
|
105542
|
-
if (fileIdMatch) {
|
|
105543
|
-
const fileId = fileIdMatch[1];
|
|
105544
|
-
e.target.src = `https://drive.google.com/thumbnail?id=${fileId}&sz=w1000`;
|
|
105545
|
-
}
|
|
105546
|
-
} })))))),
|
|
105547
|
-
productGeneratedImage && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
105548
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "subtitle1", gutterBottom: true, sx: { fontWeight: 'bold' } }, "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0434\u0443\u043A\u0442\u0430:"),
|
|
105549
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { component: "img", src: productGeneratedImage, alt: "Generated Product", sx: {
|
|
105550
|
-
width: '100%',
|
|
105551
|
-
height: 'auto',
|
|
105552
|
-
maxHeight: 400,
|
|
105553
|
-
objectFit: 'contain',
|
|
105554
|
-
border: (theme) => `2px solid ${theme.palette.primary.main}`,
|
|
105555
|
-
borderRadius: 1,
|
|
105556
|
-
mt: 1
|
|
105557
|
-
} }))),
|
|
105558
|
-
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\u044B\u0435 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043A \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438", variant: "outlined", fullWidth: true, multiline: true, minRows: 2, value: productRegeneratePrompt, onChange: (e) => setProductRegeneratePrompt(e.target.value), placeholder: "\u041D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: \u0441\u0434\u0435\u043B\u0430\u0439 \u0444\u043E\u043D \u0431\u0435\u043B\u044B\u043C, \u0443\u0432\u0435\u043B\u0438\u0447\u044C \u043B\u043E\u0433\u043E\u0442\u0438\u043F...", helperText: "\u042D\u0442\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u0431\u0443\u0434\u0443\u0442 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u043A \u0431\u0430\u0437\u043E\u0432\u043E\u043C\u0443 \u043F\u0440\u043E\u043C\u043F\u0442\u0443 (\u043D\u0435 \u0437\u0430\u043C\u0435\u043D\u044F\u044E\u0442 \u0435\u0433\u043E). \u041E\u0441\u0442\u0430\u0432\u044C\u0442\u0435 \u043F\u0443\u0441\u0442\u044B\u043C \u0434\u043B\u044F \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0431\u0430\u0437\u043E\u0432\u043E\u0433\u043E \u043F\u0440\u043E\u043C\u043F\u0442\u0430.", disabled: generatingProduct || uploadingProduct }))),
|
|
105559
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_35__["default"], { sx: { px: 3, pb: 2 } },
|
|
105560
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { onClick: () => setProductModalOpen(false), disabled: generatingProduct || uploadingProduct }, "\u0417\u0430\u043A\u0440\u044B\u0442\u044C"),
|
|
105561
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "outlined", color: "secondary", onClick: handleRegenerateProduct, disabled: generatingProduct || uploadingProduct || !productSourceImages.length || !productGeneratedImage, startIcon: generatingProduct ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20 }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_39__["default"], null) }, generatingProduct ? 'Перегенерация...' : 'Перегенерировать'),
|
|
105562
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "primary", onClick: handleUploadProduct, disabled: generatingProduct || uploadingProduct || !productGeneratedImage || !driveFolderUrl.trim(), startIcon: uploadingProduct ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20 }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_43__["default"], null) }, uploadingProduct ? 'Загрузка...' : 'Загрузить (product.png)'))),
|
|
105563
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_PromptManagerDialog__WEBPACK_IMPORTED_MODULE_53__["default"], { open: promptManagerOpen, onClose: () => setPromptManagerOpen(false) }))));
|
|
105135
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_PromptManagerDialog__WEBPACK_IMPORTED_MODULE_48__["default"], { open: promptManagerOpen, onClose: () => setPromptManagerOpen(false) }))));
|
|
105564
105136
|
}
|
|
105565
105137
|
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (App);
|
|
105566
105138
|
|
|
@@ -105995,8 +105567,8 @@ function PromptManagerDialog({ open, onClose }) {
|
|
|
105995
105567
|
const [viewModes, setViewModes] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
|
|
105996
105568
|
// selectedApproaches: array of indices from PAIR_APPROACH_POOL, ordered
|
|
105997
105569
|
const [selectedApproaches, setSelectedApproaches] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([0, 1, 2]);
|
|
105998
|
-
// imageApproachCounts:
|
|
105999
|
-
const [imageApproachCounts, setImageApproachCounts] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(
|
|
105570
|
+
// imageApproachCounts: по одному на каждый подход (CREO_APPROACHES), каждый 0–4
|
|
105571
|
+
const [imageApproachCounts, setImageApproachCounts] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(() => Array(_prompts__WEBPACK_IMPORTED_MODULE_34__.CREO_APPROACHES.length).fill(1));
|
|
106000
105572
|
// Загрузить оверрайды при открытии
|
|
106001
105573
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
106002
105574
|
if (open) {
|
|
@@ -106596,25 +106168,32 @@ function getSelectedPairApproaches() {
|
|
|
106596
106168
|
function getPairsCount() {
|
|
106597
106169
|
return getSelectedPairApproaches().length;
|
|
106598
106170
|
}
|
|
106171
|
+
/** Количество подходов для изображений. Должно совпадать с CREO_APPROACHES.length в prompts.ts */
|
|
106172
|
+
const IMAGE_APPROACH_COUNT = 10;
|
|
106599
106173
|
/**
|
|
106600
|
-
* Получить количество изображений по каждому подходу (
|
|
106174
|
+
* Получить количество изображений по каждому подходу (N элементов, 0–4).
|
|
106601
106175
|
* По умолчанию — по 1 на каждый подход.
|
|
106602
106176
|
*/
|
|
106603
106177
|
function getImageApproachCounts() {
|
|
106604
106178
|
const overrides = loadPromptOverrides();
|
|
106605
|
-
|
|
106606
|
-
|
|
106179
|
+
const defaultCounts = Array(IMAGE_APPROACH_COUNT).fill(1);
|
|
106180
|
+
if (overrides.imageApproachCounts && overrides.imageApproachCounts.length > 0) {
|
|
106181
|
+
const stored = overrides.imageApproachCounts.map(c => Math.max(0, Math.min(4, c)));
|
|
106182
|
+
// backward compat: если было 8, дополняем новыми подходами
|
|
106183
|
+
while (stored.length < IMAGE_APPROACH_COUNT)
|
|
106184
|
+
stored.push(1);
|
|
106185
|
+
return stored.slice(0, IMAGE_APPROACH_COUNT);
|
|
106607
106186
|
}
|
|
106608
106187
|
// backward compat: selectedImageApproaches -> counts (1 for selected, 0 for not)
|
|
106609
106188
|
if (overrides.selectedImageApproaches && overrides.selectedImageApproaches.length >= 1) {
|
|
106610
|
-
const counts = Array(
|
|
106189
|
+
const counts = Array(IMAGE_APPROACH_COUNT).fill(0);
|
|
106611
106190
|
for (const i of overrides.selectedImageApproaches) {
|
|
106612
|
-
if (i >= 0 && i <
|
|
106191
|
+
if (i >= 0 && i < IMAGE_APPROACH_COUNT)
|
|
106613
106192
|
counts[i] = 1;
|
|
106614
106193
|
}
|
|
106615
106194
|
return counts;
|
|
106616
106195
|
}
|
|
106617
|
-
return
|
|
106196
|
+
return defaultCounts;
|
|
106618
106197
|
}
|
|
106619
106198
|
/**
|
|
106620
106199
|
* Загрузить оверрайды из localStorage
|
|
@@ -106778,7 +106357,8 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
106778
106357
|
/* harmony export */ getTextsSystemPrompt: () => (/* binding */ getTextsSystemPrompt),
|
|
106779
106358
|
/* harmony export */ getTitlesSystemPrompt: () => (/* binding */ getTitlesSystemPrompt),
|
|
106780
106359
|
/* harmony export */ getUserPrompt: () => (/* binding */ getUserPrompt),
|
|
106781
|
-
/* harmony export */ getValidationPrompt: () => (/* binding */ getValidationPrompt)
|
|
106360
|
+
/* harmony export */ getValidationPrompt: () => (/* binding */ getValidationPrompt),
|
|
106361
|
+
/* harmony export */ pickRandomBullets: () => (/* binding */ pickRandomBullets)
|
|
106782
106362
|
/* harmony export */ });
|
|
106783
106363
|
/* harmony import */ var _promptOverrides__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./promptOverrides */ "./src/promptOverrides.ts");
|
|
106784
106364
|
/**
|
|
@@ -106786,6 +106366,56 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
106786
106366
|
* Все промпты на русском языке для консистентности
|
|
106787
106367
|
*/
|
|
106788
106368
|
|
|
106369
|
+
/** Пул шаблонов буллетов по категориям (на русском — модель переводит на язык GEO) */
|
|
106370
|
+
const BULLET_BY_CATEGORY = {
|
|
106371
|
+
action: [
|
|
106372
|
+
'Снижает дискомфорт', 'Снижает вздутие', 'Снижает боль', 'Быстрое облегчение',
|
|
106373
|
+
'Комфорт каждый день', 'Лёгкость движений', 'Меньше симптомов', 'Без боли',
|
|
106374
|
+
'Дни без дискомфорта', 'Спокойный желудок', 'Спокойные ночи', 'Видимый результат'
|
|
106375
|
+
],
|
|
106376
|
+
timeframe: [
|
|
106377
|
+
'Эффект за 7–14 дней', 'Эффект за 2 недели', 'Быстрый результат', 'Облегчение с 1 дня',
|
|
106378
|
+
'Эффект за 7 дней', 'В 3 раза быстрее', 'С первого дня', 'В первые дни'
|
|
106379
|
+
],
|
|
106380
|
+
socialProof: [
|
|
106381
|
+
'9 из 10 рекомендуют', '9 из 10 мужчин рекомендуют', '93% подтверждают',
|
|
106382
|
+
'8 из 10 довольны', 'Тысячи довольных'
|
|
106383
|
+
],
|
|
106384
|
+
formula: [
|
|
106385
|
+
'Натуральная формула', 'Натуральные ингредиенты', 'Без химии', 'Отборный состав',
|
|
106386
|
+
'Дерматологически протестировано'
|
|
106387
|
+
],
|
|
106388
|
+
qualityOfLife: [
|
|
106389
|
+
'Активный комфорт', 'Дни без забот', 'Больше энергии', 'Здоровый сон', 'Лёгкая жизнь'
|
|
106390
|
+
]
|
|
106391
|
+
};
|
|
106392
|
+
/** Какие категории для какого подхода (по 1 из каждой категории, порядок случайный) */
|
|
106393
|
+
const APPROACH_BULLET_CATEGORIES = {
|
|
106394
|
+
'Эксперт / Авторитет': ['action', 'timeframe', 'socialProof'],
|
|
106395
|
+
'Lifestyle / Момент приёма': ['timeframe', 'socialProof', 'action'], // срок + цифра + действие
|
|
106396
|
+
'Эмоция / Портрет': ['qualityOfLife', 'action', 'timeframe'], // качество жизни + действие + срок
|
|
106397
|
+
'Визуализация проблемы': ['action', 'timeframe', 'socialProof'],
|
|
106398
|
+
'Power / Сила решения': ['action', 'timeframe', 'socialProof'], // действие + скорость + цифра
|
|
106399
|
+
'Минимализм / Clean Big Text': ['action', 'timeframe', 'formula'],
|
|
106400
|
+
'Врач / Эксперт': ['action', 'timeframe', 'socialProof']
|
|
106401
|
+
};
|
|
106402
|
+
function pickOne(arr) {
|
|
106403
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
106404
|
+
}
|
|
106405
|
+
function shuffle(arr) {
|
|
106406
|
+
return [...arr].sort(() => Math.random() - 0.5);
|
|
106407
|
+
}
|
|
106408
|
+
/** Выбрать 3 случайных буллета по правилам подхода (1 из каждой категории, порядок случайный) */
|
|
106409
|
+
function pickRandomBullets(approachName) {
|
|
106410
|
+
const categories = APPROACH_BULLET_CATEGORIES[approachName];
|
|
106411
|
+
if (!categories || categories.length < 3) {
|
|
106412
|
+
// fallback: все категории
|
|
106413
|
+
const all = Object.values(BULLET_BY_CATEGORY).flat();
|
|
106414
|
+
return shuffle(all).slice(0, 3);
|
|
106415
|
+
}
|
|
106416
|
+
const bullets = categories.map(cat => pickOne(BULLET_BY_CATEGORY[cat] || []));
|
|
106417
|
+
return shuffle(bullets);
|
|
106418
|
+
}
|
|
106789
106419
|
// Debug logging — пишет в терминал Electron (или в console как fallback)
|
|
106790
106420
|
function _debugLog(...args) {
|
|
106791
106421
|
const api = typeof window !== 'undefined' ? window.electronAPI : null;
|
|
@@ -106917,16 +106547,13 @@ function getTitlesSystemPrompt(geo, noOverride, count = 3) {
|
|
|
106917
106547
|
- НЕ упоминай название продукта в заголовке — оно уже на креативе. Заголовок = только боль + решение/триггер
|
|
106918
106548
|
|
|
106919
106549
|
Технические требования:
|
|
106920
|
-
- Каждый заголовок
|
|
106921
|
-
- Каждый заголовок
|
|
106550
|
+
- Каждый заголовок может быть 1–4 строки. Допускается до 12 слов (при многострочности)
|
|
106551
|
+
- Каждый заголовок предпочтительно до 55 символов в строке. При необходимости для разнообразия допускается до 60 символов в строке
|
|
106922
106552
|
- ЗАПРЕЩЕНО использовать двоеточие (:) в заголовках. Используй тире (—) или переформулируй заголовок без двоеточия. Если в заголовке есть двоеточие — это критическая ошибка, перепиши заголовок
|
|
106923
106553
|
- Используй 1-2 эмодзи естественно внутри заголовка, не только в конце. Эмодзи должны заменять или визуально отмечать смысл (эмоция, действие, предупреждение), а не украшать или заполнять пространство. Максимум 1-2 на строку
|
|
106924
106554
|
- Избегай эмодзи ⚠️ и 🚨 — они ассоциируются с опасностью. Для привлечения внимания используй 👉, ✅, 🌿, ⏳
|
|
106925
|
-
- КРИТИЧНО: Каждый заголовок
|
|
106555
|
+
- КРИТИЧНО: Каждый заголовок ОБЯЗАН содержать ключевое слово проблемы ЯВНО. Примеры по категориям: простата → простатит, простата; похудение → лишний вес, похудение; суставы → боль в суставах, колени, скованность; пищеварение → дискомфорт, вздутие, тяжесть; сон → бессонница, усталость. Если ключевое слово отсутствует — перепиши заголовок
|
|
106926
106556
|
- Используй знания лучших практик рекламного копирайтинга
|
|
106927
|
-
- Избегай только жёстких медицинских гарантий («лечит», «вылечивает», «гарантируем»). Мягкие обещания результата разрешены («Nopți fără treziri», «Stomac liniștit», «Efect rapid»). Разрешены: сроки («în 14 zile»), цифры соц. доказательства («9 din 10 bărbați»), мягкие результаты. Запрещены: «100% гарантия», «навсегда», «излечение».
|
|
106928
|
-
- Избегай только: прямых медицинских диагнозов, слов «лечит/вылечивает/гарантируем», обещаний «100%/навсегда». Всё остальное — разрешено.
|
|
106929
|
-
- Если в категории продукта есть специфические медицинские термины или диагнозы — используй их осторожно, не в каждом заголовке. Максимум ${maxMedTerms} заголовок из ${n} может содержать прямое упоминание таких терминов. Предпочитай мягкие формулировки, описывающие симптомы и дискомфорт, а не диагнозы. Если все ${n} заголовков содержат прямые медицинские термины — перепиши все кроме ${maxMedTerms} с мягкими альтернативами, фокусируясь на ощущениях и симптомах
|
|
106930
106557
|
- После создания проверь их правильность и разнообразие
|
|
106931
106558
|
- Верни только заголовки, по одному на строку, без нумерации или буллетов
|
|
106932
106559
|
- Заголовки должны быть на языке ${geo}
|
|
@@ -106954,8 +106581,8 @@ function getTextsSystemPrompt(geo, noOverride, count = 3) {
|
|
|
106954
106581
|
.map((p, i) => `- Текст ${i + 1} → [${p.name}]. ${p.textApproach}`)
|
|
106955
106582
|
.join('\n');
|
|
106956
106583
|
const deadlineNote = n >= 2
|
|
106957
|
-
? `- В Тексте 1 ИЛИ Тексте 2 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (в языке GEO)
|
|
106958
|
-
: `- В Тексте 1 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (в языке GEO)
|
|
106584
|
+
? `- В Тексте 1 ИЛИ Тексте 2 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (в языке GEO).`
|
|
106585
|
+
: `- В Тексте 1 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (в языке GEO).`;
|
|
106959
106586
|
return `Ты — эксперт-копирайтер, специализирующийся на рекламе в Meta Ads (Facebook/Instagram). Твоя задача — создать ровно ${n} идеальных продающих рекламных текста для Meta Ads на языке ${geo}.
|
|
106960
106587
|
|
|
106961
106588
|
ВАЖНО — СИСТЕМА ПАРЫ (заголовок + текст):
|
|
@@ -106985,24 +106612,18 @@ ${deadlineNote}
|
|
|
106985
106612
|
- Избегай академических, синтетических или звучащих как ИИ слов и фраз
|
|
106986
106613
|
- Избегай искусственного или неестественного формулирования, которое реальные рекламодатели не использовали бы
|
|
106987
106614
|
- Если фраза звучит синтетически или академически — перепиши её
|
|
106988
|
-
- Избегай медицинской терминологии, которую обычный человек не использует в разговоре («secreția biliară», «diureza», «peristaltică»). Пиши так, как человек описал бы проблему другу
|
|
106989
|
-
|
|
106990
106615
|
Требования к содержанию:
|
|
106991
106616
|
- КРИТИЧНО: Держи каждый текст кратким — цель примерно 150-280 символов на текст. Тексты в стиле отзыва могут использовать до 300 символов при необходимости для естественного повествования. Будь эффектным, но коротким
|
|
106992
106617
|
- Если текст длиннее 280 символов (или 300 для стиля отзыва) — сожми и перепиши, сохраняя смысл
|
|
106993
106618
|
- КРИТИЧНО: Каждый текст должен содержать хотя бы одно ясное ключевое слово проблемы, релевантное категории продукта (например, для суставов: боль/скованность/подвижность; для пищеварения: дискомфорт/тяжесть; для сна: бессонница/усталость — примеры только для формата, адаптируй под категорию). Если ключевое слово проблемы отсутствует — перепиши текст
|
|
106994
106619
|
- КРИТИЧНО: Каждый текст должен заканчиваться сильным и явным призывом к действию (кликни, попробуй сейчас, закажи сегодня, узнай больше и т.д.)
|
|
106995
106620
|
- Избегай слабых окончаний или информационного тона. Слабые или нейтральные окончания не допускаются
|
|
106996
|
-
- CTA должен быть “нажимным”: допускаются формулировки «не откладывай», «последний шанс», «только сегодня», «забери -50%» (всё строго на языке GEO)
|
|
106621
|
+
- CTA должен быть “нажимным”: допускаются формулировки «не откладывай», «последний шанс», «только сегодня», «забери -50%» (всё строго на языке GEO)
|
|
106997
106622
|
- В каждом тексте строго следуй заданному формату старта согласно его подходу (см. распределение выше)
|
|
106998
106623
|
- Если ингредиенты предоставлены в дополнительной информации, упоминай их ТОЛЬКО если они естественно подходят выбранному рекламному подходу. Например: подходы авторитета/экспертизы выигрывают от деталей ингредиентов, в то время как подходы срочности/эмоционального триггера могут не нуждаться в них. Упоминай только релевантные ингредиенты, не обязательно все из них. Если ингредиенты не подходят подходу или не предоставлены — полностью пропусти их упоминание и не изобретай и не упоминай никакие ингредиенты
|
|
106999
106624
|
- Добавляй только существенные поддерживающие детали, которые усиливают убеждение или ясность, а не заполнители или общие утверждения. Будь кратким и прямым
|
|
107000
106625
|
- Используй 1-4 эмодзи естественно внутри текста, не только в конце. Эмодзи должны заменять или визуально отмечать смысл (эмоция, действие, предупреждение), а не украшать или заполнять пространство. Максимум 1-2 на строку
|
|
107001
106626
|
- Избегай эмодзи ⚠️ и 🚨 — они ассоциируются с опасностью. Для привлечения внимания используй 👉, ✅, 🌿, ⏳
|
|
107002
|
-
- Разрешены формулировки: «reduce frecvența», «elimină disconfortul», «rezultat vizibil în X zile», «9 din 10 observă diferența». Запрещены только: «лечит/vindecă», «гарантируем 100%», «навсегда/definitiv».
|
|
107003
|
-
- Ты можешь усиливать эффект и результаты, но без прямых медицинских обещаний
|
|
107004
|
-
- Избегай только: прямых медицинских диагнозов, слов «лечит/вылечивает/гарантируем», обещаний «100%/навсегда». Всё остальное — разрешено.
|
|
107005
|
-
- Если в категории продукта есть специфические медицинские термины или диагнозы — используй их осторожно, не в каждом тексте. Максимум ${maxMedTerms} текст из ${n} может содержать прямое упоминание таких терминов. Остальные тексты фокусируй на симптомах, дискомфорте и ощущениях пользователя, релевантных категории продукта. Избегай эвфемизмов и искусственных замен — используй естественные формулировки
|
|
107006
106627
|
- Избегай прямых упоминаний интимных или деликатных деталей, связанных с проблемой. Используй мягкие формулировки, описывающие дискомфорт и симптомы, а не физиологические процессы
|
|
107007
106628
|
- Избегай повторения полного названия продукта более 2 раз в одном тексте. Используй местоимения или более короткие ссылки при необходимости
|
|
107008
106629
|
|
|
@@ -107065,8 +106686,8 @@ function getPairsSystemPrompt(geo, noOverride, count = 3, selectedIndices) {
|
|
|
107065
106686
|
.map((p, i) => `Пара ${i + 1} → [${p.name}]:\n Заголовок: ${p.titleApproach}\n Текст: ${p.textApproach}`)
|
|
107066
106687
|
.join('\n');
|
|
107067
106688
|
const deadlineNote = n >= 2
|
|
107068
|
-
? `- В Тексте 1 ИЛИ Тексте 2 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (на языке ${geo})
|
|
107069
|
-
: `- В Тексте 1 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (на языке ${geo})
|
|
106689
|
+
? `- В Тексте 1 ИЛИ Тексте 2 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (на языке ${geo}).`
|
|
106690
|
+
: `- В Тексте 1 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (на языке ${geo}).`;
|
|
107070
106691
|
return `Ты — эксперт-копирайтер, специализирующийся на рекламе в Meta Ads (Facebook/Instagram). Создай ровно ${n} пар «заголовок + текст» для рекламы на языке ${geo}.
|
|
107071
106692
|
|
|
107072
106693
|
ФОРМАТ ОТВЕТА (строго соблюдай, без лишних символов):
|
|
@@ -107092,7 +106713,6 @@ ${distributionLines}
|
|
|
107092
106713
|
- 1–2 эмодзи естественно внутри (не только в конце); избегай ⚠️ и 🚨
|
|
107093
106714
|
- Хотя бы одно ключевое слово проблемы, релевантное продукту
|
|
107094
106715
|
- НЕ упоминай название продукта — заголовок = боль + триггер
|
|
107095
|
-
- Максимум ${maxMedTerms} из ${n} заголовков могут содержать прямые медицинские термины
|
|
107096
106716
|
- Звучит как живая рекламная фраза, а не строка ключевых слов
|
|
107097
106717
|
|
|
107098
106718
|
ТРЕБОВАНИЯ К ТЕКСТУ:
|
|
@@ -107101,7 +106721,6 @@ ${distributionLines}
|
|
|
107101
106721
|
- Сильный явный CTA в конце («кликни», «попробуй сейчас», «закажи», «не жди»)
|
|
107102
106722
|
- Короткие рубленые фразы, императивы, FOMO допустим${hasTestimonial ? '\n- Testimonial: строго от первого лица, начинается с «Имя, возраст:», конкретный результат с таймингом' : ''}
|
|
107103
106723
|
${deadlineNote}
|
|
107104
|
-
- Избегай «гарантированно», «вылечит», «100%», «навсегда»
|
|
107105
106724
|
|
|
107106
106725
|
ОБЩИЕ ТРЕБОВАНИЯ:
|
|
107107
106726
|
- Язык: ${geo} — все тексты строго на этом языке
|
|
@@ -107175,6 +106794,7 @@ function getValidationPrompt(product, geo, keywords, approachName, noOverride) {
|
|
|
107175
106794
|
// Resolve no-bullets conditions in code — don't leave "if approach = X" for the LLM
|
|
107176
106795
|
const noBulletsApproachNames = CREO_APPROACHES.filter(a => a.noBullets).map(a => a.name);
|
|
107177
106796
|
const isNoBullets = noBulletsApproachNames.includes(approachName?.trim() ?? '');
|
|
106797
|
+
const isScreenshotReviews = (approachName?.trim() ?? '') === 'Скрин отзывов';
|
|
107178
106798
|
// ШАГ 0 — hint for BULLETS depends on approach
|
|
107179
106799
|
const step0BulletsHint = isNoBullets
|
|
107180
106800
|
? `BULLETS: [] (для данного подхода буллиты запрещены — ожидается 0)`
|
|
@@ -107195,12 +106815,17 @@ function getValidationPrompt(product, geo, keywords, approachName, noOverride) {
|
|
|
107195
106815
|
- Буллиты должны быть расположены вертикально (столбиком). Если буллиты идут горизонтально в одну строку — ОШИБКА: плохая читаемость на мобиле
|
|
107196
106816
|
- Цена визуально ОТЛИЧНА от буллитов (цена — компактный блок без иконок; буллеты — с иконками/галочками). Если цена выглядит как буллет — ОШИБКА.`;
|
|
107197
106817
|
// ШАГ 3 — TEXT LIMIT: PVP has no badges allowed, normal allows urgency/trust (без проверок по длине/количеству слов)
|
|
107198
|
-
const step3TextLimit =
|
|
106818
|
+
const step3TextLimit = isScreenshotReviews
|
|
107199
106819
|
? `ШАГ 3 — OTHER_TEXT (критично):
|
|
106820
|
+
- Для «Скрин отзывов» блок отзывов (аватарки, имена, возраст, 5 звёзд, текст отзывов) — это НЕ ошибка, это основной контент.
|
|
106821
|
+
- Допускаются также trust‑печати (0–3 шт). URGENCY — не рекомендуется, но не ошибка.
|
|
106822
|
+
- На креативе допустимы: HOOK, блок отзывов, цена, скидка, CTA, опционально trust‑печати.`
|
|
106823
|
+
: isNoBullets
|
|
106824
|
+
? `ШАГ 3 — OTHER_TEXT (критично):
|
|
107200
106825
|
- OTHER_TEXT должен быть ПУСТЫМ (0 элементов). Никаких дополнительных бейджей, подписей, ярлыков, trust‑печатей/urgency — НИЧЕГО.
|
|
107201
106826
|
- Любой дополнительный текст (бейджи/подписи/пояснения/urgency/trust‑печати) — ОШИБКА: слишком много текста для punch‑креатива.
|
|
107202
106827
|
- На креативе допустимы ТОЛЬКО: HOOK, цена, скидка, кнопка CTA.`
|
|
107203
|
-
|
|
106828
|
+
: `ШАГ 3 — OTHER_TEXT (критично):
|
|
107204
106829
|
- По умолчанию любой другой рекламный текст (бейджи/подписи/пояснения) запрещён.
|
|
107205
106830
|
- НО допускаются (не обязательно) дополнительные бейджи ВНЕ упаковки — это НЕ ошибка, если ВСЕ элементы OTHER_TEXT подпадают под разрешённые типы:
|
|
107206
106831
|
A) URGENCY (0–1 шт): бейдж срочности/дефицита — только ясные формулировки («только сегодня», «последние штуки», «акция до конца дня»). ЗАПРЕЩЕНО: «24h» и подобные — непонятно что означает.
|
|
@@ -107211,7 +106836,7 @@ function getValidationPrompt(product, geo, keywords, approachName, noOverride) {
|
|
|
107211
106836
|
* TRUST‑печати — про натуральность/премиальность/безопасность (не про доставку/поддержку)
|
|
107212
106837
|
Примеры стиля (адаптируй к языку GEO, не требуются):
|
|
107213
106838
|
- URGENCY: «Ostatnie sztuki», «Tylko dziś», «Koniec dziś» (НЕ «24h» — непонятно)
|
|
107214
|
-
- TRUST: «Naturalna formuła», «Wysoka jako
|
|
106839
|
+
- TRUST: «Naturalna formuła», «Wysoka jakość» (для PL); «Ingrediente naturale», «Calitate» (для RO). ЗАПРЕЩЕНО: «NATURAL», «QUALITY», «100% NATURAL» — английские слова.
|
|
107215
106840
|
- Если OTHER_TEXT содержит что-то вне этих типов, или URGENCY > 1, или TRUST > 3 (для Минимализма — TRUST > 1) — ОШИБКА.`;
|
|
107216
106841
|
return `Ты — СТРОГИЙ валидатор рекламных креативов (1:1). Не улучшай и не предлагай идеи — только проверка и вердикт.
|
|
107217
106842
|
Продукт: ${product}. GEO/язык: ${geo}.${approachLine}
|
|
@@ -107233,10 +106858,10 @@ URGENCY_BADGE: "..." (бейдж срочности, если есть; «24h»
|
|
|
107233
106858
|
TRUST_BADGES: ["..."] (печати цветные, яркие, в стиле FDA; про натуральность/премиальность/безопасность; размер как у буллитов — крупные, если есть)
|
|
107234
106859
|
|
|
107235
106860
|
ШАГ 1 — HOOK/HEADLINE (критично):
|
|
107236
|
-
- Должен быть
|
|
107237
|
-
- Допускается 1–
|
|
106861
|
+
- Должен быть ВЫШЕ всех элементов и читабелен (хорошо читается на телефоне)
|
|
106862
|
+
- Допускается 1–4 строки (это НЕ ошибка). Ошибка только если текст нечитабелен или есть разрыв/перенос внутри слова.
|
|
107238
106863
|
- Смысл: релевантно проблеме/выгоде продукта. Допускаются и боль/дискомфорт, и обещание/выгода (например «Könnyebb napok»). НЕ считай ошибкой формулировки обещаний результата или клеймы.
|
|
107239
|
-
-
|
|
106864
|
+
- ОБЯЗАТЕЛЬНО: ключевое слово проблемы явно в формулировке. Примеры по категориям: простата → простатит, простата; похудение → лишний вес, похудение; суставы → боль, колени, скованность; пищеварение → дискомфорт, вздутие; сон → бессонница, усталость. Отсутствие ключевого слова — ОШИБКА. Дополнительно для ориентира: ${keywords.join(', ')} (можно синонимы).
|
|
107240
106865
|
- Проверь вторую часть заголовка. Если она состоит только из абстрактных слов («Mai ușor», «Mai bine», «Soluția», «Ajutor») без конкретного бенефита — отметь: РЕКОМЕНДАЦИЯ: заголовок можно усилить конкретикой
|
|
107241
106866
|
- Если заголовок слишком общий/абстрактный («Mai ușor», «Mai bine» без конкретики) — отметь как РЕКОМЕНДАЦИЯ к улучшению (не ошибка, но слабо)
|
|
107242
106867
|
- РЕКОМЕНДАЦИЯ (CTR): верхний текст лучше делать как HOOK — ОЧЕНЬ крупный, жирный, ВСЕ БУКВЫ ПРОПИСНЫЕ, на яркой контрастной плашке. Если это не так — не ошибка, но отметь рекомендацией
|
|
@@ -107251,7 +106876,7 @@ ${step3TextLimit}
|
|
|
107251
106876
|
ШАГ 4 — CTA > PRICE (критично):
|
|
107252
106877
|
- CTA: на языке ${geo}, хорошо заметная кнопка (позиция НЕ важна: можно вправо/влево/по центру). Главное — CTA читабельна и выглядит как кнопка.
|
|
107253
106878
|
- Цена: ОБЯЗАТЕЛЬНО ДВЕ — старая (до скидки) + новая. Если только одна цена — ОШИБКА. Старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Тонкое/незаметное зачёркивание — ОШИБКА.
|
|
107254
|
-
- Скидка ОБЯЗАТЕЛЬНА: «-50%» (
|
|
106879
|
+
- Скидка ОБЯЗАТЕЛЬНА: «-50%» отдельным видимым бейджем (процент должен быть явно читаем, не только в цене). Ярко, заметно. Строго НЕ на упаковке/банке.
|
|
107255
106880
|
- Не считать ошибкой, если скидка/цена конкурируют с CTA по акценту — это допустимо, пока CTA остаётся хорошо заметной и читабельной.
|
|
107256
106881
|
- Если скидка указана и она не равна -50% — ошибка.
|
|
107257
106882
|
|
|
@@ -107264,7 +106889,7 @@ ${step3TextLimit}
|
|
|
107264
106889
|
- КОНТРАСТ ПЛАШЕК: если хотя бы одна текстовая плашка (HOOK, буллеты, CTA или цена) визуально сливается с фоном и текст плохо читается — это ОШИБКА.
|
|
107265
106890
|
- ЦЕНА: ОБЯЗАТЕЛЬНО ДВЕ — старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Одна цена или тонкое зачёркивание — ОШИБКА. Цена не должна выглядеть как буллет.
|
|
107266
106891
|
- TRUST‑печати: если печати мелкие или текст нечитабелен на телефоне — ОШИБКА (размер как минимум как у буллитов).
|
|
107267
|
-
- СКИДКА «-50%»:
|
|
106892
|
+
- СКИДКА «-50%»: ОБЯЗАТЕЛЬНО отдельным видимым бейджем (процент явно читаем). Если скидка не отображается или только в цене без отдельного «-50%» — ОШИБКА. Ярко, заметно, НЕ на упаковке.
|
|
107268
106893
|
- ЦЕНА: ОБЯЗАТЕЛЬНО ДВЕ — старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Одна цена или тонкое зачёркивание — ОШИБКА.
|
|
107269
106894
|
- Бейджи срочности: «24h» и подобные неясные формулировки — ОШИБКА (непонятно что означает). Ясные («только сегодня», «последние штуки») — ок.
|
|
107270
106895
|
|
|
@@ -107331,18 +106956,17 @@ CRITICAL RULES (это ВАЛИДАЦИЯ, не рекомендации; есл
|
|
|
107331
106956
|
|
|
107332
106957
|
ЯЗЫК + ТОН:
|
|
107333
106958
|
- ВСЕ слова строго на языке ${generateGeo} (HOOK/буллеты/CTA/скидка/опциональный бейдж срочности/опциональные trust‑печати). Английские слова запрещены. Исключений нет.
|
|
107334
|
-
- Тон: убедительный, но честный. Лёгкая эмоция и срочность — ок.
|
|
106959
|
+
- Тон: убедительный, но честный. Лёгкая эмоция и срочность — ок.
|
|
107335
106960
|
|
|
107336
106961
|
HOOK / HEADLINE (строгое правило):
|
|
107337
|
-
-
|
|
107338
|
-
-
|
|
107339
|
-
-
|
|
106962
|
+
- 1–4 строки; до 12 слов. Без переноса внутри слова
|
|
106963
|
+
- ОБЯЗАТЕЛЬНО содержит ключевое слово проблемы ЯВНО. Примеры по категориям: простата → простатит, простата; похудение → лишний вес, похудение; суставы → боль в суставах, колени, скованность; пищеварение → дискомфорт, вздутие, тяжесть; сон → бессонница, усталость. Без ключевого слова — ОШИБКА
|
|
106964
|
+
- HOOK ВЫШЕ всех элементов (продукт, буллеты, CTA, цена). Самый крупный текстовый элемент на креативе
|
|
107340
106965
|
- HOOK должен быть ВИЗУАЛЬНО “тяжёлым”: ОЧЕНЬ крупный, ТОЛСТЫЙ/жирный шрифт, ВСЕ БУКВЫ ПРОПИСНЫЕ (CAPS), на яркой контрастной плашке/подложке. Это верхний главный “thumb‑stop” элемент
|
|
107341
|
-
- Можно (не обязательно) включить СРОК прямо в HOOK (например «7 dni», «14 dni») как
|
|
107342
|
-
- Заголовок должен быть РЕЗКИМ и призывным: короткие рубленые фразы, вопросы и предупреждения допустимы. Можно использовать обращение на «ты» (в языке GEO: «Masz…», «Twój…») и вопросительный знак
|
|
107343
|
-
- заголовок может содержать мягкое обещание облегчения, но не медицинскую гарантию. ПРАВИЛЬНО: «Nopți fără treziri? Posibil». НЕ ТАК: «Vindecă prostata definitiv».
|
|
106966
|
+
- Можно (не обязательно) включить СРОК прямо в HOOK (например «7 dni», «14 dni») как триггер
|
|
106967
|
+
- Заголовок должен быть РЕЗКИМ и призывным: короткие рубленые фразы, вопросы и предупреждения допустимы. Можно использовать обращение на «ты» (в языке GEO: «Masz…», «Twój…») и вопросительный знак
|
|
107344
106968
|
- заголовок НЕ должен быть абстрактным слоганом/лозунгом или метафорой без конкретной боли. Допускается «thumb‑stop» стиль (вызов/вопрос/предупреждение), если это конкретно и релевантно категории
|
|
107345
|
-
- если не влезает: сначала измени композицию (расширь блок/переставь элементы), затем перепиши короче. НЕЛЬЗЯ уменьшать шрифт.
|
|
106969
|
+
- если не влезает: сначала измени композицию (расширь блок/переставь элементы), затем перепиши короче. НЕЛЬЗЯ уменьшать шрифт. HOOK всегда остаётся выше всех остальных элементов
|
|
107346
106970
|
- Не повторяй один и тот же заголовок в разных подходах: для текущего подхода придумай новый, уникальный заголовок. Варьируй: угол боли, формулировку, акцент (проблема vs облегчение)
|
|
107347
106971
|
- Вторая часть заголовка = конкретное улучшение, не абстрактное слово. ПРАВИЛЬНО: «Zile fără dureri», «Mișcare ușoară», «Confort activ» (для суставов); «Zile fără disconfort», «Nopți liniștite» (для сна) — адаптируй под категорию. НЕ ТАК: «Mai ușor», «Mai bine», «Soluția»
|
|
107348
106972
|
- Специфические медицинские термины или диагнозы разрешены в заголовке, но не обязательны. Варьируй между прямыми формулировками (если релевантно категории) и мягкими формулировками, фокусирующимися на симптомах и дискомфорте, для разных креативов. Не используй один и тот же термин во всех креативах
|
|
@@ -107351,27 +106975,34 @@ HOOK / HEADLINE (строгое правило):
|
|
|
107351
106975
|
- ТАК (пищеварение): «Puffadás gond? Könnyebb napok»
|
|
107352
106976
|
- ТАК (суставы): «Ízületi fájdalom? Mozogj könnyebben»
|
|
107353
106977
|
- ТАК (сон): «Álmatlan éjszakák? Pihenj végre»
|
|
107354
|
-
|
|
106978
|
+
- ТАК (простата): «Prostată? Alinare rapidă»
|
|
106979
|
+
- ТАК (похудение): «Greutate în plus? Fără diete extreme»
|
|
106980
|
+
❗ Если заголовок > 12 слов, похож на длинное предложение или звучит как лозунг без ключевого слова проблемы — ОШИБКА → пересоздай вариант.
|
|
107355
106981
|
|
|
107356
106982
|
BULLETS (жёсткое правило, ровно 3):
|
|
107357
106983
|
- По умолчанию: ровно 3 буллета
|
|
107358
106984
|
- ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → буллиты ЗАПРЕЩЕНЫ (0 буллитов). Не добавляй буллет‑блок вообще
|
|
107359
106985
|
- каждый 2–3 слова (макс 4); без запятых; буллет НЕ должен выглядеть как предложение
|
|
107360
|
-
- буллеты = свойства ИЛИ ощущаемые преимущества (комфорт, лёгкость, спокойствие). Запрещено: диагнозы, жёсткие медицинские гарантии («лечит», «вылечивает», «избавляет навсегда»). Разрешено: мягкие результаты («Reduce disconfortul», «Mai puține simptome») — примеры формата, адаптируй под категорию, сроки без гарантий («Efect în 7-14 zile», «Rezultat vizibil rapid»), цифры как соц. доказательство («9 din 10 bărbați recomandă»).
|
|
107361
|
-
- Делай буллеты более активными и конкретными: глаголы действия + сроки + цифры. Примеры формата: «Reduce X», «Ulga od 1. dnia», «Efekt w 7 dni», «93% potwierdza», «3× szybciej» (адаптируй под язык GEO и категорию)
|
|
107362
106986
|
- ВИЗУАЛ БУЛЛЕТОВ: крупные, с иконками/галочками (✓), на контрастных подложках. Буллеты ВНЕ упаковки/банки — не на продукте.
|
|
107363
106987
|
- Буллиты должны быть расположены ВЕРТИКАЛЬНО (столбиком), не горизонтально в одну строку. Минимальный размер шрифта буллитов — чтобы читались на экране телефона без зума
|
|
107364
|
-
|
|
107365
|
-
|
|
107366
|
-
|
|
106988
|
+
|
|
106989
|
+
❗ ВЫБОР БУЛЛЕТОВ (КРИТИЧНО): Выбери СЛУЧАЙНЫЕ 3 буллета из пула ниже в ПРОИЗВОЛЬНОМ порядке. Адаптируй формулировки под язык ${generateGeo} и категорию продукта. НЕ используй один и тот же набор для разных подходов — каждый креатив должен иметь УНИКАЛЬНУЮ комбинацию из 3 буллетов.
|
|
106990
|
+
|
|
106991
|
+
ПУЛ ВАРИАНТОВ (выбери 3 случайных, порядок произвольный; адаптируй под GEO и категорию):
|
|
106992
|
+
• Действие/бенефит: Reduce disconfortul, Reduce balonarea, Reduce durerea, Ulga rapidă, Confort zilnic, Mișcare ușoară, Mai puține simptome, Fără dureri, Zile fără disconfort, Stomac liniștit, Nopți liniștite, Rezultat vizibil
|
|
106993
|
+
• Срок/скорость: Efect în 7-14 zile, Efect în 2 săptămâni, Rezultat rapid, Ulga od 1. dnia, Efekt w 7 dni, 3× szybciej, De la prima zi, În primele zile
|
|
106994
|
+
• Соц. доказательство: 9 din 10 recomandă, 9 din 10 bărbați recomandă, 93% potwierdza, 8 din 10 mulțumiți, Mii de clienți mulțumiți
|
|
106995
|
+
• Формула/состав: Formulă naturală, Ingrediente naturale, Fără chimicale, Compoziție selectată, Testat dermatologic
|
|
106996
|
+
• Качество жизни: Confort activ, Zile fără griji, Mai multă energie, Somn odihnitor, Viață mai ușoară
|
|
106997
|
+
|
|
107367
106998
|
❗ Если хоть один буллет > 4 слов или похож на предложение/обещание — ОШИБКА → пересоздай.
|
|
107368
106999
|
|
|
107369
107000
|
TEXT LIMIT:
|
|
107370
107001
|
- кроме: HOOK, 3 буллетов, цены, скидки, кнопки — ЛЮБОЙ другой текст запрещён (ярлыки/подписи/дисклеймеры/пояснения)
|
|
107371
107002
|
- ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → кроме HOOK, цены, скидки, CTA — НИКАКОГО другого текста. Буллиты запрещены. Other_text/urgency/trust‑печати запрещены
|
|
107372
107003
|
- ДОПУСКАЕТСЯ (не обязательно) ОДИН бейдж срочности: только ясные формулировки («только сегодня», «последние штуки», «акция до конца дня»). ЗАПРЕЩЕНО: «24h» — непонятно что означает.
|
|
107373
|
-
- ДОПУСКАЕТСЯ (не обязательно) 1–3 trust‑печати: печати цветные, яркие,
|
|
107374
|
-
- HOOK + буллеты <=
|
|
107004
|
+
- ДОПУСКАЕТСЯ (не обязательно) 1–3 trust‑печати: печати цветные, яркие, в стиле FDA. Текст СТРОГО на языке ${generateGeo} — запрещены английские слова («NATURAL», «QUALITY» и т.д.). Примеры для RO: «naturale», «Ingrediente naturale»; для PL: «naturalna». Размер как минимум как у буллитов. Мелкие печати — ОШИБКА.
|
|
107005
|
+
- HOOK + буллеты <= 24 слова суммарно (HOOK до 12, буллеты до 12); если больше — ОШИБКА → пересоздай
|
|
107375
107006
|
- Если хочешь добавить ощущение срочности — делай это через формулировки в HOOK/BULLETS, через реквизит/иконки, ИЛИ (не обязательно) через один короткий бейдж срочности. Только ясные формулировки («Ostatnie sztuki», «Tylko dziś», «Koniec dziś»). ЗАПРЕЩЕНО «24h» — непонятно что означает.
|
|
107376
107007
|
|
|
107377
107008
|
CTA > PRICE:
|
|
@@ -107382,17 +107013,17 @@ CTA > PRICE:
|
|
|
107382
107013
|
❗ ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → цена и «-50%» могут быть более крупными и заметными, но CTA всё равно должен оставаться очень заметным и выглядеть как кнопка (не теряется на фоне)
|
|
107383
107014
|
|
|
107384
107015
|
ПОКАЗЫВАЙ ТОЛЬКО ЭТИ ТЕКСТОВЫЕ ЭЛЕМЕНТЫ:
|
|
107385
|
-
- HOOK (1
|
|
107016
|
+
- HOOK (1–4 строки; выше всех элементов; самый крупный; CAPS; жирный; на яркой контрастной подложке; ключевое слово проблемы явно)
|
|
107386
107017
|
- 3 буллета (крупные, с иконками/галочками ✓, на контрастных подложках, НЕ на банке/упаковке)
|
|
107387
107018
|
- Цена: ОБЯЗАТЕЛЬНО ДВЕ — старая (2×${generatePrice} ${generateCurrency}) зачёркнута ТОЛСТОЙ контрастной линией + новая ${generatePrice} ${generateCurrency} выразительно. Одна цена = ОШИБКА. Тонкая линия зачёркивания = ОШИБКА. Без слов. Не на упаковке.
|
|
107388
|
-
- Скидка: «-50%»
|
|
107019
|
+
- Скидка: «-50%» ОБЯЗАТЕЛЬНО отдельным видимым бейджем (не только в цене!). Процент скидки должен быть явно читаем. Ярко, заметно, строго НЕ на банке/упаковке, визуально слабее CTA
|
|
107389
107020
|
- Кнопка CTA (1-2 слова)
|
|
107390
107021
|
- Опционально: 1 короткий бейдж срочности (ясные формулировки: «только сегодня», «последние штуки»; ЗАПРЕЩЕНО «24h»). Бейдж в углу кадра, контрастный, но меньше CTA. НЕ обязателен
|
|
107391
|
-
- Опционально: 1–3 trust‑печати (цветные, яркие,
|
|
107022
|
+
- Опционально: 1–3 trust‑печати (цветные, яркие, в стиле FDA; натуральность, премиальность, безопасность). Текст СТРОГО на языке ${generateGeo} — никакого английского. Для RO: «naturale», «Ingrediente naturale», «Calitate»; для PL: «naturalna», «naturalne»; НЕ «NATURAL», «QUALITY». Размер как минимум как у буллитов. НЕ обязательны
|
|
107392
107023
|
- ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → показывай только: HOOK + PRICE + DISCOUNT + CTA. Буллиты/urgency/trust‑печати запрещены
|
|
107393
107024
|
|
|
107394
107025
|
ВИЗУАЛ:
|
|
107395
|
-
- Иерархия: HOOK
|
|
107026
|
+
- Иерархия: HOOK ВЫШЕ всех элементов (продукт, буллеты, CTA, цена). HOOK — самый крупный текстовый элемент
|
|
107396
107027
|
- Буллеты: крупные, с иконками/галочками (✓), на контрастных подложках. Строго ВНЕ упаковки/банки.
|
|
107397
107028
|
- Цена: ОБЯЗАТЕЛЬНО ДВЕ — старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Одна цена или тонкое зачёркивание = ОШИБКА. Визуально ОТЛИЧНА от буллитов.
|
|
107398
107029
|
- Скидка «-50%»: ярко, заметно, на контрастной подложке. Строго ВНЕ упаковки/банки.
|
|
@@ -107402,8 +107033,9 @@ CTA > PRICE:
|
|
|
107402
107033
|
- Без ссылок/доменов и мелкого текста
|
|
107403
107034
|
- LIFESTYLE/CLEAN: продукт в контексте использования. Для пищеварения — кухня, обеденный стол, рядом с едой/чашкой. Для сна/простаты — спальня, тумбочка, стакан воды. Для суставов — стол/тумбочка рядом с эластичным бинтом, ортезом или активным фоном (кроссовки, лестница). Контекст = момент приёма. Просто продукт на белом фоне — НЕ допускается
|
|
107404
107035
|
- Цвета и композиция должны быть «thumb‑stop»: высокий контраст, яркий акцент на продукте (луч света/подсветка/обводка/глоу), избегай бледных пастельных сцен
|
|
107405
|
-
- ЛЮДИ: человек допускается ТОЛЬКО для
|
|
107406
|
-
- Если
|
|
107036
|
+
- ЛЮДИ: человек допускается ТОЛЬКО для подходов «Эмоция / Портрет» и «Врач / Эксперт». Для всех остальных — БЕЗ человека.
|
|
107037
|
+
- Если «Эмоция / Портрет»: прямой взгляд в камеру, эмоция облегчения/надежды. Возраст 50–60 (похудение/фитнес — 35–55). Этнически соответствует GEO/рынку.
|
|
107038
|
+
- Если «Врач / Эксперт»: женщина-врач 40–55 лет. Внешность и этничность СТРОГО соответствуют GEO/рынку (локал — врач выглядит как местный специалист).
|
|
107407
107039
|
|
|
107408
107040
|
ANTI-TEMPLATE DIVERSITY (КРИТИЧНО):
|
|
107409
107041
|
- Ты сейчас создаёшь ОДИН креатив для текущего подхода. В проекте будет серия из ${totalApproaches} креативов, поэтому у каждого подхода должна быть своя узнаваемая композиция.
|
|
@@ -107411,15 +107043,17 @@ ANTI-TEMPLATE DIVERSITY (КРИТИЧНО):
|
|
|
107411
107043
|
- Ориентируйся на строку «🎯 ПОДХОД: ...» и примени соответствующую раскладку ниже (только одну, соответствующую текущему подходу):
|
|
107412
107044
|
* 🎯 ПОДХОД: Эксперт / Авторитет → БЕЗ человека. Профессиональный контекст с реквизитом экспертизы вокруг продукта. Продукт в центре. HOOK сверху справа, BULLETS справа ниже, CTA снизу справа, PRICE/DISCOUNT рядом с CTA (слабее CTA). Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне).
|
|
107413
107045
|
* 🎯 ПОДХОД: Lifestyle / Момент приёма → FLAT-LAY/СВЕРХУ или 3/4 сверху на столе/тумбочке. Продукт в центре, реквизит "момент приёма" вокруг. HOOK сверху по центру, BULLETS сбоку (вертикально, шрифт достаточно крупный для чтения на телефоне без зума), CTA снизу по центру, PRICE/DISCOUNT рядом (слабее CTA). Urgency‑бейдж (если есть) — в углу, только ясные формулировки, НЕ «24h». Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне).
|
|
107414
|
-
* 🎯 ПОДХОД: Эмоция / Портрет →
|
|
107046
|
+
* 🎯 ПОДХОД: Эмоция / Портрет → Один из двух подходов с человеком. ОЧЕНЬ крупный портрет лица (thumb‑stop), взгляд в камеру — лицо занимает верхние 2/3 кадра. Продукт в руке или у подбородка. HOOK сверху по центру. BULLETS, CTA, PRICE/DISCOUNT, trust‑печати — в нижней трети.
|
|
107047
|
+
* 🎯 ПОДХОД: Врач / Эксперт → Женщина-врач (локал по GEO), продукт в руках или рядом. Профессиональный фон. HOOK сверху, BULLETS сбоку/снизу, CTA и PRICE/DISCOUNT внизу. Врач и продукт — главные элементы.
|
|
107048
|
+
* 🎯 ПОДХОД: Скрин отзывов → Имитация скриншота: блок отзывов (аватарки, имена, возраст, 5 звёзд, текст на языке GEO). HOOK ОБЯЗАТЕЛЕН — выше отзывов или поверх, крупно. Продукт виден. CTA, PRICE, DISCOUNT внизу.
|
|
107415
107049
|
* 🎯 ПОДХОД: Визуализация проблемы → Инфографика/схема: слева проблема (иконка/схема зоны тела, релевантной продукту), справа решение + продукт. HOOK сверху по центру, BULLETS справа или снизу (вертикально), CTA снизу справа, PRICE/DISCOUNT возле CTA. Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне). Красный акцент только на проблеме.
|
|
107416
107050
|
* 🎯 ПОДХОД: Power / Сила решения → БЕЗ человека. ДИНАМИЧНЫЙ комикс‑кадр, диагональная композиция. Продукт как “герой” + power‑иконки (щит/молния/бёрст). HOOK сверху слева на яркой плашке, BULLETS слева ниже, CTA снизу справа, PRICE/DISCOUNT возле CTA. Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне).
|
|
107417
107051
|
* 🎯 ПОДХОД: Минимализм / Clean Big Text → Белый/градиентный фон, минимум элементов. ОГРОМНЫЙ HOOK занимает верх/центр, продукт крупно (центр/право), CTA снизу по центру, PRICE/DISCOUNT рядом (слабее CTA). Trust‑печати (если есть) — размер как минимум как у буллитов, крупные и читабельные.
|
|
107418
|
-
* 🎯 ПОДХОД: Problem Visual Punch → БЕЗ человека. Яркий сплошной фон (красный/оранжевый) или агрессивный градиент. В центре КРУПНАЯ иконка/схема зоны тела, релевантной продукту (для суставов — колено; для пищеварения — желудок), с красным свечением. Продукт крупно рядом. HOOK
|
|
107419
|
-
* 🎯 ПОДХОД: Любительский Примитивизм → БЕЗ человека. Чёрный или кислотный сплошной фон. Продукт по центру или справа без декора. HOOK сверху — крупный, грубый bold/рукописный, CAPS,
|
|
107052
|
+
* 🎯 ПОДХОД: Problem Visual Punch → БЕЗ человека. Яркий сплошной фон (красный/оранжевый) или агрессивный градиент. В центре КРУПНАЯ иконка/схема зоны тела, релевантной продукту (для суставов — колено; для пищеварения — желудок), с красным свечением. Продукт крупно рядом. HOOK 1–4 строки, до 12 слов, огромный CAPS, выше всех. БЕЗ буллитов. PRICE + «-50%» крупно и заметно. CTA контрастной кнопкой
|
|
107053
|
+
* 🎯 ПОДХОД: Любительский Примитивизм → БЕЗ человека. Чёрный или кислотный сплошной фон. Продукт по центру или справа без декора. HOOK сверху — 1–4 строки, до 12 слов, крупный, грубый bold/рукописный, CAPS, выше всех. Крупные надписи цены/скидки вокруг продукта. CTA — плоская кнопка без градиентов. БЕЗ буллитов, бейджей, теней, иконок.
|
|
107420
107054
|
|
|
107421
107055
|
AUTO-CHECK (перед финалом):
|
|
107422
|
-
- HOOK:
|
|
107056
|
+
- HOOK: 1–4 строки, до 12 слов, CAPS, жирный на контрастной плашке, выше всех элементов, ключевое слово проблемы явно, язык ${generateGeo} без английских слов
|
|
107423
107057
|
- Буллеты: по умолчанию ровно 3, каждый <= 4 слов, без запятых, не предложения. ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → буллетов 0 (запрещены)
|
|
107424
107058
|
- Нет лишнего текста кроме: HOOK, буллетов, цены, скидки, кнопки (+ опционально 1 бейдж срочности + опционально 1–3 trust‑печати)
|
|
107425
107059
|
- Есть скидка «-50%» (вне упаковки) и она слабее CTA
|
|
@@ -107463,38 +107097,38 @@ const CREO_APPROACHES = [
|
|
|
107463
107097
|
{
|
|
107464
107098
|
name: 'Эксперт / Авторитет',
|
|
107465
107099
|
prompt: `ЭКСПЕРТ / АВТОРИТЕТ (без человека): стиль “рекомендация эксперта”, но БЕЗ человека. Профессиональный контекст — аптечные полки на фоне / медицинский планшет / стетоскоп / рецептурный блокнот как реквизит рядом с продуктом. Продукт в центре как “рекомендованное решение”. Чистый профессиональный фон, высокий контраст. Trust‑печати (1–3 шт): цветные, яркие, очень похожие на печать FDA; подчёркивают натуральность состава, премиальность, безопасность. Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная.`,
|
|
107466
|
-
headlineAngle: `HOOK: угол «предупреждение / интрига / специалисты знают». Формат: вопрос‑предупреждение ИЛИ “специалисты знают/используют” (адаптируй под GEO).
|
|
107467
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107100
|
+
headlineAngle: `HOOK: угол «предупреждение / интрига / специалисты знают». Формат: вопрос‑предупреждение ИЛИ “специалисты знают/используют” (адаптируй под GEO). 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107101
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + соц.доказательство/формула) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор для этого подхода — не повторяй комбинации из других креативов.`
|
|
107468
107102
|
},
|
|
107469
107103
|
{
|
|
107470
107104
|
name: 'Lifestyle / Момент приёма',
|
|
107471
107105
|
prompt: `LIFESTYLE / МОМЕНТ ПРИЁМА: продукт в контексте использования (кухня/стол/спальня/тумбочка). Без человека, но с "историей" (стакан воды, чай, тарелка, будильник/часы/календарь). Срочность: реквизит (часы/таймер‑иконка) и/или опциональный urgency‑бейдж (1–3 слова) в углу кадра — только ясные формулировки («только сегодня», «последние штуки»), ЗАПРЕЩЕНО «24h». Высокий контраст, яркий акцент на продукте. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. ВАЖНО: буллиты располагаются вертикально и всегда читабельны на экране телефона без зума — даже если уходят вбок, минимальный размер шрифта буллитов не уменьшается. Если буллиты не вмещаются сбоку с нужным шрифтом — переставь их вниз.`,
|
|
107472
|
-
headlineAngle: `HOOK: угол «цифры + срочность». Формат: число/процент + ограничение времени/“сейчас/сегодня” (адаптируй под GEO),
|
|
107473
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107106
|
+
headlineAngle: `HOOK: угол «цифры + срочность». Формат: число/процент + ограничение времени/“сейчас/сегодня” (адаптируй под GEO), 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107107
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (срок + цифра + действие/формула) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107474
107108
|
},
|
|
107475
107109
|
{
|
|
107476
107110
|
name: 'Эмоция / Портрет',
|
|
107477
|
-
prompt: `ЭМОЦИЯ / ПОРТРЕТ:
|
|
107478
|
-
headlineAngle: `HOOK: угол «персонально на “ты” + результат/облегчение».
|
|
107479
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107111
|
+
prompt: `ЭМОЦИЯ / ПОРТРЕТ: один из двух подходов с человеком в серии. Используй максимально: ОЧЕНЬ крупный план лица (thumb‑stop), прямой взгляд в камеру, сильная эмоция облегчения/надежды (без широкой стоковой улыбки). Возраст человека подбери под категорию и ЦА продукта: по умолчанию 50–60, но для категорий с более молодой аудиторией (например похудение/фитнес) допускается 35–55. Продукт в руке на уровне лица или рядом, хорошо виден. Свет "тень → свет" на лице допустим. Минимум отвлекающих деталей, высокий контраст. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. ИЕРАРХИЯ: лицо — главный и доминирующий визуальный элемент (верхние 2/3 кадра). Все текстовые блоки (HOOK исключение — сверху) уходят в нижнюю треть. Буллиты, цена, CTA НЕ перекрывают лицо и НЕ конкурируют с ним по визуальному весу — они заметно меньше и ниже.`,
|
|
107112
|
+
headlineAngle: `HOOK: угол «персонально на “ты” + результат/облегчение». 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно. Тон максимально личный и прямой.`,
|
|
107113
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (качество жизни + действие + срок/цифра) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107480
107114
|
},
|
|
107481
107115
|
{
|
|
107482
107116
|
name: 'Визуализация проблемы',
|
|
107483
107117
|
prompt: `ВИЗУАЛИЗАЦИЯ ПРОБЛЕМЫ (без человека): схема/иконка проблемной зоны тела, строго соответствующей категории продукта (для суставов — колено/сустав/позвоночник; для пищеварения — желудок; для простаты — силуэт мужчины) + мягкий красный акцент на этой зоне (не шок‑контент, без графики). Рядом продукт как решение. Можно добавить простую стрелку/переход “проблема → облегчение” как графику (без лишнего текста). Чистый фон, высокая читабельность. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. БЕЗ человека.`,
|
|
107484
|
-
headlineAngle: `HOOK: прямой вопрос о боли/дискомфорте (релевантно категории).
|
|
107485
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107118
|
+
headlineAngle: `HOOK: прямой вопрос о боли/дискомфорте (релевантно категории). 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно, вопросительный знак допустим.`,
|
|
107119
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + соц.доказательство) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107486
107120
|
},
|
|
107487
107121
|
{
|
|
107488
107122
|
name: 'Power / Сила решения',
|
|
107489
107123
|
prompt: `POWER / СИЛА РЕШЕНИЯ (без человека): метафоры силы и победы над проблемой: щит, молния, энергия, взрыв‑бёрст, мощные стикеры/иконки. Продукт как “герой” в центре, высокая энергия, контрастные агрессивные цвета. Можно комикс/anti‑design подачу, но всё должно быть читабельно. Без лишнего текста. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. БЕЗ человека. Только продукт + power‑иконки.`,
|
|
107490
|
-
headlineAngle: `HOOK: угол «было → стало / победа над проблемой». Можно использовать стрелку “→” как часть перехода.
|
|
107491
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107124
|
+
headlineAngle: `HOOK: угол «было → стало / победа над проблемой». Можно использовать стрелку “→” как часть перехода. 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107125
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + скорость + цифра) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107492
107126
|
},
|
|
107493
107127
|
{
|
|
107494
107128
|
name: 'Минимализм / Clean Big Text',
|
|
107495
107129
|
prompt: `МИНИМАЛИЗМ / CLEAN BIG TEXT (без человека): белый или мягкий градиентный фон, премиальное ощущение. ОГРОМНЫЙ HOOK как главный элемент (CAPS, жирный, на контрастной плашке). Продукт крупно, минимум реквизита, минимум шума. Тени/премиальные материалы допустимы, но без лишнего текста. БЕЗ человека. Urgency и trust‑печати в этом подходе НЕ рекомендуются — они нарушают чистоту и ощущение премиальности. Добавляй печати только если это критически необходимо, не более 1, но крупную — размер как у буллитов.`,
|
|
107496
|
-
headlineAngle: `HOOK: одна максимально простая сильная фраза (коротко и ясно),
|
|
107497
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107130
|
+
headlineAngle: `HOOK: одна максимально простая сильная фраза (коротко и ясно), 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107131
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + формула/цифра) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107498
107132
|
},
|
|
107499
107133
|
{
|
|
107500
107134
|
name: 'Problem Visual Punch',
|
|
@@ -107503,14 +107137,14 @@ const CREO_APPROACHES = [
|
|
|
107503
107137
|
- Фон: яркий сплошной цвет (красный/оранжевый) или агрессивный градиент
|
|
107504
107138
|
- Центр: КРУПНАЯ иконка/схема проблемной зоны тела, строго соответствующей категории продукта (для суставов — колено/сустав; для простаты — силуэт мужчины; для пищеварения — желудок), с красным свечением
|
|
107505
107139
|
- Продукт: крупно рядом
|
|
107506
|
-
- HOOK:
|
|
107140
|
+
- HOOK: 1–4 строки, до 12 слов, огромный, CAPS, ключевое слово проблемы явно
|
|
107507
107141
|
- БЕЗ буллитов
|
|
107508
107142
|
- Цена + скидка: крупно, заметно
|
|
107509
107143
|
- CTA: контрастная яркая кнопка
|
|
107510
107144
|
|
|
107511
107145
|
Цель: считывание за 1 секунду. Минимум текста, максимум визуального удара.
|
|
107512
107146
|
БЕЗ человека. Никаких trust‑печатей/urgency бейджей — только HOOK + PRICE + -50% + CTA.`,
|
|
107513
|
-
headlineAngle: `HOOK: «ПРОЩАЙ/КОНЕЦ/СТОП + [проблема]!». Эмоция победы/избавления.
|
|
107147
|
+
headlineAngle: `HOOK: «ПРОЩАЙ/КОНЕЦ/СТОП + [проблема]!». Эмоция победы/избавления. 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107514
107148
|
bulletsFocus: `БУЛЛИТЫ: ЗАПРЕЩЕНЫ. Вся информация — в HOOK и крупных надписях.`
|
|
107515
107149
|
},
|
|
107516
107150
|
{
|
|
@@ -107525,8 +107159,33 @@ const CREO_APPROACHES = [
|
|
|
107525
107159
|
- Крупные текстовые надписи рядом с продуктом: цена, скидка; допустимо одно короткое слово-восклицание («РАБОТАЕТ!», «ПРОВЕРЕНО!») на языке GEO
|
|
107526
107160
|
- Кнопка CTA: плоская, без градиентов, контрастный сплошной цвет
|
|
107527
107161
|
- НИКАКИХ буллитов, trust‑печатей, urgency‑бейджей, иконок — только продукт и текст`,
|
|
107528
|
-
headlineAngle: `HOOK: максимально прямолинейный — короткий, грубый, без украшений.
|
|
107162
|
+
headlineAngle: `HOOK: максимально прямолинейный — короткий, грубый, без украшений. 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно, как объявление на столбе. Никакого маркетингового лоска, никаких абстрактных слоганов.`,
|
|
107529
107163
|
bulletsFocus: `БУЛЛИТЫ: ЗАПРЕЩЕНЫ. Всё — в HOOK и крупных надписях вокруг продукта (цена, скидка, одно слово-восклицание).`
|
|
107164
|
+
},
|
|
107165
|
+
{
|
|
107166
|
+
name: 'Врач / Эксперт',
|
|
107167
|
+
prompt: `ВРАЧ / ЭКСПЕРТ (с человеком): женщина-врач как авторитет, рекомендующий продукт. Один из двух подходов с человеком в серии.
|
|
107168
|
+
- Врач: женщина 40–55 лет, профессиональный вид (медицинский халат, планшет/стетоскоп/рецептурный блокнот). Уверенная, располагающая поза.
|
|
107169
|
+
- ЛОКАЛИЗАЦИЯ (КРИТИЧНО): врач НЕ generic — явно угадывается регион/GEO. Черты лица, тип кожи, причёска — характерные для целевого рынка. RO → румынский тип; PL → польский; HU → венгерский; ES → испанский/латино; IT → итальянский. Врач = местный специалист, не стоковое универсальное лицо.
|
|
107170
|
+
- Продукт: в руках врача или на столе/планшете рядом, хорошо виден.
|
|
107171
|
+
- Фон: профессиональный (кабинет, аптечные полки, медицинский контекст). Высокий контраст, читабельность.
|
|
107172
|
+
- Опционально: 1–3 trust‑печати по низу (цветные, яркие, в стиле FDA). Размер как минимум как у буллитов.
|
|
107173
|
+
- ИЕРАРХИЯ: врач + продукт — главные элементы. HOOK сверху, BULLETS сбоку или снизу, CTA и PRICE/DISCOUNT в нижнем блоке.`,
|
|
107174
|
+
headlineAngle: `HOOK: угол «врач/специалист рекомендует» или «эксперты знают». 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107175
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + соц.доказательство) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107176
|
+
},
|
|
107177
|
+
{
|
|
107178
|
+
name: 'Скрин отзывов',
|
|
107179
|
+
noBullets: true,
|
|
107180
|
+
prompt: `СКРИН ОТЗЫВОВ: имитация реалистичного скриншота с положительными благодарными отзывами и оценками 5 звёзд.
|
|
107181
|
+
- Визуал: реалистичный стиль — как скриншот приложения/сайта отзывов (App Store, Google Play, или страница отзывов). Высокое качество, читабельный текст.
|
|
107182
|
+
- Отзывы: 2–4 отзыва с аватарками, именами и возрастом (формат «Имя, 45 лет»), ОБЯЗАТЕЛЬНО 5 звёзд (★★★★★) в каждом, короткий текст благодарности. Текст отзывов СТРОГО на языке GEO — никакого английского. Релевантен категории продукта.
|
|
107183
|
+
- HOOK ОБЯЗАТЕЛЕН: крупно, выше блока отзывов или поверх, CAPS, на яркой подложке. Ключевое слово проблемы явно.
|
|
107184
|
+
- Продукт: виден в кадре (рядом со скрином или интегрирован в композицию).
|
|
107185
|
+
- Цена, скидка «-50%», CTA — в нижней части. БЕЗ буллитов — отзывы заменяют их.
|
|
107186
|
+
- Стиль: не мультяшный, не комикс — максимально реалистичный скрин.`,
|
|
107187
|
+
headlineAngle: `HOOK: угол «тысячи довольны» / «9 из 10 рекомендуют» / «реальный результат». 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107188
|
+
bulletsFocus: `БУЛЛИТЫ: ЗАПРЕЩЕНЫ. Вся информация — в HOOK, отзывах (с 5 звёздами), цене и CTA.`
|
|
107530
107189
|
}
|
|
107531
107190
|
];
|
|
107532
107191
|
|