Chat / app.py
Aleksmorshen's picture
Update app.py
c8373b5 verified
raw
history blame
23.9 kB
import os
import io
import base64
from flask import Flask, request, jsonify, Response
from PIL import Image
import google.generativeai as genai
import numpy as np
app = Flask(__name__)
API_KEY_INTERNAL = "AIzaSyArKidc4o0MwbaCFKStlb2q2AwNg6Pnqns" # Hardcoded API Key
html_template = """
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>EVA - Генератор постов</title>
<style>
:root {
--system-gray-100-light: #f2f2f7;
--system-gray-75-light: #f8f8fa;
--system-gray-50-light: #ffffff;
--system-gray-dark-100-light: #000000;
--system-gray-dark-75-light: #1c1c1e;
--system-gray-dark-50-light: #3a3a3c;
--system-gray-light-75-light: #8e8e93;
--system-gray-light-50-light: #aeaeb2;
--system-blue-light: #007aff;
--system-blue-light-hover: #005ecf;
--system-red-light: #ff3b30;
--system-separator-light: rgba(60, 60, 67, 0.29);
--system-separator-opaque-light: #d1d1d6;
--system-gray-100-dark: #1c1c1e;
--system-gray-75-dark: #2c2c2e;
--system-gray-50-dark: #000000;
--system-gray-dark-100-dark: #ffffff;
--system-gray-dark-75-dark: #f2f2f7;
--system-gray-dark-50-dark: #e5e5ea;
--system-gray-light-75-dark: #8e8e93;
--system-gray-light-50-dark: #636366;
--system-blue-dark: #0a84ff;
--system-blue-dark-hover: #3b9eff;
--system-red-dark: #ff453a;
--system-separator-dark: rgba(84, 84, 88, 0.65);
--system-separator-opaque-dark: #38383a;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: var(--system-gray-50-dark);
--content-bg: var(--system-gray-100-dark);
--text-color: var(--system-gray-dark-100-dark);
--secondary-text-color: var(--system-gray-light-75-dark);
--tertiary-text-color: var(--system-gray-light-50-dark);
--border-color: var(--system-separator-dark);
--border-color-opaque: var(--system-separator-opaque-dark);
--input-bg: var(--system-gray-75-dark);
--primary-color: var(--system-blue-dark);
--primary-color-hover: var(--system-blue-dark-hover);
--error-color: var(--system-red-dark);
}
}
@media (prefers-color-scheme: light) {
:root {
--bg-color: var(--system-gray-100-light);
--content-bg: var(--system-gray-50-light);
--text-color: var(--system-gray-dark-100-light);
--secondary-text-color: var(--system-gray-light-75-light);
--tertiary-text-color: var(--system-gray-light-50-light);
--border-color: var(--system-separator-light);
--border-color-opaque: var(--system-separator-opaque-light);
--input-bg: var(--system-gray-75-light);
--primary-color: var(--system-blue-light);
--primary-color-hover: var(--system-blue-light-hover);
--error-color: var(--system-red-light);
}
}
html {
height: -webkit-fill-available;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
margin: 0;
padding: 20px env(safe-area-inset-top) 20px env(safe-area-inset-bottom);
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
min-height: -webkit-fill-available;
line-height: 1.45;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.container {
background-color: var(--content-bg);
padding: 25px 30px 30px 30px;
border-radius: 24px;
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0,0,0,0.05);
max-width: 580px;
width: calc(100% - 40px);
box-sizing: border-box;
margin-top: 30px;
}
h1 {
font-size: 32px;
font-weight: 700;
text-align: center;
margin-bottom: 8px;
color: var(--text-color);
letter-spacing: -0.5px;
}
p.subtitle {
font-size: 17px;
color: var(--secondary-text-color);
text-align: center;
margin-bottom: 35px;
font-weight: 400;
}
.form-group {
margin-bottom: 28px;
}
label.input-label {
display: block;
font-weight: 500;
margin-bottom: 10px;
font-size: 15px;
color: var(--secondary-text-color);
padding-left: 5px;
}
input[type="file"] {
width: 100%;
padding: 14px 18px;
border: 1px solid var(--border-color-opaque);
border-radius: 12px;
font-size: 16px;
background-color: var(--input-bg);
color: var(--text-color);
box-sizing: border-box;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
cursor: pointer;
font-family: inherit;
}
input[type="file"]:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 25%, transparent);
outline: none;
}
input[type="file"]::file-selector-button {
font-weight: 500;
color: var(--primary-color);
background-color: transparent;
border: none;
padding: 0;
margin-right: 12px;
cursor: pointer;
font-size: 16px;
}
.language-options {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 5px;
}
.language-options label {
display: flex;
align-items: center;
background-color: var(--input-bg);
padding: 10px 18px;
border-radius: 25px;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease, transform 0.1s ease;
font-weight: 500;
font-size: 14px;
color: var(--tertiary-text-color);
border: 1px solid transparent;
user-select: none;
}
.language-options input[type="radio"] {
display: none;
}
.language-options input[type="radio"]:checked + label {
background-color: var(--primary-color);
color: white;
font-weight: 600;
border-color: transparent;
}
.language-options label:hover {
background-color: color-mix(in srgb, var(--input-bg) 85%, var(--secondary-text-color));
}
.language-options input[type="radio"]:checked + label:hover {
background-color: var(--primary-color-hover);
}
.language-options label:active {
transform: scale(0.96);
}
button#generate-button {
width: 100%;
padding: 16px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 12px;
font-size: 17px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease, transform 0.1s ease;
margin-top: 15px;
}
button#generate-button:hover {
background-color: var(--primary-color-hover);
}
button#generate-button:active {
transform: scale(0.98);
}
button#generate-button:disabled {
background-color: var(--tertiary-text-color);
cursor: not-allowed;
}
.output-section {
margin-top: 35px;
}
.output-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
label#output-label {
font-weight: 500;
font-size: 15px;
color: var(--secondary-text-color);
padding-left: 5px;
}
button#copy-button {
background-color: transparent;
border: none;
color: var(--primary-color);
font-size: 14px;
font-weight: 500;
cursor: pointer;
padding: 5px 8px;
border-radius: 6px;
transition: background-color 0.2s ease, color 0.2s ease;
display: none;
}
button#copy-button:hover {
background-color: color-mix(in srgb, var(--primary-color) 15%, transparent);
}
button#copy-button:active {
background-color: color-mix(in srgb, var(--primary-color) 25%, transparent);
}
button#copy-button.copied {
color: #34c759; /* System Green */
}
@media (prefers-color-scheme: dark) {
button#copy-button.copied {
color: #30d158; /* System Green Dark */
}
}
#output-container {
background-color: var(--input-bg);
padding: 18px 20px;
border-radius: 12px;
min-height: 120px;
border: 1px solid var(--border-color);
white-space: pre-wrap;
word-wrap: break-word;
font-size: 15px;
color: var(--text-color);
line-height: 1.5;
transition: border-color 0.2s ease, background-color 0.2s ease;
}
#output-container.loading::before {
content: "Генерация...";
display: block;
text-align: center;
font-style: italic;
color: var(--secondary-text-color);
animation: fadePulse 1.8s infinite ease-in-out;
padding-top: 30px;
}
#output-container.error {
color: var(--error-color);
font-weight: 500;
border-color: color-mix(in srgb, var(--error-color) 50%, transparent);
background-color: color-mix(in srgb, var(--error-color) 10%, var(--input-bg));
}
#image-preview-container {
margin-top: 15px;
text-align: center;
}
#image-preview {
max-width: 100%;
max-height: 220px;
border-radius: 12px;
margin-top: 10px;
border: 1px solid var(--border-color-opaque);
display: none;
background-color: var(--input-bg); /* Placeholder bg */
}
@keyframes fadePulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
@media (max-width: 620px) {
body {
padding: 15px env(safe-area-inset-top) 15px env(safe-area-inset-bottom);
align-items: flex-start;
}
.container {
padding: 20px 20px 25px 20px;
margin-top: 15px;
border-radius: 20px;
width: calc(100% - 30px);
}
h1 {
font-size: 28px;
}
p.subtitle {
font-size: 16px;
margin-bottom: 25px;
}
.form-group {
margin-bottom: 22px;
}
input[type="file"] {
padding: 12px 15px;
}
input[type="file"]::file-selector-button {
font-size: 15px;
margin-right: 10px;
}
.language-options {
gap: 8px;
justify-content: space-evenly;
}
.language-options label {
padding: 9px 16px;
font-size: 13px;
}
button#generate-button {
padding: 15px;
font-size: 16px;
}
#output-container {
padding: 15px 18px;
font-size: 14px;
min-height: 100px;
}
.output-section {
margin-top: 30px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>EVA</h1>
<p class="subtitle">Генератор рекламных постов</p>
<form id="generate-form">
<div class="form-group">
<label class="input-label">Выберите язык поста</label>
<div class="language-options">
<input type="radio" id="lang-ru" name="language" value="Русский" checked>
<label for="lang-ru">Русский</label>
<input type="radio" id="lang-kg" name="language" value="Кыргызский">
<label for="lang-kg">Кыргызский</label>
<input type="radio" id="lang-kz" name="language" value="Казахский">
<label for="lang-kz">Казахский</label>
<input type="radio" id="lang-uz" name="language" value="Узбекский">
<label for="lang-uz">Узбекский</label>
</div>
</div>
<div class="form-group">
<label for="image-upload" class="input-label">Загрузить изображение</label>
<input type="file" id="image-upload" name="image" accept="image/*" required>
<div id="image-preview-container">
<img id="image-preview" src="#" alt="Предпросмотр изображения"/>
</div>
</div>
<button type="submit" id="generate-button">Пуск</button>
</form>
<div class="output-section">
<div class="output-header">
<label id="output-label">Результат</label>
<button id="copy-button">Копировать</button>
</div>
<div id="output-container" aria-live="polite">
</div>
</div>
</div>
<script>
const form = document.getElementById('generate-form');
const imageInput = document.getElementById('image-upload');
const imagePreview = document.getElementById('image-preview');
const outputContainer = document.getElementById('output-container');
const generateButton = document.getElementById('generate-button');
const imagePreviewContainer = document.getElementById('image-preview-container');
const copyButton = document.getElementById('copy-button');
imageInput.addEventListener('change', function(event) {
const file = event.target.files[0];
if (file && file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = function(e) {
imagePreview.src = e.target.result;
imagePreview.style.display = 'block';
}
reader.readAsDataURL(file);
} else {
imagePreview.src = '#';
imagePreview.style.display = 'none';
}
});
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (!imageInput.files || imageInput.files.length === 0) {
showError("Пожалуйста, загрузите изображение.");
return;
}
const formData = new FormData(form);
generateButton.disabled = true;
generateButton.textContent = 'Генерация...';
outputContainer.innerHTML = '';
outputContainer.classList.add('loading');
outputContainer.classList.remove('error');
copyButton.style.display = 'none';
copyButton.textContent = 'Копировать';
copyButton.classList.remove('copied');
try {
const response = await fetch('/generate', {
method: 'POST',
body: formData
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || `Ошибка сервера: ${response.status}`);
}
outputContainer.textContent = result.text;
if (result.text && result.text.trim().length > 0) {
copyButton.style.display = 'block';
} else {
copyButton.style.display = 'none';
}
} catch (error) {
console.error("Fetch Error:", error);
showError(`Ошибка: ${error.message}`);
copyButton.style.display = 'none';
} finally {
generateButton.disabled = false;
generateButton.textContent = 'Пуск';
outputContainer.classList.remove('loading');
}
});
copyButton.addEventListener('click', () => {
const textToCopy = outputContainer.textContent;
if (!textToCopy) return;
navigator.clipboard.writeText(textToCopy).then(() => {
copyButton.textContent = 'Скопировано!';
copyButton.classList.add('copied');
setTimeout(() => {
copyButton.textContent = 'Копировать';
copyButton.classList.remove('copied');
}, 1500);
}).catch(err => {
console.error('Ошибка копирования: ', err);
copyButton.textContent = 'Ошибка';
setTimeout(() => {
copyButton.textContent = 'Копировать';
}, 1500);
});
});
function showError(message) {
outputContainer.textContent = message;
outputContainer.classList.add('error');
outputContainer.classList.remove('loading');
copyButton.style.display = 'none';
}
</script>
</body>
</html>
"""
def generate_ai_content(image_data, language):
try:
genai.configure(api_key=API_KEY_INTERNAL)
except Exception as e:
print(f"Error configuring GenAI: {e}")
raise ValueError(f"Не удалось настроить Google AI. Проблема с конфигурацией. Ошибка: {e}")
try:
if not image_data:
raise ValueError("Файл изображения не найден.")
image_stream = io.BytesIO(image_data)
image = Image.open(image_stream).convert('RGB')
except Exception as e:
print(f"Error processing image: {e}")
raise ValueError(f"Не удалось обработать изображение. Убедитесь, что это действительный файл изображения. Ошибка: {e}")
base_prompt = "Напиши большой и красивый, содержательный рекламный пост минимум на 1000 символов со смайликами и 25 тематических хэштегов с ключевыми словами разных вариантов, чтобы мои клиенты могли найти меня в поиске Instagram, Google и т.д. по ключевым словам. Пост пиши исключительно под товар, который на фото, без адресов и номеров телефона."
lang_suffix = ""
if language == "Русский":
lang_suffix = " Пиши на русском языке."
elif language == "Кыргызский":
lang_suffix = " Пиши на кыргызском языке."
elif language == "Казахский":
lang_suffix = " Пиши на казахском языке."
elif language == "Узбекский":
lang_suffix = " Пиши на узбекском языке."
final_prompt = f"{base_prompt}{lang_suffix}"
try:
model = genai.GenerativeModel('learnlm-2.0-flash-experimental')
response = model.generate_content([final_prompt, image])
if hasattr(response, 'text'):
return response.text
else:
if response.parts:
return "".join(part.text for part in response.parts if hasattr(part, 'text'))
else:
print("Warning: Unexpected response structure:", response)
response.resolve()
return response.text
except Exception as e:
print(f"Error generating content with GenAI: {e}")
if "API key not valid" in str(e):
raise ValueError("Внутренняя ошибка конфигурации API.")
elif " Billing account not found" in str(e):
raise ValueError("Проблема с биллингом аккаунта Google Cloud.")
elif "Could not find model" in str(e):
raise ValueError(f"Модель 'gemini-1.5-flash' не найдена или недоступна.")
elif "resource has been exhausted" in str(e).lower():
raise ValueError("Квота запросов исчерпана. Попробуйте позже.")
elif "content has been blocked" in str(e).lower() or (hasattr(response, 'prompt_feedback') and response.prompt_feedback.block_reason):
reason = response.prompt_feedback.block_reason if hasattr(response, 'prompt_feedback') else "неизвестна"
raise ValueError(f"Генерация контента заблокирована из-за политики безопасности (причина: {reason}). Попробуйте другое изображение или запрос.")
else:
raise ValueError(f"Ошибка при генерации контента: {e}")
@app.route('/')
def index():
return Response(html_template, mimetype='text/html')
@app.route('/generate', methods=['POST'])
def handle_generate():
if 'image' not in request.files:
return jsonify({"error": "Изображение не найдено в запросе."}), 400
if 'language' not in request.form:
return jsonify({"error": "Язык не найден в запросе."}), 400
image_file = request.files['image']
language = request.form['language']
image_data = image_file.read()
if not image_data:
return jsonify({"error": "Загруженный файл изображения пуст."}), 400
try:
result_text = generate_ai_content(image_data, language)
return jsonify({"text": result_text})
except ValueError as ve:
return jsonify({"error": str(ve)}), 400
except Exception as e:
print(f"Unexpected error during generation: {e}")
return jsonify({"error": f"Внутренняя ошибка сервера: {e}"}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860, debug=False)