From 063b79477f5f264d59384b7cf4bdedc64a89c66a Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Fri, 26 Dec 2025 05:30:37 +0900 Subject: [PATCH 1/6] [Fix] Error Handling: Enhance authentication checks and add support for Groq and OpenRouter models. --- .../translation/translation_openrouter.py | 18 +++---- src-ui/logics/_useBackendErrorHandling.js | 52 +++++++++++++++++++ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src-python/models/translation/translation_openrouter.py b/src-python/models/translation/translation_openrouter.py index 20746bbf..5df2ec44 100644 --- a/src-python/models/translation/translation_openrouter.py +++ b/src-python/models/translation/translation_openrouter.py @@ -13,26 +13,20 @@ except Exception: from translation_utils import loadTranslatePromptConfig translation_lang = loadTranslationLanguages(path=".", force=True) -def _authentication_check(api_key: str) -> bool: +def _authentication_check(api_key: str, base_url: str | None = None) -> bool: """Check if the provided API key is valid by attempting to list models. """ try: - client = OpenAI( - api_key=api_key, - base_url="https://openrouter.ai/api/v1", - ) + client = OpenAI(api_key=api_key, base_url=base_url) client.models.list() return True except Exception: return False -def _get_available_text_models(api_key: str) -> list[str]: +def _get_available_text_models(api_key: str, base_url: str | None = None) -> list[str]: """Extract only OpenRouter models suitable for translation and chat applications. """ - client = OpenAI( - api_key=api_key, - base_url="https://openrouter.ai/api/v1", - ) + client = OpenAI(api_key=api_key, base_url=base_url) res = client.models.list() allowed_models = [] @@ -90,13 +84,13 @@ class OpenRouterClient: self.openrouter_llm = None def getModelList(self) -> list[str]: - return _get_available_text_models(self.api_key) if self.api_key else [] + return _get_available_text_models(self.api_key, self.base_url) if self.api_key else [] def getAuthKey(self) -> str: return self.api_key def setAuthKey(self, api_key: str) -> bool: - result = _authentication_check(api_key) + result = _authentication_check(api_key, self.base_url) if result: self.api_key = api_key return result diff --git a/src-ui/logics/_useBackendErrorHandling.js b/src-ui/logics/_useBackendErrorHandling.js index 4d494fe9..fce938f3 100644 --- a/src-ui/logics/_useBackendErrorHandling.js +++ b/src-ui/logics/_useBackendErrorHandling.js @@ -48,6 +48,12 @@ export const _useBackendErrorHandling = () => { updateOpenAIAuthKey, updateSelectedOpenAIModel, + updateGroqAuthKey, + updateSelectedGroqModel, + + updateOpenRouterAuthKey, + updateSelectedOpenRouterModel, + updateLMStudioURL, updateSelectedLMStudioModel, @@ -220,6 +226,52 @@ export const _useBackendErrorHandling = () => { } return; + case "/set/data/groq_auth_key": + if (message === "Groq auth key is not valid") { + updateGroqAuthKey(data); + showNotification_Error(message, { category_id: "groq_auth_key" }); + } else if (message === "Authentication failure of Groq auth key") { + updateGroqAuthKey(data); + showNotification_Error(message, { category_id: "groq_auth_key" }); + } else { + updateGroqAuthKey(data); + showNotification_Error(message, { category_id: "groq_auth_key" }); + } + return; + + case "/set/data/selected_groq_model": + if (message === "Groq model is not valid") { + updateSelectedGroqModel(data); + showNotification_Error(message, { category_id: "selected_groq_model" }); + } else { + updateSelectedGroqModel(data); + showNotification_Error(message, { category_id: "selected_groq_model" }); + } + return; + + case "/set/data/openrouter_auth_key": + if (message === "OpenRouter auth key is not valid") { + updateOpenRouterAuthKey(data); + showNotification_Error(message, { category_id: "openrouter_auth_key" }); + } else if (message === "Authentication failure of OpenRouter auth key") { + updateOpenRouterAuthKey(data); + showNotification_Error(message, { category_id: "openrouter_auth_key" }); + } else { + updateOpenRouterAuthKey(data); + showNotification_Error(message, { category_id: "openrouter_auth_key" }); + } + return; + + case "/set/data/selected_openrouter_model": + if (message === "OpenRouter model is not valid") { + updateSelectedOpenRouterModel(data); + showNotification_Error(message, { category_id: "selected_openrouter_model" }); + } else { + updateSelectedOpenRouterModel(data); + showNotification_Error(message, { category_id: "selected_openrouter_model" }); + } + return; + case "/set/data/lmstudio_url": if (message === "LMStudio URL is not valid") { updateLMStudioURL(data); From bf1c0236b53af903bca64c4a54a420ac5a097172 Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Mon, 29 Dec 2025 16:27:27 +0900 Subject: [PATCH 2/6] [Fix] Error Handling: Improve network disconnection handling and enhance logging mechanisms. --- src-python/controller.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src-python/controller.py b/src-python/controller.py index 67a5f1c1..95a604c9 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -1643,8 +1643,8 @@ class Controller: self.run(200, self.run_mapping["selectable_plamo_model_list"], config.SELECTABLE_PLAMO_MODEL_LIST) if config.SELECTED_PLAMO_MODEL not in config.SELECTABLE_PLAMO_MODEL_LIST: config.SELECTED_PLAMO_MODEL = config.SELECTABLE_PLAMO_MODEL_LIST[0] - model.setTranslatorPlamoModel(model=config.SELECTED_PLAMO_MODEL) - self.run(200, self.run_mapping["selected_plamo_model"], config.SELECTED_PLAMO_MODEL) + model.setTranslatorPlamoModel(model=config.SELECTED_PLAMO_MODEL) + self.run(200, self.run_mapping["selected_plamo_model"], config.SELECTED_PLAMO_MODEL) model.updateTranslatorPlamoClient() self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} @@ -1755,8 +1755,8 @@ class Controller: self.run(200, self.run_mapping["selectable_gemini_model_list"], config.SELECTABLE_GEMINI_MODEL_LIST) if config.SELECTED_GEMINI_MODEL not in config.SELECTABLE_GEMINI_MODEL_LIST: config.SELECTED_GEMINI_MODEL = config.SELECTABLE_GEMINI_MODEL_LIST[0] - model.setTranslatorGeminiModel(model=config.SELECTED_GEMINI_MODEL) - self.run(200, self.run_mapping["selected_gemini_model"], config.SELECTED_GEMINI_MODEL) + model.setTranslatorGeminiModel(model=config.SELECTED_GEMINI_MODEL) + self.run(200, self.run_mapping["selected_gemini_model"], config.SELECTED_GEMINI_MODEL) model.updateTranslatorGeminiClient() self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} @@ -1868,8 +1868,8 @@ class Controller: self.run(200, self.run_mapping["selectable_openai_model_list"], config.SELECTABLE_OPENAI_MODEL_LIST) if config.SELECTED_OPENAI_MODEL not in config.SELECTABLE_OPENAI_MODEL_LIST: config.SELECTED_OPENAI_MODEL = config.SELECTABLE_OPENAI_MODEL_LIST[0] - model.setTranslatorOpenAIModel(model=config.SELECTED_OPENAI_MODEL) - self.run(200, self.run_mapping["selected_openai_model"], config.SELECTED_OPENAI_MODEL) + model.setTranslatorOpenAIModel(model=config.SELECTED_OPENAI_MODEL) + self.run(200, self.run_mapping["selected_openai_model"], config.SELECTED_OPENAI_MODEL) model.updateTranslatorOpenAIClient() self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} @@ -1981,8 +1981,8 @@ class Controller: self.run(200, self.run_mapping["selectable_groq_model_list"], config.SELECTABLE_GROQ_MODEL_LIST) if config.SELECTED_GROQ_MODEL not in config.SELECTABLE_GROQ_MODEL_LIST: config.SELECTED_GROQ_MODEL = config.SELECTABLE_GROQ_MODEL_LIST[0] - model.setTranslatorGroqModel(model=config.SELECTED_GROQ_MODEL) - self.run(200, self.run_mapping["selected_groq_model"], config.SELECTED_GROQ_MODEL) + model.setTranslatorGroqModel(model=config.SELECTED_GROQ_MODEL) + self.run(200, self.run_mapping["selected_groq_model"], config.SELECTED_GROQ_MODEL) model.updateTranslatorGroqClient() self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} @@ -2094,8 +2094,8 @@ class Controller: self.run(200, self.run_mapping["selectable_openrouter_model_list"], config.SELECTABLE_OPENROUTER_MODEL_LIST) if config.SELECTED_OPENROUTER_MODEL not in config.SELECTABLE_OPENROUTER_MODEL_LIST: config.SELECTED_OPENROUTER_MODEL = config.SELECTABLE_OPENROUTER_MODEL_LIST[0] - model.setTranslatorOpenRouterModel(model=config.SELECTED_OPENROUTER_MODEL) - self.run(200, self.run_mapping["selected_openrouter_model"], config.SELECTED_OPENROUTER_MODEL) + model.setTranslatorOpenRouterModel(model=config.SELECTED_OPENROUTER_MODEL) + self.run(200, self.run_mapping["selected_openrouter_model"], config.SELECTED_OPENROUTER_MODEL) model.updateTranslatorOpenRouterClient() self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} From 4031a5556d44459de6ecd386f08a45f269510f5c Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Mon, 29 Dec 2025 19:15:08 +0900 Subject: [PATCH 3/6] [Fix] Error Handling: Update authentication check to use OpenRouter API for key validation. --- .../models/translation/translation_openai.py | 2 +- .../translation/translation_openrouter.py | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src-python/models/translation/translation_openai.py b/src-python/models/translation/translation_openai.py index 796de430..5c21f36f 100644 --- a/src-python/models/translation/translation_openai.py +++ b/src-python/models/translation/translation_openai.py @@ -189,7 +189,7 @@ class OpenAIClient: return content.strip() if __name__ == "__main__": - AUTH_KEY = "OPENAI_API_KEY" + AUTH_KEY = input("OPENAI_API_KEY: ") client = OpenAIClient() client.setAuthKey(AUTH_KEY) models = client.getModelList() diff --git a/src-python/models/translation/translation_openrouter.py b/src-python/models/translation/translation_openrouter.py index 5df2ec44..16bd78ab 100644 --- a/src-python/models/translation/translation_openrouter.py +++ b/src-python/models/translation/translation_openrouter.py @@ -1,3 +1,4 @@ +import requests from openai import OpenAI from langchain_openai import ChatOpenAI from pydantic import SecretStr @@ -16,12 +17,15 @@ except Exception: def _authentication_check(api_key: str, base_url: str | None = None) -> bool: """Check if the provided API key is valid by attempting to list models. """ - try: - client = OpenAI(api_key=api_key, base_url=base_url) - client.models.list() - return True - except Exception: - return False + + url = "https://openrouter.ai/api/v1/auth/key" + headers = { + "Authorization": f"Bearer {api_key}" + } + + r = requests.get(url, headers=headers, timeout=10) + + return r.status_code == 200 def _get_available_text_models(api_key: str, base_url: str | None = None) -> list[str]: """Extract only OpenRouter models suitable for translation and chat applications. @@ -192,4 +196,4 @@ if __name__ == "__main__": model = input("Select a model: ") client.setModel(model) client.updateClient() - print(client.translate("こんにちは世界", "Japanese", "English")) + print(client.translate("こんにちは世界", "Japanese", "English")) \ No newline at end of file From 6014c2d3622a9a6008b8756c79aa30966dc7c24f Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Tue, 30 Dec 2025 06:07:46 +0900 Subject: [PATCH 4/6] [Fix] Error Handling: Refactor authentication response handling to avoid exposing sensitive data. --- src-python/config.py | 4 +- src-python/controller.py | 114 +++++++++++++-------------------------- 2 files changed, 40 insertions(+), 78 deletions(-) diff --git a/src-python/config.py b/src-python/config.py index 073af8d2..f1a86916 100644 --- a/src-python/config.py +++ b/src-python/config.py @@ -272,8 +272,8 @@ class ManagedProperty: if self.readonly: raise AttributeError(f"Cannot set read-only property '{self.name}'") - # Type check if requested - if self.type_ is not None and not isinstance(value, self.type_): + # Type check if requested(Noneは常に許可) + if self.type_ is not None and value is not None and not isinstance(value, self.type_): return # Allowed-values check: can be an iterable or a callable diff --git a/src-python/controller.py b/src-python/controller.py index 95a604c9..5bacdb4f 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -1649,42 +1649,32 @@ class Controller: self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} else: - config.SELECTABLE_PLAMO_MODEL_LIST = [] - config.SELECTED_PLAMO_MODEL = None - self.run(200, self.run_mapping["selectable_plamo_model_list"], config.SELECTABLE_PLAMO_MODEL_LIST) - self.run(200, self.run_mapping["selected_plamo_model"], config.SELECTED_PLAMO_MODEL) response = { "status":400, "result":{ "message":"Authentication failure of plamo auth key", - "data": config.AUTH_KEYS[translator_name] + "data": None } } else: - config.SELECTABLE_PLAMO_MODEL_LIST = [] - config.SELECTED_PLAMO_MODEL = None - self.run(200, self.run_mapping["selectable_plamo_model_list"], config.SELECTABLE_PLAMO_MODEL_LIST) - self.run(200, self.run_mapping["selected_plamo_model"], config.SELECTED_PLAMO_MODEL) response = { "status":400, "result":{ "message":"Plamo auth key length is not correct", - "data": config.AUTH_KEYS[translator_name] + "data": None } } except Exception as e: errorLogging() - config.SELECTABLE_PLAMO_MODEL_LIST = [] - config.SELECTED_PLAMO_MODEL = None - self.run(200, self.run_mapping["selectable_plamo_model_list"], config.SELECTABLE_PLAMO_MODEL_LIST) - self.run(200, self.run_mapping["selected_plamo_model"], config.SELECTED_PLAMO_MODEL) response = { "status":400, "result":{ "message":f"Error {e}", - "data": config.AUTH_KEYS[translator_name] + "data": None } } + if response["status"] == 400: + self.delPlamoAuthKey() return response def delPlamoAuthKey(self, *args, **kwargs) -> dict: @@ -1761,42 +1751,32 @@ class Controller: self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} else: - config.SELECTABLE_GEMINI_MODEL_LIST = [] - config.SELECTED_GEMINI_MODEL = None - self.run(200, self.run_mapping["selectable_gemini_model_list"], config.SELECTABLE_GEMINI_MODEL_LIST) - self.run(200, self.run_mapping["selected_gemini_model"], config.SELECTED_GEMINI_MODEL) response = { "status":400, "result":{ "message":"Authentication failure of gemini auth key", - "data": config.AUTH_KEYS[translator_name] + "data": None } } else: - config.SELECTABLE_GEMINI_MODEL_LIST = [] - config.SELECTED_GEMINI_MODEL = None - self.run(200, self.run_mapping["selectable_gemini_model_list"], config.SELECTABLE_GEMINI_MODEL_LIST) - self.run(200, self.run_mapping["selected_gemini_model"], config.SELECTED_GEMINI_MODEL) response = { "status":400, "result":{ "message":"Gemini auth key length is not correct", - "data": config.AUTH_KEYS[translator_name] + "data": None } } except Exception as e: errorLogging() - config.SELECTABLE_GEMINI_MODEL_LIST = [] - config.SELECTED_GEMINI_MODEL = None - self.run(200, self.run_mapping["selectable_gemini_model_list"], config.SELECTABLE_GEMINI_MODEL_LIST) - self.run(200, self.run_mapping["selected_gemini_model"], config.SELECTED_GEMINI_MODEL) response = { "status":400, "result":{ "message":f"Error {e}", - "data": config.AUTH_KEYS[translator_name] + "data": None } } + if response["status"] == 400: + self.delGeminiAuthKey() return response def delGeminiAuthKey(self, *args, **kwargs) -> dict: @@ -1874,42 +1854,32 @@ class Controller: self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} else: - config.SELECTABLE_OPENAI_MODEL_LIST = [] - config.SELECTED_OPENAI_MODEL = None - self.run(200, self.run_mapping["selectable_openai_model_list"], config.SELECTABLE_OPENAI_MODEL_LIST) - self.run(200, self.run_mapping["selected_openai_model"], config.SELECTED_OPENAI_MODEL) response = { "status":400, "result":{ "message":"Authentication failure of OpenAI auth key", - "data": config.AUTH_KEYS[translator_name] + "data": None } } else: - config.SELECTABLE_OPENAI_MODEL_LIST = [] - config.SELECTED_OPENAI_MODEL = None - self.run(200, self.run_mapping["selectable_openai_model_list"], config.SELECTABLE_OPENAI_MODEL_LIST) - self.run(200, self.run_mapping["selected_openai_model"], config.SELECTED_OPENAI_MODEL) response = { "status":400, "result":{ "message":"OpenAI auth key is not valid", - "data": config.AUTH_KEYS[translator_name] + "data": None } } except Exception as e: errorLogging() - config.SELECTABLE_OPENAI_MODEL_LIST = [] - config.SELECTED_OPENAI_MODEL = None - self.run(200, self.run_mapping["selectable_openai_model_list"], config.SELECTABLE_OPENAI_MODEL_LIST) - self.run(200, self.run_mapping["selected_openai_model"], config.SELECTED_OPENAI_MODEL) response = { "status":400, "result":{ "message":f"Error {e}", - "data": config.AUTH_KEYS[translator_name] + "data": None } } + if response["status"] == 400: + self.delOpenAIAuthKey() return response def delOpenAIAuthKey(self, *args, **kwargs) -> dict: @@ -1987,42 +1957,32 @@ class Controller: self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} else: - config.SELECTABLE_GROQ_MODEL_LIST = [] - config.SELECTED_GROQ_MODEL = None - self.run(200, self.run_mapping["selectable_groq_model_list"], config.SELECTABLE_GROQ_MODEL_LIST) - self.run(200, self.run_mapping["selected_groq_model"], config.SELECTED_GROQ_MODEL) response = { "status":400, "result":{ "message":"Authentication failure of Groq auth key", - "data": config.AUTH_KEYS[translator_name] + "data": None } } else: - config.SELECTABLE_GROQ_MODEL_LIST = [] - config.SELECTED_GROQ_MODEL = None - self.run(200, self.run_mapping["selectable_groq_model_list"], config.SELECTABLE_GROQ_MODEL_LIST) - self.run(200, self.run_mapping["selected_groq_model"], config.SELECTED_GROQ_MODEL) response = { "status":400, "result":{ "message":"Groq auth key is not valid", - "data": config.AUTH_KEYS[translator_name] + "data": None } } except Exception as e: errorLogging() - config.SELECTABLE_GROQ_MODEL_LIST = [] - config.SELECTED_GROQ_MODEL = None - self.run(200, self.run_mapping["selectable_groq_model_list"], config.SELECTABLE_GROQ_MODEL_LIST) - self.run(200, self.run_mapping["selected_groq_model"], config.SELECTED_GROQ_MODEL) response = { "status":400, "result":{ "message":f"Error {e}", - "data": config.AUTH_KEYS[translator_name] + "data": None } } + if response["status"] == 400: + self.delGroqAuthKey() return response def delGroqAuthKey(self, *args, **kwargs) -> dict: @@ -2100,42 +2060,32 @@ class Controller: self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.AUTH_KEYS[translator_name]} else: - config.SELECTABLE_OPENROUTER_MODEL_LIST = [] - config.SELECTED_OPENROUTER_MODEL = None - self.run(200, self.run_mapping["selectable_openrouter_model_list"], config.SELECTABLE_OPENROUTER_MODEL_LIST) - self.run(200, self.run_mapping["selected_openrouter_model"], config.SELECTED_OPENROUTER_MODEL) response = { "status":400, "result":{ "message":"Authentication failure of OpenRouter auth key", - "data": config.AUTH_KEYS[translator_name] + "data": None } } else: - config.SELECTABLE_OPENROUTER_MODEL_LIST = [] - config.SELECTED_OPENROUTER_MODEL = None - self.run(200, self.run_mapping["selectable_openrouter_model_list"], config.SELECTABLE_OPENROUTER_MODEL_LIST) - self.run(200, self.run_mapping["selected_openrouter_model"], config.SELECTED_OPENROUTER_MODEL) response = { "status":400, "result":{ "message":"OpenRouter auth key is not valid", - "data": config.AUTH_KEYS[translator_name] + "data": None } } except Exception as e: errorLogging() - config.SELECTABLE_OPENROUTER_MODEL_LIST = [] - config.SELECTED_OPENROUTER_MODEL = None - self.run(200, self.run_mapping["selectable_openrouter_model_list"], config.SELECTABLE_OPENROUTER_MODEL_LIST) - self.run(200, self.run_mapping["selected_openrouter_model"], config.SELECTED_OPENROUTER_MODEL) response = { "status":400, "result":{ "message":f"Error {e}", - "data": config.AUTH_KEYS[translator_name] + "data": None } } + if response["status"] == 400: + self.delOpenRouterAuthKey() return response def delOpenRouterAuthKey(self, *args, **kwargs) -> dict: @@ -2208,10 +2158,12 @@ class Controller: self.updateTranslationEngineAndEngineList() response = {"status":200, "result":True} else: + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = False config.SELECTABLE_LMSTUDIO_MODEL_LIST = [] config.SELECTED_LMSTUDIO_MODEL = None self.run(200, self.run_mapping["selectable_lmstudio_model_list"], config.SELECTABLE_LMSTUDIO_MODEL_LIST) self.run(200, self.run_mapping["selected_lmstudio_model"], config.SELECTED_LMSTUDIO_MODEL) + self.updateTranslationEngineAndEngineList() response = { "status":400, "result":{ @@ -2221,10 +2173,12 @@ class Controller: } except Exception as e: errorLogging() + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = False config.SELECTABLE_LMSTUDIO_MODEL_LIST = [] config.SELECTED_LMSTUDIO_MODEL = None self.run(200, self.run_mapping["selectable_lmstudio_model_list"], config.SELECTABLE_LMSTUDIO_MODEL_LIST) self.run(200, self.run_mapping["selected_lmstudio_model"], config.SELECTED_LMSTUDIO_MODEL) + self.updateTranslationEngineAndEngineList() response = { "status":400, "result":{ @@ -2235,7 +2189,7 @@ class Controller: return response def getConnectedLMStudio(self, *args, **kwargs) -> dict: - is_connected = model.getTranslatorLMStudioConnectedStatus() + is_connected = model.getTranslatorLMStudioConnected() return {"status":200, "result": is_connected} def getTranslatorLMStudioURL(self, *args, **kwargs) -> dict: @@ -2262,10 +2216,12 @@ class Controller: self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.LMSTUDIO_URL} else: + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = False config.SELECTABLE_LMSTUDIO_MODEL_LIST = [] config.SELECTED_LMSTUDIO_MODEL = None self.run(200, self.run_mapping["selectable_lmstudio_model_list"], config.SELECTABLE_LMSTUDIO_MODEL_LIST) self.run(200, self.run_mapping["selected_lmstudio_model"], config.SELECTED_LMSTUDIO_MODEL) + self.updateTranslationEngineAndEngineList() response = { "status":400, "result":{ @@ -2275,10 +2231,12 @@ class Controller: } except Exception as e: errorLogging() + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = False config.SELECTABLE_LMSTUDIO_MODEL_LIST = [] config.SELECTED_LMSTUDIO_MODEL = None self.run(200, self.run_mapping["selectable_lmstudio_model_list"], config.SELECTABLE_LMSTUDIO_MODEL_LIST) self.run(200, self.run_mapping["selected_lmstudio_model"], config.SELECTED_LMSTUDIO_MODEL) + self.updateTranslationEngineAndEngineList() response = { "status":400, "result":{ @@ -2346,10 +2304,12 @@ class Controller: self.updateTranslationEngineAndEngineList() response = {"status":200, "result":True} else: + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = False config.SELECTABLE_OLLAMA_MODEL_LIST = [] config.SELECTED_OLLAMA_MODEL = None self.run(200, self.run_mapping["selectable_ollama_model_list"], config.SELECTABLE_OLLAMA_MODEL_LIST) self.run(200, self.run_mapping["selected_ollama_model"], config.SELECTED_OLLAMA_MODEL) + self.updateTranslationEngineAndEngineList() response = { "status":400, "result":{ @@ -2359,10 +2319,12 @@ class Controller: } except Exception as e: errorLogging() + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = False config.SELECTABLE_OLLAMA_MODEL_LIST = [] config.SELECTED_OLLAMA_MODEL = None self.run(200, self.run_mapping["selectable_ollama_model_list"], config.SELECTABLE_OLLAMA_MODEL_LIST) self.run(200, self.run_mapping["selected_ollama_model"], config.SELECTED_OLLAMA_MODEL) + self.updateTranslationEngineAndEngineList() response = { "status":400, "result":{ From 588b95eebece2e6d57e099ec90acbf0cd1bf0f59 Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Tue, 30 Dec 2025 06:44:15 +0900 Subject: [PATCH 5/6] [Fix] Error Handling: Update setter methods to allow None values for type checks and enhance sensitive data handling in authentication failures. --- src-python/docs/config.md | 12 +++++++++++- src-python/docs/controller.md | 12 ++++++++---- src-python/docs/details/translation_lmstudio.md | 12 ++++++++++++ src-python/docs/details/translation_ollama.md | 12 ++++++++++++ src-python/docs/details/translation_openrouter.md | 15 ++++++++++----- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src-python/docs/config.md b/src-python/docs/config.md index 6fd78305..2051c5c7 100644 --- a/src-python/docs/config.md +++ b/src-python/docs/config.md @@ -279,11 +279,21 @@ def SELECTED_TAB_NO(self, value): ``` 各setterは以下のパターンを実装: -1. 型チェック (`isinstance`) +1. 型チェック (`isinstance`):**`None` 値は常に許可される** (値をクリアしたい場合への対応) 2. 値の範囲・有効性チェック 3. 内部変数への代入 4. `saveConfig` 呼び出し(永続化対象の場合) +#### 型チェックの詳細(v3.3.0+) + +```python +# 型チェック実装:Noneは常に許可 +if self.type_ is not None and value is not None and not isinstance(value, self.type_): + return # 無視する +``` + +この変更により、設定値をクリア(None に設定)する用途に対応。例えば認証失敗時に API キーを `None` に設定する場合に有効。 + ### メッセージフォーマット構造 ```python diff --git a/src-python/docs/controller.md b/src-python/docs/controller.md index e86cfa98..30e371f9 100644 --- a/src-python/docs/controller.md +++ b/src-python/docs/controller.md @@ -722,12 +722,14 @@ OSC Query 機能が無効になったことを通知。無効化された機能 - 未選択の場合は先頭モデルを自動選択 - `model.updateTranslatorGroqClient()` でクライアント更新 - `updateTranslationEngineAndEngineList()` を呼び出し -4. 認証失敗時: status 400 を返却 +4. 認証失敗時 (status 400): + - レスポンス `data` フィールドを **None に設定** (sensitive data を隠す) + - `delGroqAuthKey()` を呼び出してクリーンアップ **API キー検証失敗時の処理:** - モデルリストをクリア (`config.SELECTABLE_GROQ_MODEL_LIST = []`) - 選択モデルをクリア (`config.SELECTED_GROQ_MODEL = None`) -- フロントエンドに通知 +- フロントエンドに通知(レスポンス `data` は None) #### `delGroqAuthKey(*args, **kwargs) -> dict` @@ -776,12 +778,14 @@ OSC Query 機能が無効になったことを通知。無効化された機能 - 未選択の場合は先頭モデルを自動選択 - `model.updateTranslatorOpenRouterClient()` でクライアント更新 - `updateTranslationEngineAndEngineList()` を呼び出し -4. 認証失敗時: status 400 を返却 +4. 認証失敗時 (status 400): + - レスポンス `data` フィールドを **None に設定** (sensitive data を隠す) + - `delOpenRouterAuthKey()` を呼び出してクリーンアップ **API キー検証失敗時の処理:** - モデルリストをクリア (`config.SELECTABLE_OPENROUTER_MODEL_LIST = []`) - 選択モデルをクリア (`config.SELECTED_OPENROUTER_MODEL = None`) -- フロントエンドに通知 +- フロントエンドに通知(レスポンス `data` は None) #### `delOpenRouterAuthKey(*args, **kwargs) -> dict` diff --git a/src-python/docs/details/translation_lmstudio.md b/src-python/docs/details/translation_lmstudio.md index b54d46a8..01516a91 100644 --- a/src-python/docs/details/translation_lmstudio.md +++ b/src-python/docs/details/translation_lmstudio.md @@ -4,6 +4,14 @@ LMStudio 互換 OpenAI API を利用したローカル LLM 翻訳クライアントラッパー。モデル一覧取得・モデル選択・翻訳処理を統一インターフェースで提供する。 +## 最近の更新 (2025-12-30) + +- 接続失敗時のエラーハンドリング改善 + - URL への疎通確認失敗時にモデルリストをクリア (`SELECTABLE_LMSTUDIO_MODEL_LIST = []`) + - 選択モデルをクリア (`SELECTED_LMSTUDIO_MODEL = None`) + - `SELECTABLE_TRANSLATION_ENGINE_STATUS["LMStudio"]` を False に設定 + - フロントエンドに通知して UI を同期 + ## 最近の更新 (2025-10-20) - 新規追加: ローカル LLM (LMStudio) を翻訳エンジン群へ統合 @@ -73,6 +81,10 @@ if models: - `api_key` は固定文字列 "lmstudio" (LMStudio 側で不要のため) を利用 - モデル一覧取得はエンドポイントの互換性に依存 (古いバージョン非対応の可能性) - `updateClient()` 呼び出し前は `translate()` を利用できない +- **接続失敗時の自動処理:** + - URL への疎通確認(接続テスト)が失敗すると、自動的にモデルリストと選択モデルがクリアされる + - `SELECTABLE_TRANSLATION_ENGINE_STATUS["LMStudio"]` が False に設定され、エンジンが使用不可状態になる + - Controller が自動的にフロントエンドに状態変化を通知 ## 制限事項 diff --git a/src-python/docs/details/translation_ollama.md b/src-python/docs/details/translation_ollama.md index d176644e..93d8dc88 100644 --- a/src-python/docs/details/translation_ollama.md +++ b/src-python/docs/details/translation_ollama.md @@ -4,6 +4,14 @@ Ollama サーバー上で稼働するローカル LLM を翻訳エンジンとして扱うためのクライアントラッパー。モデル一覧取得・モデル選択・翻訳実行を統一パターンで提供する。 +## 最近の更新 (2025-12-30) + +- 接続失敗時のエラーハンドリング改善 + - `/api/ping` への疎通確認失敗時にモデルリストをクリア (`SELECTABLE_OLLAMA_MODEL_LIST = []`) + - 選択モデルをクリア (`SELECTED_OLLAMA_MODEL = None`) + - `SELECTABLE_TRANSLATION_ENGINE_STATUS["Ollama"]` を False に設定 + - フロントエンドに通知して UI を同期 + ## 最近の更新 (2025-10-20) - 新規追加: Ollama を翻訳エンジン群へ統合 @@ -73,6 +81,10 @@ if client.authenticationCheck(): - サーバー既定 URL: `http://localhost:11434` - モデル一覧取得は起動しているローカルサーバー状態に依存 - `updateClient()` 呼び出し前は `translate()` を利用不可 +- **接続失敗時の自動処理:** + - `/api/ping` への疎通確認が失敗すると、自動的にモデルリストと選択モデルがクリアされる + - `SELECTABLE_TRANSLATION_ENGINE_STATUS["Ollama"]` が False に設定され、エンジンが使用不可状態になる + - Controller が自動的にフロントエンドに状態変化を通知 ## 制限事項 diff --git a/src-python/docs/details/translation_openrouter.md b/src-python/docs/details/translation_openrouter.md index 7a15398c..3936b19a 100644 --- a/src-python/docs/details/translation_openrouter.md +++ b/src-python/docs/details/translation_openrouter.md @@ -4,12 +4,15 @@ OpenRouter API を用いた統合 LLM 翻訳クライアントラッパー。OpenAI 互換エンドポイント (`https://openrouter.ai/api/v1`) を利用し、複数の LLM プロバイダーへの統一アクセスを提供する。 -## 最近の更新 (2025-12-10) +## 最近の更新 (2025-12-29) -- OpenRouter API サポートを新規追加 -- 単一 API キーで複数 LLM プロバイダーへアクセス可能 -- 除外キーワード (`whisper`, `embedding`, `image`, `tts`, `audio`, `search`, `transcribe`, `diarize`, `vision`) によるテキスト処理モデルのフィルタリング -- YAML (`prompt/translation_openrouter.yml`) からシステムプロンプトをロード +- OpenRouter API 認証チェック方法を変更 + - **以前:** `client.models.list()` を呼び出して認証確認 + - **現在:** `https://openrouter.ai/api/v1/auth/key` エンドポイントに GET リクエスト送信して確認 + - **理由:** より信頼性の高い専用認証エンドポイントを使用し、高速かつ確実に API キー有効性を検証 +- 認証失敗時の sensitive data 処理 + - API キー検証失敗時はレスポンス `data` フィールドに `None` を設定(API キーを露出させない) + - エラーメッセージのみを返却し、具体的なキー情報は隠蔽 ### 影響 @@ -22,6 +25,8 @@ OpenRouter API を用いた統合 LLM 翻訳クライアントラッパー。Ope ## 責務 - OpenRouter API Key (20文字以上) を用いた認証確認 + - `https://openrouter.ai/api/v1/auth/key` エンドポイントへの HTTP GET リクエストで検証(タイムアウト10秒) + - ステータスコード 200 で有効と判定 - 利用可能モデルのフィルタリングとソート - 選択モデルの検証と内部保持 - LangChain `ChatOpenAI` インスタンス生成(base_url に OpenRouter エンドポイント指定) From 01061cf98f39a8d7239c6293625045029e4cea6c Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Tue, 30 Dec 2025 07:52:08 +0900 Subject: [PATCH 6/6] [Fix] Error Handling: Update setter methods to clarify None value handling and enhance authentication failure response policy. --- src-python/docs/config.md | 6 +++--- src-python/docs/controller.md | 8 +++++++- src-python/models/translation/translation_openrouter.py | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src-python/docs/config.md b/src-python/docs/config.md index 2051c5c7..783772fa 100644 --- a/src-python/docs/config.md +++ b/src-python/docs/config.md @@ -279,7 +279,7 @@ def SELECTED_TAB_NO(self, value): ``` 各setterは以下のパターンを実装: -1. 型チェック (`isinstance`):**`None` 値は常に許可される** (値をクリアしたい場合への対応) +1. 型チェック (`isinstance`):`ManagedProperty` による型チェックは `None` を許容するが、個別 setter が数値変換などを行う場合は `None` を拒否するケースがある 2. 値の範囲・有効性チェック 3. 内部変数への代入 4. `saveConfig` 呼び出し(永続化対象の場合) @@ -287,12 +287,12 @@ def SELECTED_TAB_NO(self, value): #### 型チェックの詳細(v3.3.0+) ```python -# 型チェック実装:Noneは常に許可 +# 型チェック実装:ManagedProperty 経由では None を常に許可 if self.type_ is not None and value is not None and not isinstance(value, self.type_): return # 無視する ``` -この変更により、設定値をクリア(None に設定)する用途に対応。例えば認証失敗時に API キーを `None` に設定する場合に有効。 +この仕様は `ManagedProperty` を通じた型チェックに適用される。個別の setter で追加のバリデーションやキャストを行う場合、`None` は別途拒否されることがある。 ### メッセージフォーマット構造 diff --git a/src-python/docs/controller.md b/src-python/docs/controller.md index 30e371f9..ab789ee0 100644 --- a/src-python/docs/controller.md +++ b/src-python/docs/controller.md @@ -693,7 +693,13 @@ OSC Query 機能が無効になったことを通知。無効化された機能 - `config.AUTH_KEYS["DeepL_API"]` に保存 - `config.SELECTABLE_TRANSLATION_ENGINE_STATUS["DeepL_API"]` を True に - `updateTranslationEngineAndEngineList()` を呼び出し -4. 認証失敗時: status 400 を返却 +4. 認証失敗時 (status 400): + - レスポンス `data` フィールドは **常に None**(キーを返さない) + - `delDeeplAuthKey()` を呼び出してクリーンアップ + +**認証失敗時の共通ポリシー(Plamo/Gemini/OpenAI/DeepL/Groq/OpenRouter 共通)** +- レスポンス `data` はキーを含めず `None` を返す +- 対応する `del*AuthKey()` を呼び出し、保存済みキーとモデル選択をクリア #### `delDeeplAuthKey(*args, **kwargs) -> dict` diff --git a/src-python/models/translation/translation_openrouter.py b/src-python/models/translation/translation_openrouter.py index 16bd78ab..2dffba58 100644 --- a/src-python/models/translation/translation_openrouter.py +++ b/src-python/models/translation/translation_openrouter.py @@ -14,7 +14,7 @@ except Exception: from translation_utils import loadTranslatePromptConfig translation_lang = loadTranslationLanguages(path=".", force=True) -def _authentication_check(api_key: str, base_url: str | None = None) -> bool: +def _authentication_check(api_key: str) -> bool: """Check if the provided API key is valid by attempting to list models. """ @@ -94,7 +94,7 @@ class OpenRouterClient: return self.api_key def setAuthKey(self, api_key: str) -> bool: - result = _authentication_check(api_key, self.base_url) + result = _authentication_check(api_key) if result: self.api_key = api_key return result