Aleksmorshen commited on
Commit
08d68fa
·
verified ·
1 Parent(s): da2b329

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -751
app.py DELETED
@@ -1,751 +0,0 @@
1
- import os
2
- import io
3
- import base64
4
- from flask import Flask, request, jsonify, Response
5
- from PIL import Image
6
- import google.generativeai as genai
7
- import numpy as np
8
-
9
- app = Flask(__name__)
10
-
11
- # It is highly recommended to load API keys from environment variables
12
- # instead of hardcoding them for security and flexibility.
13
- # Example: API_KEY_INTERNAL = os.environ.get("GEMINI_API_KEY")
14
- API_KEY_INTERNAL = "AIzaSyArKidc4o0MwbaCFKStlb2q2AwNg6Pnqns" # Replace with your actual Google Gemini API Key
15
-
16
- html_template = """
17
- <!DOCTYPE html>
18
- <html lang="ru">
19
- <head>
20
- <meta charset="UTF-8">
21
- <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
22
- <title>EVA - Генератор постов</title>
23
- <link rel="preconnect" href="https://fonts.googleapis.com">
24
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
25
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
26
- <style>
27
- :root {
28
- /* Light Theme System Colors */
29
- --system-gray-100-light: #F2F2F7; /* Background */
30
- --system-gray-75-light: #F8F8FA; /* Input/Card background slightly lighter */
31
- --system-gray-50-light: #FFFFFF; /* Content Card background */
32
- --system-gray-dark-100-light: #000000; /* Primary Text */
33
- --system-gray-dark-75-light: #1C1C1E; /* Secondary Text (darker) */
34
- --system-gray-dark-50-light: #3A3A3C; /* Tertiary Text (darker) */
35
- --system-gray-light-75-light: #8E8E93; /* Secondary Text (lighter) */
36
- --system-gray-light-50-light: #AEAEB2; /* Tertiary Text (lighter) */
37
- --system-blue-light: #007AFF; /* Primary Accent */
38
- --system-blue-light-hover: #005ECF; /* Primary Accent Hover */
39
- --system-red-light: #FF3B30; /* Error */
40
- --system-green-light: #34C759; /* Success */
41
- --system-separator-light: rgba(60, 60, 67, 0.29); /* Subtle Separator */
42
- --system-separator-opaque-light: #D1D1D6; /* Opaque Separator */
43
-
44
- /* Dark Theme System Colors */
45
- --system-gray-100-dark: #1C1C1E; /* Background */
46
- --system-gray-75-dark: #2C2C2E; /* Input/Card background slightly lighter */
47
- --system-gray-50-dark: #000000; /* Content Card background (pure black for OLED) */
48
- --system-gray-dark-100-dark: #FFFFFF; /* Primary Text */
49
- --system-gray-dark-75-dark: #F2F2F7; /* Secondary Text (lighter) */
50
- --system-gray-dark-50-dark: #E5E5EA; /* Tertiary Text (lighter) */
51
- --system-gray-light-75-dark: #8E8E93; /* Secondary Text (darker) */
52
- --system-gray-light-50-dark: #636366; /* Tertiary Text (darker) */
53
- --system-blue-dark: #0A84FF; /* Primary Accent */
54
- --system-blue-dark-hover: #3B9EFF; /* Primary Accent Hover */
55
- --system-red-dark: #FF453A; /* Error */
56
- --system-green-dark: #30D158; /* Success */
57
- --system-separator-dark: rgba(84, 84, 88, 0.65); /* Subtle Separator */
58
- --system-separator-opaque-dark: #38383A; /* Opaque Separator */
59
-
60
- /* Semantic Colors (Resolved by prefers-color-scheme) */
61
- --bg-color: var(--system-gray-100-light);
62
- --content-bg: var(--system-gray-50-light);
63
- --text-color: var(--system-gray-dark-100-light);
64
- --secondary-text-color: var(--system-gray-light-75-light);
65
- --tertiary-text-color: var(--system-gray-light-50-light);
66
- --border-color: var(--system-separator-light);
67
- --border-color-opaque: var(--system-separator-opaque-light);
68
- --input-bg: var(--system-gray-75-light);
69
- --primary-color: var(--system-blue-light);
70
- --primary-color-hover: var(--system-blue-light-hover);
71
- --error-color: var(--system-red-light);
72
- --success-color: var(--system-green-light);
73
- }
74
-
75
- @media (prefers-color-scheme: dark) {
76
- :root {
77
- --bg-color: var(--system-gray-100-dark);
78
- --content-bg: var(--system-gray-50-dark);
79
- --text-color: var(--system-gray-dark-100-dark);
80
- --secondary-text-color: var(--system-gray-light-75-dark);
81
- --tertiary-text-color: var(--system-gray-light-50-dark);
82
- --border-color: var(--system-separator-dark);
83
- --border-color-opaque: var(--system-separator-opaque-dark);
84
- --input-bg: var(--system-gray-75-dark);
85
- --primary-color: var(--system-blue-dark);
86
- --primary-color-hover: var(--system-blue-dark-hover);
87
- --error-color: var(--system-red-dark);
88
- --success-color: var(--system-green-dark);
89
- }
90
- }
91
-
92
- html {
93
- height: -webkit-fill-available;
94
- }
95
-
96
- body {
97
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
98
- margin: 0;
99
- padding-top: 20px;
100
- padding-bottom: 20px;
101
- padding-left: max(20px, env(safe-area-inset-left));
102
- padding-right: max(20px, env(safe-area-inset-right));
103
- background-color: var(--bg-color);
104
- color: var(--text-color);
105
- display: flex;
106
- justify-content: center;
107
- align-items: flex-start;
108
- min-height: 100vh;
109
- min-height: -webkit-fill-available;
110
- line-height: 1.45;
111
- -webkit-font-smoothing: antialiased;
112
- -moz-osx-font-smoothing: grayscale;
113
- box-sizing: border-box;
114
- transition: background-color 0.3s ease;
115
- }
116
-
117
- .container {
118
- background-color: var(--content-bg);
119
- padding: 30px 30px 35px 30px;
120
- border-radius: 28px;
121
- box-shadow: 0 16px 32px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0,0,0,0.08);
122
- max-width: 600px;
123
- width: 100%;
124
- box-sizing: border-box;
125
- margin-top: 30px;
126
- transition: background-color 0.3s ease, box-shadow 0.3s ease;
127
- }
128
-
129
- h1 {
130
- font-size: 34px;
131
- font-weight: 700;
132
- text-align: center;
133
- margin-bottom: 6px;
134
- color: var(--text-color);
135
- letter-spacing: -0.6px;
136
- }
137
-
138
- p.subtitle {
139
- font-size: 18px;
140
- color: var(--secondary-text-color);
141
- text-align: center;
142
- margin-bottom: 40px;
143
- font-weight: 400;
144
- }
145
-
146
- .form-group {
147
- margin-bottom: 30px;
148
- }
149
-
150
- label.input-label {
151
- display: block;
152
- font-weight: 500;
153
- margin-bottom: 12px;
154
- font-size: 16px;
155
- color: var(--secondary-text-color);
156
- padding-left: 5px;
157
- }
158
-
159
- input[type="file"] {
160
- display: block;
161
- width: 100%;
162
- padding: 16px 20px;
163
- border: 1px solid var(--border-color-opaque);
164
- border-radius: 14px;
165
- font-size: 17px;
166
- background-color: var(--input-bg);
167
- color: var(--text-color);
168
- box-sizing: border-box;
169
- transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
170
- cursor: pointer;
171
- font-family: inherit;
172
- line-height: 1;
173
- }
174
-
175
- input[type="file"]:focus {
176
- border-color: var(--primary-color);
177
- box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary-color) 25%, transparent);
178
- outline: none;
179
- }
180
-
181
- input[type="file"]::file-selector-button {
182
- font-weight: 600;
183
- color: var(--primary-color);
184
- background-color: transparent;
185
- border: none;
186
- padding: 0;
187
- margin-right: 15px;
188
- cursor: pointer;
189
- font-size: 17px;
190
- transition: color 0.2s ease;
191
- }
192
-
193
- input[type="file"]:hover::file-selector-button {
194
- color: var(--primary-color-hover);
195
- }
196
-
197
- .language-options {
198
- display: flex;
199
- flex-wrap: wrap;
200
- gap: 12px;
201
- margin-top: 5px;
202
- justify-content: center; /* Center options horizontally */
203
- }
204
-
205
- .language-options label {
206
- display: flex;
207
- align-items: center;
208
- background-color: var(--input-bg);
209
- padding: 12px 22px;
210
- border-radius: 30px;
211
- cursor: pointer;
212
- transition: background-color 0.2s ease, color 0.2s ease, transform 0.1s ease, border-color 0.2s ease;
213
- font-weight: 500;
214
- font-size: 15px;
215
- color: var(--tertiary-text-color);
216
- border: 1px solid var(--border-color); /* Subtle border */
217
- user-select: none;
218
- min-width: 90px;
219
- text-align: center;
220
- justify-content: center;
221
- }
222
-
223
- .language-options input[type="radio"] {
224
- display: none;
225
- }
226
-
227
- .language-options input[type="radio"]:checked + label {
228
- background-color: var(--primary-color);
229
- color: white;
230
- font-weight: 600;
231
- border-color: var(--primary-color);
232
- }
233
-
234
- .language-options label:hover:not(:checked) {
235
- background-color: color-mix(in srgb, var(--input-bg) 85%, var(--secondary-text-color));
236
- border-color: color-mix(in srgb, var(--border-color-opaque) 70%, var(--primary-color));
237
- }
238
-
239
- .language-options input[type="radio"]:checked + label:hover {
240
- background-color: var(--primary-color-hover);
241
- border-color: var(--primary-color-hover);
242
- }
243
-
244
- .language-options label:active {
245
- transform: scale(0.97);
246
- }
247
-
248
- button#generate-button {
249
- width: 100%;
250
- padding: 18px;
251
- background-color: var(--primary-color);
252
- color: white;
253
- border: none;
254
- border-radius: 14px;
255
- font-size: 18px;
256
- font-weight: 600;
257
- cursor: pointer;
258
- transition: background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease;
259
- margin-top: 20px;
260
- box-shadow: 0 4px 10px rgba(0, 122, 255, 0.2); /* Soft shadow for button */
261
- }
262
-
263
- button#generate-button:hover {
264
- background-color: var(--primary-color-hover);
265
- box-shadow: 0 6px 15px rgba(0, 122, 255, 0.3);
266
- }
267
-
268
- button#generate-button:active {
269
- transform: scale(0.98);
270
- box-shadow: 0 2px 5px rgba(0, 122, 255, 0.2);
271
- }
272
-
273
- button#generate-button:disabled {
274
- background-color: var(--tertiary-text-color);
275
- cursor: not-allowed;
276
- box-shadow: none;
277
- }
278
-
279
- .output-section {
280
- margin-top: 40px;
281
- }
282
-
283
- .output-header {
284
- display: flex;
285
- justify-content: space-between;
286
- align-items: center;
287
- margin-bottom: 12px;
288
- }
289
-
290
- label#output-label {
291
- font-weight: 500;
292
- font-size: 16px;
293
- color: var(--secondary-text-color);
294
- padding-left: 5px;
295
- }
296
-
297
- button#copy-button {
298
- background-color: transparent;
299
- border: none;
300
- color: var(--primary-color);
301
- font-size: 15px;
302
- font-weight: 500;
303
- cursor: pointer;
304
- padding: 8px 12px;
305
- border-radius: 8px;
306
- transition: background-color 0.2s ease, color 0.2s ease, transform 0.1s ease;
307
- display: none;
308
- text-wrap: nowrap;
309
- }
310
-
311
- button#copy-button:hover {
312
- background-color: color-mix(in srgb, var(--primary-color) 15%, transparent);
313
- }
314
-
315
- button#copy-button:active {
316
- background-color: color-mix(in srgb, var(--primary-color) 25%, transparent);
317
- transform: scale(0.98);
318
- }
319
-
320
- button#copy-button.copied {
321
- color: var(--success-color);
322
- background-color: color-mix(in srgb, var(--success-color) 15%, transparent);
323
- }
324
- button#copy-button.copied:hover {
325
- background-color: color-mix(in srgb, var(--success-color) 25%, transparent);
326
- }
327
-
328
-
329
- #output-container {
330
- background-color: var(--input-bg);
331
- padding: 20px 22px;
332
- border-radius: 14px;
333
- min-height: 150px;
334
- border: 1px solid var(--border-color);
335
- white-space: pre-wrap;
336
- word-wrap: break-word;
337
- font-size: 16px;
338
- color: var(--text-color);
339
- line-height: 1.6;
340
- transition: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease;
341
- box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05); /* Subtle inner shadow */
342
- }
343
-
344
- #output-container.loading {
345
- display: flex;
346
- justify-content: center;
347
- align-items: center;
348
- text-align: center;
349
- font-style: italic;
350
- color: var(--secondary-text-color);
351
- animation: fadePulse 1.8s infinite ease-in-out;
352
- }
353
- #output-container.loading::before {
354
- content: "Генерация...";
355
- display: block;
356
- }
357
-
358
- #output-container.error {
359
- color: var(--error-color);
360
- font-weight: 500;
361
- border-color: color-mix(in srgb, var(--error-color) 50%, transparent);
362
- background-color: color-mix(in srgb, var(--error-color) 10%, var(--input-bg));
363
- box-shadow: inset 0 1px 3px rgba(255, 0, 0, 0.1);
364
- }
365
-
366
- #image-preview-container {
367
- margin-top: 20px;
368
- text-align: center;
369
- padding: 10px;
370
- }
371
-
372
- #image-preview {
373
- max-width: 100%;
374
- max-height: 250px;
375
- border-radius: 16px;
376
- margin-top: 10px;
377
- border: 1px solid var(--border-color-opaque);
378
- display: none;
379
- background-color: var(--input-bg);
380
- object-fit: contain;
381
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
382
- transition: border-color 0.2s ease, box-shadow 0.2s ease;
383
- }
384
-
385
- @keyframes fadePulse {
386
- 0%, 100% { opacity: 0.6; }
387
- 50% { opacity: 1; }
388
- }
389
-
390
- @media (max-width: 680px) {
391
- body {
392
- padding-top: 15px;
393
- padding-bottom: 15px;
394
- padding-left: max(15px, env(safe-area-inset-left));
395
- padding-right: max(15px, env(safe-area-inset-right));
396
- }
397
- .container {
398
- padding: 25px 20px 30px 20px;
399
- margin-top: 15px;
400
- border-radius: 24px;
401
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0,0,0,0.05);
402
- }
403
- h1 {
404
- font-size: 30px;
405
- }
406
- p.subtitle {
407
- font-size: 17px;
408
- margin-bottom: 30px;
409
- }
410
- .form-group {
411
- margin-bottom: 25px;
412
- }
413
- label.input-label {
414
- font-size: 15px;
415
- margin-bottom: 10px;
416
- }
417
- input[type="file"] {
418
- padding: 14px 18px;
419
- font-size: 16px;
420
- border-radius: 12px;
421
- }
422
- input[type="file"]::file-selector-button {
423
- font-size: 16px;
424
- margin-right: 12px;
425
- }
426
- .language-options {
427
- gap: 10px;
428
- justify-content: space-between;
429
- }
430
- .language-options label {
431
- padding: 10px 18px;
432
- font-size: 14px;
433
- min-width: unset;
434
- flex-grow: 1;
435
- }
436
- button#generate-button {
437
- padding: 16px;
438
- font-size: 17px;
439
- border-radius: 12px;
440
- margin-top: 18px;
441
- }
442
- .output-section {
443
- margin-top: 35px;
444
- }
445
- #output-container {
446
- padding: 18px 20px;
447
- font-size: 15px;
448
- min-height: 120px;
449
- border-radius: 12px;
450
- }
451
- label#output-label {
452
- font-size: 15px;
453
- }
454
- button#copy-button {
455
- font-size: 14px;
456
- padding: 6px 10px;
457
- }
458
- #image-preview {
459
- max-height: 200px;
460
- border-radius: 14px;
461
- }
462
- }
463
-
464
- @media (max-width: 480px) {
465
- .container {
466
- padding: 20px 15px 25px 15px;
467
- border-radius: 20px;
468
- }
469
- h1 {
470
- font-size: 28px;
471
- }
472
- p.subtitle {
473
- font-size: 16px;
474
- margin-bottom: 25px;
475
- }
476
- .language-options label {
477
- font-size: 13px;
478
- padding: 9px 15px;
479
- }
480
- input[type="file"] {
481
- padding: 12px 15px;
482
- font-size: 15px;
483
- }
484
- input[type="file"]::file-selector-button {
485
- font-size: 15px;
486
- margin-right: 10px;
487
- }
488
- button#generate-button {
489
- padding: 15px;
490
- font-size: 16px;
491
- }
492
- #output-container {
493
- padding: 15px 18px;
494
- font-size: 14px;
495
- min-height: 100px;
496
- }
497
- .output-section {
498
- margin-top: 30px;
499
- }
500
- }
501
- </style>
502
- </head>
503
- <body>
504
- <div class="container">
505
- <h1>EVA</h1>
506
- <p class="subtitle">Генератор рекламных постов</p>
507
-
508
- <form id="generate-form">
509
-
510
- <div class="form-group">
511
- <label class="input-label">Выберите язык поста</label>
512
- <div class="language-options">
513
- <input type="radio" id="lang-ru" name="language" value="Русский" checked>
514
- <label for="lang-ru">Русский</label>
515
-
516
- <input type="radio" id="lang-kg" name="language" value="Кыргызский">
517
- <label for="lang-kg">Кыргызский</label>
518
-
519
- <input type="radio" id="lang-kz" name="language" value="Казахский">
520
- <label for="lang-kz">Казахский</label>
521
-
522
- <input type="radio" id="lang-uz" name="language" value="Узбекский">
523
- <label for="lang-uz">Узбекский</label>
524
- </div>
525
- </div>
526
-
527
- <div class="form-group">
528
- <label for="image-upload" class="input-label">Загрузить изображение</label>
529
- <input type="file" id="image-upload" name="image" accept="image/png, image/jpeg, image/webp" required>
530
- <div id="image-preview-container">
531
- <img id="image-preview" src="#" alt="Предпросмотр изображения"/>
532
- </div>
533
- </div>
534
-
535
- <button type="submit" id="generate-button">Пуск</button>
536
- </form>
537
-
538
- <div class="output-section">
539
- <div class="output-header">
540
- <label id="output-label">Результат</label>
541
- <button id="copy-button">Копировать</button>
542
- </div>
543
- <div id="output-container" aria-live="polite">
544
- </div>
545
- </div>
546
- </div>
547
-
548
- <script>
549
- const form = document.getElementById('generate-form');
550
- const imageInput = document.getElementById('image-upload');
551
- const imagePreview = document.getElementById('image-preview');
552
- const outputContainer = document.getElementById('output-container');
553
- const generateButton = document.getElementById('generate-button');
554
- const imagePreviewContainer = document.getElementById('image-preview-container');
555
- const copyButton = document.getElementById('copy-button');
556
-
557
- imageInput.addEventListener('change', function(event) {
558
- const file = event.target.files[0];
559
- if (file) {
560
- if (file.type.startsWith('image/')) {
561
- const reader = new FileReader();
562
- reader.onload = function(e) {
563
- imagePreview.src = e.target.result;
564
- imagePreview.style.display = 'block';
565
- }
566
- reader.readAsDataURL(file);
567
- } else {
568
- showError("Выбранный файл не является изображением. Пожалуйста, загрузите файл формата PNG, JPG или WEBP.");
569
- imagePreview.src = '#';
570
- imagePreview.style.display = 'none';
571
- imageInput.value = ''; // Clear the input
572
- }
573
- } else {
574
- imagePreview.src = '#';
575
- imagePreview.style.display = 'none';
576
- }
577
- });
578
-
579
- form.addEventListener('submit', async (event) => {
580
- event.preventDefault();
581
-
582
- if (!imageInput.files || imageInput.files.length === 0) {
583
- showError("Пожалуйста, загрузите изображение.");
584
- return;
585
- }
586
-
587
- const formData = new FormData(form);
588
-
589
- generateButton.disabled = true;
590
- generateButton.textContent = 'Генерация...';
591
- outputContainer.innerHTML = '';
592
- outputContainer.classList.add('loading');
593
- outputContainer.classList.remove('error');
594
- copyButton.style.display = 'none';
595
- copyButton.textContent = 'Копировать';
596
- copyButton.classList.remove('copied');
597
-
598
-
599
- try {
600
- const response = await fetch('/generate', {
601
- method: 'POST',
602
- body: formData
603
- });
604
-
605
- const result = await response.json();
606
-
607
- if (!response.ok) {
608
- throw new Error(result.error || `Ошибка сервера: ${response.status}`);
609
- }
610
-
611
- outputContainer.textContent = result.text;
612
- if (result.text && result.text.trim().length > 0) {
613
- copyButton.style.display = 'block';
614
- } else {
615
- copyButton.style.display = 'none';
616
- }
617
-
618
- } catch (error) {
619
- console.error("Fetch Error:", error);
620
- showError(`Ошибка: ${error.message}`);
621
- copyButton.style.display = 'none';
622
- } finally {
623
- generateButton.disabled = false;
624
- generateButton.textContent = 'Пуск';
625
- outputContainer.classList.remove('loading');
626
- }
627
- });
628
-
629
- copyButton.addEventListener('click', () => {
630
- const textToCopy = outputContainer.textContent;
631
- if (!textToCopy || textToCopy.trim().length === 0) return;
632
-
633
- navigator.clipboard.writeText(textToCopy).then(() => {
634
- copyButton.textContent = 'Скопировано!';
635
- copyButton.classList.add('copied');
636
- setTimeout(() => {
637
- copyButton.textContent = 'Копировать';
638
- copyButton.classList.remove('copied');
639
- }, 1500);
640
- }).catch(err => {
641
- console.error('Ошибка копирования: ', err);
642
- copyButton.textContent = 'Ошибка';
643
- setTimeout(() => {
644
- copyButton.textContent = 'Копировать';
645
- }, 1500);
646
- });
647
- });
648
-
649
- function showError(message) {
650
- outputContainer.textContent = message;
651
- outputContainer.classList.add('error');
652
- outputContainer.classList.remove('loading');
653
- copyButton.style.display = 'none';
654
- }
655
-
656
- </script>
657
- </body>
658
- </html>
659
- """
660
-
661
- def generate_ai_content(image_data, language):
662
- try:
663
- genai.configure(api_key=API_KEY_INTERNAL)
664
- except Exception as e:
665
- raise ValueError(f"Не удалось настроить Google AI: {e}. Проверьте ключ API или подключение.")
666
-
667
- if not image_data:
668
- raise ValueError("Файл изображения пуст или не был передан.")
669
-
670
- try:
671
- image_stream = io.BytesIO(image_data)
672
- image = Image.open(image_stream).convert('RGB')
673
- except Exception as e:
674
- raise ValueError(f"Не удалось обработать изображение. Убедитесь, что это действительный файл изображения. Ошибка: {e}")
675
-
676
- base_prompt = "Напиши большой и красивый, содержательный рекламный пост минимум на 1000 символов со смайликами и 25 тематических хэштегов с ключевыми словами разных вариантов, чтобы мои клиенты могли найти меня в поиске Instagram, Google и т.д. по ключевым словам. Пост пиши исключительно под товар, который на фото, без адресов и номеров телефона."
677
-
678
- lang_suffix = ""
679
- if language == "Русский":
680
- lang_suffix = " Пиши на русском языке."
681
- elif language == "Кыргызский":
682
- lang_suffix = " Пиши на кыргызском языке."
683
- elif language == "Казахский":
684
- lang_suffix = " Пиши на казахском языке."
685
- elif language == "Узбекский":
686
- lang_suffix = " Пиши на узбекском языке."
687
- else:
688
- raise ValueError("Выбран неподдерживаемый язык.")
689
-
690
- final_prompt = f"{base_prompt}{lang_suffix}"
691
-
692
- try:
693
- model = genai.GenerativeModel('gemma-3-27b-it') # Using the latest flash model
694
- response = model.generate_content([final_prompt, image])
695
-
696
- if hasattr(response, 'text'):
697
- return response.text
698
- else:
699
- # Fallback for models that might return content in parts, though .text is standard
700
- if response.parts:
701
- return "".join(part.text for part in response.parts if hasattr(part, 'text'))
702
- else:
703
- raise ValueError("Не удалось получить текст ответа от модели. Возможно, не было сгенерировано контента.")
704
-
705
- except Exception as e:
706
- error_message = str(e)
707
- if "API key not valid" in error_message:
708
- raise ValueError("Внутренняя ошибка конфигурации API. Проверьте ваш ключ Google Gemini API.")
709
- elif "Billing account not found" in error_message or "CREDENTIALS_LOCATION_MISSING" in error_message:
710
- raise ValueError("Проблема с биллингом аккаунта Google Cloud. Убедитесь, что биллинг включен.")
711
- elif "Could not find model" in error_message:
712
- raise ValueError(f"Модель 'gemini-1.5-flash-latest' не найдена или недоступна в вашем регионе.")
713
- elif "resource has been exhausted" in error_message.lower():
714
- raise ValueError("Квота запросов исчерпана. Пожалуйста, попробуйте позже.")
715
- elif "content has been blocked" in error_message.lower() or (hasattr(response, 'prompt_feedback') and response.prompt_feedback.block_reason):
716
- reason = response.prompt_feedback.block_reason if hasattr(response, 'prompt_feedback') else "неизвестна"
717
- raise ValueError(f"Генерация контента заблокирована из-за политики безопасности (причина: {reason}). Попробуйте другое изображение или запрос.")
718
- else:
719
- raise ValueError(f"Ошибка при генерации контента: {e}")
720
-
721
-
722
- @app.route('/')
723
- def index():
724
- return Response(html_template, mimetype='text/html')
725
-
726
- @app.route('/generate', methods=['POST'])
727
- def handle_generate():
728
- if 'image' not in request.files:
729
- return jsonify({"error": "Изображение не найдено в запросе."}), 400
730
- if 'language' not in request.form:
731
- return jsonify({"error": "Язык не найден в запросе."}), 400
732
-
733
- image_file = request.files['image']
734
- language = request.form['language']
735
-
736
- image_data = image_file.read()
737
-
738
- if not image_data:
739
- return jsonify({"error": "Загруженный файл изображения пуст."}), 400
740
-
741
- try:
742
- result_text = generate_ai_content(image_data, language)
743
- return jsonify({"text": result_text})
744
- except ValueError as ve:
745
- return jsonify({"error": str(ve)}), 400
746
- except Exception as e:
747
- return jsonify({"error": f"Внутренняя ошибка сервера: {e}"}), 500
748
-
749
-
750
- if __name__ == '__main__':
751
- app.run(host='0.0.0.0', port=7860, debug=False)