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