1 line
48 KiB
JSON
1 line
48 KiB
JSON
{"repo": "byheaven/epub-translate", "files": {"calibre-plugin/config.py": {"fc621ddd45ae339270f7da19566a0054e2da1e0d": {"t": 1785746895.9655018, "n": 15893, "h": "fe0848ed91a0fc9b", "c": "\"\"\"\nconfig.py - Calibre plugin configuration panel.\n\nSettings are persisted via Calibre's JSONConfig mechanism.\n\"\"\"\n\nimport os\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nfrom calibre.utils.config import JSONConfig\nfrom qt.core import (\n QFormLayout,\n QGroupBox,\n QHBoxLayout,\n QLabel,\n QComboBox,\n QLineEdit,\n QPushButton,\n QSpinBox,\n QTextEdit,\n QVBoxLayout,\n QWidget,\n)\n\n# Stored at ~/.config/calibre/plugins/epub_translate.json\nplugin_prefs = JSONConfig(\"plugins/epub_translate\")\n\nLANGUAGE_OPTIONS = [\n (\"Simplified Chinese\", \"SIMPLIFIED_CHINESE\"),\n (\"Traditional Chinese\", \"TRADITIONAL_CHINESE\"),\n (\"English\", \"ENGLISH\"),\n (\"Japanese\", \"JAPANESE\"),\n (\"Korean\", \"KOREAN\"),\n (\"French\", \"FRENCH\"),\n (\"German\", \"GERMAN\"),\n (\"Spanish\", \"SPANISH\"),\n (\"Russian\", \"RUSSIAN\"),\n (\"Portuguese\", \"PORTUGUESE\"),\n]\n\n# Defaults\nplugin_prefs.defaults[\"python_path\"] = \"\" # manual override; empty = use auto-detected venv\nplugin_prefs.defaults[\"target_language\"] = \"SIMPLIFIED_CHINESE\"\nplugin_prefs.defaults[\"concurrency\"] = 3\nplugin_prefs.defaults[\"user_prompt\"] = \"\"\nplugin_prefs.defaults[\"llm_key\"] = \"\"\nplugin_prefs.defaults[\"llm_url\"] = \"https://api.deepseek.com\"\nplugin_prefs.defaults[\"llm_model\"] = \"deepseek-chat\"\nplugin_prefs.defaults[\"llm_token_encoding\"] = \"o200k_base\"\nplugin_prefs.defaults[\"llm_timeout\"] = 120.0\nplugin_prefs.defaults[\"llm_top_p\"] = 0.6\nplugin_prefs.defaults[\"llm_temperature\"] = 0.85\nplugin_prefs.defaults[\"llm_retry_times\"] = 5\nplugin_prefs.defaults[\"llm_retry_interval\"] = 6.0\n\n\n# ── venv auto-setup helpers ───────────────────────────────────────────────────\n\n\ndef _get_plugin_data_dir() -> Path:\n \"\"\"Return the plugin's private data directory (created if absent).\"\"\"\n from calibre.utils.config import config_dir\n d = Path(config_dir) / \"plugins\" / \"epub_translate\"\n d.mkdir(parents=True, exist_ok=True)\n return d\n\n\ndef _get_venv_python() -> \"str | None\":\n \"\"\"Return the venv python path if the venv exists and epub_translator is importable.\"\"\"\n venv_dir = _get_plugin_data_dir() / \"venv\"\n if sys.platform == \"win32\":\n python = venv_dir / \"Scripts\" / \"python.exe\"\n else:\n python = venv_dir / \"bin\" / \"python\"\n if not python.exists():\n return None\n try:\n r = subprocess.run(\n [str(python), \"-c\", \"import epub_translator\"],\n capture_output=True, timeout=10,\n )\n if r.returncode == 0:\n return str(python)\n except Exception:\n pass\n return None\n\n\ndef _find_system_python() -> \"str | None\":\n \"\"\"Search for a system Python >=3.10 to use as the venv base.\n\n We prefer version-specific binaries (3.13, 3.12, 3.11) over the generic\n 'python3', because the generic name may resolve to a very new release that\n epub-translator does not yet support. The actual compatibility check is\n left to pip at install time, so this function never needs updating when\n epub-translator raises or lowers its Python requirements.\n \"\"\"\n import shutil\n\n if sys.platform == \"win32\":\n candidates = [\n \"python3.13\", \"python3.12\", \"python3.11\", \"python3.10\",\n \"python\", \"python3\", \"py\",\n ]\n else:\n candidates = [\n # Prefer stable, well-supported releases first\n \"python3.13\",\n \"python3.12\",\n \"python3.11\",\n \"python3.10\",\n \"/opt/homebrew/bin/python3.13\",\n \"/opt/homebrew/bin/python3.12\",\n \"/opt/homebrew/bin/python3.11\",\n \"/opt/homebrew/bin/python3.10\",\n \"/usr/local/bin/python3.13\",\n \"/usr/local/bin/python3.12\",\n \"/usr/local/bin/python3.11\",\n \"/usr/local/bin/python3.10\",\n # Generic fallback — version unknown until checked\n \"python3\",\n \"/opt/homebrew/bin/python3\",\n \"/usr/local/bin/python3\",\n \"/usr/bin/python3\",\n \"python\",\n ]\n\n check_script = (\n \"import sys; assert sys.version_info >= (3, 10), \"\n \"f'too old: {sys.version}'; print('ok')\"\n )\n\n for name in candidates:\n if not os.path.isabs(name):\n full = shutil.which(name)\n if not full:\n continue\n else:\n full = name\n if not os.path.exists(full):\n continue\n\n # On Windows, \"py\" is the launcher — pass \"-3\" to select Python 3\n cmd = [full, \"-3\", \"-c\", check_script] if name == \"py\" else [full, \"-c\", check_script]\n\n try:\n r = subprocess.run(cmd, capture_output=True, text=True, timeout=10)\n if r.returncode == 0:\n if name == \"py\":\n path_r = subprocess.run(\n [full, \"-3\", \"-c\", \"import sys; print(sys.executable)\"],\n capture_output=True, text=True, timeout=10,\n )\n if path_r.returncode == 0:\n return path_r.stdout.strip()\n return full\n except Exception:\n continue\n\n return None\n\n\ndef setup_venv(on_status=None) -> None:\n \"\"\"Create the plugin venv and pip-install epub-translator.\n\n Calls on_status(str) for progress messages.\n Raises RuntimeError on failure.\n \"\"\"\n def _status(msg: str):\n if on_status:\n on_status(msg)\n\n _status(\"Finding system Python 3.10+...\")\n system_python = _find_system_python()\n if not system_python:\n raise RuntimeError(\n \"No Python 3.10+ found on this system.\\n\"\n \"Please install Python 3.13 or 3.12 from https://www.python.org/downloads/\"\n )\n _status(f\"Found Python: {system_python}\")\n\n venv_dir = _get_plugin_data_dir() / \"venv\"\n _status(f\"Creating virtual environment at {venv_dir} ...\")\n try:\n r = subprocess.run(\n [system_python, \"-m\", \"venv\", str(venv_dir)],\n capture_output=True, text=True, timeout=60,\n )\n if r.returncode != 0:\n raise RuntimeError(f\"venv creation failed:\\n{r.stderr or r.stdout}\")\n except subprocess.TimeoutExpired:\n raise RuntimeError(\"venv creation timed out (60 s).\")\n\n if sys.platform == \"win32\":\n pip = venv_dir / \"Scripts\" / \"pip\"\n python = venv_dir / \"Scripts\" / \"python.exe\"\n else:\n pip = venv_dir / \"bin\" / \"pip\"\n python = venv_dir / \"bin\" / \"python\"\n\n _status(\"Installing epub-translator (this may take a minute)...\")\n try:\n r = subprocess.run(\n [str(pip), \"install\", \"--upgrade\", \"epub-translator\"],\n capture_output=True, text=True, timeout=300,\n )\n if r.returncode != 0:\n pip_output = (r.stderr or r.stdout)\n # Detect Python version incompatibility and give an actionable message\n if \"Requires-Python\" in pip_output or \"No matching distribution\" in pip_output:\n import re\n # Extract the constraint from lines like \"0.1.9 Requires-Python >=3.11, <3.14\"\n match = re.search(r\"Requires-Python\\s+([^\\n;]+)\", pip_output)\n constraint = match.group(1).strip() if match else \"unknown\"\n py_ver = subprocess.run(\n [str(python), \"-c\", \"import sys; print(sys.version.split()[0])\"],\n capture_output=True, text=True,\n ).stdout.strip()\n import shutil as _shutil\n _shutil.rmtree(str(venv_dir), ignore_errors=True)\n raise RuntimeError(\n f\"Python {py_ver} is not compatible with epub-translator \"\n f\"(requires {constraint}).\\n\\n\"\n \"Please install a compatible Python version \"\n \"(e.g. 3.13 or 3.12) from https://www.python.org/downloads/\\n\"\n \"Then click Translate again.\"\n )\n raise RuntimeError(f\"pip install failed:\\n{pip_output[-800:]}\")\n except subprocess.TimeoutExpired:\n raise RuntimeError(\"pip install timed out (5 min).\")\n\n _status(\"Verifying installation...\")\n r = subprocess.run(\n [str(python), \"-c\",\n \"import importlib.metadata, epub_translator; \"\n \"print(importlib.metadata.version('epub-translator'))\"],\n capture_output=True, text=True, timeout=15,\n )\n if r.returncode != 0:\n detail = (r.stderr or r.stdout).strip()[-600:]\n raise RuntimeError(f\"epub-translator installed but import failed:\\n{detail}\")\n _status(f\"epub-translator {r.stdout.strip()} installed successfully.\")\n\n\n# ── Config helpers ────────────────────────────────────────────────────────────\n\n\ndef get_effective_config() -> dict:\n \"\"\"Build the config dict from plugin pref fields.\"\"\"\n return {\n \"llm\": {\n \"key\": plugin_prefs[\"llm_key\"],\n \"url\": plugin_prefs[\"llm_url\"],\n \"model\": plugin_prefs[\"llm_model\"],\n \"token_encoding\": plugin_prefs[\"llm_token_encoding\"],\n \"timeout\": plugin_prefs[\"llm_timeout\"],\n \"top_p\": plugin_prefs[\"llm_top_p\"],\n \"temperature\": plugin_prefs[\"llm_temperature\"],\n \"retry_times\": plugin_prefs[\"llm_retry_times\"],\n \"retry_interval_seconds\": plugin_prefs[\"llm_retry_interval\"],\n },\n \"target_language\": plugin_prefs[\"target_language\"],\n \"concurrency\": plugin_prefs[\"concurrency\"],\n \"user_prompt\": plugin_prefs[\"user_prompt\"] or None,\n }\n\n\n# ── Settings UI ───────────────────────────────────────────────────────────────\n\n\nclass ConfigWidget(QWidget):\n def __init__(self):\n super().__init__()\n self._build_ui()\n self._load_values()\n\n def _build_ui(self):\n layout = QVBoxLayout(self)\n\n # --- API Settings ---\n api_group = QGroupBox(\"API Settings\")\n api_form = QFormLayout(api_group)\n\n self.llm_key = QLineEdit()\n self.llm_key.setEchoMode(QLineEdit.EchoMode.Password)\n self.llm_key.setPlaceholderText(\"sk-...\")\n api_form.addRow(\"API Key:\", self.llm_key)\n\n self.llm_url = QLineEdit()\n self.llm_url.setPlaceholderText(\"https://api.deepseek.com\")\n api_form.addRow(\"API URL:\", self.llm_url)\n\n self.llm_model = QLineEdit()\n self.llm_model.setPlaceholderText(\"deepseek-chat\")\n api_form.addRow(\"Model:\", self.llm_model)\n\n layout.addWidget(api_group)\n\n # --- Translation Settings ---\n trans_group = QGroupBox(\"Translation Settings\")\n trans_form = QFormLayout(trans_group)\n\n self.target_language = QComboBox()\n for label, value in LANGUAGE_OPTIONS:\n self.target_language.addItem(label, value)\n trans_form.addRow(\"Target language:\", self.target_language)\n\n self.concurrency = QSpinBox()\n self.concurrency.setRange(1, 20)\n self.concurrency.setSuffix(\" concurrent requests\")\n trans_form.addRow(\"Concurrency:\", self.concurrency)\n\n self.user_prompt = QTextEdit()\n self.user_prompt.setMaximumHeight(80)\n self.user_prompt.setPlaceholderText(\"(Optional) Custom instructions appended to the system prompt\")\n trans_form.addRow(\"User prompt:\", self.user_prompt)\n\n layout.addWidget(trans_group)\n\n # --- Advanced ---\n adv_group = QGroupBox(\"Advanced\")\n adv_form = QFormLayout(adv_group)\n\n python_row = QHBoxLayout()\n self.python_display = QLineEdit()\n self.python_display.setReadOnly(True)\n python_row.addWidget(self.python_display, 1)\n self.reinstall_btn = QPushButton(\"Reinstall\")\n self.reinstall_btn.setFixedWidth(80)\n self.reinstall_btn.clicked.connect(self._on_reinstall)\n python_row.addWidget(self.reinstall_btn)\n adv_form.addRow(\"Python:\", python_row)\n\n self.python_path = QLineEdit()\n self.python_path.setPlaceholderText(\"(optional) /path/to/python — overrides auto-detected venv\")\n adv_form.addRow(\"Override:\", self.python_path)\n\n self.adv_status = QLineEdit()\n self.adv_status.setReadOnly(True)\n adv_form.addRow(\"Status:\", self.adv_status)\n\n layout.addWidget(adv_group)\n layout.addStretch()\n\n self._refresh_venv_status()\n\n def _refresh_venv_status(self):\n \"\"\"Update the Python path display and status label in the Advanced section.\"\"\"\n venv_python = _get_venv_python()\n if venv_python:\n self.python_display.setText(f\"auto: {venv_python}\")\n try:\n r = subprocess.run(\n [venv_python, \"-c\",\n \"import sys, importlib.metadata; \"\n \"print(sys.version.split()[0], \"\n \"importlib.metadata.version('epub-translator'))\"],\n capture_output=True, text=True, timeout=10,\n )\n if r.returncode == 0:\n parts = r.stdout.strip().split()\n py_ver = parts[0] if parts else \"?\"\n pkg_ver = parts[1] if len(parts) > 1 else \"?\"\n self.adv_status.setText(f\"OK — Python {py_ver}, epub-translator {pkg_ver}\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: green; }\")\n else:\n self.adv_status.setText(\"Warning: import check failed\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: orange; }\")\n except Exception:\n self.adv_status.setText(\"Warning: could not check version\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: orange; }\")\n else:\n manual = plugin_prefs.get(\"python_path\", \"\").strip()\n if manual:\n self.python_display.setText(f\"manual: {manual}\")\n else:\n self.python_display.setText(\"Not configured — click Translate to auto-install\")\n self.adv_status.setText(\"Venv not set up yet\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: gray; }\")\n\n def _on_reinstall(self):\n \"\"\"Remove the managed venv so it will be recreated on next Translate.\"\"\"\n import shutil as _shutil\n venv_dir = _get_plugin_data_dir() / \"venv\"\n if venv_dir.exists():\n try:\n _shutil.rmtree(str(venv_dir))\n except Exception as e:\n self.adv_status.setText(f\"Failed to remove venv: {e}\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: red; }\")\n return\n self.python_display.setText(\"Not configured — venv removed\")\n self.adv_status.setText(\"Venv removed. Click Translate to reinstall.\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: orange; }\")\n\n def _load_values(self):\n self.python_path.setText(plugin_prefs[\"python_path\"])\n self.llm_key.setText(plugin_prefs[\"llm_key\"])\n self.llm_url.setText(plugin_prefs[\"llm_url\"])\n self.llm_model.setText(plugin_prefs[\"llm_model\"])\n\n lang_value = plugin_prefs[\"target_language\"]\n for i, (_, value) in enumerate(LANGUAGE_OPTIONS):\n if value == lang_value:\n self.target_language.setCurrentIndex(i)\n break\n\n self.concurrency.setValue(plugin_prefs[\"concurrency\"])\n self.user_prompt.setPlainText(plugin_prefs[\"user_prompt\"])\n\n def save_settings(self):\n plugin_prefs[\"python_path\"] = self.python_path.text().strip()\n plugin_prefs[\"llm_key\"] = self.llm_key.text().strip()\n plugin_prefs[\"llm_url\"] = self.llm_url.text().strip()\n plugin_prefs[\"llm_model\"] = self.llm_model.text().strip()\n plugin_prefs[\"target_language\"] = self.target_language.currentData()\n plugin_prefs[\"concurrency\"] = self.concurrency.value()\n plugin_prefs[\"user_prompt\"] = self.user_prompt.toPlainText().strip()\n"}, "3de54111b3eee75db580d953a78e36183d1fbb4a": {"t": 1785768604.417199, "n": 15893, "h": "fe0848ed91a0fc9b", "c": "\"\"\"\nconfig.py - Calibre plugin configuration panel.\n\nSettings are persisted via Calibre's JSONConfig mechanism.\n\"\"\"\n\nimport os\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nfrom calibre.utils.config import JSONConfig\nfrom qt.core import (\n QFormLayout,\n QGroupBox,\n QHBoxLayout,\n QLabel,\n QComboBox,\n QLineEdit,\n QPushButton,\n QSpinBox,\n QTextEdit,\n QVBoxLayout,\n QWidget,\n)\n\n# Stored at ~/.config/calibre/plugins/epub_translate.json\nplugin_prefs = JSONConfig(\"plugins/epub_translate\")\n\nLANGUAGE_OPTIONS = [\n (\"Simplified Chinese\", \"SIMPLIFIED_CHINESE\"),\n (\"Traditional Chinese\", \"TRADITIONAL_CHINESE\"),\n (\"English\", \"ENGLISH\"),\n (\"Japanese\", \"JAPANESE\"),\n (\"Korean\", \"KOREAN\"),\n (\"French\", \"FRENCH\"),\n (\"German\", \"GERMAN\"),\n (\"Spanish\", \"SPANISH\"),\n (\"Russian\", \"RUSSIAN\"),\n (\"Portuguese\", \"PORTUGUESE\"),\n]\n\n# Defaults\nplugin_prefs.defaults[\"python_path\"] = \"\" # manual override; empty = use auto-detected venv\nplugin_prefs.defaults[\"target_language\"] = \"SIMPLIFIED_CHINESE\"\nplugin_prefs.defaults[\"concurrency\"] = 3\nplugin_prefs.defaults[\"user_prompt\"] = \"\"\nplugin_prefs.defaults[\"llm_key\"] = \"\"\nplugin_prefs.defaults[\"llm_url\"] = \"https://api.deepseek.com\"\nplugin_prefs.defaults[\"llm_model\"] = \"deepseek-chat\"\nplugin_prefs.defaults[\"llm_token_encoding\"] = \"o200k_base\"\nplugin_prefs.defaults[\"llm_timeout\"] = 120.0\nplugin_prefs.defaults[\"llm_top_p\"] = 0.6\nplugin_prefs.defaults[\"llm_temperature\"] = 0.85\nplugin_prefs.defaults[\"llm_retry_times\"] = 5\nplugin_prefs.defaults[\"llm_retry_interval\"] = 6.0\n\n\n# ── venv auto-setup helpers ───────────────────────────────────────────────────\n\n\ndef _get_plugin_data_dir() -> Path:\n \"\"\"Return the plugin's private data directory (created if absent).\"\"\"\n from calibre.utils.config import config_dir\n d = Path(config_dir) / \"plugins\" / \"epub_translate\"\n d.mkdir(parents=True, exist_ok=True)\n return d\n\n\ndef _get_venv_python() -> \"str | None\":\n \"\"\"Return the venv python path if the venv exists and epub_translator is importable.\"\"\"\n venv_dir = _get_plugin_data_dir() / \"venv\"\n if sys.platform == \"win32\":\n python = venv_dir / \"Scripts\" / \"python.exe\"\n else:\n python = venv_dir / \"bin\" / \"python\"\n if not python.exists():\n return None\n try:\n r = subprocess.run(\n [str(python), \"-c\", \"import epub_translator\"],\n capture_output=True, timeout=10,\n )\n if r.returncode == 0:\n return str(python)\n except Exception:\n pass\n return None\n\n\ndef _find_system_python() -> \"str | None\":\n \"\"\"Search for a system Python >=3.10 to use as the venv base.\n\n We prefer version-specific binaries (3.13, 3.12, 3.11) over the generic\n 'python3', because the generic name may resolve to a very new release that\n epub-translator does not yet support. The actual compatibility check is\n left to pip at install time, so this function never needs updating when\n epub-translator raises or lowers its Python requirements.\n \"\"\"\n import shutil\n\n if sys.platform == \"win32\":\n candidates = [\n \"python3.13\", \"python3.12\", \"python3.11\", \"python3.10\",\n \"python\", \"python3\", \"py\",\n ]\n else:\n candidates = [\n # Prefer stable, well-supported releases first\n \"python3.13\",\n \"python3.12\",\n \"python3.11\",\n \"python3.10\",\n \"/opt/homebrew/bin/python3.13\",\n \"/opt/homebrew/bin/python3.12\",\n \"/opt/homebrew/bin/python3.11\",\n \"/opt/homebrew/bin/python3.10\",\n \"/usr/local/bin/python3.13\",\n \"/usr/local/bin/python3.12\",\n \"/usr/local/bin/python3.11\",\n \"/usr/local/bin/python3.10\",\n # Generic fallback — version unknown until checked\n \"python3\",\n \"/opt/homebrew/bin/python3\",\n \"/usr/local/bin/python3\",\n \"/usr/bin/python3\",\n \"python\",\n ]\n\n check_script = (\n \"import sys; assert sys.version_info >= (3, 10), \"\n \"f'too old: {sys.version}'; print('ok')\"\n )\n\n for name in candidates:\n if not os.path.isabs(name):\n full = shutil.which(name)\n if not full:\n continue\n else:\n full = name\n if not os.path.exists(full):\n continue\n\n # On Windows, \"py\" is the launcher — pass \"-3\" to select Python 3\n cmd = [full, \"-3\", \"-c\", check_script] if name == \"py\" else [full, \"-c\", check_script]\n\n try:\n r = subprocess.run(cmd, capture_output=True, text=True, timeout=10)\n if r.returncode == 0:\n if name == \"py\":\n path_r = subprocess.run(\n [full, \"-3\", \"-c\", \"import sys; print(sys.executable)\"],\n capture_output=True, text=True, timeout=10,\n )\n if path_r.returncode == 0:\n return path_r.stdout.strip()\n return full\n except Exception:\n continue\n\n return None\n\n\ndef setup_venv(on_status=None) -> None:\n \"\"\"Create the plugin venv and pip-install epub-translator.\n\n Calls on_status(str) for progress messages.\n Raises RuntimeError on failure.\n \"\"\"\n def _status(msg: str):\n if on_status:\n on_status(msg)\n\n _status(\"Finding system Python 3.10+...\")\n system_python = _find_system_python()\n if not system_python:\n raise RuntimeError(\n \"No Python 3.10+ found on this system.\\n\"\n \"Please install Python 3.13 or 3.12 from https://www.python.org/downloads/\"\n )\n _status(f\"Found Python: {system_python}\")\n\n venv_dir = _get_plugin_data_dir() / \"venv\"\n _status(f\"Creating virtual environment at {venv_dir} ...\")\n try:\n r = subprocess.run(\n [system_python, \"-m\", \"venv\", str(venv_dir)],\n capture_output=True, text=True, timeout=60,\n )\n if r.returncode != 0:\n raise RuntimeError(f\"venv creation failed:\\n{r.stderr or r.stdout}\")\n except subprocess.TimeoutExpired:\n raise RuntimeError(\"venv creation timed out (60 s).\")\n\n if sys.platform == \"win32\":\n pip = venv_dir / \"Scripts\" / \"pip\"\n python = venv_dir / \"Scripts\" / \"python.exe\"\n else:\n pip = venv_dir / \"bin\" / \"pip\"\n python = venv_dir / \"bin\" / \"python\"\n\n _status(\"Installing epub-translator (this may take a minute)...\")\n try:\n r = subprocess.run(\n [str(pip), \"install\", \"--upgrade\", \"epub-translator\"],\n capture_output=True, text=True, timeout=300,\n )\n if r.returncode != 0:\n pip_output = (r.stderr or r.stdout)\n # Detect Python version incompatibility and give an actionable message\n if \"Requires-Python\" in pip_output or \"No matching distribution\" in pip_output:\n import re\n # Extract the constraint from lines like \"0.1.9 Requires-Python >=3.11, <3.14\"\n match = re.search(r\"Requires-Python\\s+([^\\n;]+)\", pip_output)\n constraint = match.group(1).strip() if match else \"unknown\"\n py_ver = subprocess.run(\n [str(python), \"-c\", \"import sys; print(sys.version.split()[0])\"],\n capture_output=True, text=True,\n ).stdout.strip()\n import shutil as _shutil\n _shutil.rmtree(str(venv_dir), ignore_errors=True)\n raise RuntimeError(\n f\"Python {py_ver} is not compatible with epub-translator \"\n f\"(requires {constraint}).\\n\\n\"\n \"Please install a compatible Python version \"\n \"(e.g. 3.13 or 3.12) from https://www.python.org/downloads/\\n\"\n \"Then click Translate again.\"\n )\n raise RuntimeError(f\"pip install failed:\\n{pip_output[-800:]}\")\n except subprocess.TimeoutExpired:\n raise RuntimeError(\"pip install timed out (5 min).\")\n\n _status(\"Verifying installation...\")\n r = subprocess.run(\n [str(python), \"-c\",\n \"import importlib.metadata, epub_translator; \"\n \"print(importlib.metadata.version('epub-translator'))\"],\n capture_output=True, text=True, timeout=15,\n )\n if r.returncode != 0:\n detail = (r.stderr or r.stdout).strip()[-600:]\n raise RuntimeError(f\"epub-translator installed but import failed:\\n{detail}\")\n _status(f\"epub-translator {r.stdout.strip()} installed successfully.\")\n\n\n# ── Config helpers ────────────────────────────────────────────────────────────\n\n\ndef get_effective_config() -> dict:\n \"\"\"Build the config dict from plugin pref fields.\"\"\"\n return {\n \"llm\": {\n \"key\": plugin_prefs[\"llm_key\"],\n \"url\": plugin_prefs[\"llm_url\"],\n \"model\": plugin_prefs[\"llm_model\"],\n \"token_encoding\": plugin_prefs[\"llm_token_encoding\"],\n \"timeout\": plugin_prefs[\"llm_timeout\"],\n \"top_p\": plugin_prefs[\"llm_top_p\"],\n \"temperature\": plugin_prefs[\"llm_temperature\"],\n \"retry_times\": plugin_prefs[\"llm_retry_times\"],\n \"retry_interval_seconds\": plugin_prefs[\"llm_retry_interval\"],\n },\n \"target_language\": plugin_prefs[\"target_language\"],\n \"concurrency\": plugin_prefs[\"concurrency\"],\n \"user_prompt\": plugin_prefs[\"user_prompt\"] or None,\n }\n\n\n# ── Settings UI ───────────────────────────────────────────────────────────────\n\n\nclass ConfigWidget(QWidget):\n def __init__(self):\n super().__init__()\n self._build_ui()\n self._load_values()\n\n def _build_ui(self):\n layout = QVBoxLayout(self)\n\n # --- API Settings ---\n api_group = QGroupBox(\"API Settings\")\n api_form = QFormLayout(api_group)\n\n self.llm_key = QLineEdit()\n self.llm_key.setEchoMode(QLineEdit.EchoMode.Password)\n self.llm_key.setPlaceholderText(\"sk-...\")\n api_form.addRow(\"API Key:\", self.llm_key)\n\n self.llm_url = QLineEdit()\n self.llm_url.setPlaceholderText(\"https://api.deepseek.com\")\n api_form.addRow(\"API URL:\", self.llm_url)\n\n self.llm_model = QLineEdit()\n self.llm_model.setPlaceholderText(\"deepseek-chat\")\n api_form.addRow(\"Model:\", self.llm_model)\n\n layout.addWidget(api_group)\n\n # --- Translation Settings ---\n trans_group = QGroupBox(\"Translation Settings\")\n trans_form = QFormLayout(trans_group)\n\n self.target_language = QComboBox()\n for label, value in LANGUAGE_OPTIONS:\n self.target_language.addItem(label, value)\n trans_form.addRow(\"Target language:\", self.target_language)\n\n self.concurrency = QSpinBox()\n self.concurrency.setRange(1, 20)\n self.concurrency.setSuffix(\" concurrent requests\")\n trans_form.addRow(\"Concurrency:\", self.concurrency)\n\n self.user_prompt = QTextEdit()\n self.user_prompt.setMaximumHeight(80)\n self.user_prompt.setPlaceholderText(\"(Optional) Custom instructions appended to the system prompt\")\n trans_form.addRow(\"User prompt:\", self.user_prompt)\n\n layout.addWidget(trans_group)\n\n # --- Advanced ---\n adv_group = QGroupBox(\"Advanced\")\n adv_form = QFormLayout(adv_group)\n\n python_row = QHBoxLayout()\n self.python_display = QLineEdit()\n self.python_display.setReadOnly(True)\n python_row.addWidget(self.python_display, 1)\n self.reinstall_btn = QPushButton(\"Reinstall\")\n self.reinstall_btn.setFixedWidth(80)\n self.reinstall_btn.clicked.connect(self._on_reinstall)\n python_row.addWidget(self.reinstall_btn)\n adv_form.addRow(\"Python:\", python_row)\n\n self.python_path = QLineEdit()\n self.python_path.setPlaceholderText(\"(optional) /path/to/python — overrides auto-detected venv\")\n adv_form.addRow(\"Override:\", self.python_path)\n\n self.adv_status = QLineEdit()\n self.adv_status.setReadOnly(True)\n adv_form.addRow(\"Status:\", self.adv_status)\n\n layout.addWidget(adv_group)\n layout.addStretch()\n\n self._refresh_venv_status()\n\n def _refresh_venv_status(self):\n \"\"\"Update the Python path display and status label in the Advanced section.\"\"\"\n venv_python = _get_venv_python()\n if venv_python:\n self.python_display.setText(f\"auto: {venv_python}\")\n try:\n r = subprocess.run(\n [venv_python, \"-c\",\n \"import sys, importlib.metadata; \"\n \"print(sys.version.split()[0], \"\n \"importlib.metadata.version('epub-translator'))\"],\n capture_output=True, text=True, timeout=10,\n )\n if r.returncode == 0:\n parts = r.stdout.strip().split()\n py_ver = parts[0] if parts else \"?\"\n pkg_ver = parts[1] if len(parts) > 1 else \"?\"\n self.adv_status.setText(f\"OK — Python {py_ver}, epub-translator {pkg_ver}\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: green; }\")\n else:\n self.adv_status.setText(\"Warning: import check failed\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: orange; }\")\n except Exception:\n self.adv_status.setText(\"Warning: could not check version\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: orange; }\")\n else:\n manual = plugin_prefs.get(\"python_path\", \"\").strip()\n if manual:\n self.python_display.setText(f\"manual: {manual}\")\n else:\n self.python_display.setText(\"Not configured — click Translate to auto-install\")\n self.adv_status.setText(\"Venv not set up yet\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: gray; }\")\n\n def _on_reinstall(self):\n \"\"\"Remove the managed venv so it will be recreated on next Translate.\"\"\"\n import shutil as _shutil\n venv_dir = _get_plugin_data_dir() / \"venv\"\n if venv_dir.exists():\n try:\n _shutil.rmtree(str(venv_dir))\n except Exception as e:\n self.adv_status.setText(f\"Failed to remove venv: {e}\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: red; }\")\n return\n self.python_display.setText(\"Not configured — venv removed\")\n self.adv_status.setText(\"Venv removed. Click Translate to reinstall.\")\n self.adv_status.setStyleSheet(\"QLineEdit { color: orange; }\")\n\n def _load_values(self):\n self.python_path.setText(plugin_prefs[\"python_path\"])\n self.llm_key.setText(plugin_prefs[\"llm_key\"])\n self.llm_url.setText(plugin_prefs[\"llm_url\"])\n self.llm_model.setText(plugin_prefs[\"llm_model\"])\n\n lang_value = plugin_prefs[\"target_language\"]\n for i, (_, value) in enumerate(LANGUAGE_OPTIONS):\n if value == lang_value:\n self.target_language.setCurrentIndex(i)\n break\n\n self.concurrency.setValue(plugin_prefs[\"concurrency\"])\n self.user_prompt.setPlainText(plugin_prefs[\"user_prompt\"])\n\n def save_settings(self):\n plugin_prefs[\"python_path\"] = self.python_path.text().strip()\n plugin_prefs[\"llm_key\"] = self.llm_key.text().strip()\n plugin_prefs[\"llm_url\"] = self.llm_url.text().strip()\n plugin_prefs[\"llm_model\"] = self.llm_model.text().strip()\n plugin_prefs[\"target_language\"] = self.target_language.currentData()\n plugin_prefs[\"concurrency\"] = self.concurrency.value()\n plugin_prefs[\"user_prompt\"] = self.user_prompt.toPlainText().strip()\n"}, "aa22d585a8fbe8d6070350e0dca5dac129648db6": {"t": 1785768607.7136784, "n": 6674, "h": "ba35e81ca7487825", "c": "\"\"\"\nconfig.py - Calibre plugin configuration panel.\n\nSettings are persisted via Calibre's JSONConfig mechanism.\nKeys mirror those in config.json so they stay consistent.\n\"\"\"\n\nfrom calibre.utils.config import JSONConfig\nfrom qt.core import (\n QComboBox,\n QFormLayout,\n QGroupBox,\n QLabel,\n QLineEdit,\n QSpinBox,\n Qt,\n QTextEdit,\n QVBoxLayout,\n QWidget,\n)\n\n# Stored at ~/.config/calibre/plugins/epub_translate.json\nplugin_prefs = JSONConfig(\"plugins/epub_translate\")\n\nLANGUAGE_OPTIONS = [\n (\"Simplified Chinese\", \"SIMPLIFIED_CHINESE\"),\n (\"Traditional Chinese\", \"TRADITIONAL_CHINESE\"),\n (\"English\", \"ENGLISH\"),\n (\"Japanese\", \"JAPANESE\"),\n (\"Korean\", \"KOREAN\"),\n (\"French\", \"FRENCH\"),\n (\"German\", \"GERMAN\"),\n (\"Spanish\", \"SPANISH\"),\n (\"Russian\", \"RUSSIAN\"),\n (\"Portuguese\", \"PORTUGUESE\"),\n]\n\n# Defaults\nplugin_prefs.defaults[\"project_path\"] = \"/Users/yubai/epub-translate\"\nplugin_prefs.defaults[\"target_language\"] = \"SIMPLIFIED_CHINESE\"\nplugin_prefs.defaults[\"concurrency\"] = 3\nplugin_prefs.defaults[\"user_prompt\"] = \"\"\nplugin_prefs.defaults[\"llm_key\"] = \"\"\nplugin_prefs.defaults[\"llm_url\"] = \"https://api.deepseek.com\"\nplugin_prefs.defaults[\"llm_model\"] = \"deepseek-chat\"\nplugin_prefs.defaults[\"llm_token_encoding\"] = \"o200k_base\"\nplugin_prefs.defaults[\"llm_timeout\"] = 120.0\nplugin_prefs.defaults[\"llm_top_p\"] = 0.6\nplugin_prefs.defaults[\"llm_temperature\"] = 0.85\nplugin_prefs.defaults[\"llm_retry_times\"] = 5\nplugin_prefs.defaults[\"llm_retry_interval\"] = 6.0\nplugin_prefs.defaults[\"use_config_json\"] = True # True = read project_path/config.json\n\n\ndef get_effective_config() -> dict:\n \"\"\"Return the config dict passed to translate_worker.py.\n\n When use_config_json is True, reads project_path/config.json directly.\n Otherwise builds the dict from plugin prefs.\n \"\"\"\n import json\n from pathlib import Path\n\n if plugin_prefs[\"use_config_json\"]:\n config_path = Path(plugin_prefs[\"project_path\"]) / \"config.json\"\n if config_path.exists():\n with open(config_path, encoding=\"utf-8\") as f:\n return json.load(f)\n\n return {\n \"llm\": {\n \"key\": plugin_prefs[\"llm_key\"],\n \"url\": plugin_prefs[\"llm_url\"],\n \"model\": plugin_prefs[\"llm_model\"],\n \"token_encoding\": plugin_prefs[\"llm_token_encoding\"],\n \"timeout\": plugin_prefs[\"llm_timeout\"],\n \"top_p\": plugin_prefs[\"llm_top_p\"],\n \"temperature\": plugin_prefs[\"llm_temperature\"],\n \"retry_times\": plugin_prefs[\"llm_retry_times\"],\n \"retry_interval_seconds\": plugin_prefs[\"llm_retry_interval\"],\n },\n \"target_language\": plugin_prefs[\"target_language\"],\n \"concurrency\": plugin_prefs[\"concurrency\"],\n \"user_prompt\": plugin_prefs[\"user_prompt\"] or None,\n }\n\n\nclass ConfigWidget(QWidget):\n def __init__(self):\n super().__init__()\n self._build_ui()\n self._load_values()\n\n def _build_ui(self):\n layout = QVBoxLayout(self)\n\n # Project path\n path_group = QGroupBox(\"epub-translate Project\")\n path_form = QFormLayout(path_group)\n\n self.project_path = QLineEdit()\n self.project_path.setPlaceholderText(\"/Users/yubai/epub-translate\")\n path_form.addRow(\"Project path:\", self.project_path)\n\n note = QLabel(\"The plugin uses .venv/bin/python and config.json from this directory.\")\n note.setWordWrap(True)\n note.setStyleSheet(\"color: gray; font-size: 11px;\")\n path_form.addRow(\"\", note)\n\n layout.addWidget(path_group)\n\n # Translation settings\n trans_group = QGroupBox(\"Translation Settings\")\n trans_form = QFormLayout(trans_group)\n\n self.target_language = QComboBox()\n for label, value in LANGUAGE_OPTIONS:\n self.target_language.addItem(label, value)\n trans_form.addRow(\"Target language:\", self.target_language)\n\n self.concurrency = QSpinBox()\n self.concurrency.setRange(1, 20)\n self.concurrency.setSuffix(\" concurrent requests\")\n trans_form.addRow(\"Concurrency:\", self.concurrency)\n\n self.user_prompt = QTextEdit()\n self.user_prompt.setMaximumHeight(80)\n self.user_prompt.setPlaceholderText(\"(Optional) Custom instructions appended to the system prompt\")\n trans_form.addRow(\"User prompt:\", self.user_prompt)\n\n layout.addWidget(trans_group)\n\n # API overrides (only active when the group is checked)\n api_group = QGroupBox(\"API Settings (overrides config.json llm section)\")\n api_group.setCheckable(True)\n api_group.setChecked(False)\n api_form = QFormLayout(api_group)\n self.api_group = api_group\n\n self.llm_key = QLineEdit()\n self.llm_key.setEchoMode(QLineEdit.EchoMode.Password)\n self.llm_key.setPlaceholderText(\"sk-...\")\n api_form.addRow(\"API Key:\", self.llm_key)\n\n self.llm_url = QLineEdit()\n self.llm_url.setPlaceholderText(\"https://api.deepseek.com\")\n api_form.addRow(\"API URL:\", self.llm_url)\n\n self.llm_model = QLineEdit()\n self.llm_model.setPlaceholderText(\"deepseek-chat\")\n api_form.addRow(\"Model:\", self.llm_model)\n\n layout.addWidget(api_group)\n layout.addStretch()\n\n def _load_values(self):\n self.project_path.setText(plugin_prefs[\"project_path\"])\n\n lang_value = plugin_prefs[\"target_language\"]\n for i, (_, value) in enumerate(LANGUAGE_OPTIONS):\n if value == lang_value:\n self.target_language.setCurrentIndex(i)\n break\n\n self.concurrency.setValue(plugin_prefs[\"concurrency\"])\n self.user_prompt.setPlainText(plugin_prefs[\"user_prompt\"])\n self.llm_key.setText(plugin_prefs[\"llm_key\"])\n self.llm_url.setText(plugin_prefs[\"llm_url\"])\n self.llm_model.setText(plugin_prefs[\"llm_model\"])\n\n if plugin_prefs[\"llm_key\"]:\n self.api_group.setChecked(True)\n\n def save_settings(self):\n plugin_prefs[\"project_path\"] = self.project_path.text().strip()\n plugin_prefs[\"target_language\"] = self.target_language.currentData()\n plugin_prefs[\"concurrency\"] = self.concurrency.value()\n plugin_prefs[\"user_prompt\"] = self.user_prompt.toPlainText().strip()\n\n if self.api_group.isChecked():\n plugin_prefs[\"llm_key\"] = self.llm_key.text().strip()\n plugin_prefs[\"llm_url\"] = self.llm_url.text().strip()\n plugin_prefs[\"llm_model\"] = self.llm_model.text().strip()\n plugin_prefs[\"use_config_json\"] = False\n else:\n plugin_prefs[\"use_config_json\"] = True\n"}, "ffd41a6a342dbee10e487bd811fa946986150584": {"t": 1785768610.0210218, "n": 6347, "h": "7f6831162cdf50df", "c": "\"\"\"\nconfig.py — Calibre 插件配置面板\n\n使用 calibre JSONConfig 持久化存储,key 与 config.json 保持一致。\n\"\"\"\n\nfrom calibre.utils.config import JSONConfig\nfrom qt.core import (\n QComboBox,\n QFormLayout,\n QGroupBox,\n QLabel,\n QLineEdit,\n QSpinBox,\n Qt,\n QTextEdit,\n QVBoxLayout,\n QWidget,\n)\n\n# 插件配置存储(~/.config/calibre/plugins/epub_translate.json)\nplugin_prefs = JSONConfig(\"plugins/epub_translate\")\n\nLANGUAGE_OPTIONS = [\n (\"简体中文\", \"SIMPLIFIED_CHINESE\"),\n (\"繁體中文\", \"TRADITIONAL_CHINESE\"),\n (\"English\", \"ENGLISH\"),\n (\"日本語\", \"JAPANESE\"),\n (\"한국어\", \"KOREAN\"),\n (\"Français\", \"FRENCH\"),\n (\"Deutsch\", \"GERMAN\"),\n (\"Español\", \"SPANISH\"),\n (\"Русский\", \"RUSSIAN\"),\n (\"Português\", \"PORTUGUESE\"),\n]\n\n# 默认值\nplugin_prefs.defaults[\"project_path\"] = \"/Users/yubai/epub-translate\"\nplugin_prefs.defaults[\"target_language\"] = \"SIMPLIFIED_CHINESE\"\nplugin_prefs.defaults[\"concurrency\"] = 3\nplugin_prefs.defaults[\"user_prompt\"] = \"\"\nplugin_prefs.defaults[\"llm_key\"] = \"\"\nplugin_prefs.defaults[\"llm_url\"] = \"https://api.deepseek.com\"\nplugin_prefs.defaults[\"llm_model\"] = \"deepseek-chat\"\nplugin_prefs.defaults[\"llm_token_encoding\"] = \"o200k_base\"\nplugin_prefs.defaults[\"llm_timeout\"] = 120.0\nplugin_prefs.defaults[\"llm_top_p\"] = 0.6\nplugin_prefs.defaults[\"llm_temperature\"] = 0.85\nplugin_prefs.defaults[\"llm_retry_times\"] = 5\nplugin_prefs.defaults[\"llm_retry_interval\"] = 6.0\nplugin_prefs.defaults[\"use_config_json\"] = True # True = 读取 project_path/config.json\n\n\ndef get_effective_config() -> dict:\n \"\"\"返回用于 translate_worker.py 的 config dict。\n 如果 use_config_json=True,直接读取 project_path/config.json,\n 否则从插件配置构建。\n \"\"\"\n import json\n from pathlib import Path\n\n if plugin_prefs[\"use_config_json\"]:\n config_path = Path(plugin_prefs[\"project_path\"]) / \"config.json\"\n if config_path.exists():\n with open(config_path, encoding=\"utf-8\") as f:\n return json.load(f)\n\n # 从插件配置构建\n return {\n \"llm\": {\n \"key\": plugin_prefs[\"llm_key\"],\n \"url\": plugin_prefs[\"llm_url\"],\n \"model\": plugin_prefs[\"llm_model\"],\n \"token_encoding\": plugin_prefs[\"llm_token_encoding\"],\n \"timeout\": plugin_prefs[\"llm_timeout\"],\n \"top_p\": plugin_prefs[\"llm_top_p\"],\n \"temperature\": plugin_prefs[\"llm_temperature\"],\n \"retry_times\": plugin_prefs[\"llm_retry_times\"],\n \"retry_interval_seconds\": plugin_prefs[\"llm_retry_interval\"],\n },\n \"target_language\": plugin_prefs[\"target_language\"],\n \"concurrency\": plugin_prefs[\"concurrency\"],\n \"user_prompt\": plugin_prefs[\"user_prompt\"] or None,\n }\n\n\nclass ConfigWidget(QWidget):\n def __init__(self):\n super().__init__()\n self._build_ui()\n self._load_values()\n\n def _build_ui(self):\n layout = QVBoxLayout(self)\n\n # === 项目路径 ===\n path_group = QGroupBox(\"epub-translate 项目\")\n path_form = QFormLayout(path_group)\n\n self.project_path = QLineEdit()\n self.project_path.setPlaceholderText(\"/Users/yubai/epub-translate\")\n path_form.addRow(\"项目路径:\", self.project_path)\n\n note = QLabel(\"插件将使用该路径下的 .venv/bin/python 和 config.json\")\n note.setWordWrap(True)\n note.setStyleSheet(\"color: gray; font-size: 11px;\")\n path_form.addRow(\"\", note)\n\n layout.addWidget(path_group)\n\n # === 翻译设置 ===\n trans_group = QGroupBox(\"翻译设置\")\n trans_form = QFormLayout(trans_group)\n\n self.target_language = QComboBox()\n for label, value in LANGUAGE_OPTIONS:\n self.target_language.addItem(label, value)\n trans_form.addRow(\"目标语言:\", self.target_language)\n\n self.concurrency = QSpinBox()\n self.concurrency.setRange(1, 20)\n self.concurrency.setSuffix(\" 个并发请求\")\n trans_form.addRow(\"并发数:\", self.concurrency)\n\n self.user_prompt = QTextEdit()\n self.user_prompt.setMaximumHeight(80)\n self.user_prompt.setPlaceholderText(\"(可选)追加到系统提示词的自定义指令\")\n trans_form.addRow(\"自定义提示词:\", self.user_prompt)\n\n layout.addWidget(trans_group)\n\n # === API 配置(仅当不使用 config.json 时生效)===\n api_group = QGroupBox(\"API 配置(覆盖 config.json 中的 llm 配置)\")\n api_group.setCheckable(True)\n api_group.setChecked(False)\n api_form = QFormLayout(api_group)\n self.api_group = api_group\n\n self.llm_key = QLineEdit()\n self.llm_key.setEchoMode(QLineEdit.EchoMode.Password)\n self.llm_key.setPlaceholderText(\"sk-...\")\n api_form.addRow(\"API Key:\", self.llm_key)\n\n self.llm_url = QLineEdit()\n self.llm_url.setPlaceholderText(\"https://api.deepseek.com\")\n api_form.addRow(\"API URL:\", self.llm_url)\n\n self.llm_model = QLineEdit()\n self.llm_model.setPlaceholderText(\"deepseek-chat\")\n api_form.addRow(\"模型:\", self.llm_model)\n\n layout.addWidget(api_group)\n layout.addStretch()\n\n def _load_values(self):\n self.project_path.setText(plugin_prefs[\"project_path\"])\n\n lang_value = plugin_prefs[\"target_language\"]\n for i, (_, value) in enumerate(LANGUAGE_OPTIONS):\n if value == lang_value:\n self.target_language.setCurrentIndex(i)\n break\n\n self.concurrency.setValue(plugin_prefs[\"concurrency\"])\n self.user_prompt.setPlainText(plugin_prefs[\"user_prompt\"])\n self.llm_key.setText(plugin_prefs[\"llm_key\"])\n self.llm_url.setText(plugin_prefs[\"llm_url\"])\n self.llm_model.setText(plugin_prefs[\"llm_model\"])\n\n # 如果有独立 API key,展开 API 配置组\n if plugin_prefs[\"llm_key\"]:\n self.api_group.setChecked(True)\n\n def save_settings(self):\n plugin_prefs[\"project_path\"] = self.project_path.text().strip()\n plugin_prefs[\"target_language\"] = self.target_language.currentData()\n plugin_prefs[\"concurrency\"] = self.concurrency.value()\n plugin_prefs[\"user_prompt\"] = self.user_prompt.toPlainText().strip()\n\n if self.api_group.isChecked():\n plugin_prefs[\"llm_key\"] = self.llm_key.text().strip()\n plugin_prefs[\"llm_url\"] = self.llm_url.text().strip()\n plugin_prefs[\"llm_model\"] = self.llm_model.text().strip()\n plugin_prefs[\"use_config_json\"] = False\n else:\n plugin_prefs[\"use_config_json\"] = True\n"}}}} |