/
dsboikov
/
aiBoardRoom
Обзор
Документация
Войти
/
dsboikov
/
aiBoardRoom
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/ui/settings.py
281 строка
11 KB
Денис
GigaChat and YandexGPT added
04 авг 2026, 09:00
04 авг 2026, 09:00
6d6fee5
Код
Авторство
О чём код?
"""Диалог настроек провайдеров: ключи, баланс, лимиты (без выбора моделей).""" from __future__ import annotations from PySide6.QtWidgets import ( QDialog, QDialogButtonBox, QFormLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QScrollArea, QSpinBox, QVBoxLayout, QWidget, ) from config import DEFAULT_PROVIDER_MODEL, PROVIDER_LABELS from providers.account import AccountStatus from providers.availability import get_ui_provider_ids from providers.factory import fetch_all_account_statuses, validate_all_keys from storage.account_cache import get_cached_balance from storage.encryption import get_secret_store from storage.sqlite import Database, get_db from ui.async_bridge import run_async from utils.logger import get_logger logger = get_logger("ui.settings") class ProviderRow(QWidget): def __init__(self, provider: str, parent: QWidget | None = None) -> None: super().__init__(parent) self.provider = provider layout = QFormLayout(self) self.key_edit = QLineEdit() self.key_edit.setEchoMode(QLineEdit.EchoMode.Password) if provider == "ollama": self.key_edit.setPlaceholderText("необязательно (по умолчанию ollama)") elif provider == "yandexgpt": self.key_edit.setPlaceholderText("folder_id:api_key") elif provider == "gigachat": self.key_edit.setPlaceholderText("ключ авторизации Base64 из studio.sber.ru") else: self.key_edit.setPlaceholderText("•••••••• (оставьте пустым, чтобы не менять)") self.limit = QSpinBox() self.limit.setRange(0, 10_000_000) self.limit.setSingleStep(1000) self.limit.setSpecialValueText("Без лимита") self.balance_label = QLabel(get_cached_balance(provider) or "Баланс: не загружен") self.balance_label.setWordWrap(True) self.balance_label.setStyleSheet("color: #9aa3b2;") self.refresh_btn = QPushButton("Обновить баланс") self.refresh_btn.setObjectName("secondaryButton") self.refresh_btn.clicked.connect(self._refresh_account) layout.addRow("API-ключ", self.key_edit) layout.addRow("Суточный лимит токенов", self.limit) layout.addRow("Аккаунт", self.balance_label) layout.addRow("", self.refresh_btn) def apply_account_status(self, status: AccountStatus) -> None: # Models are chosen in room roles — here only balance/status from storage.account_cache import save_account_status save_account_status(status) self.balance_label.setText(status.label_ru()) if status.is_available is False: self.balance_label.setStyleSheet("color: #e06c75; font-weight: 600;") elif status.is_available is True: self.balance_label.setStyleSheet("color: #7ddea0;") else: self.balance_label.setStyleSheet("color: #9aa3b2;") def _refresh_account(self) -> None: from config import OPTIONAL_API_KEY_PROVIDERS key = self.key_edit.text().strip() or get_secret_store().get_api_key(self.provider) if not key and self.provider not in OPTIONAL_API_KEY_PROVIDERS: label = PROVIDER_LABELS.get(self.provider, self.provider) QMessageBox.warning(self, "Аккаунт", f"Сначала укажите API-ключ для {label}.") return self.refresh_btn.setEnabled(False) self.refresh_btn.setText("Загрузка…") self.balance_label.setText("Загрузка баланса…") async def _load(): from providers.factory import create_provider p = create_provider(self.provider, key or None) try: return await p.get_account_status() finally: await p.aclose() def ok(status: AccountStatus) -> None: self.refresh_btn.setEnabled(True) self.refresh_btn.setText("Обновить баланс") self.apply_account_status(status) def err(msg: str) -> None: self.refresh_btn.setEnabled(True) self.refresh_btn.setText("Обновить баланс") self.balance_label.setText("Ошибка загрузки") QMessageBox.warning(self, "Аккаунт", f"Не удалось загрузить:\n{msg[:400]}") run_async(self, _load, on_finished=ok, on_error=err) class SettingsDialog(QDialog): def __init__(self, db: Database | None = None, parent: QWidget | None = None) -> None: super().__init__(parent) self.db = db or get_db() self.setWindowTitle("Провайдеры и лимиты") self.resize(520, 560) self.store = get_secret_store() self._provider_ids = get_ui_provider_ids() root = QVBoxLayout(self) hint = QLabel( "Модели выбираются в настройках ролей комнаты.\n" "Локальные сервисы (например Ollama) показываются только если запущены." ) hint.setWordWrap(True) hint.setStyleSheet("color: #9aa3b2;") root.addWidget(hint) scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QScrollArea.Shape.NoFrame) host = QWidget() host_layout = QVBoxLayout(host) host_layout.setContentsMargins(0, 0, 0, 0) self.rows: dict[str, ProviderRow] = {} if not self._provider_ids: host_layout.addWidget(QLabel("Нет доступных провайдеров.")) for provider in self._provider_ids: box = QGroupBox(PROVIDER_LABELS.get(provider, provider)) box_layout = QVBoxLayout(box) row = ProviderRow(provider) box_layout.addWidget(row) host_layout.addWidget(box) self.rows[provider] = row host_layout.addStretch() scroll.setWidget(host) root.addWidget(scroll, stretch=1) actions = QHBoxLayout() self.check_btn = QPushButton("Проверить ключи") self.check_btn.setObjectName("secondaryButton") self.check_btn.clicked.connect(self._validate_keys) actions.addWidget(self.check_btn) self.refresh_all_btn = QPushButton("Обновить балансы") self.refresh_all_btn.clicked.connect(lambda: self._refresh_all_accounts(False)) actions.addWidget(self.refresh_all_btn) actions.addStretch() root.addLayout(actions) buttons = QDialogButtonBox( QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel ) buttons.button(QDialogButtonBox.StandardButton.Save).setText("Сохранить") buttons.button(QDialogButtonBox.StandardButton.Cancel).setText("Отмена") buttons.accepted.connect(self._save) buttons.rejected.connect(self.reject) root.addWidget(buttons) self._load() def _collect_keys(self) -> dict[str, str | None]: keys: dict[str, str | None] = {} for provider, row in self.rows.items(): typed = row.key_edit.text().strip() if typed: self.store.set_api_key(provider, typed) keys[provider] = typed else: keys[provider] = self.store.get_api_key(provider) return keys def _load(self) -> None: settings = {s["provider"]: s for s in self.db.get_provider_settings()} for provider, row in self.rows.items(): conf = settings.get(provider, {}) row.limit.setValue(int(conf.get("daily_limit", 0))) if self.store.has_api_key(provider): row.key_edit.setPlaceholderText("•••••••• (сохранено)") cached_bal = get_cached_balance(provider) if cached_bal: row.balance_label.setText(cached_bal) def _save(self) -> None: settings = {s["provider"]: s for s in self.db.get_provider_settings()} for provider, row in self.rows.items(): existing = settings.get(provider, {}) model = existing.get("model") or DEFAULT_PROVIDER_MODEL.get(provider, "") self.db.upsert_provider_setting(provider, model, row.limit.value(), True) key = row.key_edit.text().strip() if key: self.store.set_api_key(provider, key) self.accept() def _validate_keys(self) -> None: keys = self._collect_keys() self.check_btn.setEnabled(False) self.check_btn.setText("Проверка…") def _restore() -> None: self.check_btn.setEnabled(True) self.check_btn.setText("Проверить ключи") def ok(results) -> None: _restore() lines = [] for k, v in results.items(): if k not in self.rows: continue label = PROVIDER_LABELS.get(k, k) if hasattr(v, "label_ru"): lines.append(f"{label}: {v.label_ru()}") else: lines.append(f"{label}: {'ОК' if v else 'ошибка'}") QMessageBox.information(self, "Проверка ключей", "\n".join(lines) or "Нет провайдеров") def err(msg: str) -> None: _restore() QMessageBox.warning(self, "Проверка ключей", msg[:500]) async def _run(): return await validate_all_keys(keys) run_async(self, _run, on_finished=ok, on_error=err) def _refresh_all_accounts(self, silent: bool = False) -> None: keys = self._collect_keys() self.refresh_all_btn.setEnabled(False) self.refresh_all_btn.setText("Обновление…") for row in self.rows.values(): row.balance_label.setText("Загрузка…") def ok(results: dict) -> None: self.refresh_all_btn.setEnabled(True) self.refresh_all_btn.setText("Обновить балансы") low = [] for name, status in results.items(): row = self.rows.get(name) if row and isinstance(status, AccountStatus): row.apply_account_status(status) if status.is_available is False: low.append(PROVIDER_LABELS.get(name, name)) if low and not silent: QMessageBox.warning( self, "Низкий баланс", "Недостаточно средств / кредитов:\n• " + "\n• ".join(low), ) elif not silent: QMessageBox.information(self, "Аккаунты", "Балансы обновлены.") def err(msg: str) -> None: self.refresh_all_btn.setEnabled(True) self.refresh_all_btn.setText("Обновить балансы") if not silent: QMessageBox.warning(self, "Аккаунты", msg[:500]) async def _run(): # Only fetch for providers shown in this dialog filtered = {k: v for k, v in keys.items() if k in self.rows} return await fetch_all_account_statuses(filtered) run_async(self, _run, on_finished=ok, on_error=err)