import gradio as gr import time import threading import os from .analyzer import NPAAnalyzer # Кольори сайту НАЗК NAZK_BLUE = "#0056b3" NAZK_LIGHT_BLUE = "#006ec7" NAZK_YELLOW = "#ffd700" NAZK_WHITE = "#ffffff" NAZK_GRAY = "#f5f7f9" NAZK_DARK_TEXT = "#333333" def create_interface(): try: # Ініціалізація аналізатора з OpenRouter за замовчуванням analyzer = NPAAnalyzer(provider_name="openrouter") # Отримання доступних провайдерів та моделей available_providers = analyzer.get_available_providers() available_models = analyzer.get_available_models() or ["Немає доступних моделей"] # Перевірка наявності API ключів has_anthropic_key = bool(os.getenv('ANTHROPIC_API_KEY')) has_openrouter_key = bool(os.getenv('OPENROUTER_API_KEY')) api_keys_warning = "" if not has_anthropic_key and not has_openrouter_key: api_keys_warning = "⚠️ Жоден з API ключів (ANTHROPIC_API_KEY, OPENROUTER_API_KEY) не встановлено. Аналіз буде недоступний." if not has_openrouter_key and analyzer.get_provider_name() == "OpenRouter": # Використовуємо Anthropic, якщо OpenRouter не доступний if has_anthropic_key: analyzer.change_provider("anthropic") print("Ключ OpenRouter відсутній, використовується Anthropic") else: api_keys_warning = "⚠️ Відсутній ключ OpenRouter, а Anthropic недоступний. Аналіз буде недоступний." # Функція для оновлення доступних моделей при зміні провайдера def update_models(provider_name): try: analyzer.change_provider(provider_name) new_models = analyzer.get_available_models() or ["Немає доступних моделей"] supports_thinking = analyzer.supports_thinking() # Оновлення елементів інтерфейсу return gr.Dropdown(choices=new_models, value=new_models[0]), gr.update(interactive=supports_thinking), gr.update(interactive=supports_thinking) except Exception as e: return gr.Dropdown(choices=["Помилка при завантаженні моделей"], value="Помилка при завантаженні моделей"), gr.update(interactive=False), gr.update(interactive=False) # Функція для оновлення моделі def update_model(model_name): try: analyzer.change_model(model_name) return f"Модель змінено на: {model_name}" except Exception as e: return f"Помилка при зміні моделі: {str(e)}" # Функція для форматування результату в Markdown def analyze_and_format(npa_text, selected_provider, selected_model, enable_thinking, thinking_budget, progress=gr.Progress()): if not npa_text.strip(): return "### Помилка\nПоле для тексту не може бути порожнім. Введіть текст для аналізу.", "", "Перевірте введені дані" if api_keys_warning: return f"### Помилка\n{api_keys_warning}", "", "Помилка конфігурації API ключів" # Переконуємось, що провайдер та модель встановлені правильно try: if analyzer.get_provider_name() != selected_provider: analyzer.change_provider(selected_provider) current_models = analyzer.get_available_models() or [] if selected_model in current_models: analyzer.change_model(selected_model) elif "Немає доступних моделей" in selected_model or "Помилка при завантаженні моделей" in selected_model: return "### Помилка\nНемає доступних моделей для вибраного провайдера. Перевірте налаштування API ключів.", "", "Помилка конфігурації моделей" except Exception as e: return f"### Помилка при налаштуванні провайдера/моделі\n{str(e)}", "", f"Помилка: {str(e)}" # Перевірка та конвертація бюджету токенів try: thinking_budget = int(thinking_budget) if thinking_budget < 1024: thinking_budget = 1024 # Мінімальний бюджет elif thinking_budget > 8000: thinking_budget = 8000 # Рекомендований максимум except (ValueError, TypeError): thinking_budget = 2000 # Значення за замовчуванням start_time = time.time() # Початкові кроки прогресу progress(0, desc="Ініціалізація аналізу...") time.sleep(0.3) progress(0.05, desc="Підготовка тексту НПА...") time.sleep(0.3) progress(0.1, desc=f"Підготовка запиту до {selected_provider}...") time.sleep(0.3) progress(0.15, desc=f"Відправка запиту до {selected_provider}...") # Змінні для контролю фонового потоку is_analyzing = True current_progress = 0.15 def update_progress_bar(): nonlocal current_progress while is_analyzing and current_progress < 0.95: # Поступово збільшуємо значення прогресу time.sleep(0.5) # Оновлюємо кожні 0.5 секунди # Використовуємо логарифмічну прогресію для повільнішого наростання в кінці step = max(0.005, (0.95 - current_progress) / 20) current_progress += step # Оновлюємо опис базуючись на поточному прогресі desc = "Аналіз тексту..." if current_progress > 0.7: desc = "Формування відповіді..." elif current_progress > 0.4: desc = "Опрацювання результатів..." progress(current_progress, desc=desc) # Запускаємо фоновий потік для оновлення прогресу progress_thread = threading.Thread(target=update_progress_bar) progress_thread.daemon = True progress_thread.start() # Аналіз з поточними налаштуваннями try: supports_thinking = analyzer.supports_thinking() result = analyzer.analyze_npa( npa_text, enable_thinking=(enable_thinking and supports_thinking), thinking_budget=thinking_budget ) except Exception as e: # Зупиняємо фоновий потік оновлення прогресу is_analyzing = False progress_thread.join(timeout=1.0) return f"### Помилка при аналізі\n```\n{str(e)}\n```\n\nПеревірте текст та налаштування або спробуйте ще раз.", "", f"Помилка аналізу: {str(e)}" # Зупиняємо фоновий потік оновлення прогресу is_analyzing = False progress_thread.join(timeout=1.0) progress(0.95, desc="Форматування результатів...") end_time = time.time() elapsed_time = end_time - start_time # Форматування основної відповіді formatted_response = f"## Результат аналізу:\n{result['response']}\n\n_Час аналізу: {elapsed_time:.2f} секунд._" # Форматування роздумів моделі - показуємо автоматично якщо режим роздумів увімкнено thinking_output = "" if enable_thinking and supports_thinking: if result['thinking']: thinking_output = f"## Хід роздумів моделі\n```\n{result['thinking']}\n```" else: thinking_output = "## Роздуми моделі недоступні\nРоздуми моделі відсутні або виникла помилка при їх отриманні." else: thinking_output = f"## Роздуми моделі недоступні\n{'Режим роздумів вимкнено.' if supports_thinking else f'Провайдер {selected_provider} не підтримує режим роздумів.'}" # Додавання інформації про налаштування та токени settings_info = f"\n\n_Провайдер: {selected_provider}, Модель: {selected_model}, " if supports_thinking: settings_info += f"режим роздумів {'увімкнено' if enable_thinking else 'вимкнено'}" if enable_thinking: settings_info += f", бюджет токенів: {thinking_budget}_" else: settings_info += "_" else: settings_info += "_" formatted_response += settings_info progress(1.0, desc="Аналіз завершено") status = "Аналіз завершено успішно" return formatted_response, thinking_output, status # Налаштування користувацького CSS для стилізації інтерфейсу custom_css = """ :root { --nazk-blue: #0056b3; --nazk-light-blue: #006ec7; --nazk-hover-blue: #0066cc; --nazk-yellow: #ffd700; --nazk-white: #ffffff; --nazk-gray: #f5f7f9; --nazk-dark-gray: #e0e0e0; --nazk-dark-text: #333333; --nazk-border-radius: 6px; --nazk-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); --nazk-transition: all 0.2s ease-in-out; } body, .gradio-container { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: var(--nazk-white) !important; color: var(--nazk-dark-text); } h1, h2, h3 { color: var(--nazk-blue) !important; margin-top: 1.5rem; margin-bottom: 1rem; } .custom-header { background-color: var(--nazk-blue) !important; color: var(--nazk-white) !important; padding: 15px; margin-bottom: 25px; text-align: center; border-radius: var(--nazk-border-radius); box-shadow: var(--nazk-box-shadow); } .custom-footer { color: var(--nazk-dark-text) !important; background-color: var(--nazk-gray) !important; padding: 15px; margin-top: 30px; text-align: center; border-radius: var(--nazk-border-radius); border-top: 1px solid var(--nazk-dark-gray); } /* Tabs styling */ .tabs { background-color: var(--nazk-white) !important; border-radius: var(--nazk-border-radius); overflow: hidden; margin-top: 20px; } .tab-nav { background-color: var(--nazk-gray) !important; border-radius: var(--nazk-border-radius) var(--nazk-border-radius) 0 0; } .tab-selected { background-color: var(--nazk-blue) !important; color: var(--nazk-white) !important; border-radius: var(--nazk-border-radius) var(--nazk-border-radius) 0 0; font-weight: 500; } .tab-unselected { background-color: var(--nazk-gray) !important; transition: var(--nazk-transition); } .tab-unselected:hover { background-color: var(--nazk-dark-gray) !important; } /* Button styling */ button, .primary-button { background-color: var(--nazk-blue) !important; color: var(--nazk-white) !important; border-radius: var(--nazk-border-radius) !important; border: none !important; padding: 10px 16px !important; font-weight: 500 !important; transition: var(--nazk-transition) !important; box-shadow: var(--nazk-box-shadow) !important; } button:hover, .primary-button:hover { background-color: var(--nazk-hover-blue) !important; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important; } .secondary-button { border: 1px solid var(--nazk-blue) !important; color: var(--nazk-blue) !important; background-color: transparent !important; border-radius: var(--nazk-border-radius) !important; padding: 9px 15px !important; transition: var(--nazk-transition) !important; } .secondary-button:hover { background-color: rgba(0, 86, 179, 0.05) !important; border-color: var(--nazk-hover-blue) !important; } /* Input fields */ input, textarea, select { border: 1px solid var(--nazk-dark-gray) !important; border-radius: var(--nazk-border-radius) !important; padding: 8px 12px !important; transition: var(--nazk-transition) !important; } input:focus, textarea:focus, select:focus { border-color: var(--nazk-blue) !important; box-shadow: 0 0 0 2px rgba(0, 86, 179, 0.2) !important; outline: none !important; } /* Checkbox and slider */ input[type="range"] { accent-color: var(--nazk-blue) !important; } input[type="range"]::-webkit-slider-thumb { background: var(--nazk-blue) !important; border-radius: 50% !important; cursor: pointer !important; transition: var(--nazk-transition) !important; } input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.1) !important; } input[type="checkbox"] { accent-color: var(--nazk-blue) !important; width: 18px !important; height: 18px !important; border-radius: 3px !important; } input[type="checkbox"]:checked { background-color: var(--nazk-blue) !important; border-color: var(--nazk-blue) !important; } .label-wrap { display: flex !important; align-items: center !important; gap: 8px !important; } /* Links */ a { color: var(--nazk-blue) !important; text-decoration: none !important; transition: var(--nazk-transition) !important; } a:hover { color: var(--nazk-hover-blue) !important; text-decoration: underline !important; } /* Progress bar */ .progress-bar { background-color: var(--nazk-blue) !important; height: 6px !important; border-radius: 3px !important; } .progress-container { background-color: var(--nazk-gray) !important; border-radius: 3px !important; } /* Accordion */ .accordion { border: 1px solid var(--nazk-dark-gray) !important; border-radius: var(--nazk-border-radius) !important; margin: 15px 0 !important; overflow: hidden !important; } .accordion-header { background-color: var(--nazk-gray) !important; padding: 12px 15px !important; cursor: pointer !important; transition: var(--nazk-transition) !important; } .accordion-header:hover { background-color: var(--nazk-dark-gray) !important; } .accordion-content { padding: 15px !important; background-color: var(--nazk-white) !important; } /* Output markdown */ .output-markdown { background-color: var(--nazk-white) !important; border-radius: var(--nazk-border-radius) !important; padding: 20px !important; box-shadow: var(--nazk-box-shadow) !important; border: 1px solid var(--nazk-dark-gray) !important; } .output-markdown code { background-color: var(--nazk-gray) !important; padding: 2px 5px !important; border-radius: 3px !important; font-family: monospace !important; } .output-markdown pre { background-color: var(--nazk-gray) !important; padding: 15px !important; border-radius: var(--nazk-border-radius) !important; overflow-x: auto !important; margin: 15px 0 !important; border: 1px solid var(--nazk-dark-gray) !important; } .output-markdown h2, .output-markdown h3 { border-bottom: 1px solid var(--nazk-dark-gray) !important; padding-bottom: 8px !important; margin-top: 25px !important; } .output-markdown blockquote { border-left: 4px solid var(--nazk-blue) !important; padding-left: 15px !important; margin-left: 0 !important; color: #555 !important; } /* Warning text */ .warning-text { color: #d63031 !important; font-weight: 500 !important; } """ with gr.Blocks(css=custom_css) as iface: # Хедер із логотипом gr.HTML(f"""