Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -16,7 +16,6 @@ except Exception as e:
|
|
| 16 |
raise gr.Error(f"Ошибка при настройке Google Gemini API: {e}.")
|
| 17 |
|
| 18 |
def get_available_models() -> List[str]:
|
| 19 |
-
# ... (код остается без изменений) ...
|
| 20 |
available_models = []
|
| 21 |
try:
|
| 22 |
for m in genai.list_models():
|
|
@@ -32,16 +31,14 @@ if not AVAILABLE_MODELS:
|
|
| 32 |
raise gr.Error("Не найдено моделей, совместимых с 'generateContent'.")
|
| 33 |
|
| 34 |
# --- Константы для настройки ---
|
| 35 |
-
DEFAULT_NUM_AI_VARIANTS = 3
|
| 36 |
|
| 37 |
# --- 2. Улучшенная асинхронная функция для взаимодействия с Gemini API ---
|
| 38 |
-
async def generate_single_variant_async(prompt: str,
|
| 39 |
"""Асинхронно генерирует один вариант ответа."""
|
| 40 |
try:
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
system_instruction=system_prompt if system_prompt else None
|
| 44 |
-
)
|
| 45 |
response = await model.generate_content_async(
|
| 46 |
contents=[prompt],
|
| 47 |
generation_config=genai.types.GenerationConfig(temperature=temperature)
|
|
@@ -50,17 +47,18 @@ async def generate_single_variant_async(prompt: str, system_prompt: str, model_n
|
|
| 50 |
except Exception as e:
|
| 51 |
return f'<div class="error-message">**Ошибка генерации:**<br>{e}</div>'
|
| 52 |
|
| 53 |
-
async def generate_content_with_variants_async(prompt: str,
|
| 54 |
-
"""Асинхронно генерирует несколько
|
| 55 |
if not prompt:
|
| 56 |
yield '<div class="error-message">**Ошибка:** Пожалуйста, введите запрос.</div>', gr.update(visible=False)
|
| 57 |
return
|
| 58 |
|
| 59 |
num_variants = max(1, num_variants)
|
| 60 |
|
| 61 |
-
async with httpx.AsyncClient(timeout=120.0) as session:
|
| 62 |
tasks = [
|
| 63 |
-
|
|
|
|
| 64 |
for _ in range(num_variants)
|
| 65 |
]
|
| 66 |
|
|
@@ -68,140 +66,113 @@ async def generate_content_with_variants_async(prompt: str, system_prompt: str,
|
|
| 68 |
for i, future in enumerate(asyncio.as_completed(tasks)):
|
| 69 |
result = await future
|
| 70 |
results.append(result)
|
| 71 |
-
|
| 72 |
-
yield format_variants_html(results, len(tasks)), gr.update(
|
| 73 |
visible=True,
|
| 74 |
-
value=f"<p style='color: #
|
| 75 |
)
|
| 76 |
|
| 77 |
-
yield format_variants_html(results
|
| 78 |
|
| 79 |
# --- 3. Вспомогательные функции для UI и форматирования ---
|
| 80 |
-
def format_variants_html(variants: List[str]
|
| 81 |
-
"""Форматирует список вариантов в
|
| 82 |
-
if not variants:
|
| 83 |
-
return ""
|
| 84 |
|
| 85 |
html_outputs = []
|
| 86 |
for i, variant_text in enumerate(variants):
|
| 87 |
-
# Уникальный ID для каждого текстового блока
|
| 88 |
-
text_id = f"response-text-{i}-{int(datetime.now().timestamp())}"
|
| 89 |
-
|
| 90 |
-
# Экранируем обратные кавычки, которые могут сломать JavaScript
|
| 91 |
js_safe_text = variant_text.replace('`', '\\`').replace('\n', '\\n').replace("'", "\\'")
|
|
|
|
| 92 |
|
| 93 |
-
copy_button_html = f"""
|
| 94 |
-
<button onclick="navigator.clipboard.writeText(`{js_safe_text}`)" class="copy-button" title="Копировать текст">
|
| 95 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
| 96 |
-
</button>
|
| 97 |
-
"""
|
| 98 |
-
|
| 99 |
-
# Оборачиваем ошибку в специальный блок без кнопки "Копировать"
|
| 100 |
if variant_text.startswith('<div class="error-message">'):
|
| 101 |
html_outputs.append(f'<div class="variant-container">{variant_text}</div>')
|
| 102 |
else:
|
| 103 |
-
html_outputs.append(f"""
|
| 104 |
-
<div class="variant-container">
|
| 105 |
-
<div class="variant-header">
|
| 106 |
-
<strong>Вариант {i + 1}</strong>
|
| 107 |
-
{copy_button_html}
|
| 108 |
-
</div>
|
| 109 |
-
<div id="{text_id}" class="variant-text">{variant_text}</div>
|
| 110 |
-
</div>
|
| 111 |
-
""")
|
| 112 |
|
| 113 |
return "".join(html_outputs)
|
| 114 |
|
| 115 |
def start_generation_flow_submit(prompt_input_value):
|
| 116 |
-
|
| 117 |
-
return gr.update(visible=True, value=initial_loading_text), prompt_input_value, ""
|
| 118 |
|
| 119 |
def start_generation_flow_regenerate(last_prompt):
|
| 120 |
if not last_prompt:
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
initial_loading_text = "<p style='color: #4A90E2; text-align: center;'>Повторная отправка запроса...</p>"
|
| 124 |
-
return gr.update(visible=True, value=initial_loading_text), last_prompt, ""
|
| 125 |
|
| 126 |
-
async def generate_and_finish_flow(prompt,
|
| 127 |
-
"""Основной асинхронный поток
|
| 128 |
-
|
| 129 |
-
previous_responses_html = current_output if current_output else ""
|
| 130 |
|
| 131 |
-
#
|
| 132 |
-
async for partial_result, loading_update in generate_content_with_variants_async(prompt,
|
| 133 |
-
# Добавляем новые ответы под старыми
|
| 134 |
full_html = f'{previous_responses_html}<hr class="response-divider">{partial_result}'
|
| 135 |
yield full_html, loading_update
|
| 136 |
|
| 137 |
def clear_chat():
|
| 138 |
return "", "", gr.update(visible=False)
|
| 139 |
|
| 140 |
-
# --- 4. CSS для
|
| 141 |
custom_css = """
|
| 142 |
-
/*
|
| 143 |
:root {
|
| 144 |
-
--primary-color: #
|
| 145 |
-
--secondary-color: #
|
| 146 |
-
--danger-color: #
|
| 147 |
-
--app-bg-color: #
|
| 148 |
-
--border-color: #
|
| 149 |
-
--text-color-secondary: #
|
| 150 |
}
|
| 151 |
.gradio-container { background-color: var(--app-bg-color) !important; }
|
| 152 |
|
| 153 |
/* Стили для кнопок */
|
| 154 |
.custom-button button { border-radius: 8px !important; font-weight: 600 !important; transition: all 0.2s ease-in-out !important; padding: 10px !important; }
|
| 155 |
.submit-button button { background: var(--primary-color) !important; color: white !important; border: 1px solid var(--primary-color) !important; }
|
| 156 |
-
.submit-button button:hover { background: var(--primary-color-hover) !important; border-color: var(--primary-color-hover) !important; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.
|
| 157 |
.regenerate-button button, .clear-button button { background: transparent !important; border: 1px solid var(--border-color) !important; }
|
| 158 |
.regenerate-button button { color: var(--secondary-color) !important; }
|
| 159 |
.clear-button button { color: var(--danger-color) !important; }
|
| 160 |
-
.regenerate-button button:hover { background: var(--secondary-color) !important; border-color: var(--secondary-color) !important; color:
|
| 161 |
.clear-button button:hover { background: var(--danger-color) !important; border-color: var(--danger-color) !important; color: white !important; }
|
| 162 |
|
| 163 |
/* Стили для полей ввода */
|
| 164 |
-
.input-container, .input-container .wrap { background-color: var(--input-bg-color) !important; border: 1px solid var(--border-color) !important; border-radius: 8px !important; transition: border-color 0.2s, box-shadow 0.2s !important; box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.
|
| 165 |
-
.input-container:focus-within, .input-container .wrap:focus-within { border-color: var(--primary-color) !important; box-shadow: 0 0 0 3px rgba(
|
| 166 |
-
.input-container textarea, .input-container .gr-form-component { border: none !important; background: transparent !important; }
|
|
|
|
| 167 |
|
| 168 |
/* Стили для выпадающего списка */
|
| 169 |
-
.gradio-container .input-container .options { background-color: var(--input-bg-color) !important; border: 1px solid var(--border-color) !important;
|
| 170 |
.gradio-container .input-container .option-item:hover, .gradio-container .input-container .option-item.selected { background-color: var(--primary-color) !important; color: white !important; }
|
| 171 |
|
| 172 |
/* Стили для слайдера */
|
| 173 |
.num-variants-slider-container { padding: 0 10px !important; }
|
| 174 |
.num-variants-slider-container .gradio-slider > input[type=range] { background-color: var(--primary-color) !important; }
|
| 175 |
|
| 176 |
-
/* Стили для заголовков (labels) */
|
| 177 |
-
.gradio-container label { font-weight: 600 !important; color: var(--label-color) !important; }
|
|
|
|
| 178 |
|
| 179 |
-
/*
|
| 180 |
.response-divider { border: none; border-top: 1px solid var(--border-color); margin: 20px 0; }
|
| 181 |
-
.variant-container { background: var(--input-bg-color); border: 1px solid var(--border-color); border-radius: 8px; margin-bottom: 15px; padding: 15px; }
|
| 182 |
-
.variant-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
| 183 |
-
.copy-button { background: transparent; border: 1px solid var(--border-color); border-radius: 5px; cursor: pointer; padding: 5px;
|
| 184 |
-
.copy-button:hover { background: #
|
| 185 |
.copy-button svg { stroke: var(--secondary-color); }
|
| 186 |
-
.error-message { background-color: #
|
| 187 |
"""
|
| 188 |
|
| 189 |
# --- 5. Создание интерфейса Gradio ---
|
| 190 |
-
with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as interface:
|
| 191 |
-
gr.Markdown("# Gemini AI Генератор Контента
|
| 192 |
-
gr.Markdown("
|
| 193 |
|
| 194 |
-
# Состояние для хранения последнего запроса (для регенерации)
|
| 195 |
last_prompt_state = gr.State("")
|
| 196 |
|
| 197 |
with gr.Row():
|
| 198 |
model_selector = gr.Dropdown(choices=AVAILABLE_MODELS, value=AVAILABLE_MODELS[0], label="Выберите модель", interactive=True, elem_classes="input-container")
|
| 199 |
-
|
| 200 |
-
# Новые поля для системной инструкции и температуры
|
| 201 |
-
with gr.Row():
|
| 202 |
-
system_prompt_input = gr.Textbox(label="Системная инструкция (необязательно)", placeholder="Например: Ты — дружелюбный ассистент, который объясняет сложные темы простыми словами.", elem_classes="input-container")
|
| 203 |
with gr.Row():
|
| 204 |
-
temperature_slider = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.7, label="Температура (креативность)", info="Низкое
|
| 205 |
with gr.Row():
|
| 206 |
num_variants_slider = gr.Slider(minimum=1, maximum=5, step=1, value=DEFAULT_NUM_AI_VARIANTS, label="Количество вариантов ответа", info="Выберите, сколько различных ответов AI должен сгенерировать.", elem_classes="num-variants-slider-container")
|
| 207 |
|
|
@@ -213,12 +184,13 @@ with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as interface: # или cs
|
|
| 213 |
clear_button = gr.Button("🗑️ Сброс", variant="stop", elem_classes=["custom-button", "clear-button"])
|
| 214 |
|
| 215 |
loading_message = gr.Markdown(visible=False)
|
| 216 |
-
output_area = gr.HTML()
|
| 217 |
|
| 218 |
# --- 6. Логика обработчиков событий ---
|
| 219 |
-
|
|
|
|
| 220 |
|
| 221 |
-
|
| 222 |
fn=start_generation_flow_submit,
|
| 223 |
inputs=[prompt_input],
|
| 224 |
outputs=[loading_message, last_prompt_state, prompt_input],
|
|
@@ -240,7 +212,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as interface: # или cs
|
|
| 240 |
outputs=[output_area, loading_message]
|
| 241 |
)
|
| 242 |
|
| 243 |
-
|
| 244 |
fn=start_generation_flow_regenerate,
|
| 245 |
inputs=[last_prompt_state],
|
| 246 |
outputs=[loading_message, last_prompt_state, output_area],
|
|
|
|
| 16 |
raise gr.Error(f"Ошибка при настройке Google Gemini API: {e}.")
|
| 17 |
|
| 18 |
def get_available_models() -> List[str]:
|
|
|
|
| 19 |
available_models = []
|
| 20 |
try:
|
| 21 |
for m in genai.list_models():
|
|
|
|
| 31 |
raise gr.Error("Не найдено моделей, совместимых с 'generateContent'.")
|
| 32 |
|
| 33 |
# --- Константы для настройки ---
|
| 34 |
+
DEFAULT_NUM_AI_VARIANTS = 3
|
| 35 |
|
| 36 |
# --- 2. Улучшенная асинхронная функция для взаимодействия с Gemini API ---
|
| 37 |
+
async def generate_single_variant_async(prompt: str, model_name: str, temperature: float, session: httpx.AsyncClient):
|
| 38 |
"""Асинхронно генерирует один вариант ответа."""
|
| 39 |
try:
|
| 40 |
+
# <--- УДАЛЕНО: system_instruction из вызова модели
|
| 41 |
+
model = genai.GenerativeModel(model_name=model_name)
|
|
|
|
|
|
|
| 42 |
response = await model.generate_content_async(
|
| 43 |
contents=[prompt],
|
| 44 |
generation_config=genai.types.GenerationConfig(temperature=temperature)
|
|
|
|
| 47 |
except Exception as e:
|
| 48 |
return f'<div class="error-message">**Ошибка генерации:**<br>{e}</div>'
|
| 49 |
|
| 50 |
+
async def generate_content_with_variants_async(prompt: str, num_variants: int, model_name: str, temperature: float):
|
| 51 |
+
"""Асинхронно генерирует несколько вариантов."""
|
| 52 |
if not prompt:
|
| 53 |
yield '<div class="error-message">**Ошибка:** Пожалуйста, введите запрос.</div>', gr.update(visible=False)
|
| 54 |
return
|
| 55 |
|
| 56 |
num_variants = max(1, num_variants)
|
| 57 |
|
| 58 |
+
async with httpx.AsyncClient(timeout=120.0) as session:
|
| 59 |
tasks = [
|
| 60 |
+
# <--- УДАЛЕНО: system_prompt из вызова функции
|
| 61 |
+
generate_single_variant_async(prompt, model_name, temperature, session)
|
| 62 |
for _ in range(num_variants)
|
| 63 |
]
|
| 64 |
|
|
|
|
| 66 |
for i, future in enumerate(asyncio.as_completed(tasks)):
|
| 67 |
result = await future
|
| 68 |
results.append(result)
|
| 69 |
+
yield format_variants_html(results), gr.update(
|
|
|
|
| 70 |
visible=True,
|
| 71 |
+
value=f"<p style='color: #60A5FA; text-align: center;'>Генерация... ({len(results)}/{num_variants})</p>"
|
| 72 |
)
|
| 73 |
|
| 74 |
+
yield format_variants_html(results), gr.update(visible=False)
|
| 75 |
|
| 76 |
# --- 3. Вспомогательные функции для UI и форматирования ---
|
| 77 |
+
def format_variants_html(variants: List[str]) -> str:
|
| 78 |
+
"""Форматирует список вариантов в HTML с кнопками 'Копировать'."""
|
| 79 |
+
if not variants: return ""
|
|
|
|
| 80 |
|
| 81 |
html_outputs = []
|
| 82 |
for i, variant_text in enumerate(variants):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
js_safe_text = variant_text.replace('`', '\\`').replace('\n', '\\n').replace("'", "\\'")
|
| 84 |
+
copy_button_html = f"""<button onclick="navigator.clipboard.writeText(`{js_safe_text}`)" class="copy-button" title="Копировать текст"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>"""
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
if variant_text.startswith('<div class="error-message">'):
|
| 87 |
html_outputs.append(f'<div class="variant-container">{variant_text}</div>')
|
| 88 |
else:
|
| 89 |
+
html_outputs.append(f"""<div class="variant-container"><div class="variant-header"><strong>Вариант {i + 1}</strong>{copy_button_html}</div><div class="variant-text">{variant_text}</div></div>""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
return "".join(html_outputs)
|
| 92 |
|
| 93 |
def start_generation_flow_submit(prompt_input_value):
|
| 94 |
+
return gr.update(visible=True, value="<p style='color: #60A5FA; text-align: center;'>Отправка запроса...</p>"), prompt_input_value, ""
|
|
|
|
| 95 |
|
| 96 |
def start_generation_flow_regenerate(last_prompt):
|
| 97 |
if not last_prompt:
|
| 98 |
+
return gr.update(visible=False), "", format_variants_html(['<div class="error-message">**Ошибка:** Нет предыдущего запроса для регенерации.</div>'])
|
| 99 |
+
return gr.update(visible=True, value="<p style='color: #60A5FA; text-align: center;'>Повторная отправка запроса...</p>"), last_prompt, ""
|
|
|
|
|
|
|
| 100 |
|
| 101 |
+
async def generate_and_finish_flow(prompt, num_variants, model, temperature, current_output):
|
| 102 |
+
"""Основной асинхронный поток генерации."""
|
| 103 |
+
previous_responses_html = current_output or ""
|
|
|
|
| 104 |
|
| 105 |
+
# <--- УДАЛЕНО: system_prompt из вызова функции
|
| 106 |
+
async for partial_result, loading_update in generate_content_with_variants_async(prompt, num_variants, model, temperature):
|
|
|
|
| 107 |
full_html = f'{previous_responses_html}<hr class="response-divider">{partial_result}'
|
| 108 |
yield full_html, loading_update
|
| 109 |
|
| 110 |
def clear_chat():
|
| 111 |
return "", "", gr.update(visible=False)
|
| 112 |
|
| 113 |
+
# --- 4. CSS для темной темы "Глубокий Космос" (рекомендуется вынести в style.css) ---
|
| 114 |
custom_css = """
|
| 115 |
+
/* Цветовая палитра "Глубокий Космос" */
|
| 116 |
:root {
|
| 117 |
+
--primary-color: #3B82F6; --primary-color-hover: #60A5FA;
|
| 118 |
+
--secondary-color: #9CA3AF; --secondary-color-hover: #E5E7EB;
|
| 119 |
+
--danger-color: #F87171; --danger-color-hover: #EF4444;
|
| 120 |
+
--app-bg-color: #111827; --input-bg-color: #1F2937;
|
| 121 |
+
--border-color: #4B5563; --text-color-primary: #F3F4F6;
|
| 122 |
+
--text-color-secondary: #9CA3AF; --label-color: #E5E7EB;
|
| 123 |
}
|
| 124 |
.gradio-container { background-color: var(--app-bg-color) !important; }
|
| 125 |
|
| 126 |
/* Стили для кнопок */
|
| 127 |
.custom-button button { border-radius: 8px !important; font-weight: 600 !important; transition: all 0.2s ease-in-out !important; padding: 10px !important; }
|
| 128 |
.submit-button button { background: var(--primary-color) !important; color: white !important; border: 1px solid var(--primary-color) !important; }
|
| 129 |
+
.submit-button button:hover { background: var(--primary-color-hover) !important; border-color: var(--primary-color-hover) !important; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2) !important; transform: translateY(-2px); }
|
| 130 |
.regenerate-button button, .clear-button button { background: transparent !important; border: 1px solid var(--border-color) !important; }
|
| 131 |
.regenerate-button button { color: var(--secondary-color) !important; }
|
| 132 |
.clear-button button { color: var(--danger-color) !important; }
|
| 133 |
+
.regenerate-button button:hover { background: var(--secondary-color) !important; border-color: var(--secondary-color) !important; color: var(--app-bg-color) !important; }
|
| 134 |
.clear-button button:hover { background: var(--danger-color) !important; border-color: var(--danger-color) !important; color: white !important; }
|
| 135 |
|
| 136 |
/* Стили для полей ввода */
|
| 137 |
+
.input-container, .input-container .wrap { background-color: var(--input-bg-color) !important; border: 1px solid var(--border-color) !important; border-radius: 8px !important; transition: border-color 0.2s, box-shadow 0.2s !important; box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1) !important; }
|
| 138 |
+
.input-container:focus-within, .input-container .wrap:focus-within { border-color: var(--primary-color) !important; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2) !important; }
|
| 139 |
+
.input-container textarea, .input-container .gr-form-component { border: none !important; background: transparent !important; color: var(--text-color-primary) !important; }
|
| 140 |
+
.input-container textarea::placeholder { color: var(--text-color-secondary); }
|
| 141 |
|
| 142 |
/* Стили для выпадающего списка */
|
| 143 |
+
.gradio-container .input-container .options { background-color: var(--input-bg-color) !important; border: 1px solid var(--border-color) !important; }
|
| 144 |
.gradio-container .input-container .option-item:hover, .gradio-container .input-container .option-item.selected { background-color: var(--primary-color) !important; color: white !important; }
|
| 145 |
|
| 146 |
/* Стили для слайдера */
|
| 147 |
.num-variants-slider-container { padding: 0 10px !important; }
|
| 148 |
.num-variants-slider-container .gradio-slider > input[type=range] { background-color: var(--primary-color) !important; }
|
| 149 |
|
| 150 |
+
/* Стили для заголовков (labels) и текста */
|
| 151 |
+
.gradio-container label, .gradio-container .gr-info { font-weight: 600 !important; color: var(--label-color) !important; }
|
| 152 |
+
.gradio-container h1, .gradio-container .gr-markdown p { color: var(--text-color-primary); }
|
| 153 |
|
| 154 |
+
/* Стили для ответов AI */
|
| 155 |
.response-divider { border: none; border-top: 1px solid var(--border-color); margin: 20px 0; }
|
| 156 |
+
.variant-container { background: var(--input-bg-color); border: 1px solid var(--border-color); border-radius: 8px; margin-bottom: 15px; padding: 15px; color: var(--text-color-primary); }
|
| 157 |
+
.variant-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; color: var(--label-color); }
|
| 158 |
+
.copy-button { background: transparent; border: 1px solid var(--border-color); border-radius: 5px; cursor: pointer; padding: 5px; }
|
| 159 |
+
.copy-button:hover { background: #374151; }
|
| 160 |
.copy-button svg { stroke: var(--secondary-color); }
|
| 161 |
+
.error-message { background-color: #450A0A; color: #F87171; border: 1px solid #7F1D1D; border-radius: 8px; padding: 15px; }
|
| 162 |
"""
|
| 163 |
|
| 164 |
# --- 5. Создание интерфейса Gradio ---
|
| 165 |
+
with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as interface:
|
| 166 |
+
gr.Markdown("# Gemini AI Генератор Контента")
|
| 167 |
+
gr.Markdown("Выберите модель, настройте параметры и введите запрос.")
|
| 168 |
|
|
|
|
| 169 |
last_prompt_state = gr.State("")
|
| 170 |
|
| 171 |
with gr.Row():
|
| 172 |
model_selector = gr.Dropdown(choices=AVAILABLE_MODELS, value=AVAILABLE_MODELS[0], label="Выберите модель", interactive=True, elem_classes="input-container")
|
| 173 |
+
|
|
|
|
|
|
|
|
|
|
| 174 |
with gr.Row():
|
| 175 |
+
temperature_slider = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.7, label="Температура (креативность)", info="Низкое = предсказуемый ответ, высокое = творческий.", elem_classes="num-variants-slider-container")
|
| 176 |
with gr.Row():
|
| 177 |
num_variants_slider = gr.Slider(minimum=1, maximum=5, step=1, value=DEFAULT_NUM_AI_VARIANTS, label="Количество вариантов ответа", info="Выберите, сколько различных ответов AI должен сгенерировать.", elem_classes="num-variants-slider-container")
|
| 178 |
|
|
|
|
| 184 |
clear_button = gr.Button("🗑️ Сброс", variant="stop", elem_classes=["custom-button", "clear-button"])
|
| 185 |
|
| 186 |
loading_message = gr.Markdown(visible=False)
|
| 187 |
+
output_area = gr.HTML()
|
| 188 |
|
| 189 |
# --- 6. Логика обработчиков событий ---
|
| 190 |
+
# <--- УДАЛЕНО: system_prompt_input из списка входов
|
| 191 |
+
inputs_for_generation = [last_prompt_state, num_variants_slider, model_selector, temperature_slider, output_area]
|
| 192 |
|
| 193 |
+
submit_button.click(
|
| 194 |
fn=start_generation_flow_submit,
|
| 195 |
inputs=[prompt_input],
|
| 196 |
outputs=[loading_message, last_prompt_state, prompt_input],
|
|
|
|
| 212 |
outputs=[output_area, loading_message]
|
| 213 |
)
|
| 214 |
|
| 215 |
+
regenerate_button.click(
|
| 216 |
fn=start_generation_flow_regenerate,
|
| 217 |
inputs=[last_prompt_state],
|
| 218 |
outputs=[loading_message, last_prompt_state, output_area],
|