import os
import zipfile
from typing import Dict, List, Optional
import asyncio
import google.generativeai as genai
from google.api_core import exceptions
import gradio as gr
import pkg_resources
# --- Диагностика ---
try:
version = pkg_resources.get_distribution("google-generativeai").version
print(f"--- УСТАНОВЛЕНА ВЕРСИЯ google-generativeai: {version} ---")
except pkg_resources.DistributionNotFound:
print("--- ПРЕДУПРЕЖДЕНИЕ: Библиотека google-generativeai не найдена ---")
# --- 1. Конфигурация API и Моделей ---
GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
if not GOOGLE_API_KEY:
raise gr.Error("Переменная окружения GOOGLE_API_KEY не установлена. Установите ее в секретах вашего Space.")
try:
genai.configure(api_key=GOOGLE_API_KEY)
except Exception as e:
raise gr.Error(f"Ошибка при настройке Google Gemini API: {e}.")
def get_available_models() -> List[str]:
available_models = []
try:
for m in genai.list_models():
if 'generateContent' in m.supported_generation_methods and not any(x in m.name.lower() for x in ['vision', 'tts', 'audio']):
available_models.append(m.name)
except Exception as e:
print(f"Предупреждение: Не удалось получить список моделей: {e}.")
available_models.extend(['models/gemini-1.5-flash-latest', 'models/gemini-pro'])
return sorted(list(set(available_models)))
AVAILABLE_MODELS = get_available_models()
if not AVAILABLE_MODELS:
raise gr.Error("Не найдено моделей, совместимых с 'generateContent'.")
# --- 2. Общие константы и глобальные переменные ---
TITLE = """
✨ Gemini/Gemma Code Analysis
"""
AVATAR_IMAGES = (None, "https://media.roboflow.com/spaces/gemini-icon.png")
TEXT_EXTENSIONS = [".bat", ".c", ".cfg", ".conf", ".cpp", ".cs", ".css", ".go", ".h", ".html", ".ini", ".java", ".js", ".json", ".jsx", ".md", ".php", ".ps1", ".py", ".rb", ".rs", ".sh", ".toml", ".ts", ".tsx", ".txt", ".xml", ".yaml", ".yml"]
DEFAULT_MODEL = "models/gemini-1.5-flash-latest" if "models/gemini-1.5-flash-latest" in AVAILABLE_MODELS else AVAILABLE_MODELS[0]
DEFAULT_NUM_AI_VARIANTS = 1
EXTRACTED_FILES: Dict[str, str] = {}
# --- 3. CSS для темной темы "Глубокий Космос" ---
custom_css = """
/* --- ИСПРАВЛЕНИЕ МАКЕТА И ПРОКРУТКИ --- */
/* 1. Класс для левой колонки: фиксированная высота, flex-контекст и главное - min-width: 0 */
.chat-left-column {
height: 80vh; /* Ограничиваем высоту колонки */
display: flex;
flex-direction: column;
min-width: 0; /* <-- КЛЮЧЕВОЕ ИСПРАВЛЕНИЕ: Предотвращает "съезд" дочерних элементов */
}
/* 2. Chatbot сам по себе должен заполнять доступное пространство в колонке */
#chatbot {
flex-grow: 1; /* Заставляет чат-бот занимать всю доступную высоту */
overflow: hidden; /* Обрезает все, что выходит за его пределы */
}
/* 3. Внутренний контейнер сообщений чат-бота получает прокрутку */
#chatbot .scroll-hide {
height: 100%; /* Занимает 100% высоты родителя (#chatbot) */
overflow-y: auto !important; /* Добавляет вертикальную прокрутку */
word-wrap: break-word; /* Принудительный перенос слов */
}
/* 4. Принудительный перенос слов для самих сообщений (на всякий случай) */
.gradio-container .message {
word-wrap: break-word;
overflow-wrap: break-word;
}
/* --- СТИЛЬ ДЛЯ КОМПАКТНЫХ КНОПОК --- */
.compact-buttons button {
padding: 4px 8px !important; /* Уменьшаем внутренние отступы */
min-height: 30px !important; /* Уменьшаем минимальную высоту */
margin-bottom: 8px !important; /* Уменьшаем отступ снизу */
}
/* --- КОНЕЦ СТИЛЕЙ --- */
:root { --primary-color: #3B82F6; --app-bg-color: #111827; --input-bg-color: #1F2937; --border-color: #4B5563; --text-color-primary: #F3F4F6; --text-color-secondary: #9CA3AF; --label-color: #E5E7EB; }
.gradio-container { background-color: var(--app-bg-color) !important; color: var(--text-color-primary) !important; }
h1, .gr-markdown p { color: var(--text-color-primary) !important; }
.gradio-container label, .gradio-container .gr-info { font-weight: 600 !important; color: var(--label-color) !important; }
.variant-container { background: var(--input-bg-color); border: 1px solid var(--border-color); border-radius: 8px; margin-bottom: 10px; padding: 15px; }
.variant-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; color: var(--label-color); }
.copy-button { background: transparent; border: 1px solid var(--border-color); border-radius: 5px; cursor: pointer; padding: 5px; }
.copy-button:hover { background: #374151; }
.copy-button svg { stroke: var(--secondary-color); }
.error-message { background-color: #450A0A; color: #F87171; border: 1px solid #7F1D1D; }
"""
# --- 4. Функции для работы с файлами ---
def extract_text_from_zip(zip_file_path: str) -> Dict[str, str]:
text_contents = {}
try:
with zipfile.ZipFile(zip_file_path, "r") as zip_ref:
for file_info in zip_ref.infolist():
if file_info.is_dir(): continue
if any(file_info.filename.lower().endswith(ext) for ext in TEXT_EXTENSIONS):
with zip_ref.open(file_info) as file:
text_contents[file_info.filename] = file.read().decode("utf-8", errors="replace")
except Exception as e:
gr.Warning(f"Не удалось прочитать ZIP-архив: {e}")
return text_contents
def extract_text_from_single_file(file_path: str) -> Dict[str, str]:
text_contents = {}
filename = os.path.basename(file_path)
if any(filename.lower().endswith(ext) for ext in TEXT_EXTENSIONS):
try:
with open(file_path, "r", encoding="utf-8", errors="replace") as file:
text_contents[filename] = file.read()
except Exception as e:
gr.Warning(f"Не удалось прочитать файл {filename}: {e}")
return text_contents
def upload_files(files: List[gr.File], chatbot: List[Dict[str, str]]):
global EXTRACTED_FILES
EXTRACTED_FILES = {}
all_extracted_files = {}
for file_obj in files:
if file_obj.name.lower().endswith(".zip"): extracted = extract_text_from_zip(file_obj.name)
else: extracted = extract_text_from_single_file(file_obj.name)
all_extracted_files.update(extracted)
if not all_extracted_files:
gr.Warning(f"Загружено {len(files)} файл(ов), но не найдено поддерживаемых текстовых файлов.")
return chatbot, ""
EXTRACTED_FILES = all_extracted_files
file_list_md = "#### Загруженные файлы:\n" + "\n".join([f"- `{name}`" for name in EXTRACTED_FILES.keys()])
chatbot.append({"role": "assistant", "content": f"Файлы ({len(EXTRACTED_FILES)} шт.) загружены. Теперь задавай свой вопрос."})
return chatbot, file_list_md
# --- 5. Логика генерации ответов ---
def format_history_for_gemini(history: List[Dict[str, str]]):
gemini_history = []
for msg in history[:-1]:
role = "user" if msg['role'] == "user" else "model"
gemini_history.append({'role': role, 'parts': [{'text': msg['content']}]})
return gemini_history
def format_variants_html(variants: List[str]) -> str:
if not variants: return ""
html_outputs = []
for i, variant_text in enumerate(variants):
js_safe_text = variant_text.replace('`', '\\`').replace('\n', '\\n').replace("'", "\\'")
copy_button_html = f""""""
if "Ошибка" in variant_text:
html_outputs.append(f'