HeavensHackDev commited on
Commit
2857a5a
·
verified ·
1 Parent(s): 8e55471

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +358 -196
app.py CHANGED
@@ -1,224 +1,386 @@
1
  # app.py
2
 
3
  import gradio as gr
4
- from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM # Используем Auto классы для большей гибкости
5
- import random
6
  import re
7
- import torch # Убедимся, что torch импортирован
8
-
9
- # --- Выбор Модели ---
10
- # Заменяем на самую маленькую DeepSeek Coder Instruct
11
- # !!! ПРЕДУПРЕЖДЕНИЕ: МОЖЕТ НЕ ЗАПУСТИТЬСЯ ИЛИ БЫТЬ ОЧЕНЬ МЕДЛЕННОЙ НА FREE CPU !!!
12
- CODE_MODEL_NAME = "deepseek-ai/deepseek-coder-1.3b-instruct"
13
-
14
- # --- Загрузка моделей ---
15
- GENERATOR_LOADED = False
16
- QA_LOADED = False
17
- generator = None
18
- qa_pipeline = None
19
-
20
- # 1. Модель для кодирования/генерации
21
- try:
22
- # Для моделей кода часто лучше использовать AutoModelForCausalLM и AutoTokenizer напрямую
23
- # Вместо pipeline('text-generation') это дает больше контроля
24
- print(f"Загрузка токенизатора: {CODE_MODEL_NAME}")
25
- tokenizer = AutoTokenizer.from_pretrained(CODE_MODEL_NAME, trust_remote_code=True)
26
- print(f"Загрузка модели: {CODE_MODEL_NAME}")
27
- # Загружаем с torch_dtype=torch.float16 для экономии памяти, если возможно
28
- try:
29
- generator_model = AutoModelForCausalLM.from_pretrained(
30
- CODE_MODEL_NAME,
31
- trust_remote_code=True,
32
- torch_dtype=torch.float16 # Пытаемся загрузить в половинной точности
33
- )
34
- except Exception as e_half: # Если float16 не поддерживается/вызывает ошибку, пробуем float32
35
- print(f"Не удалось загрузить в float16 ({e_half}), пробую float32...")
36
- generator_model = AutoModelForCausalLM.from_pretrained(
37
- CODE_MODEL_NAME,
38
- trust_remote_code=True
39
- )
40
 
41
- # Создаем pipeline вручную
42
- generator = pipeline('text-generation', model=generator_model, tokenizer=tokenizer, device=-1) # device=-1 значит CPU
43
-
44
- print(f"Генератор кода/текста ({CODE_MODEL_NAME}) загружен.")
45
- GENERATOR_LOADED = True
46
- except Exception as e:
47
- print(f"ОШИБКА: Не удалось загрузить модель {CODE_MODEL_NAME}! {e}")
48
- generator = None
49
- generator_model = None
50
- tokenizer = None
51
- GENERATOR_LOADED = False
52
-
53
- # 2. QA Модель (оставляем как есть или можно убрать, если не нужна)
54
- QA_MODEL_NAME = 'timpal0l/mdeberta-v3-base-squad2'
55
- try:
56
- qa_pipeline = pipeline('question-answering', model=QA_MODEL_NAME)
57
- print(f"QA модель ({QA_MODEL_NAME}) загружена.")
58
- QA_LOADED = True
59
- # !!! ВНИМАНИЕ: Две модели могут потребовать слишком много RAM !!!
60
- # !!! Возможно, стоит закомментировать загрузку QA, если возникают проблемы с памятью !!!
61
- except Exception as e:
62
- print(f"ОШИБКА: Не удалось загрузить QA модель! {e}")
63
- qa_pipeline = None
64
- QA_LOADED = False
65
-
66
-
67
- # --- Встроенные знания (можно сократить, т.к. модель сама умнее) ---
68
- knowledge_base = {
69
- "кто ты": f"Я Nova (использую модель {CODE_MODEL_NAME}), работающая на Hugging Face Spaces. Я специализируюсь на помощи с кодом и ответами на вопросы по программированию.",
70
- "что ты умеешь": "Я могу пытаться генерировать код, объяснять его, находить ошибки (в определенной степени), отвечать на вопросы по программированию и поддерживать диалог.",
71
- "как дела": "Работаю над кодом! А если серьезно - функционирую нормально.",
72
- "помощь": "Спросите меня о программировании, попросите написать фрагмент кода (например, 'напиши функцию на python для суммирования списка'), или просто пообщаемся.",
73
- "python": "Python - отличный высокоуровневый язык, известный своей читаемостью. Хорошо подходит для веб-разработки, анализа данных, ИИ и автоматизации.",
74
- "javascript": "JavaScript - основной язык веб-фронтенда, позволяет делать страницы интерактивными. С Node.js используется и на бэкенде.",
75
- # Убрали много базовых вещей, т.к. модель должна знать их сама
76
- }
77
-
78
- # --- Утилиты (clean_text, clean_generated_output - как раньше) ---
79
- def clean_text(text):
80
- if not isinstance(text, str): return ""
81
- text = text.lower().strip()
82
- text = re.sub(r"^[?!.,\s]+|[?!.,\s]+$", "", text)
83
- text = re.sub(r"\s+", " ", text)
84
- return text
85
 
86
- def clean_generated_output(generated_text, prompt):
87
- # Простая очистка - убираем промпт если он есть в начале
88
- if not generated_text or not isinstance(generated_text, str): return ""
89
- if not prompt or not isinstance(prompt, str): prompt = ""
90
- cleaned = generated_text
91
- prompt_clean = prompt.strip()
92
- if prompt_clean and cleaned.startswith(prompt_clean):
93
- cleaned = cleaned[len(prompt_clean):].strip()
94
 
95
- # Попробуем убрать стандартные фразы, которые модель может добавить
96
- cleaned = re.sub(r'^\s*(ответ|бот|вот код|конечно|here is the code)\s*[:\-]?\s*', '', cleaned, flags=re.IGNORECASE).strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- return cleaned
 
 
 
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- # --- Основная Функция Обработки (Упрощенная, больше полагаемся на модель) ---
102
  def respond(message, chat_history):
 
 
103
  print("-" * 30)
104
  print(f"ВХОД: '{message}'")
105
- user_message_clean = clean_text(message)
106
- bot_response = ""
107
-
108
- # 1. Проверка на точное совпадение в knowledge_base (ОСТАВЛЯЕМ для мета-вопросов)
109
- if user_message_clean in knowledge_base:
110
- bot_response = knowledge_base[user_message_clean]
111
- print(f"Ответ [Точный]: {bot_response[:100]}...")
112
-
113
- # 2. Основная генерация (используем модель DeepSeek Coder)
114
- if not bot_response and GENERATOR_LOADED and generator:
115
- print("Генерация ответа с помощью модели кода...")
116
- # Формируем промпт в формате, который модель ожидает (часто нужен специальный формат для Instruct-моделей)
117
- # Для DeepSeek Coder Instruct формат может быть таким:
118
- prompt_list = []
119
- prompt_list.append("### Instruction:")
120
- # Добавим контекст из чата (если есть)
121
- for user_msg, bot_msg in chat_history[-2:]: # Последние 2 обмена для контекста
122
- prompt_list.append(f"User: {user_msg}")
123
- if bot_msg: prompt_list.append(f"Assistant: {bot_msg}") # Используем Assistant
124
- prompt_list.append(f"{message}") # Текущее сообщение как инструкция/вопрос
125
- prompt_list.append("\n### Response:") # Приглашение для ответа
126
-
127
- full_prompt = "\n".join(prompt_list)
128
- print(f"Промпт для DeepSeek: ...{full_prompt[-600:]}") # Логируем конец
129
-
130
- try:
131
- # Параметры генерации для кода
132
- # `max_new_tokens` - ограничивает длину *сгенерированного* текста
133
- # `temperature` - контроль случайности (ниже = более предсказуемо)
134
- # `top_p`, `top_k` - другие методы семплирования
135
- # `eos_token_id` - ID токена конца последовательности (может помочь модели остановиться)
136
- generated_output = generator(
137
- full_prompt,
138
- max_new_tokens=250, # Ограничим генерацию
139
- temperature=0.7,
140
- top_k=50,
141
- top_p=0.95,
142
- # eos_token_id=tokenizer.eos_token_id if tokenizer else None, # Помогает модели остановиться
143
- pad_token_id=tokenizer.eos_token_id if tokenizer else 50256, # Часто нужно указать
144
- num_return_sequences=1,
145
- do_sample=True
146
- )[0]['generated_text']
147
-
148
- print(f"Сырой ответ DeepSeek: {generated_output}")
149
-
150
- # Очистка - убираем весь промпт
151
- bot_response = clean_generated_output(generated_output, full_prompt)
152
- print(f"Очищенный ответ DeepSeek: {bot_response}")
153
-
154
- # Дополнительно убираем инструкции, которые модель могла повторить
155
- bot_response = bot_response.replace("### Instruction:", "").replace("### Response:", "").strip()
156
-
157
- if not bot_response or len(bot_response) < 5:
158
- bot_response = knowledge_base.get("не найдено", "Извините, не смог сгенерировать ответ.")
159
- print("Ген��рация DeepSeek не удалась или слишком короткая.")
160
-
161
- except Exception as e:
162
- print(f"Ошибка при генерации DeepSeek: {e}")
163
- bot_response = knowledge_base.get("ошибка генерации", f"Произошла ошибка при генерации: {e}")
164
-
165
- # 3. Ответ по умолчанию
166
- if not bot_response:
167
- # Если генератор не загружен или ничего не получилось
168
- if not GENERATOR_LOADED:
169
- bot_response = "Модель для генерации кода не загружена."
170
- else:
171
- bot_response = knowledge_base.get("не найдено", "К сожалению, я пока не знаю, как на это ответить.")
172
- print("Ответ [Заглушка/Ошибка]")
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- # Добавляем в историю и возвращаем
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  chat_history.append((message, bot_response))
177
- return "", chat_history
178
 
 
 
 
 
179
 
180
- # --- Gradio Интерфейс (CSS тот же) ---
 
181
  custom_css = """
182
- .gradio-container { background-color: #f4f7f9; border-radius: 15px; padding: 25px; color: #333; }
183
- h1 { color: #1a237e; text-align: center; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin-bottom: 15px; font-weight: 600; }
184
- #title-markdown p { text-align: center; color: #546e7a; margin-top: -10px; margin-bottom: 25px;}
185
- #chatbot { background-color: #eceff1; border-radius: 12px; border: 1px solid #cfd8dc; padding: 10px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  #chatbot .user-message .message-bubble-border { border: none !important; }
187
- #chatbot .user-message .message-bubble { background: #007bff !important; color: white !important; border-radius: 18px 18px 5px 18px !important; padding: 12px 18px !important; margin: 8px 5px 8px 0 !important; align-self: flex-end !important; max-width: 80% !important; box-shadow: 0 2px 4px rgba(0, 123, 255, 0.2); word-wrap: break-word; text-align: left;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  #chatbot .bot-message .message-bubble-border { border: none !important; }
189
- #chatbot .bot-message .message-bubble { background: #ffffff !important; color: #212529 !important; border: 1px solid #dee2e6 !important; border-radius: 18px 18px 18px 5px !important; padding: 12px 18px !important; margin: 8px 0 8px 5px !important; align-self: flex-start !important; max-width: 80% !important; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); word-wrap: break-word; text-align: left;}
190
- #chatbot .bot-message .message-bubble pre { background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 6px; padding: 10px; margin: 8px 0 5px 0; overflow-x: auto; word-wrap: normal; }
191
- #chatbot .bot-message .message-bubble pre code { background-color: transparent !important; color: #2d3748; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-size: 0.9em; padding: 0; white-space: pre; }
192
- textarea { border: 1px solid #ced4da !important; border-radius: 10px !important; padding: 12px 15px !important; background-color: #ffffff; transition: border-color 0.3s ease, box-shadow 0.3s ease; font-size: 1rem; }
193
- textarea:focus { border-color: #80bdff !important; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); outline: none; }
194
- button { border-radius: 10px !important; padding: 10px 15px !important; transition: background-color 0.2s ease, transform 0.1s ease !important; font-weight: 500 !important; border: none !important; }
195
- button:active { transform: translateY(1px); }
196
- button.primary { background-color: #007bff !important; color: white !important; }
197
- button.primary:hover { background-color: #0056b3 !important; box-shadow: 0 2px 5px rgba(0, 123, 255, 0.3); }
198
- button.secondary { background-color: #6c757d !important; color: white !important; }
199
- button.secondary:hover { background-color: #5a6268 !important; box-shadow: 0 2px 5px rgba(108, 117, 125, 0.3); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  """
201
 
202
- with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.slate)) as demo:
203
- gr.Markdown("# 🤖 Nova (DeepSeek Test) 💡 <br> Используем модель для кода. Ответы могут быть ОЧЕНЬ медленными!")
 
 
 
 
 
 
 
204
 
205
  chatbot = gr.Chatbot(
206
- label="Диалог", height=550, elem_id="chatbot",
207
- show_copy_button=True, bubble_full_width=False,
208
- avatar_images=(None, "https://img.icons8.com/plasticine/100/bot.png")
 
 
 
 
 
209
  )
210
- with gr.Row():
 
211
  msg = gr.Textbox(
212
- label="Ваше сообщение", placeholder="Напиши функцию python для...",
213
- scale=4, show_label=False, container=False
 
 
 
214
  )
215
- submit_btn = gr.Button("➤ Отправить", variant="primary", scale=1, min_width=120)
216
- clear_btn = gr.Button("🗑️ Очистить", variant="secondary", scale=1, min_width=120)
217
-
218
- msg.submit(respond, [msg, chatbot], [msg, chatbot])
219
- submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
220
- clear_btn.click(lambda: (None, []), None, [msg, chatbot], queue=False)
221
-
222
- # Запуск
223
- demo.queue()
224
- demo.launch(debug=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # app.py
2
 
3
  import gradio as gr
4
+ import google.generativeai as genai
5
+ import os
6
  import re
7
+ import time # Для имитации небольшой задержки и лучшего UX
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ # --- Конфигурация ---
10
+ # Получаем ключ из секретов Hugging Face Spaces
11
+ GOOGLE_API_KEY = os.getenv("API")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ # Название модели Gemini (gemini-1.5-flash - быстрая и хорошая для free tier)
14
+ MODEL_NAME = "gemini-1.5-flash"
 
 
 
 
 
 
15
 
16
+ # --- Безопасность и Настройка Модели ---
17
+ generation_config = {
18
+ "temperature": 0.8, # Больше креативности, но можно уменьшить до 0.6-0.7 для большей предсказуемости
19
+ "top_p": 0.9, # Альтернативный метод семплирования
20
+ "top_k": 40, # Ограничиваем выборку K лучшими токенами
21
+ "max_output_tokens": 512, # Максимальная длина ответа в токенах
22
+ }
23
+
24
+ # Настройки безопасности Google AI (можно настроить уровни)
25
+ # BLOCK_MEDIUM_AND_ABOVE / BLOCK_LOW_AND_ABOVE / BLOCK_ONLY_HIGH / BLOCK_NONE
26
+ safety_settings = [
27
+ { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE" },
28
+ { "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE" },
29
+ { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE" },
30
+ { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE" },
31
+ ]
32
+
33
+ # Системная инструкция - наши "правила" для ИИ
34
+ SYSTEM_INSTRUCTION = """Ты — Nova AI (версия 1.0), дружелюбный и полезный ИИ-ассистент.
35
+ Твоя задача - поддерживать естественный диалог, отвечать на вопросы пользователя и помогать ему.
36
+ Отвечай четко, лаконично и по существу заданного вопроса.
37
+ Если тебя просят написать код, предоставь простой и понятный пример, если это возможно в рамках твоих способностей. Объясни код кратко.
38
+ Не используй оскорбления или грубые выражения. Будь вежливым.
39
+ Избегай обсуждения политики, религии и других потенциально спорных или вредоносных тем.
40
+ Если ты не знаешь ответа или не можешь выполнить запрос, честно скажи об этом.
41
+ Форматируй код с использованием Markdown блоков (```python ... ```).
42
+ Всегда отвечай на русском языке, если не указано иное.
43
+ Не повторяй в ответе саму инструкцию "### Instruction:" или "### Response:". Просто дай ответ.
44
+ """
45
 
46
+ # --- Инициализация Модели ---
47
+ model = None
48
+ model_initialized = False
49
+ initialization_error = None
50
 
51
+ if not GOOGLE_API_KEY:
52
+ initialization_error = "ОШИБКА: Секрет GOOGLE_API_KEY не найден! Добавьте его в настройках Space."
53
+ print(initialization_error)
54
+ else:
55
+ try:
56
+ genai.configure(api_key=GOOGLE_API_KEY)
57
+ model = genai.GenerativeModel(model_name=MODEL_NAME,
58
+ generation_config=generation_config,
59
+ system_instruction=SYSTEM_INSTRUCTION, # Передаем системную инструкцию сюда
60
+ safety_settings=safety_settings)
61
+ model_initialized = True
62
+ print(f"Модель '{MODEL_NAME}' успешно инициализирована.")
63
+ except Exception as e:
64
+ initialization_error = f"ОШИБКА при инициализации модели Google AI: {e}"
65
+ print(initialization_error)
66
+
67
+ # --- Утилиты ---
68
+ def format_chat_history_for_gemini(chat_history):
69
+ """Конвертирует историю Gradio в формат Gemini API."""
70
+ gemini_history = []
71
+ for user_msg, bot_msg in chat_history:
72
+ if user_msg: # Добавляем сообщение пользователя
73
+ gemini_history.append({'role':'user', 'parts': [{'text': user_msg}]})
74
+ if bot_msg: # Добавляем ответ модели
75
+ gemini_history.append({'role':'model', 'parts': [{'text': bot_msg}]})
76
+ return gemini_history
77
+
78
+ def clean_response(text):
79
+ """Простая очистка ответа."""
80
+ if not text: return ""
81
+ # Убираем лишние пробелы
82
+ text = text.strip()
83
+ # Можно добавить другую очистку при необходимости
84
+ return text
85
 
86
+ # --- Основная Функция Обработки ---
87
  def respond(message, chat_history):
88
+ global model, model_initialized, initialization_error # Доступ к глобальным переменным
89
+
90
  print("-" * 30)
91
  print(f"ВХОД: '{message}'")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
+ # Проверка инициализации
94
+ if not model_initialized or not model:
95
+ error_msg = initialization_error or "Модель не инициализирована."
96
+ chat_history.append((message, f"Ошибка системы: {error_msg}"))
97
+ return "", chat_history # Возвращаем ошибку в чат
98
+
99
+ # Проверка пустого сообщения
100
+ if not message or not message.strip():
101
+ chat_history.append((message, "Пожалуйста, введите сообщение."))
102
+ return "", chat_history
103
+
104
+ try:
105
+ # Форматируем историю для Gemini API
106
+ gemini_history = format_chat_history_for_gemini(chat_history)
107
+
108
+ # Создаем или продолжаем чат (start_chat для поддержания контекста)
109
+ # В новой версии API рекомендуется просто передавать историю каждый раз
110
+ # chat_session = model.start_chat(history=gemini_history)
111
+
112
+ print(f"Отправка запроса к Gemini (история {len(gemini_history)} сообщений)...")
113
+
114
+ # Отправляем сообщение модели
115
+ # Вместо start_chat передаем историю напрямую в generate_content
116
+ response = model.generate_content(
117
+ contents=gemini_history + [{'role':'user', 'parts': [{'text': message}]}],
118
+ # Не используем stream=True для простоты в Gradio
119
+ )
120
 
121
+
122
+ # --- Обработка Ответа ---
123
+ print("Получен ответ от Gemini.")
124
+
125
+ # Проверка на блокировку фильтрами безопасности
126
+ if not response.candidates:
127
+ # Ищем причину блокировки
128
+ block_reason = "Причина неизвестна"
129
+ try:
130
+ if response.prompt_feedback.block_reason:
131
+ block_reason = response.prompt_feedback.block_reason.name
132
+ except Exception:
133
+ pass # Не всегда есть feedback
134
+ print(f"Ответ заблокирован фильтрами безопасности! Причина: {block_reason}")
135
+ bot_response = f"[Ответ заблокирован системой безопасности Google. Причина: {block_reason}]"
136
+ else:
137
+ # Извлекаем текст ответа
138
+ bot_response_raw = response.text
139
+ bot_response = clean_response(bot_response_raw)
140
+ print(f"Ответ Gemini (очищенный): {bot_response[:150]}...") # Логируем начало
141
+
142
+
143
+ except Exception as e:
144
+ error_text = f"Произошла ошибка при обращении к Google AI: {e}"
145
+ print(f"ОШИБКА: {error_text}")
146
+ # Проверяем на типичные ошибки API ключа
147
+ if "API key not valid" in str(e):
148
+ error_text += "\n\nПРОВЕРЬТЕ ВАШ GOOGLE_API_KEY в Секретах Spaces!"
149
+ elif "billing account" in str(e).lower():
150
+ error_text += "\n\nВозможно, требуется включить биллинг в Google Cloud (хотя бесплатный уровень Gemini должен работать без него)."
151
+ elif "quota" in str(e).lower():
152
+ error_text += "\n\nВозможно, вы превысили бесплатные лимиты запросов к API Gemini."
153
+
154
+ bot_response = f"[Системная ошибка: {error_text}]"
155
+
156
+ # Добавляем пару в историю Gradio
157
  chat_history.append((message, bot_response))
 
158
 
159
+ # Имитация небольшой задержки для лучшего восприятия
160
+ time.sleep(0.5)
161
+
162
+ return "", chat_history # Очищаем поле ввода и возвращаем обновленную историю
163
 
164
+
165
+ # --- Создание интерфейса Gradio с Красивым Оформлением и Анимацией ---
166
  custom_css = """
167
+ /* Общий фон */
168
+ .gradio-container {
169
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); /* Нежный серо-голубой градиент */
170
+ border-radius: 15px;
171
+ padding: 25px;
172
+ color: #333;
173
+ }
174
+
175
+ /* Заголовок */
176
+ h1 {
177
+ color: #2c3e50; /* Темный серо-синий */
178
+ text-align: center;
179
+ font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* Современный шрифт */
180
+ margin-bottom: 10px; /* Уменьшили отступ */
181
+ font-weight: 700; /* Жирнее */
182
+ letter-spacing: -0.5px;
183
+ }
184
+ #title-markdown p {
185
+ text-align: center;
186
+ color: #5a6a7a; /* Приглушенный цвет подзаголовка */
187
+ margin-top: -5px;
188
+ margin-bottom: 25px;
189
+ font-size: 0.95em;
190
+ }
191
+ #title-markdown a { color: #3498db; text-decoration: none; }
192
+ #title-markdown a:hover { text-decoration: underline; }
193
+
194
+ /* --- СТИЛИ ЧАТА --- */
195
+ #chatbot {
196
+ background-color: #ffffff; /* Белый фон */
197
+ border-radius: 12px;
198
+ border: 1px solid #e0e4e7; /* Слегка видная рамка */
199
+ padding: 10px;
200
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); /* Мягкая тень */
201
+ }
202
+
203
+ /* Анимация появления сообщений (простая) */
204
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
205
+ #chatbot > div { /* Применяем ко всем контейнерам сообщений */
206
+ animation: fadeIn 0.3s ease-out;
207
+ }
208
+
209
+ /* Сообщения пользователя */
210
  #chatbot .user-message .message-bubble-border { border: none !important; }
211
+ #chatbot .user-message .message-bubble {
212
+ background: linear-gradient(to right, #007bff, #0056b3) !important; /* Синий градиент */
213
+ color: white !important;
214
+ border-radius: 18px 18px 5px 18px !important;
215
+ padding: 12px 18px !important;
216
+ margin: 8px 5px 8px 0 !important;
217
+ align-self: flex-end !important;
218
+ max-width: 80% !important;
219
+ box-shadow: 0 3px 6px rgba(0, 91, 179, 0.2);
220
+ word-wrap: break-word;
221
+ text-align: left;
222
+ font-size: 0.98em; /* Чуть меньше шрифт сообщения */
223
+ line-height: 1.5; /* Межстрочный интервал */
224
+ }
225
+
226
+ /* Сообщения бота */
227
  #chatbot .bot-message .message-bubble-border { border: none !important; }
228
+ #chatbot .bot-message .message-bubble {
229
+ background: #f8f9fa !important; /* Очень светлый фон */
230
+ color: #343a40 !important; /* Почти черный текст */
231
+ border: 1px solid #e9ecef !important;
232
+ border-radius: 18px 18px 18px 5px !important;
233
+ padding: 12px 18px !important;
234
+ margin: 8px 0 8px 5px !important;
235
+ align-self: flex-start !important;
236
+ max-width: 80% !important;
237
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.05);
238
+ word-wrap: break-word;
239
+ text-align: left;
240
+ font-size: 0.98em;
241
+ line-height: 1.5;
242
+ }
243
+
244
+ /* Аватар бота */
245
+ #chatbot .bot-message img.avatar-image { /* Стили для аватарки бота */
246
+ width: 30px !important;
247
+ height: 30px !important;
248
+ margin-right: 8px !important; /* Отступ справа от аватарки */
249
+ border-radius: 50% !important;
250
+ align-self: flex-start; /* Прижать к верху бабла */
251
+ margin-top: 5px;
252
+ }
253
+
254
+
255
+ /* Блоки кода внутри сообщений бота */
256
+ #chatbot .bot-message .message-bubble pre {
257
+ background-color: #e9ecef; /* Фон */
258
+ border: 1px solid #ced4da;
259
+ border-radius: 6px;
260
+ padding: 12px;
261
+ margin: 10px 0 5px 0;
262
+ overflow-x: auto;
263
+ word-wrap: normal;
264
+ box-shadow: inset 0 1px 2px rgba(0,0,0,0.05);
265
+ }
266
+ #chatbot .bot-message .message-bubble pre code {
267
+ background-color: transparent !important;
268
+ color: #212529; /* Цвет текста кода */
269
+ font-family: 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace; /* Красивый шрифт для кода */
270
+ font-size: 0.9em;
271
+ padding: 0;
272
+ white-space: pre;
273
+ }
274
+
275
+ /* --- ОСТАЛЬНЫЕ ЭЛЕМЕНТЫ --- */
276
+ textarea {
277
+ border: 1px solid #ced4da !important;
278
+ border-radius: 10px !important;
279
+ padding: 12px 15px !important;
280
+ background-color: #ffffff;
281
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
282
+ font-size: 1rem;
283
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
284
+ }
285
+ textarea:focus {
286
+ border-color: #80bdff !important;
287
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25), 0 1px 3px rgba(0,0,0,0.05);
288
+ outline: none;
289
+ }
290
+
291
+ /* Кнопки */
292
+ button {
293
+ border-radius: 10px !important;
294
+ padding: 11px 15px !important; /* Чуть меньше паддинг по высоте */
295
+ transition: all 0.2s ease !important; /* Плавнее анимация */
296
+ font-weight: 500 !important;
297
+ border: none !important;
298
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Базовая тень */
299
+ }
300
+ button:active {
301
+ transform: scale(0.98); /* Уменьшение при нажатии */
302
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1); /* Внутренняя тень при нажатии */
303
+ }
304
+ button.primary {
305
+ background: linear-gradient(to right, #007bff, #0056b3) !important; /* Градиент основной */
306
+ color: white !important;
307
+ }
308
+ button.primary:hover {
309
+ background: linear-gradient(to right, #0069d9, #004085) !important; /* Темнее градиент */
310
+ box-shadow: 0 4px 8px rgba(0, 123, 255, 0.2);
311
+ }
312
+ button.secondary {
313
+ background-color: #6c757d !important;
314
+ color: white !important;
315
+ }
316
+ button.secondary:hover {
317
+ background-color: #5a6268 !important;
318
+ box-shadow: 0 4px 8px rgba(108, 117, 125, 0.2);
319
+ }
320
+
321
+ /* Анимация спиннера (скрываем стандартный gradio прогресс, т.к. он часто глючит) */
322
+ .progress-bar { display: none !important; }
323
+ /* Вместо этого можно было бы добавить кастомный лоадер при желании, но пока оставим без него */
324
  """
325
 
326
+ # --- Gradio Интерфейс ---
327
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue=gr.themes.colors.indigo, secondary_hue=gr.themes.colors.slate)) as demo: # Новые цвета темы
328
+ with gr.Row():
329
+ # Аватарка для названия
330
+ gr.Image("https://img.icons8.com/external-flaticons-flat-flat-icons/64/external-nova-astronomy-flaticons-flat-flat-icons.png",
331
+ width=60, height=60, scale=0, min_width=60, show_label=False, container=False) # Иконка
332
+ with gr.Column(scale=8):
333
+ gr.Markdown("# 🌠 Nova AI Alpha 1.0 ✨", elem_id="title-markdown")
334
+ gr.Markdown("<p>Чат-бот на базе Google Gemini. <a href='https://aistudio.google.com/' target='_blank'>Используется Gemini API</a>.</p>", elem_id="title-markdown")
335
 
336
  chatbot = gr.Chatbot(
337
+ label="Диалог",
338
+ height=600, # Еще выше
339
+ elem_id="chatbot",
340
+ bubble_full_width=False,
341
+ avatar_images=(None, # Аватар юзера (можно добавить свою картинку)
342
+ "https://img.icons8.com/plasticine/100/bot.png"), # Аватар бота
343
+ show_copy_button=True,
344
+ show_share_button=False # Скрываем кнопку шаринга gradio
345
  )
346
+
347
+ with gr.Row(equal_height=True): # Выравнивание элементов в ряду по высоте
348
  msg = gr.Textbox(
349
+ label="Ваше сообщение",
350
+ placeholder="Спросите о Python, мире или просто скажите 'Привет!'...",
351
+ scale=5, # Больше места полю ввода
352
+ show_label=False,
353
+ container=False
354
  )
355
+ submit_btn = gr.Button("➤ Отправить", variant="primary", scale=1, min_width=140) # Кнопка шире
356
+ clear_btn = gr.Button("🗑️ Очистить", variant="secondary", scale=1, min_width=140) # Кнопка шире
357
+
358
+ # --- Обработчики Событий ---
359
+ # Добавляем .then() для индикации загрузки (Gradio может не успевать отображать сложные статусы)
360
+ # Базовое решение - кнопка неактивна во время обработки
361
+
362
+ # При нажатии Enter
363
+ enter_event = msg.submit(
364
+ lambda: gr.update(interactive=False), None, outputs=[submit_btn] # Деактивировать кнопку при начале
365
+ ).then(
366
+ respond, inputs=[msg, chatbot], outputs=[msg, chatbot]
367
+ ).then(
368
+ lambda: gr.update(interactive=True), None, outputs=[submit_btn] # Активировать кнопку по завершении
369
+ )
370
+
371
+ # При нажатии кнопки Отправить
372
+ click_event = submit_btn.click(
373
+ lambda: gr.update(interactive=False), None, outputs=[submit_btn]
374
+ ).then(
375
+ respond, inputs=[msg, chatbot], outputs=[msg, chatbot]
376
+ ).then(
377
+ lambda: gr.update(interactive=True), None, outputs=[submit_btn]
378
+ )
379
+
380
+ # Очистка (остается без индикации)
381
+ clear_btn.click(lambda: ("", []), None, outputs=[msg, chatbot], queue=False) # Возвращает "" для msg
382
+
383
+
384
+ # Запуск Gradio приложения
385
+ demo.queue() # Очередь запросов - важно для API и ресурсов
386
+ demo.launch(debug=True) # Включить Debug для отладки