docs-combiner 0.1.9 → 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 +796 -1027
- package/dist/renderer.js.map +1 -1
- package/package.json +1 -1
package/dist/renderer.js
CHANGED
|
@@ -99616,37 +99616,34 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
99616
99616
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/InputLabel/InputLabel.js");
|
|
99617
99617
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/Select/Select.js");
|
|
99618
99618
|
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/MenuItem/MenuItem.js");
|
|
99619
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
99620
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
99621
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
99622
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
99623
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
99624
|
-
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/
|
|
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 _mui_icons_material__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
99631
|
-
/* harmony import */ var _mui_icons_material__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(/*! @mui/icons-material */ "./node_modules/@mui/icons-material/esm/
|
|
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 xlsx__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(/*! xlsx */ "./node_modules/xlsx/xlsx.mjs");
|
|
99648
|
-
/* harmony import */ var jszip__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(/*! jszip */ "./node_modules/jszip/dist/jszip.min.js");
|
|
99649
|
-
/* harmony import */ var jszip__WEBPACK_IMPORTED_MODULE_54___default = /*#__PURE__*/__webpack_require__.n(jszip__WEBPACK_IMPORTED_MODULE_54__);
|
|
99619
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/FormControlLabel/FormControlLabel.js");
|
|
99620
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/Checkbox/Checkbox.js");
|
|
99621
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/Tooltip/Tooltip.js");
|
|
99622
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/ToggleButtonGroup/ToggleButtonGroup.js");
|
|
99623
|
+
/* harmony import */ var _mui_material__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! @mui/material */ "./node_modules/@mui/material/esm/ToggleButton/ToggleButton.js");
|
|
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/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__);
|
|
99650
99647
|
|
|
99651
99648
|
|
|
99652
99649
|
|
|
@@ -99722,7 +99719,6 @@ function App() {
|
|
|
99722
99719
|
const [pairTranslations, setPairTranslations] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
|
|
99723
99720
|
const [translatingPairs, setTranslatingPairs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99724
99721
|
const [driveFolderUrl, setDriveFolderUrl] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99725
|
-
const [brand, setBrand] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99726
99722
|
const [link, setLink] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99727
99723
|
const [openaiApiKey, setOpenaiApiKey] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99728
99724
|
const [openRouterBalance, setOpenRouterBalance] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
@@ -99747,6 +99743,10 @@ function App() {
|
|
|
99747
99743
|
return saved || _models__WEBPACK_IMPORTED_MODULE_2__.MODELS.creativeValidation;
|
|
99748
99744
|
});
|
|
99749
99745
|
const [loadingValidationModels, setLoadingValidationModels] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99746
|
+
const [validationDisabled, setValidationDisabled] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(() => {
|
|
99747
|
+
const saved = localStorage.getItem('validationDisabled');
|
|
99748
|
+
return saved === 'true';
|
|
99749
|
+
});
|
|
99750
99750
|
const [imageAspectRatio, setImageAspectRatio] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(() => {
|
|
99751
99751
|
const saved = localStorage.getItem('imageAspectRatio');
|
|
99752
99752
|
// Migrate legacy values (4:5, 9:16) to 2:3
|
|
@@ -99870,28 +99870,66 @@ function App() {
|
|
|
99870
99870
|
const { price, currency } = parsePriceAndCurrency(value);
|
|
99871
99871
|
return price.trim() !== '' && currency.trim() !== '';
|
|
99872
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]);
|
|
99873
99917
|
const [generating, setGenerating] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99874
99918
|
const [generatingImages, setGeneratingImages] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99875
99919
|
const [imagesGenerationLogs, setImagesGenerationLogs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99876
99920
|
const [generatedImagesData, setGeneratedImagesData] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99877
99921
|
const [checkingImages, setCheckingImages] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99878
99922
|
const [uploadingImages, setUploadingImages] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99879
|
-
const [generatingProduct, setGeneratingProduct] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99880
99923
|
const [uploadingProduct, setUploadingProduct] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99881
|
-
const [productModalOpen, setProductModalOpen] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99882
|
-
const [productSourceImages, setProductSourceImages] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99883
|
-
const [productGeneratedImage, setProductGeneratedImage] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99884
|
-
const [productRegeneratePrompt, setProductRegeneratePrompt] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
|
|
99885
|
-
const [productGenerationLogs, setProductGenerationLogs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99886
99924
|
const [folderFilesInfo, setFolderFilesInfo] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99925
|
+
const productFileInputRef = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(null);
|
|
99887
99926
|
const [checkingFolderFiles, setCheckingFolderFiles] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99888
99927
|
const permissionCheckedFoldersRef = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(new Set());
|
|
99889
99928
|
const folderCheckRunningRef = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(false);
|
|
99890
|
-
const [productProcessStartTime, setProductProcessStartTime] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99891
99929
|
const [imagesProcessStartTime, setImagesProcessStartTime] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99892
99930
|
const [contentProcessStartTime, setContentProcessStartTime] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
99893
99931
|
const [contentGenerationLogs, setContentGenerationLogs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
99894
|
-
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 });
|
|
99895
99933
|
const [uiNow, setUiNow] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(() => Date.now());
|
|
99896
99934
|
const [generatingLanding, setGeneratingLanding] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99897
99935
|
const [landingGenerationLogs, setLandingGenerationLogs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
|
|
@@ -99958,7 +99996,7 @@ function App() {
|
|
|
99958
99996
|
}
|
|
99959
99997
|
};
|
|
99960
99998
|
// Create theme based on mode
|
|
99961
|
-
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"])({
|
|
99962
100000
|
palette: {
|
|
99963
100001
|
mode: darkMode ? 'dark' : 'light',
|
|
99964
100002
|
...(darkMode
|
|
@@ -100050,7 +100088,7 @@ function App() {
|
|
|
100050
100088
|
};
|
|
100051
100089
|
loadKey();
|
|
100052
100090
|
// Load prompt overrides from Electron config
|
|
100053
|
-
(0,
|
|
100091
|
+
(0,_promptOverrides__WEBPACK_IMPORTED_MODULE_49__.loadOverridesFromElectron)();
|
|
100054
100092
|
}, []);
|
|
100055
100093
|
// Save form fields to localStorage whenever they change
|
|
100056
100094
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
@@ -100106,7 +100144,6 @@ function App() {
|
|
|
100106
100144
|
setGeneratedImagesData([]);
|
|
100107
100145
|
setTitles('');
|
|
100108
100146
|
setTexts(['']);
|
|
100109
|
-
setBrand('');
|
|
100110
100147
|
setLink('');
|
|
100111
100148
|
return;
|
|
100112
100149
|
}
|
|
@@ -100122,7 +100159,6 @@ function App() {
|
|
|
100122
100159
|
setGeneratedImagesData([]);
|
|
100123
100160
|
setTitles('');
|
|
100124
100161
|
setTexts(['']);
|
|
100125
|
-
setBrand('');
|
|
100126
100162
|
setLink('');
|
|
100127
100163
|
return;
|
|
100128
100164
|
}
|
|
@@ -100133,7 +100169,6 @@ function App() {
|
|
|
100133
100169
|
setGeneratedImagesData([]);
|
|
100134
100170
|
setTitles('');
|
|
100135
100171
|
setTexts(['']);
|
|
100136
|
-
setBrand('');
|
|
100137
100172
|
setLink('');
|
|
100138
100173
|
setLoadingContentFromDrive(true);
|
|
100139
100174
|
setLoadingImagesFromDrive(true);
|
|
@@ -100146,15 +100181,8 @@ function App() {
|
|
|
100146
100181
|
logToTerminal('error', '[Load] Error loading content from Google Drive:', err);
|
|
100147
100182
|
setLoadingContentFromDrive(false);
|
|
100148
100183
|
});
|
|
100149
|
-
//
|
|
100150
|
-
|
|
100151
|
-
Promise.race([loadGeneratedImagesFromDrive(folderId), imagesLoadTimeout]).then((result) => {
|
|
100152
|
-
logToTerminal('log', '[Load] Images loading completed, found:', result.found);
|
|
100153
|
-
setLoadingImagesFromDrive(false);
|
|
100154
|
-
}).catch((err) => {
|
|
100155
|
-
logToTerminal('warn', '[Load] Images loading stopped:', err?.message || err);
|
|
100156
|
-
setLoadingImagesFromDrive(false);
|
|
100157
|
-
});
|
|
100184
|
+
// Креативы не кешируются — не загружаем изображения при открытии
|
|
100185
|
+
setLoadingImagesFromDrive(false);
|
|
100158
100186
|
}, [driveFolderUrl]);
|
|
100159
100187
|
// Sync generatedTitlesData with titles when titles changes (except when user is editing in UI)
|
|
100160
100188
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
@@ -100180,12 +100208,6 @@ function App() {
|
|
|
100180
100208
|
}, [titles]); // Only depend on titles, not generatedTitlesData to avoid loops
|
|
100181
100209
|
// Auto-scroll logs when new logs are added
|
|
100182
100210
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
100183
|
-
if (productGenerationLogs.length > 0 && (generatingProduct || uploadingProduct)) {
|
|
100184
|
-
const logContainer = document.getElementById('product-generation-logs');
|
|
100185
|
-
if (logContainer) {
|
|
100186
|
-
logContainer.scrollTop = logContainer.scrollHeight;
|
|
100187
|
-
}
|
|
100188
|
-
}
|
|
100189
100211
|
if (imagesGenerationLogs.length > 0 && (generatingImages || uploadingImages)) {
|
|
100190
100212
|
const logContainer = document.getElementById('images-generation-logs');
|
|
100191
100213
|
if (logContainer) {
|
|
@@ -100198,30 +100220,7 @@ function App() {
|
|
|
100198
100220
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
100199
100221
|
}
|
|
100200
100222
|
}
|
|
100201
|
-
}, [
|
|
100202
|
-
// Timer for product generation
|
|
100203
|
-
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
100204
|
-
let interval = null;
|
|
100205
|
-
if (generatingProduct || uploadingProduct) {
|
|
100206
|
-
interval = setInterval(() => {
|
|
100207
|
-
setElapsedTime(prev => {
|
|
100208
|
-
if (productProcessStartTime) {
|
|
100209
|
-
const elapsed = Math.floor((Date.now() - productProcessStartTime) / 1000);
|
|
100210
|
-
return { ...prev, product: elapsed };
|
|
100211
|
-
}
|
|
100212
|
-
return prev;
|
|
100213
|
-
});
|
|
100214
|
-
}, 1000);
|
|
100215
|
-
}
|
|
100216
|
-
else {
|
|
100217
|
-
setProductProcessStartTime(null);
|
|
100218
|
-
setElapsedTime(prev => ({ ...prev, product: 0 }));
|
|
100219
|
-
}
|
|
100220
|
-
return () => {
|
|
100221
|
-
if (interval)
|
|
100222
|
-
clearInterval(interval);
|
|
100223
|
-
};
|
|
100224
|
-
}, [generatingProduct, uploadingProduct, productProcessStartTime]);
|
|
100223
|
+
}, [imagesGenerationLogs, generatingImages, uploadingImages, contentGenerationLogs, generating]);
|
|
100225
100224
|
// Timer for images generation
|
|
100226
100225
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
100227
100226
|
let interval = null;
|
|
@@ -100442,19 +100441,12 @@ function App() {
|
|
|
100442
100441
|
return;
|
|
100443
100442
|
if (response.ok) {
|
|
100444
100443
|
const data = await response.json();
|
|
100445
|
-
const bankaFiles = data.files.filter((f) => {
|
|
100446
|
-
const name = f.name?.toLowerCase() || '';
|
|
100447
|
-
return name.startsWith('banka') && (name.endsWith('.png') || name.endsWith('.jpg') || name.endsWith('.jpeg'));
|
|
100448
|
-
});
|
|
100449
100444
|
const hasProduct = data.files.some((f) => {
|
|
100450
100445
|
const name = f.name?.toLowerCase() || '';
|
|
100451
100446
|
return name === 'product.png' || name === 'product.jpg';
|
|
100452
100447
|
});
|
|
100453
100448
|
if (!cancelled) {
|
|
100454
|
-
setFolderFilesInfo({
|
|
100455
|
-
bankaCount: bankaFiles.length,
|
|
100456
|
-
hasProduct
|
|
100457
|
-
});
|
|
100449
|
+
setFolderFilesInfo({ hasProduct });
|
|
100458
100450
|
// If product not found, poll every 10 seconds until found
|
|
100459
100451
|
if (!hasProduct) {
|
|
100460
100452
|
pollTimeoutId = setTimeout(() => {
|
|
@@ -100679,6 +100671,10 @@ function App() {
|
|
|
100679
100671
|
setSelectedValidationModel(modelId);
|
|
100680
100672
|
localStorage.setItem('selectedValidationModel', modelId);
|
|
100681
100673
|
};
|
|
100674
|
+
const handleValidationDisabledChange = (checked) => {
|
|
100675
|
+
setValidationDisabled(checked);
|
|
100676
|
+
localStorage.setItem('validationDisabled', String(checked));
|
|
100677
|
+
};
|
|
100682
100678
|
// Fetch OpenRouter account balance and key limit
|
|
100683
100679
|
const fetchOpenRouterBalance = async (apiKey) => {
|
|
100684
100680
|
const keyToUse = apiKey ?? openaiApiKey;
|
|
@@ -101537,7 +101533,7 @@ function App() {
|
|
|
101537
101533
|
setTexts(['']);
|
|
101538
101534
|
setPairTranslations({});
|
|
101539
101535
|
// Read pairs count from settings (3–10, default 3)
|
|
101540
|
-
const pairsCountInit = (0,
|
|
101536
|
+
const pairsCountInit = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_49__.getPairsCount)();
|
|
101541
101537
|
// Initialize placeholders
|
|
101542
101538
|
const initialTitles = Array.from({ length: pairsCountInit }, (_, index) => ({
|
|
101543
101539
|
index: index + 1,
|
|
@@ -101566,7 +101562,7 @@ function App() {
|
|
|
101566
101562
|
}
|
|
101567
101563
|
// Generate all pairs (title + text) in a single request
|
|
101568
101564
|
addLog(formatLogMessage('log', '📋 Generating title+text pairs in a single request...'));
|
|
101569
|
-
const selectedIndices = (0,
|
|
101565
|
+
const selectedIndices = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_49__.getSelectedPairApproaches)();
|
|
101570
101566
|
setLastUsedApproachIndices(selectedIndices);
|
|
101571
101567
|
const pairsCount = selectedIndices.length;
|
|
101572
101568
|
addLog(formatLogMessage('log', `⚙️ Generating ${pairsCount} pairs (approaches: ${selectedIndices.join(', ')})`));
|
|
@@ -101661,7 +101657,7 @@ function App() {
|
|
|
101661
101657
|
setGenerating(false);
|
|
101662
101658
|
}
|
|
101663
101659
|
};
|
|
101664
|
-
const generateImageWithDALLE = async (prompt, referenceImageUrl) => {
|
|
101660
|
+
const generateImageWithDALLE = async (prompt, referenceImageUrl, aspectRatioOverride) => {
|
|
101665
101661
|
if (!openaiApiKey) {
|
|
101666
101662
|
throw new Error('OpenRouter API key is not set');
|
|
101667
101663
|
}
|
|
@@ -101719,6 +101715,8 @@ function App() {
|
|
|
101719
101715
|
// For image generation models, content must be an array with text and image_url objects
|
|
101720
101716
|
const finalContent = referenceImageApiUrls.length > 0 ? content : prompt;
|
|
101721
101717
|
// Build image_config based on the model family.
|
|
101718
|
+
// When aspectRatioOverride is provided (e.g. in 'both' mode), use it. Otherwise use imageAspectRatio.
|
|
101719
|
+
const effectiveRatio = aspectRatioOverride ?? (imageAspectRatio === 'both' ? '1:1' : imageAspectRatio);
|
|
101722
101720
|
// openai/gpt-5-image* models use OpenAI's gpt-image-1 underneath, which only supports
|
|
101723
101721
|
// three discrete sizes: 1024x1024, 1024x1536, 1536x1024.
|
|
101724
101722
|
// The generic OpenRouter `aspect_ratio` param is ignored for these models.
|
|
@@ -101732,7 +101730,7 @@ function App() {
|
|
|
101732
101730
|
'1:1': '1024x1024',
|
|
101733
101731
|
'2:3': '1024x1536',
|
|
101734
101732
|
};
|
|
101735
|
-
const gptSize = gptSizeMap[
|
|
101733
|
+
const gptSize = gptSizeMap[effectiveRatio] || '1024x1024';
|
|
101736
101734
|
imageConfig = { size: gptSize };
|
|
101737
101735
|
imageAspectRatioParam = gptSize;
|
|
101738
101736
|
}
|
|
@@ -101742,7 +101740,7 @@ function App() {
|
|
|
101742
101740
|
'1:1': '1:1',
|
|
101743
101741
|
'2:3': '2:3',
|
|
101744
101742
|
};
|
|
101745
|
-
imageAspectRatioParam = aspectRatioMap[
|
|
101743
|
+
imageAspectRatioParam = aspectRatioMap[effectiveRatio] || '1:1';
|
|
101746
101744
|
imageConfig = { aspect_ratio: imageAspectRatioParam };
|
|
101747
101745
|
}
|
|
101748
101746
|
const requestBody = {
|
|
@@ -101921,7 +101919,7 @@ function App() {
|
|
|
101921
101919
|
if (imageUrl) {
|
|
101922
101920
|
const img = new Image();
|
|
101923
101921
|
img.onload = () => {
|
|
101924
|
-
logToTerminal('log', `📐 Actual image dimensions: ${img.naturalWidth}×${img.naturalHeight} (requested: ${
|
|
101922
|
+
logToTerminal('log', `📐 Actual image dimensions: ${img.naturalWidth}×${img.naturalHeight} (requested: ${effectiveRatio}, sent image_config: ${JSON.stringify(imageConfig)})`);
|
|
101925
101923
|
};
|
|
101926
101924
|
img.src = imageUrl;
|
|
101927
101925
|
}
|
|
@@ -102197,362 +102195,52 @@ function App() {
|
|
|
102197
102195
|
errors: finalErrors
|
|
102198
102196
|
};
|
|
102199
102197
|
};
|
|
102200
|
-
const
|
|
102201
|
-
if (!
|
|
102202
|
-
|
|
102203
|
-
|
|
102204
|
-
if (addLog)
|
|
102205
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102206
|
-
throw new Error(errorMsg);
|
|
102207
|
-
}
|
|
102208
|
-
const logMsg = (level, ...args) => {
|
|
102209
|
-
logToTerminal(level, ...args);
|
|
102210
|
-
if (addLog)
|
|
102211
|
-
addLog(formatLogMessage(level, ...args));
|
|
102212
|
-
};
|
|
102213
|
-
const isRegeneration = !!currentProductImageUrl;
|
|
102214
|
-
logMsg('log', `=== Starting product image ${isRegeneration ? 'regeneration' : 'generation'} from source images ===`);
|
|
102215
|
-
logMsg('log', '📸 Source images count:', bankaImageUrls.length);
|
|
102216
|
-
if (isRegeneration) {
|
|
102217
|
-
logMsg('log', '🔄 Regenerating with current product image as reference');
|
|
102218
|
-
}
|
|
102219
|
-
// Ensure we have a valid access token before proceeding
|
|
102220
|
-
const validToken = await getValidAccessToken();
|
|
102221
|
-
if (!validToken) {
|
|
102222
|
-
const errorMsg = 'Not logged in to Google Drive or token expired';
|
|
102223
|
-
logMsg('error', errorMsg);
|
|
102224
|
-
throw new Error(errorMsg);
|
|
102225
|
-
}
|
|
102226
|
-
logMsg('log', '✅ Valid access token confirmed');
|
|
102227
|
-
// Base prompt for product generation
|
|
102228
|
-
let basePrompt = `Прикрепил фото упаковки продукта. Сгенерируй пожалуйста её упрощенное изображение для креативов для рекламы в Facebook.
|
|
102229
|
-
ВАЖНО: Сохрани точно такую же форму упаковки, как на исходном фото (если это упаковка таблеток - оставь упаковку таблеток, если банка - оставь банку, если тюбик - оставь тюбик).
|
|
102230
|
-
Убери мелкий текст. Нужна только сама упаковка продукта, не коробка. Оставь логотип, название, примерно тот же тон.`;
|
|
102231
|
-
// If regenerating, update prompt to mention current image
|
|
102232
|
-
if (isRegeneration) {
|
|
102233
|
-
basePrompt = `${basePrompt}
|
|
102234
|
-
|
|
102235
|
-
🚨 ПЕРЕГЕНЕРАЦИЯ: Прикреплены два типа референсов:
|
|
102236
|
-
1. Исходные фото упаковки продукта (используй их как основу для формы и деталей)
|
|
102237
|
-
2. Текущий вариант сгенерированного продукта (НЕПРАВИЛЬНЫЙ, требует ОБЯЗАТЕЛЬНЫХ изменений согласно требованиям ниже)
|
|
102238
|
-
|
|
102239
|
-
═══════════════════════════════════════════════════════════════
|
|
102240
|
-
🔥 ОБЯЗАТЕЛЬНЫЕ ТРЕБОВАНИЯ ПОЛЬЗОВАТЕЛЯ (ПРИМЕНИ ВСЕ БЕЗ ИСКЛЮЧЕНИЯ):
|
|
102241
|
-
═══════════════════════════════════════════════════════════════`;
|
|
102242
|
-
}
|
|
102243
|
-
const fullPrompt = additionalPrompt.trim()
|
|
102244
|
-
? `${basePrompt}${isRegeneration ? '' : '\nДополнительные требования: '}${additionalPrompt.split('\n').filter(line => line.trim()).map(line => `• ${line.trim()}`).join('\n')}${isRegeneration ? '\n\n⚠️ ВАЖНО: ОБЯЗАТЕЛЬНО примени ВСЕ требования выше в новом варианте изображения. НЕ копируй предыдущий вариант - создай НОВОЕ изображение с учетом всех требований.' : ''}`
|
|
102245
|
-
: basePrompt;
|
|
102246
|
-
logMsg('log', '📝 Prompt:', fullPrompt);
|
|
102247
|
-
// Convert Drive URLs to direct image URLs for API
|
|
102248
|
-
// For Google Drive, convert view URLs to direct download URLs
|
|
102249
|
-
// Note: Token is refreshed above to ensure files are accessible
|
|
102250
|
-
const convertImageUrl = (url) => {
|
|
102251
|
-
const fileIdMatch = url.match(/\/file\/d\/([a-zA-Z0-9_-]+)/);
|
|
102252
|
-
if (fileIdMatch) {
|
|
102253
|
-
const fileId = fileIdMatch[1];
|
|
102254
|
-
return `https://drive.google.com/uc?export=view&id=${fileId}`;
|
|
102255
|
-
}
|
|
102256
|
-
return url;
|
|
102257
|
-
};
|
|
102258
|
-
const sourceImageUrls = bankaImageUrls.map(convertImageUrl);
|
|
102259
|
-
// Build image URLs array: current product image first (if regenerating), then source images
|
|
102260
|
-
const imageUrls = [];
|
|
102261
|
-
if (currentProductImageUrl) {
|
|
102262
|
-
imageUrls.push(convertImageUrl(currentProductImageUrl));
|
|
102263
|
-
}
|
|
102264
|
-
imageUrls.push(...sourceImageUrls);
|
|
102265
|
-
logMsg('log', '🖼️ Total reference images:', imageUrls.length, isRegeneration ? '(current product + source images)' : '(source images only)');
|
|
102266
|
-
const requestBody = {
|
|
102267
|
-
model: _models__WEBPACK_IMPORTED_MODULE_2__.MODELS.imageGeneration,
|
|
102268
|
-
messages: [
|
|
102269
|
-
{
|
|
102270
|
-
role: 'user',
|
|
102271
|
-
content: [
|
|
102272
|
-
{ type: 'text', text: fullPrompt },
|
|
102273
|
-
...imageUrls.map(url => ({ type: 'image_url', image_url: { url } }))
|
|
102274
|
-
]
|
|
102275
|
-
}
|
|
102276
|
-
],
|
|
102277
|
-
modalities: ['image', 'text'],
|
|
102278
|
-
};
|
|
102279
|
-
logMsg('log', '📦 Request body prepared with', imageUrls.length, 'source images');
|
|
102280
|
-
logMsg('log', '🚀 Sending request to OpenRouter API...');
|
|
102281
|
-
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
102282
|
-
method: 'POST',
|
|
102283
|
-
headers: {
|
|
102284
|
-
'Content-Type': 'application/json',
|
|
102285
|
-
'Authorization': `Bearer ${openaiApiKey}`,
|
|
102286
|
-
'HTTP-Referer': window.location.origin || 'https://docs-combiner.app',
|
|
102287
|
-
'X-Title': 'Docs Combiner'
|
|
102288
|
-
},
|
|
102289
|
-
body: JSON.stringify(requestBody)
|
|
102290
|
-
});
|
|
102291
|
-
const responseText = await response.text();
|
|
102292
|
-
logMsg('log', '📊 Response status:', response.status, response.statusText);
|
|
102293
|
-
if (!response.ok) {
|
|
102294
|
-
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
102295
|
-
try {
|
|
102296
|
-
const error = JSON.parse(responseText);
|
|
102297
|
-
errorMessage = error.error?.message || error.message || errorMessage;
|
|
102298
|
-
logMsg('error', '🔍 Parsed error:', errorMessage);
|
|
102299
|
-
}
|
|
102300
|
-
catch (e) {
|
|
102301
|
-
logMsg('error', '❌ Failed to parse error response as JSON');
|
|
102302
|
-
}
|
|
102303
|
-
throw new Error(errorMessage);
|
|
102304
|
-
}
|
|
102305
|
-
logMsg('log', '📥 Parsing response...');
|
|
102306
|
-
let data;
|
|
102307
|
-
try {
|
|
102308
|
-
data = JSON.parse(responseText);
|
|
102309
|
-
logMsg('log', '✅ JSON parsed successfully');
|
|
102310
|
-
}
|
|
102311
|
-
catch (e) {
|
|
102312
|
-
logMsg('error', '❌ Failed to parse response as JSON');
|
|
102313
|
-
throw new Error(`Invalid JSON response: ${responseText.substring(0, 200)}`);
|
|
102314
|
-
}
|
|
102315
|
-
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
|
|
102316
|
-
logMsg('error', '❌ Invalid response structure');
|
|
102317
|
-
throw new Error(`Invalid response structure. Expected choices[0].message`);
|
|
102318
|
-
}
|
|
102319
|
-
const message = data.choices[0].message;
|
|
102320
|
-
if (!message.images || !message.images[0] || !message.images[0].image_url) {
|
|
102321
|
-
logMsg('error', '❌ No images in response');
|
|
102322
|
-
throw new Error(`No images found in response`);
|
|
102198
|
+
const handleUploadProductFile = () => {
|
|
102199
|
+
if (!driveFolderUrl.trim()) {
|
|
102200
|
+
alert('Укажите папку Google Drive');
|
|
102201
|
+
return;
|
|
102323
102202
|
}
|
|
102324
|
-
|
|
102325
|
-
logMsg('log', '✅ Product image generated successfully');
|
|
102326
|
-
logMsg('log', '=== End product image generation ===');
|
|
102327
|
-
// Update balance after successful API call
|
|
102328
|
-
fetchOpenRouterBalance();
|
|
102329
|
-
return imageUrl;
|
|
102203
|
+
productFileInputRef.current?.click();
|
|
102330
102204
|
};
|
|
102331
|
-
const
|
|
102205
|
+
const handleProductFileSelected = async (e) => {
|
|
102206
|
+
const file = e.target.files?.[0];
|
|
102207
|
+
if (!file)
|
|
102208
|
+
return;
|
|
102209
|
+
e.target.value = '';
|
|
102332
102210
|
const validToken = await getValidAccessToken();
|
|
102333
102211
|
if (!validToken) {
|
|
102334
|
-
alert('
|
|
102335
|
-
return;
|
|
102336
|
-
}
|
|
102337
|
-
if (!driveFolderUrl.trim()) {
|
|
102338
|
-
alert('Please fill in Google Drive Folder URL');
|
|
102212
|
+
alert('Войдите в Google Drive');
|
|
102339
102213
|
return;
|
|
102340
102214
|
}
|
|
102341
|
-
|
|
102342
|
-
|
|
102343
|
-
|
|
102344
|
-
throw new Error('Invalid Google Drive Folder URL');
|
|
102345
|
-
}
|
|
102346
|
-
// Check if product.png exists in the folder
|
|
102347
|
-
logToTerminal('log', '🔍 Checking for product.png in folder...');
|
|
102348
|
-
const existingProductImage = await fetchProductImage(folderId);
|
|
102349
|
-
if (existingProductImage) {
|
|
102350
|
-
// Product.png exists - open regeneration modal without generating
|
|
102351
|
-
logToTerminal('log', '✅ product.png found in folder, opening regeneration modal');
|
|
102352
|
-
// Load product image as blob and convert to data URL for display
|
|
102353
|
-
if (!productGeneratedImage) {
|
|
102354
|
-
try {
|
|
102355
|
-
logToTerminal('log', '📥 Downloading product.png for display...');
|
|
102356
|
-
let token = await getValidAccessToken();
|
|
102357
|
-
if (!token) {
|
|
102358
|
-
throw new Error('Not logged in to Google Drive');
|
|
102359
|
-
}
|
|
102360
|
-
let imageResponse = await fetch(`https://www.googleapis.com/drive/v3/files/${existingProductImage.id}?alt=media`, {
|
|
102361
|
-
headers: {
|
|
102362
|
-
'Authorization': `Bearer ${token}`
|
|
102363
|
-
}
|
|
102364
|
-
});
|
|
102365
|
-
// Handle 401 - try to refresh token and retry
|
|
102366
|
-
if (imageResponse.status === 401 && refreshToken) {
|
|
102367
|
-
logToTerminal('log', '🔄 Got 401, refreshing token...');
|
|
102368
|
-
const newToken = await refreshAccessToken(refreshToken);
|
|
102369
|
-
if (newToken) {
|
|
102370
|
-
token = newToken;
|
|
102371
|
-
logToTerminal('log', '✅ Token refreshed, retrying download...');
|
|
102372
|
-
imageResponse = await fetch(`https://www.googleapis.com/drive/v3/files/${existingProductImage.id}?alt=media`, {
|
|
102373
|
-
headers: {
|
|
102374
|
-
'Authorization': `Bearer ${newToken}`
|
|
102375
|
-
}
|
|
102376
|
-
});
|
|
102377
|
-
}
|
|
102378
|
-
}
|
|
102379
|
-
if (!imageResponse.ok) {
|
|
102380
|
-
throw new Error(`Failed to download product image: ${imageResponse.status} ${imageResponse.statusText}`);
|
|
102381
|
-
}
|
|
102382
|
-
const blob = await imageResponse.blob();
|
|
102383
|
-
// Convert blob to data URL using Promise
|
|
102384
|
-
const dataUrl = await new Promise((resolve, reject) => {
|
|
102385
|
-
const reader = new FileReader();
|
|
102386
|
-
reader.onloadend = () => resolve(reader.result);
|
|
102387
|
-
reader.onerror = reject;
|
|
102388
|
-
reader.readAsDataURL(blob);
|
|
102389
|
-
});
|
|
102390
|
-
setProductGeneratedImage(dataUrl);
|
|
102391
|
-
logToTerminal('log', '✅ Product image loaded and displayed');
|
|
102392
|
-
}
|
|
102393
|
-
catch (err) {
|
|
102394
|
-
logToTerminal('error', '❌ Failed to load product image:', err.message);
|
|
102395
|
-
// Fallback to direct URL if blob download fails
|
|
102396
|
-
const productImageUrl = `https://drive.google.com/uc?export=view&id=${existingProductImage.id}`;
|
|
102397
|
-
setProductGeneratedImage(productImageUrl);
|
|
102398
|
-
}
|
|
102399
|
-
}
|
|
102400
|
-
// If we have source images, just open modal
|
|
102401
|
-
if (productSourceImages.length > 0) {
|
|
102402
|
-
logToTerminal('log', '✅ Source images available, opening modal');
|
|
102403
|
-
setProductModalOpen(true);
|
|
102404
|
-
return;
|
|
102405
|
-
}
|
|
102406
|
-
// If no source images, load them without generating
|
|
102407
|
-
logToTerminal('log', '📥 Loading source images without generating');
|
|
102408
|
-
setProductModalOpen(true);
|
|
102409
|
-
setProductGenerationLogs([]);
|
|
102410
|
-
const addLog = (msg) => {
|
|
102411
|
-
setProductGenerationLogs(prev => [...prev, msg]);
|
|
102412
|
-
};
|
|
102413
|
-
try {
|
|
102414
|
-
addLog(formatLogMessage('log', '🔍 Загрузка исходных изображений...'));
|
|
102415
|
-
logToTerminal('log', '🔍 Loading source images...');
|
|
102416
|
-
const bankaImages = await fetchBankaImages(folderId);
|
|
102417
|
-
if (bankaImages.length === 0) {
|
|
102418
|
-
addLog(formatLogMessage('warn', '⚠️ Исходные изображения не найдены, но есть product.png'));
|
|
102419
|
-
logToTerminal('warn', '⚠️ No source images found, but product.png exists');
|
|
102420
|
-
}
|
|
102421
|
-
else {
|
|
102422
|
-
addLog(formatLogMessage('log', `✅ Загружено ${bankaImages.length} исходных изображений`));
|
|
102423
|
-
logToTerminal('log', `✅ Loaded ${bankaImages.length} source image(s)`);
|
|
102424
|
-
setProductSourceImages(bankaImages.map(img => img.url));
|
|
102425
|
-
}
|
|
102426
|
-
}
|
|
102427
|
-
catch (err) {
|
|
102428
|
-
const errorMsg = 'Error loading source images: ' + err.message;
|
|
102429
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102430
|
-
logToTerminal('error', '❌ Error loading source images:', err);
|
|
102431
|
-
// Don't alert, just log - user can still regenerate with existing product image
|
|
102432
|
-
}
|
|
102433
|
-
return; // IMPORTANT: Return here to prevent generation
|
|
102434
|
-
}
|
|
102435
|
-
// Product.png doesn't exist - proceed with full generation
|
|
102436
|
-
logToTerminal('log', '🎨 product.png not found, starting full generation');
|
|
102437
|
-
}
|
|
102438
|
-
catch (err) {
|
|
102439
|
-
// If check fails, proceed with generation (better to try than to block)
|
|
102440
|
-
logToTerminal('warn', '⚠️ Failed to check for product.png, proceeding with generation:', err.message);
|
|
102441
|
-
}
|
|
102442
|
-
// Full generation flow - only when product image doesn't exist
|
|
102443
|
-
setGeneratingProduct(true);
|
|
102444
|
-
setProductGenerationLogs([]);
|
|
102445
|
-
setProductProcessStartTime(Date.now());
|
|
102446
|
-
setProductModalOpen(true);
|
|
102447
|
-
const addLog = (msg) => {
|
|
102448
|
-
setProductGenerationLogs(prev => [...prev, msg]);
|
|
102449
|
-
};
|
|
102450
|
-
try {
|
|
102451
|
-
const folderId = extractFolderId(driveFolderUrl);
|
|
102452
|
-
if (!folderId) {
|
|
102453
|
-
throw new Error('Invalid Google Drive Folder URL');
|
|
102454
|
-
}
|
|
102455
|
-
addLog(formatLogMessage('log', '🔍 Searching for source images (banka*.png/jpg)...'));
|
|
102456
|
-
logToTerminal('log', '🔍 Searching for banka images...');
|
|
102457
|
-
const bankaImages = await fetchBankaImages(folderId);
|
|
102458
|
-
if (bankaImages.length === 0) {
|
|
102459
|
-
addLog(formatLogMessage('error', 'No banka*.png/jpg files found in the folder'));
|
|
102460
|
-
alert('No banka*.png/jpg files found in the folder');
|
|
102461
|
-
return;
|
|
102462
|
-
}
|
|
102463
|
-
addLog(formatLogMessage('log', `✅ Found ${bankaImages.length} source image(s)`));
|
|
102464
|
-
logToTerminal('log', `✅ Found ${bankaImages.length} banka image(s)`);
|
|
102465
|
-
setProductSourceImages(bankaImages.map(img => img.url));
|
|
102466
|
-
// Generate product image
|
|
102467
|
-
addLog(formatLogMessage('log', '🎨 Generating product image...'));
|
|
102468
|
-
logToTerminal('log', '🎨 Generating product image...');
|
|
102469
|
-
const generatedImageUrl = await generateProductFromBanka(bankaImages.map(img => img.url), '', addLog);
|
|
102470
|
-
setProductGeneratedImage(generatedImageUrl);
|
|
102471
|
-
}
|
|
102472
|
-
catch (err) {
|
|
102473
|
-
const errorMsg = 'Error generating product: ' + err.message;
|
|
102474
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102475
|
-
logToTerminal('error', '❌ Error generating product:', err);
|
|
102476
|
-
alert(errorMsg);
|
|
102477
|
-
}
|
|
102478
|
-
finally {
|
|
102479
|
-
setGeneratingProduct(false);
|
|
102480
|
-
}
|
|
102481
|
-
};
|
|
102482
|
-
const handleRegenerateProduct = async () => {
|
|
102483
|
-
if (!productSourceImages.length || !productGeneratedImage)
|
|
102215
|
+
const folderId = extractFolderId(driveFolderUrl);
|
|
102216
|
+
if (!folderId) {
|
|
102217
|
+
alert('Некорректная ссылка на папку Google Drive');
|
|
102484
102218
|
return;
|
|
102485
|
-
setGeneratingProduct(true);
|
|
102486
|
-
setProductGenerationLogs([]);
|
|
102487
|
-
if (!productProcessStartTime) {
|
|
102488
|
-
setProductProcessStartTime(Date.now());
|
|
102489
102219
|
}
|
|
102490
|
-
const
|
|
102491
|
-
|
|
102492
|
-
|
|
102493
|
-
try {
|
|
102494
|
-
if (productRegeneratePrompt.trim()) {
|
|
102495
|
-
addLog(formatLogMessage('log', '🔄 Переделка изображения продукта с пользовательскими требованиями:'));
|
|
102496
|
-
productRegeneratePrompt.split('\n').filter(line => line.trim()).forEach((line, idx) => {
|
|
102497
|
-
addLog(formatLogMessage('log', ` ${idx + 1}. ${line.trim()}`));
|
|
102498
|
-
});
|
|
102499
|
-
}
|
|
102500
|
-
else {
|
|
102501
|
-
addLog(formatLogMessage('log', '🔄 Переделка изображения продукта (без дополнительных требований)'));
|
|
102502
|
-
}
|
|
102503
|
-
logToTerminal('log', '🔄 Regenerating product image with additional prompt:', productRegeneratePrompt);
|
|
102504
|
-
// Pass current product image as reference for regeneration
|
|
102505
|
-
const generatedImageUrl = await generateProductFromBanka(productSourceImages, productRegeneratePrompt, addLog, productGeneratedImage || undefined);
|
|
102506
|
-
setProductGeneratedImage(generatedImageUrl);
|
|
102507
|
-
setProductRegeneratePrompt(''); // Clear prompt after regeneration
|
|
102508
|
-
}
|
|
102509
|
-
catch (err) {
|
|
102510
|
-
const errorMsg = 'Error regenerating product: ' + err.message;
|
|
102511
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102512
|
-
logToTerminal('error', '❌ Error regenerating product:', err);
|
|
102513
|
-
alert(errorMsg);
|
|
102514
|
-
}
|
|
102515
|
-
finally {
|
|
102516
|
-
setGeneratingProduct(false);
|
|
102517
|
-
}
|
|
102518
|
-
};
|
|
102519
|
-
const handleUploadProduct = async () => {
|
|
102520
|
-
if (!productGeneratedImage || !driveFolderUrl.trim()) {
|
|
102521
|
-
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');
|
|
102522
102223
|
return;
|
|
102523
102224
|
}
|
|
102524
102225
|
setUploadingProduct(true);
|
|
102525
|
-
|
|
102526
|
-
if (!productProcessStartTime) {
|
|
102527
|
-
setProductProcessStartTime(Date.now());
|
|
102528
|
-
}
|
|
102529
|
-
const addLog = (msg) => {
|
|
102530
|
-
setProductGenerationLogs(prev => [...prev, msg]);
|
|
102531
|
-
};
|
|
102226
|
+
const addLog = () => { };
|
|
102532
102227
|
try {
|
|
102533
|
-
const
|
|
102534
|
-
|
|
102535
|
-
|
|
102536
|
-
|
|
102537
|
-
|
|
102538
|
-
|
|
102539
|
-
|
|
102540
|
-
|
|
102541
|
-
|
|
102542
|
-
|
|
102543
|
-
|
|
102544
|
-
|
|
102545
|
-
...folderFilesInfo,
|
|
102546
|
-
hasProduct: true
|
|
102547
|
-
});
|
|
102548
|
-
}
|
|
102549
|
-
alert('product.png uploaded successfully!');
|
|
102550
|
-
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 успешно загружен в папку!');
|
|
102551
102240
|
}
|
|
102552
102241
|
catch (err) {
|
|
102553
|
-
const errorMsg = '
|
|
102554
|
-
|
|
102555
|
-
logToTerminal('error', '❌ Error uploading product:', err);
|
|
102242
|
+
const errorMsg = 'Ошибка загрузки: ' + err.message;
|
|
102243
|
+
logToTerminal('error', '❌', errorMsg);
|
|
102556
102244
|
alert(errorMsg);
|
|
102557
102245
|
}
|
|
102558
102246
|
finally {
|
|
@@ -102796,7 +102484,7 @@ function App() {
|
|
|
102796
102484
|
addLog(formatLogMessage(level, ...args));
|
|
102797
102485
|
};
|
|
102798
102486
|
logMsg('log', '📦 Creating ZIP archive with HTML and product image...');
|
|
102799
|
-
const zip = new (
|
|
102487
|
+
const zip = new (jszip__WEBPACK_IMPORTED_MODULE_51___default())();
|
|
102800
102488
|
zip.file('index.html', htmlContent);
|
|
102801
102489
|
zip.file('product.png', productImageBlob);
|
|
102802
102490
|
logMsg('log', '✅ Files added to archive: index.html, product.png');
|
|
@@ -103115,18 +102803,41 @@ function App() {
|
|
|
103115
102803
|
throw new Error(errorMsg);
|
|
103116
102804
|
}
|
|
103117
102805
|
addLog(formatLogMessage('log', '✅ product.png found'));
|
|
103118
|
-
//
|
|
103119
|
-
// NOTE: This generation prompt is intentionally STRICTER than the image validator (`validateCreativeImage`).
|
|
103120
|
-
// Keep the prompt strict even if validator allows some "acceptable" deviations (e.g. 2-line headline, benefit headline, CTA position, discount emphasis).
|
|
103121
|
-
// Use original currency symbol if available, otherwise use currency code
|
|
103122
|
-
const basePromptStructure = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.getImageGenerationBasePrompt)(generateGeo, generatePrice, currencyForPrompt, undefined, imageAspectRatio);
|
|
103123
|
-
// Generate images with different approaches (one per approach)
|
|
102806
|
+
// Generate images with different approaches (количество по каждому подходу)
|
|
103124
102807
|
const approaches = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.getCreoApproaches)();
|
|
102808
|
+
if (approaches.length === 0) {
|
|
102809
|
+
addLog(formatLogMessage('error', '❌ Укажите количество изображений (хотя бы 1) в настройках подходов'));
|
|
102810
|
+
alert('Укажите количество изображений (1–4) хотя бы для одного подхода в настройках');
|
|
102811
|
+
setGeneratingImages(false);
|
|
102812
|
+
return;
|
|
102813
|
+
}
|
|
102814
|
+
// При "both" — на каждый подход генерируем и 1:1, и 2:3
|
|
102815
|
+
const useBoth = imageAspectRatio === 'both';
|
|
102816
|
+
const tasks = useBoth
|
|
102817
|
+
? approaches.flatMap(a => [
|
|
102818
|
+
{ approach: a, ratio: '1:1' },
|
|
102819
|
+
{ approach: a, ratio: '2:3' }
|
|
102820
|
+
])
|
|
102821
|
+
: approaches.map(a => ({ approach: a, ratio: imageAspectRatio }));
|
|
102822
|
+
const additionalInfoLine = generateAdditionalInfo.trim()
|
|
102823
|
+
? `\n📋 ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ О ПРОДУКТЕ: ${generateAdditionalInfo.trim()}`
|
|
102824
|
+
: '';
|
|
102825
|
+
const imagePrompts = tasks.map(t => {
|
|
102826
|
+
const basePromptStructure = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.getImageGenerationBasePrompt)(generateGeo, generatePrice, currencyForPrompt, undefined, t.ratio);
|
|
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 обязателен; буллиты добавляй только если они разрешены для подхода).`;
|
|
102834
|
+
});
|
|
103125
102835
|
// Initialize placeholders for all images
|
|
103126
|
-
const initialPlaceholders =
|
|
102836
|
+
const initialPlaceholders = tasks.map((t, index) => ({
|
|
103127
102837
|
index: index + 1,
|
|
103128
102838
|
imageUrl: undefined,
|
|
103129
|
-
approach: approach.name,
|
|
102839
|
+
approach: t.approach.name + (useBoth ? ` (${t.ratio})` : ''),
|
|
102840
|
+
aspectRatio: t.ratio,
|
|
103130
102841
|
uploaded: false,
|
|
103131
102842
|
checking: false,
|
|
103132
102843
|
checkStatus: 'pending',
|
|
@@ -103140,13 +102851,9 @@ function App() {
|
|
|
103140
102851
|
generating: false // In sequential mode, images start queued (not generating)
|
|
103141
102852
|
}));
|
|
103142
102853
|
setGeneratedImagesData(initialPlaceholders);
|
|
103143
|
-
|
|
103144
|
-
|
|
103145
|
-
|
|
103146
|
-
const imagePrompts = approaches.map(approach => `🏷️ ПРОДУКТ: ${generateProduct}${additionalInfoLine}\n\n${basePromptStructure}🏷️ ПРОДУКТ (повторение для ясности): ${generateProduct}${additionalInfoLine}\n🎯 ПОДХОД: ${approach.name}\n\n${approach.prompt}\n\n${approach.headlineAngle}\n\n${approach.bulletsFocus}\n\nТекст — строго следуй правилам этого подхода (HOOK обязателен; буллиты добавляй только если они разрешены для подхода).`);
|
|
103147
|
-
addLog(formatLogMessage('log', `📝 Generated prompts for ${approaches.length} approaches`));
|
|
103148
|
-
const maxParallel = 3;
|
|
103149
|
-
addLog(formatLogMessage('log', `🚀 Generating ${approaches.length} images in parallel (up to ${maxParallel} at a time)...`));
|
|
102854
|
+
addLog(formatLogMessage('log', `📝 Generated prompts for ${tasks.length} images${useBoth ? ' (1:1 + 2:3 на каждый подход)' : ''}`));
|
|
102855
|
+
const maxParallel = 100; // like infinity
|
|
102856
|
+
addLog(formatLogMessage('log', `🚀 Generating ${tasks.length} images in parallel (up to ${maxParallel} at a time)...`));
|
|
103150
102857
|
const generationStartTime = Date.now();
|
|
103151
102858
|
const resultsMap = new Map();
|
|
103152
102859
|
// Ensure each slot has prompt+product reference from the start (needed for regeneration even on failures)
|
|
@@ -103157,9 +102864,11 @@ function App() {
|
|
|
103157
102864
|
})));
|
|
103158
102865
|
const runOne = async (i, isRetry) => {
|
|
103159
102866
|
const imageIndex = i + 1;
|
|
103160
|
-
const
|
|
102867
|
+
const task = tasks[i];
|
|
102868
|
+
const approachName = task?.approach.name || 'Unknown';
|
|
103161
102869
|
const prompt = imagePrompts[i];
|
|
103162
|
-
|
|
102870
|
+
const ratio = task?.ratio || '1:1';
|
|
102871
|
+
addLog(formatLogMessage('log', `${isRetry ? '🔄' : '🎨'} Генерация изображения ${imageIndex}/${tasks.length} (${approachName} ${ratio})...`));
|
|
103163
102872
|
// Reset slot state for this run
|
|
103164
102873
|
setGeneratedImagesData(prev => prev.map(img => img.index === imageIndex
|
|
103165
102874
|
? {
|
|
@@ -103177,7 +102886,7 @@ function App() {
|
|
|
103177
102886
|
}
|
|
103178
102887
|
: img));
|
|
103179
102888
|
try {
|
|
103180
|
-
const imageUrl = await generateImageWithDALLE(prompt, productImage.url);
|
|
102889
|
+
const imageUrl = await generateImageWithDALLE(prompt, productImage.url, ratio);
|
|
103181
102890
|
if (!imageUrl || typeof imageUrl !== 'string' || imageUrl.trim() === '') {
|
|
103182
102891
|
throw new Error('Generated image URL is empty or invalid');
|
|
103183
102892
|
}
|
|
@@ -103194,59 +102903,46 @@ function App() {
|
|
|
103194
102903
|
productImageUrl: productImage.url
|
|
103195
102904
|
}
|
|
103196
102905
|
: img));
|
|
103197
|
-
addLog(formatLogMessage('log', `✅ Изображение ${imageIndex}/${
|
|
103198
|
-
// Validate right after generation
|
|
103199
|
-
|
|
103200
|
-
|
|
103201
|
-
|
|
103202
|
-
|
|
103203
|
-
|
|
103204
|
-
|
|
103205
|
-
|
|
103206
|
-
|
|
103207
|
-
checking: false,
|
|
103208
|
-
checkStatus: validationResult.status,
|
|
103209
|
-
checkResult: validationResult.result,
|
|
103210
|
-
checkErrors: validationResult.errors,
|
|
103211
|
-
customRegeneratePrompt: validationResult.errors.length > 0
|
|
103212
|
-
? validationResult.errors.map(err => err.replace(/^ОШИБКА:\s*/i, '')).join('\n')
|
|
103213
|
-
: '',
|
|
103214
|
-
originalPrompt: img.originalPrompt || prompt,
|
|
103215
|
-
productImageUrl: img.productImageUrl || productImage.url
|
|
103216
|
-
}
|
|
103217
|
-
: img);
|
|
103218
|
-
// Save single image to Google Drive after validation
|
|
103219
|
-
if (driveFolderUrl) {
|
|
103220
|
-
const folderId = extractFolderId(driveFolderUrl);
|
|
103221
|
-
if (folderId) {
|
|
103222
|
-
const updatedImage = updated.find(img => img.index === imageIndex);
|
|
103223
|
-
if (updatedImage) {
|
|
103224
|
-
saveSingleImageToDrive(folderId, updatedImage).catch((err) => {
|
|
103225
|
-
console.log(`Failed to save image ${imageIndex} after validation:`, err);
|
|
103226
|
-
});
|
|
103227
|
-
}
|
|
103228
|
-
}
|
|
102906
|
+
addLog(formatLogMessage('log', `✅ Изображение ${imageIndex}/${tasks.length} сгенерировано`));
|
|
102907
|
+
// Validate right after generation (skip if validation disabled)
|
|
102908
|
+
const validationResult = validationDisabled
|
|
102909
|
+
? { status: 'ok', result: 'Проверка отключена', errors: [], checkFailed: false }
|
|
102910
|
+
: await (async () => {
|
|
102911
|
+
try {
|
|
102912
|
+
const res = await validateCreativeImage(imageUrl, generateProduct, generateGeo, addLog, approachName);
|
|
102913
|
+
if (!res)
|
|
102914
|
+
throw new Error('No validation result');
|
|
102915
|
+
return { ...res, checkFailed: false };
|
|
103229
102916
|
}
|
|
103230
|
-
|
|
103231
|
-
|
|
103232
|
-
|
|
103233
|
-
|
|
103234
|
-
|
|
103235
|
-
|
|
103236
|
-
|
|
102917
|
+
catch (validationErr) {
|
|
102918
|
+
const msg = validationErr?.message || String(validationErr);
|
|
102919
|
+
addLog(formatLogMessage('error', `❌ Ошибка проверки изображения ${imageIndex}: ${msg}`));
|
|
102920
|
+
return {
|
|
102921
|
+
status: 'needs_rebuild',
|
|
102922
|
+
result: `Ошибка проверки: ${msg}`,
|
|
102923
|
+
errors: [msg],
|
|
102924
|
+
checkFailed: true
|
|
102925
|
+
};
|
|
102926
|
+
}
|
|
102927
|
+
})();
|
|
102928
|
+
setGeneratedImagesData(prev => {
|
|
102929
|
+
const updated = prev.map(img => img.index === imageIndex
|
|
103237
102930
|
? {
|
|
103238
102931
|
...img,
|
|
103239
102932
|
checking: false,
|
|
103240
|
-
|
|
103241
|
-
|
|
103242
|
-
checkResult:
|
|
103243
|
-
checkErrors:
|
|
103244
|
-
customRegeneratePrompt:
|
|
102933
|
+
checkFailed: validationResult.checkFailed ?? false,
|
|
102934
|
+
checkStatus: validationResult.status,
|
|
102935
|
+
checkResult: validationResult.result,
|
|
102936
|
+
checkErrors: validationResult.errors,
|
|
102937
|
+
customRegeneratePrompt: validationResult.errors.length > 0
|
|
102938
|
+
? validationResult.errors.map(err => err.replace(/^ОШИБКА:\s*/i, '')).join('\n')
|
|
102939
|
+
: '',
|
|
103245
102940
|
originalPrompt: img.originalPrompt || prompt,
|
|
103246
102941
|
productImageUrl: img.productImageUrl || productImage.url
|
|
103247
102942
|
}
|
|
103248
|
-
: img)
|
|
103249
|
-
|
|
102943
|
+
: img);
|
|
102944
|
+
return updated;
|
|
102945
|
+
});
|
|
103250
102946
|
return {
|
|
103251
102947
|
index: imageIndex,
|
|
103252
102948
|
imageUrl,
|
|
@@ -103258,7 +102954,7 @@ function App() {
|
|
|
103258
102954
|
}
|
|
103259
102955
|
catch (err) {
|
|
103260
102956
|
const errorMessage = err?.message || String(err);
|
|
103261
|
-
addLog(formatLogMessage('error', `❌ Не удалось сгенерировать изображение ${imageIndex}/${
|
|
102957
|
+
addLog(formatLogMessage('error', `❌ Не удалось сгенерировать изображение ${imageIndex}/${tasks.length}: ${errorMessage}`));
|
|
103262
102958
|
setGeneratedImagesData(prev => prev.map(img => img.index === imageIndex
|
|
103263
102959
|
? {
|
|
103264
102960
|
...img,
|
|
@@ -103554,7 +103250,7 @@ ${imageData.originalPrompt}
|
|
|
103554
103250
|
});
|
|
103555
103251
|
}
|
|
103556
103252
|
addLog(formatLogMessage('log', `📤 Первые 300 символов промпта: ${improvedPrompt.substring(0, 300)}...`));
|
|
103557
|
-
const newImageUrl = await generateImageWithDALLE(improvedPrompt, referenceImages);
|
|
103253
|
+
const newImageUrl = await generateImageWithDALLE(improvedPrompt, referenceImages, imageData.aspectRatio ?? '1:1');
|
|
103558
103254
|
if (!newImageUrl)
|
|
103559
103255
|
throw new Error('No image URL returned');
|
|
103560
103256
|
addLog(formatLogMessage('log', `✅ Изображение ${imageData.index} переделано успешно`));
|
|
@@ -103582,9 +103278,15 @@ ${imageData.originalPrompt}
|
|
|
103582
103278
|
failed: false // Mark as successful
|
|
103583
103279
|
}
|
|
103584
103280
|
: img));
|
|
103585
|
-
// Run validation on the new image
|
|
103586
|
-
|
|
103587
|
-
|
|
103281
|
+
// Run validation on the new image (skip if disabled)
|
|
103282
|
+
let validationResult;
|
|
103283
|
+
if (validationDisabled) {
|
|
103284
|
+
validationResult = { status: 'ok', result: 'Проверка отключена', errors: [] };
|
|
103285
|
+
}
|
|
103286
|
+
else {
|
|
103287
|
+
addLog(formatLogMessage('log', `🔍 Проверка переделанного изображения ${imageData.index}...`));
|
|
103288
|
+
validationResult = await validateCreativeImage(newImageUrl, generateProduct, generateGeo, addLog, imageData.approach);
|
|
103289
|
+
}
|
|
103588
103290
|
// Update with validation result
|
|
103589
103291
|
setGeneratedImagesData(prev => {
|
|
103590
103292
|
const updated = prev.map(img => img.index === imageData.index
|
|
@@ -103602,18 +103304,6 @@ ${imageData.originalPrompt}
|
|
|
103602
103304
|
productImageUrl: img.productImageUrl || imageData.productImageUrl
|
|
103603
103305
|
}
|
|
103604
103306
|
: img);
|
|
103605
|
-
// Save single image to Google Drive after regeneration
|
|
103606
|
-
if (driveFolderUrl) {
|
|
103607
|
-
const folderId = extractFolderId(driveFolderUrl);
|
|
103608
|
-
if (folderId) {
|
|
103609
|
-
const updatedImage = updated.find(img => img.index === imageData.index);
|
|
103610
|
-
if (updatedImage) {
|
|
103611
|
-
saveSingleImageToDrive(folderId, updatedImage).catch((err) => {
|
|
103612
|
-
console.log(`Failed to save image ${imageData.index} after regeneration:`, err);
|
|
103613
|
-
});
|
|
103614
|
-
}
|
|
103615
|
-
}
|
|
103616
|
-
}
|
|
103617
103307
|
return updated;
|
|
103618
103308
|
});
|
|
103619
103309
|
const statusEmoji = validationResult.status === 'ok' ? '✅' : '❌';
|
|
@@ -103679,7 +103369,7 @@ ${imageData.originalPrompt}
|
|
|
103679
103369
|
${imageData.originalPrompt}
|
|
103680
103370
|
|
|
103681
103371
|
ВАЖНО: Твой ответ — это ИЗОБРАЖЕНИЕ, не текст. Не пиши план, не перечисляй элементы текстом, не рассуждай, не вызывай tools. Сразу генерируй визуальный креатив как картинку.`;
|
|
103682
|
-
const newImageUrl = await generateImageWithDALLE(freshPrompt, imageData.productImageUrl);
|
|
103372
|
+
const newImageUrl = await generateImageWithDALLE(freshPrompt, imageData.productImageUrl, imageData.aspectRatio ?? '1:1');
|
|
103683
103373
|
addLog(formatLogMessage('log', `✅ Изображение ${imageData.index} (с нуля) сгенерировано`));
|
|
103684
103374
|
// Update image data - always add timestamp for HTTP URLs to prevent browser caching
|
|
103685
103375
|
let updatedImageUrl = newImageUrl;
|
|
@@ -103704,9 +103394,15 @@ ${imageData.originalPrompt}
|
|
|
103704
103394
|
failed: false
|
|
103705
103395
|
}
|
|
103706
103396
|
: img));
|
|
103707
|
-
// Validate new image
|
|
103708
|
-
|
|
103709
|
-
|
|
103397
|
+
// Validate new image (skip if disabled)
|
|
103398
|
+
let validationResult;
|
|
103399
|
+
if (validationDisabled) {
|
|
103400
|
+
validationResult = { status: 'ok', result: 'Проверка отключена', errors: [] };
|
|
103401
|
+
}
|
|
103402
|
+
else {
|
|
103403
|
+
addLog(formatLogMessage('log', `🔍 Проверка изображения ${imageData.index} (с нуля)...`));
|
|
103404
|
+
validationResult = await validateCreativeImage(newImageUrl, generateProduct, generateGeo, addLog, imageData.approach);
|
|
103405
|
+
}
|
|
103710
103406
|
setGeneratedImagesData(prev => {
|
|
103711
103407
|
const updated = prev.map(img => img.index === imageData.index
|
|
103712
103408
|
? {
|
|
@@ -103722,18 +103418,6 @@ ${imageData.originalPrompt}
|
|
|
103722
103418
|
productImageUrl: img.productImageUrl || imageData.productImageUrl
|
|
103723
103419
|
}
|
|
103724
103420
|
: img);
|
|
103725
|
-
// Save single image to Google Drive after regeneration
|
|
103726
|
-
if (driveFolderUrl) {
|
|
103727
|
-
const folderId = extractFolderId(driveFolderUrl);
|
|
103728
|
-
if (folderId) {
|
|
103729
|
-
const updatedImage = updated.find(img => img.index === imageData.index);
|
|
103730
|
-
if (updatedImage) {
|
|
103731
|
-
saveSingleImageToDrive(folderId, updatedImage).catch((err) => {
|
|
103732
|
-
console.log(`Failed to save image ${imageData.index} after regeneration:`, err);
|
|
103733
|
-
});
|
|
103734
|
-
}
|
|
103735
|
-
}
|
|
103736
|
-
}
|
|
103737
103421
|
return updated;
|
|
103738
103422
|
});
|
|
103739
103423
|
}
|
|
@@ -103774,40 +103458,47 @@ ${imageData.originalPrompt}
|
|
|
103774
103458
|
checkErrors: undefined,
|
|
103775
103459
|
}
|
|
103776
103460
|
: img));
|
|
103777
|
-
|
|
103778
|
-
|
|
103779
|
-
|
|
103780
|
-
setGeneratedImagesData(prev => prev.map(img => img.index === imageData.index
|
|
103781
|
-
? {
|
|
103782
|
-
...img,
|
|
103783
|
-
checking: false,
|
|
103784
|
-
checkFailed: false,
|
|
103785
|
-
checkStatus: validationResult.status,
|
|
103786
|
-
checkResult: validationResult.result,
|
|
103787
|
-
checkErrors: validationResult.errors,
|
|
103788
|
-
customRegeneratePrompt: validationResult.errors.length > 0
|
|
103789
|
-
? validationResult.errors.map(err => err.replace(/^ОШИБКА:\s*/i, '')).join('\n')
|
|
103790
|
-
: '',
|
|
103791
|
-
}
|
|
103792
|
-
: img));
|
|
103793
|
-
const statusEmoji = validationResult.status === 'ok' ? '✅' : '❌';
|
|
103794
|
-
addLog(formatLogMessage('log', `${statusEmoji} Повторная проверка изображения ${imageData.index}: ${validationResult.status === 'ok' ? 'OK' : 'НУЖНА ПЕРЕСБОРКА'}`));
|
|
103461
|
+
let validationResult;
|
|
103462
|
+
if (validationDisabled) {
|
|
103463
|
+
validationResult = { status: 'ok', result: 'Проверка отключена', errors: [] };
|
|
103795
103464
|
}
|
|
103796
|
-
|
|
103797
|
-
|
|
103798
|
-
|
|
103799
|
-
|
|
103800
|
-
|
|
103801
|
-
|
|
103802
|
-
|
|
103803
|
-
|
|
103804
|
-
|
|
103805
|
-
|
|
103806
|
-
|
|
103807
|
-
|
|
103808
|
-
|
|
103809
|
-
|
|
103465
|
+
else {
|
|
103466
|
+
addLog(formatLogMessage('log', `🔍 Повторная проверка изображения ${imageData.index}...`));
|
|
103467
|
+
try {
|
|
103468
|
+
validationResult = await validateCreativeImage(imageData.imageUrl, generateProduct, generateGeo, addLog, imageData.approach);
|
|
103469
|
+
}
|
|
103470
|
+
catch (err) {
|
|
103471
|
+
const msg = err?.message || String(err);
|
|
103472
|
+
addLog(formatLogMessage('error', `❌ Ошибка повторной проверки изображения ${imageData.index}: ${msg}`));
|
|
103473
|
+
setGeneratedImagesData(prev => prev.map(img => img.index === imageData.index
|
|
103474
|
+
? {
|
|
103475
|
+
...img,
|
|
103476
|
+
checking: false,
|
|
103477
|
+
checkFailed: true,
|
|
103478
|
+
checkStatus: 'needs_rebuild',
|
|
103479
|
+
checkResult: `Ошибка проверки: ${msg}`,
|
|
103480
|
+
checkErrors: [msg],
|
|
103481
|
+
customRegeneratePrompt: '',
|
|
103482
|
+
}
|
|
103483
|
+
: img));
|
|
103484
|
+
return;
|
|
103485
|
+
}
|
|
103810
103486
|
}
|
|
103487
|
+
setGeneratedImagesData(prev => prev.map(img => img.index === imageData.index
|
|
103488
|
+
? {
|
|
103489
|
+
...img,
|
|
103490
|
+
checking: false,
|
|
103491
|
+
checkFailed: false,
|
|
103492
|
+
checkStatus: validationResult.status,
|
|
103493
|
+
checkResult: validationResult.result,
|
|
103494
|
+
checkErrors: validationResult.errors,
|
|
103495
|
+
customRegeneratePrompt: validationResult.errors.length > 0
|
|
103496
|
+
? validationResult.errors.map(err => err.replace(/^ОШИБКА:\s*/i, '')).join('\n')
|
|
103497
|
+
: '',
|
|
103498
|
+
}
|
|
103499
|
+
: img));
|
|
103500
|
+
const statusEmoji = validationResult.status === 'ok' ? '✅' : '❌';
|
|
103501
|
+
addLog(formatLogMessage('log', `${statusEmoji} Повторная проверка изображения ${imageData.index}: ${validationResult.status === 'ok' ? 'OK' : 'НУЖНА ПЕРЕСБОРКА'}`));
|
|
103811
103502
|
};
|
|
103812
103503
|
const handleUploadImage = async (imageData, folderId) => {
|
|
103813
103504
|
if (!imageData.imageUrl) {
|
|
@@ -103934,11 +103625,33 @@ ${imageData.originalPrompt}
|
|
|
103934
103625
|
const handleLinkChange = (val) => {
|
|
103935
103626
|
setLink(val);
|
|
103936
103627
|
try {
|
|
103937
|
-
|
|
103628
|
+
const toTest = /^https?:\/\//i.test(val.trim()) ? val : 'https://' + val.trim();
|
|
103629
|
+
new URL(toTest);
|
|
103630
|
+
setLinkError('');
|
|
103631
|
+
}
|
|
103632
|
+
catch {
|
|
103633
|
+
setLinkError(val.trim() ? 'Invalid URL format' : '');
|
|
103634
|
+
}
|
|
103635
|
+
};
|
|
103636
|
+
const handleLinkBlur = () => {
|
|
103637
|
+
const trimmed = link.trim();
|
|
103638
|
+
if (!trimmed)
|
|
103639
|
+
return;
|
|
103640
|
+
let toParse = trimmed;
|
|
103641
|
+
if (!/^https?:\/\//i.test(trimmed)) {
|
|
103642
|
+
toParse = 'https://' + trimmed;
|
|
103643
|
+
}
|
|
103644
|
+
try {
|
|
103645
|
+
const parsed = new URL(toParse);
|
|
103646
|
+
let path = parsed.pathname || '/';
|
|
103647
|
+
if (!path.endsWith('/'))
|
|
103648
|
+
path += '/';
|
|
103649
|
+
const result = `https://${parsed.host}${path}${parsed.search}${parsed.hash}`;
|
|
103650
|
+
setLink(result);
|
|
103938
103651
|
setLinkError('');
|
|
103939
103652
|
}
|
|
103940
103653
|
catch {
|
|
103941
|
-
|
|
103654
|
+
// invalid — leave as is
|
|
103942
103655
|
}
|
|
103943
103656
|
};
|
|
103944
103657
|
const extractFolderId = (url) => {
|
|
@@ -103982,41 +103695,13 @@ ${imageData.originalPrompt}
|
|
|
103982
103695
|
throw new Error(err.error?.message || 'Failed to fetch Drive files');
|
|
103983
103696
|
}
|
|
103984
103697
|
const data = await response.json();
|
|
103985
|
-
// Filter out
|
|
103698
|
+
// Filter out product.png/jpg (used for product reference, not creative images)
|
|
103986
103699
|
const filteredFiles = data.files.filter((f) => {
|
|
103987
103700
|
const name = f.name?.toLowerCase() || '';
|
|
103988
|
-
return
|
|
103701
|
+
return name !== 'product.png' && name !== 'product.jpg';
|
|
103989
103702
|
});
|
|
103990
103703
|
return filteredFiles.map((f) => f.id ? `https://drive.google.com/file/d/${f.id}/view?usp=sharing` : '');
|
|
103991
103704
|
};
|
|
103992
|
-
const fetchBankaImages = async (folderId) => {
|
|
103993
|
-
const validToken = await getValidAccessToken();
|
|
103994
|
-
if (!validToken)
|
|
103995
|
-
throw new Error('Not logged in');
|
|
103996
|
-
const q = `'${folderId}' in parents and trashed = false and mimeType contains 'image/'`;
|
|
103997
|
-
const fields = 'files(id, name)';
|
|
103998
|
-
const url = `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(q)}&fields=${encodeURIComponent(fields)}`;
|
|
103999
|
-
const response = await fetch(url, {
|
|
104000
|
-
headers: {
|
|
104001
|
-
'Authorization': `Bearer ${validToken}`
|
|
104002
|
-
}
|
|
104003
|
-
});
|
|
104004
|
-
if (!response.ok) {
|
|
104005
|
-
const err = await response.json();
|
|
104006
|
-
throw new Error(err.error?.message || 'Failed to fetch Drive files');
|
|
104007
|
-
}
|
|
104008
|
-
const data = await response.json();
|
|
104009
|
-
// Filter only banka* files (case insensitive)
|
|
104010
|
-
const bankaFiles = data.files.filter((f) => {
|
|
104011
|
-
const name = f.name?.toLowerCase() || '';
|
|
104012
|
-
return name.startsWith('banka') && (name.endsWith('.png') || name.endsWith('.jpg') || name.endsWith('.jpeg'));
|
|
104013
|
-
});
|
|
104014
|
-
return bankaFiles.map((f) => ({
|
|
104015
|
-
id: f.id,
|
|
104016
|
-
name: f.name,
|
|
104017
|
-
url: `https://drive.google.com/file/d/${f.id}/view?usp=sharing`
|
|
104018
|
-
}));
|
|
104019
|
-
};
|
|
104020
103705
|
const fetchProductImage = async (folderId) => {
|
|
104021
103706
|
const validToken = await getValidAccessToken();
|
|
104022
103707
|
if (!validToken)
|
|
@@ -104393,11 +104078,7 @@ ${imageData.originalPrompt}
|
|
|
104393
104078
|
logToTerminal('log', '[Load Content] Loaded generatePriceWithCurrency:', settings.generatePriceWithCurrency);
|
|
104394
104079
|
}
|
|
104395
104080
|
}
|
|
104396
|
-
// Load brand
|
|
104397
|
-
if (loadedData.brand !== undefined) {
|
|
104398
|
-
setBrand(loadedData.brand || '');
|
|
104399
|
-
logToTerminal('log', '[Load Content] Loaded brand:', loadedData.brand || '(empty)');
|
|
104400
|
-
}
|
|
104081
|
+
// Load link (brand is auto-generated from product, geo, price)
|
|
104401
104082
|
if (loadedData.link !== undefined) {
|
|
104402
104083
|
setLink(loadedData.link || '');
|
|
104403
104084
|
logToTerminal('log', '[Load Content] Loaded link:', loadedData.link || '(empty)');
|
|
@@ -104715,8 +104396,8 @@ ${imageData.originalPrompt}
|
|
|
104715
104396
|
}
|
|
104716
104397
|
setGeneratedData(rows);
|
|
104717
104398
|
// Create workbook
|
|
104718
|
-
const wb =
|
|
104719
|
-
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);
|
|
104720
104401
|
// Set column widths (approximate pixel width / 7)
|
|
104721
104402
|
ws['!cols'] = [
|
|
104722
104403
|
{ wch: 20 }, // id
|
|
@@ -104729,10 +104410,10 @@ ${imageData.originalPrompt}
|
|
|
104729
104410
|
{ wch: 40 }, // image_link
|
|
104730
104411
|
{ wch: 20 } // brand
|
|
104731
104412
|
];
|
|
104732
|
-
|
|
104413
|
+
xlsx__WEBPACK_IMPORTED_MODULE_50__.utils.book_append_sheet(wb, ws, "Products");
|
|
104733
104414
|
// Generate buffer
|
|
104734
|
-
const wbout =
|
|
104735
|
-
// Upload to Drive
|
|
104415
|
+
const wbout = xlsx__WEBPACK_IMPORTED_MODULE_50__.write(wb, { bookType: 'xlsx', type: 'array' });
|
|
104416
|
+
// Upload to Drive (имя файла по бренду)
|
|
104736
104417
|
const dateStr = new Date().toISOString().split('T')[0];
|
|
104737
104418
|
const fileName = `${brand}-${dateStr}.xlsx`;
|
|
104738
104419
|
const result = await uploadFileToDrive(wbout, fileName, folderId || undefined);
|
|
@@ -104853,13 +104534,13 @@ ${imageData.originalPrompt}
|
|
|
104853
104534
|
setTestLoading(true);
|
|
104854
104535
|
try {
|
|
104855
104536
|
// Create simple test workbook with structure
|
|
104856
|
-
const wb =
|
|
104537
|
+
const wb = xlsx__WEBPACK_IMPORTED_MODULE_50__.utils.book_new();
|
|
104857
104538
|
const rows = [
|
|
104858
104539
|
INSTRUCTION_ROW,
|
|
104859
104540
|
['id', 'title', 'description', 'availability', 'condition', 'price', 'link', 'image_link', 'brand'],
|
|
104860
104541
|
['test1', 'Test Title', 'Test Description', 'in stock', 'new', '10.00 USD', 'http://test.com', 'http://test.com/img.jpg', 'TestBrand']
|
|
104861
104542
|
];
|
|
104862
|
-
const ws =
|
|
104543
|
+
const ws = xlsx__WEBPACK_IMPORTED_MODULE_50__.utils.aoa_to_sheet(rows);
|
|
104863
104544
|
// Set column widths
|
|
104864
104545
|
ws['!cols'] = [
|
|
104865
104546
|
{ wch: 20 }, // id
|
|
@@ -104872,8 +104553,8 @@ ${imageData.originalPrompt}
|
|
|
104872
104553
|
{ wch: 40 }, // image_link
|
|
104873
104554
|
{ wch: 20 } // brand
|
|
104874
104555
|
];
|
|
104875
|
-
|
|
104876
|
-
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' });
|
|
104877
104558
|
// Try to extract folder ID if available, otherwise upload to root
|
|
104878
104559
|
const folderId = driveFolderUrl ? extractFolderId(driveFolderUrl) : undefined;
|
|
104879
104560
|
const result = await uploadFileToDrive(wbout, 'test_table.xlsx', folderId || undefined);
|
|
@@ -104907,7 +104588,7 @@ ${imageData.originalPrompt}
|
|
|
104907
104588
|
};
|
|
104908
104589
|
// Show lock screen if not unlocked
|
|
104909
104590
|
if (!unlocked) {
|
|
104910
|
-
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 },
|
|
104911
104592
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_4__["default"], null),
|
|
104912
104593
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { onClick: handleSecretClick, sx: {
|
|
104913
104594
|
width: '100vw',
|
|
@@ -104947,24 +104628,24 @@ ${imageData.originalPrompt}
|
|
|
104947
104628
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("br", null),
|
|
104948
104629
|
"Please contact system administrator"))));
|
|
104949
104630
|
}
|
|
104950
|
-
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 },
|
|
104951
104632
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_4__["default"], null),
|
|
104952
104633
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_7__["default"], { maxWidth: "lg", sx: { py: 4 } },
|
|
104953
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 } },
|
|
104954
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"),
|
|
104955
104636
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
104956
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 } },
|
|
104957
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104958
|
-
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)))),
|
|
104959
104640
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_9__["default"], { variant: "outlined", sx: { mb: 4 } },
|
|
104960
104641
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_10__["default"], null,
|
|
104961
104642
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 3 },
|
|
104962
104643
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
104963
104644
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true }, "Google Drive Authentication"),
|
|
104964
104645
|
accessToken ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_12__["default"], null,
|
|
104965
|
-
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) },
|
|
104966
104647
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { sx: { display: 'flex', alignItems: 'center', color: 'success.main' } },
|
|
104967
|
-
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 } }),
|
|
104968
104649
|
" Logged In (Credentials Hidden)")),
|
|
104969
104650
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_14__["default"], null,
|
|
104970
104651
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 2 },
|
|
@@ -104975,7 +104656,7 @@ ${imageData.originalPrompt}
|
|
|
104975
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)" }),
|
|
104976
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) }))),
|
|
104977
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 } },
|
|
104978
|
-
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' }),
|
|
104979
104660
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2", color: openRouterAccountBalance !== null ? 'text.primary' : 'text.secondary' },
|
|
104980
104661
|
"\u0411\u0430\u043B\u0430\u043D\u0441: ",
|
|
104981
104662
|
openRouterBalanceLoading ? 'Loading...' : (openRouterAccountBalance !== null ? `$${openRouterAccountBalance.toFixed(2)}` : 'N/A')),
|
|
@@ -104984,8 +104665,8 @@ ${imageData.originalPrompt}
|
|
|
104984
104665
|
"\u041B\u0438\u043C\u0438\u0442 \u043A\u043B\u044E\u0447\u0430: ",
|
|
104985
104666
|
openRouterBalanceLoading ? 'Loading...' : (openRouterBalance === -1 ? 'Без лимита' : (openRouterBalance !== null ? `${openRouterBalance.toFixed(4)}` : 'N/A'))))),
|
|
104986
104667
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2, alignItems: "center", sx: { mt: 2 } },
|
|
104987
|
-
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(
|
|
104988
|
-
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")))),
|
|
104989
104670
|
!accessToken && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
|
|
104990
104671
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_16__["default"], null),
|
|
104991
104672
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
@@ -105001,18 +104682,15 @@ ${imageData.originalPrompt}
|
|
|
105001
104682
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 14 }),
|
|
105002
104683
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0444\u0430\u0439\u043B\u043E\u0432...")))),
|
|
105003
104684
|
!checkingFolderFiles && folderFilesInfo && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_19__["default"], null,
|
|
105004
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { component: "span", sx: { display: 'block' } }, folderFilesInfo.
|
|
105005
|
-
"\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: ",
|
|
105006
|
-
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"))),
|
|
105007
|
-
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"))))),
|
|
105008
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"))))),
|
|
105009
104687
|
!driveFolderUrl.trim() ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "info", sx: { mt: 2 } },
|
|
105010
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"),
|
|
105011
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,
|
|
105012
104690
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: { xs: 'column', sm: 'row' }, spacing: 2 },
|
|
105013
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { label: "Brand (Short ID)", variant: "outlined",
|
|
105014
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
105015
|
-
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), error: !!linkError, helperText: linkError, placeholder: "
|
|
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" }),
|
|
104692
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { flex: 1, minWidth: 0 } },
|
|
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/" }))),
|
|
105016
104694
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_16__["default"], { sx: { my: 2 } }),
|
|
105017
104695
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true }, "AI Generation Settings"),
|
|
105018
104696
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
@@ -105040,18 +104718,22 @@ ${imageData.originalPrompt}
|
|
|
105040
104718
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043C\u043E\u0434\u0435\u043B\u0435\u0439...")))) : validationModels.length === 0 ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { disabled: true }, "\u041D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0445 \u043C\u043E\u0434\u0435\u043B\u0435\u0439")) : (validationModels.map((model) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_24__["default"], { key: model.id, value: model.id }, model.name))))),
|
|
105041
104719
|
!loadingValidationModels && validationModels.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_19__["default"], null, selectedValidationModel === _models__WEBPACK_IMPORTED_MODULE_2__.MODELS.creativeValidation
|
|
105042
104720
|
? 'Используется модель по умолчанию'
|
|
105043
|
-
: 'Выбрана модель: ' + (validationModels.find(m => m.id === selectedValidationModel)?.name || selectedValidationModel)))
|
|
104721
|
+
: 'Выбрана модель: ' + (validationModels.find(m => m.id === selectedValidationModel)?.name || selectedValidationModel))),
|
|
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 } })))),
|
|
105044
104723
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2, sx: { mb: 2 } },
|
|
105045
|
-
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'),
|
|
105046
104725
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 1, alignItems: "center", sx: { flexGrow: 1 } },
|
|
105047
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104726
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_27__["default"], { title: imageAspectRatio === '1:1'
|
|
105048
104727
|
? '1:1 — квадрат (1024×1024 px)'
|
|
105049
|
-
: '2:3
|
|
104728
|
+
: imageAspectRatio === '2:3'
|
|
104729
|
+
? '2:3 — портрет (1024×1536 px)'
|
|
104730
|
+
: 'Оба — квадрат и портрет на каждый подход', placement: "top" },
|
|
105050
104731
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null,
|
|
105051
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
104732
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_28__["default"], { value: imageAspectRatio, exclusive: true, onChange: (_e, val) => {
|
|
105052
104733
|
if (val && val !== imageAspectRatio) {
|
|
105053
104734
|
if (generatedImagesData.length > 0) {
|
|
105054
|
-
|
|
104735
|
+
const label = val === 'both' ? 'оба (1:1 + 2:3)' : val;
|
|
104736
|
+
if (window.confirm(`Сменить формат на ${label}? Сгенерированные изображения будут очищены.`)) {
|
|
105055
104737
|
setGeneratedImagesData([]);
|
|
105056
104738
|
setImageAspectRatio(val);
|
|
105057
104739
|
localStorage.setItem('imageAspectRatio', val);
|
|
@@ -105063,9 +104745,10 @@ ${imageData.originalPrompt}
|
|
|
105063
104745
|
}
|
|
105064
104746
|
}
|
|
105065
104747
|
}, size: "small", disabled: generatingImages, sx: { height: 42 } },
|
|
105066
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105067
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105068
|
-
|
|
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"),
|
|
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"),
|
|
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")))),
|
|
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 ||
|
|
105069
104752
|
loadingImagesFromDrive ||
|
|
105070
104753
|
!openaiApiKey ||
|
|
105071
104754
|
(!accessToken && !refreshToken) ||
|
|
@@ -105091,16 +104774,15 @@ ${imageData.originalPrompt}
|
|
|
105091
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")),
|
|
105092
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")),
|
|
105093
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 }),
|
|
105094
104778
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2, sx: { mb: 2 } },
|
|
105095
|
-
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(
|
|
105096
|
-
|
|
105097
|
-
: (productGeneratedImage ? 'Перегенерировать Product' : 'Generate Product from Banka')),
|
|
105098
|
-
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_47__["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 ||
|
|
105099
104781
|
!openaiApiKey ||
|
|
105100
104782
|
!accessToken ||
|
|
105101
104783
|
!driveFolderUrl.trim() ||
|
|
105102
104784
|
(folderFilesInfo !== null && !folderFilesInfo.hasProduct), sx: { flexGrow: 1 }, size: "large", title: folderFilesInfo !== null && !folderFilesInfo.hasProduct
|
|
105103
|
-
? 'product.png/jpg не найден в папке.
|
|
104785
|
+
? 'product.png/jpg не найден в папке. Загрузите его с помощью кнопки «Загрузить product.png/jpg»'
|
|
105104
104786
|
: undefined }, generatingLanding ? 'Creating Landing...' : 'Создать лендинг')),
|
|
105105
104787
|
(generatedImagesData.length > 0 || loadingImagesFromDrive) && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { mb: 2, mt: 3 } },
|
|
105106
104788
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true, sx: { fontWeight: 'bold' } },
|
|
@@ -105133,168 +104815,172 @@ ${imageData.originalPrompt}
|
|
|
105133
104815
|
gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' },
|
|
105134
104816
|
gap: 2,
|
|
105135
104817
|
mt: 1
|
|
105136
|
-
} }, generatedImagesData.map((imageData) =>
|
|
105137
|
-
imageData.
|
|
105138
|
-
|
|
105139
|
-
|
|
105140
|
-
|
|
105141
|
-
borderRadius: 1,
|
|
105142
|
-
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
105143
|
-
? 'rgba(25, 118, 210, 0.1)'
|
|
105144
|
-
: 'rgba(25, 118, 210, 0.05)',
|
|
105145
|
-
display: 'flex',
|
|
105146
|
-
flexDirection: 'column',
|
|
105147
|
-
alignItems: 'center',
|
|
105148
|
-
justifyContent: 'center',
|
|
105149
|
-
gap: 2,
|
|
105150
|
-
p: 3
|
|
105151
|
-
} },
|
|
105152
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 40, sx: { color: 'primary.main' } }),
|
|
105153
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2", sx: { color: 'text.secondary', textAlign: 'center', fontWeight: 'bold' } }, "\u0413\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F..."),
|
|
105154
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'text.secondary', textAlign: 'center' } }, imageData.approach),
|
|
105155
|
-
generatingImages && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'text.secondary', textAlign: 'center' } },
|
|
105156
|
-
"\u23F1\uFE0F \u041F\u0440\u043E\u0448\u043B\u043E: ",
|
|
105157
|
-
formatElapsedTime(elapsedTime.images))))) : imageData.imageUrl ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
|
|
105158
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { component: "img", src: imageData.imageUrl, alt: `Generated Image ${imageData.index}`, sx: {
|
|
104818
|
+
} }, generatedImagesData.map((imageData) => {
|
|
104819
|
+
const imgRatio = imageData.aspectRatio ?? (imageAspectRatio === 'both' ? '1:1' : imageAspectRatio);
|
|
104820
|
+
const aspectRatioCss = imgRatio === '2:3' ? '2 / 3' : '1 / 1';
|
|
104821
|
+
return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { key: imageData.index, sx: { position: 'relative' } },
|
|
104822
|
+
imageData.generating ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
105159
104823
|
width: '100%',
|
|
105160
|
-
|
|
105161
|
-
|
|
105162
|
-
objectFit: 'contain',
|
|
105163
|
-
border: (theme) => {
|
|
105164
|
-
if (imageData.uploaded || imageData.checkStatus === 'ok') {
|
|
105165
|
-
return `2px solid ${theme.palette.success.main}`;
|
|
105166
|
-
}
|
|
105167
|
-
else if (imageData.checkStatus === 'needs_rebuild') {
|
|
105168
|
-
return `2px solid ${theme.palette.error.main}`;
|
|
105169
|
-
}
|
|
105170
|
-
else if (imageData.checkStatus === 'checking') {
|
|
105171
|
-
return `2px solid ${theme.palette.warning.main}`;
|
|
105172
|
-
}
|
|
105173
|
-
else {
|
|
105174
|
-
return `2px solid ${theme.palette.primary.main}`;
|
|
105175
|
-
}
|
|
105176
|
-
},
|
|
104824
|
+
aspectRatio: aspectRatioCss,
|
|
104825
|
+
border: (theme) => `2px dashed ${theme.palette.primary.main}`,
|
|
105177
104826
|
borderRadius: 1,
|
|
105178
|
-
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
105179
|
-
|
|
105180
|
-
|
|
105181
|
-
position: 'absolute',
|
|
105182
|
-
top: '50%',
|
|
105183
|
-
left: '50%',
|
|
105184
|
-
transform: 'translate(-50%, -50%)',
|
|
104827
|
+
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
104828
|
+
? 'rgba(25, 118, 210, 0.1)'
|
|
104829
|
+
: 'rgba(25, 118, 210, 0.05)',
|
|
105185
104830
|
display: 'flex',
|
|
105186
104831
|
flexDirection: 'column',
|
|
105187
104832
|
alignItems: 'center',
|
|
105188
|
-
|
|
104833
|
+
justifyContent: 'center',
|
|
104834
|
+
gap: 2,
|
|
104835
|
+
p: 3
|
|
105189
104836
|
} },
|
|
105190
104837
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 40, sx: { color: 'primary.main' } }),
|
|
105191
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "
|
|
105192
|
-
|
|
105193
|
-
|
|
105194
|
-
|
|
105195
|
-
|
|
105196
|
-
|
|
105197
|
-
|
|
105198
|
-
|
|
105199
|
-
|
|
105200
|
-
|
|
105201
|
-
|
|
105202
|
-
|
|
105203
|
-
|
|
105204
|
-
|
|
105205
|
-
|
|
105206
|
-
|
|
105207
|
-
|
|
105208
|
-
|
|
105209
|
-
|
|
105210
|
-
|
|
105211
|
-
|
|
105212
|
-
|
|
105213
|
-
|
|
105214
|
-
|
|
105215
|
-
|
|
105216
|
-
|
|
105217
|
-
|
|
105218
|
-
|
|
105219
|
-
|
|
105220
|
-
|
|
105221
|
-
|
|
105222
|
-
|
|
105223
|
-
|
|
105224
|
-
|
|
105225
|
-
|
|
105226
|
-
|
|
105227
|
-
|
|
105228
|
-
|
|
105229
|
-
|
|
105230
|
-
|
|
105231
|
-
|
|
105232
|
-
|
|
105233
|
-
|
|
105234
|
-
|
|
105235
|
-
|
|
105236
|
-
|
|
105237
|
-
|
|
105238
|
-
|
|
105239
|
-
|
|
105240
|
-
|
|
105241
|
-
|
|
105242
|
-
|
|
105243
|
-
|
|
105244
|
-
|
|
105245
|
-
|
|
105246
|
-
|
|
105247
|
-
|
|
105248
|
-
imageData.
|
|
105249
|
-
|
|
105250
|
-
|
|
105251
|
-
|
|
105252
|
-
|
|
105253
|
-
|
|
105254
|
-
|
|
105255
|
-
|
|
105256
|
-
|
|
105257
|
-
|
|
105258
|
-
|
|
105259
|
-
|
|
105260
|
-
|
|
105261
|
-
|
|
105262
|
-
},
|
|
105263
|
-
|
|
105264
|
-
|
|
105265
|
-
|
|
105266
|
-
|
|
105267
|
-
|
|
105268
|
-
|
|
105269
|
-
|
|
105270
|
-
|
|
105271
|
-
|
|
105272
|
-
|
|
105273
|
-
|
|
105274
|
-
} }),
|
|
105275
|
-
|
|
105276
|
-
imageData.
|
|
105277
|
-
imageData.
|
|
105278
|
-
|
|
105279
|
-
|
|
105280
|
-
|
|
105281
|
-
|
|
105282
|
-
|
|
105283
|
-
|
|
105284
|
-
|
|
105285
|
-
|
|
105286
|
-
|
|
105287
|
-
|
|
105288
|
-
|
|
105289
|
-
|
|
105290
|
-
|
|
105291
|
-
|
|
105292
|
-
|
|
105293
|
-
|
|
105294
|
-
|
|
105295
|
-
|
|
104838
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2", sx: { color: 'text.secondary', textAlign: 'center', fontWeight: 'bold' } }, "\u0413\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F..."),
|
|
104839
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'text.secondary', textAlign: 'center' } }, imageData.approach),
|
|
104840
|
+
generatingImages && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'text.secondary', textAlign: 'center' } },
|
|
104841
|
+
"\u23F1\uFE0F \u041F\u0440\u043E\u0448\u043B\u043E: ",
|
|
104842
|
+
formatElapsedTime(elapsedTime.images))))) : imageData.imageUrl ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
|
|
104843
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { component: "img", src: imageData.imageUrl, alt: `Generated Image ${imageData.index}`, sx: {
|
|
104844
|
+
width: '100%',
|
|
104845
|
+
height: 'auto',
|
|
104846
|
+
display: 'block',
|
|
104847
|
+
objectFit: 'contain',
|
|
104848
|
+
border: (theme) => {
|
|
104849
|
+
if (imageData.uploaded || imageData.checkStatus === 'ok') {
|
|
104850
|
+
return `2px solid ${theme.palette.success.main}`;
|
|
104851
|
+
}
|
|
104852
|
+
else if (imageData.checkStatus === 'needs_rebuild') {
|
|
104853
|
+
return `2px solid ${theme.palette.error.main}`;
|
|
104854
|
+
}
|
|
104855
|
+
else if (imageData.checkStatus === 'checking') {
|
|
104856
|
+
return `2px solid ${theme.palette.warning.main}`;
|
|
104857
|
+
}
|
|
104858
|
+
else {
|
|
104859
|
+
return `2px solid ${theme.palette.primary.main}`;
|
|
104860
|
+
}
|
|
104861
|
+
},
|
|
104862
|
+
borderRadius: 1,
|
|
104863
|
+
backgroundColor: (theme) => theme.palette.mode === 'dark' ? '#2a2a2a' : '#f5f5f5'
|
|
104864
|
+
} }),
|
|
104865
|
+
imageData.regenerating && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
104866
|
+
position: 'absolute',
|
|
104867
|
+
top: '50%',
|
|
104868
|
+
left: '50%',
|
|
104869
|
+
transform: 'translate(-50%, -50%)',
|
|
104870
|
+
display: 'flex',
|
|
104871
|
+
flexDirection: 'column',
|
|
104872
|
+
alignItems: 'center',
|
|
104873
|
+
gap: 1
|
|
104874
|
+
} },
|
|
104875
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 40, sx: { color: 'primary.main' } }),
|
|
104876
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'white', fontWeight: 'bold', textShadow: '1px 1px 2px rgba(0,0,0,0.8)' } }, "\u041F\u0435\u0440\u0435\u0434\u0435\u043B\u043A\u0430..."))))) : imageData.failed ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
104877
|
+
width: '100%',
|
|
104878
|
+
aspectRatio: aspectRatioCss,
|
|
104879
|
+
border: (theme) => `2px dashed ${theme.palette.error.main}`,
|
|
104880
|
+
borderRadius: 1,
|
|
104881
|
+
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
104882
|
+
? 'rgba(244, 67, 54, 0.1)'
|
|
104883
|
+
: '#ffebee',
|
|
104884
|
+
display: 'flex',
|
|
104885
|
+
flexDirection: 'column',
|
|
104886
|
+
alignItems: 'center',
|
|
104887
|
+
justifyContent: 'center',
|
|
104888
|
+
gap: 2,
|
|
104889
|
+
p: 3
|
|
104890
|
+
} },
|
|
104891
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h4", sx: { color: 'error.main' } }, "\u274C"),
|
|
104892
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2", sx: { color: 'error.main', textAlign: 'center', fontWeight: 'bold' } }, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C"),
|
|
104893
|
+
imageData.errorMessage && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'error.main', textAlign: 'center', fontSize: '0.7rem' } }, imageData.errorMessage.length > 100
|
|
104894
|
+
? imageData.errorMessage.substring(0, 100) + '...'
|
|
104895
|
+
: imageData.errorMessage)))) : (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
104896
|
+
width: '100%',
|
|
104897
|
+
aspectRatio: aspectRatioCss,
|
|
104898
|
+
border: (theme) => `2px dashed ${theme.palette.divider}`,
|
|
104899
|
+
borderRadius: 1,
|
|
104900
|
+
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
104901
|
+
? 'rgba(255,255,255,0.04)'
|
|
104902
|
+
: '#f5f5f5',
|
|
104903
|
+
display: 'flex',
|
|
104904
|
+
flexDirection: 'column',
|
|
104905
|
+
alignItems: 'center',
|
|
104906
|
+
justifyContent: 'center',
|
|
104907
|
+
gap: 2,
|
|
104908
|
+
p: 3
|
|
104909
|
+
} },
|
|
104910
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h4", sx: { color: 'text.secondary' } }, "\u23F3"),
|
|
104911
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2", sx: { color: 'text.secondary', textAlign: 'center', fontWeight: 'bold' } }, "\u0412 \u043E\u0447\u0435\u0440\u0435\u0434\u0438"),
|
|
104912
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'text.secondary', textAlign: 'center' } }, generatingImages ? 'Ожидает генерации...' : 'Готово к генерации'),
|
|
104913
|
+
generatingImages && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'text.secondary', textAlign: 'center' } },
|
|
104914
|
+
"\u23F1\uFE0F \u041F\u0440\u043E\u0448\u043B\u043E: ",
|
|
104915
|
+
formatElapsedTime(elapsedTime.images))))),
|
|
104916
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { mt: 1, mb: 1 } },
|
|
104917
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { display: 'block', fontWeight: 'bold' } },
|
|
104918
|
+
imageData.index,
|
|
104919
|
+
". ",
|
|
104920
|
+
imageData.approach),
|
|
104921
|
+
imageData.checkStatus === 'ok' && imageData.checkResult !== 'Проверка отключена' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'success.main', display: 'block', fontWeight: 'bold' } }, "\u2705 \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043F\u0440\u043E\u0439\u0434\u0435\u043D\u0430")),
|
|
104922
|
+
imageData.checkStatus === 'needs_rebuild' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
104923
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'error.main', display: 'block', fontWeight: 'bold' } }, imageData.checkFailed ? '⚠️ Ошибка проверки' : '❌ Требует пересборки'),
|
|
104924
|
+
imageData.checkErrors && imageData.checkErrors.length > 0 && !imageData.checkFailed && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { mt: 0.5 } },
|
|
104925
|
+
imageData.checkErrors.slice(0, 2).map((error, idx) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { key: idx, variant: "caption", sx: {
|
|
104926
|
+
color: 'error.main',
|
|
104927
|
+
display: 'block',
|
|
104928
|
+
fontSize: '0.7rem',
|
|
104929
|
+
lineHeight: 1.2
|
|
104930
|
+
} },
|
|
104931
|
+
"\u2022 ",
|
|
104932
|
+
error.length > 50 ? error.substring(0, 50) + '...' : error))),
|
|
104933
|
+
imageData.checkErrors.length > 2 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'text.secondary', fontSize: '0.7rem' } },
|
|
104934
|
+
"+",
|
|
104935
|
+
imageData.checkErrors.length - 2,
|
|
104936
|
+
" \u0435\u0449\u0451")))),
|
|
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")))),
|
|
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...")),
|
|
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")),
|
|
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")),
|
|
104941
|
+
imageData.uploaded && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'success.main', display: 'block', mt: 0.5 } }, "\u2713 \u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043E"))),
|
|
104942
|
+
!imageData.generating && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', gap: 1, flexDirection: 'column', mt: 1 } },
|
|
104943
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], { size: "small", multiline: true, minRows: 2, maxRows: 4, fullWidth: true, placeholder: "\u0423\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u043A\u0438 (\u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E \u043F\u0440\u0438 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u0430\u0445)", value: imageData.customRegeneratePrompt || '', onChange: (e) => {
|
|
104944
|
+
setGeneratedImagesData(prev => prev.map(img => img.index === imageData.index
|
|
104945
|
+
? { ...img, customRegeneratePrompt: e.target.value }
|
|
104946
|
+
: img));
|
|
104947
|
+
}, disabled: imageData.regenerating ||
|
|
104948
|
+
imageData.uploading ||
|
|
104949
|
+
imageData.checkStatus === 'checking' ||
|
|
104950
|
+
(generatingImages && !imageData.imageUrl && !imageData.failed && !imageData.generating), sx: {
|
|
104951
|
+
'& .MuiInputBase-root': {
|
|
104952
|
+
fontSize: '0.875rem',
|
|
104953
|
+
backgroundColor: (theme) => imageData.checkStatus === 'needs_rebuild'
|
|
104954
|
+
? (theme.palette.mode === 'dark'
|
|
104955
|
+
? 'rgba(244, 67, 54, 0.15)'
|
|
104956
|
+
: 'rgba(244, 67, 54, 0.05)')
|
|
104957
|
+
: 'transparent'
|
|
104958
|
+
}
|
|
104959
|
+
} }),
|
|
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 ||
|
|
104961
|
+
imageData.uploading ||
|
|
104962
|
+
imageData.checkStatus === 'checking' ||
|
|
104963
|
+
!imageData.originalPrompt ||
|
|
104964
|
+
!imageData.productImageUrl ||
|
|
104965
|
+
(generatingImages && !imageData.imageUrl && !imageData.failed && !imageData.generating), fullWidth: true }, imageData.regenerating
|
|
104966
|
+
? (imageData.failed ? 'Генерация...' : 'Переделка...')
|
|
104967
|
+
: ((generatingImages && !imageData.imageUrl && !imageData.failed && !imageData.generating)
|
|
104968
|
+
? 'В очереди'
|
|
104969
|
+
: (!imageData.imageUrl ? 'Сгенерировать' : 'Переделать'))),
|
|
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 ||
|
|
104971
|
+
imageData.uploading ||
|
|
104972
|
+
imageData.checkStatus === 'checking' ||
|
|
104973
|
+
!imageData.originalPrompt ||
|
|
104974
|
+
!imageData.productImageUrl, fullWidth: true }, "\u041F\u0435\u0440\u0435\u0434\u0435\u043B\u0430\u0442\u044C \u0437\u0430\u043D\u043E\u0432\u043E")),
|
|
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: () => {
|
|
104976
|
+
const folderId = extractFolderId(driveFolderUrl);
|
|
104977
|
+
if (folderId) {
|
|
104978
|
+
handleUploadImage(imageData, folderId);
|
|
104979
|
+
}
|
|
104980
|
+
}, disabled: imageData.uploading || imageData.regenerating || !driveFolderUrl.trim(), fullWidth: true }, imageData.uploading ? 'Загрузка...' : 'Загрузить'))))));
|
|
104981
|
+
}))),
|
|
105296
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 } },
|
|
105297
|
-
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})`))))),
|
|
105298
104984
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_16__["default"], { sx: { my: 2 } }),
|
|
105299
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 } },
|
|
105300
104986
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true, sx: { fontWeight: 'bold' } },
|
|
@@ -105329,7 +105015,7 @@ ${imageData.originalPrompt}
|
|
|
105329
105015
|
const pairLabel = pairApproach ? pairApproach.name : `пара ${i + 1}`;
|
|
105330
105016
|
const pairGenerating = (titleData?.generating || textData?.generating);
|
|
105331
105017
|
const pairFailed = (!pairGenerating && titleData?.failed && textData?.failed);
|
|
105332
|
-
return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105018
|
+
return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_30__["default"], { key: i, variant: "outlined", sx: {
|
|
105333
105019
|
p: 2,
|
|
105334
105020
|
borderColor: pairFailed
|
|
105335
105021
|
? 'error.main'
|
|
@@ -105357,7 +105043,7 @@ ${imageData.originalPrompt}
|
|
|
105357
105043
|
"\u041F\u0430\u0440\u0430 ",
|
|
105358
105044
|
i + 1),
|
|
105359
105045
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", color: "text.secondary" }, pairLabel),
|
|
105360
|
-
translatingPairs && !pairTranslations[i] ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 13, sx: { color: 'text.disabled', ml: 0.5 } })) : pairTranslations[i] ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105046
|
+
translatingPairs && !pairTranslations[i] ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 13, sx: { color: 'text.disabled', ml: 0.5 } })) : pairTranslations[i] ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_27__["default"], { title: react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
105361
105047
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { fontWeight: 700, display: 'block', mb: 0.5 } }, "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A:"),
|
|
105362
105048
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { display: 'block', mb: 1 } }, pairTranslations[i].titleRu),
|
|
105363
105049
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { fontWeight: 700, display: 'block', mb: 0.5 } }, "\u0422\u0435\u043A\u0441\u0442:"),
|
|
@@ -105365,7 +105051,7 @@ ${imageData.originalPrompt}
|
|
|
105365
105051
|
tooltip: { sx: { maxWidth: 360, fontSize: 12, lineHeight: 1.6, p: '10px 14px' } },
|
|
105366
105052
|
} },
|
|
105367
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' } } },
|
|
105368
|
-
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),
|
|
105369
105055
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 1.5 },
|
|
105370
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 } },
|
|
105371
105057
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 16, sx: { color: 'primary.main' } }),
|
|
@@ -105406,7 +105092,7 @@ ${imageData.originalPrompt}
|
|
|
105406
105092
|
}))))),
|
|
105407
105093
|
(landingGenerationLogs.length > 0 || generatingLanding) && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { mb: 2 } },
|
|
105408
105094
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true, sx: { fontWeight: 'bold' } }, "\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043B\u0435\u043D\u0434\u0438\u043D\u0433\u0430"),
|
|
105409
|
-
landingGenerationLogs.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105095
|
+
landingGenerationLogs.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_30__["default"], { sx: {
|
|
105410
105096
|
p: 2,
|
|
105411
105097
|
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
105412
105098
|
? 'rgba(25, 118, 210, 0.1)'
|
|
@@ -105432,11 +105118,11 @@ ${imageData.originalPrompt}
|
|
|
105432
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 } },
|
|
105433
105119
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20 }),
|
|
105434
105120
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2", sx: { color: 'text.secondary' } }, formatElapsedTime(elapsedTime.landing)))),
|
|
105435
|
-
!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")))),
|
|
105436
105122
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2 },
|
|
105437
|
-
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')),
|
|
105438
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 } },
|
|
105439
|
-
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" })) },
|
|
105440
105126
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1 } },
|
|
105441
105127
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null,
|
|
105442
105128
|
"File Uploaded!",
|
|
@@ -105446,88 +105132,7 @@ ${imageData.originalPrompt}
|
|
|
105446
105132
|
getElectronAPI().openExternal(uploadedLink);
|
|
105447
105133
|
}, style: { cursor: 'pointer', textDecoration: 'underline', color: 'inherit' } }, "Click here to open in Google Drive")),
|
|
105448
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!")))))))))),
|
|
105449
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(
|
|
105450
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_30__["default"], { open: generatingProduct || uploadingProduct, sx: {
|
|
105451
|
-
position: 'absolute',
|
|
105452
|
-
zIndex: (theme) => theme.zIndex.modal + 1,
|
|
105453
|
-
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
105454
|
-
display: 'flex',
|
|
105455
|
-
flexDirection: 'column',
|
|
105456
|
-
gap: 3
|
|
105457
|
-
} },
|
|
105458
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 } },
|
|
105459
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 60, sx: { color: 'white' } }),
|
|
105460
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", sx: { color: 'white', fontWeight: 'bold' } }, formatElapsedTime(elapsedTime.product))),
|
|
105461
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_28__["default"], { sx: {
|
|
105462
|
-
p: 3,
|
|
105463
|
-
maxWidth: '80%',
|
|
105464
|
-
maxHeight: '60%',
|
|
105465
|
-
overflow: 'auto',
|
|
105466
|
-
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
105467
|
-
? 'rgba(30, 30, 30, 0.95)'
|
|
105468
|
-
: 'rgba(255, 255, 255, 0.95)'
|
|
105469
|
-
} },
|
|
105470
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true, sx: { mb: 2, fontWeight: 'bold' } }, uploadingProduct ? 'Процесс загрузки' : 'Процесс генерации'),
|
|
105471
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { id: "product-generation-logs", sx: {
|
|
105472
|
-
fontFamily: 'monospace',
|
|
105473
|
-
fontSize: '0.875rem',
|
|
105474
|
-
lineHeight: 1.6,
|
|
105475
|
-
whiteSpace: 'pre-wrap',
|
|
105476
|
-
wordBreak: 'break-word',
|
|
105477
|
-
maxHeight: '400px',
|
|
105478
|
-
overflowY: 'auto'
|
|
105479
|
-
} }, 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: {
|
|
105480
|
-
mb: 0.5,
|
|
105481
|
-
color: log.includes('❌') || log.toLowerCase().includes('error') ? 'error.main' :
|
|
105482
|
-
log.includes('⚠️') || log.toLowerCase().includes('warn') ? 'warning.main' :
|
|
105483
|
-
'text.primary'
|
|
105484
|
-
} }, log))))))),
|
|
105485
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_31__["default"], null,
|
|
105486
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6" }, "Product Image Generation")),
|
|
105487
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_32__["default"], null,
|
|
105488
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { spacing: 3, sx: { mt: 1 } },
|
|
105489
|
-
productSourceImages.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
105490
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "subtitle1", gutterBottom: true, sx: { fontWeight: 'bold' } },
|
|
105491
|
-
"\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F (",
|
|
105492
|
-
productSourceImages.length,
|
|
105493
|
-
"):"),
|
|
105494
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
105495
|
-
display: 'grid',
|
|
105496
|
-
gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' },
|
|
105497
|
-
gap: 2,
|
|
105498
|
-
mt: 1
|
|
105499
|
-
} }, 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: {
|
|
105500
|
-
width: '100%',
|
|
105501
|
-
height: 'auto',
|
|
105502
|
-
maxHeight: 300,
|
|
105503
|
-
objectFit: 'contain',
|
|
105504
|
-
border: (theme) => `1px solid ${theme.palette.mode === 'dark' ? '#444' : '#ddd'}`,
|
|
105505
|
-
borderRadius: 1
|
|
105506
|
-
}, onError: (e) => {
|
|
105507
|
-
// Fallback: try using thumbnail URL if direct view fails
|
|
105508
|
-
const fileIdMatch = url.match(/\/file\/d\/([a-zA-Z0-9_-]+)/);
|
|
105509
|
-
if (fileIdMatch) {
|
|
105510
|
-
const fileId = fileIdMatch[1];
|
|
105511
|
-
e.target.src = `https://drive.google.com/thumbnail?id=${fileId}&sz=w1000`;
|
|
105512
|
-
}
|
|
105513
|
-
} })))))),
|
|
105514
|
-
productGeneratedImage && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], null,
|
|
105515
|
-
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:"),
|
|
105516
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { component: "img", src: productGeneratedImage, alt: "Generated Product", sx: {
|
|
105517
|
-
width: '100%',
|
|
105518
|
-
height: 'auto',
|
|
105519
|
-
maxHeight: 400,
|
|
105520
|
-
objectFit: 'contain',
|
|
105521
|
-
border: (theme) => `2px solid ${theme.palette.primary.main}`,
|
|
105522
|
-
borderRadius: 1,
|
|
105523
|
-
mt: 1
|
|
105524
|
-
} }))),
|
|
105525
|
-
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 }))),
|
|
105526
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_33__["default"], { sx: { px: 3, pb: 2 } },
|
|
105527
|
-
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"),
|
|
105528
|
-
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_37__["default"], null) }, generatingProduct ? 'Перегенерация...' : 'Перегенерировать'),
|
|
105529
|
-
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_41__["default"], null) }, uploadingProduct ? 'Загрузка...' : 'Загрузить (product.png)'))),
|
|
105530
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_PromptManagerDialog__WEBPACK_IMPORTED_MODULE_51__["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) }))));
|
|
105531
105136
|
}
|
|
105532
105137
|
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (App);
|
|
105533
105138
|
|
|
@@ -105962,12 +105567,15 @@ function PromptManagerDialog({ open, onClose }) {
|
|
|
105962
105567
|
const [viewModes, setViewModes] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
|
|
105963
105568
|
// selectedApproaches: array of indices from PAIR_APPROACH_POOL, ordered
|
|
105964
105569
|
const [selectedApproaches, setSelectedApproaches] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([0, 1, 2]);
|
|
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));
|
|
105965
105572
|
// Загрузить оверрайды при открытии
|
|
105966
105573
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
105967
105574
|
if (open) {
|
|
105968
105575
|
const loaded = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_36__.loadPromptOverrides)();
|
|
105969
105576
|
setOverrides(loaded);
|
|
105970
105577
|
setSelectedApproaches((0,_promptOverrides__WEBPACK_IMPORTED_MODULE_36__.getSelectedPairApproaches)());
|
|
105578
|
+
setImageApproachCounts((0,_promptOverrides__WEBPACK_IMPORTED_MODULE_36__.getImageApproachCounts)());
|
|
105971
105579
|
setHasChanges(false);
|
|
105972
105580
|
}
|
|
105973
105581
|
}, [open]);
|
|
@@ -105992,6 +105600,17 @@ function PromptManagerDialog({ open, onClose }) {
|
|
|
105992
105600
|
return next;
|
|
105993
105601
|
});
|
|
105994
105602
|
};
|
|
105603
|
+
const handleImageApproachCountChange = (idx, count) => {
|
|
105604
|
+
const clamped = Math.max(0, Math.min(4, count));
|
|
105605
|
+
setImageApproachCounts(prev => {
|
|
105606
|
+
const next = [...prev];
|
|
105607
|
+
if (idx >= 0 && idx < next.length)
|
|
105608
|
+
next[idx] = clamped;
|
|
105609
|
+
setOverrides(o => ({ ...o, imageApproachCounts: next }));
|
|
105610
|
+
setHasChanges(true);
|
|
105611
|
+
return next;
|
|
105612
|
+
});
|
|
105613
|
+
};
|
|
105995
105614
|
const handleToggleOverride = (promptName, enabled) => {
|
|
105996
105615
|
setOverrides(prev => {
|
|
105997
105616
|
const prevOverride = prev[promptName];
|
|
@@ -106169,14 +105788,14 @@ function PromptManagerDialog({ open, onClose }) {
|
|
|
106169
105788
|
hasChanges && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_7__["default"], { label: "\u0415\u0441\u0442\u044C \u043D\u0435\u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043D\u044B\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F", color: "warning", size: "small" })))),
|
|
106170
105789
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_15__["default"], null,
|
|
106171
105790
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_16__["default"], { value: tabValue, onChange: handleTabChange, sx: { borderBottom: 1, borderColor: 'divider' } },
|
|
106172
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { label: "\
|
|
105791
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { label: "\u041F\u043E\u0434\u0445\u043E\u0434\u044B" }),
|
|
106173
105792
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { label: "\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F" }),
|
|
106174
105793
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { label: "\u0412\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u044F" }),
|
|
106175
105794
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { label: "\u041B\u0435\u043D\u0434\u0438\u043D\u0433" })),
|
|
106176
105795
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(TabPanel, { value: tabValue, index: 0 },
|
|
106177
105796
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { mb: 3, p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 1 } },
|
|
106178
105797
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { direction: "row", spacing: 2, alignItems: "center", sx: { mb: 1.5 } },
|
|
106179
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_3__["default"], { variant: "subtitle1", sx: { fontWeight: 600 } }, "\u041F\u043E\u0434\u0445\u043E\u0434\u044B \u0434\u043B\u044F \
|
|
105798
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_3__["default"], { variant: "subtitle1", sx: { fontWeight: 600 } }, "\u041F\u043E\u0434\u0445\u043E\u0434\u044B \u0434\u043B\u044F \u0442\u0435\u043A\u0441\u0442\u043E\u0432 (\u043F\u0430\u0440\u044B \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A + \u0442\u0435\u043A\u0441\u0442)"),
|
|
106180
105799
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_7__["default"], { label: `Выбрано: ${selectedApproaches.length}`, color: "primary", size: "small" }),
|
|
106181
105800
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_3__["default"], { variant: "caption", color: "text.secondary" }, "(\u043C\u0438\u043D\u0438\u043C\u0443\u043C 2, \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C 10)")),
|
|
106182
105801
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { overflowX: 'auto' } },
|
|
@@ -106204,11 +105823,34 @@ function PromptManagerDialog({ open, onClose }) {
|
|
|
106204
105823
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", { style: { padding: '4px 8px', color: '#777' } }, p.titleApproach),
|
|
106205
105824
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", { style: { padding: '4px 8px', color: '#777' } }, p.textApproach.split('\n')[0])));
|
|
106206
105825
|
}))))),
|
|
105826
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { mb: 3, p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 1 } },
|
|
105827
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { direction: "row", spacing: 2, alignItems: "center", sx: { mb: 1.5 } },
|
|
105828
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_3__["default"], { variant: "subtitle1", sx: { fontWeight: 600 } }, "\u041F\u043E\u0434\u0445\u043E\u0434\u044B \u0434\u043B\u044F \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0439"),
|
|
105829
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_7__["default"], { label: `Всего: ${imageApproachCounts.reduce((a, b) => a + b, 0)}`, color: "primary", size: "small" }),
|
|
105830
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_3__["default"], { variant: "caption", color: "text.secondary" }, "(0\u20134 \u043D\u0430 \u043A\u0430\u0436\u0434\u044B\u0439 \u043F\u043E\u0434\u0445\u043E\u0434)")),
|
|
105831
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { overflowX: 'auto' } },
|
|
105832
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: 12 } },
|
|
105833
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("thead", null,
|
|
105834
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tr", null,
|
|
105835
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", { style: { textAlign: 'center', padding: '4px 8px', borderBottom: '1px solid #ccc', width: 70 } }, "\u041A\u043E\u043B-\u0432\u043E"),
|
|
105836
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", { style: { textAlign: 'left', padding: '4px 8px', borderBottom: '1px solid #ccc', whiteSpace: 'nowrap', width: 180 } }, "\u041F\u043E\u0434\u0445\u043E\u0434"),
|
|
105837
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", { style: { textAlign: 'left', padding: '4px 8px', borderBottom: '1px solid #ccc' } }, "\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435"))),
|
|
105838
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tbody", null, _prompts__WEBPACK_IMPORTED_MODULE_34__.CREO_APPROACHES.map((approach, i) => {
|
|
105839
|
+
const count = imageApproachCounts[i] ?? 0;
|
|
105840
|
+
return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tr", { key: i, style: { opacity: count > 0 ? 1 : 0.6 } },
|
|
105841
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", { style: { textAlign: 'center', padding: '2px 8px', verticalAlign: 'middle' } },
|
|
105842
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { type: "number", size: "small", value: count, onChange: (e) => {
|
|
105843
|
+
const v = parseInt(e.target.value, 10);
|
|
105844
|
+
handleImageApproachCountChange(i, isNaN(v) ? 0 : v);
|
|
105845
|
+
}, inputProps: { min: 0, max: 4, step: 1 }, sx: { width: 56, '& .MuiInputBase-input': { textAlign: 'center', py: 0.5 } } })),
|
|
105846
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", { style: { padding: '4px 8px', fontWeight: 500, whiteSpace: 'nowrap' } }, approach.name),
|
|
105847
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", { style: { padding: '4px 8px', color: '#777' } }, approach.prompt.split('\n')[0])));
|
|
105848
|
+
}))))),
|
|
106207
105849
|
renderPromptEditor('getPairsSystemPrompt', 'Системный промпт (пары заголовок + текст)', 'Единый системный промпт, генерирующий все пары одним запросом. Переменные: ${geo}, ${count}, ${n}'),
|
|
106208
105850
|
renderPromptEditor('getPairsUserPrompt', 'Пользовательский промпт (пары)', 'Пользовательский промпт для генерации пар. Переменные: ${product}, ${geo}, ${additionalInfo}, ${count}, ${n}')),
|
|
106209
105851
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(TabPanel, { value: tabValue, index: 1 },
|
|
106210
105852
|
renderPromptEditor('getImageGenerationBasePrompt', 'Базовый промпт для изображений', 'Базовый промпт, используемый для всех подходов'),
|
|
106211
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_3__["default"], { variant: "h6", sx: { mt: 3, mb: 2 } }, "\
|
|
105853
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_3__["default"], { variant: "h6", sx: { mt: 3, mb: 2 } }, "\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u043F\u043E\u0434\u0445\u043E\u0434\u043E\u0432 \u0434\u043B\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438 \u043A\u0440\u0435\u0430\u0442\u0438\u0432\u043E\u0432"),
|
|
106212
105854
|
_prompts__WEBPACK_IMPORTED_MODULE_34__.CREO_APPROACHES.map((approach) => {
|
|
106213
105855
|
const override = overrides.creoApproaches?.[approach.name];
|
|
106214
105856
|
const enabled = override?.enabled || false;
|
|
@@ -106478,6 +106120,7 @@ const MODELS = {
|
|
|
106478
106120
|
__webpack_require__.r(__webpack_exports__);
|
|
106479
106121
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
106480
106122
|
/* harmony export */ getCreoApproachOverride: () => (/* binding */ getCreoApproachOverride),
|
|
106123
|
+
/* harmony export */ getImageApproachCounts: () => (/* binding */ getImageApproachCounts),
|
|
106481
106124
|
/* harmony export */ getPairsCount: () => (/* binding */ getPairsCount),
|
|
106482
106125
|
/* harmony export */ getPromptOverride: () => (/* binding */ getPromptOverride),
|
|
106483
106126
|
/* harmony export */ getSelectedPairApproaches: () => (/* binding */ getSelectedPairApproaches),
|
|
@@ -106525,6 +106168,33 @@ function getSelectedPairApproaches() {
|
|
|
106525
106168
|
function getPairsCount() {
|
|
106526
106169
|
return getSelectedPairApproaches().length;
|
|
106527
106170
|
}
|
|
106171
|
+
/** Количество подходов для изображений. Должно совпадать с CREO_APPROACHES.length в prompts.ts */
|
|
106172
|
+
const IMAGE_APPROACH_COUNT = 10;
|
|
106173
|
+
/**
|
|
106174
|
+
* Получить количество изображений по каждому подходу (N элементов, 0–4).
|
|
106175
|
+
* По умолчанию — по 1 на каждый подход.
|
|
106176
|
+
*/
|
|
106177
|
+
function getImageApproachCounts() {
|
|
106178
|
+
const overrides = loadPromptOverrides();
|
|
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);
|
|
106186
|
+
}
|
|
106187
|
+
// backward compat: selectedImageApproaches -> counts (1 for selected, 0 for not)
|
|
106188
|
+
if (overrides.selectedImageApproaches && overrides.selectedImageApproaches.length >= 1) {
|
|
106189
|
+
const counts = Array(IMAGE_APPROACH_COUNT).fill(0);
|
|
106190
|
+
for (const i of overrides.selectedImageApproaches) {
|
|
106191
|
+
if (i >= 0 && i < IMAGE_APPROACH_COUNT)
|
|
106192
|
+
counts[i] = 1;
|
|
106193
|
+
}
|
|
106194
|
+
return counts;
|
|
106195
|
+
}
|
|
106196
|
+
return defaultCounts;
|
|
106197
|
+
}
|
|
106528
106198
|
/**
|
|
106529
106199
|
* Загрузить оверрайды из localStorage
|
|
106530
106200
|
*/
|
|
@@ -106687,7 +106357,8 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
106687
106357
|
/* harmony export */ getTextsSystemPrompt: () => (/* binding */ getTextsSystemPrompt),
|
|
106688
106358
|
/* harmony export */ getTitlesSystemPrompt: () => (/* binding */ getTitlesSystemPrompt),
|
|
106689
106359
|
/* harmony export */ getUserPrompt: () => (/* binding */ getUserPrompt),
|
|
106690
|
-
/* harmony export */ getValidationPrompt: () => (/* binding */ getValidationPrompt)
|
|
106360
|
+
/* harmony export */ getValidationPrompt: () => (/* binding */ getValidationPrompt),
|
|
106361
|
+
/* harmony export */ pickRandomBullets: () => (/* binding */ pickRandomBullets)
|
|
106691
106362
|
/* harmony export */ });
|
|
106692
106363
|
/* harmony import */ var _promptOverrides__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./promptOverrides */ "./src/promptOverrides.ts");
|
|
106693
106364
|
/**
|
|
@@ -106695,6 +106366,56 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
106695
106366
|
* Все промпты на русском языке для консистентности
|
|
106696
106367
|
*/
|
|
106697
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
|
+
}
|
|
106698
106419
|
// Debug logging — пишет в терминал Electron (или в console как fallback)
|
|
106699
106420
|
function _debugLog(...args) {
|
|
106700
106421
|
const api = typeof window !== 'undefined' ? window.electronAPI : null;
|
|
@@ -106826,16 +106547,13 @@ function getTitlesSystemPrompt(geo, noOverride, count = 3) {
|
|
|
106826
106547
|
- НЕ упоминай название продукта в заголовке — оно уже на креативе. Заголовок = только боль + решение/триггер
|
|
106827
106548
|
|
|
106828
106549
|
Технические требования:
|
|
106829
|
-
- Каждый заголовок
|
|
106830
|
-
- Каждый заголовок
|
|
106550
|
+
- Каждый заголовок может быть 1–4 строки. Допускается до 12 слов (при многострочности)
|
|
106551
|
+
- Каждый заголовок предпочтительно до 55 символов в строке. При необходимости для разнообразия допускается до 60 символов в строке
|
|
106831
106552
|
- ЗАПРЕЩЕНО использовать двоеточие (:) в заголовках. Используй тире (—) или переформулируй заголовок без двоеточия. Если в заголовке есть двоеточие — это критическая ошибка, перепиши заголовок
|
|
106832
106553
|
- Используй 1-2 эмодзи естественно внутри заголовка, не только в конце. Эмодзи должны заменять или визуально отмечать смысл (эмоция, действие, предупреждение), а не украшать или заполнять пространство. Максимум 1-2 на строку
|
|
106833
106554
|
- Избегай эмодзи ⚠️ и 🚨 — они ассоциируются с опасностью. Для привлечения внимания используй 👉, ✅, 🌿, ⏳
|
|
106834
|
-
- КРИТИЧНО: Каждый заголовок
|
|
106555
|
+
- КРИТИЧНО: Каждый заголовок ОБЯЗАН содержать ключевое слово проблемы ЯВНО. Примеры по категориям: простата → простатит, простата; похудение → лишний вес, похудение; суставы → боль в суставах, колени, скованность; пищеварение → дискомфорт, вздутие, тяжесть; сон → бессонница, усталость. Если ключевое слово отсутствует — перепиши заголовок
|
|
106835
106556
|
- Используй знания лучших практик рекламного копирайтинга
|
|
106836
|
-
- Избегай только жёстких медицинских гарантий («лечит», «вылечивает», «гарантируем»). Мягкие обещания результата разрешены («Nopți fără treziri», «Stomac liniștit», «Efect rapid»). Разрешены: сроки («în 14 zile»), цифры соц. доказательства («9 din 10 bărbați»), мягкие результаты. Запрещены: «100% гарантия», «навсегда», «излечение».
|
|
106837
|
-
- Избегай только: прямых медицинских диагнозов, слов «лечит/вылечивает/гарантируем», обещаний «100%/навсегда». Всё остальное — разрешено.
|
|
106838
|
-
- Если в категории продукта есть специфические медицинские термины или диагнозы — используй их осторожно, не в каждом заголовке. Максимум ${maxMedTerms} заголовок из ${n} может содержать прямое упоминание таких терминов. Предпочитай мягкие формулировки, описывающие симптомы и дискомфорт, а не диагнозы. Если все ${n} заголовков содержат прямые медицинские термины — перепиши все кроме ${maxMedTerms} с мягкими альтернативами, фокусируясь на ощущениях и симптомах
|
|
106839
106557
|
- После создания проверь их правильность и разнообразие
|
|
106840
106558
|
- Верни только заголовки, по одному на строку, без нумерации или буллетов
|
|
106841
106559
|
- Заголовки должны быть на языке ${geo}
|
|
@@ -106863,8 +106581,8 @@ function getTextsSystemPrompt(geo, noOverride, count = 3) {
|
|
|
106863
106581
|
.map((p, i) => `- Текст ${i + 1} → [${p.name}]. ${p.textApproach}`)
|
|
106864
106582
|
.join('\n');
|
|
106865
106583
|
const deadlineNote = n >= 2
|
|
106866
|
-
? `- В Тексте 1 ИЛИ Тексте 2 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (в языке GEO)
|
|
106867
|
-
: `- В Тексте 1 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (в языке GEO)
|
|
106584
|
+
? `- В Тексте 1 ИЛИ Тексте 2 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (в языке GEO).`
|
|
106585
|
+
: `- В Тексте 1 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (в языке GEO).`;
|
|
106868
106586
|
return `Ты — эксперт-копирайтер, специализирующийся на рекламе в Meta Ads (Facebook/Instagram). Твоя задача — создать ровно ${n} идеальных продающих рекламных текста для Meta Ads на языке ${geo}.
|
|
106869
106587
|
|
|
106870
106588
|
ВАЖНО — СИСТЕМА ПАРЫ (заголовок + текст):
|
|
@@ -106894,24 +106612,18 @@ ${deadlineNote}
|
|
|
106894
106612
|
- Избегай академических, синтетических или звучащих как ИИ слов и фраз
|
|
106895
106613
|
- Избегай искусственного или неестественного формулирования, которое реальные рекламодатели не использовали бы
|
|
106896
106614
|
- Если фраза звучит синтетически или академически — перепиши её
|
|
106897
|
-
- Избегай медицинской терминологии, которую обычный человек не использует в разговоре («secreția biliară», «diureza», «peristaltică»). Пиши так, как человек описал бы проблему другу
|
|
106898
|
-
|
|
106899
106615
|
Требования к содержанию:
|
|
106900
106616
|
- КРИТИЧНО: Держи каждый текст кратким — цель примерно 150-280 символов на текст. Тексты в стиле отзыва могут использовать до 300 символов при необходимости для естественного повествования. Будь эффектным, но коротким
|
|
106901
106617
|
- Если текст длиннее 280 символов (или 300 для стиля отзыва) — сожми и перепиши, сохраняя смысл
|
|
106902
106618
|
- КРИТИЧНО: Каждый текст должен содержать хотя бы одно ясное ключевое слово проблемы, релевантное категории продукта (например, для суставов: боль/скованность/подвижность; для пищеварения: дискомфорт/тяжесть; для сна: бессонница/усталость — примеры только для формата, адаптируй под категорию). Если ключевое слово проблемы отсутствует — перепиши текст
|
|
106903
106619
|
- КРИТИЧНО: Каждый текст должен заканчиваться сильным и явным призывом к действию (кликни, попробуй сейчас, закажи сегодня, узнай больше и т.д.)
|
|
106904
106620
|
- Избегай слабых окончаний или информационного тона. Слабые или нейтральные окончания не допускаются
|
|
106905
|
-
- CTA должен быть “нажимным”: допускаются формулировки «не откладывай», «последний шанс», «только сегодня», «забери -50%» (всё строго на языке GEO)
|
|
106621
|
+
- CTA должен быть “нажимным”: допускаются формулировки «не откладывай», «последний шанс», «только сегодня», «забери -50%» (всё строго на языке GEO)
|
|
106906
106622
|
- В каждом тексте строго следуй заданному формату старта согласно его подходу (см. распределение выше)
|
|
106907
106623
|
- Если ингредиенты предоставлены в дополнительной информации, упоминай их ТОЛЬКО если они естественно подходят выбранному рекламному подходу. Например: подходы авторитета/экспертизы выигрывают от деталей ингредиентов, в то время как подходы срочности/эмоционального триггера могут не нуждаться в них. Упоминай только релевантные ингредиенты, не обязательно все из них. Если ингредиенты не подходят подходу или не предоставлены — полностью пропусти их упоминание и не изобретай и не упоминай никакие ингредиенты
|
|
106908
106624
|
- Добавляй только существенные поддерживающие детали, которые усиливают убеждение или ясность, а не заполнители или общие утверждения. Будь кратким и прямым
|
|
106909
106625
|
- Используй 1-4 эмодзи естественно внутри текста, не только в конце. Эмодзи должны заменять или визуально отмечать смысл (эмоция, действие, предупреждение), а не украшать или заполнять пространство. Максимум 1-2 на строку
|
|
106910
106626
|
- Избегай эмодзи ⚠️ и 🚨 — они ассоциируются с опасностью. Для привлечения внимания используй 👉, ✅, 🌿, ⏳
|
|
106911
|
-
- Разрешены формулировки: «reduce frecvența», «elimină disconfortul», «rezultat vizibil în X zile», «9 din 10 observă diferența». Запрещены только: «лечит/vindecă», «гарантируем 100%», «навсегда/definitiv».
|
|
106912
|
-
- Ты можешь усиливать эффект и результаты, но без прямых медицинских обещаний
|
|
106913
|
-
- Избегай только: прямых медицинских диагнозов, слов «лечит/вылечивает/гарантируем», обещаний «100%/навсегда». Всё остальное — разрешено.
|
|
106914
|
-
- Если в категории продукта есть специфические медицинские термины или диагнозы — используй их осторожно, не в каждом тексте. Максимум ${maxMedTerms} текст из ${n} может содержать прямое упоминание таких терминов. Остальные тексты фокусируй на симптомах, дискомфорте и ощущениях пользователя, релевантных категории продукта. Избегай эвфемизмов и искусственных замен — используй естественные формулировки
|
|
106915
106627
|
- Избегай прямых упоминаний интимных или деликатных деталей, связанных с проблемой. Используй мягкие формулировки, описывающие дискомфорт и симптомы, а не физиологические процессы
|
|
106916
106628
|
- Избегай повторения полного названия продукта более 2 раз в одном тексте. Используй местоимения или более короткие ссылки при необходимости
|
|
106917
106629
|
|
|
@@ -106974,8 +106686,8 @@ function getPairsSystemPrompt(geo, noOverride, count = 3, selectedIndices) {
|
|
|
106974
106686
|
.map((p, i) => `Пара ${i + 1} → [${p.name}]:\n Заголовок: ${p.titleApproach}\n Текст: ${p.textApproach}`)
|
|
106975
106687
|
.join('\n');
|
|
106976
106688
|
const deadlineNote = n >= 2
|
|
106977
|
-
? `- В Тексте 1 ИЛИ Тексте 2 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (на языке ${geo})
|
|
106978
|
-
: `- В Тексте 1 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (на языке ${geo})
|
|
106689
|
+
? `- В Тексте 1 ИЛИ Тексте 2 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (на языке ${geo}).`
|
|
106690
|
+
: `- В Тексте 1 ОБЯЗАТЕЛЬНО укажи ожидаемый срок результата 7–14 дней (на языке ${geo}).`;
|
|
106979
106691
|
return `Ты — эксперт-копирайтер, специализирующийся на рекламе в Meta Ads (Facebook/Instagram). Создай ровно ${n} пар «заголовок + текст» для рекламы на языке ${geo}.
|
|
106980
106692
|
|
|
106981
106693
|
ФОРМАТ ОТВЕТА (строго соблюдай, без лишних символов):
|
|
@@ -107001,7 +106713,6 @@ ${distributionLines}
|
|
|
107001
106713
|
- 1–2 эмодзи естественно внутри (не только в конце); избегай ⚠️ и 🚨
|
|
107002
106714
|
- Хотя бы одно ключевое слово проблемы, релевантное продукту
|
|
107003
106715
|
- НЕ упоминай название продукта — заголовок = боль + триггер
|
|
107004
|
-
- Максимум ${maxMedTerms} из ${n} заголовков могут содержать прямые медицинские термины
|
|
107005
106716
|
- Звучит как живая рекламная фраза, а не строка ключевых слов
|
|
107006
106717
|
|
|
107007
106718
|
ТРЕБОВАНИЯ К ТЕКСТУ:
|
|
@@ -107010,7 +106721,6 @@ ${distributionLines}
|
|
|
107010
106721
|
- Сильный явный CTA в конце («кликни», «попробуй сейчас», «закажи», «не жди»)
|
|
107011
106722
|
- Короткие рубленые фразы, императивы, FOMO допустим${hasTestimonial ? '\n- Testimonial: строго от первого лица, начинается с «Имя, возраст:», конкретный результат с таймингом' : ''}
|
|
107012
106723
|
${deadlineNote}
|
|
107013
|
-
- Избегай «гарантированно», «вылечит», «100%», «навсегда»
|
|
107014
106724
|
|
|
107015
106725
|
ОБЩИЕ ТРЕБОВАНИЯ:
|
|
107016
106726
|
- Язык: ${geo} — все тексты строго на этом языке
|
|
@@ -107084,6 +106794,7 @@ function getValidationPrompt(product, geo, keywords, approachName, noOverride) {
|
|
|
107084
106794
|
// Resolve no-bullets conditions in code — don't leave "if approach = X" for the LLM
|
|
107085
106795
|
const noBulletsApproachNames = CREO_APPROACHES.filter(a => a.noBullets).map(a => a.name);
|
|
107086
106796
|
const isNoBullets = noBulletsApproachNames.includes(approachName?.trim() ?? '');
|
|
106797
|
+
const isScreenshotReviews = (approachName?.trim() ?? '') === 'Скрин отзывов';
|
|
107087
106798
|
// ШАГ 0 — hint for BULLETS depends on approach
|
|
107088
106799
|
const step0BulletsHint = isNoBullets
|
|
107089
106800
|
? `BULLETS: [] (для данного подхода буллиты запрещены — ожидается 0)`
|
|
@@ -107096,36 +106807,37 @@ function getValidationPrompt(product, geo, keywords, approachName, noOverride) {
|
|
|
107096
106807
|
: `ШАГ 2 — BULLETS (критично):
|
|
107097
106808
|
- РОВНО 3 буллета. Если буллетов не 3 — ОШИБКА.
|
|
107098
106809
|
- Каждый буллет должен быть читабельным (хорошо читается на телефоне, не микрошрифт)
|
|
107099
|
-
-
|
|
106810
|
+
- Буллеты: крупные, с иконками/галочками (✓), на контрастных подложках. Буллеты НЕ на упаковке/банке.
|
|
107100
106811
|
- Формат бенефита РАЗРЕШЁН: «Чувствуешь себя легче», «Больше энергии», «Без дискомфорта», «Лёгкость движений» — это бенефиты, НЕ предложения
|
|
107101
|
-
- ПРЕДЛОЖЕНИЕ (ошибка) = полная грамматическая конструкция с подлежащим + сказуемым + дополнением, например «Продукт быстро снижает дискомфорт». Короткие бенефиты
|
|
106812
|
+
- ПРЕДЛОЖЕНИЕ (ошибка) = полная грамматическая конструкция с подлежащим + сказуемым + дополнением, например «Продукт быстро снижает дискомфорт». Короткие бенефиты — не считать предложениями
|
|
107102
106813
|
- Буллиты с глаголом во 2-м лице («Чувствуешь...», «Получаешь...») или безличные («Больше энергии», «Без дискомфорта») — НЕ ошибка
|
|
107103
|
-
- Буллеты = свойства, характеристики ИЛИ
|
|
107104
|
-
-
|
|
107105
|
-
-
|
|
107106
|
-
// ШАГ 3 — TEXT LIMIT: PVP has no badges allowed, normal allows urgency/trust
|
|
107107
|
-
const step3TextLimit =
|
|
107108
|
-
? `ШАГ 3 —
|
|
107109
|
-
-
|
|
107110
|
-
-
|
|
107111
|
-
-
|
|
106814
|
+
- Буллеты = свойства, характеристики ИЛИ бенефиты. Разрешено: «Reduce disconfortul», «Mai puține treziri», «Efect în 14 zile», цифры соц. доказательства.
|
|
106815
|
+
- Буллиты должны быть расположены вертикально (столбиком). Если буллиты идут горизонтально в одну строку — ОШИБКА: плохая читаемость на мобиле
|
|
106816
|
+
- Цена визуально ОТЛИЧНА от буллитов (цена — компактный блок без иконок; буллеты — с иконками/галочками). Если цена выглядит как буллет — ОШИБКА.`;
|
|
106817
|
+
// ШАГ 3 — TEXT LIMIT: PVP has no badges allowed, normal allows urgency/trust (без проверок по длине/количеству слов)
|
|
106818
|
+
const step3TextLimit = isScreenshotReviews
|
|
106819
|
+
? `ШАГ 3 — OTHER_TEXT (критично):
|
|
106820
|
+
- Для «Скрин отзывов» блок отзывов (аватарки, имена, возраст, 5 звёзд, текст отзывов) — это НЕ ошибка, это основной контент.
|
|
106821
|
+
- Допускаются также trust‑печати (0–3 шт). URGENCY — не рекомендуется, но не ошибка.
|
|
106822
|
+
- На креативе допустимы: HOOK, блок отзывов, цена, скидка, CTA, опционально trust‑печати.`
|
|
106823
|
+
: isNoBullets
|
|
106824
|
+
? `ШАГ 3 — OTHER_TEXT (критично):
|
|
106825
|
+
- OTHER_TEXT должен быть ПУСТЫМ (0 элементов). Никаких дополнительных бейджей, подписей, ярлыков, trust‑печатей/urgency — НИЧЕГО.
|
|
106826
|
+
- Любой дополнительный текст (бейджи/подписи/пояснения/urgency/trust‑печати) — ОШИБКА: слишком много текста для punch‑креатива.
|
|
107112
106827
|
- На креативе допустимы ТОЛЬКО: HOOK, цена, скидка, кнопка CTA.`
|
|
107113
|
-
|
|
107114
|
-
- Общее число слов в HOOK/HEADLINE + BULLETS <= 16. Если больше — ошибка.
|
|
106828
|
+
: `ШАГ 3 — OTHER_TEXT (критично):
|
|
107115
106829
|
- По умолчанию любой другой рекламный текст (бейджи/подписи/пояснения) запрещён.
|
|
107116
|
-
- НО допускаются (не обязательно) дополнительные
|
|
107117
|
-
A) URGENCY (0–1 шт):
|
|
107118
|
-
B) TRUST (0–3
|
|
106830
|
+
- НО допускаются (не обязательно) дополнительные бейджи ВНЕ упаковки — это НЕ ошибка, если ВСЕ элементы OTHER_TEXT подпадают под разрешённые типы:
|
|
106831
|
+
A) URGENCY (0–1 шт): бейдж срочности/дефицита — только ясные формулировки («только сегодня», «последние штуки», «акция до конца дня»). ЗАПРЕЩЕНО: «24h» и подобные — непонятно что означает.
|
|
106832
|
+
B) TRUST (0–3 шт, для «Минимализм / Clean Big Text» — макс 1): печати цветные, яркие, очень похожие на печать FDA. Подчёркивают: натуральность состава, премиальность, безопасность. Допустимо: качество, натуральные ингредиенты, экологичность, контроль качества. Печати — размер как минимум как у буллитов, крупные. Даже одна печать — крупная.
|
|
107119
106833
|
Правила для каждого элемента OTHER_TEXT:
|
|
107120
|
-
* короткий: 1–3 слова ИЛИ число+слово (например «24h»)
|
|
107121
106834
|
* строго на языке ${geo} (без английских слов)
|
|
107122
106835
|
* без цены/скидки/процентов (кроме обязательного поля DISCOUNT), без доменов/ссылок
|
|
107123
|
-
*
|
|
107124
|
-
* TRUST‑бейджи должны быть про доверие/сервис (качество/доставка/поддержка/проверено/рекомендуют), не про «лечит/результаты». Запрещено: «рекомендовано врачами», «клинически доказано», «одобрено Минздравом» и любые фразы, которые требуют доказательств/авторитетов
|
|
106836
|
+
* TRUST‑печати — про натуральность/премиальность/безопасность (не про доставку/поддержку)
|
|
107125
106837
|
Примеры стиля (адаптируй к языку GEO, не требуются):
|
|
107126
|
-
- URGENCY: «Ostatnie sztuki», «Tylko dziś», «Koniec dzi
|
|
107127
|
-
- TRUST: «Wysoka jako
|
|
107128
|
-
- Если OTHER_TEXT содержит что-то вне этих типов, или URGENCY > 1, или TRUST > 3 — ОШИБКА.`;
|
|
106838
|
+
- URGENCY: «Ostatnie sztuki», «Tylko dziś», «Koniec dziś» (НЕ «24h» — непонятно)
|
|
106839
|
+
- TRUST: «Naturalna formuła», «Wysoka jakość» (для PL); «Ingrediente naturale», «Calitate» (для RO). ЗАПРЕЩЕНО: «NATURAL», «QUALITY», «100% NATURAL» — английские слова.
|
|
106840
|
+
- Если OTHER_TEXT содержит что-то вне этих типов, или URGENCY > 1, или TRUST > 3 (для Минимализма — TRUST > 1) — ОШИБКА.`;
|
|
107129
106841
|
return `Ты — СТРОГИЙ валидатор рекламных креативов (1:1). Не улучшай и не предлагай идеи — только проверка и вердикт.
|
|
107130
106842
|
Продукт: ${product}. GEO/язык: ${geo}.${approachLine}
|
|
107131
106843
|
|
|
@@ -107142,35 +106854,29 @@ DISCOUNT: "..." (если нет — "нет")
|
|
|
107142
106854
|
CTA: "..." (если нет — "нет")
|
|
107143
106855
|
OTHER_TEXT: ["..."] (любой другой рекламный текст на макете ВНЕ упаковки и ВНЕ элементов выше; название/бренд на упаковке НЕ включай)
|
|
107144
106856
|
(если можешь, для ясности — не обязательно):
|
|
107145
|
-
URGENCY_BADGE: "..." (
|
|
107146
|
-
TRUST_BADGES: ["..."] (
|
|
106857
|
+
URGENCY_BADGE: "..." (бейдж срочности, если есть; «24h» — ОШИБКА, непонятно что означает)
|
|
106858
|
+
TRUST_BADGES: ["..."] (печати цветные, яркие, в стиле FDA; про натуральность/премиальность/безопасность; размер как у буллитов — крупные, если есть)
|
|
107147
106859
|
|
|
107148
106860
|
ШАГ 1 — HOOK/HEADLINE (критично):
|
|
107149
|
-
- Должен быть
|
|
107150
|
-
-
|
|
107151
|
-
-
|
|
107152
|
-
-
|
|
107153
|
-
- Мягкие обещания результата («Nopți liniștite», «Stomac fără griji») — не ошибка. Ошибка только если есть медицинская гарантия или диагноз.
|
|
107154
|
-
- Желательно: ключевое слово боли/проблемы в формулировке (как часть смысла), но отсутствие ключевого слова само по себе НЕ является ошибкой. Примеры для ориентира: ${keywords.join(', ')} (можно синонимы).
|
|
106861
|
+
- Должен быть ВЫШЕ всех элементов и читабелен (хорошо читается на телефоне)
|
|
106862
|
+
- Допускается 1–4 строки (это НЕ ошибка). Ошибка только если текст нечитабелен или есть разрыв/перенос внутри слова.
|
|
106863
|
+
- Смысл: релевантно проблеме/выгоде продукта. Допускаются и боль/дискомфорт, и обещание/выгода (например «Könnyebb napok»). НЕ считай ошибкой формулировки обещаний результата или клеймы.
|
|
106864
|
+
- ОБЯЗАТЕЛЬНО: ключевое слово проблемы явно в формулировке. Примеры по категориям: простата → простатит, простата; похудение → лишний вес, похудение; суставы → боль, колени, скованность; пищеварение → дискомфорт, вздутие; сон → бессонница, усталость. Отсутствие ключевого слова — ОШИБКА. Дополнительно для ориентира: ${keywords.join(', ')} (можно синонимы).
|
|
107155
106865
|
- Проверь вторую часть заголовка. Если она состоит только из абстрактных слов («Mai ușor», «Mai bine», «Soluția», «Ajutor») без конкретного бенефита — отметь: РЕКОМЕНДАЦИЯ: заголовок можно усилить конкретикой
|
|
107156
106866
|
- Если заголовок слишком общий/абстрактный («Mai ușor», «Mai bine» без конкретики) — отметь как РЕКОМЕНДАЦИЯ к улучшению (не ошибка, но слабо)
|
|
107157
106867
|
- РЕКОМЕНДАЦИЯ (CTR): верхний текст лучше делать как HOOK — ОЧЕНЬ крупный, жирный, ВСЕ БУКВЫ ПРОПИСНЫЕ, на яркой контрастной плашке. Если это не так — не ошибка, но отметь рекомендацией
|
|
107158
106868
|
|
|
107159
106869
|
${step2Bullets}
|
|
107160
106870
|
|
|
107161
|
-
ШАГ 2.5 — CLAIMS CHECK
|
|
107162
|
-
-
|
|
107163
|
-
- ЗАПРЕЩЕНЫ только: «лечит», «вылечивает», «гарантируем», «100%», «навсегда», «полностью избавляет», диагностика заболеваний
|
|
107164
|
-
- «Выводит/устраняет токсины» для детокс-продуктов — это мягкий клейм, НЕ медицинская гарантия
|
|
107165
|
-
- Если найден запрещённый клейм — ошибка. Мягкие клеймы — не ошибка.
|
|
107166
|
-
- Специфические медицинские термины или диагнозы — разрешены, но не должны повторяться чрезмерно. Если в HOOK/HEADLINE + BULLETS один и тот же специфический термин встречается более 1 раза — РЕКОМЕНДАЦИЯ: разнообразить формулировки, использовать синонимы или фокус на симптомах вместо повторения термина
|
|
106871
|
+
ШАГ 2.5 — CLAIMS CHECK:
|
|
106872
|
+
- Клеймы по результату (жёсткие/абсолютные или мягкие) — НЕ проверяются и НЕ запрещаются. Не блокируй за формулировки обещаний результата.
|
|
107167
106873
|
|
|
107168
106874
|
${step3TextLimit}
|
|
107169
106875
|
|
|
107170
106876
|
ШАГ 4 — CTA > PRICE (критично):
|
|
107171
|
-
- CTA:
|
|
107172
|
-
-
|
|
107173
|
-
- Скидка
|
|
106877
|
+
- CTA: на языке ${geo}, хорошо заметная кнопка (позиция НЕ важна: можно вправо/влево/по центру). Главное — CTA читабельна и выглядит как кнопка.
|
|
106878
|
+
- Цена: ОБЯЗАТЕЛЬНО ДВЕ — старая (до скидки) + новая. Если только одна цена — ОШИБКА. Старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Тонкое/незаметное зачёркивание — ОШИБКА.
|
|
106879
|
+
- Скидка ОБЯЗАТЕЛЬНА: «-50%» отдельным видимым бейджем (процент должен быть явно читаем, не только в цене). Ярко, заметно. Строго НЕ на упаковке/банке.
|
|
107174
106880
|
- Не считать ошибкой, если скидка/цена конкурируют с CTA по акценту — это допустимо, пока CTA остаётся хорошо заметной и читабельной.
|
|
107175
106881
|
- Если скидка указана и она не равна -50% — ошибка.
|
|
107176
106882
|
|
|
@@ -107181,13 +106887,18 @@ ${step3TextLimit}
|
|
|
107181
106887
|
ШАГ 6 — КОМПОЗИЦИЯ:
|
|
107182
106888
|
- Если креатив без человека (lifestyle/clean), проверь наличие контекста использования (кухня, стол, тумбочка, и т.д.). Продукт на полностью пустом/белом фоне без контекста — РЕКОМЕНДАЦИЯ к улучшению (не критичная ошибка, но слабый визуал)
|
|
107183
106889
|
- КОНТРАСТ ПЛАШЕК: если хотя бы одна текстовая плашка (HOOK, буллеты, CTA или цена) визуально сливается с фоном и текст плохо читается — это ОШИБКА.
|
|
106890
|
+
- ЦЕНА: ОБЯЗАТЕЛЬНО ДВЕ — старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Одна цена или тонкое зачёркивание — ОШИБКА. Цена не должна выглядеть как буллет.
|
|
106891
|
+
- TRUST‑печати: если печати мелкие или текст нечитабелен на телефоне — ОШИБКА (размер как минимум как у буллитов).
|
|
106892
|
+
- СКИДКА «-50%»: ОБЯЗАТЕЛЬНО отдельным видимым бейджем (процент явно читаем). Если скидка не отображается или только в цене без отдельного «-50%» — ОШИБКА. Ярко, заметно, НЕ на упаковке.
|
|
106893
|
+
- ЦЕНА: ОБЯЗАТЕЛЬНО ДВЕ — старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Одна цена или тонкое зачёркивание — ОШИБКА.
|
|
106894
|
+
- Бейджи срочности: «24h» и подобные неясные формулировки — ОШИБКА (непонятно что означает). Ясные («только сегодня», «последние штуки») — ок.
|
|
107184
106895
|
|
|
107185
106896
|
ФИНАЛ:
|
|
107186
106897
|
Выведи строго:
|
|
107187
106898
|
СТАТУС: OK / НУЖНА ПЕРЕСБОРКА
|
|
107188
106899
|
Затем список ошибок, каждая строка начинается с "ОШИБКА:" (кратко, по делу). Если ошибок нет — напиши "ОШИБКА: нет".
|
|
107189
106900
|
Затем список рекомендаций (если есть), каждая строка начинается с "РЕКОМЕНДАЦИЯ:" (кратко, по делу). Если рекомендаций нет — напиши "РЕКОМЕНДАЦИЯ: нет".
|
|
107190
|
-
Не блокируй за
|
|
106901
|
+
Не блокируй за клеймы по результату или формулировки обещаний.`;
|
|
107191
106902
|
}
|
|
107192
106903
|
/**
|
|
107193
106904
|
* Базовый промпт для генерации изображений креативов
|
|
@@ -107205,10 +106916,12 @@ function getImageGenerationBasePrompt(generateGeo, generatePrice, generateCurren
|
|
|
107205
106916
|
}
|
|
107206
106917
|
_debugLog('⚠️ Using DEFAULT getImageGenerationBasePrompt');
|
|
107207
106918
|
}
|
|
107208
|
-
// Подходы без буллитов —
|
|
107209
|
-
const
|
|
106919
|
+
// Подходы без буллитов — из подходов с count > 0
|
|
106920
|
+
const counts = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_0__.getImageApproachCounts)();
|
|
106921
|
+
const selectedApproaches = CREO_APPROACHES.filter((_, i) => counts[i] > 0);
|
|
106922
|
+
const noBulletsNames = selectedApproaches.filter(a => a.noBullets).map(a => a.name);
|
|
107210
106923
|
const noBulletsCond = noBulletsNames.map(n => `"${n}"`).join(' или ');
|
|
107211
|
-
const totalApproaches =
|
|
106924
|
+
const totalApproaches = counts.reduce((a, b) => a + b, 0);
|
|
107212
106925
|
const ratio = aspectRatio || '1:1';
|
|
107213
106926
|
const ratioLabel = ratio === '1:1' ? '1:1 (квадрат, 1024×1024 px)' : '2:3 (портрет, 1024×1536 px)';
|
|
107214
106927
|
const ratioShape = ratio === '1:1' ? 'квадратным' : 'вертикальным 2:3 (портрет)';
|
|
@@ -107236,20 +106949,24 @@ CRITICAL RULES (это ВАЛИДАЦИЯ, не рекомендации; есл
|
|
|
107236
106949
|
- Цена/скидка = триггер "сейчас"
|
|
107237
106950
|
- CTA = финальный шаг к действию
|
|
107238
106951
|
|
|
106952
|
+
❗ ЦЕНА (КРИТИЧНО — НЕ ПРОПУСКАЙ):
|
|
106953
|
+
- ОБЯЗАТЕЛЬНО ДВЕ цены: старая (до скидки) + новая (${generatePrice} ${generateCurrency}). Одна цена = ОШИБКА.
|
|
106954
|
+
- Старая = 2× новой (при -50%). Новая — выразительно, крупно. Старая — зачёркнута.
|
|
106955
|
+
- Зачёркивание: ТОЛСТАЯ линия (не тонкая!), контрастная, хорошо видимая. Тонкая/незаметная линия = ОШИБКА.
|
|
106956
|
+
|
|
107239
106957
|
ЯЗЫК + ТОН:
|
|
107240
|
-
- ВСЕ слова строго на языке ${generateGeo} (HOOK/буллеты/CTA/скидка/опциональный бейдж срочности/опциональные trust
|
|
107241
|
-
- Тон: убедительный, но честный. Лёгкая эмоция и срочность — ок.
|
|
106958
|
+
- ВСЕ слова строго на языке ${generateGeo} (HOOK/буллеты/CTA/скидка/опциональный бейдж срочности/опциональные trust‑печати). Английские слова запрещены. Исключений нет.
|
|
106959
|
+
- Тон: убедительный, но честный. Лёгкая эмоция и срочность — ок.
|
|
107242
106960
|
|
|
107243
106961
|
HOOK / HEADLINE (строгое правило):
|
|
107244
|
-
-
|
|
107245
|
-
-
|
|
107246
|
-
-
|
|
106962
|
+
- 1–4 строки; до 12 слов. Без переноса внутри слова
|
|
106963
|
+
- ОБЯЗАТЕЛЬНО содержит ключевое слово проблемы ЯВНО. Примеры по категориям: простата → простатит, простата; похудение → лишний вес, похудение; суставы → боль в суставах, колени, скованность; пищеварение → дискомфорт, вздутие, тяжесть; сон → бессонница, усталость. Без ключевого слова — ОШИБКА
|
|
106964
|
+
- HOOK ВЫШЕ всех элементов (продукт, буллеты, CTA, цена). Самый крупный текстовый элемент на креативе
|
|
107247
106965
|
- HOOK должен быть ВИЗУАЛЬНО “тяжёлым”: ОЧЕНЬ крупный, ТОЛСТЫЙ/жирный шрифт, ВСЕ БУКВЫ ПРОПИСНЫЕ (CAPS), на яркой контрастной плашке/подложке. Это верхний главный “thumb‑stop” элемент
|
|
107248
|
-
- Можно (не обязательно) включить СРОК прямо в HOOK (например «7 dni», «14 dni») как
|
|
107249
|
-
- Заголовок должен быть РЕЗКИМ и призывным: короткие рубленые фразы, вопросы и предупреждения допустимы. Можно использовать обращение на «ты» (в языке GEO: «Masz…», «Twój…») и вопросительный знак
|
|
107250
|
-
- заголовок может содержать мягкое обещание облегчения, но не медицинскую гарантию. ПРАВИЛЬНО: «Nopți fără treziri? Posibil». НЕ ТАК: «Vindecă prostata definitiv».
|
|
106966
|
+
- Можно (не обязательно) включить СРОК прямо в HOOK (например «7 dni», «14 dni») как триггер
|
|
106967
|
+
- Заголовок должен быть РЕЗКИМ и призывным: короткие рубленые фразы, вопросы и предупреждения допустимы. Можно использовать обращение на «ты» (в языке GEO: «Masz…», «Twój…») и вопросительный знак
|
|
107251
106968
|
- заголовок НЕ должен быть абстрактным слоганом/лозунгом или метафорой без конкретной боли. Допускается «thumb‑stop» стиль (вызов/вопрос/предупреждение), если это конкретно и релевантно категории
|
|
107252
|
-
- если не влезает: сначала измени композицию (расширь блок/переставь элементы), затем перепиши короче. НЕЛЬЗЯ уменьшать шрифт.
|
|
106969
|
+
- если не влезает: сначала измени композицию (расширь блок/переставь элементы), затем перепиши короче. НЕЛЬЗЯ уменьшать шрифт. HOOK всегда остаётся выше всех остальных элементов
|
|
107253
106970
|
- Не повторяй один и тот же заголовок в разных подходах: для текущего подхода придумай новый, уникальный заголовок. Варьируй: угол боли, формулировку, акцент (проблема vs облегчение)
|
|
107254
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»
|
|
107255
106972
|
- Специфические медицинские термины или диагнозы разрешены в заголовке, но не обязательны. Варьируй между прямыми формулировками (если релевантно категории) и мягкими формулировками, фокусирующимися на симптомах и дискомфорте, для разных креативов. Не используй один и тот же термин во всех креативах
|
|
@@ -107258,73 +106975,89 @@ HOOK / HEADLINE (строгое правило):
|
|
|
107258
106975
|
- ТАК (пищеварение): «Puffadás gond? Könnyebb napok»
|
|
107259
106976
|
- ТАК (суставы): «Ízületi fájdalom? Mozogj könnyebben»
|
|
107260
106977
|
- ТАК (сон): «Álmatlan éjszakák? Pihenj végre»
|
|
107261
|
-
|
|
106978
|
+
- ТАК (простата): «Prostată? Alinare rapidă»
|
|
106979
|
+
- ТАК (похудение): «Greutate în plus? Fără diete extreme»
|
|
106980
|
+
❗ Если заголовок > 12 слов, похож на длинное предложение или звучит как лозунг без ключевого слова проблемы — ОШИБКА → пересоздай вариант.
|
|
107262
106981
|
|
|
107263
|
-
BULLETS (жёсткое
|
|
106982
|
+
BULLETS (жёсткое правило, ровно 3):
|
|
107264
106983
|
- По умолчанию: ровно 3 буллета
|
|
107265
106984
|
- ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → буллиты ЗАПРЕЩЕНЫ (0 буллитов). Не добавляй буллет‑блок вообще
|
|
107266
106985
|
- каждый 2–3 слова (макс 4); без запятых; буллет НЕ должен выглядеть как предложение
|
|
107267
|
-
-
|
|
107268
|
-
- Делай буллеты более активными и конкретными: глаголы действия + сроки + цифры. Примеры формата: «Reduce X», «Ulga od 1. dnia», «Efekt w 7 dni», «93% potwierdza», «3× szybciej» (адаптируй под язык GEO и категорию)
|
|
106986
|
+
- ВИЗУАЛ БУЛЛЕТОВ: крупные, с иконками/галочками (✓), на контрастных подложках. Буллеты ВНЕ упаковки/банки — не на продукте.
|
|
107269
106987
|
- Буллиты должны быть расположены ВЕРТИКАЛЬНО (столбиком), не горизонтально в одну строку. Минимальный размер шрифта буллитов — чтобы читались на экране телефона без зума
|
|
107270
|
-
|
|
107271
|
-
|
|
107272
|
-
|
|
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
|
+
|
|
107273
106998
|
❗ Если хоть один буллет > 4 слов или похож на предложение/обещание — ОШИБКА → пересоздай.
|
|
107274
106999
|
|
|
107275
107000
|
TEXT LIMIT:
|
|
107276
107001
|
- кроме: HOOK, 3 буллетов, цены, скидки, кнопки — ЛЮБОЙ другой текст запрещён (ярлыки/подписи/дисклеймеры/пояснения)
|
|
107277
|
-
- ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → кроме HOOK, цены, скидки, CTA — НИКАКОГО другого текста. Буллиты запрещены. Other_text/urgency/trust
|
|
107278
|
-
- ДОПУСКАЕТСЯ (не обязательно) ОДИН
|
|
107279
|
-
- ДОПУСКАЕТСЯ (не обязательно) 1–3
|
|
107280
|
-
- HOOK + буллеты <=
|
|
107281
|
-
- Если хочешь добавить ощущение срочности — делай это через формулировки в HOOK/BULLETS, через реквизит/иконки, ИЛИ (не обязательно) через один короткий
|
|
107002
|
+
- ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → кроме HOOK, цены, скидки, CTA — НИКАКОГО другого текста. Буллиты запрещены. Other_text/urgency/trust‑печати запрещены
|
|
107003
|
+
- ДОПУСКАЕТСЯ (не обязательно) ОДИН бейдж срочности: только ясные формулировки («только сегодня», «последние штуки», «акция до конца дня»). ЗАПРЕЩЕНО: «24h» — непонятно что означает.
|
|
107004
|
+
- ДОПУСКАЕТСЯ (не обязательно) 1–3 trust‑печати: печати цветные, яркие, в стиле FDA. Текст СТРОГО на языке ${generateGeo} — запрещены английские слова («NATURAL», «QUALITY» и т.д.). Примеры для RO: «naturale», «Ingrediente naturale»; для PL: «naturalna». Размер как минимум как у буллитов. Мелкие печати — ОШИБКА.
|
|
107005
|
+
- HOOK + буллеты <= 24 слова суммарно (HOOK до 12, буллеты до 12); если больше — ОШИБКА → пересоздай
|
|
107006
|
+
- Если хочешь добавить ощущение срочности — делай это через формулировки в HOOK/BULLETS, через реквизит/иконки, ИЛИ (не обязательно) через один короткий бейдж срочности. Только ясные формулировки («Ostatnie sztuki», «Tylko dziś», «Koniec dziś»). ЗАПРЕЩЕНО «24h» — непонятно что означает.
|
|
107282
107007
|
|
|
107283
107008
|
CTA > PRICE:
|
|
107284
107009
|
- кнопка CTA (1–2 слова на языке ${generateGeo}) — главный якорь нижнего блока (самый контрастный элемент внизу)
|
|
107285
107010
|
- цена и скидка видны, но слабее CTA; скидка «-50%» ОБЯЗАТЕЛЬНА и не должна конкурировать с кнопкой
|
|
107011
|
+
- ЦЕНА: ОБЯЗАТЕЛЬНО ДВЕ — старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Одна цена или тонкое зачёркивание — ОШИБКА. Цена не должна выглядеть как буллет.
|
|
107286
107012
|
❗ Если цена/скидка/бейдж по контрасту или размеру равны или сильнее кнопки — ОШИБКА → ослабь цену/скидку или усили CTA и пересоздай.
|
|
107287
107013
|
❗ ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → цена и «-50%» могут быть более крупными и заметными, но CTA всё равно должен оставаться очень заметным и выглядеть как кнопка (не теряется на фоне)
|
|
107288
107014
|
|
|
107289
107015
|
ПОКАЗЫВАЙ ТОЛЬКО ЭТИ ТЕКСТОВЫЕ ЭЛЕМЕНТЫ:
|
|
107290
|
-
- HOOK (1
|
|
107291
|
-
- 3 буллета
|
|
107292
|
-
- Цена: ${generatePrice} ${generateCurrency}
|
|
107293
|
-
- Скидка:
|
|
107016
|
+
- HOOK (1–4 строки; выше всех элементов; самый крупный; CAPS; жирный; на яркой контрастной подложке; ключевое слово проблемы явно)
|
|
107017
|
+
- 3 буллета (крупные, с иконками/галочками ✓, на контрастных подложках, НЕ на банке/упаковке)
|
|
107018
|
+
- Цена: ОБЯЗАТЕЛЬНО ДВЕ — старая (2×${generatePrice} ${generateCurrency}) зачёркнута ТОЛСТОЙ контрастной линией + новая ${generatePrice} ${generateCurrency} выразительно. Одна цена = ОШИБКА. Тонкая линия зачёркивания = ОШИБКА. Без слов. Не на упаковке.
|
|
107019
|
+
- Скидка: «-50%» ОБЯЗАТЕЛЬНО отдельным видимым бейджем (не только в цене!). Процент скидки должен быть явно читаем. Ярко, заметно, строго НЕ на банке/упаковке, визуально слабее CTA
|
|
107294
107020
|
- Кнопка CTA (1-2 слова)
|
|
107295
|
-
- Опционально: 1 короткий бейдж срочности (
|
|
107296
|
-
- Опционально: 1–3
|
|
107297
|
-
- ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → показывай только: HOOK + PRICE + DISCOUNT + CTA. Буллиты/urgency/trust запрещены
|
|
107021
|
+
- Опционально: 1 короткий бейдж срочности (ясные формулировки: «только сегодня», «последние штуки»; ЗАПРЕЩЕНО «24h»). Бейдж в углу кадра, контрастный, но меньше CTA. НЕ обязателен
|
|
107022
|
+
- Опционально: 1–3 trust‑печати (цветные, яркие, в стиле FDA; натуральность, премиальность, безопасность). Текст СТРОГО на языке ${generateGeo} — никакого английского. Для RO: «naturale», «Ingrediente naturale», «Calitate»; для PL: «naturalna», «naturalne»; НЕ «NATURAL», «QUALITY». Размер как минимум как у буллитов. НЕ обязательны
|
|
107023
|
+
- ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → показывай только: HOOK + PRICE + DISCOUNT + CTA. Буллиты/urgency/trust‑печати запрещены
|
|
107298
107024
|
|
|
107299
107025
|
ВИЗУАЛ:
|
|
107300
|
-
- Иерархия: HOOK
|
|
107026
|
+
- Иерархия: HOOK ВЫШЕ всех элементов (продукт, буллеты, CTA, цена). HOOK — самый крупный текстовый элемент
|
|
107027
|
+
- Буллеты: крупные, с иконками/галочками (✓), на контрастных подложках. Строго ВНЕ упаковки/банки.
|
|
107028
|
+
- Цена: ОБЯЗАТЕЛЬНО ДВЕ — старая зачёркнута ТОЛСТОЙ линией (не тонкой!), новая выразительно. Одна цена или тонкое зачёркивание = ОШИБКА. Визуально ОТЛИЧНА от буллитов.
|
|
107029
|
+
- Скидка «-50%»: ярко, заметно, на контрастной подложке. Строго ВНЕ упаковки/банки.
|
|
107301
107030
|
- Текст на контрастных плашках, читабельно на телефоне. Допускается мультяшная/комиксовая стилистика текста (не обязательно)
|
|
107302
107031
|
- КОНТРАСТ ПЛАШЕК: каждая текстовая плашка (HOOK, буллеты, CTA, цена) должна иметь высокий контраст с фоном — светлый текст на тёмной подложке ИЛИ тёмный текст на светлой. Плашка, сливающаяся с фоном = ОШИБКА → добавь подложку или измени цвет фона/плашки.
|
|
107303
107032
|
- АНГЛИЙСКИЙ НА УПАКОВКЕ: если на этикетке продукта есть английский текст (например название/описание на упаковке) — не приближай её настолько, чтобы эти надписи были читабельны как отдельный контент. Упаковка = визуальный объект; мелкий этикеточный текст должен быть нечитабелен или едва различим.
|
|
107304
107033
|
- Без ссылок/доменов и мелкого текста
|
|
107305
107034
|
- LIFESTYLE/CLEAN: продукт в контексте использования. Для пищеварения — кухня, обеденный стол, рядом с едой/чашкой. Для сна/простаты — спальня, тумбочка, стакан воды. Для суставов — стол/тумбочка рядом с эластичным бинтом, ортезом или активным фоном (кроссовки, лестница). Контекст = момент приёма. Просто продукт на белом фоне — НЕ допускается
|
|
107306
107035
|
- Цвета и композиция должны быть «thumb‑stop»: высокий контраст, яркий акцент на продукте (луч света/подсветка/обводка/глоу), избегай бледных пастельных сцен
|
|
107307
|
-
- ЛЮДИ: человек допускается ТОЛЬКО для
|
|
107308
|
-
- Если
|
|
107036
|
+
- ЛЮДИ: человек допускается ТОЛЬКО для подходов «Эмоция / Портрет» и «Врач / Эксперт». Для всех остальных — БЕЗ человека.
|
|
107037
|
+
- Если «Эмоция / Портрет»: прямой взгляд в камеру, эмоция облегчения/надежды. Возраст 50–60 (похудение/фитнес — 35–55). Этнически соответствует GEO/рынку.
|
|
107038
|
+
- Если «Врач / Эксперт»: женщина-врач 40–55 лет. Внешность и этничность СТРОГО соответствуют GEO/рынку (локал — врач выглядит как местный специалист).
|
|
107309
107039
|
|
|
107310
107040
|
ANTI-TEMPLATE DIVERSITY (КРИТИЧНО):
|
|
107311
107041
|
- Ты сейчас создаёшь ОДИН креатив для текущего подхода. В проекте будет серия из ${totalApproaches} креативов, поэтому у каждого подхода должна быть своя узнаваемая композиция.
|
|
107312
107042
|
- Для ЭТОГО креатива НЕ используй универсальный шаблон. Обязательно вариируй: (1) крупность/кроп (close-up vs medium vs flat-lay), (2) угол камеры (прямо vs сверху vs диагональ), (3) расположение блоков HOOK/BULLETS/CTA/PRICE/DISCOUNT, (4) свет/фон (но без лишнего текста).
|
|
107313
107043
|
- Ориентируйся на строку «🎯 ПОДХОД: ...» и примени соответствующую раскладку ниже (только одну, соответствующую текущему подходу):
|
|
107314
|
-
* 🎯 ПОДХОД: Эксперт / Авторитет → БЕЗ человека. Профессиональный контекст с реквизитом экспертизы вокруг продукта. Продукт в центре. HOOK сверху справа, BULLETS справа ниже, CTA снизу справа, PRICE/DISCOUNT рядом с CTA (слабее CTA). Trust
|
|
107315
|
-
* 🎯 ПОДХОД: Lifestyle / Момент приёма → FLAT-LAY/СВЕРХУ или 3/4 сверху на столе/тумбочке. Продукт в центре, реквизит "момент приёма" вокруг. HOOK сверху по центру, BULLETS сбоку (вертикально, шрифт достаточно крупный для чтения на телефоне без зума), CTA снизу по центру, PRICE/DISCOUNT рядом (слабее CTA). Urgency‑бейдж (если есть) — в
|
|
107316
|
-
* 🎯 ПОДХОД: Эмоция / Портрет →
|
|
107317
|
-
* 🎯 ПОДХОД:
|
|
107318
|
-
* 🎯 ПОДХОД:
|
|
107319
|
-
* 🎯 ПОДХОД:
|
|
107320
|
-
* 🎯 ПОДХОД:
|
|
107321
|
-
* 🎯 ПОДХОД:
|
|
107044
|
+
* 🎯 ПОДХОД: Эксперт / Авторитет → БЕЗ человека. Профессиональный контекст с реквизитом экспертизы вокруг продукта. Продукт в центре. HOOK сверху справа, BULLETS справа ниже, CTA снизу справа, PRICE/DISCOUNT рядом с CTA (слабее CTA). Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне).
|
|
107045
|
+
* 🎯 ПОДХОД: Lifestyle / Момент приёма → FLAT-LAY/СВЕРХУ или 3/4 сверху на столе/тумбочке. Продукт в центре, реквизит "момент приёма" вокруг. HOOK сверху по центру, BULLETS сбоку (вертикально, шрифт достаточно крупный для чтения на телефоне без зума), CTA снизу по центру, PRICE/DISCOUNT рядом (слабее CTA). Urgency‑бейдж (если есть) — в углу, только ясные формулировки, НЕ «24h». Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне).
|
|
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 внизу.
|
|
107049
|
+
* 🎯 ПОДХОД: Визуализация проблемы → Инфографика/схема: слева проблема (иконка/схема зоны тела, релевантной продукту), справа решение + продукт. HOOK сверху по центру, BULLETS справа или снизу (вертикально), CTA снизу справа, PRICE/DISCOUNT возле CTA. Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне). Красный акцент только на проблеме.
|
|
107050
|
+
* 🎯 ПОДХОД: Power / Сила решения → БЕЗ человека. ДИНАМИЧНЫЙ комикс‑кадр, диагональная композиция. Продукт как “герой” + power‑иконки (щит/молния/бёрст). HOOK сверху слева на яркой плашке, BULLETS слева ниже, CTA снизу справа, PRICE/DISCOUNT возле CTA. Trust‑печати (если есть) — компактно по низу, размер как минимум как у буллитов (крупные, читабельны на телефоне).
|
|
107051
|
+
* 🎯 ПОДХОД: Минимализм / Clean Big Text → Белый/градиентный фон, минимум элементов. ОГРОМНЫЙ HOOK занимает верх/центр, продукт крупно (центр/право), CTA снизу по центру, PRICE/DISCOUNT рядом (слабее CTA). Trust‑печати (если есть) — размер как минимум как у буллитов, крупные и читабельные.
|
|
107052
|
+
* 🎯 ПОДХОД: Problem Visual Punch → БЕЗ человека. Яркий сплошной фон (красный/оранжевый) или агрессивный градиент. В центре КРУПНАЯ иконка/схема зоны тела, релевантной продукту (для суставов — колено; для пищеварения — желудок), с красным свечением. Продукт крупно рядом. HOOK 1–4 строки, до 12 слов, огромный CAPS, выше всех. БЕЗ буллитов. PRICE + «-50%» крупно и заметно. CTA контрастной кнопкой
|
|
107053
|
+
* 🎯 ПОДХОД: Любительский Примитивизм → БЕЗ человека. Чёрный или кислотный сплошной фон. Продукт по центру или справа без декора. HOOK сверху — 1–4 строки, до 12 слов, крупный, грубый bold/рукописный, CAPS, выше всех. Крупные надписи цены/скидки вокруг продукта. CTA — плоская кнопка без градиентов. БЕЗ буллитов, бейджей, теней, иконок.
|
|
107322
107054
|
|
|
107323
107055
|
AUTO-CHECK (перед финалом):
|
|
107324
|
-
- HOOK:
|
|
107056
|
+
- HOOK: 1–4 строки, до 12 слов, CAPS, жирный на контрастной плашке, выше всех элементов, ключевое слово проблемы явно, язык ${generateGeo} без английских слов
|
|
107325
107057
|
- Буллеты: по умолчанию ровно 3, каждый <= 4 слов, без запятых, не предложения. ИСКЛЮЧЕНИЕ: если 🎯 ПОДХОД = ${noBulletsCond} → буллетов 0 (запрещены)
|
|
107326
|
-
- Нет лишнего текста кроме: HOOK, буллетов, цены, скидки, кнопки (+ опционально 1
|
|
107058
|
+
- Нет лишнего текста кроме: HOOK, буллетов, цены, скидки, кнопки (+ опционально 1 бейдж срочности + опционально 1–3 trust‑печати)
|
|
107327
107059
|
- Есть скидка «-50%» (вне упаковки) и она слабее CTA
|
|
107060
|
+
- Цена: ОБЯЗАТЕЛЬНО ДВЕ (старая + новая). Старая зачёркнута ТОЛСТОЙ контрастной линией. Одна цена = ОШИБКА. Тонкая/незаметная линия зачёркивания = ОШИБКА
|
|
107328
107061
|
- CTA визуально сильнее цены/скидки
|
|
107329
107062
|
- Контраст: каждая текстовая плашка читабельна на телефоне — ни одна не сливается с фоном
|
|
107330
107063
|
Если хоть один пункт нарушен — пересоздай креатив.
|
|
@@ -107332,59 +107065,70 @@ AUTO-CHECK (перед финалом):
|
|
|
107332
107065
|
ВАЖНО: Твой ответ — это ИЗОБРАЖЕНИЕ, не текст. Не пиши план, не перечисляй элементы текстом, не рассуждай, не вызывай tools. Сразу генерируй визуальный креатив как картинку.`;
|
|
107333
107066
|
}
|
|
107334
107067
|
/**
|
|
107335
|
-
* Получить подходы с учетом
|
|
107068
|
+
* Получить подходы с учетом количества и оверрайдов.
|
|
107069
|
+
* Каждый подход повторяется N раз (0–4), где N = imageApproachCounts[i].
|
|
107336
107070
|
*/
|
|
107337
107071
|
function getCreoApproaches() {
|
|
107338
|
-
|
|
107072
|
+
const counts = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_0__.getImageApproachCounts)();
|
|
107073
|
+
const result = [];
|
|
107074
|
+
for (let i = 0; i < CREO_APPROACHES.length && i < counts.length; i++) {
|
|
107075
|
+
const approach = CREO_APPROACHES[i];
|
|
107076
|
+
if (!approach)
|
|
107077
|
+
continue;
|
|
107339
107078
|
const override = (0,_promptOverrides__WEBPACK_IMPORTED_MODULE_0__.getCreoApproachOverride)(approach.name);
|
|
107340
|
-
|
|
107341
|
-
|
|
107342
|
-
return {
|
|
107079
|
+
const resolved = override?.enabled
|
|
107080
|
+
? {
|
|
107343
107081
|
...approach,
|
|
107344
107082
|
prompt: override.customPrompt || approach.prompt,
|
|
107345
107083
|
headlineAngle: override.customHeadlineAngle || approach.headlineAngle,
|
|
107346
107084
|
bulletsFocus: override.customBulletsFocus || approach.bulletsFocus
|
|
107347
|
-
}
|
|
107085
|
+
}
|
|
107086
|
+
: approach;
|
|
107087
|
+
if (override?.enabled) {
|
|
107088
|
+
_debugLog(`✅ Using CUSTOM approach: ${approach.name}`, `hasPrompt=${!!override.customPrompt}`, `hasHeadline=${!!override.customHeadlineAngle}`, `hasBullets=${!!override.customBulletsFocus}`);
|
|
107348
107089
|
}
|
|
107349
|
-
|
|
107350
|
-
|
|
107090
|
+
for (let k = 0; k < counts[i]; k++) {
|
|
107091
|
+
result.push(resolved);
|
|
107092
|
+
}
|
|
107093
|
+
}
|
|
107094
|
+
return result;
|
|
107351
107095
|
}
|
|
107352
107096
|
const CREO_APPROACHES = [
|
|
107353
107097
|
{
|
|
107354
107098
|
name: 'Эксперт / Авторитет',
|
|
107355
|
-
prompt: `ЭКСПЕРТ / АВТОРИТЕТ (без человека): стиль “рекомендация эксперта”, но БЕЗ человека. Профессиональный контекст — аптечные полки на фоне / медицинский планшет / стетоскоп / рецептурный блокнот как реквизит рядом с продуктом. Продукт в центре как “рекомендованное решение”. Чистый профессиональный фон, высокий контраст. Trust
|
|
107356
|
-
headlineAngle: `HOOK: угол «предупреждение / интрига / специалисты знают». Формат: вопрос‑предупреждение ИЛИ “специалисты знают/используют” (адаптируй под GEO).
|
|
107357
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107099
|
+
prompt: `ЭКСПЕРТ / АВТОРИТЕТ (без человека): стиль “рекомендация эксперта”, но БЕЗ человека. Профессиональный контекст — аптечные полки на фоне / медицинский планшет / стетоскоп / рецептурный блокнот как реквизит рядом с продуктом. Продукт в центре как “рекомендованное решение”. Чистый профессиональный фон, высокий контраст. Trust‑печати (1–3 шт): цветные, яркие, очень похожие на печать FDA; подчёркивают натуральность состава, премиальность, безопасность. Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная.`,
|
|
107100
|
+
headlineAngle: `HOOK: угол «предупреждение / интрига / специалисты знают». Формат: вопрос‑предупреждение ИЛИ “специалисты знают/используют” (адаптируй под GEO). 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107101
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + соц.доказательство/формула) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор для этого подхода — не повторяй комбинации из других креативов.`
|
|
107358
107102
|
},
|
|
107359
107103
|
{
|
|
107360
107104
|
name: 'Lifestyle / Момент приёма',
|
|
107361
|
-
prompt: `LIFESTYLE / МОМЕНТ ПРИЁМА: продукт в контексте использования (кухня/стол/спальня/тумбочка). Без человека, но с "историей" (стакан воды, чай, тарелка, будильник/часы/календарь). Срочность: реквизит (часы/таймер‑иконка) и/или опциональный urgency‑бейдж (1–3 слова) в углу
|
|
107362
|
-
headlineAngle: `HOOK: угол «цифры + срочность». Формат: число/процент + ограничение времени/“сейчас/сегодня” (адаптируй под GEO),
|
|
107363
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107105
|
+
prompt: `LIFESTYLE / МОМЕНТ ПРИЁМА: продукт в контексте использования (кухня/стол/спальня/тумбочка). Без человека, но с "историей" (стакан воды, чай, тарелка, будильник/часы/календарь). Срочность: реквизит (часы/таймер‑иконка) и/или опциональный urgency‑бейдж (1–3 слова) в углу кадра — только ясные формулировки («только сегодня», «последние штуки»), ЗАПРЕЩЕНО «24h». Высокий контраст, яркий акцент на продукте. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. ВАЖНО: буллиты располагаются вертикально и всегда читабельны на экране телефона без зума — даже если уходят вбок, минимальный размер шрифта буллитов не уменьшается. Если буллиты не вмещаются сбоку с нужным шрифтом — переставь их вниз.`,
|
|
107106
|
+
headlineAngle: `HOOK: угол «цифры + срочность». Формат: число/процент + ограничение времени/“сейчас/сегодня” (адаптируй под GEO), 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107107
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (срок + цифра + действие/формула) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107364
107108
|
},
|
|
107365
107109
|
{
|
|
107366
107110
|
name: 'Эмоция / Портрет',
|
|
107367
|
-
prompt: `ЭМОЦИЯ / ПОРТРЕТ:
|
|
107368
|
-
headlineAngle: `HOOK: угол «персонально на “ты” + результат/облегчение».
|
|
107369
|
-
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 СЛУЧАЙНЫХ из пула (качество жизни + действие + срок/цифра) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107370
107114
|
},
|
|
107371
107115
|
{
|
|
107372
107116
|
name: 'Визуализация проблемы',
|
|
107373
|
-
prompt: `ВИЗУАЛИЗАЦИЯ ПРОБЛЕМЫ (без человека): схема/иконка проблемной зоны тела, строго соответствующей категории продукта (для суставов — колено/сустав/позвоночник; для пищеварения — желудок; для простаты — силуэт мужчины) + мягкий красный акцент на этой зоне (не шок‑контент, без графики). Рядом продукт как решение. Можно добавить простую стрелку/переход “проблема → облегчение” как графику (без лишнего текста). Чистый фон, высокая читабельность. Опционально: 1–3 trust
|
|
107374
|
-
headlineAngle: `HOOK: прямой вопрос о боли/дискомфорте (релевантно категории).
|
|
107375
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107117
|
+
prompt: `ВИЗУАЛИЗАЦИЯ ПРОБЛЕМЫ (без человека): схема/иконка проблемной зоны тела, строго соответствующей категории продукта (для суставов — колено/сустав/позвоночник; для пищеварения — желудок; для простаты — силуэт мужчины) + мягкий красный акцент на этой зоне (не шок‑контент, без графики). Рядом продукт как решение. Можно добавить простую стрелку/переход “проблема → облегчение” как графику (без лишнего текста). Чистый фон, высокая читабельность. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. БЕЗ человека.`,
|
|
107118
|
+
headlineAngle: `HOOK: прямой вопрос о боли/дискомфорте (релевантно категории). 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно, вопросительный знак допустим.`,
|
|
107119
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + соц.доказательство) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107376
107120
|
},
|
|
107377
107121
|
{
|
|
107378
107122
|
name: 'Power / Сила решения',
|
|
107379
|
-
prompt: `POWER / СИЛА РЕШЕНИЯ (без человека): метафоры силы и победы над проблемой: щит, молния, энергия, взрыв‑бёрст, мощные стикеры/иконки. Продукт как “герой” в центре, высокая энергия, контрастные агрессивные цвета. Можно комикс/anti‑design подачу, но всё должно быть читабельно. Без лишнего текста. Опционально: 1–3 trust
|
|
107380
|
-
headlineAngle: `HOOK: угол «было → стало / победа над проблемой». Можно использовать стрелку “→” как часть перехода.
|
|
107381
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107123
|
+
prompt: `POWER / СИЛА РЕШЕНИЯ (без человека): метафоры силы и победы над проблемой: щит, молния, энергия, взрыв‑бёрст, мощные стикеры/иконки. Продукт как “герой” в центре, высокая энергия, контрастные агрессивные цвета. Можно комикс/anti‑design подачу, но всё должно быть читабельно. Без лишнего текста. Опционально: 1–3 trust‑печати по низу (цветные, яркие, очень похожие на печать FDA; натуральность/премиальность/безопасность). Размер как минимум как у буллитов — крупные, читабельны на телефоне. Даже одна печать — крупная. БЕЗ человека. Только продукт + power‑иконки.`,
|
|
107124
|
+
headlineAngle: `HOOK: угол «было → стало / победа над проблемой». Можно использовать стрелку “→” как часть перехода. 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107125
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + скорость + цифра) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107382
107126
|
},
|
|
107383
107127
|
{
|
|
107384
107128
|
name: 'Минимализм / Clean Big Text',
|
|
107385
|
-
prompt: `МИНИМАЛИЗМ / CLEAN BIG TEXT (без человека): белый или мягкий градиентный фон, премиальное ощущение. ОГРОМНЫЙ HOOK как главный элемент (CAPS, жирный, на контрастной плашке). Продукт крупно, минимум реквизита, минимум шума. Тени/премиальные материалы допустимы, но без лишнего текста. БЕЗ человека. Urgency и trust
|
|
107386
|
-
headlineAngle: `HOOK: одна максимально простая сильная фраза (коротко и ясно),
|
|
107387
|
-
bulletsFocus: `БУЛЛИТЫ:
|
|
107129
|
+
prompt: `МИНИМАЛИЗМ / CLEAN BIG TEXT (без человека): белый или мягкий градиентный фон, премиальное ощущение. ОГРОМНЫЙ HOOK как главный элемент (CAPS, жирный, на контрастной плашке). Продукт крупно, минимум реквизита, минимум шума. Тени/премиальные материалы допустимы, но без лишнего текста. БЕЗ человека. Urgency и trust‑печати в этом подходе НЕ рекомендуются — они нарушают чистоту и ощущение премиальности. Добавляй печати только если это критически необходимо, не более 1, но крупную — размер как у буллитов.`,
|
|
107130
|
+
headlineAngle: `HOOK: одна максимально простая сильная фраза (коротко и ясно), 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107131
|
+
bulletsFocus: `БУЛЛИТЫ: выбери 3 СЛУЧАЙНЫХ из пула (действие + срок + формула/цифра) в ПРОИЗВОЛЬНОМ порядке. Уникальный набор — не повторяй комбинации из других креативов.`
|
|
107388
107132
|
},
|
|
107389
107133
|
{
|
|
107390
107134
|
name: 'Problem Visual Punch',
|
|
@@ -107393,14 +107137,14 @@ const CREO_APPROACHES = [
|
|
|
107393
107137
|
- Фон: яркий сплошной цвет (красный/оранжевый) или агрессивный градиент
|
|
107394
107138
|
- Центр: КРУПНАЯ иконка/схема проблемной зоны тела, строго соответствующей категории продукта (для суставов — колено/сустав; для простаты — силуэт мужчины; для пищеварения — желудок), с красным свечением
|
|
107395
107139
|
- Продукт: крупно рядом
|
|
107396
|
-
- HOOK:
|
|
107140
|
+
- HOOK: 1–4 строки, до 12 слов, огромный, CAPS, ключевое слово проблемы явно
|
|
107397
107141
|
- БЕЗ буллитов
|
|
107398
107142
|
- Цена + скидка: крупно, заметно
|
|
107399
107143
|
- CTA: контрастная яркая кнопка
|
|
107400
107144
|
|
|
107401
107145
|
Цель: считывание за 1 секунду. Минимум текста, максимум визуального удара.
|
|
107402
|
-
БЕЗ человека. Никаких trust
|
|
107403
|
-
headlineAngle: `HOOK: «ПРОЩАЙ/КОНЕЦ/СТОП + [проблема]!». Эмоция победы/избавления.
|
|
107146
|
+
БЕЗ человека. Никаких trust‑печатей/urgency бейджей — только HOOK + PRICE + -50% + CTA.`,
|
|
107147
|
+
headlineAngle: `HOOK: «ПРОЩАЙ/КОНЕЦ/СТОП + [проблема]!». Эмоция победы/избавления. 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно.`,
|
|
107404
107148
|
bulletsFocus: `БУЛЛИТЫ: ЗАПРЕЩЕНЫ. Вся информация — в HOOK и крупных надписях.`
|
|
107405
107149
|
},
|
|
107406
107150
|
{
|
|
@@ -107414,9 +107158,34 @@ const CREO_APPROACHES = [
|
|
|
107414
107158
|
- Элементы выглядят «вырезанными и приклеенными»: неровные края, отсутствие выравнивания по сетке
|
|
107415
107159
|
- Крупные текстовые надписи рядом с продуктом: цена, скидка; допустимо одно короткое слово-восклицание («РАБОТАЕТ!», «ПРОВЕРЕНО!») на языке GEO
|
|
107416
107160
|
- Кнопка CTA: плоская, без градиентов, контрастный сплошной цвет
|
|
107417
|
-
- НИКАКИХ буллитов,
|
|
107418
|
-
headlineAngle: `HOOK: максимально прямолинейный — короткий, грубый, без украшений.
|
|
107161
|
+
- НИКАКИХ буллитов, trust‑печатей, urgency‑бейджей, иконок — только продукт и текст`,
|
|
107162
|
+
headlineAngle: `HOOK: максимально прямолинейный — короткий, грубый, без украшений. 1–4 строки, до 12 слов, CAPS, ключевое слово проблемы явно, как объявление на столбе. Никакого маркетингового лоска, никаких абстрактных слоганов.`,
|
|
107419
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.`
|
|
107420
107189
|
}
|
|
107421
107190
|
];
|
|
107422
107191
|
|