From e1125ae241660a6ec6e516f09f885f41c8f0a8de Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:31:07 +0900 Subject: [PATCH 1/7] [Update] UI: Add new translation engines and adjust UI styles for better layout. --- src-ui/logics/ui_configs.js | 11 ++++++++--- .../TranslatorSelector.module.scss | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src-ui/logics/ui_configs.js b/src-ui/logics/ui_configs.js index 191693c8..8d1d4487 100644 --- a/src-ui/logics/ui_configs.js +++ b/src-ui/logics/ui_configs.js @@ -101,12 +101,17 @@ export const getPluginsList = () => { if (IS_PLUGIN_PATH_DEV_MODE || IS_PLUGIN_LIST_URL_DEV_MODE) console.warn("ui_configs IS_PLUGIN_PATH_DEV_MODE or IS_PLUGIN_LIST_URL_DEV_MODE is true. Turn to 'false' when it's production environment."); export const translator_status = [ - { id: "DeepL", label: "DeepL", is_available: false }, - { id: "DeepL_API", label: `DeepL API`, is_available: false }, + { id: "CTranslate2", label: `AI\nCTranslate2`, is_available: false, is_default: true }, { id: "Google", label: "Google", is_available: false }, { id: "Bing", label: "Bing", is_available: false }, { id: "Papago", label: "Papago", is_available: false }, - { id: "CTranslate2", label: `AI\nCTranslate2`, is_available: false, is_default: true }, + { id: "DeepL", label: "DeepL", is_available: false }, + { id: "DeepL_API", label: `DeepL API`, is_available: false }, + { id: "Plamo_API", label: `Plamo API`, is_available: false }, + { id: "Gemini_API", label: `Gemini API`, is_available: false }, + { id: "OpenAI_API", label: `OpenAI API`, is_available: false }, + { id: "LMStudio", label: `LMStudio`, is_available: false }, + { id: "Ollama", label: `Ollama`, is_available: false }, ]; export const ctranslate2_weight_type_status = [ diff --git a/src-ui/views/app/main_page/sidebar_section/language_settings/translator_selector_open_button/translator_selector/TranslatorSelector.module.scss b/src-ui/views/app/main_page/sidebar_section/language_settings/translator_selector_open_button/translator_selector/TranslatorSelector.module.scss index b73fef4c..50bc5d8d 100644 --- a/src-ui/views/app/main_page/sidebar_section/language_settings/translator_selector_open_button/translator_selector/TranslatorSelector.module.scss +++ b/src-ui/views/app/main_page/sidebar_section/language_settings/translator_selector_open_button/translator_selector/TranslatorSelector.module.scss @@ -8,6 +8,7 @@ display: flex; justify-content: center; align-items: center; + overflow-y: auto; } .relative_container { @@ -17,8 +18,7 @@ } .wrapper { - width: 100%; - height: 100%; + padding: 2rem 0; display: flex; flex-direction: column; justify-content: center; From ef06cd1c7a30b0208aa53f3fcb93a5d52f5acae2 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:19:39 +0900 Subject: [PATCH 2/7] [Update] UI: Implement LLM connection handling and add connection check UI components.(Test UI) --- src-ui/logics/_useBackendErrorHandling.js | 18 +++++++ src-ui/logics/common/index.js | 3 +- src-ui/logics/common/useLLMConnection.js | 47 +++++++++++++++++++ src-ui/logics/store.js | 2 + src-ui/logics/useReceiveRoutes.js | 3 ++ .../ConnectionCheckButton.jsx | 19 ++++++++ .../ConnectionCheckButton.module.scss | 15 ++++++ .../setting_box/_components/index.js | 3 +- .../setting_box/_templates/Templates.jsx | 5 ++ .../setting_box/translation/Translation.jsx | 39 +++++++++++++++ 10 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 src-ui/logics/common/useLLMConnection.js create mode 100644 src-ui/views/app/config_page/setting_section/setting_box/_components/connection_check_button/ConnectionCheckButton.jsx create mode 100644 src-ui/views/app/config_page/setting_section/setting_box/_components/connection_check_button/ConnectionCheckButton.module.scss diff --git a/src-ui/logics/_useBackendErrorHandling.js b/src-ui/logics/_useBackendErrorHandling.js index f224686c..eb8bcae0 100644 --- a/src-ui/logics/_useBackendErrorHandling.js +++ b/src-ui/logics/_useBackendErrorHandling.js @@ -2,6 +2,7 @@ import { useI18n } from "@useI18n"; import { useNotificationStatus, + useLLMConnection, } from "@logics_common"; import { @@ -46,6 +47,11 @@ export const _useBackendErrorHandling = () => { updateWebsocketPort, } = useAdvancedSettings(); + const { + updateIsOllamaConnected, + updateIsLMStudioConnected, + } = useLLMConnection(); + const errorHandling_Backend = ({message, data, endpoint, result}) => { switch (endpoint) { case "/run/error_device": @@ -221,6 +227,18 @@ export const _useBackendErrorHandling = () => { } return; + case "/run/lmstudio_connection": + updateIsLMStudioConnected(data); + showNotification_Error(message); + console.error(message); + return; + + case "/run/ollama_connection": + updateIsOllamaConnected(data); + showNotification_Error(message); + console.error(message); + return; + default: console.error(`Invalid endpoint or message: ${endpoint}\nmessage: ${message}\nresult: ${JSON.stringify(result)}`); return; diff --git a/src-ui/logics/common/index.js b/src-ui/logics/common/index.js index 7cac177c..5975f52f 100644 --- a/src-ui/logics/common/index.js +++ b/src-ui/logics/common/index.js @@ -14,4 +14,5 @@ export { useHandleNetworkConnection } from "./useHandleNetworkConnection"; export { useHandleOscQuery } from "./useHandleOscQuery"; export { useIsOscAvailable } from "./useIsOscAvailable"; export { useIsVrctAvailable } from "./useIsVrctAvailable"; -export { useFetch } from "./useFetch"; \ No newline at end of file +export { useFetch } from "./useFetch"; +export { useLLMConnection } from "./useLLMConnection"; \ No newline at end of file diff --git a/src-ui/logics/common/useLLMConnection.js b/src-ui/logics/common/useLLMConnection.js new file mode 100644 index 00000000..70bd350a --- /dev/null +++ b/src-ui/logics/common/useLLMConnection.js @@ -0,0 +1,47 @@ +import { useStdoutToPython } from "@useStdoutToPython"; +import { + useStore_IsLMStudioConnected, + useStore_IsOllamaConnected, +} from "@store"; + +export const useLLMConnection = () => { + const { asyncStdoutToPython } = useStdoutToPython(); + const { + currentIsLMStudioConnected, + updateIsLMStudioConnected, + pendingIsLMStudioConnected, + } = useStore_IsLMStudioConnected(); + const { + currentIsOllamaConnected, + updateIsOllamaConnected, + pendingIsOllamaConnected, + } = useStore_IsOllamaConnected(); + + const checkConnection_LMStudio = () => { + pendingIsLMStudioConnected(); + asyncStdoutToPython("/run/lmstudio_connection"); + }; + const setConnectionStatus_LMStudio = (is_connected) => { + updateIsLMStudioConnected(is_connected); + }; + + const checkConnection_Ollama = () => { + pendingIsOllamaConnected(); + asyncStdoutToPython("/run/ollama_connection"); + }; + const setConnectionStatus_Ollama = (is_connected) => { + updateIsOllamaConnected(is_connected); + }; + + return { + currentIsLMStudioConnected, + updateIsLMStudioConnected, + setConnectionStatus_LMStudio, + checkConnection_LMStudio, + + currentIsOllamaConnected, + updateIsOllamaConnected, + setConnectionStatus_Ollama, + checkConnection_Ollama, + }; +}; \ No newline at end of file diff --git a/src-ui/logics/store.js b/src-ui/logics/store.js index 8f332a6e..831d63da 100644 --- a/src-ui/logics/store.js +++ b/src-ui/logics/store.js @@ -167,6 +167,8 @@ export const { atomInstance: Atom_NotificationStatus, useHook: useStore_Notifica key: 0, message: "", }, "NotificationStatus"); +export const { atomInstance: Atom_IsLMStudioConnected, useHook: useStore_IsLMStudioConnected } = createAtomWithHook(false, "IsLMStudioConnected", {is_state_ok: true}); +export const { atomInstance: Atom_IsOllamaConnected, useHook: useStore_IsOllamaConnected } = createAtomWithHook(false, "IsOllamaConnected", {is_state_ok: true}); // Main Page // Common diff --git a/src-ui/logics/useReceiveRoutes.js b/src-ui/logics/useReceiveRoutes.js index e9936936..c3368c96 100644 --- a/src-ui/logics/useReceiveRoutes.js +++ b/src-ui/logics/useReceiveRoutes.js @@ -20,6 +20,9 @@ export const STATIC_ROUTE_META_LIST = [ { endpoint: "/run/open_filepath_logs", ns: common, hook_name: "useOpenFolder", method_name: "openedFolder_MessageLogs" }, { endpoint: "/run/open_filepath_config_file", ns: common, hook_name: "useOpenFolder", method_name: "openedFolder_ConfigFile" }, + { endpoint: "/run/lmstudio_connection", ns: common, hook_name: "useLLMConnection", method_name: "setConnectionStatus_LMStudio" }, + { endpoint: "/run/ollama_connection", ns: common, hook_name: "useLLMConnection", method_name: "setConnectionStatus_Ollama" }, + // Software Version { endpoint: "/get/data/version", ns: common, hook_name: "useSoftwareVersion", method_name: "updateSoftwareVersion" }, // Latest Software Version Info diff --git a/src-ui/views/app/config_page/setting_section/setting_box/_components/connection_check_button/ConnectionCheckButton.jsx b/src-ui/views/app/config_page/setting_section/setting_box/_components/connection_check_button/ConnectionCheckButton.jsx new file mode 100644 index 00000000..075a0e10 --- /dev/null +++ b/src-ui/views/app/config_page/setting_section/setting_box/_components/connection_check_button/ConnectionCheckButton.jsx @@ -0,0 +1,19 @@ +import styles from "./ConnectionCheckButton.module.scss"; + +export const ConnectionCheckButton = (props) => { + const label = props.state === "pending" + ? "Checking... 🌀" + : props.variable === true + ? "Connected ✅" + : "Disconnected ❌"; + + return ( +
+

{label}

+

{`UI Status: ${props.state}`}

+ +
+ ); +}; \ No newline at end of file diff --git a/src-ui/views/app/config_page/setting_section/setting_box/_components/connection_check_button/ConnectionCheckButton.module.scss b/src-ui/views/app/config_page/setting_section/setting_box/_components/connection_check_button/ConnectionCheckButton.module.scss new file mode 100644 index 00000000..116feab4 --- /dev/null +++ b/src-ui/views/app/config_page/setting_section/setting_box/_components/connection_check_button/ConnectionCheckButton.module.scss @@ -0,0 +1,15 @@ +.button_wrapper { + padding: 1.6rem; + border-radius: 0.4rem; + &:hover { + background-color: var(--dark_825_color); + } + &:active { + background-color: var(--dark_900_color); + } +} + +.button_svg { + width: 2.4rem; + color: var(--dark_400_color); +} \ No newline at end of file diff --git a/src-ui/views/app/config_page/setting_section/setting_box/_components/index.js b/src-ui/views/app/config_page/setting_section/setting_box/_components/index.js index 2e7b54ca..d28f063d 100644 --- a/src-ui/views/app/config_page/setting_section/setting_box/_components/index.js +++ b/src-ui/views/app/config_page/setting_section/setting_box/_components/index.js @@ -13,4 +13,5 @@ export { SwitchBox } from "./switch_box/SwitchBox"; export { ThresholdComponent } from "./threshold_component/ThresholdComponent"; export { WordFilter, WordFilterListToggleComponent } from "./word_filter/WordFilter"; export { DownloadModels } from "./download_models/DownloadModels"; -export { MessageFormat } from "./message_format/MessageFormat"; \ No newline at end of file +export { MessageFormat } from "./message_format/MessageFormat"; +export { ConnectionCheckButton } from "./connection_check_button/ConnectionCheckButton"; \ No newline at end of file diff --git a/src-ui/views/app/config_page/setting_section/setting_box/_templates/Templates.jsx b/src-ui/views/app/config_page/setting_section/setting_box/_templates/Templates.jsx index 0c0845e1..b4952c99 100644 --- a/src-ui/views/app/config_page/setting_section/setting_box/_templates/Templates.jsx +++ b/src-ui/views/app/config_page/setting_section/setting_box/_templates/Templates.jsx @@ -19,6 +19,7 @@ import { WordFilterListToggleComponent, DownloadModels, MessageFormat, + ConnectionCheckButton, } from "../_components"; import { Checkbox } from "@common_components"; @@ -181,6 +182,10 @@ export const DownloadModelsContainer = (props) => ( ); +export const ConnectionCheckButtonContainer = (props) => ( + +); + export const MessageFormatContainer = (props) => { return ( <> diff --git a/src-ui/views/app/config_page/setting_section/setting_box/translation/Translation.jsx b/src-ui/views/app/config_page/setting_section/setting_box/translation/Translation.jsx index 7ad243ff..5a496206 100644 --- a/src-ui/views/app/config_page/setting_section/setting_box/translation/Translation.jsx +++ b/src-ui/views/app/config_page/setting_section/setting_box/translation/Translation.jsx @@ -17,6 +17,7 @@ import { EntryWithSaveButtonContainer, RadioButtonContainer, DropdownMenuContainer, + ConnectionCheckButtonContainer, useOnMouseLeaveDropdownMenu, } from "../_templates/Templates"; @@ -25,9 +26,11 @@ import { DropdownMenu, MultiDropdownMenu, LabelComponent, + ConnectionCheckButton, } from "../_components"; import { deepl_auth_key_url } from "@ui_configs"; +import { useLLMConnection } from "@logics_common"; export const Translation = () => { return ( @@ -46,9 +49,11 @@ export const Translation = () => { + + ); @@ -422,6 +427,23 @@ const OpenAIModelContainer = () => { +const LMStudioConnectionCheck_Box = () => { + const { t } = useI18n(); + const { currentIsLMStudioConnected, checkConnection_LMStudio } = useLLMConnection(); + + return ( + <> + + + ); +}; const LMStudioURL_Box = () => { const { t } = useI18n(); const { currentLMStudioURL, setLMStudioURL, deleteLMStudioURL } = useTranslation(); @@ -475,6 +497,23 @@ const LMStudioModelContainer = () => { ); }; +const OllamaConnectionCheck_Box = () => { + const { t } = useI18n(); + const { currentIsOllamaConnected, checkConnection_Ollama } = useLLMConnection(); + + return ( + <> + + + ); +}; const OllamaModelContainer = () => { const { t } = useI18n(); const { From 27b3006ffd88cc5e095742220df2f296bfdf6ff8 Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Thu, 20 Nov 2025 01:38:50 +0900 Subject: [PATCH 3/7] [Update] Controller: Add error handling for empty translation model lists and improve logging for translation engine availability checks. --- src-python/controller.py | 48 +++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src-python/controller.py b/src-python/controller.py index 06df12bf..db326cb2 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -1909,10 +1909,12 @@ class Controller: config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = True config.SELECTABLE_LMSTUDIO_MODEL_LIST = model.getTranslatorLMStudioModelList() self.run(200, self.run_mapping["selectable_lmstudio_model_list"], config.SELECTABLE_LMSTUDIO_MODEL_LIST) + if len(config.SELECTABLE_LMSTUDIO_MODEL_LIST) == 0: + raise Exception("No LMStudio models available") if config.SELECTED_LMSTUDIO_MODEL not in config.SELECTABLE_LMSTUDIO_MODEL_LIST: config.SELECTED_LMSTUDIO_MODEL = config.SELECTABLE_LMSTUDIO_MODEL_LIST[0] - model.setTranslatorLMStudioModel(model=config.SELECTED_LMSTUDIO_MODEL) - self.run(200, self.run_mapping["selected_lmstudio_model"], config.SELECTED_LMSTUDIO_MODEL) + model.setTranslatorLMStudioModel(model=config.SELECTED_LMSTUDIO_MODEL) + self.run(200, self.run_mapping["selected_lmstudio_model"], config.SELECTED_LMSTUDIO_MODEL) model.updateTranslatorLMStudioClient() self.updateTranslationEngineAndEngineList() response = {"status":200, "result":True} @@ -1949,10 +1951,12 @@ class Controller: config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = True config.SELECTABLE_LMSTUDIO_MODEL_LIST = model.getTranslatorLMStudioModelList() self.run(200, self.run_mapping["selectable_lmstudio_model_list"], config.SELECTABLE_LMSTUDIO_MODEL_LIST) + if len(config.SELECTABLE_LMSTUDIO_MODEL_LIST) == 0: + raise Exception("No LMStudio models available") if config.SELECTED_LMSTUDIO_MODEL not in config.SELECTABLE_LMSTUDIO_MODEL_LIST: config.SELECTED_LMSTUDIO_MODEL = config.SELECTABLE_LMSTUDIO_MODEL_LIST[0] - model.setTranslatorLMStudioModel(model=config.SELECTED_LMSTUDIO_MODEL) - self.run(200, self.run_mapping["selected_lmstudio_model"], config.SELECTED_LMSTUDIO_MODEL) + model.setTranslatorLMStudioModel(model=config.SELECTED_LMSTUDIO_MODEL) + self.run(200, self.run_mapping["selected_lmstudio_model"], config.SELECTED_LMSTUDIO_MODEL) model.updateTranslatorLMStudioClient() self.updateTranslationEngineAndEngineList() response = {"status":200, "result":config.LMSTUDIO_URL} @@ -2020,10 +2024,12 @@ class Controller: config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = True config.SELECTABLE_OLLAMA_MODEL_LIST = model.getTranslatorOllamaModelList() self.run(200, self.run_mapping["selectable_ollama_model_list"], config.SELECTABLE_OLLAMA_MODEL_LIST) + if len(config.SELECTABLE_OLLAMA_MODEL_LIST) == 0: + raise Exception("No Ollama models available") if config.SELECTED_OLLAMA_MODEL not in config.SELECTABLE_OLLAMA_MODEL_LIST: config.SELECTED_OLLAMA_MODEL = config.SELECTABLE_OLLAMA_MODEL_LIST[0] - model.setTranslatorOllamaModel(model=config.SELECTED_OLLAMA_MODEL) - self.run(200, self.run_mapping["selected_ollama_model"], config.SELECTED_OLLAMA_MODEL) + model.setTranslatorOllamaModel(model=config.SELECTED_OLLAMA_MODEL) + self.run(200, self.run_mapping["selected_ollama_model"], config.SELECTED_OLLAMA_MODEL) model.updateTranslatorOllamaClient() self.updateTranslationEngineAndEngineList() response = {"status":200, "result":True} @@ -2957,13 +2963,13 @@ class Controller: config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False if config.AUTH_KEYS[engine] is not None: if model.authenticationTranslatorPlamoAuthKey(auth_key=config.AUTH_KEYS[engine]) is True: - config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True - printLog("Plamo API Key is valid") config.SELECTABLE_PLAMO_MODEL_LIST = model.getTranslatorPlamoModelList() if config.SELECTED_PLAMO_MODEL not in config.SELECTABLE_PLAMO_MODEL_LIST: config.SELECTED_PLAMO_MODEL = config.SELECTABLE_PLAMO_MODEL_LIST[0] model.setTranslatorPlamoModel(config.SELECTED_PLAMO_MODEL) model.updateTranslatorPlamoClient() + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True + printLog("Plamo API Key is valid") else: # error update Auth key auth_keys = config.AUTH_KEYS @@ -2975,13 +2981,13 @@ class Controller: config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False if config.AUTH_KEYS[engine] is not None: if model.authenticationTranslatorGeminiAuthKey(auth_key=config.AUTH_KEYS[engine]) is True: - config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True - printLog("Gemini API Key is valid") config.SELECTABLE_GEMINI_MODEL_LIST = model.getTranslatorGeminiModelList() if config.SELECTED_GEMINI_MODEL not in config.SELECTABLE_GEMINI_MODEL_LIST: config.SELECTED_GEMINI_MODEL = config.SELECTABLE_GEMINI_MODEL_LIST[0] model.setTranslatorGeminiModel(config.SELECTED_GEMINI_MODEL) model.updateTranslatorGeminiClient() + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True + printLog("Gemini API Key is valid") else: # error update Auth key auth_keys = config.AUTH_KEYS @@ -2993,13 +2999,13 @@ class Controller: config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False if config.AUTH_KEYS[engine] is not None: if model.authenticationTranslatorOpenAIAuthKey(auth_key=config.AUTH_KEYS[engine]) is True: - config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True - printLog("OpenAI API Key is valid") config.SELECTABLE_OPENAI_MODEL_LIST = model.getTranslatorOpenAIModelList() if config.SELECTED_OPENAI_MODEL not in config.SELECTABLE_OPENAI_MODEL_LIST: config.SELECTED_OPENAI_MODEL = config.SELECTABLE_OPENAI_MODEL_LIST[0] model.setTranslatorOpenAIModel(config.SELECTED_OPENAI_MODEL) model.updateTranslatorOpenAIClient() + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True + printLog("OpenAI API Key is valid") else: # error update Auth key auth_keys = config.AUTH_KEYS @@ -3007,30 +3013,36 @@ class Controller: config.AUTH_KEYS = auth_keys printLog("OpenAI API Key is invalid") case "LMStudio": - printLog("Start check LMStudio API Key") + printLog("Start check LMStudio Server") config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False if config.LMSTUDIO_URL is not None: if model.authenticationTranslatorLMStudio(base_url=config.LMSTUDIO_URL) is True: - config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True - printLog("LMStudio URL is valid") config.SELECTABLE_LMSTUDIO_MODEL_LIST = model.getTranslatorLMStudioModelList() + if len(config.SELECTABLE_LMSTUDIO_MODEL_LIST) == 0: + printLog("LMStudio model list is empty") + break if config.SELECTED_LMSTUDIO_MODEL not in config.SELECTABLE_LMSTUDIO_MODEL_LIST: config.SELECTED_LMSTUDIO_MODEL = config.SELECTABLE_LMSTUDIO_MODEL_LIST[0] model.setTranslatorLMStudioModel(config.SELECTED_LMSTUDIO_MODEL) model.updateTranslatorLMStudioClient() + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True + printLog("LMStudio is available") else: printLog("LMStudio is not available") case "Ollama": - printLog("Start check Ollama API Key") + printLog("Start check Ollama Server") config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False if model.authenticationTranslatorOllama() is True: - config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True - printLog("Ollama is available") config.SELECTABLE_OLLAMA_MODEL_LIST = model.getTranslatorOllamaModelList() + if len(config.SELECTABLE_OLLAMA_MODEL_LIST) == 0: + printLog("Ollama model list is empty") + break if config.SELECTED_OLLAMA_MODEL not in config.SELECTABLE_OLLAMA_MODEL_LIST: config.SELECTED_OLLAMA_MODEL = config.SELECTABLE_OLLAMA_MODEL_LIST[0] model.setTranslatorOllamaModel(config.SELECTED_OLLAMA_MODEL) model.updateTranslatorOllamaClient() + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True + printLog("Ollama is available") else: printLog("Ollama is not available") case _: From add96c1fda61e74ab82dbaa9749d9da9ee0822e0 Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Sun, 23 Nov 2025 00:07:32 +0900 Subject: [PATCH 4/7] [Update] Config: Enhance configuration persistence by filtering serializable properties and updating loading logic for managed properties. --- src-python/config.py | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src-python/config.py b/src-python/config.py index 3a3e7c3b..c841b2ae 100644 --- a/src-python/config.py +++ b/src-python/config.py @@ -558,8 +558,16 @@ class Config: return cls._instance def saveConfigToFile(self) -> None: + # 永続化対象を descriptor 情報 (json_serializable_vars) から再構成 + filtered = {} + for var_name, var_func in json_serializable_vars.items(): + try: + filtered[var_name] = var_func(self) + except Exception: + pass + self._config_data = filtered with open(self.PATH_CONFIG, "w", encoding="utf-8") as fp: - json_dump(self._config_data, fp, indent=4, ensure_ascii=False) + json_dump(filtered, fp, indent=4, ensure_ascii=False) def saveConfig(self, key: str, value: Any, immediate_save: bool = False) -> None: self._config_data[key] = value @@ -601,12 +609,12 @@ class Config: # Read Write # --- Simple boolean flags (managed by descriptor) --- - ENABLE_TRANSLATION = ManagedProperty('ENABLE_TRANSLATION', type_=bool) - ENABLE_TRANSCRIPTION_SEND = ManagedProperty('ENABLE_TRANSCRIPTION_SEND', type_=bool) - ENABLE_TRANSCRIPTION_RECEIVE = ManagedProperty('ENABLE_TRANSCRIPTION_RECEIVE', type_=bool) - ENABLE_FOREGROUND = ManagedProperty('ENABLE_FOREGROUND', type_=bool) - ENABLE_CHECK_ENERGY_SEND = ManagedProperty('ENABLE_CHECK_ENERGY_SEND', type_=bool) - ENABLE_CHECK_ENERGY_RECEIVE = ManagedProperty('ENABLE_CHECK_ENERGY_RECEIVE', type_=bool) + ENABLE_TRANSLATION = ManagedProperty('ENABLE_TRANSLATION', type_=bool, serialize=False) + ENABLE_TRANSCRIPTION_SEND = ManagedProperty('ENABLE_TRANSCRIPTION_SEND', type_=bool, serialize=False) + ENABLE_TRANSCRIPTION_RECEIVE = ManagedProperty('ENABLE_TRANSCRIPTION_RECEIVE', type_=bool, serialize=False) + ENABLE_FOREGROUND = ManagedProperty('ENABLE_FOREGROUND', type_=bool, serialize=False) + ENABLE_CHECK_ENERGY_SEND = ManagedProperty('ENABLE_CHECK_ENERGY_SEND', type_=bool, serialize=False) + ENABLE_CHECK_ENERGY_RECEIVE = ManagedProperty('ENABLE_CHECK_ENERGY_RECEIVE', type_=bool, serialize=False) # --- Selectable dict/list properties (managed by descriptor, not serialized) --- # These are dynamically generated in init_config() based on installed packages/APIs @@ -1006,15 +1014,24 @@ class Config: self._config_data = json_load(fp) for key, value in self._config_data.items(): + # 読み込み時: serialize=True かつ readonlyでない Descriptor のみ反映。 + # 未知キー(Descriptorなし)は無視して注入を防止。 try: - setattr(self, key, value) + descriptor = getattr(type(self), key, None) + if isinstance(descriptor, ManagedProperty): + if descriptor.readonly or not descriptor.serialize: + continue + setattr(self, key, value) + elif isinstance(descriptor, ValidatedProperty): + if not descriptor.serialize: + continue + setattr(self, key, value) + else: + # 不明キーは破棄(古い/不要/改竄の可能性) + continue except Exception: errorLogging() - - with open(self.PATH_CONFIG, 'w', encoding="utf-8") as fp: - for var_name, var_func in json_serializable_vars.items(): - self._config_data[var_name] = var_func(self) - json_dump(self._config_data, fp, indent=4, ensure_ascii=False) + self.saveConfigToFile() # Auto-register all descriptors after Config class definition _auto_register_descriptors() From 5ca809c8dd92cdd86a7e43e84ef26c0555c87053 Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Sun, 23 Nov 2025 00:59:09 +0900 Subject: [PATCH 5/7] [Update] Config: Add model revalidation logic to ensure selected models are valid based on available options. --- src-python/config.py | 43 +++++++++++++++++++++++++++++++++++----- src-python/controller.py | 3 +++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src-python/config.py b/src-python/config.py index c841b2ae..55a46122 100644 --- a/src-python/config.py +++ b/src-python/config.py @@ -527,6 +527,19 @@ def _compute_device_validator(val, inst): return copy.deepcopy(val) return None +def _allowed_in_populated(list_attr_name: str): + def _inner(value, inst): + try: + lst = getattr(inst, list_attr_name) + except Exception: + return True # インスタンス状態取得失敗時も弾かない + if not lst: # 空/未初期化 + return True + if value is None: + return True + return value in lst + return _inner + class Config: """Application configuration singleton. @@ -719,11 +732,11 @@ class Config: USE_EXCLUDE_WORDS = ManagedProperty('USE_EXCLUDE_WORDS', type_=bool) CTRANSLATE2_WEIGHT_TYPE = ManagedProperty('CTRANSLATE2_WEIGHT_TYPE', type_=str, allowed=lambda v, inst: v in inst.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST) WHISPER_WEIGHT_TYPE = ManagedProperty('WHISPER_WEIGHT_TYPE', type_=str, allowed=lambda v, inst: v in inst.SELECTABLE_WHISPER_WEIGHT_TYPE_LIST) - SELECTED_PLAMO_MODEL = ManagedProperty('SELECTED_PLAMO_MODEL', type_=str, allowed=lambda v, inst: v in inst.SELECTABLE_PLAMO_MODEL_LIST) - SELECTED_GEMINI_MODEL = ManagedProperty('SELECTED_GEMINI_MODEL', type_=str, allowed=lambda v, inst: v in inst.SELECTABLE_GEMINI_MODEL_LIST) - SELECTED_OPENAI_MODEL = ManagedProperty('SELECTED_OPENAI_MODEL', type_=str, allowed=lambda v, inst: v in inst.SELECTABLE_OPENAI_MODEL_LIST) - SELECTED_LMSTUDIO_MODEL = ManagedProperty('SELECTED_LMSTUDIO_MODEL', type_=str, allowed=lambda v, inst: v in inst.SELECTABLE_LMSTUDIO_MODEL_LIST) - SELECTED_OLLAMA_MODEL = ManagedProperty('SELECTED_OLLAMA_MODEL', type_=str, allowed=lambda v, inst: v in inst.SELECTABLE_OLLAMA_MODEL_LIST) + SELECTED_PLAMO_MODEL = ManagedProperty('SELECTED_PLAMO_MODEL', type_=str, allowed=_allowed_in_populated('SELECTABLE_PLAMO_MODEL_LIST')) + SELECTED_GEMINI_MODEL = ManagedProperty('SELECTED_GEMINI_MODEL', type_=str, allowed=_allowed_in_populated('SELECTABLE_GEMINI_MODEL_LIST')) + SELECTED_OPENAI_MODEL = ManagedProperty('SELECTED_OPENAI_MODEL', type_=str, allowed=_allowed_in_populated('SELECTABLE_OPENAI_MODEL_LIST')) + SELECTED_LMSTUDIO_MODEL = ManagedProperty('SELECTED_LMSTUDIO_MODEL', type_=str, allowed=_allowed_in_populated('SELECTABLE_LMSTUDIO_MODEL_LIST')) + SELECTED_OLLAMA_MODEL = ManagedProperty('SELECTED_OLLAMA_MODEL', type_=str, allowed=_allowed_in_populated('SELECTABLE_OLLAMA_MODEL_LIST')) # --- Translation and language settings --- MIC_WORD_FILTER = ValidatedProperty('MIC_WORD_FILTER', _mic_word_filter_validator) @@ -1033,6 +1046,26 @@ class Config: errorLogging() self.saveConfigToFile() + def revalidate_selected_models(self): + pairs = [ + ('SELECTED_PLAMO_MODEL', 'SELECTABLE_PLAMO_MODEL_LIST'), + ('SELECTED_GEMINI_MODEL', 'SELECTABLE_GEMINI_MODEL_LIST'), + ('SELECTED_OPENAI_MODEL', 'SELECTABLE_OPENAI_MODEL_LIST'), + ('SELECTED_LMSTUDIO_MODEL', 'SELECTABLE_LMSTUDIO_MODEL_LIST'), + ('SELECTED_OLLAMA_MODEL', 'SELECTABLE_OLLAMA_MODEL_LIST'), + ] + for sel_attr, list_attr in pairs: + try: + current = getattr(self, sel_attr) + lst = getattr(self, list_attr) + if lst and current is not None and current not in lst: + if len(lst) > 0: + setattr(self, sel_attr, lst[0]) + else: + setattr(self, sel_attr, None) + except Exception: + errorLogging() + # Auto-register all descriptors after Config class definition _auto_register_descriptors() diff --git a/src-python/controller.py b/src-python/controller.py index db326cb2..2f3aa530 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -3138,6 +3138,9 @@ class Controller: model.stopWebSocketServer() printLog("WebSocket server host or port is not available") + printLog("Revalidate Selected Models") + config.revalidate_selected_models() + printLog("Update settings") self.updateConfigSettings() From 9a35577ec64a8ea707d429270a2beeac727b415e Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:01:30 +0900 Subject: [PATCH 6/7] [Update] Controller: Add methods for LMStudio and Ollama connection status checks --- src-python/controller.py | 10 ++++++ src-python/mainloop.py | 2 ++ src-python/model.py | 8 +++++ .../translation/translation_lmstudio.py | 32 ++++++++++--------- .../models/translation/translation_ollama.py | 7 ++-- .../translation/translation_translator.py | 20 ++++++++++++ 6 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src-python/controller.py b/src-python/controller.py index 2f3aa530..714b6a85 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -1900,6 +1900,9 @@ class Controller: } return response + def getTranslatorLMStudioConnection(self, *args, **kwargs) -> dict: + return {"status":200, "result":model.getTranslatorLMStudioConnected()} + def checkTranslatorLMStudioConnection(self, *args, **kwargs) -> dict: printLog("Check Translator LMStudio Connection") translator_name = "LMStudio" @@ -1937,6 +1940,10 @@ class Controller: } return response + def getConnectedLMStudio(self, *args, **kwargs) -> dict: + is_connected = model.getTranslatorLMStudioConnectedStatus() + return {"status":200, "result": is_connected} + def getTranslatorLMStudioURL(self, *args, **kwargs) -> dict: return {"status":200, "result":config.LMSTUDIO_URL} @@ -2015,6 +2022,9 @@ class Controller: } return response + def getTranslatorOllamaConnection(self, *args, **kwargs) -> dict: + return {"status":200, "result":model.getTranslatorOllamaConnected()} + def checkTranslatorOllamaConnection(self, *args, **kwargs) -> dict: printLog("Check Translator Ollama Connection") translator_name = "Ollama" diff --git a/src-python/mainloop.py b/src-python/mainloop.py index 58a760a5..20b4bf2f 100644 --- a/src-python/mainloop.py +++ b/src-python/mainloop.py @@ -207,6 +207,7 @@ mapping = { "/set/data/openai_auth_key": {"status": True, "variable":controller.setOpenAIAuthKey}, "/delete/data/openai_auth_key": {"status": True, "variable":controller.delOpenAIAuthKey}, + "/get/data/connected_lmstudio": {"status": True, "variable":controller.getTranslatorLMStudioConnection}, "/run/lmstudio_connection": {"status": True, "variable":controller.checkTranslatorLMStudioConnection}, "/get/data/selectable_lmstudio_model_list": {"status": True, "variable":controller.getTranslatorLStudioModelList}, "/get/data/selected_lmstudio_model": {"status": True, "variable":controller.getTranslatorLMStudioModel}, @@ -214,6 +215,7 @@ mapping = { "/get/data/lmstudio_url": {"status": True, "variable":controller.getTranslatorLMStudioURL}, "/set/data/lmstudio_url": {"status": True, "variable":controller.setTranslatorLMStudioURL}, + "/get/data/connected_ollama": {"status": True, "variable":controller.getTranslatorOllamaConnection}, "/run/ollama_connection": {"status": True, "variable":controller.checkTranslatorOllamaConnection}, "/get/data/selectable_ollama_model_list": {"status": True, "variable":controller.getTranslatorOllamaModelList}, "/get/data/selected_ollama_model": {"status": True, "variable":controller.getTranslatorOllamaModel}, diff --git a/src-python/model.py b/src-python/model.py index 58f947dc..0283d7d7 100644 --- a/src-python/model.py +++ b/src-python/model.py @@ -249,6 +249,10 @@ class Model: self.ensure_initialized() self.translator.updateOpenAIClient() + def getTranslatorLMStudioConnected(self) -> bool: + self.ensure_initialized() + return self.translator.getLMStudioConnected() + def authenticationTranslatorLMStudio(self, base_url: str) -> bool: result = self.translator.setLMStudioClientURL(base_url=base_url, root_path=config.PATH_LOCAL) return result @@ -265,6 +269,10 @@ class Model: self.ensure_initialized() self.translator.updateLMStudioClient() + def getTranslatorOllamaConnected(self) -> bool: + self.ensure_initialized() + return self.translator.getOllamaConnected() + def authenticationTranslatorOllama(self) -> bool: result = self.translator.checkOllamaClient(root_path=config.PATH_LOCAL) return result diff --git a/src-python/models/translation/translation_lmstudio.py b/src-python/models/translation/translation_lmstudio.py index 7751dc16..aa8509e8 100644 --- a/src-python/models/translation/translation_lmstudio.py +++ b/src-python/models/translation/translation_lmstudio.py @@ -1,6 +1,6 @@ -from openai import OpenAI from langchain_openai import ChatOpenAI from pydantic import SecretStr +import requests try: from .translation_languages import translation_lang @@ -8,33 +8,35 @@ try: except Exception: import sys from os import path as os_path - sys.path.append(os_path.dirname(os_path.dirname(os_path.dirname(os_path.abspath(__file__))))) - from translation_languages import translation_lang + sys.path.append(os_path.dirname(os_path.abspath(__file__))) + from translation_languages import translation_lang, loadTranslationLanguages from translation_utils import loadPromptConfig + loadTranslationLanguages(path=".", force=True) -def _authentication_check(api_key: str, base_url: str | None = None) -> bool: +def _authentication_check(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 + response = requests.get(f"{base_url}/models", timeout=0.2) + if response.status_code == 200: + return True + else: + return False except Exception: return False -def _get_available_text_models(api_key: str, base_url: str | None = None) -> list[str]: +def _get_available_text_models(base_url: str | None = None) -> list[str]: """Extract the list of available text models from the LM Studio. """ try: - client = OpenAI(api_key=api_key, base_url=base_url) - res = client.models.list() - models = res.data + response = requests.get(f"{base_url}/models", timeout=0.2) + models = response.json()["data"] except Exception: models = [] allowed_models = [] for model in models: - allowed_models.append(model.id) + allowed_models.append(model["id"]) allowed_models.sort() return allowed_models @@ -58,13 +60,13 @@ class LMStudioClient: return self.base_url def setBaseURL(self, base_url: str | None) -> None: - result = _authentication_check(api_key=self.api_key, base_url=base_url) + result = _authentication_check(base_url=base_url) if result: self.base_url = base_url return result def getModelList(self) -> list[str]: - return _get_available_text_models(api_key=self.api_key, base_url=self.base_url) if self.base_url else [] + return _get_available_text_models(base_url=self.base_url) if self.base_url else [] def getModel(self) -> str: return self.model @@ -108,7 +110,7 @@ class LMStudioClient: return content.strip() if __name__ == "__main__": - client = LMStudioClient(base_url="http://192.168.68.110:1234/v1") + client = LMStudioClient(base_url="http://127.0.0.1:1234/v1") models = client.getModelList() if models: print("Available models:", models) diff --git a/src-python/models/translation/translation_ollama.py b/src-python/models/translation/translation_ollama.py index 4152c953..ae4cc860 100644 --- a/src-python/models/translation/translation_ollama.py +++ b/src-python/models/translation/translation_ollama.py @@ -7,15 +7,16 @@ try: except Exception: import sys from os import path as os_path - sys.path.append(os_path.dirname(os_path.dirname(os_path.dirname(os_path.abspath(__file__))))) - from translation_languages import translation_lang + sys.path.append(os_path.dirname(os_path.abspath(__file__))) + from translation_languages import translation_lang, loadTranslationLanguages from translation_utils import loadPromptConfig + loadTranslationLanguages(path=".", force=True) def _authentication_check(base_url: str | None = None) -> bool: """Check authentication for Ollama API. """ try: - response = requests.get(f"{base_url}") + response = requests.get(f"{base_url}", timeout=0.2) if response.status_code == 200: return True else: diff --git a/src-python/models/translation/translation_translator.py b/src-python/models/translation/translation_translator.py index 249a09f1..4023bcc4 100644 --- a/src-python/models/translation/translation_translator.py +++ b/src-python/models/translation/translation_translator.py @@ -176,6 +176,16 @@ class Translator: """Update the OpenAI client (fetch available models).""" self.openai_client.updateClient() + def getLMStudioConnected(self) -> bool: + """Get LM Studio connection status. + + Returns True if connected, False otherwise. + """ + if self.lmstudio_client is None: + return False + else: + return True + def setLMStudioClientURL(self, base_url: str | None = None, root_path: str = None) -> bool: """Authenticate LM Studio with the provided base URL. @@ -207,6 +217,16 @@ class Translator: """Update the LM Studio client (fetch available models).""" self.lmstudio_client.updateClient() + def getOllamaConnected(self) -> bool: + """Get Ollama connection status. + + Returns True if connected, False otherwise. + """ + if self.ollama_client is None: + return False + else: + return True + def checkOllamaClient(self, root_path: str = None) -> bool: """Check if Ollama client is available. From fca4be59510954eb92c6e18b33894655ea13e9db Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:41:02 +0900 Subject: [PATCH 7/7] [Update] UI: Add 'get' endpoints for connection status retrieval. --- src-ui/logics/store.js | 4 ++-- src-ui/logics/useReceiveRoutes.js | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src-ui/logics/store.js b/src-ui/logics/store.js index 831d63da..abca4809 100644 --- a/src-ui/logics/store.js +++ b/src-ui/logics/store.js @@ -167,8 +167,8 @@ export const { atomInstance: Atom_NotificationStatus, useHook: useStore_Notifica key: 0, message: "", }, "NotificationStatus"); -export const { atomInstance: Atom_IsLMStudioConnected, useHook: useStore_IsLMStudioConnected } = createAtomWithHook(false, "IsLMStudioConnected", {is_state_ok: true}); -export const { atomInstance: Atom_IsOllamaConnected, useHook: useStore_IsOllamaConnected } = createAtomWithHook(false, "IsOllamaConnected", {is_state_ok: true}); +export const { atomInstance: Atom_IsLMStudioConnected, useHook: useStore_IsLMStudioConnected } = createAtomWithHook(false, "IsLMStudioConnected"); +export const { atomInstance: Atom_IsOllamaConnected, useHook: useStore_IsOllamaConnected } = createAtomWithHook(false, "IsOllamaConnected"); // Main Page // Common diff --git a/src-ui/logics/useReceiveRoutes.js b/src-ui/logics/useReceiveRoutes.js index c3368c96..164cce7e 100644 --- a/src-ui/logics/useReceiveRoutes.js +++ b/src-ui/logics/useReceiveRoutes.js @@ -20,7 +20,10 @@ export const STATIC_ROUTE_META_LIST = [ { endpoint: "/run/open_filepath_logs", ns: common, hook_name: "useOpenFolder", method_name: "openedFolder_MessageLogs" }, { endpoint: "/run/open_filepath_config_file", ns: common, hook_name: "useOpenFolder", method_name: "openedFolder_ConfigFile" }, + { endpoint: "/get/data/connected_lmstudio", ns: common, hook_name: "useLLMConnection", method_name: "setConnectionStatus_LMStudio" }, { endpoint: "/run/lmstudio_connection", ns: common, hook_name: "useLLMConnection", method_name: "setConnectionStatus_LMStudio" }, + + { endpoint: "/get/data/connected_ollama", ns: common, hook_name: "useLLMConnection", method_name: "setConnectionStatus_Ollama" }, { endpoint: "/run/ollama_connection", ns: common, hook_name: "useLLMConnection", method_name: "setConnectionStatus_Ollama" }, // Software Version