Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -1,224 +1,386 @@
|
|
1 |
# app.py
|
2 |
|
3 |
import gradio as gr
|
4 |
-
|
5 |
-
import
|
6 |
import re
|
7 |
-
import
|
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 |
-
|
42 |
-
|
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 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
|
98 |
-
|
|
|
|
|
|
|
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 |
-
|
|
|
181 |
custom_css = """
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
186 |
#chatbot .user-message .message-bubble-border { border: none !important; }
|
187 |
-
#chatbot .user-message .message-bubble {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
#chatbot .bot-message .message-bubble-border { border: none !important; }
|
189 |
-
#chatbot .bot-message .message-bubble {
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
"""
|
201 |
|
202 |
-
|
203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
204 |
|
205 |
chatbot = gr.Chatbot(
|
206 |
-
label="Диалог",
|
207 |
-
|
208 |
-
|
|
|
|
|
|
|
|
|
|
|
209 |
)
|
210 |
-
|
|
|
211 |
msg = gr.Textbox(
|
212 |
-
label="Ваше сообщение",
|
213 |
-
|
|
|
|
|
|
|
214 |
)
|
215 |
-
submit_btn = gr.Button("➤ Отправить", variant="primary", scale=1, min_width=
|
216 |
-
clear_btn = gr.Button("🗑️ Очистить", variant="secondary", scale=1, min_width=
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
#
|
223 |
-
|
224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 для отладки
|