liberalusa commited on
Commit
dd985eb
·
verified ·
1 Parent(s): 2df1e1f

Create image.js

Browse files
Files changed (1) hide show
  1. image.js +345 -0
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();