Merge branch 'develop'

# Conflicts:
#	locales/en.yml
#	locales/ja.yml
This commit is contained in:
misyaguziya
2025-02-13 08:44:08 +09:00
170 changed files with 3688 additions and 1918 deletions

6
.github/FUNDING.yml vendored
View File

@@ -1,13 +1,13 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [misyaguziya] github: [misyaguziya]
patreon: # Replace with a single Patreon username patreon: vrct_dev
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: vrct_dev
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username polar: # Replace with a single Polar username
custom: ["https://misyaguziya.booth.pm/", "https://vrct-dev.fanbox.cc/"] custom: ["https://misyaguziya.booth.pm", "https://vrct-dev.fanbox.cc"]

View File

@@ -1,12 +1,56 @@
<div align="center"> <div align="center">
![](docs/vrct_logo.png) <picture>
<source srcset="docs/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
<source srcset="docs/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
<img src="docs/vrct_logo.png" alt="VRCT Logo" width="50%">
</picture>
<br>
<br>
[![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases) [![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases)
[![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases) [![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases)
[![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE) [![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
[![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325) [![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325)
[![Github Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-30363D?&logo=GitHub-Sponsors&logoColor=EA4AAA)](https://github.com/sponsors/misyaguziya) [![Github Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-30363D?&logo=GitHub-Sponsors&logoColor=EA4AAA)](https://github.com/sponsors/misyaguziya)
[![PIXIV FANBOX](https://img.shields.io/badge/PIXIV%20FANBOX-30363D?)](https://vrct-dev.fanbox.cc/)
<h3>
Become a VRCT Supporter on:
</h3>
<a href="https://vrct-dev.fanbox.cc">
<picture>
<source srcset="docs/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
<source srcset="docs/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
<img src="docs/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
</picture>
</a>&emsp;&nbsp;
<a href="https://patreon.com/vrct_dev">
<picture>
<source srcset="docs/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
<source srcset="docs/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
<img src="docs/patreon_logo_black.png" alt="Patreon" height="22px">
</picture>
</a>&emsp;&nbsp;
<a href="https://ko-fi.com/vrct_dev">
<picture>
<img src="docs/kofi_logo.png" alt="Ko-fi" height="22px">
</picture>
</a>&emsp;&nbsp;
<br>
<picture>
<source srcset="docs/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
<source srcset="docs/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
<img src="docs/supporter_section_border_d.png" alt="Supporter Section Border">
</picture>
<br>
<br>
| [English](./README.md) | **日本語** | [한국어](./README.ko.md) | [繁體中文](./README.zh-Hant.md) | | [English](./README.md) | **日本語** | [한국어](./README.ko.md) | [繁體中文](./README.zh-Hant.md) |

View File

@@ -1,14 +1,58 @@
<div align="center"> <div align="center">
![](docs/vrct_logo.png) <picture>
<source srcset="docs/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
<source srcset="docs/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
<img src="docs/vrct_logo.png" alt="VRCT Logo" width="50%">
</picture>
<br>
<br>
[![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases) [![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases)
[![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases) [![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases)
[![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE) [![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
[![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325) [![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325)
[![Github Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-30363D?&logo=GitHub-Sponsors&logoColor=EA4AAA)](https://github.com/sponsors/misyaguziya) [![Github Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-30363D?&logo=GitHub-Sponsors&logoColor=EA4AAA)](https://github.com/sponsors/misyaguziya)
[![PIXIV FANBOX](https://img.shields.io/badge/PIXIV%20FANBOX-30363D?)](https://vrct-dev.fanbox.cc/)
| [English](./README.md) | [日本語](./README.ja.md) | **한국어** | <h3>
Become a VRCT Supporter on:
</h3>
<a href="https://vrct-dev.fanbox.cc">
<picture>
<source srcset="docs/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
<source srcset="docs/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
<img src="docs/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
</picture>
</a>&emsp;&nbsp;
<a href="https://patreon.com/vrct_dev">
<picture>
<source srcset="docs/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
<source srcset="docs/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
<img src="docs/patreon_logo_black.png" alt="Patreon" height="22px">
</picture>
</a>&emsp;&nbsp;
<a href="https://ko-fi.com/vrct_dev">
<picture>
<img src="docs/kofi_logo.png" alt="Ko-fi" height="22px">
</picture>
</a>&emsp;&nbsp;
<br>
<picture>
<source srcset="docs/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
<source srcset="docs/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
<img src="docs/supporter_section_border_d.png" alt="Supporter Section Border">
</picture>
<br>
<br>
| [English](./README.md) | [日本語](./README.ja.md) | **한국어** | [繁體中文](./README.zh-Hant.md) |
<h3> <h3>
VRCT는 음성인식 및 번역 기능을 통해 VRChat의 대화를 지원하는 소프트웨어입니다. VRCT는 음성인식 및 번역 기능을 통해 VRChat의 대화를 지원하는 소프트웨어입니다.

View File

@@ -1,12 +1,56 @@
<div align="center"> <div align="center">
![](docs/vrct_logo.png) <picture>
<source srcset="docs/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
<source srcset="docs/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
<img src="docs/vrct_logo.png" alt="VRCT Logo" width="50%">
</picture>
<br>
<br>
[![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases) [![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases)
[![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases) [![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases)
[![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE) [![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
[![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325) [![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325)
[![Github Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-30363D?&logo=GitHub-Sponsors&logoColor=EA4AAA)](https://github.com/sponsors/misyaguziya) [![Github Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-30363D?&logo=GitHub-Sponsors&logoColor=EA4AAA)](https://github.com/sponsors/misyaguziya)
[![PIXIV FANBOX](https://img.shields.io/badge/PIXIV%20FANBOX-30363D?)](https://vrct-dev.fanbox.cc/)
<h3>
Become a VRCT Supporter on:
</h3>
<a href="https://vrct-dev.fanbox.cc">
<picture>
<source srcset="docs/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
<source srcset="docs/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
<img src="docs/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
</picture>
</a>&emsp;&nbsp;
<a href="https://patreon.com/vrct_dev">
<picture>
<source srcset="docs/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
<source srcset="docs/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
<img src="docs/patreon_logo_black.png" alt="Patreon" height="22px">
</picture>
</a>&emsp;&nbsp;
<a href="https://ko-fi.com/vrct_dev">
<picture>
<img src="docs/kofi_logo.png" alt="Ko-fi" height="22px">
</picture>
</a>&emsp;&nbsp;
<br>
<picture>
<source srcset="docs/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
<source srcset="docs/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
<img src="docs/supporter_section_border_d.png" alt="Supporter Section Border">
</picture>
<br>
<br>
| **English** | [日本語](./README.ja.md) | [한국어](./README.ko.md) | [繁體中文](./README.zh-Hant.md) | | **English** | [日本語](./README.ja.md) | [한국어](./README.ko.md) | [繁體中文](./README.zh-Hant.md) |

View File

@@ -1,12 +1,56 @@
<div align="center"> <div align="center">
![](docs/vrct_logo.png) <picture>
<source srcset="docs/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
<source srcset="docs/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
<img src="docs/vrct_logo.png" alt="VRCT Logo" width="50%">
</picture>
<br>
<br>
[![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases) [![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases)
[![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases) [![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases)
[![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE) [![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
[![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325) [![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325)
[![Github Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-30363D?&logo=GitHub-Sponsors&logoColor=EA4AAA)](https://github.com/sponsors/misyaguziya) [![Github Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-30363D?&logo=GitHub-Sponsors&logoColor=EA4AAA)](https://github.com/sponsors/misyaguziya)
[![PIXIV FANBOX](https://img.shields.io/badge/PIXIV%20FANBOX-30363D?)](https://vrct-dev.fanbox.cc/)
<h3>
Become a VRCT Supporter on:
</h3>
<a href="https://vrct-dev.fanbox.cc">
<picture>
<source srcset="docs/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
<source srcset="docs/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
<img src="docs/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
</picture>
</a>&emsp;&nbsp;
<a href="https://patreon.com/vrct_dev">
<picture>
<source srcset="docs/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
<source srcset="docs/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
<img src="docs/patreon_logo_black.png" alt="Patreon" height="22px">
</picture>
</a>&emsp;&nbsp;
<a href="https://ko-fi.com/vrct_dev">
<picture>
<img src="docs/kofi_logo.png" alt="Ko-fi" height="22px">
</picture>
</a>&emsp;&nbsp;
<br>
<picture>
<source srcset="docs/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
<source srcset="docs/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
<img src="docs/supporter_section_border_d.png" alt="Supporter Section Border">
</picture>
<br>
<br>
| [English](./README.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | **繁體中文** | | [English](./README.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | **繁體中文** |

BIN
docs/kofi_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/patreon_logo_black.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
docs/patreon_logo_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
docs/pixiv_fanbox_black.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
docs/pixiv_fanbox_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

BIN
docs/vrct_logo_black.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
docs/vrct_logo_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -1,268 +1,279 @@
# ================================= # =================================
# IMPORTANT: # IMPORTANT:
# Please read 'readme_first.txt' before making any changes. # Please read 'readme_first.txt' before making any changes.
# ================================= # =================================
common: common:
go_back_button_label: Go Back go_back_button_label: Go Back
main_page: main_page:
translation: Translation translation: Translation
transcription_send: Voice2Chatbox transcription_send: Voice2Chatbox
transcription_receive: Speaker2Log transcription_receive: Speaker2Log
foreground: Foreground foreground: Foreground
language_settings: Language Settings language_settings: Language Settings
your_language: Your Language your_language: Your Language
translate_each_other_label: Translate Each Other translate_each_other_label: Translate Each Other
swap_button_label: Swap Languages swap_button_label: Swap Languages
target_language: Target Language target_language: Target Language
translator: Translator translator: Translator
translator_ctranslate2: Internal (Default) translator_ctranslate2: Internal (Default)
translator_selector: translator_selector:
is_selected_same_language: |- is_selected_same_language: |-
Since the same language is selected for both '{{your_language}}' and '{{target_language}}', only '{{translator_ctranslate2}}' is available. Since the same language is selected for both '{{your_language}}' and '{{target_language}}', only '{{translator_ctranslate2}}' is available.
message_log: message_log:
all: All all: All
sent: Sent sent: Sent
received: Received received: Received
system: System system: System
show_resend_button: Show Resend Button show_resend_button: Show Resend Button
resend_button_on_hover_desc: Press and hold to send resend_button_on_hover_desc: Press and hold to send
# textbox_system_message: # 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_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. # enabled_translation: Translation feature is turned on.
# disabled_translation: Translation feature is turned off. # disabled_translation: Translation feature is turned off.
# enabled_voice2chatbox: Transcription from the microphone has started. # enabled_voice2chatbox: Transcription from the microphone has started.
# disabled_voice2chatbox: Transcription from the microphone has been stopped. # disabled_voice2chatbox: Transcription from the microphone has been stopped.
# enabled_speaker2log: Transcription from the speaker has started. # enabled_speaker2log: Transcription from the speaker has started.
# disabled_speaker2log: Transcription from the speaker has been stopped. # disabled_speaker2log: Transcription from the speaker has been stopped.
# enabled_foreground: The screen is fixed in the foreground. # enabled_foreground: The screen is fixed in the foreground.
# disabled_foreground: The foreground fixation has been released. # disabled_foreground: The foreground fixation has been released.
# auth_key_success: Auth key update completed. # auth_key_success: Auth key update completed.
# auth_key_error: Auth Key is incorrect or Usage limit reached. # auth_key_error: Auth Key is incorrect or Usage limit reached.
# no_mic_device_detected_error: No mic device detected. # no_mic_device_detected_error: No mic device detected.
# no_speaker_device_detected_error: No speaker 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. # 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. # 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_your_language: '"Your Language" has set to {{your_language}}.'
# selected_target_language: '"Target Language" has set to {{target_language}}.' # selected_target_language: '"Target Language" has set to {{target_language}}.'
# switched_language_preset_tab: Switched to Language Preset Tab No.{{tab_no}}." # 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}}. # 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_booth: Opened Booth page in your web browser.
# opened_web_page_vrct_documents: |- # opened_web_page_vrct_documents: |-
# Opened VRCT Documents page in your web browser. # 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)! # 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_enabled: Enabled
state_text_disabled: Disabled state_text_disabled: Disabled
language_selector: language_selector:
title_your_language: Select Your Language title_your_language: Select Your Language
title_target_language: Select Target Language title_target_language: Select Target Language
update_available: New version is here! update_available: New version is here!
updating: Now updating... updating: Now updating...
update_modal: update_modal:
cpu_desc: Use CPU only as the compute device. cpu_desc: Use CPU only as the compute device.
cuda_desc: Selectable between CPU and NVIDIA GPUs as compute devices. 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_compare_cpu_desc: With GPU selection, processing is faster compared to a CPU.
cuda_disk_space_desc: Requires approximately {{size}} of disk space. cuda_disk_space_desc: Requires approximately {{size}} of disk space.
close_modal: Close close_modal: Close
download_latest_and_restart: |- download_latest_and_restart: |-
The latest version will be downloaded, The latest version will be downloaded,
and the app will automatically restart. and the app will automatically restart.
is_latest_version_already: Already using the latest version is_latest_version_already: Already using the latest version
is_current_compute_device: Currently using this version is_current_compute_device: Currently using this version
config_page: config_page:
version: version {{version}} version: version {{version}}
# config_title: Settings # config_title: Settings
# compact_mode: Compact Mode # compact_mode: Compact Mode
# restart_message: Apply changes with a restart. # restart_message: Apply changes with a restart.
# common_error_message: # common_error_message:
# invalid_value: Invalid value. # invalid_value: Invalid value.
model_download_button_label: Download model_download_button_label: Download
side_menu_labels: side_menu_labels:
device: Device device: Device
appearance: Appearance appearance: Appearance
translation: Translation translation: Translation
transcription: Transcription transcription: Transcription
vr: VR vr: VR
others: Others others: Others
advanced_settings: Advanced Settings hotkeys: Hotkeys
supporters: Supporters advanced_settings: Advanced Settings
about_vrct: About VRCT supporters: Supporters
about_vrct: About VRCT
device:
check_volume: Check Volume device:
mic_host_device: check_volume: Check Volume
label: Mic Device mic_host_device:
label_auto_select: Auto Select label: Mic Device
label_host: Host/Driver label_auto_select: Auto Select
label_device: Device label_host: Host/Driver
mic_dynamic_energy_threshold: label_device: Device
label_for_automatic: 'Mic Energy Threshold (Current Setting: Automatic)' mic_dynamic_energy_threshold:
desc_for_automatic: Automatically determine microphone input sensitivity. label_for_automatic: 'Mic Energy Threshold (Current Setting: Automatic)'
label_for_manual: 'Mic Energy Threshold (Current Setting: Manual)' desc_for_automatic: Automatically determine microphone input sensitivity.
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. label_for_manual: 'Mic Energy Threshold (Current Setting: Manual)'
error_message: You can set it with a value between 0 to {{max}}. 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.
speaker_device: error_message: You can set it with a value between 0 to {{max}}.
label: Speaker Device speaker_device:
label_auto_select: Auto Select label: Speaker Device
label_device: Device label_auto_select: Auto Select
speaker_dynamic_energy_threshold: label_device: Device
label_for_automatic: 'Speaker Energy Threshold (Current Setting: Automatic)' speaker_dynamic_energy_threshold:
desc_for_automatic: Automatically determine speaker input sensitivity. label_for_automatic: 'Speaker Energy Threshold (Current Setting: Automatic)'
label_for_manual: 'Speaker Energy Threshold (Current Setting: Manual)' desc_for_automatic: Automatically determine speaker input sensitivity.
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. label_for_manual: 'Speaker Energy Threshold (Current Setting: Manual)'
error_message: You can set it with a value between 0 to {{max}}. 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.
no_device_error_message: No speaker device detected. error_message: You can set it with a value between 0 to {{max}}.
no_device_error_message: No speaker device detected.
appearance:
transparency: appearance:
label: Transparency transparency:
desc: Change the main window's transparency. label: Transparency
ui_size: desc: Change the main window's transparency.
label: UI Size ui_size:
textbox_ui_size: label: UI Size
label: Message Logs Font Size textbox_ui_size:
desc: You can adjust the font size used in the logs relative to the UI size. label: Message Logs Font Size
send_message_button_type: desc: You can adjust the font size used in the logs relative to the UI size.
label: Send Message Button send_message_button_type:
hide: Hide (Use enter key to send) label: Send Message Button
show: Show hide: Hide (Use enter key to send)
show_and_disable_enter_key: Show and disable to send when pressed enter key show: Show
font_family: show_and_disable_enter_key: Show and disable to send when pressed enter key
label: Font Family font_family:
ui_language: label: Font Family
label: UI Language ui_language:
label: UI Language
translation:
ctranslate2_weight_type: translation:
label: Internal Translation Model ctranslate2_weight_type:
desc: You can choose the translation model to use for the internal translation engine. label: Internal Translation Model
small: Basic model ({{capacity}}) desc: You can choose the translation model to use for the internal translation engine.
large: High accuracy model ({{capacity}}) small: Basic model ({{capacity}})
ctranslate2_compute_device: large: High accuracy model ({{capacity}})
label: Internal Translation Compute Device ctranslate2_compute_device:
deepl_auth_key: label: Internal Translation Compute Device
label: DeepL Auth Key deepl_auth_key:
desc: Please select {{translator}} on the main screen with DeepL_API when using. ※Some languages may not be supported. label: DeepL Auth Key
save: Save desc: Please select {{translator}} on the main screen with DeepL_API when using. ※Some languages may not be supported.
edit: Edit save: Save
open_auth_key_webpage: Open DeepL Account Webpage edit: Edit
auth_key_success: Auth key update completed. open_auth_key_webpage: Open DeepL Account Webpage
auth_key_error: Auth Key is incorrect or Usage limit reached. auth_key_success: Auth key update completed.
auth_key_error: Auth Key is incorrect or Usage limit reached.
transcription:
section_label_mic: Mic transcription:
section_label_speaker: Speaker section_label_mic: Mic
section_label_transcription_engines: Transcription Engines section_label_speaker: Speaker
mic_record_timeout: section_label_transcription_engines: Transcription Engines
label: Mic Record Timeout mic_record_timeout:
desc: Detects silence and, when the specified number of seconds has passed, considers the mic input to have ended. (Second(s)) label: Mic Record Timeout
error_message: It cannot be greater than '{{mic_phrase_timeout_label}}' with a value of 0 or more. desc: Detects silence and, when the specified number of seconds has passed, considers the mic input to have ended. (Second(s))
mic_phrase_timeout: error_message: It cannot be greater than '{{mic_phrase_timeout_label}}' with a value of 0 or more.
label: Mic Phrase Timeout mic_phrase_timeout:
desc: Transcription processing is performed at intervals of the specified number of seconds. label: Mic Phrase Timeout
error_message: It cannot be set lower than '{{mic_record_timeout_label}}' with a value of 0 or more. desc: Transcription processing is performed at intervals of the specified number of seconds.
mic_max_phrase: error_message: It cannot be set lower than '{{mic_record_timeout_label}}' with a value of 0 or more.
label: Mic Max Words mic_max_phrase:
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. label: Mic Max Words
error_message: You can set a number equal to or greater than 0. 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.
mic_word_filter: error_message: You can set a number equal to or greater than 0.
label: Mic Word Filter mic_word_filter:
desc: |- label: Mic Word Filter
If a registered word is detected, the text will not be sent. To add multiple words at once, separate them with a "," (comma). desc: |-
*Duplicate words will not be registered. If a registered word is detected, the text will not be sent. To add multiple words at once, separate them with a "," (comma).
add_button_label: Add *Duplicate words will not be registered.
count_desc: 'Current registered word count: {{count}}' add_button_label: Add
speaker_record_timeout: count_desc: 'Current registered word count: {{count}}'
label: Speaker Record Timeout speaker_record_timeout:
desc: Detects silence and, when the specified number of seconds has passed, considers the speaker input to have ended. (Second(s)) label: Speaker Record Timeout
error_message: It cannot be greater than '{{speaker_phrase_timeout_label}}' with a value of 0 or more. desc: Detects silence and, when the specified number of seconds has passed, considers the speaker input to have ended. (Second(s))
speaker_phrase_timeout: error_message: It cannot be greater than '{{speaker_phrase_timeout_label}}' with a value of 0 or more.
label: Speaker Phrase Timeout speaker_phrase_timeout:
desc: Transcription processing is performed at intervals of the specified number of seconds. label: Speaker Phrase Timeout
error_message: It cannot be set lower than '{{speaker_record_timeout_label}}' with a value of 0 or more. desc: Transcription processing is performed at intervals of the specified number of seconds.
speaker_max_phrase: error_message: It cannot be set lower than '{{speaker_record_timeout_label}}' with a value of 0 or more.
label: Speaker Max Words speaker_max_phrase:
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. label: Speaker Max Words
error_message: You can set a number equal to or greater than 0. 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.
select_transcription_engine: error_message: You can set a number equal to or greater than 0.
label: Transcription Engine select_transcription_engine:
whisper_weight_type: label: Transcription Engine
label: Whisper Model whisper_weight_type:
desc: |- label: Whisper Model
Larger models tend to have higher accuracy, but they also consume more CPU or GPU resources. desc: |-
Especially for models larger than medium, it may be difficult or even impossible to use them depending on the performance of your CPU/GPU. Larger models tend to have higher accuracy, but they also consume more CPU or GPU resources.
model_template: '{{model_name}} model ({{capacity}})' Especially for models larger than medium, it may be difficult or even impossible to use them depending on the performance of your CPU/GPU.
recommended_model_template: '{{model_name}} model ({{capacity}}) (Recommended)' model_template: '{{model_name}} model ({{capacity}})'
whisper_compute_device: recommended_model_template: '{{model_name}} model ({{capacity}}) (Recommended)'
label: Whisper Compute Device whisper_compute_device:
label: Whisper Compute Device
vr:
single_line: Single line vr:
multi_lines: Multi lines single_line: Single line
overlay_enable: Enable multi_lines: Multi lines
restore_default_settings: Restore Default Settings overlay_enable: Enable
position: Position restore_default_settings: Restore Default Settings
rotation: Rotation position: Position
x_position: X-axis (left-right) rotation: Rotation
y_position: Y-axis (up-down) x_position: X-axis (left-right)
z_position: Z-axis (front-back) y_position: Y-axis (up-down)
x_rotation: X-axis rotation z_position: Z-axis (front-back)
y_rotation: Y-axis rotation x_rotation: X-axis rotation
z_rotation: Z-axis rotation y_rotation: Y-axis rotation
sample_text_button: z_rotation: Z-axis rotation
start: |- sample_text_button:
Send sample texts start: |-
to Overlay Send sample texts
stop: Stop Sending to Overlay
sample_text: Sample text. stop: Stop Sending
opacity: Opacity sample_text: Sample text.
ui_scaling: UI Scaling opacity: Opacity
display_duration: Display duration ui_scaling: UI Scaling
fadeout_duration: Fadeout duration display_duration: Display duration
tracker: Tracker fadeout_duration: Fadeout duration
hmd: HMD tracker: Tracker
left_hand: Left hand hmd: HMD
right_hand: Right hand left_hand: Left hand
common_settings: Common Settings right_hand: Right hand
overlay_show_only_translated_messages: common_settings: Common Settings
label: Show Only Translated Messages overlay_show_only_translated_messages:
label: Show Only Translated Messages
others:
auto_clear_the_message_box: others:
label: Auto Clear The Message Box auto_clear_the_message_box:
send_only_translated_messages: label: Auto Clear The Message Box
label: Send Only Translated Messages send_only_translated_messages:
auto_export_message_logs: label: Send Only Translated Messages
label: Auto Export Message Logs auto_export_message_logs:
desc: Automatically export the conversation messages as a text file. label: Auto Export Message Logs
vrc_mic_mute_sync: desc: Automatically export the conversation messages as a text file.
label: VRC Mic Mute Sync vrc_mic_mute_sync:
desc: |- label: VRC Mic Mute Sync
VRCT will not send the message to VRChat while VRChat's mic is muted. desc: |-
*There is a bit latency and Push-To-Talk is not supported. VRCT will not send the message to VRChat while VRChat's mic is muted.
send_message_to_vrc: *There is a bit latency and Push-To-Talk is not supported.
label: Send Message To VRChat send_message_to_vrc:
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. label: Send Message To VRChat
send_received_message_to_vrc: 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.
label: Send Received Message To VRChat send_received_message_to_vrc:
desc: Send the message you received from the speaker's sound to VRChat's chatbox. 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: hotkeys:
label: OSC IP Address toggle_vrct_visibility:
osc_port: label: Toggle VRCT Visibility
label: OSC Port toggle_translation:
open_config_filepath: label: Toggle {{translation}}
label: Open Config File toggle_transcription_send:
switch_compute_device: label: Toggle {{transcription_send}}
toggle_transcription_receive:
label: Toggle {{transcription_receive}}
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 label: Switch VRCT to CPU/GPU Version

View File

@@ -1,266 +1,277 @@
# ================================= # =================================
# IMPORTANT: # IMPORTANT:
# Please read 'readme_first.txt' before making any changes. # Please read 'readme_first.txt' before making any changes.
# ================================= # =================================
common: common:
go_back_button_label: 戻る go_back_button_label: 戻る
main_page: main_page:
translation: 翻訳 translation: 翻訳
transcription_send: 音声認識(マイク) transcription_send: 音声認識(マイク)
transcription_receive: 音声認識(スピーカー) transcription_receive: 音声認識(スピーカー)
foreground: 最前面表示 foreground: 最前面表示
language_settings: 言語設定 language_settings: 言語設定
your_language: あなたの言語 your_language: あなたの言語
translate_each_other_label: 双方向に翻訳 translate_each_other_label: 双方向に翻訳
swap_button_label: 言語を入れ替え swap_button_label: 言語を入れ替え
target_language: 相手の言語 target_language: 相手の言語
translator: 翻訳エンジン translator: 翻訳エンジン
translator_ctranslate2: オフライン翻訳 (Default) translator_ctranslate2: オフライン翻訳 (Default)
translator_selector: translator_selector:
is_selected_same_language: |- is_selected_same_language: |-
「{{your_language}}」と「{{target_language}}」に同じ言語が選択がされているため、「{{translator_ctranslate2}}」のみが使用できます。 「{{your_language}}」と「{{target_language}}」に同じ言語が選択がされているため、「{{translator_ctranslate2}}」のみが使用できます。
message_log: message_log:
all: 全て all: 全て
sent: 送信 sent: 送信
received: 受信 received: 受信
system: システム system: システム
show_resend_button: 再送信ボタンを表示する show_resend_button: 再送信ボタンを表示する
resend_button_on_hover_desc: 長押しで送信 resend_button_on_hover_desc: 長押しで送信
# textbox_system_message: # textbox_system_message:
# enabled_translation: 翻訳機能をONにしました。 # enabled_translation: 翻訳機能をONにしました。
# disabled_translation: 翻訳機能をOFFしました。 # disabled_translation: 翻訳機能をOFFしました。
# enabled_voice2chatbox: マイクからの音声入力、文字起こしを開始します。 # enabled_voice2chatbox: マイクからの音声入力、文字起こしを開始します。
# disabled_voice2chatbox: マイクからの音声入力、文字起こしを終了しました。 # disabled_voice2chatbox: マイクからの音声入力、文字起こしを終了しました。
# enabled_speaker2log: スピーカーからの音声聞き取り、文字起こしを開始します。 # enabled_speaker2log: スピーカーからの音声聞き取り、文字起こしを開始します。
# disabled_speaker2log: スピーカーからの音声聞き取り、文字起こしを終了しました。 # disabled_speaker2log: スピーカーからの音声聞き取り、文字起こしを終了しました。
# enabled_foreground: 画面を常に最前面へ固定します。 # enabled_foreground: 画面を常に最前面へ固定します。
# disabled_foreground: 最前面への固定を解除しました。 # disabled_foreground: 最前面への固定を解除しました。
# auth_key_success: 認証キーの更新が完了しました。 # auth_key_success: 認証キーの更新が完了しました。
# auth_key_error: 認証キーが間違っているか、API使用制限が上限に達しています。 # auth_key_error: 認証キーが間違っているか、API使用制限が上限に達しています。
# no_mic_device_detected_error: マイクデバイスが検出されませんでした。 # no_mic_device_detected_error: マイクデバイスが検出されませんでした。
# no_speaker_device_detected_error: スピーカーデバイスが検出されませんでした。 # no_speaker_device_detected_error: スピーカーデバイスが検出されませんでした。
# translation_engine_limit_error: 翻訳エンジンを自動的に変更しました。対象翻訳エンジンへのリクエストが多すぎるため、一時的にアクセスが制限されています。同じ翻訳エンジンを使用したい場合はしばらく待ってから、VRCTの再起動をしてもう一度試してみてください。 # translation_engine_limit_error: 翻訳エンジンを自動的に変更しました。対象翻訳エンジンへのリクエストが多すぎるため、一時的にアクセスが制限されています。同じ翻訳エンジンを使用したい場合はしばらく待ってから、VRCTの再起動をしてもう一度試してみてください。
# detected_by_word_filter: ワードフィルターに登録されている単語 {{detected_message}} が検出されたため送信しませんでした。 # detected_by_word_filter: ワードフィルターに登録されている単語 {{detected_message}} が検出されたため送信しませんでした。
# selected_your_language: 「あなたの言語」 を {{your_language}} に設定しました。 # selected_your_language: 「あなたの言語」 を {{your_language}} に設定しました。
# selected_target_language: 「相手の言語」 を {{target_language}} に設定しました。 # selected_target_language: 「相手の言語」 を {{target_language}} に設定しました。
# switched_language_preset_tab: 言語プリセット番号 {{tab_no}} に切り替わりました。 # switched_language_preset_tab: 言語プリセット番号 {{tab_no}} に切り替わりました。
# latest_language_setting: 現在「あなたの言語」は {{your_language}}、「相手の言語」は {{target_language}} に設定されています。 # latest_language_setting: 現在「あなたの言語」は {{your_language}}、「相手の言語」は {{target_language}} に設定されています。
# opened_web_page_booth: お使いのブラウザで、Boothのページを開きました。 # opened_web_page_booth: お使いのブラウザで、Boothのページを開きました。
# opened_web_page_vrct_documents: |- # opened_web_page_vrct_documents: |-
# お使いのブラウザで、VRCTのドキュメントを開きました。使用方法などはそちらに記載されています。 # お使いのブラウザで、VRCTのドキュメントを開きました。使用方法などはそちらに記載されています。
# 不具合、ご要望、その他お問い合わせはドキュメント最下部にあるLinks、「お問合せフォーム」もしくはX (元Twitter) にて気軽にご連絡ください! # 不具合、ご要望、その他お問い合わせはドキュメント最下部にあるLinks、「お問合せフォーム」もしくはX (元Twitter) にて気軽にご連絡ください!
state_text_enabled: 有効 state_text_enabled: 有効
state_text_disabled: 無効 state_text_disabled: 無効
language_selector: language_selector:
title_your_language: あなたの言語 title_your_language: あなたの言語
title_target_language: 相手の言語 title_target_language: 相手の言語
update_available: 新しいバージョンが出ました! update_available: 新しいバージョンが出ました!
updating: アップデート中... updating: アップデート中...
update_modal: update_modal:
cpu_desc: 処理デバイスとしてCPUのみを使用 cpu_desc: 処理デバイスとしてCPUのみを使用
cuda_desc: 処理デバイスとしてCPUとNVIDIA製のGPUを選択可能 cuda_desc: 処理デバイスとしてCPUとNVIDIA製のGPUを選択可能
cuda_compare_cpu_desc: GPU選択時、CPUと比べて処理が高速 cuda_compare_cpu_desc: GPU選択時、CPUと比べて処理が高速
cuda_disk_space_desc: 約{{size}}のディスク容量が必要 cuda_disk_space_desc: 約{{size}}のディスク容量が必要
close_modal: 閉じる close_modal: 閉じる
download_latest_and_restart: |- download_latest_and_restart: |-
最新版がダウンロードされ、 最新版がダウンロードされ、
アプリは自動的に再起動します。 アプリは自動的に再起動します。
is_latest_version_already: すでに最新版を使用中 is_latest_version_already: すでに最新版を使用中
is_current_compute_device: 現在使用中のバージョン is_current_compute_device: 現在使用中のバージョン
config_page: config_page:
version: バージョン {{version}} version: バージョン {{version}}
# config_title: 設定 # config_title: 設定
# compact_mode: コンパクトモード # compact_mode: コンパクトモード
# restart_message: 再起動して変更を適用する。 # restart_message: 再起動して変更を適用する。
# common_error_message: # common_error_message:
# invalid_value: 無効な値です。 # invalid_value: 無効な値です。
model_download_button_label: ダウンロード model_download_button_label: ダウンロード
side_menu_labels: side_menu_labels:
device: デバイス device: デバイス
appearance: デザイン appearance: デザイン
translation: 翻訳 translation: 翻訳
transcription: 音声認識 transcription: 音声認識
others: その他 others: その他
advanced_settings: 高度な設定 hotkeys: ホットキー
advanced_settings: 高度な設定
device:
check_volume: 音量チェック device:
mic_host_device: check_volume: 音量チェック
label: マイク (デバイス) mic_host_device:
label_auto_select: 自動選択 label: マイク (デバイス)
label_host: ホスト/ドライバー label_auto_select: 自動選択
label_device: デバイス label_host: ホスト/ドライバー
mic_dynamic_energy_threshold: label_device: デバイス
label_for_automatic: 'マイク入力感度の調整 (現在の設定: 自動)' mic_dynamic_energy_threshold:
desc_for_automatic: マイク入力感度を自動的に調節する。 label_for_automatic: 'マイク入力感度の調整 (現在の設定: 自動)'
label_for_manual: 'マイク入力感度の調整 (現在の設定: 手動)' desc_for_automatic: マイク入力感度を自動的に調節する。
desc_for_manual: スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 label_for_manual: 'マイク入力感度の調整 (現在の設定: 手動)'
error_message: 0 から {{max}} までの数値で設定できます。 desc_for_manual: スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。
speaker_device: error_message: 0 から {{max}} までの数値で設定できます。
label: スピーカー (デバイス) speaker_device:
label_auto_select: 自動選択 label: スピーカー (デバイス)
speaker_dynamic_energy_threshold: label_auto_select: 自動選択
label_for_automatic: 'スピーカー入力感度の調整 (現在の設定: 自動)' speaker_dynamic_energy_threshold:
desc_for_automatic: スピーカー入力感度を自動的に調節する。 label_for_automatic: 'スピーカー入力感度の調整 (現在の設定: 自動)'
label_for_manual: 'スピーカー入力感度の調整 (現在の設定: 手動)' desc_for_automatic: スピーカー入力感度を自動的に調節する。
desc_for_manual: スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。 label_for_manual: 'スピーカー入力感度の調整 (現在の設定: 手動)'
error_message: 0 から {{max}} までの数値で設定できます。 desc_for_manual: スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。
no_device_error_message: スピーカーデバイスが検出されませんでした error_message: 0 から {{max}} までの数値で設定できます
no_device_error_message: スピーカーデバイスが検出されませんでした。
appearance:
transparency: appearance:
label: 透明度 transparency:
desc: メイン画面の透明度を変更できます。 label: 透明度
ui_size: desc: メイン画面の透明度を変更できます。
label: UIサイズ ui_size:
textbox_ui_size: label: UIサイズ
label: ログのフォントサイズ textbox_ui_size:
desc: ログに表示されるフォントサイズを、UIサイズを基準にして倍率を変えられます。 label: ログフォントサイズ
send_message_button_type: desc: ログに表示されるフォントのサイズを、UIサイズを基準にして倍率を変えられます。
label: メッセージ送信ボタン send_message_button_type:
hide: 非表示 (エンターキーを使って送信) label: メッセージ送信ボタン
show: 表示 hide: 表示 (エンターキーを使って送信)
show_and_disable_enter_key: 表示し、エンターキーでの送信を無効 show: 表示
font_family: show_and_disable_enter_key: 表示し、エンターキーでの送信を無効
label: 使用フォント font_family:
ui_language: label: 使用フォント
label: UIの言語 ui_language:
label: UIの言語
translation:
ctranslate2_weight_type: translation:
label: オフライン翻訳のタイプ ctranslate2_weight_type:
desc: 翻訳エンジン(オフライン翻訳)で翻訳する際に、使用する翻訳モデルを選択できます。 label: オフライン翻訳のタイプ
small: 通常モデル ({{capacity}}) desc: 翻訳エンジン(オフライン翻訳)で翻訳する際に、使用する翻訳モデルを選択できます。
large: 高精度モデル ({{capacity}}) small: 通常モデル ({{capacity}})
ctranslate2_compute_device: large: 高精度モデル ({{capacity}})
label: オフライン翻訳の処理デバイス ctranslate2_compute_device:
deepl_auth_key: label: オフライン翻訳の処理デバイス
label: DeepL 認証キー deepl_auth_key:
desc: |- label: DeepL 認証キー
使用の際は、メイン画面にある {{translator}} をDeepL_APIに変更してください。 desc: |-
※対応していない言語もあります。 使用の際は、メイン画面にある {{translator}} をDeepL_APIに変更してください。
open_auth_key_webpage: DeepLアカウントページを開く ※対応していない言語もあります。
save: 保存 open_auth_key_webpage: DeepLアカウントページを開く
edit: 編集 save: 保存
auth_key_success: 認証キーの更新が完了しました。 edit: 編集
auth_key_error: 認証キーが間違っているか、API使用制限が上限に達しています auth_key_success: 認証キーの更新が完了しました
auth_key_error: 認証キーが間違っているか、API使用制限が上限に達しています。
transcription:
section_label_mic: マイク transcription:
section_label_speaker: スピーカー section_label_mic: マイク
section_label_transcription_engines: 音声認識エンジン section_label_speaker: スピーカー
mic_record_timeout: section_label_transcription_engines: 音声認識エンジン
label: 入力が終了したとみなす無音時間 mic_record_timeout:
desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。 label: 入力が終了したとみなす無音時間
error_message: 0 以上で 「{{mic_phrase_timeout_label}}」より大きくすることはできません desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします
mic_phrase_timeout: error_message: 0 以上で 「{{mic_phrase_timeout_label}}」より大きくすることはできません。
label: 一度に文字起こしする時間の長さ mic_phrase_timeout:
desc: 設定された秒数ごとに文字起こし処理が行われます。 label: 一度に文字起こしする時間の長さ
error_message: 0 以上で 「{{mic_record_timeout_label}}」より小さくすることはできません desc: 設定された秒数ごとに文字起こし処理が行われます
mic_max_phrase: error_message: 0 以上で 「{{mic_record_timeout_label}}」より小さくすることはできません。
label: 送信するまでに保持する単語数 mic_max_phrase:
desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をVRChatへ送信し、ログに表示します。 label: 送信するまでに保持する単語数
error_message: 0以上の数値を設定できます。 desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をVRChatへ送信し、ログに表示します。
mic_word_filter: error_message: 0以上の数値を設定できます。
label: ワードフィルター mic_word_filter:
desc: |- label: ワードフィルター
登録された単語を検出すると、その文章は送信されません。 desc: |-
「,」カンマで区切ると、まとめて複数の単語を追加できます 登録された単語を検出すると、その文章は送信されません
※重複した単語は登録されません。 「,」カンマで区切ると、まとめて複数の単語を追加できます。
add_button_label: 追加 ※重複した単語は登録されません。
count_desc: '現在登録されている単語数: {{count}}' add_button_label: 追加
speaker_record_timeout: count_desc: '現在登録されている単語数: {{count}}'
label: 入力が終了したとみなす無音時間 speaker_record_timeout:
desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。 label: 入力が終了したとみなす無音時間
error_message: 0 以上で 「{{speaker_phrase_timeout_label}}」より大きくすることはできません desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします
speaker_phrase_timeout: error_message: 0 以上で 「{{speaker_phrase_timeout_label}}」より大きくすることはできません。
label: 一度に文字起こしする時間の長さ speaker_phrase_timeout:
desc: 設定された秒数ごとに文字起こし処理が行われます。 label: 一度に文字起こしする時間の長さ
error_message: 0 以上で 「{{speaker_record_timeout_label}}」より小さくすることはできません desc: 設定された秒数ごとに文字起こし処理が行われます
speaker_max_phrase: error_message: 0 以上で 「{{speaker_record_timeout_label}}」より小さくすることはできません。
label: ログとして表示するまでに保持する単語数 speaker_max_phrase:
desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をログに表示します。 label: ログとして表示するまでに保持する単語数
error_message: 0以上の数値を設定できます。 desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をログに表示します。
select_transcription_engine: error_message: 0以上の数値を設定できます。
label: 音声認識で使用するエンジン select_transcription_engine:
whisper_weight_type: label: 音声認識で使用するエンジン
label: Whisperモデルのタイプ whisper_weight_type:
desc: |- label: Whisperモデルのタイプ
容量が大きいモデルほど精度は高いですが、その分CPUやGPUを占有します。 desc: |-
※特にmediumより容量大きいモデルは、CPU/GPUの性能によっては使用すらも困難です。 容量大きいモデルほど精度は高いですが、その分CPUGPUを占有します。
model_template: '{{model_name}} モデル ({{capacity}})' ※特にmediumより容量の大きいモデルは、CPU/GPUの性能によっては使用すらも困難です。
recommended_model_template: '{{model_name}} モデル ({{capacity}}) (推奨)' model_template: '{{model_name}} モデル ({{capacity}})'
whisper_compute_device: recommended_model_template: '{{model_name}} モデル ({{capacity}}) (推奨)'
label: Whisperで使用する処理デバイス whisper_compute_device:
label: Whisperで使用する処理デバイス
vr:
single_line: 一行 vr:
multi_lines: 複数 single_line:
overlay_enable: 有効にする multi_lines: 複数行
restore_default_settings: 初期値に戻す overlay_enable: 有効にする
position: 位置 restore_default_settings: 初期値に戻す
rotation: 回転 position: 位置
x_position: X軸左右 rotation: 回転
y_position: Y軸(上下 x_position: X軸(左右
z_position: Z軸(前後 y_position: Y軸(上下
x_rotation: X軸の回転 z_position: Z軸前後
y_rotation: Y軸の回転 x_rotation: X軸の回転
z_rotation: Z軸の回転 y_rotation: Y軸の回転
sample_text_button: z_rotation: Z軸の回転
start: |- sample_text_button:
サンプルテキストを start: |-
Overlayに送信する サンプルテキストを
stop: 送信を停止 Overlayに送信する
sample_text: サンプルテキスト stop: 送信を停止
opacity: 透明度 sample_text: サンプルテキスト
ui_scaling: サイズ opacity: 透明度
display_duration: 表示時間 ui_scaling: サイズ
fadeout_duration: フェードアウト時間 display_duration: 表示時間
common_settings: 共通設定 fadeout_duration: フェードアウト時間
hmd: HMD common_settings: 共通設定
left_hand: 左手 hmd: HMD
right_hand: left_hand:
tracker: 表示するトラッカーの位置 right_hand: 右手
overlay_show_only_translated_messages: tracker: 表示するトラッカーの位置
label: 翻訳後のメッセージのみ表示する overlay_show_only_translated_messages:
label: 翻訳後のメッセージのみ表示する
others:
auto_clear_the_message_box: others:
label: 送信後はチャットボックスを空にする auto_clear_the_message_box:
send_only_translated_messages: label: 送信後はチャットボックスを空にする
label: 翻訳後のメッセージのみ送信する send_only_translated_messages:
auto_export_message_logs: label: 翻訳後のメッセージのみ送信する
label: 会話ログを自動的に保存する auto_export_message_logs:
desc: テキストファイルとしてログがlogsフォルダ内に保存されます。 label: 会話ログを自動的に保存する
vrc_mic_mute_sync: desc: テキストファイルとしてログがlogsフォルダ内に保存されます。
label: VRCマイクミュート同期 vrc_mic_mute_sync:
desc: |- label: VRCマイクミュート同期
VRChatのマイクがミュートされている間は、メッセージをVRChatに送信しません。 desc: |-
※若干の遅延はあります。また、Push-To-Talkは非対応です。 VRChatのマイクがミュートされている間は、メッセージをVRChatに送信しません。
send_message_to_vrc: ※若干の遅延はあります。また、Push-To-Talkは非対応です。
label: VRChatにメッセージを送信する send_message_to_vrc:
desc: サポート対象外ですが、VRChatにメッセージを送信せずに使う方法があります。送信したい場合、この機能を有効にする事を忘れないでください。 label: VRChatにメッセージを送信する
send_received_message_to_vrc: desc: サポート対象外ですが、VRChatにメッセージを送信せずに使う方法があります。送信したい場合、この機能を有効にする事を忘れないでください。
label: 受信したメッセージをVRChatに送信する send_received_message_to_vrc:
desc: スピーカーから聞き取り、文字起こしされたメッセージをVRChatに送信します。 label: 受信したメッセージをVRChatに送信する
desc: スピーカーから聞き取り、文字起こしされたメッセージをVRChatに送信します。
advanced_settings:
osc_ip_address: hotkeys:
label: OSC IP Address toggle_vrct_visibility:
osc_port: label: VRCTの最小化/アクティブ化の切り替え
label: OSC Port toggle_translation:
open_config_filepath: label: '{{translation}}機能切り替え'
label: 設定ファイルを開く toggle_transcription_send:
switch_compute_device: label: '{{transcription_send}}機能切り替え'
toggle_transcription_receive:
label: '{{transcription_receive}}機能切り替え'
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バージョンの切り替え label: VRCT CPU/GPUバージョンの切り替え

View File

@@ -17,10 +17,9 @@
"dev-ui": "npm-run-all --parallel vite tauri-dev", "dev-ui": "npm-run-all --parallel vite tauri-dev",
"build": "npm run build-python && npm run vite-build && npm run tauri build", "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-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": "npm run build && python zip.py --zip_name VRCT.zip",
"release": "python zip.py --zip_name VRCT.zip", "release-cuda": "npm run build-cuda && python zip.py --zip_name VRCT_cuda.zip",
"release-cuda": "python zip.py --zip_name VRCT_cuda.zip", "release-all": "npm run release && npm run release-cuda"
"release-all": "npm run build && npm run release && npm run build-cuda && npm run release-cuda"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "11.14.0", "@emotion/react": "11.14.0",

View File

@@ -237,6 +237,15 @@ class Config:
if isinstance(value, dict): if isinstance(value, dict):
self._SELECTABLE_TRANSLATION_ENGINE_STATUS = value self._SELECTABLE_TRANSLATION_ENGINE_STATUS = value
@property
def SELECTABLE_TRANSCRIPTION_ENGINE_STATUS(self):
return self._SELECTABLE_TRANSCRIPTION_ENGINE_STATUS
@SELECTABLE_TRANSCRIPTION_ENGINE_STATUS.setter
def SELECTABLE_TRANSCRIPTION_ENGINE_STATUS(self, value):
if isinstance(value, dict):
self._SELECTABLE_TRANSCRIPTION_ENGINE_STATUS = value
# Save Json Data # Save Json Data
## Main Window ## Main Window
@property @property
@@ -533,6 +542,19 @@ class Config:
self._MIC_WORD_FILTER = sorted(set(value), key=value.index) self._MIC_WORD_FILTER = sorted(set(value), key=value.index)
self.saveConfig(inspect.currentframe().f_code.co_name, value) self.saveConfig(inspect.currentframe().f_code.co_name, value)
@property
@json_serializable('HOTKEYS')
def HOTKEYS(self):
return self._HOTKEYS
@HOTKEYS.setter
def HOTKEYS(self, value):
if isinstance(value, dict) and set(value.keys()) == set(self.HOTKEYS.keys()):
for key, value in value.items():
if isinstance(value, list) or value is None:
self._HOTKEYS[key] = value
self.saveConfig(inspect.currentframe().f_code.co_name, self.HOTKEYS, immediate_save=True)
@property @property
@json_serializable('MIC_AVG_LOGPROB') @json_serializable('MIC_AVG_LOGPROB')
def MIC_AVG_LOGPROB(self): def MIC_AVG_LOGPROB(self):
@@ -911,7 +933,7 @@ class Config:
def init_config(self): def init_config(self):
# Read Only # Read Only
self._VERSION = "3.0.1" self._VERSION = "3.0.2"
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
self._PATH_LOCAL = os_path.dirname(sys.executable) self._PATH_LOCAL = os_path.dirname(sys.executable)
else: else:
@@ -964,6 +986,9 @@ class Config:
self._SELECTABLE_TRANSLATION_ENGINE_STATUS = {} self._SELECTABLE_TRANSLATION_ENGINE_STATUS = {}
for engine in self.SELECTABLE_TRANSLATION_ENGINE_LIST: for engine in self.SELECTABLE_TRANSLATION_ENGINE_LIST:
self._SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False self._SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
self._SELECTABLE_TRANSCRIPTION_ENGINE_STATUS = {}
for engine in self.SELECTABLE_TRANSCRIPTION_ENGINE_LIST:
self._SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
# Save Json Data # Save Json Data
## Main Window ## Main Window
@@ -1026,6 +1051,12 @@ class Config:
self._MIC_PHRASE_TIMEOUT = 3 self._MIC_PHRASE_TIMEOUT = 3
self._MIC_MAX_PHRASES = 10 self._MIC_MAX_PHRASES = 10
self._MIC_WORD_FILTER = [] self._MIC_WORD_FILTER = []
self._HOTKEYS = {
"toggle_vrct_visibility": None,
"toggle_translation": None,
"toggle_transcription_send": None,
"toggle_transcription_receive": None,
}
self._MIC_AVG_LOGPROB = -0.8 self._MIC_AVG_LOGPROB = -0.8
self._MIC_NO_SPEECH_PROB = 0.6 self._MIC_NO_SPEECH_PROB = 0.6
self._AUTO_SPEAKER_SELECT = True self._AUTO_SPEAKER_SELECT = True

View File

@@ -6,7 +6,7 @@ import re
from device_manager import device_manager from device_manager import device_manager
from config import config from config import config
from model import model from model import model
from utils import removeLog, printLog, errorLogging from utils import removeLog, printLog, errorLogging, isConnectedNetwork
class Controller: class Controller:
def __init__(self) -> None: def __init__(self) -> None:
@@ -25,6 +25,34 @@ class Controller:
self.run = run self.run = run
# response functions # response functions
def connectedNetwork(self) -> None:
self.run(
200,
self.run_mapping["connected_network"],
True,
)
def disconnectedNetwork(self) -> None:
self.run(
200,
self.run_mapping["connected_network"],
False,
)
def enableAiModels(self) -> None:
self.run(
200,
self.run_mapping["enable_ai_models"],
True,
)
def disableAiModels(self) -> None:
self.run(
200,
self.run_mapping["enable_ai_models"],
False,
)
def updateMicHostList(self) -> None: def updateMicHostList(self) -> None:
self.run( self.run(
200, 200,
@@ -148,15 +176,25 @@ class Controller:
) )
def downloaded(self) -> None: def downloaded(self) -> None:
weight_type_dict = config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT if model.checkTranslatorCTranslate2ModelWeight(self.weight_type) is True:
weight_type_dict[self.weight_type] = True weight_type_dict = config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT
config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = weight_type_dict weight_type_dict[self.weight_type] = True
config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = weight_type_dict
self.run( self.run(
200, 200,
self.run_mapping["downloaded_ctranslate2_weight"], self.run_mapping["downloaded_ctranslate2_weight"],
self.weight_type, self.weight_type,
) )
else:
self.run(
400,
self.run_mapping["error_ctranslate2_weight"],
{
"message":"CTranslate2 weight download error",
"data": None
},
)
class DownloadWhisper: class DownloadWhisper:
def __init__(self, run_mapping:dict, weight_type:str, run:Callable[[int, str, Any], None]) -> None: def __init__(self, run_mapping:dict, weight_type:str, run:Callable[[int, str, Any], None]) -> None:
@@ -173,15 +211,25 @@ class Controller:
) )
def downloaded(self) -> None: def downloaded(self) -> None:
weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT if model.checkTranscriptionWhisperModelWeight(self.weight_type) is True:
weight_type_dict[self.weight_type] = True weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict weight_type_dict[self.weight_type] = True
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict
self.run( self.run(
200, 200,
self.run_mapping["downloaded_whisper_weight"], self.run_mapping["downloaded_whisper_weight"],
self.weight_type, self.weight_type,
) )
else:
self.run(
400,
self.run_mapping["error_whisper_weight"],
{
"message":"Whisper weight download error",
"data": None
},
)
def micMessage(self, result: dict) -> None: def micMessage(self, result: dict) -> None:
message = result["text"] message = result["text"]
@@ -494,7 +542,10 @@ class Controller:
your_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"] your_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]
for target_language in config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO].values(): 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: if your_language["language"] == target_language["language"] and target_language["enable"] is True:
engines = ["CTranslate2"] if config.SELECTABLE_TRANSLATION_ENGINE_STATUS["CTranslate2"] is True:
engines = ["CTranslate2"]
else:
engines = []
return {"status":200, "result":engines} return {"status":200, "result":engines}
@@ -541,6 +592,11 @@ class Controller:
self.updateTranslationEngineAndEngineList() self.updateTranslationEngineAndEngineList()
return {"status":200, "result":config.SELECTED_TARGET_LANGUAGES} return {"status":200, "result":config.SELECTED_TARGET_LANGUAGES}
@staticmethod
def getTranscriptionEngines(*args, **kwargs) -> dict:
engines = [key for key, value in config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS.items() if value is True]
return {"status":200, "result":engines}
@staticmethod @staticmethod
def getSelectedTranscriptionEngine(*args, **kwargs) -> dict: def getSelectedTranscriptionEngine(*args, **kwargs) -> dict:
return {"status":200, "result":config.SELECTED_TRANSCRIPTION_ENGINE} return {"status":200, "result":config.SELECTED_TRANSCRIPTION_ENGINE}
@@ -996,6 +1052,15 @@ class Controller:
response = {"status":200, "result":config.SPEAKER_MAX_PHRASES} response = {"status":200, "result":config.SPEAKER_MAX_PHRASES}
return response return response
@staticmethod
def getHotkeys(*args, **kwargs) -> dict:
return {"status":200, "result":config.HOTKEYS}
@staticmethod
def setHotkeys(data, *args, **kwargs) -> dict:
config.HOTKEYS = data
return {"status":200, "result":config.HOTKEYS}
@staticmethod @staticmethod
def getSpeakerAvgLogprob(*args, **kwargs) -> dict: def getSpeakerAvgLogprob(*args, **kwargs) -> dict:
return {"status":200, "result":config.SPEAKER_AVG_LOGPROB} return {"status":200, "result":config.SPEAKER_AVG_LOGPROB}
@@ -1411,6 +1476,7 @@ class Controller:
) )
else: else:
model.downloadCTranslate2ModelWeight(weight_type, download_ctranslate2.progressBar, download_ctranslate2.downloaded) model.downloadCTranslate2ModelWeight(weight_type, download_ctranslate2.progressBar, download_ctranslate2.downloaded)
model.downloadCTranslate2ModelTokenizer(weight_type)
return {"status":200, "result":True} return {"status":200, "result":True}
def downloadWhisperWeight(self, data:str, asynchronous:bool=True, *args, **kwargs) -> dict: def downloadWhisperWeight(self, data:str, asynchronous:bool=True, *args, **kwargs) -> dict:
@@ -1572,10 +1638,22 @@ class Controller:
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict
def updateTranscriptionEngine(self): def updateTranscriptionEngine(self):
weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT
weight_type = config.WHISPER_WEIGHT_TYPE weight_type = config.WHISPER_WEIGHT_TYPE
if config.SELECTED_TRANSCRIPTION_ENGINE == "Whisper" and weight_type_dict[weight_type] is False: weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT
config.SELECTED_TRANSCRIPTION_ENGINE = "Google" weight_available = bool(weight_type_dict.get(weight_type))
current_engine = config.SELECTED_TRANSCRIPTION_ENGINE
selected_engines = [key for key, value in config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS.items() if value is True]
# 選択可能なエンジンがなければ、Whisper に変更
if current_engine in {"Whisper", "Google"}:
if current_engine not in selected_engines:
if weight_available:
alternate = "Google" if current_engine == "Whisper" else "Whisper"
config.SELECTED_TRANSCRIPTION_ENGINE = alternate if alternate in selected_engines else None
else:
config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper"
else:
config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper"
def startCheckMicEnergy(self) -> None: def startCheckMicEnergy(self) -> None:
while self.device_access_status is False: while self.device_access_status is False:
@@ -1654,12 +1732,55 @@ class Controller:
self.run(200, self.run_mapping["initialization_progress"], progress) self.run(200, self.run_mapping["initialization_progress"], progress)
def init(self, *args, **kwargs) -> None: def init(self, *args, **kwargs) -> None:
printLog("Start Initialization")
removeLog() removeLog()
printLog("Start Initialization")
connected_network = isConnectedNetwork()
if connected_network is True:
self.connectedNetwork()
else:
self.disconnectedNetwork()
printLog(f"Connected Network: {connected_network}")
self.initializationProgress(1)
if connected_network is True:
# 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()
if (model.checkTranslatorCTranslate2ModelWeight(config.CTRANSLATE2_WEIGHT_TYPE) is False or
model.checkTranscriptionWhisperModelWeight(config.WHISPER_WEIGHT_TYPE) is False):
self.disableAiModels()
else:
self.enableAiModels()
printLog("Init Translation Engine Status") printLog("Init Translation Engine Status")
for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST: for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST:
match engine: match engine:
case "CTranslate2":
if model.checkTranslatorCTranslate2ModelWeight(config.CTRANSLATE2_WEIGHT_TYPE) is True:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
else:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
case "DeepL_API": case "DeepL_API":
printLog("Start check DeepL API Key") printLog("Start check DeepL API Key")
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
@@ -1672,33 +1793,23 @@ class Controller:
auth_keys[engine] = None auth_keys[engine] = None
config.AUTH_KEYS = auth_keys config.AUTH_KEYS = auth_keys
case _: case _:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True if connected_network is True:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
self.initializationProgress(1) else:
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
# 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()
for engine in config.SELECTABLE_TRANSCRIPTION_ENGINE_LIST:
match engine:
case "Whisper":
if model.checkTranscriptionWhisperModelWeight(config.WHISPER_WEIGHT_TYPE) is True:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = True
else:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
case _:
if connected_network is True:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = True
else:
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
self.initializationProgress(2) self.initializationProgress(2)
# set Translation Engine # set Translation Engine

View File

@@ -7,9 +7,10 @@ from queue import Queue
from controller import Controller from controller import Controller
from utils import printLog, printResponse, errorLogging, encodeBase64 from utils import printLog, printResponse, errorLogging, encodeBase64
controller = Controller()
run_mapping = { run_mapping = {
"connected_network":"/run/connected_network",
"enable_ai_models":"/run/enable_ai_models",
"transcription_mic":"/run/transcription_send_mic_message", "transcription_mic":"/run/transcription_send_mic_message",
"transcription_speaker":"/run/transcription_receive_speaker_message", "transcription_speaker":"/run/transcription_receive_speaker_message",
@@ -22,8 +23,10 @@ run_mapping = {
"download_progress_ctranslate2_weight":"/run/download_progress_ctranslate2_weight", "download_progress_ctranslate2_weight":"/run/download_progress_ctranslate2_weight",
"downloaded_ctranslate2_weight":"/run/downloaded_ctranslate2_weight", "downloaded_ctranslate2_weight":"/run/downloaded_ctranslate2_weight",
"error_ctranslate2_weight":"/run/error_ctranslate2_weight",
"download_progress_whisper_weight":"/run/download_progress_whisper_weight", "download_progress_whisper_weight":"/run/download_progress_whisper_weight",
"downloaded_whisper_weight":"/run/downloaded_whisper_weight", "downloaded_whisper_weight":"/run/downloaded_whisper_weight",
"error_whisper_weight":"/run/error_whisper_weight",
"selected_mic_device":"/run/selected_mic_device", "selected_mic_device":"/run/selected_mic_device",
"selected_speaker_device":"/run/selected_speaker_device", "selected_speaker_device":"/run/selected_speaker_device",
@@ -41,11 +44,11 @@ run_mapping = {
"initialization_complete":"/run/initialization_complete", "initialization_complete":"/run/initialization_complete",
} }
controller.setRunMapping(run_mapping)
def run(status:int, endpoint:str, result:Any) -> None: def run(status:int, endpoint:str, result:Any) -> None:
printResponse(status, endpoint, result) printResponse(status, endpoint, result)
controller = Controller()
controller.setRunMapping(run_mapping)
controller.setRun(run) controller.setRun(run)
mapping = { mapping = {
@@ -81,6 +84,7 @@ mapping = {
"/get/data/selected_target_languages": {"status": True, "variable":controller.getSelectedTargetLanguages}, "/get/data/selected_target_languages": {"status": True, "variable":controller.getSelectedTargetLanguages},
"/set/data/selected_target_languages": {"status": True, "variable":controller.setSelectedTargetLanguages}, "/set/data/selected_target_languages": {"status": True, "variable":controller.setSelectedTargetLanguages},
"/get/data/transcription_engines": {"status": False, "variable":controller.getTranscriptionEngines},
"/get/data/selected_transcription_engine": {"status": False, "variable":controller.getSelectedTranscriptionEngine}, "/get/data/selected_transcription_engine": {"status": False, "variable":controller.getSelectedTranscriptionEngine},
"/set/data/selected_transcription_engine": {"status": False, "variable":controller.setSelectedTranscriptionEngine}, "/set/data/selected_transcription_engine": {"status": False, "variable":controller.setSelectedTranscriptionEngine},
@@ -183,6 +187,9 @@ mapping = {
"/get/data/mic_max_phrases": {"status": True, "variable":controller.getMicMaxPhrases}, "/get/data/mic_max_phrases": {"status": True, "variable":controller.getMicMaxPhrases},
"/set/data/mic_max_phrases": {"status": True, "variable":controller.setMicMaxPhrases}, "/set/data/mic_max_phrases": {"status": True, "variable":controller.setMicMaxPhrases},
"/get/data/hotkeys": {"status": True, "variable":controller.getHotkeys},
"/set/data/hotkeys": {"status": True, "variable":controller.setHotkeys},
"/get/data/mic_avg_logprob": {"status": True, "variable":controller.getMicAvgLogprob}, "/get/data/mic_avg_logprob": {"status": True, "variable":controller.getMicAvgLogprob},
"/set/data/mic_avg_logprob": {"status": True, "variable":controller.setMicAvgLogprob}, "/set/data/mic_avg_logprob": {"status": True, "variable":controller.setMicAvgLogprob},

View File

@@ -24,7 +24,7 @@ from models.transcription.transcription_recorder import SelectedMicEnergyRecorde
from models.transcription.transcription_transcriber import AudioTranscriber from models.transcription.transcription_transcriber import AudioTranscriber
from models.translation.translation_languages import translation_lang from models.translation.translation_languages import translation_lang
from models.transcription.transcription_languages import transcription_lang from models.transcription.transcription_languages import transcription_lang
from models.translation.translation_utils import checkCTranslate2Weight, downloadCTranslate2Weight from models.translation.translation_utils import checkCTranslate2Weight, downloadCTranslate2Weight, downloadCTranslate2Tokenizer
from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight
from models.overlay.overlay import Overlay from models.overlay.overlay import Overlay
from models.overlay.overlay_image import OverlayImage from models.overlay.overlay_image import OverlayImage
@@ -72,12 +72,15 @@ class Model:
self.th_check_device = None self.th_check_device = None
self.mic_print_transcript = None self.mic_print_transcript = None
self.mic_audio_recorder = None self.mic_audio_recorder = None
self.mic_transcriber = None
self.mic_energy_recorder = None self.mic_energy_recorder = None
self.mic_energy_plot_progressbar = None self.mic_energy_plot_progressbar = None
self.speaker_print_transcript = None self.speaker_print_transcript = None
self.speaker_audio_recorder = None self.speaker_audio_recorder = None
self.speaker_transcriber = None
self.speaker_energy_recorder = None self.speaker_energy_recorder = None
self.speaker_energy_plot_progressbar = None self.speaker_energy_plot_progressbar = None
self.previous_send_message = "" self.previous_send_message = ""
self.previous_receive_message = "" self.previous_receive_message = ""
self.translator = Translator() self.translator = Translator()
@@ -110,6 +113,9 @@ class Model:
def downloadCTranslate2ModelWeight(self, weight_type, callback=None, end_callback=None): def downloadCTranslate2ModelWeight(self, weight_type, callback=None, end_callback=None):
return downloadCTranslate2Weight(config.PATH_LOCAL, weight_type, callback, end_callback) return downloadCTranslate2Weight(config.PATH_LOCAL, weight_type, callback, end_callback)
def downloadCTranslate2ModelTokenizer(self, weight_type):
return downloadCTranslate2Tokenizer(config.PATH_LOCAL, weight_type)
def isLoadedCTranslate2Model(self): def isLoadedCTranslate2Model(self):
return self.translator.isLoadedCTranslate2Model() return self.translator.isLoadedCTranslate2Model()
@@ -426,13 +432,14 @@ class Model:
selected_your_languages = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO] 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] 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] countries = [data["country"] for data in selected_your_languages.values() if data["enable"] is True]
res = self.mic_transcriber.transcribeAudioQueue( if isinstance(self.mic_transcriber, AudioTranscriber) is True:
self.mic_audio_queue, res = self.mic_transcriber.transcribeAudioQueue(
languages, self.mic_audio_queue,
countries, languages,
config.MIC_AVG_LOGPROB, countries,
config.MIC_NO_SPEECH_PROB config.MIC_AVG_LOGPROB,
) config.MIC_NO_SPEECH_PROB
)
if res: if res:
result = self.mic_transcriber.getTranscript() result = self.mic_transcriber.getTranscript()
fnc(result) fnc(result)
@@ -444,7 +451,7 @@ class Model:
self.mic_audio_queue.get() self.mic_audio_queue.get()
# while not self.mic_energy_queue.empty(): # while not self.mic_energy_queue.empty():
# self.mic_energy_queue.get() # self.mic_energy_queue.get()
del self.mic_transcriber self.mic_transcriber = None
gc.collect() gc.collect()
# def sendMicEnergy(): # def sendMicEnergy():
@@ -593,13 +600,14 @@ class Model:
selected_target_languages = config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO] 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] 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] countries = [data["country"] for data in selected_target_languages.values() if data["enable"] is True]
res = self.speaker_transcriber.transcribeAudioQueue( if isinstance(self.speaker_transcriber, AudioTranscriber) is True:
speaker_audio_queue, res = self.speaker_transcriber.transcribeAudioQueue(
languages, speaker_audio_queue,
countries, languages,
config.SPEAKER_AVG_LOGPROB, countries,
config.SPEAKER_NO_SPEECH_PROB config.SPEAKER_AVG_LOGPROB,
) config.SPEAKER_NO_SPEECH_PROB
)
if res: if res:
result = self.speaker_transcriber.getTranscript() result = self.speaker_transcriber.getTranscript()
fnc(result) fnc(result)
@@ -611,7 +619,7 @@ class Model:
speaker_audio_queue.get() speaker_audio_queue.get()
# while not speaker_energy_queue.empty(): # while not speaker_energy_queue.empty():
# speaker_energy_queue.get() # speaker_energy_queue.get()
del self.speaker_transcriber self.speaker_transcriber = None
gc.collect() gc.collect()
# def sendSpeakerEnergy(): # def sendSpeakerEnergy():

View File

@@ -68,7 +68,7 @@ class OverlayImage:
text_width = draw.textlength(text, font) text_width = draw.textlength(text, font)
character_width = text_width // len(text) character_width = text_width // len(text)
character_line_num = (base_width // character_width) - 12 character_line_num = int((base_width // character_width) - 12)
if len(text) > character_line_num: 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 = "\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 text_height = font_size * (len(text.split("\n")) + 1) + 20

View File

@@ -4,6 +4,7 @@ from typing import Callable
import huggingface_hub import huggingface_hub
from faster_whisper import WhisperModel from faster_whisper import WhisperModel
import logging import logging
from utils import getBestComputeType
logger = logging.getLogger('faster_whisper') logger = logging.getLogger('faster_whisper')
logger.setLevel(logging.CRITICAL) logger.setLevel(logging.CRITICAL)
@@ -73,7 +74,7 @@ def downloadWhisperWeight(root, weight_type, callback=None, end_callback=None):
def getWhisperModel(root, weight_type, device="cpu", device_index=0): def getWhisperModel(root, weight_type, device="cpu", device_index=0):
path = os_path.join(root, "weights", "whisper", weight_type) path = os_path.join(root, "weights", "whisper", weight_type)
compute_type = "int8" if device == "cpu" else "float16" compute_type = getBestComputeType(device, device_index)
return WhisperModel( return WhisperModel(
path, path,
device=device, device=device,

View File

@@ -1,12 +1,17 @@
from os import path as os_path from os import path as os_path
from deepl import Translator as deepl_Translator from deepl import Translator as deepl_Translator
from translators import translate_text as other_web_Translator try:
from translators import translate_text as other_web_Translator
ENABLE_TRANSLATORS = True
except Exception:
ENABLE_TRANSLATORS = False
from .translation_languages import translation_lang from .translation_languages import translation_lang
from .translation_utils import ctranslate2_weights from .translation_utils import ctranslate2_weights
import ctranslate2 import ctranslate2
import transformers import transformers
from utils import errorLogging from utils import errorLogging, getBestComputeType
import warnings import warnings
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
@@ -18,6 +23,7 @@ class Translator():
self.ctranslate2_translator = None self.ctranslate2_translator = None
self.ctranslate2_tokenizer = None self.ctranslate2_tokenizer = None
self.is_loaded_ctranslate2_model = False self.is_loaded_ctranslate2_model = False
self.is_enable_translators = ENABLE_TRANSLATORS
def authenticationDeepLAuthKey(self, authkey): def authenticationDeepLAuthKey(self, authkey):
result = True result = True
@@ -37,7 +43,7 @@ class Translator():
weight_path = os_path.join(path, "weights", "ctranslate2", directory_name) weight_path = os_path.join(path, "weights", "ctranslate2", directory_name)
tokenizer_path = os_path.join(path, "weights", "ctranslate2", directory_name, "tokenizer") tokenizer_path = os_path.join(path, "weights", "ctranslate2", directory_name, "tokenizer")
compute_type = "int8" if device == "cpu" else "float16" compute_type = getBestComputeType(device, device_index)
self.ctranslate2_translator = ctranslate2.Translator( self.ctranslate2_translator = ctranslate2.Translator(
weight_path, weight_path,
device=device, device=device,
@@ -97,42 +103,47 @@ class Translator():
source_language, target_language = self.getLanguageCode(translator_name, target_country, source_language, target_language) source_language, target_language = self.getLanguageCode(translator_name, target_country, source_language, target_language)
match translator_name: match translator_name:
case "DeepL": case "DeepL":
result = other_web_Translator( if self.is_enable_translators is True:
query_text=message, result = other_web_Translator(
translator="deepl", query_text=message,
from_language=source_language, translator="deepl",
to_language=target_language, from_language=source_language,
) to_language=target_language,
)
case "DeepL_API": case "DeepL_API":
if self.deepl_client is None: if self.is_enable_translators is True:
result = False if self.deepl_client is None:
else: result = False
result = self.deepl_client.translate_text( else:
message, result = self.deepl_client.translate_text(
source_lang=source_language, message,
target_lang=target_language, source_lang=source_language,
).text target_lang=target_language,
).text
case "Google": case "Google":
result = other_web_Translator( if self.is_enable_translators is True:
query_text=message, result = other_web_Translator(
translator="google", query_text=message,
from_language=source_language, translator="google",
to_language=target_language, from_language=source_language,
) to_language=target_language,
)
case "Bing": case "Bing":
result = other_web_Translator( if self.is_enable_translators is True:
query_text=message, result = other_web_Translator(
translator="bing", query_text=message,
from_language=source_language, translator="bing",
to_language=target_language, from_language=source_language,
) to_language=target_language,
)
case "Papago": case "Papago":
result = other_web_Translator( if self.is_enable_translators is True:
query_text=message, result = other_web_Translator(
translator="papago", query_text=message,
from_language=source_language, translator="papago",
to_language=target_language, from_language=source_language,
) to_language=target_language,
)
case "CTranslate2": case "CTranslate2":
result = self.translateCTranslate2( result = self.translateCTranslate2(
message=message, message=message,

View File

@@ -5,6 +5,7 @@ from os import makedirs as os_makedirs
from requests import get as requests_get from requests import get as requests_get
from typing import Callable from typing import Callable
import hashlib import hashlib
import transformers
from utils import errorLogging from utils import errorLogging
ctranslate2_weights = { ctranslate2_weights = {
@@ -86,4 +87,17 @@ def downloadCTranslate2Weight(root, weight_type="small", callback=None, end_call
errorLogging() errorLogging()
if isinstance(end_callback, Callable): if isinstance(end_callback, Callable):
end_callback() end_callback()
def downloadCTranslate2Tokenizer(path, weight_type="small"):
directory_name = ctranslate2_weights[weight_type]["directory_name"]
tokenizer = ctranslate2_weights[weight_type]["tokenizer"]
tokenizer_path = os_path.join(path, "weights", "ctranslate2", directory_name, "tokenizer")
try:
os_makedirs(tokenizer_path, exist_ok=True)
transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path)
except Exception:
errorLogging()
tokenizer_path = os_path.join("./weights", "ctranslate2", directory_name, "tokenizer")
transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path)

View File

@@ -1,68 +1,28 @@
import base64 import base64
from typing import Any from typing import Any
import json import json
import random
from typing import Union
from os import path as os_path, rename as os_rename
import traceback import traceback
import logging import logging
from PIL.Image import open as Image_open from logging.handlers import RotatingFileHandler
def getImageFile(file_name): from ctranslate2 import get_supported_compute_types
img = Image_open(os_path.join(os_path.dirname(__file__), "img", file_name)) import requests
return img
def callFunctionIfCallable(function, *args): def isConnectedNetwork(url="http://www.google.com", timeout=3):
if callable(function) is True: try:
function(*args) response = requests.get(url, timeout=timeout)
return response.status_code == 200
except requests.RequestException:
return False
def isEven(number): def getBestComputeType(device, device_index) -> str:
return number % 2 == 0 compute_types = get_supported_compute_types(device, device_index)
compute_types = set(compute_types)
preferred_types = ["int8_bfloat16", "int8_float16", "int8", "bfloat16", "float16", "int8_float32", "float32"]
def makeEven(number, minus:bool=False): for preferred_type in preferred_types:
if minus is True: if preferred_type in compute_types:
return number if isEven(number) else number - 1 return preferred_type
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: def encodeBase64(data:str) -> dict:
return json.loads(base64.b64decode(data).decode('utf-8')) return json.loads(base64.b64decode(data).decode('utf-8'))
@@ -80,8 +40,17 @@ def setupLogger(name, log_file, level=logging.INFO):
logger.setLevel(level) logger.setLevel(level)
logger.propagate = False # 親ロガーへの伝播を防ぐ logger.propagate = False # 親ロガーへの伝播を防ぐ
# filled with 10MB logs
max_log_size = 10 * 1024 * 1024 # 10MB
# ハンドラーを作成 # ハンドラーを作成
file_handler = logging.FileHandler(log_file, encoding="utf-8", delay=True) file_handler = RotatingFileHandler(
log_file,
maxBytes=max_log_size,
backupCount=1,
encoding="utf-8",
delay=True
)
file_handler.setLevel(level) file_handler.setLevel(level)
# フォーマッターを設定 # フォーマッターを設定

1070
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,12 +11,13 @@ edition = "2021"
tauri-build = { version = "1", features = [] } tauri-build = { version = "1", features = [] }
[dependencies] [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"] } tauri = { version = "1", features = [ "window-hide", "window-set-focus", "global-shortcut-all", "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 = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
font-kit = "0.14.2" font-kit = "0.14.2"
window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" } window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" }
[features] [features]
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"] custom-protocol = ["tauri/custom-protocol"]

View File

@@ -1,9 +1,9 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// use tauri::command;
use tauri::Manager; use tauri::Manager;
use window_shadows::set_shadow; use window_shadows::set_shadow;
fn main() { fn main() {
tauri::Builder::default() tauri::Builder::default()
.setup(|app| { .setup(|app| {
@@ -17,6 +17,11 @@ fn main() {
Ok(()) Ok(())
}) })
.on_window_event(|event| { // This is for fix the bug that the window scaling issue when dragging between monitors.
if let tauri::WindowEvent::ScaleFactorChanged { new_inner_size, .. } = event.event() {
event.window().set_size(tauri::Size::Physical(*new_inner_size)).unwrap();
}
})
.invoke_handler(tauri::generate_handler![get_font_list]) .invoke_handler(tauri::generate_handler![get_font_list])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
@@ -40,4 +45,4 @@ async fn get_font_list() -> Vec<String> {
} }
font_families.into_iter().collect() font_families.into_iter().collect()
} }

View File

@@ -15,8 +15,10 @@
"window": { "window": {
"all": false, "all": false,
"setAlwaysOnTop": true, "setAlwaysOnTop": true,
"setFocus": true,
"setDecorations": true, "setDecorations": true,
"close": true, "close": true,
"hide": true,
"setPosition": true, "setPosition": true,
"setSize": true, "setSize": true,
"maximize": true, "maximize": true,
@@ -24,7 +26,10 @@
"unmaximize": true, "unmaximize": true,
"unminimize": true, "unminimize": true,
"startDragging": true "startDragging": true
}, },
"globalShortcut": {
"all": true
},
"shell": { "shell": {
"all": false, "all": false,
"open": true, "open": true,

View File

@@ -1,14 +1,9 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import {
useWindow,
} from "@logics_common";
// import React from "react";
import { import {
KeyEventController, KeyEventController,
StartPythonController, StartPythonController,
GlobalHotKeyController,
UiLanguageController, UiLanguageController,
ConfigPageCloseTriggerController, ConfigPageCloseTriggerController,
UiSizeController, UiSizeController,
@@ -24,9 +19,10 @@ import { UpdatingComponent } from "./updating_component/UpdatingComponent";
import { ModalController } from "./modal_controller/ModalController"; import { ModalController } from "./modal_controller/ModalController";
import { SnackbarController } from "./snackbar_controller/SnackbarController"; import { SnackbarController } from "./snackbar_controller/SnackbarController";
import styles from "./App.module.scss"; import styles from "./App.module.scss";
import { useIsBackendReady, useIsSoftwareUpdating } from "@logics_common"; import { useIsBackendReady, useIsSoftwareUpdating, useIsVrctAvailable, useWindow } from "@logics_common";
export const App = () => { export const App = () => {
const { currentIsVrctAvailable } = useIsVrctAvailable();
const { currentIsBackendReady } = useIsBackendReady(); const { currentIsBackendReady } = useIsBackendReady();
const { WindowGeometryController } = useWindow(); const { WindowGeometryController } = useWindow();
const { i18n } = useTranslation(); const { i18n } = useTranslation();
@@ -35,6 +31,7 @@ export const App = () => {
<div className={styles.container}> <div className={styles.container}>
<KeyEventController /> <KeyEventController />
<StartPythonController /> <StartPythonController />
<GlobalHotKeyController />
<UiLanguageController /> <UiLanguageController />
<ConfigPageCloseTriggerController /> <ConfigPageCloseTriggerController />
<UiSizeController /> <UiSizeController />
@@ -42,10 +39,12 @@ export const App = () => {
<TransparencyController /> <TransparencyController />
<WindowGeometryController /> <WindowGeometryController />
{currentIsBackendReady.data === false {(currentIsBackendReady.data === false || currentIsVrctAvailable.data === false)
? <SplashComponent /> ? <SplashComponent />
: <Contents key={i18n.language}/> : <Contents key={i18n.language}/>
} }
<SnackbarController />
</div> </div>
); );
}; };
@@ -61,7 +60,6 @@ const Contents = () => {
<ConfigPage /> <ConfigPage />
<MainPage /> <MainPage />
<ModalController /> <ModalController />
<SnackbarController />
</div> </div>
: :
<UpdatingComponent /> <UpdatingComponent />

View File

@@ -9,6 +9,8 @@ import {
useMainFunction, useMainFunction,
} from "@logics_main"; } from "@logics_main";
import { useHotkeys } from "@logics_configs";
import { useStore_MainFunctionsStateMemory } from "@store"; import { useStore_MainFunctionsStateMemory } from "@store";
export const ConfigPageCloseTriggerController = () => { export const ConfigPageCloseTriggerController = () => {
@@ -27,6 +29,8 @@ export const ConfigPageCloseTriggerController = () => {
volumeCheckStop_Speaker, volumeCheckStop_Speaker,
} = useVolume(); } = useVolume();
const { registerShortcuts, unregisterAll } = useHotkeys();
const memorizeLatestMainFunctionsState = () => { const memorizeLatestMainFunctionsState = () => {
updateMainFunctionsStateMemory({ updateMainFunctionsStateMemory({
@@ -43,9 +47,11 @@ export const ConfigPageCloseTriggerController = () => {
useEffect(() => { useEffect(() => {
if (currentIsOpenedConfigPage.data === true) { // When config page is opened. if (currentIsOpenedConfigPage.data === true) { // When config page is opened.
memorizeLatestMainFunctionsState(); memorizeLatestMainFunctionsState();
unregisterAll();
if (currentTranscriptionSendStatus.data === true) setTranscriptionSend(false); if (currentTranscriptionSendStatus.data === true) setTranscriptionSend(false);
if (currentTranscriptionReceiveStatus.data === true) setTranscriptionReceive(false); if (currentTranscriptionReceiveStatus.data === true) setTranscriptionReceive(false);
} else if (currentIsOpenedConfigPage.data === false) { // When config page is closed. } else if (currentIsOpenedConfigPage.data === false) { // When config page is closed.
registerShortcuts();
if (currentMicThresholdCheckStatus.data === true) volumeCheckStop_Mic(); if (currentMicThresholdCheckStatus.data === true) volumeCheckStop_Mic();
if (currentSpeakerThresholdCheckStatus.data === true) volumeCheckStop_Speaker(); if (currentSpeakerThresholdCheckStatus.data === true) volumeCheckStop_Speaker();
restoreMainFunctionState(); restoreMainFunctionState();

View File

@@ -0,0 +1,25 @@
import { useEffect } from "react";
import { useHotkeys } from "@logics_configs";
import { useIsBackendReady, useIsSoftwareUpdating, useIsVrctAvailable } from "@logics_common";
export const GlobalHotKeyController = () => {
const { currentIsBackendReady } = useIsBackendReady();
const { currentIsSoftwareUpdating } = useIsSoftwareUpdating();
const { registerShortcuts, unregisterAll } = useHotkeys();
const { currentIsVrctAvailable } = useIsVrctAvailable();
useEffect(() => {
const is_backend_ready = currentIsBackendReady.data;
const is_software_updating = currentIsSoftwareUpdating.data;
const is_vrct_available = currentIsVrctAvailable.data;
if (is_vrct_available && is_backend_ready && !is_software_updating) {
registerShortcuts();
} else {
unregisterAll();
}
}, [currentIsBackendReady.data, currentIsSoftwareUpdating.data, currentIsVrctAvailable.data]);
return null;
};

View File

@@ -2,24 +2,29 @@ import { useEffect } from "react";
export const KeyEventController = () => { export const KeyEventController = () => {
useEffect(() => { useEffect(() => {
const handleKeydown = (event) => { const handleKeydown = (event) => {
if (event.key === "F5" || (event.ctrlKey && event.key === "r") || if (
(event.metaKey && event.key === "r")) { event.key === "F5" || // Page reload
event.key === "F10" || // Focus thw window menu (maybe)
event.key === "F12" || // Open dev tool
(event.ctrlKey && event.key === "r") ||
(event.metaKey && event.key === "r")
) {
event.preventDefault();
}
};
const handleContextmenu = (event) => {
event.preventDefault(); event.preventDefault();
} };
};
const handleContextmenu = (event) => { document.addEventListener("keydown", handleKeydown);
event.preventDefault(); document.addEventListener("contextmenu", handleContextmenu);
};
document.addEventListener("keydown", handleKeydown); return () => {
document.addEventListener("contextmenu", handleContextmenu); document.removeEventListener("keydown", handleKeydown);
document.removeEventListener("contextmenu", handleContextmenu);
return () => { };
document.removeEventListener("keydown", handleKeydown);
document.removeEventListener("contextmenu", handleContextmenu);
};
}, []); }, []);
return null; return null;

View File

@@ -1,5 +1,6 @@
export { KeyEventController } from "./KeyEventController"; export { KeyEventController } from "./KeyEventController";
export { StartPythonController } from "./StartPythonController"; export { StartPythonController } from "./StartPythonController";
export { GlobalHotKeyController } from "./GlobalHotKeyController";
export { UiLanguageController } from "./UiLanguageController"; export { UiLanguageController } from "./UiLanguageController";
export { ConfigPageCloseTriggerController } from "./ConfigPageCloseTriggerController"; export { ConfigPageCloseTriggerController } from "./ConfigPageCloseTriggerController";
export { UiSizeController } from "./UiSizeController"; export { UiSizeController } from "./UiSizeController";

View File

@@ -3,7 +3,7 @@
:root { :root {
font-size: 62.5%; font-size: 62.5%;
color: #F2F2F2; color: var(--dark_basic_text_color);
} }
* { * {

View File

@@ -20,6 +20,10 @@
--sent_400_color: #6197b4; --sent_400_color: #6197b4;
--received_300_color: #a861b4; --received_300_color: #a861b4;
--error_bc_color: #bb4448;
--error_bc_active_color: #9c3938;
--success_bc_color: #368777;
--waring_color: #cb944f;
--dark_basic_text_color: #f2f2f2; --dark_basic_text_color: #f2f2f2;
--dark_100_color: #f5f7fb; --dark_100_color: #f5f7fb;
@@ -48,8 +52,11 @@
--dark_975_color: #1a1b1d; --dark_975_color: #1a1b1d;
--dark_1000_color: #151517; --dark_1000_color: #151517;
--dark_825_color_cc: #434447cc;
--dark_550_color_22: #94959922; --dark_550_color_22: #94959922;
--dark_825_color_cc: #434447cc;
--dark_1000_color_66: #15151766;
--dark_1000_color_aa: #151517aa;
--dark_1000_color_dd: #151517dd;
--title_bar_height: 2rem; --title_bar_height: 2rem;

View File

@@ -3,22 +3,9 @@ import styles from "./ConfigPage.module.scss";
import { Topbar } from "./topbar/Topbar.jsx"; import { Topbar } from "./topbar/Topbar.jsx";
import { SidebarSection } from "./sidebar_section/SidebarSection.jsx"; import { SidebarSection } from "./sidebar_section/SidebarSection.jsx";
import { SettingSection } from "./setting_section/SettingSection.jsx"; import { SettingSection } from "./setting_section/SettingSection.jsx";
import { VersionLabel } from "./version_label/VersionLabel.jsx";
import { useSoftwareVersion } from "@logics_configs";
import { useComputeMode } from "@logics_common";
import { useTranslation } from "react-i18next";
export const ConfigPage = () => { 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 ( return (
<div className={styles.page}> <div className={styles.page}>
<div className={styles.container}> <div className={styles.container}>
@@ -27,7 +14,7 @@ export const ConfigPage = () => {
<SidebarSection /> <SidebarSection />
<SettingSection /> <SettingSection />
</div> </div>
<p className={styles.software_version}>{version_label}</p> <VersionLabel />
</div> </div>
</div> </div>
); );

View File

@@ -23,12 +23,4 @@
height: 100%; height: 100%;
display: flex; display: flex;
padding-top: var(--config_page_topbar_height); 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);
} }

View File

@@ -1,6 +1,6 @@
.scroll_container { .scroll_container {
width: 100%; width: 100%;
overflow-y: auto; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
} }

View File

@@ -8,6 +8,7 @@ import {
Others, Others,
AdvancedSettings, AdvancedSettings,
Vr, Vr,
Hotkeys,
Supporters, Supporters,
AboutVrct, AboutVrct,
} from "@setting_box"; } from "@setting_box";
@@ -27,6 +28,8 @@ export const SettingBox = () => {
return <Others />; return <Others />;
case "vr": case "vr":
return <Vr />; return <Vr />;
case "hotkeys":
return <Hotkeys />;
case "advanced_settings": case "advanced_settings":
return <AdvancedSettings />; return <AdvancedSettings />;
case "supporters": case "supporters":

View File

@@ -8,23 +8,36 @@ const _Entry = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
focus: () => { focus: () => {
inputRef.current.focus(); inputRef.current.focus();
},
blur: () => {
inputRef.current.blur();
} }
})); }));
const input_class_names = clsx(styles.entry_input_area, { const input_class_names = clsx(styles.entry_input_area, {
[styles.is_disabled]: props.is_disabled [styles.is_disabled]: props.is_disabled,
});
const input_wrapper_class_names = clsx(styles.entry_wrapper, {
[styles.is_activated]: props.is_activated,
}); });
return ( return (
<div className={styles.entry_container}> <div className={styles.entry_container}>
<div <div
className={styles.entry_wrapper} className={input_wrapper_class_names}
style={{width: props.width }} style={{width: props.width }}
> >
<input <input
ref={inputRef} ref={inputRef}
text={props.text ? props.text : "text"}
placeholder={props.placeholder ? props.placeholder : ""}
className={input_class_names} className={input_class_names}
value={props.ui_variable === null ? "" : props.ui_variable} value={props.ui_variable === null ? "" : props.ui_variable}
onChange={(e) => props.onChange(e)} onChange={(e) => props.onChange?.(e)}
onFocus={(e) => props.onFocus?.(e)}
onBlur={(e) => props.onBlur?.(e)}
onKeyDown={(e) => props.onKeyDown?.(e)}
onKeyUp={(e) => props.onKeyUp?.(e)}
readOnly={props.readOnly === true ? true : false}
/> />
</div> </div>
</div> </div>

View File

@@ -9,6 +9,9 @@
background-color: var(--dark_875_color); background-color: var(--dark_875_color);
border: 0.1rem solid var(--dark_750_color); border: 0.1rem solid var(--dark_750_color);
border-radius: 0.4rem; border-radius: 0.4rem;
&.is_activated {
border: 0.1rem solid var(--primary_400_color);
}
} }
.entry_input_area { .entry_input_area {
@@ -16,7 +19,6 @@
height: 100%; height: 100%;
font-size: 1.4rem; font-size: 1.4rem;
resize: none; resize: none;
color: var(--dark_basic_text_color);
&.is_disabled { &.is_disabled {
color: var(--dark_500_color); color: var(--dark_500_color);
pointer-events: none; pointer-events: none;

View File

@@ -22,11 +22,11 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
border-radius: 0.4rem; border-radius: 0.4rem;
background-color: (#00000044); background-color: var(--dark_1000_color_66);
backdrop-filter: blur(4rem); backdrop-filter: blur(4rem);
border: solid 0.1rem var(--dark_700_color); border: solid 0.1rem var(--dark_700_color);
&:hover { &:hover {
background-color: (#00000088); background-color: var(--dark_1000_color_aa);
} }
&:active { &:active {
backdrop-filter: blur(1.4rem); backdrop-filter: blur(1.4rem);
@@ -35,7 +35,6 @@
.edit_button { .edit_button {
padding: 0.8rem 1.2rem; padding: 0.8rem 1.2rem;
color: var(--dark_basic_text_color);
height: 100%; height: 100%;
width: 100%; width: 100%;
font-size: 1.4rem; font-size: 1.4rem;
@@ -62,7 +61,6 @@
} }
.save_button_label { .save_button_label {
color: var(--dark_basic_text_color);
font-size: 1.4rem; font-size: 1.4rem;
} }
@@ -91,7 +89,6 @@
.open_webpage_text { .open_webpage_text {
font-size: 1.2rem; font-size: 1.2rem;
color: var(--dark_basic_text_color);
} }
.external_link_svg { .external_link_svg {

View File

@@ -1,12 +1,10 @@
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import clsx from "clsx";
import CircularProgress from "@mui/material/CircularProgress"; import CircularProgress from "@mui/material/CircularProgress";
import styles from "./DownloadModels.module.scss"; import styles from "./DownloadModels.module.scss";
import { import {
RadioButton, RadioButton,
// DownloadModels,
} from "../index"; } from "../index";
export const DownloadModels = (props) => { export const DownloadModels = (props) => {
const options = props.options.map(item => ({ const options = props.options.map(item => ({
...item, ...item,
@@ -25,25 +23,11 @@ export const DownloadModels = (props) => {
downloadStartFunction={props.downloadStartFunction} downloadStartFunction={props.downloadStartFunction}
/> />
</> </>
// <div className={styles.container}>
// {props.models.map((option) => (
// <ModelSelector key={option.model_id} option={option} {...props}/>
// ))}
// </div>
); );
}; };
const ModelSelector = ({option, ...props}) => { const ModelSelector = ({option, ...props}) => {
const { t } = useTranslation(); 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 renderContent = () => {
const circular_progress = Math.floor(option.progress / 10) * 10; const circular_progress = Math.floor(option.progress / 10) * 10;
@@ -56,13 +40,13 @@ const ModelSelector = ({option, ...props}) => {
variant={(option.progress === 100) ? "indeterminate" : "determinate"} variant={(option.progress === 100) ? "indeterminate" : "determinate"}
value={circular_progress} value={circular_progress}
size="3rem" size="3rem"
sx={{ color: circular_color_2 }} sx={{ color: "var(--primary_300_color)" }}
/> />
<p className={styles.progress_label}>{`${Math.round(option.progress)}%`}</p> <p className={styles.progress_label}>{`${Math.round(option.progress)}%`}</p>
</> </>
); );
case option.is_pending: case option.is_pending:
return <CircularProgress size="3rem" sx={{ color: circular_color }}/>; return <CircularProgress size="3rem" sx={{ color: "var(--dark_600_color)" }}/>;
case !option.is_downloaded: case !option.is_downloaded:
return ( return (
<button <button

View File

@@ -22,11 +22,9 @@
} }
.download_button_label { .download_button_label {
font-size: 1.2rem; font-size: 1.2rem;
color: var(--dark_basic_text_color);
} }
.progress_label { .progress_label {
position: absolute; position: absolute;
font-size: 1rem; font-size: 1rem;
color: var(--dark_basic_text_color);
} }

View File

@@ -0,0 +1,122 @@
import styles from "./HotkeysEntry.module.scss";
import { _Entry } from "../_atoms/_entry/_Entry";
import { useState, useRef, useEffect } from "react";
import DeleteSvg from "@images/cancel.svg?react";
import { clsx } from "clsx";
export const HotkeysEntry = (props) => {
const [isAcceptingInput, setIsAcceptingInput] = useState(false);
const [displayValue, setDisplayValue] = useState("");
const lastKeyRef = useRef(null);
const isModifierOnlyRef = useRef(false);
const entryRef = useRef(null);
const pressedKeys = useRef(new Set());
const keysRef = useRef([]);
useEffect(() => {
const init_display_value = props.value[props.hotkey_id] ? props.value[props.hotkey_id].join(" + ") : "";
setDisplayValue(init_display_value);
}, []);
const updateHotkeys = (keys) => {
entryRef.current.blur();
const result = props.setHotkeys({ [props.hotkey_id]: keys });
if (result === false) setDisplayValue("");
};
const processKey = (key) => {
if (/^[a-zA-Z]$/.test(key)) return key.toUpperCase();
if (key === "Meta") return "Super";
return key;
};
const handleKeyInput = (event) => {
const keys = [];
const nonModifierKeys = [];
["Ctrl", "Shift", "Alt", "Meta"].forEach((modKey) => {
if (event[`${modKey.toLowerCase()}Key`] && !keys.includes(modKey)) {
let register_mod_key = (modKey === "Meta") ? "Super" : modKey;
keys.push(register_mod_key);
}
});
const key = processKey(event.key);
if (!["Control", "Shift", "Alt", "Meta"].includes(event.key)) {
keys.push(key);
nonModifierKeys.push(key);
}
if (!pressedKeys.current.has(key)) {
pressedKeys.current.add(key);
}
keysRef.current = keys;
setDisplayValue(keys.join(" + "));
isModifierOnlyRef.current = nonModifierKeys.length === 0;
};
const handleKeyDown = (event) => {
event.preventDefault();
if (lastKeyRef.current === event.key) return;
lastKeyRef.current = event.key;
handleKeyInput(event);
};
const handleKeyUp = (event) => {
lastKeyRef.current = null;
const key = processKey(event.key);
pressedKeys.current.delete(key);
if (isModifierOnlyRef.current) {
setDisplayValue("");
}
if (pressedKeys.current.size === 0) {
const hasNonModifierKeys = keysRef.current.some(
(key) => !["Ctrl", "Shift", "Alt", "Super"].includes(key)
);
if (hasNonModifierKeys) {
updateHotkeys(keysRef.current);
} else {
const display_value = props.value[props.hotkey_id] ? props.value[props.hotkey_id].join(" + ") : "";
setDisplayValue(display_value);
}
}
};
const handleBlur = () => {
setIsAcceptingInput(false);
pressedKeys.current.clear();
};
const handleDelete = () => {
updateHotkeys(null);
setDisplayValue("");
};
const is_pending = props.state === "pending";
return (
<div className={styles.container}>
{is_pending && <span className={styles.loader}></span>}
<_Entry
ref={entryRef}
type="text"
onFocus={() => setIsAcceptingInput(true)}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
ui_variable={displayValue}
width="20rem"
is_activated={isAcceptingInput}
is_disabled={is_pending}
readOnly
/>
<button className={clsx(styles.delete_button, { [styles.is_pending]: is_pending })} onClick={handleDelete}>
<DeleteSvg className={styles.delete_svg}/>
</button>
</div>
);
};

View File

@@ -0,0 +1,41 @@
@import "@scss_mixins";
.container {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
position: relative;
}
.delete_button {
padding: 0.4rem;
font-size: 1.4rem;
// background-color: var(--dark_800_color);
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
border-radius: 0.2rem;
&:hover {
background-color: var(--dark_850_color);
}
&:active {
background-color: var(--dark_900_color);
}
&.is_pending {
pointer-events: none;
& .delete_svg {
color: var(--dark_600_color);
}
}
}
.delete_svg {
width: 2.2rem;
color: var(--error_bc_color);
}
.loader {
@include loader(2rem, 0.2rem, left, -2.2rem);
}

View File

@@ -3,6 +3,7 @@ export { ComputeDevice } from "./compute_device/ComputeDevice";
export { DeeplAuthKey, OpenWebpage_DeeplAuthKey } from "./deepl_auth_key/DeeplAuthKey"; export { DeeplAuthKey, OpenWebpage_DeeplAuthKey } from "./deepl_auth_key/DeeplAuthKey";
export { DropdownMenu } from "./dropdown_menu/DropdownMenu"; export { DropdownMenu } from "./dropdown_menu/DropdownMenu";
export { Entry } from "./entry/Entry"; export { Entry } from "./entry/Entry";
export { HotkeysEntry } from "./hotkeys_entry/HotkeysEntry";
export { LabelComponent } from "./label_component/LabelComponent"; export { LabelComponent } from "./label_component/LabelComponent";
export { RadioButton } from "./radio_button/RadioButton"; export { RadioButton } from "./radio_button/RadioButton";
export { SectionLabelComponent } from "./section_label_component/SectionLabelComponent"; export { SectionLabelComponent } from "./section_label_component/SectionLabelComponent";

View File

@@ -8,7 +8,6 @@
.label { .label {
font-size: 1.6rem; font-size: 1.6rem;
font-weight: 400; font-weight: 400;
color: var(--dark_basic_text_color);
white-space: nowrap; white-space: nowrap;
width: max-content; width: max-content;
} }

View File

@@ -31,13 +31,6 @@ export const Slider = (props) => {
backgroundColor: "var(--dark_800_color)", backgroundColor: "var(--dark_800_color)",
padding: "0.6rem 1rem", padding: "0.6rem 1rem",
lineHeight: "1.15", lineHeight: "1.15",
// top: "-1.4rem",
// "&::before": {
// left: "30%",
// width: "1rem",
// height: "1rem",
// clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
// },
}, },
}, },
"& .MuiSlider-markLabel": { "& .MuiSlider-markLabel": {

View File

@@ -16,7 +16,6 @@
height: 100%; height: 100%;
font-size: 1.4rem; font-size: 1.4rem;
resize: none; resize: none;
color: var(--dark_basic_text_color);
&.is_disable { &.is_disable {
color: var(--dark_500_color); color: var(--dark_500_color);
pointer-events: none; pointer-events: none;

View File

@@ -41,5 +41,4 @@
.button_text { .button_text {
font-size: 1.2rem; font-size: 1.2rem;
color: var(--dark_basic_text_color);
} }

View File

@@ -32,7 +32,6 @@
.item_text { .item_text {
font-size: 1.4rem; font-size: 1.4rem;
font-weight: 300; font-weight: 300;
color: var(--dark_basic_text_color);
&.is_redoable { &.is_redoable {
text-decoration: line-through; text-decoration: line-through;
} }
@@ -56,7 +55,7 @@
.delete_svg { .delete_svg {
width: 2.4rem; width: 2.4rem;
color: #bb4448; color: var(--error_bc_color);
} }
.redo_svg { .redo_svg {
@@ -77,7 +76,6 @@
.add_button { .add_button {
padding: 0.8rem 1.2rem; padding: 0.8rem 1.2rem;
background-color: var(--primary_600_color); background-color: var(--primary_600_color);
color: var(--dark_basic_text_color);
font-size: 1.4rem; font-size: 1.4rem;
border-radius: 0.4rem; border-radius: 0.4rem;
text-align: center; text-align: center;

View File

@@ -8,6 +8,7 @@ import {
Slider, Slider,
SwitchBox, SwitchBox,
Entry, Entry,
HotkeysEntry,
RadioButton, RadioButton,
OpenWebpage_DeeplAuthKey, OpenWebpage_DeeplAuthKey,
DeeplAuthKey, DeeplAuthKey,
@@ -75,6 +76,10 @@ export const EntryContainer = (props) => (
<CommonContainer Component={Entry} {...props} add_break_point={false} /> <CommonContainer Component={Entry} {...props} add_break_point={false} />
); );
export const HotkeysEntryContainer = (props) => (
<CommonContainer Component={HotkeysEntry} {...props} />
);
export const RadioButtonContainer = (props) => ( export const RadioButtonContainer = (props) => (
<CommonContainer Component={RadioButton} {...props} /> <CommonContainer Component={RadioButton} {...props} />
); );

View File

@@ -3,10 +3,20 @@ import dev_section_title from "@images/about_vrct/dev_section_title.png";
import dev_misya from "@images/about_vrct/dev_misya.png"; import dev_misya from "@images/about_vrct/dev_misya.png";
import dev_shiina from "@images/about_vrct/dev_shiina.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 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_section_title from "@images/about_vrct/contributors_section_title.png";
import contributors_members from "@images/about_vrct/contributors_members.png"; import contributor_done from "@images/about_vrct/contributor_done.png";
import contributor_iya from "@images/about_vrct/contributor_iya.png";
import contributor_rera from "@images/about_vrct/contributor_rera.png";
import contributor_poposuke from "@images/about_vrct/contributor_poposuke.png";
import contributor_kumaguma from "@images/about_vrct/contributor_kumaguma.png";
import localization_section_title from "@images/about_vrct/localization_section_title.png"; import localization_section_title from "@images/about_vrct/localization_section_title.png";
import localization_members from "@images/about_vrct/localization_members.png"; import localization_1 from "@images/about_vrct/localization_1.png";
import localization_2 from "@images/about_vrct/localization_2.png";
import localization_3 from "@images/about_vrct/localization_3.png";
import localization_4 from "@images/about_vrct/localization_4.png";
import localization_5 from "@images/about_vrct/localization_5.png";
import special_thanks_section_title from "@images/about_vrct/special_thanks_section_title.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_members from "@images/about_vrct/special_thanks_members.png";
@@ -15,8 +25,6 @@ import special_thanks_message_ja from "@images/about_vrct/special_thanks_message
import poster_showcase_section_title from "@images/about_vrct/poster_showcase_section_title.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 clsx from "clsx";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useStore_UiLanguage } from "@store"; import { useStore_UiLanguage } from "@store";
@@ -43,7 +51,9 @@ export const AboutVrct = () => {
</div> </div>
<div className={styles.project_links_and_logo_section}> <div className={styles.project_links_and_logo_section}>
<img src={vrct_logo_for_about_vrct} className={styles.about_vrct_logo} /> <div className={styles.about_vrct_logo_wrapper}>
<img src={vrct_logo_for_about_vrct} className={styles.about_vrct_logo} />
</div>
<div className={styles.project_links_wrapper}> <div className={styles.project_links_wrapper}>
<OpenLinkContainer className={styles.project_link} href_id="project_link_booth" /> <OpenLinkContainer className={styles.project_link} href_id="project_link_booth" />
<OpenLinkContainer className={styles.project_link} href_id="project_link_documents" /> <OpenLinkContainer className={styles.project_link} href_id="project_link_documents" />
@@ -55,19 +65,46 @@ export const AboutVrct = () => {
<div className={styles.contributors_section}> <div className={styles.contributors_section}>
<img src={contributors_section_title} className={clsx(styles.section_title, styles.contributors)} /> <img src={contributors_section_title} className={clsx(styles.section_title, styles.contributors)} />
<div className={styles.contributors_img_wrapper}> <div className={styles.contributors_img_wrapper}>
<img src={contributors_members} className={clsx(styles.contributors_img, styles.contributors)} /> <div className={styles.contributor_card_wrapper}>
<OpenLinkContainer className={styles.contributors_done_san_x} href_id="contributors_done_san_x" /> <img src={contributor_done} className={clsx(styles.contributors_img, styles.contributors)} />
<OpenLinkContainer className={styles.contributors_iya_x} href_id="contributors_iya_x" /> <OpenLinkContainer className={styles.contributors_done_san_x} href_id="contributors_done_san_x" />
<OpenLinkContainer className={styles.contributors_rera_x} href_id="contributors_rera_x" /> </div>
<OpenLinkContainer className={styles.contributors_rera_github} href_id="contributors_rera_github" /> <div className={styles.contributor_card_wrapper}>
<OpenLinkContainer className={styles.contributors_poposuke_x} href_id="contributors_poposuke_x" /> <img src={contributor_iya} className={clsx(styles.contributors_img, styles.contributors)} />
<OpenLinkContainer className={styles.contributors_kumaguma_x} href_id="contributors_kumaguma_x" /> <OpenLinkContainer className={styles.contributors_iya_x} href_id="contributors_iya_x" />
</div>
<div className={styles.contributor_card_wrapper}>
<img src={contributor_rera} className={clsx(styles.contributors_img, styles.contributors)} />
<OpenLinkContainer className={styles.contributors_rera_x} href_id="contributors_rera_x" />
<OpenLinkContainer className={styles.contributors_rera_github} href_id="contributors_rera_github" />
</div>
<div className={styles.contributor_card_wrapper}>
<img src={contributor_poposuke} className={clsx(styles.contributors_img, styles.contributors)} />
<OpenLinkContainer className={styles.contributors_poposuke_x} href_id="contributors_poposuke_x" />
</div>
<div className={styles.contributor_card_wrapper}>
<img src={contributor_kumaguma} className={clsx(styles.contributors_img, styles.contributors)} />
<OpenLinkContainer className={styles.contributors_kumaguma_x} href_id="contributors_kumaguma_x" />
</div>
</div> </div>
</div> </div>
<div className={styles.localization_section}> <div className={styles.localization_section}>
<img src={localization_section_title} className={clsx(styles.section_title, styles.localization)} /> <img src={localization_section_title} className={clsx(styles.section_title, styles.localization)} />
<img src={localization_members} className={clsx(styles.localization_members_img, styles.localization)} /> <div className={styles.localization_members_wrapper}>
<div className={styles.localization_members_row_wrapper}>
<img src={localization_1} className={styles.localization_members_img} />
<img src={localization_2} className={styles.localization_members_img} />
</div>
<div className={styles.localization_members_row_wrapper}>
<img src={localization_3} className={styles.localization_members_img} />
<img src={localization_4} className={styles.localization_members_img} />
</div>
<div className={styles.localization_members_row_wrapper}>
<img src={localization_5} className={styles.localization_members_img} />
{/* <img src={localization_6} className={styles.localization_members_img} /> */}
</div>
</div>
</div> </div>
<div className={styles.special_thanks_section}> <div className={styles.special_thanks_section}>
@@ -87,7 +124,7 @@ export const AboutVrct = () => {
</div> </div>
<div className={styles.vrchat_disclaimer_section}> <div className={styles.vrchat_disclaimer_section}>
<img src={vrchat_disclaimer} className={styles.vrchat_disclaimer} /> <p className={styles.vrchat_disclaimer}>VRCT is not endorsed by VRChat and does not reflect the views or opinions of VRChat or anyone officially involved in producing or managing VRChat properties. VRChat and all associated properties are trademarks or registered trademarks of VRChat Inc. VRChat © VRChat Inc.</p>
</div> </div>

View File

@@ -1,9 +1,11 @@
.container { .container {
display: flex; display: flex;
gap: 2.2rem; gap: 2.2rem;
justify-content: center;
align-items: center;
flex-direction: column; flex-direction: column;
width: 72rem; max-width: 72rem;
// background-color: gray; margin: auto;
} }
.section_title { .section_title {
@@ -30,20 +32,23 @@
} }
.dev_section_wrapper { .dev_section_wrapper {
display: flex; display: flex;
justify-content: space-between; gap: 2rem;
flex-wrap: wrap;
width: 100%;
justify-content: center;
} }
.dev_card_wrapper { .dev_card_wrapper {
width: 34.6rem;
position: relative; position: relative;
// width: 100%;
} }
.dev_card_img { .dev_card_img {
width: 100%; width: 34.6rem;
} }
@mixin dev_sns_styles($right) { @mixin dev_sns_styles($right) {
position: absolute; position: absolute;
right: $right; right: $right;
bottom: 1.2rem; bottom: 0.6rem;
width: 2.8rem; width: 2.8rem;
padding: 0.4rem; padding: 0.4rem;
border-radius: 0.4rem; border-radius: 0.4rem;
@@ -69,8 +74,16 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
text-align: center;
padding: 0 5.5rem; padding: 0 5.5rem;
flex-wrap: wrap;
gap: 1rem;
}
.about_vrct_logo_wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
padding: 2rem 0;
} }
.about_vrct_logo { .about_vrct_logo {
width: 20rem; width: 20rem;
@@ -78,7 +91,7 @@
} }
.project_links_wrapper { .project_links_wrapper {
display: flex; display: flex;
flex-direction: column; flex-wrap: wrap;
gap: 0.2rem; gap: 0.2rem;
align-items: start; align-items: start;
} }
@@ -87,6 +100,7 @@
height: 2.6rem; height: 2.6rem;
padding: 0.4rem 1rem; padding: 0.4rem 1rem;
border-radius: 0.4rem; border-radius: 0.4rem;
flex-shrink: 0;
&:hover { &:hover {
background-color: var(--dark_850_color); background-color: var(--dark_850_color);
} }
@@ -96,19 +110,27 @@
} }
.contributors_img_wrapper { .contributors_img_wrapper {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
align-items: center;
}
.contributor_card_wrapper {
position: relative; position: relative;
} }
.contributors_img { .contributors_img {
width: 100%; width: 22rem;
} }
@mixin contributors_sns_styles($top, $left) { @mixin contributors_sns_styles($bottom, $left) {
position: absolute; position: absolute;
left: $left; left: $left;
top: $top; bottom: $bottom;
width: 2.4rem; width: 2.4rem;
padding: 0.4rem; padding: 0.4rem;
border-radius: 0.4rem; border-radius: 0.4rem;
transform: translate(50%, 50%);
&:hover { &:hover {
background-color: var(--dark_825_color); background-color: var(--dark_825_color);
} }
@@ -117,34 +139,51 @@
} }
} }
$first_line_top: 6.2rem; $bottom_pos: 16%;
$sns_left_pos: 0.8rem;
.contributors_done_san_x { .contributors_done_san_x {
@include contributors_sns_styles($first_line_top, 2rem); @include contributors_sns_styles($bottom_pos, $sns_left_pos);
} }
.contributors_iya_x { .contributors_iya_x {
@include contributors_sns_styles($first_line_top, 27.6rem); @include contributors_sns_styles($bottom_pos, $sns_left_pos);
} }
.contributors_rera_x { .contributors_rera_x {
@include contributors_sns_styles($first_line_top, 52.2rem); @include contributors_sns_styles($bottom_pos, calc($sns_left_pos - 1.4rem));
} }
.contributors_rera_github { .contributors_rera_github {
@include contributors_sns_styles($first_line_top, 55rem); @include contributors_sns_styles($bottom_pos, calc($sns_left_pos + 1.4rem));
} }
$second_line_top: 16.6rem;
.contributors_poposuke_x { .contributors_poposuke_x {
@include contributors_sns_styles($second_line_top, 14.8rem); @include contributors_sns_styles($bottom_pos, $sns_left_pos);
} }
.contributors_kumaguma_x { .contributors_kumaguma_x {
@include contributors_sns_styles($second_line_top, 40.8rem); @include contributors_sns_styles($bottom_pos, $sns_left_pos);
} }
.localization_section { .localization_section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%;
gap: 0.6rem;
}
.localization_members_wrapper {
display: flex;
justify-content: center;
align-items: start;
column-gap: 6rem;
row-gap: 1rem;
flex-wrap: wrap;
}
.localization_members_row_wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: start;
gap: 0.2rem;
} }
.localization_members_img { .localization_members_img {
width: 100%; height: 2.2rem;
} }
.special_thanks_section { .special_thanks_section {
@@ -163,9 +202,11 @@ $second_line_top: 16.6rem;
.poster_showcase_section { .poster_showcase_section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%;
} }
.vrchat_disclaimer { .vrchat_disclaimer {
width: 100%; font-size: 1.2rem;
margin-top: 8rem; margin-top: 8rem;
color: var(--dark_600_color);
} }

View File

@@ -1,6 +1,7 @@
.container { .container {
display: flex; display: flex;
justify-content: space-between; justify-content: center;
align-items: start; align-items: start;
gap: 2rem; gap: 2rem;
flex-wrap: wrap;
} }

View File

@@ -1,9 +1,10 @@
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; // flex: 1;
gap: 1rem; gap: 1rem;
justify-content: space-between; justify-content: space-between;
width: 42rem;
} }
$image_height: 2.8rem; $image_height: 2.8rem;
$y_padding: 0.4rem; $y_padding: 0.4rem;
@@ -32,6 +33,7 @@ $image_height_gap: 0.4rem;
} }
.poster_showcase_world_img { .poster_showcase_world_img {
height: $image_height; height: $image_height;
flex-shrink: 0;
} }
.pagination_container { .pagination_container {

View File

@@ -3,6 +3,7 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-shrink: 0;
} }
.poster_pagination_wrapper { .poster_pagination_wrapper {
display: flex; display: flex;

View File

@@ -61,7 +61,6 @@
.device_label { .device_label {
font-size: 1.8rem; font-size: 1.8rem;
color: var(--dark_basic_text_color);
} }
.device_contents { .device_contents {
@@ -76,6 +75,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.2rem; gap: 1.2rem;
justify-content: center;
align-items: center;
} }
.device_dropdown_wrapper { .device_dropdown_wrapper {
@@ -97,6 +98,7 @@
.device_secondary_label { .device_secondary_label {
padding-left: 0.2rem; padding-left: 0.2rem;
padding-right: 0.4rem;
font-size: 1.4rem; font-size: 1.4rem;
color: var(--dark_500_color); color: var(--dark_500_color);
white-space: nowrap; white-space: nowrap;

View File

@@ -0,0 +1,49 @@
import { useHotkeys } from "@logics_configs";
import styles from "./Hotkeys.module.scss";
import { HotkeysEntryContainer } from "../_templates/Templates";
import { useTranslation } from "react-i18next";
export const Hotkeys = () => {
return (
<div className={styles.container}>
<HotkeysBoxContainer />
</div>
);
};
const HotkeysBoxContainer = () => {
const { t } = useTranslation();
const { currentHotkeys, setHotkeys } = useHotkeys();
return (
<div className={styles.container}>
<HotkeysEntryContainer
label={t("config_page.hotkeys.toggle_vrct_visibility.label")}
hotkey_id="toggle_vrct_visibility"
value={currentHotkeys.data}
state={currentHotkeys.state}
setHotkeys={setHotkeys}
/>
<HotkeysEntryContainer
label={t("config_page.hotkeys.toggle_translation.label", {translation: t("main_page.translation")})}
hotkey_id="toggle_translation"
value={currentHotkeys.data}
state={currentHotkeys.state}
setHotkeys={setHotkeys}
/>
<HotkeysEntryContainer
label={t("config_page.hotkeys.toggle_transcription_send.label", {transcription_send: t("main_page.transcription_send")})}
hotkey_id="toggle_transcription_send"
value={currentHotkeys.data}
state={currentHotkeys.state}
setHotkeys={setHotkeys}
/>
<HotkeysEntryContainer
label={t("config_page.hotkeys.toggle_transcription_receive.label", {transcription_receive: t("main_page.transcription_receive")})}
hotkey_id="toggle_transcription_receive"
value={currentHotkeys.data}
state={currentHotkeys.state}
setHotkeys={setHotkeys}
/>
</div>
);
};

View File

@@ -0,0 +1,5 @@
.container {
display: flex;
// gap: 6.4rem;
flex-direction: column;
}

View File

@@ -5,5 +5,6 @@ export { Transcription } from "./transcription/Transcription";
export { Others, VrcMicMuteSyncContainer } from "./others/Others"; export { Others, VrcMicMuteSyncContainer } from "./others/Others";
export { AdvancedSettings } from "./advanced_settings/AdvancedSettings"; export { AdvancedSettings } from "./advanced_settings/AdvancedSettings";
export { Vr } from "./vr/Vr"; export { Vr } from "./vr/Vr";
export { Hotkeys } from "./hotkeys/Hotkeys";
export { AboutVrct } from "./about_vrct/AboutVrct"; export { AboutVrct } from "./about_vrct/AboutVrct";
export { Supporters } from "./supporters/Supporters"; export { Supporters } from "./supporters/Supporters";

View File

@@ -1,224 +1,22 @@
import { useState, useEffect } from "react";
import styles from "./Supporters.module.scss"; import styles from "./Supporters.module.scss";
import clsx from "clsx"; import { SupportUsContainer } from "./support_us_container/SupportUsContainer";
import { useTranslation } from "react-i18next"; import { SupportersContainer } from "./supporters_container/SupportersContainer";
import { useSupporters } from "@logics_configs";
import { import { useEffect } from "react";
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 = () => { export const Supporters = () => {
const { asyncFetchSupportersData } = useSupporters();
useEffect(() => {
asyncFetchSupportersData();
}, []);
return ( return (
<div className={styles.container}> <div className={styles.container}>
<SupportUsContainer /> <SupportUsContainer />
<SupportersContainer /> <div className={styles.supportersWrapper}>
</div> <SupportersContainer />
);
};
const SupportUsContainer = () => {
return (
<div className={styles.support_us_container}>
<img className={styles.fanbox_img} src={fanbox_img} />
<div className={styles.support_us_button_wrapper}>
<div className={styles.fanbox_wrapper}>
<a className={styles.fanbox_button} href="https://vrct-dev.fanbox.cc/" target="_blank" rel="noreferrer">
<img style={{ height: "100%", width: "100%", objectFit: "contain" }} src={fanbox_button} />
</a>
<p className={styles.mainly_japanese}>日本語 / Mainly Japanese</p>
</div>
<div className={styles.kofi_wrapper}>
<img className={styles.kofi_preparing} src={kofi_preparing} />
<p className={styles.mainly_english}>Mainly English</p>
</div>
</div> </div>
</div> </div>
); );
};
const getRandomImage = (images) => {
const random_index = Math.floor(Math.random() * images.length);
return images[random_index];
};
export const SupportersContainer = () => {
return (
<div className={styles.supporters_container}>
<img className={styles.vrct_supporters_title} src={vrct_supporters_title} />
<p className={styles.vrct_supporters_desc}>{`VRCT3.0のアップデートに向けて、めちゃ大変な開発を支えてくれた "Early Supporters" です。\nThey are the 'Early Supporters' who supported us through the incredibly challenging development toward the VRCT3.0 update.`}</p>
<ProgressBar />
<SupportsWrapper />
<ProgressBar />
<p className={styles.vrct_supporters_desc_end}>{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(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!`}</p>
</div>
);
};
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 (
<div
className={clsx(styles.progress_bar, {
[styles.progress_bar_active]: is_active,
})}
/>
);
};
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 ? (
<div
key={file_name}
className={clsx(styles.supporter_image_wrapper, options.class_name)}
style={{ "--delay": random_delay }}
>
<img className={styles.supporter_image} src={img_src} />
{chato_expression_src && (
<img
className={styles.default_chato_expression_image}
src={chato_expression_src}
/>
)}
{options.is_and_you_icon ? <AndYouIcon /> : null}
</div>
) : null;
});
};
return (
<div className={styles.supporters_wrapper}>
{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)}
<a href="https://vrct-dev.fanbox.cc/" target="_blank" rel="noreferrer">
{renderImages(imagesState.and_you_images, imagesState.chato_images, { is_and_you_icon: true, class_name: styles.and_you_image })}
</a>
</div>
);
};
const AndYouIcon = () => {
return (
<>
<div className={styles.and_you_container}>
<div className={styles.and_you_1}></div>
<div className={styles.and_you_2}></div>
</div>
<p className={styles.and_you_fanbox_link_text}>
FANBOX
<ExternalLink className={styles.external_link_svg} />
</p>
</>
);
}; };

View File

@@ -1,205 +1,21 @@
.container { .container {
display: flex; display: flex;
gap: 1.2rem; gap: 3.2rem;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%; width: 100%;
// background-color: gray; }
.supportersWrapper {
opacity: 0;
animation: fadeIn 0.8s ease-in-out 1.6s forwards;
} }
.support_us_container { @keyframes fadeIn {
display: flex; from {
flex-direction: column; opacity: 0;
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;
} }
} to {
.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; 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%;
}
} }

View File

@@ -0,0 +1,48 @@
import top_img from "@images/supporters/patreon_1600x400px.png";
import fanbox_logo from "@images/supporters/fanbox_logo.png";
import kofi_logo from "@images/supporters/kofi_logo.png";
import patreon_logo from "@images/supporters/patreon_logo.png";
import styles from "./SupportUsContainer.module.scss";
import { clsx } from "clsx";
export const SupportUsContainer = () => {
return (
<div id="support_us_container" className={styles.support_us_container}>
<img className={styles.top_img} src={top_img} />
<div className={styles.support_buttons_wrapper}>
<div className={styles.support_us_button_wrapper}>
<a className={styles.support_button} href="https://vrct-dev.fanbox.cc" target="_blank" rel="noreferrer">
<img
src={fanbox_logo}
className={clsx(styles.support_img, styles.fanbox_logo)}
/>
<div className={styles.spiral_top}></div>
<div className={styles.spiral_bottom}></div>
</a>
<a className={styles.support_button} href="https://ko-fi.com/vrct_dev" target="_blank" rel="noreferrer">
<img
src={kofi_logo}
className={clsx(styles.support_img, styles.kofi_logo)}
/>
<div className={styles.spiral_top}></div>
<div className={styles.spiral_bottom}></div>
</a>
<a className={styles.support_button} href="https://www.patreon.com/vrct_dev" target="_blank" rel="noreferrer">
<img
src={patreon_logo}
className={clsx(styles.support_img, styles.patreon_logo)}
/>
<div className={styles.spiral_top}></div>
<div className={styles.spiral_bottom}></div>
</a>
</div>
<div className={styles.lines_container}>
<div className={styles.line_basic}></div>
<div className={styles.line_fuwa}></div>
<div className={styles.line_mochi}></div>
<div className={styles.line_mogu}></div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,219 @@
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
@keyframes revealTopImg {
0% {
clip-path: inset(0 50% 0 50%);
opacity: 0;
}
100% {
clip-path: inset(0 0 0 0);
opacity: 1;
}
}
@keyframes bounceIn {
0% {
transform: translateY(100%);
opacity: 0;
}
60% {
transform: translateY(-10%);
opacity: 1;
}
80% {
transform: translateY(10%);
opacity: 1;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes expandWidth {
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
}
.support_us_container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 1.4rem;
width: 100%;
}
.support_buttons_wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 1.2rem;
}
.top_img {
width: 100%;
animation: revealTopImg 0.8s ease forwards;
}
.lines_container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
gap: 3.6rem;
}
.line_basic,
.line_fuwa,
.line_mochi,
.line_mogu {
width: 8.6rem;
height: 0.2rem;
transform: scaleX(0);
transform-origin: left center;
}
.line_basic {
background-color: var(--dark_800_color);
animation: expandWidth 1s $progress_ease 0.4s forwards;
}
.line_fuwa {
background-color: #5788a2;
animation: expandWidth 1s $progress_ease 0.6s forwards;
}
.line_mochi {
background-color: var(--received_300_color);
animation: expandWidth 1s $progress_ease 0.8s forwards;
}
.line_mogu {
background-color: var(--dark_basic_text_color);
animation: expandWidth 1s $progress_ease 1s forwards;
}
.support_us_button_wrapper {
display: flex;
justify-content: center;
align-items: center;
column-gap: 3.6rem;
flex-wrap: wrap;
}
.support_button {
position: relative;
padding: 1.2rem 1.6rem;
opacity: 0;
transform: translateY(100%);
}
.support_button:nth-child(1) {
animation: bounceIn 0.4s ease-out 1.2s forwards;
}
.support_button:nth-child(2) {
animation: bounceIn 0.4s ease-out 1.4s forwards;
}
.support_button:nth-child(3) {
animation: bounceIn 0.4s ease-out 1.6s forwards;
}
.support_img {
&.fanbox_logo {
height: 1.8rem;
}
&.kofi_logo, &.patreon_logo {
height: 2.2rem;
}
}
.spiral_top::before,
.spiral_top::after,
.spiral_bottom::before,
.spiral_bottom::after {
content: "";
position: absolute;
transition-duration: 0.3s;
}
.spiral_top::before {
background: var(--dark_800_color);
box-shadow: 0 0 0.4rem 0 var(--dark_800_color);
}
.spiral_top::after {
background: #5788a2;
box-shadow: 0 0 0.4rem 0 #5788a2;
}
.spiral_bottom::before {
background: var(--received_300_color);
box-shadow: 0 0 0.4rem 0 var(--received_300_color);
}
.spiral_bottom::after {
background: var(--dark_basic_text_color);
box-shadow: 0 0 0.4rem 0 var(--dark_basic_text_color);
}
.spiral_top::before {
top: 0;
left: 0;
width: 0.1rem;
height: 0;
}
.spiral_top::after {
top: 0;
right: 0;
width: 0;
height: 0.1rem;
}
.spiral_bottom::before {
bottom: 0;
left: 0;
width: 0;
height: 0.1rem;
}
.spiral_bottom::after {
bottom: 0;
right: 0;
width: 0.1rem;
height: 0;
}
.support_button:hover .spiral_top::before {
height: 100%;
}
.support_button:hover .spiral_top::after {
width: 100%;
}
.support_button:hover .spiral_bottom::before {
width: 100%;
}
.support_button:hover .spiral_bottom::after {
height: 100%;
}
.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;
}

View File

@@ -0,0 +1,30 @@
import styles from "./SupportersContainer.module.scss";
import { SupportersWrapper } from "./supporters_wrapper/SupportersWrapper";
import { useSupporters } from "@logics_configs";
import { supporters_images_url } from "@ui_configs";
import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png";
export const SupportersContainer = () => {
const { currentSupportersData } = useSupporters();
if (currentSupportersData.state === "error")
return <div>Failed to retrieve data.</div>;
if (currentSupportersData.state === "pending" || currentSupportersData.data === null)
return <div>Loading...</div>;
const supporters_settings = currentSupportersData.data.supporters_settings;
const last_updated_local_date = new Date(supporters_settings.last_updated_utc_date).toLocaleString();
return (
<div className={styles.supporters_container}>
<div className={styles.vrct_supporters_title_wrapper}>
<img className={styles.vrct_supporters_title} src={vrct_supporters_title}/>
<img className={styles.calc_period} src={`${supporters_images_url}/calc_period_label.png`}/>
<p className={styles.last_updated_local_date}>{`Last updated date: ${last_updated_local_date}`}</p>
</div>
<SupportersWrapper />
<p className={styles.vrct_supporters_desc_end}>{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(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!`}</p>
</div>
);
};

View File

@@ -0,0 +1,34 @@
.supporters_container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 1rem;
}
.vrct_supporters_title_wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 0.2rem;
width: 100%;
}
.vrct_supporters_title {
height: 4.2rem;
}
.calc_period {
height: 1.6rem;
}
.last_updated_local_date {
font-size: 1.2rem;
color: var(--dark_600_color);
width: 100%;
text-align: end;
}
.vrct_supporters_desc_end {
font-size: 1.4rem;
margin-top: 2rem;
color: var(--dark_300_color);
}

View File

@@ -0,0 +1,316 @@
import React, { useState, useCallback, useEffect } from "react";
import clsx from "clsx";
import ArrowLeftSvg from "@images/arrow_left.svg?react";
import styles from "./SupportersWrapper.module.scss";
import { shuffleArray, randomIntMinMax, randomMinMax } from "@utils";
import {
useSettingBoxScrollPosition,
useSupporters,
} from "@logics_configs";
import { supporters_images_url } from "@ui_configs";
const SHUFFLE_INTERVAL_TIME = 20000;
const and_you_data = {
supporter_id: "and_you",
};
const image_sets = {
supporter_cards: `${supporters_images_url}/supporter_cards/`,
chato_expressions: `${supporters_images_url}/chato_expressions/`,
supporters_labels: `${supporters_images_url}/supporters_labels/`,
supporters_icons: `${supporters_images_url}/supporters_icons/`,
};
const getSupporterCard = (plan_name) => {
const card_map = {
"mogu_2000": "mogu_card",
"mochi_1000": "mochi_card",
"fuwa_500": "fuwa_card",
"basic_300": "basic_card",
};
if (!card_map[plan_name]) return `${image_sets.supporter_cards}basic_card.png`;
return `${image_sets.supporter_cards}${card_map[plan_name]}.png`;
};
const getChatoExpressionsPath = (file_name) => `${image_sets.chato_expressions}${file_name}.png`;
const getSupportersLabelsPath = (file_name) => `${image_sets.supporters_labels}${file_name}.png`;
const getSupportersIconsPath = (file_name) => `${image_sets.supporters_icons}${file_name}.png`;
export const SupportersWrapper = () => {
const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition();
const { currentSupportersData } = useSupporters();
const [json_data, setJsonData] = useState();
const [supportersData, setSupportersData] = useState([]);
const [chatoExpressions, setChatoExpressions] = useState([]);
useEffect(() => {
setJsonData(currentSupportersData.data);
}, [currentSupportersData.data]);
const supporters_settings = currentSupportersData.data.supporters_settings;
const calc_support_period = supporters_settings.calc_support_period;
const chato_ex_count = supporters_settings.chato_ex_count;
const recalcAndUpdateSupporters = useCallback(() => {
if (!json_data) return;
const grouped_data = {
mogu_2000: [],
mochi_1000: [],
fuwa_500: [],
basic_300: [],
former_supporter: [],
and_you: [],
};
json_data.supporters_data.forEach((supporter) => {
const value = supporter.highest_plan_during_the_period || "former_supporter";
if (grouped_data[value]) {
grouped_data[value].push(supporter);
} else {
grouped_data["former_supporter"].push(supporter);
}
});
const newSupportersData = [
...shuffleArray(grouped_data["mogu_2000"]),
...shuffleArray(grouped_data["mochi_1000"]),
...shuffleArray(grouped_data["fuwa_500"]),
...shuffleArray(grouped_data["basic_300"]),
...shuffleArray(grouped_data["former_supporter"]),
and_you_data,
];
setSupportersData(newSupportersData);
setChatoExpressions(
newSupportersData.map(() =>
getChatoExpressionsPath(
`chato_expression_${randomIntMinMax(1, chato_ex_count)}`
)
)
);
}, [json_data]);
useEffect(() => {
recalcAndUpdateSupporters();
}, [json_data, recalcAndUpdateSupporters]);
const shuffleSupporters = useCallback(() => {
if (!json_data) return;
saveScrollPosition();
recalcAndUpdateSupporters();
setTimeout(() => restoreScrollPosition(), 0);
}, [json_data, recalcAndUpdateSupporters, saveScrollPosition, restoreScrollPosition]);
useEffect(() => {
const interval = setInterval(() => {
shuffleSupporters();
}, SHUFFLE_INTERVAL_TIME);
return () => clearInterval(interval);
}, [shuffleSupporters]);
return (
<div className={styles.container}>
<ProgressBar />
<div className={styles.supporters_wrapper}>
<SupporterCardsComponent
supportersData={supportersData}
chatoExpressions={chatoExpressions}
calc_support_period={calc_support_period}
/>
</div>
<ProgressBar />
</div>
);
};
const AndYouIcon = () => {
return (
<>
<div className={styles.and_you_container}>
<div className={styles.and_you_1}></div>
<div className={styles.and_you_2}></div>
</div>
<p className={styles.and_you_fanbox_link_text}>
FANBOX Ko-fi Patreon
</p>
<ArrowLeftSvg className={styles.arrow_left_svg} />
</>
);
};
const SupporterCardsComponent = ({ supportersData, chatoExpressions, calc_support_period }) => {
return supportersData.map((item, index) => {
const target_plan = item.highest_plan_during_the_period;
const img_src = getSupporterCard(target_plan);
const is_and_you = item.supporter_id === "and_you";
const random_delay = `${randomMinMax(0.1, 6).toFixed(1)}s`;
const supporter_image_wrapper_classname = clsx(
styles.supporter_image_wrapper,
{
[styles.mogu_image]: target_plan === "mogu_2000",
}
);
return is_and_you ? (
<a href="#support_us_container" key={item.supporter_id}>
<div className={styles.supporter_image_container}>
<div
className={supporter_image_wrapper_classname}
style={{ "--delay": random_delay }}
>
<img
className={styles.supporter_image}
src={img_src}
alt="supporter"
/>
<SupporterLabelComponent
target_plan={target_plan}
item={item}
chatoExpressions={chatoExpressions}
/>
<AndYouIcon />
</div>
</div>
</a>
) : img_src ? (
<div key={item.supporter_id} className={styles.supporter_image_container}>
<div
className={supporter_image_wrapper_classname}
style={{ "--delay": random_delay }}
>
<img
className={styles.supporter_image}
src={img_src}
alt="supporter"
/>
<SupporterLabelComponent
target_plan={target_plan}
item={item}
chato_src={chatoExpressions[index]}
index={index}
/>
</div>
<SupporterPeriodContainer settings={item} calc_support_period={calc_support_period}/>
</div>
) : null;
});
};
const SupporterLabelComponent = ({ item, target_plan, chato_src }) => {
const is_icon_plan = ["mogu_2000", "mochi_1000"].includes(
target_plan
);
const supporter_label_component_classname = clsx(
styles.supporter_label_component,
{
[styles.is_icon_plan]: is_icon_plan,
}
);
const is_and_you = item.supporter_id === "and_you";
const is_default_icon = item.supporter_icon_id === "";
const file_name = is_and_you ? "and_you" : `supporter_${item.supporter_id}`;
const label_img_src = getSupportersLabelsPath(file_name);
const icon_img_src = getSupportersIconsPath(
`supporter_icon_${item.supporter_icon_id}`
);
return (
<div className={supporter_label_component_classname}>
{is_icon_plan && (
<div className={styles.supporter_icon_wrapper}>
{is_default_icon ? (
<img
className={styles.default_chato_expression_image}
src={chato_src}
alt="chato expression"
/>
) : (
<img
className={styles.supporter_icon}
src={icon_img_src}
alt="supporter icon"
/>
)}
</div>
)}
<img
className={styles.supporter_label_image}
src={label_img_src}
alt="supporter label"
/>
</div>
);
};
const SupporterPeriodContainer = ({ settings, calc_support_period }) => {
const period_data = extractKeys(settings, calc_support_period);
return (
<div className={styles.supporter_period_container}>
{Object.entries(period_data).map(([key, item], index) => {
if (item === "") return null;
const class_name = clsx(styles.period_box, {
[styles.mogu_bar]: item === "mogu_2000",
[styles.mochi_bar]: item === "mochi_1000",
[styles.fuwa_bar]: item === "fuwa_500",
[styles.basic_bar]: item === "basic_300",
});
return <div key={index} className={class_name}></div>;
})}
</div>
);
};
const extractKeys = (data, keys_to_extract) => {
const result = {};
for (const key of keys_to_extract) {
if (key in data) {
result[key] = data[key];
}
}
return result;
};
const ProgressBar = () => {
const [is_active, setIsActive] = useState(false);
useEffect(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
setIsActive(true);
});
});
const interval = setInterval(() => {
setIsActive(false);
setTimeout(() => setIsActive(true), 50);
}, SHUFFLE_INTERVAL_TIME);
return () => clearInterval(interval);
}, []);
return (
<div
className={clsx(styles.progress_bar, {
[styles.progress_bar_active]: is_active,
})}
/>
);
};

View File

@@ -0,0 +1,240 @@
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 2rem;
}
.supporters_wrapper {
display: flex;
justify-content: center;
align-items: center;
align-content: start;
flex-wrap: wrap;
column-gap: 1.8rem;
row-gap: 1rem;
}
.supporter_image_container {
position: relative;
width: 18rem;
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.supporter_image_wrapper {
position: relative;
overflow: hidden;
}
.supporter_image {
position: relative;
width: 100%;
}
.supporter_label_component {
position: absolute;
left: 0.6rem;
&.is_icon_plan {
top: 50%;
transform: translateY(-50%);
}
top: 0.4rem;
display: flex;
justify-content: center;
gap: 0.4rem;
}
.supporter_label_image {
height: 2rem;
// margin-top: 0.2rem;
&.small {
height: 1.4rem;
}
}
.supporter_icon_wrapper {
height: 4rem;
aspect-ratio: 1 /1;
border-radius: 50%;
background-color: #ffffff;
overflow: hidden;
position: relative;
}
.supporter_icon {
aspect-ratio: 1 / 1;
height: 100%;
width: 100%;
}
.default_chato_expression_image {
position: absolute;
top: 52%;
left: 50%;
transform: translate(-50%, -50%) rotate(10deg);
width: 2.8rem;
opacity: 0.8;
}
.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%;
}
}
.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_container {
&:hover .and_you_container {
top: 36%;
transform: translate(-50%, -50%) rotate(180deg);
animation: disappear 0.3s forwards;
}
&:hover .and_you_fanbox_link_text {
top: 74%;
opacity: 1;
}
&:hover .arrow_left_svg {
opacity: 0;
animation: arrow_up_down 0.3s forwards;
animation-delay: 0.3s;
}
}
.supporter_image_container.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%;
width: 100%;
text-align: center;
transform: translate(-50%, -50%);
transition: all 0.3s ease;
opacity: 0;
}
.arrow_left_svg {
position: absolute;
top: 60%;
left: 50%;
transform: translate(-50%, -50%) rotate(90deg);
color: var(--dark_400_color);
width: 2rem;
opacity: 0;
}
@keyframes arrow_up_down {
0% {
top: 60%;
}
100% {
top: 36%;
opacity: 1;
}
}
@keyframes disappear {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.supporter_period_container {
display: flex;
gap: 0.2rem;
padding-left: 0.4rem;
}
.period_box {
width: 1.8rem;
height: 0.3rem;
border-radius: 0.3rem;
&.mogu_bar {
background-color: var(--dark_basic_text_color);
}
&.mochi_bar {
background-color: var(--received_300_color);
}
&.fuwa_bar {
background-color: #5788a2;
}
&.basic_bar {
background-color: var(--dark_800_color);
}
}
.progress_bar {
height: 0.2rem;
width: 0%;
&.progress_bar_active {
transition: width 20000ms linear;
background-color: var(--primary_400_color);
width: 100%;
}
}

View File

@@ -39,7 +39,7 @@
background-color: var(--dark_850_color); background-color: var(--dark_850_color);
} }
&.is_selected .controller_switcher_label { &.is_selected .controller_switcher_label {
color: var(--dark_basic_text_color); color: var(--dark_200_color);
} }
} }
.controller_switcher_label { .controller_switcher_label {
@@ -92,7 +92,6 @@
transform: translateX(10%) rotate(90deg); transform: translateX(10%) rotate(90deg);
} }
.sample_text_button_label { .sample_text_button_label {
color: var(--dark_basic_text_color);
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 110%; top: 110%;
@@ -281,7 +280,6 @@
} }
.restore_default_settings_button { .restore_default_settings_button {
color: var(--dark_basic_text_color);
font-size: 1.2rem; font-size: 1.2rem;
margin-top: 6rem; margin-top: 6rem;
padding: 0.8rem; padding: 0.8rem;

View File

@@ -3,18 +3,21 @@ import styles from "./SidebarSection.module.scss";
export const SidebarSection = () => { export const SidebarSection = () => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.tabs_wrapper}> <div className={styles.scroll_container}>
<Tab tab_id="device" /> <div className={styles.tabs_wrapper}>
<Tab tab_id="appearance" /> <Tab tab_id="device" />
<Tab tab_id="translation" /> <Tab tab_id="appearance" />
<Tab tab_id="transcription" /> <Tab tab_id="translation" />
<Tab tab_id="vr" /> <Tab tab_id="transcription" />
<Tab tab_id="others" /> <Tab tab_id="vr" />
<Tab tab_id="advanced_settings" /> <Tab tab_id="others" />
</div> <Tab tab_id="hotkeys" />
<div className={styles.separated_tabs_wrapper}> <Tab tab_id="advanced_settings" />
<Tab tab_id="supporters" /> </div>
<Tab tab_id="about_vrct" /> <div className={styles.separated_tabs_wrapper}>
<Tab tab_id="supporters" />
<Tab tab_id="about_vrct" />
</div>
</div> </div>
</div> </div>
); );

View File

@@ -1,10 +1,15 @@
.container { .container {
width: var(--config_page_sidebar_width); width: var(--config_page_sidebar_width);
flex-shrink: 0; flex-shrink: 0;
padding: 0rem 0rem 5.8rem 1.2rem;
}
.scroll_container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
padding: 0rem 0rem 5.8rem 1.2rem; overflow-y: auto;
height: 100%;
max-height: 60rem; max-height: 60rem;
} }
@@ -22,7 +27,6 @@
display: flex; display: flex;
justify-content: left; justify-content: left;
align-items: center; align-items: center;
color: var(--dark_basic_text_color);
padding: 0.8rem 0 0.8rem 1rem; padding: 0.8rem 0 0.8rem 1rem;
cursor: pointer; cursor: pointer;
&:hover { &:hover {

View File

@@ -0,0 +1,49 @@
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { clsx } from "clsx";
import styles from "./VersionLabel.module.scss";
import { useSoftwareVersion } from "@logics_configs";
import { useComputeMode } from "@logics_common";
import CopySvg from "@images/copy.svg?react";
import CheckMarkSvg from "@images/check_mark.svg?react";
export const VersionLabel = () => {
const [is_copied, setIsCopied] = useState(false);
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 });
const is_cpu = currentComputeMode.data === "cpu";
const copyToClipboard = async () => {
if (is_copied) return;
const copy_text = is_cpu ? `${currentSoftwareVersion.data}` : `${currentSoftwareVersion.data} CUDA`;
await navigator.clipboard.writeText(copy_text);
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 1000);
};
return (
<div className={styles.container}>
<div className={clsx(styles.wrapper, {[styles.is_copied]: is_copied})} onClick={copyToClipboard}>
<p className={styles.version_label}>{version_label}</p>
{is_copied
? <CheckMarkSvg className={styles.check_mark_svg}/>
: <CopySvg className={styles.copy_svg}/>
}
</div>
</div>
);
};

View File

@@ -0,0 +1,33 @@
.container {
position: absolute;
bottom: 1.2rem;
left: 1.4rem;
font-size: 1.2rem;
color: var(--dark_400_color);
}
.wrapper {
position: relative;
display: flex;
align-items: center;
gap: 0.6rem;
cursor: pointer;
&.is_copied {
cursor: default;
}
}
.version_label {
font-size: 1.2rem;
color: var(--dark_400_color);
}
.copy_svg {
width: 1.4rem;
color: var(--dark_500_color);
}
.check_mark_svg {
width: 1.4rem;
color: var(--primary_300_color);
}

View File

@@ -25,40 +25,4 @@
justify-content: space-between; justify-content: space-between;
background-color: var(--dark_888_color); background-color: var(--dark_888_color);
position: relative; 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);
}
} }

View File

@@ -46,5 +46,4 @@
.language_label { .language_label {
font-size: 1.4rem; font-size: 1.4rem;
color: var(--dark_basic_text_color);
} }

View File

@@ -1,99 +1,67 @@
import { useResizable } from "react-resizable-layout"; import { useResizable } from "react-resizable-layout";
import { useRef, useEffect, useState } from "react"; import { useRef, useEffect, useState, forwardRef } from "react";
import styles from "./MessageContainer.module.scss"; import styles from "./MessageContainer.module.scss";
import { appWindow } from "@tauri-apps/api/window";
import { LogBox } from "./log_box/LogBox"; import { LogBox } from "./log_box/LogBox";
import { MessageLogSettingsContainer } from "./message_log_settings_container/MessageLogSettingsContainer"; import { MessageLogSettingsContainer } from "./message_log_settings_container/MessageLogSettingsContainer";
import { MessageInputBox } from "./message_input_box/MessageInputBox"; import { MessageInputBox } from "./message_input_box/MessageInputBox";
import { useMessageInputBoxRatio } from "@logics_main"; import { useMessageInputBoxRatio } from "@logics_main";
import { useUiScaling } from "@logics_configs";
import { useStore_IsAppliedInitMessageBoxHeight } from "@store";
export const MessageContainer = () => { export const MessageContainer = () => {
const { currentMessageInputBoxRatio, asyncSetMessageInputBoxRatio } = useMessageInputBoxRatio(); const { currentMessageInputBoxRatio, asyncSetMessageInputBoxRatio } = useMessageInputBoxRatio();
const { currentUiScaling } = useUiScaling(); const [ui_message_box_ratio, setUiMessageBoxRatio] = useState(false);
const [is_hovered, setIsHovered] = useState(false); 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 container_ref = useRef(null);
const separator_ref = useRef(null);
const log_box_ref = useRef(null); const log_box_ref = useRef(null);
const message_box_wrapper_ref = useRef(null); const message_input_box_wrapper_ref = useRef(null);
const asyncSetMessageBoxHeightInRem = async (data) => { const calculateMessageInputBoxRatio = (position) => {
const minimized = await appWindow.isMinimized(); if (log_box_ref.current && message_input_box_wrapper_ref.current && separator_ref.current && container_ref.current) {
if (minimized) return; // don't save while the window is minimized. const container_padding_bottom = parseFloat(
setMessageBoxHeightInRem(data); window.getComputedStyle(container_ref.current).paddingBottom
);
const total_height =
log_box_ref.current.offsetHeight +
separator_ref.current.offsetHeight * 2 +
message_input_box_wrapper_ref.current.offsetHeight;
const adjusted_position = position - container_padding_bottom;
const message_box_ratio = (adjusted_position / total_height) * 100;
return message_box_ratio;
}
console.warn("References not ready for calculation");
return 10; // Default initial height percentage
}; };
const calculateMessageBoxRatioAndHeight = () => { const asyncSaveRatio = (position) => {
if (!currentIsAppliedInitMessageBoxHeight.data) { if (position > 0) {
asyncSetMessageInputBoxRatio(currentMessageInputBoxRatio.data); asyncSetMessageInputBoxRatio(calculateMessageInputBoxRatio(position));
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({ const { position, separatorProps } = useResizable({
axis: "y", axis: "y",
reverse: true, reverse: true,
onResizeEnd: calculateMessageBoxRatioAndHeight,
}); });
useEffect(() => { useEffect(() => {
asyncSetMessageBoxHeightInRem((position / FONT_SIZE_STANDARD) - 1.4); if (position > 0) {
setUiMessageBoxRatio(calculateMessageInputBoxRatio(position));
const timeout = setTimeout(() => {
asyncSaveRatio(position);
}, 200);
return () => clearTimeout(timeout);
}
}, [position]); }, [position]);
useEffect(() => { useEffect(() => {
asyncSetMessageBoxHeightInRem(convertRatioToRem(currentMessageInputBoxRatio.data)); setUiMessageBoxRatio(currentMessageInputBoxRatio.data);
}, [currentMessageInputBoxRatio.data]); }, [currentMessageInputBoxRatio]);
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 ( return (
<div className={styles.container} ref={container_ref}> <div className={styles.container} ref={container_ref}>
<div className={styles.log_box_resize_wrapper} <div
className={styles.log_box_resize_wrapper}
ref={log_box_ref} ref={log_box_ref}
onMouseOver={() => setIsHovered(true)} onMouseOver={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
@@ -101,11 +69,11 @@ export const MessageContainer = () => {
<LogBox /> <LogBox />
<MessageLogSettingsContainer to_visible_toggle_bar={is_hovered} /> <MessageLogSettingsContainer to_visible_toggle_bar={is_hovered} />
</div> </div>
<Separator {...separatorProps} onDragStart={calculateMessageBoxRatioAndHeight} /> <Separator {...separatorProps} ref={separator_ref} />
<div <div
className={styles.message_box_resize_wrapper} className={styles.message_box_resize_wrapper}
ref={message_box_wrapper_ref} ref={message_input_box_wrapper_ref}
style={{ height: `${message_box_height_in_rem}rem` }} style={{ height: `${ui_message_box_ratio}%` }}
> >
<MessageInputBox /> <MessageInputBox />
</div> </div>
@@ -113,8 +81,8 @@ export const MessageContainer = () => {
); );
}; };
const Separator = ({ onDragStart, ...props }) => ( const Separator = forwardRef((props, ref) => (
<div tabIndex={0} className={styles.separator} {...props} onDragStart={onDragStart}> <div tabIndex={0} className={styles.separator} ref={ref} {...props}>
<span className={styles.separator_line}></span> <span className={styles.separator_line}></span>
</div> </div>
); ));

View File

@@ -36,7 +36,7 @@
} }
.message_box_resize_wrapper { .message_box_resize_wrapper {
height: 10rem; height: 10%;
min-height: 3.8rem; min-height: 3.8rem;
max-height: 90%; max-height: 90%;
} }

View File

@@ -1,50 +1,26 @@
import { useEffect, useLayoutEffect, useRef, useState } from "react"; import React, { useRef, useLayoutEffect, useEffect } from "react";
import styles from "./LogBox.module.scss"; import styles from "./LogBox.module.scss";
import { store } from "@store";
import { MessageContainer } from "./message_container/MessageContainer"; import { MessageContainer } from "./message_container/MessageContainer";
import { scrollToBottom } from "@utils";
import { useMessage } from "@logics_common"; import { useMessage } from "@logics_common";
import { useMessageLogScroll } from "@logics_main";
import { store } from "@store";
export const LogBox = () => { export const LogBox = () => {
const { currentMessageLogs } = useMessage(); const { currentMessageLogs } = useMessage();
const log_container_ref = useRef(null); const { scrollToBottom, isScrolling } = useMessageLogScroll();
const [is_scrolling, setIsScrolling] = useState(false); const logContainerRef = useRef(null);
useLayoutEffect(() => { useLayoutEffect(() => {
store.log_box_ref = log_container_ref; store.log_box_ref = logContainerRef;
if (!is_scrolling) { if (!isScrolling) {
scrollToBottom(store.log_box_ref); scrollToBottom();
// scrollToBottom(store.log_box_ref, true); [Fix me]
} }
}, [currentMessageLogs.data]); }, [currentMessageLogs.data, isScrolling]);
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 ( return (
<div id="log_container" className={styles.container} ref={log_container_ref}> <div id="log_container" className={styles.container} ref={logContainerRef}>
<MessageLogUiSizeController /> <MessageLogUiSizeController />
{currentMessageLogs.data.map(message_data => ( {currentMessageLogs.data.map((message_data) => (
<MessageContainer key={message_data.id} {...message_data} /> <MessageContainer key={message_data.id} {...message_data} />
))} ))}
</div> </div>

View File

@@ -72,18 +72,21 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
&.sent_message { &.sent_message {
width: 100%;
align-items: end; align-items: end;
text-align: end; text-align: end;
} }
&.received_message { &.received_message {
width: 100%;
align-items: start; align-items: start;
} }
} }
.message_main { .message_main {
color: var(--dark_basic_text_color);
user-select: text; user-select: text;
font-size: 1.4em; font-size: 1.4em;
overflow-wrap: break-word;
max-width: 100%;
} }
.message_second { .message_second {

View File

@@ -30,7 +30,7 @@
.tooltip_title { .tooltip_title {
font-size: 1.2rem; font-size: 1.2rem;
color: var(--dark_basic_text_color); color: var(--dark_200_color);
} }
.hold_progress_bar { .hold_progress_bar {

View File

@@ -1,10 +1,11 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useLayoutEffect, useRef } from "react";
import styles from "./MessageInputBox.module.scss"; import styles from "./MessageInputBox.module.scss";
import SendMessageSvg from "@images/send_message.svg?react"; import SendMessageSvg from "@images/send_message.svg?react";
import { useMessage } from "@logics_common"; import { useMessage } from "@logics_common";
import { useSendMessageButtonType, useEnableAutoClearMessageInputBox } from "@logics_configs"; import { useSendMessageButtonType, useEnableAutoClearMessageInputBox } from "@logics_configs";
import { useMessageLogScroll } from "@logics_main";
import { store } from "@store"; import { store } from "@store";
import { scrollToBottom } from "@utils"; import { appWindow } from "@tauri-apps/api/window";
export const MessageInputBox = () => { export const MessageInputBox = () => {
const [message_history, setMessageHistory] = useState([]); const [message_history, setMessageHistory] = useState([]);
@@ -21,6 +22,14 @@ export const MessageInputBox = () => {
const { currentEnableAutoClearMessageInputBox } = useEnableAutoClearMessageInputBox(); const { currentEnableAutoClearMessageInputBox } = useEnableAutoClearMessageInputBox();
const { currentSendMessageButtonType } = useSendMessageButtonType(); const { currentSendMessageButtonType } = useSendMessageButtonType();
const { scrollToBottom } = useMessageLogScroll();
const log_box_ref = useRef(null);
useLayoutEffect(() => {
store.text_area_ref = log_box_ref;
}, []);
useEffect(() => { useEffect(() => {
if (currentMessageLogs.data) { if (currentMessageLogs.data) {
const sentMessages = currentMessageLogs.data const sentMessages = currentMessageLogs.data
@@ -32,6 +41,7 @@ export const MessageInputBox = () => {
const onSubmitFunction = (e) => { const onSubmitFunction = (e) => {
e.preventDefault(); e.preventDefault();
// appWindow.minimize();
if (!currentMessageInputValue.data.trim()) return updateMessageInputValue(""); if (!currentMessageInputValue.data.trim()) return updateMessageInputValue("");
@@ -40,7 +50,7 @@ export const MessageInputBox = () => {
if (currentEnableAutoClearMessageInputBox.data) updateMessageInputValue(""); if (currentEnableAutoClearMessageInputBox.data) updateMessageInputValue("");
setTimeout(() => { setTimeout(() => {
scrollToBottom(store.log_box_ref); scrollToBottom();
}, 10); }, 10);
setHistoryIndex(-1); setHistoryIndex(-1);
@@ -69,7 +79,7 @@ export const MessageInputBox = () => {
if (history_index > -1) { if (history_index > -1) {
const new_index = history_index - 1; const new_index = history_index - 1;
setHistoryIndex(new_index); setHistoryIndex(new_index);
setInputValue( updateMessageInputValue(
new_index >= 0 new_index >= 0
? message_history[message_history.length - 1 - new_index] ? message_history[message_history.length - 1 - new_index]
: "" : ""
@@ -89,6 +99,7 @@ export const MessageInputBox = () => {
<div className={styles.container}> <div className={styles.container}>
<div className={styles.message_box_wrapper}> <div className={styles.message_box_wrapper}>
<textarea <textarea
ref={log_box_ref}
className={styles.message_box_input_area} className={styles.message_box_input_area}
onChange={onChangeFunction} onChange={onChangeFunction}
onBlur={stopTyping} onBlur={stopTyping}

View File

@@ -1,13 +1,12 @@
.container { .container {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: row; gap: 1rem;
} }
.message_box_wrapper { .message_box_wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
margin-right: 1rem;
padding: 0.8rem; padding: 0.8rem;
background-color: var(--dark_875_color); background-color: var(--dark_875_color);
border: 0.1rem solid var(--dark_750_color); border: 0.1rem solid var(--dark_750_color);

View File

@@ -8,8 +8,8 @@
&.is_compact_mode { &.is_compact_mode {
min-width: auto; min-width: auto;
.scroll_container { .scroll_container {
overflow-y: hidden; // overflow-y: hidden;
width: auto; // width: auto;
} }
} }
} }

View File

@@ -57,7 +57,6 @@
.selected_language { .selected_language {
font-size: 1.2rem; font-size: 1.2rem;
color: var(--dark_basic_text_color);
} }
.arrow_left_svg { .arrow_left_svg {
@@ -66,7 +65,6 @@
margin: 0 0.2rem; margin: 0 0.2rem;
transform: rotate(180deg); transform: rotate(180deg);
width: 1.6rem; width: 1.6rem;
color: var(--dark_basic_text_color);
&.reverse { &.reverse {
transform: rotate(0deg); transform: rotate(0deg);
} }

View File

@@ -31,6 +31,6 @@
font-size: 1.2rem; font-size: 1.2rem;
color: var(--dark_500_color); color: var(--dark_500_color);
&.is_hovered { &.is_hovered {
color: var(--dark_basic_text_color); color: var(--dark_200_color);
} }
} }

View File

@@ -21,7 +21,7 @@
} }
&.is_selected { &.is_selected {
background-color: var(--dark_800_color); background-color: var(--dark_800_color);
color: var(--dark_basic_text_color); color: var(--dark_200_color);
cursor: default; cursor: default;
pointer-events: none; pointer-events: none;
} }

View File

@@ -24,7 +24,6 @@
.label { .label {
font-size: 1.2rem; font-size: 1.2rem;
color: var(--dark_basic_text_color);
white-space: nowrap; white-space: nowrap;
} }
@@ -32,5 +31,5 @@
margin-left: 0.2rem; margin-left: 0.2rem;
padding-bottom: 0.2rem; padding-bottom: 0.2rem;
width: 1.8rem; width: 1.8rem;
color: #cb944f; color: var(--waring_color);
} }

View File

@@ -3,8 +3,7 @@
bottom: 100%; bottom: 100%;
width: 100%; width: 100%;
height: 26rem; height: 26rem;
background-color: (#000000dd); background-color: var(--dark_1000_color_dd);
// background-color: (var(--dark_875_color) + 80);
backdrop-filter: blur(0.1rem); backdrop-filter: blur(0.1rem);
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -75,7 +74,7 @@ $box_size: 6.8rem;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: (#22222233); background-color: var(--dark_1000_color_66);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: start; align-items: start;
@@ -87,5 +86,4 @@ $box_size: 6.8rem;
font-size: 1.4rem; font-size: 1.4rem;
text-align: center; text-align: center;
text-wrap: balance; text-wrap: balance;
color: var(--dark_basic_text_color);
} }

View File

@@ -92,11 +92,11 @@ export const SwitchContainer = ({ switchLabel, switch_id, children, currentState
return ( return (
<div className={getClassNames(styles.switch_container)} <div className={getClassNames(styles.switch_container)}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
onMouseUp={onMouseUp} onMouseUp={onMouseUp}
onClick={toggleFunction} onClick={toggleFunction}
> >
<div className={styles.label_wrapper}> <div className={styles.label_wrapper}>
<SvgComponent className={getClassNames(styles.switch_svg)} /> <SvgComponent className={getClassNames(styles.switch_svg)} />

View File

@@ -22,6 +22,7 @@
} }
&.is_compact_mode { &.is_compact_mode {
padding: 1.5rem; padding: 1.5rem;
justify-content: center;
} }
&.is_pending { &.is_pending {
pointer-events: none; pointer-events: none;
@@ -35,11 +36,9 @@
gap: 0.8rem; gap: 0.8rem;
} }
$basic_label_color: var(--dark_basic_text_color);
$pending_label_color: var(--dark_500_color); $pending_label_color: var(--dark_500_color);
.switch_label { .switch_label {
font-size: 1.4rem; font-size: 1.4rem;
color: $basic_label_color;
&.is_compact_mode { &.is_compact_mode {
display: none; display: none;
} }
@@ -50,7 +49,6 @@ $pending_label_color: var(--dark_500_color);
.switch_svg { .switch_svg {
width: 1.8rem; width: 1.8rem;
color: $basic_label_color;
&.is_pending { &.is_pending {
color: $pending_label_color; color: $pending_label_color;
} }

View File

@@ -66,7 +66,6 @@
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
color: var(--dark_basic_text_color);
} }
.accept_button { .accept_button {
@@ -91,19 +90,19 @@
border-radius: 0.4rem; border-radius: 0.4rem;
} }
&.is_latest_version_already { &.is_latest_version_already {
background-color: var(--dark_800_color); background-color: var(--dark_825_color);
pointer-events: none; pointer-events: none;
} }
} }
} }
.deny_button { .deny_button {
background-color: var(--dark_800_color); background-color: var(--dark_825_color);
&:hover { &:hover {
background-color: var(--dark_700_color); background-color: var(--dark_800_color);
} }
&:active { &:active {
background-color: var(--dark_800_color); background-color: var(--dark_850_color);
} }
} }
@@ -137,7 +136,6 @@
gap: 0.6rem; gap: 0.6rem;
} }
.version_desc_point { .version_desc_point {
background-color: var(--dark_basic_text_color);
width: 0.3rem; width: 0.3rem;
border-radius: 50%; border-radius: 50%;
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
@@ -147,5 +145,4 @@
max-width: 48rem; max-width: 48rem;
// text-align: center; // text-align: center;
font-weight: 300; font-weight: 300;
color: var(--dark_basic_text_color);
} }

View File

@@ -17,17 +17,23 @@ export const SnackbarController = () => {
[styles.is_error]: currentNotificationStatus.data.status === "error", [styles.is_error]: currentNotificationStatus.data.status === "error",
}); });
const settings = currentNotificationStatus.data;
let hide_duration = 5000;
if (settings.options?.hide_duration === null) hide_duration = null;
if (Number(settings.options?.hide_duration)) hide_duration = settings.options.hide_duration;
return ( return (
<div> <div>
<Snackbar <Snackbar
open={currentNotificationStatus.data.is_open} open={settings.is_open}
onClose={handleClose} onClose={handleClose}
TransitionComponent={SlideTransition} TransitionComponent={SlideTransition}
key={currentNotificationStatus.data.key} key={settings.key}
autoHideDuration={5000} autoHideDuration={hide_duration}
> >
<div className={snackbar_classname}> <div className={snackbar_classname}>
<p className={styles.snackbar_message}>{currentNotificationStatus.data.message}</p> <p className={styles.snackbar_message}>{settings.message}</p>
</div> </div>
</Snackbar> </Snackbar>
</div> </div>

View File

@@ -4,10 +4,10 @@
padding: 2rem; padding: 2rem;
color: #fff; color: #fff;
&.is_success { &.is_success {
background-color: #368777; background-color: var(--success_bc_color);
} }
&.is_error { &.is_error {
background-color: #bb4448; background-color: var(--error_bc_color);
} }
} }

View File

@@ -22,13 +22,13 @@
animation: appear .3s ease; animation: appear .3s ease;
} }
& .announcements_link_svg { & .announcements_link_svg {
color: var(--dark_basic_text_color); color: var(--dark_200_color);
} }
} }
&:hover { &:hover {
background-color: var(--dark_825_color); background-color: var(--dark_825_color);
& .announcements_label { & .announcements_label {
color: var(--dark_basic_text_color); color: var(--dark_200_color);
} }
& .announcements_link_svg { & .announcements_link_svg {
color: var(--primary_300_color); color: var(--primary_300_color);
@@ -79,14 +79,14 @@
width: 68px; width: 68px;
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
&:hover { &:hover {
background-color: #bb4448; background-color: var(--error_bc_color);
& .x_mark_svg { & .x_mark_svg {
color: var(--dark_200_color); color: var(--dark_200_color);
transform: rotate(45deg); transform: rotate(45deg);
} }
} }
&:active { &:active {
background-color: #9c3938; background-color: var(--error_bc_active_color);
} }
transition: all 0.1s ease; transition: all 0.1s ease;
} }

View File

@@ -1,4 +1,5 @@
$progress_ease: cubic-bezier(0, 1, 0.75, 1); $progress_ease: cubic-bezier(0, 1, 0.75, 1);
// Duplicated
.container { .container {
position: absolute; position: absolute;

View File

@@ -61,10 +61,10 @@
} }
.close_button { .close_button {
&:hover { &:hover {
background-color: #bb4448; background-color: var(--error_bc_color);
} }
&:active { &:active {
background-color: #9c3938; background-color: var(--error_bc_active_color);
} }
} }

Some files were not shown because too many files have changed in this diff Show More