Spaces:
Running
Running
Create image.js
Browse files
image.js
ADDED
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Required modules
|
2 |
+
import { GoogleGenerativeAI, HarmCategory, HarmBlockThreshold } from "@google/generative-ai";
|
3 |
+
import fs from 'fs/promises'; // For saving the image file
|
4 |
+
import path from 'path'; // For handling file paths
|
5 |
+
|
6 |
+
// --- Configuration ---
|
7 |
+
// ВАЖНО: Храните ваш API ключ безопасно! Использование переменных окружения рекомендуется.
|
8 |
+
// Оставляю ваш плейсхолдер согласно запросу. НЕ ИСПОЛЬЗУЙТЕ НАСТОЯЩИЙ КЛЮЧ ТАК В РЕАЛЬНЫХ ПРОЕКТАХ!
|
9 |
+
const API_KEY = "AIzaSyBCURZf72ZWwMN2SHZ7XCQoNgExV4WMX8E";
|
10 |
+
if (API_KEY === "AIzaSyBCURZf72ZWwMN2SHZ7XCQoNgExV4WMX8E") {
|
11 |
+
console.warn("ПРЕДУПРЕЖДЕНИЕ: Используется плейсхолдер API ключа. Замените его на ваш настоящий ключ или используйте переменные окружения для безопасного хранения.");
|
12 |
+
// В реальном приложении здесь стоит прервать выполнение, если ключ не настоящий
|
13 |
+
// process.exit(1);
|
14 |
+
}
|
15 |
+
|
16 |
+
// Настройки безопасности (можно настроить по необходимости)
|
17 |
+
const safetySettings = [
|
18 |
+
{ category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE },
|
19 |
+
{ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE },
|
20 |
+
{ category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE },
|
21 |
+
{ category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE },
|
22 |
+
];
|
23 |
+
|
24 |
+
// Initialize Gemini client
|
25 |
+
const genAI = new GoogleGenerativeAI(API_KEY);
|
26 |
+
|
27 |
+
// Модели, максимально приближенные к указанным в Python скрипте:
|
28 |
+
// Python: gemini-2.0-flash-exp-image-generation -> JS: gemini-1.5-flash (ближайшая общедоступная быстрая модель)
|
29 |
+
const textModel = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
30 |
+
// Python: imagen-3.0-generate-002 -> JS: imagen-3 (стандартное имя для Imagen 3 в SDK)
|
31 |
+
const imageModel = genAI.getGenerativeModel({ model: "imagen-3", safetySettings }); // Добавляем safetySettings к модели Imagen
|
32 |
+
|
33 |
+
|
34 |
+
// --- Helper Functions --- (Остаются такими же, как в предыдущем ответе)
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Extracts text content from a Gemini API response.
|
38 |
+
* @param {GenerateContentResponse} response - The API response object.
|
39 |
+
* @returns {string} - The extracted text.
|
40 |
+
* @throws {Error} If response is invalid or lacks text.
|
41 |
+
*/
|
42 |
+
function extractText(response) {
|
43 |
+
try {
|
44 |
+
// Check for safety ratings first - might indicate blocked content
|
45 |
+
if (response.response.promptFeedback?.blockReason) {
|
46 |
+
throw new Error(`Request blocked due to: ${response.response.promptFeedback.blockReason}`);
|
47 |
+
}
|
48 |
+
if (!response.response.candidates?.[0]?.content?.parts?.[0]?.text) {
|
49 |
+
console.error("Invalid response structure or missing text:", JSON.stringify(response, null, 2));
|
50 |
+
throw new Error("Failed to extract text from API response.");
|
51 |
+
}
|
52 |
+
return response.response.candidates[0].content.parts[0].text.trim();
|
53 |
+
} catch (error) {
|
54 |
+
console.error("Error extracting text:", error);
|
55 |
+
console.error("Full API Response on Error:", JSON.stringify(response, null, 2));
|
56 |
+
throw error;
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Extracts image data (Base64) from a Gemini API response.
|
62 |
+
* @param {GenerateContentResponse} response - The API response object.
|
63 |
+
* @returns {{mimeType: string, data: string}} - Object with mimeType and Base64 image data.
|
64 |
+
* @throws {Error} If response is invalid or lacks image data.
|
65 |
+
*/
|
66 |
+
function extractImageData(response) {
|
67 |
+
try {
|
68 |
+
if (response.response.promptFeedback?.blockReason) {
|
69 |
+
throw new Error(`Request blocked due to: ${response.response.promptFeedback.blockReason}`);
|
70 |
+
}
|
71 |
+
const imagePart = response.response.candidates?.[0]?.content?.parts?.find(part => part.inlineData);
|
72 |
+
if (!imagePart || !imagePart.inlineData.data) {
|
73 |
+
console.error("Invalid response structure or missing image data:", JSON.stringify(response, null, 2));
|
74 |
+
throw new Error("Failed to extract image data from API response.");
|
75 |
+
}
|
76 |
+
return {
|
77 |
+
mimeType: imagePart.inlineData.mimeType,
|
78 |
+
data: imagePart.inlineData.data
|
79 |
+
};
|
80 |
+
} catch (error) {
|
81 |
+
console.error("Error extracting image data:", error);
|
82 |
+
console.error("Full API Response on Error:", JSON.stringify(response, null, 2));
|
83 |
+
throw error;
|
84 |
+
}
|
85 |
+
}
|
86 |
+
|
87 |
+
// --- Core Logic Functions --- (Остаются такими же)
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Analyze and optimize the original prompt using a text model.
|
91 |
+
* @param {string} originalPrompt - User's original text prompt.
|
92 |
+
* @returns {Promise<string>} - The enhanced prompt.
|
93 |
+
*/
|
94 |
+
async function analyzeAndEnhancePrompt(originalPrompt) {
|
95 |
+
console.log("✍️ Улучшение промпта...");
|
96 |
+
const prompt = `Ты эксперт по инженерии промптов. Улучши этот промпт для генерации изображений:
|
97 |
+
1. Добавь детальные спецификации (цвета, стиль, композиция, освещение, настроение)
|
98 |
+
2. Уточни неоднозначные элементы
|
99 |
+
3. Предложи художественные улучшения и специфичные ключевые слова
|
100 |
+
4. Обеспечь физическую правдоподобность, где это уместно
|
101 |
+
|
102 |
+
Оригинальный промпт: "${originalPrompt}"
|
103 |
+
|
104 |
+
Верни ТОЛЬКО улучшенный промпт без объяснений, вступлений или заголовков.`;
|
105 |
+
|
106 |
+
try {
|
107 |
+
const result = await textModel.generateContent(prompt);
|
108 |
+
return extractText(result);
|
109 |
+
} catch (error) {
|
110 |
+
console.error("Ошибка при улучшении промпта:", error);
|
111 |
+
console.warn("Возвращаемся к оригинальному промпту из-за ошибки улучшения.");
|
112 |
+
return originalPrompt;
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Simulate diffusion process: conceptual exploration -> refinement.
|
118 |
+
* Identifies concepts to avoid and elements to include.
|
119 |
+
* @param {string} prompt - The (usually enhanced) prompt.
|
120 |
+
* @returns {Promise<object>} - Object with 'negativeConcepts' and 'positiveDetails' arrays.
|
121 |
+
*/
|
122 |
+
async function simulateDiffusion(prompt) {
|
123 |
+
console.log("🧪 Симуляция процесса диффузии...");
|
124 |
+
try {
|
125 |
+
// Фаза 1: Идентификация того, что НЕ нужно включать
|
126 |
+
const negativePrompt = `Перечисли 3 абстрактных или конкретных концепта/элемента, которые явно НЕ должны появляться на изображении, сгенерированном по этому промпту: "${prompt}"\n\nОтформатируй вывод строго как: Концепт 1 | Концепт 2 | Концепт 3`;
|
127 |
+
const negativeResult = await textModel.generateContent(negativePrompt);
|
128 |
+
const negativeConceptsText = extractText(negativeResult);
|
129 |
+
const negativeConcepts = negativeConceptsText.split('|').map(s => s.trim()).filter(Boolean);
|
130 |
+
|
131 |
+
// Фаза 2: Идентификация существенных деталей
|
132 |
+
const positivePrompt = `Извлеки 5 самых важных визуальных элементов или качеств, которые ДОЛЖНЫ появиться или быть представлены на изображении, сгенерированном для промпта: "${prompt}"\n\nОтформатируй вывод строго как: Элемент 1 | Элемент 2 | Элемент 3 | Элемент 4 | Элемент 5`;
|
133 |
+
const positiveResult = await textModel.generateContent(positivePrompt);
|
134 |
+
const positiveDetailsText = extractText(positiveResult);
|
135 |
+
const positiveDetails = positiveDetailsText.split('|').map(s => s.trim()).filter(Boolean);
|
136 |
+
|
137 |
+
if (negativeConcepts.length === 0) console.warn("Не удалось извлечь негативные концепты.");
|
138 |
+
if (positiveDetails.length === 0) console.warn("Не удалось извлечь позитивные детали.");
|
139 |
+
|
140 |
+
return {
|
141 |
+
negativeConcepts: negativeConcepts.length > 0 ? negativeConcepts : ["размытые элементы", "текстовые артефакты", "плохая композиция"],
|
142 |
+
positiveDetails: positiveDetails.length > 0 ? positiveDetails : ["четкий фокус", "хорошее освещение", "высокая детализация", "правильные цвета", "ясная композиция"]
|
143 |
+
};
|
144 |
+
} catch (error) {
|
145 |
+
console.error("Ошибка во время симуляции диффузии:", error);
|
146 |
+
console.warn("Используем стандартные данные диффузии из-за ошибки симуляции.");
|
147 |
+
return {
|
148 |
+
negativeConcepts: ["размытые элементы", "текстовые артефакты", "водяные знаки"],
|
149 |
+
positiveDetails: ["четкий фокус", "яркие цвета", "реалистичные текстуры", "хорошее освещение", "ясная композиция"]
|
150 |
+
};
|
151 |
+
}
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Generate image with diffusion constraints and potentially iterative feedback.
|
156 |
+
* @param {string} prompt - The current working prompt.
|
157 |
+
* @param {object} diffusionData - Contains 'negativeConcepts' and 'positiveDetails'.
|
158 |
+
* @param {object|null} previousImageData - Optional { mimeType: string, data: string } of the previous image (Base64).
|
159 |
+
* @returns {Promise<object>} - Object with { mimeType: string, data: string } for the generated image (Base64).
|
160 |
+
*/
|
161 |
+
async function generateImageWithCritique(prompt, diffusionData, previousImageData = null) {
|
162 |
+
console.log("🖼️ Генерация изображения...");
|
163 |
+
|
164 |
+
const generationPrompt = `
|
165 |
+
${prompt}
|
166 |
+
|
167 |
+
**Строгие указания:**
|
168 |
+
* **ИЗБЕГАТЬ:** ${diffusionData.negativeConcepts.join(", ")}.
|
169 |
+
* **ОБЯЗАТЕЛЬНО ВКЛЮЧИТЬ:** ${diffusionData.positiveDetails.join(", ")}.
|
170 |
+
* Максимизировать детализацию текстур и освещения.
|
171 |
+
* Обеспечить физическую точность и реализм всех объектов, если не указано иное (например, стиль фэнтези).
|
172 |
+
* Высокое разрешение, фотореалистичный стиль (если промпт не указывает иное).
|
173 |
+
|
174 |
+
${previousImageData ? "**Заметка по уточнению:** Улучшить предыдущую попытку на основе критики." : "**Начальная генерация.**"}
|
175 |
+
`;
|
176 |
+
|
177 |
+
const parts = [ { text: generationPrompt } ];
|
178 |
+
|
179 |
+
if (previousImageData) {
|
180 |
+
console.log(" ...используем предыдущее изображение для уточнения.");
|
181 |
+
parts.push({
|
182 |
+
inlineData: {
|
183 |
+
mimeType: previousImageData.mimeType,
|
184 |
+
data: previousImageData.data
|
185 |
+
}
|
186 |
+
});
|
187 |
+
}
|
188 |
+
|
189 |
+
try {
|
190 |
+
const result = await imageModel.generateContent({ contents: [{ parts }] });
|
191 |
+
return extractImageData(result);
|
192 |
+
} catch (error) {
|
193 |
+
console.error("Ошибка генерации изображения:", error);
|
194 |
+
throw new Error(`Генерация изображения не удалась: ${error.message}`);
|
195 |
+
}
|
196 |
+
}
|
197 |
+
|
198 |
+
/**
|
199 |
+
* Generate detailed artistic critique of the image based on the prompt.
|
200 |
+
* @param {object} imageData - { mimeType: string, data: string } of the image (Base64).
|
201 |
+
* @param {string} originalPrompt - The prompt the image was *intended* to match.
|
202 |
+
* @returns {Promise<string>} - The critique text.
|
203 |
+
*/
|
204 |
+
async function getImageCritique(imageData, originalPrompt) {
|
205 |
+
console.log("🧐 Критика изображения...");
|
206 |
+
const prompt = `Ты профессиональный арт-критик и аналитик ИИ-изображений. Проанализируй предоставленное изображение на основе следующего исходного промпта:
|
207 |
+
Промпт: "${originalPrompt}"
|
208 |
+
|
209 |
+
Предоставь краткую критику, сфокусированную *только* на действенных замечаниях для улучшения в *следующей* итерации генерации. Затронь эти пункты:
|
210 |
+
1. **Соответствие промпту:** Насколько хорошо оно соответствует *ключевым элементам* промпта? Что отсутствует или неверно?
|
211 |
+
2. **Реализм/Правдоподобие:** Определи любые нереалистичные, искаженные или физически неверные элементы (если это не задумано стилистически промптом).
|
212 |
+
3. **Композиция и Эстетика:** Предложи конкретные улучшения композиции, освещения, детализации или художественного качества.
|
213 |
+
4. **Технические Недостатки:** Отметь любые распространенные артефакты ИИ (например, лишние конечности, искаженный текст, размытые области).
|
214 |
+
|
215 |
+
Будь конкретным и конструктивным. Сформулируй 3-5 ключевых пунктов для улучшения.`;
|
216 |
+
|
217 |
+
const parts = [
|
218 |
+
{ text: prompt },
|
219 |
+
{ inlineData: { mimeType: imageData.mimeType, data: imageData.data } }
|
220 |
+
];
|
221 |
+
|
222 |
+
try {
|
223 |
+
const result = await textModel.generateContent({ contents: [{ parts }] });
|
224 |
+
return extractText(result);
|
225 |
+
} catch (error) {
|
226 |
+
console.error("Ошибка получения критики изображения:", error);
|
227 |
+
return "Генерация критики не удалась. Продолжаем с текущим промптом.";
|
228 |
+
}
|
229 |
+
}
|
230 |
+
|
231 |
+
/**
|
232 |
+
* Refine the prompt based on critique using a text model.
|
233 |
+
* @param {string} currentPrompt - The prompt used for the last generation.
|
234 |
+
* @param {string} critique - The critique received for the last image.
|
235 |
+
* @returns {Promise<string>} - The new, improved prompt.
|
236 |
+
*/
|
237 |
+
async function updatePromptFromCritique(currentPrompt, critique) {
|
238 |
+
console.log("✍️ Обновление промпта на основе критики...");
|
239 |
+
const prompt = `Улучши следующий промпт для генерации изображений, основываясь *только* на предоставленной критике. Тонко интегрируй обратную связь, чтобы направить *следующую* попытку генерации изображения. Не просто перечисляй пункты критики.
|
240 |
+
|
241 |
+
Текущий промпт: "${currentPrompt}"
|
242 |
+
|
243 |
+
Критика: "${critique}"
|
244 |
+
|
245 |
+
Верни ТОЛЬКО новый, улучшенный промпт без каких-либо объяснений, вступлений или заголовков. Сфокусируйся на включении предложений в описательный текст.`;
|
246 |
+
|
247 |
+
try {
|
248 |
+
const result = await textModel.generateContent(prompt);
|
249 |
+
return extractText(result);
|
250 |
+
} catch (error) {
|
251 |
+
console.error("Ошибка обновления промпта по критике:", error);
|
252 |
+
console.warn("Возвращаемся к предыдущему промпту из-за ошибки обновления.");
|
253 |
+
return currentPrompt;
|
254 |
+
}
|
255 |
+
}
|
256 |
+
|
257 |
+
/**
|
258 |
+
* Main function to generate an image with enhanced prompt and iterative critique.
|
259 |
+
* @param {string} initialUserPrompt - User's original text prompt.
|
260 |
+
* @param {number} iterations - Number of improvement cycles (default: 3).
|
261 |
+
* @returns {Promise<object>} - { mimeType: string, data: string } of the final optimized image (Base64).
|
262 |
+
*/
|
263 |
+
async function generateEnhancedImage(initialUserPrompt, iterations = 3) {
|
264 |
+
if (!initialUserPrompt) {
|
265 |
+
throw new Error("Начальный промпт пользователя не может быть пустым.");
|
266 |
+
}
|
267 |
+
console.log(`🚀 Запуск улучшенной генерации изображения для: "${initialUserPrompt}" с ${iterations} итерациями.`);
|
268 |
+
|
269 |
+
let currentPrompt = await analyzeAndEnhancePrompt(initialUserPrompt);
|
270 |
+
console.log(`✨ Начальный улучшенный промпт: ${currentPrompt}`);
|
271 |
+
|
272 |
+
console.log("🔍 Симуляция фазы диффузии: Идентификация ключевых концептов...");
|
273 |
+
const diffusionData = await simulateDiffusion(currentPrompt);
|
274 |
+
console.log(` Концепты для избегания: ${diffusionData.negativeConcepts.join(', ')}`);
|
275 |
+
console.log(` Детали для включения: ${diffusionData.positiveDetails.join(', ')}`);
|
276 |
+
|
277 |
+
let bestImageData = null;
|
278 |
+
for (let i = 0; i < iterations; i++) {
|
279 |
+
console.log(`\n🔄 Итерация ${i + 1}/${iterations}`);
|
280 |
+
|
281 |
+
try {
|
282 |
+
const currentImageData = await generateImageWithCritique(
|
283 |
+
currentPrompt,
|
284 |
+
diffusionData,
|
285 |
+
bestImageData
|
286 |
+
);
|
287 |
+
|
288 |
+
if (i < iterations - 1) {
|
289 |
+
const critique = await getImageCritique(currentImageData, currentPrompt);
|
290 |
+
console.log(`📝 Критика: ${critique}`);
|
291 |
+
currentPrompt = await updatePromptFromCritique(currentPrompt, critique);
|
292 |
+
console.log(`✨ Уточненный промпт для следующей итерации: ${currentPrompt}`);
|
293 |
+
} else {
|
294 |
+
console.log("✅ Последняя итерация завершена. Дальнейшая критика не требуется.");
|
295 |
+
}
|
296 |
+
bestImageData = currentImageData;
|
297 |
+
|
298 |
+
} catch (error) {
|
299 |
+
console.error(`Ошибка во время итерации ${i + 1}:`, error);
|
300 |
+
if (error.message.includes("Генерация изображения не удалась")) {
|
301 |
+
console.error("Остановка генерации из-за критической ошибки.");
|
302 |
+
throw error;
|
303 |
+
}
|
304 |
+
console.warn("Продолжаем следующую итерацию, несмотря на некритическую ошибку в текущей.");
|
305 |
+
if (!bestImageData && i === 0) { // Если первая же генерация не удалась
|
306 |
+
throw new Error("Начальная генерация изображения не удалась, невозможно продолжить.");
|
307 |
+
}
|
308 |
+
}
|
309 |
+
// Optional delay
|
310 |
+
// await new Promise(resolve => setTimeout(resolve, 1000));
|
311 |
+
}
|
312 |
+
|
313 |
+
if (!bestImageData) {
|
314 |
+
throw new Error("Процесс генерации изображения завершился без создания финального изображения.");
|
315 |
+
}
|
316 |
+
|
317 |
+
return bestImageData;
|
318 |
+
}
|
319 |
+
|
320 |
+
// --- Example Usage ---
|
321 |
+
async function main() {
|
322 |
+
const userPrompt = "Футуристический космический корабль на орбите очень близко к вращающейся, ярко светящейся нейтронной звезде, реалистичный стиль, высокая детализация"; // Промпт из примера
|
323 |
+
const outputFilename = "enhanced_spaceship.png";
|
324 |
+
const outputDir = "./output"; // Папка для сохранения
|
325 |
+
|
326 |
+
try {
|
327 |
+
await fs.mkdir(outputDir, { recursive: true });
|
328 |
+
const outputFilePath = path.join(outputDir, outputFilename);
|
329 |
+
|
330 |
+
const finalImageData = await generateEnhancedImage(userPrompt, 3); // 3 итерации
|
331 |
+
|
332 |
+
console.log(`\n💾 Сохранение финального изображения в ${outputFilePath}...`);
|
333 |
+
const imageBuffer = Buffer.from(finalImageData.data, 'base64');
|
334 |
+
await fs.writeFile(outputFilePath, imageBuffer);
|
335 |
+
|
336 |
+
console.log("✅ Изображение успешно сгенерировано и сохранено!");
|
337 |
+
|
338 |
+
} catch (error) {
|
339 |
+
console.error("\n❌ Произошла ошибка в процессе генерации изображения:", error);
|
340 |
+
process.exitCode = 1;
|
341 |
+
}
|
342 |
+
}
|
343 |
+
|
344 |
+
// Run the main function
|
345 |
+
main();
|