docs-combiner 0.1.11 → 0.1.12
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 +531 -837
- package/dist/renderer.js.map +1 -1
- package/package.json +1 -1
package/dist/renderer.js
CHANGED
|
@@ -99948,8 +99948,7 @@ function App() {
|
|
|
99948
99948
|
const [linkCopied, setLinkCopied] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99949
99949
|
const [openRouterKeyCopied, setOpenRouterKeyCopied] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99950
99950
|
const [loadingContentFromDrive, setLoadingContentFromDrive] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
|
99951
|
-
const [
|
|
99952
|
-
const [driveFilesFound, setDriveFilesFound] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({ content: false, images: false });
|
|
99951
|
+
const [driveFilesFound, setDriveFilesFound] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({ content: false });
|
|
99953
99952
|
// Theme state
|
|
99954
99953
|
const [darkMode, setDarkMode] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(() => {
|
|
99955
99954
|
const saved = localStorage.getItem('themeMode');
|
|
@@ -100117,7 +100116,7 @@ function App() {
|
|
|
100117
100116
|
const timeoutId = setTimeout(async () => {
|
|
100118
100117
|
try {
|
|
100119
100118
|
logToTerminal('log', '[Auto Save] Auto-saving settings to Google Drive');
|
|
100120
|
-
await saveGeneratedContentToDrive(folderId,
|
|
100119
|
+
await saveGeneratedContentToDrive(folderId, {
|
|
100121
100120
|
generateProduct,
|
|
100122
100121
|
generateGeo,
|
|
100123
100122
|
generateAdditionalInfo,
|
|
@@ -100130,14 +100129,13 @@ function App() {
|
|
|
100130
100129
|
}
|
|
100131
100130
|
}, 1000);
|
|
100132
100131
|
return () => clearTimeout(timeoutId);
|
|
100133
|
-
}, [generateProduct, generateGeo, generateAdditionalInfo, generatePriceWithCurrency, brand, link, driveFolderUrl,
|
|
100132
|
+
}, [generateProduct, generateGeo, generateAdditionalInfo, generatePriceWithCurrency, brand, link, driveFolderUrl, loadingContentFromDrive]);
|
|
100134
100133
|
// Load generated content from Google Drive when driveFolderUrl changes
|
|
100135
100134
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
100136
100135
|
if (!driveFolderUrl) {
|
|
100137
100136
|
logToTerminal('log', '[Load] No driveFolderUrl, skipping load');
|
|
100138
100137
|
setLoadingContentFromDrive(false);
|
|
100139
|
-
|
|
100140
|
-
setDriveFilesFound({ content: false, images: false });
|
|
100138
|
+
setDriveFilesFound({ content: false });
|
|
100141
100139
|
// Clear all data when driveFolderUrl is cleared
|
|
100142
100140
|
setGeneratedTitlesData([]);
|
|
100143
100141
|
setGeneratedTextsData([]);
|
|
@@ -100151,8 +100149,7 @@ function App() {
|
|
|
100151
100149
|
if (!folderId) {
|
|
100152
100150
|
logToTerminal('warn', '[Load] Invalid driveFolderUrl, cannot extract folderId:', driveFolderUrl);
|
|
100153
100151
|
setLoadingContentFromDrive(false);
|
|
100154
|
-
|
|
100155
|
-
setDriveFilesFound({ content: false, images: false });
|
|
100152
|
+
setDriveFilesFound({ content: false });
|
|
100156
100153
|
// Clear all data when folderId cannot be extracted
|
|
100157
100154
|
setGeneratedTitlesData([]);
|
|
100158
100155
|
setGeneratedTextsData([]);
|
|
@@ -100171,8 +100168,7 @@ function App() {
|
|
|
100171
100168
|
setTexts(['']);
|
|
100172
100169
|
setLink('');
|
|
100173
100170
|
setLoadingContentFromDrive(true);
|
|
100174
|
-
|
|
100175
|
-
setDriveFilesFound({ content: false, images: false });
|
|
100171
|
+
setDriveFilesFound({ content: false });
|
|
100176
100172
|
// Load content from Google Drive
|
|
100177
100173
|
loadGeneratedContentFromDrive(folderId).then((result) => {
|
|
100178
100174
|
logToTerminal('log', '[Load] Content loading completed, found:', result.found);
|
|
@@ -100181,8 +100177,6 @@ function App() {
|
|
|
100181
100177
|
logToTerminal('error', '[Load] Error loading content from Google Drive:', err);
|
|
100182
100178
|
setLoadingContentFromDrive(false);
|
|
100183
100179
|
});
|
|
100184
|
-
// Креативы не кешируются — не загружаем изображения при открытии
|
|
100185
|
-
setLoadingImagesFromDrive(false);
|
|
100186
100180
|
}, [driveFolderUrl]);
|
|
100187
100181
|
// Sync generatedTitlesData with titles when titles changes (except when user is editing in UI)
|
|
100188
100182
|
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
@@ -100445,8 +100439,12 @@ function App() {
|
|
|
100445
100439
|
const name = f.name?.toLowerCase() || '';
|
|
100446
100440
|
return name === 'product.png' || name === 'product.jpg';
|
|
100447
100441
|
});
|
|
100442
|
+
const hasCreativeImages = data.files.some((f) => {
|
|
100443
|
+
const name = f.name?.toLowerCase() || '';
|
|
100444
|
+
return name !== 'product.png' && name !== 'product.jpg';
|
|
100445
|
+
});
|
|
100448
100446
|
if (!cancelled) {
|
|
100449
|
-
setFolderFilesInfo({ hasProduct });
|
|
100447
|
+
setFolderFilesInfo({ hasProduct, hasCreativeImages });
|
|
100450
100448
|
// If product not found, poll every 10 seconds until found
|
|
100451
100449
|
if (!hasProduct) {
|
|
100452
100450
|
pollTimeoutId = setTimeout(() => {
|
|
@@ -101599,20 +101597,8 @@ function App() {
|
|
|
101599
101597
|
try {
|
|
101600
101598
|
const folderId = extractFolderId(driveFolderUrl);
|
|
101601
101599
|
if (folderId) {
|
|
101602
|
-
addLog(formatLogMessage('log', '💾 Saving
|
|
101603
|
-
|
|
101604
|
-
index: index + 1,
|
|
101605
|
-
title: p.title,
|
|
101606
|
-
generating: false,
|
|
101607
|
-
failed: !p.title
|
|
101608
|
-
}));
|
|
101609
|
-
const currentTextsData = pairsResult.map((p, index) => ({
|
|
101610
|
-
index: index + 1,
|
|
101611
|
-
text: p.text,
|
|
101612
|
-
generating: false,
|
|
101613
|
-
failed: !p.text
|
|
101614
|
-
}));
|
|
101615
|
-
await saveGeneratedContentToDrive(folderId, currentTitlesData, currentTextsData, {
|
|
101600
|
+
addLog(formatLogMessage('log', '💾 Saving settings to Google Drive...'));
|
|
101601
|
+
await saveGeneratedContentToDrive(folderId, {
|
|
101616
101602
|
generateProduct,
|
|
101617
101603
|
generateGeo,
|
|
101618
101604
|
generateAdditionalInfo,
|
|
@@ -102207,24 +102193,26 @@ function App() {
|
|
|
102207
102193
|
if (!file)
|
|
102208
102194
|
return;
|
|
102209
102195
|
e.target.value = '';
|
|
102210
|
-
const validToken = await getValidAccessToken();
|
|
102211
|
-
if (!validToken) {
|
|
102212
|
-
alert('Войдите в Google Drive');
|
|
102213
|
-
return;
|
|
102214
|
-
}
|
|
102215
|
-
const folderId = extractFolderId(driveFolderUrl);
|
|
102216
|
-
if (!folderId) {
|
|
102217
|
-
alert('Некорректная ссылка на папку Google Drive');
|
|
102218
|
-
return;
|
|
102219
|
-
}
|
|
102220
102196
|
const ext = file.name.toLowerCase().split('.').pop();
|
|
102221
102197
|
if (ext !== 'png' && ext !== 'jpg' && ext !== 'jpeg') {
|
|
102222
102198
|
alert('Выберите изображение PNG или JPG');
|
|
102223
102199
|
return;
|
|
102224
102200
|
}
|
|
102225
102201
|
setUploadingProduct(true);
|
|
102226
|
-
const addLog = () => { };
|
|
102227
102202
|
try {
|
|
102203
|
+
const validToken = await getValidAccessToken();
|
|
102204
|
+
if (!validToken) {
|
|
102205
|
+
alert('Войдите в Google Drive');
|
|
102206
|
+
setUploadingProduct(false);
|
|
102207
|
+
return;
|
|
102208
|
+
}
|
|
102209
|
+
const folderId = extractFolderId(driveFolderUrl);
|
|
102210
|
+
if (!folderId) {
|
|
102211
|
+
alert('Некорректная ссылка на папку Google Drive');
|
|
102212
|
+
setUploadingProduct(false);
|
|
102213
|
+
return;
|
|
102214
|
+
}
|
|
102215
|
+
const addLog = () => { };
|
|
102228
102216
|
const dataUrl = await new Promise((resolve, reject) => {
|
|
102229
102217
|
const reader = new FileReader();
|
|
102230
102218
|
reader.onloadend = () => resolve(reader.result);
|
|
@@ -102235,7 +102223,7 @@ function App() {
|
|
|
102235
102223
|
logToTerminal('log', '📤 Загрузка product.png/jpg...');
|
|
102236
102224
|
await uploadImageToDrive(dataUrl, filename, folderId, addLog);
|
|
102237
102225
|
logToTerminal('log', '✅ product.png/jpg загружен');
|
|
102238
|
-
setFolderFilesInfo({ hasProduct: true });
|
|
102226
|
+
setFolderFilesInfo(prev => prev ? { ...prev, hasProduct: true } : { hasProduct: true });
|
|
102239
102227
|
alert('product.png/jpg успешно загружен в папку!');
|
|
102240
102228
|
}
|
|
102241
102229
|
catch (err) {
|
|
@@ -102631,483 +102619,476 @@ function App() {
|
|
|
102631
102619
|
alert('OpenRouter API key is not set');
|
|
102632
102620
|
return;
|
|
102633
102621
|
}
|
|
102634
|
-
const validToken = await getValidAccessToken();
|
|
102635
|
-
if (!validToken) {
|
|
102636
|
-
alert('Please log in with Google first');
|
|
102637
|
-
return;
|
|
102638
|
-
}
|
|
102639
102622
|
setGeneratingLanding(true);
|
|
102640
102623
|
setLandingGenerationLogs([]);
|
|
102641
102624
|
setLandingProcessStartTime(Date.now());
|
|
102642
|
-
const addLog = (msg) => {
|
|
102643
|
-
setLandingGenerationLogs(prev => [...prev, msg]);
|
|
102644
|
-
};
|
|
102645
102625
|
try {
|
|
102646
|
-
const
|
|
102647
|
-
if (!
|
|
102648
|
-
|
|
102649
|
-
|
|
102650
|
-
|
|
102651
|
-
addLog(formatLogMessage('log', '🔍 Получение изображения продукта...'));
|
|
102652
|
-
logToTerminal('log', '🔍 Получение изображения продукта...');
|
|
102653
|
-
const productImage = await fetchProductImage(folderId);
|
|
102654
|
-
if (!productImage) {
|
|
102655
|
-
throw new Error('product.png/jpg not found in the folder');
|
|
102656
|
-
}
|
|
102657
|
-
// Download product image as blob
|
|
102658
|
-
addLog(formatLogMessage('log', '📥 Скачивание product.png...'));
|
|
102659
|
-
logToTerminal('log', '📥 Скачивание product.png...');
|
|
102660
|
-
let token = await getValidAccessToken();
|
|
102661
|
-
if (!token) {
|
|
102662
|
-
throw new Error('Not logged in to Google Drive');
|
|
102626
|
+
const validToken = await getValidAccessToken();
|
|
102627
|
+
if (!validToken) {
|
|
102628
|
+
alert('Please log in with Google first');
|
|
102629
|
+
setGeneratingLanding(false);
|
|
102630
|
+
return;
|
|
102663
102631
|
}
|
|
102664
|
-
|
|
102665
|
-
|
|
102666
|
-
|
|
102632
|
+
const addLog = (msg) => {
|
|
102633
|
+
setLandingGenerationLogs(prev => [...prev, msg]);
|
|
102634
|
+
};
|
|
102635
|
+
try {
|
|
102636
|
+
const folderId = extractFolderId(driveFolderUrl);
|
|
102637
|
+
if (!folderId) {
|
|
102638
|
+
throw new Error('Invalid Google Drive Folder URL');
|
|
102667
102639
|
}
|
|
102668
|
-
|
|
102669
|
-
|
|
102670
|
-
|
|
102671
|
-
|
|
102672
|
-
|
|
102673
|
-
|
|
102674
|
-
|
|
102675
|
-
|
|
102676
|
-
|
|
102677
|
-
|
|
102678
|
-
|
|
102679
|
-
|
|
102640
|
+
// Get product image
|
|
102641
|
+
addLog(formatLogMessage('log', '🔍 Получение изображения продукта...'));
|
|
102642
|
+
logToTerminal('log', '🔍 Получение изображения продукта...');
|
|
102643
|
+
const productImage = await fetchProductImage(folderId);
|
|
102644
|
+
if (!productImage) {
|
|
102645
|
+
throw new Error('product.png/jpg not found in the folder');
|
|
102646
|
+
}
|
|
102647
|
+
// Download product image as blob
|
|
102648
|
+
addLog(formatLogMessage('log', '📥 Скачивание product.png...'));
|
|
102649
|
+
logToTerminal('log', '📥 Скачивание product.png...');
|
|
102650
|
+
let token = await getValidAccessToken();
|
|
102651
|
+
if (!token) {
|
|
102652
|
+
throw new Error('Not logged in to Google Drive');
|
|
102653
|
+
}
|
|
102654
|
+
let imageResponse = await fetch(`https://www.googleapis.com/drive/v3/files/${productImage.id}?alt=media`, {
|
|
102655
|
+
headers: {
|
|
102656
|
+
'Authorization': `Bearer ${token}`
|
|
102657
|
+
}
|
|
102658
|
+
});
|
|
102659
|
+
// Handle 401 - try to refresh token and retry
|
|
102660
|
+
if (imageResponse.status === 401 && refreshToken) {
|
|
102661
|
+
addLog(formatLogMessage('log', '🔄 Got 401, refreshing token...'));
|
|
102662
|
+
const newToken = await refreshAccessToken(refreshToken);
|
|
102663
|
+
if (newToken) {
|
|
102664
|
+
token = newToken;
|
|
102665
|
+
addLog(formatLogMessage('log', '✅ Token refreshed, retrying download...'));
|
|
102666
|
+
imageResponse = await fetch(`https://www.googleapis.com/drive/v3/files/${productImage.id}?alt=media`, {
|
|
102667
|
+
headers: {
|
|
102668
|
+
'Authorization': `Bearer ${newToken}`
|
|
102669
|
+
}
|
|
102670
|
+
});
|
|
102671
|
+
}
|
|
102672
|
+
}
|
|
102673
|
+
if (!imageResponse.ok) {
|
|
102674
|
+
throw new Error(`Failed to download product image: ${imageResponse.status} ${imageResponse.statusText}`);
|
|
102675
|
+
}
|
|
102676
|
+
const productImageBlob = await imageResponse.blob();
|
|
102677
|
+
addLog(formatLogMessage('log', '✅ Изображение продукта скачано'));
|
|
102678
|
+
logToTerminal('log', '✅ Изображение продукта скачано, размер:', (productImageBlob.size / 1024).toFixed(2), 'KB');
|
|
102679
|
+
// Generate HTML landing page
|
|
102680
|
+
if (!generateProduct.trim() || !generateGeo.trim()) {
|
|
102681
|
+
throw new Error('Please fill in Product and Geo fields');
|
|
102682
|
+
}
|
|
102683
|
+
addLog(formatLogMessage('log', '🎨 Генерация HTML лендинга...'));
|
|
102684
|
+
logToTerminal('log', '🎨 Генерация HTML лендинга...');
|
|
102685
|
+
const htmlContent = await generateLandingHTML(generateProduct, generateGeo, addLog);
|
|
102686
|
+
addLog(formatLogMessage('log', '✅ HTML лендинг сгенерирован'));
|
|
102687
|
+
// Save HTML and image for preview
|
|
102688
|
+
setGeneratedLandingHTML(htmlContent);
|
|
102689
|
+
setGeneratedLandingImageBlob(productImageBlob);
|
|
102690
|
+
// Open preview in browser immediately
|
|
102691
|
+
try {
|
|
102692
|
+
addLog(formatLogMessage('log', '👁️ Открытие предпросмотра в браузере...'));
|
|
102693
|
+
logToTerminal('log', '👁️ Открытие предпросмотра в браузере...');
|
|
102694
|
+
// Convert image blob to base64
|
|
102695
|
+
const imageBase64 = await new Promise((resolve, reject) => {
|
|
102696
|
+
const reader = new FileReader();
|
|
102697
|
+
reader.onloadend = () => {
|
|
102698
|
+
const base64String = reader.result;
|
|
102699
|
+
resolve(base64String);
|
|
102700
|
+
};
|
|
102701
|
+
reader.onerror = reject;
|
|
102702
|
+
reader.readAsDataURL(productImageBlob);
|
|
102680
102703
|
});
|
|
102704
|
+
// Replace relative image path with base64 data URL in HTML
|
|
102705
|
+
const htmlWithImage = htmlContent.replace(/src=["']product\.(png|jpe?g)["']/gi, `src="${imageBase64}"`);
|
|
102706
|
+
// Create blob URL and open in new window
|
|
102707
|
+
const blob = new Blob([htmlWithImage], { type: 'text/html' });
|
|
102708
|
+
const url = URL.createObjectURL(blob);
|
|
102709
|
+
window.open(url, '_blank');
|
|
102710
|
+
addLog(formatLogMessage('log', '✅ Предпросмотр открыт в браузере'));
|
|
102711
|
+
logToTerminal('log', '✅ Предпросмотр открыт в браузере');
|
|
102712
|
+
// Clean up URL after a delay (give browser time to load)
|
|
102713
|
+
setTimeout(() => {
|
|
102714
|
+
URL.revokeObjectURL(url);
|
|
102715
|
+
}, 1000);
|
|
102681
102716
|
}
|
|
102717
|
+
catch (previewErr) {
|
|
102718
|
+
addLog(formatLogMessage('warn', '⚠️ Не удалось открыть предпросмотр: ' + previewErr.message));
|
|
102719
|
+
logToTerminal('warn', '⚠️ Не удалось открыть предпросмотр:', previewErr);
|
|
102720
|
+
}
|
|
102721
|
+
// Create ZIP archive with HTML and product image
|
|
102722
|
+
addLog(formatLogMessage('log', '📦 Создание ZIP архива с HTML и изображением...'));
|
|
102723
|
+
logToTerminal('log', '📦 Создание ZIP архива...');
|
|
102724
|
+
const zipBlob = await createZipArchive(htmlContent, productImageBlob, addLog);
|
|
102725
|
+
addLog(formatLogMessage('log', '✅ ZIP архив создан'));
|
|
102726
|
+
// Upload ZIP to Google Drive
|
|
102727
|
+
const zipFilename = `landing_${Date.now()}.zip`;
|
|
102728
|
+
addLog(formatLogMessage('log', '📤 Загрузка ZIP архива на Google Drive...'));
|
|
102729
|
+
logToTerminal('log', '📤 Загрузка ZIP архива на Google Drive...');
|
|
102730
|
+
const driveLink = await uploadZipToDrive(zipBlob, zipFilename, folderId, addLog);
|
|
102731
|
+
addLog(formatLogMessage('log', '✅ ZIP архив загружен на Google Drive'));
|
|
102732
|
+
logToTerminal('log', '✅ ZIP архив загружен:', driveLink);
|
|
102682
102733
|
}
|
|
102683
|
-
|
|
102684
|
-
|
|
102685
|
-
|
|
102686
|
-
|
|
102687
|
-
|
|
102688
|
-
logToTerminal('log', '✅ Изображение продукта скачано, размер:', (productImageBlob.size / 1024).toFixed(2), 'KB');
|
|
102689
|
-
// Generate HTML landing page
|
|
102690
|
-
if (!generateProduct.trim() || !generateGeo.trim()) {
|
|
102691
|
-
throw new Error('Please fill in Product and Geo fields');
|
|
102692
|
-
}
|
|
102693
|
-
addLog(formatLogMessage('log', '🎨 Генерация HTML лендинга...'));
|
|
102694
|
-
logToTerminal('log', '🎨 Генерация HTML лендинга...');
|
|
102695
|
-
const htmlContent = await generateLandingHTML(generateProduct, generateGeo, addLog);
|
|
102696
|
-
addLog(formatLogMessage('log', '✅ HTML лендинг сгенерирован'));
|
|
102697
|
-
// Save HTML and image for preview
|
|
102698
|
-
setGeneratedLandingHTML(htmlContent);
|
|
102699
|
-
setGeneratedLandingImageBlob(productImageBlob);
|
|
102700
|
-
// Open preview in browser immediately
|
|
102701
|
-
try {
|
|
102702
|
-
addLog(formatLogMessage('log', '👁️ Открытие предпросмотра в браузере...'));
|
|
102703
|
-
logToTerminal('log', '👁️ Открытие предпросмотра в браузере...');
|
|
102704
|
-
// Convert image blob to base64
|
|
102705
|
-
const imageBase64 = await new Promise((resolve, reject) => {
|
|
102706
|
-
const reader = new FileReader();
|
|
102707
|
-
reader.onloadend = () => {
|
|
102708
|
-
const base64String = reader.result;
|
|
102709
|
-
resolve(base64String);
|
|
102710
|
-
};
|
|
102711
|
-
reader.onerror = reject;
|
|
102712
|
-
reader.readAsDataURL(productImageBlob);
|
|
102713
|
-
});
|
|
102714
|
-
// Replace relative image path with base64 data URL in HTML
|
|
102715
|
-
const htmlWithImage = htmlContent.replace(/src=["']product\.(png|jpe?g)["']/gi, `src="${imageBase64}"`);
|
|
102716
|
-
// Create blob URL and open in new window
|
|
102717
|
-
const blob = new Blob([htmlWithImage], { type: 'text/html' });
|
|
102718
|
-
const url = URL.createObjectURL(blob);
|
|
102719
|
-
window.open(url, '_blank');
|
|
102720
|
-
addLog(formatLogMessage('log', '✅ Предпросмотр открыт в браузере'));
|
|
102721
|
-
logToTerminal('log', '✅ Предпросмотр открыт в браузере');
|
|
102722
|
-
// Clean up URL after a delay (give browser time to load)
|
|
102723
|
-
setTimeout(() => {
|
|
102724
|
-
URL.revokeObjectURL(url);
|
|
102725
|
-
}, 1000);
|
|
102734
|
+
catch (err) {
|
|
102735
|
+
const errorMsg = 'Ошибка создания лендинга: ' + err.message;
|
|
102736
|
+
addLog(formatLogMessage('error', errorMsg));
|
|
102737
|
+
logToTerminal('error', '❌ Ошибка создания лендинга:', err);
|
|
102738
|
+
alert(errorMsg);
|
|
102726
102739
|
}
|
|
102727
|
-
|
|
102728
|
-
|
|
102729
|
-
logToTerminal('warn', '⚠️ Не удалось открыть предпросмотр:', previewErr);
|
|
102740
|
+
finally {
|
|
102741
|
+
setGeneratingLanding(false);
|
|
102730
102742
|
}
|
|
102731
|
-
// Create ZIP archive with HTML and product image
|
|
102732
|
-
addLog(formatLogMessage('log', '📦 Создание ZIP архива с HTML и изображением...'));
|
|
102733
|
-
logToTerminal('log', '📦 Создание ZIP архива...');
|
|
102734
|
-
const zipBlob = await createZipArchive(htmlContent, productImageBlob, addLog);
|
|
102735
|
-
addLog(formatLogMessage('log', '✅ ZIP архив создан'));
|
|
102736
|
-
// Upload ZIP to Google Drive
|
|
102737
|
-
const zipFilename = `landing_${Date.now()}.zip`;
|
|
102738
|
-
addLog(formatLogMessage('log', '📤 Загрузка ZIP архива на Google Drive...'));
|
|
102739
|
-
logToTerminal('log', '📤 Загрузка ZIP архива на Google Drive...');
|
|
102740
|
-
const driveLink = await uploadZipToDrive(zipBlob, zipFilename, folderId, addLog);
|
|
102741
|
-
addLog(formatLogMessage('log', '✅ ZIP архив загружен на Google Drive'));
|
|
102742
|
-
logToTerminal('log', '✅ ZIP архив загружен:', driveLink);
|
|
102743
102743
|
}
|
|
102744
102744
|
catch (err) {
|
|
102745
|
-
const errorMsg = 'Ошибка создания лендинга: ' + err.message;
|
|
102746
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102747
|
-
logToTerminal('error', '❌ Ошибка создания лендинга:', err);
|
|
102748
|
-
alert(errorMsg);
|
|
102749
|
-
}
|
|
102750
|
-
finally {
|
|
102751
102745
|
setGeneratingLanding(false);
|
|
102746
|
+
logToTerminal('error', '❌ Error:', err?.message || err);
|
|
102752
102747
|
}
|
|
102753
102748
|
};
|
|
102754
102749
|
const handleGenerateImages = async () => {
|
|
102755
|
-
logToTerminal('log', '🎨 === Starting image generation process ===');
|
|
102756
|
-
logToTerminal('log', '📅 Timestamp:', new Date().toISOString());
|
|
102757
|
-
const validToken = await getValidAccessToken();
|
|
102758
|
-
if (!validToken) {
|
|
102759
|
-
logToTerminal('error', '❌ Not logged in to Google');
|
|
102760
|
-
alert('Please log in with Google first');
|
|
102761
|
-
return;
|
|
102762
|
-
}
|
|
102763
|
-
logToTerminal('log', '✅ Google authentication OK');
|
|
102764
|
-
if (!generateProduct.trim() || !generateGeo.trim()) {
|
|
102765
|
-
logToTerminal('error', '❌ Missing product or geo');
|
|
102766
|
-
alert('Please fill in Product and Geo fields');
|
|
102767
|
-
return;
|
|
102768
|
-
}
|
|
102769
|
-
const { price: generatePrice, currency: generateCurrency, currencySymbol } = parsePriceAndCurrency(generatePriceWithCurrency);
|
|
102770
|
-
// Use original currency symbol if available, otherwise use currency code
|
|
102771
|
-
const currencyForPrompt = currencySymbol || generateCurrency;
|
|
102772
|
-
logToTerminal('log', '📦 Product:', generateProduct);
|
|
102773
|
-
logToTerminal('log', '🌍 Geo:', generateGeo);
|
|
102774
|
-
logToTerminal('log', '💰 Price:', generatePrice, currencyForPrompt);
|
|
102775
|
-
if (!driveFolderUrl.trim()) {
|
|
102776
|
-
logToTerminal('error', '❌ Missing Drive folder URL');
|
|
102777
|
-
alert('Please fill in Google Drive Folder URL');
|
|
102778
|
-
return;
|
|
102779
|
-
}
|
|
102780
|
-
logToTerminal('log', '📁 Drive folder URL:', driveFolderUrl);
|
|
102781
102750
|
setGeneratingImages(true);
|
|
102782
102751
|
setImagesGenerationLogs([]);
|
|
102783
102752
|
setImagesProcessStartTime(Date.now());
|
|
102784
102753
|
setCheckingImages(false);
|
|
102785
|
-
|
|
102786
|
-
|
|
102787
|
-
setImagesGenerationLogs(prev => [...prev, msg]);
|
|
102788
|
-
};
|
|
102754
|
+
logToTerminal('log', '🎨 === Starting image generation process ===');
|
|
102755
|
+
logToTerminal('log', '📅 Timestamp:', new Date().toISOString());
|
|
102789
102756
|
try {
|
|
102790
|
-
const
|
|
102791
|
-
if (!
|
|
102792
|
-
|
|
102793
|
-
|
|
102794
|
-
|
|
102757
|
+
const validToken = await getValidAccessToken();
|
|
102758
|
+
if (!validToken) {
|
|
102759
|
+
logToTerminal('error', '❌ Not logged in to Google');
|
|
102760
|
+
alert('Please log in with Google first');
|
|
102761
|
+
setGeneratingImages(false);
|
|
102762
|
+
return;
|
|
102795
102763
|
}
|
|
102796
|
-
|
|
102797
|
-
|
|
102798
|
-
|
|
102799
|
-
|
|
102800
|
-
|
|
102801
|
-
|
|
102802
|
-
addLog(formatLogMessage('error', errorMsg));
|
|
102803
|
-
throw new Error(errorMsg);
|
|
102764
|
+
logToTerminal('log', '✅ Google authentication OK');
|
|
102765
|
+
if (!generateProduct.trim() || !generateGeo.trim()) {
|
|
102766
|
+
logToTerminal('error', '❌ Missing product or geo');
|
|
102767
|
+
alert('Please fill in Product and Geo fields');
|
|
102768
|
+
setGeneratingImages(false);
|
|
102769
|
+
return;
|
|
102804
102770
|
}
|
|
102805
|
-
|
|
102806
|
-
|
|
102807
|
-
|
|
102808
|
-
|
|
102809
|
-
|
|
102810
|
-
|
|
102771
|
+
const { price: generatePrice, currency: generateCurrency, currencySymbol } = parsePriceAndCurrency(generatePriceWithCurrency);
|
|
102772
|
+
const currencyForPrompt = currencySymbol || generateCurrency;
|
|
102773
|
+
logToTerminal('log', '📦 Product:', generateProduct);
|
|
102774
|
+
logToTerminal('log', '🌍 Geo:', generateGeo);
|
|
102775
|
+
logToTerminal('log', '💰 Price:', generatePrice, currencyForPrompt);
|
|
102776
|
+
if (!driveFolderUrl.trim()) {
|
|
102777
|
+
logToTerminal('error', '❌ Missing Drive folder URL');
|
|
102778
|
+
alert('Please fill in Google Drive Folder URL');
|
|
102811
102779
|
setGeneratingImages(false);
|
|
102812
102780
|
return;
|
|
102813
102781
|
}
|
|
102814
|
-
|
|
102815
|
-
const
|
|
102816
|
-
const
|
|
102817
|
-
|
|
102818
|
-
|
|
102819
|
-
|
|
102820
|
-
|
|
102821
|
-
|
|
102822
|
-
|
|
102823
|
-
|
|
102824
|
-
|
|
102825
|
-
|
|
102826
|
-
|
|
102827
|
-
|
|
102828
|
-
|
|
102829
|
-
|
|
102830
|
-
|
|
102831
|
-
|
|
102832
|
-
|
|
102833
|
-
|
|
102834
|
-
|
|
102835
|
-
|
|
102836
|
-
|
|
102837
|
-
|
|
102838
|
-
|
|
102839
|
-
|
|
102840
|
-
|
|
102841
|
-
|
|
102842
|
-
|
|
102843
|
-
|
|
102844
|
-
|
|
102845
|
-
|
|
102846
|
-
|
|
102847
|
-
|
|
102848
|
-
|
|
102849
|
-
|
|
102850
|
-
|
|
102851
|
-
|
|
102852
|
-
|
|
102853
|
-
|
|
102854
|
-
|
|
102855
|
-
|
|
102856
|
-
|
|
102857
|
-
|
|
102858
|
-
|
|
102859
|
-
|
|
102860
|
-
|
|
102861
|
-
|
|
102862
|
-
|
|
102863
|
-
|
|
102864
|
-
|
|
102865
|
-
|
|
102866
|
-
const
|
|
102867
|
-
|
|
102868
|
-
|
|
102869
|
-
|
|
102870
|
-
|
|
102871
|
-
|
|
102872
|
-
|
|
102873
|
-
|
|
102874
|
-
|
|
102875
|
-
|
|
102876
|
-
|
|
102877
|
-
|
|
102878
|
-
|
|
102879
|
-
|
|
102880
|
-
|
|
102881
|
-
|
|
102882
|
-
|
|
102883
|
-
|
|
102884
|
-
|
|
102885
|
-
|
|
102886
|
-
|
|
102887
|
-
|
|
102888
|
-
|
|
102889
|
-
|
|
102890
|
-
|
|
102891
|
-
|
|
102892
|
-
|
|
102893
|
-
|
|
102782
|
+
logToTerminal('log', '📁 Drive folder URL:', driveFolderUrl);
|
|
102783
|
+
const overallStartTime = Date.now();
|
|
102784
|
+
const addLog = (msg) => {
|
|
102785
|
+
setImagesGenerationLogs(prev => [...prev, msg]);
|
|
102786
|
+
};
|
|
102787
|
+
try {
|
|
102788
|
+
const folderId = extractFolderId(driveFolderUrl);
|
|
102789
|
+
if (!folderId) {
|
|
102790
|
+
const errorMsg = 'Invalid Google Drive Folder URL';
|
|
102791
|
+
addLog(formatLogMessage('error', '❌ Invalid folder ID extracted'));
|
|
102792
|
+
throw new Error(errorMsg);
|
|
102793
|
+
}
|
|
102794
|
+
addLog(formatLogMessage('log', '✅ Folder ID extracted:', folderId));
|
|
102795
|
+
// Check for product.png
|
|
102796
|
+
addLog(formatLogMessage('log', '🔍 Checking for product.png...'));
|
|
102797
|
+
const productImage = await fetchProductImage(folderId);
|
|
102798
|
+
if (!productImage) {
|
|
102799
|
+
const errorMsg = 'product.png/jpg not found in the folder. Please generate it first using "Generate Product from Banka" button.';
|
|
102800
|
+
addLog(formatLogMessage('error', errorMsg));
|
|
102801
|
+
throw new Error(errorMsg);
|
|
102802
|
+
}
|
|
102803
|
+
addLog(formatLogMessage('log', '✅ product.png found'));
|
|
102804
|
+
// Generate images with different approaches (количество по каждому подходу)
|
|
102805
|
+
const approaches = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.getCreoApproaches)();
|
|
102806
|
+
if (approaches.length === 0) {
|
|
102807
|
+
addLog(formatLogMessage('error', '❌ Укажите количество изображений (хотя бы 1) в настройках подходов'));
|
|
102808
|
+
alert('Укажите количество изображений (1–4) хотя бы для одного подхода в настройках');
|
|
102809
|
+
setGeneratingImages(false);
|
|
102810
|
+
return;
|
|
102811
|
+
}
|
|
102812
|
+
// При "both" — на каждый подход генерируем и 1:1, и 2:3
|
|
102813
|
+
const useBoth = imageAspectRatio === 'both';
|
|
102814
|
+
const tasks = useBoth
|
|
102815
|
+
? approaches.flatMap(a => [
|
|
102816
|
+
{ approach: a, ratio: '1:1' },
|
|
102817
|
+
{ approach: a, ratio: '2:3' }
|
|
102818
|
+
])
|
|
102819
|
+
: approaches.map(a => ({ approach: a, ratio: imageAspectRatio }));
|
|
102820
|
+
const additionalInfoLine = generateAdditionalInfo.trim()
|
|
102821
|
+
? `\n📋 ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ О ПРОДУКТЕ: ${generateAdditionalInfo.trim()}`
|
|
102822
|
+
: '';
|
|
102823
|
+
const imagePrompts = tasks.map(t => {
|
|
102824
|
+
const basePromptStructure = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.getImageGenerationBasePrompt)(generateGeo, generatePrice, currencyForPrompt, undefined, t.ratio);
|
|
102825
|
+
const bulletsLine = t.approach.noBullets
|
|
102826
|
+
? t.approach.bulletsFocus
|
|
102827
|
+
: (() => {
|
|
102828
|
+
const [b1, b2, b3] = (0,_prompts__WEBPACK_IMPORTED_MODULE_1__.pickRandomBullets)(t.approach.name);
|
|
102829
|
+
return `ОБЯЗАТЕЛЬНЫЕ БУЛЛЕТЫ (используй именно эти 3, в указанном порядке; переведи на язык ${generateGeo}): 1) ${b1} 2) ${b2} 3) ${b3}`;
|
|
102830
|
+
})();
|
|
102831
|
+
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 обязателен; буллиты добавляй только если они разрешены для подхода).`;
|
|
102832
|
+
});
|
|
102833
|
+
// Initialize placeholders for all images
|
|
102834
|
+
const initialPlaceholders = tasks.map((t, index) => ({
|
|
102835
|
+
index: index + 1,
|
|
102836
|
+
imageUrl: undefined,
|
|
102837
|
+
approach: t.approach.name + (useBoth ? ` (${t.ratio})` : ''),
|
|
102838
|
+
aspectRatio: t.ratio,
|
|
102839
|
+
uploaded: false,
|
|
102840
|
+
checking: false,
|
|
102841
|
+
checkStatus: 'pending',
|
|
102842
|
+
checkResult: undefined,
|
|
102843
|
+
checkErrors: undefined,
|
|
102844
|
+
originalPrompt: undefined,
|
|
102845
|
+
productImageUrl: undefined,
|
|
102846
|
+
regenerating: false,
|
|
102847
|
+
customRegeneratePrompt: '',
|
|
102848
|
+
failed: false,
|
|
102849
|
+
generating: false // In sequential mode, images start queued (not generating)
|
|
102850
|
+
}));
|
|
102851
|
+
setGeneratedImagesData(initialPlaceholders);
|
|
102852
|
+
addLog(formatLogMessage('log', `📝 Generated prompts for ${tasks.length} images${useBoth ? ' (1:1 + 2:3 на каждый подход)' : ''}`));
|
|
102853
|
+
const maxParallel = 100; // like infinity
|
|
102854
|
+
addLog(formatLogMessage('log', `🚀 Generating ${tasks.length} images in parallel (up to ${maxParallel} at a time)...`));
|
|
102855
|
+
const generationStartTime = Date.now();
|
|
102856
|
+
const resultsMap = new Map();
|
|
102857
|
+
// Ensure each slot has prompt+product reference from the start (needed for regeneration even on failures)
|
|
102858
|
+
setGeneratedImagesData(prev => prev.map((img, i) => ({
|
|
102859
|
+
...img,
|
|
102860
|
+
originalPrompt: img.originalPrompt || imagePrompts[i],
|
|
102861
|
+
productImageUrl: img.productImageUrl || productImage.url
|
|
102862
|
+
})));
|
|
102863
|
+
const runOne = async (i, isRetry) => {
|
|
102864
|
+
const imageIndex = i + 1;
|
|
102865
|
+
const task = tasks[i];
|
|
102866
|
+
const approachName = task?.approach.name || 'Unknown';
|
|
102867
|
+
const prompt = imagePrompts[i];
|
|
102868
|
+
const ratio = task?.ratio || '1:1';
|
|
102869
|
+
addLog(formatLogMessage('log', `${isRetry ? '🔄' : '🎨'} Генерация изображения ${imageIndex}/${tasks.length} (${approachName} ${ratio})...`));
|
|
102870
|
+
// Reset slot state for this run
|
|
102894
102871
|
setGeneratedImagesData(prev => prev.map(img => img.index === imageIndex
|
|
102895
102872
|
? {
|
|
102896
102873
|
...img,
|
|
102897
|
-
|
|
102898
|
-
generating: false,
|
|
102874
|
+
generating: true,
|
|
102899
102875
|
failed: false,
|
|
102900
|
-
|
|
102901
|
-
|
|
102876
|
+
errorMessage: undefined,
|
|
102877
|
+
imageUrl: undefined,
|
|
102878
|
+
checking: false,
|
|
102879
|
+
checkStatus: 'pending',
|
|
102880
|
+
checkResult: undefined,
|
|
102881
|
+
checkErrors: undefined,
|
|
102902
102882
|
originalPrompt: prompt,
|
|
102903
102883
|
productImageUrl: productImage.url
|
|
102904
102884
|
}
|
|
102905
102885
|
: img));
|
|
102906
|
-
|
|
102907
|
-
|
|
102908
|
-
|
|
102909
|
-
|
|
102910
|
-
|
|
102911
|
-
|
|
102912
|
-
|
|
102913
|
-
if (!res)
|
|
102914
|
-
throw new Error('No validation result');
|
|
102915
|
-
return { ...res, checkFailed: false };
|
|
102916
|
-
}
|
|
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
|
|
102886
|
+
try {
|
|
102887
|
+
const imageUrl = await generateImageWithDALLE(prompt, productImage.url, ratio);
|
|
102888
|
+
if (!imageUrl || typeof imageUrl !== 'string' || imageUrl.trim() === '') {
|
|
102889
|
+
throw new Error('Generated image URL is empty or invalid');
|
|
102890
|
+
}
|
|
102891
|
+
// Update UI immediately when image is ready
|
|
102892
|
+
setGeneratedImagesData(prev => prev.map(img => img.index === imageIndex
|
|
102930
102893
|
? {
|
|
102931
102894
|
...img,
|
|
102932
|
-
|
|
102933
|
-
|
|
102934
|
-
|
|
102935
|
-
|
|
102936
|
-
|
|
102937
|
-
|
|
102938
|
-
|
|
102939
|
-
: '',
|
|
102940
|
-
originalPrompt: img.originalPrompt || prompt,
|
|
102941
|
-
productImageUrl: img.productImageUrl || productImage.url
|
|
102895
|
+
imageUrl,
|
|
102896
|
+
generating: false,
|
|
102897
|
+
failed: false,
|
|
102898
|
+
checking: true,
|
|
102899
|
+
checkStatus: 'checking',
|
|
102900
|
+
originalPrompt: prompt,
|
|
102901
|
+
productImageUrl: productImage.url
|
|
102942
102902
|
}
|
|
102943
|
-
: img);
|
|
102944
|
-
|
|
102945
|
-
|
|
102946
|
-
|
|
102947
|
-
|
|
102948
|
-
|
|
102949
|
-
|
|
102950
|
-
|
|
102951
|
-
|
|
102952
|
-
|
|
102953
|
-
|
|
102954
|
-
|
|
102955
|
-
|
|
102956
|
-
|
|
102957
|
-
|
|
102958
|
-
|
|
102959
|
-
|
|
102960
|
-
|
|
102961
|
-
|
|
102962
|
-
|
|
102963
|
-
|
|
102964
|
-
|
|
102903
|
+
: img));
|
|
102904
|
+
addLog(formatLogMessage('log', `✅ Изображение ${imageIndex}/${tasks.length} сгенерировано`));
|
|
102905
|
+
// Validate right after generation (skip if validation disabled)
|
|
102906
|
+
const validationResult = validationDisabled
|
|
102907
|
+
? { status: 'ok', result: 'Проверка отключена', errors: [], checkFailed: false }
|
|
102908
|
+
: await (async () => {
|
|
102909
|
+
try {
|
|
102910
|
+
const res = await validateCreativeImage(imageUrl, generateProduct, generateGeo, addLog, approachName);
|
|
102911
|
+
if (!res)
|
|
102912
|
+
throw new Error('No validation result');
|
|
102913
|
+
return { ...res, checkFailed: false };
|
|
102914
|
+
}
|
|
102915
|
+
catch (validationErr) {
|
|
102916
|
+
const msg = validationErr?.message || String(validationErr);
|
|
102917
|
+
addLog(formatLogMessage('error', `❌ Ошибка проверки изображения ${imageIndex}: ${msg}`));
|
|
102918
|
+
return {
|
|
102919
|
+
status: 'needs_rebuild',
|
|
102920
|
+
result: `Ошибка проверки: ${msg}`,
|
|
102921
|
+
errors: [msg],
|
|
102922
|
+
checkFailed: true
|
|
102923
|
+
};
|
|
102924
|
+
}
|
|
102925
|
+
})();
|
|
102926
|
+
setGeneratedImagesData(prev => {
|
|
102927
|
+
const updated = prev.map(img => img.index === imageIndex
|
|
102928
|
+
? {
|
|
102929
|
+
...img,
|
|
102930
|
+
checking: false,
|
|
102931
|
+
checkFailed: validationResult.checkFailed ?? false,
|
|
102932
|
+
checkStatus: validationResult.status,
|
|
102933
|
+
checkResult: validationResult.result,
|
|
102934
|
+
checkErrors: validationResult.errors,
|
|
102935
|
+
customRegeneratePrompt: validationResult.errors.length > 0
|
|
102936
|
+
? validationResult.errors.map(err => err.replace(/^ОШИБКА:\s*/i, '')).join('\n')
|
|
102937
|
+
: '',
|
|
102938
|
+
originalPrompt: img.originalPrompt || prompt,
|
|
102939
|
+
productImageUrl: img.productImageUrl || productImage.url
|
|
102940
|
+
}
|
|
102941
|
+
: img);
|
|
102942
|
+
return updated;
|
|
102943
|
+
});
|
|
102944
|
+
return {
|
|
102945
|
+
index: imageIndex,
|
|
102946
|
+
imageUrl,
|
|
102947
|
+
success: true,
|
|
102948
|
+
approach: approachName,
|
|
102965
102949
|
originalPrompt: prompt,
|
|
102966
102950
|
productImageUrl: productImage.url
|
|
102967
|
-
}
|
|
102968
|
-
|
|
102969
|
-
|
|
102970
|
-
|
|
102971
|
-
|
|
102972
|
-
|
|
102973
|
-
|
|
102974
|
-
|
|
102975
|
-
|
|
102976
|
-
|
|
102977
|
-
|
|
102978
|
-
|
|
102979
|
-
|
|
102980
|
-
|
|
102981
|
-
|
|
102982
|
-
|
|
102983
|
-
|
|
102984
|
-
|
|
102985
|
-
let cursor = 0;
|
|
102986
|
-
const workerCount = Math.min(maxParallel, imagePrompts.length);
|
|
102987
|
-
const workers = Array.from({ length: workerCount }, () => (async () => {
|
|
102988
|
-
while (true) {
|
|
102989
|
-
const i = cursor++;
|
|
102990
|
-
if (i >= imagePrompts.length)
|
|
102991
|
-
break;
|
|
102992
|
-
const res = await runOne(i, false);
|
|
102993
|
-
resultsMap.set(res.index, res);
|
|
102994
|
-
}
|
|
102995
|
-
})());
|
|
102996
|
-
await Promise.all(workers);
|
|
102997
|
-
let generationResults = Array.from(resultsMap.values()).sort((a, b) => a.index - b.index);
|
|
102998
|
-
// Safety: fill any missing slots as failed
|
|
102999
|
-
if (generationResults.length !== imagePrompts.length) {
|
|
103000
|
-
for (let i = 0; i < imagePrompts.length; i++) {
|
|
103001
|
-
const idx = i + 1;
|
|
103002
|
-
if (!resultsMap.has(idx)) {
|
|
103003
|
-
generationResults.push({
|
|
103004
|
-
index: idx,
|
|
102951
|
+
};
|
|
102952
|
+
}
|
|
102953
|
+
catch (err) {
|
|
102954
|
+
const errorMessage = err?.message || String(err);
|
|
102955
|
+
addLog(formatLogMessage('error', `❌ Не удалось сгенерировать изображение ${imageIndex}/${tasks.length}: ${errorMessage}`));
|
|
102956
|
+
setGeneratedImagesData(prev => prev.map(img => img.index === imageIndex
|
|
102957
|
+
? {
|
|
102958
|
+
...img,
|
|
102959
|
+
generating: false,
|
|
102960
|
+
failed: true,
|
|
102961
|
+
errorMessage,
|
|
102962
|
+
checking: false,
|
|
102963
|
+
originalPrompt: prompt,
|
|
102964
|
+
productImageUrl: productImage.url
|
|
102965
|
+
}
|
|
102966
|
+
: img));
|
|
102967
|
+
return {
|
|
102968
|
+
index: imageIndex,
|
|
103005
102969
|
imageUrl: null,
|
|
103006
102970
|
success: false,
|
|
103007
|
-
error:
|
|
103008
|
-
approach:
|
|
103009
|
-
originalPrompt:
|
|
102971
|
+
error: err,
|
|
102972
|
+
approach: approachName,
|
|
102973
|
+
originalPrompt: prompt,
|
|
103010
102974
|
productImageUrl: productImage.url
|
|
103011
|
-
}
|
|
102975
|
+
};
|
|
103012
102976
|
}
|
|
103013
|
-
|
|
103014
|
-
|
|
103015
|
-
|
|
103016
|
-
|
|
103017
|
-
|
|
103018
|
-
|
|
103019
|
-
|
|
103020
|
-
const
|
|
103021
|
-
const
|
|
103022
|
-
// isRefusal и hasReasoning — случайные отказы модели → ретраим с логом
|
|
103023
|
-
// Всё остальное (сеть/JSON/таймаут/Provider) тоже ретраим
|
|
103024
|
-
return (isRefusal ||
|
|
103025
|
-
r.error?.hasReasoning === true ||
|
|
103026
|
-
errorMsg.includes('Invalid JSON response') ||
|
|
103027
|
-
errorMsg.includes('Unexpected end of JSON input') ||
|
|
103028
|
-
(errorMsg.includes('JSON') && errorMsg.includes('parse')) ||
|
|
103029
|
-
errorMsg.includes('Failed to fetch') ||
|
|
103030
|
-
errorMsg.includes('Network error') ||
|
|
103031
|
-
errorMsg.includes('timeout') ||
|
|
103032
|
-
errorMsg.includes('Request timeout') ||
|
|
103033
|
-
errorMsg.includes('Provider returned error') ||
|
|
103034
|
-
errorMsg.toLowerCase().includes('provider error'));
|
|
103035
|
-
});
|
|
103036
|
-
if (retryableErrors.length > 0) {
|
|
103037
|
-
const refusalCount = retryableErrors.filter(r => r.error?.isRefusal === true).length;
|
|
103038
|
-
const otherCount = retryableErrors.length - refusalCount;
|
|
103039
|
-
const parts = [];
|
|
103040
|
-
if (refusalCount > 0)
|
|
103041
|
-
parts.push(`${refusalCount} отказ${refusalCount > 1 ? 'а' : ''} модели`);
|
|
103042
|
-
if (otherCount > 0)
|
|
103043
|
-
parts.push(`${otherCount} сеть/таймаут/JSON`);
|
|
103044
|
-
addLog(formatLogMessage('log', `🔄 Повтор: ${parts.join(', ')}. Пробую ещё раз...`));
|
|
103045
|
-
const retryIndices = retryableErrors.map(r => r.index).sort((a, b) => a - b);
|
|
103046
|
-
let retryCursor = 0;
|
|
103047
|
-
const retryWorkers = Array.from({ length: Math.min(maxParallel, retryIndices.length) }, () => (async () => {
|
|
102977
|
+
finally {
|
|
102978
|
+
// Small yield to keep UI responsive in Electron
|
|
102979
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
102980
|
+
}
|
|
102981
|
+
};
|
|
102982
|
+
// First pass (limited concurrency)
|
|
102983
|
+
let cursor = 0;
|
|
102984
|
+
const workerCount = Math.min(maxParallel, imagePrompts.length);
|
|
102985
|
+
const workers = Array.from({ length: workerCount }, () => (async () => {
|
|
103048
102986
|
while (true) {
|
|
103049
|
-
const
|
|
103050
|
-
if (
|
|
102987
|
+
const i = cursor++;
|
|
102988
|
+
if (i >= imagePrompts.length)
|
|
103051
102989
|
break;
|
|
103052
|
-
|
|
103053
|
-
await new Promise(resolve => setTimeout(resolve, 1200));
|
|
103054
|
-
const i = idx - 1;
|
|
103055
|
-
const res = await runOne(i, true);
|
|
102990
|
+
const res = await runOne(i, false);
|
|
103056
102991
|
resultsMap.set(res.index, res);
|
|
103057
102992
|
}
|
|
103058
102993
|
})());
|
|
103059
|
-
await Promise.all(
|
|
103060
|
-
generationResults = Array.from(resultsMap.values()).sort((a, b) => a.index - b.index);
|
|
103061
|
-
|
|
103062
|
-
|
|
103063
|
-
|
|
103064
|
-
|
|
103065
|
-
|
|
103066
|
-
|
|
103067
|
-
|
|
103068
|
-
|
|
103069
|
-
|
|
103070
|
-
|
|
103071
|
-
|
|
103072
|
-
|
|
103073
|
-
|
|
103074
|
-
addLog(formatLogMessage('log', `⏱️ Total time: ${overallDuration}ms (${(overallDuration / 1000).toFixed(2)}s)`));
|
|
103075
|
-
addLog(formatLogMessage('log', `✅ Successfully generated ${successfulImages.length}/${imagePrompts.length} images`));
|
|
103076
|
-
// Stop generating state to hide backdrop and show images
|
|
103077
|
-
setGeneratingImages(false);
|
|
103078
|
-
setCheckingImages(false);
|
|
103079
|
-
// Save generated images to Google Drive
|
|
103080
|
-
if (driveFolderUrl) {
|
|
103081
|
-
try {
|
|
103082
|
-
const folderId = extractFolderId(driveFolderUrl);
|
|
103083
|
-
if (folderId) {
|
|
103084
|
-
addLog(formatLogMessage('log', '💾 Saving generated images data to Google Drive...'));
|
|
103085
|
-
// Get latest state and save
|
|
103086
|
-
setGeneratedImagesData(prev => {
|
|
103087
|
-
// Save all images to separate files
|
|
103088
|
-
saveGeneratedImagesToDrive(folderId, prev).then(() => {
|
|
103089
|
-
addLog(formatLogMessage('log', '✅ Generated images data saved to Google Drive'));
|
|
103090
|
-
}).catch((err) => {
|
|
103091
|
-
addLog(formatLogMessage('warn', `⚠️ Failed to save images data to Google Drive: ${err.message || 'Unknown error'}`));
|
|
102994
|
+
await Promise.all(workers);
|
|
102995
|
+
let generationResults = Array.from(resultsMap.values()).sort((a, b) => a.index - b.index);
|
|
102996
|
+
// Safety: fill any missing slots as failed
|
|
102997
|
+
if (generationResults.length !== imagePrompts.length) {
|
|
102998
|
+
for (let i = 0; i < imagePrompts.length; i++) {
|
|
102999
|
+
const idx = i + 1;
|
|
103000
|
+
if (!resultsMap.has(idx)) {
|
|
103001
|
+
generationResults.push({
|
|
103002
|
+
index: idx,
|
|
103003
|
+
imageUrl: null,
|
|
103004
|
+
success: false,
|
|
103005
|
+
error: new Error('Generation did not complete'),
|
|
103006
|
+
approach: approaches[i]?.name || 'Unknown',
|
|
103007
|
+
originalPrompt: imagePrompts[i],
|
|
103008
|
+
productImageUrl: productImage.url
|
|
103092
103009
|
});
|
|
103093
|
-
|
|
103094
|
-
});
|
|
103010
|
+
}
|
|
103095
103011
|
}
|
|
103012
|
+
generationResults = generationResults.sort((a, b) => a.index - b.index);
|
|
103096
103013
|
}
|
|
103097
|
-
|
|
103098
|
-
|
|
103014
|
+
// Retry once for all transient failures including refusals (model sometimes randomly refuses)
|
|
103015
|
+
const retryableErrors = generationResults.filter(r => {
|
|
103016
|
+
if (r.success)
|
|
103017
|
+
return false;
|
|
103018
|
+
const errorMsg = r.error?.message || '';
|
|
103019
|
+
const isRefusal = r.error?.isRefusal === true;
|
|
103020
|
+
// isRefusal и hasReasoning — случайные отказы модели → ретраим с логом
|
|
103021
|
+
// Всё остальное (сеть/JSON/таймаут/Provider) тоже ретраим
|
|
103022
|
+
return (isRefusal ||
|
|
103023
|
+
r.error?.hasReasoning === true ||
|
|
103024
|
+
errorMsg.includes('Invalid JSON response') ||
|
|
103025
|
+
errorMsg.includes('Unexpected end of JSON input') ||
|
|
103026
|
+
(errorMsg.includes('JSON') && errorMsg.includes('parse')) ||
|
|
103027
|
+
errorMsg.includes('Failed to fetch') ||
|
|
103028
|
+
errorMsg.includes('Network error') ||
|
|
103029
|
+
errorMsg.includes('timeout') ||
|
|
103030
|
+
errorMsg.includes('Request timeout') ||
|
|
103031
|
+
errorMsg.includes('Provider returned error') ||
|
|
103032
|
+
errorMsg.toLowerCase().includes('provider error'));
|
|
103033
|
+
});
|
|
103034
|
+
if (retryableErrors.length > 0) {
|
|
103035
|
+
const refusalCount = retryableErrors.filter(r => r.error?.isRefusal === true).length;
|
|
103036
|
+
const otherCount = retryableErrors.length - refusalCount;
|
|
103037
|
+
const parts = [];
|
|
103038
|
+
if (refusalCount > 0)
|
|
103039
|
+
parts.push(`${refusalCount} отказ${refusalCount > 1 ? 'а' : ''} модели`);
|
|
103040
|
+
if (otherCount > 0)
|
|
103041
|
+
parts.push(`${otherCount} сеть/таймаут/JSON`);
|
|
103042
|
+
addLog(formatLogMessage('log', `🔄 Повтор: ${parts.join(', ')}. Пробую ещё раз...`));
|
|
103043
|
+
const retryIndices = retryableErrors.map(r => r.index).sort((a, b) => a - b);
|
|
103044
|
+
let retryCursor = 0;
|
|
103045
|
+
const retryWorkers = Array.from({ length: Math.min(maxParallel, retryIndices.length) }, () => (async () => {
|
|
103046
|
+
while (true) {
|
|
103047
|
+
const idx = retryIndices[retryCursor++];
|
|
103048
|
+
if (idx === undefined)
|
|
103049
|
+
break;
|
|
103050
|
+
// backoff
|
|
103051
|
+
await new Promise(resolve => setTimeout(resolve, 1200));
|
|
103052
|
+
const i = idx - 1;
|
|
103053
|
+
const res = await runOne(i, true);
|
|
103054
|
+
resultsMap.set(res.index, res);
|
|
103055
|
+
}
|
|
103056
|
+
})());
|
|
103057
|
+
await Promise.all(retryWorkers);
|
|
103058
|
+
generationResults = Array.from(resultsMap.values()).sort((a, b) => a.index - b.index);
|
|
103059
|
+
}
|
|
103060
|
+
const generationDuration = Date.now() - generationStartTime;
|
|
103061
|
+
const successfulImages = generationResults.filter(r => r.success);
|
|
103062
|
+
const finalFailedImages = generationResults.filter(r => !r.success);
|
|
103063
|
+
addLog(formatLogMessage('log', `⏱️ Генерация завершена за ${Math.round(generationDuration / 1000)}s`));
|
|
103064
|
+
addLog(formatLogMessage('log', `✅ Успешно: ${successfulImages.length}/${imagePrompts.length}, ошибок: ${finalFailedImages.length}`));
|
|
103065
|
+
if (successfulImages.length === 0) {
|
|
103066
|
+
const allErrors = finalFailedImages.map(f => f.error?.message || String(f.error || 'Unknown error')).join('; ');
|
|
103067
|
+
addLog(formatLogMessage('warn', `⚠️ No images were successfully generated. All ${imagePrompts.length} slots are available for regeneration.`));
|
|
103068
|
+
// Don't throw error - just show empty slots with regenerate buttons
|
|
103099
103069
|
}
|
|
103070
|
+
const overallDuration = Date.now() - overallStartTime;
|
|
103071
|
+
addLog(formatLogMessage('log', `🎉 === Image generation process completed ===`));
|
|
103072
|
+
addLog(formatLogMessage('log', `⏱️ Total time: ${overallDuration}ms (${(overallDuration / 1000).toFixed(2)}s)`));
|
|
103073
|
+
addLog(formatLogMessage('log', `✅ Successfully generated ${successfulImages.length}/${imagePrompts.length} images`));
|
|
103074
|
+
// Stop generating state to hide backdrop and show images
|
|
103075
|
+
setGeneratingImages(false);
|
|
103076
|
+
setCheckingImages(false);
|
|
103077
|
+
// Validation is done individually per image; no final summary block
|
|
103078
|
+
}
|
|
103079
|
+
catch (err) {
|
|
103080
|
+
const errorMessage = err.message || 'Unknown error';
|
|
103081
|
+
addLog(formatLogMessage('error', '❌ === Error in image generation process ==='));
|
|
103082
|
+
addLog(formatLogMessage('error', errorMessage));
|
|
103083
|
+
alert('Error generating images: ' + errorMessage);
|
|
103084
|
+
}
|
|
103085
|
+
finally {
|
|
103086
|
+
setGeneratingImages(false);
|
|
103100
103087
|
}
|
|
103101
|
-
// Validation is done individually per image; no final summary block
|
|
103102
103088
|
}
|
|
103103
103089
|
catch (err) {
|
|
103104
|
-
const errorMessage = err.message || 'Unknown error';
|
|
103105
|
-
addLog(formatLogMessage('error', '❌ === Error in image generation process ==='));
|
|
103106
|
-
addLog(formatLogMessage('error', errorMessage));
|
|
103107
|
-
alert('Error generating images: ' + errorMessage);
|
|
103108
|
-
}
|
|
103109
|
-
finally {
|
|
103110
103090
|
setGeneratingImages(false);
|
|
103091
|
+
logToTerminal('error', '❌ Error:', err?.message || err);
|
|
103111
103092
|
}
|
|
103112
103093
|
};
|
|
103113
103094
|
const handleRegenerateImage = async (imageData) => {
|
|
@@ -103517,23 +103498,8 @@ ${imageData.originalPrompt}
|
|
|
103517
103498
|
addLog(formatLogMessage('log', `📤 Uploading image ${imageData.index} to Drive: ${filename}`));
|
|
103518
103499
|
const driveUrl = await uploadImageToDrive(imageData.imageUrl, filename, folderId, addLog);
|
|
103519
103500
|
addLog(formatLogMessage('log', `✅ Image ${imageData.index} uploaded: ${driveUrl}`));
|
|
103520
|
-
// Update uploaded status
|
|
103521
|
-
setGeneratedImagesData(prev => {
|
|
103522
|
-
const updated = prev.map(img => img.index === imageData.index ? { ...img, uploaded: true } : img);
|
|
103523
|
-
// Save single image to its own file
|
|
103524
|
-
if (driveFolderUrl) {
|
|
103525
|
-
const currentFolderId = extractFolderId(driveFolderUrl);
|
|
103526
|
-
if (currentFolderId) {
|
|
103527
|
-
const updatedImage = updated.find(img => img.index === imageData.index);
|
|
103528
|
-
if (updatedImage) {
|
|
103529
|
-
saveSingleImageToDrive(currentFolderId, updatedImage).catch((err) => {
|
|
103530
|
-
console.log(`Failed to save image ${imageData.index} to Drive:`, err);
|
|
103531
|
-
});
|
|
103532
|
-
}
|
|
103533
|
-
}
|
|
103534
|
-
}
|
|
103535
|
-
return updated;
|
|
103536
|
-
});
|
|
103501
|
+
// Update uploaded status
|
|
103502
|
+
setGeneratedImagesData(prev => prev.map(img => img.index === imageData.index ? { ...img, uploaded: true } : img));
|
|
103537
103503
|
// (Removed legacy Generated Images links list)
|
|
103538
103504
|
}
|
|
103539
103505
|
catch (err) {
|
|
@@ -103556,7 +103522,7 @@ ${imageData.originalPrompt}
|
|
|
103556
103522
|
alert('Invalid Google Drive Folder URL');
|
|
103557
103523
|
return;
|
|
103558
103524
|
}
|
|
103559
|
-
const notUploaded = generatedImagesData.filter(img => !img.uploaded);
|
|
103525
|
+
const notUploaded = generatedImagesData.filter(img => !img.uploaded && img.imageUrl);
|
|
103560
103526
|
if (notUploaded.length === 0) {
|
|
103561
103527
|
alert('All images are already uploaded');
|
|
103562
103528
|
return;
|
|
@@ -103566,57 +103532,53 @@ ${imageData.originalPrompt}
|
|
|
103566
103532
|
setImagesGenerationLogs(prev => [...prev, msg]);
|
|
103567
103533
|
logToTerminal('log', msg.replace(/\[.*?\]\s*/, ''));
|
|
103568
103534
|
};
|
|
103535
|
+
// Mark all as uploading
|
|
103536
|
+
setGeneratedImagesData(prev => prev.map(img => notUploaded.some(n => n.index === img.index)
|
|
103537
|
+
? { ...img, uploading: true, uploadStartTime: Date.now() }
|
|
103538
|
+
: img));
|
|
103569
103539
|
try {
|
|
103570
|
-
addLog(formatLogMessage('log', `📤 Uploading ${notUploaded.length} image(s) to Drive...`));
|
|
103571
|
-
|
|
103572
|
-
|
|
103573
|
-
|
|
103574
|
-
|
|
103575
|
-
|
|
103540
|
+
addLog(formatLogMessage('log', `📤 Uploading ${notUploaded.length} image(s) to Drive (parallel)...`));
|
|
103541
|
+
const baseTs = Date.now();
|
|
103542
|
+
const results = await Promise.allSettled(notUploaded.map((imageData, i) => {
|
|
103543
|
+
const filename = `${generateProduct.replace(/\s+/g, '_')}_${imageData.index}_${baseTs}_${i}.png`;
|
|
103544
|
+
addLog(formatLogMessage('log', `📤 Starting upload image ${imageData.index}: ${filename}`));
|
|
103545
|
+
return uploadImageToDrive(imageData.imageUrl, filename, folderId, addLog).then(driveUrl => ({
|
|
103546
|
+
index: imageData.index,
|
|
103547
|
+
driveUrl,
|
|
103548
|
+
success: true
|
|
103549
|
+
}));
|
|
103550
|
+
}));
|
|
103551
|
+
const successfulIndices = new Set();
|
|
103552
|
+
results.forEach((result, i) => {
|
|
103553
|
+
const imageData = notUploaded[i];
|
|
103554
|
+
if (result.status === 'fulfilled' && result.value.success) {
|
|
103555
|
+
addLog(formatLogMessage('log', `✅ Image ${imageData.index} uploaded: ${result.value.driveUrl}`));
|
|
103556
|
+
successfulIndices.add(imageData.index);
|
|
103576
103557
|
}
|
|
103577
|
-
|
|
103578
|
-
const
|
|
103579
|
-
addLog(formatLogMessage('
|
|
103580
|
-
const driveUrl = await uploadImageToDrive(imageData.imageUrl, filename, folderId, addLog);
|
|
103581
|
-
addLog(formatLogMessage('log', `✅ Image ${imageData.index} uploaded: ${driveUrl}`));
|
|
103582
|
-
// Update uploaded status
|
|
103583
|
-
setGeneratedImagesData(prev => {
|
|
103584
|
-
const updated = prev.map(img => img.index === imageData.index ? { ...img, uploaded: true } : img);
|
|
103585
|
-
// Save to Google Drive after upload
|
|
103586
|
-
saveGeneratedImagesToDrive(folderId, updated).catch((err) => {
|
|
103587
|
-
console.log('Failed to save images data after upload:', err);
|
|
103588
|
-
});
|
|
103589
|
-
return updated;
|
|
103590
|
-
});
|
|
103591
|
-
// (Removed legacy Generated Images links list)
|
|
103592
|
-
uploadedCount++;
|
|
103558
|
+
else {
|
|
103559
|
+
const errMsg = result.status === 'rejected' ? result.reason?.message : 'Unknown error';
|
|
103560
|
+
addLog(formatLogMessage('error', `❌ Failed to upload image ${imageData.index}:`, errMsg));
|
|
103593
103561
|
}
|
|
103594
|
-
|
|
103595
|
-
|
|
103562
|
+
});
|
|
103563
|
+
const uploadedCount = successfulIndices.size;
|
|
103564
|
+
// Update uploaded status and clear uploading state for all
|
|
103565
|
+
setGeneratedImagesData(prev => prev.map(img => notUploaded.some(n => n.index === img.index)
|
|
103566
|
+
? {
|
|
103567
|
+
...img,
|
|
103568
|
+
uploaded: successfulIndices.has(img.index),
|
|
103569
|
+
uploading: false,
|
|
103570
|
+
uploadStartTime: undefined
|
|
103596
103571
|
}
|
|
103597
|
-
|
|
103598
|
-
addLog(formatLogMessage('log', `✅ Uploaded ${uploadedCount} image(s) successfully`));
|
|
103599
|
-
|
|
103600
|
-
try {
|
|
103601
|
-
addLog(formatLogMessage('log', '💾 Saving images data to Google Drive...'));
|
|
103602
|
-
// Get latest state and save all images
|
|
103603
|
-
setGeneratedImagesData(prev => {
|
|
103604
|
-
saveGeneratedImagesToDrive(folderId, prev).then(() => {
|
|
103605
|
-
addLog(formatLogMessage('log', '✅ Images data saved to Google Drive'));
|
|
103606
|
-
}).catch((err) => {
|
|
103607
|
-
addLog(formatLogMessage('warn', `⚠️ Failed to save images data to Google Drive: ${err.message || 'Unknown error'}`));
|
|
103608
|
-
});
|
|
103609
|
-
return prev;
|
|
103610
|
-
});
|
|
103611
|
-
}
|
|
103612
|
-
catch (err) {
|
|
103613
|
-
addLog(formatLogMessage('warn', `⚠️ Failed to save images data to Google Drive: ${err.message || 'Unknown error'}`));
|
|
103614
|
-
}
|
|
103615
|
-
alert(`Successfully uploaded ${uploadedCount} image(s) to Google Drive!`);
|
|
103572
|
+
: img));
|
|
103573
|
+
addLog(formatLogMessage('log', `✅ Uploaded ${uploadedCount}/${notUploaded.length} image(s) successfully`));
|
|
103574
|
+
alert(`Successfully uploaded ${uploadedCount} of ${notUploaded.length} image(s) to Google Drive!`);
|
|
103616
103575
|
}
|
|
103617
103576
|
catch (err) {
|
|
103618
103577
|
addLog(formatLogMessage('error', `❌ Error uploading images:`, err.message));
|
|
103619
103578
|
alert('Error uploading images: ' + err.message);
|
|
103579
|
+
setGeneratedImagesData(prev => prev.map(img => notUploaded.some(n => n.index === img.index)
|
|
103580
|
+
? { ...img, uploading: false, uploadStartTime: undefined }
|
|
103581
|
+
: img));
|
|
103620
103582
|
}
|
|
103621
103583
|
finally {
|
|
103622
103584
|
setUploadingImages(false);
|
|
@@ -103858,11 +103820,9 @@ ${imageData.originalPrompt}
|
|
|
103858
103820
|
errorMessage: item.errorMessage
|
|
103859
103821
|
}));
|
|
103860
103822
|
};
|
|
103861
|
-
// Save
|
|
103862
|
-
const saveGeneratedContentToDrive = async (folderId,
|
|
103863
|
-
|
|
103864
|
-
const sanitizedTextsData = sanitizeGeneratedTextsData(textsData);
|
|
103865
|
-
logToTerminal('log', '[Save Content] Saving content to folderId:', folderId, 'titles:', sanitizedTitlesData.length, 'texts:', sanitizedTextsData.length);
|
|
103823
|
+
// Save settings, brand, link to Google Drive (titles and texts are not saved)
|
|
103824
|
+
const saveGeneratedContentToDrive = async (folderId, aiSettings, brandValue, linkValue) => {
|
|
103825
|
+
logToTerminal('log', '[Save Content] Saving settings to folderId:', folderId);
|
|
103866
103826
|
const validToken = await getValidAccessToken();
|
|
103867
103827
|
if (!validToken) {
|
|
103868
103828
|
logToTerminal('error', '[Save Content] No valid token');
|
|
@@ -103871,8 +103831,6 @@ ${imageData.originalPrompt}
|
|
|
103871
103831
|
// Get or create 'env' folder
|
|
103872
103832
|
const envFolderId = await getOrCreateEnvFolder(folderId);
|
|
103873
103833
|
const dataToSave = {
|
|
103874
|
-
generatedTitlesData: sanitizedTitlesData,
|
|
103875
|
-
generatedTextsData: sanitizedTextsData,
|
|
103876
103834
|
aiGenerationSettings: aiSettings || {
|
|
103877
103835
|
generateProduct: generateProduct || '',
|
|
103878
103836
|
generateGeo: generateGeo || '',
|
|
@@ -104025,38 +103983,8 @@ ${imageData.originalPrompt}
|
|
|
104025
103983
|
logToTerminal('log', '[Load Content] File downloaded, length:', jsonContent.length);
|
|
104026
103984
|
try {
|
|
104027
103985
|
const loadedData = JSON.parse(jsonContent);
|
|
104028
|
-
logToTerminal('log', '[Load Content] Parsed
|
|
104029
|
-
|
|
104030
|
-
const sanitizedTitlesData = sanitizeGeneratedTitlesData(loadedData.generatedTitlesData);
|
|
104031
|
-
logToTerminal('log', '[Load Content] Loading titles, count:', sanitizedTitlesData.length);
|
|
104032
|
-
setGeneratedTitlesData(sanitizedTitlesData);
|
|
104033
|
-
// Also update titles field
|
|
104034
|
-
const titlesString = sanitizedTitlesData
|
|
104035
|
-
.map((t) => t.title)
|
|
104036
|
-
.filter((t) => t)
|
|
104037
|
-
.join('\n');
|
|
104038
|
-
if (titlesString) {
|
|
104039
|
-
setTitles(titlesString);
|
|
104040
|
-
}
|
|
104041
|
-
}
|
|
104042
|
-
else {
|
|
104043
|
-
setGeneratedTitlesData([]);
|
|
104044
|
-
}
|
|
104045
|
-
if (loadedData.generatedTextsData && Array.isArray(loadedData.generatedTextsData)) {
|
|
104046
|
-
const sanitizedTextsData = sanitizeGeneratedTextsData(loadedData.generatedTextsData);
|
|
104047
|
-
logToTerminal('log', '[Load Content] Loading texts, count:', sanitizedTextsData.length);
|
|
104048
|
-
setGeneratedTextsData(sanitizedTextsData);
|
|
104049
|
-
// Also update texts field
|
|
104050
|
-
const textsArray = sanitizedTextsData
|
|
104051
|
-
.map((t) => t.text)
|
|
104052
|
-
.filter((t) => t);
|
|
104053
|
-
if (textsArray.length > 0) {
|
|
104054
|
-
setTexts(textsArray);
|
|
104055
|
-
}
|
|
104056
|
-
}
|
|
104057
|
-
else {
|
|
104058
|
-
setGeneratedTextsData([]);
|
|
104059
|
-
}
|
|
103986
|
+
logToTerminal('log', '[Load Content] Parsed settings');
|
|
103987
|
+
// Titles and texts are not loaded from Drive (no longer saved there)
|
|
104060
103988
|
// Load AI Generation Settings
|
|
104061
103989
|
if (loadedData.aiGenerationSettings) {
|
|
104062
103990
|
logToTerminal('log', '[Load Content] Loading AI Generation Settings');
|
|
@@ -104093,250 +104021,7 @@ ${imageData.originalPrompt}
|
|
|
104093
104021
|
return { found: false };
|
|
104094
104022
|
}
|
|
104095
104023
|
};
|
|
104096
|
-
// Save single image to Google Drive as separate file
|
|
104097
|
-
const saveSingleImageToDrive = async (folderId, imageData) => {
|
|
104098
|
-
logToTerminal('log', '[Save Image] Saving image', imageData.index, 'to folderId:', folderId);
|
|
104099
|
-
const validToken = await getValidAccessToken();
|
|
104100
|
-
if (!validToken) {
|
|
104101
|
-
logToTerminal('error', '[Save Image] No valid token');
|
|
104102
|
-
throw new Error('Not logged in to Google Drive');
|
|
104103
|
-
}
|
|
104104
|
-
// Get or create 'env' folder
|
|
104105
|
-
const envFolderId = await getOrCreateEnvFolder(folderId);
|
|
104106
|
-
const jsonContent = JSON.stringify(imageData, null, 2);
|
|
104107
|
-
const filename = `temp-image-${imageData.index}.json`;
|
|
104108
|
-
logToTerminal('log', '[Save Image] Filename:', filename, 'content length:', jsonContent.length);
|
|
104109
|
-
// Check if file already exists in 'env' folder
|
|
104110
|
-
const q = `'${envFolderId}' in parents and trashed = false and name = '${filename}'`;
|
|
104111
|
-
const fields = 'files(id, name)';
|
|
104112
|
-
const searchUrl = `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(q)}&fields=${encodeURIComponent(fields)}`;
|
|
104113
|
-
const searchResponse = await fetch(searchUrl, {
|
|
104114
|
-
headers: {
|
|
104115
|
-
'Authorization': `Bearer ${validToken}`
|
|
104116
|
-
}
|
|
104117
|
-
});
|
|
104118
|
-
let fileId = null;
|
|
104119
|
-
if (searchResponse.ok) {
|
|
104120
|
-
const searchData = await searchResponse.json();
|
|
104121
|
-
logToTerminal('log', '[Save Image] Search response, files found:', searchData.files?.length || 0);
|
|
104122
|
-
if (searchData.files && searchData.files.length > 0) {
|
|
104123
|
-
// Filter out duplicate files (with (1), (2), etc.) - prefer exact match
|
|
104124
|
-
const exactMatch = searchData.files.find((f) => f.name === filename);
|
|
104125
|
-
if (exactMatch) {
|
|
104126
|
-
fileId = exactMatch.id;
|
|
104127
|
-
logToTerminal('log', '[Save Image] Found exact match, will update, fileId:', fileId);
|
|
104128
|
-
}
|
|
104129
|
-
else {
|
|
104130
|
-
// If no exact match, use first file but log warning
|
|
104131
|
-
fileId = searchData.files[0].id;
|
|
104132
|
-
logToTerminal('warn', '[Save Image] Found duplicate file, will update first one, fileId:', fileId, 'name:', searchData.files[0].name);
|
|
104133
|
-
}
|
|
104134
|
-
}
|
|
104135
|
-
else {
|
|
104136
|
-
logToTerminal('log', '[Save Image] File does not exist, will create new');
|
|
104137
|
-
}
|
|
104138
|
-
}
|
|
104139
|
-
else {
|
|
104140
|
-
logToTerminal('warn', '[Save Image] Search failed, status:', searchResponse.status);
|
|
104141
|
-
}
|
|
104142
|
-
if (fileId) {
|
|
104143
|
-
// Update existing file - don't include parents in metadata for updates
|
|
104144
|
-
logToTerminal('log', '[Save Image] Updating existing file');
|
|
104145
|
-
const updateMetadata = {
|
|
104146
|
-
name: filename,
|
|
104147
|
-
mimeType: 'application/json'
|
|
104148
|
-
};
|
|
104149
|
-
const updateForm = new FormData();
|
|
104150
|
-
updateForm.append('metadata', new Blob([JSON.stringify(updateMetadata)], { type: 'application/json' }));
|
|
104151
|
-
updateForm.append('file', new Blob([jsonContent], { type: 'application/json' }));
|
|
104152
|
-
const updateUrl = `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=multipart`;
|
|
104153
|
-
const updateResponse = await fetch(updateUrl, {
|
|
104154
|
-
method: 'PATCH',
|
|
104155
|
-
headers: {
|
|
104156
|
-
'Authorization': `Bearer ${validToken}`
|
|
104157
|
-
},
|
|
104158
|
-
body: updateForm
|
|
104159
|
-
});
|
|
104160
|
-
if (!updateResponse.ok) {
|
|
104161
|
-
const err = await updateResponse.json();
|
|
104162
|
-
logToTerminal('error', '[Save Image] Update failed:', JSON.stringify(err));
|
|
104163
|
-
throw new Error(err.error?.message || 'Failed to update file in Google Drive');
|
|
104164
|
-
}
|
|
104165
|
-
logToTerminal('log', '[Save Image] Successfully updated file');
|
|
104166
|
-
}
|
|
104167
|
-
else {
|
|
104168
|
-
// Create new file - include parents for new files (in 'env' folder)
|
|
104169
|
-
logToTerminal('log', '[Save Image] Creating new file in env folder');
|
|
104170
|
-
const createMetadata = {
|
|
104171
|
-
name: filename,
|
|
104172
|
-
mimeType: 'application/json',
|
|
104173
|
-
parents: [envFolderId]
|
|
104174
|
-
};
|
|
104175
|
-
const createForm = new FormData();
|
|
104176
|
-
createForm.append('metadata', new Blob([JSON.stringify(createMetadata)], { type: 'application/json' }));
|
|
104177
|
-
createForm.append('file', new Blob([jsonContent], { type: 'application/json' }));
|
|
104178
|
-
const createUrl = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart';
|
|
104179
|
-
const createResponse = await fetch(createUrl, {
|
|
104180
|
-
method: 'POST',
|
|
104181
|
-
headers: {
|
|
104182
|
-
'Authorization': `Bearer ${validToken}`
|
|
104183
|
-
},
|
|
104184
|
-
body: createForm
|
|
104185
|
-
});
|
|
104186
|
-
if (!createResponse.ok) {
|
|
104187
|
-
const err = await createResponse.json();
|
|
104188
|
-
logToTerminal('error', '[Save Image] Create failed:', JSON.stringify(err));
|
|
104189
|
-
throw new Error(err.error?.message || 'Failed to create file in Google Drive');
|
|
104190
|
-
}
|
|
104191
|
-
const result = await createResponse.json();
|
|
104192
|
-
logToTerminal('log', '[Save Image] Successfully created file, id:', result.id);
|
|
104193
|
-
}
|
|
104194
|
-
};
|
|
104195
|
-
// Save all images to Google Drive (each in separate file)
|
|
104196
|
-
const saveGeneratedImagesToDrive = async (folderId, imagesData) => {
|
|
104197
|
-
const validToken = await getValidAccessToken();
|
|
104198
|
-
if (!validToken) {
|
|
104199
|
-
throw new Error('Not logged in to Google Drive');
|
|
104200
|
-
}
|
|
104201
|
-
// Save each image to its own file
|
|
104202
|
-
for (const imageData of imagesData) {
|
|
104203
|
-
try {
|
|
104204
|
-
await saveSingleImageToDrive(folderId, imageData);
|
|
104205
|
-
}
|
|
104206
|
-
catch (err) {
|
|
104207
|
-
console.error(`Failed to save image ${imageData.index} to Drive:`, err);
|
|
104208
|
-
// Continue with other images even if one fails
|
|
104209
|
-
}
|
|
104210
|
-
}
|
|
104211
|
-
};
|
|
104212
|
-
// Load generated images data from Google Drive (from separate files)
|
|
104213
|
-
const loadGeneratedImagesFromDrive = async (folderId) => {
|
|
104214
|
-
logToTerminal('log', '[Load Images] Starting load from Google Drive, folderId:', folderId);
|
|
104215
|
-
const validToken = await getValidAccessToken();
|
|
104216
|
-
if (!validToken) {
|
|
104217
|
-
logToTerminal('warn', '[Load Images] No valid token, skipping load');
|
|
104218
|
-
setDriveFilesFound(prev => ({ ...prev, images: false }));
|
|
104219
|
-
return { found: false };
|
|
104220
|
-
}
|
|
104221
|
-
// Get 'env' folder
|
|
104222
|
-
let envFolderId;
|
|
104223
|
-
try {
|
|
104224
|
-
envFolderId = await getOrCreateEnvFolder(folderId);
|
|
104225
|
-
}
|
|
104226
|
-
catch (err) {
|
|
104227
|
-
logToTerminal('warn', '[Load Images] Failed to get env folder:', err.message || 'Unknown error');
|
|
104228
|
-
return { found: false };
|
|
104229
|
-
}
|
|
104230
|
-
// Find all temp-image-*.json files in 'env' folder
|
|
104231
|
-
const q = `'${envFolderId}' in parents and trashed = false and name contains 'temp-image-'`;
|
|
104232
|
-
const fields = 'files(id, name)';
|
|
104233
|
-
const url = `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(q)}&fields=${encodeURIComponent(fields)}`;
|
|
104234
|
-
logToTerminal('log', '[Load Images] Searching for image files');
|
|
104235
|
-
const response = await fetch(url, {
|
|
104236
|
-
headers: {
|
|
104237
|
-
'Authorization': `Bearer ${validToken}`
|
|
104238
|
-
}
|
|
104239
|
-
});
|
|
104240
|
-
if (!response.ok) {
|
|
104241
|
-
const errorText = await response.text();
|
|
104242
|
-
logToTerminal('warn', '[Load Images] Search failed, status:', response.status, 'error:', errorText);
|
|
104243
|
-
setDriveFilesFound(prev => ({ ...prev, images: false }));
|
|
104244
|
-
return { found: false };
|
|
104245
|
-
}
|
|
104246
|
-
const data = await response.json();
|
|
104247
|
-
logToTerminal('log', '[Load Images] Search response, files found:', data.files?.length || 0);
|
|
104248
|
-
if (!data.files || data.files.length === 0) {
|
|
104249
|
-
logToTerminal('log', '[Load Images] No image files found - no temp-image-*.json files in folder');
|
|
104250
|
-
return { found: false };
|
|
104251
|
-
}
|
|
104252
|
-
// Filter out duplicate files (with (1), (2), etc.) - only keep exact matches
|
|
104253
|
-
const exactMatchFiles = data.files.filter((f) => {
|
|
104254
|
-
const match = f.name.match(/^temp-image-(\d+)\.json$/);
|
|
104255
|
-
return match !== null;
|
|
104256
|
-
});
|
|
104257
|
-
logToTerminal('log', '[Load Images] Found', exactMatchFiles.length, 'exact match files:', exactMatchFiles.map((f) => f.name).join(', '));
|
|
104258
|
-
// Group files by index and take only one file per index (prefer exact match, then first one)
|
|
104259
|
-
const filesByIndex = new Map();
|
|
104260
|
-
for (const file of exactMatchFiles) {
|
|
104261
|
-
const match = file.name.match(/^temp-image-(\d+)\.json$/);
|
|
104262
|
-
if (match) {
|
|
104263
|
-
const index = parseInt(match[1], 10);
|
|
104264
|
-
if (!filesByIndex.has(index)) {
|
|
104265
|
-
filesByIndex.set(index, file);
|
|
104266
|
-
}
|
|
104267
|
-
}
|
|
104268
|
-
}
|
|
104269
|
-
const uniqueFiles = Array.from(filesByIndex.values());
|
|
104270
|
-
logToTerminal('log', '[Load Images] After deduplication:', uniqueFiles.length, 'unique files, indices:', Array.from(filesByIndex.keys()).sort((a, b) => a - b).join(', '));
|
|
104271
|
-
// Helper function to download file with retries
|
|
104272
|
-
const downloadFileWithRetry = async (file, maxRetries = 3, delayMs = 1000) => {
|
|
104273
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
104274
|
-
try {
|
|
104275
|
-
if (attempt > 1) {
|
|
104276
|
-
logToTerminal('log', `[Load Images] Retry ${attempt}/${maxRetries} for file:`, file.name);
|
|
104277
|
-
await new Promise(resolve => setTimeout(resolve, delayMs * (attempt - 1))); // Exponential backoff
|
|
104278
|
-
}
|
|
104279
|
-
else {
|
|
104280
|
-
logToTerminal('log', '[Load Images] Loading file:', file.name);
|
|
104281
|
-
}
|
|
104282
|
-
const downloadUrl = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`;
|
|
104283
|
-
const downloadResponse = await fetch(downloadUrl, {
|
|
104284
|
-
headers: {
|
|
104285
|
-
'Authorization': `Bearer ${validToken}`
|
|
104286
|
-
}
|
|
104287
|
-
});
|
|
104288
|
-
if (downloadResponse.ok) {
|
|
104289
|
-
const jsonContent = await downloadResponse.text();
|
|
104290
|
-
logToTerminal('log', '[Load Images] File', file.name, 'downloaded, length:', jsonContent.length);
|
|
104291
|
-
const imageData = JSON.parse(jsonContent);
|
|
104292
|
-
logToTerminal('log', '[Load Images] Parsed image data for index:', imageData.index);
|
|
104293
|
-
return imageData;
|
|
104294
|
-
}
|
|
104295
|
-
else {
|
|
104296
|
-
const errorText = await downloadResponse.text();
|
|
104297
|
-
logToTerminal('warn', `[Load Images] Download failed for ${file.name} (attempt ${attempt}/${maxRetries}), status:`, downloadResponse.status);
|
|
104298
|
-
if (attempt === maxRetries) {
|
|
104299
|
-
logToTerminal('error', `[Load Images] Failed to load ${file.name} after ${maxRetries} attempts`);
|
|
104300
|
-
return null;
|
|
104301
|
-
}
|
|
104302
|
-
}
|
|
104303
|
-
}
|
|
104304
|
-
catch (err) {
|
|
104305
|
-
logToTerminal('error', `[Load Images] Failed to load image file ${file.name} (attempt ${attempt}/${maxRetries}):`, err);
|
|
104306
|
-
if (attempt === maxRetries) {
|
|
104307
|
-
logToTerminal('error', `[Load Images] Failed to load ${file.name} after ${maxRetries} attempts`);
|
|
104308
|
-
return null;
|
|
104309
|
-
}
|
|
104310
|
-
}
|
|
104311
|
-
}
|
|
104312
|
-
return null;
|
|
104313
|
-
};
|
|
104314
|
-
// Load all image files in parallel
|
|
104315
|
-
logToTerminal('log', '[Load Images] Starting parallel download of', uniqueFiles.length, 'files');
|
|
104316
|
-
const downloadPromises = uniqueFiles.map(file => downloadFileWithRetry(file));
|
|
104317
|
-
// Wait for all downloads to complete
|
|
104318
|
-
const results = await Promise.all(downloadPromises);
|
|
104319
|
-
const imagesData = results.filter((data) => data !== null);
|
|
104320
|
-
// Sort by index and update state
|
|
104321
|
-
if (imagesData.length > 0) {
|
|
104322
|
-
imagesData.sort((a, b) => a.index - b.index);
|
|
104323
|
-
logToTerminal('log', '[Load Images] Successfully loaded', imagesData.length, 'images, indices:', imagesData.map(img => img.index).join(', '));
|
|
104324
|
-
setGeneratedImagesData(imagesData);
|
|
104325
|
-
setDriveFilesFound(prev => ({ ...prev, images: false }));
|
|
104326
|
-
return { found: true };
|
|
104327
|
-
}
|
|
104328
|
-
else {
|
|
104329
|
-
logToTerminal('log', '[Load Images] No images loaded');
|
|
104330
|
-
setDriveFilesFound(prev => ({ ...prev, images: false }));
|
|
104331
|
-
return { found: false };
|
|
104332
|
-
}
|
|
104333
|
-
};
|
|
104334
104024
|
const handleGenerate = async () => {
|
|
104335
|
-
const validToken = await getValidAccessToken();
|
|
104336
|
-
if (!validToken) {
|
|
104337
|
-
alert('Please log in with Google first');
|
|
104338
|
-
return;
|
|
104339
|
-
}
|
|
104340
104025
|
if (!driveFolderUrl || !brand || !link) {
|
|
104341
104026
|
alert('Please fill all required fields');
|
|
104342
104027
|
return;
|
|
@@ -104348,18 +104033,28 @@ ${imageData.originalPrompt}
|
|
|
104348
104033
|
alert('Invalid Link URL');
|
|
104349
104034
|
return;
|
|
104350
104035
|
}
|
|
104351
|
-
const titleList = titles.split('\n').map(t => t.trim()).filter(t => t)
|
|
104036
|
+
const titleList = titles.split('\n').map(t => t.trim()).filter(t => t).length > 0
|
|
104037
|
+
? titles.split('\n').map(t => t.trim()).filter(t => t)
|
|
104038
|
+
: generatedTitlesData.map(t => t.title).filter(Boolean);
|
|
104352
104039
|
if (titleList.length === 0) {
|
|
104353
104040
|
alert('Please add at least one title');
|
|
104354
104041
|
return;
|
|
104355
104042
|
}
|
|
104356
|
-
const textList = texts.map(t => t.trim()).filter(t => t)
|
|
104043
|
+
const textList = texts.map(t => t.trim()).filter(t => t).length > 0
|
|
104044
|
+
? texts.map(t => t.trim()).filter(t => t)
|
|
104045
|
+
: generatedTextsData.map(t => t.text).filter(Boolean);
|
|
104357
104046
|
if (textList.length === 0) {
|
|
104358
104047
|
alert('Please add at least one text variant');
|
|
104359
104048
|
return;
|
|
104360
104049
|
}
|
|
104361
104050
|
setLoading(true);
|
|
104362
104051
|
try {
|
|
104052
|
+
const validToken = await getValidAccessToken();
|
|
104053
|
+
if (!validToken) {
|
|
104054
|
+
alert('Please log in with Google first');
|
|
104055
|
+
setLoading(false);
|
|
104056
|
+
return;
|
|
104057
|
+
}
|
|
104363
104058
|
const folderId = extractFolderId(driveFolderUrl);
|
|
104364
104059
|
if (!folderId) {
|
|
104365
104060
|
throw new Error('Invalid Google Drive Folder URL');
|
|
@@ -104686,7 +104381,24 @@ ${imageData.originalPrompt}
|
|
|
104686
104381
|
!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"))))),
|
|
104687
104382
|
!driveFolderUrl.trim() ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "info", sx: { mt: 2 } },
|
|
104688
104383
|
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"),
|
|
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(
|
|
104384
|
+
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."))) : !extractFolderId(driveFolderUrl) ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "warning", sx: { mt: 2 } },
|
|
104385
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body1", sx: { fontWeight: 'bold', mb: 1 } }, "\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"),
|
|
104386
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2" }, "\u0421\u0441\u044B\u043B\u043A\u0430 \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u0432 \u0444\u043E\u0440\u043C\u0430\u0442\u0435: https://drive.google.com/drive/folders/..."))) : loadingContentFromDrive ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
104387
|
+
display: 'flex',
|
|
104388
|
+
flexDirection: 'column',
|
|
104389
|
+
alignItems: 'center',
|
|
104390
|
+
justifyContent: 'center',
|
|
104391
|
+
gap: 2,
|
|
104392
|
+
p: 4,
|
|
104393
|
+
mt: 2,
|
|
104394
|
+
border: (theme) => `2px dashed ${theme.palette.primary.main}`,
|
|
104395
|
+
borderRadius: 1,
|
|
104396
|
+
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
104397
|
+
? 'rgba(25, 118, 210, 0.1)'
|
|
104398
|
+
: 'rgba(25, 118, 210, 0.05)'
|
|
104399
|
+
} },
|
|
104400
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 40, sx: { color: 'primary.main' } }),
|
|
104401
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body1", sx: { color: 'text.secondary', fontWeight: 'bold' } }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0434\u0430\u043D\u043D\u044B\u0445 \u0438\u0437 \u043F\u0430\u043F\u043A\u0438..."))) : (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
|
|
104690
104402
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: { xs: 'column', sm: 'row' }, spacing: 2 },
|
|
104691
104403
|
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
104404
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { flex: 1, minWidth: 0 } },
|
|
@@ -104749,7 +104461,6 @@ ${imageData.originalPrompt}
|
|
|
104749
104461
|
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
104462
|
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
104463
|
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 ||
|
|
104752
|
-
loadingImagesFromDrive ||
|
|
104753
104464
|
!openaiApiKey ||
|
|
104754
104465
|
(!accessToken && !refreshToken) ||
|
|
104755
104466
|
!generateProduct.trim() ||
|
|
@@ -104767,6 +104478,18 @@ ${imageData.originalPrompt}
|
|
|
104767
104478
|
: !driveFolderUrl.trim()
|
|
104768
104479
|
? 'Заполните URL папки Google Drive'
|
|
104769
104480
|
: undefined }, generatingImages ? 'Generating Images...' : 'Generate Images')),
|
|
104481
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_17__["default"], { variant: "contained", color: "success", size: "large", onClick: handleGenerate, disabled: loading, 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 Catalog'),
|
|
104482
|
+
uploadedLink && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "success", sx: { flexGrow: 1, minWidth: 0 }, 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 } },
|
|
104483
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_39__["default"], { fontSize: "small" })) },
|
|
104484
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' } },
|
|
104485
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null,
|
|
104486
|
+
"\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D!",
|
|
104487
|
+
' ',
|
|
104488
|
+
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", { href: "#", onClick: (e) => {
|
|
104489
|
+
e.preventDefault();
|
|
104490
|
+
getElectronAPI().openExternal(uploadedLink);
|
|
104491
|
+
}, style: { cursor: 'pointer', textDecoration: 'underline', color: 'inherit' } }, "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 Google Drive")),
|
|
104492
|
+
linkCopied && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'success.dark', fontWeight: 'bold' } }, "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u043E!"))))),
|
|
104770
104493
|
folderFilesInfo !== null && !folderFilesInfo.hasProduct && driveFolderUrl.trim() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_20__["default"], { severity: "warning", sx: { mt: 1 } }, "\u0414\u043B\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0439 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F product.png/jpg. \u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439\u0442\u0435 \u0435\u0433\u043E \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043A\u043D\u043E\u043F\u043A\u0438 \"Generate Product from Banka\".")),
|
|
104771
104494
|
!generatingImages && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
|
|
104772
104495
|
!openaiApiKey && (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 OpenRouter API Key")),
|
|
@@ -104784,7 +104507,7 @@ ${imageData.originalPrompt}
|
|
|
104784
104507
|
(folderFilesInfo !== null && !folderFilesInfo.hasProduct), sx: { flexGrow: 1 }, size: "large", title: folderFilesInfo !== null && !folderFilesInfo.hasProduct
|
|
104785
104508
|
? 'product.png/jpg не найден в папке. Загрузите его с помощью кнопки «Загрузить product.png/jpg»'
|
|
104786
104509
|
: undefined }, generatingLanding ? 'Creating Landing...' : 'Создать лендинг')),
|
|
104787
|
-
|
|
104510
|
+
generatedImagesData.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { mb: 2, mt: 3 } },
|
|
104788
104511
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "h6", gutterBottom: true, sx: { fontWeight: 'bold' } },
|
|
104789
104512
|
"\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u043A\u0440\u0435\u043E (",
|
|
104790
104513
|
generatedImagesData.filter(img => img.imageUrl).length,
|
|
@@ -104794,22 +104517,6 @@ ${imageData.originalPrompt}
|
|
|
104794
104517
|
checkingImages && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 } },
|
|
104795
104518
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 16 }),
|
|
104796
104519
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "caption", sx: { color: 'text.secondary' } }, "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u0430..."))),
|
|
104797
|
-
loadingImagesFromDrive && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
104798
|
-
display: 'flex',
|
|
104799
|
-
flexDirection: 'column',
|
|
104800
|
-
alignItems: 'center',
|
|
104801
|
-
justifyContent: 'center',
|
|
104802
|
-
gap: 2,
|
|
104803
|
-
p: 4,
|
|
104804
|
-
border: (theme) => `2px dashed ${theme.palette.primary.main}`,
|
|
104805
|
-
borderRadius: 1,
|
|
104806
|
-
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
104807
|
-
? 'rgba(25, 118, 210, 0.1)'
|
|
104808
|
-
: 'rgba(25, 118, 210, 0.05)',
|
|
104809
|
-
minHeight: 200
|
|
104810
|
-
} },
|
|
104811
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 40, sx: { color: 'primary.main' } }),
|
|
104812
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body1", sx: { color: 'text.secondary', fontWeight: 'bold' } }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0439 \u0438\u0437 Google Drive..."))),
|
|
104813
104520
|
generatedImagesData.length > 0 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: {
|
|
104814
104521
|
display: 'grid',
|
|
104815
104522
|
gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' },
|
|
@@ -105118,20 +104825,7 @@ ${imageData.originalPrompt}
|
|
|
105118
104825
|
generatingLanding && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 } },
|
|
105119
104826
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_18__["default"], { size: 20 }),
|
|
105120
104827
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_6__["default"], { variant: "body2", sx: { color: 'text.secondary' } }, formatElapsedTime(elapsedTime.landing)))),
|
|
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")))),
|
|
105122
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_11__["default"], { direction: "row", spacing: 2 },
|
|
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')),
|
|
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 } },
|
|
105125
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material__WEBPACK_IMPORTED_MODULE_39__["default"], { fontSize: "small" })) },
|
|
105126
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_material__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { display: 'flex', alignItems: 'center', gap: 1 } },
|
|
105127
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null,
|
|
105128
|
-
"File Uploaded!",
|
|
105129
|
-
' ',
|
|
105130
|
-
react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", { href: "#", onClick: (e) => {
|
|
105131
|
-
e.preventDefault();
|
|
105132
|
-
getElectronAPI().openExternal(uploadedLink);
|
|
105133
|
-
}, style: { cursor: 'pointer', textDecoration: 'underline', color: 'inherit' } }, "Click here to open in Google Drive")),
|
|
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!")))))))))),
|
|
104828
|
+
!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"))))))))),
|
|
105135
104829
|
react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_PromptManagerDialog__WEBPACK_IMPORTED_MODULE_48__["default"], { open: promptManagerOpen, onClose: () => setPromptManagerOpen(false) }))));
|
|
105136
104830
|
}
|
|
105137
104831
|
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (App);
|