diff --git a/gui/config_tab.py b/gui/config_tab.py index bf87301..436586f 100644 --- a/gui/config_tab.py +++ b/gui/config_tab.py @@ -43,7 +43,16 @@ class ModelFetchThread(QThread): def run(self): models = [] errors = [] - for fetcher in (self._fetch_openai, self._fetch_anthropic, self._fetch_gemini): + _url = self.api_url.lower() + # Only attempt each provider's fetcher when the configured URL matches. + # Avoids sending a DeepSeek (or other) key to Anthropic and getting a + # spurious 401 authentication error. + fetchers = [self._fetch_openai] + if not _url or "anthropic" in _url: + fetchers.append(self._fetch_anthropic) + if not _url or "googleapis" in _url or "gemini" in _url: + fetchers.append(self._fetch_gemini) + for fetcher in fetchers: try: models.extend(fetcher()) except Exception as exc: @@ -59,11 +68,14 @@ class ModelFetchThread(QThread): if self.api_url: kwargs["base_url"] = self.api_url client = openai.OpenAI(**kwargs) + all_models = [m.id for m in client.models.list()] + # When using a custom URL (non-OpenAI provider like DeepSeek), return all + # model IDs unfiltered. For the default OpenAI endpoint, keep only the + # GPT / o-series models to avoid a cluttered list. + if self.api_url: + return sorted(all_models) prefixes = ("gpt-", "o1", "o2", "o3", "o4", "chatgpt") - return sorted( - m.id for m in client.models.list() - if any(m.id.lower().startswith(p) for p in prefixes) - ) + return sorted(m for m in all_models if any(m.lower().startswith(p) for p in prefixes)) def _fetch_anthropic(self): import anthropic @@ -313,7 +325,7 @@ class ConfigTab(QWidget): ("OpenAI", "https://api.openai.com/v1"), ("Claude (Anthropic)", "https://api.anthropic.com/v1"), ("Gemini", "https://generativelanguage.googleapis.com/v1beta/openai/"), - ("DeepSeek", "https://api.deepseek.com/v1"), + ("DeepSeek", "https://api.deepseek.com/v1/"), ] for _name, _url in _url_presets: _action = api_url_menu.addAction(_name) diff --git a/util/translation.py b/util/translation.py index cb9381b..1cbaa08 100644 --- a/util/translation.py +++ b/util/translation.py @@ -815,7 +815,16 @@ def translateText(system, user, history, penalty, formatType, model, numLines=No if not system or not str(system).strip(): raise ValueError("System content cannot be empty") - _is_claude = model and any(x in model.lower() for x in ("claude", "sonnet", "haiku", "opus")) + _live_api_check = os.getenv("api", "").strip() + # Only route to the native Anthropic SDK when the model looks like Claude AND + # the configured API URL is either unset (implying default Anthropic usage) or + # explicitly points at anthropic.com. Any other custom URL (e.g. DeepSeek, + # OpenAI proxy) should use the OpenAI-compatible path even for Claude-named models. + _is_claude = ( + model + and any(x in model.lower() for x in ("claude", "sonnet", "haiku", "opus")) + and (not _live_api_check or "anthropic" in _live_api_check.lower()) + ) _is_deepseek = model and "deepseek" in model.lower() # Build message list. @@ -1549,7 +1558,12 @@ def translateAI(text, history, config, filename=None, pbar=None, lock=None, mism # Track exact cache write size (static_system, constant across batches) # and accumulate non-cached (vocab + user + history) tokens per batch. - _is_claude_est = config.model and any(x in config.model.lower() for x in ("claude", "sonnet", "haiku", "opus")) + _est_api = os.getenv("api", "").strip() + _is_claude_est = ( + config.model + and any(x in config.model.lower() for x in ("claude", "sonnet", "haiku", "opus")) + and (not _est_api or "anthropic" in _est_api.lower()) + ) if _is_claude_est: # Use Anthropic's count_tokens API once to get the exact cached token count. # Only called on the first batch; result reused for all subsequent batches.