Svetikos commited on
Commit
f7b7a35
·
verified ·
1 Parent(s): 2af21c0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -87
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, system_prompt: str, model_name: str, temperature: float, session: httpx.AsyncClient):
39
  """Асинхронно генерирует один вариант ответа."""
40
  try:
41
- model = genai.GenerativeModel(
42
- model_name=model_name,
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, system_prompt: str, num_variants: int, model_name: str, temperature: float):
54
- """Асинхронно генерирует несколько вариантов, используя httpx для параллельных запросов."""
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
- generate_single_variant_async(prompt, system_prompt, model_name, temperature, session)
 
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
- # Обновляем UI после получения каждого ответа
72
- yield format_variants_html(results, len(tasks)), gr.update(
73
  visible=True,
74
- value=f"<p style='color: #4A90E2; text-align: center;'>Генерация... ({len(results)}/{len(tasks)})</p>"
75
  )
76
 
77
- yield format_variants_html(results, len(tasks)), gr.update(visible=False)
78
 
79
  # --- 3. Вспомогательные функции для UI и форматирования ---
80
- def format_variants_html(variants: List[str], total: int) -> str:
81
- """Форматирует список вариантов в красивый HTML с кнопками 'Копировать'."""
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
- initial_loading_text = "<p style='color: #4A90E2; text-align: center;'>Отправка запроса...</p>"
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
- error_html = format_variants_html(['<div class="error-message">**Ошибка:** Нет предыдущего запроса для регенерации.</div>'], 1)
122
- return gr.update(visible=False), "", error_html
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, system_prompt, num_variants, model, temperature, current_output):
127
- """Основной асинхронный поток генерации, который обновляет UI."""
128
- # Сохраняем предыдущие ответы
129
- previous_responses_html = current_output if current_output else ""
130
 
131
- # Генератор будет обновлять UI по мере поступления ответов
132
- async for partial_result, loading_update in generate_content_with_variants_async(prompt, system_prompt, num_variants, model, temperature):
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 для профессионального вида (рекомендуется вынести в style.css) ---
141
  custom_css = """
142
- /* Основная палитра */
143
  :root {
144
- --primary-color: #4A90E2; --primary-color-hover: #357ABD;
145
- --secondary-color: #7B8B9A; --secondary-color-hover: #5F6C7A;
146
- --danger-color: #E57373; --danger-color-hover: #D32F2F;
147
- --app-bg-color: #F9FAFB; --input-bg-color: #FFFFFF;
148
- --border-color: #D1D5DB; --text-color-primary: #111827;
149
- --text-color-secondary: #6B7280; --label-color: #374151;
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.08) !important; transform: translateY(-2px); }
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: white !important; }
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.05) !important; }
165
- .input-container:focus-within, .input-container .wrap:focus-within { border-color: var(--primary-color) !important; box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.15) !important; }
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; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1) !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; display: flex; align-items: center; justify-content: center; }
184
- .copy-button:hover { background: #e5e7eb; }
185
  .copy-button svg { stroke: var(--secondary-color); }
186
- .error-message { background-color: #FFF1F2; color: #D32F2F; border: 1px solid #E57373; border-radius: 8px; padding: 15px; }
187
  """
188
 
189
  # --- 5. Создание интерфейса Gradio ---
190
- with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as interface: # или css="style.css"
191
- gr.Markdown("# Gemini AI Генератор Контента (Pro)")
192
- gr.Markdown("Задайте роль для AI, выберите модель, настройте параметры и введите запрос.")
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="Низкое значение = более предсказуемый ответ, высокое = более творческий.", elem_classes="num-variants-slider-container")
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() # Используем HTML для рендеринга кнопок 'Копировать'
217
 
218
  # --- 6. Логика обработчиков событий ---
219
- inputs_for_generation = [last_prompt_state, system_prompt_input, num_variants_slider, model_selector, temperature_slider, output_area]
 
220
 
221
- submit_event = submit_button.click(
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
- regenerate_event = regenerate_button.click(
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],