diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..60334c65 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,24 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": [ + "plugin:react/recommended" + ], + "parserOptions": { + "ecmaFeatures": { + "jsx": true // Allows for the parsing of JSX + } + }, + "rules": { + "react/prop-types": "off", + "@typescript-eslint/no-empty-interface": 0, + "no-unreachable": "warn", + "no-unused-vars": "warn", + "react/react-in-jsx-scope": "off", + "semi": ["error", "always"] + }, + "settings": { + "react": { + "version": "detect" + } + } +} diff --git a/.gitignore b/.gitignore index e7b75066..43c32504 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,44 @@ build/ dist/ /config.json memo.txt -VRCT.spec *.pyc logs/ .venv/ +.venv_cuda/ weights/ .vscode error.log *.exe *.ipynb + + +# Added by WebUI migration +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +.vscode +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.venv + +# Customize +/build \ No newline at end of file diff --git a/backend.spec b/backend.spec new file mode 100644 index 00000000..33ec3cae --- /dev/null +++ b/backend.spec @@ -0,0 +1,45 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['src-python\\mainloop.py'], + pathex=[], + binaries=[], + datas=[('./fonts', 'fonts/'), ('.venv/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv/Lib/site-packages/openvr', 'openvr/'), ('.venv/Lib/site-packages/pykakasi', 'pykakasi/'), ('.venv/Lib/site-packages/faster_whisper', 'faster_whisper/')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['pandas', 'matplotlib', 'PyQt5'], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='VRCT-sidecar-x86_64-pc-windows-msvc', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=[], +) +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='.', +) diff --git a/backend_cuda.spec b/backend_cuda.spec new file mode 100644 index 00000000..34ed248f --- /dev/null +++ b/backend_cuda.spec @@ -0,0 +1,45 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['src-python\\mainloop.py'], + pathex=[], + binaries=[], + datas=[('./fonts', 'fonts/'), ('.venv_cuda/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv_cuda/Lib/site-packages/openvr', 'openvr/'), ('.venv_cuda/Lib/site-packages/pykakasi', 'pykakasi/'), ('.venv_cuda/Lib/site-packages/faster_whisper', 'faster_whisper/')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['pandas', 'matplotlib', 'PyQt5'], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='VRCT-sidecar-x86_64-pc-windows-msvc', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=[], +) +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='.', +) diff --git a/batch/restart.bat b/batch/restart.bat deleted file mode 100644 index b3327d40..00000000 --- a/batch/restart.bat +++ /dev/null @@ -1,9 +0,0 @@ -@if not "%~0"=="%~dp0.\%~nx0" start /min cmd /c,"%~dp0.\%~nx0" %* & goto :eof - -set name=%1 -set local_path=%~dp0 - -taskkill /im %name% /F -ping -n 2 127.0.0.1 > nul -START "" "%local_path%%name%" -del /f "%~dp0%~nx0" \ No newline at end of file diff --git a/batch/update.bat b/batch/update.bat deleted file mode 100644 index 36087ca3..00000000 --- a/batch/update.bat +++ /dev/null @@ -1,20 +0,0 @@ -@if not "%~0"=="%~dp0.\%~nx0" start /min cmd /c,"%~dp0.\%~nx0" %* & goto :eof - -set exe_name=%1 -set folder_name=%2 -set folder_tmp=%3 -set restart=%4 -set local_path=%~dp0 - -taskkill /im %exe_name% /F -ping -n 2 127.0.0.1 > nul -del /f "%local_path%%exe_name%" -rmdir /s /q "%local_path%%folder_name%" -xcopy "%local_path%%folder_tmp%" "%local_path%" /E /I -rmdir /s /q "%local_path%%folder_tmp%" - -if %restart% == True ( - START "" "%local_path%%exe_name%" -) - -del /f "%~dp0%~nx0" \ No newline at end of file diff --git a/build.bat b/build.bat index b1e119f4..89ee8cbb 100644 --- a/build.bat +++ b/build.bat @@ -1,2 +1,2 @@ -pyinstaller --windowed --clean --noconfirm --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./fonts;fonts/" --add-data "./locales;locales/" --add-data "./batch;batch/" --name VRCT --add-data ".venv\Lib\site-packages\customtkinter;customtkinter/" --add-data ".venv\Lib\site-packages\zeroconf;zeroconf/" --add-data ".venv\Lib\site-packages\openvr;openvr/" --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py -"C:\Program Files (x86)\NSIS\makensis.exe" installer/installer.nsi \ No newline at end of file +call .venv/Scripts/activate +pyinstaller backend.spec --distpath src-tauri/bin --clean --noconfirm \ No newline at end of file diff --git a/build_cuda.bat b/build_cuda.bat new file mode 100644 index 00000000..73c85676 --- /dev/null +++ b/build_cuda.bat @@ -0,0 +1,2 @@ +call .venv_cuda/Scripts/activate +pyinstaller backend_cuda.spec --distpath src-tauri/bin --clean --noconfirm \ No newline at end of file diff --git a/config.py b/config.py deleted file mode 100644 index f5f89b49..00000000 --- a/config.py +++ /dev/null @@ -1,1157 +0,0 @@ -import sys -import inspect -from os import path as os_path, makedirs as os_makedirs -from json import load as json_load -from json import dump as json_dump -import tkinter as tk -from tkinter import font -from models.translation.translation_languages import translation_lang -from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice, getOutputDevices, getDefaultOutputDevice -from models.transcription.transcription_languages import transcription_lang -from utils import generatePercentageStringsList, isUniqueStrings - -json_serializable_vars = {} -def json_serializable(var_name): - def decorator(func): - json_serializable_vars[var_name] = func - return func - return decorator - -def saveJson(path, key, value): - with open(path, "r", encoding="utf-8") as fp: - json_data = json_load(fp) - json_data[key] = value - with open(path, "w", encoding="utf-8") as fp: - json_dump(json_data, fp, indent=4, ensure_ascii=False) - -class Config: - _instance = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super(Config, cls).__new__(cls) - cls._instance.init_config() - cls._instance.load_config() - return cls._instance - - # Read Only - @property - def VERSION(self): - return self._VERSION - - @property - def ENABLE_SPEAKER2CHATBOX_PASS_CONFIRMATION(self): - return self._ENABLE_SPEAKER2CHATBOX_PASS_CONFIRMATION - - @property - def PATH_LOCAL(self): - return self._PATH_LOCAL - - @property - def PATH_CONFIG(self): - return self._PATH_CONFIG - - @property - def PATH_LOGS(self): - return self._PATH_LOGS - - @property - def GITHUB_URL(self): - return self._GITHUB_URL - - @property - def UPDATER_URL(self): - return self._UPDATER_URL - - @property - def BOOTH_URL(self): - return self._BOOTH_URL - - @property - def DOCUMENTS_URL(self): - return self._DOCUMENTS_URL - - @property - def DEEPL_AUTH_KEY_PAGE_URL(self): - return self._DEEPL_AUTH_KEY_PAGE_URL - - @property - def TRANSPARENCY_RANGE(self): - return self._TRANSPARENCY_RANGE - - @property - def APPEARANCE_THEME_LIST(self): - return self._APPEARANCE_THEME_LIST - - @property - def UI_SCALING_LIST(self): - return self._UI_SCALING_LIST - - @property - def TEXTBOX_UI_SCALING_RANGE(self): - return self._TEXTBOX_UI_SCALING_RANGE - - @property - def MESSAGE_BOX_RATIO_RANGE(self): - return self._MESSAGE_BOX_RATIO_RANGE - - @property - def SELECTABLE_UI_LANGUAGES_DICT(self): - return self._SELECTABLE_UI_LANGUAGES_DICT - - @property - def SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT(self): - return self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT - - @property - def SELECTABLE_WHISPER_WEIGHT_TYPE_DICT(self): - return self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT - - @property - def MAX_MIC_ENERGY_THRESHOLD(self): - return self._MAX_MIC_ENERGY_THRESHOLD - - @property - def MAX_SPEAKER_ENERGY_THRESHOLD(self): - return self._MAX_SPEAKER_ENERGY_THRESHOLD - - # Read Write - @property - def ENABLE_SPEAKER2CHATBOX(self): - return self._ENABLE_SPEAKER2CHATBOX - - @ENABLE_SPEAKER2CHATBOX.setter - def ENABLE_SPEAKER2CHATBOX(self, value): - if isinstance(value, bool): - self._ENABLE_SPEAKER2CHATBOX = value - - @property - def ENABLE_TRANSLATION(self): - return self._ENABLE_TRANSLATION - - @ENABLE_TRANSLATION.setter - def ENABLE_TRANSLATION(self, value): - if isinstance(value, bool): - self._ENABLE_TRANSLATION = value - - @property - def ENABLE_TRANSCRIPTION_SEND(self): - return self._ENABLE_TRANSCRIPTION_SEND - - @ENABLE_TRANSCRIPTION_SEND.setter - def ENABLE_TRANSCRIPTION_SEND(self, value): - if isinstance(value, bool): - self._ENABLE_TRANSCRIPTION_SEND = value - - @property - def ENABLE_TRANSCRIPTION_RECEIVE(self): - return self._ENABLE_TRANSCRIPTION_RECEIVE - - @ENABLE_TRANSCRIPTION_RECEIVE.setter - def ENABLE_TRANSCRIPTION_RECEIVE(self, value): - if isinstance(value, bool): - self._ENABLE_TRANSCRIPTION_RECEIVE = value - - @property - def ENABLE_FOREGROUND(self): - return self._ENABLE_FOREGROUND - - @ENABLE_FOREGROUND.setter - def ENABLE_FOREGROUND(self, value): - if isinstance(value, bool): - self._ENABLE_FOREGROUND = value - - @property - def SOURCE_COUNTRY(self): - return self._SOURCE_COUNTRY - - @SOURCE_COUNTRY.setter - def SOURCE_COUNTRY(self, value): - if isinstance(value, str): - self._SOURCE_COUNTRY = value - - @property - def SOURCE_LANGUAGE(self): - return self._SOURCE_LANGUAGE - - @SOURCE_LANGUAGE.setter - def SOURCE_LANGUAGE(self, value): - if isinstance(value, str): - self._SOURCE_LANGUAGE = value - - @property - def TARGET_COUNTRY(self): - return self._TARGET_COUNTRY - - @TARGET_COUNTRY.setter - def TARGET_COUNTRY(self, value): - if isinstance(value, str): - self._TARGET_COUNTRY = value - - @property - def TARGET_LANGUAGE(self): - return self._TARGET_LANGUAGE - - @TARGET_LANGUAGE.setter - def TARGET_LANGUAGE(self, value): - if isinstance(value, str): - self._TARGET_LANGUAGE = value - - @property - def CHOICE_INPUT_TRANSLATOR(self): - return self._CHOICE_INPUT_TRANSLATOR - - @CHOICE_INPUT_TRANSLATOR.setter - def CHOICE_INPUT_TRANSLATOR(self, value): - if value in list(translation_lang.keys()): - self._CHOICE_INPUT_TRANSLATOR= value - - @property - def CHOICE_OUTPUT_TRANSLATOR(self): - return self._CHOICE_OUTPUT_TRANSLATOR - - @CHOICE_OUTPUT_TRANSLATOR.setter - def CHOICE_OUTPUT_TRANSLATOR(self, value): - if value in list(translation_lang.keys()): - self._CHOICE_OUTPUT_TRANSLATOR = value - - @property - def SENT_MESSAGES_LOG(self): - return self._SENT_MESSAGES_LOG - - @SENT_MESSAGES_LOG.setter - def SENT_MESSAGES_LOG(self, value): - if isinstance(value, list): - self._SENT_MESSAGES_LOG = value - - @property - def CURRENT_SENT_MESSAGES_LOG_INDEX(self): - return self._CURRENT_SENT_MESSAGES_LOG_INDEX - - @CURRENT_SENT_MESSAGES_LOG_INDEX.setter - def CURRENT_SENT_MESSAGES_LOG_INDEX(self, value): - if isinstance(value, int): - self._CURRENT_SENT_MESSAGES_LOG_INDEX = value - - @property - def IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION(self): - return self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION - - @IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION.setter - def IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION(self, value): - if isinstance(value, bool): - self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = value - - @property - def IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER(self): - return self._IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER - - @IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER.setter - def IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER(self, value): - if isinstance(value, bool): - self._IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = value - - @property - def IS_EASTER_EGG_ENABLED(self): - return self._IS_EASTER_EGG_ENABLED - - @IS_EASTER_EGG_ENABLED.setter - def IS_EASTER_EGG_ENABLED(self, value): - if isinstance(value, bool): - self._IS_EASTER_EGG_ENABLED = value - - # Save Json Data - ## Main Window - @property - @json_serializable('SELECTED_TAB_NO') - def SELECTED_TAB_NO(self): - return self._SELECTED_TAB_NO - - @SELECTED_TAB_NO.setter - def SELECTED_TAB_NO(self, value): - if isinstance(value, str): - self._SELECTED_TAB_NO = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('SELECTED_TAB_YOUR_TRANSLATOR_ENGINES') - def SELECTED_TAB_YOUR_TRANSLATOR_ENGINES(self): - return self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES - - @SELECTED_TAB_YOUR_TRANSLATOR_ENGINES.setter - def SELECTED_TAB_YOUR_TRANSLATOR_ENGINES(self, value): - if isinstance(value, dict): - self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('SELECTED_TAB_TARGET_TRANSLATOR_ENGINES') - def SELECTED_TAB_TARGET_TRANSLATOR_ENGINES(self): - return self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES - - @SELECTED_TAB_TARGET_TRANSLATOR_ENGINES.setter - def SELECTED_TAB_TARGET_TRANSLATOR_ENGINES(self, value): - if isinstance(value, dict): - self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('SELECTED_TAB_YOUR_LANGUAGES') - def SELECTED_TAB_YOUR_LANGUAGES(self): - return self._SELECTED_TAB_YOUR_LANGUAGES - - @SELECTED_TAB_YOUR_LANGUAGES.setter - def SELECTED_TAB_YOUR_LANGUAGES(self, value): - try: - if isinstance(value, dict): - value_old = self.SELECTED_TAB_YOUR_LANGUAGES - for k, v in value.items(): - language = v["language"] - country = v["country"] - if language not in list(transcription_lang.keys()) or country not in list(transcription_lang[language].keys()): - value[k] = value_old[k] - self._SELECTED_TAB_YOUR_LANGUAGES = value - except Exception: - pass - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('SELECTED_TAB_TARGET_LANGUAGES') - def SELECTED_TAB_TARGET_LANGUAGES(self): - return self._SELECTED_TAB_TARGET_LANGUAGES - - @SELECTED_TAB_TARGET_LANGUAGES.setter - def SELECTED_TAB_TARGET_LANGUAGES(self, value): - try: - if isinstance(value, dict): - value_old = self.SELECTED_TAB_TARGET_LANGUAGES - for k, v in value.items(): - language = v["language"] - country = v["country"] - if language not in list(transcription_lang.keys()) or country not in list(transcription_lang[language].keys()): - value[k] = value_old[k] - self._SELECTED_TAB_TARGET_LANGUAGES = value - except Exception: - pass - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('SELECTED_TRANSCRIPTION_ENGINE') - def SELECTED_TRANSCRIPTION_ENGINE(self): - return self._SELECTED_TRANSCRIPTION_ENGINE - - @SELECTED_TRANSCRIPTION_ENGINE.setter - def SELECTED_TRANSCRIPTION_ENGINE(self, value): - if isinstance(value, str): - self._SELECTED_TRANSCRIPTION_ENGINE = value - # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE') - def IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE(self): - return self._IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE - - @IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE.setter - def IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE(self, value): - if isinstance(value, bool): - self._IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - ## Config Window - @property - @json_serializable('TRANSPARENCY') - def TRANSPARENCY(self): - return self._TRANSPARENCY - - @TRANSPARENCY.setter - def TRANSPARENCY(self, value): - if isinstance(value, int) and self.TRANSPARENCY_RANGE[0] <= value <= self.TRANSPARENCY_RANGE[1]: - self._TRANSPARENCY = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('APPEARANCE_THEME') - def APPEARANCE_THEME(self): - return self._APPEARANCE_THEME - - @APPEARANCE_THEME.setter - def APPEARANCE_THEME(self, value): - if value in self.APPEARANCE_THEME_LIST: - self._APPEARANCE_THEME = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('UI_SCALING') - def UI_SCALING(self): - return self._UI_SCALING - - @UI_SCALING.setter - def UI_SCALING(self, value): - if value in self.UI_SCALING_LIST: - self._UI_SCALING = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('TEXTBOX_UI_SCALING') - def TEXTBOX_UI_SCALING(self): - return self._TEXTBOX_UI_SCALING - - @TEXTBOX_UI_SCALING.setter - def TEXTBOX_UI_SCALING(self, value): - if isinstance(value, int) and self.TEXTBOX_UI_SCALING_RANGE[0] <= value <= self.TEXTBOX_UI_SCALING_RANGE[1]: - self._TEXTBOX_UI_SCALING = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('MESSAGE_BOX_RATIO') - def MESSAGE_BOX_RATIO(self): - return self._MESSAGE_BOX_RATIO - - @MESSAGE_BOX_RATIO.setter - def MESSAGE_BOX_RATIO(self, value): - if isinstance(value, int) and self.MESSAGE_BOX_RATIO_RANGE[0] <= value <= self.MESSAGE_BOX_RATIO_RANGE[1]: - self._MESSAGE_BOX_RATIO = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('FONT_FAMILY') - def FONT_FAMILY(self): - return self._FONT_FAMILY - - @FONT_FAMILY.setter - def FONT_FAMILY(self, value): - root = tk.Tk() - root.withdraw() - if value in list(font.families()): - self._FONT_FAMILY = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - root.destroy() - - @property - @json_serializable('UI_LANGUAGE') - def UI_LANGUAGE(self): - return self._UI_LANGUAGE - - @UI_LANGUAGE.setter - def UI_LANGUAGE(self, value): - if value in list(self.SELECTABLE_UI_LANGUAGES_DICT.keys()): - self._UI_LANGUAGE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY') - def ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY(self): - return self._ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY - - @ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY.setter - def ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY(self, value): - if isinstance(value, bool): - self._ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('MAIN_WINDOW_GEOMETRY') - def MAIN_WINDOW_GEOMETRY(self): - return self._MAIN_WINDOW_GEOMETRY - - @MAIN_WINDOW_GEOMETRY.setter - def MAIN_WINDOW_GEOMETRY(self, value): - if isinstance(value, dict) and set(value.keys()) == set(self.MAIN_WINDOW_GEOMETRY.keys()): - for key, value in value.items(): - if isinstance(value, str): - self._MAIN_WINDOW_GEOMETRY[key] = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.MAIN_WINDOW_GEOMETRY) - - @property - @json_serializable('CHOICE_MIC_HOST') - def CHOICE_MIC_HOST(self): - return self._CHOICE_MIC_HOST - - @CHOICE_MIC_HOST.setter - def CHOICE_MIC_HOST(self, value): - if value in [host for host in getInputDevices().keys()]: - self._CHOICE_MIC_HOST = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('CHOICE_MIC_DEVICE') - def CHOICE_MIC_DEVICE(self): - return self._CHOICE_MIC_DEVICE - - @CHOICE_MIC_DEVICE.setter - def CHOICE_MIC_DEVICE(self, value): - if value in [device["name"] for device in getInputDevices()[self.CHOICE_MIC_HOST]]: - self._CHOICE_MIC_DEVICE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_MIC_ENERGY_THRESHOLD') - def INPUT_MIC_ENERGY_THRESHOLD(self): - return self._INPUT_MIC_ENERGY_THRESHOLD - - @INPUT_MIC_ENERGY_THRESHOLD.setter - def INPUT_MIC_ENERGY_THRESHOLD(self, value): - if isinstance(value, int): - self._INPUT_MIC_ENERGY_THRESHOLD = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD') - def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self): - return self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD - - @INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD.setter - def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self, value): - if isinstance(value, bool): - self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_MIC_RECORD_TIMEOUT') - def INPUT_MIC_RECORD_TIMEOUT(self): - return self._INPUT_MIC_RECORD_TIMEOUT - - @INPUT_MIC_RECORD_TIMEOUT.setter - def INPUT_MIC_RECORD_TIMEOUT(self, value): - if isinstance(value, int): - self._INPUT_MIC_RECORD_TIMEOUT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_MIC_PHRASE_TIMEOUT') - def INPUT_MIC_PHRASE_TIMEOUT(self): - return self._INPUT_MIC_PHRASE_TIMEOUT - - @INPUT_MIC_PHRASE_TIMEOUT.setter - def INPUT_MIC_PHRASE_TIMEOUT(self, value): - if isinstance(value, int): - self._INPUT_MIC_PHRASE_TIMEOUT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_MIC_MAX_PHRASES') - def INPUT_MIC_MAX_PHRASES(self): - return self._INPUT_MIC_MAX_PHRASES - - @INPUT_MIC_MAX_PHRASES.setter - def INPUT_MIC_MAX_PHRASES(self, value): - if isinstance(value, int): - self._INPUT_MIC_MAX_PHRASES = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_MIC_WORD_FILTER') - def INPUT_MIC_WORD_FILTER(self): - return self._INPUT_MIC_WORD_FILTER - - @INPUT_MIC_WORD_FILTER.setter - def INPUT_MIC_WORD_FILTER(self, value): - if isinstance(value, list): - self._INPUT_MIC_WORD_FILTER = sorted(set(value), key=value.index) - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_MIC_AVG_LOGPROB') - def INPUT_MIC_AVG_LOGPROB(self): - return self._INPUT_MIC_AVG_LOGPROB - - @INPUT_MIC_AVG_LOGPROB.setter - def INPUT_MIC_AVG_LOGPROB(self, value): - if isinstance(value, float) or isinstance(value, int): - self._INPUT_MIC_AVG_LOGPROB = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_MIC_NO_SPEECH_PROB') - def INPUT_MIC_NO_SPEECH_PROB(self): - return self._INPUT_MIC_NO_SPEECH_PROB - - @INPUT_MIC_NO_SPEECH_PROB.setter - def INPUT_MIC_NO_SPEECH_PROB(self, value): - if isinstance(value, float) or isinstance(value, int): - self._INPUT_MIC_NO_SPEECH_PROB = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('CHOICE_SPEAKER_DEVICE') - def CHOICE_SPEAKER_DEVICE(self): - return self._CHOICE_SPEAKER_DEVICE - - @CHOICE_SPEAKER_DEVICE.setter - def CHOICE_SPEAKER_DEVICE(self, value): - if value in [device["name"] for device in getOutputDevices()]: - self._CHOICE_SPEAKER_DEVICE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_SPEAKER_ENERGY_THRESHOLD') - def INPUT_SPEAKER_ENERGY_THRESHOLD(self): - return self._INPUT_SPEAKER_ENERGY_THRESHOLD - - @INPUT_SPEAKER_ENERGY_THRESHOLD.setter - def INPUT_SPEAKER_ENERGY_THRESHOLD(self, value): - if isinstance(value, int): - self._INPUT_SPEAKER_ENERGY_THRESHOLD = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD') - def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self): - return self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD - - @INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.setter - def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self, value): - if isinstance(value, bool): - self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_SPEAKER_RECORD_TIMEOUT') - def INPUT_SPEAKER_RECORD_TIMEOUT(self): - return self._INPUT_SPEAKER_RECORD_TIMEOUT - - @INPUT_SPEAKER_RECORD_TIMEOUT.setter - def INPUT_SPEAKER_RECORD_TIMEOUT(self, value): - if isinstance(value, int): - self._INPUT_SPEAKER_RECORD_TIMEOUT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_SPEAKER_PHRASE_TIMEOUT') - def INPUT_SPEAKER_PHRASE_TIMEOUT(self): - return self._INPUT_SPEAKER_PHRASE_TIMEOUT - - @INPUT_SPEAKER_PHRASE_TIMEOUT.setter - def INPUT_SPEAKER_PHRASE_TIMEOUT(self, value): - if isinstance(value, int): - self._INPUT_SPEAKER_PHRASE_TIMEOUT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_SPEAKER_MAX_PHRASES') - def INPUT_SPEAKER_MAX_PHRASES(self): - return self._INPUT_SPEAKER_MAX_PHRASES - - @INPUT_SPEAKER_MAX_PHRASES.setter - def INPUT_SPEAKER_MAX_PHRASES(self, value): - if isinstance(value, int): - self._INPUT_SPEAKER_MAX_PHRASES = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_SPEAKER_AVG_LOGPROB') - def INPUT_SPEAKER_AVG_LOGPROB(self): - return self._INPUT_SPEAKER_AVG_LOGPROB - - @INPUT_SPEAKER_AVG_LOGPROB.setter - def INPUT_SPEAKER_AVG_LOGPROB(self, value): - if isinstance(value, float) or isinstance(value, int): - self._INPUT_SPEAKER_AVG_LOGPROB = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('INPUT_SPEAKER_NO_SPEECH_PROB') - def INPUT_SPEAKER_NO_SPEECH_PROB(self): - return self._INPUT_SPEAKER_NO_SPEECH_PROB - - @INPUT_SPEAKER_NO_SPEECH_PROB.setter - def INPUT_SPEAKER_NO_SPEECH_PROB(self, value): - if isinstance(value, float) or isinstance(value, int): - self._INPUT_SPEAKER_NO_SPEECH_PROB = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('OSC_IP_ADDRESS') - def OSC_IP_ADDRESS(self): - return self._OSC_IP_ADDRESS - - @OSC_IP_ADDRESS.setter - def OSC_IP_ADDRESS(self, value): - if isinstance(value, str): - self._OSC_IP_ADDRESS = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('OSC_PORT') - def OSC_PORT(self): - return self._OSC_PORT - - @OSC_PORT.setter - def OSC_PORT(self, value): - if isinstance(value, int): - self._OSC_PORT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('AUTH_KEYS') - def AUTH_KEYS(self): - return self._AUTH_KEYS - - @AUTH_KEYS.setter - def AUTH_KEYS(self, value): - if isinstance(value, dict) and set(value.keys()) == set(self.AUTH_KEYS.keys()): - for key, value in value.items(): - if isinstance(value, str): - self._AUTH_KEYS[key] = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) - - @property - @json_serializable('USE_TRANSLATION_FEATURE') - def USE_TRANSLATION_FEATURE(self): - return self._USE_TRANSLATION_FEATURE - - @USE_TRANSLATION_FEATURE.setter - def USE_TRANSLATION_FEATURE(self, value): - if isinstance(value, bool): - self._USE_TRANSLATION_FEATURE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('USE_WHISPER_FEATURE') - def USE_WHISPER_FEATURE(self): - return self._USE_WHISPER_FEATURE - - @USE_WHISPER_FEATURE.setter - def USE_WHISPER_FEATURE(self, value): - if isinstance(value, bool): - self._USE_WHISPER_FEATURE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('CTRANSLATE2_WEIGHT_TYPE') - def CTRANSLATE2_WEIGHT_TYPE(self): - return self._CTRANSLATE2_WEIGHT_TYPE - - @CTRANSLATE2_WEIGHT_TYPE.setter - def CTRANSLATE2_WEIGHT_TYPE(self, value): - # if isinstance(value, str) and value in self.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT: - if isinstance(value, str): - self._CTRANSLATE2_WEIGHT_TYPE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('WHISPER_WEIGHT_TYPE') - def WHISPER_WEIGHT_TYPE(self): - return self._WHISPER_WEIGHT_TYPE - - @WHISPER_WEIGHT_TYPE.setter - def WHISPER_WEIGHT_TYPE(self, value): - if isinstance(value, str): - self._WHISPER_WEIGHT_TYPE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('ENABLE_AUTO_CLEAR_MESSAGE_BOX') - def ENABLE_AUTO_CLEAR_MESSAGE_BOX(self): - return self._ENABLE_AUTO_CLEAR_MESSAGE_BOX - - @ENABLE_AUTO_CLEAR_MESSAGE_BOX.setter - def ENABLE_AUTO_CLEAR_MESSAGE_BOX(self, value): - if isinstance(value, bool): - self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('ENABLE_SEND_ONLY_TRANSLATED_MESSAGES') - def ENABLE_SEND_ONLY_TRANSLATED_MESSAGES(self): - return self._ENABLE_SEND_ONLY_TRANSLATED_MESSAGES - - @ENABLE_SEND_ONLY_TRANSLATED_MESSAGES.setter - def ENABLE_SEND_ONLY_TRANSLATED_MESSAGES(self, value): - if isinstance(value, bool): - self._ENABLE_SEND_ONLY_TRANSLATED_MESSAGES = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('SEND_MESSAGE_BUTTON_TYPE') - def SEND_MESSAGE_BUTTON_TYPE(self): - return self._SEND_MESSAGE_BUTTON_TYPE - - @SEND_MESSAGE_BUTTON_TYPE.setter - def SEND_MESSAGE_BUTTON_TYPE(self, value): - if isinstance(value, str): - self._SEND_MESSAGE_BUTTON_TYPE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('OVERLAY_SETTINGS') - def OVERLAY_SETTINGS(self): - return self._OVERLAY_SETTINGS - - @OVERLAY_SETTINGS.setter - def OVERLAY_SETTINGS(self, value): - if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SETTINGS.keys()): - for key, value in value.items(): - if isinstance(value, float): - self._OVERLAY_SETTINGS[key] = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.OVERLAY_SETTINGS) - - @property - @json_serializable('ENABLE_OVERLAY_SMALL_LOG') - def ENABLE_OVERLAY_SMALL_LOG(self): - return self._ENABLE_OVERLAY_SMALL_LOG - - @ENABLE_OVERLAY_SMALL_LOG.setter - def ENABLE_OVERLAY_SMALL_LOG(self, value): - if isinstance(value, bool): - self._ENABLE_OVERLAY_SMALL_LOG = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('OVERLAY_SMALL_LOG_SETTINGS') - def OVERLAY_SMALL_LOG_SETTINGS(self): - return self._OVERLAY_SMALL_LOG_SETTINGS - - @OVERLAY_SMALL_LOG_SETTINGS.setter - def OVERLAY_SMALL_LOG_SETTINGS(self, value): - if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SMALL_LOG_SETTINGS.keys()): - for key, value in value.items(): - match (key): - case "x_pos" | "y_pos" | "z_pos" | "x_rotation" | "y_rotation" | "z_rotation": - if isinstance(value, float): - self._OVERLAY_SMALL_LOG_SETTINGS[key] = value - case "display_duration" | "fadeout_duration": - if isinstance(value, int): - self._OVERLAY_SMALL_LOG_SETTINGS[key] = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.OVERLAY_SMALL_LOG_SETTINGS) - - @property - @json_serializable('OVERLAY_UI_TYPE') - def OVERLAY_UI_TYPE(self): - return self._OVERLAY_UI_TYPE - - @OVERLAY_UI_TYPE.setter - def OVERLAY_UI_TYPE(self, value): - if isinstance(value, str): - self._OVERLAY_UI_TYPE = value - # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('ENABLE_SEND_MESSAGE_TO_VRC') - def ENABLE_SEND_MESSAGE_TO_VRC(self): - return self._ENABLE_SEND_MESSAGE_TO_VRC - - @ENABLE_SEND_MESSAGE_TO_VRC.setter - def ENABLE_SEND_MESSAGE_TO_VRC(self, value): - if isinstance(value, bool): - self._ENABLE_SEND_MESSAGE_TO_VRC = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('SEND_MESSAGE_FORMAT') - def SEND_MESSAGE_FORMAT(self): - return self._SEND_MESSAGE_FORMAT - - @SEND_MESSAGE_FORMAT.setter - def SEND_MESSAGE_FORMAT(self, value): - if isinstance(value, str): - if isUniqueStrings(["[message]"], value) is False: - value = "[message]" - self._SEND_MESSAGE_FORMAT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('SEND_MESSAGE_FORMAT_WITH_T') - def SEND_MESSAGE_FORMAT_WITH_T(self): - return self._SEND_MESSAGE_FORMAT_WITH_T - - @SEND_MESSAGE_FORMAT_WITH_T.setter - def SEND_MESSAGE_FORMAT_WITH_T(self, value): - if isinstance(value, str): - if isUniqueStrings(["[message]", "[translation]"], value) is False: - value = "[message]([translation])" - self._SEND_MESSAGE_FORMAT_WITH_T = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('RECEIVED_MESSAGE_FORMAT') - def RECEIVED_MESSAGE_FORMAT(self): - return self._RECEIVED_MESSAGE_FORMAT - - @RECEIVED_MESSAGE_FORMAT.setter - def RECEIVED_MESSAGE_FORMAT(self, value): - if isinstance(value, str): - if isUniqueStrings(["[message]"], value) is False: - value = "[message]" - self._RECEIVED_MESSAGE_FORMAT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('RECEIVED_MESSAGE_FORMAT_WITH_T') - def RECEIVED_MESSAGE_FORMAT_WITH_T(self): - return self._RECEIVED_MESSAGE_FORMAT_WITH_T - - @RECEIVED_MESSAGE_FORMAT_WITH_T.setter - def RECEIVED_MESSAGE_FORMAT_WITH_T(self, value): - if isinstance(value, str): - if isUniqueStrings(["[message]", "[translation]"], value) is False: - value = "[message]([translation])" - self._RECEIVED_MESSAGE_FORMAT_WITH_T = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - # Speaker2Chatbox------------------ - @property - @json_serializable('ENABLE_SPEAKER2CHATBOX_PASS') - def ENABLE_SPEAKER2CHATBOX_PASS(self): - return self._ENABLE_SPEAKER2CHATBOX_PASS - - @ENABLE_SPEAKER2CHATBOX_PASS.setter - def ENABLE_SPEAKER2CHATBOX_PASS(self, value): - if isinstance(value, str): - self._ENABLE_SPEAKER2CHATBOX_PASS = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC') - def ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC(self): - return self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC - - @ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC.setter - def ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC(self, value): - if isinstance(value, bool): - if self._ENABLE_SPEAKER2CHATBOX is True: - self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = value - else: - self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = False - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - # Speaker2Chatbox------------------ - - - - @property - @json_serializable('ENABLE_LOGGER') - def ENABLE_LOGGER(self): - return self._ENABLE_LOGGER - - @ENABLE_LOGGER.setter - def ENABLE_LOGGER(self, value): - if isinstance(value, bool): - self._ENABLE_LOGGER = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('ENABLE_VRC_MIC_MUTE_SYNC') - def ENABLE_VRC_MIC_MUTE_SYNC(self): - return self._ENABLE_VRC_MIC_MUTE_SYNC - - @ENABLE_VRC_MIC_MUTE_SYNC.setter - def ENABLE_VRC_MIC_MUTE_SYNC(self, value): - if isinstance(value, bool): - self._ENABLE_VRC_MIC_MUTE_SYNC = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - @json_serializable('IS_CONFIG_WINDOW_COMPACT_MODE') - def IS_CONFIG_WINDOW_COMPACT_MODE(self): - return self._IS_CONFIG_WINDOW_COMPACT_MODE - - @IS_CONFIG_WINDOW_COMPACT_MODE.setter - def IS_CONFIG_WINDOW_COMPACT_MODE(self, value): - if isinstance(value, bool): - self._IS_CONFIG_WINDOW_COMPACT_MODE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - def init_config(self): - # Read Only - self._VERSION = "2.2.9" - self._ENABLE_SPEAKER2CHATBOX = False # Speaker2Chatbox - self._ENABLE_SPEAKER2CHATBOX_PASS_CONFIRMATION = "VRCT=0YEN" - self._PATH_LOCAL = os_path.dirname(sys.argv[0]) - self._PATH_CONFIG = os_path.join(self._PATH_LOCAL, "config.json") - self._PATH_LOGS = os_path.join(self._PATH_LOCAL, "logs") - os_makedirs(self._PATH_LOGS, exist_ok=True) - self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" - self._UPDATER_URL = "https://api.github.com/repos/misyaguziya/VRCT_updater/releases/latest" - self._BOOTH_URL = "https://misyaguziya.booth.pm/" - self._DOCUMENTS_URL = "https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246" - self._DEEPL_AUTH_KEY_PAGE_URL = "https://www.deepl.com/ja/account/summary" - self._TRANSPARENCY_RANGE = (50, 100) - self._APPEARANCE_THEME_LIST = ["Light", "Dark", "System"] - self._UI_SCALING_LIST = generatePercentageStringsList(start=40, end=200, step=10) - self._TEXTBOX_UI_SCALING_RANGE = (50, 200) - self._MESSAGE_BOX_RATIO_RANGE = (1, 99) - self._SELECTABLE_UI_LANGUAGES_DICT = { - "en": "English", - "ja": "日本語", - "ko": "한국어", - "zh-Hant": "繁體中文", - "zh-Hans": "简体中文" - # If you want to add a new language and key, please append it here. - } - self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = { - # {Save json str}: {i18n_placeholder} pairs - "Small": "Small", - "Large": "Large", - } - - self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = { - # {Save json str}: {i18n_placeholder} pairs - "tiny": "tiny", - "base": "base", - "small": "small", - "medium": "medium", - "large-v1": "large-v1", - "large-v2": "large-v2", - "large-v3": "large-v3", - } - - self._MAX_MIC_ENERGY_THRESHOLD = 2000 - self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 - - # Read Write - self._ENABLE_TRANSLATION = False - self._ENABLE_TRANSCRIPTION_SEND = False - self._ENABLE_TRANSCRIPTION_RECEIVE = False - self._ENABLE_FOREGROUND = False - self._CHOICE_INPUT_TRANSLATOR = "CTranslate2" - self._CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" - self._SOURCE_LANGUAGE = "Japanese" - self._SOURCE_COUNTRY = "Japan" - self._TARGET_LANGUAGE = "English" - self._TARGET_COUNTRY = "United States" - self._SENT_MESSAGES_LOG = [] - self._CURRENT_SENT_MESSAGES_LOG_INDEX = 0 - self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False - self._IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False - self._IS_EASTER_EGG_ENABLED = False - - # Save Json Data - ## Main Window - self._SELECTED_TAB_NO = "1" - self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = { - "1":"CTranslate2", - "2":"CTranslate2", - "3":"CTranslate2", - } - self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = { - "1":"CTranslate2", - "2":"CTranslate2", - "3":"CTranslate2", - } - self._SELECTED_TAB_YOUR_LANGUAGES = { - "1":{ - "language":"Japanese", - "country":"Japan" - }, - "2":{ - "language":"Japanese", - "country":"Japan" - }, - "3":{ - "language":"Japanese", - "country":"Japan" - }, - } - self._SELECTED_TAB_TARGET_LANGUAGES = { - "1":{ - "language":"English", - "country":"United States" - }, - "2":{ - "language":"English", - "country":"United States" - }, - "3":{ - "language":"English", - "country":"United States" - }, - } - self._SELECTED_TRANSCRIPTION_ENGINE = "Google" - self._IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False - - ## Config Window - self._TRANSPARENCY = 100 - self._APPEARANCE_THEME = "Dark" - self._UI_SCALING = "100%" - self._TEXTBOX_UI_SCALING = 100 - self._MESSAGE_BOX_RATIO = 10 - self._FONT_FAMILY = "Yu Gothic UI" - self._UI_LANGUAGE = "en" - self._ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY = True - self._MAIN_WINDOW_GEOMETRY = { - "x_pos": "0", - "y_pos": "0", - "width": "870", - "height": "654", - } - self._CHOICE_MIC_HOST = getDefaultInputDevice()["host"]["name"] - self._CHOICE_MIC_DEVICE = getDefaultInputDevice()["device"]["name"] - self._INPUT_MIC_ENERGY_THRESHOLD = 300 - self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = False - self._INPUT_MIC_RECORD_TIMEOUT = 3 - self._INPUT_MIC_PHRASE_TIMEOUT = 3 - self._INPUT_MIC_MAX_PHRASES = 10 - self._INPUT_MIC_WORD_FILTER = [] - self._INPUT_MIC_AVG_LOGPROB=-0.8 - self._INPUT_MIC_NO_SPEECH_PROB=0.6 - self._CHOICE_SPEAKER_DEVICE = getDefaultOutputDevice()["device"]["name"] - self._INPUT_SPEAKER_ENERGY_THRESHOLD = 300 - self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = False - self._INPUT_SPEAKER_RECORD_TIMEOUT = 3 - self._INPUT_SPEAKER_PHRASE_TIMEOUT = 3 - self._INPUT_SPEAKER_MAX_PHRASES = 10 - self._INPUT_SPEAKER_AVG_LOGPROB=-0.8 - self._INPUT_SPEAKER_NO_SPEECH_PROB=0.6 - self._OSC_IP_ADDRESS = "127.0.0.1" - self._OSC_PORT = 9000 - self._AUTH_KEYS = { - "DeepL_API": None, - } - self._USE_TRANSLATION_FEATURE = True - self._CTRANSLATE2_WEIGHT_TYPE = "Small" - self._USE_WHISPER_FEATURE = False - self._WHISPER_WEIGHT_TYPE = "base" - self._SEND_MESSAGE_FORMAT = "[message]" - self._SEND_MESSAGE_FORMAT_WITH_T = "[message]([translation])" - self._RECEIVED_MESSAGE_FORMAT = "[message]" - self._RECEIVED_MESSAGE_FORMAT_WITH_T = "[message]([translation])" - self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = True - self._ENABLE_SEND_ONLY_TRANSLATED_MESSAGES = False - self._SEND_MESSAGE_BUTTON_TYPE = "show" - self._OVERLAY_SETTINGS = { - "opacity": 1.0, - "ui_scaling": 1.0, - } - self._ENABLE_OVERLAY_SMALL_LOG = False - self._OVERLAY_SMALL_LOG_SETTINGS = { - "x_pos": 0.0, - "y_pos": 0.0, - "z_pos": 0.0, - "x_rotation": 0.0, - "y_rotation": 0.0, - "z_rotation": 0.0, - "display_duration": 5, - "fadeout_duration": 2, - } - self._OVERLAY_UI_TYPE = "default" - self._ENABLE_SEND_MESSAGE_TO_VRC = True - self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = False # Speaker2Chatbox - self._ENABLE_SPEAKER2CHATBOX_PASS = "000000000" - self._ENABLE_LOGGER = False - self._ENABLE_VRC_MIC_MUTE_SYNC = False - self._IS_CONFIG_WINDOW_COMPACT_MODE = False - - def load_config(self): - if os_path.isfile(self.PATH_CONFIG) is not False: - with open(self.PATH_CONFIG, 'r', encoding="utf-8") as fp: - config = json_load(fp) - - old_message_format = None - for key in config.keys(): - if key == "MESSAGE_FORMAT": - old_message_format = config[key] - setattr(self, key, config[key]) - - # Force to Enable Speaker2Chatbox - self.ENABLE_SPEAKER2CHATBOX = True - - if old_message_format is not None: - setattr(self, "SEND_MESSAGE_FORMAT_WITH_T", old_message_format) - - with open(self.PATH_CONFIG, 'w', encoding="utf-8") as fp: - config = {} - for var_name, var_func in json_serializable_vars.items(): - config[var_name] = var_func(self) - json_dump(config, fp, indent=4, ensure_ascii=False) - -config = Config() \ No newline at end of file diff --git a/controller.py b/controller.py deleted file mode 100644 index 57ee6779..00000000 --- a/controller.py +++ /dev/null @@ -1,1183 +0,0 @@ -from time import sleep -from subprocess import Popen -from threading import Thread -from config import config -from model import model -from view import view -from utils import getKeyByValue, isUniqueStrings, strPctToInt -import argparse - -# Common -def callbackUpdateSoftware(func=None): - setMainWindowGeometry() - model.updateSoftware(restart=True, func=func) - -def callbackRestartSoftware(): - setMainWindowGeometry() - model.reStartSoftware() - -def callbackFilepathLogs(): - print("callbackFilepathLogs", config.PATH_LOGS.replace('/', '\\')) - Popen(['explorer', config.PATH_LOGS.replace('/', '\\')], shell=True) - -def callbackFilepathConfigFile(): - print("callbackFilepathConfigFile", config.PATH_LOCAL.replace('/', '\\')) - Popen(['explorer', config.PATH_LOCAL.replace('/', '\\')], shell=True) - -def callbackQuitVrct(): - setMainWindowGeometry() - -def callbackEnableEasterEgg(): - config.IS_EASTER_EGG_ENABLED = True - config.OVERLAY_UI_TYPE = "sakura" - view.printToTextbox_enableEasterEgg() - -def setMainWindowGeometry(): - PRE_SCALING_INT = strPctToInt(view.getPreUiScaling()) - NEW_SCALING_INT = strPctToInt(config.UI_SCALING) - MULTIPLY_FLOAT = (NEW_SCALING_INT / PRE_SCALING_INT) - main_window_geometry = view.getMainWindowGeometry(return_int=True) - main_window_geometry["width"] = str(int(main_window_geometry["width"] * MULTIPLY_FLOAT)) - main_window_geometry["height"] = str(int(main_window_geometry["height"] * MULTIPLY_FLOAT)) - main_window_geometry["x_pos"] = str(main_window_geometry["x_pos"]) - main_window_geometry["y_pos"] = str(main_window_geometry["y_pos"]) - config.MAIN_WINDOW_GEOMETRY = main_window_geometry - -def messageFormatter(format_type:str, translation, message): - if format_type == "RECEIVED": - FORMAT_WITH_T = config.RECEIVED_MESSAGE_FORMAT_WITH_T - FORMAT = config.RECEIVED_MESSAGE_FORMAT - elif format_type == "SEND": - FORMAT_WITH_T = config.SEND_MESSAGE_FORMAT_WITH_T - FORMAT = config.SEND_MESSAGE_FORMAT - else: - raise ValueError("format_type is not found", format_type) - - if len(translation) > 0: - osc_message = FORMAT_WITH_T.replace("[message]", message) - osc_message = osc_message.replace("[translation]", translation) - else: - osc_message = FORMAT.replace("[message]", message) - return osc_message - -def changeToCTranslate2Process(): - if config.CHOICE_INPUT_TRANSLATOR != "CTranslate2" or config.CHOICE_OUTPUT_TRANSLATOR != "CTranslate2": - config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" - config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" - updateTranslationEngineAndEngineList() - view.printToTextbox_TranslationEngineLimitError() - -# func transcription send message -def sendMicMessage(message): - if len(message) > 0: - addSentMessageLog(message) - translation = "" - if model.checkKeywords(message): - view.printToTextbox_DetectedByWordFilter(detected_message=message) - return - elif model.detectRepeatSendMessage(message): - return - elif config.ENABLE_TRANSLATION is False: - pass - else: - translation, success = model.getInputTranslate(message) - if success is False: - changeToCTranslate2Process() - - if config.ENABLE_TRANSCRIPTION_SEND is True: - if config.ENABLE_SEND_MESSAGE_TO_VRC is True: - if config.ENABLE_SEND_ONLY_TRANSLATED_MESSAGES is True: - if config.ENABLE_TRANSLATION is False: - osc_message = messageFormatter("SEND", "", message) - else: - osc_message = messageFormatter("SEND", "", translation) - else: - osc_message = messageFormatter("SEND", translation, message) - model.oscSendMessage(osc_message) - - - view.printToTextbox_SentMessage(message, translation) - if config.ENABLE_LOGGER is True: - if len(translation) > 0: - translation = f" ({translation})" - model.logger.info(f"[SENT] {message}{translation}") - - # if config.ENABLE_OVERLAY_SMALL_LOG is True: - # overlay_image = model.createOverlayImageShort(message, translation) - # model.updateOverlay(overlay_image) - # overlay_image = model.createOverlayImageLong("send", message, translation) - # model.updateOverlay(overlay_image) - -def startTranscriptionSendMessage(): - model.startMicTranscript(sendMicMessage, view.printToTextbox_TranscriptionSendNoDeviceError) - view.setMainWindowAllWidgetsStatusToNormal() - -def stopTranscriptionSendMessage(): - model.stopMicTranscript() - view.setMainWindowAllWidgetsStatusToNormal() - -def startThreadingTranscriptionSendMessage(): - view.printToTextbox_enableTranscriptionSend() - th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessage) - th_startTranscriptionSendMessage.daemon = True - th_startTranscriptionSendMessage.start() - -def stopThreadingTranscriptionSendMessage(): - view.printToTextbox_disableTranscriptionSend() - th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessage) - th_stopTranscriptionSendMessage.daemon = True - th_stopTranscriptionSendMessage.start() - -def startTranscriptionSendMessageOnCloseConfigWindow(): - model.startMicTranscript(sendMicMessage, view.printToTextbox_TranscriptionSendNoDeviceError) - -def stopTranscriptionSendMessageOnOpenConfigWindow(): - model.stopMicTranscript() - -def startThreadingTranscriptionSendMessageOnCloseConfigWindow(): - th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessageOnCloseConfigWindow) - th_startTranscriptionSendMessage.daemon = True - th_startTranscriptionSendMessage.start() - -def stopThreadingTranscriptionSendMessageOnOpenConfigWindow(): - th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessageOnOpenConfigWindow) - th_stopTranscriptionSendMessage.daemon = True - th_stopTranscriptionSendMessage.start() - -# func transcription receive message -def receiveSpeakerMessage(message): - if len(message) > 0: - translation = "" - if model.detectRepeatReceiveMessage(message): - return - elif config.ENABLE_TRANSLATION is False: - pass - else: - translation, success = model.getOutputTranslate(message) - if success is False: - changeToCTranslate2Process() - - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - if config.ENABLE_OVERLAY_SMALL_LOG is True: - if model.overlay.initialized is True: - overlay_image = model.createOverlayImageShort(message, translation) - model.updateOverlay(overlay_image) - # overlay_image = model.createOverlayImageLong("receive", message, translation) - # model.updateOverlay(overlay_image) - - # ------------Speaker2Chatbox------------ - if config.ENABLE_SPEAKER2CHATBOX is True: - # send OSC message - if config.ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC is True: - osc_message = messageFormatter("RECEIVED", translation, message) - model.oscSendMessage(osc_message) - # ------------Speaker2Chatbox------------ - - # update textbox message log (Received) - view.printToTextbox_ReceivedMessage(message, translation) - if config.ENABLE_LOGGER is True: - if len(translation) > 0: - translation = f" ({translation})" - model.logger.info(f"[RECEIVED] {message}{translation}") - -def startTranscriptionReceiveMessage(): - model.startSpeakerTranscript(receiveSpeakerMessage, view.printToTextbox_TranscriptionReceiveNoDeviceError) - view.setMainWindowAllWidgetsStatusToNormal() - -def stopTranscriptionReceiveMessage(): - model.stopSpeakerTranscript() - view.setMainWindowAllWidgetsStatusToNormal() - -def startThreadingTranscriptionReceiveMessage(): - view.printToTextbox_enableTranscriptionReceive() - th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessage) - th_startTranscriptionReceiveMessage.daemon = True - th_startTranscriptionReceiveMessage.start() - -def stopThreadingTranscriptionReceiveMessage(): - view.printToTextbox_disableTranscriptionReceive() - th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessage) - th_stopTranscriptionReceiveMessage.daemon = True - th_stopTranscriptionReceiveMessage.start() - -def startTranscriptionReceiveMessageOnCloseConfigWindow(): - model.startSpeakerTranscript(receiveSpeakerMessage, view.printToTextbox_TranscriptionReceiveNoDeviceError) - - -def stopTranscriptionReceiveMessageOnOpenConfigWindow(): - model.stopSpeakerTranscript() - -def startThreadingTranscriptionReceiveMessageOnCloseConfigWindow(): - th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessageOnCloseConfigWindow) - th_startTranscriptionReceiveMessage.daemon = True - th_startTranscriptionReceiveMessage.start() - -def stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow(): - th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessageOnOpenConfigWindow) - th_stopTranscriptionReceiveMessage.daemon = True - th_stopTranscriptionReceiveMessage.start() - -# func message box -def sendChatMessage(message): - if len(message) > 0: - addSentMessageLog(message) - translation = "" - if config.ENABLE_TRANSLATION is False: - pass - else: - translation, success = model.getInputTranslate(message) - if success is False: - changeToCTranslate2Process() - - # send OSC message - if config.ENABLE_SEND_MESSAGE_TO_VRC is True: - if config.ENABLE_SEND_ONLY_TRANSLATED_MESSAGES is True: - if config.ENABLE_TRANSLATION is False: - osc_message = messageFormatter("SEND", "", message) - else: - osc_message = messageFormatter("SEND", "", translation) - else: - osc_message = messageFormatter("SEND", translation, message) - model.oscSendMessage(osc_message) - - # if config.ENABLE_OVERLAY_SMALL_LOG is True: - # overlay_image = model.createOverlayImageShort(message, translation) - # model.updateOverlay(overlay_image) - # overlay_image = model.createOverlayImageLong("send", message, translation) - # model.updateOverlay(overlay_image) - - # update textbox message log (Sent) - view.printToTextbox_SentMessage(message, translation) - if config.ENABLE_LOGGER is True: - if len(translation) > 0: - translation = f" ({translation})" - model.logger.info(f"[SENT] {message}{translation}") - - # delete message in entry message box - if config.ENABLE_AUTO_CLEAR_MESSAGE_BOX is True: - view.clearMessageBox() - -def messageBoxPressKeyEnter(): - model.oscStopSendTyping() - message = view.getTextFromMessageBox() - sendChatMessage(message) - -def messageBoxPressKeyAny(e): - if config.ENABLE_SEND_MESSAGE_TO_VRC is True: - model.oscStartSendTyping() - else: - model.oscStopSendTyping() - -def messageBoxFocusIn(e): - view.foregroundOffIfForegroundEnabled() - -def messageBoxFocusOut(e): - view.foregroundOnIfForegroundEnabled() - if config.ENABLE_SEND_MESSAGE_TO_VRC is True: - model.oscStopSendTyping() - -def addSentMessageLog(sent_message): - config.SENT_MESSAGES_LOG.append(sent_message) - config.CURRENT_SENT_MESSAGES_LOG_INDEX = len(config.SENT_MESSAGES_LOG) - -def updateMessageBox(index_offset): - if len(config.SENT_MESSAGES_LOG) == 0: - return - try: - new_index = config.CURRENT_SENT_MESSAGES_LOG_INDEX + index_offset - target_message_text = config.SENT_MESSAGES_LOG[new_index] - view.replaceMessageBox(target_message_text) - config.CURRENT_SENT_MESSAGES_LOG_INDEX = new_index - except IndexError: - pass - -def messageBoxUpKeyPress(): - if config.CURRENT_SENT_MESSAGES_LOG_INDEX > 0: - updateMessageBox(-1) - -def messageBoxDownKeyPress(): - if config.CURRENT_SENT_MESSAGES_LOG_INDEX < len(config.SENT_MESSAGES_LOG) - 1: - updateMessageBox(1) - -def updateTranslationEngineAndEngineList(): - engine = config.CHOICE_INPUT_TRANSLATOR - engines = model.findTranslationEngines(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - if engine not in engines: - engine = engines[0] - config.CHOICE_INPUT_TRANSLATOR = engine - config.CHOICE_OUTPUT_TRANSLATOR = engine - view.updateSelectableTranslationEngineList(engines) - view.setGuiVariable_SelectedTranslationEngine(engine) - -def initSetTranslateEngine(): - engine = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] - config.CHOICE_INPUT_TRANSLATOR = engine - engine = config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] - config.CHOICE_OUTPUT_TRANSLATOR = engine - -def initSetLanguageAndCountry(): - select = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] - config.SOURCE_LANGUAGE = select["language"] - config.SOURCE_COUNTRY = select["country"] - select = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] - config.TARGET_LANGUAGE = select["language"] - config.TARGET_COUNTRY = select["country"] - -def setYourTranslateEngine(select): - engines = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES - engines[config.SELECTED_TAB_NO] = select - config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = engines - config.CHOICE_INPUT_TRANSLATOR = select - -def setTargetTranslateEngine(select): - engines = config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES - engines[config.SELECTED_TAB_NO] = select - config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = engines - config.CHOICE_OUTPUT_TRANSLATOR = select - -def setYourLanguageAndCountry(select): - languages = config.SELECTED_TAB_YOUR_LANGUAGES - languages[config.SELECTED_TAB_NO] = select - config.SELECTED_TAB_YOUR_LANGUAGES = languages - config.SOURCE_LANGUAGE = select["language"] - config.SOURCE_COUNTRY = select["country"] - updateTranslationEngineAndEngineList() - view.printToTextbox_selectedYourLanguages(select) - -def setTargetLanguageAndCountry(select): - languages = config.SELECTED_TAB_TARGET_LANGUAGES - languages[config.SELECTED_TAB_NO] = select - config.SELECTED_TAB_TARGET_LANGUAGES = languages - config.TARGET_LANGUAGE = select["language"] - config.TARGET_COUNTRY = select["country"] - updateTranslationEngineAndEngineList() - view.printToTextbox_selectedTargetLanguages(select) - -def swapYourLanguageAndTargetLanguage(): - your_language = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] - target_language = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] - setYourLanguageAndCountry(target_language) - setTargetLanguageAndCountry(your_language) - # Update Selected Languages for UI - view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) - - -def callbackSelectedLanguagePresetTab(selected_tab_no): - config.SELECTED_TAB_NO = selected_tab_no - view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) - - engines = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES - engine = engines[config.SELECTED_TAB_NO] - config.CHOICE_INPUT_TRANSLATOR = engine - - engines = config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES - engine = engines[config.SELECTED_TAB_NO] - config.CHOICE_OUTPUT_TRANSLATOR = engine - - languages = config.SELECTED_TAB_YOUR_LANGUAGES - select = languages[config.SELECTED_TAB_NO] - config.SOURCE_LANGUAGE = select["language"] - config.SOURCE_COUNTRY = select["country"] - - languages = config.SELECTED_TAB_TARGET_LANGUAGES - select = languages[config.SELECTED_TAB_NO] - config.TARGET_LANGUAGE = select["language"] - config.TARGET_COUNTRY = select["country"] - view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) - updateTranslationEngineAndEngineList() - -def callbackSelectedTranslationEngine(selected_translation_engine): - print("callbackSelectedTranslationEngine", selected_translation_engine) - setYourTranslateEngine(selected_translation_engine) - setTargetTranslateEngine(selected_translation_engine) - view.setGuiVariable_SelectedTranslationEngine(config.CHOICE_OUTPUT_TRANSLATOR) - -# command func -def callbackToggleTranslation(is_turned_on): - config.ENABLE_TRANSLATION = is_turned_on - if config.ENABLE_TRANSLATION is True: - if model.isLoadedCTranslate2Model() is False: - model.changeTranslatorCTranslate2Model() - view.printToTextbox_enableTranslation() - else: - view.printToTextbox_disableTranslation() - -def callbackToggleTranscriptionSend(is_turned_on): - view.setMainWindowAllWidgetsStatusToDisabled() - config.ENABLE_TRANSCRIPTION_SEND = is_turned_on - if config.ENABLE_TRANSCRIPTION_SEND is True: - startThreadingTranscriptionSendMessage() - view.changeTranscriptionDisplayStatus("MIC_ON") - else: - stopThreadingTranscriptionSendMessage() - view.changeTranscriptionDisplayStatus("MIC_OFF") - -def callbackToggleTranscriptionReceive(is_turned_on): - view.setMainWindowAllWidgetsStatusToDisabled() - config.ENABLE_TRANSCRIPTION_RECEIVE = is_turned_on - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - startThreadingTranscriptionReceiveMessage() - view.changeTranscriptionDisplayStatus("SPEAKER_ON") - else: - stopThreadingTranscriptionReceiveMessage() - view.changeTranscriptionDisplayStatus("SPEAKER_OFF") - - if config.ENABLE_TRANSCRIPTION_RECEIVE is True and config.ENABLE_OVERLAY_SMALL_LOG is True: - if model.overlay.initialized is False and model.overlay.checkSteamvrRunning() is True: - model.startOverlay() - elif config.ENABLE_TRANSCRIPTION_RECEIVE is False: - pass - -def callbackToggleForeground(is_turned_on): - config.ENABLE_FOREGROUND = is_turned_on - if config.ENABLE_FOREGROUND is True: - view.printToTextbox_enableForeground() - view.foregroundOn() - else: - view.printToTextbox_disableForeground() - view.foregroundOff() - -def callbackEnableMainWindowSidebarCompactMode(): - config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True - view.enableMainWindowSidebarCompactMode() - -def callbackDisableMainWindowSidebarCompactMode(): - config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False - view.disableMainWindowSidebarCompactMode() - -# Config Window -def callbackOpenConfigWindow(): - view.setMainWindowAllWidgetsStatusToDisabled() - if config.ENABLE_TRANSCRIPTION_SEND is True: - stopThreadingTranscriptionSendMessageOnOpenConfigWindow() - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow() - if config.ENABLE_FOREGROUND is True: - view.foregroundOff() - -def callbackCloseConfigWindow(): - model.stopCheckMicEnergy() - model.stopCheckSpeakerEnergy() - view.initMicThresholdCheckButton() - view.initSpeakerThresholdCheckButton() - - if config.ENABLE_TRANSCRIPTION_SEND is True: - startThreadingTranscriptionSendMessageOnCloseConfigWindow() - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - sleep(2) - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - startThreadingTranscriptionReceiveMessageOnCloseConfigWindow() - if config.ENABLE_FOREGROUND is True: - view.foregroundOn() - view.setMainWindowAllWidgetsStatusToNormal() - -# Compact Mode Switch -def callbackEnableConfigWindowCompactMode(): - config.IS_CONFIG_WINDOW_COMPACT_MODE = True - model.stopCheckMicEnergy() - view.initMicThresholdCheckButton() - model.stopCheckSpeakerEnergy() - view.initSpeakerThresholdCheckButton() - - view.enableConfigWindowCompactMode() - -def callbackDisableConfigWindowCompactMode(): - config.IS_CONFIG_WINDOW_COMPACT_MODE = False - model.stopCheckMicEnergy() - view.initMicThresholdCheckButton() - model.stopCheckSpeakerEnergy() - view.initSpeakerThresholdCheckButton() - - view.disableConfigWindowCompactMode() - -# Appearance Tab -def callbackSetTransparency(value): - print("callbackSetTransparency", int(value)) - config.TRANSPARENCY = int(value) - view.setMainWindowTransparency(config.TRANSPARENCY/100) - -def callbackSetAppearance(value): - print("callbackSetAppearance", value) - config.APPEARANCE_THEME = value - view.showRestartButtonIfRequired() - -def callbackSetUiScaling(value): - print("callbackSetUiScaling", value) - config.UI_SCALING = value - new_scaling_float = strPctToInt(value) / 100 - print("callbackSetUiScaling_new_scaling_float", new_scaling_float) - view.showRestartButtonIfRequired() - -def callbackSetTextboxUiScaling(value): - print("callbackSetTextboxUiScaling", int(value)) - config.TEXTBOX_UI_SCALING = int(value) - view.setMainWindowTextboxUiSize(config.TEXTBOX_UI_SCALING/100) - -def callbackSetMessageBoxRatio(value): - print("callbackSetMessageBoxRatio", int(value)) - config.MESSAGE_BOX_RATIO = int(value) - view.setMainWindowMessageBoxRatio(config.MESSAGE_BOX_RATIO) - -def callbackSetFontFamily(value): - print("callbackSetFontFamily", value) - config.FONT_FAMILY = value - view.showRestartButtonIfRequired() - -def callbackSetUiLanguage(value): - print("callbackSetUiLanguage", value) - value = getKeyByValue(config.SELECTABLE_UI_LANGUAGES_DICT, value) - print("callbackSetUiLanguage__after_getKeyByValue", value) - config.UI_LANGUAGE = value - view.showRestartButtonIfRequired(locale=config.UI_LANGUAGE) - -def callbackSetEnableRestoreMainWindowGeometry(value): - print("callbackSetEnableRestoreMainWindowGeometry", value) - config.ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY = value - -# Translation Tab -def callbackSetUseTranslationFeature(value): - print("callbackSetUseTranslationFeature", value) - config.USE_TRANSLATION_FEATURE = value - if config.USE_TRANSLATION_FEATURE is True: - view.useTranslationFeatureProcess("Normal") - if model.checkCTranslatorCTranslate2ModelWeight(): - config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False - def callback(): - model.changeTranslatorCTranslate2Model() - th_callback = Thread(target=callback) - th_callback.daemon = True - th_callback.start() - else: - config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = True - view.useTranslationFeatureProcess("Restart") - else: - config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False - view.useTranslationFeatureProcess("Disable") - view.showRestartButtonIfRequired() - -def callbackSetCtranslate2WeightType(value): - print("callbackSetCtranslate2WeightType", value) - config.CTRANSLATE2_WEIGHT_TYPE = str(value) - view.updateSelectedCtranslate2WeightType(config.CTRANSLATE2_WEIGHT_TYPE) - view.setWidgetsStatus_changeWeightType_Pending() - if model.checkCTranslatorCTranslate2ModelWeight(): - config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False - def callback(): - model.changeTranslatorCTranslate2Model() - view.useTranslationFeatureProcess("Normal") - view.setWidgetsStatus_changeWeightType_Done() - th_callback = Thread(target=callback) - th_callback.daemon = True - th_callback.start() - else: - config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = True - view.useTranslationFeatureProcess("Restart") - view.setWidgetsStatus_changeWeightType_Done() - view.showRestartButtonIfRequired() - -def callbackSetDeeplAuthKey(value): - print("callbackSetDeeplAuthKey", str(value)) - view.clearNotificationMessage() - if len(value) == 36 or len(value) == 39: - result = model.authenticationTranslatorDeepLAuthKey(auth_key=value) - if result is True: - key = value - view.printToTextbox_AuthenticationSuccess() - view.showSuccessMessage_DeeplAuthKey() - else: - key = None - view.printToTextbox_AuthenticationError() - view.showErrorMessage_DeeplAuthKey() - auth_keys = config.AUTH_KEYS - auth_keys["DeepL_API"] = key - config.AUTH_KEYS = auth_keys - elif len(value) == 0: - auth_keys = config.AUTH_KEYS - auth_keys["DeepL_API"] = None - config.AUTH_KEYS = auth_keys - updateTranslationEngineAndEngineList() - -# Transcription Tab -# Transcription (Mic) -def callbackSetMicHost(value): - print("callbackSetMicHost", value) - config.CHOICE_MIC_HOST = value - config.CHOICE_MIC_DEVICE = model.getInputDefaultDevice() - - view.updateSelected_MicDevice(config.CHOICE_MIC_DEVICE) - view.updateList_MicDevice(model.getListInputDevice()) - - model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() - -def callbackSetMicDevice(value): - print("callbackSetMicDevice", value) - config.CHOICE_MIC_DEVICE = value - - model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() - -def callbackSetMicEnergyThreshold(value): - print("callbackSetMicEnergyThreshold", value) - if value == "": - return - try: - value = int(value) - if 0 <= value and value <= config.MAX_MIC_ENERGY_THRESHOLD: - view.clearNotificationMessage() - config.INPUT_MIC_ENERGY_THRESHOLD = value - view.setGuiVariable_MicEnergyThreshold(config.INPUT_MIC_ENERGY_THRESHOLD) - else: - raise ValueError() - except Exception: - view.showErrorMessage_MicEnergyThreshold() - -def callbackSetMicDynamicEnergyThreshold(value): - print("callbackSetMicDynamicEnergyThreshold", value) - config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value - if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: - view.closeMicEnergyThresholdWidget() - else: - view.openMicEnergyThresholdWidget() - -def setProgressBarMicEnergy(energy): - view.updateSetProgressBar_MicEnergy(energy) - -def callbackCheckMicThreshold(is_turned_on): - print("callbackCheckMicThreshold", is_turned_on) - if is_turned_on is True: - view.replaceMicThresholdCheckButton_Disabled() - model.startCheckMicEnergy(setProgressBarMicEnergy, view.initProgressBar_MicEnergy) - view.replaceMicThresholdCheckButton_Active() - else: - view.replaceMicThresholdCheckButton_Disabled() - model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() - -def callbackSetMicRecordTimeout(value): - print("callbackSetMicRecordTimeout", value) - if value == "": - return - try: - value = int(value) - if 0 <= value and value <= config.INPUT_MIC_PHRASE_TIMEOUT: - view.clearNotificationMessage() - config.INPUT_MIC_RECORD_TIMEOUT = value - view.setGuiVariable_MicRecordTimeout(config.INPUT_MIC_RECORD_TIMEOUT) - else: - raise ValueError() - except Exception: - view.showErrorMessage_MicRecordTimeout() - -def callbackSetMicPhraseTimeout(value): - print("callbackSetMicPhraseTimeout", value) - if value == "": - return - try: - value = int(value) - if 0 <= value and value >= config.INPUT_MIC_RECORD_TIMEOUT: - view.clearNotificationMessage() - config.INPUT_MIC_PHRASE_TIMEOUT = value - view.setGuiVariable_MicPhraseTimeout(config.INPUT_MIC_PHRASE_TIMEOUT) - else: - raise ValueError() - except Exception: - view.showErrorMessage_MicPhraseTimeout() - -def callbackSetMicMaxPhrases(value): - print("callbackSetMicMaxPhrases", value) - if value == "": - return - try: - value = int(value) - if 0 <= value: - view.clearNotificationMessage() - config.INPUT_MIC_MAX_PHRASES = value - view.setGuiVariable_MicMaxPhrases(config.INPUT_MIC_MAX_PHRASES) - else: - raise ValueError() - except Exception: - view.showErrorMessage_MicMaxPhrases() - -def callbackSetMicWordFilter(values): - print("callbackSetMicWordFilter", values) - values = str(values) - values = [w.strip() for w in values.split(",") if len(w.strip()) > 0] - # Copy the list - new_input_mic_word_filter_list = config.INPUT_MIC_WORD_FILTER - new_added_value = [] - for value in values: - if value in new_input_mic_word_filter_list: - # If the value is already in the list, do nothing. - pass - else: - new_input_mic_word_filter_list.append(value) - new_added_value.append(value) - config.INPUT_MIC_WORD_FILTER = new_input_mic_word_filter_list - - view.addValueToList_WordFilter(new_added_value) - view.clearEntryBox_WordFilter() - view.setLatestConfigVariable("MicMicWordFilter") - - model.resetKeywordProcessor() - model.addKeywords() - -def callbackDeleteMicWordFilter(value): - print("callbackDeleteMicWordFilter", value) - try: - new_input_mic_word_filter_list = config.INPUT_MIC_WORD_FILTER - new_input_mic_word_filter_list.remove(str(value)) - config.INPUT_MIC_WORD_FILTER = new_input_mic_word_filter_list - view.setLatestConfigVariable("MicMicWordFilter") - model.resetKeywordProcessor() - model.addKeywords() - except Exception: - print("There was no the target word in config.INPUT_MIC_WORD_FILTER") - -# Transcription (Speaker) -def callbackSetSpeakerDevice(value): - print("callbackSetSpeakerDevice", value) - config.CHOICE_SPEAKER_DEVICE = value - - model.stopCheckSpeakerEnergy() - view.replaceSpeakerThresholdCheckButton_Passive() - -def callbackSetSpeakerEnergyThreshold(value): - print("callbackSetSpeakerEnergyThreshold", value) - if value == "": - return - try: - value = int(value) - if 0 <= value and value <= config.MAX_SPEAKER_ENERGY_THRESHOLD: - view.clearNotificationMessage() - config.INPUT_SPEAKER_ENERGY_THRESHOLD = value - view.setGuiVariable_SpeakerEnergyThreshold(config.INPUT_SPEAKER_ENERGY_THRESHOLD) - else: - raise ValueError() - except Exception: - view.showErrorMessage_SpeakerEnergyThreshold() - -def callbackSetSpeakerDynamicEnergyThreshold(value): - print("callbackSetSpeakerDynamicEnergyThreshold", value) - config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value - if config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD is True: - view.closeSpeakerEnergyThresholdWidget() - else: - view.openSpeakerEnergyThresholdWidget() - -def setProgressBarSpeakerEnergy(energy): - view.updateSetProgressBar_SpeakerEnergy(energy) - -def callbackCheckSpeakerThreshold(is_turned_on): - print("callbackCheckSpeakerThreshold", is_turned_on) - if is_turned_on is True: - view.replaceSpeakerThresholdCheckButton_Disabled() - model.startCheckSpeakerEnergy( - setProgressBarSpeakerEnergy, - view.initProgressBar_SpeakerEnergy, - view.showErrorMessage_CheckSpeakerThreshold_NoDevice - ) - - view.replaceSpeakerThresholdCheckButton_Active() - else: - view.replaceSpeakerThresholdCheckButton_Disabled() - model.stopCheckSpeakerEnergy() - view.replaceSpeakerThresholdCheckButton_Passive() - -def callbackSetSpeakerRecordTimeout(value): - print("callbackSetSpeakerRecordTimeout", value) - if value == "": - return - try: - value = int(value) - if 0 <= value and value <= config.INPUT_SPEAKER_PHRASE_TIMEOUT: - view.clearNotificationMessage() - config.INPUT_SPEAKER_RECORD_TIMEOUT = value - view.setGuiVariable_SpeakerRecordTimeout(config.INPUT_SPEAKER_RECORD_TIMEOUT) - else: - raise ValueError() - except Exception: - view.showErrorMessage_SpeakerRecordTimeout() - -def callbackSetSpeakerPhraseTimeout(value): - print("callbackSetSpeakerPhraseTimeout", value) - if value == "": - return - try: - value = int(value) - if 0 <= value and value >= config.INPUT_SPEAKER_RECORD_TIMEOUT: - view.clearNotificationMessage() - config.INPUT_SPEAKER_PHRASE_TIMEOUT = value - view.setGuiVariable_SpeakerPhraseTimeout(config.INPUT_SPEAKER_PHRASE_TIMEOUT) - else: - raise ValueError() - except Exception: - view.showErrorMessage_SpeakerPhraseTimeout() - -def callbackSetSpeakerMaxPhrases(value): - print("callbackSetSpeakerMaxPhrases", value) - if value == "": - return - try: - value = int(value) - if 0 <= value: - view.clearNotificationMessage() - config.INPUT_SPEAKER_MAX_PHRASES = value - view.setGuiVariable_SpeakerMaxPhrases(config.INPUT_SPEAKER_MAX_PHRASES) - else: - raise ValueError() - except Exception: - view.showErrorMessage_SpeakerMaxPhrases() - -# Transcription (Internal AI Model) -def callbackSetUserWhisperFeature(value): - print("callbackSetUserWhisperFeature", value) - config.USE_WHISPER_FEATURE = value - if config.USE_WHISPER_FEATURE is True: - view.openWhisperWeightTypeWidget() - if model.checkTranscriptionWhisperModelWeight() is True: - config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False - config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper" - else: - config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = True - config.SELECTED_TRANSCRIPTION_ENGINE = "Google" - else: - view.closeWhisperWeightTypeWidget() - config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False - config.SELECTED_TRANSCRIPTION_ENGINE = "Google" - view.showRestartButtonIfRequired() - -def callbackSetWhisperWeightType(value): - print("callbackSetWhisperWeightType", value) - config.WHISPER_WEIGHT_TYPE = str(value) - view.updateSelectedWhisperWeightType(config.WHISPER_WEIGHT_TYPE) - if model.checkTranscriptionWhisperModelWeight() is True: - config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = False - config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper" - else: - config.IS_RESET_BUTTON_DISPLAYED_FOR_WHISPER = True - config.SELECTED_TRANSCRIPTION_ENGINE = "Google" - view.showRestartButtonIfRequired() - -# VR Tab -def callbackSetOverlaySettings(value, set_type:str): - print("callbackSetOverlaySettings", value, set_type) - pre_settings = config.OVERLAY_SETTINGS - pre_settings[set_type] = value - config.OVERLAY_SETTINGS = pre_settings - match (set_type): - case "opacity": - model.updateOverlayImageOpacity() - case "ui_scaling": - model.updateOverlayImageUiScaling() - -def callbackSetEnableOverlaySmallLog(value): - print("callbackSetEnableOverlaySmallLog", value) - config.ENABLE_OVERLAY_SMALL_LOG = value - - if config.ENABLE_OVERLAY_SMALL_LOG is True and config.ENABLE_TRANSCRIPTION_RECEIVE is True: - if model.overlay.initialized is False and model.overlay.checkSteamvrRunning() is True: - model.startOverlay() - elif config.ENABLE_OVERLAY_SMALL_LOG is False: - model.clearOverlayImage() - model.shutdownOverlay() - - if config.ENABLE_OVERLAY_SMALL_LOG is True: - view.setStateOverlaySmallLog("enabled") - elif config.ENABLE_OVERLAY_SMALL_LOG is False: - view.setStateOverlaySmallLog("disabled") - -def callbackSetOverlaySmallLogSettings(value, set_type:str): - print("callbackSetOverlaySmallLogSettings", value, set_type) - pre_settings = config.OVERLAY_SMALL_LOG_SETTINGS - pre_settings[set_type] = value - config.OVERLAY_SMALL_LOG_SETTINGS = pre_settings - match (set_type): - case "x_pos" | "y_pos" | "z_pos" | "x_rotation" | "y_rotation" | "z_rotation": - model.updateOverlayPosition() - case "display_duration" | "fadeout_duration": - model.updateOverlayTimes() - -# Others Tab -def callbackSetEnableAutoClearMessageBox(value): - print("callbackSetEnableAutoClearMessageBox", value) - config.ENABLE_AUTO_CLEAR_MESSAGE_BOX = value - -def callbackSetEnableSendOnlyTranslatedMessages(value): - print("callbackSetEnableSendOnlyTranslatedMessages", value) - config.ENABLE_SEND_ONLY_TRANSLATED_MESSAGES = value - -def callbackSetSendMessageButtonType(value): - print("callbackSetSendMessageButtonType", value) - config.SEND_MESSAGE_BUTTON_TYPE = value - view.changeMainWindowSendMessageButton(config.SEND_MESSAGE_BUTTON_TYPE) - -def callbackSetEnableAutoExportMessageLogs(value): - print("callbackSetEnableAutoExportMessageLogs", value) - config.ENABLE_LOGGER = value - - if config.ENABLE_LOGGER is True: - model.startLogger() - else: - model.stopLogger() - -def callbackSetEnableVrcMicMuteSync(value): - print("callbackSetEnableVrcMicMuteSync", value) - config.ENABLE_VRC_MIC_MUTE_SYNC = value - if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - model.startCheckMuteSelfStatus() - view.setStateVrcMicMuteSync("enabled") - else: - model.stopCheckMuteSelfStatus() - view.setStateVrcMicMuteSync("disabled") - model.changeMicTranscriptStatus() - - -def callbackSetEnableSendMessageToVrc(value): - print("callbackSetEnableSendMessageToVrc", value) - config.ENABLE_SEND_MESSAGE_TO_VRC = value - -# Others (Message Formats(Send) -def callbackSetSendMessageFormat(value): - print("callbackSetSendMessageFormat", value) - if isUniqueStrings(["[message]"], value) is True: - config.SEND_MESSAGE_FORMAT = value - view.clearNotificationMessage() - view.setSendMessageFormat_EntryWidgets(config.SEND_MESSAGE_FORMAT) - else: - view.showErrorMessage_SendMessageFormat() - view.setSendMessageFormat_EntryWidgets(config.SEND_MESSAGE_FORMAT) - -def callbackSetSendMessageFormatWithT(value): - print("callbackSetSendMessageFormatWithT", value) - if len(value) > 0: - if isUniqueStrings(["[message]", "[translation]"], value) is True: - config.SEND_MESSAGE_FORMAT_WITH_T = value - view.clearNotificationMessage() - view.setSendMessageFormatWithT_EntryWidgets(config.SEND_MESSAGE_FORMAT_WITH_T) - else: - view.showErrorMessage_SendMessageFormatWithT() - view.setSendMessageFormatWithT_EntryWidgets(config.SEND_MESSAGE_FORMAT_WITH_T) - -# Others (Message Formats(Received) -def callbackSetReceivedMessageFormat(value): - print("callbackSetReceivedMessageFormat", value) - if isUniqueStrings(["[message]"], value) is True: - config.RECEIVED_MESSAGE_FORMAT = value - view.clearNotificationMessage() - view.setReceivedMessageFormat_EntryWidgets(config.RECEIVED_MESSAGE_FORMAT) - else: - view.showErrorMessage_ReceivedMessageFormat() - view.setReceivedMessageFormat_EntryWidgets(config.RECEIVED_MESSAGE_FORMAT) - -def callbackSetReceivedMessageFormatWithT(value): - print("callbackSetReceivedMessageFormatWithT", value) - if len(value) > 0: - if isUniqueStrings(["[message]", "[translation]"], value) is True: - config.RECEIVED_MESSAGE_FORMAT_WITH_T = value - view.clearNotificationMessage() - view.setReceivedMessageFormatWithT_EntryWidgets(config.RECEIVED_MESSAGE_FORMAT_WITH_T) - else: - view.showErrorMessage_ReceivedMessageFormatWithT() - view.setReceivedMessageFormatWithT_EntryWidgets(config.RECEIVED_MESSAGE_FORMAT_WITH_T) - -# ---------------------Speaker2Chatbox--------------------- -def callbackSetEnableSendReceivedMessageToVrc(value): - print("callbackSetEnableSendReceivedMessageToVrc", value) - config.ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = value -# ---------------------Speaker2Chatbox--------------------- - -# Advanced Settings Tab -def callbackSetOscIpAddress(value): - if value == "": - return - print("callbackSetOscIpAddress", str(value)) - config.OSC_IP_ADDRESS = str(value) - -def callbackSetOscPort(value): - if value == "": - return - print("callbackSetOscPort", int(value)) - config.OSC_PORT = int(value) - - -def initSetConfigByExeArguments(): - parser = argparse.ArgumentParser() - parser.add_argument("--ip") - parser.add_argument("--port") - args = parser.parse_args() - if args.ip is not None: - config.OSC_IP_ADDRESS = str(args.ip) - view.setGuiVariable_OscIpAddress(config.OSC_IP_ADDRESS) - if args.port is not None: - config.OSC_PORT = int(args.port) - view.setGuiVariable_OscPort(config.OSC_PORT) - -def createMainWindow(splash): - splash.toProgress(1) - # create GUI - view.createGUI() - splash.toProgress(2) - - # init config - initSetConfigByExeArguments() - initSetTranslateEngine() - initSetLanguageAndCountry() - - if config.AUTH_KEYS["DeepL_API"] is not None: - if model.authenticationTranslatorDeepLAuthKey(auth_key=config.AUTH_KEYS["DeepL_API"]) is False: - # error update Auth key - auth_keys = config.AUTH_KEYS - auth_keys["DeepL_API"] = None - config.AUTH_KEYS = auth_keys - view.printToTextbox_AuthenticationError() - - # set Translation Engine - updateTranslationEngineAndEngineList() - - # set Transcription Engine - if config.USE_WHISPER_FEATURE is True: - config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper" - else: - config.SELECTED_TRANSCRIPTION_ENGINE = "Google" - - # set word filter - model.addKeywords() - - # check Software Updated - if model.checkSoftwareUpdated() is True: - view.showUpdateAvailableButton() - - # init logger - if config.ENABLE_LOGGER is True: - model.startLogger() - - # init OSC receive - model.startReceiveOSC() - if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - model.startCheckMuteSelfStatus() - - splash.toProgress(3) # Last one. - - # set UI and callback - view.register( - common_registers={ - "callback_enable_easter_egg": callbackEnableEasterEgg, - - "callback_update_software": callbackUpdateSoftware, - "callback_restart_software": callbackRestartSoftware, - "callback_filepath_logs": callbackFilepathLogs, - "callback_filepath_config_file": callbackFilepathConfigFile, - "callback_quit_vrct": callbackQuitVrct, - }, - - window_action_registers={ - "callback_open_config_window": callbackOpenConfigWindow, - "callback_close_config_window": callbackCloseConfigWindow, - }, - - main_window_registers={ - "callback_enable_main_window_sidebar_compact_mode": callbackEnableMainWindowSidebarCompactMode, - "callback_disable_main_window_sidebar_compact_mode": callbackDisableMainWindowSidebarCompactMode, - - "callback_toggle_translation": callbackToggleTranslation, - "callback_toggle_transcription_send": callbackToggleTranscriptionSend, - "callback_toggle_transcription_receive": callbackToggleTranscriptionReceive, - "callback_toggle_foreground": callbackToggleForeground, - - "callback_your_language": setYourLanguageAndCountry, - "callback_target_language": setTargetLanguageAndCountry, - "values": model.getListLanguageAndCountry(), - "callback_swap_languages": swapYourLanguageAndTargetLanguage, - - "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, - - "callback_selected_translation_engine": callbackSelectedTranslationEngine, - - "message_box_bind_Return": messageBoxPressKeyEnter, - "message_box_bind_Any_KeyPress": messageBoxPressKeyAny, - "message_box_bind_FocusIn": messageBoxFocusIn, - "message_box_bind_FocusOut": messageBoxFocusOut, - "message_box_bind_Up_KeyPress": messageBoxUpKeyPress, - "message_box_bind_Down_KeyPress": messageBoxDownKeyPress, - }, - - config_window_registers={ - # Compact Mode Switch - "callback_disable_config_window_compact_mode": callbackEnableConfigWindowCompactMode, - "callback_enable_config_window_compact_mode": callbackDisableConfigWindowCompactMode, - - # Appearance Tab - "callback_set_transparency": callbackSetTransparency, - "callback_set_appearance": callbackSetAppearance, - "callback_set_ui_scaling": callbackSetUiScaling, - "callback_set_textbox_ui_scaling": callbackSetTextboxUiScaling, - "callback_set_message_box_ratio": callbackSetMessageBoxRatio, - "callback_set_font_family": callbackSetFontFamily, - "callback_set_ui_language": callbackSetUiLanguage, - "callback_set_enable_restore_main_window_geometry": callbackSetEnableRestoreMainWindowGeometry, - - # Translation Tab - "callback_set_use_translation_feature": callbackSetUseTranslationFeature, - "callback_set_ctranslate2_weight_type": callbackSetCtranslate2WeightType, - "callback_set_deepl_auth_key": callbackSetDeeplAuthKey, - - # Transcription Tab (Mic) - "callback_set_mic_host": callbackSetMicHost, - "list_mic_host": model.getListInputHost(), - "callback_set_mic_device": callbackSetMicDevice, - "list_mic_device": model.getListInputDevice(), - "callback_set_mic_energy_threshold": callbackSetMicEnergyThreshold, - "callback_set_mic_dynamic_energy_threshold": callbackSetMicDynamicEnergyThreshold, - "callback_check_mic_threshold": callbackCheckMicThreshold, - "callback_set_mic_record_timeout": callbackSetMicRecordTimeout, - "callback_set_mic_phrase_timeout": callbackSetMicPhraseTimeout, - "callback_set_mic_max_phrases": callbackSetMicMaxPhrases, - "callback_set_mic_word_filter": callbackSetMicWordFilter, - "callback_delete_mic_word_filter": callbackDeleteMicWordFilter, - - # Transcription Tab (Speaker) - "callback_set_speaker_device": callbackSetSpeakerDevice, - "list_speaker_device": model.getListOutputDevice(), - "callback_set_speaker_energy_threshold": callbackSetSpeakerEnergyThreshold, - "callback_set_speaker_dynamic_energy_threshold": callbackSetSpeakerDynamicEnergyThreshold, - "callback_check_speaker_threshold": callbackCheckSpeakerThreshold, - "callback_set_speaker_record_timeout": callbackSetSpeakerRecordTimeout, - "callback_set_speaker_phrase_timeout": callbackSetSpeakerPhraseTimeout, - "callback_set_speaker_max_phrases": callbackSetSpeakerMaxPhrases, - - # Transcription Tab (Internal AI Model) - "callback_set_use_whisper_feature": callbackSetUserWhisperFeature, - "callback_set_whisper_weight_type": callbackSetWhisperWeightType, - - # VR Tab - "callback_set_overlay_settings": callbackSetOverlaySettings, - "callback_set_enable_overlay_small_log": callbackSetEnableOverlaySmallLog, - "callback_set_overlay_small_log_settings": callbackSetOverlaySmallLogSettings, - - # Others Tab - "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearMessageBox, - "callback_set_send_only_translated_messages": callbackSetEnableSendOnlyTranslatedMessages, - "callback_set_send_message_button_type": callbackSetSendMessageButtonType, - "callback_set_enable_auto_export_message_logs": callbackSetEnableAutoExportMessageLogs, - "callback_set_enable_vrc_mic_mute_sync": callbackSetEnableVrcMicMuteSync, - "callback_set_enable_send_message_to_vrc": callbackSetEnableSendMessageToVrc, - # Others(Message Formats(Send) - "callback_set_send_message_format": callbackSetSendMessageFormat, - "callback_set_send_message_format_with_t": callbackSetSendMessageFormatWithT, - # Others(Message Formats(Received) - "callback_set_received_message_format": callbackSetReceivedMessageFormat, - "callback_set_received_message_format_with_t": callbackSetReceivedMessageFormatWithT, - - # Speaker2Chatbox---------------- - "callback_set_enable_send_received_message_to_vrc": callbackSetEnableSendReceivedMessageToVrc, - # Speaker2Chatbox---------------- - - # Advanced Settings Tab - "callback_set_osc_ip_address": callbackSetOscIpAddress, - "callback_set_osc_port": callbackSetOscPort, - }, - ) - -def showMainWindow(): - view.startMainLoop() \ No newline at end of file diff --git a/img/VRCT_now_downloading.png b/img/VRCT_now_downloading.png deleted file mode 100644 index 8e372d87..00000000 Binary files a/img/VRCT_now_downloading.png and /dev/null differ diff --git a/img/VRCT_starting_up.png b/img/VRCT_starting_up.png deleted file mode 100644 index 9feac583..00000000 Binary files a/img/VRCT_starting_up.png and /dev/null differ diff --git a/img/about_vrct/arrow_left.png b/img/about_vrct/arrow_left.png deleted file mode 100644 index a1c4b35e..00000000 Binary files a/img/about_vrct/arrow_left.png and /dev/null differ diff --git a/img/about_vrct/arrow_right.png b/img/about_vrct/arrow_right.png deleted file mode 100644 index 69e005ba..00000000 Binary files a/img/about_vrct/arrow_right.png and /dev/null differ diff --git a/img/about_vrct/contributors_github_icon.png b/img/about_vrct/contributors_github_icon.png deleted file mode 100644 index 1c6e0727..00000000 Binary files a/img/about_vrct/contributors_github_icon.png and /dev/null differ diff --git a/img/about_vrct/contributors_x_icon.png b/img/about_vrct/contributors_x_icon.png deleted file mode 100644 index c17fb57d..00000000 Binary files a/img/about_vrct/contributors_x_icon.png and /dev/null differ diff --git a/img/about_vrct/dev_github_icon.png b/img/about_vrct/dev_github_icon.png deleted file mode 100644 index 8cbaaff7..00000000 Binary files a/img/about_vrct/dev_github_icon.png and /dev/null differ diff --git a/img/about_vrct/dev_shiina.png b/img/about_vrct/dev_shiina.png deleted file mode 100644 index cfef057a..00000000 Binary files a/img/about_vrct/dev_shiina.png and /dev/null differ diff --git a/img/about_vrct/dev_x_icon.png b/img/about_vrct/dev_x_icon.png deleted file mode 100644 index dfd6d109..00000000 Binary files a/img/about_vrct/dev_x_icon.png and /dev/null differ diff --git a/img/about_vrct/poster_showcase_pagination_button.png b/img/about_vrct/poster_showcase_pagination_button.png deleted file mode 100644 index bbfee311..00000000 Binary files a/img/about_vrct/poster_showcase_pagination_button.png and /dev/null differ diff --git a/img/about_vrct/poster_showcase_pagination_button_chato.png b/img/about_vrct/poster_showcase_pagination_button_chato.png deleted file mode 100644 index a4df1d52..00000000 Binary files a/img/about_vrct/poster_showcase_pagination_button_chato.png and /dev/null differ diff --git a/img/about_vrct/poster_showcase_section_title.png b/img/about_vrct/poster_showcase_section_title.png deleted file mode 100644 index 83b1f21f..00000000 Binary files a/img/about_vrct/poster_showcase_section_title.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/bar_asagao.png b/img/about_vrct/showcased_worlds/bar_asagao.png deleted file mode 100644 index 9965198c..00000000 Binary files a/img/about_vrct/showcased_worlds/bar_asagao.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/cafe_cian.png b/img/about_vrct/showcased_worlds/cafe_cian.png deleted file mode 100644 index 9b38a299..00000000 Binary files a/img/about_vrct/showcased_worlds/cafe_cian.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/cam.png b/img/about_vrct/showcased_worlds/cam.png deleted file mode 100644 index 41339042..00000000 Binary files a/img/about_vrct/showcased_worlds/cam.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/chakachaka_multipurpose_room.png b/img/about_vrct/showcased_worlds/chakachaka_multipurpose_room.png deleted file mode 100644 index 0360e4e4..00000000 Binary files a/img/about_vrct/showcased_worlds/chakachaka_multipurpose_room.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/ehon_no_heikousekai.png b/img/about_vrct/showcased_worlds/ehon_no_heikousekai.png deleted file mode 100644 index 3cb91ec4..00000000 Binary files a/img/about_vrct/showcased_worlds/ehon_no_heikousekai.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/ehon_no_heikousekai_jimusho.png b/img/about_vrct/showcased_worlds/ehon_no_heikousekai_jimusho.png deleted file mode 100644 index 2e2b64cf..00000000 Binary files a/img/about_vrct/showcased_worlds/ehon_no_heikousekai_jimusho.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/ikoiba.png b/img/about_vrct/showcased_worlds/ikoiba.png deleted file mode 100644 index 04a85165..00000000 Binary files a/img/about_vrct/showcased_worlds/ikoiba.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/ippaidou.png b/img/about_vrct/showcased_worlds/ippaidou.png deleted file mode 100644 index ea2f4534..00000000 Binary files a/img/about_vrct/showcased_worlds/ippaidou.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/japanese_culture_osenbeito.png b/img/about_vrct/showcased_worlds/japanese_culture_osenbeito.png deleted file mode 100644 index 9630bda2..00000000 Binary files a/img/about_vrct/showcased_worlds/japanese_culture_osenbeito.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/kimodameshi.png b/img/about_vrct/showcased_worlds/kimodameshi.png deleted file mode 100644 index 7bee627b..00000000 Binary files a/img/about_vrct/showcased_worlds/kimodameshi.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/kokekkopiyopiyo.png b/img/about_vrct/showcased_worlds/kokekkopiyopiyo.png deleted file mode 100644 index dc59ee56..00000000 Binary files a/img/about_vrct/showcased_worlds/kokekkopiyopiyo.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/kr_jp_exchange.png b/img/about_vrct/showcased_worlds/kr_jp_exchange.png deleted file mode 100644 index 44b25835..00000000 Binary files a/img/about_vrct/showcased_worlds/kr_jp_exchange.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/kuroinu_work_room.png b/img/about_vrct/showcased_worlds/kuroinu_work_room.png deleted file mode 100644 index c7cb7209..00000000 Binary files a/img/about_vrct/showcased_worlds/kuroinu_work_room.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/mamehinata_dogrun.png b/img/about_vrct/showcased_worlds/mamehinata_dogrun.png deleted file mode 100644 index da443ffe..00000000 Binary files a/img/about_vrct/showcased_worlds/mamehinata_dogrun.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/monogatari_meetup.png b/img/about_vrct/showcased_worlds/monogatari_meetup.png deleted file mode 100644 index 3f10ed22..00000000 Binary files a/img/about_vrct/showcased_worlds/monogatari_meetup.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/nihongokurabu.png b/img/about_vrct/showcased_worlds/nihongokurabu.png deleted file mode 100644 index 9425e0b5..00000000 Binary files a/img/about_vrct/showcased_worlds/nihongokurabu.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/omoshiro_kotoba_asobi_game.png b/img/about_vrct/showcased_worlds/omoshiro_kotoba_asobi_game.png deleted file mode 100644 index 9a6cfbba..00000000 Binary files a/img/about_vrct/showcased_worlds/omoshiro_kotoba_asobi_game.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/parallel_collar.png b/img/about_vrct/showcased_worlds/parallel_collar.png deleted file mode 100644 index b2ab89d6..00000000 Binary files a/img/about_vrct/showcased_worlds/parallel_collar.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/re_yatuha_room.png b/img/about_vrct/showcased_worlds/re_yatuha_room.png deleted file mode 100644 index 505c63a1..00000000 Binary files a/img/about_vrct/showcased_worlds/re_yatuha_room.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/smokerz_guild_v2.png b/img/about_vrct/showcased_worlds/smokerz_guild_v2.png deleted file mode 100644 index a02edf1c..00000000 Binary files a/img/about_vrct/showcased_worlds/smokerz_guild_v2.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/stretch_club_starting_from_minus.png b/img/about_vrct/showcased_worlds/stretch_club_starting_from_minus.png deleted file mode 100644 index 05d7db3b..00000000 Binary files a/img/about_vrct/showcased_worlds/stretch_club_starting_from_minus.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/study_japanese_world_japanichijou.png b/img/about_vrct/showcased_worlds/study_japanese_world_japanichijou.png deleted file mode 100644 index 95a4cf67..00000000 Binary files a/img/about_vrct/showcased_worlds/study_japanese_world_japanichijou.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/sushi_guru_annex.png b/img/about_vrct/showcased_worlds/sushi_guru_annex.png deleted file mode 100644 index e8576420..00000000 Binary files a/img/about_vrct/showcased_worlds/sushi_guru_annex.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/tyuuniti_kouryuukai.png b/img/about_vrct/showcased_worlds/tyuuniti_kouryuukai.png deleted file mode 100644 index 0d4656d2..00000000 Binary files a/img/about_vrct/showcased_worlds/tyuuniti_kouryuukai.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/uj_club.png b/img/about_vrct/showcased_worlds/uj_club.png deleted file mode 100644 index 9118f48f..00000000 Binary files a/img/about_vrct/showcased_worlds/uj_club.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/una_yosh.png b/img/about_vrct/showcased_worlds/una_yosh.png deleted file mode 100644 index da1a6789..00000000 Binary files a/img/about_vrct/showcased_worlds/una_yosh.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/usanezumi_shrine2.png b/img/about_vrct/showcased_worlds/usanezumi_shrine2.png deleted file mode 100644 index f10cce6c..00000000 Binary files a/img/about_vrct/showcased_worlds/usanezumi_shrine2.png and /dev/null differ diff --git a/img/about_vrct/showcased_worlds/yuttari_eikaiwa.png b/img/about_vrct/showcased_worlds/yuttari_eikaiwa.png deleted file mode 100644 index c9616217..00000000 Binary files a/img/about_vrct/showcased_worlds/yuttari_eikaiwa.png and /dev/null differ diff --git a/img/about_vrct/special_thanks_message_and_you.png b/img/about_vrct/special_thanks_message_and_you.png deleted file mode 100644 index f006ff15..00000000 Binary files a/img/about_vrct/special_thanks_message_and_you.png and /dev/null differ diff --git a/img/arrow_left_black.png b/img/arrow_left_black.png deleted file mode 100644 index 5e3f8dfb..00000000 Binary files a/img/arrow_left_black.png and /dev/null differ diff --git a/img/arrow_left_disabled.png b/img/arrow_left_disabled.png deleted file mode 100644 index 5b5f3afa..00000000 Binary files a/img/arrow_left_disabled.png and /dev/null differ diff --git a/img/arrow_left_white.png b/img/arrow_left_white.png deleted file mode 100644 index 463d8a74..00000000 Binary files a/img/arrow_left_white.png and /dev/null differ diff --git a/img/cancel_icon.png b/img/cancel_icon.png deleted file mode 100644 index aa6c386e..00000000 Binary files a/img/cancel_icon.png and /dev/null differ diff --git a/img/chato_delivering.png b/img/chato_delivering.png deleted file mode 100644 index d38f1447..00000000 Binary files a/img/chato_delivering.png and /dev/null differ diff --git a/img/chato_unpackaging.png b/img/chato_unpackaging.png deleted file mode 100644 index 52eb66dc..00000000 Binary files a/img/chato_unpackaging.png and /dev/null differ diff --git a/img/configuration_icon_black.png b/img/configuration_icon_black.png deleted file mode 100644 index c97dfefe..00000000 Binary files a/img/configuration_icon_black.png and /dev/null differ diff --git a/img/configuration_icon_disabled.png b/img/configuration_icon_disabled.png deleted file mode 100644 index 03a4a217..00000000 Binary files a/img/configuration_icon_disabled.png and /dev/null differ diff --git a/img/configuration_icon_white.png b/img/configuration_icon_white.png deleted file mode 100644 index ee6e050e..00000000 Binary files a/img/configuration_icon_white.png and /dev/null differ diff --git a/img/downloading_unpackaging_d.png b/img/downloading_unpackaging_d.png deleted file mode 100644 index 5c3c7031..00000000 Binary files a/img/downloading_unpackaging_d.png and /dev/null differ diff --git a/img/downloading_unpackaging_u.png b/img/downloading_unpackaging_u.png deleted file mode 100644 index 684433d5..00000000 Binary files a/img/downloading_unpackaging_u.png and /dev/null differ diff --git a/img/folder_open_icon_black.png b/img/folder_open_icon_black.png deleted file mode 100644 index 988184b3..00000000 Binary files a/img/folder_open_icon_black.png and /dev/null differ diff --git a/img/folder_open_icon_white.png b/img/folder_open_icon_white.png deleted file mode 100644 index 3de75dfe..00000000 Binary files a/img/folder_open_icon_white.png and /dev/null differ diff --git a/img/foreground_icon_black.png b/img/foreground_icon_black.png deleted file mode 100644 index ff3594c2..00000000 Binary files a/img/foreground_icon_black.png and /dev/null differ diff --git a/img/foreground_icon_disabled.png b/img/foreground_icon_disabled.png deleted file mode 100644 index 91778b7b..00000000 Binary files a/img/foreground_icon_disabled.png and /dev/null differ diff --git a/img/foreground_icon_white.png b/img/foreground_icon_white.png deleted file mode 100644 index 50370ad1..00000000 Binary files a/img/foreground_icon_white.png and /dev/null differ diff --git a/img/headphones_icon_black.png b/img/headphones_icon_black.png deleted file mode 100644 index 2cb97ec2..00000000 Binary files a/img/headphones_icon_black.png and /dev/null differ diff --git a/img/headphones_icon_disabled.png b/img/headphones_icon_disabled.png deleted file mode 100644 index f81f812e..00000000 Binary files a/img/headphones_icon_disabled.png and /dev/null differ diff --git a/img/headphones_icon_white.png b/img/headphones_icon_white.png deleted file mode 100644 index 54f7359d..00000000 Binary files a/img/headphones_icon_white.png and /dev/null differ diff --git a/img/help_icon_black.png b/img/help_icon_black.png deleted file mode 100644 index 156ca026..00000000 Binary files a/img/help_icon_black.png and /dev/null differ diff --git a/img/help_icon_white.png b/img/help_icon_white.png deleted file mode 100644 index ee5cb936..00000000 Binary files a/img/help_icon_white.png and /dev/null differ diff --git a/img/link_icon_black.png b/img/link_icon_black.png deleted file mode 100644 index e4790914..00000000 Binary files a/img/link_icon_black.png and /dev/null differ diff --git a/img/link_icon_white.png b/img/link_icon_white.png deleted file mode 100644 index 58a56d45..00000000 Binary files a/img/link_icon_white.png and /dev/null differ diff --git a/img/mic_icon_black.png b/img/mic_icon_black.png deleted file mode 100644 index 1210b069..00000000 Binary files a/img/mic_icon_black.png and /dev/null differ diff --git a/img/mic_icon_disabled.png b/img/mic_icon_disabled.png deleted file mode 100644 index 542d2824..00000000 Binary files a/img/mic_icon_disabled.png and /dev/null differ diff --git a/img/mic_icon_white.png b/img/mic_icon_white.png deleted file mode 100644 index e1837463..00000000 Binary files a/img/mic_icon_white.png and /dev/null differ diff --git a/img/narrow_arrow_down_black.png b/img/narrow_arrow_down_black.png deleted file mode 100644 index e53829e5..00000000 Binary files a/img/narrow_arrow_down_black.png and /dev/null differ diff --git a/img/narrow_arrow_down_white.png b/img/narrow_arrow_down_white.png deleted file mode 100644 index 309560c8..00000000 Binary files a/img/narrow_arrow_down_white.png and /dev/null differ diff --git a/img/overlay_br_sakura.png b/img/overlay_br_sakura.png deleted file mode 100644 index 61e8ccd7..00000000 Binary files a/img/overlay_br_sakura.png and /dev/null differ diff --git a/img/overlay_tl_sakura.png b/img/overlay_tl_sakura.png deleted file mode 100644 index d6211f41..00000000 Binary files a/img/overlay_tl_sakura.png and /dev/null differ diff --git a/img/redo_icon_black.png b/img/redo_icon_black.png deleted file mode 100644 index 989581a3..00000000 Binary files a/img/redo_icon_black.png and /dev/null differ diff --git a/img/redo_icon_white.png b/img/redo_icon_white.png deleted file mode 100644 index 2a27e302..00000000 Binary files a/img/redo_icon_white.png and /dev/null differ diff --git a/img/refresh_icon.png b/img/refresh_icon.png deleted file mode 100644 index 408bc2a6..00000000 Binary files a/img/refresh_icon.png and /dev/null differ diff --git a/img/refresh_update_icon.png b/img/refresh_update_icon.png deleted file mode 100644 index c5acad15..00000000 Binary files a/img/refresh_update_icon.png and /dev/null differ diff --git a/img/send_message_icon_black.png b/img/send_message_icon_black.png deleted file mode 100644 index e4240e56..00000000 Binary files a/img/send_message_icon_black.png and /dev/null differ diff --git a/img/send_message_icon_white.png b/img/send_message_icon_white.png deleted file mode 100644 index cdead72b..00000000 Binary files a/img/send_message_icon_white.png and /dev/null differ diff --git a/img/swap_icon_black.png b/img/swap_icon_black.png deleted file mode 100644 index eec6f830..00000000 Binary files a/img/swap_icon_black.png and /dev/null differ diff --git a/img/translation_icon_black.png b/img/translation_icon_black.png deleted file mode 100644 index 3e184a72..00000000 Binary files a/img/translation_icon_black.png and /dev/null differ diff --git a/img/translation_icon_disabled.png b/img/translation_icon_disabled.png deleted file mode 100644 index 533feb81..00000000 Binary files a/img/translation_icon_disabled.png and /dev/null differ diff --git a/img/translation_icon_white.png b/img/translation_icon_white.png deleted file mode 100644 index 706b0bbc..00000000 Binary files a/img/translation_icon_white.png and /dev/null differ diff --git a/img/unpackage_icon.png b/img/unpackage_icon.png deleted file mode 100644 index 8fd8e7d2..00000000 Binary files a/img/unpackage_icon.png and /dev/null differ diff --git a/img/vrchat_chatbox_trasnlator_transcription.png b/img/vrchat_chatbox_trasnlator_transcription.png deleted file mode 100644 index 9f2e401e..00000000 Binary files a/img/vrchat_chatbox_trasnlator_transcription.png and /dev/null differ diff --git a/img/vrct_logo_for_light_mode.png b/img/vrct_logo_for_light_mode.png deleted file mode 100644 index ad2b0b95..00000000 Binary files a/img/vrct_logo_for_light_mode.png and /dev/null differ diff --git a/img/vrct_logo_mark_black.ico b/img/vrct_logo_mark_black.ico deleted file mode 100644 index 7b711af3..00000000 Binary files a/img/vrct_logo_mark_black.ico and /dev/null differ diff --git a/img/vrct_logo_mark_black.png b/img/vrct_logo_mark_black.png deleted file mode 100644 index 4e4fce17..00000000 Binary files a/img/vrct_logo_mark_black.png and /dev/null differ diff --git a/img/vrct_logo_mark_black_icon.png b/img/vrct_logo_mark_black_icon.png deleted file mode 100644 index 1d73b8a8..00000000 Binary files a/img/vrct_logo_mark_black_icon.png and /dev/null differ diff --git a/img/vrct_update_process.png b/img/vrct_update_process.png deleted file mode 100644 index 27a77150..00000000 Binary files a/img/vrct_update_process.png and /dev/null differ diff --git a/img/xsoverlay2.png b/img/xsoverlay2.png deleted file mode 100644 index 20b4021a..00000000 Binary files a/img/xsoverlay2.png and /dev/null differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..15162d79 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + VRCT + + + +
+ + + diff --git a/install.bat b/install.bat index 8d2a5d51..03469d35 100644 --- a/install.bat +++ b/install.bat @@ -1,2 +1,10 @@ +python -m venv .venv +python -m venv .venv_cuda + +call .venv/Scripts/activate python.exe -m pip install --upgrade pip -pip install -r requirements.txt \ No newline at end of file +pip install -r requirements.txt + +call .venv_cuda/Scripts/activate +python.exe -m pip install --upgrade pip +pip install -r requirements_cuda.txt \ No newline at end of file diff --git a/installer/installer.nsi b/installer/installer.nsi deleted file mode 100644 index 48187fd4..00000000 --- a/installer/installer.nsi +++ /dev/null @@ -1,245 +0,0 @@ -!define PRODUCT_VERSION "1.0.0.0" -!define VERSION "1.0.0.0" -VIProductVersion "${PRODUCT_VERSION}" -VIFileVersion "${VERSION}" -VIAddVersionKey "FileVersion" "${VERSION}" -VIAddVersionKey "ProductName" "VRCT" -VIAddVersionKey "ProductVersion" "${PRODUCT_VERSION}" -VIAddVersionKey "LegalCopyright" "Copyright m's software" -VIAddVersionKey "FileDescription" "Communication tool with translation & transcription for VRChat" - -; Modern UI -!include MUI2.nsh -; nsDialogs -!include nsDialogs.nsh -; LogicLib -!include LogicLib.nsh -; FileFunc -!include FileFunc.nsh - -!define MUI_ICON "..\img\vrct_logo_mark_black.ico" -!define MUI_UNICON "..\img\vrct_logo_mark_black.ico" - -Unicode true -; アプリケーション名 -Name "VRCT" -; 作成されるインストーラ -OutFile "VRCT_Setup.exe" - -RequestExecutionLevel admin -ShowInstDetails show - -; 圧縮メソッド -SetCompressor lzma -; インストールされるディレクトリ -InstallDir "$LOCALAPPDATA\VRCT" -; XPマニフェスト -XPStyle on -; ページ -!insertmacro MUI_PAGE_WELCOME -!insertmacro MUI_PAGE_LICENSE "..\LICENSE" -Page custom OptionPage1 OptionPageLeave1 -Page custom OptionPage2 OptionPageLeave2 -!insertmacro MUI_PAGE_INSTFILES -!insertmacro MUI_PAGE_FINISH -; アンインストーラ ページ -!insertmacro MUI_UNPAGE_WELCOME -!insertmacro MUI_UNPAGE_CONFIRM -!insertmacro MUI_UNPAGE_INSTFILES -!insertmacro MUI_UNPAGE_FINISH -; 日本語UI -!insertmacro MUI_LANGUAGE "Japanese" -; 引数取得マクロ -!insertmacro GetParameters -!insertmacro GetOptions -; インターフェース 設定 -!define MUI_ABORTWARNING -; 変数 -Var Checkbox_InstallShortcut -Var Dialog_Options -Var InstallShortcut -Var DropList_Language -Var Set_Langage -Var DownloadWeight -Var RadioButton_Download -Var RadioButton_NotDownload -Var Label_Translation_subtitle_1 -Var Label_Translation_subtitle_2 -Var subFont - -; 初期化時コールバック -Function .onInit - ; オプション値を初期化します。 - StrCpy $InstallShortcut ${BST_CHECKED} - StrCpy $DropList_Language "English" - StrCpy $DownloadWeight ${BST_CHECKED} -FunctionEnd - -; オプション ページ 1 -Function OptionPage1 - !insertmacro MUI_HEADER_TEXT "オプション (Options)" "オプションを設定してください。 (Please set the options.)" - ; nsDialogsを作成します。 - nsDialogs::Create 1018 - ; 作成されたnsDialogsを変数に代入します。 - Pop $Dialog_Options - - ${If} $Dialog_Options == error - ; ダイアログの作成に失敗した場合には終了します。 - Abort - ${EndIf} - - ${NSD_CreateCheckbox} 0 0u 100% 12u "デスクトップにショートカットを作成 (Install shortcut on desktop)" - Pop $Checkbox_InstallShortcut - - ${If} $InstallShortcut == ${BST_CHECKED} - ; チェックが入力済の場合、チェックボックスにチェックを入れます。 - ${NSD_Check} $Checkbox_InstallShortcut - ${EndIf} - nsDialogs::Show -FunctionEnd - -; オプション ページ 1 退出コールバック -Function OptionPageLeave1 - ${NSD_GetState} $Checkbox_InstallShortcut $InstallShortcut -FunctionEnd - -; オプション ページ 2 -Function OptionPage2 - CreateFont $subFont "MS UI Gothic" "8" "400" - - !insertmacro MUI_HEADER_TEXT "初期設定 (Initial Settings)" "後から変更可能です。 (Changeable later.)" - ; nsDialogsを作成します。 - nsDialogs::Create 1018 - ; 作成されたnsDialogsを変数に代入します。 - Pop $Dialog_Options - - ${If} $Dialog_Options == error - ; ダイアログの作成に失敗した場合には終了します。 - Abort - ${EndIf} - - ; ComboBoxを作成します。 - ${NSD_CreateLabel} 0 20u 30% 12u "UIの言語 (Language)" - - ${NSD_CreateDropList} 33% 20u 33% 12u "" - Pop $DropList_Language - - # ラジオボタンを追加しWEIGHTをDownloadするか選択する - ${NSD_CreateLabel} 0 70u 30% 12u "翻訳機能 (Translation)" - ${NSD_CreateLabel} 0 83u 30% 8u "言語モデルをダウンロード" - Pop $Label_Translation_subtitle_1 - SendMessage $Label_Translation_subtitle_1 ${WM_SETFONT} $subFont 0 - SetCtlColors $Label_Translation_subtitle_1 0x696969 0xF0F0F0 - ${NSD_CreateLabel} 0 92u 30% 8u "(Download language model)" - Pop $Label_Translation_subtitle_2 - SendMessage $Label_Translation_subtitle_2 ${WM_SETFONT} $subFont 0 - SetCtlColors $Label_Translation_subtitle_2 0x696969 0xF0F0F0 - - ${NSD_CreateRadioButton} 33% 70u 33% 12u "使用する (Use)" - Pop $RadioButton_Download - ${NSD_CreateRadioButton} 66% 70u 33% 12u "使用しない (Don't use)" - Pop $RadioButton_NotDownload - - ${NSD_CB_AddString} $DropList_Language "English" - ${NSD_CB_AddString} $DropList_Language "日本語" - ${NSD_CB_AddString} $DropList_Language "한국어" - - ${NSD_CB_SelectString} $DropList_Language "English" - - ${If} $DownloadWeight == ${BST_CHECKED} - ; チェックが入力済の場合、チェックボックスにチェックを入れます。 - ${NSD_Check} $RadioButton_Download - ${EndIf} - nsDialogs::Show -FunctionEnd - -; オプション ページ 2 退出コールバック -Function OptionPageLeave2 - ${NSD_GetText} $DropList_Language $DropList_Language - ${NSD_GetState} $RadioButton_Download $DownloadWeight -FunctionEnd - -; デフォルト セクション -Section - ; If VRCT is already running, display a warning message and exit - StrCpy $1 "VRCT.exe" - nsProcess::_FindProcess "$1" - Pop $R1 - ${If} $R1 = 0 - nsExec::ExecToStack "taskkill /IM VRCT.exe" - ${EndIf} - - ; ディレクトリを削除 - RMDir /r "$INSTDIR" - ; スタート メニューから削除 - Delete "$SMPROGRAMS\VRCT\VRCT.lnk" - RMDir "$SMPROGRAMS\VRCT" - ; デスクトップ ショートカットを削除 - Delete "$DESKTOP\VRCT.lnk" - ; レジストリ キーを削除 - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VRCT" - - ; 出力先を指定します。 - SetOutPath "$INSTDIR" - ; インストールされるファイル - File /r "..\dist\VRCT\" - - ; アンインストーラを出力 - WriteUninstaller "$INSTDIR\Uninstall.exe" - - ${If} $InstallShortcut == ${BST_CHECKED} - ; デスクトップにショートカットを作成 - CreateShortCut "$DESKTOP\VRCT.lnk" "$INSTDIR\VRCT.exe" - ${EndIf} - - ; ComboBoxの選択値から言語を判定しconfig.jsonを$INSTDIRに作成 - ${If} $DropList_Language == "English" - StrCpy $Set_Langage "en" - ${ElseIf} $DropList_Language == "日本語" - StrCpy $Set_Langage "ja" - ${ElseIf} $DropList_Language == "한국어" - StrCpy $Set_Langage "ko" - ${EndIf} - - ${If} $DownloadWeight == 1 - StrCpy $DownloadWeight "true" - ${Else} - StrCpy $DownloadWeight "false" - ${EndIf} - - StrCpy $1 '{"UI_LANGUAGE": "$Set_Langage", "USE_TRANSLATION_FEATURE": $DownloadWeight}' - FileOpen $0 "$INSTDIR\config.json" w - FileWrite $0 $1 - FileClose $0 - - ; スタート メニューにショートカットを登録 - CreateDirectory "$SMPROGRAMS\VRCT" - SetOutPath "$INSTDIR" - CreateShortcut "$SMPROGRAMS\VRCT\VRCT.lnk" "$INSTDIR\VRCT.exe" "" - ; レジストリに登録 - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VRCT" "DisplayName" "VRCT" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VRCT" "UninstallString" '"$INSTDIR\Uninstall.exe"' - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VRCT" "DisplayIcon" '"$INSTDIR\_internal\img\vrct_logo_mark_black.ico"' -SectionEnd - -; アンインストーラ -Section Uninstall - ; If VRCT is already running, display a warning message and exit - StrCpy $1 "VRCT.exe" - nsProcess::_FindProcess "$1" - Pop $R1 - ${If} $R1 = 0 - MessageBox MB_OK|MB_ICONEXCLAMATION "VRCT is still running. Cannot uninstall this software.$\nPlease close VRCT and try again." /SD IDOK - Abort - ${EndIf} - ; ディレクトリを削除 - RMDir /r "$INSTDIR" - RMDir /r "$LOCALAPPDATA\VRCT" - ; スタート メニューから削除 - Delete "$SMPROGRAMS\VRCT\VRCT.lnk" - RMDir "$SMPROGRAMS\VRCT" - ; デスクトップ ショートカットを削除 - Delete "$DESKTOP\VRCT.lnk" - ; レジストリ キーを削除 - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VRCT" -SectionEnd \ No newline at end of file diff --git a/locales/config.js b/locales/config.js new file mode 100644 index 00000000..f7c1b084 --- /dev/null +++ b/locales/config.js @@ -0,0 +1,38 @@ +import yaml from "js-yaml"; +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; + +import en_yml from "./en.yml?raw"; +import ja_yml from "./ja.yml?raw"; +import ko_yml from "./ko.yml?raw"; +import zh_hant_yml from "./zh-Hant.yml?raw"; +import zh_hans_yml from "./zh-Hans.yml?raw"; + +const translation_en = yaml.load(en_yml); +const translation_ja = yaml.load(ja_yml); +const translation_ko = yaml.load(ko_yml); +const translation_zh_Hant = yaml.load(zh_hant_yml); +const translation_zh_Hans = yaml.load(zh_hans_yml); + + +const resources = { + en: { translation: translation_en }, + ja: { translation: translation_ja }, + ko: { translation: translation_ko }, + "zh-Hant": { translation: translation_zh_Hant }, + "zh-Hans": { translation: translation_zh_Hans }, +}; + +i18n + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + resources, + lng: "en", + fallbackLng: "en", + // debug: true, + interpolation: { + escapeValue: false, // react already safes from xss + }, +}); + +export default i18n; diff --git a/locales/en.yml b/locales/en.yml index 2f5cdd0a..810d05e3 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -1,300 +1,268 @@ -main_window: - translation: "Translation" - transcription_send: "Voice2Chatbox" - transcription_receive: "Speaker2Log" - foreground: "Foreground" - - language_settings: "Language Settings" - your_language: "Your Language" - translate_each_other_label: "Translate Each Other" - swap_button_label: "Swap Languages" - target_language: "Target Language" - translator: "Translator" - translator_ctranslate2: "Internal (Default)" - - textbox_tab_all: "All" - textbox_tab_sent: "Sent" - textbox_tab_received: "Received" - textbox_tab_system: "System" - - textbox_system_message: - enabled_easter_egg: "Whoa! You caught us! There is something...like...easter-egg-ish function has enabled! It'll affect to Overlay (VR) for now;)." - enabled_translation: "Translation feature is turned on." - disabled_translation: "Translation feature is turned off." - enabled_voice2chatbox: "Transcription from the microphone has started." - disabled_voice2chatbox: "Transcription from the microphone has been stopped." - enabled_speaker2log: "Transcription from the speaker has started." - disabled_speaker2log: "Transcription from the speaker has been stopped." - enabled_foreground: "The screen is fixed in the foreground." - disabled_foreground: "The foreground fixation has been released." - - auth_key_success: "Auth key update completed." - auth_key_error: "Auth Key is incorrect or Usage limit reached." - - no_mic_device_detected_error: "No mic device detected." - no_speaker_device_detected_error: "No speaker device detected." - translation_engine_limit_error: "It has automatically changed the translation engine. Access has been temporarily restricted due to an excessive number of requests to the translation engine. If you want to use the same translation engine, please wait for a while, restart VRCT, and try again." - - detected_by_word_filter: "The word %{detected_message} has not been sent due to detection by the word filter." - - selected_your_language: "'Your Language' has set to %{your_language}." - selected_target_language: "'Target Language' has set to %{target_language}." - switched_language_preset_tab: "Switched to Language Preset Tab No.%{tab_no}." - latest_language_setting: "Currently, 'Your Language' is set to %{your_language}, and 'Target Language' is set to %{target_language}." - - opened_web_page_booth: "Opened Booth page in your web browser." - opened_web_page_vrct_documents: "Opened VRCT Documents page in your web browser.\nFor any issues, requests, or inquiries, please feel free to contact us through the links at the bottom of the documents page, the 'Contact Form' or via X (formerly Twitter)!" - - update_available: "New version is here!" - state_text_enabled: "Enabled" - state_text_disabled: "Disabled" - - cover_message: "The functionality is temporarily disabled until the settings window is closed." - - confirmation_message: - update_software: "Download the new version and automatically restart the app.\nIt'll take a while. Do it now?" - deny_update_software: "Do it later" - accept_update_software: "Update and Restart" - updating: "Now updating..." - - detected_over_ui_size: "Current UI Size: %{current_ui_size}\nVRCT's window size may be larger than your display size.\n* Depending on your display size, you may need to adjust it multiple times." - deny_adjust_ui_size: "Keep it at this size" - accept_adjust_ui_size: "Set it smaller and restart" - - -selectable_language_window: - title_your_language: "Select Your Language" - title_target_language: "Select Target Language" - go_back_button: "Go Back" - - -overlay_settings: - restore_default_settings: "Restore Default Settings" - opacity: "Opacity" - ui_scaling: "UI Scaling" - x_position: "X-axis (left-right)" - y_position: "Y-axis (up-down)" - z_position: "Z-axis (front-back)" - x_rotation: "X-axis rotation" - y_rotation: "Y-axis rotation" - z_rotation: "Z-axis rotation" - display_duration: "Display duration" - fadeout_duration: "Fadeout duration" - - -config_window: - config_title: "Settings" - compact_mode: "Compact Mode" - version: "version %{version}" - restart_message: "Apply changes with a restart." - common_error_message: - invalid_value: "Invalid value." - - side_menu_labels: - appearance: "Appearance" - translation: "Translation" - transcription: "Transcription" - transcription_mic: "Mic" - transcription_speaker: "Speaker" - transcription_internal_model: "Transcription Model" - vr: "VR" - others: "Others" - others_send_message_formats: "Message Formats (Send)" - others_received_message_formats: "Message Formats (XSOverlay & Speaker2Chatbox)" - others_speaker2chatbox: "Speaker2Chatbox" - advanced_settings: "Advanced Settings" - - - transparency: - label: "Transparency" - desc: "Change the main window's transparency." - - appearance_theme: - label: "Theme" - desc: "Change the color theme." - - ui_size: - label: "UI Size" - - textbox_ui_size: - label: "Text Box Font Size" - desc: "You can adjust the font size used in the logs relative to the UI size." - - message_box_ratio: - label: "Message Box Size" - desc: "You can change the size of the input message box. It is in ratio to the text box.\n*No exact calculations." - - font_family: - label: "Font Family" - - ui_language: - label: "UI Language" - - to_restore_main_window_geometry: - label: "Remember The Main Window Position" - desc: "Restore the position and size of the previous window upon startup." - - use_translation_feature: - label: "Use Translation Feature" - desc: "You can't use the translation feature while this is turned off. Instead, the VRCT startup becomes a little faster. This is for users who don't need the translation feature and only use VRCT as a chatbox and transcription tool." - - ctranslate2_weight_type: - label: "Select Internal Translation Model" - desc: "You can choose the translation model to use for the internal translation engine." - small: "Basic model (%{capacity})" - large: "High accuracy model (%{capacity})" - - deepl_auth_key: - label: "DeepL Auth Key" - desc: "Please select %{translator} on the main screen with DeepL_API when using. ※Some languages may not be supported." - open_auth_key_webpage: "Open DeepL Account Webpage" - auth_key_success: "Auth key update completed." - auth_key_error: "Auth Key is incorrect or Usage limit reached." - - mic_host: - label: "Mic Host/Driver" - - mic_device: - label: "Mic Device" - - mic_dynamic_energy_threshold: - label_for_automatic: "Mic Energy Threshold (Current Setting: Automatic)" - desc_for_automatic: "Automatically determine microphone input sensitivity." - label_for_manual: "Mic Energy Threshold (Current Setting: Manual)" - desc_for_manual: "Manually determine the microphone input sensitivity using the slider. Press the microphone icon to input your voice and adjust the sensitivity while monitoring the volume." - error_message: "You can set it with a value between 0 to %{max}." - - mic_record_timeout: - label: "Mic Record Timeout" - desc: "Detects silence and, when the specified number of seconds has passed, considers the mic input to have ended. (Second(s))" - error_message: "It cannot be greater than '%{mic_phrase_timeout_label}' with a value of 0 or more." - - mic_phrase_timeout: - label: "Mic Phrase Timeout" - desc: "Transcription processing is performed at intervals of the specified number of seconds." - error_message: "It cannot be set lower than '%{mic_record_timeout_label}' with a value of 0 or more." - - mic_max_phrase: - label: "Mic Max Words" - desc: "It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs and send to VRChat." - error_message: "You can set a number equal to or greater than 0." - - mic_word_filter: - label: "Mic Word Filter" - desc: "If a registered word is detected, the text will not be sent. To add multiple words at once, separate them with a ',' (comma). \n*Duplicate words will not be registered." - add_button_label: "Add" - count_desc: "Current registered word count: %{count}" - - - speaker_device: - label: "Speaker Device" - - speaker_dynamic_energy_threshold: - label_for_automatic: "Speaker Energy Threshold (Current Setting: Automatic)" - desc_for_automatic: "Automatically determine speaker input sensitivity." - label_for_manual: "Speaker Energy Threshold (Current Setting: Manual)" - desc_for_manual: "Manually determine the speaker input sensitivity using the slider. Press the headphones icon to listen to the audio and adjust the sensitivity while monitoring the volume." - error_message: "You can set it with a value between 0 to %{max}." - no_device_error_message: "No speaker device detected." - - speaker_record_timeout: - label: "Speaker Record Timeout" - desc: "Detects silence and, when the specified number of seconds has passed, considers the speaker input to have ended. (Second(s))" - error_message: "It cannot be greater than '%{speaker_phrase_timeout_label}' with a value of 0 or more." - - speaker_phrase_timeout: - label: "Speaker Phrase Timeout" - desc: "Transcription processing is performed at intervals of the specified number of seconds." - error_message: "It cannot be set lower than '%{speaker_record_timeout_label}' with a value of 0 or more." - - speaker_max_phrase: - label: "Speaker Max Words" - desc: "It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs." - error_message: "You can set a number equal to or greater than 0." - - use_whisper_feature: - label: "Use Whisper Model As Transcription" - desc: "In some languages, the accuracy of speech recognition may improve. During speech recognition usage, CPU usage increases, so please consider your PC specs before using this feature." - - whisper_weight_type: - label: "Select Whisper Model" - desc: "Generally, models with larger capacity tend to have higher accuracy, but this also results in longer transcription times and increased CPU usage. Please refer to the documentation for explanations of each model.\n※Larger models, especially those exceeding medium size, can be challenging to run even depending on the CPU's performance." - model_template: "%{model_name} model (%{capacity})" - recommended_model_template: "%{model_name} model (%{capacity}) (Recommended)" - - - enable_overlay_small_log: - label: "Enable Overlay" - # desc: - open_overlay_settings: "Open Overlay Customized Settings" - - auto_clear_the_message_box: - label: "Auto Clear The Message Box" - - send_only_translated_messages: - label: "Send Only Translated Messages" - - send_message_button_type: - label: "Send Message Button" - hide: "Hide (Use enter key to send)" - show: "Show" - show_and_disable_enter_key: "Show and disable to send when pressed enter key" - - notice_xsoverlay: - label: "Notification XSOverlay" - desc: "Notify received messages by using XSOverlay's notification feature." - - auto_export_message_logs: - label: "Auto Export Message Logs" - desc: "Automatically export the conversation messages as a text file." - - vrc_mic_mute_sync: - label: "VRC Mic Mute Sync" - desc: "VRCT will not send the message to VRChat while VRChat's mic is muted.\n*There is a bit latency and Push-To-Talk is not supported." - - send_message_to_vrc: - label: "Send Message To VRChat" - desc: "There is a way to use it without sending messages to VRChat, but it is not supported. Enable this feature when you intend to send a message to VRChat." - - - send_message_format: - label: "Message Format" - desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message." - example_text: "This is an example sentence. Fonts, line breaks, etc. may differ from the actual display." - error_message: "Cannot use the term '[message]'." - - send_message_format_with_t: - label: "Message Format (with Translation)" - desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message." - example_text: "This is an example sentence. Fonts, line breaks, etc. may differ from the actual display." - error_message: "Cannot use the terms '[message]' and '[translation]'." - - received_message_format: - label: "Format of Received Messages" - desc: "Used for XSOverlay notification receiving feature.\n[message] will be replaced with the message. \n※It will be used in Speaker2Chatbox too." - example_text: "This is an example sentence. Actual display may vary, including font and line breaks." - error_message: "Cannot use the term '[message]'." - - received_message_format_with_t: - label: "Format of Received Messages (with Translation)" - desc: "Used for XSOverlay notification receiving feature.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message.\n※It will be used in Speaker2Chatbox too." - example_text: "This is an example sentence. Actual display may vary, including font and line breaks." - error_message: "Cannot use the terms '[message]' and '[translation]'." - - - # Note: Speaker2Chatbox localization is fine only in English. Do not translate it into other languages. - # Speaker2Chatbox - send_received_message_to_vrc: - label: "Send Received Message To VRChat" - desc: "Send the message you received from the speaker's sound to VRChat's chatbox." - # Speaker2Chatbox - - - osc_ip_address: - label: "OSC IP Address" - - osc_port: - label: "OSC Port" - - open_config_filepath: - label: "Open Config File" \ No newline at end of file +# ================================= +# IMPORTANT: +# Please read 'readme_first.txt' before making any changes. +# ================================= + +common: + go_back_button_label: Go Back + +main_page: + translation: Translation + transcription_send: Voice2Chatbox + transcription_receive: Speaker2Log + foreground: Foreground + language_settings: Language Settings + your_language: Your Language + translate_each_other_label: Translate Each Other + swap_button_label: Swap Languages + target_language: Target Language + translator: Translator + translator_ctranslate2: Internal (Default) + + translator_selector: + is_selected_same_language: |- + Since the same language is selected for both '{{your_language}}' and '{{target_language}}', only '{{translator_ctranslate2}}' is available. + + message_log: + all: All + sent: Sent + received: Received + system: System + + show_resend_button: Show Resend Button + resend_button_on_hover_desc: Press and hold to send + + # textbox_system_message: + # enabled_easter_egg: Whoa! You caught us! There is something...like...easter-egg-ish function has enabled! It'll affect to Overlay(VR) for now;). + # enabled_translation: Translation feature is turned on. + # disabled_translation: Translation feature is turned off. + # enabled_voice2chatbox: Transcription from the microphone has started. + # disabled_voice2chatbox: Transcription from the microphone has been stopped. + # enabled_speaker2log: Transcription from the speaker has started. + # disabled_speaker2log: Transcription from the speaker has been stopped. + # enabled_foreground: The screen is fixed in the foreground. + # disabled_foreground: The foreground fixation has been released. + # auth_key_success: Auth key update completed. + # auth_key_error: Auth Key is incorrect or Usage limit reached. + # no_mic_device_detected_error: No mic device detected. + # no_speaker_device_detected_error: No speaker device detected. + # translation_engine_limit_error: It has automatically changed the translation engine. Access has been temporarily restricted due to an excessive number of requests to the translation engine. If you want to use the same translation engine, please wait for a while, restart VRCT, and try again. + # detected_by_word_filter: The word {{detected_message}} has not been sent due to detection by the word filter. + # selected_your_language: '"Your Language" has set to {{your_language}}.' + # selected_target_language: '"Target Language" has set to {{target_language}}.' + # switched_language_preset_tab: Switched to Language Preset Tab No.{{tab_no}}." + # latest_language_setting: Currently, "Your Language" is set to {{your_language}}, and "Target Language" is set to {{target_language}}. + # opened_web_page_booth: Opened Booth page in your web browser. + # opened_web_page_vrct_documents: |- + # Opened VRCT Documents page in your web browser. + # For any issues, requests, or inquiries, please feel free to contact us through the links at the bottom of the documents page, the "Contact Form," or via X (formerly Twitter)! + + state_text_enabled: Enabled + state_text_disabled: Disabled + + language_selector: + title_your_language: Select Your Language + title_target_language: Select Target Language + + update_available: New version is here! + updating: Now updating... + +update_modal: + cpu_desc: Use CPU only as the compute device. + cuda_desc: Selectable between CPU and NVIDIA GPUs as compute devices. + cuda_compare_cpu_desc: With GPU selection, processing is faster compared to a CPU. + cuda_disk_space_desc: Requires approximately {{size}} of disk space. + close_modal: Close + download_latest_and_restart: |- + The latest version will be downloaded, + and the app will automatically restart. + is_latest_version_already: Already using the latest version + is_current_compute_device: Currently using this version + +config_page: + version: version {{version}} + # config_title: Settings + # compact_mode: Compact Mode + # restart_message: Apply changes with a restart. + # common_error_message: + # invalid_value: Invalid value. + model_download_button_label: Download + side_menu_labels: + device: Device + appearance: Appearance + translation: Translation + transcription: Transcription + vr: VR + others: Others + advanced_settings: Advanced Settings + supporters: Supporters + about_vrct: About VRCT + + device: + check_volume: Check Volume + mic_host_device: + label: Mic Device + label_auto_select: Auto Select + label_host: Host/Driver + label_device: Device + mic_dynamic_energy_threshold: + label_for_automatic: 'Mic Energy Threshold (Current Setting: Automatic)' + desc_for_automatic: Automatically determine microphone input sensitivity. + label_for_manual: 'Mic Energy Threshold (Current Setting: Manual)' + desc_for_manual: Manually determine the microphone input sensitivity using the slider. Press the microphone icon to input your voice and adjust the sensitivity while monitoring the volume. + error_message: You can set it with a value between 0 to {{max}}. + speaker_device: + label: Speaker Device + label_auto_select: Auto Select + label_device: Device + speaker_dynamic_energy_threshold: + label_for_automatic: 'Speaker Energy Threshold (Current Setting: Automatic)' + desc_for_automatic: Automatically determine speaker input sensitivity. + label_for_manual: 'Speaker Energy Threshold (Current Setting: Manual)' + desc_for_manual: Manually determine the speaker input sensitivity using the slider. Press the headphones icon to listen to the audio and adjust the sensitivity while monitoring the volume. + error_message: You can set it with a value between 0 to {{max}}. + no_device_error_message: No speaker device detected. + + appearance: + transparency: + label: Transparency + desc: Change the main window's transparency. + ui_size: + label: UI Size + textbox_ui_size: + label: Message Logs Font Size + desc: You can adjust the font size used in the logs relative to the UI size. + send_message_button_type: + label: Send Message Button + hide: Hide (Use enter key to send) + show: Show + show_and_disable_enter_key: Show and disable to send when pressed enter key + font_family: + label: Font Family + ui_language: + label: UI Language + + translation: + ctranslate2_weight_type: + label: Internal Translation Model + desc: You can choose the translation model to use for the internal translation engine. + small: Basic model ({{capacity}}) + large: High accuracy model ({{capacity}}) + ctranslate2_compute_device: + label: Internal Translation Compute Device + deepl_auth_key: + label: DeepL Auth Key + desc: Please select {{translator}} on the main screen with DeepL_API when using. ※Some languages may not be supported. + save: Save + edit: Edit + open_auth_key_webpage: Open DeepL Account Webpage + auth_key_success: Auth key update completed. + auth_key_error: Auth Key is incorrect or Usage limit reached. + + transcription: + section_label_mic: Mic + section_label_speaker: Speaker + section_label_transcription_engines: Transcription Engines + mic_record_timeout: + label: Mic Record Timeout + desc: Detects silence and, when the specified number of seconds has passed, considers the mic input to have ended. (Second(s)) + error_message: It cannot be greater than '{{mic_phrase_timeout_label}}' with a value of 0 or more. + mic_phrase_timeout: + label: Mic Phrase Timeout + desc: Transcription processing is performed at intervals of the specified number of seconds. + error_message: It cannot be set lower than '{{mic_record_timeout_label}}' with a value of 0 or more. + mic_max_phrase: + label: Mic Max Words + desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs and send to VRChat. + error_message: You can set a number equal to or greater than 0. + mic_word_filter: + label: Mic Word Filter + desc: |- + If a registered word is detected, the text will not be sent. To add multiple words at once, separate them with a "," (comma). + *Duplicate words will not be registered. + add_button_label: Add + count_desc: 'Current registered word count: {{count}}' + speaker_record_timeout: + label: Speaker Record Timeout + desc: Detects silence and, when the specified number of seconds has passed, considers the speaker input to have ended. (Second(s)) + error_message: It cannot be greater than '{{speaker_phrase_timeout_label}}' with a value of 0 or more. + speaker_phrase_timeout: + label: Speaker Phrase Timeout + desc: Transcription processing is performed at intervals of the specified number of seconds. + error_message: It cannot be set lower than '{{speaker_record_timeout_label}}' with a value of 0 or more. + speaker_max_phrase: + label: Speaker Max Words + desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs. + error_message: You can set a number equal to or greater than 0. + select_transcription_engine: + label: Transcription Engine + whisper_weight_type: + label: Whisper Model + desc: |- + Larger models tend to have higher accuracy, but they also consume more CPU or GPU resources. + Especially for models larger than medium, it may be difficult or even impossible to use them depending on the performance of your CPU/GPU. + model_template: '{{model_name}} model ({{capacity}})' + recommended_model_template: '{{model_name}} model ({{capacity}}) (Recommended)' + whisper_compute_device: + label: Whisper Compute Device + + vr: + single_line: Single line + multi_lines: Multi lines + overlay_enable: Enable + restore_default_settings: Restore Default Settings + position: Position + rotation: Rotation + x_position: X-axis (left-right) + y_position: Y-axis (up-down) + z_position: Z-axis (front-back) + x_rotation: X-axis rotation + y_rotation: Y-axis rotation + z_rotation: Z-axis rotation + sample_text_button: + start: |- + Send sample texts + to Overlay + stop: Stop Sending + sample_text: Sample text. + opacity: Opacity + ui_scaling: UI Scaling + display_duration: Display duration + fadeout_duration: Fadeout duration + tracker: Tracker + hmd: HMD + left_hand: Left hand + right_hand: Right hand + common_settings: Common Settings + overlay_show_only_translated_messages: + label: Show Only Translated Messages + + others: + auto_clear_the_message_box: + label: Auto Clear The Message Box + send_only_translated_messages: + label: Send Only Translated Messages + auto_export_message_logs: + label: Auto Export Message Logs + desc: Automatically export the conversation messages as a text file. + vrc_mic_mute_sync: + label: VRC Mic Mute Sync + desc: |- + VRCT will not send the message to VRChat while VRChat's mic is muted. + *There is a bit latency and Push-To-Talk is not supported. + send_message_to_vrc: + label: Send Message To VRChat + desc: There is a way to use it without sending messages to VRChat, but it is not supported. Enable this feature when you intend to send a message to VRChat. + send_received_message_to_vrc: + label: Send Received Message To VRChat + desc: Send the message you received from the speaker's sound to VRChat's chatbox. + + advanced_settings: + osc_ip_address: + label: OSC IP Address + osc_port: + label: OSC Port + open_config_filepath: + label: Open Config File + switch_compute_device: + label: Switch VRCT to CPU/GPU Version \ No newline at end of file diff --git a/locales/ja.yml b/locales/ja.yml index 1a194973..97784185 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -1,292 +1,266 @@ -main_window: - translation: "翻訳" - transcription_send: "音声認識(マイク)" - transcription_receive: "音声認識(スピーカー)" - foreground: "最前面表示" - - language_settings: "言語設定" - your_language: "あなたの言語" - translate_each_other_label: "双方向に翻訳" - swap_button_label: "言語を入れ替え" - target_language: "相手の言語" - translator: "翻訳エンジン" - translator_ctranslate2: "オフライン翻訳 (Default)" - - textbox_tab_all: "全て" - textbox_tab_sent: "送信" - textbox_tab_received: "受信" - textbox_tab_system: "システム" - - textbox_system_message: - enabled_translation: "翻訳機能をONにしました。" - disabled_translation: "翻訳機能をOFFしました。" - enabled_voice2chatbox: "マイクからの音声入力、文字起こしを開始します。" - disabled_voice2chatbox: "マイクからの音声入力、文字起こしを終了しました。" - enabled_speaker2log: "スピーカーからの音声聞き取り、文字起こしを開始します。" - disabled_speaker2log: "スピーカーからの音声聞き取り、文字起こしを終了しました。" - enabled_foreground: "画面を常に最前面へ固定します。" - disabled_foreground: "最前面への固定を解除しました。" - - auth_key_success: "認証キーの更新が完了しました。" - auth_key_error: "認証キーが間違っているか、API使用制限が上限に達しています。" - - no_mic_device_detected_error: "マイクデバイスが検出されませんでした。" - no_speaker_device_detected_error: "スピーカーデバイスが検出されませんでした。" - translation_engine_limit_error: "翻訳エンジンを自動的に変更しました。対象翻訳エンジンへのリクエストが多すぎるため、一時的にアクセスが制限されています。同じ翻訳エンジンを使用したい場合はしばらく待ってから、VRCTの再起動をしてもう一度試してみてください。" - - detected_by_word_filter: "ワードフィルターに登録されている単語 %{detected_message} が検出されたため送信しませんでした。" - - selected_your_language: "「あなたの言語」 を %{your_language} に設定しました。" - selected_target_language: "「相手の言語」 を %{target_language} に設定しました。" - switched_language_preset_tab: "言語プリセット番号 %{tab_no} に切り替わりました。" - latest_language_setting: "現在「あなたの言語」は %{your_language}、「相手の言語」は %{target_language} に設定されています。" - - opened_web_page_booth: "お使いのブラウザで、Boothのページを開きました。" - opened_web_page_vrct_documents: "お使いのブラウザで、VRCTのドキュメントを開きました。使用方法などはそちらに記載されています。\n不具合、ご要望、その他お問い合わせはドキュメント最下部にあるLinks、「お問合せフォーム」もしくはX (元Twitter) にて気軽にご連絡ください!" - - update_available: "新しいバージョンが出ました!" - state_text_enabled: "有効" - state_text_disabled: "無効" - - cover_message: "設定画面が閉じられるまで、一時的に機能を停止しています。" - - confirmation_message: - update_software: "新しいバージョンをダウンロードしてアプリを再起動します。\n少し時間がかかります。今すぐ行いますか?" - deny_update_software: "後でする" - accept_update_software: "アップデートして再起動" - updating: "アップデート中..." - - detected_over_ui_size: "現在のUI サイズ: %{current_ui_size}\nVRCTのウィンドウサイズが、お使いのディスプレイサイズより大きい可能性があります。\n※ディスプレイサイズによっては、何度か再設定が必要な場合があります。" - deny_adjust_ui_size: "このサイズのままで良い" - accept_adjust_ui_size: "小さく設定して再起動" - - -selectable_language_window: - title_your_language: "あなたの言語" - title_target_language: "相手の言語" - go_back_button: "戻る" - - -overlay_settings: - restore_default_settings: "初期値に戻す" - opacity: "透明度" - ui_scaling: "サイズ" - x_position: "X軸(左右)" - y_position: "Y軸(上下)" - z_position: "Z軸(前後)" - x_rotation: "X軸の回転" - y_rotation: "Y軸の回転" - z_rotation: "Z軸の回転" - display_duration: "表示時間" - fadeout_duration: "フェードアウト時間" - - -config_window: - config_title: "設定" - compact_mode: "コンパクトモード" - version: "バージョン %{version}" - restart_message: "再起動して変更を適用する。" - common_error_message: - invalid_value: "無効な値です。" - - side_menu_labels: - appearance: "デザイン" - translation: "翻訳" - transcription: "音声認識" - transcription_mic: "マイク" - transcription_speaker: "スピーカー" - transcription_internal_model: "音声認識モデル" - others: "その他" - others_send_message_formats: "メッセージフォーマット (送信)" - others_received_message_formats: "メッセージフォーマット (XSOverlay & Speaker2Chatbox)" - advanced_settings: "高度な設定" - - - transparency: - label: "透明度" - desc: "メイン画面の透明度を変更できます。" - - appearance_theme: - label: "外観テーマ" - desc: "カラーテーマを変更できます。" - - ui_size: - label: "UIサイズ" - - textbox_ui_size: - label: "テキストボックス フォントサイズ" - desc: "ログに表示されるフォントのサイズを、UIサイズを基準にして倍率を変えられます。" - - message_box_ratio: - label: "メッセージ入力欄のサイズ" - desc: "メッセージ入力欄のサイズを変更できます。テキストボックスとの比率となっています。\n※厳密な計算はしていません。" - - font_family: - label: "使用フォント" - - ui_language: - label: "UIの言語 / UI Language" - - to_restore_main_window_geometry: - label: "メイン画面の位置を記憶する" - desc: "起動時、前回の画面の位置とサイズを復元します。" - - use_translation_feature: - label: "翻訳機能を使用する" - desc: "オフにしている間は、翻訳機能を使わない代わり、VRCTの起動が少し速くなります。\n翻訳機能を必要とせず、VRCTをチャット送信と文字起こしツールとしてのみ使用するユーザー用です。" - - ctranslate2_weight_type: - label: "オフライン翻訳のタイプ" - desc: "翻訳エンジン(オフライン翻訳)で翻訳する際に、使用する翻訳モデルを選択できます。" - small: "通常モデル (%{capacity})" - large: "高精度モデル (%{capacity})" - - deepl_auth_key: - label: "DeepL 認証キー" - desc: "使用の際は、メイン画面にある %{translator} をDeepL_APIに変更してください。\n※対応していない言語もあります。" - open_auth_key_webpage: "DeepLアカウントページを開く" - auth_key_success: "認証キーの更新が完了しました。" - auth_key_error: "認証キーが間違っているか、API使用制限が上限に達しています。" - - mic_host: - label: "マイク(ホスト/ドライバー)" - - mic_device: - label: "マイク (デバイス)" - - mic_dynamic_energy_threshold: - label_for_automatic: "マイク入力感度の調整 (現在の設定: 自動)" - desc_for_automatic: "マイクの入力感度を自動的に調節する。" - label_for_manual: "マイク入力感度の調整 (現在の設定: 手動)" - desc_for_manual: "スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。" - error_message: "0 から %{max} までの数値で設定できます。" - - mic_record_timeout: - label: "入力が終了したとみなす無音時間" - desc: "無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。" - error_message: "0 以上で 「%{mic_phrase_timeout_label}」より大きくすることはできません。" - - mic_phrase_timeout: - label: "一度に文字起こしする時間の長さ" - desc: "設定された秒数ごとに文字起こし処理が行われます。" - error_message: "0 以上で 「%{mic_record_timeout_label}」より小さくすることはできません。" - - mic_max_phrase: - label: "送信するまでに保持する単語数" - desc: "文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をVRChatへ送信し、ログに表示します。" - error_message: "0以上の数値を設定できます。" - - mic_word_filter: - label: "ワードフィルター" - desc: "登録された単語を検出すると、その文章は送信されません。\n「,」カンマで区切ると、まとめて複数の単語を追加できます。\n※重複した単語は登録されません。" - add_button_label: "追加" - count_desc: "現在登録されている単語数: %{count}" - - - speaker_device: - label: "スピーカー (デバイス)" - - speaker_dynamic_energy_threshold: - label_for_automatic: "スピーカー入力感度の調整 (現在の設定: 自動)" - desc_for_automatic: "スピーカーの入力感度を自動的に調節する。" - label_for_manual: "スピーカー入力感度の調整 (現在の設定: 手動)" - desc_for_manual: "スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。" - error_message: "0 から %{max} までの数値で設定できます。" - no_device_error_message: "スピーカーデバイスが検出されませんでした。" - - speaker_record_timeout: - label: "入力が終了したとみなす無音時間" - desc: "無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。" - error_message: "0 以上で 「%{speaker_phrase_timeout_label}」より大きくすることはできません。" - - speaker_phrase_timeout: - label: "一度に文字起こしする時間の長さ" - desc: "設定された秒数ごとに文字起こし処理が行われます。" - error_message: "0 以上で 「%{speaker_record_timeout_label}」より小さくすることはできません。" - - speaker_max_phrase: - label: "ログとして表示するまでに保持する単語数" - desc: "文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をログに表示します。" - error_message: "0以上の数値を設定できます。" - - use_whisper_feature: - label: "音声認識にWhisperモデルを使用する" - desc: "一部の言語では、音声認識の精度が向上するかもしれません。音声認識使用中、CPUの使用率が上がるので、お使いのPCスペックと相談してこの機能を使用してください。" - - whisper_weight_type: - label: "Whisperモデルのタイプ" - desc: "基本的に、容量が多いモデルほど精度は高いですが、文字起こしまでの時間が伸び、CPU使用率も増加します。各モデルの説明はドキュメントをご覧ください。\n※特にmediumより容量の大きいモデルは、CPUの性能によっては使用すらも困難です。" - model_template: "%{model_name} モデル (%{capacity})" - recommended_model_template: "%{model_name} モデル (%{capacity}) (推奨)" - - - enable_overlay_small_log: - label: "Overlay機能を有効" - # desc: - open_overlay_settings: "Overlay詳細設定を開く" - - - - auto_clear_the_message_box: - label: "送信後はチャットボックスを空にする" - - send_only_translated_messages: - label: "翻訳後のメッセージのみ送信する" - - send_message_button_type: - label: "メッセージ送信ボタン" - hide: "非表示 (エンターキーを使って送信)" - show: "表示" - show_and_disable_enter_key: "表示し、エンターキーでの送信を無効" - - notice_xsoverlay: - label: "XSOverlayでの通知受け取り機能を有効" - desc: "文字起こし (受信) されたメッセージをXSOverlayの機能を使って通知として受け取れます。" - - auto_export_message_logs: - label: "会話ログを自動的に保存する" - desc: "テキストファイルとしてログがlogsフォルダ内に保存されます。" - - vrc_mic_mute_sync: - label: "VRCマイクミュート同期" - desc: "VRChatのマイクがミュートされている間は、メッセージをVRChatに送信しません。\n※若干の遅延はあります。また、Push-To-Talkは非対応です。" - - - send_message_to_vrc: - label: "VRChatにメッセージを送信する" - desc: "サポート対象外ですが、VRChatにメッセージを送信せずに使う方法があります。送信したい場合、この機能を有効にする事を忘れないでください。" - - - send_message_format: - label: "送信するメッセージのフォーマット" - desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換されます。" - example_text: "これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。" - error_message: "[message]という文字は使えません。" - - send_message_format_with_t: - label: "送信するメッセージのフォーマット(翻訳付き)" - desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換され、[translation]が翻訳されたメッセージに置換されます。" - example_text: "これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。" - error_message: "[message]と[translation]という文字は使えません。" - - received_message_format: - label: "受信するメッセージのフォーマット" - desc: "XSOverlay通知受け取り機能で使用されます。\n[message]がメッセージに置換されます。\n※Speaker2Chatboxでの送信機能にも使われます。" - example_text: "これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。" - error_message: "[message]という文字は使えません。" - - received_message_format_with_t: - label: "受信するメッセージのフォーマット(翻訳付き)" - desc: "XSOverlay通知受け取り機能で使用されます。\n[message]がメッセージに置換され、[translation]が翻訳されたメッセージに置換されます。\n※Speaker2Chatboxでの送信機能にも使われます。" - example_text: "これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。" - error_message: "[message]と[translation]という文字は使えません。" - - - osc_ip_address: - label: "OSC IP Address" - - osc_port: - label: "OSC Port" - - open_config_filepath: - label: "設定ファイルを開く" \ No newline at end of file +# ================================= +# IMPORTANT: +# Please read 'readme_first.txt' before making any changes. +# ================================= + +common: + go_back_button_label: 戻る + +main_page: + translation: 翻訳 + transcription_send: 音声認識(マイク) + transcription_receive: 音声認識(スピーカー) + foreground: 最前面表示 + language_settings: 言語設定 + your_language: あなたの言語 + translate_each_other_label: 双方向に翻訳 + swap_button_label: 言語を入れ替え + target_language: 相手の言語 + translator: 翻訳エンジン + translator_ctranslate2: オフライン翻訳 (Default) + + translator_selector: + is_selected_same_language: |- + 「{{your_language}}」と「{{target_language}}」に同じ言語が選択がされているため、「{{translator_ctranslate2}}」のみが使用できます。 + + message_log: + all: 全て + sent: 送信 + received: 受信 + system: システム + + show_resend_button: 再送信ボタンを表示する + resend_button_on_hover_desc: 長押しで送信 + + # textbox_system_message: + # enabled_translation: 翻訳機能をONにしました。 + # disabled_translation: 翻訳機能をOFFしました。 + # enabled_voice2chatbox: マイクからの音声入力、文字起こしを開始します。 + # disabled_voice2chatbox: マイクからの音声入力、文字起こしを終了しました。 + # enabled_speaker2log: スピーカーからの音声聞き取り、文字起こしを開始します。 + # disabled_speaker2log: スピーカーからの音声聞き取り、文字起こしを終了しました。 + # enabled_foreground: 画面を常に最前面へ固定します。 + # disabled_foreground: 最前面への固定を解除しました。 + # auth_key_success: 認証キーの更新が完了しました。 + # auth_key_error: 認証キーが間違っているか、API使用制限が上限に達しています。 + # no_mic_device_detected_error: マイクデバイスが検出されませんでした。 + # no_speaker_device_detected_error: スピーカーデバイスが検出されませんでした。 + # translation_engine_limit_error: 翻訳エンジンを自動的に変更しました。対象翻訳エンジンへのリクエストが多すぎるため、一時的にアクセスが制限されています。同じ翻訳エンジンを使用したい場合はしばらく待ってから、VRCTの再起動をしてもう一度試してみてください。 + # detected_by_word_filter: ワードフィルターに登録されている単語 {{detected_message}} が検出されたため送信しませんでした。 + # selected_your_language: 「あなたの言語」 を {{your_language}} に設定しました。 + # selected_target_language: 「相手の言語」 を {{target_language}} に設定しました。 + # switched_language_preset_tab: 言語プリセット番号 {{tab_no}} に切り替わりました。 + # latest_language_setting: 現在「あなたの言語」は {{your_language}}、「相手の言語」は {{target_language}} に設定されています。 + # opened_web_page_booth: お使いのブラウザで、Boothのページを開きました。 + # opened_web_page_vrct_documents: |- + # お使いのブラウザで、VRCTのドキュメントを開きました。使用方法などはそちらに記載されています。 + # 不具合、ご要望、その他お問い合わせはドキュメント最下部にあるLinks、「お問合せフォーム」もしくはX (元Twitter) にて気軽にご連絡ください! + + state_text_enabled: 有効 + state_text_disabled: 無効 + + language_selector: + title_your_language: あなたの言語 + title_target_language: 相手の言語 + + update_available: 新しいバージョンが出ました! + updating: アップデート中... + +update_modal: + cpu_desc: 処理デバイスとしてCPUのみを使用 + cuda_desc: 処理デバイスとしてCPUとNVIDIA製のGPUを選択可能 + cuda_compare_cpu_desc: GPU選択時、CPUと比べて処理が高速 + cuda_disk_space_desc: 約{{size}}のディスク容量が必要 + close_modal: 閉じる + download_latest_and_restart: |- + 最新版がダウンロードされ、 + アプリは自動的に再起動します。 + is_latest_version_already: すでに最新版を使用中 + is_current_compute_device: 現在使用中のバージョン + +config_page: + version: バージョン {{version}} + # config_title: 設定 + # compact_mode: コンパクトモード + # restart_message: 再起動して変更を適用する。 + # common_error_message: + # invalid_value: 無効な値です。 + model_download_button_label: ダウンロード + side_menu_labels: + device: デバイス + appearance: デザイン + translation: 翻訳 + transcription: 音声認識 + others: その他 + advanced_settings: 高度な設定 + + device: + check_volume: 音量チェック + mic_host_device: + label: マイク (デバイス) + label_auto_select: 自動選択 + label_host: ホスト/ドライバー + label_device: デバイス + mic_dynamic_energy_threshold: + label_for_automatic: 'マイク入力感度の調整 (現在の設定: 自動)' + desc_for_automatic: マイクの入力感度を自動的に調節する。 + label_for_manual: 'マイク入力感度の調整 (現在の設定: 手動)' + desc_for_manual: スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 + error_message: 0 から {{max}} までの数値で設定できます。 + speaker_device: + label: スピーカー (デバイス) + label_auto_select: 自動選択 + speaker_dynamic_energy_threshold: + label_for_automatic: 'スピーカー入力感度の調整 (現在の設定: 自動)' + desc_for_automatic: スピーカーの入力感度を自動的に調節する。 + label_for_manual: 'スピーカー入力感度の調整 (現在の設定: 手動)' + desc_for_manual: スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。 + error_message: 0 から {{max}} までの数値で設定できます。 + no_device_error_message: スピーカーデバイスが検出されませんでした。 + + appearance: + transparency: + label: 透明度 + desc: メイン画面の透明度を変更できます。 + ui_size: + label: UIサイズ + textbox_ui_size: + label: ログのフォントサイズ + desc: ログに表示されるフォントのサイズを、UIサイズを基準にして倍率を変えられます。 + send_message_button_type: + label: メッセージ送信ボタン + hide: 非表示 (エンターキーを使って送信) + show: 表示 + show_and_disable_enter_key: 表示し、エンターキーでの送信を無効 + font_family: + label: 使用フォント + ui_language: + label: UIの言語 + + translation: + ctranslate2_weight_type: + label: オフライン翻訳のタイプ + desc: 翻訳エンジン(オフライン翻訳)で翻訳する際に、使用する翻訳モデルを選択できます。 + small: 通常モデル ({{capacity}}) + large: 高精度モデル ({{capacity}}) + ctranslate2_compute_device: + label: オフライン翻訳の処理デバイス + deepl_auth_key: + label: DeepL 認証キー + desc: |- + 使用の際は、メイン画面にある {{translator}} をDeepL_APIに変更してください。 + ※対応していない言語もあります。 + open_auth_key_webpage: DeepLアカウントページを開く + save: 保存 + edit: 編集 + auth_key_success: 認証キーの更新が完了しました。 + auth_key_error: 認証キーが間違っているか、API使用制限が上限に達しています。 + + transcription: + section_label_mic: マイク + section_label_speaker: スピーカー + section_label_transcription_engines: 音声認識エンジン + mic_record_timeout: + label: 入力が終了したとみなす無音時間 + desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。 + error_message: 0 以上で 「{{mic_phrase_timeout_label}}」より大きくすることはできません。 + mic_phrase_timeout: + label: 一度に文字起こしする時間の長さ + desc: 設定された秒数ごとに文字起こし処理が行われます。 + error_message: 0 以上で 「{{mic_record_timeout_label}}」より小さくすることはできません。 + mic_max_phrase: + label: 送信するまでに保持する単語数 + desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をVRChatへ送信し、ログに表示します。 + error_message: 0以上の数値を設定できます。 + mic_word_filter: + label: ワードフィルター + desc: |- + 登録された単語を検出すると、その文章は送信されません。 + 「,」カンマで区切ると、まとめて複数の単語を追加できます。 + ※重複した単語は登録されません。 + add_button_label: 追加 + count_desc: '現在登録されている単語数: {{count}}' + speaker_record_timeout: + label: 入力が終了したとみなす無音時間 + desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。 + error_message: 0 以上で 「{{speaker_phrase_timeout_label}}」より大きくすることはできません。 + speaker_phrase_timeout: + label: 一度に文字起こしする時間の長さ + desc: 設定された秒数ごとに文字起こし処理が行われます。 + error_message: 0 以上で 「{{speaker_record_timeout_label}}」より小さくすることはできません。 + speaker_max_phrase: + label: ログとして表示するまでに保持する単語数 + desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をログに表示します。 + error_message: 0以上の数値を設定できます。 + select_transcription_engine: + label: 音声認識で使用するエンジン + whisper_weight_type: + label: Whisperモデルのタイプ + desc: |- + 容量が大きいモデルほど精度は高いですが、その分CPUやGPUを占有します。 + ※特にmediumより容量の大きいモデルは、CPU/GPUの性能によっては使用すらも困難です。 + model_template: '{{model_name}} モデル ({{capacity}})' + recommended_model_template: '{{model_name}} モデル ({{capacity}}) (推奨)' + whisper_compute_device: + label: Whisperで使用する処理デバイス + + vr: + single_line: 一行 + multi_lines: 複数行 + overlay_enable: 有効にする + restore_default_settings: 初期値に戻す + position: 位置 + rotation: 回転 + x_position: X軸(左右) + y_position: Y軸(上下) + z_position: Z軸(前後) + x_rotation: X軸の回転 + y_rotation: Y軸の回転 + z_rotation: Z軸の回転 + sample_text_button: + start: |- + サンプルテキストを + Overlayに送信する + stop: 送信を停止 + sample_text: サンプルテキスト + opacity: 透明度 + ui_scaling: サイズ + display_duration: 表示時間 + fadeout_duration: フェードアウト時間 + common_settings: 共通設定 + hmd: HMD + left_hand: 左手 + right_hand: 右手 + tracker: 表示するトラッカーの位置 + overlay_show_only_translated_messages: + label: 翻訳後のメッセージのみ表示する + + others: + auto_clear_the_message_box: + label: 送信後はチャットボックスを空にする + send_only_translated_messages: + label: 翻訳後のメッセージのみ送信する + auto_export_message_logs: + label: 会話ログを自動的に保存する + desc: テキストファイルとしてログがlogsフォルダ内に保存されます。 + vrc_mic_mute_sync: + label: VRCマイクミュート同期 + desc: |- + VRChatのマイクがミュートされている間は、メッセージをVRChatに送信しません。 + ※若干の遅延はあります。また、Push-To-Talkは非対応です。 + send_message_to_vrc: + label: VRChatにメッセージを送信する + desc: サポート対象外ですが、VRChatにメッセージを送信せずに使う方法があります。送信したい場合、この機能を有効にする事を忘れないでください。 + send_received_message_to_vrc: + label: 受信したメッセージをVRChatに送信する + desc: スピーカーから聞き取り、文字起こしされたメッセージをVRChatに送信します。 + + advanced_settings: + osc_ip_address: + label: OSC IP Address + osc_port: + label: OSC Port + open_config_filepath: + label: 設定ファイルを開く + switch_compute_device: + label: VRCT CPU/GPUバージョンの切り替え \ No newline at end of file diff --git a/locales/ko.yml b/locales/ko.yml index 02e70c0b..c9e98fe3 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -1,291 +1,204 @@ -main_window: - translation: "번역" - transcription_send: "음성인식 (마이크)" - transcription_receive: "음성인식 (스피커)" - foreground: "항상 위로" - - language_settings: "언어 설정" - your_language: "당신의 언어" - translate_each_other_label: "양방향으로 번역" - swap_button_label: "언어 교체" - target_language: "상대방의 언어" - translator: "번역 엔진" - translator_ctranslate2: "오프라인 번역 (기본값)" - - textbox_tab_all: "전체" - textbox_tab_sent: "전송" - textbox_tab_received: "수신" - textbox_tab_system: "시스템" - - textbox_system_message: - enabled_easter_egg: "우와! 이스터에그를 발견하셨군요! 이제 Overlay (VR)에 영향을 주는 숨겨진 기능이 활성화되었습니다!" - enabled_translation: "번역 기능을 시작합니다." - disabled_translation: "번역 기능이 중지되었습니다." - enabled_voice2chatbox: "마이크에서의 음성인식을 시작합니다." - disabled_voice2chatbox: "마이크에서의 음성인식이 중지되었습니다." - enabled_speaker2log: "스피커에서의 음성인식을 시작합니다." - disabled_speaker2log: "스피커에서의 음성인식이 중지되었습니다." - enabled_foreground: "화면을 항상 위로 고정합니다." - disabled_foreground: "화면 고정이 해제되었습니다." - - auth_key_success: "인증 키 갱신이 완료되었습니다." - auth_key_error: "인증 키가 잘못되었거나 API 사용 제한이 상한선에 도달했습니다." - - no_mic_device_detected_error: "마이크 장치를 찾지 못했습니다." - no_speaker_device_detected_error: "스피커 장치를 찾지 못했습니다." - translation_engine_limit_error: "번역 엔진을 자동으로 변경했습니다. 대상 번역 엔진에 대한 요청이 너무 많아 일시적으로 접근이 제한되었습니다. 해당 번역 엔진을 사용하려면 잠시 기다린 후 VRCT를 재시작하여 다시 시도해 보시기 바랍니다." - - detected_by_word_filter: "단어 필터에 등록된 단어 '%{detected_message}'(이)가 감지되어 전송하지 않았습니다." - - selected_your_language: "'당신의 언어'가 %{your_language}(으)로 설정되었습니다." - selected_target_language: "'상대방의 언어'가 %{target_language}(으)로 설정되었습니다." - switched_language_preset_tab: "언어 프리셋 번호 %{tab_no}로 전환되었습니다." - latest_language_setting: "현재 '당신의 언어'는 %{your_language}, '상대방의 언어'는 %{target_language}(으)로 설정되어 있습니다." - - opened_web_page_booth: "브라우저에서 Booth 페이지를 열었습니다." - opened_web_page_vrct_documents: "웹 브라우저에서 VRCT 문서 페이지가 열렸습니다.\n문제, 요청 또는 문의 사항이 있는 경우 문서 페이지 하단의 링크, '문의 양식' 또는 X(구 트위터)를 통해 언제든지 문의해 주세요!" - - update_available: "새로운 버전이 나왔습니다!" - state_text_enabled: "활성화됨" - state_text_disabled: "비활성화됨" - - cover_message: "설정 화면이 닫힐 때까지 일시적으로 기능을 정지하고 있습니다." - - confirmation_message: - update_software: "새 버전을 다운로드하고 재시작합니다. \n 조금 시간이 걸립니다. 지금 시작할까요?" - deny_update_software: "나중에 하기" - accept_update_software: "업데이트 및 재시작" - updating: "업데이트 중..." - - detected_over_ui_size: "현재 UI 크기: %{current_ui_size}\nVRCT의 창 크기가 사용자의 디스플레이 크기보다 클 수 있습니다. \n* 디스플레이 크기에 따라 여러 번 재설정해야 할 수도 있습니다." - deny_adjust_ui_size: "지금 상태를 유지" - accept_adjust_ui_size: "작게 줄이고 재시작" - - -selectable_language_window: - title_your_language: "당신의 언어" - title_target_language: "상대방의 언어" - go_back_button: "돌아가기" - - -overlay_settings: - restore_default_settings: "기본값으로 복원" - opacity: "불투명도" - ui_scaling: "UI 크기 조절" - x_position: "X축 위치 (좌우)" - y_position: "Y축 위치 (상하)" - z_position: "Z축 위치 (전후)" - x_rotation: "X축 회전" - y_rotation: "Y축 회전" - z_rotation: "Z축 회전" - display_duration: "표시 시간" - fadeout_duration: "서서히 사라지는 시간" - - -config_window: - config_title: "설정" - compact_mode: "컴팩트 모드" - version: "버전 %{version}" - restart_message: "재시작하여 변경 사항을 적용합니다." - common_error_message: - invalid_value: "유효하지 않은 값입니다." - - side_menu_labels: - appearance: "모양" - translation: "번역" - transcription: "음성인식" - transcription_mic: "마이크" - transcription_speaker: "스피커" - transcription_internal_model: "내부 엔진" - others: "기타" - others_send_message_formats: "메시지 형식 (전송)" - others_received_message_formats: "메시지 형식 (수신)" - others_speaker2chatbox: "스피커->챗박스" - advanced_settings: "고급 설정" - - - transparency: - label: "투명도" - desc: "메인 화면의 투명도를 변경합니다." - - appearance_theme: - label: "테마" - desc: "색상 테마를 변경합니다." - - ui_size: - label: "UI 크기" - - textbox_ui_size: - label: "텍스트 박스 글자 크기" - desc: "로그에 표시되는 글자 크기의 배율을 UI 크기에 따라 변경합니다." - - message_box_ratio: - label: "메시지 입력창 크기" - desc: "메시지 입력창의 크기를 변경합니다. 텍스트 박스와의 비율로 설정되어 있습니다. \n* 정확한 계산은 하지 않았습니다." - - font_family: - label: "글꼴" - - ui_language: - label: "UI 언어 / UI Language" - - to_restore_main_window_geometry: - label: "메인 화면 위치 기억" - desc: "시작 시 이전 화면의 위치와 크기를 복원합니다." - - use_translation_feature: - label: "번역 기능 사용" - desc: "번역 기능이 꺼져 있는 동안에는 번역을 하지 않는 대신 VRCT가 조금 더 빠르게 실행됩니다. \n 번역 기능이 필요하지 않고 VRCT를 채팅 전송 및 음성 인식 도구로만 사용하는 사용자를 위한 기능입니다." - - ctranslate2_weight_type: - label: "번역 모델" - desc: "오프라인 번역 시 사용할 번역 모델을 변경합니다." - small: "일반 모델 (%{capacity})" - large: "정밀 모델 (%{capacity})" - - deepl_auth_key: - label: "DeepL 인증 키" - desc: "사용 시 메인 화면에 있는 %{translator}를 DeepL_API로 변경해 주세요.\n지원하지 않는 언어도 있습니다." - open_auth_key_webpage: "DeepL 계정 페이지 열기" - auth_key_success: "인증 키 갱신이 완료되었습니다." - auth_key_error: "인증 키가 잘못되었거나 API 사용 제한이 상한에 도달했습니다." - - mic_host: - label: "마이크 호스트/드라이버" - - mic_device: - label: "마이크 장치" - - mic_dynamic_energy_threshold: - label_for_automatic: "음성 입력 최소 볼륨 (현재 설정: 자동)" - desc_for_automatic: "마이크의 입력 감도를 자동으로 조절합니다." - label_for_manual: "음성 입력 최소 볼륨 (현재 설정: 수동)" - desc_for_manual: "슬라이더를 움직여 입력 감도를 수동으로 조절합니다. 마이크 아이콘을 누르면 실제 음성의 볼륨을 확인하며 감도를 조절할 수 있습니다." - error_message: "0에서 %{max}까지의 숫자로만 설정할 수 있습니다." - - mic_record_timeout: - label: "최대 무음 시간" - desc: "무음을 감지하고 설정된 시간(초)만큼의 시간이 지나면 음성 입력이 종료된 것으로 판단합니다." - error_message: "0 이상에서 '%{mic_phrase_timeout_label}'보다 클 수 없습니다." - - mic_phrase_timeout: - label: "최대 인식 시간" - desc: "설정된 초 단위로 음성인식 처리가 이루어집니다." - error_message: "0 이상에서 '%{mic_record_timeout_label}'보다 작을 수 없습니다." - - mic_max_phrase: - label: "최대 입력 절(phrases) 수" - desc: "인식된 단어 수의 하한값으로, 이 수치를 초과하는 경우에만 결과를 VRChat으로 전송하고 로그에 표시합니다." - error_message: "0 이상의 숫자만 설정할 수 있습니다." - - mic_word_filter: - label: "단어 필터" - desc: "등록된 단어가 감지되면 해당 문장은 전송되지 않습니다. \n ',' 쉼표로 구분하면 여러 단어를 추가할 수 있습니다. \n* 중복된 단어는 등록되지 않습니다." - add_button_label: "추가" - count_desc: "현재 등록되어 있는 단어 수: %{count}" - - - speaker_device: - label: "스피커 장치" - - speaker_dynamic_energy_threshold: - label_for_automatic: "음성 입력 최소 볼륨 (현재 설정: 자동)" - desc_for_automatic: "스피커의 입력 감도를 자동으로 조절합니다." - label_for_manual: "음성 입력 최소 볼륨 (현재 설정: 수동)" - desc_for_manual: "슬라이더를 움직여 입력 감도를 수동으로 조절합니다. 헤드폰 아이콘을 누르면 실제 음성의 볼륨을 확인하며 감도를 조절할 수 있습니다." - error_message: "0에서 %{max}까지의 숫자로만 설정할 수 있습니다." - no_device_error_message: "스피커 디바이스를 찾지 못했습니다." - - speaker_record_timeout: - label: "최대 무음 시간" - desc: "무음을 감지하고 설정된 시간(초)만큼의 시간이 지나면 음성 입력이 종료된 것으로 판단합니다." - error_message: "0 이상에서 '%{speaker_phrase_timeout_label}'보다 클 수 없습니다." - - speaker_phrase_timeout: - label: "최대 인식 시간" - desc: "설정된 초 단위로 음성인식 처리가 이루어집니다." - error_message: "0 이상에서 '%{speaker_record_timeout_label}'보다 작을 수 없습니다." - - speaker_max_phrase: - label: "최대 입력 절(phrases) 수" - desc: "인식된 단어 수의 하한값으로, 이 수치를 초과하는 경우에만 결과를 로그에 표시합니다." - error_message: "0 이상의 숫자만 설정할 수 있습니다." - - use_whisper_feature: - label: "음성 인식에 Whisper 모델 사용" - desc: "일부 언어에서는 음성 인식의 정확도가 향상될 수 있습니다. 음성 인식 중 CPU 사용률이 올라가기 때문에 사용하시는 PC의 사양을 고려하여 이 기능을 사용해 주세요." - - whisper_weight_type: - label: "Whisper 모델 타입" - desc: "기본적으로 모델의 용량이 클수록 정확도는 높지만, 음성 인식 시간이 늘어나며 CPU 사용률도 증가합니다. 각 모델의 설명은 문서를 참조해 주세요.\n* 특히 medium보다 용량이 큰 모델은 CPU 성능에 따라 사용이 어려울 수 있습니다." - model_template: "%{model_name} 모델 (%{capacity})" - recommended_model_template: "%{model_name} 모델 (%{capacity}) (권장)" - - - enable_overlay_small_log: - label: "Overlay 활성화" - # desc: - open_overlay_settings: "Overlay 설정 창 열기" - - auto_clear_the_message_box: - label: "챗박스 자동 삭제" - - send_only_translated_messages: - label: "번역된 메시지만 전송" - - send_message_button_type: - label: "메시지 전송 버튼" - hide: "숨김 (Enter 키를 사용하여 전송)" - show: "표시" - show_and_disable_enter_key: "표시 (Enter 키 전송 비활성화)" - - notice_xsoverlay: - label: "XSOverlay에서 알림 수신 기능 활성화" - desc: "수신된 메시지를 XSOverlay의 기능을 통해 알림으로 받아볼 수 있습니다." - - auto_export_message_logs: - label: "대화 로그 자동 저장" - desc: "logs 폴더에 텍스트 파일로 로그가 저장됩니다." - - vrc_mic_mute_sync: - label: "VRChat 마이크 음소거 동기화" - desc: "VRChat의 마이크가 음소거되어 있는 동안에는 VRCT가 VRChat으로 메시지를 보내지 않습니다.\n* 약간의 지연이 있을 수 있으며, '눌러서 말하기' 기능은 지원되지 않습니다." - - send_message_to_vrc: - label: "VRChat에 메시지 전송" - desc: "VRChat에 메시지를 보내지 않고 사용할 수 있는 방법이 있지만 지원되지 않습니다. VRChat에 메시지를 보내려면 이 기능을 활성화하세요." - - - send_message_format: - label: "메시지 형식" - desc: "메시지가 실제로 보이는 형식을 변경합니다. \n[message]가 메시지로 대체됩니다." - example_text: "이것은 예문입니다. 폰트, 줄바꿈 등 실제 표시와 다를 수 있습니다." - error_message: "[message]라는 문자는 사용할 수 없습니다." - - send_message_format_with_t: - label: "메시지 형식 (번역 포함)" - desc: "메시지가 실제로 보이는 형식을 변경합니다. \n[message]는 메시지로, [translation]은 번역된 메시지로 대체됩니다.\nXSOverlay의 알림에서도 사용됩니다." - example_text: "예문입니다. 글꼴, 줄 바꿈 등이 실제 표시와 다를 수 있습니다." - error_message: "[message]와 [translation]이라는 문자는 사용할 수 없습니다." - - received_message_format: - label: "메시지 형식" - desc: "메시지가 실제로 보이는 형식을 변경합니다. \n[message]가 메시지로 대체됩니다." - example_text: "이것은 예문입니다. 폰트, 줄바꿈 등 실제 표시와 다를 수 있습니다." - error_message: "[message]라는 문자는 사용할 수 없습니다." - - received_message_format_with_t: - label: "메시지 형식 (번역 포함)" - desc: "메시지가 실제로 보이는 형식을 변경합니다. \n[message]는 메시지로, [translation]은 번역된 메시지로 대체됩니다.\nXSOverlay의 알림에서도 사용됩니다." - example_text: "이것은 예문입니다. 폰트, 줄바꿈 등 실제 표시와 다를 수 있습니다." - error_message: "[message]와 [translation]이라는 문자는 사용할 수 없습니다." - - - osc_ip_address: - label: "OSC IP 주소" - - osc_port: - label: "OSC 포트" - - open_config_filepath: - label: "설정 파일 열기" \ No newline at end of file +# ================================= +# IMPORTANT: +# Please read 'readme_first.txt' before making any changes. +# ================================= + +common: + go_back_button_label: 돌아가기 + +main_page: + translation: 번역 + transcription_send: 음성인식 (마이크) + transcription_receive: 음성인식 (스피커) + foreground: 항상 위로 + language_settings: 언어 설정 + your_language: 당신의 언어 + translate_each_other_label: 양방향으로 번역 + swap_button_label: 언어 교체 + target_language: 상대방의 언어 + translator: 번역 엔진 + translator_ctranslate2: 오프라인 번역 (기본값) + + message_log: + all: 전체 + sent: 전송 + received: 수신 + system: 시스템 + + # textbox_system_message: + # enabled_translation: 번역 기능을 시작합니다. + # disabled_translation: 번역 기능이 중지되었습니다. + # enabled_voice2chatbox: 마이크에서의 음성인식을 시작합니다. + # disabled_voice2chatbox: 마이크에서의 음성인식이 중지되었습니다. + # enabled_speaker2log: 스피커에서의 음성인식을 시작합니다. + # disabled_speaker2log: 스피커에서의 음성인식이 중지되었습니다. + # enabled_foreground: 화면을 항상 위로 고정합니다. + # disabled_foreground: 화면 고정이 해제되었습니다. + # auth_key_success: 인증키 갱신이 완료되었습니다. + # auth_key_error: 인증 키가 잘못되었거나 API 사용 제한이 상한선에 도달했습니다. + # no_mic_device_detected_error: 마이크 디바이스를 찾지 못했습니다. + # no_speaker_device_detected_error: 스피커 디바이스를 찾지 못했습니다. + # translation_engine_limit_error: 번역 엔진을 자동으로 변경했습니다. 대상 번역 엔진에 대한 요청이 너무 많아 일시적으로 접근이 제한되었습니다. 해당 번역 엔진을 사용하려면 잠시 기다린 후 VRCT를 재시작하여 다시 시도해 보시기 바랍니다 + # detected_by_word_filter: 단어 필터에 등록된 단어 {{detected_message}}(이)가 감지되어 전송하지 않았습니다. + # selected_your_language: "'당신의 언어'가 {{your_language}}(으)로 설정되었습니다." + # selected_target_language: "'상대방의 언어'가 {{target_language}}(으)로 설정되었습니다." + # switched_language_preset_tab: 언어 프리셋 번호 {{tab_no}}로 전환되었습니다. + # latest_language_setting: 현재 '당신의 언어'는 {{your_language}}, '상대방의 언어'는 {{target_language}}(으)로 설정되어 있습니다. + # opened_web_page_booth: 브라우저에서 Booth 페이지를 열었습니다. + # opened_web_page_vrct_documents: |- + # 웹 브라우저에서 VRCT 문서 페이지가 열렸습니다. + # 문제, 요청 또는 문의 사항이 있는 경우 문서 페이지 하단의 링크, '문의 양식' 또는 X(구 트위터)를 통해 언제든지 문의해 주세요! + + state_text_enabled: Enabled + state_text_disabled: Disabled + + language_selector: + title_your_language: 당신의 언어 + title_target_language: 상대방의 언어 + + update_available: 새로운 버전이 나왔습니다! + updating: 업데이트 중... + +update_modal: + update_software: |- + 새 버전을 다운로드하고 재시작합니다. + 조금 시간이 걸립니다. 지금 시작할까요? + deny_update_software: 나중에 하기 + accept_update_software: 업데이트 및 재시작 + + +config_page: + version: 버전 {{version}} + # config_title: 설정 + # compact_mode: 컴팩트 모드 + # restart_message: 재시작하여 변경 사항을 적용합니다. + # common_error_message: + # invalid_value: 유효하지 않은 값입니다. + side_menu_labels: + appearance: 모양 + translation: 번역 + transcription: 음성인식 + others: 기타 + advanced_settings: 고급 설정 + + device: + mic_host: + label: 마이크 호스트/드라이버 + mic_device: + label: 마이크 장치 + mic_dynamic_energy_threshold: + label_for_automatic: '음성 입력 최소 볼륨 (현재 설정: 자동)' + desc_for_automatic: 마이크의 입력 감도를 자동으로 조절합니다. + label_for_manual: '음성 입력 최소 볼륨 (현재 설정: 수동)' + desc_for_manual: 슬라이더를 움직여 입력 감도를 수동으로 조절합니다. 마이크 아이콘을 누르면 실제 음성의 볼륨을 확인하며 감도를 조절할 수 있습니다. + error_message: 0에서 {{max}}까지의 숫자로만 설정할 수 있습니다. + speaker_device: + label: 스피커 장치 + speaker_dynamic_energy_threshold: + label_for_automatic: '음성 입력 최소 볼륨 (현재 설정: 자동)' + desc_for_automatic: 스피커의 입력 감도를 자동으로 조절합니다. + label_for_manual: '음성 입력 최소 볼륨 (현재 설정: 수동)' + desc_for_manual: 슬라이더를 움직여 입력 감도를 수동으로 조절합니다. 헤드폰 아이콘을 누르면 실제 음성의 볼륨을 확인하며 감도를 조절할 수 있습니다. + error_message: 0에서 {{max}}까지의 숫자로만 설정할 수 있습니다. + no_device_error_message: 스피커 디바이스를 찾지 못했습니다. + + appearance: + transparency: + label: 투명도 + desc: 메인 화면의 투명도를 변경합니다. + ui_size: + label: UI 크기 + textbox_ui_size: + label: 텍스트 박스 글자 크기 + desc: 로그에 표시되는 글자 크기의 배율을 UI 크기에 따라 변경합니다. + send_message_button_type: + label: 메시지 전송 버튼 + hide: 숨김 (Enter 키를 사용하여 전송) + show: 표시 + show_and_disable_enter_key: 표시 (Enter 키 전송 비활성화) + font_family: + label: 폰트 + ui_language: + label: UI 언어 + + translation: + ctranslate2_weight_type: + label: 번역 모델 + desc: 오프라인 번역 시의 번역 모델을 변경합니다. + small: 일반 모델 ({{capacity}}) + large: 정밀 모델 ({{capacity}}) + deepl_auth_key: + label: DeepL 인증키 + desc: |- + 사용시 메인화면에 있는 {{translator}}를 DeepL_API로 변경해 주세요. + 지원하지 않는 언어도 있습니다. + open_auth_key_webpage: DeepL 계정 페이지 열기 + auth_key_success: 인증키 갱신이 완료되었습니다. + auth_key_error: 인증키가 잘못되었거나 API 사용 제한이 상한에 도달했습니다. + + transcription: + section_label_mic: 마이크 + section_label_speaker: 스피커 + mic_record_timeout: + label: 최대 무음 시간 + desc: 무음을 감지하고 설정된 시간(초)만큼의 시간이 지나면 음성 입력이 종료된 것으로 판단합니다. + error_message: 0 이상에서 '{{mic_phrase_timeout_label}}'보다 클 수 없습니다. + mic_phrase_timeout: + label: 최대 인식 시간 + desc: 설정된 초 단위로 음성인식 처리가 이루어집니다. + error_message: 0 이상에서 '{{mic_record_timeout_label}}'보다 작을 수 없습니다. + mic_max_phrase: + label: 최대 입력 절(phrases) 수 + desc: 인식된 단어 수의 하한값으로, 이 수치를 초과하는 경우에만 결과를 VRChat으로 전송하고 로그에 표시합니다. + error_message: 0 이상의 숫자만 설정할 수 있습니다. + mic_word_filter: + label: 단어 필터 + desc: |- + 등록된 단어가 감지되면 해당 문장은 전송되지 않습니다. + ',' 쉼표로 구분하면 여러 단어를 추가할 수 있습니다. + * 중복된 단어는 등록되지 않습니다. + add_button_label: 추가 + count_desc: '현재 등록되어 있는 단어 수: {{count}}' + speaker_record_timeout: + label: 최대 무음 시간 + desc: 무음을 감지하고 설정된 시간(초)만큼의 시간이 지나면 음성 입력이 종료된 것으로 판단합니다. + error_message: 0 이상에서 '{{speaker_phrase_timeout_label}}'보다 클 수 없습니다. + speaker_phrase_timeout: + label: 최대 인식 시간 + desc: 설정된 초 단위로 음성인식 처리가 이루어집니다. + error_message: 0 이상에서 '{{speaker_record_timeout_label}}'보다 작을 수 없습니다. + speaker_max_phrase: + label: 최대 입력 절(phrases) 수 + desc: 식된 단어 수의 하한값으로, 이 수치를 초과하는 경우에만 결과를 로그에 표시합니다. + error_message: 0 이상의 숫자만 설정할 수 있습니다. + use_whisper_feature: + label: 음성 인식에 Whisper 모델을 사용 + desc: 일부 언어에서는 음성 인식의 정확도가 향상될 수 있어요. 음성 인식 중 CPU 사용률이 올라가기 때문에 사용하시는 PC의 사양을 고려하여 이 기능을 사용해주세요. + whisper_weight_type: + label: Whisper 모델 타입 + # desc: "기본적으로 용량이 많은 모델일수록 정밀도는 높지만, 음성 인식의 시간이 늘어나며 CPU 사용률도 늘어나요.각 모델의 설명은 문서를 참조해주세요.\n※특히 medium보다 용량이 큰 모델은 CPU의 성능에 따라서는 사용조차 어려울 수 있어요. " + model_template: '{{model_name}} 모델 ({{capacity}})' + recommended_model_template: '{{model_name}} 모델 ({{capacity}}) (권장)' + + others: + auto_clear_the_message_box: + label: 챗박스 자동 삭제 + send_only_translated_messages: + label: 번역된 메시지만 전송 + notice_xsoverlay: + label: XSOverlay에서 알림 수신 기능 활성화 + desc: 수신된 메시지를 XSOverlay의 기능을 통해 알림으로 받아볼 수 있습니다. + auto_export_message_logs: + label: 대화 로그 자동 저장 + desc: logs 폴더에 텍스트 파일로 로그가 저장됩니다. + send_message_to_vrc: + label: VRChat에 메시지 전송 + desc: VRChat에 메시지를 보내지 않고 사용할 수 있는 방법이 있지만 지원되지 않습니다. VRChat에 메시지를 보내려면 이 기능을 활성화하세요. + + advanced_settings: + osc_ip_address: + label: OSC IP 주소 + osc_port: + label: OSC 포트 + open_config_filepath: + label: 설정 파일 열기 \ No newline at end of file diff --git a/locales/readme_first.txt b/locales/readme_first.txt index 9d3bc1e8..7c62a102 100644 --- a/locales/readme_first.txt +++ b/locales/readme_first.txt @@ -1,3 +1 @@ -Thank you for considering translating VRCT's UI. -For your information, we are planning to transition VRCT's UI from Python's tkinter to a web-based platform using HTML, CSS, and JavaScript. -This means there might be structural changes to the UI. We apologize if any of the sentences you translated are removed due to these changes. \ No newline at end of file +Thank you for considering translating VRCT's UI. However, please refrain from making any changes at this time. I am currently organizing the files, including reordering, adding, and removing elements, and some parts may change frequently until the UI becomes stable. (Note: This message was updated in December 2024.) \ No newline at end of file diff --git a/locales/zh-Hans.yml b/locales/zh-Hans.yml index 204ee9ee..29dc2c22 100644 --- a/locales/zh-Hans.yml +++ b/locales/zh-Hans.yml @@ -1,293 +1,222 @@ -main_window: - translation: "翻译" - transcription_send: "你的语音转文字" - transcription_receive: "他人语音转文字" - foreground: "顶层显示" - - language_settings: "语言设定" - your_language: "你的语言" - translate_each_other_label: "双向翻译" - swap_button_label: "互换" - target_language: "目标语言" - translator: "翻译器" - translator_ctranslate2: "离线翻译(默认)" - - textbox_tab_all: "全部" - textbox_tab_sent: "发送" - textbox_tab_received: "接受" - textbox_tab_system: "系统" - - textbox_system_message: - enabled_translation: "翻译已启动." - disabled_translation: "翻译已关闭." - enabled_voice2chatbox: "正在翻译你的语音并转成文字." - disabled_voice2chatbox: "你的语音翻译结束了." - enabled_speaker2log: "正在翻译他人语音并转成文字." - disabled_speaker2log: "第三者的语音翻译结束了." - enabled_foreground: "顶层显示开启." - disabled_foreground: "顶层显示关闭." - - auth_key_success: "授权密匙更新完毕" - auth_key_error: "授权密匙错误或已达到翻译API(翻译器决定)使用次数上限." - - no_mic_device_detected_error: "未检测到你的麦克风." - no_speaker_device_detected_error: "未检测到他人语音输入." - translation_engine_limit_error: "自动更换了翻译器.原因是对该翻译器请求太频繁,它暂时拒绝了接收翻译请求.如仍想使用原本翻译器,请稍等片刻后在重启VRCT." - - detected_by_word_filter: "该单词 %{detected_message} 被单词过滤器检测出所以没有发送." - - selected_your_language: "[你的语言]设定为 %{your_language} " - selected_target_language: "[目标语言]设定为 %{target_language} " - switched_language_preset_tab: "已切换为第 %{tab_no} 号语言设定" - latest_language_setting: "现在,你的语言是 %{your_language},目标语言是 %{target_language} ." - - opened_web_page_booth: "在你的默认浏览器上打开了Booth页面" - opened_web_page_vrct_documents: "在你的默认浏览器上打开了VRCT文档,有着关于VRCT的使用方法\n其他问题、请求、查询等请通过文档底部的链接或X (Twitter) 联系我们!" - - update_available: "有新版本可供使用!" - state_text_enabled: "启用" - state_text_disabled: "停用" - - cover_message: "在设置窗口关闭前,VRCT的功能暂时停用" - - confirmation_message: - update_software: "下载新版本并自动启动\n会花少许时间,现在更新吗?" - deny_update_software: "稍后再说" - accept_update_software: "更新后自动启动" - updating: "更新中..." - - detected_over_ui_size: "现在的界面大小: %{current_ui_size}\nVRCT的窗口大小有可能会大于显示器\n请根据你的屏幕大小设置合适的VRCT的大小" - deny_adjust_ui_size: "现在界面大小是合适的" - accept_adjust_ui_size: "缩小界面大小并重新启动" - - -selectable_language_window: - title_your_language: "你的语言" - title_target_language: "目标语言" - go_back_button: "返回" - - -overlay_settings: - restore_default_settings: "恢复默认设置" - opacity: "透明度" - ui_scaling: "大小" - x_position: "X轴(左右)" - y_position: "Y轴(上下)" - z_position: "Z轴(前后)" - x_rotation: "X轴旋转" - y_rotation: "Y轴旋转" - z_rotation: "Z轴旋转" - display_duration: "显示持续时间" - fadeout_duration: "渐隐持续时间" - - -config_window: - config_title: "设定" - compact_mode: "精简模式" - version: "版本 %{version}" - restart_message: "重启并应用设定" - common_error_message: - invalid_value: "无效的值" - - side_menu_labels: - appearance: "外观" - translation: "翻译" - transcription: "转录" - transcription_mic: "你的麦克风" - transcription_speaker: "他人声音" - transcription_internal_model: "转录模型" - others: "其他" - # others_send_message_formats: "メッセージフォーマット (送信)" - # others_received_message_formats: "メッセージフォーマット (XSOverlay & Speaker2Chatbox)" - advanced_settings: "高级设置" - - - transparency: - label: "透明度" - desc: "更改主视窗透明度" - - appearance_theme: - label: "主题" - desc: "更改主题配色" - - ui_size: - label: "界面大小" - - textbox_ui_size: - label: "文本框字体大小" - desc: "你可以根据用户界面大小调整文本框中使用的字体大小。" - - message_box_ratio: - label: "文本框大小" - desc: "你可以根据界面比例调整文本框大小\n※可能不准确" - - font_family: - label: "字体" - - ui_language: - label: "界面语言" - - to_restore_main_window_geometry: - label: "记录主界面位置" - desc: "启动时,按照上次的大小和位置启动" - - use_translation_feature: - label: "启用翻译功能" - desc: "关闭此功能时,无法使用翻译功能.VRCT的启动速度会变得更快一些.这适用于不需要翻译功能,只将VRCT用作聊天框和转录工具的用户." - - ctranslate2_weight_type: - label: "选择离线翻译模型" - desc: "可以选择用于离线翻译的翻译模型" - small: "普通模型 (%{capacity})" - large: "高精度模型 (%{capacity})" - - deepl_auth_key: - label: "DeepL 授权密匙" - desc: "在使用的时候,使用时请在主屏幕上通过 DeepL_API 选择 %{translator} \n※某些语言可能不支持" - open_auth_key_webpage: "打开DeepL账号页面" - auth_key_success: "授权密匙认证完成。" - auth_key_error: "授权密匙错误或已达API使用上限" - - mic_host: - label: "麦克风(host/driver)" - - mic_device: - label: "麦克风 (设备)" - - mic_dynamic_energy_threshold: - label_for_automatic: "麦克风输入阈值(当前设置:自动)" - desc_for_automatic: "自动调整麦克风输入阈值" - label_for_manual: "麦克风输入阈值(当前设置:手动)" - desc_for_manual: "使用滑杆手动确定麦克风输入灵敏度。按下麦克风图标输入语音,并在监控音量的同时调节灵敏度。" - error_message: "数值应为 0 至 %{max} 之间。" - - mic_record_timeout: - label: "语音输入结束后的静音时间" - desc: "当检测到静音并经过设定的秒数后,语音输入即被视为完成。" - error_message: "数值应为 0 至 [%{mic_phrase_timeout_label}]" - - mic_phrase_timeout: - label: "转录间隔" - desc: "在经过设定的时间后执行转录" - error_message: "转录间隔时间大于0秒且不能小于「%{mic_record_timeout_label}」" - - mic_max_phrase: - label: "麦克风发送时的最小单词数" - desc: "转录字数的下限,只有超过这个数字,才会记录翻译结果并发送到VRC" - error_message: "数值应为 0 以上" - - mic_word_filter: - label: "单词过滤器" - desc: "检测出被记录的单词时,不会发送这段话\n如要添加多个单词,可以用逗号来分割\n※不会记录重复的单词" - add_button_label: "添加" - count_desc: "现在被记录的单词数: %{count}" - - - speaker_device: - label: "他人语音 (设备)" - - speaker_dynamic_energy_threshold: - label_for_automatic: "他人语音接收阈值(当前设置:自动)" - desc_for_automatic: "自动调节他人语音接收阈值" - label_for_manual: "他人语音接收阈值(当前设置:手动)" - desc_for_manual: "使用滑杆手动调整他人语音接收阈值.在按下耳机按钮时,请根据实际听到的声音调整该大小" - error_message: "设定的数值从 0 到 %{max} " - no_device_error_message: "未检测到他人语音" - - speaker_record_timeout: - label: "语音接收结束后的静音时间" - desc: "当检测到静音并经过设定的秒数后,语音接收即被视为完成。" - error_message: "数值应为 0 至 「%{speaker_phrase_timeout_label}」" - - speaker_phrase_timeout: - label: "转录间隔" - desc: "在经过设定的时间后执行转录" - error_message: "转录间隔时间大于0秒且不能小于「%{speaker_record_timeout_label}」" - - - speaker_max_phrase: - label: "语音接收时的最小单词数" - desc: "转录字数的下限,只有超过这个数字,才会记录转录结果" - error_message: "数值应为 0 以上" - - use_whisper_feature: - label: "使用Whisper模型翻译" - desc: "在某些语言中,语音识别的准确性可能会提高.语音识别的过程中,CPU占有率可能会提高,请根据你的pc性能来决定是否使用它." - - whisper_weight_type: - label: "选择某个Whisper模型" - desc: "通常来说,容量越大的模型精度也会越高,但也会增加文字显示所需要的时间和CPU的使用率。请浏览各个模型的文档\n※特别是大于medium容量的模型、因CPU性能原因甚至无法使用。" - model_template: "%{model_name} 模型 (%{capacity})" - recommended_model_template: "%{model_name} 模型 (%{capacity}) (推荐)" - - - enable_overlay_small_log: - label: "可使用Overlay" - # desc: - open_overlay_settings: "打开Overlay进阶设置" - - - - auto_clear_the_message_box: - label: "发言后自动清空chatbox" - - send_only_translated_messages: - label: "只发送翻译后的信息" - - send_message_button_type: - label: "发送信息按钮" - hide: "隐藏 (可使用回车发送信息)" - show: "显示" - show_and_disable_enter_key: "显示,并且停用‘回车发送信息’" - - # notice_xsoverlay: - # label: "XSOverlayでの通知受け取り機能を有効" - # desc: "文字起こし (受信) されたメッセージをXSOverlayの機能を使って通知として受け取れます。" - - auto_export_message_logs: - label: "自动导出聊天记录" - desc: "以文本文件的形式在logs文件夹中保存。" - - vrc_mic_mute_sync: - label: "与VRC中的麦克风静音同步" - desc: "当VRChat的麦克风处于静音时,不在VRChat中发送信息\n※存在少许延迟且不支持按键发言." - - - send_message_to_vrc: - label: "发送信息至VRChat" - desc: "不发送信息至VRChat的情况下也能使用它,但该功能现在并未完成.在想要发送信息时,请不要忘记打开这个功能." - - - # send_message_format: - # label: "送信するメッセージのフォーマット" - # desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換されます。" - # example_text: "これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。" - # error_message: "[message]という文字は使えません。" - - # send_message_format_with_t: - # label: "送信するメッセージのフォーマット(翻訳付き)" - # desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換され、[translation]が翻訳されたメッセージに置換されます。" - # example_text: "これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。" - # error_message: "[message]と[translation]という文字は使えません。" - - # received_message_format: - # label: "受信するメッセージのフォーマット" - # desc: "XSOverlay通知受け取り機能で使用されます。\n[message]がメッセージに置換されます。\n※Speaker2Chatboxでの送信機能にも使われます。" - # example_text: "これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。" - # error_message: "[message]という文字は使えません。" - - # received_message_format_with_t: - # label: "受信するメッセージのフォーマット(翻訳付き)" - # desc: "XSOverlay通知受け取り機能で使用されます。\n[message]がメッセージに置換され、[translation]が翻訳されたメッセージに置換されます。\n※Speaker2Chatboxでの送信機能にも使われます。" - # example_text: "これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。" - # error_message: "[message]と[translation]という文字は使えません。" - - - osc_ip_address: - label: "OSC IP 地址" - - osc_port: - label: "OSC 端口" - - open_config_filepath: - label: "打开设置文件" \ No newline at end of file +# ================================= +# IMPORTANT: +# Please read 'readme_first.txt' before making any changes. +# ================================= + +common: + go_back_button_label: 返回 + +main_page: + translation: 翻译 + transcription_send: 你的语音转文字 + transcription_receive: 他人语音转文字 + foreground: 顶层显示 + language_settings: 语言设定 + your_language: 你的语言 + translate_each_other_label: 双向翻译 + swap_button_label: 互换 + target_language: 目标语言 + translator: 翻译器 + translator_ctranslate2: 离线翻译(默认) + + message_log: + all: 全部 + sent: 发送 + received: 接受 + system: 系统 + + # textbox_system_message: + # enabled_translation: 翻译已启动. + # disabled_translation: 翻译已关闭. + # enabled_voice2chatbox: 正在翻译你的语音并转成文字. + # disabled_voice2chatbox: 你的语音翻译结束了. + # enabled_speaker2log: 正在翻译他人语音并转成文字. + # disabled_speaker2log: 第三者的语音翻译结束了. + # enabled_foreground: 顶层显示开启. + # disabled_foreground: 顶层显示关闭. + # auth_key_success: 授权密匙更新完毕 + # auth_key_error: 授权密匙错误或已达到翻译API(翻译器决定)使用次数上限. + # no_mic_device_detected_error: 未检测到你的麦克风. + # no_speaker_device_detected_error: 未检测到他人语音输入. + # translation_engine_limit_error: 自动更换了翻译器.原因是对该翻译器请求太频繁,它暂时拒绝了接收翻译请求.如仍想使用原本翻译器,请稍等片刻后在重启VRCT. + # detected_by_word_filter: 该单词 {{detected_message}} 被单词过滤器检测出所以没有发送. + # selected_your_language: '[你的语言]设定为 {{your_language}} ' + # selected_target_language: '[目标语言]设定为 {{target_language}} ' + # switched_language_preset_tab: 已切换为第 {{tab_no}} 号语言设定 + # latest_language_setting: 现在,你的语言是 {{your_language}},目标语言是 {{target_language}} . + # opened_web_page_booth: 在你的默认浏览器上打开了Booth页面 + # opened_web_page_vrct_documents: |- + # 在你的默认浏览器上打开了VRCT文档,有着关于VRCT的使用方法 + # 其他问题、请求、查询等请通过文档底部的链接或X (Twitter) 联系我们! + + + state_text_enabled: 启用 + state_text_disabled: 停用 + + language_selector: + title_your_language: 你的语言 + title_target_language: 目标语言 + + update_available: 有新版本可供使用! + updating: 更新中... + +update_modal: + update_software_desc: |- + 下载新版本并自动启动 + 会花少许时间,现在更新吗? + deny_update_software: 稍后再说 + accept_update_software: 更新后自动启动 + + +config_page: + version: 版本 {{version}} + # config_title: 设定 + # compact_mode: 精简模式 + # restart_message: 重启并应用设定 + # common_error_message: + # invalid_value: 无效的值 + side_menu_labels: + appearance: 外观 + translation: 翻译 + transcription: 转录 + others: 其他 + advanced_settings: 高级设置 + + device: + mic_host: + label: 麦克风(host/driver) + mic_device: + label: 麦克风 (设备) + mic_dynamic_energy_threshold: + label_for_automatic: 麦克风输入阈值(当前设置:自动) + desc_for_automatic: 自动调整麦克风输入阈值 + label_for_manual: 麦克风输入阈值(当前设置:手动) + desc_for_manual: 使用滑杆手动确定麦克风输入灵敏度。按下麦克风图标输入语音,并在监控音量的同时调节灵敏度。 + error_message: 数值应为 0 至 {{max}} 之间。 + speaker_device: + label: 他人语音 (设备) + speaker_dynamic_energy_threshold: + label_for_automatic: 他人语音接收阈值(当前设置:自动) + desc_for_automatic: 自动调节他人语音接收阈值 + label_for_manual: 他人语音接收阈值(当前设置:手动) + desc_for_manual: 使用滑杆手动调整他人语音接收阈值.在按下耳机按钮时,请根据实际听到的声音调整该大小 + error_message: '设定的数值从 0 到 {{max}} ' + no_device_error_message: 未检测到他人语音 + + appearance: + transparency: + label: 透明度 + desc: 更改主视窗透明度 + ui_size: + label: 界面大小 + textbox_ui_size: + label: 文本框字体大小 + desc: 你可以根据用户界面大小调整文本框中使用的字体大小。 + send_message_button_type: + label: 发送信息按钮 + hide: 隐藏 (可使用回车发送信息) + show: 显示 + show_and_disable_enter_key: 显示,并且停用‘回车发送信息’ + font_family: + label: 字体 + ui_language: + label: 界面语言 + + translation: + ctranslate2_weight_type: + label: 选择离线翻译模型 + desc: 可以选择用于离线翻译的翻译模型 + small: 普通模型 ({{capacity}}) + large: 高精度模型 ({{capacity}}) + deepl_auth_key: + label: DeepL 授权密匙 + desc: |- + 在使用的时候,使用时请在主屏幕上通过 DeepL_API 选择 {{translator}} + ※某些语言可能不支持 + open_auth_key_webpage: 打开DeepL账号页面 + auth_key_success: 授权密匙认证完成。 + auth_key_error: 授权密匙错误或已达API使用上限\ + + transcription: + section_label_mic: 你的麦克风 + section_label_speaker: 他人声音 + mic_record_timeout: + label: 语音输入结束后的静音时间 + desc: 当检测到静音并经过设定的秒数后,语音输入即被视为完成。 + error_message: 数值应为 0 至 [{{mic_phrase_timeout_label}}] + mic_phrase_timeout: + label: 转录间隔 + desc: 在经过设定的时间后执行转录 + error_message: 转录间隔时间大于0秒且不能小于「{{mic_record_timeout_label}}」 + mic_max_phrase: + label: 麦克风发送时的最小单词数 + desc: 转录字数的下限,只有超过这个数字,才会记录翻译结果并发送到VRC + error_message: 数值应为 0 以上 + mic_word_filter: + label: 单词过滤器 + desc: |- + 检测出被记录的单词时,不会发送这段话 + 如要添加多个单词,可以用逗号来分割 + ※不会记录重复的单词 + add_button_label: 添加 + count_desc: '现在被记录的单词数: {{count}}' + speaker_record_timeout: + label: 语音接收结束后的静音时间 + desc: 当检测到静音并经过设定的秒数后,语音接收即被视为完成。 + error_message: 数值应为 0 至 「{{speaker_phrase_timeout_label}}」 + speaker_phrase_timeout: + label: 转录间隔 + desc: 在经过设定的时间后执行转录 + error_message: 转录间隔时间大于0秒且不能小于「{{speaker_record_timeout_label}}」 + speaker_max_phrase: + label: 语音接收时的最小单词数 + desc: 转录字数的下限,只有超过这个数字,才会记录转录结果 + error_message: 数值应为 0 以上 + use_whisper_feature: + label: 使用Whisper模型翻译 + desc: 在某些语言中,语音识别的准确性可能会提高.语音识别的过程中,CPU占有率可能会提高,请根据你的pc性能来决定是否使用它. + whisper_weight_type: + label: 选择某个Whisper模型 + # desc: |- + # 通常来说,容量越大的模型精度也会越高,但也会增加文字显示所需要的时间和CPU的使用率。请浏览各个模型的文档 + # ※特别是大于medium容量的模型、因CPU性能原因甚至无法使用。 + model_template: '{{model_name}} 模型 ({{capacity}})' + recommended_model_template: '{{model_name}} 模型 ({{capacity}}) (推荐)' + + vr: + restore_default_settings: 恢复默认设置 + opacity: 透明度 + ui_scaling: 大小 + x_position: X轴(左右) + y_position: Y轴(上下) + z_position: Z轴(前后) + x_rotation: X轴旋转 + y_rotation: Y轴旋转 + z_rotation: Z轴旋转 + display_duration: 显示持续时间 + fadeout_duration: 渐隐持续时间 + + others: + auto_clear_the_message_box: + label: 发言后自动清空chatbox + send_only_translated_messages: + label: 只发送翻译后的信息 + auto_export_message_logs: + label: 自动导出聊天记录 + desc: 以文本文件的形式在logs文件夹中保存。 + vrc_mic_mute_sync: + label: 与VRC中的麦克风静音同步 + desc: |- + 当VRChat的麦克风处于静音时,不在VRChat中发送信息 + ※存在少许延迟且不支持按键发言. + send_message_to_vrc: + label: 发送信息至VRChat + desc: 不发送信息至VRChat的情况下也能使用它,但该功能现在并未完成.在想要发送信息时,请不要忘记打开这个功能. + + advanced_settings: + osc_ip_address: + label: OSC IP 地址 + osc_port: + label: OSC 端口 + open_config_filepath: + label: 打开设置文件 \ No newline at end of file diff --git a/locales/zh-Hant.yml b/locales/zh-Hant.yml index 26e79a07..ddeee4a3 100644 --- a/locales/zh-Hant.yml +++ b/locales/zh-Hant.yml @@ -1,283 +1,223 @@ -main_window: - translation: "翻譯" - transcription_send: "麥克風轉文字" - transcription_receive: "喇叭轉文字" - foreground: "最上層顯示" +# ================================= +# IMPORTANT: +# Please read 'readme_first.txt' before making any changes. +# ================================= - language_settings: "語言設定" - your_language: "你的語言" - translate_each_other_label: "互相翻譯" - swap_button_label: "交換語言" - target_language: "目標語言" - translator: "翻譯器" - translator_ctranslate2: "離線翻譯(預設)" +common: + go_back_button_label: 返回 - textbox_tab_all: "全部" - textbox_tab_sent: "已發送" - textbox_tab_received: "已接收" - textbox_tab_system: "系統" +main_page: + translation: 翻譯 + transcription_send: 麥克風轉文字 + transcription_receive: 喇叭轉文字 + foreground: 最上層顯示 + language_settings: 語言設定 + your_language: 你的語言 + translate_each_other_label: 互相翻譯 + swap_button_label: 交換語言 + target_language: 目標語言 + translator: 翻譯器 + translator_ctranslate2: 離線翻譯(預設) - textbox_system_message: - enabled_easter_egg: "你找到了彩蛋!看看你的 VR Overlay 有沒有什麼變化?" - enabled_translation: "翻譯功能已啟用。" - disabled_translation: "翻譯功能已停用。" - enabled_voice2chatbox: "麥克風轉文字已啟用。" - disabled_voice2chatbox: "麥克風轉文字已停用。" - enabled_speaker2log: "喇叭轉文字已啟用。" - disabled_speaker2log: "喇叭轉文字已停用。" - enabled_foreground: "最上層顯示已啟用。" - disabled_foreground: "最上層顯示已停用。" + message_log: + all: 全部 + sent: 已發送 + received: 已接收 + system: 系統 - auth_key_success: "授權金鑰更新完成。" - auth_key_error: "授權金鑰錯誤或已達使用上限。" + # textbox_system_message: + # enabled_easter_egg: 你找到了彩蛋!看看你的 VR Overlay 有沒有什麼變化? + # enabled_translation: 翻譯功能已啟用。 + # disabled_translation: 翻譯功能已停用。 + # enabled_voice2chatbox: 麥克風轉文字已啟用。 + # disabled_voice2chatbox: 麥克風轉文字已停用。 + # enabled_speaker2log: 喇叭轉文字已啟用。 + # disabled_speaker2log: 喇叭轉文字已停用。 + # enabled_foreground: 最上層顯示已啟用。 + # disabled_foreground: 最上層顯示已停用。 + # auth_key_success: 授權金鑰更新完成。 + # auth_key_error: 授權金鑰錯誤或已達使用上限。 + # no_mic_device_detected_error: 未偵測到麥克風。 + # no_speaker_device_detected_error: 未偵測到喇叭。 + # translation_engine_limit_error: 翻譯引擎已自動變更。由於請求太頻繁,已被這個翻譯引擎暫時受限。如果你想使用相同的翻譯引擎,請稍等片刻,重新啟動 VRCT 並重試。 + # detected_by_word_filter: 由於詞語過濾器的偵測,「{{detected_message}}」未被發送。 + # selected_your_language: 「你的語言」已設為 {{your_language}}。 + # selected_target_language: 「目標語言」已設為 {{target_language}}。 + # switched_language_preset_tab: 已切換到第 {{tab_no}} 個語言設定。 + # latest_language_setting: 目前「你的語言」設為 {{your_language}},「目標語言」設為 {{target_language}}。 + # opened_web_page_booth: 已在瀏覽器中打開 Booth 頁面。 + # opened_web_page_vrct_documents: |- + # 已在瀏覽器中打開VRCT文件頁面。 + # 如有任何問題、請求或查詢,請通過文件頁面底部的連結、「聯絡表單」或 X (Twitter) 聯絡我們! - no_mic_device_detected_error: "未偵測到麥克風。" - no_speaker_device_detected_error: "未偵測到喇叭。" - translation_engine_limit_error: "翻譯引擎已自動變更。由於請求太頻繁,已被這個翻譯引擎暫時受限。如果你想使用相同的翻譯引擎,請稍等片刻,重新啟動 VRCT 並重試。" + state_text_enabled: 啟用 + state_text_disabled: 停用 - detected_by_word_filter: "由於詞語過濾器的偵測,「%{detected_message}」未被發送。" + language_selector: + title_your_language: 選擇你的語言 + title_target_language: 選擇目標語言 - selected_your_language: "「你的語言」已設為 %{your_language}。" - selected_target_language: "「目標語言」已設為 %{target_language}。" - switched_language_preset_tab: "已切換到第 %{tab_no} 個語言設定。" - latest_language_setting: "目前「你的語言」設為 %{your_language},「目標語言」設為 %{target_language}。" + update_available: 有新版本可供使用! + updating: 正在更新... - opened_web_page_booth: "已在瀏覽器中打開 Booth 頁面。" - opened_web_page_vrct_documents: "已在瀏覽器中打開VRCT文件頁面。\n如有任何問題、請求或查詢,請通過文件頁面底部的連結、「聯絡表單」或 X (Twitter) 聯絡我們!" +update_modal: + update_software_desc: |- + 下載新版本並自動更新 VRCT。 + 會花一些時間,現在更新嗎? + deny_update_software: 稍後再說 + accept_update_software: 更新 - update_available: "有新版本可供使用!" - state_text_enabled: "啟用" - state_text_disabled: "停用" - cover_message: "VRCT 功能在設定視窗關閉前暫時停用。" +config_page: + version: 版本 {{version}} + # config_title: 設定 + # compact_mode: 精簡模式 + # restart_message: 重新啟動以應用變更。 + # common_error_message: + # invalid_value: 無效值。 + side_menu_labels: + appearance: 外觀 + translation: 翻譯 + transcription: 轉錄 + vr: VR + others: 其他 + advanced_settings: 進階設定 - confirmation_message: - update_software: "下載新版本並自動更新 VRCT。\n會花一些時間,現在更新嗎?" - deny_update_software: "稍後再說" - accept_update_software: "更新" - updating: "正在更新..." + device: + mic_host: + label: 麥克風 Host/Driver + mic_device: + label: 麥克風裝置 + mic_dynamic_energy_threshold: + label_for_automatic: 麥克風能量閾值(當前設置:自動) + desc_for_automatic: 自動判定麥克風輸入靈敏度。 + label_for_manual: 麥克風能量閾值(當前設置:手動) + desc_for_manual: 使用滑桿調整麥克風輸入靈敏度,你可以按下麥克風圖示來測試。 + error_message: 可以設置 0 到 {{max}} 之間的值。 + speaker_device: + label: 喇叭裝置 + speaker_dynamic_energy_threshold: + label_for_automatic: 喇叭能量閾值(當前設置:自動) + desc_for_automatic: 自動確定喇叭輸入靈敏度。 + label_for_manual: 喇叭能量閾值(當前設置:手動) + desc_for_manual: 使用滑桿調整喇叭輸入靈敏度,你可以按下喇叭圖示來測試。 + error_message: 可以設置 0 到 {{max}} 之間的值。 + no_device_error_message: 未偵測到喇叭裝置。 - detected_over_ui_size: "介面大小:%{current_ui_size}\nVRCT 的視窗大小可能超過你的螢幕大小。" - deny_adjust_ui_size: "保持目前大小" - accept_adjust_ui_size: "縮小介面並重新啟動" + appearance: + transparency: + label: 透明度 + desc: 變更主視窗的透明度。 + ui_size: + label: 介面大小 + textbox_ui_size: + label: 訊息框字體大小 + desc: 你可以根據介面大小調整記錄中使用的字體大小。 + send_message_button_type: + label: 發送訊息按鈕 + hide: 隱藏(使用 Enter 鍵發送) + show: 顯示 + show_and_disable_enter_key: 顯示並停用 Enter 鍵發送 + font_family: + label: 字型 + ui_language: + label: 介面語言 -selectable_language_window: - title_your_language: "選擇你的語言" - title_target_language: "選擇目標語言" - go_back_button: "返回" + translation: + ctranslate2_weight_type: + label: 選擇離線翻譯模型 + desc: 你可以選擇用於離線翻譯引擎的翻譯模型。 + small: 基本模型({{capacity}}) + large: 高準確率模型({{capacity}}) + deepl_auth_key: + label: DeepL 授權金鑰 + desc: 使用 DeepL API 時請在主螢幕選擇 {{translator}}。※可能不支援某些語言。 + open_auth_key_webpage: 打開 DeepL 帳號頁面 + auth_key_success: 授權金鑰更新完成。 + auth_key_error: 授權金鑰錯誤或已達使用上限。 -overlay_settings: - restore_default_settings: "恢復預設設定" - opacity: "透明度" - ui_scaling: "介面縮放" - x_position: "X軸(左右)" - y_position: "Y軸(上下)" - z_position: "Z軸(前後)" - x_rotation: "X軸旋轉" - y_rotation: "Y軸旋轉" - z_rotation: "Z軸旋轉" - display_duration: "顯示持續時間" - fadeout_duration: "淡出持續時間" + transcription: + section_label_mic: 麥克風 + section_label_speaker: 喇叭 + mic_record_timeout: + label: 麥克風音訊 - 判定結束時間 + desc: 麥克風未收到音訊後,結束一段話的判定時間(秒)。 + error_message: 不能大於「{{mic_phrase_timeout_label}}」,應為 0 或更高。 + mic_phrase_timeout: + label: 麥克風音訊 - 紀錄間隔時間 + desc: 每隔多久要紀錄一次音訊。 + error_message: 不能小於「{{mic_record_timeout_label}}」,應為 0 或更高。 + mic_max_phrase: + label: 麥克風音訊 - 最大單詞數量 + desc: 只有在單詞超過此數量時,才會記錄結果並發送到 VRChat。 + error_message: 可以設置為 0 或更高的數值。 + mic_word_filter: + label: 麥克風單詞過濾器 + desc: |- + 如果偵測到清單內的單詞,則不會發送訊息。要一次新增多個詞語,請用「,」(半形逗號)分隔。 + *重複詞語會被忽略。 + add_button_label: 新增 + count_desc: 當前註冊詞語數量:{{count}} + speaker_record_timeout: + label: 喇叭音訊 - 判定結束時間 + desc: 偵測到靜音並在指定秒數後認為喇叭輸入已結束。(秒) + error_message: 不能大於「{{speaker_phrase_timeout_label}}」,應為 0 或更高。 + speaker_phrase_timeout: + label: 喇叭音訊 - 紀錄間隔時間 + desc: 以指定秒數間隔進行轉錄處理。 + error_message: 不能小於「{{speaker_record_timeout_label}}」,應為 0 或更高。 + speaker_max_phrase: + label: 喇叭音訊 - 最大單詞數量 + desc: 只有在單詞超過此數量時,才會記錄結果並發送到 VRChat。 + error_message: 可以設置 0 或更高的數值。 + use_whisper_feature: + label: 使用 Whisper 模型進行轉錄 + desc: 在某些語言中,語音識別的準確性可能會提高。使用語音識別時,CPU使用率會增加,請根據你的PC規格考慮是否使用此功能。 + whisper_weight_type: + label: 選擇 Whisper 模型 + # desc: |- + # 一般來說,容量較大的模型往往具有更高的準確性,但這也導致轉錄時間較長和CPU使用率增加。請參考文檔了解各模型的說明。 + # ※特別是超過中等大小的模型,根據CPU性能可能難以運行。 + model_template: '{{model_name}}模型({{capacity}})' + recommended_model_template: '{{model_name}}模型({{capacity}})(推薦)' -config_window: - config_title: "設定" - compact_mode: "精簡模式" - version: "版本 %{version}" - restart_message: "重新啟動以應用變更。" - common_error_message: - invalid_value: "無效值。" + vr: + restore_default_settings: 恢復預設設定 + opacity: 透明度 + ui_scaling: 介面縮放 + x_position: X軸(左右) + y_position: Y軸(上下) + z_position: Z軸(前後) + x_rotation: X軸旋轉 + y_rotation: Y軸旋轉 + z_rotation: Z軸旋轉 + display_duration: 顯示持續時間 + fadeout_duration: 淡出持續時間 - side_menu_labels: - appearance: "外觀" - translation: "翻譯" - transcription: "轉錄" - transcription_mic: "麥克風" - transcription_speaker: "喇叭" - transcription_internal_model: "轉錄模型" - vr: "VR" - others: "其他" - others_send_message_formats: "訊息格式(發送)" - others_received_message_formats: "訊息格式(XSOverlay & 喇叭轉文字)" - others_speaker2chatbox: "喇叭轉文字" - advanced_settings: "進階設定" + others: + auto_clear_the_message_box: + label: 自動清除 Chatbox + send_only_translated_messages: + label: 僅發送翻譯訊息 + notice_xsoverlay: + label: XSOverlay 通知 + desc: 從 XSOverlay 的通知功能接收訊息。 + auto_export_message_logs: + label: 自動匯出訊息記錄 + desc: 自動將對話訊息匯出為文字文件。 + vrc_mic_mute_sync: + label: VRC 麥克風靜音同步 + desc: |- + 當 VRChat 的麥克風靜音時,VRCT 將不會向 VRChat 發送訊息。 + *存在一些延遲且不支援按鍵發話 (PTT)。 + send_message_to_vrc: + label: 發送訊息到 VRChat + desc: 當你打算向 VRChat 發送訊息時啟用此功能。 - transparency: - label: "透明度" - desc: "變更主視窗的透明度。" - - appearance_theme: - label: "主題" - desc: "變更配色主題。" - - ui_size: - label: "介面大小" - - textbox_ui_size: - label: "訊息框字體大小" - desc: "你可以根據介面大小調整記錄中使用的字體大小。" - - message_box_ratio: - label: "訊息框大小" - desc: "你可以依介面比例縮放輸入訊息框。\n*可能不準確。" - - font_family: - label: "字型" - - ui_language: - label: "介面語言" - - to_restore_main_window_geometry: - label: "記住主視窗位置" - desc: "啟動時恢復上次視窗的位置和大小。" - - use_translation_feature: - label: "使用翻譯功能" - desc: "當此功能關閉時,無法使用翻譯功能。但 VRCT 會啟動得更快。適合不需要翻譯功能、只使用VRCT作為聊天框和轉錄工具的使用者。" - - ctranslate2_weight_type: - label: "選擇離線翻譯模型" - desc: "你可以選擇用於離線翻譯引擎的翻譯模型。" - small: "基本模型(%{capacity})" - large: "高準確率模型(%{capacity})" - - deepl_auth_key: - label: "DeepL 授權金鑰" - desc: "使用 DeepL API 時請在主螢幕選擇 %{translator}。※可能不支援某些語言。" - open_auth_key_webpage: "打開 DeepL 帳號頁面" - auth_key_success: "授權金鑰更新完成。" - auth_key_error: "授權金鑰錯誤或已達使用上限。" - - mic_host: - label: "麥克風 Host/Driver" - - mic_device: - label: "麥克風裝置" - - mic_dynamic_energy_threshold: - label_for_automatic: "麥克風能量閾值(當前設置:自動)" - desc_for_automatic: "自動判定麥克風輸入靈敏度。" - label_for_manual: "麥克風能量閾值(當前設置:手動)" - desc_for_manual: "使用滑桿調整麥克風輸入靈敏度,你可以按下麥克風圖示來測試。" - error_message: "可以設置 0 到 %{max} 之間的值。" - - mic_record_timeout: - label: "麥克風音訊 - 判定結束時間" - desc: "麥克風未收到音訊後,結束一段話的判定時間(秒)。" - error_message: "不能大於「%{mic_phrase_timeout_label}」,應為 0 或更高。" - - mic_phrase_timeout: - label: "麥克風音訊 - 紀錄間隔時間" - desc: "每隔多久要紀錄一次音訊。" - error_message: "不能小於「%{mic_record_timeout_label}」,應為 0 或更高。" - - mic_max_phrase: - label: "麥克風音訊 - 最大單詞數量" - desc: "只有在單詞超過此數量時,才會記錄結果並發送到 VRChat。" - error_message: "可以設置為 0 或更高的數值。" - - mic_word_filter: - label: "麥克風單詞過濾器" - desc: "如果偵測到清單內的單詞,則不會發送訊息。要一次新增多個詞語,請用「,」(半形逗號)分隔。\n*重複詞語會被忽略。" - add_button_label: "新增" - count_desc: "當前註冊詞語數量:%{count}" - - speaker_device: - label: "喇叭裝置" - - speaker_dynamic_energy_threshold: - label_for_automatic: "喇叭能量閾值(當前設置:自動)" - desc_for_automatic: "自動確定喇叭輸入靈敏度。" - label_for_manual: "喇叭能量閾值(當前設置:手動)" - desc_for_manual: "使用滑桿調整喇叭輸入靈敏度,你可以按下喇叭圖示來測試。" - error_message: "可以設置 0 到 %{max} 之間的值。" - no_device_error_message: "未偵測到喇叭裝置。" - - speaker_record_timeout: - label: "喇叭音訊 - 判定結束時間" - desc: "偵測到靜音並在指定秒數後認為喇叭輸入已結束。(秒)" - error_message: "不能大於「%{speaker_phrase_timeout_label}」,應為 0 或更高。" - - speaker_phrase_timeout: - label: "喇叭音訊 - 紀錄間隔時間" - desc: "以指定秒數間隔進行轉錄處理。" - error_message: "不能小於「%{speaker_record_timeout_label}」,應為 0 或更高。" - - speaker_max_phrase: - label: "喇叭音訊 - 最大單詞數量" - desc: "只有在單詞超過此數量時,才會記錄結果並發送到 VRChat。" - error_message: "可以設置 0 或更高的數值。" - - use_whisper_feature: - label: "使用 Whisper 模型進行轉錄" - desc: "在某些語言中,語音識別的準確性可能會提高。使用語音識別時,CPU使用率會增加,請根據你的PC規格考慮是否使用此功能。" - - whisper_weight_type: - label: "選擇 Whisper 模型" - desc: "一般來說,容量較大的模型往往具有更高的準確性,但這也導致轉錄時間較長和CPU使用率增加。請參考文檔了解各模型的說明。\n※特別是超過中等大小的模型,根據CPU性能可能難以運行。" - model_template: "%{model_name}模型(%{capacity})" - recommended_model_template: "%{model_name}模型(%{capacity})(推薦)" - - enable_overlay_small_log: - label: "啟用 Overlay" - open_overlay_settings: "打開 Overlay 自定義設定" - - auto_clear_the_message_box: - label: "自動清除 Chatbox" - - send_only_translated_messages: - label: "僅發送翻譯訊息" - - send_message_button_type: - label: "發送訊息按鈕" - hide: "隱藏(使用 Enter 鍵發送)" - show: "顯示" - show_and_disable_enter_key: "顯示並停用 Enter 鍵發送" - - notice_xsoverlay: - label: "XSOverlay 通知" - desc: "從 XSOverlay 的通知功能接收訊息。" - - auto_export_message_logs: - label: "自動匯出訊息記錄" - desc: "自動將對話訊息匯出為文字文件。" - - vrc_mic_mute_sync: - label: "VRC 麥克風靜音同步" - desc: "當 VRChat 的麥克風靜音時,VRCT 將不會向 VRChat 發送訊息。\n*存在一些延遲且不支援按鍵發話 (PTT)。" - - send_message_to_vrc: - label: "發送訊息到 VRChat" - desc: "當你打算向 VRChat 發送訊息時啟用此功能。" - - send_message_format: - label: "發送的訊息格式" - desc: "你可以設定要發送的訊息格式。\n[message] 將被替換為訊息。" - example_text: "這是一個範例。字體、換行等可能與實際顯示不同。" - error_message: "不能使用 [message] 。" - - send_message_format_with_t: - label: "發送的訊息格式(帶翻譯)" - desc: "你可以設定要發送的訊息格式。\n[message] 將被替換為訊息,並且 [translation] 將被替換為翻譯訊息。" - example_text: "這是一個範例。字體、換行等可能與實際顯示不同。" - error_message: "不能使用 [message] 和 [translation] 。" - - received_message_format: - label: "接收的訊息格式" - desc: "用於 XSOverlay 的通知接收功能。\n[message] 將被替換為訊息。\n※它也將用於喇叭轉文字功能。" - example_text: "這是一個範例。字體、換行等可能與實際顯示不同。" - error_message: "不能使用 [message] 。" - - received_message_format_with_t: - label: "接收的訊息格式(附翻譯)" - desc: "用於 XSOverlay 的通知接收功能。\n[message] 將被替換為訊息,並且 [translation] 將被替換為翻譯訊息。\n※它也將用於喇叭轉文字功能。" - example_text: "這是一個範例。字體、換行等可能與實際顯示不同。" - error_message: "不能使用 [message] 和 [translation] 。" - - osc_ip_address: - label: "OSC IP 位址" - - osc_port: - label: "OSC 端口" - - open_config_filepath: - label: "打開設定文件" \ No newline at end of file + advanced_settings: + osc_ip_address: + label: OSC IP 位址 + osc_port: + label: OSC 端口 + open_config_filepath: + label: 打開設定文件 \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100644 index 257a3a63..00000000 --- a/main.py +++ /dev/null @@ -1,33 +0,0 @@ -if __name__ == "__main__": - try: - import ctypes - ctypes.windll.shcore.SetProcessDpiAwareness(0) - - from vrct_gui.splash_window import SplashWindow - splash = SplashWindow() - splash.showSplash() - - from config import config - # version 2.2.0からweightフォルダをweightsに変更する - from utils import renameWeightFolder - renameWeightFolder(config.PATH_LOCAL) - - from models.translation.translation_utils import downloadCTranslate2Weight - if config.USE_TRANSLATION_FEATURE is True: - downloadCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE, splash.updateDownloadProgress) - - from models.transcription.transcription_whisper import downloadWhisperWeight - if config.USE_WHISPER_FEATURE is True: - downloadWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE, splash.updateDownloadProgress) - - splash.toProgress(0) - - import controller - controller.createMainWindow(splash) - splash.destroySplash() - controller.showMainWindow() - - except Exception: - import traceback - with open('error.log', 'a') as f: - traceback.print_exc(file=f) \ No newline at end of file diff --git a/model.py b/model.py deleted file mode 100644 index 58b587a4..00000000 --- a/model.py +++ /dev/null @@ -1,706 +0,0 @@ -import gc -from subprocess import Popen -from os import makedirs as os_makedirs -from os import path as os_path -from shutil import copyfile -from datetime import datetime -from logging import getLogger, FileHandler, Formatter, INFO -from time import sleep -from queue import Queue -from threading import Thread -from packaging.version import parse - -from requests import get as requests_get -from flashtext import KeywordProcessor -from models.translation.translation_translator import Translator -from models.transcription.transcription_utils import getInputDevices, getOutputDevices -from models.osc.osc_tools import sendTyping, sendMessage, receiveOscParameters, getOSCParameterValue -from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder -from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakerEnergyRecorder -from models.transcription.transcription_transcriber import AudioTranscriber -from models.translation.translation_languages import translation_lang -from models.transcription.transcription_languages import transcription_lang -from models.translation.translation_utils import checkCTranslate2Weight -from models.transcription.transcription_whisper import checkWhisperWeight -from models.overlay.overlay import Overlay -from models.overlay.overlay_image import OverlayImage - -from config import config - -class threadFnc(Thread): - def __init__(self, fnc, end_fnc=None, daemon=True, *args, **kwargs): - super(threadFnc, self).__init__(daemon=daemon, target=fnc, *args, **kwargs) - self.fnc = fnc - self.end_fnc = end_fnc - self.loop = True - self._pause = False - - def stop(self): - self.loop = False - - def pause(self): - self._pause = True - - def resume(self): - self._pause = False - - def run(self): - while self.loop: - self.fnc(*self._args, **self._kwargs) - while self._pause: - sleep(0.1) - - if callable(self.end_fnc): - self.end_fnc() - return - -class Model: - _instance = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super(Model, cls).__new__(cls) - cls._instance.init() - return cls._instance - - def init(self): - self.logger = None - self.mic_print_transcript = None - self.mic_audio_recorder = None - self.mic_energy_recorder = None - self.mic_energy_plot_progressbar = None - self.speaker_print_transcript = None - self.speaker_audio_recorder = None - self.speaker_energy_recorder = None - self.speaker_energy_plot_progressbar = None - self.previous_send_message = "" - self.previous_receive_message = "" - self.translator = Translator() - self.keyword_processor = KeywordProcessor() - self.overlay = Overlay( - config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], - config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"], - config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"], - config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"], - config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"], - config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"], - config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"], - config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"], - config.OVERLAY_SETTINGS["opacity"], - config.OVERLAY_SETTINGS["ui_scaling"], - ) - self.overlay_image = OverlayImage() - self.pre_overlay_message = None - self.th_overlay = None - self.mic_audio_queue = None - self.mic_mute_status = None - self.mic_mute_status_check = None - - def checkCTranslatorCTranslate2ModelWeight(self): - return checkCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE) - - def changeTranslatorCTranslate2Model(self): - self.translator.changeCTranslate2Model(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE) - - def isLoadedCTranslate2Model(self): - return self.translator.isLoadedCTranslate2Model() - - def checkTranscriptionWhisperModelWeight(self): - return checkWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE) - - def resetKeywordProcessor(self): - del self.keyword_processor - self.keyword_processor = KeywordProcessor() - - def authenticationTranslatorDeepLAuthKey(self, auth_key): - result = self.translator.authenticationDeepLAuthKey(auth_key) - return result - - def startLogger(self): - os_makedirs(config.PATH_LOGS, exist_ok=True) - logger = getLogger() - logger.setLevel(INFO) - file_name = os_path.join(config.PATH_LOGS, f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log") - file_handler = FileHandler(file_name, encoding="utf-8", delay=True) - formatter = Formatter("[%(asctime)s] %(message)s") - file_handler.setFormatter(formatter) - logger.addHandler(file_handler) - self.logger = logger - self.logger.disabled = False - - def stopLogger(self): - self.logger.disabled = True - self.logger = None - - def getListLanguageAndCountry(self): - transcription_langs = list(transcription_lang.keys()) - translation_langs = [] - for tl_key in translation_lang.keys(): - for lang in translation_lang[tl_key]["source"]: - translation_langs.append(lang) - translation_langs = list(set(translation_langs)) - supported_langs = list(filter(lambda x: x in transcription_langs, translation_langs)) - - languages = [] - for language in supported_langs: - for country in transcription_lang[language]: - languages.append( - { - "language" : language, - "country" : country, - } - ) - languages = sorted(languages, key=lambda x: x['language']) - return languages - - def findTranslationEngines(self, source_lang, target_lang): - compatible_engines = [] - for engine in list(translation_lang.keys()): - languages = translation_lang.get(engine, {}).get("source", {}) - if source_lang in languages and target_lang in languages: - compatible_engines.append(engine) - if "DeepL_API" in compatible_engines: - if config.AUTH_KEYS["DeepL_API"] is None: - compatible_engines.remove('DeepL_API') - return compatible_engines - - def getTranslate(self, translator_name, source_language, target_language, target_country, message): - success_flag = False - translation = self.translator.translate( - translator_name=translator_name, - source_language=source_language, - target_language=target_language, - target_country=target_country, - message=message - ) - - # 翻訳失敗時のフェールセーフ処理 - if isinstance(translation, str): - success_flag = True - else: - while True: - translation = self.translator.translate( - translator_name="CTranslate2", - source_language=source_language, - target_language=target_language, - target_country=target_country, - message=message - ) - if translation is not False: - break - sleep(0.1) - return translation, success_flag - - def getInputTranslate(self, message): - translator_name=config.CHOICE_INPUT_TRANSLATOR - source_language=config.SOURCE_LANGUAGE - target_language=config.TARGET_LANGUAGE - target_country = config.TARGET_COUNTRY - - translation, success_flag = self.getTranslate( - translator_name, - source_language, - target_language, - target_country, - message - ) - return translation, success_flag - - def getOutputTranslate(self, message): - translator_name=config.CHOICE_OUTPUT_TRANSLATOR - source_language=config.TARGET_LANGUAGE - target_language=config.SOURCE_LANGUAGE - target_country=config.SOURCE_COUNTRY - - translation, success_flag = self.getTranslate( - translator_name, - source_language, - target_language, - target_country, - message - ) - return translation, success_flag - - def addKeywords(self): - for f in config.INPUT_MIC_WORD_FILTER: - self.keyword_processor.add_keyword(f) - - def checkKeywords(self, message): - return len(self.keyword_processor.extract_keywords(message)) != 0 - - def detectRepeatSendMessage(self, message): - repeat_flag = False - if self.previous_send_message == message: - repeat_flag = True - self.previous_send_message = message - return repeat_flag - - def detectRepeatReceiveMessage(self, message): - repeat_flag = False - if self.previous_receive_message == message: - repeat_flag = True - self.previous_receive_message = message - return repeat_flag - - @staticmethod - def oscStartSendTyping(): - sendTyping(True, config.OSC_IP_ADDRESS, config.OSC_PORT) - - @staticmethod - def oscStopSendTyping(): - sendTyping(False, config.OSC_IP_ADDRESS, config.OSC_PORT) - - @staticmethod - def oscSendMessage(message): - sendMessage(message, config.OSC_IP_ADDRESS, config.OSC_PORT) - - @staticmethod - def getMuteSelfStatus(): - return getOSCParameterValue(address="/avatar/parameters/MuteSelf") - - def startCheckMuteSelfStatus(self): - def checkMuteSelfStatus(): - if self.mic_mute_status is not None: - self.changeMicTranscriptStatus() - self.stopCheckMuteSelfStatus() - - status = self.getMuteSelfStatus() - if status is not None: - self.mic_mute_status = status - self.changeMicTranscriptStatus() - self.stopCheckMuteSelfStatus() - - if not isinstance(self.mic_mute_status_check, threadFnc): - self.mic_mute_status_check = threadFnc(checkMuteSelfStatus) - self.mic_mute_status_check.daemon = True - self.mic_mute_status_check.start() - - def stopCheckMuteSelfStatus(self): - if isinstance(self.mic_mute_status_check, threadFnc): - self.mic_mute_status_check.stop() - self.mic_mute_status_check = None - - def startReceiveOSC(self): - osc_parameter_prefix = "/avatar/parameters/" - param_MuteSelf = "MuteSelf" - - def change_handler_mute(address, osc_arguments): - if osc_arguments is True and self.mic_mute_status is False: - self.mic_mute_status = osc_arguments - self.changeMicTranscriptStatus() - elif osc_arguments is False and self.mic_mute_status is True: - self.mic_mute_status = osc_arguments - self.changeMicTranscriptStatus() - - dict_filter_and_target = { - osc_parameter_prefix + param_MuteSelf: change_handler_mute, - } - - th_osc_server = Thread(target=receiveOscParameters, args=(dict_filter_and_target,)) - th_osc_server.daemon = True - th_osc_server.start() - - @staticmethod - def checkSoftwareUpdated(): - # check update - update_flag = False - response = requests_get(config.GITHUB_URL) - json_data = response.json() - - version = json_data.get("name", None) - if isinstance(version, str): - new_version = parse(version) - current_version = parse(config.VERSION) - if new_version > current_version: - update_flag = True - print("software version", "now:", config.VERSION, "new:", version) - - return update_flag - - @staticmethod - def updateSoftware(restart:bool=True, func=None): - # try to update at most 5 times - for _ in range(5): - try: - program_name = "update.exe" - current_directory = config.PATH_LOCAL - res = requests_get(config.UPDATER_URL) - assets = res.json()['assets'] - url = [i["browser_download_url"] for i in assets if i["name"] == program_name][0] - res = requests_get(url, stream=True) - with open(os_path.join(current_directory, program_name), 'wb') as file: - for chunk in res.iter_content(chunk_size=1024*5): - file.write(chunk) - break - except Exception: - import traceback - with open('error.log', 'a') as f: - traceback.print_exc(file=f) - # run updater - Popen(program_name, cwd=current_directory) - while True: - sleep(1) - - @staticmethod - def reStartSoftware(): - program_name = 'VRCT.exe' - folder_name = '_internal' - batch_name = 'restart.bat' - current_directory = config.PATH_LOCAL - copyfile(os_path.join(current_directory, folder_name, "batch", batch_name), os_path.join(current_directory, batch_name)) - command = [os_path.join(current_directory, batch_name), program_name] - Popen(command, cwd=current_directory) - - @staticmethod - def getListInputHost(): - return [host for host in getInputDevices().keys()] - - @staticmethod - def getInputDefaultDevice(): - return getInputDevices().get(config.CHOICE_MIC_HOST, [{"name": "NoDevice"}])[0]["name"] - - @staticmethod - def getListInputDevice(): - return [device["name"] for device in getInputDevices().get(config.CHOICE_MIC_HOST, [{"name": "NoDevice"}])] - - @staticmethod - def getListOutputDevice(): - return [device["name"] for device in getOutputDevices()] - - def startMicTranscript(self, fnc, error_fnc=None): - mic_device_list = getInputDevices().get(config.CHOICE_MIC_HOST, [{"name": "NoDevice"}]) - choice_mic_device = [device for device in mic_device_list if device["name"] == config.CHOICE_MIC_DEVICE] - if len(choice_mic_device) == 0: - try: - error_fnc() - except Exception: - pass - return - - self.mic_audio_queue = Queue() - # self.mic_energy_queue = Queue() - - mic_device = choice_mic_device[0] - record_timeout = config.INPUT_MIC_RECORD_TIMEOUT - phrase_timeout = config.INPUT_MIC_PHRASE_TIMEOUT - if record_timeout > phrase_timeout: - record_timeout = phrase_timeout - - self.mic_audio_recorder = SelectedMicEnergyAndAudioRecorder( - device=mic_device, - energy_threshold=config.INPUT_MIC_ENERGY_THRESHOLD, - dynamic_energy_threshold=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, - record_timeout=record_timeout, - ) - # self.mic_audio_recorder.recordIntoQueue(self.mic_audio_queue, mic_energy_queue) - self.mic_audio_recorder.recordIntoQueue(self.mic_audio_queue, None) - self.mic_transcriber = AudioTranscriber( - speaker=False, - source=self.mic_audio_recorder.source, - phrase_timeout=phrase_timeout, - max_phrases=config.INPUT_MIC_MAX_PHRASES, - transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, - root=config.PATH_LOCAL, - whisper_weight_type=config.WHISPER_WEIGHT_TYPE, - ) - def sendMicTranscript(): - try: - res = self.mic_transcriber.transcribeAudioQueue( - self.mic_audio_queue, - config.SOURCE_LANGUAGE, - config.SOURCE_COUNTRY, - config.INPUT_MIC_AVG_LOGPROB, - config.INPUT_MIC_NO_SPEECH_PROB - ) - if res: - message = self.mic_transcriber.getTranscript() - fnc(message) - except Exception: - pass - - def endMicTranscript(): - while not self.mic_audio_queue.empty(): - self.mic_audio_queue.get() - # while not self.mic_energy_queue.empty(): - # self.mic_energy_queue.get() - del self.mic_transcriber - gc.collect() - - # def sendMicEnergy(): - # if mic_energy_queue.empty() is False: - # energy = mic_energy_queue.get() - # # print("mic energy:", energy) - # try: - # fnc(energy) - # except Exception: - # pass - # sleep(0.01) - - self.mic_print_transcript = threadFnc(sendMicTranscript, end_fnc=endMicTranscript) - self.mic_print_transcript.daemon = True - self.mic_print_transcript.start() - - # self.mic_get_energy = threadFnc(sendMicEnergy) - # self.mic_get_energy.daemon = True - # self.mic_get_energy.start() - - self.changeMicTranscriptStatus() - - def resumeMicTranscript(self): - # キューをクリア - if isinstance(self.mic_audio_queue, Queue): - while not self.mic_audio_queue.empty(): - self.mic_audio_queue.get() - - # 文字起こしを再開 - # if isinstance(self.mic_print_transcript, threadFnc): - # self.mic_print_transcript.resume() - - # 音声のレコードを再開 - if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): - self.mic_audio_recorder.resume() - - def pauseMicTranscript(self): - # 文字起こしを一時停止 - # if isinstance(self.mic_print_transcript, threadFnc): - # self.mic_print_transcript.pause() - - # 音声のレコードを一時停止 - if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): - self.mic_audio_recorder.pause() - - def changeMicTranscriptStatus(self): - if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - if self.mic_mute_status is True: - self.pauseMicTranscript() - elif self.mic_mute_status is False: - self.resumeMicTranscript() - else: - pass - else: - self.resumeMicTranscript() - - def stopMicTranscript(self): - if isinstance(self.mic_print_transcript, threadFnc): - self.mic_print_transcript.stop() - self.mic_print_transcript.join() - self.mic_print_transcript = None - if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): - self.mic_audio_recorder.resume() - self.mic_audio_recorder.stop() - self.mic_audio_recorder = None - # if isinstance(self.mic_get_energy, threadFnc): - # self.mic_get_energy.stop() - # self.mic_get_energy = None - - def startCheckMicEnergy(self, fnc, end_fnc, error_fnc=None): - mic_device_list = getInputDevices().get(config.CHOICE_MIC_HOST, [{"name": "NoDevice"}]) - choice_mic_device = [device for device in mic_device_list if device["name"] == config.CHOICE_MIC_DEVICE] - if len(choice_mic_device) == 0: - try: - error_fnc() - except Exception: - pass - return - - def sendMicEnergy(): - if mic_energy_queue.empty() is False: - energy = mic_energy_queue.get() - try: - fnc(energy) - except Exception: - pass - sleep(0.01) - - mic_energy_queue = Queue() - mic_device = choice_mic_device[0] - self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) - self.mic_energy_recorder.recordIntoQueue(mic_energy_queue) - self.mic_energy_plot_progressbar = threadFnc(sendMicEnergy, end_fnc=end_fnc) - self.mic_energy_plot_progressbar.daemon = True - self.mic_energy_plot_progressbar.start() - - def stopCheckMicEnergy(self): - if isinstance(self.mic_energy_plot_progressbar, threadFnc): - self.mic_energy_plot_progressbar.stop() - self.mic_energy_plot_progressbar = None - if isinstance(self.mic_energy_recorder, SelectedMicEnergyRecorder): - self.mic_energy_recorder.stop() - self.mic_energy_recorder = None - - def startSpeakerTranscript(self, fnc, error_fnc=None): - speaker_device_list = getOutputDevices() - choice_speaker_device = [device for device in speaker_device_list if device["name"] == config.CHOICE_SPEAKER_DEVICE] - if len(choice_speaker_device) == 0: - try: - error_fnc() - except Exception: - pass - return - - speaker_audio_queue = Queue() - # speaker_energy_queue = Queue() - speaker_device = choice_speaker_device[0] - record_timeout = config.INPUT_SPEAKER_RECORD_TIMEOUT - phrase_timeout = config.INPUT_SPEAKER_PHRASE_TIMEOUT - if record_timeout > phrase_timeout: - record_timeout = phrase_timeout - - self.speaker_audio_recorder = SelectedSpeakerEnergyAndAudioRecorder( - device=speaker_device, - energy_threshold=config.INPUT_SPEAKER_ENERGY_THRESHOLD, - dynamic_energy_threshold=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - record_timeout=record_timeout, - ) - # self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, speaker_energy_queue) - self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, None) - self.speaker_transcriber = AudioTranscriber( - speaker=True, - source=self.speaker_audio_recorder.source, - phrase_timeout=phrase_timeout, - max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, - transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, - root=config.PATH_LOCAL, - whisper_weight_type=config.WHISPER_WEIGHT_TYPE, - ) - def sendSpeakerTranscript(): - try: - res = self.speaker_transcriber.transcribeAudioQueue( - speaker_audio_queue, - config.TARGET_LANGUAGE, - config.TARGET_COUNTRY, - config.INPUT_SPEAKER_AVG_LOGPROB, - config.INPUT_SPEAKER_NO_SPEECH_PROB - ) - if res: - message = self.speaker_transcriber.getTranscript() - fnc(message) - except Exception: - pass - - def endSpeakerTranscript(): - speaker_audio_queue.queue.clear() - # speaker_energy_queue.queue.clear() - del self.speaker_transcriber - gc.collect() - - # def sendSpeakerEnergy(): - # if speaker_energy_queue.empty() is False: - # energy = speaker_energy_queue.get() - # # print("speaker energy:", energy) - # try: - # fnc(energy) - # except Exception: - # pass - # sleep(0.01) - - self.speaker_print_transcript = threadFnc(sendSpeakerTranscript, end_fnc=endSpeakerTranscript) - self.speaker_print_transcript.daemon = True - self.speaker_print_transcript.start() - - # self.speaker_get_energy = threadFnc(sendSpeakerEnergy) - # self.speaker_get_energy.daemon = True - # self.speaker_get_energy.start() - - def stopSpeakerTranscript(self): - if isinstance(self.speaker_print_transcript, threadFnc): - self.speaker_print_transcript.stop() - self.speaker_print_transcript.join() - self.speaker_print_transcript = None - if isinstance(self.speaker_audio_recorder, SelectedSpeakerEnergyAndAudioRecorder): - self.speaker_audio_recorder.stop() - self.speaker_audio_recorder = None - # if isinstance(self.speaker_get_energy, threadFnc): - # self.speaker_get_energy.stop() - # self.speaker_get_energy = None - - def startCheckSpeakerEnergy(self, fnc, end_fnc, error_fnc=None): - speaker_device_list = getOutputDevices() - choice_speaker_device = [device for device in speaker_device_list if device["name"] == config.CHOICE_SPEAKER_DEVICE] - if len(choice_speaker_device) == 0: - try: - error_fnc() - except Exception: - pass - return - - def sendSpeakerEnergy(): - if speaker_energy_queue.empty() is False: - energy = speaker_energy_queue.get() - try: - fnc(energy) - except Exception: - pass - sleep(0.01) - - speaker_energy_queue = Queue() - speaker_device = choice_speaker_device[0] - self.speaker_energy_recorder = SelectedSpeakerEnergyRecorder(speaker_device) - self.speaker_energy_recorder.recordIntoQueue(speaker_energy_queue) - self.speaker_energy_plot_progressbar = threadFnc(sendSpeakerEnergy, end_fnc=end_fnc) - self.speaker_energy_plot_progressbar.daemon = True - self.speaker_energy_plot_progressbar.start() - - def stopCheckSpeakerEnergy(self): - if isinstance(self.speaker_energy_plot_progressbar, threadFnc): - self.speaker_energy_plot_progressbar.stop() - self.speaker_energy_plot_progressbar = None - if isinstance(self.speaker_energy_recorder, SelectedSpeakerEnergyRecorder): - self.speaker_energy_recorder.stop() - self.speaker_energy_recorder = None - - def createOverlayImageShort(self, message, translation): - your_language = config.TARGET_LANGUAGE - target_language = config.SOURCE_LANGUAGE - ui_type = config.OVERLAY_UI_TYPE - self.pre_overlay_message = { - "message" : message, - "your_language" : your_language, - "translation" : translation, - "target_language" : target_language, - "ui_type" : ui_type, - } - return self.overlay_image.createOverlayImageShort(message, your_language, translation, target_language, ui_type) - - # def createOverlayImageLong(self, message_type, message, translation): - # your_language = config.TARGET_LANGUAGE if message_type == "receive" else config.SOURCE_LANGUAGE - # target_language = config.SOURCE_LANGUAGE if message_type == "receive" else config.TARGET_LANGUAGE - # return self.overlay_image.create_overlay_image_long(message_type, message, your_language, translation, target_language) - - def clearOverlayImage(self): - self.overlay.clearImage() - - def updateOverlay(self, img): - self.overlay.updateImage(img) - - def startOverlay(self): - self.overlay.startOverlay() - - def updateOverlayPosition(self): - self.overlay.updatePosition( - config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], - config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"], - config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"], - config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"], - config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"], - config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"], - ) - - def updateOverlayTimes(self): - display_duration = config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"] - self.overlay.updateDisplayDuration(display_duration) - fadeout_duration = config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"] - self.overlay.updateFadeoutDuration(fadeout_duration) - - def updateOverlayImageOpacity(self): - opacity = config.OVERLAY_SETTINGS["opacity"] - self.overlay.updateOpacity(opacity, with_fade=True) - - def updateOverlayImageUiScaling(self): - ui_scaling = config.OVERLAY_SETTINGS["ui_scaling"] - self.overlay.updateUiScaling(ui_scaling) - - def shutdownOverlay(self): - self.overlay.shutdownOverlay() - -model = Model() \ No newline at end of file diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py deleted file mode 100644 index 67ceee5c..00000000 --- a/models/osc/osc_tools.py +++ /dev/null @@ -1,103 +0,0 @@ -from time import sleep -from pythonosc import osc_message_builder -from pythonosc import udp_client -from pythonosc import dispatcher -from pythonosc import osc_server -from tinyoscquery.queryservice import OSCQueryService -from tinyoscquery.query import OSCQueryBrowser, OSCQueryClient -from tinyoscquery.utility import get_open_udp_port, get_open_tcp_port - -# send OSC message typing -def sendTyping(flag=False, ip_address="127.0.0.1", port=9000): - typing = osc_message_builder.OscMessageBuilder(address="/chatbox/typing") - typing.add_arg(flag) - b_typing = typing.build() - client = udp_client.SimpleUDPClient(ip_address, port) - client.send(b_typing) - -# send OSC message -def sendMessage(message=None, ip_address="127.0.0.1", port=9000): - if message is not None: - msg = osc_message_builder.OscMessageBuilder(address="/chatbox/input") - msg.add_arg(f"{message}") - msg.add_arg(True) - msg.add_arg(True) - b_msg = msg.build() - client = udp_client.SimpleUDPClient(ip_address, port) - client.send(b_msg) - -def sendTestAction(ip_address="127.0.0.1", port=9000): - client = udp_client.SimpleUDPClient(ip_address, port) - client.send_message("/input/Vertical", 1) - sleep(0.01) - client.send_message("/input/Vertical", False) - -# send Input Voice -def sendInputVoice(flag=False, ip_address="127.0.0.1", port=9000): - input_voice = osc_message_builder.OscMessageBuilder(address="/input/Voice") - input_voice.add_arg(flag) - b_input_voice = input_voice.build() - client = udp_client.SimpleUDPClient(ip_address, port) - client.send(b_input_voice) - -def sendChangeVoice(ip_address="127.0.0.1", port=9000): - sendInputVoice(flag=0, ip_address=ip_address, port=port) - sleep(0.05) - sendInputVoice(flag=1, ip_address=ip_address, port=port) - sleep(0.05) - sendInputVoice(flag=0, ip_address=ip_address, port=port) - sleep(0.05) - -def getOSCParameterValue(address, server_name="VRChat-Client"): - value = None - try: - browser = OSCQueryBrowser() - sleep(1) - service = browser.find_service_by_name(server_name) - if service is not None: - oscq = OSCQueryClient(service) - mute_self_node = oscq.query_node(address) - value = mute_self_node.value[0] - browser.zc.close() - browser.browser.cancel() - - except Exception: - pass - return value - -def receiveOscParameters(dict_filter_and_target, ip_address="127.0.0.1", title="VRCT"): - osc_port = get_open_udp_port() - http_port = get_open_tcp_port() - osc_dispatcher = dispatcher.Dispatcher() - for filter, target in dict_filter_and_target.items(): - osc_dispatcher.map(filter, target) - osc_udp_server = osc_server.ThreadingOSCUDPServer((ip_address, osc_port), osc_dispatcher) - - osc_client = OSCQueryService(title, http_port, osc_port) - for filter, target in dict_filter_and_target.items(): - osc_client.advertise_endpoint(filter) - - osc_udp_server.serve_forever() - -if __name__ == "__main__": - osc_parameter_prefix = "/avatar/parameters/" - osc_avatar_change_path = "/avatar/change" - param_MuteSelf = "MuteSelf" - param_Voice = "Voice" - - def print_handler_all(address, *args): - print(f"all {address}: {args}") - - def print_handler_muteself(address, *args): - print(f"muteself {address}: {args}") - - def print_handler_voice(address, *args): - print(f"voice {address}: {args}") - - dict_filter_and_target = { - # osc_parameter_prefix + "*": print_handler_all, - osc_parameter_prefix + param_MuteSelf: print_handler_muteself, - osc_parameter_prefix + param_Voice: print_handler_voice, - } - - receiveOscParameters(dict_filter_and_target) \ No newline at end of file diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py deleted file mode 100644 index 7ebb05bc..00000000 --- a/models/overlay/overlay.py +++ /dev/null @@ -1,317 +0,0 @@ -import os -import ctypes -import time -from psutil import process_iter -from threading import Thread -import openvr -import numpy as np -from PIL import Image -try: - from . import overlay_utils as utils -except ImportError: - import overlay_utils as utils - -def mat34Id(array): - arr = openvr.HmdMatrix34_t() - for i in range(3): - for j in range(4): - arr[i][j] = array[i][j] - return arr - -def getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation): - arr = np.zeros((3, 4)) - rot = utils.euler_to_rotation_matrix((x_rotation, y_rotation, z_rotation)) - - for i in range(3): - for j in range(3): - arr[i][j] = rot[i][j] - - arr[0][3] = x_pos * z_pos - arr[1][3] = y_pos * z_pos - arr[2][3] = - z_pos - return arr - -def getHMDBaseMatrix(): - x_pos = 0.0 - y_pos = -0.4 - z_pos = 1.0 - x_rotation = 0.0 - y_rotation = 0.0 - z_rotation = 0.0 - arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation) - return arr - -def getLeftHandBaseMatrix(): - x_pos = 0.0 - y_pos = -0.06 - z_pos = -0.14 - x_rotation = -62.0 - y_rotation = 154.0 - z_rotation = 71.0 - arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation) - return arr - -def getRightHandBaseMatrix(): - x_pos = 0.0 - y_pos = -0.06 - z_pos = -0.14 - x_rotation = -62.0 - y_rotation = -154.0 - z_rotation = -71.0 - arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation) - return arr - -class Overlay: - def __init__(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, display_duration, fadeout_duration, opacity, ui_scaling): - self.initialized = False - settings = { - "color": [1, 1, 1], - "opacity": opacity, - "x_pos": x_pos, - "y_pos": y_pos, - "z_pos": z_pos, - "x_rotation": x_rotation, - "y_rotation": y_rotation, - "z_rotation": z_rotation, - "display_duration": display_duration, - "fadeout_duration": fadeout_duration, - "ui_scaling": ui_scaling, - } - self.settings = settings - self.system = None - self.overlay = None - self.handle = None - self.lastUpdate = time.monotonic() - self.thread_overlay = None - self.fadeRatio = 1 - self.loop = True - - def init(self): - try: - self.system = openvr.init(openvr.VRApplication_Background) - self.overlay = openvr.IVROverlay() - self.overlay_system = openvr.IVRSystem() - self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "Overlay_Speaker2log_UI") - self.overlay.showOverlay(self.handle) - self.initialized = True - - self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) - self.updateColor(self.settings["color"]) - self.updateOpacity(self.settings["opacity"]) - self.updateUiScaling(self.settings["ui_scaling"]) - self.updatePosition( - self.settings["x_pos"], - self.settings["y_pos"], - self.settings["z_pos"], - self.settings["x_rotation"], - self.settings["y_rotation"], - self.settings["z_rotation"], - ) - - except Exception as e: - print("Could not initialise OpenVR", e) - - def updateImage(self, img): - if self.initialized is True: - width, height = img.size - img = img.tobytes() - img = (ctypes.c_char * len(img)).from_buffer_copy(img) - - try: - self.overlay.setOverlayRaw(self.handle, img, width, height, 4) - except Exception as e: - print("Could not update image", e) - self.initialized = False - self.reStartOverlay() - while self.initialized is False: - time.sleep(0.1) - self.overlay.setOverlayRaw(self.handle, img, width, height, 4) - self.updateOpacity(self.settings["opacity"]) - self.lastUpdate = time.monotonic() - - def clearImage(self): - if self.initialized is True: - self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) - - def updateColor(self, col): - """ - col is a 3-tuple representing (r, g, b) - """ - self.settings["color"] = col - if self.initialized is True: - r, g, b = self.settings["color"] - self.overlay.setOverlayColor(self.handle, r, g, b) - - def updateOpacity(self, opacity, with_fade=False): - self.settings["opacity"] = opacity - - if self.initialized is True: - if with_fade is True: - if self.fadeRatio > 0: - self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings["opacity"]) - else: - self.overlay.setOverlayAlpha(self.handle, self.settings["opacity"]) - - def updateUiScaling(self, ui_scaling): - self.settings["ui_scaling"] = ui_scaling - if self.initialized is True: - self.overlay.setOverlayWidthInMeters(self.handle, self.settings["ui_scaling"]) - - def updatePosition(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, tracker="HMD"): - """ - x_pos, y_pos, z_pos are floats representing the position of overlay - x_rotation, y_rotation, z_rotation are floats representing the rotation of overlay - tracker is a string representing the tracker to use ("HMD", "LeftHand", "RightHand") - """ - - self.settings["x_pos"] = x_pos - self.settings["y_pos"] = y_pos - self.settings["z_pos"] = z_pos - self.settings["x_rotation"] = x_rotation - self.settings["y_rotation"] = y_rotation - self.settings["z_rotation"] = z_rotation - - match tracker: - case "HMD": - base_matrix = getHMDBaseMatrix() - trackerIndex = openvr.k_unTrackedDeviceIndex_Hmd - case "LeftHand": - base_matrix = getLeftHandBaseMatrix() - trackerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_LeftHand) - case "RightHand": - base_matrix = getRightHandBaseMatrix() - trackerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_RightHand) - case _: - base_matrix = getHMDBaseMatrix() - trackerIndex = openvr.k_unTrackedDeviceIndex_Hmd - - translation = (self.settings["x_pos"], self.settings["y_pos"], - self.settings["z_pos"]) - rotation = (self.settings["x_rotation"], self.settings["y_rotation"], self.settings["z_rotation"]) - transform = utils.transform_matrix(base_matrix, translation, rotation) - self.transform = mat34Id(transform) - - if self.initialized is True: - self.overlay.setOverlayTransformTrackedDeviceRelative( - self.handle, - trackerIndex, - self.transform - ) - - def updateDisplayDuration(self, display_duration): - self.settings["display_duration"] = display_duration - - def updateFadeoutDuration(self, fadeout_duration): - self.settings["fadeout_duration"] = fadeout_duration - - def checkActive(self): - try: - if self.system is not None and self.initialized is True: - new_event = openvr.VREvent_t() - while self.system.pollNextEvent(new_event): - if new_event.eventType == openvr.VREvent_Quit: - return False - return True - except Exception as e: - print("Could not check SteamVR running") - print(e) - return False - - def evaluateOpacityFade(self, lastUpdate, currentTime): - if (currentTime - lastUpdate) > self.settings["display_duration"]: - timeThroughInterval = currentTime - lastUpdate - self.settings["display_duration"] - self.fadeRatio = 1 - timeThroughInterval / self.settings["fadeout_duration"] - if self.fadeRatio < 0: - self.fadeRatio = 0 - self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings["opacity"]) - - def update(self): - currTime = time.monotonic() - if self.settings["fadeout_duration"] != 0: - self.evaluateOpacityFade(self.lastUpdate, currTime) - else: - self.updateOpacity(self.settings["opacity"]) - - def mainloop(self): - self.loop = True - while self.checkActive() is True and self.loop is True: - startTime = time.monotonic() - self.update() - sleepTime = (1 / 16) - (time.monotonic() - startTime) - if sleepTime > 0: - time.sleep(sleepTime) - - def main(self): - self.init() - if self.initialized is True: - self.mainloop() - - def startOverlay(self): - self.thread_overlay = Thread(target=self.main) - self.thread_overlay.daemon = True - self.thread_overlay.start() - - def shutdownOverlay(self): - if isinstance(self.thread_overlay, Thread): - self.loop = False - self.thread_overlay.join() - self.thread_overlay = None - if isinstance(self.overlay, openvr.IVROverlay) and isinstance(self.handle, int): - self.overlay.destroyOverlay(self.handle) - self.overlay = None - if isinstance(self.system, openvr.IVRSystem): - openvr.shutdown() - self.system = None - self.initialized = False - - def reStartOverlay(self): - self.shutdownOverlay() - self.startOverlay() - - @staticmethod - def checkSteamvrRunning() -> bool: - _proc_name = "vrmonitor.exe" if os.name == "nt" else "vrmonitor" - return _proc_name in (p.name() for p in process_iter()) - -if __name__ == "__main__": - # from overlay_image import OverlayImage - # overlay_image = OverlayImage() - - # overlay = Overlay(0, 0, 1, 1, 0, 1, 1) - # overlay.startOverlay() - # time.sleep(1) - - # # Example usage - # img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") - # overlay.updateImage(img) - # time.sleep(100000) - - # for i in range(100): - # print(i) - # overlay = Overlay(0, 0, 1, 1, 1, 1, 1) - # overlay.startOverlay() - # time.sleep(1) - - # # Example usage - # img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") - # overlay.updateImage(img) - # time.sleep(0.5) - - # img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") - # overlay.updateImage(img) - # time.sleep(0.5) - - # overlay.shutdownOverlay() - - x_pos = 0 - y_pos = 0 - z_pos = 0 - x_rotation = 0 - y_rotation = 0 - z_rotation = 0 - - base_matrix = getLeftHandBaseMatrix() - translation = (x_pos * z_pos, y_pos * z_pos, z_pos) - rotation = (x_rotation, y_rotation, z_rotation) - transform = utils.transform_matrix(base_matrix, translation, rotation) - transform = mat34Id(transform) - print(transform) \ No newline at end of file diff --git a/models/overlay/overlay_image.py b/models/overlay/overlay_image.py deleted file mode 100644 index c3150f5e..00000000 --- a/models/overlay/overlay_image.py +++ /dev/null @@ -1,231 +0,0 @@ -from os import path as os_path -# from datetime import datetime -from typing import Tuple -from PIL import Image, ImageDraw, ImageFont - -class OverlayImage: - # TEXT_COLOR_LARGE = (223, 223, 223) - # TEXT_COLOR_SMALL = (190, 190, 190) - # TEXT_COLOR_SEND = (70, 161, 146) - # TEXT_COLOR_RECEIVE = (220, 20, 60) - # TEXT_COLOR_TIME = (120, 120, 120) - # FONT_SIZE_LARGE = HEIGHT - # FONT_SIZE_SMALL = int(FONT_SIZE_LARGE * 2 / 3) - LANGUAGES = { - "Japanese": "NotoSansJP-Regular", - "Korean": "NotoSansKR-Regular", - "Chinese Simplified": "NotoSansSC-Regular", - "Chinese Traditional": "NotoSansTC-Regular", - } - - def __init__(self): - pass - - @staticmethod - def concatenateImagesVertically(img1: Image, img2: Image) -> Image: - dst = Image.new("RGBA", (img1.width, img1.height + img2.height)) - dst.paste(img1, (0, 0)) - dst.paste(img2, (0, img1.height)) - return dst - - @staticmethod - def addImageMargin(image: Image, top: int, right: int, bottom: int, left: int, color: Tuple[int, int, int, int]) -> Image: - width, height = image.size - new_width = width + right + left - new_height = height + top + bottom - result = Image.new(image.mode, (new_width, new_height), color) - result.paste(image, (left, top)) - return result - - # def create_textimage(self, message_type, size, text, language): - # font_size = self.FONT_SIZE_LARGE if size == "large" else self.FONT_SIZE_SMALL - # text_color = self.TEXT_COLOR_LARGE if size == "large" else self.TEXT_COLOR_SMALL - # anchor = "lm" if message_type == "receive" else "rm" - # text_x = 0 if message_type == "receive" else self.WIDTH - # align = "left" if message_type == "receive" else "right" - - # font_family = self.LANGUAGES.get(language, "NotoSansJP-Regular") - # img = Image.new("RGBA", (0, 0), (0, 0, 0, 0)) - # draw = ImageDraw.Draw(img) - # font = ImageFont.truetype(os_path.join(os_path.dirname(__file__), "fonts", f"{font_family}.ttf"), font_size) - # # font = ImageFont.truetype(os_path.join("./fonts", f"{font_family}.ttf"), font_size) - # text_width = draw.textlength(text, font) - # character_width = text_width // len(text) - # character_line_num = int(self.WIDTH // character_width) - # if len(text) > character_line_num: - # text = "\n".join([text[i:i+character_line_num] for i in range(0, len(text), character_line_num)]) - - # n_num = len(text.split("\n")) - 1 - # text_height = int(font_size*(n_num+2)) - - # img = Image.new("RGBA", (self.WIDTH, text_height), (0, 0, 0, 0)) - # draw = ImageDraw.Draw(img) - - # text_y = text_height // 2 - - # draw.multiline_text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font, align=align) - # return img - - # def create_textimage_message_type(self, message_type): - # anchor = "lm" if message_type == "receive" else "rm" - # text = "Receive" if message_type == "receive" else "Send" - # text_color = self.TEXT_COLOR_RECEIVE if message_type == "receive" else self.TEXT_COLOR_SEND - # text_color_time = self.TEXT_COLOR_TIME - - # now = datetime.now() - # formatted_time = now.strftime("%H:%M") - # font_size = self.FONT_SIZE_SMALL - # img = Image.new("RGBA", (0, 0), (0, 0, 0, 0)) - # draw = ImageDraw.Draw(img) - # font = ImageFont.truetype(os_path.join(os_path.dirname(__file__), "fonts", "NotoSansJP-Regular.ttf"), font_size) - # # font = ImageFont.truetype(os_path.join("./fonts", "NotoSansJP-Regular.ttf"), font_size) - # text_height = font_size*2 - # text_width = draw.textlength(formatted_time, font) - # character_width = text_width // len(formatted_time) - # img = Image.new("RGBA", (self.WIDTH, text_height), (0, 0, 0, 0)) - # draw = ImageDraw.Draw(img) - # text_y = text_height // 2 - # text_time_x = 0 if message_type == "receive" else self.WIDTH - (text_width + character_width) - # text_x = (text_width + character_width) if message_type == "receive" else self.WIDTH - - # draw.text((text_time_x, text_y), formatted_time, text_color_time, anchor=anchor, stroke_width=0, font=font) - # draw.text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font) - # return img - - # def create_textbox(self, message_type, message, your_language, translation, target_language): - # message_type_img = self.create_textimage_message_type(message_type) - # if len(translation) > 0 and target_language is not None: - # img = self.create_textimage(message_type, "small", message, your_language) - # translation_img = self.create_textimage(message_type, "large",translation, target_language) - # img = self.concatenateImagesVertically(img, translation_img) - # else: - # img = self.create_textimage(message_type, "large", message, your_language) - # return self.concatenateImagesVertically(message_type_img, img) - - # def create_overlay_image_long(self, message_type, message, your_language, translation="", target_language=None): - # if len(self.log_data) > 10: - # self.log_data.pop(0) - - # self.log_data.append( - # { - # "message_type":message_type, - # "message":message, - # "your_language":your_language, - # "translation":translation, - # "target_language":target_language, - # } - # ) - - # imgs = [] - # for log in self.log_data: - # message_type = log["message_type"] - # message = log["message"] - # your_language = log["your_language"] - # translation = log["translation"] - # target_language = log["target_language"] - # img = self.create_textbox(message_type, message, your_language, translation, target_language) - # imgs.append(img) - - # img = imgs[0] - # for i in imgs[1:]: - # img = self.concatenateImagesVertically(img, i) - # img = self.addImageMargin(img, 0, 20, 0, 20, (0, 0, 0, 0)) - - # width, height = img.size - # background = Image.new("RGBA", (width, height), (0, 0, 0, 0)) - # draw = ImageDraw.Draw(background) - # draw.rounded_rectangle([(0, 0), (width, height)], radius=15, fill=self.BACKGROUND_COLOR, outline=self.BACKGROUND_OUTLINE_COLOR, width=5) - # img = Image.alpha_composite(background, img) - # return img - - def getUiSize(self): - return { - "width": int(960*4), - "height": int(23*4), - "font_size": int(23*4), - } - - def getUiColors(self, ui_type): - match ui_type: - case "default": - background_color = (41, 42, 45) - background_outline_color = (41, 42, 45) - text_color = (223, 223, 223) - case "sakura": - background_color = (225, 40, 30) - background_outline_color = (255, 255, 255) - text_color = (223, 223, 223) - return { - "background_color": background_color, - "background_outline_color": background_outline_color, - "text_color": text_color - } - - def createDecorationImage(self, ui_type, image_size): - decoration_image = Image.new("RGBA", image_size, (0, 0, 0, 0)) - match ui_type: - case "default": - pass - case "sakura": - margin = 7 - alpha_ratio = 0.4 - overlay_tl = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "overlay_tl_sakura.png")) - overlay_br = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "overlay_br_sakura.png")) - if overlay_tl.size[1] > image_size[1]: - overlay_tl = overlay_tl.resize((image_size[1]-margin, image_size[1]-margin)) - if overlay_br.size[1] > image_size[1]: - overlay_br = overlay_br.resize((image_size[1]-margin, image_size[1]-margin)) - - alpha = overlay_tl.getchannel("A") - alpha = alpha.point(lambda x: x * alpha_ratio) - overlay_tl.putalpha(alpha) - alpha = overlay_br.getchannel("A") - alpha = alpha.point(lambda x: x * alpha_ratio) - overlay_br.putalpha(alpha) - decoration_image.paste(overlay_tl, (margin, margin)) - decoration_image.paste(overlay_br, (image_size[0]-overlay_br.size[0]-margin, image_size[1]-overlay_br.size[1]-margin)) - return decoration_image - - def createTextboxShort(self, text, language, text_color, base_width, base_height, font_size): - font_family = self.LANGUAGES.get(language, "NotoSansJP-Regular") - img = Image.new("RGBA", (base_width, base_height), (0, 0, 0, 0)) - draw = ImageDraw.Draw(img) - font = ImageFont.truetype(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "fonts", f"{font_family}.ttf"), font_size) - text_width = draw.textlength(text, font) - character_width = text_width // len(text) - character_line_num = int((base_width) // character_width) - 12 - if len(text) > character_line_num: - text = "\n".join([text[i:i+character_line_num] for i in range(0, len(text), character_line_num)]) - text_height = font_size * (len(text.split("\n")) + 1) + 20 - img = Image.new("RGBA", (base_width, text_height), (0, 0, 0, 0)) - draw = ImageDraw.Draw(img) - - text_x = base_width // 2 - text_y = text_height // 2 - draw.text((text_x, text_y), text, text_color, anchor="mm", stroke_width=0, font=font, align="center") - return img - - def createOverlayImageShort(self, message, your_language, translation="", target_language=None, ui_type="default"): - ui_size = self.getUiSize() - height = ui_size["height"] - width = ui_size["width"] - font_size = ui_size["font_size"] - - ui_colors = self.getUiColors(ui_type) - text_color = ui_colors["text_color"] - background_color = ui_colors["background_color"] - background_outline_color = ui_colors["background_outline_color"] - - img = self.createTextboxShort(message, your_language, text_color, width, height, font_size) - if len(translation) > 0 and target_language is not None: - translation_img = self.createTextboxShort(translation, target_language, text_color, width, height, font_size) - img = self.concatenateImagesVertically(img, translation_img) - - background = Image.new("RGBA", img.size, (0, 0, 0, 0)) - draw = ImageDraw.Draw(background) - draw.rounded_rectangle([(0, 0), img.size], radius=30, fill=background_color, outline=background_outline_color, width=5) - - decoration_image = self.createDecorationImage(ui_type, img.size) - background = Image.alpha_composite(background, decoration_image) - img = Image.alpha_composite(background, img) - return img \ No newline at end of file diff --git a/models/transcription/transcription_utils.py b/models/transcription/transcription_utils.py deleted file mode 100644 index 4292ba23..00000000 --- a/models/transcription/transcription_utils.py +++ /dev/null @@ -1,70 +0,0 @@ -from pyaudiowpatch import PyAudio, paWASAPI - -def getInputDevices(): - devices = {} - with PyAudio() as p: - for host_index in range(0, p.get_host_api_count()): - host = p.get_host_api_info_by_index(host_index) - for device_index in range(0, p.get_host_api_info_by_index(host_index)['deviceCount']): - device = p.get_device_info_by_host_api_device_index(host_index, device_index) - if device["maxInputChannels"] > 0 and device["isLoopbackDevice"] is False: - if host["name"] in devices.keys(): - devices[host["name"]].append(device) - else: - devices[host["name"]] = [device] - if len(devices) == 0: - devices = {"NoHost": [{"name": "NoDevice"}]} - return devices - -def getDefaultInputDevice(): - with PyAudio() as p: - api_info = p.get_default_host_api_info() - defaultInputDevice = api_info["defaultInputDevice"] - - for host_index in range(0, p.get_host_api_count()): - host = p.get_host_api_info_by_index(host_index) - for device_index in range(0, p.get_host_api_info_by_index(host_index)['deviceCount']): - device = p.get_device_info_by_host_api_device_index(host_index, device_index) - if device["index"] == defaultInputDevice: - return {"host": host, "device": device} - return {"host": {"name": "NoHost"}, "device": {"name": "NoDevice"}} - -def getOutputDevices(): - devices = [] - with PyAudio() as p: - wasapi_info = p.get_host_api_info_by_type(paWASAPI) - for host_index in range(0, p.get_host_api_count()): - host = p.get_host_api_info_by_index(host_index) - if host["name"] == wasapi_info["name"]: - for device_index in range(0, p.get_host_api_info_by_index(host_index)['deviceCount']): - device = p.get_device_info_by_host_api_device_index(host_index, device_index) - if not device["isLoopbackDevice"]: - for loopback in p.get_loopback_device_info_generator(): - if device["name"] in loopback["name"]: - devices.append(loopback) - - if len(devices) == 0: - devices = [{"name": "NoDevice"}] - else: - devices = [dict(t) for t in {tuple(d.items()) for d in devices}] - return devices - -def getDefaultOutputDevice(): - with PyAudio() as p: - wasapi_info = p.get_host_api_info_by_type(paWASAPI) - defaultOutputDevice = wasapi_info["defaultOutputDevice"] - - for host_index in range(0, p.get_host_api_count()): - for device_index in range(0, p. get_host_api_info_by_index(host_index)['deviceCount']): - device = p.get_device_info_by_host_api_device_index(host_index, device_index) - if device["index"] == defaultOutputDevice: - default_speakers = device - if not default_speakers["isLoopbackDevice"]: - for loopback in p.get_loopback_device_info_generator(): - if default_speakers["name"] in loopback["name"]: - return {"device": loopback} - return {"device": {"name": "NoDevice"}} - -if __name__ == "__main__": - print("getOutputDevices()", getOutputDevices()) - print("getDefaultOutputDevice()", getDefaultOutputDevice()) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..ebda8372 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5576 @@ +{ + "name": "tauri-app", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tauri-app", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "11.14.0", + "@emotion/styled": "11.14.0", + "@mui/material": "6.2.0", + "@tauri-apps/api": "1.6.0", + "@vitejs/plugin-react": "4.3.4", + "clsx": "2.1.1", + "eslint": "8.57.0", + "eslint-plugin-react": "7.37.2", + "i18next": "24.1.0", + "jotai": "2.10.3", + "js-base64": "3.7.7", + "js-yaml": "4.1.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-i18next": "15.2.0", + "react-resizable-layout": "0.7.2" + }, + "devDependencies": { + "@tauri-apps/cli": "1.6.3", + "npm-run-all": "4.1.5", + "sass": "1.79.4", + "vite": "6.0.3", + "vite-plugin-svgr": "4.3.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "dependencies": { + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "dependencies": { + "@babel/types": "^7.26.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.2.0.tgz", + "integrity": "sha512-Nn5PSkUqbDrvezpiiiYZiAbX4SFEiy3CbikUL6pWOXEUsq+L1j50OOyyUIHpaX2Hr+5V5UxTh+fPeC4nsGNhdw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.2.0.tgz", + "integrity": "sha512-7FXXUPIyYzP02a7GvqwJ7ocmdP+FkvLvmy/uxG1TDmTlsr8nEClQp75uxiVznJqAY/jJy4d+Rj/fNWNxwidrYQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.2.0", + "@mui/system": "^6.2.0", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.2.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.2.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.2.0.tgz", + "integrity": "sha512-lYd2MrVddhentF1d/cMXKnwlDjr/shbO3A2eGq22PCYUoZaqtAGZMc0U86KnJ/Sh5YzNYePqTOaaowAN8Qea8A==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.2.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.2.0.tgz", + "integrity": "sha512-rV4YCu6kcCjMnHFXU/tQcL6XfYVfFVR8n3ZVNGnk2rpXnt/ctOPJsF+eUQuhkHciueLVKpI06+umr1FxWWhVmQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.2.0.tgz", + "integrity": "sha512-DCeqev9Cd4f4pm3O1lqSGW/DIHHBG6ZpqMX9iIAvN4asYv+pPWv2/lKov9kWk5XThhxFnGSv93SRNE1kNRRg5Q==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.2.0", + "@mui/styled-engine": "^6.2.0", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.2.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.19", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", + "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.2.0.tgz", + "integrity": "sha512-77CaFJi+OIi2SjbPwCis8z5DXvE0dfx9hBz5FguZHt1VYFlWEPCWTHcMsQCahSErnfik5ebLsYK8+D+nsjGVfw==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", + "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@tauri-apps/api": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.6.0.tgz", + "integrity": "sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg==", + "engines": { + "node": ">= 14.6.0", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.6.3.tgz", + "integrity": "sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==", + "dev": true, + "dependencies": { + "semver": ">=7.5.2" + }, + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "1.6.3", + "@tauri-apps/cli-darwin-x64": "1.6.3", + "@tauri-apps/cli-linux-arm-gnueabihf": "1.6.3", + "@tauri-apps/cli-linux-arm64-gnu": "1.6.3", + "@tauri-apps/cli-linux-arm64-musl": "1.6.3", + "@tauri-apps/cli-linux-x64-gnu": "1.6.3", + "@tauri-apps/cli-linux-x64-musl": "1.6.3", + "@tauri-apps/cli-win32-arm64-msvc": "1.6.3", + "@tauri-apps/cli-win32-ia32-msvc": "1.6.3", + "@tauri-apps/cli-win32-x64-msvc": "1.6.3" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.3.tgz", + "integrity": "sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.3.tgz", + "integrity": "sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.3.tgz", + "integrity": "sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.3.tgz", + "integrity": "sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.3.tgz", + "integrity": "sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.3.tgz", + "integrity": "sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.3.tgz", + "integrity": "sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.3.tgz", + "integrity": "sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.3.tgz", + "integrity": "sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.3.tgz", + "integrity": "sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" + }, + "node_modules/@types/react": { + "version": "18.3.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", + "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001688", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz", + "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "devOptional": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", + "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.5.tgz", + "integrity": "sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", + "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.3", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", + "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.1.0", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.1.0.tgz", + "integrity": "sha512-suKlX82AlptkMUO5YRfaAeH4FQyyKvR66jNaubTMiyPPMx7INU6PXAiy3PGULc0q6K+t9nxmDf/TRj9KjAivmw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.23.2" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "devOptional": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", + "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/iterator.prototype": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.4.tgz", + "integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "reflect.getprototypeof": "^1.0.8", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jotai": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.10.3.tgz", + "integrity": "sha512-Nnf4IwrLhNfuz2JOQLI0V/AgwcpxvVy8Ec8PidIIDeRi4KCFpwTFIpHAAcU+yCgnw/oASYElq9UY0YdUUegsSA==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-i18next": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.2.0.tgz", + "integrity": "sha512-iJNc8111EaDtVTVMKigvBtPHyrJV+KblWG73cUxqp+WmJCcwkzhWNFXmkAD5pwP2Z4woeDj/oXDdbjDsb3Gutg==", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==" + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-resizable-layout": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/react-resizable-layout/-/react-resizable-layout-0.7.2.tgz", + "integrity": "sha512-GrVzAecB6+osdAx5PPP3G8R9n7A2uDd3NL+PyrHWNRaVBivZmW/o0+yFjQdS5Bo016A2fIP11fAhefsknWN4aw==", + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "devOptional": true, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", + "integrity": "sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "dunder-proto": "^1.0.0", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.2.0", + "which-builtin-type": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sass": { + "version": "1.79.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.4.tgz", + "integrity": "sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==", + "devOptional": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vite": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz", + "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==", + "dependencies": { + "esbuild": "^0.24.0", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-svgr": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz", + "integrity": "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.3", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": ">=2.6.0" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.0.tgz", + "integrity": "sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==", + "dependencies": { + "call-bind": "^1.0.7", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..bbd3c011 --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "tauri-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "setup-python": "install.bat", + "build-python": "build.bat", + "build-python-cuda": "build_cuda.bat", + "vite": "vite", + "vite-build": "vite build", + "vite-preview": "vite preview", + "tauri": "tauri", + "tauri-dev": "tauri dev", + "dev": "npm run build-python && npm run dev-ui", + "dev-cuda": "npm run build-python-cuda && npm run dev-ui", + "dev-ui": "npm-run-all --parallel vite tauri-dev", + "build": "npm run build-python && npm run vite-build && npm run tauri build", + "build-cuda": "npm run build-python-cuda && npm run vite-build && npm run tauri build", + "build-ui": "npm run vite-build && npm run tauri build", + "release": "python zip.py --zip_name VRCT.zip", + "release-cuda": "python zip.py --zip_name VRCT_cuda.zip", + "release-all": "npm run build && npm run release && npm run build-cuda && npm run release-cuda" + }, + "dependencies": { + "@emotion/react": "11.14.0", + "@emotion/styled": "11.14.0", + "@mui/material": "6.2.0", + "@tauri-apps/api": "1.6.0", + "@vitejs/plugin-react": "4.3.4", + "clsx": "2.1.1", + "eslint": "8.57.0", + "eslint-plugin-react": "7.37.2", + "i18next": "24.1.0", + "jotai": "2.10.3", + "js-base64": "3.7.7", + "js-yaml": "4.1.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-i18next": "15.2.0", + "react-resizable-layout": "0.7.2" + }, + "devDependencies": { + "@tauri-apps/cli": "1.6.3", + "npm-run-all": "4.1.5", + "sass": "1.79.4", + "vite": "6.0.3", + "vite-plugin-svgr": "4.3.0" + } +} diff --git a/requirements.txt b/requirements.txt index e5b4bc5a..950c83d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,20 @@ +torch==2.2.2 +faster-whisper==1.0.3 +ctranslate2==4.3.1 +transformers==4.40.2 pillow == 10.0.0 PyAudioWPatch == 0.2.12.6 -python-osc == 1.8.3 -customtkinter == 5.2.0 +python-osc == 1.9.0 deepl == 1.15.0 -flashtext == 2.7 -pyyaml == 6.0.1 -python-i18n == 0.3.9 -CTkToolTip == 0.8 -pyinstaller==6.2.0 +flashtext ==2.7 +pyinstaller==6.10.0 numpy==1.26.4 -torch==2.2.2 -transformers==4.37.2 -sentencepiece==0.1.99 -ctranslate2==4.1.0 -faster-whisper==1.0.3 +sentencepiece==0.2.0 openvr==1.26.701 pydub==0.25.1 psutil==5.9.8 +pykakasi==2.3.0 +pycaw==20240210 translators @ git+https://github.com/misyaguziya/translators@5.9.2.1 -SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4 -tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.2 \ No newline at end of file +SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1 +tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3 \ No newline at end of file diff --git a/requirements_cuda.txt b/requirements_cuda.txt new file mode 100644 index 00000000..df90696a --- /dev/null +++ b/requirements_cuda.txt @@ -0,0 +1,21 @@ +torch==2.2.2 +--extra-index-url https://download.pytorch.org/whl/cu121 +faster-whisper==1.0.3 +ctranslate2==4.3.1 +transformers==4.40.2 +pillow == 10.0.0 +PyAudioWPatch == 0.2.12.6 +python-osc == 1.9.0 +deepl == 1.15.0 +flashtext ==2.7 +pyinstaller==6.10.0 +numpy==1.26.4 +sentencepiece==0.2.0 +openvr==1.26.701 +pydub==0.25.1 +psutil==5.9.8 +pykakasi==2.3.0 +pycaw==20240210 +translators @ git+https://github.com/misyaguziya/translators@5.9.2.1 +SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1 +tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3 \ No newline at end of file diff --git a/src-python/config.py b/src-python/config.py new file mode 100644 index 00000000..56d28265 --- /dev/null +++ b/src-python/config.py @@ -0,0 +1,1105 @@ +import sys +import copy +import inspect +from os import path as os_path, makedirs as os_makedirs +from json import load as json_load +from json import dump as json_dump +import threading +import torch +from device_manager import device_manager +from models.translation.translation_languages import translation_lang +from models.translation.translation_utils import ctranslate2_weights +from models.transcription.transcription_languages import transcription_lang +from models.transcription.transcription_whisper import _MODELS as whisper_models +from utils import errorLogging + +json_serializable_vars = {} +def json_serializable(var_name): + def decorator(func): + json_serializable_vars[var_name] = func + return func + return decorator + +class Config: + _instance = None + _config_data = {} + _timer = None + _debounce_time = 2 + + def __new__(cls): + if cls._instance is None: + cls._instance = super(Config, cls).__new__(cls) + cls._instance.init_config() + cls._instance.load_config() + return cls._instance + + def saveConfigToFile(self): + with open(self.PATH_CONFIG, "w", encoding="utf-8") as fp: + json_dump(self._config_data, fp, indent=4, ensure_ascii=False) + + def saveConfig(self, key, value, immediate_save=False): + self._config_data[key] = value + + if isinstance(self._timer, threading.Timer) and self._timer.is_alive(): + self._timer.cancel() + + if immediate_save: + self.saveConfigToFile() + else: + self._timer = threading.Timer(self._debounce_time, self.saveConfigToFile) + self._timer.daemon = True + self._timer.start() + + # Read Only + @property + def VERSION(self): + return self._VERSION + + @property + def PATH_LOCAL(self): + return self._PATH_LOCAL + + @property + def PATH_CONFIG(self): + return self._PATH_CONFIG + + @property + def PATH_LOGS(self): + return self._PATH_LOGS + + @property + def GITHUB_URL(self): + return self._GITHUB_URL + + @property + def UPDATER_URL(self): + return self._UPDATER_URL + + @property + def BOOTH_URL(self): + return self._BOOTH_URL + + @property + def DOCUMENTS_URL(self): + return self._DOCUMENTS_URL + + @property + def DEEPL_AUTH_KEY_PAGE_URL(self): + return self._DEEPL_AUTH_KEY_PAGE_URL + + @property + def MAX_MIC_THRESHOLD(self): + return self._MAX_MIC_THRESHOLD + + @property + def MAX_SPEAKER_THRESHOLD(self): + return self._MAX_SPEAKER_THRESHOLD + + @property + def WATCHDOG_TIMEOUT(self): + return self._WATCHDOG_TIMEOUT + + @property + def WATCHDOG_INTERVAL(self): + return self._WATCHDOG_INTERVAL + + @property + def SELECTABLE_TAB_NO_LIST(self): + return self._SELECTABLE_TAB_NO_LIST + + @property + def SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST(self): + return self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST + + @property + def SELECTABLE_WHISPER_WEIGHT_TYPE_LIST(self): + return self._SELECTABLE_WHISPER_WEIGHT_TYPE_LIST + + @property + def SELECTABLE_TRANSLATION_ENGINE_LIST(self): + return self._SELECTABLE_TRANSLATION_ENGINE_LIST + + @property + def SELECTABLE_TRANSCRIPTION_ENGINE_LIST(self): + return self._SELECTABLE_TRANSCRIPTION_ENGINE_LIST + + @property + def SELECTABLE_UI_LANGUAGE_LIST(self): + return self._SELECTABLE_UI_LANGUAGE_LIST + + @property + def COMPUTE_MODE(self): + return self._COMPUTE_MODE + + @property + def SELECTABLE_COMPUTE_DEVICE_LIST(self): + return self._SELECTABLE_COMPUTE_DEVICE_LIST + + @property + def SEND_MESSAGE_BUTTON_TYPE_LIST(self): + return self._SEND_MESSAGE_BUTTON_TYPE_LIST + + @property + def SEND_MESSAGE_FORMAT(self): + return self._SEND_MESSAGE_FORMAT + + @property + def SEND_MESSAGE_FORMAT_WITH_T(self): + return self._SEND_MESSAGE_FORMAT_WITH_T + + @property + def RECEIVED_MESSAGE_FORMAT(self): + return self._RECEIVED_MESSAGE_FORMAT + + @property + def RECEIVED_MESSAGE_FORMAT_WITH_T(self): + return self._RECEIVED_MESSAGE_FORMAT_WITH_T + + # Read Write + @property + def ENABLE_TRANSLATION(self): + return self._ENABLE_TRANSLATION + + @ENABLE_TRANSLATION.setter + def ENABLE_TRANSLATION(self, value): + if isinstance(value, bool): + self._ENABLE_TRANSLATION = value + + @property + def ENABLE_TRANSCRIPTION_SEND(self): + return self._ENABLE_TRANSCRIPTION_SEND + + @ENABLE_TRANSCRIPTION_SEND.setter + def ENABLE_TRANSCRIPTION_SEND(self, value): + if isinstance(value, bool): + self._ENABLE_TRANSCRIPTION_SEND = value + + @property + def ENABLE_TRANSCRIPTION_RECEIVE(self): + return self._ENABLE_TRANSCRIPTION_RECEIVE + + @ENABLE_TRANSCRIPTION_RECEIVE.setter + def ENABLE_TRANSCRIPTION_RECEIVE(self, value): + if isinstance(value, bool): + self._ENABLE_TRANSCRIPTION_RECEIVE = value + + @property + def ENABLE_FOREGROUND(self): + return self._ENABLE_FOREGROUND + + @ENABLE_FOREGROUND.setter + def ENABLE_FOREGROUND(self, value): + if isinstance(value, bool): + self._ENABLE_FOREGROUND = value + + @property + def ENABLE_CHECK_ENERGY_SEND(self): + return self._ENABLE_CHECK_ENERGY_SEND + + @ENABLE_CHECK_ENERGY_SEND.setter + def ENABLE_CHECK_ENERGY_SEND(self, value): + if isinstance(value, bool): + self._ENABLE_CHECK_ENERGY_SEND = value + + @property + def ENABLE_CHECK_ENERGY_RECEIVE(self): + return self._ENABLE_CHECK_ENERGY_RECEIVE + + @ENABLE_CHECK_ENERGY_RECEIVE.setter + def ENABLE_CHECK_ENERGY_RECEIVE(self, value): + if isinstance(value, bool): + self._ENABLE_CHECK_ENERGY_RECEIVE = value + + @property + def SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT(self): + return self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT + + @SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT.setter + def SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT(self, value): + if isinstance(value, dict): + self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = value + + @property + def SELECTABLE_WHISPER_WEIGHT_TYPE_DICT(self): + return self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT + + @SELECTABLE_WHISPER_WEIGHT_TYPE_DICT.setter + def SELECTABLE_WHISPER_WEIGHT_TYPE_DICT(self, value): + if isinstance(value, dict): + self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = value + + @property + def SELECTABLE_TRANSLATION_ENGINE_STATUS(self): + return self._SELECTABLE_TRANSLATION_ENGINE_STATUS + + @SELECTABLE_TRANSLATION_ENGINE_STATUS.setter + def SELECTABLE_TRANSLATION_ENGINE_STATUS(self, value): + if isinstance(value, dict): + self._SELECTABLE_TRANSLATION_ENGINE_STATUS = value + + # Save Json Data + ## Main Window + @property + @json_serializable('SELECTED_TAB_NO') + def SELECTED_TAB_NO(self): + return self._SELECTED_TAB_NO + + @SELECTED_TAB_NO.setter + def SELECTED_TAB_NO(self, value): + if isinstance(value, str): + if value in self.SELECTABLE_TAB_NO_LIST: + self._SELECTED_TAB_NO = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_TRANSLATION_ENGINES') + def SELECTED_TRANSLATION_ENGINES(self): + return self._SELECTED_TRANSLATION_ENGINES + + @SELECTED_TRANSLATION_ENGINES.setter + def SELECTED_TRANSLATION_ENGINES(self, value): + if isinstance(value, dict): + old_value = self.SELECTED_TRANSLATION_ENGINES + for k, v in value.items(): + if v not in self.SELECTABLE_TRANSLATION_ENGINE_LIST: + value[k] = old_value[k] + self._SELECTED_TRANSLATION_ENGINES = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_YOUR_LANGUAGES') + def SELECTED_YOUR_LANGUAGES(self): + return self._SELECTED_YOUR_LANGUAGES + + @SELECTED_YOUR_LANGUAGES.setter + def SELECTED_YOUR_LANGUAGES(self, value): + if isinstance(value, dict): + value_old = self.SELECTED_YOUR_LANGUAGES + for k0, v0 in value.items(): + for k1, v1 in v0.items(): + language = v1["language"] + country = v1["country"] + enable = v1["enable"] + if (language not in list(transcription_lang.keys()) or + country not in list(transcription_lang[language].keys()) or + not isinstance(enable, bool)): + value[k0][k1] = value_old[k0][k1] + self._SELECTED_YOUR_LANGUAGES = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_TARGET_LANGUAGES') + def SELECTED_TARGET_LANGUAGES(self): + return self._SELECTED_TARGET_LANGUAGES + + @SELECTED_TARGET_LANGUAGES.setter + def SELECTED_TARGET_LANGUAGES(self, value): + if isinstance(value, dict): + value_old = self.SELECTED_TARGET_LANGUAGES + for k0, v0 in value.items(): + for k1, v1 in v0.items(): + language = v1["language"] + country = v1["country"] + enable = v1["enable"] + if (language not in list(transcription_lang.keys()) or + country not in list(transcription_lang[language].keys()) or + not isinstance(enable, bool)): + value[k0][k1] = value_old[k0][k1] + self._SELECTED_TARGET_LANGUAGES = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_TRANSCRIPTION_ENGINE') + def SELECTED_TRANSCRIPTION_ENGINE(self): + return self._SELECTED_TRANSCRIPTION_ENGINE + + @SELECTED_TRANSCRIPTION_ENGINE.setter + def SELECTED_TRANSCRIPTION_ENGINE(self, value): + if isinstance(value, str): + if value in self.SELECTABLE_TRANSCRIPTION_ENGINE_LIST: + self._SELECTED_TRANSCRIPTION_ENGINE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('CONVERT_MESSAGE_TO_ROMAJI') + def CONVERT_MESSAGE_TO_ROMAJI(self): + return self._CONVERT_MESSAGE_TO_ROMAJI + + @CONVERT_MESSAGE_TO_ROMAJI.setter + def CONVERT_MESSAGE_TO_ROMAJI(self, value): + if isinstance(value, bool): + self._CONVERT_MESSAGE_TO_ROMAJI = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('CONVERT_MESSAGE_TO_HIRAGANA') + def CONVERT_MESSAGE_TO_HIRAGANA(self): + return self._CONVERT_MESSAGE_TO_HIRAGANA + + @CONVERT_MESSAGE_TO_HIRAGANA.setter + def CONVERT_MESSAGE_TO_HIRAGANA(self, value): + if isinstance(value, bool): + self._CONVERT_MESSAGE_TO_HIRAGANA = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MAIN_WINDOW_SIDEBAR_COMPACT_MODE') + def MAIN_WINDOW_SIDEBAR_COMPACT_MODE(self): + return self._MAIN_WINDOW_SIDEBAR_COMPACT_MODE + + @MAIN_WINDOW_SIDEBAR_COMPACT_MODE.setter + def MAIN_WINDOW_SIDEBAR_COMPACT_MODE(self, value): + if isinstance(value, bool): + self._MAIN_WINDOW_SIDEBAR_COMPACT_MODE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + ## Config Window + @property + @json_serializable('TRANSPARENCY') + def TRANSPARENCY(self): + return self._TRANSPARENCY + + @TRANSPARENCY.setter + def TRANSPARENCY(self, value): + if isinstance(value, int): + self._TRANSPARENCY = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('UI_SCALING') + def UI_SCALING(self): + return self._UI_SCALING + + @UI_SCALING.setter + def UI_SCALING(self, value): + if isinstance(value, int): + self._UI_SCALING = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('TEXTBOX_UI_SCALING') + def TEXTBOX_UI_SCALING(self): + return self._TEXTBOX_UI_SCALING + + @TEXTBOX_UI_SCALING.setter + def TEXTBOX_UI_SCALING(self, value): + if isinstance(value, int): + self._TEXTBOX_UI_SCALING = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MESSAGE_BOX_RATIO') + def MESSAGE_BOX_RATIO(self): + return self._MESSAGE_BOX_RATIO + + @MESSAGE_BOX_RATIO.setter + def MESSAGE_BOX_RATIO(self, value): + if isinstance(value, (int, float)): + self._MESSAGE_BOX_RATIO = value + self.saveConfig(inspect.currentframe().f_code.co_name, value, immediate_save=True) + + @property + @json_serializable('FONT_FAMILY') + def FONT_FAMILY(self): + return self._FONT_FAMILY + + @FONT_FAMILY.setter + def FONT_FAMILY(self, value): + if isinstance(value, str): + self._FONT_FAMILY = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('UI_LANGUAGE') + def UI_LANGUAGE(self): + return self._UI_LANGUAGE + + @UI_LANGUAGE.setter + def UI_LANGUAGE(self, value): + if isinstance(value, str): + if value in self.SELECTABLE_UI_LANGUAGE_LIST: + self._UI_LANGUAGE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MAIN_WINDOW_GEOMETRY') + def MAIN_WINDOW_GEOMETRY(self): + return self._MAIN_WINDOW_GEOMETRY + + @MAIN_WINDOW_GEOMETRY.setter + def MAIN_WINDOW_GEOMETRY(self, value): + if isinstance(value, dict) and set(value.keys()) == set(self.MAIN_WINDOW_GEOMETRY.keys()): + for key, value in value.items(): + if isinstance(value, int): + self._MAIN_WINDOW_GEOMETRY[key] = value + self.saveConfig(inspect.currentframe().f_code.co_name, self.MAIN_WINDOW_GEOMETRY, immediate_save=True) + + @property + @json_serializable('AUTO_MIC_SELECT') + def AUTO_MIC_SELECT(self): + return self._AUTO_MIC_SELECT + + @AUTO_MIC_SELECT.setter + def AUTO_MIC_SELECT(self, value): + if isinstance(value, bool): + self._AUTO_MIC_SELECT = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_MIC_HOST') + def SELECTED_MIC_HOST(self): + return self._SELECTED_MIC_HOST + + @SELECTED_MIC_HOST.setter + def SELECTED_MIC_HOST(self, value): + if value in [host for host in device_manager.getMicDevices().keys()]: + self._SELECTED_MIC_HOST = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_MIC_DEVICE') + def SELECTED_MIC_DEVICE(self): + return self._SELECTED_MIC_DEVICE + + @SELECTED_MIC_DEVICE.setter + def SELECTED_MIC_DEVICE(self, value): + if value in [device["name"] for device in device_manager.getMicDevices()[self.SELECTED_MIC_HOST]]: + self._SELECTED_MIC_DEVICE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MIC_THRESHOLD') + def MIC_THRESHOLD(self): + return self._MIC_THRESHOLD + + @MIC_THRESHOLD.setter + def MIC_THRESHOLD(self, value): + if isinstance(value, int): + self._MIC_THRESHOLD = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MIC_AUTOMATIC_THRESHOLD') + def MIC_AUTOMATIC_THRESHOLD(self): + return self._MIC_AUTOMATIC_THRESHOLD + + @MIC_AUTOMATIC_THRESHOLD.setter + def MIC_AUTOMATIC_THRESHOLD(self, value): + if isinstance(value, bool): + self._MIC_AUTOMATIC_THRESHOLD = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MIC_RECORD_TIMEOUT') + def MIC_RECORD_TIMEOUT(self): + return self._MIC_RECORD_TIMEOUT + + @MIC_RECORD_TIMEOUT.setter + def MIC_RECORD_TIMEOUT(self, value): + if isinstance(value, int): + self._MIC_RECORD_TIMEOUT = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MIC_PHRASE_TIMEOUT') + def MIC_PHRASE_TIMEOUT(self): + return self._MIC_PHRASE_TIMEOUT + + @MIC_PHRASE_TIMEOUT.setter + def MIC_PHRASE_TIMEOUT(self, value): + if isinstance(value, int): + self._MIC_PHRASE_TIMEOUT = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MIC_MAX_PHRASES') + def MIC_MAX_PHRASES(self): + return self._MIC_MAX_PHRASES + + @MIC_MAX_PHRASES.setter + def MIC_MAX_PHRASES(self, value): + if isinstance(value, int): + self._MIC_MAX_PHRASES = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MIC_WORD_FILTER') + def MIC_WORD_FILTER(self): + return self._MIC_WORD_FILTER + + @MIC_WORD_FILTER.setter + def MIC_WORD_FILTER(self, value): + if isinstance(value, list): + self._MIC_WORD_FILTER = sorted(set(value), key=value.index) + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MIC_AVG_LOGPROB') + def MIC_AVG_LOGPROB(self): + return self._MIC_AVG_LOGPROB + + @MIC_AVG_LOGPROB.setter + def MIC_AVG_LOGPROB(self, value): + if isinstance(value, (int, float)): + self._MIC_AVG_LOGPROB = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('MIC_NO_SPEECH_PROB') + def MIC_NO_SPEECH_PROB(self): + return self._MIC_NO_SPEECH_PROB + + @MIC_NO_SPEECH_PROB.setter + def MIC_NO_SPEECH_PROB(self, value): + if isinstance(value, (int, float)): + self._MIC_NO_SPEECH_PROB = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('AUTO_SPEAKER_SELECT') + def AUTO_SPEAKER_SELECT(self): + return self._AUTO_SPEAKER_SELECT + + @AUTO_SPEAKER_SELECT.setter + def AUTO_SPEAKER_SELECT(self, value): + if isinstance(value, bool): + self._AUTO_SPEAKER_SELECT = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_SPEAKER_DEVICE') + def SELECTED_SPEAKER_DEVICE(self): + return self._SELECTED_SPEAKER_DEVICE + + @SELECTED_SPEAKER_DEVICE.setter + def SELECTED_SPEAKER_DEVICE(self, value): + if value in [device["name"] for device in device_manager.getSpeakerDevices()]: + self._SELECTED_SPEAKER_DEVICE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SPEAKER_THRESHOLD') + def SPEAKER_THRESHOLD(self): + return self._SPEAKER_THRESHOLD + + @SPEAKER_THRESHOLD.setter + def SPEAKER_THRESHOLD(self, value): + if isinstance(value, int): + self._SPEAKER_THRESHOLD = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SPEAKER_AUTOMATIC_THRESHOLD') + def SPEAKER_AUTOMATIC_THRESHOLD(self): + return self._SPEAKER_AUTOMATIC_THRESHOLD + + @SPEAKER_AUTOMATIC_THRESHOLD.setter + def SPEAKER_AUTOMATIC_THRESHOLD(self, value): + if isinstance(value, bool): + self._SPEAKER_AUTOMATIC_THRESHOLD = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SPEAKER_RECORD_TIMEOUT') + def SPEAKER_RECORD_TIMEOUT(self): + return self._SPEAKER_RECORD_TIMEOUT + + @SPEAKER_RECORD_TIMEOUT.setter + def SPEAKER_RECORD_TIMEOUT(self, value): + if isinstance(value, int): + self._SPEAKER_RECORD_TIMEOUT = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SPEAKER_PHRASE_TIMEOUT') + def SPEAKER_PHRASE_TIMEOUT(self): + return self._SPEAKER_PHRASE_TIMEOUT + + @SPEAKER_PHRASE_TIMEOUT.setter + def SPEAKER_PHRASE_TIMEOUT(self, value): + if isinstance(value, int): + self._SPEAKER_PHRASE_TIMEOUT = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SPEAKER_MAX_PHRASES') + def SPEAKER_MAX_PHRASES(self): + return self._SPEAKER_MAX_PHRASES + + @SPEAKER_MAX_PHRASES.setter + def SPEAKER_MAX_PHRASES(self, value): + if isinstance(value, int): + self._SPEAKER_MAX_PHRASES = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SPEAKER_AVG_LOGPROB') + def SPEAKER_AVG_LOGPROB(self): + return self._SPEAKER_AVG_LOGPROB + + @SPEAKER_AVG_LOGPROB.setter + def SPEAKER_AVG_LOGPROB(self, value): + if isinstance(value, (int, float)): + self._SPEAKER_AVG_LOGPROB = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SPEAKER_NO_SPEECH_PROB') + def SPEAKER_NO_SPEECH_PROB(self): + return self._SPEAKER_NO_SPEECH_PROB + + @SPEAKER_NO_SPEECH_PROB.setter + def SPEAKER_NO_SPEECH_PROB(self, value): + if isinstance(value, (int, float)): + self._SPEAKER_NO_SPEECH_PROB = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('OSC_IP_ADDRESS') + def OSC_IP_ADDRESS(self): + return self._OSC_IP_ADDRESS + + @OSC_IP_ADDRESS.setter + def OSC_IP_ADDRESS(self, value): + if isinstance(value, str): + self._OSC_IP_ADDRESS = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('OSC_PORT') + def OSC_PORT(self): + return self._OSC_PORT + + @OSC_PORT.setter + def OSC_PORT(self, value): + if isinstance(value, int): + self._OSC_PORT = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('AUTH_KEYS') + def AUTH_KEYS(self): + return self._AUTH_KEYS + + @AUTH_KEYS.setter + def AUTH_KEYS(self, value): + if isinstance(value, dict) and set(value.keys()) == set(self.AUTH_KEYS.keys()): + for key, value in value.items(): + if isinstance(value, str): + self._AUTH_KEYS[key] = value + self.saveConfig(inspect.currentframe().f_code.co_name, self.AUTH_KEYS) + + @property + @json_serializable('USE_EXCLUDE_WORDS') + def USE_EXCLUDE_WORDS(self): + return self._USE_EXCLUDE_WORDS + + @USE_EXCLUDE_WORDS.setter + def USE_EXCLUDE_WORDS(self, value): + if isinstance(value, bool): + self._USE_EXCLUDE_WORDS = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_TRANSLATION_COMPUTE_DEVICE') + def SELECTED_TRANSLATION_COMPUTE_DEVICE(self): + return self._SELECTED_TRANSLATION_COMPUTE_DEVICE + + @SELECTED_TRANSLATION_COMPUTE_DEVICE.setter + def SELECTED_TRANSLATION_COMPUTE_DEVICE(self, value): + if isinstance(value, dict): + if value in self.SELECTABLE_COMPUTE_DEVICE_LIST: + self._SELECTED_TRANSLATION_COMPUTE_DEVICE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_TRANSCRIPTION_COMPUTE_DEVICE') + def SELECTED_TRANSCRIPTION_COMPUTE_DEVICE(self): + return self._SELECTED_TRANSCRIPTION_COMPUTE_DEVICE + + @SELECTED_TRANSCRIPTION_COMPUTE_DEVICE.setter + def SELECTED_TRANSCRIPTION_COMPUTE_DEVICE(self, value): + if isinstance(value, dict): + if value in self.SELECTABLE_COMPUTE_DEVICE_LIST: + self._SELECTED_TRANSCRIPTION_COMPUTE_DEVICE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('CTRANSLATE2_WEIGHT_TYPE') + def CTRANSLATE2_WEIGHT_TYPE(self): + return self._CTRANSLATE2_WEIGHT_TYPE + + @CTRANSLATE2_WEIGHT_TYPE.setter + def CTRANSLATE2_WEIGHT_TYPE(self, value): + if isinstance(value, str): + if value in self.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST: + self._CTRANSLATE2_WEIGHT_TYPE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('WHISPER_WEIGHT_TYPE') + def WHISPER_WEIGHT_TYPE(self): + return self._WHISPER_WEIGHT_TYPE + + @WHISPER_WEIGHT_TYPE.setter + def WHISPER_WEIGHT_TYPE(self, value): + if isinstance(value, str): + if value in self.SELECTABLE_WHISPER_WEIGHT_TYPE_LIST: + self._WHISPER_WEIGHT_TYPE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('AUTO_CLEAR_MESSAGE_BOX') + def AUTO_CLEAR_MESSAGE_BOX(self): + return self._AUTO_CLEAR_MESSAGE_BOX + + @AUTO_CLEAR_MESSAGE_BOX.setter + def AUTO_CLEAR_MESSAGE_BOX(self, value): + if isinstance(value, bool): + self._AUTO_CLEAR_MESSAGE_BOX = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SEND_ONLY_TRANSLATED_MESSAGES') + def SEND_ONLY_TRANSLATED_MESSAGES(self): + return self._SEND_ONLY_TRANSLATED_MESSAGES + + @SEND_ONLY_TRANSLATED_MESSAGES.setter + def SEND_ONLY_TRANSLATED_MESSAGES(self, value): + if isinstance(value, bool): + self._SEND_ONLY_TRANSLATED_MESSAGES = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SEND_MESSAGE_BUTTON_TYPE') + def SEND_MESSAGE_BUTTON_TYPE(self): + return self._SEND_MESSAGE_BUTTON_TYPE + + @SEND_MESSAGE_BUTTON_TYPE.setter + def SEND_MESSAGE_BUTTON_TYPE(self, value): + if isinstance(value, str): + if value in self.SEND_MESSAGE_BUTTON_TYPE_LIST: + self._SEND_MESSAGE_BUTTON_TYPE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('OVERLAY_SMALL_LOG') + def OVERLAY_SMALL_LOG(self): + return self._OVERLAY_SMALL_LOG + + @OVERLAY_SMALL_LOG.setter + def OVERLAY_SMALL_LOG(self, value): + if isinstance(value, bool): + self._OVERLAY_SMALL_LOG = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('OVERLAY_SMALL_LOG_SETTINGS') + def OVERLAY_SMALL_LOG_SETTINGS(self): + return self._OVERLAY_SMALL_LOG_SETTINGS + + @OVERLAY_SMALL_LOG_SETTINGS.setter + def OVERLAY_SMALL_LOG_SETTINGS(self, value): + if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SMALL_LOG_SETTINGS.keys()): + for key, value in value.items(): + match (key): + case "tracker": + if isinstance(value, str): + if value in ["HMD", "LeftHand", "RightHand"]: + self._OVERLAY_SMALL_LOG_SETTINGS[key] = value + case "x_pos" | "y_pos" | "z_pos" | "x_rotation" | "y_rotation" | "z_rotation": + if isinstance(value, (int, float)): + self._OVERLAY_SMALL_LOG_SETTINGS[key] = float(value) + case "display_duration" | "fadeout_duration": + if isinstance(value, int): + self._OVERLAY_SMALL_LOG_SETTINGS[key] = value + case "opacity" | "ui_scaling": + if isinstance(value, (int, float)): + self._OVERLAY_SMALL_LOG_SETTINGS[key] = float(value) + self.saveConfig(inspect.currentframe().f_code.co_name, self.OVERLAY_SMALL_LOG_SETTINGS) + + @property + @json_serializable('OVERLAY_LARGE_LOG') + def OVERLAY_LARGE_LOG(self): + return self._OVERLAY_LARGE_LOG + + @OVERLAY_LARGE_LOG.setter + def OVERLAY_LARGE_LOG(self, value): + if isinstance(value, bool): + self._OVERLAY_LARGE_LOG = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('OVERLAY_LARGE_LOG_SETTINGS') + def OVERLAY_LARGE_LOG_SETTINGS(self): + return self._OVERLAY_LARGE_LOG_SETTINGS + + @OVERLAY_LARGE_LOG_SETTINGS.setter + def OVERLAY_LARGE_LOG_SETTINGS(self, value): + if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_LARGE_LOG_SETTINGS.keys()): + for key, value in value.items(): + match (key): + case "tracker": + if isinstance(value, str): + if value in ["HMD", "LeftHand", "RightHand"]: + self._OVERLAY_LARGE_LOG_SETTINGS[key] = value + case "x_pos" | "y_pos" | "z_pos" | "x_rotation" | "y_rotation" | "z_rotation": + if isinstance(value, (int, float)): + self._OVERLAY_LARGE_LOG_SETTINGS[key] = float(value) + case "display_duration" | "fadeout_duration": + if isinstance(value, int): + self._OVERLAY_LARGE_LOG_SETTINGS[key] = value + case "opacity" | "ui_scaling": + if isinstance(value, (int, float)): + self._OVERLAY_LARGE_LOG_SETTINGS[key] = float(value) + self.saveConfig(inspect.currentframe().f_code.co_name, self.OVERLAY_LARGE_LOG_SETTINGS) + + @property + @json_serializable('OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES') + def OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES(self): + return self._OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES + + @OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES.setter + def OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES(self, value): + if isinstance(value, bool): + self._OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SEND_MESSAGE_TO_VRC') + def SEND_MESSAGE_TO_VRC(self): + return self._SEND_MESSAGE_TO_VRC + + @SEND_MESSAGE_TO_VRC.setter + def SEND_MESSAGE_TO_VRC(self, value): + if isinstance(value, bool): + self._SEND_MESSAGE_TO_VRC = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SEND_RECEIVED_MESSAGE_TO_VRC') + def SEND_RECEIVED_MESSAGE_TO_VRC(self): + return self._SEND_RECEIVED_MESSAGE_TO_VRC + + @SEND_RECEIVED_MESSAGE_TO_VRC.setter + def SEND_RECEIVED_MESSAGE_TO_VRC(self, value): + if isinstance(value, bool): + self._SEND_RECEIVED_MESSAGE_TO_VRC = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('LOGGER_FEATURE') + def LOGGER_FEATURE(self): + return self._LOGGER_FEATURE + + @LOGGER_FEATURE.setter + def LOGGER_FEATURE(self, value): + if isinstance(value, bool): + self._LOGGER_FEATURE = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('VRC_MIC_MUTE_SYNC') + def VRC_MIC_MUTE_SYNC(self): + return self._VRC_MIC_MUTE_SYNC + + @VRC_MIC_MUTE_SYNC.setter + def VRC_MIC_MUTE_SYNC(self, value): + if isinstance(value, bool): + self._VRC_MIC_MUTE_SYNC = value + self.saveConfig(inspect.currentframe().f_code.co_name, value) + + def init_config(self): + # Read Only + self._VERSION = "3.0.0" + if getattr(sys, 'frozen', False): + self._PATH_LOCAL = os_path.dirname(sys.executable) + else: + self._PATH_LOCAL = os_path.dirname(os_path.abspath(__file__)) + self._PATH_CONFIG = os_path.join(self._PATH_LOCAL, "config.json") + self._PATH_LOGS = os_path.join(self._PATH_LOCAL, "logs") + os_makedirs(self._PATH_LOGS, exist_ok=True) + self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" + self._UPDATER_URL = "https://api.github.com/repos/misyaguziya/VRCT_updater/releases/latest" + self._BOOTH_URL = "https://misyaguziya.booth.pm/" + self._DOCUMENTS_URL = "https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246" + self._DEEPL_AUTH_KEY_PAGE_URL = "https://www.deepl.com/ja/account/summary" + + self._MAX_MIC_THRESHOLD = 2000 + self._MAX_SPEAKER_THRESHOLD = 4000 + self._WATCHDOG_TIMEOUT = 60 + self._WATCHDOG_INTERVAL = 20 + + self._SELECTABLE_TAB_NO_LIST = ["1", "2", "3"] + self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST = ctranslate2_weights.keys() + self._SELECTABLE_WHISPER_WEIGHT_TYPE_LIST = whisper_models.keys() + self._SELECTABLE_TRANSLATION_ENGINE_LIST = translation_lang.keys() + self._SELECTABLE_TRANSCRIPTION_ENGINE_LIST = list(transcription_lang[list(transcription_lang.keys())[0]].values())[0].keys() + self._SELECTABLE_UI_LANGUAGE_LIST = ["en", "ja", "ko", "zh-Hant", "zh-Hans"] + self._COMPUTE_MODE = "cuda" if torch.cuda.is_available() else "cpu" + self._SELECTABLE_COMPUTE_DEVICE_LIST = [] + if torch.cuda.is_available(): + for i in range(torch.cuda.device_count()): + self._SELECTABLE_COMPUTE_DEVICE_LIST.append({"device":"cuda", "device_index": i, "device_name": torch.cuda.get_device_name(i)}) + self._SELECTABLE_COMPUTE_DEVICE_LIST.append({"device":"cpu", "device_index": 0, "device_name": "cpu"}) + self._SEND_MESSAGE_BUTTON_TYPE_LIST = ["show", "hide", "show_and_disable_enter_key"] + self._SEND_MESSAGE_FORMAT = "[message]" + self._SEND_MESSAGE_FORMAT_WITH_T = "[message]\n[translation]" + self._RECEIVED_MESSAGE_FORMAT = "[message]" + self._RECEIVED_MESSAGE_FORMAT_WITH_T = "[message]\n[translation]" + + # Read Write + self._ENABLE_TRANSLATION = False + self._ENABLE_TRANSCRIPTION_SEND = False + self._ENABLE_TRANSCRIPTION_RECEIVE = False + self._ENABLE_FOREGROUND = False + self._ENABLE_CHECK_ENERGY_SEND = False + self._ENABLE_CHECK_ENERGY_RECEIVE = False + self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = {} + for weight_type in self.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST: + self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT[weight_type] = False + self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = {} + for weight_type in self.SELECTABLE_WHISPER_WEIGHT_TYPE_LIST: + self._SELECTABLE_WHISPER_WEIGHT_TYPE_DICT[weight_type] = False + self._SELECTABLE_TRANSLATION_ENGINE_STATUS = {} + for engine in self.SELECTABLE_TRANSLATION_ENGINE_LIST: + self._SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False + + # Save Json Data + ## Main Window + self._SELECTED_TAB_NO = "1" + self._SELECTED_TRANSLATION_ENGINES = {} + for tab_no in self.SELECTABLE_TAB_NO_LIST: + self._SELECTED_TRANSLATION_ENGINES[tab_no] = "CTranslate2" + self._SELECTED_YOUR_LANGUAGES = {} + for tab_no in self.SELECTABLE_TAB_NO_LIST: + self._SELECTED_YOUR_LANGUAGES[tab_no] = { + "1": { + "language": "Japanese", + "country": "Japan", + "enable": True, + }, + } + self._SELECTED_TARGET_LANGUAGES = {} + for tab_no in self.SELECTABLE_TAB_NO_LIST: + self._SELECTED_TARGET_LANGUAGES[tab_no] = { + "1": { + "language": "English", + "country": "United States", + "enable": True, + }, + "2": { + "language": "English", + "country": "United States", + "enable": False, + }, + "3": { + "language": "English", + "country": "United States", + "enable": False, + }, + } + self._SELECTED_TRANSCRIPTION_ENGINE = "Google" + self._CONVERT_MESSAGE_TO_ROMAJI = False + self._CONVERT_MESSAGE_TO_HIRAGANA = False + self._MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False + + ## Config Window + self._TRANSPARENCY = 100 + self._UI_SCALING = 100 + self._TEXTBOX_UI_SCALING = 100 + self._MESSAGE_BOX_RATIO = 10 + self._FONT_FAMILY = "Yu Gothic UI" + self._UI_LANGUAGE = "en" + self._MAIN_WINDOW_GEOMETRY = { + "x_pos": 0, + "y_pos": 0, + "width": 870, + "height": 654, + } + self._AUTO_MIC_SELECT = True + self._SELECTED_MIC_HOST = device_manager.getDefaultMicDevice()["host"]["name"] + self._SELECTED_MIC_DEVICE = device_manager.getDefaultMicDevice()["device"]["name"] + self._MIC_THRESHOLD = 300 + self._MIC_AUTOMATIC_THRESHOLD = False + self._MIC_RECORD_TIMEOUT = 3 + self._MIC_PHRASE_TIMEOUT = 3 + self._MIC_MAX_PHRASES = 10 + self._MIC_WORD_FILTER = [] + self._MIC_AVG_LOGPROB = -0.8 + self._MIC_NO_SPEECH_PROB = 0.6 + self._AUTO_SPEAKER_SELECT = True + self._SELECTED_SPEAKER_DEVICE = device_manager.getDefaultSpeakerDevice()["device"]["name"] + self._SPEAKER_THRESHOLD = 300 + self._SPEAKER_AUTOMATIC_THRESHOLD = False + self._SPEAKER_RECORD_TIMEOUT = 3 + self._SPEAKER_PHRASE_TIMEOUT = 3 + self._SPEAKER_MAX_PHRASES = 10 + self._SPEAKER_AVG_LOGPROB = -0.8 + self._SPEAKER_NO_SPEECH_PROB = 0.6 + self._OSC_IP_ADDRESS = "127.0.0.1" + self._OSC_PORT = 9000 + self._AUTH_KEYS = { + "DeepL_API": None, + } + self._USE_EXCLUDE_WORDS = True + self._SELECTED_TRANSLATION_COMPUTE_DEVICE = copy.deepcopy(self.SELECTABLE_COMPUTE_DEVICE_LIST[0]) + self._SELECTED_TRANSCRIPTION_COMPUTE_DEVICE = copy.deepcopy(self.SELECTABLE_COMPUTE_DEVICE_LIST[0]) + self._CTRANSLATE2_WEIGHT_TYPE = "small" + self._WHISPER_WEIGHT_TYPE = "base" + self._AUTO_CLEAR_MESSAGE_BOX = True + self._SEND_ONLY_TRANSLATED_MESSAGES = False + self._SEND_MESSAGE_BUTTON_TYPE = "show" + self._OVERLAY_SMALL_LOG = False + self._OVERLAY_SMALL_LOG_SETTINGS = { + "x_pos": 0.0, + "y_pos": 0.0, + "z_pos": 0.0, + "x_rotation": 0.0, + "y_rotation": 0.0, + "z_rotation": 0.0, + "display_duration": 5, + "fadeout_duration": 2, + "opacity": 1.0, + "ui_scaling": 1.0, + "tracker": "HMD", + } + self._OVERLAY_LARGE_LOG = False + self._OVERLAY_LARGE_LOG_SETTINGS = { + "x_pos": 0.0, + "y_pos": 0.0, + "z_pos": 0.0, + "x_rotation": 0.0, + "y_rotation": 0.0, + "z_rotation": 0.0, + "display_duration": 5, + "fadeout_duration": 2, + "opacity": 1.0, + "ui_scaling": 1.0, + "tracker": "LeftHand", + } + self._OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES = False + self._SEND_MESSAGE_TO_VRC = True + self._SEND_RECEIVED_MESSAGE_TO_VRC = False + self._LOGGER_FEATURE = False + self._VRC_MIC_MUTE_SYNC = False + + def load_config(self): + if os_path.isfile(self.PATH_CONFIG) is not False: + with open(self.PATH_CONFIG, 'r', encoding="utf-8") as fp: + if fp.readable() and fp.seek(0, 2) > 0: + fp.seek(0) + self._config_data = json_load(fp) + + for key, value in self._config_data.items(): + try: + setattr(self, key, value) + 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) + +config = Config() \ No newline at end of file diff --git a/src-python/controller.py b/src-python/controller.py new file mode 100644 index 00000000..43fbe9e3 --- /dev/null +++ b/src-python/controller.py @@ -0,0 +1,1757 @@ +from typing import Callable, Union, Any +from time import sleep +from subprocess import Popen +from threading import Thread +import re +from device_manager import device_manager +from config import config +from model import model +from utils import removeLog, printLog, errorLogging + +class Controller: + def __init__(self) -> None: + self.init_mapping = {} + self.run_mapping = {} + self.run = None + self.device_access_status = True + + def setInitMapping(self, init_mapping:dict) -> None: + self.init_mapping = init_mapping + + def setRunMapping(self, run_mapping:dict) -> None: + self.run_mapping = run_mapping + + def setRun(self, run:Callable[[int, str, Any], None]) -> None: + self.run = run + + # response functions + def updateMicHostList(self) -> None: + self.run( + 200, + self.run_mapping["mic_host_list"], + model.getListMicHost(), + ) + + def updateMicDeviceList(self) -> None: + self.run( + 200, + self.run_mapping["mic_device_list"], + model.getListMicDevice(), + ) + + def updateSpeakerDeviceList(self) -> None: + self.run( + 200, + self.run_mapping["speaker_device_list"], + model.getListSpeakerDevice(), + ) + + def updateConfigSettings(self) -> None: + settings = {} + for endpoint, dict_data in self.init_mapping.items(): + response = dict_data["variable"](None) + result = response.get("result", None) + settings[endpoint] = result + self.run( + 200, + self.run_mapping["initialization_complete"], + settings, + ) + + def restartAccessDevices(self) -> None: + if config.ENABLE_TRANSCRIPTION_SEND is True: + self.startThreadingTranscriptionSendMessage() + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + self.startThreadingTranscriptionReceiveMessage() + if config.ENABLE_CHECK_ENERGY_SEND is True: + model.startCheckMicEnergy( + self.progressBarMicEnergy, + ) + if config.ENABLE_CHECK_ENERGY_RECEIVE is True: + model.startCheckSpeakerEnergy( + self.progressBarSpeakerEnergy, + ) + + def stopAccessDevices(self) -> None: + if config.ENABLE_TRANSCRIPTION_SEND is True: + self.stopThreadingTranscriptionSendMessage() + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + self.stopThreadingTranscriptionReceiveMessage() + if config.ENABLE_CHECK_ENERGY_SEND is True: + model.stopCheckMicEnergy() + if config.ENABLE_CHECK_ENERGY_RECEIVE is True: + model.stopCheckSpeakerEnergy() + + def updateSelectedMicDevice(self, host, device) -> None: + config.SELECTED_MIC_HOST = host + config.SELECTED_MIC_DEVICE = device + self.run( + 200, + self.run_mapping["selected_mic_device"], + {"host":host, "device":device}, + ) + + def updateSelectedSpeakerDevice(self, device) -> None: + config.SELECTED_SPEAKER_DEVICE = device + self.run( + 200, + self.run_mapping["selected_speaker_device"], + device, + ) + + def progressBarMicEnergy(self, energy) -> None: + if energy is False: + self.run( + 400, + self.run_mapping["error_device"], + { + "message":"No mic device detected", + "data": None + }, + ) + else: + self.run( + 200, + self.run_mapping["check_mic_volume"], + energy, + ) + + def progressBarSpeakerEnergy(self, energy) -> None: + if energy is False: + self.run( + 400, + self.run_mapping["error_device"], + { + "message":"No mic device detected", + "data": None + }, + ) + else: + self.run( + 200, + self.run_mapping["check_speaker_volume"], + energy, + ) + + class DownloadCTranslate2: + def __init__(self, run_mapping:dict, weight_type:str, run:Callable[[int, str, Any], None]) -> None: + self.run_mapping = run_mapping + self.weight_type = weight_type + self.run = run + + def progressBar(self, progress) -> None: + printLog("CTranslate2 Weight Download Progress", progress) + self.run( + 200, + self.run_mapping["download_progress_ctranslate2_weight"], + {"weight_type": self.weight_type, "progress": progress}, + ) + + def downloaded(self) -> None: + weight_type_dict = config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT + weight_type_dict[self.weight_type] = True + config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = weight_type_dict + + self.run( + 200, + self.run_mapping["downloaded_ctranslate2_weight"], + self.weight_type, + ) + + class DownloadWhisper: + def __init__(self, run_mapping:dict, weight_type:str, run:Callable[[int, str, Any], None]) -> None: + self.run_mapping = run_mapping + self.weight_type = weight_type + self.run = run + + def progressBar(self, progress) -> None: + printLog("Whisper Weight Download Progress", progress) + self.run( + 200, + self.run_mapping["download_progress_whisper_weight"], + {"weight_type": self.weight_type, "progress": progress}, + ) + + def downloaded(self) -> None: + weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT + weight_type_dict[self.weight_type] = True + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict + + self.run( + 200, + self.run_mapping["downloaded_whisper_weight"], + self.weight_type, + ) + + def micMessage(self, result: dict) -> None: + message = result["text"] + language = result["language"] + if isinstance(message, bool) and message is False: + self.run( + 400, + self.run_mapping["error_device"], + { + "message":"No mic device detected", + "data": None + }, + ) + + elif isinstance(message, str) and len(message) > 0: + translation = [] + transliteration = [] + if model.checkKeywords(message): + self.run( + 200, + self.run_mapping["word_filter"], + {"message":f"Detected by word filter: {message}"}, + ) + return + elif model.detectRepeatSendMessage(message): + return + elif config.ENABLE_TRANSLATION is False: + pass + else: + translation, success = model.getInputTranslate(message, source_language=language) + if all(success) is not True: + self.changeToCTranslate2Process() + self.run( + 400, + self.run_mapping["error_translation_engine"], + { + "message":"Translation engine limit error", + "data": None + }, + ) + + if config.CONVERT_MESSAGE_TO_ROMAJI is True or config.CONVERT_MESSAGE_TO_HIRAGANA is True: + if config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] == "Japanese": + transliteration = model.convertMessageToTransliteration(translation[0]) + + if config.ENABLE_TRANSCRIPTION_SEND is True: + if config.SEND_MESSAGE_TO_VRC is True: + if config.SEND_ONLY_TRANSLATED_MESSAGES is True: + if config.ENABLE_TRANSLATION is False: + osc_message = self.messageFormatter("SEND", "", [message]) + else: + osc_message = self.messageFormatter("SEND", "", translation) + else: + osc_message = self.messageFormatter("SEND", translation, [message]) + model.oscSendMessage(osc_message) + + self.run( + 200, + self.run_mapping["transcription_mic"], + { + "message":message, + "translation":translation, + "transliteration":transliteration + }) + if config.LOGGER_FEATURE is True: + if len(translation) > 0: + translation = " (" + "/".join(translation) + ")" + model.logger.info(f"[SENT] {message}{translation}") + + if config.OVERLAY_LARGE_LOG is True and model.overlay.initialized is True: + if config.OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES is True and len(translation) > 0: + overlay_image = model.createOverlayImageLargeLog("send", translation[0], "") + else: + overlay_image = model.createOverlayImageLargeLog("send", message, translation[0] if len(translation) > 0 else "") + model.updateOverlayLargeLog(overlay_image) + + def speakerMessage(self, result:dict) -> None: + message = result["text"] + language = result["language"] + if isinstance(message, bool) and message is False: + self.run( + 400, + self.run_mapping["error_device"], + { + "message":"No speaker device detected", + "data": None + }, + ) + elif isinstance(message, str) and len(message) > 0: + translation = [] + transliteration = [] + if model.detectRepeatReceiveMessage(message): + return + elif config.ENABLE_TRANSLATION is False: + pass + else: + translation, success = model.getOutputTranslate(message, source_language=language) + if all(success) is not True: + self.changeToCTranslate2Process() + self.run( + 400, + self.run_mapping["error_translation_engine"], + { + "message":"Translation engine limit error", + "data": None + }, + ) + + if config.CONVERT_MESSAGE_TO_ROMAJI is True or config.CONVERT_MESSAGE_TO_HIRAGANA is True: + if config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] == "Japanese": + transliteration = model.convertMessageToTransliteration(message) + + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + if config.OVERLAY_SMALL_LOG is True and model.overlay.initialized is True: + if config.OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES is True and len(translation) > 0: + overlay_image = model.createOverlayImageSmallLog(translation[0], "") + else: + overlay_image = model.createOverlayImageSmallLog(message, translation[0] if len(translation) > 0 else "") + model.updateOverlaySmallLog(overlay_image) + + if config.OVERLAY_LARGE_LOG is True and model.overlay.initialized is True: + if config.OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES is True and len(translation) > 0: + overlay_image = model.createOverlayImageLargeLog("receive", translation[0], "") + else: + overlay_image = model.createOverlayImageLargeLog("receive", message, translation[0] if len(translation) > 0 else "") + model.updateOverlayLargeLog(overlay_image) + + if config.SEND_RECEIVED_MESSAGE_TO_VRC is True: + osc_message = self.messageFormatter("RECEIVED", translation, [message]) + model.oscSendMessage(osc_message) + + # update textbox message log (Received) + self.run( + 200, + self.run_mapping["transcription_speaker"], + { + "message":message, + "translation":translation, + "transliteration":transliteration, + }) + if config.LOGGER_FEATURE is True: + if len(translation) > 0: + translation = " (" + "/".join(translation) + ")" + model.logger.info(f"[RECEIVED] {message}{translation}") + + def chatMessage(self, data) -> None: + id = data["id"] + message = data["message"] + if len(message) > 0: + translation = [] + transliteration = [] + if config.ENABLE_TRANSLATION is False: + pass + else: + if config.USE_EXCLUDE_WORDS is True: + replacement_message, replacement_dict = self.replaceExclamationsWithRandom(message) + translation, success = model.getInputTranslate(replacement_message) + + message = self.removeExclamations(message) + for i in range(len(translation)): + translation[i] = self.restoreText(translation[i], replacement_dict) + else: + translation, success = model.getInputTranslate(message) + + if all(success) is not True: + self.changeToCTranslate2Process() + self.run( + 400, + self.run_mapping["error_translation_engine"], + { + "message":"Translation engine limit error", + "data": None + }, + ) + + if config.CONVERT_MESSAGE_TO_ROMAJI is True or config.CONVERT_MESSAGE_TO_HIRAGANA is True: + if config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] == "Japanese": + transliteration = model.convertMessageToTransliteration(translation[0]) + + # send OSC message + if config.SEND_MESSAGE_TO_VRC is True: + if config.SEND_ONLY_TRANSLATED_MESSAGES is True: + if config.ENABLE_TRANSLATION is False: + osc_message = self.messageFormatter("SEND", "", [message]) + else: + osc_message = self.messageFormatter("SEND", "", translation) + else: + osc_message = self.messageFormatter("SEND", translation, [message]) + model.oscSendMessage(osc_message) + + if config.OVERLAY_LARGE_LOG is True: + if config.OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES is True and len(translation) > 0: + overlay_image = model.createOverlayImageLargeLog("send", translation[0], "") + else: + overlay_image = model.createOverlayImageLargeLog("send", message, translation[0] if len(translation) > 0 else "") + model.updateOverlayLargeLog(overlay_image) + + # update textbox message log (Sent) + if config.LOGGER_FEATURE is True: + if len(translation) > 0: + translation_text = " (" + "/".join(translation) + ")" + model.logger.info(f"[SENT] {message}{translation_text}") + + return {"status":200, + "result":{ + "id":id, + "message":message, + "translation":translation, + "transliteration":transliteration, + }, + } + + @staticmethod + def getVersion(*args, **kwargs) -> dict: + return {"status":200, "result":config.VERSION} + + def checkSoftwareUpdated(self) -> dict: + update_flag = model.checkSoftwareUpdated() + self.run( + 200, + self.run_mapping["update_software_flag"], + update_flag, + ) + + @staticmethod + def getComputeMode(*args, **kwargs) -> dict: + return {"status":200, "result":config.COMPUTE_MODE} + + @staticmethod + def getComputeDeviceList(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTABLE_COMPUTE_DEVICE_LIST} + + @staticmethod + def getSelectedTranslationComputeDevice(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_TRANSLATION_COMPUTE_DEVICE} + + @staticmethod + def setSelectedTranslationComputeDevice(device:str, *args, **kwargs) -> dict: + printLog("setSelectedTranslationComputeDevice", device) + config.SELECTED_TRANSLATION_COMPUTE_DEVICE = device + model.changeTranslatorCTranslate2Model() + return {"status":200,"result":config.SELECTED_TRANSLATION_COMPUTE_DEVICE} + + @staticmethod + def getSelectableCtranslate2WeightTypeDict(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT} + + @staticmethod + def getSelectedTranscriptionComputeDevice(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_TRANSCRIPTION_COMPUTE_DEVICE} + + @staticmethod + def setSelectedTranscriptionComputeDevice(device:str, *args, **kwargs) -> dict: + printLog("setSelectedTranscriptionComputeDevice", device) + config.SELECTED_TRANSCRIPTION_COMPUTE_DEVICE = device + return {"status":200,"result":config.SELECTED_TRANSCRIPTION_COMPUTE_DEVICE} + + @staticmethod + def getSelectableWhisperWeightTypeDict(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT} + + # @staticmethod + # def getMaxMicThreshold(*args, **kwargs) -> dict: + # return {"status":200, "result":config.MAX_MIC_THRESHOLD} + + # @staticmethod + # def getMaxSpeakerThreshold(*args, **kwargs) -> dict: + # return {"status":200, "result":config.MAX_SPEAKER_THRESHOLD} + + @staticmethod + def setEnableTranslation(*args, **kwargs) -> dict: + if model.isLoadedCTranslate2Model() is False: + model.changeTranslatorCTranslate2Model() + config.ENABLE_TRANSLATION = True + return {"status":200, "result":config.ENABLE_TRANSLATION} + + @staticmethod + def setDisableTranslation(*args, **kwargs) -> dict: + config.ENABLE_TRANSLATION = False + return {"status":200, "result":config.ENABLE_TRANSLATION} + + @staticmethod + def setEnableForeground(*args, **kwargs) -> dict: + config.ENABLE_FOREGROUND = True + return {"status":200, "result":config.ENABLE_FOREGROUND} + + @staticmethod + def setDisableForeground(*args, **kwargs) -> dict: + config.ENABLE_FOREGROUND = False + return {"status":200, "result":config.ENABLE_FOREGROUND} + + @staticmethod + def getSelectedTabNo(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_TAB_NO} + + def setSelectedTabNo(self, selected_tab_no:str, *args, **kwargs) -> dict: + printLog("setSelectedTabNo", selected_tab_no) + config.SELECTED_TAB_NO = selected_tab_no + self.updateTranslationEngineAndEngineList() + return {"status":200, "result":config.SELECTED_TAB_NO} + + @staticmethod + def getTranslationEngines(*args, **kwargs) -> dict: + engines = model.findTranslationEngines( + config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO], + config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO], + config.SELECTABLE_TRANSLATION_ENGINE_STATUS, + ) + + your_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"] + for target_language in config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO].values(): + if your_language["language"] == target_language["language"] and target_language["enable"] is True: + engines = ["CTranslate2"] + + return {"status":200, "result":engines} + + @staticmethod + def getListLanguageAndCountry(*args, **kwargs) -> dict: + return {"status":200, "result": model.getListLanguageAndCountry()} + + @staticmethod + def getMicHostList(*args, **kwargs) -> dict: + return {"status":200, "result": model.getListMicHost()} + + @staticmethod + def getMicDeviceList(*args, **kwargs) -> dict: + return {"status":200, "result": model.getListMicDevice()} + + @staticmethod + def getSpeakerDeviceList(*args, **kwargs) -> dict: + return {"status":200, "result": model.getListSpeakerDevice()} + + @staticmethod + def getSelectedTranslationEngines(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_TRANSLATION_ENGINES} + + @staticmethod + def setSelectedTranslationEngines(data:dict, *args, **kwargs) -> dict: + config.SELECTED_TRANSLATION_ENGINES = data + return {"status":200,"result":config.SELECTED_TRANSLATION_ENGINES} + + @staticmethod + def getSelectedYourLanguages(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_YOUR_LANGUAGES} + + def setSelectedYourLanguages(self, select:dict, *args, **kwargs) -> dict: + config.SELECTED_YOUR_LANGUAGES = select + self.updateTranslationEngineAndEngineList() + return {"status":200, "result":config.SELECTED_YOUR_LANGUAGES} + + @staticmethod + def getSelectedTargetLanguages(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_TARGET_LANGUAGES} + + def setSelectedTargetLanguages(self, select:dict, *args, **kwargs) -> dict: + config.SELECTED_TARGET_LANGUAGES = select + self.updateTranslationEngineAndEngineList() + return {"status":200, "result":config.SELECTED_TARGET_LANGUAGES} + + @staticmethod + def getSelectedTranscriptionEngine(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_TRANSCRIPTION_ENGINE} + + @staticmethod + def setSelectedTranscriptionEngine(data, *args, **kwargs) -> dict: + config.SELECTED_TRANSCRIPTION_ENGINE = str(data) + return {"status":200, "result":config.SELECTED_TRANSCRIPTION_ENGINE} + + @staticmethod + def getConvertMessageToRomaji(*args, **kwargs) -> dict: + return {"status":200, "result":config.CONVERT_MESSAGE_TO_ROMAJI} + + @staticmethod + def setEnableConvertMessageToRomaji(*args, **kwargs) -> dict: + config.CONVERT_MESSAGE_TO_ROMAJI = True + return {"status":200, "result":config.CONVERT_MESSAGE_TO_ROMAJI} + + @staticmethod + def setDisableConvertMessageToRomaji(*args, **kwargs) -> dict: + config.CONVERT_MESSAGE_TO_ROMAJI = False + return {"status":200, "result":config.CONVERT_MESSAGE_TO_ROMAJI} + + @staticmethod + def getConvertMessageToHiragana(*args, **kwargs) -> dict: + return {"status":200, "result":config.CONVERT_MESSAGE_TO_HIRAGANA} + + @staticmethod + def setEnableConvertMessageToHiragana(*args, **kwargs) -> dict: + config.CONVERT_MESSAGE_TO_HIRAGANA = True + return {"status":200, "result":config.CONVERT_MESSAGE_TO_HIRAGANA} + + @staticmethod + def setDisableConvertMessageToHiragana(*args, **kwargs) -> dict: + config.CONVERT_MESSAGE_TO_HIRAGANA = False + return {"status":200, "result":config.CONVERT_MESSAGE_TO_HIRAGANA} + + @staticmethod + def getMainWindowSidebarCompactMode(*args, **kwargs) -> dict: + return {"status":200, "result":config.MAIN_WINDOW_SIDEBAR_COMPACT_MODE} + + @staticmethod + def setEnableMainWindowSidebarCompactMode(*args, **kwargs) -> dict: + config.MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True + return {"status":200, "result":config.MAIN_WINDOW_SIDEBAR_COMPACT_MODE} + + @staticmethod + def setDisableMainWindowSidebarCompactMode(*args, **kwargs) -> dict: + config.MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False + return {"status":200, "result":config.MAIN_WINDOW_SIDEBAR_COMPACT_MODE} + + @staticmethod + def getTransparency(*args, **kwargs) -> dict: + return {"status":200, "result":config.TRANSPARENCY} + + @staticmethod + def setTransparency(data, *args, **kwargs) -> dict: + config.TRANSPARENCY = int(data) + return {"status":200, "result":config.TRANSPARENCY} + + @staticmethod + def getUiScaling(*args, **kwargs) -> dict: + return {"status":200, "result":config.UI_SCALING} + + @staticmethod + def setUiScaling(data, *args, **kwargs) -> dict: + config.UI_SCALING = int(data) + return {"status":200, "result":config.UI_SCALING} + + @staticmethod + def getTextboxUiScaling(*args, **kwargs) -> dict: + return {"status":200, "result":config.TEXTBOX_UI_SCALING} + + @staticmethod + def setTextboxUiScaling(data, *args, **kwargs) -> dict: + config.TEXTBOX_UI_SCALING = int(data) + return {"status":200, "result":config.TEXTBOX_UI_SCALING} + + @staticmethod + def getMessageBoxRatio(*args, **kwargs) -> dict: + return {"status":200, "result":config.MESSAGE_BOX_RATIO} + + @staticmethod + def setMessageBoxRatio(data, *args, **kwargs) -> dict: + config.MESSAGE_BOX_RATIO = data + return {"status":200, "result":config.MESSAGE_BOX_RATIO} + + @staticmethod + def getFontFamily(*args, **kwargs) -> dict: + return {"status":200, "result":config.FONT_FAMILY} + + @staticmethod + def setFontFamily(data, *args, **kwargs) -> dict: + config.FONT_FAMILY = data + return {"status":200, "result":config.FONT_FAMILY} + + @staticmethod + def getUiLanguage(*args, **kwargs) -> dict: + return {"status":200, "result":config.UI_LANGUAGE} + + @staticmethod + def setUiLanguage(data, *args, **kwargs) -> dict: + config.UI_LANGUAGE = data + return {"status":200, "result":config.UI_LANGUAGE} + + @staticmethod + def getMainWindowGeometry(*args, **kwargs) -> dict: + return {"status":200, "result":config.MAIN_WINDOW_GEOMETRY} + + @staticmethod + def setMainWindowGeometry(data, *args, **kwargs) -> dict: + config.MAIN_WINDOW_GEOMETRY = data + return {"status":200, "result":config.MAIN_WINDOW_GEOMETRY} + + @staticmethod + def getAutoMicSelect(*args, **kwargs) -> dict: + return {"status":200, "result":config.AUTO_MIC_SELECT} + + def setEnableAutoMicSelect(self, *args, **kwargs) -> dict: + config.AUTO_MIC_SELECT = True + device_manager.setCallbackProcessBeforeUpdateDevices(self.stopAccessDevices) + device_manager.setCallbackDefaultMicDevice(self.updateSelectedMicDevice) + device_manager.setCallbackProcessAfterUpdateDevices(self.restartAccessDevices) + device_manager.forceUpdateAndSetMicDevices() + return {"status":200, "result":config.AUTO_MIC_SELECT} + + @staticmethod + def setDisableAutoMicSelect(*args, **kwargs) -> dict: + device_manager.clearCallbackProcessBeforeUpdateDevices() + device_manager.clearCallbackDefaultMicDevice() + device_manager.clearCallbackProcessAfterUpdateDevices() + config.AUTO_MIC_SELECT = False + return {"status":200, "result":config.AUTO_MIC_SELECT} + + @staticmethod + def getSelectedMicHost(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_MIC_HOST} + + def setSelectedMicHost(self, data, *args, **kwargs) -> dict: + config.SELECTED_MIC_HOST = data + config.SELECTED_MIC_DEVICE = model.getMicDefaultDevice() + if config.ENABLE_CHECK_ENERGY_SEND is True: + self.stopThreadingCheckMicEnergy() + self.startThreadingTranscriptionSendMessage() + return {"status":200, + "result":{ + "host":config.SELECTED_MIC_HOST, + "device":config.SELECTED_MIC_DEVICE, + }, + } + + @staticmethod + def getSelectedMicDevice(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_MIC_DEVICE} + + def setSelectedMicDevice(self, data, *args, **kwargs) -> dict: + config.SELECTED_MIC_DEVICE = data + if config.ENABLE_CHECK_ENERGY_SEND is True: + self.stopThreadingCheckMicEnergy() + self.startThreadingTranscriptionSendMessage() + return {"status":200, "result": config.SELECTED_MIC_DEVICE} + + @staticmethod + def getMicThreshold(*args, **kwargs) -> dict: + return {"status":200, "result":config.MIC_THRESHOLD} + + @staticmethod + def setMicThreshold(data, *args, **kwargs) -> dict: + try: + data = int(data) + if 0 <= data <= config.MAX_MIC_THRESHOLD: + config.MIC_THRESHOLD = data + status = 200 + else: + raise ValueError() + except Exception: + errorLogging() + response = { + "status":400, + "result":{ + "message":"Speaker energy threshold value is out of range", + "data": config.MIC_THRESHOLD + } + } + else: + response = {"status":status, "result":config.MIC_THRESHOLD} + return response + + @staticmethod + def getMicAutomaticThreshold(*args, **kwargs) -> dict: + return {"status":200, "result":config.MIC_AUTOMATIC_THRESHOLD} + + @staticmethod + def setEnableMicAutomaticThreshold(*args, **kwargs) -> dict: + config.MIC_AUTOMATIC_THRESHOLD = True + return {"status":200, "result":config.MIC_AUTOMATIC_THRESHOLD} + + @staticmethod + def setDisableMicAutomaticThreshold(*args, **kwargs) -> dict: + config.MIC_AUTOMATIC_THRESHOLD = False + return {"status":200, "result":config.MIC_AUTOMATIC_THRESHOLD} + + @staticmethod + def getMicRecordTimeout(*args, **kwargs) -> dict: + return {"status":200, "result":config.MIC_RECORD_TIMEOUT} + + @staticmethod + def setMicRecordTimeout(data, *args, **kwargs) -> dict: + printLog("Set Mic Record Timeout", data) + try: + data = int(data) + if 0 <= data <= config.MIC_PHRASE_TIMEOUT: + config.MIC_RECORD_TIMEOUT = data + else: + raise ValueError() + except Exception: + errorLogging() + response = { + "status":400, + "result":{ + "message":"Mic record timeout value is out of range", + "data": config.MIC_RECORD_TIMEOUT + } + } + else: + response = {"status":200, "result":config.MIC_RECORD_TIMEOUT} + return response + + @staticmethod + def getMicPhraseTimeout(*args, **kwargs) -> dict: + return {"status":200, "result":config.MIC_PHRASE_TIMEOUT} + + @staticmethod + def setMicPhraseTimeout(data, *args, **kwargs) -> dict: + try: + data = int(data) + if data >= config.MIC_RECORD_TIMEOUT: + config.MIC_PHRASE_TIMEOUT = data + else: + raise ValueError() + except Exception: + errorLogging() + response = { + "status":400, + "result":{ + "message":"Mic phrase timeout value is out of range", + "data": config.MIC_PHRASE_TIMEOUT + } + } + else: + response = {"status":200, "result":config.MIC_PHRASE_TIMEOUT} + return response + + @staticmethod + def getMicMaxPhrases(*args, **kwargs) -> dict: + return {"status":200, "result":config.MIC_MAX_PHRASES} + + @staticmethod + def setMicMaxPhrases(data, *args, **kwargs) -> dict: + try: + data = int(data) + if 0 <= data: + config.MIC_MAX_PHRASES = data + else: + raise ValueError() + except Exception: + errorLogging() + response = { + "status":400, + "result":{ + "message":"Mic max phrases value is out of range", + "data": config.MIC_MAX_PHRASES + } + } + else: + response = {"status":200, "result":config.MIC_MAX_PHRASES} + return response + + @staticmethod + def getMicWordFilter(*args, **kwargs) -> dict: + return {"status":200, "result":config.MIC_WORD_FILTER} + + @staticmethod + def setMicWordFilter(data, *args, **kwargs) -> dict: + config.MIC_WORD_FILTER = sorted(set(data), key=data.index) + model.resetKeywordProcessor() + model.addKeywords() + return {"status":200, "result":config.MIC_WORD_FILTER} + + @staticmethod + def getMicAvgLogprob(*args, **kwargs) -> dict: + return {"status":200, "result":config.MIC_AVG_LOGPROB} + + @staticmethod + def setMicAvgLogprob(data, *args, **kwargs) -> dict: + config.MIC_AVG_LOGPROB = float(data) + return {"status":200, "result":config.MIC_AVG_LOGPROB} + + @staticmethod + def getMicNoSpeechProb(*args, **kwargs) -> dict: + return {"status":200, "result":config.MIC_NO_SPEECH_PROB} + + @staticmethod + def setMicNoSpeechProb(data, *args, **kwargs) -> dict: + config.MIC_NO_SPEECH_PROB = float(data) + return {"status":200, "result":config.MIC_NO_SPEECH_PROB} + + @staticmethod + def getAutoSpeakerSelect(*args, **kwargs) -> dict: + return {"status":200, "result":config.AUTO_SPEAKER_SELECT} + + def setEnableAutoSpeakerSelect(self, *args, **kwargs) -> dict: + config.AUTO_SPEAKER_SELECT = True + device_manager.setCallbackProcessBeforeUpdateDevices(self.stopAccessDevices) + device_manager.setCallbackDefaultSpeakerDevice(self.updateSelectedSpeakerDevice) + device_manager.setCallbackProcessAfterUpdateDevices(self.restartAccessDevices) + device_manager.forceUpdateAndSetSpeakerDevices() + + return {"status":200, "result":config.AUTO_SPEAKER_SELECT} + + @staticmethod + def setDisableAutoSpeakerSelect(*args, **kwargs) -> dict: + device_manager.clearCallbackProcessBeforeUpdateDevices() + device_manager.clearCallbackDefaultSpeakerDevice() + device_manager.clearCallbackProcessAfterUpdateDevices() + config.AUTO_SPEAKER_SELECT = False + return {"status":200, "result":config.AUTO_SPEAKER_SELECT} + + @staticmethod + def getSelectedSpeakerDevice(*args, **kwargs) -> dict: + return {"status":200, "result":config.SELECTED_SPEAKER_DEVICE} + + def setSelectedSpeakerDevice(self, data, *args, **kwargs) -> dict: + config.SELECTED_SPEAKER_DEVICE = data + if config.ENABLE_CHECK_ENERGY_RECEIVE is True: + self.stopThreadingCheckSpeakerEnergy() + self.startThreadingTranscriptionReceiveMessage() + return {"status":200, "result":config.SELECTED_SPEAKER_DEVICE} + + @staticmethod + def getSpeakerThreshold(*args, **kwargs) -> dict: + return {"status":200, "result":config.SPEAKER_THRESHOLD} + + @staticmethod + def setSpeakerThreshold(data, *args, **kwargs) -> dict: + printLog("Set Speaker Energy Threshold", data) + try: + data = int(data) + if 0 <= data <= config.MAX_SPEAKER_THRESHOLD: + config.SPEAKER_THRESHOLD = data + else: + raise ValueError() + except Exception: + errorLogging() + response = { + "status":400, + "result":{ + "message":"Speaker energy threshold value is out of range", + "data": config.SPEAKER_THRESHOLD + } + } + else: + response = {"status":200, "result":config.SPEAKER_THRESHOLD} + return response + + @staticmethod + def getSpeakerAutomaticThreshold(*args, **kwargs) -> dict: + return {"status":200, "result":config.SPEAKER_AUTOMATIC_THRESHOLD} + + @staticmethod + def setEnableSpeakerAutomaticThreshold(*args, **kwargs) -> dict: + config.SPEAKER_AUTOMATIC_THRESHOLD = True + return {"status":200, "result":config.SPEAKER_AUTOMATIC_THRESHOLD} + + @staticmethod + def setDisableSpeakerAutomaticThreshold(*args, **kwargs) -> dict: + config.SPEAKER_AUTOMATIC_THRESHOLD = False + return {"status":200, "result":config.SPEAKER_AUTOMATIC_THRESHOLD} + + @staticmethod + def getSpeakerRecordTimeout(*args, **kwargs) -> dict: + return {"status":200, "result":config.SPEAKER_RECORD_TIMEOUT} + + @staticmethod + def setSpeakerRecordTimeout(data, *args, **kwargs) -> dict: + try: + data = int(data) + if 0 <= data <= config.SPEAKER_PHRASE_TIMEOUT: + config.SPEAKER_RECORD_TIMEOUT = data + else: + raise ValueError() + except Exception: + errorLogging() + response = { + "status":400, + "result":{ + "message":"Speaker record timeout value is out of range", + "data": config.SPEAKER_RECORD_TIMEOUT + } + } + else: + response = {"status":200, "result":config.SPEAKER_RECORD_TIMEOUT} + return response + + @staticmethod + def getSpeakerPhraseTimeout(*args, **kwargs) -> dict: + return {"status":200, "result":config.SPEAKER_PHRASE_TIMEOUT} + + @staticmethod + def setSpeakerPhraseTimeout(data, *args, **kwargs) -> dict: + try: + data = int(data) + if 0 <= data and data >= config.SPEAKER_RECORD_TIMEOUT: + config.SPEAKER_PHRASE_TIMEOUT = data + else: + raise ValueError() + except Exception: + errorLogging() + response = { + "status":400, + "result":{ + "message":"Speaker phrase timeout value is out of range", + "data": config.SPEAKER_PHRASE_TIMEOUT + } + } + else: + response = {"status":200, "result":config.SPEAKER_PHRASE_TIMEOUT} + return response + + @staticmethod + def getSpeakerMaxPhrases(*args, **kwargs) -> dict: + return {"status":200, "result":config.SPEAKER_MAX_PHRASES} + + @staticmethod + def setSpeakerMaxPhrases(data, *args, **kwargs) -> dict: + printLog("Set Speaker Max Phrases", data) + try: + data = int(data) + if 0 <= data: + config.SPEAKER_MAX_PHRASES = data + else: + raise ValueError() + except Exception: + errorLogging() + response = { + "status":400, + "result":{ + "message":"Speaker max phrases value is out of range", + "data": config.SPEAKER_MAX_PHRASES + } + } + else: + response = {"status":200, "result":config.SPEAKER_MAX_PHRASES} + return response + + @staticmethod + def getSpeakerAvgLogprob(*args, **kwargs) -> dict: + return {"status":200, "result":config.SPEAKER_AVG_LOGPROB} + + @staticmethod + def setSpeakerAvgLogprob(data, *args, **kwargs) -> dict: + config.SPEAKER_AVG_LOGPROB = float(data) + return {"status":200, "result":config.SPEAKER_AVG_LOGPROB} + + @staticmethod + def getSpeakerNoSpeechProb(*args, **kwargs) -> dict: + return {"status":200, "result":config.SPEAKER_NO_SPEECH_PROB} + + @staticmethod + def setSpeakerNoSpeechProb(data, *args, **kwargs) -> dict: + config.SPEAKER_NO_SPEECH_PROB = float(data) + return {"status":200, "result":config.SPEAKER_NO_SPEECH_PROB} + + @staticmethod + def getOscIpAddress(*args, **kwargs) -> dict: + return {"status":200, "result":config.OSC_IP_ADDRESS} + + @staticmethod + def setOscIpAddress(data, *args, **kwargs) -> dict: + config.OSC_IP_ADDRESS = data + model.setOscIpAddress(config.OSC_IP_ADDRESS) + return {"status":200, "result":config.OSC_IP_ADDRESS} + + @staticmethod + def getOscPort(*args, **kwargs) -> dict: + return {"status":200, "result":config.OSC_PORT} + + @staticmethod + def setOscPort(data, *args, **kwargs) -> dict: + config.OSC_PORT = int(data) + model.setOscPort(config.OSC_PORT) + return {"status":200, "result":config.OSC_PORT} + + @staticmethod + def getDeepLAuthKey(*args, **kwargs) -> dict: + return {"status":200, "result":config.AUTH_KEYS["DeepL_API"]} + + def setDeeplAuthKey(self, data, *args, **kwargs) -> dict: + printLog("Set DeepL Auth Key", data) + translator_name = "DeepL_API" + try: + data = str(data) + if len(data) == 36 or len(data) == 39: + result = model.authenticationTranslatorDeepLAuthKey(auth_key=data) + if result is True: + key = data + auth_keys = config.AUTH_KEYS + auth_keys[translator_name] = key + config.AUTH_KEYS = auth_keys + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = True + self.updateTranslationEngineAndEngineList() + response = {"status":200, "result":config.AUTH_KEYS[translator_name]} + else: + response = { + "status":400, + "result":{ + "message":"DeepL auth key length is not correct", + "data": config.AUTH_KEYS[translator_name] + } + } + else: + response = { + "status":400, + "result":{ + "message":"Authentication failure of deepL auth key", + "data": config.AUTH_KEYS[translator_name] + } + } + except Exception as e: + errorLogging() + response = { + "status":400, + "result":{ + "message":f"Error {e}", + "data": config.AUTH_KEYS[translator_name] + } + } + return response + + def delDeeplAuthKey(self, *args, **kwargs) -> dict: + translator_name = "DeepL_API" + auth_keys = config.AUTH_KEYS + auth_keys[translator_name] = None + config.AUTH_KEYS = auth_keys + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[translator_name] = False + self.updateTranslationEngineAndEngineList() + return {"status":200, "result":config.AUTH_KEYS[translator_name]} + + @staticmethod + def getCtranslate2WeightType(*args, **kwargs) -> dict: + return {"status":200, "result":config.CTRANSLATE2_WEIGHT_TYPE} + + @staticmethod + def setCtranslate2WeightType(data, *args, **kwargs) -> dict: + config.CTRANSLATE2_WEIGHT_TYPE = str(data) + if model.checkTranslatorCTranslate2ModelWeight(config.CTRANSLATE2_WEIGHT_TYPE): + def callback(): + model.changeTranslatorCTranslate2Model() + th_callback = Thread(target=callback) + th_callback.daemon = True + th_callback.start() + th_callback.join() + return {"status":200, "result":config.CTRANSLATE2_WEIGHT_TYPE} + + @staticmethod + def getWhisperWeightType(*args, **kwargs) -> dict: + return {"status":200, "result":config.WHISPER_WEIGHT_TYPE} + + @staticmethod + def setWhisperWeightType(data, *args, **kwargs) -> dict: + config.WHISPER_WEIGHT_TYPE = str(data) + return {"status":200, "result": config.WHISPER_WEIGHT_TYPE} + + @staticmethod + def getAutoClearMessageBox(*args, **kwargs) -> dict: + return {"status":200, "result":config.AUTO_CLEAR_MESSAGE_BOX} + + @staticmethod + def setEnableAutoClearMessageBox(*args, **kwargs) -> dict: + config.AUTO_CLEAR_MESSAGE_BOX = True + return {"status":200, "result":config.AUTO_CLEAR_MESSAGE_BOX} + + @staticmethod + def setDisableAutoClearMessageBox(*args, **kwargs) -> dict: + config.AUTO_CLEAR_MESSAGE_BOX = False + return {"status":200, "result":config.AUTO_CLEAR_MESSAGE_BOX} + + @staticmethod + def getSendOnlyTranslatedMessages(*args, **kwargs) -> dict: + return {"status":200, "result":config.SEND_ONLY_TRANSLATED_MESSAGES} + + @staticmethod + def setEnableSendOnlyTranslatedMessages(*args, **kwargs) -> dict: + config.SEND_ONLY_TRANSLATED_MESSAGES = True + return {"status":200, "result":config.SEND_ONLY_TRANSLATED_MESSAGES} + + @staticmethod + def setDisableSendOnlyTranslatedMessages(*args, **kwargs) -> dict: + config.SEND_ONLY_TRANSLATED_MESSAGES = False + return {"status":200, "result":config.SEND_ONLY_TRANSLATED_MESSAGES} + + @staticmethod + def getSendMessageButtonType(*args, **kwargs) -> dict: + return {"status":200, "result":config.SEND_MESSAGE_BUTTON_TYPE} + + @staticmethod + def setSendMessageButtonType(data, *args, **kwargs) -> dict: + config.SEND_MESSAGE_BUTTON_TYPE = data + return {"status":200, "result":config.SEND_MESSAGE_BUTTON_TYPE} + + @staticmethod + def getOverlaySmallLog(*args, **kwargs) -> dict: + return {"status":200, "result":config.OVERLAY_SMALL_LOG} + + @staticmethod + def setEnableOverlaySmallLog(*args, **kwargs) -> dict: + config.OVERLAY_SMALL_LOG = True + if config.OVERLAY_LARGE_LOG is False: + model.startOverlay() + return {"status":200, "result":config.OVERLAY_SMALL_LOG} + + @staticmethod + def setDisableOverlaySmallLog(*args, **kwargs) -> dict: + config.OVERLAY_SMALL_LOG = False + model.clearOverlayImageSmallLog() + if config.OVERLAY_LARGE_LOG is False: + model.shutdownOverlay() + return {"status":200, "result":config.OVERLAY_SMALL_LOG} + + @staticmethod + def getOverlaySmallLogSettings(*args, **kwargs) -> dict: + return {"status":200, "result":config.OVERLAY_SMALL_LOG_SETTINGS} + + @staticmethod + def setOverlaySmallLogSettings(data, *args, **kwargs) -> dict: + config.OVERLAY_SMALL_LOG_SETTINGS = data + model.updateOverlaySmallLogSettings() + return {"status":200, "result":config.OVERLAY_SMALL_LOG_SETTINGS} + + @staticmethod + def getOverlayLargeLog(*args, **kwargs) -> dict: + return {"status":200, "result":config.OVERLAY_LARGE_LOG} + + @staticmethod + def setEnableOverlayLargeLog(*args, **kwargs) -> dict: + config.OVERLAY_LARGE_LOG = True + if config.OVERLAY_SMALL_LOG is False: + model.startOverlay() + return {"status":200, "result":config.OVERLAY_LARGE_LOG} + + @staticmethod + def setDisableOverlayLargeLog(*args, **kwargs) -> dict: + config.OVERLAY_LARGE_LOG = False + model.clearOverlayImageLargeLog() + if config.OVERLAY_SMALL_LOG is False: + model.shutdownOverlay() + return {"status":200, "result":config.OVERLAY_LARGE_LOG} + + @staticmethod + def getOverlayLargeLogSettings(*args, **kwargs) -> dict: + return {"status":200, "result":config.OVERLAY_LARGE_LOG_SETTINGS} + + @staticmethod + def setOverlayLargeLogSettings(data, *args, **kwargs) -> dict: + config.OVERLAY_LARGE_LOG_SETTINGS = data + model.updateOverlayLargeLogSettings() + return {"status":200, "result":config.OVERLAY_LARGE_LOG_SETTINGS} + + @staticmethod + def getOverlayShowOnlyTranslatedMessages(*args, **kwargs) -> dict: + return {"status":200, "result":config.OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES} + + @staticmethod + def setEnableOverlayShowOnlyTranslatedMessages(*args, **kwargs) -> dict: + config.OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES = True + return {"status":200, "result":config.OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES} + + @staticmethod + def setDisableOverlayShowOnlyTranslatedMessages(*args, **kwargs) -> dict: + config.OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES = False + return {"status":200, "result":config.OVERLAY_SHOW_ONLY_TRANSLATED_MESSAGES} + + @staticmethod + def getSendMessageToVrc(*args, **kwargs) -> dict: + return {"status":200, "result":config.SEND_MESSAGE_TO_VRC} + + @staticmethod + def setEnableSendMessageToVrc(*args, **kwargs) -> dict: + config.SEND_MESSAGE_TO_VRC = True + return {"status":200, "result":config.SEND_MESSAGE_TO_VRC} + + @staticmethod + def setDisableSendMessageToVrc(*args, **kwargs) -> dict: + config.SEND_MESSAGE_TO_VRC = False + return {"status":200, "result":config.SEND_MESSAGE_TO_VRC} + + @staticmethod + def getSendReceivedMessageToVrc(*args, **kwargs) -> dict: + return {"status":200, "result":config.SEND_RECEIVED_MESSAGE_TO_VRC} + + @staticmethod + def setEnableSendReceivedMessageToVrc(*args, **kwargs) -> dict: + config.SEND_RECEIVED_MESSAGE_TO_VRC = True + return {"status":200, "result":config.SEND_RECEIVED_MESSAGE_TO_VRC} + + @staticmethod + def setDisableSendReceivedMessageToVrc(*args, **kwargs) -> dict: + config.SEND_RECEIVED_MESSAGE_TO_VRC = False + return {"status":200, "result":config.SEND_RECEIVED_MESSAGE_TO_VRC} + + @staticmethod + def getLoggerFeature(*args, **kwargs) -> dict: + return {"status":200, "result":config.LOGGER_FEATURE} + + @staticmethod + def setEnableLoggerFeature(*args, **kwargs) -> dict: + config.LOGGER_FEATURE = True + model.startLogger() + return {"status":200, "result":config.LOGGER_FEATURE} + + @staticmethod + def setDisableLoggerFeature(*args, **kwargs) -> dict: + model.stopLogger() + config.LOGGER_FEATURE = False + return {"status":200, "result":config.LOGGER_FEATURE} + + @staticmethod + def getVrcMicMuteSync(*args, **kwargs) -> dict: + return {"status":200, "result":config.VRC_MIC_MUTE_SYNC} + + @staticmethod + def setEnableVrcMicMuteSync(*args, **kwargs) -> dict: + config.VRC_MIC_MUTE_SYNC = True + model.setMuteSelfStatus() + model.changeMicTranscriptStatus() + return {"status":200, "result":config.VRC_MIC_MUTE_SYNC} + + @staticmethod + def setDisableVrcMicMuteSync(*args, **kwargs) -> dict: + config.VRC_MIC_MUTE_SYNC = False + model.changeMicTranscriptStatus() + return {"status":200, "result":config.VRC_MIC_MUTE_SYNC} + + def setEnableCheckSpeakerThreshold(self, *args, **kwargs) -> dict: + self.startThreadingCheckSpeakerEnergy() + config.ENABLE_CHECK_ENERGY_RECEIVE = True + return {"status":200, "result":config.ENABLE_CHECK_ENERGY_RECEIVE} + + def setDisableCheckSpeakerThreshold(self, *args, **kwargs) -> dict: + self.stopThreadingCheckSpeakerEnergy() + config.ENABLE_CHECK_ENERGY_RECEIVE = False + return {"status":200, "result":config.ENABLE_CHECK_ENERGY_RECEIVE} + + def setEnableCheckMicThreshold(self, *args, **kwargs) -> dict: + self.startThreadingCheckMicEnergy() + config.ENABLE_CHECK_ENERGY_SEND = True + return {"status":200, "result":config.ENABLE_CHECK_ENERGY_SEND} + + def setDisableCheckMicThreshold(self, *args, **kwargs) -> dict: + self.stopThreadingCheckMicEnergy() + config.ENABLE_CHECK_ENERGY_SEND = False + return {"status":200, "result":config.ENABLE_CHECK_ENERGY_SEND} + + @staticmethod + def openFilepathLogs(*args, **kwargs) -> dict: + Popen(['explorer', config.PATH_LOGS.replace('/', '\\')], shell=True) + return {"status":200, "result":True} + + @staticmethod + def openFilepathConfigFile(*args, **kwargs) -> dict: + Popen(['explorer', config.PATH_LOCAL.replace('/', '\\')], shell=True) + return {"status":200, "result":True} + + def setEnableTranscriptionSend(self, *args, **kwargs) -> dict: + self.startThreadingTranscriptionSendMessage() + config.ENABLE_TRANSCRIPTION_SEND = True + return {"status":200, "result":config.ENABLE_TRANSCRIPTION_SEND} + + def setDisableTranscriptionSend(self, *args, **kwargs) -> dict: + self.stopThreadingTranscriptionSendMessage() + config.ENABLE_TRANSCRIPTION_SEND = False + return {"status":200, "result":config.ENABLE_TRANSCRIPTION_SEND} + + def setEnableTranscriptionReceive(self, *args, **kwargs) -> dict: + self.startThreadingTranscriptionReceiveMessage() + config.ENABLE_TRANSCRIPTION_RECEIVE = True + return {"status":200, "result":config.ENABLE_TRANSCRIPTION_RECEIVE} + + def setDisableTranscriptionReceive(self, *args, **kwargs) -> dict: + self.stopThreadingTranscriptionReceiveMessage() + config.ENABLE_TRANSCRIPTION_RECEIVE = False + return {"status":200, "result":config.ENABLE_TRANSCRIPTION_RECEIVE} + + def sendMessageBox(self, data, *args, **kwargs) -> dict: + response = self.chatMessage(data) + return response + + @staticmethod + def typingMessageBox(*args, **kwargs) -> dict: + if config.SEND_MESSAGE_TO_VRC is True: + model.oscStartSendTyping() + return {"status":200, "result":True} + + @staticmethod + def stopTypingMessageBox(*args, **kwargs) -> dict: + if config.SEND_MESSAGE_TO_VRC is True: + model.oscStopSendTyping() + return {"status":200, "result":True} + + @staticmethod + def sendTextOverlay(data, *args, **kwargs) -> dict: + if config.OVERLAY_SMALL_LOG is True: + if model.overlay.initialized is True: + overlay_image = model.createOverlayImageSmallMessage(data) + model.updateOverlaySmallLog(overlay_image) + + if config.OVERLAY_LARGE_LOG is True: + if model.overlay.initialized is True: + overlay_image = model.createOverlayImageLargeMessage(data) + model.updateOverlayLargeLog(overlay_image) + return {"status":200, "result":data} + + def swapYourLanguageAndTargetLanguage(self, *args, **kwargs) -> dict: + your_languages = config.SELECTED_YOUR_LANGUAGES + your_language_temp = your_languages[config.SELECTED_TAB_NO]["1"] + + target_languages = config.SELECTED_TARGET_LANGUAGES + target_language_temp = target_languages[config.SELECTED_TAB_NO]["1"] + + your_languages[config.SELECTED_TAB_NO]["1"] = target_language_temp + target_languages[config.SELECTED_TAB_NO]["1"] = your_language_temp + + self.setSelectedYourLanguages(your_languages) + self.setSelectedTargetLanguages(target_languages) + return { + "status":200, + "result":{ + "your":config.SELECTED_YOUR_LANGUAGES, + "target":config.SELECTED_TARGET_LANGUAGES, + } + } + + def updateSoftware(self, *args, **kwargs) -> dict: + th_start_update_software = Thread(target=model.updateSoftware) + th_start_update_software.daemon = True + th_start_update_software.start() + return {"status":200, "result":True} + + def updateCudaSoftware(self, *args, **kwargs) -> dict: + th_start_update_cuda_software = Thread(target=model.updateCudaSoftware) + th_start_update_cuda_software.daemon = True + th_start_update_cuda_software.start() + return {"status":200, "result":True} + + def downloadCtranslate2Weight(self, data:str, asynchronous:bool=True, *args, **kwargs) -> dict: + weight_type = str(data) + download_ctranslate2 = self.DownloadCTranslate2( + self.run_mapping, + weight_type, + self.run + ) + + if asynchronous is True: + self.startThreadingDownloadCtranslate2Weight( + weight_type, + download_ctranslate2.progressBar, + download_ctranslate2.downloaded, + ) + else: + model.downloadCTranslate2ModelWeight(weight_type, download_ctranslate2.progressBar, download_ctranslate2.downloaded) + return {"status":200, "result":True} + + def downloadWhisperWeight(self, data:str, asynchronous:bool=True, *args, **kwargs) -> dict: + weight_type = str(data) + download_whisper = self.DownloadWhisper( + self.run_mapping, + weight_type, + self.run + ) + if asynchronous is True: + self.startThreadingDownloadWhisperWeight( + weight_type, + download_whisper.progressBar, + download_whisper.downloaded, + ) + else: + model.downloadWhisperModelWeight(weight_type, download_whisper.progressBar, download_whisper.downloaded) + return {"status":200, "result":True} + + @staticmethod + def messageFormatter(format_type:str, translation:list, message:list) -> str: + if format_type == "RECEIVED": + FORMAT_WITH_T = config.RECEIVED_MESSAGE_FORMAT_WITH_T + FORMAT = config.RECEIVED_MESSAGE_FORMAT + elif format_type == "SEND": + FORMAT_WITH_T = config.SEND_MESSAGE_FORMAT_WITH_T + FORMAT = config.SEND_MESSAGE_FORMAT + else: + raise ValueError("format_type is not found", format_type) + + if len(translation) > 0: + osc_message = FORMAT_WITH_T.replace("[message]", "\n".join(message)) + osc_message = osc_message.replace("[translation]", "\n".join(translation)) + else: + osc_message = FORMAT.replace("[message]", "\n".join(message)) + return osc_message + + def changeToCTranslate2Process(self) -> None: + selected_engines = config.SELECTED_TRANSLATION_ENGINES[config.SELECTED_TAB_NO] + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[selected_engines] = False + config.SELECTED_TRANSLATION_ENGINES[config.SELECTED_TAB_NO] = "CTranslate2" + selectable_engines = self.getTranslationEngines()["result"] + self.run(200, self.run_mapping["selected_translation_engines"], config.SELECTED_TRANSLATION_ENGINES) + self.run(200, self.run_mapping["translation_engines"], selectable_engines) + + def startTranscriptionSendMessage(self) -> None: + while self.device_access_status is False: + sleep(1) + self.device_access_status = False + model.startMicTranscript(self.micMessage) + self.device_access_status = True + + @staticmethod + def stopTranscriptionSendMessage() -> None: + model.stopMicTranscript() + + def startThreadingTranscriptionSendMessage(self) -> None: + th_startTranscriptionSendMessage = Thread(target=self.startTranscriptionSendMessage) + th_startTranscriptionSendMessage.daemon = True + th_startTranscriptionSendMessage.start() + + def stopThreadingTranscriptionSendMessage(self) -> None: + th_stopTranscriptionSendMessage = Thread(target=self.stopTranscriptionSendMessage) + th_stopTranscriptionSendMessage.daemon = True + th_stopTranscriptionSendMessage.start() + th_stopTranscriptionSendMessage.join() + + def startTranscriptionReceiveMessage(self) -> None: + while self.device_access_status is False: + sleep(1) + self.device_access_status = False + model.startSpeakerTranscript(self.speakerMessage) + self.device_access_status = True + + @staticmethod + def stopTranscriptionReceiveMessage() -> None: + model.stopSpeakerTranscript() + + def startThreadingTranscriptionReceiveMessage(self) -> None: + th_startTranscriptionReceiveMessage = Thread(target=self.startTranscriptionReceiveMessage) + th_startTranscriptionReceiveMessage.daemon = True + th_startTranscriptionReceiveMessage.start() + + def stopThreadingTranscriptionReceiveMessage(self) -> None: + th_stopTranscriptionReceiveMessage = Thread(target=self.stopTranscriptionReceiveMessage) + th_stopTranscriptionReceiveMessage.daemon = True + th_stopTranscriptionReceiveMessage.start() + th_stopTranscriptionReceiveMessage.join() + + @staticmethod + def replaceExclamationsWithRandom(text): + # ![...] にマッチする正規表現 + pattern = r'!\[(.*?)\]' + + # 乱数と置換部分を保存する辞書 + replacement_dict = {} + + num = 4096 + # マッチした部分を4096から始まる整数に置換する。置換毎に4097, 4098, ... と増える + def replace(match): + original = match.group(1) + nonlocal num + rand_value = hex(num) + replacement_dict[rand_value] = original + num += 1 + return f" ${rand_value} " + + # 文章内の ![] の部分を置換 + replaced_text = re.sub(pattern, replace, text) + + return replaced_text, replacement_dict + + @staticmethod + def restoreText(escaped_text, escape_dict): + # 大文字小文字を無視して置換するために、正規表現を使う + for escape_seq, char in escape_dict.items(): + # escaped_text の部分を pattern で置換 + pattern = re.escape(f"${escape_seq}") + r"|\$\s+" + re.escape(escape_seq) + escaped_text = re.sub(pattern, char, escaped_text, flags=re.IGNORECASE) + return escaped_text + + @staticmethod + def removeExclamations(text): + # ![...] を [...] に置換する正規表現 + pattern = r'!\[(.*?)\]' + # ![...] の部分を [] 内のテキストに置換 + cleaned_text = re.sub(pattern, r'\1', text) + return cleaned_text + + def updateDownloadedCTranslate2ModelWeight(self) -> None: + weight_type_dict = config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT + for weight_type in weight_type_dict.keys(): + weight_type_dict[weight_type] = model.checkTranslatorCTranslate2ModelWeight(weight_type) + config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = weight_type_dict + + def updateTranslationEngineAndEngineList(self): + engines = config.SELECTED_TRANSLATION_ENGINES + engine = engines[config.SELECTED_TAB_NO] + selectable_engines = self.getTranslationEngines()["result"] + if engine not in selectable_engines: + engine = "CTranslate2" + engines[config.SELECTED_TAB_NO] = engine + config.SELECTED_TRANSLATION_ENGINES = engines + + your_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"] + for target_language in config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO].values(): + if your_language["language"] == target_language["language"] and target_language["enable"] is True: + engines[config.SELECTED_TAB_NO] = "CTranslate2" + config.SELECTED_TRANSLATION_ENGINES = engines + break + + self.run(200, self.run_mapping["selected_translation_engines"], config.SELECTED_TRANSLATION_ENGINES) + self.run(200, self.run_mapping["translation_engines"], selectable_engines) + + def updateDownloadedWhisperModelWeight(self) -> None: + weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT + for weight_type in weight_type_dict.keys(): + weight_type_dict[weight_type] = model.checkTranscriptionWhisperModelWeight(weight_type) + config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict + + def updateTranscriptionEngine(self): + weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT + weight_type = config.WHISPER_WEIGHT_TYPE + if config.SELECTED_TRANSCRIPTION_ENGINE == "Whisper" and weight_type_dict[weight_type] is False: + config.SELECTED_TRANSCRIPTION_ENGINE = "Google" + + def startCheckMicEnergy(self) -> None: + while self.device_access_status is False: + sleep(1) + self.device_access_status = False + model.startCheckMicEnergy(self.progressBarMicEnergy) + self.device_access_status = True + + def startThreadingCheckMicEnergy(self) -> None: + th_startCheckMicEnergy = Thread(target=self.startCheckMicEnergy) + th_startCheckMicEnergy.daemon = True + th_startCheckMicEnergy.start() + + def stopCheckMicEnergy(self) -> None: + model.stopCheckMicEnergy() + + def stopThreadingCheckMicEnergy(self) -> None: + th_stopCheckMicEnergy = Thread(target=self.stopCheckMicEnergy) + th_stopCheckMicEnergy.daemon = True + th_stopCheckMicEnergy.start() + th_stopCheckMicEnergy.join() + + def startCheckSpeakerEnergy(self) -> None: + while self.device_access_status is False: + sleep(1) + self.device_access_status = False + model.startCheckSpeakerEnergy(self.progressBarSpeakerEnergy) + self.device_access_status = True + + def startThreadingCheckSpeakerEnergy(self) -> None: + th_startCheckSpeakerEnergy = Thread(target=self.startCheckSpeakerEnergy) + th_startCheckSpeakerEnergy.daemon = True + th_startCheckSpeakerEnergy.start() + + def stopCheckSpeakerEnergy(self) -> None: + model.stopCheckSpeakerEnergy() + + def stopThreadingCheckSpeakerEnergy(self) -> None: + th_stopCheckSpeakerEnergy = Thread(target=self.stopCheckSpeakerEnergy) + th_stopCheckSpeakerEnergy.daemon = True + th_stopCheckSpeakerEnergy.start() + th_stopCheckSpeakerEnergy.join() + + @staticmethod + def startThreadingDownloadCtranslate2Weight(weight_type:str, callback:Callable[[float], None], end_callback:Callable[[float], None]) -> None: + th_download = Thread(target=model.downloadCTranslate2ModelWeight, args=(weight_type, callback, end_callback)) + th_download.daemon = True + th_download.start() + + @staticmethod + def startThreadingDownloadWhisperWeight(weight_type:str, callback:Callable[[float], None], end_callback:Callable[[float], None]) -> None: + th_download = Thread(target=model.downloadWhisperModelWeight, args=(weight_type, callback, end_callback)) + th_download.daemon = True + th_download.start() + + @staticmethod + def startWatchdog(*args, **kwargs) -> dict: + model.startWatchdog() + return {"status":200, "result":True} + + @staticmethod + def feedWatchdog(*args, **kwargs) -> dict: + model.feedWatchdog() + return {"status":200, "result":True} + + @staticmethod + def setWatchdogCallback(callback) -> dict: + model.setWatchdogCallback(callback) + + @staticmethod + def stopWatchdog(*args, **kwargs) -> dict: + model.stopWatchdog() + return {"status":200, "result":True} + + def initializationProgress(self, progress): + self.run(200, self.run_mapping["initialization_progress"], progress) + + def init(self, *args, **kwargs) -> None: + printLog("Start Initialization") + removeLog() + + printLog("Init Translation Engine Status") + for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST: + match engine: + case "DeepL_API": + printLog("Start check DeepL API Key") + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False + if config.AUTH_KEYS[engine] is not None: + if model.authenticationTranslatorDeepLAuthKey(auth_key=config.AUTH_KEYS[engine]) is True: + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True + else: + # error update Auth key + auth_keys = config.AUTH_KEYS + auth_keys[engine] = None + config.AUTH_KEYS = auth_keys + case _: + config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True + + self.initializationProgress(1) + + # download CTranslate2 Model Weight + printLog("Download CTranslate2 Model Weight") + weight_type = config.CTRANSLATE2_WEIGHT_TYPE + th_download_ctranslate2 = None + if model.checkTranslatorCTranslate2ModelWeight(weight_type) is False: + th_download_ctranslate2 = Thread(target=self.downloadCtranslate2Weight, args=(weight_type, False)) + th_download_ctranslate2.daemon = True + th_download_ctranslate2.start() + + # download Whisper Model Weight + printLog("Download Whisper Model Weight") + weight_type = config.WHISPER_WEIGHT_TYPE + th_download_whisper = None + if model.checkTranscriptionWhisperModelWeight(weight_type) is False: + th_download_whisper = Thread(target=self.downloadWhisperWeight, args=(weight_type, False)) + th_download_whisper.daemon = True + th_download_whisper.start() + + if isinstance(th_download_ctranslate2, Thread): + th_download_ctranslate2.join() + if isinstance(th_download_whisper, Thread): + th_download_whisper.join() + + self.initializationProgress(2) + + # set Translation Engine + printLog("Set Translation Engine") + self.updateDownloadedCTranslate2ModelWeight() + self.updateTranslationEngineAndEngineList() + + # set Transcription Engine + printLog("Set Transcription Engine") + self.updateDownloadedWhisperModelWeight() + self.updateTranscriptionEngine() + + self.initializationProgress(3) + + # set word filter + printLog("Set Word Filter") + model.addKeywords() + + # check Software Updated + printLog("Check Software Updated") + self.checkSoftwareUpdated() + + # init logger + printLog("Init Logger") + if config.LOGGER_FEATURE is True: + model.startLogger() + + self.initializationProgress(4) + + # init OSC receive + printLog("Init OSC Receive") + model.startReceiveOSC() + if config.VRC_MIC_MUTE_SYNC is True: + self.setEnableVrcMicMuteSync() + + # init Auto device selection + printLog("Init Device Manager") + device_manager.setCallbackHostList(self.updateMicHostList) + device_manager.setCallbackMicDeviceList(self.updateMicDeviceList) + device_manager.setCallbackSpeakerDeviceList(self.updateSpeakerDeviceList) + + printLog("Init Auto Device Selection") + if config.AUTO_MIC_SELECT is True: + self.setEnableAutoMicSelect() + if config.AUTO_SPEAKER_SELECT is True: + self.setEnableAutoSpeakerSelect() + + printLog("Init Overlay") + if (config.OVERLAY_SMALL_LOG is True or config.OVERLAY_LARGE_LOG is True): + model.startOverlay() + + printLog("Update settings") + self.updateConfigSettings() + + printLog("End Initialization") + self.startWatchdog() \ No newline at end of file diff --git a/src-python/device_manager.py b/src-python/device_manager.py new file mode 100644 index 00000000..e2a8571a --- /dev/null +++ b/src-python/device_manager.py @@ -0,0 +1,327 @@ +from typing import Callable +from time import sleep +from threading import Thread +import comtypes +from pyaudiowpatch import PyAudio, paWASAPI +from pycaw.callbacks import MMNotificationClient +from pycaw.utils import AudioUtilities +from utils import errorLogging + +class Client(MMNotificationClient): + def __init__(self): + super().__init__() + self.loop = True + + def on_default_device_changed(self, flow, flow_id, role, role_id, default_device_id): + self.loop = False + + def on_device_added(self, added_device_id): + self.loop = False + + def on_device_removed(self, removed_device_id): + self.loop = False + + def on_device_state_changed(self, device_id, state): + self.loop = False + + # def on_property_value_changed(self, device_id, key): + # self.loop = False + +class DeviceManager: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(DeviceManager, cls).__new__(cls) + cls._instance.init() + return cls._instance + + def init(self): + self.mic_devices = {"NoHost": [{"index": -1, "name": "NoDevice"}]} + self.default_mic_device = {"host": {"index": -1, "name": "NoHost"}, "device": {"index": -1, "name": "NoDevice"}} + self.speaker_devices = [{"index": -1, "name": "NoDevice"}] + self.default_speaker_device = {"device": {"index": -1, "name": "NoDevice"}} + + self.update() + + self.prev_mic_host = [host for host in self.mic_devices] + self.prev_mic_devices = self.mic_devices + self.prev_default_mic_device = self.default_mic_device + self.prev_speaker_devices = self.speaker_devices + self.prev_default_speaker_device = self.default_speaker_device + + self.update_flag_default_mic_device = False + self.update_flag_default_speaker_device = False + self.update_flag_host_list = False + self.update_flag_mic_device_list = False + self.update_flag_speaker_device_list = False + + self.callback_default_mic_device = None + self.callback_default_speaker_device = None + self.callback_host_list = None + self.callback_mic_device_list = None + self.callback_speaker_device_list = None + self.callback_process_before_update_devices = None + self.callback_process_after_update_devices = None + + self.monitoring_flag = False + self.startMonitoring() + + def update(self): + buffer_mic_devices = {} + buffer_default_mic_device = {"host": {"index": -1, "name": "NoHost"}, "device": {"index": -1, "name": "NoDevice"}} + buffer_speaker_devices = [] + buffer_default_speaker_device = {"device": {"index": -1, "name": "NoDevice"}} + + with PyAudio() as p: + for host_index in range(p.get_host_api_count()): + host = p.get_host_api_info_by_index(host_index) + device_count = host.get('deviceCount', 0) + for device_index in range(device_count): + device = p.get_device_info_by_host_api_device_index(host_index, device_index) + if device.get("maxInputChannels", 0) > 0 and not device.get("isLoopbackDevice", True): + buffer_mic_devices.setdefault(host["name"], []).append(device) + if not buffer_mic_devices: + buffer_mic_devices = {"NoHost": [{"index": -1, "name": "NoDevice"}]} + + api_info = p.get_default_host_api_info() + default_mic_device = api_info["defaultInputDevice"] + + for host_index in range(p.get_host_api_count()): + host = p.get_host_api_info_by_index(host_index) + device_count = host.get('deviceCount', 0) + for device_index in range(device_count): + device = p.get_device_info_by_host_api_device_index(host_index, device_index) + if device["index"] == default_mic_device: + buffer_default_mic_device = {"host": host, "device": device} + break + else: + continue + break + + speaker_devices = [] + wasapi_info = p.get_host_api_info_by_type(paWASAPI) + wasapi_name = wasapi_info["name"] + for host_index in range(p.get_host_api_count()): + host = p.get_host_api_info_by_index(host_index) + if host["name"] == wasapi_name: + device_count = host.get('deviceCount', 0) + for device_index in range(device_count): + device = p.get_device_info_by_host_api_device_index(host_index, device_index) + if not device.get("isLoopbackDevice", True): + for loopback in p.get_loopback_device_info_generator(): + if device["name"] in loopback["name"]: + speaker_devices.append(loopback) + speaker_devices = [dict(t) for t in {tuple(d.items()) for d in speaker_devices}] or [{"index": -1, "name": "NoDevice"}] + buffer_speaker_devices = sorted(speaker_devices, key=lambda d: d['index']) + + wasapi_info = p.get_host_api_info_by_type(paWASAPI) + default_speaker_device_index = wasapi_info["defaultOutputDevice"] + + for host_index in range(p.get_host_api_count()): + host_info = p.get_host_api_info_by_index(host_index) + device_count = host_info.get('deviceCount', 0) + for device_index in range(0, device_count): + device = p.get_device_info_by_host_api_device_index(host_index, device_index) + if device["index"] == default_speaker_device_index: + default_speakers = device + if not default_speakers.get("isLoopbackDevice", True): + for loopback in p.get_loopback_device_info_generator(): + if default_speakers["name"] in loopback["name"]: + buffer_default_speaker_device = {"device": loopback} + break + break + + if buffer_default_speaker_device["device"]["name"] != "NoDevice": + break + + self.mic_devices = buffer_mic_devices + self.default_mic_device = buffer_default_mic_device + self.speaker_devices = buffer_speaker_devices + self.default_speaker_device = buffer_default_speaker_device + + def checkUpdate(self): + if self.prev_default_mic_device["device"]["name"] != self.default_mic_device["device"]["name"]: + self.update_flag_default_mic_device = True + self.prev_default_mic_device = self.default_mic_device + if self.prev_default_speaker_device["device"]["name"] != self.default_speaker_device["device"]["name"]: + self.update_flag_default_speaker_device = True + self.prev_default_speaker_device = self.default_speaker_device + if self.prev_mic_host != [host for host in self.mic_devices]: + self.update_flag_host_list = True + self.prev_mic_host = [host for host in self.mic_devices] + if {key: [device['name'] for device in devices] for key, devices in self.prev_mic_devices.items()} != {key: [device['name'] for device in devices] for key, devices in self.mic_devices.items()}: + self.update_flag_mic_device_list = True + self.prev_mic_devices = self.mic_devices + if [device['name'] for device in self.prev_speaker_devices] != [device['name'] for device in self.speaker_devices]: + self.update_flag_speaker_device_list = True + self.prev_speaker_devices = self.speaker_devices + + update_flag = ( + self.update_flag_default_mic_device or + self.update_flag_default_speaker_device or + self.update_flag_host_list or + self.update_flag_mic_device_list or + self.update_flag_speaker_device_list + ) + return update_flag + + def monitoring(self): + try: + while self.monitoring_flag is True: + try: + comtypes.CoInitialize() + cb = Client() + enumerator = AudioUtilities.GetDeviceEnumerator() + enumerator.RegisterEndpointNotificationCallback(cb) + while cb.loop is True: + sleep(1) + enumerator.UnregisterEndpointNotificationCallback(cb) + comtypes.CoUninitialize() + self.runProcessBeforeUpdateDevices() + sleep(2) + for _ in range(10): + self.update() + if self.checkUpdate(): + break + sleep(2) + self.noticeUpdateDevices() + self.runProcessAfterUpdateDevices() + except Exception: + errorLogging() + finally: + pass + except Exception: + errorLogging() + + def startMonitoring(self): + self.monitoring_flag = True + self.th_monitoring = Thread(target=self.monitoring) + self.th_monitoring.daemon = True + self.th_monitoring.start() + + def stopMonitoring(self): + self.monitoring_flag = False + self.th_monitoring.join() + + def setCallbackDefaultMicDevice(self, callback): + self.callback_default_mic_device = callback + + def clearCallbackDefaultMicDevice(self): + self.callback_default_mic_device = None + + def setCallbackDefaultSpeakerDevice(self, callback): + self.callback_default_speaker_device = callback + + def clearCallbackDefaultSpeakerDevice(self): + self.callback_default_speaker_device = None + + def setCallbackHostList(self, callback): + self.callback_host_list = callback + + def clearCallbackHostList(self): + self.callback_host_list = None + + def setCallbackMicDeviceList(self, callback): + self.callback_mic_device_list = callback + + def clearCallbackMicDeviceList(self): + self.callback_mic_device_list = None + + def setCallbackSpeakerDeviceList(self, callback): + self.callback_speaker_device_list = callback + + def clearCallbackSpeakerDeviceList(self): + self.callback_speaker_device_list = None + + def setCallbackProcessBeforeUpdateDevices(self, callback): + self.callback_process_before_update_devices = callback + + def clearCallbackProcessBeforeUpdateDevices(self): + self.callback_process_before_update_devices = None + + def runProcessBeforeUpdateDevices(self): + if isinstance(self.callback_process_before_update_devices, Callable): + self.callback_process_before_update_devices() + + def setCallbackProcessAfterUpdateDevices(self, callback): + self.callback_process_after_update_devices = callback + + def clearCallbackProcessAfterUpdateDevices(self): + self.callback_process_after_update_devices = None + + def runProcessAfterUpdateDevices(self): + if isinstance(self.callback_process_after_update_devices, Callable): + self.callback_process_after_update_devices() + + def noticeUpdateDevices(self): + if self.update_flag_default_mic_device is True: + self.setMicDefaultDevice() + if self.update_flag_default_speaker_device is True: + self.setSpeakerDefaultDevice() + if self.update_flag_host_list is True: + self.setMicHostList() + if self.update_flag_mic_device_list is True: + self.setMicDeviceList() + if self.update_flag_speaker_device_list is True: + self.setSpeakerDeviceList() + + self.update_flag_default_mic_device = False + self.update_flag_default_speaker_device = False + self.update_flag_host_list = False + self.update_flag_mic_device_list = False + self.update_flag_speaker_device_list = False + + def setMicDefaultDevice(self): + if isinstance(self.callback_default_mic_device, Callable): + self.callback_default_mic_device(self.default_mic_device["host"]["name"], self.default_mic_device["device"]["name"]) + + def setSpeakerDefaultDevice(self): + if isinstance(self.callback_default_speaker_device, Callable): + self.callback_default_speaker_device(self.default_speaker_device["device"]["name"]) + + def setMicHostList(self): + if isinstance(self.callback_host_list, Callable): + self.callback_host_list() + + def setMicDeviceList(self): + if isinstance(self.callback_mic_device_list, Callable): + self.callback_mic_device_list() + + def setSpeakerDeviceList(self): + if isinstance(self.callback_speaker_device_list, Callable): + self.callback_speaker_device_list() + + def getMicDevices(self): + return self.mic_devices + + def getDefaultMicDevice(self): + return self.default_mic_device + + def getSpeakerDevices(self): + return self.speaker_devices + + def getDefaultSpeakerDevice(self): + return self.default_speaker_device + + def forceUpdateAndSetMicDevices(self): + self.update() + self.setMicHostList() + self.setMicDeviceList() + self.setMicDefaultDevice() + + def forceUpdateAndSetSpeakerDevices(self): + self.update() + self.setSpeakerDeviceList() + self.setSpeakerDefaultDevice() + +device_manager = DeviceManager() + +if __name__ == "__main__": + # print("getMicDevices()", device_manager.getMicDevices()) + # print("getDefaultMicDevice()", device_manager.getDefaultMicDevice()) + # print("getSpeakerDevices()", device_manager.getSpeakerDevices()) + # print("getDefaultSpeakerDevice()", device_manager.getDefaultSpeakerDevice()) + + while True: + sleep(1) \ No newline at end of file diff --git a/src-python/mainloop.py b/src-python/mainloop.py new file mode 100644 index 00000000..c17c9564 --- /dev/null +++ b/src-python/mainloop.py @@ -0,0 +1,576 @@ +import sys +import json +import time +from typing import Any +from threading import Thread +from queue import Queue +from controller import Controller +from utils import printLog, printResponse, errorLogging, encodeBase64 + +controller = Controller() + +run_mapping = { + "transcription_mic":"/run/transcription_send_mic_message", + "transcription_speaker":"/run/transcription_receive_speaker_message", + + "check_mic_volume":"/run/check_mic_volume", + "check_speaker_volume":"/run/check_speaker_volume", + + "error_device":"/run/error_device", + "error_translation_engine":"/run/error_translation_engine", + "word_filter":"/run/word_filter", + + "download_progress_ctranslate2_weight":"/run/download_progress_ctranslate2_weight", + "downloaded_ctranslate2_weight":"/run/downloaded_ctranslate2_weight", + "download_progress_whisper_weight":"/run/download_progress_whisper_weight", + "downloaded_whisper_weight":"/run/downloaded_whisper_weight", + + "selected_mic_device":"/run/selected_mic_device", + "selected_speaker_device":"/run/selected_speaker_device", + + "selected_translation_engines":"/run/selected_translation_engines", + "translation_engines":"/run/translation_engines", + + "mic_host_list":"/run/mic_host_list", + "mic_device_list":"/run/mic_device_list", + "speaker_device_list":"/run/speaker_device_list", + + "update_software_flag":"/run/update_software_flag", + + "initialization_progress":"/run/initialization_progress", + "initialization_complete":"/run/initialization_complete", +} + +controller.setRunMapping(run_mapping) + +def run(status:int, endpoint:str, result:Any) -> None: + printResponse(status, endpoint, result) + +controller.setRun(run) + +mapping = { + # Main Window + "/set/enable/translation": {"status": False, "variable":controller.setEnableTranslation}, + "/set/disable/translation": {"status": False, "variable":controller.setDisableTranslation}, + + "/set/enable/transcription_send": {"status": False, "variable":controller.setEnableTranscriptionSend}, + "/set/disable/transcription_send": {"status": False, "variable":controller.setDisableTranscriptionSend}, + + "/set/enable/transcription_receive": {"status": False, "variable":controller.setEnableTranscriptionReceive}, + "/set/disable/transcription_receive": {"status": False, "variable":controller.setDisableTranscriptionReceive}, + + "/set/enable/foreground": {"status": True, "variable":controller.setEnableForeground}, + "/set/disable/foreground": {"status": True, "variable":controller.setDisableForeground}, + + "/get/data/selected_tab_no": {"status": True, "variable":controller.getSelectedTabNo}, + "/set/data/selected_tab_no": {"status": True, "variable":controller.setSelectedTabNo}, + + "/get/data/main_window_sidebar_compact_mode": {"status": True, "variable":controller.getMainWindowSidebarCompactMode}, + "/set/enable/main_window_sidebar_compact_mode": {"status": True, "variable":controller.setEnableMainWindowSidebarCompactMode}, + "/set/disable/main_window_sidebar_compact_mode": {"status": True, "variable":controller.setDisableMainWindowSidebarCompactMode}, + + "/get/data/translation_engines": {"status": True, "variable":controller.getTranslationEngines}, + "/get/data/selectable_language_list": {"status": True, "variable":controller.getListLanguageAndCountry}, + + "/get/data/selected_translation_engines": {"status": False, "variable":controller.getSelectedTranslationEngines}, + "/set/data/selected_translation_engines": {"status": True, "variable":controller.setSelectedTranslationEngines}, + + "/get/data/selected_your_languages": {"status": True, "variable":controller.getSelectedYourLanguages}, + "/set/data/selected_your_languages": {"status": True, "variable":controller.setSelectedYourLanguages}, + + "/get/data/selected_target_languages": {"status": True, "variable":controller.getSelectedTargetLanguages}, + "/set/data/selected_target_languages": {"status": True, "variable":controller.setSelectedTargetLanguages}, + + "/get/data/selected_transcription_engine": {"status": False, "variable":controller.getSelectedTranscriptionEngine}, + "/set/data/selected_transcription_engine": {"status": False, "variable":controller.setSelectedTranscriptionEngine}, + + "/run/send_message_box": {"status": False, "variable":controller.sendMessageBox}, + "/run/typing_message_box": {"status": False, "variable":controller.typingMessageBox}, + "/run/stop_typing_message_box": {"status": False, "variable":controller.stopTypingMessageBox}, + + "/run/send_text_overlay": {"status": True, "variable":controller.sendTextOverlay}, + + "/run/swap_your_language_and_target_language": {"status": True, "variable":controller.swapYourLanguageAndTargetLanguage}, + + "/run/update_software": {"status": True, "variable":controller.updateSoftware}, + "/run/update_cuda_software": {"status": True, "variable":controller.updateCudaSoftware}, + + # Config Window + # Appearance + "/get/data/version": {"status": True, "variable":controller.getVersion}, + + "/get/data/transparency": {"status": True, "variable":controller.getTransparency}, + "/set/data/transparency": {"status": True, "variable":controller.setTransparency}, + + "/get/data/ui_scaling": {"status": True, "variable":controller.getUiScaling}, + "/set/data/ui_scaling": {"status": True, "variable":controller.setUiScaling}, + + "/get/data/textbox_ui_scaling": {"status": True, "variable":controller.getTextboxUiScaling}, + "/set/data/textbox_ui_scaling": {"status": True, "variable":controller.setTextboxUiScaling}, + + "/get/data/message_box_ratio": {"status": True, "variable":controller.getMessageBoxRatio}, + "/set/data/message_box_ratio": {"status": True, "variable":controller.setMessageBoxRatio}, + + "/get/data/font_family": {"status": True, "variable":controller.getFontFamily}, + "/set/data/font_family": {"status": True, "variable":controller.setFontFamily}, + + "/get/data/ui_language": {"status": True, "variable":controller.getUiLanguage}, + "/set/data/ui_language": {"status": True, "variable":controller.setUiLanguage}, + + "/get/data/main_window_geometry": {"status": True, "variable":controller.getMainWindowGeometry}, + "/set/data/main_window_geometry": {"status": True, "variable":controller.setMainWindowGeometry}, + + # Compute device + "/get/data/compute_mode": {"status": True, "variable":controller.getComputeMode}, + "/get/data/translation_compute_device_list": {"status": True, "variable":controller.getComputeDeviceList}, + "/get/data/selected_translation_compute_device": {"status": True, "variable":controller.getSelectedTranslationComputeDevice}, + "/set/data/selected_translation_compute_device": {"status": True, "variable":controller.setSelectedTranslationComputeDevice}, + "/get/data/transcription_compute_device_list": {"status": True, "variable":controller.getComputeDeviceList}, + "/get/data/selected_transcription_compute_device": {"status": True, "variable":controller.getSelectedTranscriptionComputeDevice}, + "/set/data/selected_transcription_compute_device": {"status": True, "variable":controller.setSelectedTranscriptionComputeDevice}, + + # Translation + "/get/data/selectable_ctranslate2_weight_type_dict": {"status": True, "variable":controller.getSelectableCtranslate2WeightTypeDict}, + + "/get/data/ctranslate2_weight_type": {"status": True, "variable":controller.getCtranslate2WeightType}, + "/set/data/ctranslate2_weight_type": {"status": True, "variable":controller.setCtranslate2WeightType}, + + "/run/download_ctranslate2_weight": {"status": True, "variable":controller.downloadCtranslate2Weight}, + + "/get/data/deepl_auth_key": {"status": False, "variable":controller.getDeepLAuthKey}, + "/set/data/deepl_auth_key": {"status": False, "variable":controller.setDeeplAuthKey}, + "/delete/data/deepl_auth_key": {"status": False, "variable":controller.delDeeplAuthKey}, + + "/get/data/convert_message_to_romaji": {"status": True, "variable":controller.getConvertMessageToRomaji}, + "/set/enable/convert_message_to_romaji": {"status": True, "variable":controller.setEnableConvertMessageToRomaji}, + "/set/disable/convert_message_to_romaji": {"status": True, "variable":controller.setDisableConvertMessageToRomaji}, + + "/get/data/convert_message_to_hiragana": {"status": True, "variable":controller.getConvertMessageToHiragana}, + "/set/enable/convert_message_to_hiragana": {"status": True, "variable":controller.setEnableConvertMessageToHiragana}, + "/set/disable/convert_message_to_hiragana": {"status": True, "variable":controller.setDisableConvertMessageToHiragana}, + + # Transcription + "/get/data/mic_host_list": {"status": True, "variable":controller.getMicHostList}, + "/get/data/mic_device_list": {"status": True, "variable":controller.getMicDeviceList}, + "/get/data/speaker_device_list": {"status": True, "variable":controller.getSpeakerDeviceList}, + + # "/get/data/max_mic_threshold": {"status": True, "variable":controller.getMaxMicThreshold}, + # "/get/data/max_speaker_threshold": {"status": True, "variable":controller.getMaxSpeakerThreshold}, + + "/get/data/auto_mic_select": {"status": True, "variable":controller.getAutoMicSelect}, + "/set/enable/auto_mic_select": {"status": True, "variable":controller.setEnableAutoMicSelect}, + "/set/disable/auto_mic_select": {"status": True, "variable":controller.setDisableAutoMicSelect}, + + "/get/data/selected_mic_host": {"status": True, "variable":controller.getSelectedMicHost}, + "/set/data/selected_mic_host": {"status": True, "variable":controller.setSelectedMicHost}, + + "/get/data/selected_mic_device": {"status": True, "variable":controller.getSelectedMicDevice}, + "/set/data/selected_mic_device": {"status": True, "variable":controller.setSelectedMicDevice}, + + "/get/data/mic_threshold": {"status": True, "variable":controller.getMicThreshold}, + "/set/data/mic_threshold": {"status": True, "variable":controller.setMicThreshold}, + + "/get/data/mic_automatic_threshold": {"status": True, "variable":controller.getMicAutomaticThreshold}, + "/set/enable/mic_automatic_threshold": {"status": True, "variable":controller.setEnableMicAutomaticThreshold}, + "/set/disable/mic_automatic_threshold": {"status": True, "variable":controller.setDisableMicAutomaticThreshold}, + + "/get/data/mic_record_timeout": {"status": True, "variable":controller.getMicRecordTimeout}, + "/set/data/mic_record_timeout": {"status": True, "variable":controller.setMicRecordTimeout}, + + "/get/data/mic_phrase_timeout": {"status": True, "variable":controller.getMicPhraseTimeout}, + "/set/data/mic_phrase_timeout": {"status": True, "variable":controller.setMicPhraseTimeout}, + + "/get/data/mic_max_phrases": {"status": True, "variable":controller.getMicMaxPhrases}, + "/set/data/mic_max_phrases": {"status": True, "variable":controller.setMicMaxPhrases}, + + "/get/data/mic_avg_logprob": {"status": True, "variable":controller.getMicAvgLogprob}, + "/set/data/mic_avg_logprob": {"status": True, "variable":controller.setMicAvgLogprob}, + + "/get/data/mic_no_speech_prob": {"status": True, "variable":controller.getMicNoSpeechProb}, + "/set/data/mic_no_speech_prob": {"status": True, "variable":controller.setMicNoSpeechProb}, + + "/set/enable/check_mic_threshold": {"status": True, "variable":controller.setEnableCheckMicThreshold}, + "/set/disable/check_mic_threshold": {"status": True, "variable":controller.setDisableCheckMicThreshold}, + + "/get/data/mic_word_filter": {"status": True, "variable":controller.getMicWordFilter}, + "/set/data/mic_word_filter": {"status": True, "variable":controller.setMicWordFilter}, + + "/get/data/auto_speaker_select": {"status": True, "variable":controller.getAutoSpeakerSelect}, + "/set/enable/auto_speaker_select": {"status": True, "variable":controller.setEnableAutoSpeakerSelect}, + "/set/disable/auto_speaker_select": {"status": True, "variable":controller.setDisableAutoSpeakerSelect}, + + "/get/data/selected_speaker_device": {"status": True, "variable":controller.getSelectedSpeakerDevice}, + "/set/data/selected_speaker_device": {"status": True, "variable":controller.setSelectedSpeakerDevice}, + + "/get/data/speaker_threshold": {"status": True, "variable":controller.getSpeakerThreshold}, + "/set/data/speaker_threshold": {"status": True, "variable":controller.setSpeakerThreshold}, + + "/get/data/speaker_automatic_threshold": {"status": True, "variable":controller.getSpeakerAutomaticThreshold}, + "/set/enable/speaker_automatic_threshold": {"status": True, "variable":controller.setEnableSpeakerAutomaticThreshold}, + "/set/disable/speaker_automatic_threshold": {"status": True, "variable":controller.setDisableSpeakerAutomaticThreshold}, + + "/get/data/speaker_record_timeout": {"status": True, "variable":controller.getSpeakerRecordTimeout}, + "/set/data/speaker_record_timeout": {"status": True, "variable":controller.setSpeakerRecordTimeout}, + + "/get/data/speaker_phrase_timeout": {"status": True, "variable":controller.getSpeakerPhraseTimeout}, + "/set/data/speaker_phrase_timeout": {"status": True, "variable":controller.setSpeakerPhraseTimeout}, + + "/get/data/speaker_max_phrases": {"status": True, "variable":controller.getSpeakerMaxPhrases}, + "/set/data/speaker_max_phrases": {"status": True, "variable":controller.setSpeakerMaxPhrases}, + + "/get/data/speaker_avg_logprob": {"status": True, "variable":controller.getSpeakerAvgLogprob}, + "/set/data/speaker_avg_logprob": {"status": True, "variable":controller.setSpeakerAvgLogprob}, + + "/get/data/speaker_no_speech_prob": {"status": True, "variable":controller.getSpeakerNoSpeechProb}, + "/set/data/speaker_no_speech_prob": {"status": True, "variable":controller.setSpeakerNoSpeechProb}, + + "/set/enable/check_speaker_threshold": {"status": True, "variable":controller.setEnableCheckSpeakerThreshold}, + "/set/disable/check_speaker_threshold": {"status": True, "variable":controller.setDisableCheckSpeakerThreshold}, + + "/get/data/selectable_whisper_weight_type_dict": {"status": True, "variable":controller.getSelectableWhisperWeightTypeDict}, + "/get/data/whisper_weight_type": {"status": True, "variable":controller.getWhisperWeightType}, + "/set/data/whisper_weight_type": {"status": True, "variable":controller.setWhisperWeightType}, + "/run/download_whisper_weight": {"status": True, "variable":controller.downloadWhisperWeight}, + + # VR + "/get/data/overlay_small_log": {"status": True, "variable":controller.getOverlaySmallLog}, + "/set/enable/overlay_small_log": {"status": True, "variable":controller.setEnableOverlaySmallLog}, + "/set/disable/overlay_small_log": {"status": True, "variable":controller.setDisableOverlaySmallLog}, + + "/get/data/overlay_small_log_settings": {"status": True, "variable":controller.getOverlaySmallLogSettings}, + "/set/data/overlay_small_log_settings": {"status": True, "variable":controller.setOverlaySmallLogSettings}, + + "/get/data/overlay_large_log": {"status": True, "variable":controller.getOverlayLargeLog}, + "/set/enable/overlay_large_log": {"status": True, "variable":controller.setEnableOverlayLargeLog}, + "/set/disable/overlay_large_log": {"status": True, "variable":controller.setDisableOverlayLargeLog}, + + "/get/data/overlay_large_log_settings": {"status": True, "variable":controller.getOverlayLargeLogSettings}, + "/set/data/overlay_large_log_settings": {"status": True, "variable":controller.setOverlayLargeLogSettings}, + + "/get/data/overlay_show_only_translated_messages": {"status": True, "variable":controller.getOverlayShowOnlyTranslatedMessages}, + "/set/enable/overlay_show_only_translated_messages": {"status": True, "variable":controller.setEnableOverlayShowOnlyTranslatedMessages}, + "/set/disable/overlay_show_only_translated_messages": {"status": True, "variable":controller.setDisableOverlayShowOnlyTranslatedMessages}, + + # Others + "/get/data/auto_clear_message_box": {"status": True, "variable":controller.getAutoClearMessageBox}, + "/set/enable/auto_clear_message_box": {"status": True, "variable":controller.setEnableAutoClearMessageBox}, + "/set/disable/auto_clear_message_box": {"status": True, "variable":controller.setDisableAutoClearMessageBox}, + + "/get/data/send_only_translated_messages": {"status": True, "variable":controller.getSendOnlyTranslatedMessages}, + "/set/enable/send_only_translated_messages": {"status": True, "variable":controller.setEnableSendOnlyTranslatedMessages}, + "/set/disable/send_only_translated_messages": {"status": True, "variable":controller.setDisableSendOnlyTranslatedMessages}, + + "/get/data/send_message_button_type": {"status": True, "variable":controller.getSendMessageButtonType}, + "/set/data/send_message_button_type": {"status": True, "variable":controller.setSendMessageButtonType}, + + "/get/data/logger_feature": {"status": True, "variable":controller.getLoggerFeature}, + "/set/enable/logger_feature": {"status": True, "variable":controller.setEnableLoggerFeature}, + "/set/disable/logger_feature": {"status": True, "variable":controller.setDisableLoggerFeature}, + + "/run/open_filepath_logs": {"status": True, "variable":controller.openFilepathLogs}, + + "/get/data/vrc_mic_mute_sync": {"status": True, "variable":controller.getVrcMicMuteSync}, + "/set/enable/vrc_mic_mute_sync": {"status": True, "variable":controller.setEnableVrcMicMuteSync}, + "/set/disable/vrc_mic_mute_sync": {"status": True, "variable":controller.setDisableVrcMicMuteSync}, + + "/get/data/send_message_to_vrc": {"status": True, "variable":controller.getSendMessageToVrc}, + "/set/enable/send_message_to_vrc": {"status": True, "variable":controller.setEnableSendMessageToVrc}, + "/set/disable/send_message_to_vrc": {"status": True, "variable":controller.setDisableSendMessageToVrc}, + + "/get/data/send_received_message_to_vrc": {"status": True, "variable":controller.getSendReceivedMessageToVrc}, + "/set/enable/send_received_message_to_vrc": {"status": True, "variable":controller.setEnableSendReceivedMessageToVrc}, + "/set/disable/send_received_message_to_vrc": {"status": True, "variable":controller.setDisableSendReceivedMessageToVrc}, + + # Advanced Settings + "/get/data/osc_ip_address": {"status": True, "variable":controller.getOscIpAddress}, + "/set/data/osc_ip_address": {"status": True, "variable":controller.setOscIpAddress}, + + "/get/data/osc_port": {"status": True, "variable":controller.getOscPort}, + "/set/data/osc_port": {"status": True, "variable":controller.setOscPort}, + + "/run/open_filepath_config_file": {"status": True, "variable":controller.openFilepathConfigFile}, + + # "/run/start_watchdog": {"status": True, "variable":controller.startWatchdog}, + "/run/feed_watchdog": {"status": True, "variable":controller.feedWatchdog}, + # "/run/stop_watchdog": {"status": True, "variable":controller.stopWatchdog}, +} + +init_mapping = {key:value for key, value in mapping.items() if key.startswith("/get/data/")} +controller.setInitMapping(init_mapping) + +class Main: + def __init__(self) -> None: + self.queue = Queue() + self.main_loop = True + + def receiver(self) -> None: + while True: + received_data = sys.stdin.readline().strip() + received_data = json.loads(received_data) + + if received_data: + endpoint = received_data.get("endpoint", None) + data = received_data.get("data", None) + data = encodeBase64(data) if data is not None else None + printLog(endpoint, {"receive_data":data}) + self.queue.put((endpoint, data)) + + def startReceiver(self) -> None: + th_receiver = Thread(target=self.receiver) + th_receiver.daemon = True + th_receiver.start() + + def handleRequest(self, endpoint, data=None) -> tuple: + handler = mapping.get(endpoint) + if handler is None: + response = "Invalid endpoint" + status = 404 + elif handler["status"] is False: + response = "Locked endpoint" + status = 423 + else: + try: + response = handler["variable"](data) + status = response.get("status", None) + result = response.get("result", None) + except Exception as e: + errorLogging() + result = str(e) + status = 500 + return result, status + + def handler(self) -> None: + while True: + if not self.queue.empty(): + try: + endpoint, data = self.queue.get() + result, status = self.handleRequest(endpoint, data) + except Exception as e: + errorLogging() + result = str(e) + status = 500 + + if status == 423: + self.queue.put((endpoint, data)) + else: + printLog(endpoint, {"send_data":result}) + printResponse(status, endpoint, result) + time.sleep(0.1) + + def startHandler(self) -> None: + th_handler = Thread(target=self.handler) + th_handler.daemon = True + th_handler.start() + + def start(self) -> None: + while self.main_loop: + time.sleep(1) + + def stop(self) -> None: + self.main_loop = False + +if __name__ == "__main__": + main = Main() + main.startReceiver() + main.startHandler() + + controller.setWatchdogCallback(main.stop) + controller.init() + + # mappingのすべてのstatusをTrueにする + for key in mapping.keys(): + mapping[key]["status"] = True + + process = "main" + match process: + case "main": + main.start() + + case "test": + for _ in range(100): + time.sleep(0.5) + endpoint = "/get/data/mic_host_list" + result, status = main.handleRequest(endpoint) + printResponse(status, endpoint, result) + + case "test_all": + import time + for endpoint, value in mapping.items(): + printLog("endpoint", endpoint) + + match endpoint: + case "/run/send_message_box": + # handleRequest("/set/enable/translation") + # handleRequest("/set/enable/convert_message_to_romaji") + data = {"id":"123456", "message":"テスト"} + case "/set/data/selected_translation_engines": + data = { + "1":"CTranslate2", + "2":"CTranslate2", + "3":"CTranslate2", + } + case "/set/data/selected_your_languages": + data = { + "1":{ + "1":{ + "language": "English", + "country": "Hong Kong" + }, + }, + "2":{ + "1":{ + "language":"Japanese", + "country":"Japan" + }, + }, + "3":{ + "1":{ + "language":"Japanese", + "country":"Japan" + }, + }, + } + case "/set/data/selected_target_languages": + data ={ + "1":{ + "1": { + "language": "Japanese", + "country": "Japan", + "enabled": True, + }, + "secondary": { + "language": "English", + "country": "United States", + "enabled": True, + }, + "tertiary": { + "language": "Chinese Simplified", + "country": "China", + "enabled": True, + } + }, + "2":{ + "1":{ + "language":"English", + "country":"United States", + "enabled": True, + }, + "secondary":{ + "language":"English", + "country":"United States", + "enabled": True, + }, + "tertiary":{ + "language":"English", + "country":"United States", + "enabled": True, + }, + }, + "3":{ + "1":{ + "language":"English", + "country":"United States", + "enabled": True, + }, + "secondary":{ + "language":"English", + "country":"United States", + "enabled": True, + }, + "tertiary":{ + "language":"English", + "country":"United States", + "enabled": True, + }, + }, + } + case "/set/data/transparency": + data = 0.5 + case "/set/appearance": + data = "Dark" + case "/set/data/ui_scaling": + data = 1.5 + case "/set/data/appearance_theme": + data = "Dark" + case "/set/data/textbox_ui_scaling": + data = 1.5 + case "/set/data/message_box_ratio": + data = 0.5 + case "/set/data/font_family": + data = "Yu Gothic UI" + case "/set/data/ui_language": + data = "ja" + case "/set/data/ctranslate2_weight_type": + data = "small" + case "/set/data/deepl_auth_key": + data = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:fx" + case "/set/data/selected_mic_host": + data = "MME" + case "/set/data/selected_mic_device": + data = "マイク (Realtek High Definition Audio)" + case "/set/data/mic_threshold": + data = 0.5 + case "/set/data/mic_record_timeout": + data = 1 + case "/set/data/mic_phrase_timeout": + data = 5 + case "/set/data/mic_max_phrases": + data = 5 + case "/set//data/mic_word_filter": + data = "test0, test1, test2" + case "/set/data/selected_speaker_device": + data = "スピーカー (Realtek High Definition Audio)" + case "/set/data/speaker_threshold": + data = 0.5 + case "/set/data/speaker_record_timeout": + data = 5 + case "/set/data/speaker_phrase_timeout": + data = 5 + case "/set/data/speaker_max_phrases": + data = 5 + case "/set/data/whisper_weight_type": + data = "base" + case "/set/data/overlay_settings": + data = { + "opacity": 0.5, + "ui_scaling": 1.5, + } + case "/set/data/overlay_small_log_settings": + data = { + "x_pos": 0, + "y_pos": 0, + "z_pos": 0, + "x_rotation": 0, + "y_rotation": 0, + "z_rotation": 0, + "display_duration": 5, + "fadeout_duration": 0.5, + } + case "/set/data/send_message_button_type": + data = "show" + case "/set/data/send_message_format": + data = "[message]" + case "/set/data/send_message_format_with_t": + data = "[message]([translation])" + case "/set/data/received_message_format": + data = "[message]" + case "/set/data/received_message_format_with_t": + data = "[message]([translation])" + case "/set/data/osc_ip_address": + data = "127.0.0.1" + case "/set/data/osc_port": + data = 8000 + case "/set/data/speaker_no_speech_prob": + data = 0.5 + case "/set/data/speaker_avg_logprob": + data = 0.5 + case "/set/data/mic_no_speech_prob": + data = 0.5 + case "/set/data/mic_avg_logprob": + data = 0.5 + case _: + data = None + + result, status = main.handleRequest(endpoint, data) + printResponse(status, endpoint, result) + time.sleep(0.5) \ No newline at end of file diff --git a/src-python/model.py b/src-python/model.py new file mode 100644 index 00000000..251d9869 --- /dev/null +++ b/src-python/model.py @@ -0,0 +1,815 @@ +import copy +import gc +from subprocess import Popen +from os import makedirs as os_makedirs +from os import path as os_path +from datetime import datetime +from time import sleep +from queue import Queue +from threading import Thread +from requests import get as requests_get +from typing import Callable +from packaging.version import parse + +from flashtext import KeywordProcessor +from pykakasi import kakasi + +from device_manager import device_manager +from config import config + +from models.translation.translation_translator import Translator +from models.osc.osc import OSCHandler +from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder +from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakerEnergyRecorder +from models.transcription.transcription_transcriber import AudioTranscriber +from models.translation.translation_languages import translation_lang +from models.transcription.transcription_languages import transcription_lang +from models.translation.translation_utils import checkCTranslate2Weight, downloadCTranslate2Weight +from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight +from models.overlay.overlay import Overlay +from models.overlay.overlay_image import OverlayImage +from models.watchdog.watchdog import Watchdog +from utils import errorLogging, setupLogger + +class threadFnc(Thread): + def __init__(self, fnc, end_fnc=None, daemon=True, *args, **kwargs): + super(threadFnc, self).__init__(daemon=daemon, target=fnc, *args, **kwargs) + self.fnc = fnc + self.end_fnc = end_fnc + self.loop = True + self._pause = False + + def stop(self): + self.loop = False + + def pause(self): + self._pause = True + + def resume(self): + self._pause = False + + def run(self): + while self.loop: + self.fnc(*self._args, **self._kwargs) + while self._pause: + sleep(0.1) + + if callable(self.end_fnc): + self.end_fnc() + return + +class Model: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(Model, cls).__new__(cls) + cls._instance.init() + return cls._instance + + def init(self): + self.logger = None + self.th_check_device = None + self.mic_print_transcript = None + self.mic_audio_recorder = None + self.mic_energy_recorder = None + self.mic_energy_plot_progressbar = None + self.speaker_print_transcript = None + self.speaker_audio_recorder = None + self.speaker_energy_recorder = None + self.speaker_energy_plot_progressbar = None + self.previous_send_message = "" + self.previous_receive_message = "" + self.translator = Translator() + self.keyword_processor = KeywordProcessor() + overlay_small_log_settings = copy.deepcopy(config.OVERLAY_SMALL_LOG_SETTINGS) + overlay_large_log_settings = copy.deepcopy(config.OVERLAY_LARGE_LOG_SETTINGS) + overlay_large_log_settings["ui_scaling"] = overlay_large_log_settings["ui_scaling"] * 0.25 + overlay_settings = { + "small": overlay_small_log_settings, + "large": overlay_large_log_settings, + } + self.overlay = Overlay(overlay_settings) + self.overlay_image = OverlayImage() + self.mic_audio_queue = None + self.mic_mute_status = None + self.kks = kakasi() + self.watchdog = Watchdog(config.WATCHDOG_TIMEOUT, config.WATCHDOG_INTERVAL) + self.osc_handler = OSCHandler(config.OSC_IP_ADDRESS, config.OSC_PORT) + + def checkTranslatorCTranslate2ModelWeight(self, weight_type:str): + return checkCTranslate2Weight(config.PATH_LOCAL, weight_type) + + def changeTranslatorCTranslate2Model(self): + self.translator.changeCTranslate2Model( + config.PATH_LOCAL, + config.CTRANSLATE2_WEIGHT_TYPE, + config.SELECTED_TRANSLATION_COMPUTE_DEVICE["device"], + config.SELECTED_TRANSLATION_COMPUTE_DEVICE["device_index"]) + + def downloadCTranslate2ModelWeight(self, weight_type, callback=None, end_callback=None): + return downloadCTranslate2Weight(config.PATH_LOCAL, weight_type, callback, end_callback) + + def isLoadedCTranslate2Model(self): + return self.translator.isLoadedCTranslate2Model() + + def checkTranscriptionWhisperModelWeight(self, weight_type:str): + return checkWhisperWeight(config.PATH_LOCAL, weight_type) + + def downloadWhisperModelWeight(self, weight_type, callback=None, end_callback=None): + return downloadWhisperWeight(config.PATH_LOCAL, weight_type, callback, end_callback) + + def resetKeywordProcessor(self): + del self.keyword_processor + self.keyword_processor = KeywordProcessor() + + def authenticationTranslatorDeepLAuthKey(self, auth_key): + result = self.translator.authenticationDeepLAuthKey(auth_key) + return result + + def startLogger(self): + os_makedirs(config.PATH_LOGS, exist_ok=True) + file_name = os_path.join(config.PATH_LOGS, f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log") + self.logger = setupLogger("log", file_name) + self.logger.disabled = False + + def stopLogger(self): + self.logger.disabled = True + self.logger = None + + def getListLanguageAndCountry(self): + transcription_langs = list(transcription_lang.keys()) + translation_langs = [] + for tl_key in translation_lang.keys(): + for lang in translation_lang[tl_key]["source"]: + translation_langs.append(lang) + translation_langs = list(set(translation_langs)) + supported_langs = list(filter(lambda x: x in transcription_langs, translation_langs)) + + languages = [] + for language in supported_langs: + for country in transcription_lang[language]: + languages.append( + { + "language" : language, + "country" : country, + } + ) + languages = sorted(languages, key=lambda x: x['language']) + return languages + + def findTranslationEngines(self, source_lang, target_lang, engines_status): + selectable_engines = [key for key, value in engines_status.items() if value is True] + compatible_engines = [] + for engine in list(translation_lang.keys()): + languages = translation_lang.get(engine, {}).get("source", {}) + source_langs = [e["language"] for e in list(source_lang.values()) if e["enable"] is True] + target_langs = [e["language"] for e in list(target_lang.values()) if e["enable"] is True] + language_list = list(languages.keys()) + + if all(e in language_list for e in source_langs) and all(e in language_list for e in target_langs): + if engine in selectable_engines: + compatible_engines.append(engine) + + return compatible_engines + + def getTranslate(self, translator_name, source_language, target_language, target_country, message): + success_flag = False + translation = self.translator.translate( + translator_name=translator_name, + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message + ) + + # 翻訳失敗時のフェールセーフ処理 + if isinstance(translation, str): + success_flag = True + else: + while True: + translation = self.translator.translate( + translator_name="CTranslate2", + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message + ) + if translation is not False: + break + sleep(0.1) + return translation, success_flag + + def getInputTranslate(self, message, source_language=None): + translator_name=config.SELECTED_TRANSLATION_ENGINES[config.SELECTED_TAB_NO] + if source_language is None: + source_language=config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] + target_languages=config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + + translations = [] + success_flags = [] + for value in target_languages.values(): + if value["enable"] is True: + target_language = value["language"] + target_country = value["country"] + if target_language is not None or target_country is not None: + translation, success_flag = self.getTranslate( + translator_name, + source_language, + target_language, + target_country, + message + ) + translations.append(translation) + success_flags.append(success_flag) + + return translations, success_flags + + def getOutputTranslate(self, message, source_language=None): + translator_name=config.SELECTED_TRANSLATION_ENGINES[config.SELECTED_TAB_NO] + if source_language is None: + source_language=config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] + target_language=config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] + target_country=config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["country"] + + translation, success_flag = self.getTranslate( + translator_name, + source_language, + target_language, + target_country, + message + ) + return [translation], [success_flag] + + def addKeywords(self): + for f in config.MIC_WORD_FILTER: + self.keyword_processor.add_keyword(f) + + def checkKeywords(self, message): + return len(self.keyword_processor.extract_keywords(message)) != 0 + + def detectRepeatSendMessage(self, message): + repeat_flag = False + if self.previous_send_message == message: + repeat_flag = True + self.previous_send_message = message + return repeat_flag + + def detectRepeatReceiveMessage(self, message): + repeat_flag = False + if self.previous_receive_message == message: + repeat_flag = True + self.previous_receive_message = message + return repeat_flag + + def convertMessageToTransliteration(self, message: str) -> str: + data_list = self.kks.convert(message) + keys_to_keep = {"orig", "hira", "hepburn"} + filtered_list = [] + for item in data_list: + filtered_item = {key: value for key, value in item.items() if key in keys_to_keep} + filtered_list.append(filtered_item) + return filtered_list + + def setOscIpAddress(self, ip_address): + self.osc_handler.setOscIpAddress(ip_address) + + def setOscPort(self, port): + self.osc_handler.setOscPort(port) + + def oscStartSendTyping(self): + self.osc_handler.sendTyping(flag=True) + + def oscStopSendTyping(self): + self.osc_handler.sendTyping(flag=False) + + def oscSendMessage(self, message, notification=True): + self.osc_handler.sendMessage(message=message, notification=notification) + + def getMuteSelfStatus(self): + return self.osc_handler.getOSCParameterMuteSelf() + + def setMuteSelfStatus(self): + self.mic_mute_status = self.getMuteSelfStatus() + + def startReceiveOSC(self): + def changeHandlerMute(address, osc_arguments): + if config.ENABLE_TRANSCRIPTION_SEND is True: + if osc_arguments is True and self.mic_mute_status is False: + self.mic_mute_status = osc_arguments + self.changeMicTranscriptStatus() + elif osc_arguments is False and self.mic_mute_status is True: + self.mic_mute_status = osc_arguments + self.changeMicTranscriptStatus() + + dict_filter_and_target = { + self.osc_handler.osc_parameter_muteself: changeHandlerMute, + } + self.osc_handler.receiveOscParameters(dict_filter_and_target) + + def stopReceiveOSC(self): + self.osc_handler.oscServerStop() + + @staticmethod + def checkSoftwareUpdated(): + # check update + update_flag = False + try: + response = requests_get(config.GITHUB_URL) + json_data = response.json() + version = json_data.get("name", None) + if isinstance(version, str): + new_version = parse(version) + current_version = parse(config.VERSION) + if new_version > current_version: + update_flag = True + except Exception: + errorLogging() + return update_flag + + @staticmethod + def updateSoftware(): + # try to update at most 5 times + for _ in range(5): + try: + program_name = "update.exe" + current_directory = config.PATH_LOCAL + res = requests_get(config.UPDATER_URL) + assets = res.json()['assets'] + url = [i["browser_download_url"] for i in assets if i["name"] == program_name][0] + res = requests_get(url, stream=True) + with open(os_path.join(current_directory, program_name), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024*5): + file.write(chunk) + break + except Exception: + errorLogging() + # run updater + Popen(program_name, cwd=current_directory) + + @staticmethod + def updateCudaSoftware(): + # try to update at most 5 times + for _ in range(5): + try: + program_name = "update.exe" + current_directory = config.PATH_LOCAL + res = requests_get(config.UPDATER_URL) + assets = res.json()['assets'] + url = [i["browser_download_url"] for i in assets if i["name"] == program_name][0] + res = requests_get(url, stream=True) + with open(os_path.join(current_directory, program_name), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024*5): + file.write(chunk) + break + except Exception: + errorLogging() + # run updater + Popen([program_name, "--cuda"], cwd=current_directory) + + def getListMicHost(self): + result = [host for host in device_manager.getMicDevices().keys()] + return result + + def getMicDefaultDevice(self): + result = device_manager.getMicDevices().get(config.SELECTED_MIC_HOST, [{"name": "NoDevice"}])[0]["name"] + return result + + def getListMicDevice(self): + result = [device["name"] for device in device_manager.getMicDevices().get(config.SELECTED_MIC_HOST, [{"name": "NoDevice"}])] + return result + + def getListSpeakerDevice(self): + result = [device["name"] for device in device_manager.getSpeakerDevices()] + return result + + def startMicTranscript(self, fnc): + mic_host_name = config.SELECTED_MIC_HOST + mic_device_name = config.SELECTED_MIC_DEVICE + + mic_device_list = device_manager.getMicDevices().get(mic_host_name, [{"name": "NoDevice"}]) + selected_mic_device = [device for device in mic_device_list if device["name"] == mic_device_name] + + if len(selected_mic_device) == 0: + return False + + self.mic_audio_queue = Queue() + # self.mic_energy_queue = Queue() + + mic_device = selected_mic_device[0] + record_timeout = config.MIC_RECORD_TIMEOUT + phrase_timeout = config.MIC_PHRASE_TIMEOUT + if record_timeout > phrase_timeout: + record_timeout = phrase_timeout + + self.mic_audio_recorder = SelectedMicEnergyAndAudioRecorder( + device=mic_device, + energy_threshold=config.MIC_THRESHOLD, + dynamic_energy_threshold=config.MIC_AUTOMATIC_THRESHOLD, + phrase_time_limit=record_timeout, + ) + # self.mic_audio_recorder.recordIntoQueue(self.mic_audio_queue, mic_energy_queue) + self.mic_audio_recorder.recordIntoQueue(self.mic_audio_queue, None) + self.mic_transcriber = AudioTranscriber( + speaker=False, + source=self.mic_audio_recorder.source, + phrase_timeout=phrase_timeout, + max_phrases=config.MIC_MAX_PHRASES, + transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, + root=config.PATH_LOCAL, + whisper_weight_type=config.WHISPER_WEIGHT_TYPE, + device=config.SELECTED_TRANSCRIPTION_COMPUTE_DEVICE["device"], + device_index=config.SELECTED_TRANSCRIPTION_COMPUTE_DEVICE["device_index"], + ) + def sendMicTranscript(): + try: + selected_your_languages = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO] + languages = [data["language"] for data in selected_your_languages.values() if data["enable"] is True] + countries = [data["country"] for data in selected_your_languages.values() if data["enable"] is True] + res = self.mic_transcriber.transcribeAudioQueue( + self.mic_audio_queue, + languages, + countries, + config.MIC_AVG_LOGPROB, + config.MIC_NO_SPEECH_PROB + ) + if res: + result = self.mic_transcriber.getTranscript() + fnc(result) + except Exception: + errorLogging() + + def endMicTranscript(): + while not self.mic_audio_queue.empty(): + self.mic_audio_queue.get() + # while not self.mic_energy_queue.empty(): + # self.mic_energy_queue.get() + del self.mic_transcriber + gc.collect() + + # def sendMicEnergy(): + # if mic_energy_queue.empty() is False: + # energy = mic_energy_queue.get() + # # print("mic energy:", energy) + # try: + # fnc(energy) + # except Exception: + # pass + # sleep(0.01) + + self.mic_print_transcript = threadFnc(sendMicTranscript, end_fnc=endMicTranscript) + self.mic_print_transcript.daemon = True + self.mic_print_transcript.start() + + # self.mic_get_energy = threadFnc(sendMicEnergy) + # self.mic_get_energy.daemon = True + # self.mic_get_energy.start() + + self.changeMicTranscriptStatus() + + def resumeMicTranscript(self): + # キューをクリア + if isinstance(self.mic_audio_queue, Queue): + while not self.mic_audio_queue.empty(): + self.mic_audio_queue.get() + + # 文字起こしを再開 + # if isinstance(self.mic_print_transcript, threadFnc): + # self.mic_print_transcript.resume() + + # 音声のレコードを再開 + if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): + self.mic_audio_recorder.resume() + + def pauseMicTranscript(self): + # 文字起こしを一時停止 + # if isinstance(self.mic_print_transcript, threadFnc): + # self.mic_print_transcript.pause() + + # 音声のレコードを一時停止 + if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): + self.mic_audio_recorder.pause() + + def changeMicTranscriptStatus(self): + if config.VRC_MIC_MUTE_SYNC is True: + if self.mic_mute_status is True: + self.pauseMicTranscript() + elif self.mic_mute_status is False: + self.resumeMicTranscript() + else: + pass + else: + self.resumeMicTranscript() + + def stopMicTranscript(self): + if isinstance(self.mic_print_transcript, threadFnc): + self.mic_print_transcript.stop() + self.mic_print_transcript.join() + self.mic_print_transcript = None + if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): + self.mic_audio_recorder.resume() + self.mic_audio_recorder.stop() + self.mic_audio_recorder = None + # if isinstance(self.mic_get_energy, threadFnc): + # self.mic_get_energy.stop() + # self.mic_get_energy = None + + def startCheckMicEnergy(self, fnc:Callable[[float], None]=None) -> None: + if isinstance(fnc, Callable): + self.check_mic_energy_fnc = fnc + + mic_host_name = config.SELECTED_MIC_HOST + mic_device_name = config.SELECTED_MIC_DEVICE + + mic_device_list = device_manager.getMicDevices().get(mic_host_name, [{"name": "NoDevice"}]) + selected_mic_device = [device for device in mic_device_list if device["name"] == mic_device_name] + + if len(selected_mic_device) == 0: + return False + + def sendMicEnergy(): + if mic_energy_queue.empty() is False: + energy = mic_energy_queue.get() + try: + self.check_mic_energy_fnc(energy) + except Exception: + errorLogging() + sleep(0.01) + + mic_energy_queue = Queue() + mic_device = selected_mic_device[0] + self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) + self.mic_energy_recorder.recordIntoQueue(mic_energy_queue) + self.mic_energy_plot_progressbar = threadFnc(sendMicEnergy) + self.mic_energy_plot_progressbar.daemon = True + self.mic_energy_plot_progressbar.start() + + def stopCheckMicEnergy(self): + if isinstance(self.mic_energy_plot_progressbar, threadFnc): + self.mic_energy_plot_progressbar.stop() + self.mic_energy_plot_progressbar.join() + self.mic_energy_plot_progressbar = None + if isinstance(self.mic_energy_recorder, SelectedMicEnergyRecorder): + self.mic_energy_recorder.resume() + self.mic_energy_recorder.stop() + self.mic_energy_recorder = None + + def startSpeakerTranscript(self, fnc): + speaker_device_list = device_manager.getSpeakerDevices() + selected_speaker_device = [device for device in speaker_device_list if device["name"] == config.SELECTED_SPEAKER_DEVICE] + + if len(selected_speaker_device) == 0: + return False + + speaker_audio_queue = Queue() + # speaker_energy_queue = Queue() + speaker_device = selected_speaker_device[0] + record_timeout = config.SPEAKER_RECORD_TIMEOUT + phrase_timeout = config.SPEAKER_PHRASE_TIMEOUT + if record_timeout > phrase_timeout: + record_timeout = phrase_timeout + + self.speaker_audio_recorder = SelectedSpeakerEnergyAndAudioRecorder( + device=speaker_device, + energy_threshold=config.SPEAKER_THRESHOLD, + dynamic_energy_threshold=config.SPEAKER_AUTOMATIC_THRESHOLD, + phrase_time_limit=record_timeout, + ) + # self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, speaker_energy_queue) + self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, None) + self.speaker_transcriber = AudioTranscriber( + speaker=True, + source=self.speaker_audio_recorder.source, + phrase_timeout=phrase_timeout, + max_phrases=config.SPEAKER_MAX_PHRASES, + transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE, + root=config.PATH_LOCAL, + whisper_weight_type=config.WHISPER_WEIGHT_TYPE, + device=config.SELECTED_TRANSCRIPTION_COMPUTE_DEVICE["device"], + device_index=config.SELECTED_TRANSCRIPTION_COMPUTE_DEVICE["device_index"], + ) + def sendSpeakerTranscript(): + try: + selected_target_languages = config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + languages = [data["language"] for data in selected_target_languages.values() if data["enable"] is True] + countries = [data["country"] for data in selected_target_languages.values() if data["enable"] is True] + res = self.speaker_transcriber.transcribeAudioQueue( + speaker_audio_queue, + languages, + countries, + config.SPEAKER_AVG_LOGPROB, + config.SPEAKER_NO_SPEECH_PROB + ) + if res: + result = self.speaker_transcriber.getTranscript() + fnc(result) + except Exception: + errorLogging() + + def endSpeakerTranscript(): + while not speaker_audio_queue.empty(): + speaker_audio_queue.get() + # while not speaker_energy_queue.empty(): + # speaker_energy_queue.get() + del self.speaker_transcriber + gc.collect() + + # def sendSpeakerEnergy(): + # if speaker_energy_queue.empty() is False: + # energy = speaker_energy_queue.get() + # # print("speaker energy:", energy) + # try: + # fnc(energy) + # except Exception: + # pass + # sleep(0.01) + + self.speaker_print_transcript = threadFnc(sendSpeakerTranscript, end_fnc=endSpeakerTranscript) + self.speaker_print_transcript.daemon = True + self.speaker_print_transcript.start() + + # self.speaker_get_energy = threadFnc(sendSpeakerEnergy) + # self.speaker_get_energy.daemon = True + # self.speaker_get_energy.start() + + def stopSpeakerTranscript(self): + if isinstance(self.speaker_print_transcript, threadFnc): + self.speaker_print_transcript.stop() + self.speaker_print_transcript.join() + self.speaker_print_transcript = None + if isinstance(self.speaker_audio_recorder, SelectedSpeakerEnergyAndAudioRecorder): + self.speaker_audio_recorder.stop() + self.speaker_audio_recorder = None + # if isinstance(self.speaker_get_energy, threadFnc): + # self.speaker_get_energy.stop() + # self.speaker_get_energy = None + + def startCheckSpeakerEnergy(self, fnc:Callable[[float], None]=None) -> None: + if isinstance(fnc, Callable): + self.check_speaker_energy_fnc = fnc + + speaker_device_list = device_manager.getSpeakerDevices() + selected_speaker_device = [device for device in speaker_device_list if device["name"] == config.SELECTED_SPEAKER_DEVICE] + + if len(selected_speaker_device) == 0: + return False + + def sendSpeakerEnergy(): + if speaker_energy_queue.empty() is False: + energy = speaker_energy_queue.get() + try: + self.check_speaker_energy_fnc(energy) + except Exception: + errorLogging() + sleep(0.01) + + speaker_energy_queue = Queue() + speaker_device = selected_speaker_device[0] + self.speaker_energy_recorder = SelectedSpeakerEnergyRecorder(speaker_device) + self.speaker_energy_recorder.recordIntoQueue(speaker_energy_queue) + self.speaker_energy_plot_progressbar = threadFnc(sendSpeakerEnergy) + self.speaker_energy_plot_progressbar.daemon = True + self.speaker_energy_plot_progressbar.start() + + def stopCheckSpeakerEnergy(self): + if isinstance(self.speaker_energy_plot_progressbar, threadFnc): + self.speaker_energy_plot_progressbar.stop() + self.speaker_energy_plot_progressbar.join() + self.speaker_energy_plot_progressbar = None + if isinstance(self.speaker_energy_recorder, SelectedSpeakerEnergyRecorder): + self.speaker_energy_recorder.resume() + self.speaker_energy_recorder.stop() + self.speaker_energy_recorder = None + + def createOverlayImageSmallLog(self, message, translation): + your_language = config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] + target_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] + return self.overlay_image.createOverlayImageSmallLog(message, your_language, translation, target_language) + + def createOverlayImageSmallMessage(self, message): + ui_language = config.UI_LANGUAGE + convert_languages = { + "en": "Japanese", + "jp": "Japanese", + "ko":"Korean", + "zh-Hans":"Chinese Simplified", + "zh-Hant":"Chinese Traditional", + } + language = convert_languages.get(ui_language, "Japanese") + return self.overlay_image.createOverlayImageSmallLog(message, language) + + def clearOverlayImageSmallLog(self): + self.overlay.clearImage("small") + + def updateOverlaySmallLog(self, img): + self.overlay.updateImage(img, "small") + + def updateOverlaySmallLogSettings(self): + size = "small" + + if (self.overlay.settings[size]["x_pos"] != config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"] or + self.overlay.settings[size]["y_pos"] != config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"] or + self.overlay.settings[size]["z_pos"] != config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"] or + self.overlay.settings[size]["x_rotation"] != config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"] or + self.overlay.settings[size]["y_rotation"] != config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"] or + self.overlay.settings[size]["z_rotation"] != config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"] or + self.overlay.settings[size]["tracker"] != config.OVERLAY_SMALL_LOG_SETTINGS["tracker"]): + self.overlay.updatePosition( + config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"], + config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"], + config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"], + config.OVERLAY_SMALL_LOG_SETTINGS["tracker"], + size, + ) + if (self.overlay.settings[size]["display_duration"] != config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"]): + self.overlay.updateDisplayDuration(config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"], size) + if (self.overlay.settings[size]["fadeout_duration"] != config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"]): + self.overlay.updateFadeoutDuration(config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"], size) + if (self.overlay.settings[size]["opacity"] != config.OVERLAY_SMALL_LOG_SETTINGS["opacity"]): + self.overlay.updateOpacity(config.OVERLAY_SMALL_LOG_SETTINGS["opacity"], size, True) + if (self.overlay.settings[size]["ui_scaling"] != config.OVERLAY_SMALL_LOG_SETTINGS["ui_scaling"]): + self.overlay.updateUiScaling(config.OVERLAY_SMALL_LOG_SETTINGS["ui_scaling"], size) + + def createOverlayImageLargeLog(self, message_type:str, message:str, translation:str): + your_language = config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] + target_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] + return self.overlay_image.createOverlayImageLargeLog(message_type, message, your_language, translation, target_language) + + def createOverlayImageLargeMessage(self, message): + ui_language = config.UI_LANGUAGE + convert_languages = { + "en": "Japanese", + "jp": "Japanese", + "ko":"Korean", + "zh-Hans":"Chinese Simplified", + "zh-Hant":"Chinese Traditional", + } + language = convert_languages.get(ui_language, "Japanese") + overlay_image = OverlayImage() + + for _ in range(2): + overlay_image.createOverlayImageLargeLog("send", message, language) + overlay_image.createOverlayImageLargeLog("receive", message, language) + return overlay_image.createOverlayImageLargeLog("send", message, language) + + def clearOverlayImageLargeLog(self): + self.overlay.clearImage("large") + + def updateOverlayLargeLog(self, img): + self.overlay.updateImage(img, "large") + + def updateOverlayLargeLogSettings(self): + size = "large" + if (self.overlay.settings[size]["x_pos"] != config.OVERLAY_LARGE_LOG_SETTINGS["x_pos"] or + self.overlay.settings[size]["y_pos"] != config.OVERLAY_LARGE_LOG_SETTINGS["y_pos"] or + self.overlay.settings[size]["z_pos"] != config.OVERLAY_LARGE_LOG_SETTINGS["z_pos"] or + self.overlay.settings[size]["x_rotation"] != config.OVERLAY_LARGE_LOG_SETTINGS["x_rotation"] or + self.overlay.settings[size]["y_rotation"] != config.OVERLAY_LARGE_LOG_SETTINGS["y_rotation"] or + self.overlay.settings[size]["z_rotation"] != config.OVERLAY_LARGE_LOG_SETTINGS["z_rotation"] or + self.overlay.settings[size]["tracker"] != config.OVERLAY_LARGE_LOG_SETTINGS["tracker"]): + self.overlay.updatePosition( + config.OVERLAY_LARGE_LOG_SETTINGS["x_pos"], + config.OVERLAY_LARGE_LOG_SETTINGS["y_pos"], + config.OVERLAY_LARGE_LOG_SETTINGS["z_pos"], + config.OVERLAY_LARGE_LOG_SETTINGS["x_rotation"], + config.OVERLAY_LARGE_LOG_SETTINGS["y_rotation"], + config.OVERLAY_LARGE_LOG_SETTINGS["z_rotation"], + config.OVERLAY_LARGE_LOG_SETTINGS["tracker"], + size, + ) + if (self.overlay.settings[size]["display_duration"] != config.OVERLAY_LARGE_LOG_SETTINGS["display_duration"]): + self.overlay.updateDisplayDuration(config.OVERLAY_LARGE_LOG_SETTINGS["display_duration"], size) + if (self.overlay.settings[size]["fadeout_duration"] != config.OVERLAY_LARGE_LOG_SETTINGS["fadeout_duration"]): + self.overlay.updateFadeoutDuration(config.OVERLAY_LARGE_LOG_SETTINGS["fadeout_duration"], size) + if (self.overlay.settings[size]["opacity"] != config.OVERLAY_LARGE_LOG_SETTINGS["opacity"]): + self.overlay.updateOpacity(config.OVERLAY_LARGE_LOG_SETTINGS["opacity"], size, True) + if (self.overlay.settings[size]["ui_scaling"] != config.OVERLAY_LARGE_LOG_SETTINGS["ui_scaling"]): + self.overlay.updateUiScaling(config.OVERLAY_LARGE_LOG_SETTINGS["ui_scaling"] * 0.25, size) + + def startOverlay(self): + self.overlay.startOverlay() + + def shutdownOverlay(self): + self.overlay.shutdownOverlay() + + def startWatchdog(self): + self.th_watchdog = threadFnc(self.watchdog.start) + self.th_watchdog.daemon = True + self.th_watchdog.start() + + def feedWatchdog(self): + self.watchdog.feed() + + def setWatchdogCallback(self, callback): + self.watchdog.setCallback(callback) + + def stopWatchdog(self): + if isinstance(self.th_watchdog, threadFnc): + self.th_watchdog.stop() + self.th_watchdog.join() + self.th_watchdog = None + +model = Model() \ No newline at end of file diff --git a/src-python/models/osc/osc.py b/src-python/models/osc/osc.py new file mode 100644 index 00000000..5336fdb6 --- /dev/null +++ b/src-python/models/osc/osc.py @@ -0,0 +1,105 @@ +import asyncio +from typing import Any +from time import sleep +from threading import Thread +from pythonosc import udp_client, dispatcher, osc_server +from tinyoscquery.queryservice import OSCQueryService +from tinyoscquery.query import OSCQueryBrowser, OSCQueryClient +from tinyoscquery.utility import get_open_udp_port, get_open_tcp_port +from tinyoscquery.shared.node import OSCAccess +from utils import errorLogging + +class OSCHandler: + def __init__(self, ip_address="127.0.0.1", port=9000) -> None: + self.osc_ip_address = ip_address + self.osc_port = port + self.osc_parameter_muteself = "/avatar/parameters/MuteSelf" + self.osc_parameter_chatbox_typing = "/chatbox/typing" + self.osc_parameter_chatbox_input = "/chatbox/input" + self.udp_client = udp_client.SimpleUDPClient(self.osc_ip_address, self.osc_port) + self.osc_server_name = "VRChat-Client" + self.osc_server = None + self.osc_query_service = None + self.osc_query_service_name = "VRCT" + self.osc_server_ip_address = ip_address + self.http_port = None + self.osc_server_port = None + + def setOscIpAddress(self, ip_address:str) -> None: + self.osc_ip_address = ip_address + self.udp_client = udp_client.SimpleUDPClient(self.osc_ip_address, self.osc_port) + + def setOscPort(self, port:int) -> None: + self.osc_port = port + self.udp_client = udp_client.SimpleUDPClient(self.osc_ip_address, self.osc_port) + + # send OSC message typing + def sendTyping(self, flag:bool=False) -> None: + self.udp_client.send_message(self.osc_parameter_chatbox_typing, [flag]) + + # send OSC message + def sendMessage(self, message:str="", notification:bool=True) -> None: + if len(message) > 0: + self.udp_client.send_message(self.osc_parameter_chatbox_input, [f"{message}", True, notification]) + + def getOSCParameterValue(self, address:str) -> Any: + value = None + try: + browser = OSCQueryBrowser() + sleep(1) + service = browser.find_service_by_name(self.osc_server_name) + if service is not None: + osc_query_client = OSCQueryClient(service) + mute_self_node = osc_query_client.query_node(address) + value = mute_self_node.value[0] + browser.zc.close() + browser.browser.cancel() + + except Exception: + errorLogging() + return value + + def getOSCParameterMuteSelf(self) -> bool: + return self.getOSCParameterValue(self.osc_parameter_muteself) + + def receiveOscParameters(self, dict_filter_and_target:dict) -> None: + self.osc_server_port = get_open_udp_port() + self.http_port = get_open_tcp_port() + osc_dispatcher = dispatcher.Dispatcher() + for filter, target in dict_filter_and_target.items(): + osc_dispatcher.map(filter, target) + self.osc_server = osc_server.ThreadingOSCUDPServer((self.osc_server_ip_address, self.osc_server_port), osc_dispatcher, asyncio.get_event_loop()) + Thread(target=self.oscServerServe, daemon=True).start() + + while True: + try: + self.osc_query_service = OSCQueryService(self.osc_query_service_name, self.http_port, self.osc_server_port) + for filter, target in dict_filter_and_target.items(): + self.osc_query_service.advertise_endpoint(filter, access=OSCAccess.READWRITE_VALUE) + break + except Exception: + errorLogging() + sleep(1) + + def oscServerServe(self) -> None: + self.osc_server.serve_forever(2) + + def oscServerStop(self) -> None: + if isinstance(self.osc_server, osc_server.ThreadingOSCUDPServer): + self.osc_server.shutdown() + self.osc_server = None + if isinstance(self.osc_query_service, OSCQueryService): + self.osc_query_service.http_server.shutdown() + self.osc_query_service = None + +if __name__ == "__main__": + handler = OSCHandler() + handler.receiveOscParameters({ + "/avatar/parameters/MuteSelf": print, + }) + sleep(5) + handler.sendTyping(True) + sleep(1) + handler.sendMessage(message="Hello World", notification=True) + sleep(60) + handler.oscServerStop() \ No newline at end of file diff --git a/src-python/models/overlay/overlay.py b/src-python/models/overlay/overlay.py new file mode 100644 index 00000000..71df4c98 --- /dev/null +++ b/src-python/models/overlay/overlay.py @@ -0,0 +1,369 @@ +import os +import ctypes +import time +from psutil import process_iter +from threading import Thread +import openvr +import numpy as np +from PIL import Image +try: + from utils import errorLogging +except ImportError: + def errorLogging(): + import traceback + print(traceback.format_exc()) + +try: + from . import overlay_utils as utils +except ImportError: + import overlay_utils as utils + +def mat34Id(array): + arr = openvr.HmdMatrix34_t() + for i in range(3): + for j in range(4): + arr[i][j] = array[i][j] + return arr + +def getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation): + arr = np.zeros((3, 4)) + rot = utils.euler_to_rotation_matrix((x_rotation, y_rotation, z_rotation)) + + for i in range(3): + for j in range(3): + arr[i][j] = rot[i][j] + + arr[0][3] = x_pos * z_pos + arr[1][3] = y_pos * z_pos + arr[2][3] = - z_pos + return arr + +def getHMDBaseMatrix(): + x_pos = 0.0 + y_pos = -0.4 + z_pos = 1.0 + x_rotation = 0.0 + y_rotation = 0.0 + z_rotation = 0.0 + arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation) + return arr + +def getLeftHandBaseMatrix(): + x_pos = 0.3 + y_pos = 0.1 + z_pos = -0.31 + x_rotation = -65.0 + y_rotation = 165.0 + z_rotation = 115.0 + arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation) + return arr + +def getRightHandBaseMatrix(): + x_pos = -0.3 + y_pos = 0.1 + z_pos = -0.31 + x_rotation = -65.0 + y_rotation = -165.0 + z_rotation = -115.0 + arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation) + return arr + +class Overlay: + def __init__(self, settings_dict): + self.system = None + self.overlay = None + self.handle = None + self.init_process = False + self.initialized = False + self.loop = False + self.thread_overlay = None + + self.settings = {} + self.lastUpdate = {} + self.fadeRatio = {} + for key, value in settings_dict.items(): + self.settings[key] = value + self.lastUpdate[key] = time.monotonic() + self.fadeRatio[key] = 1 + + def init(self): + try: + self.system = openvr.init(openvr.VRApplication_Background) + self.overlay = openvr.IVROverlay() + self.overlay_system = openvr.IVRSystem() + self.handle = {} + for i, size in enumerate(self.settings.keys()): + self.handle[size] = self.overlay.createOverlay(f"VRCT{i}", f"VRCT{i}") + self.overlay.showOverlay(self.handle[size]) + self.initialized = True + + for size in self.settings.keys(): + self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)), size) + self.updateColor([1, 1, 1], size) + self.updateOpacity(self.settings[size]["opacity"], size) + self.updateUiScaling(self.settings[size]["ui_scaling"], size) + self.updatePosition( + self.settings[size]["x_pos"], + self.settings[size]["y_pos"], + self.settings[size]["z_pos"], + self.settings[size]["x_rotation"], + self.settings[size]["y_rotation"], + self.settings[size]["z_rotation"], + self.settings[size]["tracker"], + size + ) + self.updateDisplayDuration(self.settings[size]["display_duration"], size) + self.updateFadeoutDuration(self.settings[size]["fadeout_duration"], size) + self.init_process = False + + except Exception: + errorLogging() + + def updateImage(self, img, size): + if self.initialized is True: + width, height = img.size + img = img.tobytes() + img = (ctypes.c_char * len(img)).from_buffer_copy(img) + + try: + self.overlay.setOverlayRaw(self.handle[size], img, width, height, 4) + except Exception: + errorLogging() + self.reStartOverlay() + while self.initialized is False: + time.sleep(0.1) + self.overlay.setOverlayRaw(self.handle[size], img, width, height, 4) + self.updateOpacity(self.settings[size]["opacity"], size) + self.lastUpdate[size] = time.monotonic() + + def clearImage(self, size): + if self.initialized is True: + self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)), size) + + def updateColor(self, col, size): + """ + col is a 3-tuple representing (r, g, b) + """ + if self.initialized is True: + r, g, b = col + self.overlay.setOverlayColor(self.handle[size], r, g, b) + + def updateOpacity(self, opacity, size, with_fade=False): + self.settings[size]["opacity"] = opacity + + if self.initialized is True: + if with_fade is True: + if self.fadeRatio[size] > 0: + self.overlay.setOverlayAlpha(self.handle[size], self.fadeRatio[size] * self.settings[size]["opacity"]) + else: + self.overlay.setOverlayAlpha(self.handle[size], self.settings[size]["opacity"]) + + def updateUiScaling(self, ui_scaling, size): + self.settings[size]["ui_scaling"] = ui_scaling + if self.initialized is True: + self.overlay.setOverlayWidthInMeters(self.handle[size], self.settings[size]["ui_scaling"]) + + def updatePosition(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, tracker, size): + """ + x_pos, y_pos, z_pos are floats representing the position of overlay + x_rotation, y_rotation, z_rotation are floats representing the rotation of overlay + tracker is a string representing the tracker to use ("HMD", "LeftHand", "RightHand") + """ + + self.settings[size]["x_pos"] = x_pos + self.settings[size]["y_pos"] = y_pos + self.settings[size]["z_pos"] = z_pos + self.settings[size]["x_rotation"] = x_rotation + self.settings[size]["y_rotation"] = y_rotation + self.settings[size]["z_rotation"] = z_rotation + self.settings[size]["tracker"] = tracker + + if self.initialized is True: + match tracker: + case "HMD": + base_matrix = getHMDBaseMatrix() + trackerIndex = openvr.k_unTrackedDeviceIndex_Hmd + case "LeftHand": + base_matrix = getLeftHandBaseMatrix() + trackerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_LeftHand) + case "RightHand": + base_matrix = getRightHandBaseMatrix() + trackerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_RightHand) + case _: + base_matrix = getHMDBaseMatrix() + trackerIndex = openvr.k_unTrackedDeviceIndex_Hmd + + translation = (self.settings[size]["x_pos"], self.settings[size]["y_pos"], - self.settings[size]["z_pos"]) + rotation = (self.settings[size]["x_rotation"], self.settings[size]["y_rotation"], self.settings[size]["z_rotation"]) + transform = utils.transform_matrix(base_matrix, translation, rotation) + transform = mat34Id(transform) + + self.overlay.setOverlayTransformTrackedDeviceRelative( + self.handle[size], + trackerIndex, + transform + ) + + def updateDisplayDuration(self, display_duration, size): + self.settings[size]["display_duration"] = display_duration + + def updateFadeoutDuration(self, fadeout_duration, size): + self.settings[size]["fadeout_duration"] = fadeout_duration + + def checkActive(self): + try: + if self.system is not None and self.initialized is True: + new_event = openvr.VREvent_t() + while self.system.pollNextEvent(new_event): + if new_event.eventType == openvr.VREvent_Quit: + return False + return True + except Exception: + errorLogging() + return False + + def evaluateOpacityFade(self, size): + currentTime = time.monotonic() + if (currentTime - self.lastUpdate[size]) > self.settings[size]["display_duration"]: + timeThroughInterval = currentTime - self.lastUpdate[size] - self.settings[size]["display_duration"] + self.fadeRatio[size] = 1 - timeThroughInterval / self.settings[size]["fadeout_duration"] + if self.fadeRatio[size] < 0: + self.fadeRatio[size] = 0 + self.overlay.setOverlayAlpha(self.handle[size], self.fadeRatio[size] * self.settings[size]["opacity"]) + + def update(self, size): + if self.settings[size]["fadeout_duration"] != 0: + self.evaluateOpacityFade(size) + else: + self.updateOpacity(self.settings[size]["opacity"], size) + + def mainloop(self): + self.loop = True + while self.checkActive() is True and self.loop is True: + startTime = time.monotonic() + for size in self.settings.keys(): + self.update(size) + sleepTime = (1 / 16) - (time.monotonic() - startTime) + if sleepTime > 0: + time.sleep(sleepTime) + + def main(self): + while self.checkSteamvrRunning() is False: + time.sleep(10) + self.init() + if self.initialized is True: + self.mainloop() + + def startOverlay(self): + if self.initialized is False and self.init_process is False: + self.init_process = True + self.thread_overlay = Thread(target=self.main) + self.thread_overlay.daemon = True + self.thread_overlay.start() + + def shutdownOverlay(self): + if self.initialized is True and self.init_process is False: + if isinstance(self.thread_overlay, Thread): + self.loop = False + self.thread_overlay.join() + self.thread_overlay = None + if isinstance(self.overlay, openvr.IVROverlay): + for size in self.settings.keys(): + if isinstance(self.handle[size], int): + self.overlay.destroyOverlay(self.handle[size]) + self.overlay = None + if isinstance(self.system, openvr.IVRSystem): + openvr.shutdown() + self.system = None + self.initialized = False + + def reStartOverlay(self): + self.shutdownOverlay() + self.startOverlay() + + @staticmethod + def checkSteamvrRunning() -> bool: + _proc_name = "vrmonitor.exe" if os.name == "nt" else "vrmonitor" + return _proc_name in (p.name() for p in process_iter()) + +if __name__ == "__main__": + from overlay_image import OverlayImage + import logging + + logging.basicConfig(level=logging.DEBUG) + + small_settings = { + "x_pos": 0.0, + "y_pos": 0.0, + "z_pos": 0.0, + "x_rotation": 0.0, + "y_rotation": 0.0, + "z_rotation": 0.0, + "display_duration": 5, + "fadeout_duration": 2, + "opacity": 1.0, + "ui_scaling": 1.0, + "tracker": "HMD", + } + + large_settings = { + "x_pos": 0.0, + "y_pos": 0.0, + "z_pos": 0.0, + "x_rotation": 0.0, + "y_rotation": 0.0, + "z_rotation": 0.0, + "display_duration": 5, + "fadeout_duration": 2, + "opacity": 1.0, + "ui_scaling": 0.25, + "tracker": "LeftHand", + } + + settings_dict = { + "small": small_settings, + "large": large_settings + } + + # オーバーレイの初期化設定を確認 + logging.debug(f"Settings Dict: {settings_dict}") + + overlay_image = OverlayImage() + overlay = Overlay(settings_dict) + overlay.startOverlay() + + while overlay.initialized is False: + time.sleep(1) + + # Example usage + for i in range(1000): + try: + print(i) + img = overlay_image.createOverlayImageLargeLog("send", f"こんにちは、世界!さようなら {i}", "Japanese", "Hello,World!Goodbye", "Japanese") + logging.debug(f"Generated Image: {img}") + overlay.updateImage(img, "large") + img = overlay_image.createOverlayImageSmallLog(f"こんにちは、世界!さようなら_{i}", "Japanese", "Hello,World!Goodbye", "Japanese") + overlay.updateImage(img, "small") + time.sleep(1) + except openvr.error_code.OverlayError_InvalidParameter as e: + errorLogging() + logging.error(f"OverlayError_InvalidParameter: {e}") + break + except Exception as e: + errorLogging() + logging.error(f"Unexpected error: {e}") + break + + # for i in range(100): + # print(i) + # # Example usage + # img = overlay_image.createOverlayImageSmallLog(f"こんにちは、世界!さようなら_{i}", "Japanese", "Hello,World!Goodbye", "Japanese") + # overlay.updateImage(img, "small") + # time.sleep(5) + + # if i%2 == 0: + # overlay.updatePosition(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, "HMD", "small") + # else: + # overlay.updatePosition(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, "RightHand", "small") + + overlay.shutdownOverlay() \ No newline at end of file diff --git a/src-python/models/overlay/overlay_image.py b/src-python/models/overlay/overlay_image.py new file mode 100644 index 00000000..f9ddf201 --- /dev/null +++ b/src-python/models/overlay/overlay_image.py @@ -0,0 +1,263 @@ +from os import path as os_path +from datetime import datetime +from typing import Tuple +from PIL import Image, ImageDraw, ImageFont +try: + from utils import errorLogging +except ImportError: + def errorLogging(): + import traceback + print(traceback.format_exc()) + +class OverlayImage: + LANGUAGES = { + "Japanese": "NotoSansJP-Regular", + "Korean": "NotoSansKR-Regular", + "Chinese Simplified": "NotoSansSC-Regular", + "Chinese Traditional": "NotoSansTC-Regular", + } + + def __init__(self): + self.message_log = [] + + @staticmethod + def concatenateImagesVertically(img1: Image, img2: Image, margin: int = 0) -> Image: + total_height = img1.height + img2.height + margin + dst = Image.new("RGBA", (img1.width, total_height)) + dst.paste(img1, (0, 0)) + dst.paste(img2, (0, img1.height + margin)) + return dst + + @staticmethod + def addImageMargin(image: Image, top: int, right: int, bottom: int, left: int, color: Tuple[int, int, int, int]) -> Image: + new_width = image.width + right + left + new_height = image.height + top + bottom + result = Image.new(image.mode, (new_width, new_height), color) + result.paste(image, (left, top)) + return result + + @staticmethod + def getUiSizeSmallLog() -> dict: + return { + "width": 3840, + "height": 92, + "font_size": 92, + } + + @staticmethod + def getUiColorSmallLog() -> dict: + colors = { + "background_color": (41, 42, 45), + "background_outline_color": (41, 42, 45), + "text_color": (223, 223, 223) + } + return colors + + def createTextboxSmallLog(self, text:str, language:str, text_color:tuple, base_width:int, base_height:int, font_size:int) -> Image: + font_family = self.LANGUAGES.get(language, "NotoSansJP-Regular") + img = Image.new("RGBA", (base_width, base_height), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + + try: + font_path = os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "fonts", f"{font_family}.ttf") + font = ImageFont.truetype(font_path, font_size) + except Exception: + errorLogging() + font_path = os_path.join(os_path.dirname(__file__), "..", "..", "..", "fonts", f"{font_family}.ttf") + font = ImageFont.truetype(font_path, font_size) + + text_width = draw.textlength(text, font) + character_width = text_width // len(text) + character_line_num = (base_width // character_width) - 12 + if len(text) > character_line_num: + text = "\n".join([text[i:i + character_line_num] for i in range(0, len(text), character_line_num)]) + text_height = font_size * (len(text.split("\n")) + 1) + 20 + img = Image.new("RGBA", (base_width, text_height), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + + text_x = base_width // 2 + text_y = text_height // 2 + draw.text((text_x, text_y), text, text_color, anchor="mm", stroke_width=0, font=font, align="center") + return img + + def createOverlayImageSmallLog(self, message:str, your_language:str, translation:str="", target_language:str=None) -> Image: + ui_size = self.getUiSizeSmallLog() + width, height, font_size = ui_size["width"], ui_size["height"], ui_size["font_size"] + + ui_colors = self.getUiColorSmallLog() + text_color = ui_colors["text_color"] + background_color = ui_colors["background_color"] + background_outline_color = ui_colors["background_outline_color"] + + img = self.createTextboxSmallLog(message, your_language, text_color, width, height, font_size) + if translation and target_language: + translation_img = self.createTextboxSmallLog(translation, target_language, text_color, width, height, font_size) + img = self.concatenateImagesVertically(img, translation_img) + + background = Image.new("RGBA", img.size, (0, 0, 0, 0)) + draw = ImageDraw.Draw(background) + draw.rounded_rectangle([(0, 0), img.size], radius=50, fill=background_color, outline=background_outline_color, width=5) + + return Image.alpha_composite(background, img) + + @staticmethod + def getUiSizeLargeLog() -> dict: + return { + "width": 960, + "font_size_large": 30, + "font_size_small": 20, + "margin": 25, + "radius": 25, + "padding": 10, + "clause_margin": 20, + } + + @staticmethod + def getUiColorLargeLog() -> dict: + return { + "background_color": (41, 42, 45), + "background_outline_color": (41, 42, 45), + "text_color_large": (223, 223, 223), + "text_color_small": (190, 190, 190), + "text_color_send": (97, 151, 180), + "text_color_receive": (168, 97, 180), + "text_color_time": (120, 120, 120) + } + + def createTextImageLargeLog(self, message_type:str, size:str, text:str, language:str) -> Image: + ui_size = self.getUiSizeLargeLog() + font_size = ui_size["font_size_large"] if size == "large" else ui_size["font_size_small"] + text_color = self.getUiColorLargeLog()[f"text_color_{size}"] + anchor = "lm" if message_type == "receive" else "rm" + text_x = 0 if message_type == "receive" else ui_size["width"] + align = "left" if message_type == "receive" else "right" + font_family = self.LANGUAGES.get(language, "NotoSansJP-Regular") + + img = Image.new("RGBA", (0, 0), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + + try: + font_path = os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "fonts", f"{font_family}.ttf") + font = ImageFont.truetype(font_path, font_size) + except Exception: + errorLogging() + font_path = os_path.join(os_path.dirname(__file__), "..", "..", "..", "fonts", f"{font_family}.ttf") + font = ImageFont.truetype(font_path, font_size) + + text_width = draw.textlength(text, font) + character_width = text_width // len(text) + character_line_num = int((ui_size["width"] // character_width) - 1) + if len(text) > character_line_num: + text = "\n".join([text[i:i + character_line_num] for i in range(0, len(text), character_line_num)]) + text_height = font_size * len(text.split("\n")) + ui_size["padding"] + img = Image.new("RGBA", (ui_size["width"], text_height), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + text_y = text_height // 2 + draw.multiline_text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font, align=align) + return img + + def createTextImageMessageType(self, message_type:str, date_time:str) -> Image: + ui_size = self.getUiSizeLargeLog() + font_size = ui_size["font_size_small"] + ui_padding = ui_size["padding"] + + ui_color = self.getUiColorLargeLog() + text_color = ui_color[f"text_color_{message_type}"] + text_color_time = ui_color["text_color_time"] + + anchor = "lm" if message_type == "receive" else "rm" + text = "Receive" if message_type == "receive" else "Send" + + img = Image.new("RGBA", (0, 0), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + + try: + font_path = os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "fonts", "NotoSansJP-Regular.ttf") + font = ImageFont.truetype(font_path, font_size) + except Exception: + errorLogging() + font_path = os_path.join(os_path.dirname(__file__), "..", "..", "..", "fonts", "NotoSansJP-Regular.ttf") + font = ImageFont.truetype(font_path, font_size) + + text_height = font_size + ui_padding + text_width = draw.textlength(date_time, font) + character_width = text_width // len(date_time) + img = Image.new("RGBA", (ui_size["width"], text_height), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + text_y = text_height // 2 + text_time_x = 0 if message_type == "receive" else ui_size["width"] - (text_width + character_width) + text_x = (text_width + character_width) if message_type == "receive" else ui_size["width"] + draw.text((text_time_x, text_y), date_time, text_color_time, anchor=anchor, stroke_width=0, font=font) + draw.text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font) + return img + + def createTextboxLargeLog(self, message_type:str, message:str, your_language:str, translation:str, target_language:str, date_time:str) -> Image: + message_type_img = self.createTextImageMessageType(message_type, date_time) + if translation and target_language: + img = self.createTextImageLargeLog(message_type, "small", message, your_language) + translation_img = self.createTextImageLargeLog(message_type, "large", translation, target_language) + img = self.concatenateImagesVertically(img, translation_img) + else: + img = self.createTextImageLargeLog(message_type, "large", message, your_language) + return self.concatenateImagesVertically(message_type_img, img) + + def createOverlayImageLargeLog(self, message_type:str, message:str, your_language:str, translation:str="", target_language:str=None) -> Image: + ui_color = self.getUiColorLargeLog() + background_color = ui_color["background_color"] + background_outline_color = ui_color["background_outline_color"] + + ui_size = self.getUiSizeLargeLog() + ui_margin = ui_size["margin"] + ui_radius = ui_size["radius"] + ui_clause_margin = ui_size["clause_margin"] + + self.message_log.append({ + "message_type": message_type, + "message": message, + "your_language": your_language, + "translation": translation, + "target_language": target_language, + "datetime": datetime.now().strftime("%H:%M") + }) + + if len(self.message_log) > 5: + self.message_log = self.message_log[-5:] + + imgs = [ + self.createTextboxLargeLog( + log["message_type"], + log["message"], + log["your_language"], + log["translation"], + log["target_language"], + log["datetime"]) for log in self.message_log + ] + + img = imgs[0] + for i in imgs[1:]: + img = self.concatenateImagesVertically(img, i, ui_clause_margin) + img = self.addImageMargin(img, ui_margin, ui_margin, ui_margin, ui_margin, (0, 0, 0, 0)) + + width, height = img.size + background = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + draw = ImageDraw.Draw(background) + draw.rounded_rectangle([(0, 0), (width, height)], radius=ui_radius, fill=background_color, outline=background_outline_color, width=5) + return Image.alpha_composite(background, img) + +if __name__ == "__main__": + overlay = OverlayImage() + img = overlay.createOverlayImageSmallLog("Hello, World!", "English", "こんにちは、世界!", "Japanese") + img.save("overlay_small.png") + img = overlay.createOverlayImageLargeLog("send", "Hello, World!", "English", "こんにちは、世界!", "Japanese") + img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", "Hello, World!", "English") + img = overlay.createOverlayImageLargeLog("send", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English", "aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああこんにちは、世界!", "Japanese") + img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "Japanese", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English") + img = overlay.createOverlayImageLargeLog("send", "Hello, World!", "English", "こんにちは、世界!", "Japanese") + img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", "Hello, World!", "English") + img = overlay.createOverlayImageLargeLog("send", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English", "aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああこんにちは、世界!", "Japanese") + img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "Japanese", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English") + img = overlay.createOverlayImageLargeLog("send", "Hello, World!", "English", "こんにちは、世界!", "Japanese") + img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", "Hello, World!", "English") + img = overlay.createOverlayImageLargeLog("send", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English", "aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああこんにちは、世界!", "Japanese") + img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "Japanese", "Hello, World!aaaaaaaaaaaaaaaaaあああああああああああああああaaaaaaaaaaaaaaaaaあああああああああああああああ", "English") + img.save("overlay_large.png") \ No newline at end of file diff --git a/models/overlay/overlay_utils.py b/src-python/models/overlay/overlay_utils.py similarity index 100% rename from models/overlay/overlay_utils.py rename to src-python/models/overlay/overlay_utils.py diff --git a/models/transcription/transcription_languages.py b/src-python/models/transcription/transcription_languages.py similarity index 100% rename from models/transcription/transcription_languages.py rename to src-python/models/transcription/transcription_languages.py diff --git a/models/transcription/transcription_recorder.py b/src-python/models/transcription/transcription_recorder.py similarity index 82% rename from models/transcription/transcription_recorder.py rename to src-python/models/transcription/transcription_recorder.py index 0e9b147d..f30c071f 100644 --- a/models/transcription/transcription_recorder.py +++ b/src-python/models/transcription/transcription_recorder.py @@ -91,10 +91,12 @@ class SelectedSpeakerEnergyRecorder(BaseEnergyRecorder): # self.adjustForNoise() class BaseEnergyAndAudioRecorder: - def __init__(self, source, energy_threshold, dynamic_energy_threshold, record_timeout): + def __init__(self, source, energy_threshold, dynamic_energy_threshold, phrase_time_limit, phrase_timeout, record_timeout): self.recorder = Recognizer() self.recorder.energy_threshold = energy_threshold self.recorder.dynamic_energy_threshold = dynamic_energy_threshold + self.phrase_time_limit = phrase_time_limit + self.phrase_timeout = phrase_timeout self.record_timeout = record_timeout self.stop = None @@ -117,20 +119,29 @@ class BaseEnergyAndAudioRecorder: self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background( source=self.source, callback=audioRecordCallback, - phrase_time_limit=self.record_timeout, - callback_energy=energyRecordCallback if energy_queue is not None else None) + phrase_time_limit=self.phrase_time_limit, + callback_energy=energyRecordCallback if energy_queue is not None else None, + phrase_timeout=self.phrase_timeout, + record_timeout=self.record_timeout) class SelectedMicEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): - def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): + def __init__(self, device, energy_threshold, dynamic_energy_threshold, phrase_time_limit, phrase_timeout:int=1, record_timeout:int=5): source=Microphone( device_index=device['index'], sample_rate=int(device["defaultSampleRate"]), ) - super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout) + super().__init__( + source=source, + energy_threshold=energy_threshold, + dynamic_energy_threshold=dynamic_energy_threshold, + phrase_time_limit=phrase_time_limit, + phrase_timeout=phrase_timeout, + record_timeout=record_timeout, + ) # self.adjustForNoise() class SelectedSpeakerEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): - def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): + def __init__(self, device, energy_threshold, dynamic_energy_threshold, phrase_time_limit, phrase_timeout:int=1, record_timeout:int=5): source = Microphone(speaker=True, device_index= device["index"], @@ -138,5 +149,12 @@ class SelectedSpeakerEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): chunk_size=get_sample_size(paInt16), channels=device["maxInputChannels"] ) - super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout) + super().__init__( + source=source, + energy_threshold=energy_threshold, + dynamic_energy_threshold=dynamic_energy_threshold, + phrase_time_limit=phrase_time_limit, + phrase_timeout=phrase_timeout, + record_timeout=record_timeout, + ) # self.adjustForNoise() \ No newline at end of file diff --git a/models/transcription/transcription_transcriber.py b/src-python/models/transcription/transcription_transcriber.py similarity index 59% rename from models/transcription/transcription_transcriber.py rename to src-python/models/transcription/transcription_transcriber.py index a535cd8a..5407253a 100644 --- a/models/transcription/transcription_transcriber.py +++ b/src-python/models/transcription/transcription_transcriber.py @@ -3,6 +3,7 @@ from io import BytesIO from threading import Event import wave from speech_recognition import Recognizer, AudioData, AudioFile +from speech_recognition.exceptions import UnknownValueError from datetime import timedelta from pyaudiowpatch import get_sample_size, paInt16 from .transcription_languages import transcription_lang @@ -11,12 +12,16 @@ from .transcription_whisper import getWhisperModel, checkWhisperWeight import torch import numpy as np from pydub import AudioSegment +from utils import errorLogging + +import warnings +warnings.simplefilter('ignore', RuntimeWarning) PHRASE_TIMEOUT = 3 MAX_PHRASES = 10 class AudioTranscriber: - def __init__(self, speaker, source, phrase_timeout, max_phrases, transcription_engine, root=None, whisper_weight_type=None): + def __init__(self, speaker, source, phrase_timeout, max_phrases, transcription_engine, root=None, whisper_weight_type=None, device="cpu", device_index=0): self.speaker = speaker self.phrase_timeout = phrase_timeout self.max_phrases = max_phrases @@ -36,50 +41,69 @@ class AudioTranscriber: } if transcription_engine == "Whisper" and checkWhisperWeight(root, whisper_weight_type) is True: - self.whisper_model = getWhisperModel(root, whisper_weight_type) + self.whisper_model = getWhisperModel(root, whisper_weight_type, device=device, device_index=device_index) self.transcription_engine = "Whisper" - def transcribeAudioQueue(self, audio_queue, language, country, avg_logprob=-0.8, no_speech_prob=0.6): + def transcribeAudioQueue(self, audio_queue, languages, countries, avg_logprob=-0.8, no_speech_prob=0.6): if audio_queue.empty(): time.sleep(0.01) return False audio, time_spoken = audio_queue.get() self.updateLastSampleAndPhraseStatus(audio, time_spoken) - text = '' + confidences = [{"confidence": 0, "text": "", "language": None}] try: audio_data = self.audio_sources["process_data_func"]() match self.transcription_engine: case "Google": - text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country][self.transcription_engine]) + for language, country in zip(languages, countries): + try: + text, confidence = self.audio_recognizer.recognize_google( + audio_data, + language=transcription_lang[language][country][self.transcription_engine], + with_confidence=True + ) + confidences.append({"confidence": confidence, "text": text, "language": language}) + except Exception: + pass case "Whisper": audio_data = np.frombuffer(audio_data.get_raw_data(convert_rate=16000, convert_width=2), np.int16).flatten().astype(np.float32) / 32768.0 if isinstance(audio_data, torch.Tensor): audio_data = audio_data.detach().numpy() - segments, _ = self.whisper_model.transcribe( - audio_data, - beam_size=5, - temperature=0.0, - log_prob_threshold=-0.8, - no_speech_threshold=0.6, - language=transcription_lang[language][country][self.transcription_engine], - word_timestamps=False, - without_timestamps=True, - task="transcribe", - vad_filter=False, - ) - for s in segments: - if s.avg_logprob < avg_logprob or s.no_speech_prob > no_speech_prob: - continue - text += s.text - except Exception: + for language, country in zip(languages, countries): + text = "" + source_language = transcription_lang[language][country][self.transcription_engine] if len(languages) == 1 else None + segments, info = self.whisper_model.transcribe( + audio_data, + beam_size=5, + temperature=0.0, + log_prob_threshold=-0.8, + no_speech_threshold=0.6, + language=source_language, + word_timestamps=False, + without_timestamps=True, + task="transcribe", + vad_filter=False, + ) + for s in segments: + if s.avg_logprob < avg_logprob or s.no_speech_prob > no_speech_prob: + continue + text += s.text + confidences.append({"confidence": info.language_probability, "text": text, "language": language}) + if (len(languages) == 1) or (transcription_lang[language][country][self.transcription_engine] == info.language): + break + + except UnknownValueError: pass + except Exception: + errorLogging() finally: pass - if text != '': - self.updateTranscript(text) + result = max(confidences, key=lambda x: x["confidence"]) + if result["text"] != "": + self.updateTranscript(result) return True def updateLastSampleAndPhraseStatus(self, data, time_spoken): @@ -117,23 +141,23 @@ class AudioTranscriber: audio = self.audio_recognizer.record(source) return audio - def updateTranscript(self, text): + def updateTranscript(self, result): source_info = self.audio_sources transcript = self.transcript_data if source_info["new_phrase"] or len(transcript) == 0: if len(transcript) > self.max_phrases: transcript.pop(-1) - transcript.insert(0, text) + transcript.insert(0, result) else: - transcript[0] = text + transcript[0] = result def getTranscript(self): if len(self.transcript_data) > 0: - text = self.transcript_data.pop(-1) + result = self.transcript_data.pop(-1) else: - text = "" - return text + result = {"confidence": 0, "text": "", "language": None} + return result def clearTranscriptData(self): self.transcript_data.clear() diff --git a/models/transcription/transcription_whisper.py b/src-python/models/transcription/transcription_whisper.py similarity index 61% rename from models/transcription/transcription_whisper.py rename to src-python/models/transcription/transcription_whisper.py index c6412d35..3eb574c7 100644 --- a/models/transcription/transcription_whisper.py +++ b/src-python/models/transcription/transcription_whisper.py @@ -4,6 +4,7 @@ from typing import Callable import huggingface_hub from faster_whisper import WhisperModel import logging + logger = logging.getLogger('faster_whisper') logger.setLevel(logging.CRITICAL) @@ -33,14 +34,13 @@ def downloadFile(url, path, func=None): file_size = int(res.headers.get('content-length', 0)) total_chunk = 0 with open(os_path.join(path), 'wb') as file: - for chunk in res.iter_content(chunk_size=1024*5): + for chunk in res.iter_content(chunk_size=1024*2000): file.write(chunk) if isinstance(func, Callable): total_chunk += len(chunk) func(total_chunk/file_size) - - except Exception as e: - print("error:downloadFile()", e) + except Exception: + pass def checkWhisperWeight(root, weight_type): path = os_path.join(root, "weights", "whisper", weight_type) @@ -60,25 +60,25 @@ def checkWhisperWeight(root, weight_type): pass return result -def downloadWhisperWeight(root, weight_type, callbackFunc): +def downloadWhisperWeight(root, weight_type, callback=None, end_callback=None): path = os_path.join(root, "weights", "whisper", weight_type) os_makedirs(path, exist_ok=True) - if checkWhisperWeight(root, weight_type) is True: - return + if checkWhisperWeight(root, weight_type) is False: + for filename in _FILENAMES: + file_path = os_path.join(path, filename) + url = huggingface_hub.hf_hub_url(_MODELS[weight_type], filename) + downloadFile(url, file_path, func=callback if filename == "model.bin" else None) + if isinstance(end_callback, Callable): + end_callback() - for filename in _FILENAMES: - print("Downloading", filename, "...") - file_path = os_path.join(path, filename) - url = huggingface_hub.hf_hub_url(_MODELS[weight_type], filename) - downloadFile(url, file_path, func=callbackFunc) - -def getWhisperModel(root, weight_type): +def getWhisperModel(root, weight_type, device="cpu", device_index=0): path = os_path.join(root, "weights", "whisper", weight_type) + compute_type = "int8" if device == "cpu" else "float16" return WhisperModel( path, - device="cpu", - device_index=0, - compute_type="int8", + device=device, + device_index=device_index, + compute_type=compute_type, cpu_threads=4, num_workers=1, local_files_only=True, @@ -89,10 +89,14 @@ if __name__ == "__main__": print(value) pass - downloadWhisperWeight("./", "tiny", callback) - downloadWhisperWeight("./", "base", callback) - downloadWhisperWeight("./", "small", callback) - downloadWhisperWeight("./", "medium", callback) - downloadWhisperWeight("./", "large-v1", callback) - downloadWhisperWeight("./", "large-v2", callback) - downloadWhisperWeight("./", "large-v3", callback) \ No newline at end of file + def end_callback(): + print("end") + pass + + downloadWhisperWeight("./", "tiny", callback, end_callback) + downloadWhisperWeight("./", "base", callback, end_callback) + downloadWhisperWeight("./", "small", callback, end_callback) + downloadWhisperWeight("./", "medium", callback, end_callback) + downloadWhisperWeight("./", "large-v1", callback, end_callback) + downloadWhisperWeight("./", "large-v2", callback, end_callback) + downloadWhisperWeight("./", "large-v3", callback, end_callback) \ No newline at end of file diff --git a/models/translation/translation_languages.py b/src-python/models/translation/translation_languages.py similarity index 100% rename from models/translation/translation_languages.py rename to src-python/models/translation/translation_languages.py diff --git a/models/translation/translation_translator.py b/src-python/models/translation/translation_translator.py similarity index 89% rename from models/translation/translation_translator.py rename to src-python/models/translation/translation_translator.py index 56c5cf64..44004155 100644 --- a/models/translation/translation_translator.py +++ b/src-python/models/translation/translation_translator.py @@ -1,4 +1,4 @@ -import os +from os import path as os_path from deepl import Translator as deepl_Translator from translators import translate_text as other_web_Translator from .translation_languages import translation_lang @@ -6,6 +6,10 @@ from .translation_utils import ctranslate2_weights import ctranslate2 import transformers +from utils import errorLogging + +import warnings +warnings.filterwarnings("ignore") # Translator class Translator(): @@ -21,29 +25,32 @@ class Translator(): self.deepl_client = deepl_Translator(authkey) self.deepl_client.translate_text(" ", target_lang="EN-US") except Exception: + errorLogging() self.deepl_client = None result = False return result - def changeCTranslate2Model(self, path, model_type): + def changeCTranslate2Model(self, path, model_type, device="cpu", device_index=0): self.is_loaded_ctranslate2_model = False directory_name = ctranslate2_weights[model_type]["directory_name"] tokenizer = ctranslate2_weights[model_type]["tokenizer"] - weight_path = os.path.join(path, "weights", "ctranslate2", directory_name) - tokenizer_path = os.path.join(path, "weights", "ctranslate2", directory_name, "tokenizer") + weight_path = os_path.join(path, "weights", "ctranslate2", directory_name) + tokenizer_path = os_path.join(path, "weights", "ctranslate2", directory_name, "tokenizer") + + compute_type = "int8" if device == "cpu" else "float16" self.ctranslate2_translator = ctranslate2.Translator( weight_path, - device="cpu", - device_index=0, - compute_type="int8", + device=device, + device_index=device_index, + compute_type=compute_type, inter_threads=1, intra_threads=4 ) try: self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) - except Exception as e: - print("Error: changeCTranslate2Model()", e) - tokenizer_path = os.path.join("./weights", "ctranslate2", directory_name, "tokenizer") + except Exception: + errorLogging() + tokenizer_path = os_path.join("./weights", "ctranslate2", directory_name, "tokenizer") self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) self.is_loaded_ctranslate2_model = True @@ -61,7 +68,7 @@ class Translator(): target = results[0].hypotheses[0][1:] result = self.ctranslate2_tokenizer.decode(self.ctranslate2_tokenizer.convert_tokens_to_ids(target)) except Exception: - pass + errorLogging() return result @staticmethod @@ -133,8 +140,6 @@ class Translator(): target_language=target_language, ) except Exception: - import traceback - with open('error.log', 'a') as f: - traceback.print_exc(file=f) + errorLogging() result = False return result \ No newline at end of file diff --git a/models/translation/translation_utils.py b/src-python/models/translation/translation_utils.py similarity index 66% rename from models/translation/translation_utils.py rename to src-python/models/translation/translation_utils.py index 308a3eab..47c53e05 100644 --- a/models/translation/translation_utils.py +++ b/src-python/models/translation/translation_utils.py @@ -5,9 +5,10 @@ from os import makedirs as os_makedirs from requests import get as requests_get from typing import Callable import hashlib +from utils import errorLogging ctranslate2_weights = { - "Small": { # M2M-100 418M-parameter model + "small": { # M2M-100 418M-parameter model "url": "https://github.com/misyaguziya/VRCT-weights/releases/download/v1.0/m2m100_418m.zip", "directory_name": "m2m100_418m", "tokenizer": "facebook/m2m100_418M", @@ -17,7 +18,7 @@ ctranslate2_weights = { "shared_vocabulary.txt": "bd440aa21b8ca3453fc792a0018a1f3fe68b3464aadddd4d16a4b72f73c86d8c", } }, - "Large": { # M2M-100 1.2B-parameter model + "large": { # M2M-100 1.2B-parameter model "url": "https://github.com/misyaguziya/VRCT-weights/releases/download/v1.0/m2m100_12b.zip", "directory_name": "m2m100_12b", "tokenizer": "facebook/m2m100_1.2b", @@ -38,7 +39,7 @@ def calculate_file_hash(file_path, block_size=65536): return hash_object.hexdigest() -def checkCTranslate2Weight(path, weight_type="Small"): +def checkCTranslate2Weight(root, weight_type="small"): weight_directory_name = ctranslate2_weights[weight_type]["directory_name"] hash_data = ctranslate2_weights[weight_type]["hash"] files = [ @@ -46,6 +47,7 @@ def checkCTranslate2Weight(path, weight_type="Small"): "sentencepiece.model", "shared_vocabulary.txt" ] + path = os_path.join(root, "weights", "ctranslate2") # check already downloaded already_downloaded = False @@ -59,28 +61,29 @@ def checkCTranslate2Weight(path, weight_type="Small"): already_downloaded = True return already_downloaded -def downloadCTranslate2Weight(root, weight_type="Small", func=None): +def downloadCTranslate2Weight(root, weight_type="small", callback=None, end_callback=None): url = ctranslate2_weights[weight_type]["url"] filename = "weight.zip" path = os_path.join(root, "weights", "ctranslate2") os_makedirs(path, exist_ok=True) - if checkCTranslate2Weight(path, weight_type): - return + if checkCTranslate2Weight(root, weight_type) is False: + try: + with tempfile.TemporaryDirectory() as tmp_path: + res = requests_get(url, stream=True) + file_size = int(res.headers.get('content-length', 0)) + total_chunk = 0 + with open(os_path.join(tmp_path, filename), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024*2000): + file.write(chunk) + if isinstance(callback, Callable): + total_chunk += len(chunk) + callback(total_chunk/file_size) - try: - with tempfile.TemporaryDirectory() as tmp_path: - res = requests_get(url, stream=True) - file_size = int(res.headers.get('content-length', 0)) - total_chunk = 0 - with open(os_path.join(tmp_path, filename), 'wb') as file: - for chunk in res.iter_content(chunk_size=1024*5): - file.write(chunk) - if isinstance(func, Callable): - total_chunk += len(chunk) - func(total_chunk/file_size) + with ZipFile(os_path.join(tmp_path, filename)) as zf: + zf.extractall(path) + except Exception: + errorLogging() - with ZipFile(os_path.join(tmp_path, filename)) as zf: - zf.extractall(path) - except Exception as e: - print("error:downloadCTranslate2Weight()", e) \ No newline at end of file + if isinstance(end_callback, Callable): + end_callback() \ No newline at end of file diff --git a/src-python/models/watchdog/watchdog.py b/src-python/models/watchdog/watchdog.py new file mode 100644 index 00000000..73803e10 --- /dev/null +++ b/src-python/models/watchdog/watchdog.py @@ -0,0 +1,20 @@ +from typing import Callable +import time + +class Watchdog: + def __init__(self, timeout:int=60, interval:int=20): + self.timeout = timeout + self.interval = interval + self.last_feed_time = time.time() + + def feed(self): + self.last_feed_time = time.time() + + def setCallback(self, callback): + self.callback = callback + + def start(self): + if time.time() - self.last_feed_time > self.timeout: + if isinstance(self.callback, Callable): + self.callback() + time.sleep(self.interval) \ No newline at end of file diff --git a/src-python/utils.py b/src-python/utils.py new file mode 100644 index 00000000..31d589bd --- /dev/null +++ b/src-python/utils.py @@ -0,0 +1,131 @@ +import base64 +from typing import Any +import json +import random +from typing import Union +from os import path as os_path, rename as os_rename +import traceback +import logging +from PIL.Image import open as Image_open + +def getImageFile(file_name): + img = Image_open(os_path.join(os_path.dirname(__file__), "img", file_name)) + return img + +def callFunctionIfCallable(function, *args): + if callable(function) is True: + function(*args) + +def isEven(number): + return number % 2 == 0 + +def makeEven(number, minus:bool=False): + if minus is True: + return number if isEven(number) else number - 1 + return number if isEven(number) else number + 1 + +def intToPctStr(value:int): + return f"{value}%" + +def floatToPctStr(value:float): + return f"{int(value*100)}%" + +def strPctToInt(value:str): + return int(value.replace("%", "")) + +def isUniqueStrings(unique_strings:Union[str, list], input_string:str, require=False): + import re + if isinstance(unique_strings, str): + unique_strings = [unique_strings] + patterns = [re.escape(s) for s in unique_strings] + + counts = [len(re.findall(pattern, input_string)) for pattern in patterns] + + if require is True: + # If require is True, unique_strings must appear once + return all(count == 1 for count in counts) and counts.count(1) == 2 + else: + # If require is False, check if unique strings are used exactly once + return all(count == 1 for count in counts) + +# path先のweightフォルダがある場合にはそのフォルダ名をweightsに変更する +def renameWeightFolder(path): + weight_path = os_path.join(path, "weight") + if os_path.exists(weight_path): + os_rename(weight_path, os_path.join(path, "weights")) + +def splitList(lst:list, split_count:int, to_shuffle:bool=False): + if to_shuffle is True: + random.shuffle(lst) + + split_lists = [] + for i in range(0, len(lst), split_count): + sub_list = lst[i:i+split_count] + split_lists.append(sub_list) + return split_lists + +def encodeBase64(data:str) -> dict: + return json.loads(base64.b64decode(data).decode('utf-8')) + +def removeLog(): + with open('process.log', 'w', encoding="utf-8") as f: + f.write("") + +def setupLogger(name, log_file, level=logging.INFO): + """ + 特定の名前とログファイルを持つロガーを設定します。 + """ + # ロガーを作成 + logger = logging.getLogger(name) + logger.setLevel(level) + logger.propagate = False # 親ロガーへの伝播を防ぐ + + # ハンドラーを作成 + file_handler = logging.FileHandler(log_file, encoding="utf-8", delay=True) + file_handler.setLevel(level) + + # フォーマッターを設定 + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + + # ロガーにハンドラーを追加 + logger.addHandler(file_handler) + + return logger + +process_logger = None +def printLog(log:str, data:Any=None) -> None: + global process_logger + if process_logger is None: + process_logger = setupLogger("process", "process.log", logging.INFO) + + response = { + "status": 348, + "log": log, + "data": str(data), + } + process_logger.info(response) + response = json.dumps(response) + print(response, flush=True) + +def printResponse(status:int, endpoint:str, result:Any=None) -> None: + global process_logger + if process_logger is None: + process_logger = setupLogger("process", "process.log", logging.INFO) + + response = { + "status": status, + "endpoint": endpoint, + "result": result, + } + process_logger.info(response) + response = json.dumps(response) + print(response, flush=True) + +error_logger = None +def errorLogging() -> None: + global error_logger + if error_logger is None: + error_logger = setupLogger("error", "error.log", logging.ERROR) + + error_logger.error(traceback.format_exc()) \ No newline at end of file diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore new file mode 100644 index 00000000..a52506d9 --- /dev/null +++ b/src-tauri/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas + + +# Customize +/bin \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 00000000..6291f841 --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,3985 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "VRCT" +version = "0.0.0" +dependencies = [ + "font-kit", + "serde", + "serde_json", + "tauri", + "tauri-build", + "window-shadows", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "atk" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" +dependencies = [ + "atk-sys", + "bitflags 1.3.2", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cairo-rs" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "glib", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "cargo_toml" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" +dependencies = [ + "serde", + "toml 0.7.8", +] + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.5", +] + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics 0.22.3", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics 0.23.2", + "foreign-types 0.5.0", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation", + "core-graphics 0.23.2", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.65", +] + +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn 2.0.65", +] + +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.65", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dtoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" + +[[package]] +name = "dtoa-short" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "dwrote" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "embed-resource" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6985554d0688b687c5cb73898a34fbe3ad6c24c58c238a4d91d5e840670ee9d" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.8.2", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "font-kit" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64b34f4efd515f905952d91bc185039863705592c0c53ae6d979805dd154520" +dependencies = [ + "bitflags 2.5.0", + "byteorder", + "core-foundation", + "core-graphics 0.23.2", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +dependencies = [ + "bitflags 1.3.2", + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "gdk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps 6.2.2", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps 6.2.2", +] + +[[package]] +name = "gdkx11-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps 6.2.2", + "x11", +] + +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows 0.48.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "gio" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-io", + "gio-sys", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", + "winapi", +] + +[[package]] +name = "glib" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.15.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" +dependencies = [ + "anyhow", + "heck 0.4.1", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "glib-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +dependencies = [ + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "gobject-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "gtk" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +dependencies = [ + "atk", + "bitflags 1.3.2", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "once_cell", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps 6.2.2", +] + +[[package]] +name = "gtk3-macros" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d" +dependencies = [ + "anyhow", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.11", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.6", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "infer" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc" +dependencies = [ + "cfb", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "javascriptcore-rs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b" +dependencies = [ + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "kuchikiki" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 1.9.3", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + +[[package]] +name = "line-wrap" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "open" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" +dependencies = [ + "pathdiff", + "windows-sys 0.42.0", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pango" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +dependencies = [ + "bitflags 1.3.2", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "parking_lot" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.1", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf07ef4804cfa9aea3b04a7bbdd5a40031dbb6b4f2cbaf2b011666c80c5b4f2" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros 0.11.2", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plist" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" +dependencies = [ + "base64 0.21.7", + "indexmap 2.2.6", + "line-wrap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "indexmap 2.2.6", + "itoa 1.0.11", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_with" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared_child" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "soup2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" +dependencies = [ + "bitflags 1.3.2", + "gio", + "glib", + "libc", + "once_cell", + "soup2-sys", +] + +[[package]] +name = "soup2-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" +dependencies = [ + "bitflags 1.3.2", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" +dependencies = [ + "cfg-expr 0.9.1", + "heck 0.3.3", + "pkg-config", + "toml 0.5.11", + "version-compare 0.0.11", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr 0.15.8", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare 0.2.0", +] + +[[package]] +name = "tao" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575c856fc21e551074869dcfaad8f706412bd5b803dfa0fbf6881c4ff4bfafab" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "cc", + "cocoa 0.24.1", + "core-foundation", + "core-graphics 0.22.3", + "crossbeam-channel", + "dispatch", + "gdk", + "gdk-pixbuf", + "gdk-sys", + "gdkwayland-sys", + "gdkx11-sys", + "gio", + "glib", + "glib-sys", + "gtk", + "image", + "instant", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "png", + "raw-window-handle", + "scopeguard", + "serde", + "tao-macros", + "unicode-segmentation", + "uuid", + "windows 0.39.0", + "windows-implement", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec114582505d158b669b136e6851f85840c109819d77c42bb7c0709f727d18c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tar" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "tauri" +version = "1.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77567d2b3b74de4588d544147142d02297f3eaa171a25a065252141d8597a516" +dependencies = [ + "anyhow", + "cocoa 0.24.1", + "dirs-next", + "dunce", + "embed_plist", + "encoding_rs", + "flate2", + "futures-util", + "getrandom 0.2.15", + "glib", + "glob", + "gtk", + "heck 0.5.0", + "http", + "ignore", + "objc", + "once_cell", + "open", + "os_pipe", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", + "regex", + "semver", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "shared_child", + "state", + "tar", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "tempfile", + "thiserror", + "tokio", + "url", + "uuid", + "webkit2gtk", + "webview2-com", + "windows 0.39.0", +] + +[[package]] +name = "tauri-build" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab30cba12974d0f9b09794f61e72cad6da2142d3ceb81e519321bab86ce53312" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs-next", + "heck 0.5.0", + "json-patch", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a1d90db526a8cdfd54444ad3f34d8d4d58fa5c536463915942393743bd06f8" +dependencies = [ + "base64 0.21.7", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "regex", + "semver", + "serde", + "serde_json", + "sha2", + "tauri-utils", + "thiserror", + "time", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a582d75414250122e4a597b9dd7d3c910a2c77906648fc2ac9353845ff0feec" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 1.0.109", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-runtime" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7ffddf36d450791018e63a3ddf54979b9581d9644c584a5fb5611e6b5f20b4" +dependencies = [ + "gtk", + "http", + "http-range", + "rand 0.8.5", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror", + "url", + "uuid", + "webview2-com", + "windows 0.39.0", +] + +[[package]] +name = "tauri-runtime-wry" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1989b3b4d611f5428b3414a4abae6fa6df30c7eb8ed33250ca90a5f7e5bb3655" +dependencies = [ + "cocoa 0.24.1", + "gtk", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", + "tauri-runtime", + "tauri-utils", + "uuid", + "webkit2gtk", + "webview2-com", + "windows 0.39.0", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "450b17a7102e5d46d4bdabae0d1590fd27953e704e691fc081f06c06d2253b35" +dependencies = [ + "brotli", + "ctor", + "dunce", + "glob", + "heck 0.5.0", + "html5ever", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.2", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "serde_with", + "thiserror", + "url", + "walkdir", + "windows-version", +] + +[[package]] +name = "tauri-winres" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +dependencies = [ + "embed-resource", + "toml 0.7.8", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa 1.0.11", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "bytes", + "num_cpus", + "pin-project-lite", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version-compare" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.65", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "webkit2gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup2", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +dependencies = [ + "atk-sys", + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pango-sys", + "pkg-config", + "soup2-sys", + "system-deps 6.2.2", +] + +[[package]] +name = "webview2-com" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.39.0", + "windows-implement", +] + +[[package]] +name = "webview2-com-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "webview2-com-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" +dependencies = [ + "regex", + "serde", + "serde_json", + "thiserror", + "windows 0.39.0", + "windows-bindgen", + "windows-metadata", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-shadows" +version = "0.2.2" +source = "git+https://github.com/tauri-apps/window-shadows.git#48fa81bbd9553b1b69e61590b4669bfad8b13ec8" +dependencies = [ + "cocoa 0.25.0", + "objc", + "raw-window-handle", + "windows-sys 0.52.0", +] + +[[package]] +name = "windows" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +dependencies = [ + "windows-implement", + "windows_aarch64_msvc 0.39.0", + "windows_i686_gnu 0.39.0", + "windows_i686_msvc 0.39.0", + "windows_x86_64_gnu 0.39.0", + "windows_x86_64_msvc 0.39.0", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-bindgen" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" +dependencies = [ + "windows-metadata", + "windows-tokens", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-implement" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" +dependencies = [ + "syn 1.0.109", + "windows-tokens", +] + +[[package]] +name = "windows-metadata" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows-tokens" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" + +[[package]] +name = "windows-version" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wry" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00711278ed357350d44c749c286786ecac644e044e4da410d466212152383b45" +dependencies = [ + "base64 0.13.1", + "block", + "cocoa 0.24.1", + "core-graphics 0.22.3", + "crossbeam-channel", + "dunce", + "gdk", + "gio", + "glib", + "gtk", + "html5ever", + "http", + "kuchikiki", + "libc", + "log", + "objc", + "objc_id", + "once_cell", + "serde", + "serde_json", + "sha2", + "soup2", + "tao", + "thiserror", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.39.0", + "windows-implement", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 00000000..46da9fa7 --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "VRCT" +version = "0.0.0" +description = "VRCT Application" +authors = ["misyaguziya"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +tauri-build = { version = "1", features = [] } + +[dependencies] +tauri = { version = "1", features = [ "window-set-size", "window-set-position", "window-unmaximize", "window-close", "window-maximize", "window-minimize", "window-unminimize", "window-start-dragging", "window-set-decorations", "window-set-always-on-top", "shell-sidecar", "shell-open", "devtools"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +font-kit = "0.14.2" +window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" } + +[features] +# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! +custom-protocol = ["tauri/custom-protocol"] diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 00000000..2ba80a8b --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 00000000..e3e07f2f Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 00000000..ce0678c5 Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 00000000..ec374b95 Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 00000000..ea0f7561 Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 00000000..37f53912 Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 00000000..4432c4c2 Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 00000000..eb3ca8cb Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 00000000..58900c04 Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 00000000..97c9ebef Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 00000000..b9c36069 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 00000000..a366f5bb Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 00000000..03f4dfb2 Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 00000000..a1eaf5a2 Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 00000000..9f170333 Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 00000000..f10a4374 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 00000000..1baed626 Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/nsis/plugins/x86-unicode/inetc.dll b/src-tauri/nsis/plugins/x86-unicode/inetc.dll new file mode 100644 index 00000000..d867f8c2 Binary files /dev/null and b/src-tauri/nsis/plugins/x86-unicode/inetc.dll differ diff --git a/src-tauri/nsis/plugins/x86-unicode/nsJSON.dll b/src-tauri/nsis/plugins/x86-unicode/nsJSON.dll new file mode 100644 index 00000000..c7999466 Binary files /dev/null and b/src-tauri/nsis/plugins/x86-unicode/nsJSON.dll differ diff --git a/src-tauri/nsis/plugins/x86-unicode/nsisunz.dll b/src-tauri/nsis/plugins/x86-unicode/nsisunz.dll new file mode 100644 index 00000000..2fcb5ec9 Binary files /dev/null and b/src-tauri/nsis/plugins/x86-unicode/nsisunz.dll differ diff --git a/src-tauri/nsis/template.nsi b/src-tauri/nsis/template.nsi new file mode 100644 index 00000000..53e957d3 --- /dev/null +++ b/src-tauri/nsis/template.nsi @@ -0,0 +1,1025 @@ +Unicode true +; Set the compression algorithm. Default is LZMA. +!if "{{compression}}" == "" + SetCompressor /SOLID lzma +!else + SetCompressor /SOLID "{{compression}}" +!endif + +!include MUI2.nsh +!include FileFunc.nsh +!include x64.nsh +!include WordFunc.nsh +!include "StrFunc.nsh" +!include "Win\COM.nsh" +!include "Win\Propkey.nsh" +${StrCase} +${StrLoc} + +!define MANUFACTURER "{{manufacturer}}" +!define PRODUCTNAME "{{product_name}}" +!define FILEDISCRIPTION "{{file_description}}" +!define VERSION "{{version}}" +!define VERSIONWITHBUILD "{{version_with_build}}" +!define INSTALLMODE "{{install_mode}}" +!define LICENSE "{{license}}" +!define INSTALLERICON "{{installer_icon}}" +!define SIDEBARIMAGE "{{sidebar_image}}" +!define HEADERIMAGE "{{header_image}}" +!define MAINBINARYNAME "{{main_binary_name}}" +!define MAINBINARYSRCPATH "{{main_binary_path}}" +!define BUNDLEID "{{bundle_id}}" +!define COPYRIGHT "{{copyright}}" +!define OUTFILE "{{out_file}}" +!define ARCH "{{arch}}" +!define PLUGINSPATH "{{additional_plugins_path}}" +!define ALLOWDOWNGRADES "{{allow_downgrades}}" +!define DISPLAYLANGUAGESELECTOR "{{display_language_selector}}" +!define INSTALLWEBVIEW2MODE "{{install_webview2_mode}}" +!define WEBVIEW2INSTALLERARGS "{{webview2_installer_args}}" +!define WEBVIEW2BOOTSTRAPPERPATH "{{webview2_bootstrapper_path}}" +!define WEBVIEW2INSTALLERPATH "{{webview2_installer_path}}" +!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" +!define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}" +!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}" +!define ESTIMATEDSIZE "{{estimated_size}}" + +Name "${PRODUCTNAME}" +BrandingText "${COPYRIGHT}" +OutFile "${OUTFILE}" + +VIProductVersion "${VERSIONWITHBUILD}" +VIAddVersionKey "ProductName" "${PRODUCTNAME}" +VIAddVersionKey "FileDescription" "${PRODUCTNAME}" +VIAddVersionKey "LegalCopyright" "${COPYRIGHT}" +VIAddVersionKey "FileVersion" "${VERSION}" +VIAddVersionKey "ProductVersion" "${VERSION}" + +; Plugins path, currently exists for linux only +!if "${PLUGINSPATH}" != "" + !addplugindir "${PLUGINSPATH}" +!endif + +!if "${UNINSTALLERSIGNCOMMAND}" != "" + !uninstfinalize '${UNINSTALLERSIGNCOMMAND}' +!endif + +; Handle install mode, `perUser`, `perMachine` or `both` +!if "${INSTALLMODE}" == "perMachine" + RequestExecutionLevel highest +!endif + +!if "${INSTALLMODE}" == "currentUser" + RequestExecutionLevel user +!endif + +!if "${INSTALLMODE}" == "both" + !define MULTIUSER_MUI + !define MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCTNAME}" + !define MULTIUSER_INSTALLMODE_COMMANDLINE + !if "${ARCH}" == "x64" + !define MULTIUSER_USE_PROGRAMFILES64 + !else if "${ARCH}" == "arm64" + !define MULTIUSER_USE_PROGRAMFILES64 + !endif + !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "${UNINSTKEY}" + !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "CurrentUser" + !define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME + !define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation + !define MULTIUSER_EXECUTIONLEVEL Highest + !include MultiUser.nsh +!endif + +; installer icon +!if "${INSTALLERICON}" != "" + !define MUI_ICON "${INSTALLERICON}" +!endif + +; installer sidebar image +!if "${SIDEBARIMAGE}" != "" + !define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBARIMAGE}" +!endif + +; installer header image +!if "${HEADERIMAGE}" != "" + !define MUI_HEADERIMAGE + !define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}" +!endif + +; Define registry key to store installer language +!define MUI_LANGDLL_REGISTRY_ROOT "HKCU" +!define MUI_LANGDLL_REGISTRY_KEY "${MANUPRODUCTKEY}" +!define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language" + +; Installer pages, must be ordered as they appear +; 1. Welcome Page +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive +!insertmacro MUI_PAGE_WELCOME + +; 2. License Page (if defined) +!if "${LICENSE}" != "" + !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive + !insertmacro MUI_PAGE_LICENSE "${LICENSE}" +!endif + +; 3. Install mode (if it is set to `both`) +!if "${INSTALLMODE}" == "both" + !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive + !insertmacro MULTIUSER_PAGE_INSTALLMODE +!endif + +; 4-1. Choose language page +Var DropListLanguages +Var SelectedLangage +Var DialogChooseLanguage +Page custom PageChooseLanguage PageLeaveChooseLanguage +Function PageChooseLanguage + !insertmacro MUI_HEADER_TEXT "Initial Settings" "Set the language of the VRCT UI (can be changed later)." + nsDialogs::Create 1018 + Pop $DialogChooseLanguage + + ${If} $DialogChooseLanguage == error + Abort + ${EndIf} + + ${NSD_CreateLabel} 0 21u 30% 12u "UI Language" + ${NSD_CreateDropList} 33% 20u 33% 12u "" + Pop $DropListLanguages + ${NSD_CB_AddString} $DropListLanguages "English" + ${NSD_CB_AddString} $DropListLanguages "日本語" + ${NSD_CB_AddString} $DropListLanguages "한국어" + ${NSD_CB_AddString} $DropListLanguages "繁體中文" + ${NSD_CB_SelectString} $DropListLanguages "English" + StrCpy $SelectedLangage "en" + nsDialogs::Show +FunctionEnd + +Function PageLeaveChooseLanguage + ${NSD_GetText} $DropListLanguages $0 + ${If} "English" == $0 + StrCpy $SelectedLangage "en" + ${ElseIf} "日本語" == $0 + StrCpy $SelectedLangage "ja" + ${ElseIf} "한국어" == $0 + StrCpy $SelectedLangage "ko" + ${ElseIf} "繁體中文" == $0 + StrCpy $SelectedLangage "zh-Hant" + ${EndIf} +FunctionEnd + +; 4-2. Page Download Translate Model Weight +Var CheckboxUseTranslate +Var DropListCTranslate2DownloadWeightType +Var SelectedCTranslate2DownloadWeightType +Var DialogTranslate +Page custom PageTranslate PageLeaveTranslate +Function PageTranslate + !insertmacro MUI_HEADER_TEXT "Initial Settings" "Set to use the translation function (can be changed later)." + nsDialogs::Create 1018 + Pop $DialogTranslate + + ${If} $DialogTranslate == error + Abort + ${EndIf} + + ${NSD_CreateLabel} 0 21u 33% 12u "Enable Translation" + ${NSD_CreateCheckBox} 33% 20u 33% 12u "" + Pop $CheckboxUseTranslate + ${NSD_CreateLabel} 0 52u 33% 12u "Select AI Model Size" + ${NSD_CreateDropList} 33% 50u 40% 12u "" + Pop $DropListCTranslate2DownloadWeightType + ${NSD_CB_AddString} $DropListCTranslate2DownloadWeightType "Basic model(418MB)" + ${NSD_CB_AddString} $DropListCTranslate2DownloadWeightType "High accuracy model(1.3GB)" + ${NSD_CB_SelectString} $DropListCTranslate2DownloadWeightType "Basic model(418MB)" + StrCpy $SelectedCTranslate2DownloadWeightType "Small" + EnableWindow $DropListCTranslate2DownloadWeightType 0 + ${NSD_OnClick} $CheckboxUseTranslate OnCheckboxCTranslate2DownloadWeightClick + nsDialogs::Show +FunctionEnd + +Function PageLeaveTranslate + ${NSD_GetState} $CheckboxUseTranslate $0 + ${If} $0 == 1 + StrCpy $CheckboxUseTranslate "true" + ${Else} + StrCpy $CheckboxUseTranslate "false" + ${EndIf} + + ${NSD_GetText} $CheckboxCTranslate2DownloadWeight $0 + ${If} "Basic model(418MB)" == $0 + StrCpy $SelectedCTranslate2DownloadWeightType "Small" + ${ElseIf} "High accuracy model(1.3GB)" == $0 + StrCpy $SelectedCTranslate2DownloadWeightType "Large" + ${EndIf} +FunctionEnd + +Function OnCheckboxCTranslate2DownloadWeightClick + Pop $CheckboxUseTranslate + ${NSD_GetState} $CheckboxUseTranslate $0 + ${If} $0 == 1 + EnableWindow $DropListCTranslate2DownloadWeightType 1 + ${Else} + EnableWindow $DropListCTranslate2DownloadWeightType 0 + ${EndIf} +FunctionEnd + +; 4-3. Page Download Transcript Model Weight +Var DropLListTranscriptEngines +Var SelectedTranscriptEngine +Var DropListWhisperDownloadWeightType +Var SelectedWhisperDownloadWeightType +Var DialogTranscript +Page custom PageTranscript PageLeaveTranscript +Function PageTranscript + !insertmacro MUI_HEADER_TEXT "Initial Settings" "Set to use the transcript engine (can be changed later)." + nsDialogs::Create 1018 + Pop $DialogTranscript + + ${If} $DialogTranscript == error + Abort + ${EndIf} + + ${NSD_CreateLabel} 0 21u 33% 12u "Select Transcript Engine" + ${NSD_CreateDropList} 33% 20u 33% 12u "" + Pop $DropLListTranscriptEngines + ${NSD_CB_AddString} $DropLListTranscriptEngines "Google" + ${NSD_CB_AddString} $DropLListTranscriptEngines "Wishper" + ${NSD_CB_SelectString} $DropLListTranscriptEngines "Google" + ${NSD_CreateLabel} 0 52u 33% 12u "Select AI Model Size" + ${NSD_CreateDropList} 33% 50u 40% 12u "" + Pop $DropListWhisperDownloadWeightType + ${NSD_CB_AddString} $DropListWhisperDownloadWeightType "tiny model(74.5MB)" + ${NSD_CB_AddString} $DropListWhisperDownloadWeightType "base model(141MB)" + ${NSD_CB_AddString} $DropListWhisperDownloadWeightType "small model(463MB)" + ${NSD_CB_AddString} $DropListWhisperDownloadWeightType "medium model(1.42GB)" + ${NSD_CB_AddString} $DropListWhisperDownloadWeightType "large-v1 model(2.87GB)" + ${NSD_CB_AddString} $DropListWhisperDownloadWeightType "large-v2 model(2.87GB)" + ${NSD_CB_AddString} $DropListWhisperDownloadWeightType "large-v3 model(2.87GB)" + ${NSD_CB_SelectString} $DropListWhisperDownloadWeightType "base model(141MB)" + + StrCpy $SelectedWhisperDownloadWeightType "base" + EnableWindow $DropListWhisperDownloadWeightType 0 + ${NSD_OnChange} $DropLListTranscriptEngines OnDropListWishperDownloadWeightClick + nsDialogs::Show +FunctionEnd + +Function PageLeaveTranscript + ${NSD_GetText} $DropLListTranscriptEngines $0 + ${If} $0 == "Google" + StrCpy $SelectedTranscriptEngine "google" + ${Else} + StrCpy $SelectedTranscriptEngine "wishper" + ${EndIf} + ${NSD_GetText} $DropListWhisperDownloadWeightType $0 + ${If} "tiny model(74.5MB)" == $0 + StrCpy $SelectedWhisperDownloadWeightType "tiny" + ${ElseIf} "base model(141MB)" == $0 + StrCpy $SelectedWhisperDownloadWeightType "base" + ${ElseIf} "small model(463MB)" == $0 + StrCpy $SelectedWhisperDownloadWeightType "small" + ${ElseIf} "medium model(1.42GB)" == $0 + StrCpy $SelectedWhisperDownloadWeightType "medium" + ${ElseIf} "large-v1 model(2.87GB)" == $0 + StrCpy $SelectedWhisperDownloadWeightType "large-v1" + ${ElseIf} "large-v2 model(2.87GB)" == $0 + StrCpy $SelectedWhisperDownloadWeightType "large-v2" + ${ElseIf} "large-v3 model(2.87GB)" == $0 + StrCpy $SelectedWhisperDownloadWeightType "large-v3" + ${EndIf} +FunctionEnd + +Function OnDropListWishperDownloadWeightClick + ${NSD_GetText} $DropLListTranscriptEngines $0 + ${If} $0 == "Wishper" + EnableWindow $DropListWhisperDownloadWeightType 1 + ${Else} + EnableWindow $DropListWhisperDownloadWeightType 0 + ${EndIf} +FunctionEnd + + +Var CheckboxUseCUDA +Var DialogSelectInstallDeviceVersion +Page custom PageSelectInstallDeviceVersion PageLeaveSelectInstallDeviceVersion +Function PageSelectInstallDeviceVersion + !insertmacro MUI_HEADER_TEXT "Initial Settings" "Enable GPUs for translation and transcription." + nsDialogs::Create 1018 + Pop $DialogSelectInstallDeviceVersion + + ${If} $DialogSelectInstallDeviceVersion == error + Abort + ${EndIf} + + ${NSD_CreateLabel} 0 21u 33% 12u "Enable the use of GPUs" + ${NSD_CreateCheckBox} 33% 20u 33% 12u "" + Pop $CheckboxUseCUDA + nsDialogs::Show +FunctionEnd + +Function PageLeaveSelectInstallDeviceVersion + ${NSD_GetState} $CheckboxUseCUDA $0 + ${If} $0 == 1 + StrCpy $CheckboxUseCUDA "true" + ${Else} + StrCpy $CheckboxUseCUDA "false" + ${EndIf} +FunctionEnd + +!insertmacro MUI_PAGE_COMPONENTS + +; 4-4. Custom page to ask user if he wants to reinstall/uninstall +; only if a previous installtion was detected +Var ReinstallPageCheck +Page custom PageReinstall PageLeaveReinstall +Function PageReinstall + ; Uninstall previous WiX installation if exists. + ; + ; A WiX installer stores the isntallation info in registry + ; using a UUID and so we have to loop through all keys under + ; `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall` + ; and check if `DisplayName` and `Publisher` keys match ${PRODUCTNAME} and ${MANUFACTURER} + ; + ; This has a potentional issue that there maybe another installation that matches + ; our ${PRODUCTNAME} and ${MANUFACTURER} but wasn't installed by our WiX installer, + ; however, this should be fine since the user will have to confirm the uninstallation + ; and they can chose to abort it if doesn't make sense. + StrCpy $0 0 + wix_loop: + EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0 + StrCmp $1 "" wix_done ; Exit loop if there is no more keys to loop on + IntOp $0 $0 + 1 + ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "DisplayName" + ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "Publisher" + StrCmp "$R0$R1" "${PRODUCTNAME}${MANUFACTURER}" 0 wix_loop + ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "UninstallString" + ${StrCase} $R1 $R0 "L" + ${StrLoc} $R0 $R1 "msiexec" ">" + StrCmp $R0 0 0 wix_done + StrCpy $R7 "wix" + StrCpy $R6 "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" + Goto compare_version + wix_done: + + ; Check if there is an existing installation, if not, abort the reinstall page + ReadRegStr $R0 SHCTX "${UNINSTKEY}" "" + ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString" + ${IfThen} "$R0$R1" == "" ${|} Abort ${|} + + ; Compare this installar version with the existing installation + ; and modify the messages presented to the user accordingly + compare_version: + StrCpy $R4 "$(older)" + ${If} $R7 == "wix" + ReadRegStr $R0 HKLM "$R6" "DisplayVersion" + ${Else} + ReadRegStr $R0 SHCTX "${UNINSTKEY}" "DisplayVersion" + ${EndIf} + ${IfThen} $R0 == "" ${|} StrCpy $R4 "$(unknown)" ${|} + + nsis_tauri_utils::SemverCompare "${VERSION}" $R0 + Pop $R0 + ; Reinstalling the same version + ${If} $R0 == 0 + StrCpy $R1 "$(alreadyInstalledLong)" + StrCpy $R2 "$(addOrReinstall)" + StrCpy $R3 "$(uninstallApp)" + !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(chooseMaintenanceOption)" + StrCpy $R5 "2" + ; Upgrading + ${ElseIf} $R0 == 1 + StrCpy $R1 "$(olderOrUnknownVersionInstalled)" + StrCpy $R2 "$(uninstallBeforeInstalling)" + StrCpy $R3 "$(dontUninstall)" + !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)" + StrCpy $R5 "1" + ; Downgrading + ${ElseIf} $R0 == -1 + StrCpy $R1 "$(newerVersionInstalled)" + StrCpy $R2 "$(uninstallBeforeInstalling)" + !if "${ALLOWDOWNGRADES}" == "true" + StrCpy $R3 "$(dontUninstall)" + !else + StrCpy $R3 "$(dontUninstallDowngrade)" + !endif + !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)" + StrCpy $R5 "1" + ${Else} + Abort + ${EndIf} + + Call SkipIfPassive + + nsDialogs::Create 1018 + Pop $R4 + ${IfThen} $(^RTL) == 1 ${|} nsDialogs::SetRTL $(^RTL) ${|} + + ${NSD_CreateLabel} 0 0 100% 24u $R1 + Pop $R1 + + ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2 + Pop $R2 + ${NSD_OnClick} $R2 PageReinstallUpdateSelection + + ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3 + Pop $R3 + ; disable this radio button if downgrading and downgrades are disabled + !if "${ALLOWDOWNGRADES}" == "false" + ${IfThen} $R0 == -1 ${|} EnableWindow $R3 0 ${|} + !endif + ${NSD_OnClick} $R3 PageReinstallUpdateSelection + + ; Check the first radio button if this the first time + ; we enter this page or if the second button wasn't + ; selected the last time we were on this page + ${If} $ReinstallPageCheck != 2 + SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${Else} + SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${EndIf} + + ${NSD_SetFocus} $R2 + nsDialogs::Show +FunctionEnd +Function PageReinstallUpdateSelection + ${NSD_GetState} $R2 $R1 + ${If} $R1 == ${BST_CHECKED} + StrCpy $ReinstallPageCheck 1 + ${Else} + StrCpy $ReinstallPageCheck 2 + ${EndIf} +FunctionEnd +Function PageLeaveReinstall + ${NSD_GetState} $R2 $R1 + + ; $R5 holds whether we are reinstalling the same version or not + ; $R5 == "1" -> different versions + ; $R5 == "2" -> same version + ; + ; $R1 holds the radio buttons state. its meaning is dependant on the context + StrCmp $R5 "1" 0 +2 ; Existing install is not the same version? + StrCmp $R1 "1" reinst_uninstall reinst_done ; $R1 == "1", then user chose to uninstall existing version, otherwise skip uninstalling + StrCmp $R1 "1" reinst_done ; Same version? skip uninstalling + + reinst_uninstall: + HideWindow + ClearErrors + + ${If} $R7 == "wix" + ReadRegStr $R1 HKLM "$R6" "UninstallString" + ExecWait '$R1' $0 + ${Else} + ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" "" + ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString" + ExecWait '$R1 /P _?=$4' $0 + ${EndIf} + + BringToFront + + ${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code + + ${If} $0 <> 0 + ${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe" + ${If} $0 = 1 ; User aborted uninstaller? + StrCmp $R5 "2" 0 +2 ; Is the existing install the same version? + Quit ; ...yes, already installed, we are done + Abort + ${EndIf} + MessageBox MB_ICONEXCLAMATION "$(unableToUninstall)" + Abort + ${Else} + StrCpy $0 $R1 1 + ${IfThen} $0 == '"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString + Delete $R1 + RMDir $INSTDIR + ${EndIf} + reinst_done: +FunctionEnd + +; 5. Choose install directoy page +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive +!insertmacro MUI_PAGE_DIRECTORY + +; 6. Start menu shortcut page +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive +Var AppStartMenuFolder +!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder + +; 7. Installation page +!insertmacro MUI_PAGE_INSTFILES + + + +; 8. Finish page +; +; Don't auto jump to finish page after installation page, +; because the installation page has useful info that can be used debug any issues with the installer. +!define MUI_FINISHPAGE_NOAUTOCLOSE +; Use show readme button in the finish page as a button create a desktop shortcut +!define MUI_FINISHPAGE_SHOWREADME +!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)" +!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut +; Show run app after installation. +!define MUI_FINISHPAGE_RUN +!define MUI_FINISHPAGE_RUN_FUNCTION RunMainBinary +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive +!insertmacro MUI_PAGE_FINISH + +Function RunMainBinary + nsis_tauri_utils::RunAsUser "$INSTDIR\${MAINBINARYNAME}.exe" "" +FunctionEnd + +; Uninstaller Pages +; 1. Confirm uninstall page +Var DeleteAppDataCheckbox +Var DeleteAppDataCheckboxState +!define /ifndef WS_EX_LAYOUTRTL 0x00400000 +!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmShow +Function un.ConfirmShow + FindWindow $1 "#32770" "" $HWNDPARENT ; Find inner dialog + ${If} $(^RTL) == 1 + System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE}|${WS_EX_LAYOUTRTL},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 50,i 100,i 400, i 25,i$1,i0,i0,i0)i.s' + ${Else} + System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 0,i 100,i 400, i 25,i$1,i0,i0,i0)i.s' + ${EndIf} + Pop $DeleteAppDataCheckbox + SendMessage $HWNDPARENT ${WM_GETFONT} 0 0 $1 + SendMessage $DeleteAppDataCheckbox ${WM_SETFONT} $1 1 +FunctionEnd +!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.ConfirmLeave +Function un.ConfirmLeave + SendMessage $DeleteAppDataCheckbox ${BM_GETCHECK} 0 0 $DeleteAppDataCheckboxState +FunctionEnd +!insertmacro MUI_UNPAGE_CONFIRM + +; 2. Uninstalling Page +!insertmacro MUI_UNPAGE_INSTFILES + +;Languages +{{#each languages}} +!insertmacro MUI_LANGUAGE "{{this}}" +{{/each}} +!insertmacro MUI_RESERVEFILE_LANGDLL +{{#each language_files}} + !include "{{this}}" +{{/each}} + +!macro SetContext + !if "${INSTALLMODE}" == "currentUser" + SetShellVarContext current + !else if "${INSTALLMODE}" == "perMachine" + SetShellVarContext all + !endif + + ${If} ${RunningX64} + !if "${ARCH}" == "x64" + SetRegView 64 + !else if "${ARCH}" == "arm64" + SetRegView 64 + !else + SetRegView 32 + !endif + ${EndIf} +!macroend + +Var PassiveMode +Function .onInit + ${GetOptions} $CMDLINE "/P" $PassiveMode + IfErrors +2 0 + StrCpy $PassiveMode 1 + + !if "${DISPLAYLANGUAGESELECTOR}" == "true" + !insertmacro MUI_LANGDLL_DISPLAY + !endif + + !insertmacro SetContext + + ${If} $INSTDIR == "" + ; Set default install location + !if "${INSTALLMODE}" == "perMachine" + ${If} ${RunningX64} + !if "${ARCH}" == "x64" + StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}" + !else if "${ARCH}" == "arm64" + StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}" + !else + StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}" + !endif + ${Else} + StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}" + ${EndIf} + !else if "${INSTALLMODE}" == "currentUser" + StrCpy $INSTDIR "$LOCALAPPDATA\${PRODUCTNAME}" + !endif + + Call RestorePreviousInstallLocation + ${EndIf} + + + !if "${INSTALLMODE}" == "both" + !insertmacro MULTIUSER_INIT + !endif +FunctionEnd + + +Section EarlyChecks + ; Abort silent installer if downgrades is disabled + !if "${ALLOWDOWNGRADES}" == "false" + IfSilent 0 silent_downgrades_done + ; If downgrading + ${If} $R0 == -1 + System::Call 'kernel32::AttachConsole(i -1)i.r0' + ${If} $0 != 0 + System::Call 'kernel32::GetStdHandle(i -11)i.r0' + System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color + FileWrite $0 "$(silentDowngrades)" + ${EndIf} + Abort + ${EndIf} + silent_downgrades_done: + !endif + +SectionEnd + +Section WebView2 + ; Check if Webview2 is already installed and skip this section + ${If} ${RunningX64} + ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${Else} + ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${EndIf} + ReadRegStr $5 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + + StrCmp $4 "" 0 webview2_done + StrCmp $5 "" 0 webview2_done + + ; Webview2 install modes + !if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper" + Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe" + DetailPrint "$(webview2Downloading)" + NSISdl::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe" + Pop $0 + ${If} $0 == "success" + DetailPrint "$(webview2DownloadSuccess)" + ${Else} + DetailPrint "$(webview2DownloadError)" + Abort "$(webview2AbortError)" + ${EndIf} + StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe" + Goto install_webview2 + !endif + + !if "${INSTALLWEBVIEW2MODE}" == "embedBootstrapper" + Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe" + File "/oname=$TEMP\MicrosoftEdgeWebview2Setup.exe" "${WEBVIEW2BOOTSTRAPPERPATH}" + DetailPrint "$(installingWebview2)" + StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe" + Goto install_webview2 + !endif + + !if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller" + Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" + File "/oname=$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}" + DetailPrint "$(installingWebview2)" + StrCpy $6 "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" + Goto install_webview2 + !endif + + Goto webview2_done + + install_webview2: + DetailPrint "$(installingWebview2)" + ; $6 holds the path to the webview2 installer + ExecWait "$6 ${WEBVIEW2INSTALLERARGS} /install" $1 + ${If} $1 == 0 + DetailPrint "$(webview2InstallSuccess)" + ${Else} + DetailPrint "$(webview2InstallError)" + Abort "$(webview2AbortError)" + ${EndIf} + webview2_done: +SectionEnd + +!macro CheckIfAppIsRunning + !if "${INSTALLMODE}" == "currentUser" + nsis_tauri_utils::FindProcessCurrentUser "${MAINBINARYNAME}.exe" + !else + nsis_tauri_utils::FindProcess "${MAINBINARYNAME}.exe" + !endif + Pop $R0 + ${If} $R0 = 0 + IfSilent kill 0 + ${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL "$(appRunningOkKill)" IDOK kill IDCANCEL cancel ${|} + kill: + !if "${INSTALLMODE}" == "currentUser" + nsis_tauri_utils::KillProcessCurrentUser "${MAINBINARYNAME}.exe" + !else + nsis_tauri_utils::KillProcess "${MAINBINARYNAME}.exe" + !endif + Pop $R0 + Sleep 500 + ${If} $R0 = 0 + Goto app_check_done + ${Else} + IfSilent silent ui + silent: + System::Call 'kernel32::AttachConsole(i -1)i.r0' + ${If} $0 != 0 + System::Call 'kernel32::GetStdHandle(i -11)i.r0' + System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color + FileWrite $0 "$(appRunning)$\n" + ${EndIf} + Abort + ui: + Abort "$(failedToKillApp)" + ${EndIf} + cancel: + Abort "$(appRunning)" + ${EndIf} + app_check_done: +!macroend + +Section Install + SetOutPath $INSTDIR + + !insertmacro CheckIfAppIsRunning + + ; ; Copy main executable + ; File "${MAINBINARYSRCPATH}" + + ; ; Copy resources + ; {{#each resources_dirs}} + ; CreateDirectory "$INSTDIR\\{{this}}" + ; {{/each}} + ; {{#each resources}} + ; File /a "/oname={{this.[1]}}" "{{unescape-dollar-sign @key}}" + ; {{/each}} + + ; ; Copy external binaries + ; {{#each binaries}} + ; File /a "/oname={{this}}" "{{unescape-dollar-sign @key}}" + ; {{/each}} + + !addplugindir "..\..\..\..\nsis\plugins\x86-unicode" + ; 指定のURLからファイルをダウンロード + !define SOFTWARE_RELEASE_URL "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" + !define SOFTWARE_DOWNLOAD_FILENAME "VRCT.zip" + !define SOFTWARE_CUDA_DOWNLOAD_FILENAME "VRCT_cuda.zip" + !define SOFTWARE_JSON_FILENAME "response.json" + Var /GLOBAL i + Var /GLOBAL cmder_dl + Var /GLOBAL cmder_version + Var /GLOBAL file_name + + ${If} $CheckboxUseCUDA == "true" + StrCpy $file_name "${SOFTWARE_CUDA_DOWNLOAD_FILENAME}" + ${Else} + StrCpy $file_name "${SOFTWARE_DOWNLOAD_FILENAME}" + ${EndIf} + + DetailPrint "Fetching Latest Release from GitHub (${SOFTWARE_RELEASE_URL})" + inetc::get /SILENT "${SOFTWARE_RELEASE_URL}" "$TEMP\${SOFTWARE_JSON_FILENAME}" + + DetailPrint "Parsing JSON..." + nsJSON::Set /file "$TEMP\${SOFTWARE_JSON_FILENAME}" + + nsJSON::Get 'tag_name' /end + Pop $cmder_version + DetailPrint "Found version $cmder_version" + + nsJSON::Get /count 'assets' /end + Pop $R0 + + ${ForEach} $i 0 $R0 + 1 + nsJSON::Get 'assets' /index $i 'name' /end + Pop $R1 + StrCmp $R1 $file_name done + ${Next} + done: + + nsJSON::Get 'assets' /index $i 'browser_download_url' /end + Pop $cmder_dl + DetailPrint "Got URL : $cmder_dl" + + DetailPrint "Downloading $file_name..." + inetc::get $cmder_dl "$TEMP\$file_name" + Pop $0 + StrCmp "$0" "OK" dlok + DetailPrint "Download Failed $0" + Abort + + dlok: + DetailPrint "Extracting $file_name ..." + nsisunz::UnzipToStack "$TEMP\$file_name" $INSTDIR + + ; Create uninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + ; Save $INSTDIR in registry for future installations + WriteRegStr SHCTX "${MANUPRODUCTKEY}" "" $INSTDIR + + !if "${INSTALLMODE}" == "both" + ; Save install mode to be selected by default for the next installation such as updating + ; or when uninstalling + WriteRegStr SHCTX "${UNINSTKEY}" $MultiUser.InstallMode 1 + !endif + + ; Save current MAINBINARYNAME for future updates from v2 updater + WriteRegStr SHCTX "${UNINSTKEY}" "MainBinaryName" "${MAINBINARYNAME}.exe" + + ; Registry information for add/remove programs + WriteRegStr SHCTX "${UNINSTKEY}" "DisplayName" "${PRODUCTNAME}" + WriteRegStr SHCTX "${UNINSTKEY}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\"" + WriteRegStr SHCTX "${UNINSTKEY}" "DisplayVersion" "${VERSION}" + WriteRegStr SHCTX "${UNINSTKEY}" "Publisher" "${MANUFACTURER}" + WriteRegStr SHCTX "${UNINSTKEY}" "InstallLocation" "$\"$INSTDIR$\"" + WriteRegStr SHCTX "${UNINSTKEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegDWORD SHCTX "${UNINSTKEY}" "NoModify" "1" + WriteRegDWORD SHCTX "${UNINSTKEY}" "NoRepair" "1" + WriteRegDWORD SHCTX "${UNINSTKEY}" "EstimatedSize" "${ESTIMATEDSIZE}" + + ; Create start menu shortcut (GUI) + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + Call CreateStartMenuShortcut + !insertmacro MUI_STARTMENU_WRITE_END + + ; Create shortcuts for silent and passive installers, which + ; can be disabled by passing `/NS` flag + ; GUI installer has buttons for users to control creating them + IfSilent check_ns_flag 0 + ${IfThen} $PassiveMode == 1 ${|} Goto check_ns_flag ${|} + Goto shortcuts_done + check_ns_flag: + ${GetOptions} $CMDLINE "/NS" $R0 + IfErrors 0 shortcuts_done + Call CreateDesktopShortcut + Call CreateStartMenuShortcut + shortcuts_done: + + ; Auto close this page for passive mode + ${IfThen} $PassiveMode == 1 ${|} SetAutoClose true ${|} +SectionEnd + +Function .onInstSuccess + ; Check for `/R` flag only in silent and passive installers because + ; GUI installer has a toggle for the user to (re)start the app + IfSilent check_r_flag 0 + ${IfThen} $PassiveMode == 1 ${|} Goto check_r_flag ${|} + Goto run_done + check_r_flag: + ${GetOptions} $CMDLINE "/R" $R0 + IfErrors run_done 0 + ${GetOptions} $CMDLINE "/ARGS" $R0 + nsis_tauri_utils::RunAsUser "$INSTDIR\${MAINBINARYNAME}.exe" "$R0" + run_done: + + StrCpy $1 '{"UI_LANGUAGE": "$SelectedLangage", "USE_TRANSLATION_FEATURE": $CheckboxUseTranslate, "SELECTED_TRANSCRIPTION_ENGINE": "$SelectedTranscriptEngine", "CTRANSLATE2_WEIGHT_TYPE": "$SelectedCTranslate2DownloadWeightType", "WHISPER_WEIGHT_TYPE": "$SelectedWhisperDownloadWeightType"}' + FileOpen $0 "$INSTDIR\config.json" w + FileWrite $0 $1 + FileClose $0 +FunctionEnd + +Function un.onInit + !insertmacro SetContext + + !if "${INSTALLMODE}" == "both" + !insertmacro MULTIUSER_UNINIT + !endif + + !insertmacro MUI_UNGETLANGUAGE +FunctionEnd + +!macro DeleteAppUserModelId + !insertmacro ComHlpr_CreateInProcInstance ${CLSID_DestinationList} ${IID_ICustomDestinationList} r1 "" + ${If} $1 P<> 0 + ${ICustomDestinationList::DeleteList} $1 '("${BUNDLEID}")' + ${IUnknown::Release} $1 "" + ${EndIf} + !insertmacro ComHlpr_CreateInProcInstance ${CLSID_ApplicationDestinations} ${IID_IApplicationDestinations} r1 "" + ${If} $1 P<> 0 + ${IApplicationDestinations::SetAppID} $1 '("${BUNDLEID}")i.r0' + ${If} $0 >= 0 + ${IApplicationDestinations::RemoveAllDestinations} $1 '' + ${EndIf} + ${IUnknown::Release} $1 "" + ${EndIf} +!macroend + +; From https://stackoverflow.com/a/42816728/16993372 +!macro UnpinShortcut shortcut + !insertmacro ComHlpr_CreateInProcInstance ${CLSID_StartMenuPin} ${IID_IStartMenuPinnedList} r0 "" + ${If} $0 P<> 0 + System::Call 'SHELL32::SHCreateItemFromParsingName(ws, p0, g "${IID_IShellItem}", *p0r1)' "${shortcut}" + ${If} $1 P<> 0 + ${IStartMenuPinnedList::RemoveFromList} $0 '(r1)' + ${IUnknown::Release} $1 "" + ${EndIf} + ${IUnknown::Release} $0 "" + ${EndIf} +!macroend + +Section Uninstall + !insertmacro CheckIfAppIsRunning + + ; Delete the app directory and its content from disk + ; Copy main executable + Delete "$INSTDIR\${MAINBINARYNAME}.exe" + + ; Delete resources + {{#each resources}} + Delete "$INSTDIR\\{{this.[1]}}" + {{/each}} + + ; Delete external binaries + {{#each binaries}} + Delete "$INSTDIR\\{{this}}" + {{/each}} + + ; Delete uninstaller + Delete "$INSTDIR\uninstall.exe" + + {{#each resources_ancestors}} + RMDir /REBOOTOK "$INSTDIR\\{{this}}" + {{/each}} + RMDir "$INSTDIR" + + !insertmacro DeleteAppUserModelId + !insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" + !insertmacro UnpinShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" + + ; Remove start menu shortcut + !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder + Delete "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" + RMDir "$SMPROGRAMS\$AppStartMenuFolder" + + ; Remove desktop shortcuts + Delete "$DESKTOP\${MAINBINARYNAME}.lnk" + + ; Remove registry information for add/remove programs + !if "${INSTALLMODE}" == "both" + DeleteRegKey SHCTX "${UNINSTKEY}" + !else if "${INSTALLMODE}" == "perMachine" + DeleteRegKey HKLM "${UNINSTKEY}" + !else + DeleteRegKey HKCU "${UNINSTKEY}" + !endif + + DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language" + + ; Delete app data + ${If} $DeleteAppDataCheckboxState == 1 + SetShellVarContext current + RmDir /r "$APPDATA\${BUNDLEID}" + RmDir /r "$LOCALAPPDATA\${BUNDLEID}" + ${EndIf} + + ${GetOptions} $CMDLINE "/P" $R0 + IfErrors +2 0 + SetAutoClose true +SectionEnd + +Function RestorePreviousInstallLocation + ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" "" + StrCmp $4 "" +2 0 + StrCpy $INSTDIR $4 +FunctionEnd + +Function SkipIfPassive + ${IfThen} $PassiveMode == 1 ${|} Abort ${|} +FunctionEnd + +!macro SetLnkAppUserModelId shortcut + !insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 "" + ${If} $0 P<> 0 + ${IUnknown::QueryInterface} $0 '("${IID_IPersistFile}",.r1)' + ${If} $1 P<> 0 + ${IPersistFile::Load} $1 '("${shortcut}", ${STGM_READWRITE})' + ${IUnknown::QueryInterface} $0 '("${IID_IPropertyStore}",.r2)' + ${If} $2 P<> 0 + System::Call 'Oleaut32::SysAllocString(w "${BUNDLEID}") i.r3' + System::Call '*${SYSSTRUCT_PROPERTYKEY}(${PKEY_AppUserModel_ID})p.r4' + System::Call '*${SYSSTRUCT_PROPVARIANT}(${VT_BSTR},,&i4 $3)p.r5' + ${IPropertyStore::SetValue} $2 '($4,$5)' + + System::Call 'Oleaut32::SysFreeString($3)' + System::Free $4 + System::Free $5 + ${IPropertyStore::Commit} $2 "" + ${IUnknown::Release} $2 "" + ${IPersistFile::Save} $1 '("${shortcut}",1)' + ${EndIf} + ${IUnknown::Release} $1 "" + ${EndIf} + ${IUnknown::Release} $0 "" + ${EndIf} +!macroend + +Function CreateDesktopShortcut + CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + !insertmacro SetLnkAppUserModelId "$DESKTOP\${MAINBINARYNAME}.lnk" +FunctionEnd + +Function CreateStartMenuShortcut + CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" + CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + !insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" +FunctionEnd \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 00000000..66464eff --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,43 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +// use tauri::command; +use tauri::Manager; +use window_shadows::set_shadow; +fn main() { + tauri::Builder::default() + .setup(|app| { + let main_window = app.get_window("main").unwrap(); // `main_window` is declared here for all builds + + #[cfg(debug_assertions)] + { main_window.open_devtools(); } + + #[cfg(any(windows, target_os = "macos"))] + set_shadow(main_window, true).unwrap(); + + Ok(()) + }) + .invoke_handler(tauri::generate_handler![get_font_list]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} + + +use font_kit::{source::SystemSource}; +use std::collections::HashSet; + +#[tauri::command] +async fn get_font_list() -> Vec { + let source = SystemSource::new(); + let mut font_families = HashSet::new(); + + if let Ok(fonts) = source.all_fonts() { + for font in fonts { + if let Ok(info) = font.load() { + font_families.insert(info.family_name().to_string()); + } + } + } + + font_families.into_iter().collect() +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 00000000..e47c9aef --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,84 @@ +{ + "build": { + "beforeDevCommand": "", + "beforeBuildCommand": "", + "devPath": "http://localhost:1420", + "distDir": "../dist" + }, + "package": { + "productName": "VRCT", + "version": "3.0.0" + }, + "tauri": { + "allowlist": { + "all": false, + "window": { + "all": false, + "setAlwaysOnTop": true, + "setDecorations": true, + "close": true, + "setPosition": true, + "setSize": true, + "maximize": true, + "minimize": true, + "unmaximize": true, + "unminimize": true, + "startDragging": true + }, + "shell": { + "all": false, + "open": true, + "sidecar": true, + "scope": [ + { + "name": "bin/VRCT-sidecar", "sidecar": true,"args": true + } + ] + } +}, +"windows": [ + { + "title": "VRCT", + "center": true, + "width": 450, + "height": 220, + "minWidth": 400, + "minHeight": 200, + "transparent": true, + "decorations": false + } + ], + "security": { + "csp": null + }, + "bundle": { + "active": true, + "targets": "nsis", + "identifier": "com.vrct.dev", + "publisher": "m's software", + "copyright": "Copyright m's software", + "shortDescription": "VRCT", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "externalBin": [ + "bin/VRCT-sidecar" + ], + "resources":{ + "bin/_internal": "_internal" + }, + "windows": { + "nsis": { + "template": "nsis/template.nsi", + "license": "../LICENSE", + "installMode": "both", + "displayLanguageSelector": true + } + } + } + } +} diff --git a/src-ui/app/App.jsx b/src-ui/app/App.jsx new file mode 100644 index 00000000..051855e2 --- /dev/null +++ b/src-ui/app/App.jsx @@ -0,0 +1,71 @@ +import { useTranslation } from "react-i18next"; + +import { + useWindow, +} from "@logics_common"; + +// import React from "react"; + +import { + KeyEventController, + StartPythonController, + UiLanguageController, + ConfigPageCloseTriggerController, + UiSizeController, + FontFamilyController, + TransparencyController, +} from "./_app_controllers/index.js"; + +import { WindowTitleBar } from "./window_title_bar/WindowTitleBar"; +import { MainPage } from "./main_page/MainPage"; +import { ConfigPage } from "./config_page/ConfigPage"; +import { SplashComponent } from "./splash_component/SplashComponent"; +import { UpdatingComponent } from "./updating_component/UpdatingComponent"; +import { ModalController } from "./modal_controller/ModalController"; +import { SnackbarController } from "./snackbar_controller/SnackbarController"; +import styles from "./App.module.scss"; +import { useIsBackendReady, useIsSoftwareUpdating } from "@logics_common"; + +export const App = () => { + const { currentIsBackendReady } = useIsBackendReady(); + const { WindowGeometryController } = useWindow(); + const { i18n } = useTranslation(); + + return ( +
+ + + + + + + + + + {currentIsBackendReady.data === false + ? + : + } +
+ ); +}; + +const Contents = () => { + const { currentIsSoftwareUpdating } = useIsSoftwareUpdating(); + return ( + <> + + {currentIsSoftwareUpdating.data === false + ? +
+ + + + +
+ : + + } + + ); +}; \ No newline at end of file diff --git a/src-ui/app/App.module.scss b/src-ui/app/App.module.scss new file mode 100644 index 00000000..6313451d --- /dev/null +++ b/src-ui/app/App.module.scss @@ -0,0 +1,18 @@ +.container { + width: 100%; + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; + background-color: var(--dark_888_color); + align-items: center; +} + + +.pages_wrapper { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; +} \ No newline at end of file diff --git a/src-ui/app/_app_controllers/ConfigPageCloseTriggerController.jsx b/src-ui/app/_app_controllers/ConfigPageCloseTriggerController.jsx new file mode 100644 index 00000000..7705aecc --- /dev/null +++ b/src-ui/app/_app_controllers/ConfigPageCloseTriggerController.jsx @@ -0,0 +1,55 @@ +import { useEffect } from "react"; + +import { + useVolume, + useIsOpenedConfigPage, +} from "@logics_common"; + +import { + useMainFunction, +} from "@logics_main"; + +import { useStore_MainFunctionsStateMemory } from "@store"; + +export const ConfigPageCloseTriggerController = () => { + const { currentIsOpenedConfigPage } = useIsOpenedConfigPage(); + const { currentMainFunctionsStateMemory, updateMainFunctionsStateMemory} = useStore_MainFunctionsStateMemory(); + const { + currentTranscriptionSendStatus, + setTranscriptionSend, + currentTranscriptionReceiveStatus, + setTranscriptionReceive, + } = useMainFunction(); + const { + currentMicThresholdCheckStatus, + volumeCheckStop_Mic, + currentSpeakerThresholdCheckStatus, + volumeCheckStop_Speaker, + } = useVolume(); + + + const memorizeLatestMainFunctionsState = () => { + updateMainFunctionsStateMemory({ + transcription_send: currentTranscriptionSendStatus.data, + transcription_receive: currentTranscriptionReceiveStatus.data, + }); + }; + + const restoreMainFunctionState = () => { + if (currentMainFunctionsStateMemory.data.transcription_send === true) setTranscriptionSend(true); + if (currentMainFunctionsStateMemory.data.transcription_receive === true) setTranscriptionReceive(true); + }; + + useEffect(() => { + if (currentIsOpenedConfigPage.data === true) { // When config page is opened. + memorizeLatestMainFunctionsState(); + if (currentTranscriptionSendStatus.data === true) setTranscriptionSend(false); + if (currentTranscriptionReceiveStatus.data === true) setTranscriptionReceive(false); + } else if (currentIsOpenedConfigPage.data === false) { // When config page is closed. + if (currentMicThresholdCheckStatus.data === true) volumeCheckStop_Mic(); + if (currentSpeakerThresholdCheckStatus.data === true) volumeCheckStop_Speaker(); + restoreMainFunctionState(); + } + }, [currentIsOpenedConfigPage.data]); + return null; +}; \ No newline at end of file diff --git a/src-ui/app/_app_controllers/FontFamilyController.jsx b/src-ui/app/_app_controllers/FontFamilyController.jsx new file mode 100644 index 00000000..f7110d1d --- /dev/null +++ b/src-ui/app/_app_controllers/FontFamilyController.jsx @@ -0,0 +1,11 @@ +import { useEffect } from "react"; +import { useSelectedFontFamily } from "@logics_configs"; + +export const FontFamilyController = () => { + const { currentSelectedFontFamily } = useSelectedFontFamily(); + useEffect(() => { + document.documentElement.style.setProperty("--font_family", currentSelectedFontFamily.data); + }, [currentSelectedFontFamily.data]); + + return null; +}; diff --git a/src-ui/app/_app_controllers/KeyEventController.jsx b/src-ui/app/_app_controllers/KeyEventController.jsx new file mode 100644 index 00000000..9e8c43a6 --- /dev/null +++ b/src-ui/app/_app_controllers/KeyEventController.jsx @@ -0,0 +1,26 @@ +import { useEffect } from "react"; + +export const KeyEventController = () => { + useEffect(() => { + const handleKeydown = (event) => { + if (event.key === "F5" || (event.ctrlKey && event.key === "r") || + (event.metaKey && event.key === "r")) { + event.preventDefault(); + } + }; + + const handleContextmenu = (event) => { + event.preventDefault(); + }; + + document.addEventListener("keydown", handleKeydown); + document.addEventListener("contextmenu", handleContextmenu); + + return () => { + document.removeEventListener("keydown", handleKeydown); + document.removeEventListener("contextmenu", handleContextmenu); + }; + }, []); + + return null; +}; diff --git a/src-ui/app/_app_controllers/StartPythonController.jsx b/src-ui/app/_app_controllers/StartPythonController.jsx new file mode 100644 index 00000000..62bf0b28 --- /dev/null +++ b/src-ui/app/_app_controllers/StartPythonController.jsx @@ -0,0 +1,48 @@ +import { invoke } from "@tauri-apps/api/tauri"; +import { useEffect, useRef } from "react"; +import { useStartPython } from "@logics/useStartPython"; +import { useStdoutToPython } from "@logics/useStdoutToPython"; + +import { useStore_SelectableFontFamilyList } from "@store"; +import { arrayToObject } from "@utils"; + +export const StartPythonController = () => { + const { asyncStartPython } = useStartPython(); + const hasRunRef = useRef(false); + const { asyncFetchFonts } = useAsyncFetchFonts(); + + useEffect(() => { + if (!hasRunRef.current) { + asyncStartPython().then(() => { + startFeedingToWatchDogController(); + asyncFetchFonts(); + }).catch((err) => { + console.error(err); + }); + } + return () => hasRunRef.current = true; + }, []); + + return null; +}; + +const useAsyncFetchFonts = () => { + const { updateSelectableFontFamilyList } = useStore_SelectableFontFamilyList(); + const asyncFetchFonts = async () => { + try { + let fonts = await invoke("get_font_list"); + fonts = fonts.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" })); + updateSelectableFontFamilyList(arrayToObject(fonts)); + } catch (error) { + console.error("Error fetching fonts:", error); + } + }; + return { asyncFetchFonts }; +}; + +const startFeedingToWatchDogController = () => { + const { asyncStdoutToPython } = useStdoutToPython(); + setInterval(() => { + asyncStdoutToPython("/run/feed_watchdog"); + }, 20000); // 20000ミリ秒 = 20秒 +}; \ No newline at end of file diff --git a/src-ui/app/_app_controllers/TransparencyController.jsx b/src-ui/app/_app_controllers/TransparencyController.jsx new file mode 100644 index 00000000..46982413 --- /dev/null +++ b/src-ui/app/_app_controllers/TransparencyController.jsx @@ -0,0 +1,11 @@ +import { useEffect } from "react"; +import { useTransparency } from "@logics_configs"; + +export const TransparencyController = () => { + const { currentTransparency } = useTransparency(); + useEffect(() => { + document.documentElement.style.setProperty("opacity", `${currentTransparency.data / 100}`); + }, [currentTransparency.data]); + + return null; +}; \ No newline at end of file diff --git a/src-ui/app/_app_controllers/UiLanguageController.jsx b/src-ui/app/_app_controllers/UiLanguageController.jsx new file mode 100644 index 00000000..5b45c503 --- /dev/null +++ b/src-ui/app/_app_controllers/UiLanguageController.jsx @@ -0,0 +1,14 @@ +import { useEffect } from "react"; + +import { useTranslation } from "react-i18next"; +import { useUiLanguage } from "@logics_configs"; + +export const UiLanguageController = () => { + const { currentUiLanguage } = useUiLanguage(); + const { i18n } = useTranslation(); + + useEffect(() => { + i18n.changeLanguage(currentUiLanguage.data); + }, [currentUiLanguage.data]); + return null; +}; \ No newline at end of file diff --git a/src-ui/app/_app_controllers/UiSizeController.jsx b/src-ui/app/_app_controllers/UiSizeController.jsx new file mode 100644 index 00000000..8b970c0f --- /dev/null +++ b/src-ui/app/_app_controllers/UiSizeController.jsx @@ -0,0 +1,13 @@ +import { useEffect } from "react"; +import { useUiScaling } from "@logics_configs"; + +export const UiSizeController = () => { + const { currentUiScaling } = useUiScaling(); + const font_size = 62.5 * currentUiScaling.data / 100; + + useEffect(() => { + document.documentElement.style.setProperty("font-size", `${font_size}%`); + }, [currentUiScaling.data]); + + return null; +}; \ No newline at end of file diff --git a/src-ui/app/_app_controllers/index.js b/src-ui/app/_app_controllers/index.js new file mode 100644 index 00000000..abe6f018 --- /dev/null +++ b/src-ui/app/_app_controllers/index.js @@ -0,0 +1,7 @@ +export { KeyEventController } from "./KeyEventController"; +export { StartPythonController } from "./StartPythonController"; +export { UiLanguageController } from "./UiLanguageController"; +export { ConfigPageCloseTriggerController } from "./ConfigPageCloseTriggerController"; +export { UiSizeController } from "./UiSizeController"; +export { FontFamilyController } from "./FontFamilyController"; +export { TransparencyController } from "./TransparencyController"; \ No newline at end of file diff --git a/src-ui/app/_index_css/reset.css b/src-ui/app/_index_css/reset.css new file mode 100644 index 00000000..c8104a66 --- /dev/null +++ b/src-ui/app/_index_css/reset.css @@ -0,0 +1,418 @@ +/*! destyle.css v4.0.1 | MIT License | https://github.com/nicolas-cusan/destyle.css */ + +/* Reset box-model and set borders */ +/* ============================================ */ + +*, +::before, +::after { + box-sizing: border-box; + border-style: solid; + border-width: 0; + min-width: 0; +} + +/* Document */ +/* ============================================ */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + * 3. Remove gray overlay on links for iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + -webkit-tap-highlight-color: transparent; /* 3*/ +} + +/* Sections */ +/* ============================================ */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/* Vertical rhythm */ +/* ============================================ */ + +p, +table, +blockquote, +address, +pre, +iframe, +form, +figure, +dl { + margin: 0; +} + +/* Headings */ +/* ============================================ */ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; + margin: 0; +} + +/* Lists (enumeration) */ +/* ============================================ */ + +ul, +ol { + margin: 0; + padding: 0; + list-style: none; +} + +/* Lists (definition) */ +/* ============================================ */ + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; +} + +/* Grouping content */ +/* ============================================ */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ + border-top-width: 1px; + margin: 0; + clear: both; + color: inherit; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: inherit; /* 2 */ +} + +address { + font-style: inherit; +} + +/* Text-level semantics */ +/* ============================================ */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; + text-decoration: none; + color: inherit; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: inherit; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Replaced content */ +/* ============================================ */ + +/** + * Prevent vertical alignment issues. + */ + +svg, +img, +embed, +object, +iframe { + vertical-align: bottom; +} + +/* Forms */ +/* ============================================ */ + +/** + * Reset form fields to make them styleable. + * 1. Make form elements stylable across systems iOS especially. + * 2. Inherit text-transform from parent. + */ + +button, +input, +optgroup, +select, +textarea { + -webkit-appearance: none; /* 1 */ + appearance: none; + vertical-align: middle; + color: inherit; + font: inherit; + background: transparent; + padding: 0; + margin: 0; + border-radius: 0; + text-align: inherit; + text-transform: inherit; /* 2 */ + + /* Customize */ + outline: none; +} + +/** + * Correct cursors for clickable elements. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + cursor: pointer; +} + +button:disabled, +[type="button"]:disabled, +[type="reset"]:disabled, +[type="submit"]:disabled { + cursor: default; +} + +/** + * Improve outlines for Firefox and unify style with input elements & buttons. + */ + +:-moz-focusring { + outline: auto; +} + +select:disabled { + opacity: inherit; +} + +/** + * Remove padding + */ + +option { + padding: 0; +} + +/** + * Reset to invisible + */ + +fieldset { + margin: 0; + padding: 0; + min-width: 0; +} + +legend { + padding: 0; +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * Correct the outline style in Safari. + */ + +[type="search"] { + outline-offset: -2px; /* 1 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Fix font inheritance. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/** + * Fix appearance for Firefox + */ +[type="number"] { + appearance: textfield; +} + +/** + * Clickable labels + */ + +label[for] { + cursor: pointer; +} + +/* Interactive */ +/* ============================================ */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* + * Remove outline for editable content. + */ + +[contenteditable]:focus { + outline: auto; +} + +/* Tables */ +/* ============================================ */ + +/** +1. Correct table border color inheritance in all Chrome and Safari. +*/ + +table { + border-color: inherit; /* 1 */ + border-collapse: collapse; +} + +caption { + text-align: left; +} + +td, +th { + vertical-align: top; + padding: 0; +} + +th { + text-align: left; + font-weight: bold; +} \ No newline at end of file diff --git a/src-ui/app/_index_css/root.css b/src-ui/app/_index_css/root.css new file mode 100644 index 00000000..d2391abf --- /dev/null +++ b/src-ui/app/_index_css/root.css @@ -0,0 +1,45 @@ +@import "./reset.css"; +@import "./variables.css"; + +:root { + font-size: 62.5%; + color: #F2F2F2; +} + +* { + user-select: none; + &::-webkit-scrollbar { + width: 0.8rem; + } + &::-webkit-scrollbar-track { + background-color: var(--dark_925_color); + border-radius: 0.4rem; + } + &::-webkit-scrollbar-thumb { + background-color: var(--dark_800_color); + border-radius: 0.4rem; + } +} + +html, body { + height: 100%; + font-family: var(--font_family); /* If not found the font family where 'root:' that is selected by user*/ + border-radius: 1.8rem; +} + + +#root { + height: 100%; + + /* For controlling a whole window transparency */ + opacity: 1; +} + +/* SVG内のすべての要素にfillを適用 (colorの調整をcssでするため) */ +svg { + fill: currentColor; +} + +p { + white-space: pre-wrap; +} \ No newline at end of file diff --git a/src-ui/app/_index_css/variables.css b/src-ui/app/_index_css/variables.css new file mode 100644 index 00000000..2d38f67a --- /dev/null +++ b/src-ui/app/_index_css/variables.css @@ -0,0 +1,61 @@ +:root { + --primary_100_color: #b7ded8; + --primary_150_color: #a1d4cc; + --primary_200_color: #8acac0; + --primary_250_color: #76bfb4; + --primary_300_color: #61b4a7; + --primary_350_color: #55ac9e; + --primary_400_color: #48a495; + --primary_450_color: #429c8c; + --primary_500_color: #3b9483; + --primary_550_color: #398E7D; + --primary_600_color: #368777; + --primary_650_color: #347f6f; + --primary_700_color: #317767; + --primary_750_color: #2f6f60; + --primary_800_color: #2c6759; + --primary_900_color: #214b3f; + + --primary_600_color_44: #36877744; + + --sent_400_color: #6197b4; + --received_300_color: #a861b4; + + --dark_basic_text_color: #f2f2f2; + --dark_100_color: #f5f7fb; + --dark_200_color: #f1f2f6; + --dark_300_color: #e9eaee; + --dark_350_color: #d8d9dd; + --dark_400_color: #c7c8cc; + --dark_450_color: #b8b9bd; + --dark_500_color: #a9aaae; + --dark_550_color: #949599; + --dark_600_color: #7f8084; + --dark_650_color: #75767a; + --dark_700_color: #6a6c6f; + --dark_725_color: #636467; + --dark_750_color: #5b5c5f; + --dark_775_color: #535457; + --dark_800_color: #4b4c4f; + --dark_825_color: #434447; + --dark_850_color: #3a3b3e; + --dark_863_color: #36373a; + --dark_875_color: #323336; + --dark_888_color: #2e2f32; + --dark_900_color: #292a2d; + --dark_925_color: #242528; + --dark_950_color: #1f2022; + --dark_975_color: #1a1b1d; + --dark_1000_color: #151517; + + --dark_825_color_cc: #434447cc; + --dark_550_color_22: #94959922; + + + --title_bar_height: 2rem; + --main_page_topbar_height: 4.8rem; + --config_page_sidebar_width: 16.8rem; + --config_page_topbar_height: 8rem; + + --font_family: "Yu Gothic UI"; +} \ No newline at end of file diff --git a/src-ui/app/config_page/ConfigPage.jsx b/src-ui/app/config_page/ConfigPage.jsx new file mode 100644 index 00000000..6eb33644 --- /dev/null +++ b/src-ui/app/config_page/ConfigPage.jsx @@ -0,0 +1,34 @@ +import styles from "./ConfigPage.module.scss"; + +import { Topbar } from "./topbar/Topbar.jsx"; +import { SidebarSection } from "./sidebar_section/SidebarSection.jsx"; +import { SettingSection } from "./setting_section/SettingSection.jsx"; + +import { useSoftwareVersion } from "@logics_configs"; +import { useComputeMode } from "@logics_common"; +import { useTranslation } from "react-i18next"; + +export const ConfigPage = () => { + const { t } = useTranslation(); + const { currentSoftwareVersion } = useSoftwareVersion(); + const { currentComputeMode } = useComputeMode(); + + const version_label = currentComputeMode.data === "cpu" + ? t("config_page.version", { version: currentSoftwareVersion.data }) + : currentComputeMode.data === "cuda" + ? t("config_page.version", { version: currentSoftwareVersion.data }) + " CUDA" + : t("config_page.version", { version: currentSoftwareVersion.data }); + + return ( +
+
+ +
+ + +
+

{version_label}

+
+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/ConfigPage.module.scss b/src-ui/app/config_page/ConfigPage.module.scss new file mode 100644 index 00000000..28d2fd0e --- /dev/null +++ b/src-ui/app/config_page/ConfigPage.module.scss @@ -0,0 +1,34 @@ +.page { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + overflow: hidden; +} + +.container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: var(--dark_900_color); + overflow: hidden; + position: relative; +} + +.main_container { + width: 100%; + height: 100%; + display: flex; + padding-top: var(--config_page_topbar_height); +} + +.software_version { + position: absolute; + bottom: 0.8rem; + left: 1.2rem; + font-size: 1.2rem; + color: var(--dark_400_color); +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/SettingSection.jsx b/src-ui/app/config_page/setting_section/SettingSection.jsx new file mode 100644 index 00000000..b70b7763 --- /dev/null +++ b/src-ui/app/config_page/setting_section/SettingSection.jsx @@ -0,0 +1,28 @@ +import { useRef, useLayoutEffect, useEffect } from "react"; + +import styles from "./SettingSection.module.scss"; +import { SettingBox } from "./setting_box/SettingBox"; +import { store, useStore_SelectedConfigTabId } from "@store"; +import { useSettingBoxScrollPosition } from "@logics_configs"; + +export const SettingSection = () => { + const { currentSelectedConfigTabId } = useStore_SelectedConfigTabId(); + const { resetScrollPosition } = useSettingBoxScrollPosition(); + const scrollContainerRef = useRef(null); + + useLayoutEffect(() => { + store.setting_box_scroll_container = scrollContainerRef; + }, []); + + useEffect(() => { + resetScrollPosition(); + }, [currentSelectedConfigTabId.data]); + + return ( +
+
+ +
+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/SettingSection.module.scss b/src-ui/app/config_page/setting_section/SettingSection.module.scss new file mode 100644 index 00000000..2ed9e9e6 --- /dev/null +++ b/src-ui/app/config_page/setting_section/SettingSection.module.scss @@ -0,0 +1,9 @@ +.scroll_container { + width: 100%; + overflow-y: auto; + overflow-x: hidden; +} + +.container { + margin: 0rem 4rem 16rem 0.6rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/SettingBox.jsx b/src-ui/app/config_page/setting_section/setting_box/SettingBox.jsx new file mode 100644 index 00000000..eba09ff7 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/SettingBox.jsx @@ -0,0 +1,40 @@ +import { useStore_SelectedConfigTabId } from "@store"; + +import { + Device, + Appearance, + Translation, + Transcription, + Others, + AdvancedSettings, + Vr, + Supporters, + AboutVrct, +} from "@setting_box"; + +export const SettingBox = () => { + const { currentSelectedConfigTabId } = useStore_SelectedConfigTabId(); + switch (currentSelectedConfigTabId.data) { + case "device": + return ; + case "appearance": + return ; + case "translation": + return ; + case "transcription": + return ; + case "others": + return ; + case "vr": + return ; + case "advanced_settings": + return ; + case "supporters": + return ; + case "about_vrct": + return ; + + default: + return null; + } +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/_atoms/_entry/_Entry.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/_atoms/_entry/_Entry.jsx new file mode 100644 index 00000000..e8bba371 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/_atoms/_entry/_Entry.jsx @@ -0,0 +1,36 @@ +import clsx from "clsx"; +import React, { useRef, forwardRef, useImperativeHandle } from "react"; +import styles from "./_Entry.module.scss"; + +const _Entry = forwardRef((props, ref) => { + const inputRef = useRef(); + + useImperativeHandle(ref, () => ({ + focus: () => { + inputRef.current.focus(); + } + })); + const input_class_names = clsx(styles.entry_input_area, { + [styles.is_disabled]: props.is_disabled + }); + + return ( +
+
+ props.onChange(e)} + /> +
+
+ ); +}); + +_Entry.displayName = "_Entry"; + +export { _Entry }; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/_atoms/_entry/_Entry.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/_atoms/_entry/_Entry.module.scss new file mode 100644 index 00000000..c9744de6 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/_atoms/_entry/_Entry.module.scss @@ -0,0 +1,24 @@ +.entry_container { + width: 100%; +} + +.entry_wrapper { + width: 10rem; + height: 100%; + padding: 0.6rem; + background-color: var(--dark_875_color); + border: 0.1rem solid var(--dark_750_color); + border-radius: 0.4rem; +} + +.entry_input_area { + width: 100%; + height: 100%; + font-size: 1.4rem; + resize: none; + color: var(--dark_basic_text_color); + &.is_disabled { + color: var(--dark_500_color); + pointer-events: none; + } +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/action_button/ActionButton.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/action_button/ActionButton.jsx new file mode 100644 index 00000000..d85fce2a --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/action_button/ActionButton.jsx @@ -0,0 +1,11 @@ +import styles from "./ActionButton.module.scss"; + +export const ActionButton = ({IconComponent, onclickFunction}) => { + return ( +
+ +
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/action_button/ActionButton.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/action_button/ActionButton.module.scss new file mode 100644 index 00000000..116feab4 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/action_button/ActionButton.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/app/config_page/setting_section/setting_box/_components/deepl_auth_key/DeeplAuthKey.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/deepl_auth_key/DeeplAuthKey.jsx new file mode 100644 index 00000000..e462998c --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/deepl_auth_key/DeeplAuthKey.jsx @@ -0,0 +1,72 @@ +import styles from "./DeeplAuthKey.module.scss"; +import { useTranslation } from "react-i18next"; +import clsx from "clsx"; +import CircularProgress from "@mui/material/CircularProgress"; +import ExternalLink from "@images/external_link.svg?react"; +import { _Entry } from "../_atoms/_entry/_Entry"; +import { useState, useRef } from "react"; +import { useEffect } from "react"; + +export const DeeplAuthKey = (props) => { + const { t } = useTranslation(); + const [is_editable, seIsEditable] = useState(false); + const entryRef = useRef(null); + + const revealEditAuthKey = () => { + seIsEditable(true); + entryRef.current.focus(); + }; + + const onchangeEntryAuthKey = (e) => { + props.onChangeFunction(e.target.value); + }; + const saveAuthKey = () => { + props.saveFunction(); + }; + + useEffect(() => { + if (props.variable === "" || props.variable === null) { + seIsEditable(true); + } + }, [props.variable]); + + const is_disabled = props.state === "pending"; + + const save_button_class_names = clsx(styles.save_button, { + [styles.is_disabled]: is_disabled + }); + + return ( +
+
+ <_Entry ref={entryRef} width="30rem" onChange={onchangeEntryAuthKey} ui_variable={props.variable} is_disabled={is_disabled}/> + + {is_editable + ? null + : +
+ +
+ } +
+
+ ); +}; + + +export const OpenWebpage_DeeplAuthKey = () => { + const { t } = useTranslation(); + return ( + + ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/deepl_auth_key/DeeplAuthKey.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/deepl_auth_key/DeeplAuthKey.module.scss new file mode 100644 index 00000000..915f300c --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/deepl_auth_key/DeeplAuthKey.module.scss @@ -0,0 +1,101 @@ +.container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 1rem; + flex-shrink: 0; +} + +.entry_section_wrapper { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + position: relative; +} + +.entry_edit_cover { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + border-radius: 0.4rem; + background-color: (#00000044); + backdrop-filter: blur(4rem); + border: solid 0.1rem var(--dark_700_color); + &:hover { + background-color: (#00000088); + } + &:active { + backdrop-filter: blur(1.4rem); + } +} + +.edit_button { + padding: 0.8rem 1.2rem; + color: var(--dark_basic_text_color); + height: 100%; + width: 100%; + font-size: 1.4rem; + text-align: center; +} + +.save_button { + padding: 0.8rem 1.2rem; + background-color: var(--primary_600_color); + border-radius: 0.4rem; + text-align: center; + flex-shrink: 0; + min-width: 5.4rem; + &:hover { + background-color: var(--primary_500_color); + } + &:active { + background-color: var(--primary_700_color); + } + &.is_disabled { + pointer-events: none; + background-color: var(--primary_800_color); + } +} + +.save_button_label { + color: var(--dark_basic_text_color); + font-size: 1.4rem; +} + +.open_webpage_button_wrapper { + display: flex; + justify-content: center; + align-items: center; +} + +.open_webpage_button { + padding: 0.6rem 2.8rem; + display: flex; + gap: 1rem; + justify-content: center; + align-items: center; + border-radius: 0.4rem; + cursor: pointer; + flex-shrink: 0; + &:hover { + background-color: var(--dark_825_color); + } + &:active { + background-color: var(--dark_900_color); + } +} + +.open_webpage_text { + font-size: 1.2rem; + color: var(--dark_basic_text_color); +} + +.external_link_svg { + color: var(--dark_500_color); + width: 1.6rem; + flex-shrink: 0; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/download_models/DownloadModels.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/download_models/DownloadModels.jsx new file mode 100644 index 00000000..6ec51fb1 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/download_models/DownloadModels.jsx @@ -0,0 +1,81 @@ +import { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import clsx from "clsx"; +import CircularProgress from "@mui/material/CircularProgress"; +import styles from "./DownloadModels.module.scss"; +import { + RadioButton, + // DownloadModels, +} from "../index"; +export const DownloadModels = (props) => { + const options = props.options.map(item => ({ + ...item, + disabled: !item.is_downloaded + })); + + return ( + <> + + + //
+ // {props.models.map((option) => ( + // + // ))} + //
+ ); +}; + +const ModelSelector = ({option, ...props}) => { + const { t } = useTranslation(); + const [circular_color, setCircularColor] = useState(""); + const [circular_color_2, setCircularColor2] = useState(""); + useEffect(() => { + const circular_color = getComputedStyle(document.documentElement).getPropertyValue("--dark_600_color"); + setCircularColor(circular_color.trim()); + const circular_color_2 = getComputedStyle(document.documentElement).getPropertyValue("--primary_300_color"); + setCircularColor2(circular_color_2.trim()); + }, []); + + + const renderContent = () => { + const circular_progress = Math.floor(option.progress / 10) * 10; + + switch (true) { + case option.progress !== null: + return ( + <> + +

{`${Math.round(option.progress)}%`}

+ + ); + case option.is_pending: + return ; + case !option.is_downloaded: + return ( + + ); + default: + return null; + } + }; + + return
{renderContent()}
; +}; diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/download_models/DownloadModels.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/download_models/DownloadModels.module.scss new file mode 100644 index 00000000..aa17a737 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/download_models/DownloadModels.module.scss @@ -0,0 +1,32 @@ +@import "@scss_mixins"; + +.download_container { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + max-width: 8rem; +} + +.download_button { + pointer-events: auto; + background-color: var(--dark_800_color); + padding: 0.8rem; + flex-shrink: 0; + &:hover { + background-color: var(--dark_750_color); + } + &:active { + background-color: var(--dark_800_color); + } +} +.download_button_label { + font-size: 1.2rem; + color: var(--dark_basic_text_color); +} + +.progress_label { + position: absolute; + font-size: 1rem; + color: var(--dark_basic_text_color); +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/dropdown_menu/DropdownMenu.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/dropdown_menu/DropdownMenu.jsx new file mode 100644 index 00000000..423de2ef --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/dropdown_menu/DropdownMenu.jsx @@ -0,0 +1,76 @@ +import styles from "./DropdownMenu.module.scss"; +import clsx from "clsx"; +import ArrowLeftSvg from "@images/arrow_left.svg?react"; +import { useStore_IsOpenedDropdownMenu } from "@store"; + +export const DropdownMenu = (props) => { + const { updateIsOpenedDropdownMenu, currentIsOpenedDropdownMenu } = useStore_IsOpenedDropdownMenu(); + + const toggleDropdownMenu = () => { + if (currentIsOpenedDropdownMenu.data === props.dropdown_id) { + updateIsOpenedDropdownMenu(""); + } else { + if (props.openListFunction !== undefined) props.openListFunction(); + updateIsOpenedDropdownMenu(props.dropdown_id); + } + }; + + const selectValue = (key) => { + updateIsOpenedDropdownMenu(""); + props.selectFunction({ + dropdown_id: props.dropdown_id, + selected_id: key, + }); + }; + + const dropdown_content_wrapper_class_name = clsx(styles["dropdown_content_wrapper"], { + [styles.is_opened]: (currentIsOpenedDropdownMenu.data === props.dropdown_id) ? true : false, + [styles.is_disabled]: props.is_disabled, + }); + + const dropdown_toggle_button_class_name = clsx(styles["dropdown_toggle_button"], { + [styles.is_pending]: (props.state === "pending") ? true : false, + [styles.is_disabled]: props.is_disabled, + }); + + const arrow_class_names = clsx(styles["arrow_left_svg"], { + [styles.is_opened]: (currentIsOpenedDropdownMenu.data === props.dropdown_id) ? true : false + }); + + const getSelectedText = () => { + if (props.state !== "ok") return; + if (props.list[props.selected_id] === undefined) return props.selected_id; // [Fix me] + + return props.list[props.selected_id]; + }; + const list = (props.list === undefined) ? {} : props.list; + + return ( +
+
+ {(props.state === "pending") + ?

Loading...

+ :

{getSelectedText()}

+ } + {(props.state === "pending") + ? + : + } +
+
+
+ {(props.state === "ok") + ? Object.entries(list).map(([key, value]) => { + return ( +
selectValue(key)}> +

{value}

+
+ ); + }) + : null + } +
+
+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/dropdown_menu/DropdownMenu.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/dropdown_menu/DropdownMenu.module.scss new file mode 100644 index 00000000..386ee592 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/dropdown_menu/DropdownMenu.module.scss @@ -0,0 +1,97 @@ +@import "@scss_mixins"; + +.container { + position: relative; +} + +.dropdown_toggle_button { + position: relative; + background-color: var(--dark_950_color); + min-width: 20rem; + padding: 0.8rem 1.4rem; + cursor: pointer; + border-radius: 0.4rem; + &:hover { + background-color: var(--dark_925_color); + } + &:active { + background-color: var(--dark_975_color); + } + &.is_pending { + pointer-events: none; + } + &.is_disabled { + pointer-events: none; + .dropdown_selected_text, .arrow_left_svg { + color: var(--dark_550_color); + } + } +} + +.dropdown_selected_text { + font-size: 1.4rem; + padding-right: 2.8rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dropdown_content_wrapper { + display: none; + position: absolute; + top: 100%; // Position it below the toggle button + right: 0; + min-width: 20rem; + z-index: 1; + &.is_opened { + display: block; + } + &.is_disabled { + pointer-events: none; + .value_text { + color: var(--dark_550_color); + } + } +} + +.dropdown_content { + background-color: var(--dark_900_color); + border: 0.1rem solid var(--dark_600_color); + display: flex; + flex-direction: column; + gap: 0.1rem; + white-space: nowrap; + max-height: 20rem; + overflow-y: scroll; +} + +.value_button { + background-color: var(--dark_875_color); + padding: 1.2rem; + cursor: pointer; + &:hover { + background-color: var(--dark_800_color); + } + &:active { + background-color: var(--dark_900_color); + } +} + +.value_text { + font-size: 1.4rem; +} + +.loader { + @include loader(2rem, 0.2rem, right, 0); +} + +.arrow_left_svg { + position: absolute; + top: 50%; + right: 0; + transform: translate(-50%, -50%) rotate(-90deg); + width: 1.4rem; + &.is_opened { + transform: translate(-50%, -50%) rotate(90deg); + } +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/entry/Entry.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/entry/Entry.jsx new file mode 100644 index 00000000..a9908c9d --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/entry/Entry.jsx @@ -0,0 +1,10 @@ +import styles from "./Entry.module.scss"; +import { _Entry } from "../_atoms/_entry/_Entry"; + +export const Entry = (props) => { + return ( +
+ <_Entry {...props} /> +
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/entry/Entry.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/entry/Entry.module.scss new file mode 100644 index 00000000..e69de29b diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/index.js b/src-ui/app/config_page/setting_section/setting_box/_components/index.js new file mode 100644 index 00000000..7ead1049 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/index.js @@ -0,0 +1,12 @@ +export { ActionButton } from "./action_button/ActionButton"; +export { DeeplAuthKey, OpenWebpage_DeeplAuthKey } from "./deepl_auth_key/DeeplAuthKey"; +export { DropdownMenu } from "./dropdown_menu/DropdownMenu"; +export { Entry } from "./entry/Entry"; +export { LabelComponent } from "./label_component/LabelComponent"; +export { RadioButton } from "./radio_button/RadioButton"; +export { SectionLabelComponent } from "./section_label_component/SectionLabelComponent"; +export { Slider } from "./slider/Slider"; +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"; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/label_component/LabelComponent.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/label_component/LabelComponent.jsx new file mode 100644 index 00000000..a4a520db --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/label_component/LabelComponent.jsx @@ -0,0 +1,13 @@ +import styles from "./LabelComponent.module.scss"; + +export const LabelComponent = (props) => { + return ( +
+

{props.label}

+ {props.desc + ?

{props.desc}

+ : null + } +
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/label_component/LabelComponent.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/label_component/LabelComponent.module.scss new file mode 100644 index 00000000..c49055a8 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/label_component/LabelComponent.module.scss @@ -0,0 +1,22 @@ +.label_component { + display: flex; + flex-direction: column; + gap: 0.4rem; + // flex-shrink: 0; +} + +.label { + font-size: 1.6rem; + font-weight: 400; + color: var(--dark_basic_text_color); + white-space: nowrap; + width: max-content; +} + +.desc { + font-size: 1.4rem; + font-weight: 300; + color: var(--dark_500_color); + max-width: 38rem; + overflow-wrap: break-word; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/radio_button/RadioButton.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/radio_button/RadioButton.jsx new file mode 100644 index 00000000..c0278a07 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/radio_button/RadioButton.jsx @@ -0,0 +1,42 @@ +import styles from "./RadioButton.module.scss"; +import clsx from "clsx"; + +export const RadioButton = (props) => { + const containerClass = clsx(styles.container, { + [styles.column]: props.column === true, + }); + + return ( +
+ {props.checked_variable.state === "pending" && } + {props.options.map((option) => { + const radioWrapperClass = clsx(styles.radio_button_container, { + [styles.is_selected]: props.checked_variable.data === option.id, + }); + + const labelClass = clsx(styles.radio_button_wrapper, { + [styles.is_selected]: props.checked_variable.data === option.id, + [styles.disabled]: option.disabled === true || props.checked_variable.state === "pending", + }); + + return ( +
+ + {props.ChildComponent && } +
+ ); + })} +
+ ); +}; diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/radio_button/RadioButton.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/radio_button/RadioButton.module.scss new file mode 100644 index 00000000..b8ed8515 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/radio_button/RadioButton.module.scss @@ -0,0 +1,74 @@ +@import "@scss_mixins"; + +.container { + display: flex; + gap: 0.4rem; + position: relative; + flex-shrink: 0; + flex-wrap: wrap; + max-width: 70%; + &.column { + flex-direction: column; + } +} + +.radio_button_container { + display: flex; + align-items: center; + justify-content: space-between; + gap: 2rem; + position: relative; +} + + +.radio_button_wrapper { + display: flex; + flex-shrink: 0; + align-items: center; + cursor: pointer; + gap: 1rem; + padding: 0.6rem 0.8rem; + border-radius: 0.4rem; + &:hover { + background-color: var(--dark_850_color); + } + &:active { + background-color: var(--dark_925_color); + } + &.is_selected { + pointer-events: none; + } + &.disabled { + pointer-events: none; + color: var(--dark_600_color); + } +} + +.radio_button_input { + appearance: none; + margin: 0; + width: 2rem; + height: 2rem; + border: 0.3rem solid var(--dark_600_color); + border-radius: 50%; + transition: border-color .1s ease, border-width .1s ease; + flex-shrink: 0; + cursor: inherit; + &:checked { + border-color: var(--primary_400_color); + border-width: 0.6rem; + } + &:disabled { + border-color: var(--dark_825_color); + } +} + +.radio_button_label { + font-size: 1.4rem; + font-weight: 400; + flex-shrink: 0; +} + +.loader { + @include loader(2rem, 0.2rem, left, -1.6rem); +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/section_label_component/SectionLabelComponent.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/section_label_component/SectionLabelComponent.jsx new file mode 100644 index 00000000..7b79ebef --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/section_label_component/SectionLabelComponent.jsx @@ -0,0 +1,11 @@ +import styles from "./SectionLabelComponent.module.scss"; +import clsx from "clsx"; + +export const SectionLabelComponent = (props) => { + return ( +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/section_label_component/SectionLabelComponent.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/section_label_component/SectionLabelComponent.module.scss new file mode 100644 index 00000000..93be3c4b --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/section_label_component/SectionLabelComponent.module.scss @@ -0,0 +1,16 @@ +.container { + display: flex; + justify-content: center; + align-items: center; + gap: 2rem; + padding-bottom: 2rem; +} +.section_label { + font-size: 2rem; + flex-shrink: 0; +} +.section_line { + width: 100%; + height: 0.1rem; + background: linear-gradient(90deg, var(--dark_400_color) 0%, var(--dark_600_color) 35%, var(--dark_800_color) 100%); +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.jsx new file mode 100644 index 00000000..3e896a59 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.jsx @@ -0,0 +1,55 @@ +import React from "react"; +import styles from "./Slider.module.scss"; +import MUI_Slider from "@mui/material/Slider"; +import { clsx } from "clsx"; + +export const Slider = (props) => { + return ( +
+ props.onchangeFunction(value)} + onChangeCommitted={(_e, value) => props.onchangeCommittedFunction ? props.onchangeCommittedFunction(value) : null} + marks={props.marks} + track={props.track} + orientation={props.orientation} + valueLabelFormat={`${props.valueLabelFormat ? props.valueLabelFormat : props.variable}`} + sx={{ + color: "var(--dark_700_color)", + "& .MuiSlider-thumb": { + backgroundColor: "var(--primary_600_color)", + "&:hover, &.Mui-focusVisible, &.Mui-active": { + boxShadow: `0 0 0 0.8rem var(--primary_600_color_44)`, + }, + "& .MuiSlider-valueLabel": { + fontSize: "1.4rem", + backgroundColor: "var(--dark_800_color)", + padding: "0.6rem 1rem", + lineHeight: "1.15", + // top: "-1.4rem", + // "&::before": { + // left: "30%", + // width: "1rem", + // height: "1rem", + // clipPath: "polygon(50% 0, 100% 100%, 0 100%)", + // }, + }, + }, + "& .MuiSlider-markLabel": { + fontSize: "1.4rem", + color: "var(--dark_550_color)", + whiteSpace: "nowrap", + }, + "& .MuiSlider-markLabelActive": { + color: "var(--primary_300_color)", + }, + }} + /> +
+ ); +}; diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.module.scss new file mode 100644 index 00000000..79db6294 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/slider/Slider.module.scss @@ -0,0 +1,11 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + padding-left: 4rem; + &.no_padding { + padding-left: 0; + } +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/switch_box/SwitchBox.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/switch_box/SwitchBox.jsx new file mode 100644 index 00000000..2d77a673 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/switch_box/SwitchBox.jsx @@ -0,0 +1,44 @@ +import clsx from "clsx"; +import { useState } from "react"; +import styles from "./SwitchBox.module.scss"; + +export const SwitchBox = (props) => { + const [is_hovered, setIsHovered] = useState(false); + const [is_mouse_down, setIsMouseDown] = useState(false); + + const is_pending = (props.variable.state === "pending"); + + const getClassNames = (base_class) => clsx(base_class, { + [styles.is_active]: (props.variable.data === true), + [styles.is_pending]: is_pending, + [styles.is_hovered]: is_hovered, + [styles.is_mouse_down]: is_mouse_down, + }); + + const onMouseEnter = () => setIsHovered(true); + const onMouseLeave = () => setIsHovered(false); + const onMouseDown = () => setIsMouseDown(true); + const onMouseUp = () => setIsMouseDown(false); + + const toggleFunction = () => { + props.toggleFunction(); + }; + + + return ( +
+
+
+ + {is_pending && } +
+
+
+ ); +}; diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/switch_box/SwitchBox.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/switch_box/SwitchBox.module.scss new file mode 100644 index 00000000..8f76c0b6 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/switch_box/SwitchBox.module.scss @@ -0,0 +1,33 @@ +@import "@scss_mixins"; + +.switchbox_container { + width: 100%; + display: flex; + justify-content: end; + align-items: center; + height: 2rem; +} + +.switchbox_wrapper { + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + padding: 2rem; + height: 100%; + flex-shrink: 0; + &.is_pending { + pointer-events: none; + } +} + +.toggle_control { + @include toggle_control_styles; + display: flex; + justify-content: center; + align-items: center; +} + +.loader { + @include loader(2rem, 0.2rem, right, -4rem); +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/ThresholdComponent.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/ThresholdComponent.jsx new file mode 100644 index 00000000..018f3845 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/ThresholdComponent.jsx @@ -0,0 +1,136 @@ +import { useEffect, useState } from "react"; +import styles from "./ThresholdComponent.module.scss"; +import { SliderAndMeter } from "./slider_and_meter/SliderAndMeter"; +import { ThresholdEntry } from "./threshold_entry/ThresholdEntry"; +import { VolumeCheckButton } from "./volume_check_button/VolumeCheckButton"; +import { useVolume } from "@logics_common"; +import MicSvg from "@images/mic.svg?react"; +import HeadphonesSvg from "@images/headphones.svg?react"; +import { + useMicThreshold, + useSpeakerThreshold, +} from "@logics_configs"; + +export const ThresholdComponent = (props) => { + return ( +
+ {props.id === "mic_threshold" + ? + : + } +
+ ); +}; + +const MicComponent = (props) => { + const { + currentMicThreshold, + setMicThreshold, + currentEnableAutomaticMicThreshold, + } = useMicThreshold(); + const [ui_threshold, setUiThreshold] = useState(currentMicThreshold.data); + const { + volumeCheckStart_Mic, + volumeCheckStop_Mic, + currentMicThresholdCheckStatus, + } = useVolume(); + + + useEffect(() => { + if (currentEnableAutomaticMicThreshold.data === true) { + setUiThreshold("Auto"); + } else { + setUiThreshold(currentMicThreshold.data); + } + }, [currentMicThreshold.data, currentEnableAutomaticMicThreshold]); + + const setUiThresholdFunction = (payload_ui_threshold) => { + setUiThreshold(payload_ui_threshold); + }; + const setThresholdFunction = (payload_threshold) => { + setMicThreshold(payload_threshold); + }; + + const is_disable = currentEnableAutomaticMicThreshold.data === true ? true : false; + + return ( + <> + + + + + ); +}; + +const SpeakerComponent = (props) => { + const { + currentSpeakerThreshold, + setSpeakerThreshold, + currentEnableAutomaticSpeakerThreshold, + } = useSpeakerThreshold(); + const [ui_threshold, setUiThreshold] = useState(currentSpeakerThreshold.data); + const { + volumeCheckStart_Speaker, + volumeCheckStop_Speaker, + currentSpeakerThresholdCheckStatus, + } = useVolume(); + + useEffect(() => { + if (currentEnableAutomaticSpeakerThreshold.data === true) { + setUiThreshold("Auto"); + } else { + setUiThreshold(currentSpeakerThreshold.data); + } + }, [currentSpeakerThreshold.data, currentEnableAutomaticSpeakerThreshold]); + + const setUiThresholdFunction = (payload_ui_threshold) => { + setUiThreshold(payload_ui_threshold); + }; + const setThresholdFunction = (payload_threshold) => { + setSpeakerThreshold(payload_threshold); + }; + + const is_disable = currentEnableAutomaticSpeakerThreshold.data === true ? true : false; + + return ( + <> + + + + + ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/ThresholdComponent.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/ThresholdComponent.module.scss new file mode 100644 index 00000000..205b7160 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/ThresholdComponent.module.scss @@ -0,0 +1,7 @@ +.container { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + gap: 2rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/slider_and_meter/SliderAndMeter.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/slider_and_meter/SliderAndMeter.jsx new file mode 100644 index 00000000..8c67637c --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/slider_and_meter/SliderAndMeter.jsx @@ -0,0 +1,89 @@ +import styles from "./SliderAndMeter.module.scss"; +import { + useStore_MicVolume, + useStore_SpeakerVolume, +} from "@store"; +import { + useMicThreshold, + useSpeakerThreshold, +} from "@logics_configs"; + +export const SliderAndMeter = (props) => { + return ( +
+
+ {props.id === "mic_threshold" + ? + : + } +
+
+ ); +}; + +const ThresholdVolumeMeter_Mic = (props) => { + const { currentMicVolume } = useStore_MicVolume(); + + const { currentEnableAutomaticMicThreshold } = useMicThreshold(); + + const currentVolumeVariable = Math.min(currentMicVolume.data, props.max); + const volume_width_percentage = (currentVolumeVariable / props.max) * 100; + + return ( + <> + + {currentEnableAutomaticMicThreshold.data === false && + props.setUiThresholdFunction(e.target.value)} + onMouseUp={(e) => props.setThresholdFunction(e.target.value)} + className={styles.threshold_slider} + /> + } + + ); +}; + +const ThresholdVolumeMeter_Speaker = (props) => { + const { currentSpeakerVolume } = useStore_SpeakerVolume(); + + const { currentEnableAutomaticSpeakerThreshold } = useSpeakerThreshold(); + + const currentVolumeVariable = Math.min(currentSpeakerVolume.data, props.max); + const volume_width_percentage = (currentVolumeVariable / props.max) * 100; + + return ( + <> + + {currentEnableAutomaticSpeakerThreshold.data === false && + props.setUiThresholdFunction(e.target.value)} + onMouseUp={(e) => props.setThresholdFunction(e.target.value)} + className={styles.threshold_slider} + /> + } + + ); +}; + + + +const VolumeMeter = ({ volume_width_percentage, volume, threshold }) => { + + return ( +
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/slider_and_meter/SliderAndMeter.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/slider_and_meter/SliderAndMeter.module.scss new file mode 100644 index 00000000..1ff43512 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/slider_and_meter/SliderAndMeter.module.scss @@ -0,0 +1,55 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + flex: 1; + // width: 100%; + position: relative; // for dev +} + +.meter_container { + position: relative; + width: 100%; + height: 0.8rem; + background: var(--dark_800_color); + border-radius: 0.4rem; +} + +.volume_meter { + height: 100%; + border-radius: inherit; + transition: width 0.1s ease, background-color 0.1s ease; +} + +.threshold_slider { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: all; + + &::-webkit-slider-runnable-track { + width: 100%; + background: transparent; + cursor: pointer; + } + + &::-webkit-slider-thumb { + -webkit-appearance: none; + width: 0.4rem; + height: 4rem; + background: var(--primary_600_color); + border-radius: 0.2rem; + cursor: pointer; + } + + &:hover::-webkit-slider-thumb{ + background: var(--primary_500_color); + } + + &:focus { + outline: none; + } +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/threshold_entry/ThresholdEntry.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/threshold_entry/ThresholdEntry.jsx new file mode 100644 index 00000000..cb7b010c --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/threshold_entry/ThresholdEntry.jsx @@ -0,0 +1,59 @@ +import clsx from "clsx"; +import styles from "./ThresholdEntry.module.scss"; + +export const ThresholdEntry = (props) => { + return ( +
+
+ {props.id === "mic_threshold" + ? + : + } +
+
+ ); +}; + +const ThresholdEntry_Mic = (props) => { + const onChangeFunction = (e) => { + if (e.currentTarget.value === "") { + props.setThresholdFunction("0"); + } else { + props.setThresholdFunction(e.currentTarget.value); + } + }; + + const class_names = clsx(styles.entry_input_area, { + [styles.is_disable]: props.is_disable + }); + + return ( + + ); +}; + +const ThresholdEntry_Speaker = (props) => { + const onChangeFunction = (e) => { + if (e.currentTarget.value === "") { + props.setThresholdFunction("0"); + } else { + props.setThresholdFunction(e.currentTarget.value); + } + }; + + const class_names = clsx(styles.entry_input_area, { + [styles.is_disable]: props.is_disable + }); + + return ( + + ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/threshold_entry/ThresholdEntry.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/threshold_entry/ThresholdEntry.module.scss new file mode 100644 index 00000000..929f8975 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/threshold_entry/ThresholdEntry.module.scss @@ -0,0 +1,24 @@ +.container { + +} + +.entry_wrapper { + width: 6rem; + height: 100%; + padding: 0.6rem; + background-color: var(--dark_875_color); + border: 0.1rem solid var(--dark_750_color); + border-radius: 0.4rem; +} + +.entry_input_area { + width: 100%; + height: 100%; + font-size: 1.4rem; + resize: none; + color: var(--dark_basic_text_color); + &.is_disable { + color: var(--dark_500_color); + pointer-events: none; + } +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/volume_check_button/VolumeCheckButton.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/volume_check_button/VolumeCheckButton.jsx new file mode 100644 index 00000000..0f733b48 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/volume_check_button/VolumeCheckButton.jsx @@ -0,0 +1,33 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import clsx from "clsx"; +import styles from "./VolumeCheckButton.module.scss"; + +export const VolumeCheckButton = React.memo((props) => { + const { t } = useTranslation(); + const getClassNames = (baseClass) => clsx(baseClass, { + [styles.is_active]: (props.isChecking?.data === true), + [styles.is_pending]: (props.isChecking.state === "pending"), + }); + + const toggleFunction = () => { + if (props.isChecking?.data === true) { + props.stopFunction(); + } else if (props.isChecking?.data === false) { + props.startFunction(); + } + }; + + + return ( +
+
+ +

{t("config_page.device.check_volume")}

+
+
+ ); +}); + + +VolumeCheckButton.displayName = "VolumeCheckButton"; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/volume_check_button/VolumeCheckButton.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/volume_check_button/VolumeCheckButton.module.scss new file mode 100644 index 00000000..302ab71e --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/threshold_component/volume_check_button/VolumeCheckButton.module.scss @@ -0,0 +1,45 @@ +.button { + width: 100%; + background-color: var(--dark_800_color); + display: flex; + justify-content: center; + align-items: center; + padding: 0.8rem 1rem; + border-radius: 0.4rem; + gap: 0.4rem; + cursor: pointer; + &.is_active { + background-color: var(--primary_500_color); + &:hover { + background-color: var(--primary_450_color); + } + &:active { + background-color: var(--primary_550_color); + } + } + &.is_pending { + background-color: var(--dark_850_color); + pointer-events: none; + .button_svg, .button_text { + color: var(--dark_500_color); + } + } + + + &:hover { + background-color: var(--dark_775_color); + } + &:active { + background-color: var(--dark_850_color); + } +} + +.button_svg { + width: 1.4rem; + color: var(--dark_350_color); +} + +.button_text { + font-size: 1.2rem; + color: var(--dark_basic_text_color); +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/word_filter/WordFilter.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/word_filter/WordFilter.jsx new file mode 100644 index 00000000..d2a4a62d --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/word_filter/WordFilter.jsx @@ -0,0 +1,153 @@ +import styles from "./WordFilter.module.scss"; +import { _Entry } from "../_atoms/_entry/_Entry"; +import { useState } from "react"; +import { useStore_IsOpenedMicWordFilterList } from "@store"; +import { useMicWordFilterList } from "@logics_configs"; + +export const WordFilter = () => { + const [input_value, setInputValue] = useState(""); + const { currentMicWordFilterList, updateMicWordFilterList, setMicWordFilterList } = useMicWordFilterList(); + const { currentIsOpenedMicWordFilterList, updateIsOpenedMicWordFilterList } = useStore_IsOpenedMicWordFilterList(); + + const extractRedoableFalseItem = (updated_list) => { + return updated_list.filter(item => { + if (item.is_redoable === false) return true; + }); + }; + + const onChangeEntry = (e) => { + setInputValue(e.target.value); + }; + + const addWords = () => { + if (input_value === undefined) return; + updateMicWordFilterList((prev_list) => { + const input_value_array = input_value.split(","); + let updated_list = [...prev_list.data]; + for (let each_input_value of input_value_array) { + each_input_value = each_input_value.trim(); + if (each_input_value) { + const target_item = updated_list.find((item) => item.value === each_input_value); + if (target_item === undefined) { + // Add + updated_list = [...updated_list, { value: each_input_value, is_redoable: false }]; + } else { + // Update + updated_list = updated_list.map(item => + item.value === each_input_value ? { ...item, is_redoable: false } : item + ); + } + } + } + const updated_list_for_restoring = extractRedoableFalseItem(updated_list).map((d) => d.value); + setMicWordFilterList(updated_list_for_restoring); + return updated_list; + }); + + updateIsOpenedMicWordFilterList(true); + setInputValue(""); + }; + + + const updateRedoable = (target_item_value, is_redoable) => { + updateMicWordFilterList((prev_list) => { + const updated_list = prev_list.data.map(item => + item.value === target_item_value ? { ...item, is_redoable: is_redoable } : item + ); + const updated_list_for_restoring = extractRedoableFalseItem(updated_list).map((d) => d.value); + setMicWordFilterList(updated_list_for_restoring); + return updated_list; + }); + }; + + const deleteAction = (target_item_value) => { + updateRedoable(target_item_value, true); + }; + + const redoAction = (target_item_value) => { + updateRedoable(target_item_value, false); + }; + + + return ( +
+ { currentIsOpenedMicWordFilterList.data && +
+ { + currentMicWordFilterList.data.map((item, index) => { + return ; + }) + } +
+ } +
+ <_Entry width="30rem" onChange={onChangeEntry} ui_variable={input_value}/> + +
+
+ ); +}; + +import DeleteSvg from "@images/cancel.svg?react"; +import RedoSvg from "@images/redo.svg?react"; +import clsx from "clsx"; +const WordFilterItem = (props) => { + + + const item_wrapper_class_names = clsx(styles["item_wrapper"], { + [styles["is_redoable"]]: props.is_redoable + }); + + const item_text_class_names = clsx(styles["item_text"], { + [styles["is_redoable"]]: props.is_redoable + }); + + const target_item_value = props.value; + + return ( +
+

{target_item_value}

+ {props.is_redoable + ? + + : + + } +
+ ); +}; + +import { useTranslation } from "react-i18next"; + +import ArrowLeftSvg from "@images/arrow_left.svg?react"; +export const WordFilterListToggleComponent = (props) => { + const { t } = useTranslation(); + const { currentIsOpenedMicWordFilterList, updateIsOpenedMicWordFilterList } = useStore_IsOpenedMicWordFilterList(); + const { currentMicWordFilterList } = useMicWordFilterList(); + + + const svg_class_names = clsx(styles["arrow_left_svg"], { + [styles.to_down]: !currentIsOpenedMicWordFilterList.data, + [styles.to_up]: currentIsOpenedMicWordFilterList.data + }); + + const OnclickFunction = () => { + updateIsOpenedMicWordFilterList(!currentIsOpenedMicWordFilterList.data); + }; + + const word_filter_list_length = currentMicWordFilterList.data.filter(item => item.is_redoable === false).length; + + + return ( +
+

{t("config_page.transcription.mic_word_filter.count_desc", {count: word_filter_list_length} )}

+ +
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/word_filter/WordFilter.module.scss b/src-ui/app/config_page/setting_section/setting_box/_components/word_filter/WordFilter.module.scss new file mode 100644 index 00000000..74e42ef5 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_components/word_filter/WordFilter.module.scss @@ -0,0 +1,125 @@ +.container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 2rem; +} + + +.list_section_wrapper { + display: flex; + flex-wrap: wrap; + width: 100%; + gap: 0.6rem; + overflow-y: auto; + max-height: 20rem; + padding-right: 2rem; +} + +.item_wrapper { + display: flex; + justify-content: center; + align-items: center; + gap: 0.4rem; + background-color: var(--dark_800_color); + padding: 0.2rem 0.2rem 0.2rem 1rem; + border-radius: 0.4rem; + &.is_redoable { + background-color: var(--dark_850_color); + } +} +.item_text { + font-size: 1.4rem; + font-weight: 300; + color: var(--dark_basic_text_color); + &.is_redoable { + text-decoration: line-through; + } +} + +.action_button { + border-radius: 0.4rem; + &:hover { + background-color: var(--dark_750_color); + } + &:active { + background-color: var(--dark_850_color); + } + &.delete { + padding: 0.2rem; + } + &.redo { + padding: 0.6rem; + } +} + +.delete_svg { + width: 2.4rem; + color: #bb4448; +} + +.redo_svg { + width: 1.6rem; + color: var(--dark_500_color); +} + + + + +.entry_section_wrapper { + display: flex; + justify-content: center; + align-items: center; + gap: 2rem; +} + +.add_button { + padding: 0.8rem 1.2rem; + background-color: var(--primary_600_color); + color: var(--dark_basic_text_color); + font-size: 1.4rem; + border-radius: 0.4rem; + text-align: center; + flex-shrink: 0; + &:hover { + background-color: var(--primary_500_color); + } + &:active { + background-color: var(--primary_700_color); + } +} + + + +.toggle_button_container { + display: flex; + justify-content: center; + align-items: center; + gap: 2rem; +} + +.words_count_text { + font-size: 1.6rem; +} + +.toggle_button_wrapper { + padding: 1.6rem; + border-radius: 0.4rem; + &:hover { + background-color: var(--dark_825_color); + } + &:active { + background-color: var(--dark_900_color); + } +} + +.arrow_left_svg { + width: 2.4rem; + &.to_down { + transform: rotate(-90deg); + } + &.to_up { + transform: rotate(90deg); + } +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_templates/Templates.jsx b/src-ui/app/config_page/setting_section/setting_box/_templates/Templates.jsx new file mode 100644 index 00000000..5f42276f --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_templates/Templates.jsx @@ -0,0 +1,118 @@ +import clsx from "clsx"; + +import styles from "./Templates.module.scss"; +import { useStore_IsOpenedDropdownMenu, useStore_IsBreakPoint } from "@store"; +import { + LabelComponent, + DropdownMenu, + Slider, + SwitchBox, + Entry, + RadioButton, + OpenWebpage_DeeplAuthKey, + DeeplAuthKey, + ActionButton, + WordFilter, + WordFilterListToggleComponent, + DownloadModels, +} from "../_components/"; +import { Checkbox } from "@common_components"; + +const LabeledContainer = ({ children, label, desc, custom_class_name }) => ( +
+ + {children} +
+); + +export const useOnMouseLeaveDropdownMenu = () => { + const { updateIsOpenedDropdownMenu } = useStore_IsOpenedDropdownMenu(); + + const onMouseLeaveFunction = () => { + updateIsOpenedDropdownMenu(""); + }; + + return { onMouseLeaveFunction }; +}; + +export const DropdownMenuContainer = (props) => { + const { onMouseLeaveFunction } = useOnMouseLeaveDropdownMenu(); + return ( +
+ + +
+ ); +}; + +const CommonContainer = ({ Component, ...props }) => { + const { currentIsBreakPoint } = useStore_IsBreakPoint(); + + const container_class = clsx(styles.container, { + [styles.is_break_point]: props.add_break_point ?? currentIsBreakPoint.data, + }); + + return ( + + + + ); +}; +export const SliderContainer = (props) => ( + +); + +export const CheckboxContainer = (props) => ( + +); + +export const SwitchBoxContainer = (props) => ( + +); + +export const EntryContainer = (props) => ( + +); + +export const RadioButtonContainer = (props) => ( + +); + +export const DeeplAuthKeyContainer = (props) => { + const { currentIsBreakPoint } = useStore_IsBreakPoint(); + const container_class = clsx(styles.container, { + [styles.is_break_point]: currentIsBreakPoint.data, + }); + + return ( +
+
+ + +
+ +
+ ); +}; + +export const ActionButtonContainer = (props) => ( + +); + +export const WordFilterContainer = (props) => ( +
+
+
+ +
+ +
+
+ +
+
+); + +export const DownloadModelsContainer = (props) => ( + +); \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/_templates/Templates.module.scss b/src-ui/app/config_page/setting_section/setting_box/_templates/Templates.module.scss new file mode 100644 index 00000000..cab3118a --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/_templates/Templates.module.scss @@ -0,0 +1,57 @@ +.container { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + padding: 2rem; + gap: 2rem; + &.flex_column { + flex-direction: column; + } + border-bottom: solid 0.1rem var(--dark_800_color); + + &.is_break_point { + flex-direction: column; + gap: 2rem; + align-items: start; + } +} + +.label_only_section { + width: 100%; +} + +.deepl_auth_key_label_section { + max-width: 34rem; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 1.4rem; +} + +.message_format_section { + width: 100%; +} + + +.word_filter_container { + display: flex; + width: 100%; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 2rem; + padding: 2rem; +} + +.word_filter_switch_section { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; +} + +.word_filter_label_wrapper { + max-width: 34rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/about_vrct/AboutVrct.jsx b/src-ui/app/config_page/setting_section/setting_box/about_vrct/AboutVrct.jsx new file mode 100644 index 00000000..5a8d49b3 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/about_vrct/AboutVrct.jsx @@ -0,0 +1,135 @@ +import styles from "./AboutVrct.module.scss"; +import dev_section_title from "@images/about_vrct/dev_section_title.png"; +import dev_misya from "@images/about_vrct/dev_misya.png"; +import dev_shiina from "@images/about_vrct/dev_shiina.png"; +import vrct_logo_for_about_vrct from "@images/about_vrct/vrct_logo_for_about_vrct.png"; +import contributors_section_title from "@images/about_vrct/contributors_section_title.png"; +import contributors_members from "@images/about_vrct/contributors_members.png"; +import localization_section_title from "@images/about_vrct/localization_section_title.png"; +import localization_members from "@images/about_vrct/localization_members.png"; + +import special_thanks_section_title from "@images/about_vrct/special_thanks_section_title.png"; +import special_thanks_members from "@images/about_vrct/special_thanks_members.png"; +import special_thanks_message_en from "@images/about_vrct/special_thanks_message_en.png"; +import special_thanks_message_ja from "@images/about_vrct/special_thanks_message_ja.png"; + +import poster_showcase_section_title from "@images/about_vrct/poster_showcase_section_title.png"; + +import vrchat_disclaimer from "@images/about_vrct/vrchat_disclaimer.png"; + +import clsx from "clsx"; +import { useTranslation } from "react-i18next"; +import { useStore_UiLanguage } from "@store"; +import { PosterShowcaseContents } from "./poster_showcase_contents/PosterShowcaseContents"; + +export const AboutVrct = () => { + const { t } = useTranslation(); + const { currentUiLanguage } = useStore_UiLanguage(); + return ( +
+
+ +
+
+ + + +
+
+ + +
+
+
+ +
+ +
+ + + + +
+
+ +
+ +
+ + + + + + + +
+
+ +
+ + +
+ +
+ + + { + currentUiLanguage.data === "ja" + ? + : + } +
+ + +
+ + +
+ +
+ +
+ + +
+ ); +}; + +import dev_x_icon from "@images/about_vrct/dev_x_icon.png"; +import dev_github_icon from "@images/about_vrct/dev_github_icon.png"; +import contributors_x_icon from "@images/about_vrct/contributors_x_icon.png"; +import contributors_github_icon from "@images/about_vrct/contributors_github_icon.png"; + +import project_link_booth from "@images/about_vrct/project_link_booth.png"; +import project_link_documents from "@images/about_vrct/project_link_documents.png"; +import project_link_vrct_github from "@images/about_vrct/project_link_vrct_github.png"; +import project_link_contact_us from "@images/about_vrct/project_link_contact_us.png"; + +const about_vrct_links = { + dev_misya_x: { img: dev_x_icon, href: "https://twitter.com/misya_ai" }, + dev_misya_github: { img: dev_github_icon, href: "https://github.com/misyaguziya" }, + dev_shiina_x: { img: dev_x_icon, href: "https://twitter.com/Shiina_12siy" }, + + project_link_booth: { img: project_link_booth, href: "https://misyaguziya.booth.pm/items/5155325" }, + project_link_documents: { img: project_link_documents, href: "https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246" }, + project_link_vrct_github: { img: project_link_vrct_github, href: "https://github.com/misyaguziya/VRCT" }, + project_link_contact_us: { img: project_link_contact_us, href: "https://docs.google.com/forms/d/e/1FAIpQLSei-xoydOY60ivXqhOjaTzNN8PiBQIDcNhzfy6cw2sjYkcg_g/viewform" }, + + contributors_done_san_x: { img: contributors_x_icon, href: "https://twitter.com/done_vrc" }, + contributors_iya_x: { img: contributors_x_icon, href: "https://twitter.com/IYAA_HHHH" }, + contributors_rera_x: { img: contributors_x_icon, href: "https://twitter.com/rerassi" }, + contributors_rera_github: { img: contributors_github_icon, href: "https://github.com/soumt-r" }, + contributors_poposuke_x: { img: contributors_x_icon, href: "https://twitter.com/sig_popo" }, + contributors_kumaguma_x: { img: contributors_x_icon, href: "https://twitter.com/K_kumaguma_A" }, +}; + +const OpenLinkContainer = ({className, href_id}) => { + const href = about_vrct_links[href_id].href; + const img = about_vrct_links[href_id].img; + return ( + + {/* for adjust size to their parent component's width. */} + + + ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/about_vrct/AboutVrct.module.scss b/src-ui/app/config_page/setting_section/setting_box/about_vrct/AboutVrct.module.scss new file mode 100644 index 00000000..741b2934 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/about_vrct/AboutVrct.module.scss @@ -0,0 +1,171 @@ +.container { + display: flex; + gap: 2.2rem; + flex-direction: column; + width: 72rem; + // background-color: gray; +} + +.section_title { + height: 1.2rem; + object-fit: contain; + object-position: left; + &.the_developers { + margin-bottom: 0.8rem; + } + &.contributors { + margin-bottom: 0.8rem; + } + &.special_thanks { + margin-bottom: 0.6rem; + } + &.poster_showcase { + margin-bottom: 0.6rem; + } +} + +.dev_section { + display: flex; + flex-direction: column; +} +.dev_section_wrapper { + display: flex; + justify-content: space-between; +} +.dev_card_wrapper { + width: 34.6rem; + position: relative; +} +.dev_card_img { + width: 100%; +} + +@mixin dev_sns_styles($right) { + position: absolute; + right: $right; + bottom: 1.2rem; + width: 2.8rem; + padding: 0.4rem; + border-radius: 0.4rem; + &:hover { + background-color: var(--dark_800_color); + } + &:active { + background-color: var(--dark_900_color) + } +} +.dev_misya_x { + @include dev_sns_styles(6rem); +} +.dev_misya_github { + @include dev_sns_styles(3rem); +} +.dev_shiina_x { + @include dev_sns_styles(2.4rem); +} + + +.project_links_and_logo_section { + display: flex; + flex-direction: row; + justify-content: space-between; + text-align: center; + padding: 0 5.5rem; +} +.about_vrct_logo { + width: 20rem; + object-fit: contain; +} +.project_links_wrapper { + display: flex; + flex-direction: column; + gap: 0.2rem; + align-items: start; +} + +.project_link { + height: 2.6rem; + padding: 0.4rem 1rem; + border-radius: 0.4rem; + &:hover { + background-color: var(--dark_850_color); + } + &:active { + background-color: var(--dark_900_color) + } +} + +.contributors_img_wrapper { + position: relative; +} +.contributors_img { + width: 100%; +} + +@mixin contributors_sns_styles($top, $left) { + position: absolute; + left: $left; + top: $top; + width: 2.4rem; + padding: 0.4rem; + border-radius: 0.4rem; + &:hover { + background-color: var(--dark_825_color); + } + &:active { + background-color: var(--dark_888_color) + } +} + +$first_line_top: 6.2rem; +.contributors_done_san_x { + @include contributors_sns_styles($first_line_top, 2rem); +} +.contributors_iya_x { + @include contributors_sns_styles($first_line_top, 27.6rem); +} +.contributors_rera_x { + @include contributors_sns_styles($first_line_top, 52.2rem); +} +.contributors_rera_github { + @include contributors_sns_styles($first_line_top, 55rem); +} + +$second_line_top: 16.6rem; +.contributors_poposuke_x { + @include contributors_sns_styles($second_line_top, 14.8rem); +} +.contributors_kumaguma_x { + @include contributors_sns_styles($second_line_top, 40.8rem); +} + +.localization_section { + display: flex; + flex-direction: column; +} +.localization_members_img { + width: 100%; +} + +.special_thanks_section { + display: flex; + flex-direction: column; +} +.special_thanks_members_img { + width: 100%; + margin-bottom: 0.4rem; +} +.special_thanks_message_img { + width: 100%; +} + + +.poster_showcase_section { + display: flex; + flex-direction: column; +} + +.vrchat_disclaimer { + width: 100%; + margin-top: 8rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/PosterShowcaseContents.jsx b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/PosterShowcaseContents.jsx new file mode 100644 index 00000000..25bed537 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/PosterShowcaseContents.jsx @@ -0,0 +1,12 @@ +import styles from "./PosterShowcaseContents.module.scss"; +import { PostersContents } from "./posters_contents/PostersContents"; +import { PosterShowcaseWorldsContents } from "./poster_showcase_worlds_contents/PosterShowcaseWorldsContents"; + +export const PosterShowcaseContents = () => { + return ( +
+ + +
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/PosterShowcaseContents.module.scss b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/PosterShowcaseContents.module.scss new file mode 100644 index 00000000..c7b760b7 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/PosterShowcaseContents.module.scss @@ -0,0 +1,6 @@ +.container { + display: flex; + justify-content: space-between; + align-items: start; + gap: 2rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/poster_showcase_worlds_contents/PosterShowcaseWorldsContents.jsx b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/poster_showcase_worlds_contents/PosterShowcaseWorldsContents.jsx new file mode 100644 index 00000000..e51c8ea5 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/poster_showcase_worlds_contents/PosterShowcaseWorldsContents.jsx @@ -0,0 +1,93 @@ +import clsx from "clsx"; +import styles from "./PosterShowcaseWorldsContents.module.scss"; +import { useStore_PosterShowcaseWorldPageIndex } from "@store"; +const images = import.meta.glob("@images/about_vrct/showcased_worlds/*.{png,jpg,jpeg,svg}", { eager: true }); + +const getImageByFileName = (file_name) => { + const imagePath = Object.keys(images).find((path) => path.endsWith(file_name + ".png")); + return imagePath ? images[imagePath]?.default : null; +}; + +import poster_showcase_worlds_settings from "./poster_showcase_worlds_settings"; +import { chunkArray } from "@utils"; + +export const PosterShowcaseWorldsContents = () => { + const { currentPosterShowcaseWorldPageIndex } = useStore_PosterShowcaseWorldPageIndex(); + const poster_showcase_world_images = poster_showcase_worlds_settings.map((setting) => ({ + img: getImageByFileName(setting.image_file_name), + x_post_num: setting.x_post_num + })); + + const chunked_poster_showcase_world_images = chunkArray(poster_showcase_world_images, 8); + const target_poster_showcase_world_images = chunked_poster_showcase_world_images[currentPosterShowcaseWorldPageIndex.data]; + + + return ( +
+
+ {target_poster_showcase_world_images.map((poster, index) => { + const class_names = clsx(styles.poster_showcase_world_wrapper, { + [styles.clickable]: (poster.x_post_num !== null) + }); + + const content = ( +
+ +
+ ); + if (poster.x_post_num !== null) { + return ( + + {content} + + ); + } else { + return ( +
+ {content} +
+ ); + } + })} +
+ +
+ ); +}; + +import chat_white_square from "@images/chato_white_square.png"; +import { useEffect } from "react"; +import { randomIntMinMax } from "@utils"; +const PosterShowcaseWorldsPagination = ({ page_length }) => { + const { currentPosterShowcaseWorldPageIndex, updatePosterShowcaseWorldPageIndex } = useStore_PosterShowcaseWorldPageIndex(); + + useEffect(() => { + updatePosterShowcaseWorldPageIndex(randomIntMinMax(page_length -1)); + },[page_length]); + + const setPage = (index) => { + updatePosterShowcaseWorldPageIndex(index); + }; + + const getClassNames = (index, baseClass) => clsx(baseClass, { + [styles.is_active]: (currentPosterShowcaseWorldPageIndex.data === index), + }); + + return ( +
+ {[...Array(page_length).keys()].map((index) => { + return ( +
setPage(index)}> +
+ +
+
+
+

{index + 1}

+
+
+ ); + })} +
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/poster_showcase_worlds_contents/PosterShowcaseWorldsContents.module.scss b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/poster_showcase_worlds_contents/PosterShowcaseWorldsContents.module.scss new file mode 100644 index 00000000..fa819d64 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/poster_showcase_worlds_contents/PosterShowcaseWorldsContents.module.scss @@ -0,0 +1,126 @@ +.container { + display: flex; + flex-direction: column; + flex: 1; + gap: 1rem; + justify-content: space-between; +} +$image_height: 2.8rem; +$y_padding: 0.4rem; +$image_height_gap: 0.4rem; +.poster_showcase_world_container { + display: flex; + flex-direction: column; + align-items: stretch; + gap: $image_height_gap; + height: calc( (($image_height + ($y_padding*2)) * 8) + ($image_height_gap * (8 - 1)) ); +} +.poster_showcase_world_wrapper { + display: flex; + padding: $y_padding 0.6rem $y_padding 0.8rem; + border-radius: 0.4rem 0 0 0.4rem; + &.clickable { + cursor: pointer; + &:hover { + background-color: var(--dark_850_color); + } + &:active { + background-color: var(--dark_925_color); + } + } + +} +.poster_showcase_world_img { + height: $image_height; +} + +.pagination_container { + display: flex; + justify-content: space-between; + gap: 6%; + margin: 0 2.6rem; +} + +$animation_duration: .1s; +.pagination_box { + width: 100%; + cursor: pointer; + &:active .pagination_chato_img { + animation: tremble_animation $animation_duration ease-out; + } + &:active.is_active .pagination_chato_img { + transform: translate(-50%, -50%) rotate(-22deg); + } + &.is_active .pagination_chato_img { + top: 48%; + animation: rotate_animation $animation_duration ease-out; + } + &:hover { + & .pagination_chato_img { + top: 108%; + } + &.is_active .pagination_chato_img { + animation: tremble_animation $animation_duration ease-out; + } + & .pagination_num { + color: var(--dark_400_color); + } + } +} + +.indicator_box { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.indicator { + width: 100%; + height: 0.2rem; + background-color: var(--dark_825_color); + &.is_active { + background-color: var(--primary_400_color); + } +} + +.pagination_num { + font-size: 1.8rem; + padding: 1rem; + color: var(--dark_600_color); + &.is_active { + color: var(--primary_300_color); + } +} + +.chato_box { + position: relative; + width: 100%; + height: 6rem; + overflow: hidden; +} + +.pagination_chato_img { + position: absolute; + top: 200%; + left: 51%; + transform: translate(-50%, -50%) rotate(22deg); + width: 2.8rem; + transition: top $animation_duration ease-out; +} +@keyframes rotate_animation { + 0% { + transform: translate(-50%, -50%) rotate(22deg); + } + 100% { + transform: translate(-50%, -50%) rotate(360deg + 22deg); + } +} + +@keyframes tremble_animation { + 0% { left: 51%; } + 25% { left: 55%; } + 50% { left: 45%; } + 75% { left: 48%; } + 100% { left: 51%; } +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/poster_showcase_worlds_contents/poster_showcase_worlds_settings.js b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/poster_showcase_worlds_contents/poster_showcase_worlds_settings.js new file mode 100644 index 00000000..74e15af6 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/poster_showcase_worlds_contents/poster_showcase_worlds_settings.js @@ -0,0 +1,95 @@ +const poster_showcase_worlds_settings = [ + // トサカひよ + { image_file_name: "kokekkopiyopiyo", x_post_num: "1779076974369276014" }, + + // MiuJepang + { image_file_name: "ippaidou", x_post_num: "1787801976354513319" }, + { image_file_name: "nihongokurabu", x_post_num: "1779004631936614893" }, + { image_file_name: "language_exchange_tervern", x_post_num: "1779749425923150317" }, + { image_file_name: "japanese_culture_osenbeito", x_post_num: "1788522972409721137" }, + { image_file_name: "silakan_datang_ke_rumahku", x_post_num: "1788522607631056941" }, + { image_file_name: "uj_club", x_post_num: "1780791654196388201" }, + { image_file_name: "sushi_stand_guruguru", x_post_num: "1788523302404952218" }, + { image_file_name: "sushi_guru_annex", x_post_num: "1825426749770932457" }, + { image_file_name: "una_yosh", x_post_num: "1820329216598311065" }, + { image_file_name: "cam", x_post_num: "1825427064985686138" }, + { image_file_name: "language_exchange_park", x_post_num: "1825806455322324993" }, + + // poposuke_sig + { image_file_name: "usanezumi_shrine2", x_post_num: "1781224020383506649" }, + + // KUROINU_YOUHEI + { image_file_name: "kuroinu_work_room", x_post_num: "1779750007564112146" }, + + // いちや_ICHIYA + { image_file_name: "ehon_no_heikousekai", x_post_num: "1843891361478783425" }, + { image_file_name: "ehon_no_heikousekai_1st_anniv", x_post_num: "1842088383746875535" }, + { image_file_name: "ehon_no_heikousekai_jimusho", x_post_num: "1780792306976850285" }, + { image_file_name: "ikoiba", x_post_num: "1782723006923780580" }, + { image_file_name: "kimodameshi", x_post_num: "1781224391692714133" }, + { image_file_name: "parallel_collar", x_post_num: "1820693442105934068" }, + { image_file_name: "yoru_color", x_post_num: "1842088701599396075" }, + + // HayaTikaze + { image_file_name: "study_japanese_world_japanichijou", x_post_num: "1781539871829766550" }, + + // aji_3 + { image_file_name: "yuttari_eikaiwa", x_post_num: "1779002892999078046" }, + + // 八葉そるち + { image_file_name: "re_yatuha_room", x_post_num: "1779830390435590196" }, + + // chakamoto + { image_file_name: "chakachaka_multipurpose_room", x_post_num: "1818107831289295065" }, + + // MloYolM (よるむ) + { image_file_name: "cafe_cian", x_post_num: "1787802552907739504" }, + + // ミラクル・オルカ + { image_file_name: "mamehinata_dogrun", x_post_num: "1782723423179100471" }, + + // いんく(Eenkoo) + { image_file_name: "tyuuniti_kouryuukai", x_post_num: "1818109101731422617" }, + + // 1ban_meno + { image_file_name: "bar_asagao", x_post_num: "1788523857642758370" }, + + // 沈黙静寂 + { image_file_name: "monogatari_meetup", x_post_num: "1781538415789674976" }, + + // tommie_500 + { image_file_name: "stretch_club_starting_from_minus", x_post_num: "1825048889550102597" }, + + // MiMi_Sorahana # VRC日韓交流会 (KRJPEX.1355) + { image_file_name: "kr_jp_exchange", x_post_num: "1820328755950473668" }, + + // Ein(アイン) + { image_file_name: "smokerz_guild_v2", x_post_num: "1825049450190127187" }, + + // KokiM1018 + { image_file_name: "poker_room_elysion", x_post_num: "1818880695344980208" }, + + // NEET ENGINEER + { image_file_name: "japan_street", x_post_num: "1818881593114861924" }, + + // RIKU_VR + { image_file_name: "celestial_blooms", x_post_num: "1820694531568001061" }, + + // ღKAEDEಇ + { image_file_name: "omoshiro_kotoba_asobi_game", x_post_num: "1825806909343199700" }, + { image_file_name: "chill_sleep_room_03", x_post_num: "1842741645231677506" }, + { image_file_name: "chill_sleep_room_04", x_post_num: "1842742135042555906" }, + + // アスタルテア + { image_file_name: "oto_no_shitatei", x_post_num: "1831575615520305619" }, + + // Sayascape + { image_file_name: "sayasuke_hotel", x_post_num: "1843537673224630740" }, + + // ふながし + { image_file_name: "su", x_post_num: "1843537207401058558" }, + + // さや-sayasoft + { image_file_name: "saya_town", x_post_num: null }, +]; +export default poster_showcase_worlds_settings; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/posters_contents/PostersContents.jsx b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/posters_contents/PostersContents.jsx new file mode 100644 index 00000000..6cb5ffe1 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/posters_contents/PostersContents.jsx @@ -0,0 +1,69 @@ +import clsx from "clsx"; +import styles from "./PostersContents.module.scss"; +import { useStore_UiLanguage } from "@store"; + +import { useStore_VrctPosterIndex } from "@store"; +import ArrowLeftSvg from "@images/arrow_left.svg?react"; + +import iya_vrct_poster_ja from "@images/about_vrct/vrct_posters/iya_vrct_poster_ja.png"; +import iya_vrct_poster_en from "@images/about_vrct/vrct_posters/iya_vrct_poster_en.png"; +import iya_vrct_poster_cn from "@images/about_vrct/vrct_posters/iya_vrct_poster_cn.png"; +import iya_vrct_poster_ko from "@images/about_vrct/vrct_posters/iya_vrct_poster_ko.png"; +import iya_vrct_manga_ja from "@images/about_vrct/vrct_posters/iya_vrct_manga_ja.png"; +import iya_vrct_manga_en from "@images/about_vrct/vrct_posters/iya_vrct_manga_en.png"; +import iya_vrct_manga_ko from "@images/about_vrct/vrct_posters/iya_vrct_manga_ko.png"; + +const poster_images = [ + { img: iya_vrct_poster_ja, poster_type: "poster" }, + { img: iya_vrct_poster_en, poster_type: "poster" }, + { img: iya_vrct_poster_cn, poster_type: "poster" }, + { img: iya_vrct_poster_ko, poster_type: "poster" }, + { img: iya_vrct_manga_ja, poster_type: "manga" }, + { img: iya_vrct_manga_en, poster_type: "manga" }, + { img: iya_vrct_manga_ko, poster_type: "manga" }, +]; + +import poster_images_authors_ja from "@images/about_vrct/vrct_posters/authors/poster_images_authors_ja.png"; +import poster_images_authors_en from "@images/about_vrct/vrct_posters/authors/poster_images_authors_en.png"; +import poster_images_authors_m_ja from "@images/about_vrct/vrct_posters/authors/poster_images_authors_m_ja.png"; +import poster_images_authors_m_en from "@images/about_vrct/vrct_posters/authors/poster_images_authors_m_en.png"; + +export const PostersContents = () => { + const { currentVrctPosterIndex, updateVrctPosterIndex } = useStore_VrctPosterIndex(); + const { currentUiLanguage } = useStore_UiLanguage(); + + + const updateIndex = (delta) => { + const newIndex = (currentVrctPosterIndex.data + delta + poster_images.length) % poster_images.length; + updateVrctPosterIndex(newIndex); + }; + + const current_poster = poster_images[currentVrctPosterIndex.data]; + const current_poster_authors_img_ja = (current_poster.poster_type === "poster") ? poster_images_authors_ja : poster_images_authors_m_ja; + const current_poster_authors_img_en = (current_poster.poster_type === "poster") ? poster_images_authors_en : poster_images_authors_m_en; + + return ( +
+
+ + + +
+ { + currentUiLanguage.data === "ja" + ? + : + } +
+ ); +}; diff --git a/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/posters_contents/PostersContents.module.scss b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/posters_contents/PostersContents.module.scss new file mode 100644 index 00000000..739fe78d --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/about_vrct/poster_showcase_contents/posters_contents/PostersContents.module.scss @@ -0,0 +1,46 @@ +.poster_pagination_container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +.poster_pagination_wrapper { + display: flex; +} + +$poster_img_width: 18rem; +.poster_img { + width: $poster_img_width; +} + +$poster_pagination_button_width: 4.6rem; +.poster_pagination_button { + width: $poster_pagination_button_width; + display: flex; + justify-content: center; + align-items: center; + color: var(--dark_700_color); + &:hover { + background-color: var(--dark_900_color); + } + &:active { + background-color: var(--dark_925_color); + } + &.poster_prev { + border-radius: 0.8rem 0 0 0.8rem; + } + &.poster_next { + border-radius: 0 0.8rem 0.8rem 0; + } +} + +.poster_pagination_svg { + width: 3.2rem; + &.poster_next_svg { + transform: rotate(180deg); + } +} + +.poster_authors_img { + width: $poster_img_width + $poster_pagination_button_width; +} diff --git a/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.jsx b/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.jsx new file mode 100644 index 00000000..9c1ab116 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.jsx @@ -0,0 +1,116 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import styles from "./AdvancedSettings.module.scss"; + +import { useOpenFolder } from "@logics_common"; +import { + useOscIpAddress, + useOscPort, +} from "@logics_configs"; + +import { + ActionButtonContainer, + EntryContainer, +} from "../_templates/Templates"; + + +import OpenFolderSvg from "@images/open_folder.svg?react"; +import HelpSvg from "@images/help.svg?react"; + +export const AdvancedSettings = () => { + return ( + <> + + + + + + ); +}; + +const OscIpAddressContainer = () => { + const { t } = useTranslation(); + const [ui_variable, setUiVariable] = useState(""); + const { currentOscIpAddress, setOscIpAddress } = useOscIpAddress(); + const onChangeFunction = (e) => { + const value = e.currentTarget.value; + if (value === "") { + setUiVariable(""); + } else { + setUiVariable(value); + setOscIpAddress(value); + } + }; + + useEffect(()=> { + setUiVariable(currentOscIpAddress.data); + }, [currentOscIpAddress]); + + return ( + + ); +}; + +const OscPortContainer = () => { + const { t } = useTranslation(); + const [ui_variable, setUiVariable] = useState(""); + const { currentOscPort, setOscPort } = useOscPort(); + const onChangeFunction = (e) => { + const value = e.currentTarget.value; + if (value === "") { + setUiVariable(""); + } else { + setUiVariable(value); + setOscPort(value); + } + }; + + useEffect(()=> { + setUiVariable(currentOscPort.data); + }, [currentOscPort]); + + return ( + + ); +}; +const OpenConfigFolderContainer = () => { + const { t } = useTranslation(); + const { openFolder_ConfigFile } = useOpenFolder(); + + return ( + <> + + + ); +}; + +import { useStore_OpenedQuickSetting } from "@store"; +const OpenSwitchComputeDeviceModalContainer = () => { + const { t } = useTranslation(); + const { updateOpenedQuickSetting } = useStore_OpenedQuickSetting(); + const onClickFunction = () => { + updateOpenedQuickSetting("update_software"); + }; + + return ( + <> + + + ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.module.scss b/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.module.scss new file mode 100644 index 00000000..fa5eefb3 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/advanced_settings/AdvancedSettings.module.scss @@ -0,0 +1,22 @@ +.container { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + padding: 2rem; + align-items: center; + gap: 2rem; + &.flex_column { + flex-direction: column; + } + border-bottom: solid 0.1rem var(--dark_800_color); +} + +.switch_section_container { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + align-items: center; + gap: 2rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/appearance/Appearance.jsx b/src-ui/app/config_page/setting_section/setting_box/appearance/Appearance.jsx new file mode 100644 index 00000000..d9ecb7c6 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/appearance/Appearance.jsx @@ -0,0 +1,222 @@ +import clsx from "clsx"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import styles from "./Appearance.module.scss"; +import { ui_configs } from "@ui_configs"; +import { useStore_SelectableFontFamilyList } from "@store"; + +import { + useWindow, +} from "@logics_common"; + +import { + useUiLanguage, + useUiScaling, + useMessageLogUiScaling, + useSendMessageButtonType, + useSelectedFontFamily, + useTransparency, +} from "@logics_configs"; + +import { + SliderContainer, + DropdownMenuContainer, + RadioButtonContainer, +} from "../_templates/Templates"; + +export const Appearance = () => { + return ( + <> + + + + + + + + ); +}; + +const UiLanguageContainer = () => { + const { t } = useTranslation(); + const { currentUiLanguage, setUiLanguage } = useUiLanguage(); + + const is_not_en_lang = currentUiLanguage.data !== "en" && currentUiLanguage.data !== undefined; + return ( + + ); +}; + +const UiScalingContainer = () => { + const { t } = useTranslation(); + const { currentUiScaling, setUiScaling } = useUiScaling(); + const { asyncUpdateBreakPoint } = useWindow(); + + const [ui_ui_scaling, setUiUiScaling] = useState(currentUiScaling.data); + + const onchangeFunction = (value) => { + setUiUiScaling(value); + }; + const onchangeCommittedFunction = (value) => { + setUiScaling(value); + }; + useEffect(() => { + setUiUiScaling(currentUiScaling.data); + asyncUpdateBreakPoint(); + }, [currentUiScaling.data]); + + const createMarks = (min, max) => { + const marks = []; + for (let value = min; value <= max; value += 10) { + const label = ([50,70,90,110,130,150,170,190].includes(value)) ? "" : value; + marks.push({ value, label: `${label}` }); + } + return marks; + }; + + const marks = createMarks(40, 200); + + return ( + + ); +}; + + +export const MessageLogUiScalingContainer = () => { + const { t } = useTranslation(); + const { currentMessageLogUiScaling, setMessageLogUiScaling } = useMessageLogUiScaling(); + const [ui_message_log_ui_scaling, setUiMessageLogUiScaling] = useState(currentMessageLogUiScaling.data); + + const onchangeFunction = (value) => { + setUiMessageLogUiScaling(value); + }; + const onchangeCommittedFunction = (value) => { + setMessageLogUiScaling(value); + }; + useEffect(() => { + setUiMessageLogUiScaling(currentMessageLogUiScaling.data); + }, [currentMessageLogUiScaling.data]); + + const createMarks = (min, max) => { + const marks = []; + for (let value = min; value <= max; value += 10) { + const label = ([50,70,90,110,130,150,170,190].includes(value)) ? "" : value; + marks.push({ value, label: `${label}` }); + } + return marks; + }; + + const marks = createMarks(40, 200); + + return ( + + ); +}; + +const SendMessageButtonTypeContainer = () => { + const { t } = useTranslation(); + const { currentSendMessageButtonType, setSendMessageButtonType } = useSendMessageButtonType(); + + return ( + + ); +}; + +const FontFamilyContainer = () => { + const { t } = useTranslation(); + const { currentSelectedFontFamily, setSelectedFontFamily } = useSelectedFontFamily(); + + const selectFunction = (selected_data) => { + setSelectedFontFamily(selected_data.selected_id); + }; + const { currentSelectableFontFamilyList } = useStore_SelectableFontFamilyList(); + + return ( + + ); +}; + +const TransparencyContainer = () => { + const { t } = useTranslation(); + const { currentTransparency, setTransparency } = useTransparency(); + const [ui_message_log_ui_scaling, setUiTransparency] = useState(currentTransparency.data); + + const onchangeFunction = (value) => { + setUiTransparency(value); + }; + const onchangeCommittedFunction = (value) => { + setTransparency(value); + }; + useEffect(() => { + setUiTransparency(currentTransparency.data); + }, [currentTransparency.data]); + + const createMarks = (min, max) => { + const marks = []; + for (let value = min; value <= max; value += 10) { + marks.push({ value, label: `${value}` }); + } + return marks; + }; + + const marks = createMarks(40, 100); + + return ( + + ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/appearance/Appearance.module.scss b/src-ui/app/config_page/setting_section/setting_box/appearance/Appearance.module.scss new file mode 100644 index 00000000..9e9d2c53 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/appearance/Appearance.module.scss @@ -0,0 +1 @@ +@import "@scss_mixins"; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/device/Device.jsx b/src-ui/app/config_page/setting_section/setting_box/device/Device.jsx new file mode 100644 index 00000000..958cddf8 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/device/Device.jsx @@ -0,0 +1,220 @@ +import { useTranslation } from "react-i18next"; +import styles from "./Device.module.scss"; +import clsx from "clsx"; +import { useStore_IsBreakPoint } from "@store"; +import { ui_configs } from "@ui_configs"; +import { + useEnableAutoMicSelect, + useMicHostList, + useSelectedMicHost, + useMicDeviceList, + useSelectedMicDevice, + useMicThreshold, + useEnableAutoSpeakerSelect, + useSpeakerDeviceList, + useSelectedSpeakerDevice, + useSpeakerThreshold, +} from "@logics_configs"; + +import { + useOnMouseLeaveDropdownMenu, +} from "../_templates/Templates"; + +import { + LabelComponent, + DropdownMenu, + ThresholdComponent, + SwitchBox, +} from "../_components/"; + +export const Device = () => { + return ( + <> + + + + ); +}; + +const Mic_Container = () => { + const { t } = useTranslation(); + const { currentEnableAutoMicSelect, toggleEnableAutoMicSelect } = useEnableAutoMicSelect(); + const { currentSelectedMicHost, setSelectedMicHost } = useSelectedMicHost(); + const { currentMicHostList } = useMicHostList(); + const { currentSelectedMicDevice, setSelectedMicDevice } = useSelectedMicDevice(); + const { currentMicDeviceList } = useMicDeviceList(); + const { onMouseLeaveFunction } = useOnMouseLeaveDropdownMenu(); + const { currentEnableAutomaticMicThreshold, toggleEnableAutomaticMicThreshold } = useMicThreshold(); + + const selectFunction_host = (selected_data) => { + setSelectedMicHost(selected_data.selected_id); + }; + + const selectFunction_device = (selected_data) => { + setSelectedMicDevice(selected_data.selected_id); + }; + + const is_disabled_selector = currentEnableAutoMicSelect.data === true || currentEnableAutoMicSelect.data === "pending"; + + const getLabels = () => { + if (currentEnableAutomaticMicThreshold.data === true) { + return { + label: t("config_page.device.mic_dynamic_energy_threshold.label_for_automatic"), + desc: t("config_page.device.mic_dynamic_energy_threshold.desc_for_automatic"), + }; + } else { + return { + label: t("config_page.device.mic_dynamic_energy_threshold.label_for_manual"), + desc: t("config_page.device.mic_dynamic_energy_threshold.desc_for_manual"), + }; + } + }; + + const { currentIsBreakPoint } = useStore_IsBreakPoint(); + const device_container_class = clsx(styles.device_container, { + [styles.is_break_point]: currentIsBreakPoint.data, + }); + + return ( +
+
+ +
+ +
+

{t("config_page.device.mic_host_device.label_auto_select")}

+ +
+ +
+
+

{t("config_page.device.mic_host_device.label_host")}

+ +
+ +
+

{t("config_page.device.mic_host_device.label_device")}

+ +
+
+
+
+
+
+ + +
+
+ +
+
+
+ ); +}; + +const Speaker_Container = () => { + const { t } = useTranslation(); + const { currentEnableAutoSpeakerSelect, toggleEnableAutoSpeakerSelect } = useEnableAutoSpeakerSelect(); + const { currentSelectedSpeakerDevice, setSelectedSpeakerDevice } = useSelectedSpeakerDevice(); + const { currentSpeakerDeviceList } = useSpeakerDeviceList(); + const { onMouseLeaveFunction } = useOnMouseLeaveDropdownMenu(); + const { currentEnableAutomaticSpeakerThreshold, toggleEnableAutomaticSpeakerThreshold } = useSpeakerThreshold(); + + const selectFunction = (selected_data) => { + setSelectedSpeakerDevice(selected_data.selected_id); + }; + + const is_disabled_selector = currentEnableAutoSpeakerSelect.data === true || currentEnableAutoSpeakerSelect.data === "pending"; + + const getLabels = () => { + if (currentEnableAutomaticSpeakerThreshold.data === true) { + return { + label: t("config_page.device.speaker_dynamic_energy_threshold.label_for_automatic"), + desc: t("config_page.device.speaker_dynamic_energy_threshold.desc_for_automatic"), + }; + } else { + return { + label: t("config_page.device.speaker_dynamic_energy_threshold.label_for_manual"), + desc: t("config_page.device.speaker_dynamic_energy_threshold.desc_for_manual"), + }; + } + + }; + + const { currentIsBreakPoint } = useStore_IsBreakPoint(); + const device_container_class = clsx(styles.device_container, { + [styles.is_break_point]: currentIsBreakPoint.data, + }); + + return ( +
+
+ +
+ +
+

{t("config_page.device.speaker_device.label_auto_select")}

+ +
+ +
+

{t("config_page.device.mic_host_device.label_device")}

+ +
+
+
+
+
+ + +
+
+ +
+
+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/device/Device.module.scss b/src-ui/app/config_page/setting_section/setting_box/device/Device.module.scss new file mode 100644 index 00000000..65fab80a --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/device/Device.module.scss @@ -0,0 +1,103 @@ +.mic_container { + display: flex; + flex-direction: column; + border-bottom: solid 0.1rem var(--dark_800_color); + padding-bottom: 1rem; +} + +.speaker_container { + padding-top: 0rem; +} + +.device_container { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + padding: 2rem; + margin-bottom: 0rem; + &.is_break_point { + flex-direction: column; + gap: 2rem; + align-items: start; + & .device_contents { + display: flex; + width: 100%; + justify-content: space-between; + padding-left: 0rem; + } + } +} + +.threshold_container { + padding: 2rem; +} + + + +.threshold_container { + display: flex; + width: 100%; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 2rem; +} + +.threshold_switch_section { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + flex-shrink: 0; +} + +.threshold_section { + width: 100%; +} + + + + +.device_label { + font-size: 1.8rem; + color: var(--dark_basic_text_color); +} + +.device_contents { + display: flex; + width: 100%; + justify-content: end; + padding-left: 2rem; + gap: 2rem; +} + +.device_auto_select_wrapper { + display: flex; + flex-direction: column; + gap: 1.2rem; +} + +.device_dropdown_wrapper { + display: flex; + flex-direction: row; + gap: 2.8rem; +} + +.device_dropdown { + display: flex; + flex-direction: column; + gap: 0.6rem; + white-space: nowrap; + max-width: 24rem; + &.is_disabled { + pointer-events: none; + } +} + +.device_secondary_label { + padding-left: 0.2rem; + font-size: 1.4rem; + color: var(--dark_500_color); + white-space: nowrap; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/index.js b/src-ui/app/config_page/setting_section/setting_box/index.js new file mode 100644 index 00000000..489f63ca --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/index.js @@ -0,0 +1,9 @@ +export { Device } from "./device/Device"; +export { Appearance, MessageLogUiScalingContainer } from "./appearance/Appearance"; +export { Translation } from "./translation/Translation"; +export { Transcription } from "./transcription/Transcription"; +export { Others, VrcMicMuteSyncContainer } from "./others/Others"; +export { AdvancedSettings } from "./advanced_settings/AdvancedSettings"; +export { Vr } from "./vr/Vr"; +export { AboutVrct } from "./about_vrct/AboutVrct"; +export { Supporters } from "./supporters/Supporters"; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/others/Others.jsx b/src-ui/app/config_page/setting_section/setting_box/others/Others.jsx new file mode 100644 index 00000000..f1e9a251 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/others/Others.jsx @@ -0,0 +1,133 @@ +import { useTranslation } from "react-i18next"; +import styles from "./Others.module.scss"; + +import { useOpenFolder } from "@logics_common"; +import { + useEnableAutoClearMessageInputBox, + useEnableSendOnlyTranslatedMessages, + useEnableAutoExportMessageLogs, + useEnableVrcMicMuteSync, + useEnableSendMessageToVrc, + useEnableSendReceivedMessageToVrc, +} from "@logics_configs"; + +import { + CheckboxContainer, +} from "../_templates/Templates"; + +import { + LabelComponent, + ActionButton, + SectionLabelComponent, +} from "../_components/"; +import { Checkbox } from "@common_components"; + +import OpenFolderSvg from "@images/open_folder.svg?react"; + +export const Others = () => { + return ( +
+
+ + + + + +
+
+ + +
+
+ ); +}; + +const AutoClearMessageInputBoxContainer = () => { + const { t } = useTranslation(); + const { currentEnableAutoClearMessageInputBox, toggleEnableAutoClearMessageInputBox } = useEnableAutoClearMessageInputBox(); + + return ( + + ); +}; +const SendOnlyTranslatedMessagesContainer = () => { + const { t } = useTranslation(); + const { currentEnableSendOnlyTranslatedMessages, toggleEnableSendOnlyTranslatedMessages } = useEnableSendOnlyTranslatedMessages(); + + return ( + + ); +}; +const AutoExportMessageLogsContainer = () => { + const { t } = useTranslation(); + const { currentEnableAutoExportMessageLogs, toggleEnableAutoExportMessageLogs } = useEnableAutoExportMessageLogs(); + const { openFolder_MessageLogs } = useOpenFolder(); + + return ( +
+ +
+ + +
+
+ ); +}; +export const VrcMicMuteSyncContainer = () => { + const { t } = useTranslation(); + const { currentEnableVrcMicMuteSync, toggleEnableVrcMicMuteSync } = useEnableVrcMicMuteSync(); + + return ( + + ); +}; +const SendMessageToVrcContainer = () => { + const { t } = useTranslation(); + const { currentEnableSendMessageToVrc, toggleEnableSendMessageToVrc } = useEnableSendMessageToVrc(); + + return ( + + ); +}; + + +const SendReceivedMessageToVrcContainer = () => { + const { t } = useTranslation(); + const { currentEnableSendReceivedMessageToVrc, toggleEnableSendReceivedMessageToVrc } = useEnableSendReceivedMessageToVrc(); + + return ( + + ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/others/Others.module.scss b/src-ui/app/config_page/setting_section/setting_box/others/Others.module.scss new file mode 100644 index 00000000..acd7ac93 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/others/Others.module.scss @@ -0,0 +1,28 @@ +.container { + display: flex; + gap: 6.4rem; + flex-direction: column; +} + +.auto_export_message_logs_container { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + padding: 2rem; + align-items: center; + gap: 2rem; + &.flex_column { + flex-direction: column; + } + border-bottom: solid 0.1rem var(--dark_800_color); +} + +.auto_export_message_logs_switch_section_container { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + align-items: center; + gap: 2rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.jsx b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.jsx new file mode 100644 index 00000000..1d64b9c5 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.jsx @@ -0,0 +1,224 @@ +import { useState, useEffect } from "react"; +import styles from "./Supporters.module.scss"; +import clsx from "clsx"; +import { useTranslation } from "react-i18next"; + +import { + useSettingBoxScrollPosition, +} from "@logics_configs" + +const supporter_images = import.meta.glob("@images/supporters/supporters_images/*.{png,jpg,jpeg,svg}", { eager: true }); +const chato_expression_images = import.meta.glob("@images/supporters/chato_expressions/*.{png,jpg,jpeg,svg}", { eager: true }); +import fanbox_img from "@images/supporters/c_fanbox_1620x580.png"; +import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png"; +import fanbox_button from "@images/supporters/fanbox_button.png"; +import kofi_preparing from "@images/supporters/kofi_preparing.png"; + +import ExternalLink from "@images/external_link.svg?react"; + +const mogu_count = 8; +const mochi_count = 3; +const fuwa_count = 4; +const basic_count = 5; +const former_count = 2; +const and_you_count = 1; +const default_icon_numbers = ["05", "06", "07", "11"]; + +const supporters_filenames = Array.from({ length: 23 }, (_, index) => `supporter_${String(index + 1).padStart(2, "0")}`); +const chato_expressions_filenames = Array.from({ length: 7 }, (_, index) => `chato_expression_${String(index + 1).padStart(2, "0")}`); + +const SHUFFLE_INTERVAL_TIME = 20000; +const shuffleArray = (array) => { + return array + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); +}; + +export const Supporters = () => { + return ( +
+ + +
+ ); +}; + +const SupportUsContainer = () => { + return ( +
+ +
+
+ + + +

日本語 / Mainly Japanese

+
+
+ +

Mainly English

+
+
+
+ ); +}; + +const getRandomImage = (images) => { + const random_index = Math.floor(Math.random() * images.length); + return images[random_index]; +}; + +export const SupportersContainer = () => { + + return ( +
+ +

{`VRCT3.0のアップデートに向けて、めちゃ大変な開発を支えてくれた "Early Supporters" です。\nThey are the 'Early Supporters' who supported us through the incredibly challenging development toward the VRCT3.0 update.`}

+ + + +

{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(in開発室) しいなは喜び庭駆け回っています!!!ふわもちもぐもぐです!ありがとうございます。これからもまだまだ進化するVRCTをどうかよろしくお願いします!\nThanks to everyone, Misha has been granted the privilege of sleeping in a proper bed (in the development room), and Shiina is so happy, running around the yard! Fuwa-mochi-mogu-mogu! Thank you so much! We hope you'll continue to support the ever-evolving VRCT!`}

+
+ ); +}; + +const ProgressBar = () => { + const [is_active, setIsActive] = useState(false); + + useEffect(() => { + setIsActive(true); + const interval = setInterval(() => { + setIsActive(false); + setTimeout(() => setIsActive(true), 50); + }, SHUFFLE_INTERVAL_TIME); + + return () => clearInterval(interval); + }, []); + + return ( +
+ ); +}; + +const SupportsWrapper = () => { + const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition(); + const [imagesState, setImagesState] = useState({ + mogu_images: [], + mochi_images: [], + fuwa_images: [], + basic_images: [], + former_images: [], + and_you_images: [], + chato_images: [], + }); + + const shuffleImages = () => { + saveScrollPosition(); + const getCategoryImages = (start, count) => { + const category_images = supporters_filenames.slice(start, start + count); + return shuffleArray(category_images); + }; + + const randomChatoImages = shuffleArray( + Array.from({ length: mogu_count + mochi_count + fuwa_count + basic_count + former_count }, () => + getRandomImage(chato_expressions_filenames) + ) + ); + + setImagesState({ + mogu_images: getCategoryImages(0, mogu_count), + mochi_images: getCategoryImages(mogu_count, mochi_count), + fuwa_images: getCategoryImages(mogu_count + mochi_count, fuwa_count), + basic_images: getCategoryImages(mogu_count + mochi_count + fuwa_count, basic_count), + former_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count, former_count), + and_you_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count + former_count, and_you_count), + chato_images: randomChatoImages, + }); + setTimeout(() => restoreScrollPosition(), 0); + }; + + useEffect(() => { + shuffleImages(); + const interval = setInterval(() => { + shuffleImages(); + }, SHUFFLE_INTERVAL_TIME); + + return () => clearInterval(interval); + }, []); + + + const getSupportersImageByFileName = (file_name) => { + const image_path = Object.keys(supporter_images).find((path) => path.endsWith(file_name + ".png")); + return image_path ? supporter_images[image_path]?.default : null; + }; + + const getChatoImageByFileName = (file_name) => { + const image_path = Object.keys(chato_expression_images).find((path) => path.endsWith(file_name + ".png")); + return image_path ? chato_expression_images[image_path]?.default : null; + }; + + const getRandomDelay = (min, max) => { + const random_value = Math.random() * (max - min) + min; + return `${random_value.toFixed(1)}s`; + }; + + + const renderImages = (image_list, chato_list, options = {}) => { + return image_list.map((file_name, index) => { + const img_src = getSupportersImageByFileName(file_name); + const is_default_icon = default_icon_numbers.some((element) => file_name.endsWith(element)); + const chato_expression_src = is_default_icon ? getChatoImageByFileName(chato_list[index]) : null; + const random_delay = getRandomDelay(0.1, 6); + + return img_src ? ( +
+ + {chato_expression_src && ( + + )} + {options.is_and_you_icon ? : null} +
+ ) : null; + }); + }; + + return ( +
+ {renderImages(imagesState.mogu_images, imagesState.chato_images, { class_name: styles.mogu_image })} + {renderImages(imagesState.mochi_images, imagesState.chato_images)} + {renderImages(imagesState.fuwa_images, imagesState.chato_images)} + {renderImages(imagesState.basic_images, imagesState.chato_images)} + {renderImages(imagesState.former_images, imagesState.chato_images)} + + {renderImages(imagesState.and_you_images, imagesState.chato_images, { is_and_you_icon: true, class_name: styles.and_you_image })} + +
+ ); +}; + +const AndYouIcon = () => { + return ( + <> +
+
+
+
+

+ FANBOX + +

+ + ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss new file mode 100644 index 00000000..4288b653 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss @@ -0,0 +1,205 @@ +.container { + display: flex; + gap: 1.2rem; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + // background-color: gray; +} + +.support_us_container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 2rem; + width: 100%; + +} +.fanbox_img { + width: 60vw; +} + +.support_us_button_wrapper { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + gap: 4.8rem; +} +.fanbox_wrapper, .kofi_wrapper { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 1rem; +} +.fanbox_button { + width: 14rem; + transition: all 0.3s ease; + &:hover { + width: 16rem; + } +} +.kofi_preparing { + width: 6rem; +} + +.mainly_japanese, .mainly_english { + font-size: 1.2rem; + color: var(--dark_400_color); +} + + +.supporters_container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 1rem; +} +.vrct_supporters_title { + height: 6rem; +} +.vrct_supporters_desc { + font-size: 1.4rem; + text-align: start; +} + +.supporters_wrapper { + display: flex; + justify-content: center; + align-items: center; + align-content: start; + flex-wrap: wrap; + column-gap: 1.4rem; + row-gap: 0.8rem; +} + +.supporter_image_wrapper { + position: relative; + width: 18rem; + overflow: hidden; +} + +.supporter_image { + width: 100%; +} + +.mogu_image { + position: relative; + &::after { + content: ""; + position: absolute; + top: -200%; + left: -200%; + width: 300%; + height: 300%; + background: linear-gradient(45deg, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0) 70%); + transform: rotate(90deg); + animation: shine 2.5s infinite; + filter: blur(0.4rem); + animation-delay: var(--delay, 0s); + } + +} + +@keyframes shine { + 0% { + top: -200%; + left: -200%; + } + 50% { + top: 50%; + left: 50%; + opacity: 0.7; + } + 100% { + top: 200%; + left: 200%; + } +} + +.default_chato_expression_image { + position: absolute; + top: 50%; + left: 2.9rem; + width: 2.8rem; + transform: translate(-50%, -50%) rotate(10deg); + opacity: 0.8; +} + +.and_you_container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + transition: all 0.3s ease; +} + +.and_you_1, .and_you_2 { + width: 2.2rem; + height: 0.2rem; + border-radius: 50%; + background-color: var(--dark_400_color); +} + +.and_you_2 { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(90deg); +} + +.supporter_image_wrapper { + &:hover .and_you_container { + top: 40%; + transform: translate(-50%, -50%) rotate(180deg); + } + &:hover .and_you_fanbox_link_text { + top: 70%; + opacity: 1; + } +} + +.supporter_image_wrapper.and_you_image { + cursor: pointer; + &:active { + opacity: 0.6; + } +} + +.and_you_fanbox_link_text { + font-size: 1.2rem; + color: var(--dark_400_color); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + transition: all 0.3s ease; + opacity: 0; +} + +.external_link_svg { + color: var(--dark_200_color); + width: 1.2rem; + margin-left: 0.6rem; + padding-bottom: 0.2rem; +} + +.vrct_supporters_desc_end { + font-size: 1.4rem; + margin-top: 2rem; + color: var(--dark_300_color); +} + +.progress_bar { + height: 0.2rem; + width: 0%; + &.progress_bar_active { + transition: width 20000ms linear; + background-color: var(--primary_400_color); + width: 100%; + } +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/transcription/Transcription.jsx b/src-ui/app/config_page/setting_section/setting_box/transcription/Transcription.jsx new file mode 100644 index 00000000..334ed0de --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/transcription/Transcription.jsx @@ -0,0 +1,395 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import styles from "./Transcription.module.scss"; +import { updateLabelsById } from "@utils"; + +import { + useMicRecordTimeout, + useMicPhraseTimeout, + useMicMaxWords, + + useSpeakerRecordTimeout, + useSpeakerPhraseTimeout, + useSpeakerMaxWords, + + useSelectedTranscriptionEngine, + useWhisperWeightTypeStatus, + useSelectedWhisperWeightType, + + useSelectedWhisperComputeDevice, + useSelectableWhisperComputeDeviceList, +} from "@logics_configs"; + +import { + EntryContainer, + WordFilterContainer, + DownloadModelsContainer, + RadioButtonContainer, + DropdownMenuContainer, +} from "../_templates/Templates"; + +import { + SectionLabelComponent, +} from "../_components/"; + +export const Transcription = () => { + return ( +
+ + + +
+ ); +}; + + +const Mic_Container = () => { + const { t } = useTranslation(); + return ( +
+ + + + + +
+ ); +}; + +const MicRecordTimeout_Box = () => { + const { t } = useTranslation(); + const [ui_variable, setUiVariable] = useState(""); + const { currentMicRecordTimeout, setMicRecordTimeout } = useMicRecordTimeout(); + const onChangeFunction = (e) => { + const value = e.currentTarget.value; + if (value === "") { + setUiVariable(""); + } else { + setUiVariable(value); + setMicRecordTimeout(value); + } + }; + + useEffect(()=> { + setUiVariable(currentMicRecordTimeout.data); + }, [currentMicRecordTimeout]); + + return ( + + ); +}; +const MicPhraseTimeout_Box = () => { + const { t } = useTranslation(); + const [ui_variable, setUiVariable] = useState(""); + const { currentMicPhraseTimeout, setMicPhraseTimeout } = useMicPhraseTimeout(); + const onChangeFunction = (e) => { + const value = e.currentTarget.value; + if (value === "") { + setUiVariable(""); + } else { + setUiVariable(value); + setMicPhraseTimeout(value); + } + }; + + useEffect(()=> { + setUiVariable(currentMicPhraseTimeout.data); + }, [currentMicPhraseTimeout]); + + return ( + + ); +}; +const MicMaxWords_Box = () => { + const { t } = useTranslation(); + const [ui_variable, setUiVariable] = useState(""); + const { currentMicMaxWords, setMicMaxWords } = useMicMaxWords(); + const onChangeFunction = (e) => { + const value = e.currentTarget.value; + if (value === "") { + setUiVariable(""); + } else { + setUiVariable(value); + setMicMaxWords(value); + } + }; + + useEffect(()=> { + setUiVariable(currentMicMaxWords.data); + }, [currentMicMaxWords]); + + return ( + + ); +}; + +const MicWordFilter_Box = () => { + const { t } = useTranslation(); + + return ( + + ); +}; + + + + +const Speaker_Container = () => { + const { t } = useTranslation(); + return ( +
+ + + + +
+ ); +}; + +const SpeakerRecordTimeout_Box = () => { + const { t } = useTranslation(); + const [ui_variable, setUiVariable] = useState(""); + const { currentSpeakerRecordTimeout, setSpeakerRecordTimeout } = useSpeakerRecordTimeout(); + const onChangeFunction = (e) => { + const value = e.currentTarget.value; + if (value === "") { + setUiVariable(""); + } else { + setUiVariable(value); + setSpeakerRecordTimeout(value); + } + }; + + useEffect(()=> { + setUiVariable(currentSpeakerRecordTimeout.data); + }, [currentSpeakerRecordTimeout]); + + return ( + + ); +}; +const SpeakerPhraseTimeout_Box = () => { + const { t } = useTranslation(); + const [ui_variable, setUiVariable] = useState(""); + const { currentSpeakerPhraseTimeout, setSpeakerPhraseTimeout } = useSpeakerPhraseTimeout(); + const onChangeFunction = (e) => { + const value = e.currentTarget.value; + if (value === "") { + setUiVariable(""); + } else { + setUiVariable(value); + setSpeakerPhraseTimeout(value); + } + }; + + useEffect(()=> { + setUiVariable(currentSpeakerPhraseTimeout.data); + }, [currentSpeakerPhraseTimeout]); + + return ( + + ); +}; +const SpeakerMaxWords_Box = () => { + const { t } = useTranslation(); + const [ui_variable, setUiVariable] = useState(""); + const { currentSpeakerMaxWords, setSpeakerMaxWords } = useSpeakerMaxWords(); + const onChangeFunction = (e) => { + const value = e.currentTarget.value; + if (value === "") { + setUiVariable(""); + } else { + setUiVariable(value); + setSpeakerMaxWords(value); + } + }; + + useEffect(()=> { + setUiVariable(currentSpeakerMaxWords.data); + }, [currentSpeakerMaxWords]); + + return ( + + ); +}; + + + +const TranscriptionEngine_Container = () => { + const { t } = useTranslation(); + return ( +
+ + + + +
+ ); +}; + +const TranscriptionEngine_Box = () => { + const { t } = useTranslation(); + const { currentSelectedTranscriptionEngine, setSelectedTranscriptionEngine } = useSelectedTranscriptionEngine(); + + return ( + + ); +}; + +const WhisperWeightType_Box = () => { + const { t } = useTranslation(); + const { + currentWhisperWeightTypeStatus, + pendingWhisperWeightType, + downloadWhisperWeight, + } = useWhisperWeightTypeStatus(); + const { currentSelectedWhisperWeightType, setSelectedWhisperWeightType } = useSelectedWhisperWeightType(); + + const selectFunction = (id) => { + setSelectedWhisperWeightType(id); + }; + + const downloadStartFunction = (id) => { + pendingWhisperWeightType(id); + downloadWhisperWeight(id); + }; + + const new_labels = [ + { id: "tiny", label: t("config_page.transcription.whisper_weight_type.model_template", {model_name: "tiny", capacity: "74.5MB"}) }, + { id: "base", label: t("config_page.transcription.whisper_weight_type.recommended_model_template", {model_name: "base", capacity: "141MB"}) }, + { id: "small", label: t("config_page.transcription.whisper_weight_type.model_template", {model_name: "small", capacity: "463MB"}) }, + { id: "medium", label: t("config_page.transcription.whisper_weight_type.model_template", {model_name: "medium", capacity: "1.42GB"}) }, + { id: "large-v1", label: t("config_page.transcription.whisper_weight_type.model_template", {model_name: "large-v1", capacity: "2.87GB"}) }, + { id: "large-v2", label: t("config_page.transcription.whisper_weight_type.model_template", {model_name: "large-v2", capacity: "2.87GB"}) }, + { id: "large-v3", label: t("config_page.transcription.whisper_weight_type.model_template", {model_name: "large-v3", capacity: "2.87GB"}) }, + ]; + + const whisper_weight_types = updateLabelsById(currentWhisperWeightTypeStatus.data, new_labels); + + return ( + <> + + + ); +}; + +const WhisperComputeDevice_Box = () => { + const { t } = useTranslation(); + const { currentSelectedWhisperComputeDevice, setSelectedWhisperComputeDevice } = useSelectedWhisperComputeDevice(); + const { currentSelectableWhisperComputeDeviceList } = useSelectableWhisperComputeDeviceList(); + + const selectFunction = (selected_data) => { + const target_obj = currentSelectableWhisperComputeDeviceList.data[selected_data.selected_id]; + setSelectedWhisperComputeDevice(target_obj); + }; + + const list_for_ui = transformDeviceArray(currentSelectableWhisperComputeDeviceList.data); + + const target_index = findKeyByDeviceValue(currentSelectableWhisperComputeDeviceList.data, currentSelectedWhisperComputeDevice.data); + + + return ( + + ); +}; + +// Duplicate +const transformDeviceArray = (devices) => { + const name_counts = Object.values(devices).reduce((counts, device) => { + const name = device.device_name; + counts[name] = (counts[name] || 0) + 1; + return counts; + }, {}); + + const name_indices = {}; + const result = {}; + + Object.entries(devices).forEach(([key, device]) => { + const name = device.device_name; + + if (name_counts[name] > 1) { + name_indices[name] = (name_indices[name] || 0); + const value = `${name}:${name_indices[name]}`; + name_indices[name]++; + result[key] = value; + } else { + result[key] = name; + } + }); + + return result; +}; + +const findKeyByDeviceValue = (devices, target_value) => { + for (const [key, value] of Object.entries(devices)) { + if ( + value.device === target_value.device && + value.device_index === target_value.device_index && + value.device_name === target_value.device_name + ) { + return parseInt(key); + } + } + return null; +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/transcription/Transcription.module.scss b/src-ui/app/config_page/setting_section/setting_box/transcription/Transcription.module.scss new file mode 100644 index 00000000..1170a41c --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/transcription/Transcription.module.scss @@ -0,0 +1,5 @@ +.container { + display: flex; + flex-direction: column; + gap: 6.4rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/translation/Translation.jsx b/src-ui/app/config_page/setting_section/setting_box/translation/Translation.jsx new file mode 100644 index 00000000..ce4bc0d1 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/translation/Translation.jsx @@ -0,0 +1,173 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import styles from "./Translation.module.scss"; +import { updateLabelsById } from "@utils"; + +import { + useDeepLAuthKey, + useCTranslate2WeightTypeStatus, + useSelectedCTranslate2WeightType, + useSelectedCTranslate2ComputeDevice, + useSelectableCTranslate2ComputeDeviceList, +} from "@logics_configs"; + +import { + DownloadModelsContainer, + DeeplAuthKeyContainer, + DropdownMenuContainer, +} from "../_templates/Templates"; + +export const Translation = () => { + return ( + <> + + + + + ); +}; + +const CTranslate2WeightType_Box = () => { + const { t } = useTranslation(); + const { + currentCTranslate2WeightTypeStatus, + pendingCTranslate2WeightType, + downloadCTranslate2Weight, + } = useCTranslate2WeightTypeStatus(); + const { currentSelectedCTranslate2WeightType, setSelectedCTranslate2WeightType } = useSelectedCTranslate2WeightType(); + + const selectFunction = (id) => { + setSelectedCTranslate2WeightType(id); + }; + + const downloadStartFunction = (id) => { + pendingCTranslate2WeightType(id); + downloadCTranslate2Weight(id); + }; + + const new_labels = [ + { id: "small", label: t("config_page.translation.ctranslate2_weight_type.small", {capacity: "418MB"}) }, + { id: "large", label: t("config_page.translation.ctranslate2_weight_type.large", {capacity: "1.2GB"}) }, + ]; + + const c_translate2_weight_types = updateLabelsById(currentCTranslate2WeightTypeStatus.data, new_labels); + + return ( + <> + + + ); +}; + +const CTranslation2ComputeDevice_Box = () => { + const { t } = useTranslation(); + const { currentSelectedCTranslate2ComputeDevice, setSelectedCTranslate2ComputeDevice } = useSelectedCTranslate2ComputeDevice(); + const { currentSelectableCTranslate2ComputeDeviceList } = useSelectableCTranslate2ComputeDeviceList(); + + const selectFunction = (selected_data) => { + const target_obj = currentSelectableCTranslate2ComputeDeviceList.data[selected_data.selected_id]; + setSelectedCTranslate2ComputeDevice(target_obj); + }; + + const list_for_ui = transformDeviceArray(currentSelectableCTranslate2ComputeDeviceList.data); + + const target_index = findKeyByDeviceValue(currentSelectableCTranslate2ComputeDeviceList.data, currentSelectedCTranslate2ComputeDevice.data); + + return ( + + ); +}; + +const DeeplAuthKey_Box = () => { + const { t } = useTranslation(); + const { currentDeepLAuthKey, setDeepLAuthKey, deleteDeepLAuthKey } = useDeepLAuthKey(); + const [input_value, seInputValue] = useState(currentDeepLAuthKey.data); + + const onChangeFunction = (value) => { + seInputValue(value); + }; + + const saveFunction = () => { + if (input_value === "") return deleteDeepLAuthKey(); + setDeepLAuthKey(input_value); + }; + + useEffect(() => { + seInputValue(currentDeepLAuthKey.data); + }, [currentDeepLAuthKey]); + + return ( + <> + + + ); +}; + +// Duplicate +const transformDeviceArray = (devices) => { + const name_counts = Object.values(devices).reduce((counts, device) => { + const name = device.device_name; + counts[name] = (counts[name] || 0) + 1; + return counts; + }, {}); + + const name_indices = {}; + const result = {}; + + Object.entries(devices).forEach(([key, device]) => { + const name = device.device_name; + + if (name_counts[name] > 1) { + name_indices[name] = (name_indices[name] || 0); + const value = `${name}:${name_indices[name]}`; + name_indices[name]++; + result[key] = value; + } else { + result[key] = name; + } + }); + + return result; +}; + +const findKeyByDeviceValue = (devices, target_value) => { + for (const [key, value] of Object.entries(devices)) { + if ( + value.device === target_value.device && + value.device_index === target_value.device_index && + value.device_name === target_value.device_name + ) { + return parseInt(key); + } + } + return null; +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/translation/Translation.module.scss b/src-ui/app/config_page/setting_section/setting_box/translation/Translation.module.scss new file mode 100644 index 00000000..e69de29b diff --git a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx new file mode 100644 index 00000000..e1197b22 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.jsx @@ -0,0 +1,448 @@ +import React, { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { clsx } from "clsx"; +import styles from "./Vr.module.scss"; +import { ui_configs } from "@ui_configs"; +import { Slider } from "../_components/"; +import { + RadioButtonContainer, + SwitchBoxContainer, + CheckboxContainer, +} from "../_templates/Templates"; + +import { + SectionLabelComponent, +} from "../_components/"; + +import { + useIsEnabledOverlaySmallLog, + useOverlaySmallLogSettings, + useIsEnabledOverlayLargeLog, + useOverlayLargeLogSettings, + useOverlayShowOnlyTranslatedMessages, + useSendTextToOverlay, +} from "@logics_configs"; + +import RedoSvg from "@images/redo.svg?react"; + +export const Vr = () => { + const { t } = useTranslation(); + const [is_opened_small_settings, setIsOpenedSmallSettings] = useState(true); + const toggleIsOpenedSmallSettings = () => { + setIsOpenedSmallSettings(!is_opened_small_settings); + }; + + const { currentOverlaySmallLogSettings, setOverlaySmallLogSettings } = useOverlaySmallLogSettings(); + const { currentIsEnabledOverlaySmallLog, toggleIsEnabledOverlaySmallLog } = useIsEnabledOverlaySmallLog(); + + const { currentOverlayLargeLogSettings, setOverlayLargeLogSettings } = useOverlayLargeLogSettings(); + const { currentIsEnabledOverlayLargeLog, toggleIsEnabledOverlayLargeLog } = useIsEnabledOverlayLargeLog(); + + const restoreDefaultSettings = () => { + setOverlaySmallLogSettings(ui_configs.overlay_small_log_default_settings); + setOverlayLargeLogSettings(ui_configs.overlay_large_log_default_settings); + }; + + return ( +
+
+ + {is_opened_small_settings ? ( + + ) : ( + + )} +
+ + +
+ ); +}; + +const OverlaySettingsContainer = ({ + current_overlay_settings, + set_overlay_settings, + current_is_enabled_overlay, + toggle_is_enabled_overlay, + ui_configs, + default_ui_configs, + id +}) => { + + const { t } = useTranslation(); + useEffect(() => { + setSettings(current_overlay_settings); + }, [current_overlay_settings]); + + const [settings, setSettings] = useState(current_overlay_settings); + const [timeout_id, setTimeoutId] = useState(null); + + const [is_opened_position_controller, setIsOpenedPositionController] = useState(true); + const togglePositionRotationController = () => { + setIsOpenedPositionController(!is_opened_position_controller); + }; + + const onchangeFunction = (key, value) => { + setSettings((prev) => ({ ...prev, [key]: value })); + + if (timeout_id) clearTimeout(timeout_id); + + const newTimeoutId = setTimeout(() => { + const new_data = { ...settings, [key]: value }; + set_overlay_settings(new_data); + }, 50); + + setTimeoutId(newTimeoutId); + }; + + const selectFunction = (key, value) => { + const new_data = { ...settings, [key]: value }; + set_overlay_settings(new_data); + }; + + + return ( + <> + + + +
+ {is_opened_position_controller ? ( + + ) : ( + + )} + +
+ + selectFunction("tracker", value)} + name={id} + options={[ + { id: "HMD", label: t("config_page.vr.hmd") }, + { id: "LeftHand", label: t("config_page.vr.left_hand") }, + { id: "RightHand", label: t("config_page.vr.right_hand") }, + ]} + checked_variable={{data: settings.tracker}} + column={true} + /> + + ); +}; + + +const PageSwitcherContainer = (props) => { + const toggle_button_class_names__position = clsx(styles.controller_type_switcher, { + [styles.is_selected]: props.is_selected, + }); + const toggle_button_class_names__rotation = clsx(styles.controller_type_switcher, { + [styles.is_selected]: !props.is_selected, + }); + + return ( +
props.toggleFunction()}> +
+

{props.label_1}

+
+
+

{props.label_2}

+
+
+ ); +}; + +const PositionControls = ({settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs}) => { + const { t } = useTranslation(); + + return ( +
+
+

+ {t("config_page.vr.x_position")} + selectFunction("x_pos", default_ui_configs.x_pos)} /> +

+ onchangeFunction("x_pos", value)} + /> +
+
+

+ {t("config_page.vr.y_position")} + selectFunction("y_pos", default_ui_configs.y_pos)} /> +

+ onchangeFunction("y_pos", value)} + orientation="vertical" + /> +
+
+

+ {t("config_page.vr.z_position")} + selectFunction("z_pos", default_ui_configs.z_pos)} /> +

+ onchangeFunction("z_pos", value)} + orientation="vertical" + /> +
+
+ ); +}; + +const RotationControls = ({settings, onchangeFunction, selectFunction, default_ui_configs}) => { + const { t } = useTranslation(); + + return ( +
+
+

+ {t("config_page.vr.x_rotation")} + selectFunction("x_rotation", default_ui_configs.y_pos)} /> +

+ onchangeFunction("x_rotation", -value)} + orientation="vertical" + /> +
+
+

+ {t("config_page.vr.y_rotation")} + selectFunction("y_rotation", default_ui_configs.y_pos)} /> +

+ onchangeFunction("y_rotation", value)} + /> +
+
+

+ {t("config_page.vr.z_rotation")} + selectFunction("z_rotation", default_ui_configs.y_pos)} /> +

+ onchangeFunction("z_rotation", value)} + orientation="vertical" + /> +
+
+ ); +}; + +const OtherControls = ({settings, onchangeFunction, ui_configs}) => { + const { t } = useTranslation(); + + const ui_variable_opacity = (settings.opacity * 100).toFixed(0); + const ui_variable_ui_scaling = (settings.ui_scaling * 100).toFixed(0); + + return( +
+
+

+ {t("config_page.vr.opacity")} +

+ onchangeFunction("opacity", value / 100)} + /> +
+
+

+ {t("config_page.vr.ui_scaling")} +

+ onchangeFunction("ui_scaling", value / 100)} + /> +
+
+

{t("config_page.vr.display_duration")}

+ onchangeFunction("display_duration", value)} + /> +
+
+

{t("config_page.vr.fadeout_duration")}

+ onchangeFunction("fadeout_duration", value)} + /> +
+
+ ); +}; + + +const CommonSettingsContainer = () => { + const { t } = useTranslation(); + const { currentOverlayShowOnlyTranslatedMessages, toggleOverlayShowOnlyTranslatedMessages } = useOverlayShowOnlyTranslatedMessages(); + + return ( +
+ + +
+ ); +}; + +const ResetButton = ({onClickFunction}) => { + return ( + + ); +}; + +import SquareSvg from "@images/square.svg?react"; +import TriangleSvg from "@images/triangle.svg?react"; +import { randomIntMinMax } from "@utils"; + +const SendSampleTextToggleButton = () => { + const { t } = useTranslation(); + const { sendTextToOverlay } = useSendTextToOverlay(); + const [is_started, setIsStarted] = useState(false); + + useEffect(() => { + let interval_id; + + if (is_started) { + interval_id = setInterval(() => { + const text_data = Array.from( + { length: randomIntMinMax(1, 5) }, + () => t("config_page.vr.sample_text_button.sample_text") + ).join(" "); + sendTextToOverlay(text_data); + }, 1000); + }; + + return () => { + if (interval_id) { + clearInterval(interval_id); + } + }; + }, [is_started]); + + const toggleFunction = () => { + setIsStarted(!is_started); + }; + + const label = is_started + ? t("config_page.vr.sample_text_button.stop") + : t("config_page.vr.sample_text_button.start"); + + return ( +
+ +

{label}

+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss new file mode 100644 index 00000000..83dec71d --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/vr/Vr.module.scss @@ -0,0 +1,295 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; + padding: 2rem; + width: 100%; + gap: 4rem; +} + +.wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + max-width: 56rem; + gap: 2rem; +} + +.controller_type_switch { + margin-top: 2rem; + display: flex; + border: 0.1rem solid var(--dark_600_color); + border-radius: 0.4rem; + width: 80%; + align-items: center; + justify-content: center; + cursor: pointer; + color: var(--dark_600_color); + &:hover { + color: var(--dark_400_color); + } +} +.controller_type_switcher { + width: 100%; + &.is_selected { + background-color: var(--dark_850_color); + } + &.is_selected .controller_switcher_label { + color: var(--dark_basic_text_color); + } +} +.controller_switcher_label { + padding: 1rem; + font-size: 1.6rem; +} + +.position_rotation_controls_box { + margin-top: 8rem; + position: relative; + aspect-ratio: 1 / 1; + width: 36%; + max-width: 36rem; + transform: translate(-10%); +} + +.sample_text_button_wrapper { + position: absolute; + bottom: 0; + left: -74%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} +.sample_text_button { + background-color: var(--dark_850_color); + padding: 1.8rem; + border-radius: 50%; + &:hover { + background-color: var(--dark_800_color); + } + &:active { + background-color: var(--dark_925_color); + } + &.is_started { + background-color: var(--primary_600_color); + &:hover { + background-color: var(--primary_500_color); + } + &:active { + background-color: var(--primary_700_color); + } + } +} +.sample_text_button_triangle_svg, .sample_text_button_square_svg { + width: 2.4rem; +} +.sample_text_button_triangle_svg { + transform: translateX(10%) rotate(90deg); +} +.sample_text_button_label { + color: var(--dark_basic_text_color); + position: absolute; + left: 50%; + top: 110%; + // bottom: -2rem; + transform: translateX(-50%); + white-space: pre-wrap; + font-size: 1.2rem; + width: max-content; + text-align: center; +} + +// .position_controls { +// background-color: gray; +// } + +// .position_wrapper { +// background-color: gray; +// } + +.slider_label { + font-size: 1.4rem; + width: 100%; + display: flex; + align-items: center; + gap: 0.6rem; + white-space: nowrap; +} +.x_position_label { + position: absolute; + bottom: -4.6rem; + right: -46%; + justify-content: end; +} +.y_position_label { + position: absolute; + top: -44%; + left: 10%; + justify-content: start; +} +.z_position_label { + position: absolute; + top: 30%; + left: 80%; +} + +.x_position_slider { + position: absolute; + bottom: 0; + left: 27%; + width: 100%; + height: 0%; +} + +.y_position_slider { + position: absolute; + bottom: 27%; + left: 0; + width: 0%; + height: 100%; +} + +.z_position_slider { + position: absolute; + bottom: 61%; + left: 61%; + transform: translate(50%,50%) rotate(45deg); + width: 0%; + height: 100%; +} + + + + +// .rotation_controls { +// background-color: gray; +// } + +// .rotation_wrapper { +// background-color: gray; +// } + +.x_rotation_label { + position: absolute; + top: -44%; + left: 10%; +} +.y_rotation_label { + position: absolute; + bottom: -4.6rem; + right: -46%; + justify-content: end; +} +.z_rotation_label { + position: absolute; + top: -10%; + right: -110%; +} + +.x_rotation_slider { + position: absolute; + bottom: 27%; + left: 0; + width: 0%; + height: 100%; +} + +.y_rotation_slider { + position: absolute; + bottom: 0; + left: 27%; + width: 100%; + height: 0%; +} + +.z_rotation_slider { + position: absolute; + bottom: 80%; + left: 100%; + transform: translate(50%,50%) rotate(-45deg); + width: 0%; + height: 100%; +} + + +.slider_reset_button { + background-color: var(--dark_875_color); + padding: 0.6rem; + display: flex; + justify-content: center; + align-items: center; + border-radius: 0.4rem; + flex-shrink: 0; + &:hover { + background-color: var(--dark_825_color); + & .slider_reset_svg { + color: var(--dark_200_color); + } + } + &:active { + background-color: var(--dark_925_color); + } +} + +.slider_reset_svg { + width: 1.4rem; + color: var(--dark_550_color); +} + + + +.other_controls { + margin-top: 6rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + width: 100%; +} + +.other_controls_wrapper { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 100%; + position: relative; +} +.other_controls_slider { + // margin-left: 18rem; + // width: 60%; +} + +.other_controls_slider_label { + // position: absolute; + font-size: 1.6rem; + flex-shrink: 0; + width: 30%; +} + +.common_container { + width: 100%; +} + +.common_label { + font-size: 1.4rem; +} + +.restore_default_settings_button { + color: var(--dark_basic_text_color); + font-size: 1.2rem; + margin-top: 6rem; + padding: 0.8rem; + border-radius: 0.4rem; + &:hover { + background-color: var(--dark_775_color); + } + &:active { + background-color: var(--dark_900_color); + } +} \ No newline at end of file diff --git a/src-ui/app/config_page/sidebar_section/SidebarSection.jsx b/src-ui/app/config_page/sidebar_section/SidebarSection.jsx new file mode 100644 index 00000000..2b109910 --- /dev/null +++ b/src-ui/app/config_page/sidebar_section/SidebarSection.jsx @@ -0,0 +1,48 @@ +import styles from "./SidebarSection.module.scss"; + +export const SidebarSection = () => { + return ( +
+
+ + + + + + + +
+
+ + +
+
+ ); +}; + + +import clsx from "clsx"; +import { useTranslation } from "react-i18next"; +import { useStore_SelectedConfigTabId } from "@store"; + +const Tab = (props) => { + const { t } = useTranslation(); + const { updateSelectedConfigTabId, currentSelectedConfigTabId } = useStore_SelectedConfigTabId(); + const onclickFunction = () => { + updateSelectedConfigTabId(props.tab_id); + }; + + const tab_container_class_names = clsx(styles["tab_container"], { + [styles["is_selected"]]: (currentSelectedConfigTabId.data === props.tab_id) ? true : false + }); + const switch_indicator_class_names = clsx(styles["switch_indicator"], { + [styles["is_selected"]]: (currentSelectedConfigTabId.data === props.tab_id) ? true : false + }); + + return ( +
+

{t(`config_page.side_menu_labels.${props.tab_id}`)}

+
+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/sidebar_section/SidebarSection.module.scss b/src-ui/app/config_page/sidebar_section/SidebarSection.module.scss new file mode 100644 index 00000000..6051cff6 --- /dev/null +++ b/src-ui/app/config_page/sidebar_section/SidebarSection.module.scss @@ -0,0 +1,63 @@ +.container { + width: var(--config_page_sidebar_width); + flex-shrink: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 0rem 0rem 5.8rem 1.2rem; + max-height: 60rem; +} + +.tabs_wrapper { + width: 100%; + display: flex; + flex-direction: column; + gap: 0.1rem; + flex: 1; +} + +.tab_container { + position: relative; + width: 100%; + display: flex; + justify-content: left; + align-items: center; + color: var(--dark_basic_text_color); + padding: 0.8rem 0 0.8rem 1rem; + cursor: pointer; + &:hover { + background-color: var(--dark_800_color); + } + &:active { + background-color: var(--dark_900_color); + } + &.is_selected { + background-color: inherit; + color: var(--primary_200_color); + cursor: default; + pointer-events: none; + } +} + +.switch_indicator { + display: none; + &.is_selected { + display: block; + position: absolute; + top: 50%; + left: 0rem; + transform: translate(-50%, -50%); + width: 0.2rem; + height: 2.6rem; + border-radius: 0.1rem; + background-color: var(--primary_300_color); + } +} + +.tab_text { + font-size: 1.6rem; +} + +.separated_tabs_wrapper { + // padding-bottom: 1.2rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/topbar/Topbar.jsx b/src-ui/app/config_page/topbar/Topbar.jsx new file mode 100644 index 00000000..64c8f593 --- /dev/null +++ b/src-ui/app/config_page/topbar/Topbar.jsx @@ -0,0 +1,38 @@ +import { useTranslation } from "react-i18next"; +import clsx from "clsx"; + +import styles from "./Topbar.module.scss"; +import { useIsOpenedConfigPage } from "@logics_common"; +import ArrowLeftSvg from "@images/arrow_left.svg?react"; + +import { TitleBox } from "./title_box/TitleBox"; +import { SectionTitleBox } from "./section_title_box/SectionTitleBox"; +import { CompactSwitchBox } from "./compact_switch_box/CompactSwitchBox"; + +export const Topbar = () => { + const { t } = useTranslation(); + const { currentIsOpenedConfigPage, setIsOpenedConfigPage } = useIsOpenedConfigPage(); + const closeConfigPage = () => { + setIsOpenedConfigPage(false); + }; + return ( +
+
closeConfigPage()}> +
+ +
+
+

{t("common.go_back_button_label")}

+
+ + + {/* + + */} +
+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/topbar/Topbar.module.scss b/src-ui/app/config_page/topbar/Topbar.module.scss new file mode 100644 index 00000000..331f1581 --- /dev/null +++ b/src-ui/app/config_page/topbar/Topbar.module.scss @@ -0,0 +1,82 @@ +.container { + width: 100%; + height: 0%; + transition: top 0.5s ease; + position: absolute; + top: 0; + left: 0; +} + +.show_config.container { + top: 0; +} + +.show_main.container { + top: 100%; +} + + +.wrapper { + background-color: var(--dark_850_color); + cursor: pointer; + height: 1rem; + width: 100%; + position: relative; + transition: all 0.3s ease; + + &:hover { + height: 2rem; + .go_back_button { + top: -12rem; + transform: rotate(35deg); + } + .arrow_left_svg { + color: var(--dark_400_color); + } + .go_back_text { + color: var(--dark_400_color); + } + .go_back_text_wrapper { + padding-top: 8.2rem; + } + } + +} + +.go_back_button { + height: 16rem; + width: 16rem; + border-radius: 1.2rem; + background-color: var(--dark_850_color); + position: absolute; + top: -13.2rem; + right: 10rem; + transform: rotate(30deg); + transition: all 0.3s ease; +} + +.arrow_left_svg { + height: 2.8rem; + position: absolute; + right: 1rem; + bottom: 0.4rem; + color: var(--dark_600_color); + transform: rotate(-100deg); +} + +.go_back_text_wrapper { + position: absolute; + top: -4.6rem; + right: 0rem; + padding-top: 7.2rem; + padding-bottom: 1rem; + width: 15.2rem; + transition: all 0.3s ease; +} + +.go_back_text { + color: var(--dark_650_color); + font-size: 1.6rem; + padding-left: 4rem; + transition: all 0.3s ease; +} \ No newline at end of file diff --git a/src-ui/app/config_page/topbar/compact_switch_box/CompactSwitchBox.jsx b/src-ui/app/config_page/topbar/compact_switch_box/CompactSwitchBox.jsx new file mode 100644 index 00000000..4d36a1e5 --- /dev/null +++ b/src-ui/app/config_page/topbar/compact_switch_box/CompactSwitchBox.jsx @@ -0,0 +1,12 @@ +import { useTranslation } from "react-i18next"; + +import styles from "./CompactSwitchBox.module.scss"; + +export const CompactSwitchBox = () => { + const { t } = useTranslation(); + return ( +
+

{t("config_page.compact_mode")}

+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/topbar/compact_switch_box/CompactSwitchBox.module.scss b/src-ui/app/config_page/topbar/compact_switch_box/CompactSwitchBox.module.scss new file mode 100644 index 00000000..f824b7d0 --- /dev/null +++ b/src-ui/app/config_page/topbar/compact_switch_box/CompactSwitchBox.module.scss @@ -0,0 +1,9 @@ +.container { + // flex: 0; + // width: 100%; + width: 14rem; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/src-ui/app/config_page/topbar/section_title_box/SectionTitleBox.jsx b/src-ui/app/config_page/topbar/section_title_box/SectionTitleBox.jsx new file mode 100644 index 00000000..53bb6b16 --- /dev/null +++ b/src-ui/app/config_page/topbar/section_title_box/SectionTitleBox.jsx @@ -0,0 +1,13 @@ +import { useTranslation } from "react-i18next"; +import styles from "./SectionTitleBox.module.scss"; +import { useStore_SelectedConfigTabId } from "@store"; + +export const SectionTitleBox = () => { + const { t } = useTranslation(); + const { currentSelectedConfigTabId } = useStore_SelectedConfigTabId(); + return ( +
+

{t(`config_page.side_menu_labels.${currentSelectedConfigTabId.data}`)}

+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/topbar/section_title_box/SectionTitleBox.module.scss b/src-ui/app/config_page/topbar/section_title_box/SectionTitleBox.module.scss new file mode 100644 index 00000000..bb022d2e --- /dev/null +++ b/src-ui/app/config_page/topbar/section_title_box/SectionTitleBox.module.scss @@ -0,0 +1,13 @@ +.container { + flex: 1; + width: 100%; + height: 100%; + display: flex; + justify-content: left; + align-items: center; + padding-left: 2rem; +} + +.title { + font-size: 2.2rem; +} \ No newline at end of file diff --git a/src-ui/app/config_page/topbar/title_box/TitleBox.jsx b/src-ui/app/config_page/topbar/title_box/TitleBox.jsx new file mode 100644 index 00000000..c2bb2355 --- /dev/null +++ b/src-ui/app/config_page/topbar/title_box/TitleBox.jsx @@ -0,0 +1,14 @@ +import { useTranslation } from "react-i18next"; + +import styles from "./TitleBox.module.scss"; +import chato_img from "@images/chato_white.png"; + +export const TitleBox = () => { + const { t } = useTranslation(); + return ( +
+ VRCT logo chato +

{t("config_page.config_title")}

+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/config_page/topbar/title_box/TitleBox.module.scss b/src-ui/app/config_page/topbar/title_box/TitleBox.module.scss new file mode 100644 index 00000000..33d38361 --- /dev/null +++ b/src-ui/app/config_page/topbar/title_box/TitleBox.module.scss @@ -0,0 +1,19 @@ +.container { + // flex: 0; + width: var(--config_page_sidebar_width); + height: 100%; + display: flex; + justify-content: left; + align-items: center; + padding-left: 2.6rem; + gap: 1.4rem; +} + +.logo_chato { + width: 3.2rem; + padding-top: 0.6rem; +} + +.title { + font-size: 2.2rem; +} \ No newline at end of file diff --git a/src-ui/app/index.jsx b/src-ui/app/index.jsx new file mode 100644 index 00000000..03c4e13a --- /dev/null +++ b/src-ui/app/index.jsx @@ -0,0 +1,12 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import "@root/locales/config.js"; +import "./_index_css/root.css"; + +import { App } from "./App"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + , +); \ No newline at end of file diff --git a/src-ui/app/main_page/MainPage.jsx b/src-ui/app/main_page/MainPage.jsx new file mode 100644 index 00000000..a83ef09a --- /dev/null +++ b/src-ui/app/main_page/MainPage.jsx @@ -0,0 +1,21 @@ +import clsx from "clsx"; +import styles from "./MainPage.module.scss"; +import { SidebarSection } from "./sidebar_section/SidebarSection"; +import { MainSection } from "./main_section/MainSection"; +import { useIsOpenedConfigPage } from "@logics_common"; + +export const MainPage = () => { + const { currentIsOpenedConfigPage } = useIsOpenedConfigPage(); + + return ( +
+
+ + +
+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/main_page/MainPage.module.scss b/src-ui/app/main_page/MainPage.module.scss new file mode 100644 index 00000000..50fbe594 --- /dev/null +++ b/src-ui/app/main_page/MainPage.module.scss @@ -0,0 +1,64 @@ +.page { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + overflow: hidden; + transition: transform 0.5s ease; +} + +.show_config.main_page { + transform: translateY(-100%); +} + +.show_main.main_page { + transform: translateY(0%); +} + + +.container { + width: 100%; + height: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + background-color: var(--dark_888_color); + position: relative; +} + +.main_page_cover { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: (#000000cc); + display: flex; + flex-direction: column; + gap: 2rem; + justify-content: center; + align-items: center; + backdrop-filter: blur(0.8rem); + color: var(--dark_basic_text_color); +} + +.cover_message { + font-size: 2rem; + font-weight: 300; +} + +.close_settings_window_button { + font-size: 1.6rem; + font-weight: 300; + padding: 1rem 1.4rem; + border-radius: 0.4rem; + background-color: var(--dark_888_color); + cursor: pointer; + &:hover { + background-color: var(--dark_825_color); + } + &:active { + background-color: var(--dark_925_color); + } +} \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/MainSection.jsx b/src-ui/app/main_page/main_section/MainSection.jsx new file mode 100644 index 00000000..faf910bd --- /dev/null +++ b/src-ui/app/main_page/main_section/MainSection.jsx @@ -0,0 +1,87 @@ +import { useTranslation } from "react-i18next"; +import styles from "./MainSection.module.scss"; + +import { TopBar } from "./top_bar/TopBar"; +import { MessageContainer } from "./message_container/MessageContainer"; +import { LanguageSelector } from "./language_selector/LanguageSelector"; + +import { useStore_IsOpenedLanguageSelector } from "@store"; +import { useLanguageSettings } from "@logics_main"; +import { useEffect } from "react"; + +export const MainSection = () => { + + return ( +
+ + + +
+ ); +}; + + +const HandleLanguageSelector = () => { + const { t } = useTranslation(); + const { currentIsOpenedLanguageSelector, updateIsOpenedLanguageSelector } = useStore_IsOpenedLanguageSelector(); + const { + currentSelectedPresetTabNumber, + currentSelectedYourLanguages, + setSelectedYourLanguages, + currentSelectedTargetLanguages, + setSelectedTargetLanguages, + } = useLanguageSettings(); + + useEffect(() => { + updateIsOpenedLanguageSelector({ + your_language: false, + target_language: false, + target_key: "1" + }); + + }, [currentSelectedPresetTabNumber.data, currentSelectedYourLanguages.data, currentSelectedTargetLanguages.data]); + + const getTitle = (target_selector_key) => { + if (target_selector_key === "your_language") return t("main_page.language_selector.title_your_language"); + if (target_selector_key === "target_language") { + if (currentSelectedTargetLanguages.data[currentSelectedPresetTabNumber.data]["2"].enable === false) return t("main_page.language_selector.title_target_language"); + return `${t("main_page.language_selector.title_target_language")} (${currentIsOpenedLanguageSelector.data.target_key})`; + } + }; + + + + if (currentIsOpenedLanguageSelector.data.your_language === true) { + const onclickFunction_YourLanguage = (payload) => { + updateIsOpenedLanguageSelector({ your_language: false, target_language: false, target_key: currentIsOpenedLanguageSelector.data.target_key }); + setSelectedYourLanguages({ + ...payload, + target_key: currentIsOpenedLanguageSelector.data.target_key, + }); + }; + const title = getTitle("your_language"); + return ( + + ); + } else if (currentIsOpenedLanguageSelector.data.target_language === true) { + const onclickFunction_TargetLanguage = (payload) => { + updateIsOpenedLanguageSelector({ your_language: false, target_language: false, target_key: currentIsOpenedLanguageSelector.data.target_key }); + setSelectedTargetLanguages({ + ...payload, + target_key: currentIsOpenedLanguageSelector.data.target_key, + }); + }; + const title = getTitle("target_language"); + return ( + + ); + } else { + return null; + } +}; \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/MainSection.module.scss b/src-ui/app/main_page/main_section/MainSection.module.scss new file mode 100644 index 00000000..29a7907a --- /dev/null +++ b/src-ui/app/main_page/main_section/MainSection.module.scss @@ -0,0 +1,13 @@ +.container { + position: relative; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.language_selector_container { + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/language_selector/LanguageSelector.jsx b/src-ui/app/main_page/main_section/language_selector/LanguageSelector.jsx new file mode 100644 index 00000000..8fce4e2d --- /dev/null +++ b/src-ui/app/main_page/main_section/language_selector/LanguageSelector.jsx @@ -0,0 +1,63 @@ +import { useTranslation } from "react-i18next"; + +import { useSelectableLanguageList } from "@logics_main"; +import styles from "./LanguageSelector.module.scss"; + +import { LanguageSelectorTopBar } from "./language_selector_top_bar/LanguageSelectorTopBar"; +export const LanguageSelector = ({ title, onClickFunction }) => { + const { t } = useTranslation(); + const { currentSelectableLanguageList } = useSelectableLanguageList(); + + const groupLanguagesByFirstLetter = (languages) => { + return languages.reduce((acc, { language, country }) => { + const firstLetter = language[0].toUpperCase(); + if (!acc[firstLetter]) { + acc[firstLetter] = []; + } + acc[firstLetter].push({ language, country }); + return acc; + }, {}); + }; + + const groupedLanguages = groupLanguagesByFirstLetter(currentSelectableLanguageList.data); + + return ( +
+ +
+
+ {Object.entries(groupedLanguages).map(([letter, languages]) => ( + + ))} +
+
+
+ ); +}; + +const LanguageGroup = ({ onClickFunction, letter, languages }) => { + return ( +
+

{letter}

+ {languages.map((language_data, index) => ( + + ))} +
+ ); +}; + +const LanguageButton = ({ onClickFunction, language_data }) => { + + const adjustedOnClickFunction = () => { + onClickFunction({ + language: language_data.language, + country: language_data.country, + }); + }; + + return ( +
+

{language_data.language} ({language_data.country})

+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/language_selector/LanguageSelector.module.scss b/src-ui/app/main_page/main_section/language_selector/LanguageSelector.module.scss new file mode 100644 index 00000000..4a9fd247 --- /dev/null +++ b/src-ui/app/main_page/main_section/language_selector/LanguageSelector.module.scss @@ -0,0 +1,50 @@ +.container { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: var(--dark_875_color); + flex: 1; + overflow-y: hidden; +} + +.language_list_scroll_wrapper { + height: 100%; + overflow-y: auto; + padding: 1rem 1rem 8rem 1.6rem; +} + +.language_list { + column-count: auto; + column-width: 16rem; +} + +.language_each_letter_box { + break-inside: avoid-column; + margin-bottom: 1.4rem; + display: flex; + flex-direction: column; + gap: 0.1rem; +} + +.language_latter { + font-size: 1.4rem; + color: var(--dark_500_color); +} + +.language_button { + padding: 0.8rem 0.6rem; + cursor: pointer; + &:hover{ + background-color: var(--dark_825_color); + } + &:active{ + background-color: var(--dark_888_color); + } +} + +.language_label { + font-size: 1.4rem; + color: var(--dark_basic_text_color); +} \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/language_selector/language_selector_top_bar/LanguageSelectorTopBar.jsx b/src-ui/app/main_page/main_section/language_selector/language_selector_top_bar/LanguageSelectorTopBar.jsx new file mode 100644 index 00000000..1ea1c832 --- /dev/null +++ b/src-ui/app/main_page/main_section/language_selector/language_selector_top_bar/LanguageSelectorTopBar.jsx @@ -0,0 +1,24 @@ +import { useTranslation } from "react-i18next"; +import styles from "./LanguageSelectorTopBar.module.scss"; +import { useStore_IsOpenedLanguageSelector } from "@store"; + +export const LanguageSelectorTopBar = (props) => { + const { t } = useTranslation(); + const { updateIsOpenedLanguageSelector } = useStore_IsOpenedLanguageSelector(); + const closeLanguageSelector = () => { + updateIsOpenedLanguageSelector({ + your_language: false, + target_language: false, + target_key: "1" + }); + }; + + return ( +
+
+

{t("common.go_back_button_label")}

+
+

{props.title}

+
+ ); +}; \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/language_selector/language_selector_top_bar/LanguageSelectorTopBar.module.scss b/src-ui/app/main_page/main_section/language_selector/language_selector_top_bar/LanguageSelectorTopBar.module.scss new file mode 100644 index 00000000..b2e7d219 --- /dev/null +++ b/src-ui/app/main_page/main_section/language_selector/language_selector_top_bar/LanguageSelectorTopBar.module.scss @@ -0,0 +1,32 @@ +.container { + height: var(--main_page_topbar_height); + background-color: var(--dark_850_color); + display: flex; + justify-content: center; + align-items: center; + position: relative; +} + +.title { + font-size: 2rem; + color: var(--dark_400_color); +} + +.go_back_button_wrapper { + position: absolute; + left: 0; + background-color: var(--dark_800_color); + padding: 1.2rem; + cursor: pointer; + &:hover{ + background-color: var(--dark_750_color); + } + &:active{ + background-color: var(--dark_875_color); + } +} + +.go_back_button_label { + font-size: 1.4rem; + color: var(--dark_400_color); +} \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/message_container/MessageContainer.jsx b/src-ui/app/main_page/main_section/message_container/MessageContainer.jsx new file mode 100644 index 00000000..dbc5c100 --- /dev/null +++ b/src-ui/app/main_page/main_section/message_container/MessageContainer.jsx @@ -0,0 +1,120 @@ +import { useResizable } from "react-resizable-layout"; +import { useRef, useEffect, useState } from "react"; +import styles from "./MessageContainer.module.scss"; +import { appWindow } from "@tauri-apps/api/window"; +import { LogBox } from "./log_box/LogBox"; +import { MessageLogSettingsContainer } from "./message_log_settings_container/MessageLogSettingsContainer"; +import { MessageInputBox } from "./message_input_box/MessageInputBox"; +import { useMessageInputBoxRatio } from "@logics_main"; +import { useUiScaling } from "@logics_configs"; +import { useStore_IsAppliedInitMessageBoxHeight } from "@store"; + +export const MessageContainer = () => { + const { currentMessageInputBoxRatio, asyncSetMessageInputBoxRatio } = useMessageInputBoxRatio(); + const { currentUiScaling } = useUiScaling(); + const [is_hovered, setIsHovered] = useState(false); + const [message_box_height_in_rem, setMessageBoxHeightInRem] = useState(10); + const FONT_SIZE_STANDARD = 10 * currentUiScaling.data / 100; // 10px = 1rem + const { currentIsAppliedInitMessageBoxHeight, updateIsAppliedInitMessageBoxHeight } = useStore_IsAppliedInitMessageBoxHeight(); + + const container_ref = useRef(null); + const log_box_ref = useRef(null); + const message_box_wrapper_ref = useRef(null); + + const asyncSetMessageBoxHeightInRem = async (data) => { + const minimized = await appWindow.isMinimized(); + if (minimized) return; // don't save while the window is minimized. + setMessageBoxHeightInRem(data); + }; + + const calculateMessageBoxRatioAndHeight = () => { + if (!currentIsAppliedInitMessageBoxHeight.data) { + asyncSetMessageInputBoxRatio(currentMessageInputBoxRatio.data); + asyncSetMessageBoxHeightInRem(convertRatioToRem(currentMessageInputBoxRatio.data)); + return; + } + + if (log_box_ref.current && message_box_wrapper_ref.current && container_ref.current) { + const container_height = container_ref.current.offsetHeight; + const container_padding_bottom = parseFloat(window.getComputedStyle(container_ref.current).paddingBottom); + const total_height = container_height - container_padding_bottom; + + const message_box_height = message_box_wrapper_ref.current.offsetHeight; + const message_box_ratio = (message_box_height / total_height) * 100; + + asyncSetMessageInputBoxRatio(message_box_ratio); + asyncSetMessageBoxHeightInRem(convertRatioToRem(message_box_ratio)); + } else { + console.warn("References not ready for calculation"); + } + }; + + const { position, separatorProps } = useResizable({ + axis: "y", + reverse: true, + onResizeEnd: calculateMessageBoxRatioAndHeight, + }); + + useEffect(() => { + asyncSetMessageBoxHeightInRem((position / FONT_SIZE_STANDARD) - 1.4); + }, [position]); + + useEffect(() => { + asyncSetMessageBoxHeightInRem(convertRatioToRem(currentMessageInputBoxRatio.data)); + }, [currentMessageInputBoxRatio.data]); + + const convertRatioToRem = (ratio) => { + if (!container_ref.current) return 0; + const container_height = container_ref.current.offsetHeight; + const container_padding_bottom = parseFloat(window.getComputedStyle(container_ref.current).paddingBottom); + const total_height = container_height - container_padding_bottom; + return total_height === 0 ? 0 : ((ratio / 100) * total_height / FONT_SIZE_STANDARD); + }; + + useEffect(() => { + calculateMessageBoxRatioAndHeight(); + updateIsAppliedInitMessageBoxHeight(true); // Ensure this happens after initial calculation + }, []); + + useEffect(() => { + let resizeTimeout; + + const unlisten = appWindow.onResized(() => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + calculateMessageBoxRatioAndHeight(); + }, 200); + }); + + return () => { + unlisten.then((dispose) => dispose()); + }; + }, []); + + return ( +
+
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + +
+ +
+ +
+
+ ); +}; + +const Separator = ({ onDragStart, ...props }) => ( +
+ +
+); diff --git a/src-ui/app/main_page/main_section/message_container/MessageContainer.module.scss b/src-ui/app/main_page/main_section/message_container/MessageContainer.module.scss new file mode 100644 index 00000000..e1886dfe --- /dev/null +++ b/src-ui/app/main_page/main_section/message_container/MessageContainer.module.scss @@ -0,0 +1,42 @@ +.container { + height: 0%; + display: flex; + flex-direction: column; + flex: 1; + padding: 0 1.6rem 1rem 1.6rem; +} + +.log_box_resize_wrapper { + flex: 1; + overflow: auto; + position: relative; +} + +.separator { + position: relative; + width: 100%; + height: 0.8rem; + cursor: row-resize; + flex-shrink: 0; + &:hover { + & .separator_line { + background-color: var(--primary_300_color); + } + } +} +.separator_line { + position: absolute; + bottom: 0%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + height: 50%; + width: 99%; + transition: background-color .15s ease-out; +} + +.message_box_resize_wrapper { + height: 10rem; + min-height: 3.8rem; + max-height: 90%; +} \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/message_container/log_box/LogBox.jsx b/src-ui/app/main_page/main_section/message_container/log_box/LogBox.jsx new file mode 100644 index 00000000..4f9ef5a0 --- /dev/null +++ b/src-ui/app/main_page/main_section/message_container/log_box/LogBox.jsx @@ -0,0 +1,65 @@ +import { useEffect, useLayoutEffect, useRef, useState } from "react"; +import styles from "./LogBox.module.scss"; +import { store } from "@store"; +import { MessageContainer } from "./message_container/MessageContainer"; +import { scrollToBottom } from "@utils"; +import { useMessage } from "@logics_common"; + +export const LogBox = () => { + const { currentMessageLogs } = useMessage(); + const log_container_ref = useRef(null); + const [is_scrolling, setIsScrolling] = useState(false); + + + useLayoutEffect(() => { + store.log_box_ref = log_container_ref; + if (!is_scrolling) { + scrollToBottom(store.log_box_ref); + // scrollToBottom(store.log_box_ref, true); [Fix me] + } + }, [currentMessageLogs.data]); + + useEffect(() => { + const handleScroll = () => { + const element = log_container_ref.current; + if (!element) return; + const currentScrollTop = element.scrollTop; + const at_bottom = element.scrollHeight - currentScrollTop === element.clientHeight; + + if (at_bottom) { + setIsScrolling(false); + } else { + setIsScrolling(true); + } + }; + + const element = log_container_ref.current; + element.addEventListener("scroll", handleScroll); + + return () => { + element.removeEventListener("scroll", handleScroll); + }; + }, []); + + return ( +
+ + {currentMessageLogs.data.map(message_data => ( + + ))} +
+ ); +}; + +import { useMessageLogUiScaling } from "@logics_configs"; +const MessageLogUiSizeController = () => { + const { currentMessageLogUiScaling } = useMessageLogUiScaling(); + const font_size = currentMessageLogUiScaling.data / 100; + + useEffect(() => { + const log_container_el = document.getElementById("log_container"); + log_container_el.style.setProperty("font-size", `${font_size}rem`); + }, [currentMessageLogUiScaling.data]); + + return null; +}; \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/message_container/log_box/LogBox.module.scss b/src-ui/app/main_page/main_section/message_container/log_box/LogBox.module.scss new file mode 100644 index 00000000..35d49601 --- /dev/null +++ b/src-ui/app/main_page/main_section/message_container/log_box/LogBox.module.scss @@ -0,0 +1,10 @@ +.container { + height: 100%; + width: 100%; + flex: 1; + background-color: var(--dark_900_color); + overflow: auto; + border-radius: 0.8rem; + padding: 1rem; + font-size: 1rem; // This is the standard font size for message logs, and is controlled by JavaScript. All child elements this are generally expressed in "em" and are affected by the font size setting here. +} \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/message_container/log_box/message_container/MessageContainer.jsx b/src-ui/app/main_page/main_section/message_container/log_box/message_container/MessageContainer.jsx new file mode 100644 index 00000000..1298c8da --- /dev/null +++ b/src-ui/app/main_page/main_section/message_container/log_box/message_container/MessageContainer.jsx @@ -0,0 +1,101 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import clsx from "clsx"; +import styles from "./MessageContainer.module.scss"; +import { MessageSubMenuContainer } from "./message_sub_menu_container/MessageSubMenuContainer"; +import { useMessage } from "@logics_common"; +import { useIsVisibleResendButton } from "@logics_main"; + +export const MessageContainer = ({ messages, status, category, created_at }) => { + const { t } = useTranslation(); + const { + sendMessage, + updateMessageInputValue, + } = useMessage(); + const { currentIsVisibleResendButton } = useIsVisibleResendButton(); + const [is_hovered, setIsHovered] = useState(false); + const [is_locked, setIsLocked] = useState(false); + + const resendFunction = () => { + sendMessage(messages.original); + }; + const editFunction = () => { + updateMessageInputValue(messages.original); + }; + + const handleMouseEnter = () => { + if (!is_locked) { + setIsHovered(true); + } + }; + + const handleMouseLeave = () => { + setIsHovered(false); + setIsLocked(false); + }; + + const lockHoverState = () => { + setIsHovered(false); + setIsLocked(true); + }; + + const is_translated_exist = messages.translated?.length >= 1; + const is_pending = status === "pending"; + const is_sent_message = category === "sent"; + const is_system_message = category === "system"; + const category_text = is_sent_message + ? t("main_page.message_log.sent") + : is_system_message + ? t("main_page.message_log.system") + : t("main_page.message_log.received"); + + const message_type_class_name = clsx({ + [styles.sent_message]: is_sent_message, + [styles.received_message]: !is_sent_message && !is_system_message, + [styles.system_message]: is_system_message, + }); + + return ( +
+
+
+

{created_at}

+

{category_text}

+ {is_sent_message && is_pending && } +
+
+ {is_system_message ? ( +

{messages.message}

+ ) : is_translated_exist ? ( + + ) : ( +

{messages.original}

+ )} +
+
+ {currentIsVisibleResendButton.data && is_sent_message && is_hovered ? ( + + ) : null} +
+ ); +}; + +const WithTranslatedMessages = ({ messages }) => { + const translated_data = Array.isArray(messages.translated) ? messages.translated : [messages.translated]; + return ( + <> +

{messages.original}

+ {translated_data.map((message, index) => ( +

{message}

+ ))} + + ); +}; diff --git a/src-ui/app/main_page/main_section/message_container/log_box/message_container/MessageContainer.module.scss b/src-ui/app/main_page/main_section/message_container/log_box/message_container/MessageContainer.module.scss new file mode 100644 index 00000000..966ed455 --- /dev/null +++ b/src-ui/app/main_page/main_section/message_container/log_box/message_container/MessageContainer.module.scss @@ -0,0 +1,111 @@ +@import "@scss_mixins"; + +// ******************* ******************* +// ******************* Express in "em" not "rem" ******************* +// ******************* ******************* +.container { + width: 100%; + display: flex; + user-select: text; + gap: 0.6rem; + &.sent_message.is_shown_resend_button:hover { + background-color: var(--dark_950_color); + } +} + +.message_wrapper { + display: flex; + width: 100%; + flex-direction: column; + justify-content: center; + align-items: end; + user-select: text; + padding: 0.6em 0; + &.sent_message { + align-items: end; + } + &.received_message { + align-items: start; + } + &.system_message { + flex-direction: row; + align-items: center; + gap: 0.6rem; + } +} + +.info_box { + position: relative; + display: flex; + gap: 0.8em; + justify-content: center; + &.sent_message { + align-items: end; + } + &.received_message { + flex-flow: row-reverse; + align-items: start; + } +} + +.loader { + @include loader(0.8em, 0.1em, left, -1em); +} + +.time { + font-size: 1em; + color: var(--dark_600_color); +} + +.category { + font-size: 1em; + &.sent_message { + color: var(--sent_400_color); + } + &.received_message { + align-items: start; + color: var(--received_300_color); + } +} + +.message_box { + display: flex; + flex-direction: column; + &.sent_message { + align-items: end; + } + &.received_message { + align-items: start; + } +} + +.message_main { + color: var(--dark_basic_text_color); + user-select: text; + font-size: 1.4em; +} + +.message_second { + color: var(--dark_450_color); + user-select: text; + font-size: 1em; +} + + +.system_message { + justify-content: center; + text-align: center; + + .category { + color: var(--primary_300_color); + } + + .message_box { + align-items: center; + } + + .message_main_system { + font-size: 1.2rem; + color: var(--dark_500_color); + } +} \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/message_container/log_box/message_container/message_sub_menu_container/MessageSubMenuContainer.jsx b/src-ui/app/main_page/main_section/message_container/log_box/message_container/message_sub_menu_container/MessageSubMenuContainer.jsx new file mode 100644 index 00000000..6144ff37 --- /dev/null +++ b/src-ui/app/main_page/main_section/message_container/log_box/message_container/message_sub_menu_container/MessageSubMenuContainer.jsx @@ -0,0 +1,73 @@ +import React, { useState, useRef } from "react"; +import { useTranslation } from "react-i18next"; +import Tooltip, { tooltipClasses } from '@mui/material/Tooltip'; +import styles from "./MessageSubMenuContainer.module.scss"; +import SendMessageSvg from "@images/send_message.svg?react"; +import RefreshSvg from "@images/refresh_2.svg?react"; + +export const MessageSubMenuContainer = (props) => { + const [is_holding, setIsHolding] = useState(false); + const progressRef = useRef(null); + const holdTimeout = useRef(null); + + const startHold = () => { + setIsHolding(true); + if (progressRef.current) { + progressRef.current.style.transition = "width 500ms linear"; + progressRef.current.style.width = "100%"; + } + holdTimeout.current = setTimeout(() => { + props.resendFunction(); + props.setIsHovered(false); + }, 500); + }; + + const cancelHold = () => { + setIsHolding(false); + if (progressRef.current) { + progressRef.current.style.transition = "none"; + progressRef.current.style.width = "0%"; + } + clearTimeout(holdTimeout.current); + }; + + const onClickFunction = () => { + props.editFunction(); + + }; + + const offset = { + popper: { + sx: { + [`&.${tooltipClasses.popper}[data-popper-placement*="top"] .${tooltipClasses.tooltip}`]: { marginBottom: "0.2em" }, + } + } + }; + + return ( +
+ } + placement="top" + slotProps={offset} + > + + +
+ ); +}; + +const Title_p = () => { + const { t } = useTranslation(); + return

{t("main_page.message_log.resend_button_on_hover_desc")}

; +}; diff --git a/src-ui/app/main_page/main_section/message_container/log_box/message_container/message_sub_menu_container/MessageSubMenuContainer.module.scss b/src-ui/app/main_page/main_section/message_container/log_box/message_container/message_sub_menu_container/MessageSubMenuContainer.module.scss new file mode 100644 index 00000000..30b436b1 --- /dev/null +++ b/src-ui/app/main_page/main_section/message_container/log_box/message_container/message_sub_menu_container/MessageSubMenuContainer.module.scss @@ -0,0 +1,45 @@ +// ******************* ******************* +// ******************* Express in "em" not "rem" ******************* +// ******************* ******************* +.container { +} +.resend_button { + background-color: var(--dark_825_color); + position: relative; + height: 100%; + width: 3.8em; +} + +.send_message_svg { + position: absolute; + top: 58%; + left: 50%; + transform: translate(-50%, -50%); + width: 2.2em; + color: var(--dark_400_color); +} +.refresh_svg { + position: absolute; + top: 36%; + left: 42%; + transform: translate(-50%, -50%); + width: 1.8em; + color: var(--sent_400_color); + filter: drop-shadow(0.2em 0.2em 0 var(--dark_825_color)); +} + +.tooltip_title { + font-size: 1.2rem; + color: var(--dark_basic_text_color); +} + +.hold_progress_bar { + position: absolute; + top: 10%; + left: 50%; + transform: translate(-50%, -50%); + width: 0%; + height: 0.4em; + background-color: var(--sent_400_color); + transition: none; +} \ No newline at end of file diff --git a/src-ui/app/main_page/main_section/message_container/message_input_box/MessageInputBox.jsx b/src-ui/app/main_page/main_section/message_container/message_input_box/MessageInputBox.jsx new file mode 100644 index 00000000..88ee0605 --- /dev/null +++ b/src-ui/app/main_page/main_section/message_container/message_input_box/MessageInputBox.jsx @@ -0,0 +1,117 @@ +import { useState, useEffect } from "react"; +import styles from "./MessageInputBox.module.scss"; +import SendMessageSvg from "@images/send_message.svg?react"; +import { useMessage } from "@logics_common"; +import { useSendMessageButtonType, useEnableAutoClearMessageInputBox } from "@logics_configs"; +import { store } from "@store"; +import { scrollToBottom } from "@utils"; + +export const MessageInputBox = () => { + const [message_history, setMessageHistory] = useState([]); + const [history_index, setHistoryIndex] = useState(-1); + const { + sendMessage, + currentMessageLogs, + currentMessageInputValue, + updateMessageInputValue, + startTyping, + stopTyping, + } = useMessage(); + + const { currentEnableAutoClearMessageInputBox } = useEnableAutoClearMessageInputBox(); + const { currentSendMessageButtonType } = useSendMessageButtonType(); + + useEffect(() => { + if (currentMessageLogs.data) { + const sentMessages = currentMessageLogs.data + .filter(log => log.category === "sent") + .map(log => log.messages.original); + setMessageHistory(sentMessages); + } + }, [currentMessageLogs.data]); + + const onSubmitFunction = (e) => { + e.preventDefault(); + + if (!currentMessageInputValue.data.trim()) return updateMessageInputValue(""); + + sendMessage(currentMessageInputValue.data); + + if (currentEnableAutoClearMessageInputBox.data) updateMessageInputValue(""); + + setTimeout(() => { + scrollToBottom(store.log_box_ref); + }, 10); + + setHistoryIndex(-1); + }; + + const onChangeFunction = (e) => { + const value = e.currentTarget.value; + updateMessageInputValue(value); + value.trim() ? startTyping() : stopTyping(); + }; + + const onKeyDownFunction = (e) => { + if (e.key === "ArrowUp" && e.shiftKey) { + e.preventDefault(); + + if (history_index + 1 < message_history.length) { + const new_index = history_index + 1; + setHistoryIndex(new_index); + updateMessageInputValue(message_history[message_history.length - 1 - new_index]); + } + } + + if (e.key === "ArrowDown" && e.shiftKey) { + e.preventDefault(); + + if (history_index > -1) { + const new_index = history_index - 1; + setHistoryIndex(new_index); + setInputValue( + new_index >= 0 + ? message_history[message_history.length - 1 - new_index] + : "" + ); + } + } + + if (currentSendMessageButtonType.data === "show_and_disable_enter_key") return; + + if (e.keyCode === 13 && !e.shiftKey) { + onSubmitFunction(e); + } + + }; + + return ( +
+
+