diff --git a/.gitignore b/.gitignore
index 525719c..ca439dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,188 +1,55 @@
-# Created by .ignore support plugin (hsz.mobi)
-### Python template
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# Custom
-model_small/
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-env/
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*,cover
-.hypothesis/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
+# Logs
+logs
*.log
-local_settings.py
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
-# Flask stuff:
-instance/
-.webassets-cache
+# Editor directories and files
+.vscode
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
-# Scrapy stuff:
-.scrapy
+# Etc
+__other
+psd
+list.py
+tree.txt
-# Sphinx documentation
-docs/_build/
+# Rust / Cargo 🔧
+# Ignore Cargo build output, generated files, and common Rust artifacts
+/target/
+**/target/
+/target/doc/
-# PyBuilder
-target/
+# Binaries and dynamic libraries
+*.exe
+# *.dll
+*.so
+*.dylib
-# IPython Notebook
-.ipynb_checkpoints
+# Static libraries
+*.a
+# *.lib
-# pyenv
-.python-version
+# Rust compilation artifacts
+*.rlib
+*.rmeta
-# celery beat schedule file
-celerybeat-schedule
+# Backup files and editor temp files
+*.rs.bk
-# dotenv
-.env
+# Ignore packaged crates
+*.crate
-# virtualenv
-venv/
-ENV/
-
-# Spyder project settings
-.spyderproject
-
-# Rope project settings
-.ropeproject
-### VirtualEnv template
-# Virtualenv
-# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
-[Bb]in
-[Ii]nclude
-[Ll]ib
-[Ll]ib64
-[Ll]ocal
-[Ss]cripts
-pyvenv.cfg
-.venv
-pip-selfcheck.json
-
-### JetBrains template
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# AWS User-specific
-.idea/**/aws.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-# .idea/artifacts
-# .idea/compiler.xml
-# .idea/jarRepositories.xml
-# .idea/modules.xml
-# .idea/*.iml
-# .idea/modules
-# *.iml
-# *.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# SonarLint plugin
-.idea/sonarlint/
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
-
-# idea folder, uncomment if you don't need it
-# .idea
\ No newline at end of file
+# Ignore AI models
+/resources/models/*
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 7f9be7c..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jarvis.iml b/.idea/jarvis.iml
deleted file mode 100644
index b55f7bb..0000000
--- a/.idea/jarvis.iml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index b1d0f0b..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index fbd2bfc..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..9361469
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,9582 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "ab_glyph"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
+dependencies = [
+ "ab_glyph_rasterizer",
+ "owned_ttf_parser",
+]
+
+[[package]]
+name = "ab_glyph_rasterizer"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.3.4",
+ "once_cell",
+ "serde",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "aligned"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
+dependencies = [
+ "as-slice",
+]
+
+[[package]]
+name = "aligned-vec"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
+dependencies = [
+ "equator",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "alsa"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43"
+dependencies = [
+ "alsa-sys",
+ "bitflags 2.10.0",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "alsa-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "android-activity"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046"
+dependencies = [
+ "android-properties",
+ "bitflags 2.10.0",
+ "cc",
+ "cesu8",
+ "jni",
+ "jni-sys",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "num_enum",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "android-properties"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
+
+[[package]]
+name = "anymap3"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "170433209e817da6aae2c51aa0dd443009a613425dd041ebfb2492d1c4c11a25"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "arc-swap"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "arg_enum_proc_macro"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "array-init"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
+
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "as-raw-xcb-connection"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
+
+[[package]]
+name = "as-slice"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "atk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b"
+dependencies = [
+ "atk-sys",
+ "glib 0.18.5",
+ "libc",
+]
+
+[[package]]
+name = "atk-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086"
+dependencies = [
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "atomic-arena"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73e8ed45f88ed32e6827a96b62d8fd4086d72defc754c5c6bd08470c1aaf648e"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "av-scenechange"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
+dependencies = [
+ "aligned",
+ "anyhow",
+ "arg_enum_proc_macro",
+ "arrayvec",
+ "log",
+ "num-rational",
+ "num-traits",
+ "pastey",
+ "rayon",
+ "thiserror 2.0.17",
+ "v_frame",
+ "y4m",
+]
+
+[[package]]
+name = "av1-grain"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "log",
+ "nom 8.0.0",
+ "num-rational",
+ "v_frame",
+]
+
+[[package]]
+name = "avif-serialize"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "aws-lc-rs"
+version = "1.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86"
+dependencies = [
+ "aws-lc-sys",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8"
+dependencies = [
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
+
+[[package]]
+name = "bit_field"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "bitstream-io"
+version = "4.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757"
+dependencies = [
+ "core2",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block2"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
+dependencies = [
+ "objc2 0.5.2",
+]
+
+[[package]]
+name = "block2"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
+dependencies = [
+ "objc2 0.6.3",
+]
+
+[[package]]
+name = "brotli"
+version = "8.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bstr"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "built"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+
+[[package]]
+name = "bytemuck"
+version = "1.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bytes"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bytesize"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659"
+
+[[package]]
+name = "cairo-rs"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
+dependencies = [
+ "bitflags 2.10.0",
+ "cairo-sys-rs",
+ "glib 0.18.5",
+ "libc",
+ "once_cell",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
+dependencies = [
+ "glib-sys 0.18.1",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "calloop"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
+dependencies = [
+ "bitflags 2.10.0",
+ "log",
+ "polling",
+ "rustix 0.38.44",
+ "slab",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "calloop-wayland-source"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
+dependencies = [
+ "calloop",
+ "rustix 0.38.44",
+ "wayland-backend",
+ "wayland-client",
+]
+
+[[package]]
+name = "camino"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "candle-core"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9f51e2ecf6efe9737af8f993433c839f956d2b6ed4fd2dd4a7c6d8b0fa667ff"
+dependencies = [
+ "byteorder",
+ "gemm 0.17.1",
+ "half",
+ "memmap2",
+ "num-traits",
+ "num_cpus",
+ "rand 0.9.2",
+ "rand_distr",
+ "rayon",
+ "safetensors 0.4.5",
+ "thiserror 1.0.69",
+ "ug",
+ "yoke 0.7.5",
+ "zip",
+]
+
+[[package]]
+name = "candle-nn"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1980d53280c8f9e2c6cbe1785855d7ff8010208b46e21252b978badf13ad69d"
+dependencies = [
+ "candle-core",
+ "half",
+ "num-traits",
+ "rayon",
+ "safetensors 0.4.5",
+ "serde",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.17",
+]
+
+[[package]]
+name = "cargo_toml"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77"
+dependencies = [
+ "serde",
+ "toml 0.9.11+spec-1.1.0",
+]
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfb"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
+dependencies = [
+ "byteorder",
+ "fnv",
+ "uuid",
+]
+
+[[package]]
+name = "cfg-expr"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
+dependencies = [
+ "smallvec",
+ "target-lexicon 0.12.16",
+]
+
+[[package]]
+name = "cfg-expr"
+version = "0.20.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720"
+dependencies = [
+ "smallvec",
+ "target-lexicon 0.13.3",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chrono"
+version = "0.4.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
+dependencies = [
+ "atty",
+ "bitflags 1.3.2",
+ "clap_lex",
+ "indexmap 1.9.3",
+ "once_cell",
+ "strsim 0.10.0",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "compact_str"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "serde",
+ "static_assertions",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "cookie"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
+dependencies = [
+ "time",
+ "version_check",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core-graphics"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation 0.9.4",
+ "core-graphics-types 0.1.3",
+ "foreign-types 0.5.0",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
+dependencies = [
+ "bitflags 2.10.0",
+ "core-foundation 0.10.1",
+ "core-graphics-types 0.2.0",
+ "foreign-types 0.5.0",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation 0.9.4",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
+dependencies = [
+ "bitflags 2.10.0",
+ "core-foundation 0.10.1",
+ "libc",
+]
+
+[[package]]
+name = "core2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "coreaudio-rs"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aae284fbaf7d27aa0e292f7677dfbe26503b0d555026f702940805a630eac17"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+ "objc2-audio-toolbox",
+ "objc2-core-audio",
+ "objc2-core-audio-types",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "cpal"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbd307f43cc2a697e2d1f8bc7a1d824b5269e052209e28883e5bc04d095aaa3f"
+dependencies = [
+ "alsa",
+ "coreaudio-rs",
+ "dasp_sample",
+ "jni",
+ "js-sys",
+ "libc",
+ "mach2",
+ "ndk",
+ "ndk-context",
+ "num-derive",
+ "num-traits",
+ "objc2-audio-toolbox",
+ "objc2-core-audio",
+ "objc2-core-audio-types",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows 0.54.0",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cssparser"
+version = "0.29.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa"
+dependencies = [
+ "cssparser-macros",
+ "dtoa-short",
+ "itoa",
+ "matches",
+ "phf 0.10.1",
+ "proc-macro2",
+ "quote",
+ "smallvec",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "cssparser-macros"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
+dependencies = [
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "ctor"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
+dependencies = [
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "cursor-icon"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
+
+[[package]]
+name = "darling"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+dependencies = [
+ "darling_core 0.20.11",
+ "darling_macro 0.20.11",
+]
+
+[[package]]
+name = "darling"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
+dependencies = [
+ "darling_core 0.21.3",
+ "darling_macro 0.21.3",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.11.1",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.11.1",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+dependencies = [
+ "darling_core 0.20.11",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
+dependencies = [
+ "darling_core 0.21.3",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "dary_heap"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "dashmap"
+version = "5.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
+dependencies = [
+ "cfg-if",
+ "hashbrown 0.14.5",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "dasp"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7381b67da416b639690ac77c73b86a7b5e64a29e31d1f75fb3b1102301ef355a"
+dependencies = [
+ "dasp_envelope",
+ "dasp_frame",
+ "dasp_interpolate",
+ "dasp_peak",
+ "dasp_ring_buffer",
+ "dasp_rms",
+ "dasp_sample",
+ "dasp_signal",
+ "dasp_slice",
+ "dasp_window",
+]
+
+[[package]]
+name = "dasp_envelope"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ec617ce7016f101a87fe85ed44180839744265fae73bb4aa43e7ece1b7668b6"
+dependencies = [
+ "dasp_frame",
+ "dasp_peak",
+ "dasp_ring_buffer",
+ "dasp_rms",
+ "dasp_sample",
+]
+
+[[package]]
+name = "dasp_frame"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6"
+dependencies = [
+ "dasp_sample",
+]
+
+[[package]]
+name = "dasp_interpolate"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fc975a6563bb7ca7ec0a6c784ead49983a21c24835b0bc96eea11ee407c7486"
+dependencies = [
+ "dasp_frame",
+ "dasp_ring_buffer",
+ "dasp_sample",
+]
+
+[[package]]
+name = "dasp_peak"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cf88559d79c21f3d8523d91250c397f9a15b5fc72fbb3f87fdb0a37b79915bf"
+dependencies = [
+ "dasp_frame",
+ "dasp_sample",
+]
+
+[[package]]
+name = "dasp_ring_buffer"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07d79e19b89618a543c4adec9c5a347fe378a19041699b3278e616e387511ea1"
+
+[[package]]
+name = "dasp_rms"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6c5dcb30b7e5014486e2822537ea2beae50b19722ffe2ed7549ab03774575aa"
+dependencies = [
+ "dasp_frame",
+ "dasp_ring_buffer",
+ "dasp_sample",
+]
+
+[[package]]
+name = "dasp_sample"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
+
+[[package]]
+name = "dasp_signal"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa1ab7d01689c6ed4eae3d38fe1cea08cba761573fbd2d592528d55b421077e7"
+dependencies = [
+ "dasp_envelope",
+ "dasp_frame",
+ "dasp_interpolate",
+ "dasp_peak",
+ "dasp_ring_buffer",
+ "dasp_rms",
+ "dasp_sample",
+ "dasp_window",
+]
+
+[[package]]
+name = "dasp_slice"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e1c7335d58e7baedafa516cb361360ff38d6f4d3f9d9d5ee2a2fc8e27178fa1"
+dependencies = [
+ "dasp_frame",
+ "dasp_sample",
+]
+
+[[package]]
+name = "dasp_window"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99ded7b88821d2ce4e8b842c9f1c86ac911891ab89443cc1de750cae764c5076"
+dependencies = [
+ "dasp_sample",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
+
+[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
+dependencies = [
+ "powerfmt",
+ "serde_core",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
+dependencies = [
+ "darling 0.20.11",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
+dependencies = [
+ "derive_builder_core",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.114",
+ "unicode-xid",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-next"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users 0.5.2",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users 0.4.6",
+ "winapi",
+]
+
+[[package]]
+name = "dispatch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+
+[[package]]
+name = "dispatch2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.6.2",
+ "libc",
+ "objc2 0.6.3",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "dlib"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+dependencies = [
+ "libloading 0.8.9",
+]
+
+[[package]]
+name = "dlopen2"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4"
+dependencies = [
+ "dlopen2_derive",
+ "libc",
+ "once_cell",
+ "winapi",
+]
+
+[[package]]
+name = "dlopen2_derive"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
+[[package]]
+name = "dpi"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "dtoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590"
+
+[[package]]
+name = "dtoa-short"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
+dependencies = [
+ "dtoa",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+
+[[package]]
+name = "dyn-stack"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e53799688f5632f364f8fb387488dd05db9fe45db7011be066fc20e7027f8b"
+dependencies = [
+ "bytemuck",
+ "reborrow",
+]
+
+[[package]]
+name = "dyn-stack"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c4713e43e2886ba72b8271aa66c93d722116acf7a75555cce11dcde84388fe8"
+dependencies = [
+ "bytemuck",
+ "dyn-stack-macros",
+]
+
+[[package]]
+name = "dyn-stack-macros"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9"
+
+[[package]]
+name = "easyfft"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "767e39eef2ad8a3b6f1d733be3ec70364d21d437d06d4f18ea76ce08df20b75f"
+dependencies = [
+ "array-init",
+ "generic_singleton",
+ "num-complex",
+ "realfft",
+ "rustfft",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "embed-resource"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e"
+dependencies = [
+ "cc",
+ "memchr",
+ "rustc_version",
+ "toml 0.9.11+spec-1.1.0",
+ "vswhom",
+ "winreg",
+]
+
+[[package]]
+name = "embed_plist"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "enum-as-inner"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "env_filter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_home"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
+
+[[package]]
+name = "env_logger"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
+[[package]]
+name = "equator"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
+dependencies = [
+ "equator-macro",
+]
+
+[[package]]
+name = "equator-macro"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "erased-serde"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3"
+dependencies = [
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "esaxx-rs"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6"
+
+[[package]]
+name = "exr"
+version = "1.74.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
+dependencies = [
+ "bit_field",
+ "half",
+ "lebe",
+ "miniz_oxide",
+ "rayon-core",
+ "smallvec",
+ "zune-inflate",
+]
+
+[[package]]
+name = "extended"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365"
+
+[[package]]
+name = "fastembed"
+version = "5.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59a3f841f27a44bcc32214f8df75cc9b6cea55dbbebbfe546735690eab5bb2d2"
+dependencies = [
+ "anyhow",
+ "ndarray",
+ "ort",
+ "safetensors 0.7.0",
+ "serde",
+ "serde_json",
+ "tokenizers",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "fax"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
+dependencies = [
+ "fax_derive",
+]
+
+[[package]]
+name = "fax_derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "field-offset"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
+dependencies = [
+ "memoffset",
+ "rustc_version",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
+
+[[package]]
+name = "flate2"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fluent"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477"
+dependencies = [
+ "fluent-bundle",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-bundle"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4"
+dependencies = [
+ "fluent-langneg",
+ "fluent-syntax",
+ "intl-memoizer",
+ "intl_pluralrules",
+ "rustc-hash",
+ "self_cell",
+ "smallvec",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-langneg"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-syntax"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198"
+dependencies = [
+ "memchr",
+ "thiserror 2.0.17",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared 0.1.1",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared 0.3.1",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
+[[package]]
+name = "futf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
+dependencies = [
+ "mac",
+ "new_debug_unreachable",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "gdk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691"
+dependencies = [
+ "cairo-rs",
+ "gdk-pixbuf",
+ "gdk-sys",
+ "gio",
+ "glib 0.18.5",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gdk-pixbuf"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec"
+dependencies = [
+ "gdk-pixbuf-sys",
+ "gio",
+ "glib 0.18.5",
+ "libc",
+ "once_cell",
+]
+
+[[package]]
+name = "gdk-pixbuf-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7"
+dependencies = [
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "gdk-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "pango-sys",
+ "pkg-config",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "gdkwayland-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69"
+dependencies = [
+ "gdk-sys",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "pkg-config",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "gdkx11"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe"
+dependencies = [
+ "gdk",
+ "gdkx11-sys",
+ "gio",
+ "glib 0.18.5",
+ "libc",
+ "x11",
+]
+
+[[package]]
+name = "gdkx11-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d"
+dependencies = [
+ "gdk-sys",
+ "glib-sys 0.18.1",
+ "libc",
+ "system-deps 6.2.2",
+ "x11",
+]
+
+[[package]]
+name = "gemm"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32"
+dependencies = [
+ "dyn-stack 0.10.0",
+ "gemm-c32 0.17.1",
+ "gemm-c64 0.17.1",
+ "gemm-common 0.17.1",
+ "gemm-f16 0.17.1",
+ "gemm-f32 0.17.1",
+ "gemm-f64 0.17.1",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 10.7.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451"
+dependencies = [
+ "dyn-stack 0.13.2",
+ "gemm-c32 0.18.2",
+ "gemm-c64 0.18.2",
+ "gemm-common 0.18.2",
+ "gemm-f16 0.18.2",
+ "gemm-f32 0.18.2",
+ "gemm-f64 0.18.2",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 11.6.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-c32"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0"
+dependencies = [
+ "dyn-stack 0.10.0",
+ "gemm-common 0.17.1",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 10.7.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-c32"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847"
+dependencies = [
+ "dyn-stack 0.13.2",
+ "gemm-common 0.18.2",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 11.6.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-c64"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a"
+dependencies = [
+ "dyn-stack 0.10.0",
+ "gemm-common 0.17.1",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 10.7.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-c64"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf"
+dependencies = [
+ "dyn-stack 0.13.2",
+ "gemm-common 0.18.2",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 11.6.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-common"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8"
+dependencies = [
+ "bytemuck",
+ "dyn-stack 0.10.0",
+ "half",
+ "num-complex",
+ "num-traits",
+ "once_cell",
+ "paste",
+ "pulp 0.18.22",
+ "raw-cpuid 10.7.0",
+ "rayon",
+ "seq-macro",
+ "sysctl 0.5.5",
+]
+
+[[package]]
+name = "gemm-common"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3"
+dependencies = [
+ "bytemuck",
+ "dyn-stack 0.13.2",
+ "half",
+ "libm",
+ "num-complex",
+ "num-traits",
+ "once_cell",
+ "paste",
+ "pulp 0.21.5",
+ "raw-cpuid 11.6.0",
+ "rayon",
+ "seq-macro",
+ "sysctl 0.6.0",
+]
+
+[[package]]
+name = "gemm-f16"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4"
+dependencies = [
+ "dyn-stack 0.10.0",
+ "gemm-common 0.17.1",
+ "gemm-f32 0.17.1",
+ "half",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 10.7.0",
+ "rayon",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-f16"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109"
+dependencies = [
+ "dyn-stack 0.13.2",
+ "gemm-common 0.18.2",
+ "gemm-f32 0.18.2",
+ "half",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 11.6.0",
+ "rayon",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-f32"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113"
+dependencies = [
+ "dyn-stack 0.10.0",
+ "gemm-common 0.17.1",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 10.7.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-f32"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864"
+dependencies = [
+ "dyn-stack 0.13.2",
+ "gemm-common 0.18.2",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 11.6.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-f64"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0"
+dependencies = [
+ "dyn-stack 0.10.0",
+ "gemm-common 0.17.1",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 10.7.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "gemm-f64"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd"
+dependencies = [
+ "dyn-stack 0.13.2",
+ "gemm-common 0.18.2",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "raw-cpuid 11.6.0",
+ "seq-macro",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "generic_singleton"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2d5de0fc83987dac514f3b910c5d08392b220efe8cf72086c660029a197bf73"
+dependencies = [
+ "anymap3",
+ "lazy_static",
+ "parking_lot",
+]
+
+[[package]]
+name = "gethostname"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
+dependencies = [
+ "rustix 1.1.3",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi",
+ "wasip2",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gif"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "gio"
+version = "0.18.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "gio-sys 0.18.1",
+ "glib 0.18.5",
+ "libc",
+ "once_cell",
+ "pin-project-lite",
+ "smallvec",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
+dependencies = [
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps 6.2.2",
+ "winapi",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22"
+dependencies = [
+ "glib-sys 0.21.5",
+ "gobject-sys 0.21.5",
+ "libc",
+ "system-deps 7.0.7",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "glam"
+version = "0.30.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9"
+dependencies = [
+ "mint",
+]
+
+[[package]]
+name = "glib"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
+dependencies = [
+ "bitflags 2.10.0",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys 0.18.1",
+ "glib-macros 0.18.5",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "memchr",
+ "once_cell",
+ "smallvec",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "glib"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b"
+dependencies = [
+ "bitflags 2.10.0",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys 0.21.5",
+ "glib-macros 0.21.5",
+ "glib-sys 0.21.5",
+ "gobject-sys 0.21.5",
+ "libc",
+ "memchr",
+ "smallvec",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro-crate 2.0.2",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro-crate 3.4.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
+dependencies = [
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c"
+dependencies = [
+ "libc",
+ "system-deps 7.0.7",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
+[[package]]
+name = "gobject-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
+dependencies = [
+ "glib-sys 0.18.1",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294"
+dependencies = [
+ "glib-sys 0.21.5",
+ "libc",
+ "system-deps 7.0.7",
+]
+
+[[package]]
+name = "gtk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a"
+dependencies = [
+ "atk",
+ "cairo-rs",
+ "field-offset",
+ "futures-channel",
+ "gdk",
+ "gdk-pixbuf",
+ "gio",
+ "glib 0.18.5",
+ "gtk-sys",
+ "gtk3-macros",
+ "libc",
+ "pango",
+ "pkg-config",
+]
+
+[[package]]
+name = "gtk-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414"
+dependencies = [
+ "atk-sys",
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk-sys",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "pango-sys",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "gtk3-macros"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "h2"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap 2.13.0",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "half"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
+dependencies = [
+ "bytemuck",
+ "cfg-if",
+ "crunchy",
+ "num-traits",
+ "rand 0.9.2",
+ "rand_distr",
+ "zerocopy",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac-sha256"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0f0ae375a85536cac3a243e3a9cda80a47910348abdea7e2c22f8ec556d586d"
+
+[[package]]
+name = "hound"
+version = "3.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
+
+[[package]]
+name = "html5ever"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "match_token",
+]
+
+[[package]]
+name = "http"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "humantime"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
+
+[[package]]
+name = "hyper"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "pin-utils",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "system-configuration",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "windows-registry",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core 0.62.2",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ico"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98"
+dependencies = [
+ "byteorder",
+ "png 0.17.16",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke 0.8.1",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+
+[[package]]
+name = "icu_properties"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+
+[[package]]
+name = "icu_provider"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke 0.8.1",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "image"
+version = "0.25.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "color_quant",
+ "exr",
+ "gif",
+ "image-webp",
+ "moxcms",
+ "num-traits",
+ "png 0.18.0",
+ "qoi",
+ "ravif",
+ "rayon",
+ "rgb",
+ "tiff",
+ "zune-core 0.5.1",
+ "zune-jpeg 0.5.9",
+]
+
+[[package]]
+name = "image-webp"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
+dependencies = [
+ "byteorder-lite",
+ "quick-error",
+]
+
+[[package]]
+name = "imgref"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "infer"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "intent-classifier"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d4532decd2fe3806d9e068b57ceee827c7f1ab7b4ce4b2ed4c8c80679b7eefa"
+dependencies = [
+ "ahash",
+ "chrono",
+ "dashmap",
+ "regex",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+ "tokio",
+ "tracing",
+ "unicode-segmentation",
+ "uuid",
+]
+
+[[package]]
+name = "interpolate_name"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "intl-memoizer"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f"
+dependencies = [
+ "type-map",
+ "unic-langid",
+]
+
+[[package]]
+name = "intl_pluralrules"
+version = "7.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+
+[[package]]
+name = "iri-string"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "is-docker"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "is-wsl"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
+dependencies = [
+ "is-docker",
+ "once_cell",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+
+[[package]]
+name = "jarvis-app"
+version = "0.1.0"
+dependencies = [
+ "glib 0.21.5",
+ "gtk",
+ "image",
+ "jarvis-core",
+ "log",
+ "once_cell",
+ "parking_lot",
+ "platform-dirs",
+ "rand 0.8.5",
+ "simple-log",
+ "tokio",
+ "tray-icon",
+ "winapi",
+ "winit",
+]
+
+[[package]]
+name = "jarvis-cli"
+version = "0.1.0"
+dependencies = [
+ "env_logger",
+ "jarvis-core",
+ "log",
+ "parking_lot",
+ "tokio",
+]
+
+[[package]]
+name = "jarvis-core"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "fastembed",
+ "fluent",
+ "fluent-bundle",
+ "futures-util",
+ "hound",
+ "intent-classifier",
+ "kira",
+ "log",
+ "mlua",
+ "ndarray",
+ "nnnoiseless",
+ "once_cell",
+ "ort",
+ "parking_lot",
+ "platform-dirs",
+ "pv_recorder",
+ "rand 0.8.5",
+ "regex",
+ "reqwest 0.13.1",
+ "rodio",
+ "rustpotter",
+ "seqdiff",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "sha2",
+ "sys-locale",
+ "tempfile",
+ "tokenizers",
+ "tokio",
+ "tokio-tungstenite",
+ "toml 0.9.11+spec-1.1.0",
+ "unic-langid",
+ "vosk",
+ "winrt-notification",
+]
+
+[[package]]
+name = "jarvis-gui"
+version = "0.1.0"
+dependencies = [
+ "jarvis-core",
+ "lazy_static",
+ "log",
+ "once_cell",
+ "parking_lot",
+ "peak_alloc",
+ "platform-dirs",
+ "serde",
+ "serde_json",
+ "simple-log",
+ "sysinfo",
+ "systemstat",
+ "tauri",
+ "tauri-build",
+ "tauri-plugin-dialog",
+ "tauri-plugin-fs",
+ "tauri-plugin-shell",
+]
+
+[[package]]
+name = "javascriptcore-rs"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc"
+dependencies = [
+ "bitflags 1.3.2",
+ "glib 0.18.5",
+ "javascriptcore-rs-sys",
+]
+
+[[package]]
+name = "javascriptcore-rs-sys"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124"
+dependencies = [
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "jiff"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50"
+dependencies = [
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.4",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-patch"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08"
+dependencies = [
+ "jsonptr",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "jsonptr"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "keyboard-types"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
+dependencies = [
+ "bitflags 2.10.0",
+ "serde",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "kira"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8b2ed698ad54d270ee1d8b340604201bf538dacf8bf31e378da00690b9ea20a"
+dependencies = [
+ "atomic-arena",
+ "cpal",
+ "glam",
+ "mint",
+ "pastey",
+ "rtrb",
+ "send_wrapper",
+ "symphonia",
+ "triple_buffer",
+]
+
+[[package]]
+name = "kuchikiki"
+version = "0.8.8-speedreader"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2"
+dependencies = [
+ "cssparser",
+ "html5ever",
+ "indexmap 2.13.0",
+ "selectors",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lebe"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
+
+[[package]]
+name = "libappindicator"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a"
+dependencies = [
+ "glib 0.18.5",
+ "gtk",
+ "gtk-sys",
+ "libappindicator-sys",
+ "log",
+]
+
+[[package]]
+name = "libappindicator-sys"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
+dependencies = [
+ "gtk-sys",
+ "libloading 0.7.4",
+ "once_cell",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.180"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
+
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
+dependencies = [
+ "arbitrary",
+ "cc",
+]
+
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "libredox"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
+dependencies = [
+ "bitflags 2.10.0",
+ "libc",
+ "redox_syscall 0.7.0",
+]
+
+[[package]]
+name = "libxdo"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db"
+dependencies = [
+ "libxdo-sys",
+]
+
+[[package]]
+name = "libxdo-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212"
+dependencies = [
+ "libc",
+ "x11",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+[[package]]
+name = "litemap"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "log-mdc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7"
+
+[[package]]
+name = "log4rs"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e947bb896e702c711fccc2bf02ab2abb6072910693818d1d6b07ee2b9dfd86c"
+dependencies = [
+ "anyhow",
+ "arc-swap",
+ "chrono",
+ "derive_more 2.1.1",
+ "flate2",
+ "fnv",
+ "humantime",
+ "libc",
+ "log",
+ "log-mdc",
+ "mock_instant",
+ "parking_lot",
+ "rand 0.9.2",
+ "serde",
+ "serde-value",
+ "serde_json",
+ "serde_yaml",
+ "thiserror 2.0.17",
+ "thread-id",
+ "unicode-segmentation",
+ "winapi",
+]
+
+[[package]]
+name = "loop9"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
+dependencies = [
+ "imgref",
+]
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "lua-src"
+version = "550.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e836dc8ae16806c9bdcf42003a88da27d163433e3f9684c52f0301258004a4fb"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "luajit-src"
+version = "210.6.6+707c12b"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a86cc925d4053d0526ae7f5bc765dbd0d7a5d1a63d43974f4966cb349ca63295"
+dependencies = [
+ "cc",
+ "which",
+]
+
+[[package]]
+name = "lzma-rust2"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69"
+
+[[package]]
+name = "mac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+
+[[package]]
+name = "mach2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "macro_rules_attribute"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520"
+dependencies = [
+ "macro_rules_attribute-proc_macro",
+ "paste",
+]
+
+[[package]]
+name = "macro_rules_attribute-proc_macro"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
+
+[[package]]
+name = "markup5ever"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18"
+dependencies = [
+ "log",
+ "phf 0.11.3",
+ "phf_codegen 0.11.3",
+ "string_cache",
+ "string_cache_codegen",
+ "tendril",
+]
+
+[[package]]
+name = "match_token"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+
+[[package]]
+name = "matrixmultiply"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
+dependencies = [
+ "autocfg",
+ "rawpointer",
+]
+
+[[package]]
+name = "maybe-rayon"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
+dependencies = [
+ "cfg-if",
+ "rayon",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+[[package]]
+name = "memmap2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490"
+dependencies = [
+ "libc",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mint"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
+
+[[package]]
+name = "mio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+dependencies = [
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "mlua"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccd36acfa49ce6ee56d1307a061dd302c564eee757e6e4cd67eb4f7204846fab"
+dependencies = [
+ "bstr",
+ "either",
+ "erased-serde",
+ "futures-util",
+ "libc",
+ "mlua-sys",
+ "num-traits",
+ "parking_lot",
+ "rustc-hash",
+ "rustversion",
+ "serde",
+ "serde-value",
+]
+
+[[package]]
+name = "mlua-sys"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f1c3a7fc7580227ece249fd90aa2fa3b39eb2b49d3aec5e103b3e85f2c3dfc8"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "libc",
+ "lua-src",
+ "luajit-src",
+ "pkg-config",
+]
+
+[[package]]
+name = "mock_instant"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6"
+
+[[package]]
+name = "monostate"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67"
+dependencies = [
+ "monostate-impl",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "monostate-impl"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "moxcms"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
+dependencies = [
+ "num-traits",
+ "pxfm",
+]
+
+[[package]]
+name = "muda"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a"
+dependencies = [
+ "crossbeam-channel",
+ "dpi",
+ "gtk",
+ "keyboard-types",
+ "libxdo",
+ "objc2 0.6.3",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "once_cell",
+ "png 0.17.16",
+ "serde",
+ "thiserror 2.0.17",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe 0.1.6",
+ "openssl-sys",
+ "schannel",
+ "security-framework 2.11.1",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "ndarray"
+version = "0.17.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d"
+dependencies = [
+ "matrixmultiply",
+ "num-complex",
+ "num-integer",
+ "num-traits",
+ "portable-atomic",
+ "portable-atomic-util",
+ "rawpointer",
+]
+
+[[package]]
+name = "ndk"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
+dependencies = [
+ "bitflags 2.10.0",
+ "jni-sys",
+ "log",
+ "ndk-sys",
+ "num_enum",
+ "raw-window-handle",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-sys"
+version = "0.6.0+11769913"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+
+[[package]]
+name = "nnnoiseless"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "805d5964d1e7a0006a7fdced7dae75084d66d18b35f1dfe81bd76929b1f8da0c"
+dependencies = [
+ "anyhow",
+ "clap",
+ "dasp",
+ "dasp_interpolate",
+ "dasp_ring_buffer",
+ "easyfft",
+ "hound",
+ "once_cell",
+]
+
+[[package]]
+name = "nodrop"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "noop_proc_macro"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
+
+[[package]]
+name = "ntapi"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "bytemuck",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
+dependencies = [
+ "hermit-abi 0.5.2",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
+dependencies = [
+ "num_enum_derive",
+ "rustversion",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
+dependencies = [
+ "proc-macro-crate 3.4.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "objc-sys"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
+
+[[package]]
+name = "objc2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
+dependencies = [
+ "objc-sys",
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
+dependencies = [
+ "objc2-encode",
+ "objc2-exception-helper",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.5.1",
+ "libc",
+ "objc2 0.5.2",
+ "objc2-core-data 0.2.2",
+ "objc2-core-image 0.2.2",
+ "objc2-foundation 0.2.2",
+ "objc2-quartz-core 0.2.2",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.6.2",
+ "libc",
+ "objc2 0.6.3",
+ "objc2-cloud-kit 0.3.2",
+ "objc2-core-data 0.3.2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-core-image 0.3.2",
+ "objc2-core-text",
+ "objc2-core-video",
+ "objc2-foundation 0.3.2",
+ "objc2-quartz-core 0.3.2",
+]
+
+[[package]]
+name = "objc2-audio-toolbox"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
+dependencies = [
+ "bitflags 2.10.0",
+ "libc",
+ "objc2 0.6.3",
+ "objc2-core-audio",
+ "objc2-core-audio-types",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+]
+
+[[package]]
+name = "objc2-cloud-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-core-location",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-cloud-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
+dependencies = [
+ "bitflags 2.10.0",
+ "objc2 0.6.3",
+ "objc2-foundation 0.3.2",
+]
+
+[[package]]
+name = "objc2-contacts"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
+dependencies = [
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-core-audio"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
+dependencies = [
+ "dispatch2",
+ "objc2 0.6.3",
+ "objc2-core-audio-types",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-core-audio-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
+dependencies = [
+ "bitflags 2.10.0",
+ "objc2 0.6.3",
+]
+
+[[package]]
+name = "objc2-core-data"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-core-data"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
+dependencies = [
+ "bitflags 2.10.0",
+ "objc2 0.6.3",
+ "objc2-foundation 0.3.2",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags 2.10.0",
+ "dispatch2",
+ "objc2 0.6.3",
+]
+
+[[package]]
+name = "objc2-core-graphics"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
+dependencies = [
+ "bitflags 2.10.0",
+ "dispatch2",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+ "objc2-io-surface",
+]
+
+[[package]]
+name = "objc2-core-image"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
+dependencies = [
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+ "objc2-metal",
+]
+
+[[package]]
+name = "objc2-core-image"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
+dependencies = [
+ "objc2 0.6.3",
+ "objc2-foundation 0.3.2",
+]
+
+[[package]]
+name = "objc2-core-location"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
+dependencies = [
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-contacts",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-core-text"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
+dependencies = [
+ "bitflags 2.10.0",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+]
+
+[[package]]
+name = "objc2-core-video"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
+dependencies = [
+ "bitflags 2.10.0",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-io-surface",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+[[package]]
+name = "objc2-exception-helper"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "objc2-foundation"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.5.1",
+ "dispatch",
+ "libc",
+ "objc2 0.5.2",
+]
+
+[[package]]
+name = "objc2-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.6.2",
+ "libc",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-io-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
+dependencies = [
+ "libc",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-io-surface"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
+dependencies = [
+ "bitflags 2.10.0",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-javascript-core"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586"
+dependencies = [
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-link-presentation"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
+dependencies = [
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-app-kit 0.2.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-metal"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-quartz-core"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+ "objc2-metal",
+]
+
+[[package]]
+name = "objc2-quartz-core"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
+dependencies = [
+ "bitflags 2.10.0",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+]
+
+[[package]]
+name = "objc2-security"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a"
+dependencies = [
+ "bitflags 2.10.0",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-symbols"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
+dependencies = [
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-ui-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-cloud-kit 0.2.2",
+ "objc2-core-data 0.2.2",
+ "objc2-core-image 0.2.2",
+ "objc2-core-location",
+ "objc2-foundation 0.2.2",
+ "objc2-link-presentation",
+ "objc2-quartz-core 0.2.2",
+ "objc2-symbols",
+ "objc2-uniform-type-identifiers",
+ "objc2-user-notifications",
+]
+
+[[package]]
+name = "objc2-ui-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
+dependencies = [
+ "bitflags 2.10.0",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+]
+
+[[package]]
+name = "objc2-uniform-type-identifiers"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
+dependencies = [
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-user-notifications"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.5.1",
+ "objc2 0.5.2",
+ "objc2-core-location",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-web-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.6.2",
+ "objc2 0.6.3",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "objc2-javascript-core",
+ "objc2-security",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "onig"
+version = "6.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0"
+dependencies = [
+ "bitflags 2.10.0",
+ "libc",
+ "once_cell",
+ "onig_sys",
+]
+
+[[package]]
+name = "onig_sys"
+version = "69.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "open"
+version = "5.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc"
+dependencies = [
+ "dunce",
+ "is-wsl",
+ "libc",
+ "pathdiff",
+]
+
+[[package]]
+name = "openssl"
+version = "0.10.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
+dependencies = [
+ "bitflags 2.10.0",
+ "cfg-if",
+ "foreign-types 0.3.2",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-probe"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "orbclient"
+version = "0.3.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd"
+dependencies = [
+ "libc",
+ "libredox",
+]
+
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "ort"
+version = "2.0.0-rc.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5df903c0d2c07b56950f1058104ab0c8557159f2741782223704de9be73c3c"
+dependencies = [
+ "ndarray",
+ "ort-sys",
+ "smallvec",
+ "tracing",
+ "ureq",
+]
+
+[[package]]
+name = "ort-sys"
+version = "2.0.0-rc.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06503bb33f294c5f1ba484011e053bfa6ae227074bdb841e9863492dc5960d4b"
+dependencies = [
+ "hmac-sha256",
+ "lzma-rust2",
+ "ureq",
+]
+
+[[package]]
+name = "os_pipe"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
+
+[[package]]
+name = "owned_ttf_parser"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
+dependencies = [
+ "ttf-parser",
+]
+
+[[package]]
+name = "pango"
+version = "0.18.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4"
+dependencies = [
+ "gio",
+ "glib 0.18.5",
+ "libc",
+ "once_cell",
+ "pango-sys",
+]
+
+[[package]]
+name = "pango-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
+dependencies = [
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.5.18",
+ "smallvec",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pastey"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
+
+[[package]]
+name = "pathdiff"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
+
+[[package]]
+name = "peak_alloc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccc90935a8dd139fdf341762773687a1e361d3f54b396a55d9dd1f7001e484bb"
+
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_shared 0.8.0",
+]
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_macros 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_macros 0.11.3",
+ "phf_shared 0.11.3",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
+dependencies = [
+ "phf_generator 0.8.0",
+ "phf_shared 0.8.0",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
+dependencies = [
+ "phf_generator 0.11.3",
+ "phf_shared 0.11.3",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+dependencies = [
+ "phf_shared 0.8.0",
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared 0.10.0",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
+dependencies = [
+ "phf_shared 0.11.3",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
+dependencies = [
+ "phf_generator 0.11.3",
+ "phf_shared 0.11.3",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher 0.3.11",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher 0.3.11",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+dependencies = [
+ "siphasher 1.0.1",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "platform-dirs"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e188d043c1a692985f78b5464853a263f1a27e5bd6322bad3a4078ee3c998a38"
+dependencies = [
+ "dirs-next",
+]
+
+[[package]]
+name = "plist"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
+dependencies = [
+ "base64 0.22.1",
+ "indexmap 2.13.0",
+ "quick-xml",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "png"
+version = "0.17.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "png"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
+dependencies = [
+ "bitflags 2.10.0",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi 0.5.2",
+ "pin-project-lite",
+ "rustix 1.1.3",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "primal-check"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08"
+dependencies = [
+ "num-integer",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit 0.19.15",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24"
+dependencies = [
+ "toml_datetime 0.6.3",
+ "toml_edit 0.20.2",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
+dependencies = [
+ "toml_edit 0.23.10+spec-1.0.0",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.20+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "profiling"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
+dependencies = [
+ "profiling-procmacros",
+]
+
+[[package]]
+name = "profiling-procmacros"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
+dependencies = [
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "pulp"
+version = "0.18.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0a01a0dc67cf4558d279f0c25b0962bd08fc6dec0137699eae304103e882fe6"
+dependencies = [
+ "bytemuck",
+ "libm",
+ "num-complex",
+ "reborrow",
+]
+
+[[package]]
+name = "pulp"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96b86df24f0a7ddd5e4b95c94fc9ed8a98f1ca94d3b01bdce2824097e7835907"
+dependencies = [
+ "bytemuck",
+ "cfg-if",
+ "libm",
+ "num-complex",
+ "reborrow",
+ "version_check",
+]
+
+[[package]]
+name = "pv_recorder"
+version = "1.2.7"
+source = "git+https://github.com/Priler/pvrecorder#33dfcdf7ae121dd4ff5b1b1ed61eda43e5f85b7f"
+dependencies = [
+ "libc",
+ "libloading 0.8.9",
+]
+
+[[package]]
+name = "pxfm"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "qoi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quick-xml"
+version = "0.38.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror 2.0.17",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
+dependencies = [
+ "aws-lc-rs",
+ "bytes",
+ "getrandom 0.3.4",
+ "lru-slab",
+ "rand 0.9.2",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.17",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.17",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "rand_distr"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463"
+dependencies = [
+ "num-traits",
+ "rand 0.9.2",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rav1e"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
+dependencies = [
+ "aligned-vec",
+ "arbitrary",
+ "arg_enum_proc_macro",
+ "arrayvec",
+ "av-scenechange",
+ "av1-grain",
+ "bitstream-io",
+ "built",
+ "cfg-if",
+ "interpolate_name",
+ "itertools",
+ "libc",
+ "libfuzzer-sys",
+ "log",
+ "maybe-rayon",
+ "new_debug_unreachable",
+ "noop_proc_macro",
+ "num-derive",
+ "num-traits",
+ "paste",
+ "profiling",
+ "rand 0.9.2",
+ "rand_chacha 0.9.0",
+ "simd_helpers",
+ "thiserror 2.0.17",
+ "v_frame",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "ravif"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285"
+dependencies = [
+ "avif-serialize",
+ "imgref",
+ "loop9",
+ "quick-error",
+ "rav1e",
+ "rayon",
+ "rgb",
+]
+
+[[package]]
+name = "raw-cpuid"
+version = "10.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "raw-cpuid"
+version = "11.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
+dependencies = [
+ "bitflags 2.10.0",
+]
+
+[[package]]
+name = "raw-window-handle"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
+
+[[package]]
+name = "rawpointer"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
+
+[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-cond"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f"
+dependencies = [
+ "either",
+ "itertools",
+ "rayon",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "realfft"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677"
+dependencies = [
+ "rustfft",
+]
+
+[[package]]
+name = "reborrow"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430"
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.10.0",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
+dependencies = [
+ "bitflags 2.10.0",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
+dependencies = [
+ "getrandom 0.2.17",
+ "libredox",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
+dependencies = [
+ "getrandom 0.2.17",
+ "libredox",
+ "thiserror 2.0.17",
+]
+
+[[package]]
+name = "ref-cast"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+
+[[package]]
+name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "encoding_rs",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "rustls-platform-verifier",
+ "serde",
+ "serde_json",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "rfd"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672"
+dependencies = [
+ "block2 0.6.2",
+ "dispatch2",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "gtk-sys",
+ "js-sys",
+ "log",
+ "objc2 0.6.3",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "raw-window-handle",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "rgb"
+version = "0.8.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rodio"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e40ecf59e742e03336be6a3d53755e789fd05a059fa22dfa0ed624722319e183"
+dependencies = [
+ "cpal",
+ "dasp_sample",
+ "num-rational",
+ "symphonia",
+]
+
+[[package]]
+name = "rtrb"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba"
+
+[[package]]
+name = "rubato"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6dd52e80cfc21894deadf554a5673002938ae4625f7a283e536f9cf7c17b0d5"
+dependencies = [
+ "num-complex",
+ "num-integer",
+ "num-traits",
+ "realfft",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustfft"
+version = "6.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89"
+dependencies = [
+ "num-complex",
+ "num-integer",
+ "num-traits",
+ "primal-check",
+ "strength_reduce",
+ "transpose",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+dependencies = [
+ "bitflags 2.10.0",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.15",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
+dependencies = [
+ "bitflags 2.10.0",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.11.0",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
+dependencies = [
+ "aws-lc-rs",
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
+dependencies = [
+ "openssl-probe 0.2.0",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework 3.5.1",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
+dependencies = [
+ "core-foundation 0.10.1",
+ "core-foundation-sys",
+ "jni",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-native-certs",
+ "rustls-platform-verifier-android",
+ "rustls-webpki",
+ "security-framework 3.5.1",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
+dependencies = [
+ "aws-lc-rs",
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustpotter"
+version = "3.0.2"
+source = "git+https://github.com/Priler/rustpotter#f0d5c4d6b63aa1ebb6e0e81089993e4ae7aef3d8"
+dependencies = [
+ "candle-core",
+ "candle-nn",
+ "ciborium",
+ "hound",
+ "rubato",
+ "rustfft",
+ "serde",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
+
+[[package]]
+name = "safetensors"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44560c11236a6130a46ce36c836a62936dc81ebf8c36a37947423571be0e55b6"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "safetensors"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "675656c1eabb620b921efea4f9199f97fc86e36dd6ffd1fbbe48d0f59a4987f5"
+dependencies = [
+ "hashbrown 0.16.1",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "schemars"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
+dependencies = [
+ "dyn-clone",
+ "indexmap 1.9.3",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sctk-adwaita"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
+dependencies = [
+ "ab_glyph",
+ "log",
+ "memmap2",
+ "smithay-client-toolkit",
+ "tiny-skia",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.10.0",
+ "core-foundation 0.9.4",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework"
+version = "3.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
+dependencies = [
+ "bitflags 2.10.0",
+ "core-foundation 0.10.1",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "selectors"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416"
+dependencies = [
+ "bitflags 1.3.2",
+ "cssparser",
+ "derive_more 0.99.20",
+ "fxhash",
+ "log",
+ "phf 0.8.0",
+ "phf_codegen 0.8.0",
+ "precomputed-hash",
+ "servo_arc",
+ "smallvec",
+]
+
+[[package]]
+name = "self_cell"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
+
+[[package]]
+name = "semver"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "send_wrapper"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
+
+[[package]]
+name = "seq-macro"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
+
+[[package]]
+name = "seqdiff"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e058ed56aee6d266fa5aa7809f99899b07d350c30aecd4f3d859b4f0d9dbbbdf"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-untagged"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058"
+dependencies = [
+ "erased-serde",
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.149"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
+dependencies = [
+ "base64 0.22.1",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.13.0",
+ "schemars 0.9.0",
+ "schemars 1.2.0",
+ "serde_core",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
+dependencies = [
+ "darling 0.21.3",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap 2.13.0",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
+name = "serialize-to-javascript"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5"
+dependencies = [
+ "serde",
+ "serde_json",
+ "serialize-to-javascript-impl",
+]
+
+[[package]]
+name = "serialize-to-javascript-impl"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "servo_arc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741"
+dependencies = [
+ "nodrop",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shared_child"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7"
+dependencies = [
+ "libc",
+ "sigchld",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "sigchld"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1"
+dependencies = [
+ "libc",
+ "os_pipe",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+
+[[package]]
+name = "simd_helpers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
+dependencies = [
+ "quote",
+]
+
+[[package]]
+name = "simple-log"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f8030713cfe4aa369ef3c8cbf5e391cc18d7f6fba1f2a68ec959c5504413e51"
+dependencies = [
+ "log",
+ "log4rs",
+ "once_cell",
+ "serde",
+ "winnow 0.7.14",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
+
+[[package]]
+name = "siphasher"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
+dependencies = [
+ "bitflags 2.10.0",
+ "calloop",
+ "calloop-wayland-source",
+ "cursor-icon",
+ "libc",
+ "log",
+ "memmap2",
+ "rustix 0.38.44",
+ "thiserror 1.0.69",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-csd-frame",
+ "wayland-cursor",
+ "wayland-protocols",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkeysym",
+]
+
+[[package]]
+name = "smol_str"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "socket2"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "socks"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b"
+dependencies = [
+ "byteorder",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "softbuffer"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3"
+dependencies = [
+ "bytemuck",
+ "js-sys",
+ "ndk",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation 0.3.2",
+ "objc2-quartz-core 0.3.2",
+ "raw-window-handle",
+ "redox_syscall 0.5.18",
+ "tracing",
+ "wasm-bindgen",
+ "web-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "soup3"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f"
+dependencies = [
+ "futures-channel",
+ "gio",
+ "glib 0.18.5",
+ "libc",
+ "soup3-sys",
+]
+
+[[package]]
+name = "soup3-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27"
+dependencies = [
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "spm_precompiled"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326"
+dependencies = [
+ "base64 0.13.1",
+ "nom 7.1.3",
+ "serde",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strength_reduce"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
+
+[[package]]
+name = "strict-num"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
+
+[[package]]
+name = "string_cache"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
+dependencies = [
+ "new_debug_unreachable",
+ "parking_lot",
+ "phf_shared 0.11.3",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
+dependencies = [
+ "phf_generator 0.11.3",
+ "phf_shared 0.11.3",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "strum"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb"
+dependencies = [
+ "heck 0.3.3",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "swift-rs"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7"
+dependencies = [
+ "base64 0.21.7",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "symphonia"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039"
+dependencies = [
+ "lazy_static",
+ "symphonia-bundle-flac",
+ "symphonia-bundle-mp3",
+ "symphonia-codec-aac",
+ "symphonia-codec-pcm",
+ "symphonia-codec-vorbis",
+ "symphonia-core",
+ "symphonia-format-isomp4",
+ "symphonia-format-ogg",
+ "symphonia-format-riff",
+ "symphonia-metadata",
+]
+
+[[package]]
+name = "symphonia-bundle-flac"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976"
+dependencies = [
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+ "symphonia-utils-xiph",
+]
+
+[[package]]
+name = "symphonia-bundle-mp3"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed"
+dependencies = [
+ "lazy_static",
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+]
+
+[[package]]
+name = "symphonia-codec-aac"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790"
+dependencies = [
+ "lazy_static",
+ "log",
+ "symphonia-core",
+]
+
+[[package]]
+name = "symphonia-codec-pcm"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95"
+dependencies = [
+ "log",
+ "symphonia-core",
+]
+
+[[package]]
+name = "symphonia-codec-vorbis"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73"
+dependencies = [
+ "log",
+ "symphonia-core",
+ "symphonia-utils-xiph",
+]
+
+[[package]]
+name = "symphonia-core"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af"
+dependencies = [
+ "arrayvec",
+ "bitflags 1.3.2",
+ "bytemuck",
+ "lazy_static",
+ "log",
+]
+
+[[package]]
+name = "symphonia-format-isomp4"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5"
+dependencies = [
+ "encoding_rs",
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+ "symphonia-utils-xiph",
+]
+
+[[package]]
+name = "symphonia-format-ogg"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb"
+dependencies = [
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+ "symphonia-utils-xiph",
+]
+
+[[package]]
+name = "symphonia-format-riff"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f"
+dependencies = [
+ "extended",
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+]
+
+[[package]]
+name = "symphonia-metadata"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16"
+dependencies = [
+ "encoding_rs",
+ "lazy_static",
+ "log",
+ "symphonia-core",
+]
+
+[[package]]
+name = "symphonia-utils-xiph"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16"
+dependencies = [
+ "symphonia-core",
+ "symphonia-metadata",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "sys-locale"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "sysctl"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea"
+dependencies = [
+ "bitflags 2.10.0",
+ "byteorder",
+ "enum-as-inner",
+ "libc",
+ "thiserror 1.0.69",
+ "walkdir",
+]
+
+[[package]]
+name = "sysctl"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc"
+dependencies = [
+ "bitflags 2.10.0",
+ "byteorder",
+ "enum-as-inner",
+ "libc",
+ "thiserror 1.0.69",
+ "walkdir",
+]
+
+[[package]]
+name = "sysinfo"
+version = "0.37.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f"
+dependencies = [
+ "libc",
+ "memchr",
+ "ntapi",
+ "objc2-core-foundation",
+ "objc2-io-kit",
+ "windows 0.61.3",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
+dependencies = [
+ "bitflags 2.10.0",
+ "core-foundation 0.9.4",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
+dependencies = [
+ "cfg-expr 0.15.8",
+ "heck 0.5.0",
+ "pkg-config",
+ "toml 0.8.2",
+ "version-compare",
+]
+
+[[package]]
+name = "system-deps"
+version = "7.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f"
+dependencies = [
+ "cfg-expr 0.20.5",
+ "heck 0.5.0",
+ "pkg-config",
+ "toml 0.9.11+spec-1.1.0",
+ "version-compare",
+]
+
+[[package]]
+name = "systemstat"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5021f5184d44b26fb184acd689671bbe1e4bbd24bbdaa6bc7ec383fad32d2033"
+dependencies = [
+ "bytesize",
+ "lazy_static",
+ "libc",
+ "nom 7.1.3",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "tao"
+version = "0.34.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7"
+dependencies = [
+ "bitflags 2.10.0",
+ "block2 0.6.2",
+ "core-foundation 0.10.1",
+ "core-graphics 0.24.0",
+ "crossbeam-channel",
+ "dispatch",
+ "dlopen2",
+ "dpi",
+ "gdkwayland-sys",
+ "gdkx11-sys",
+ "gtk",
+ "jni",
+ "lazy_static",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "objc2 0.6.3",
+ "objc2-app-kit 0.3.2",
+ "objc2-foundation 0.3.2",
+ "once_cell",
+ "parking_lot",
+ "raw-window-handle",
+ "scopeguard",
+ "tao-macros",
+ "unicode-segmentation",
+ "url",
+ "windows 0.61.3",
+ "windows-core 0.61.2",
+ "windows-version",
+ "x11-dl",
+]
+
+[[package]]
+name = "tao-macros"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+
+[[package]]
+name = "target-lexicon"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
+
+[[package]]
+name = "tauri"
+version = "2.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "cookie",
+ "dirs",
+ "dunce",
+ "embed_plist",
+ "getrandom 0.3.4",
+ "glob",
+ "gtk",
+ "heck 0.5.0",
+ "http",
+ "jni",
+ "libc",
+ "log",
+ "mime",
+ "muda",
+ "objc2 0.6.3",
+ "objc2-app-kit 0.3.2",
+ "objc2-foundation 0.3.2",
+ "objc2-ui-kit 0.3.2",
+ "objc2-web-kit",
+ "percent-encoding",
+ "plist",
+ "raw-window-handle",
+ "reqwest 0.12.28",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "serialize-to-javascript",
+ "swift-rs",
+ "tauri-build",
+ "tauri-macros",
+ "tauri-runtime",
+ "tauri-runtime-wry",
+ "tauri-utils",
+ "thiserror 2.0.17",
+ "tokio",
+ "tray-icon",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "window-vibrancy",
+ "windows 0.61.3",
+]
+
+[[package]]
+name = "tauri-build"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08"
+dependencies = [
+ "anyhow",
+ "cargo_toml",
+ "dirs",
+ "glob",
+ "heck 0.5.0",
+ "json-patch",
+ "schemars 0.8.22",
+ "semver",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "tauri-winres",
+ "toml 0.9.11+spec-1.1.0",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-codegen"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf"
+dependencies = [
+ "base64 0.22.1",
+ "brotli",
+ "ico",
+ "json-patch",
+ "plist",
+ "png 0.17.16",
+ "proc-macro2",
+ "quote",
+ "semver",
+ "serde",
+ "serde_json",
+ "sha2",
+ "syn 2.0.114",
+ "tauri-utils",
+ "thiserror 2.0.17",
+ "time",
+ "url",
+ "uuid",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-macros"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+ "tauri-codegen",
+ "tauri-utils",
+]
+
+[[package]]
+name = "tauri-plugin"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e1d0a4860b7ff570c891e1d2a586bf1ede205ff858fbc305e0b5ae5d14c1377"
+dependencies = [
+ "anyhow",
+ "glob",
+ "plist",
+ "schemars 0.8.22",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "toml 0.9.11+spec-1.1.0",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-plugin-dialog"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b"
+dependencies = [
+ "log",
+ "raw-window-handle",
+ "rfd",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-plugin",
+ "tauri-plugin-fs",
+ "thiserror 2.0.17",
+ "url",
+]
+
+[[package]]
+name = "tauri-plugin-fs"
+version = "2.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804"
+dependencies = [
+ "anyhow",
+ "dunce",
+ "glob",
+ "percent-encoding",
+ "schemars 0.8.22",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "tauri",
+ "tauri-plugin",
+ "tauri-utils",
+ "thiserror 2.0.17",
+ "toml 0.9.11+spec-1.1.0",
+ "url",
+]
+
+[[package]]
+name = "tauri-plugin-shell"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39b76f884a3937e04b631ffdc3be506088fa979369d25147361352f2f352e5ed"
+dependencies = [
+ "encoding_rs",
+ "log",
+ "open",
+ "os_pipe",
+ "regex",
+ "schemars 0.8.22",
+ "serde",
+ "serde_json",
+ "shared_child",
+ "tauri",
+ "tauri-plugin",
+ "thiserror 2.0.17",
+ "tokio",
+]
+
+[[package]]
+name = "tauri-runtime"
+version = "2.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892"
+dependencies = [
+ "cookie",
+ "dpi",
+ "gtk",
+ "http",
+ "jni",
+ "objc2 0.6.3",
+ "objc2-ui-kit 0.3.2",
+ "objc2-web-kit",
+ "raw-window-handle",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "thiserror 2.0.17",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "windows 0.61.3",
+]
+
+[[package]]
+name = "tauri-runtime-wry"
+version = "2.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065"
+dependencies = [
+ "gtk",
+ "http",
+ "jni",
+ "log",
+ "objc2 0.6.3",
+ "objc2-app-kit 0.3.2",
+ "objc2-foundation 0.3.2",
+ "once_cell",
+ "percent-encoding",
+ "raw-window-handle",
+ "softbuffer",
+ "tao",
+ "tauri-runtime",
+ "tauri-utils",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "windows 0.61.3",
+ "wry",
+]
+
+[[package]]
+name = "tauri-utils"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490"
+dependencies = [
+ "anyhow",
+ "brotli",
+ "cargo_metadata",
+ "ctor",
+ "dunce",
+ "glob",
+ "html5ever",
+ "http",
+ "infer",
+ "json-patch",
+ "kuchikiki",
+ "log",
+ "memchr",
+ "phf 0.11.3",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "schemars 0.8.22",
+ "semver",
+ "serde",
+ "serde-untagged",
+ "serde_json",
+ "serde_with",
+ "swift-rs",
+ "thiserror 2.0.17",
+ "toml 0.9.11+spec-1.1.0",
+ "url",
+ "urlpattern",
+ "uuid",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-winres"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0"
+dependencies = [
+ "dunce",
+ "embed-resource",
+ "toml 0.9.11+spec-1.1.0",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.4",
+ "once_cell",
+ "rustix 1.1.3",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tendril"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
+dependencies = [
+ "futf",
+ "mac",
+ "utf-8",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+dependencies = [
+ "thiserror-impl 2.0.17",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "thread-id"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2010d27add3f3240c1fef7959f46c814487b216baee662af53be645ba7831c07"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tiff"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
+dependencies = [
+ "fax",
+ "flate2",
+ "half",
+ "quick-error",
+ "weezl",
+ "zune-jpeg 0.4.21",
+]
+
+[[package]]
+name = "time"
+version = "0.3.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
+
+[[package]]
+name = "time-macros"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tiny-skia"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "tiny-skia-path",
+]
+
+[[package]]
+name = "tiny-skia-path"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "strict-num",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
+dependencies = [
+ "displaydoc",
+ "serde_core",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokenizers"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b238e22d44a15349529690fb07bd645cf58149a1b1e44d6cb5bd1641ff1a6223"
+dependencies = [
+ "ahash",
+ "aho-corasick",
+ "compact_str",
+ "dary_heap",
+ "derive_builder",
+ "esaxx-rs",
+ "getrandom 0.3.4",
+ "itertools",
+ "log",
+ "macro_rules_attribute",
+ "monostate",
+ "onig",
+ "paste",
+ "rand 0.9.2",
+ "rayon",
+ "rayon-cond",
+ "regex",
+ "regex-syntax",
+ "serde",
+ "serde_json",
+ "spm_precompiled",
+ "thiserror 2.0.17",
+ "unicode-normalization-alignments",
+ "unicode-segmentation",
+ "unicode_categories",
+]
+
+[[package]]
+name = "tokio"
+version = "1.49.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
+dependencies = [
+ "futures-util",
+ "log",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
+dependencies = [
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.3",
+ "toml_edit 0.20.2",
+]
+
+[[package]]
+name = "toml"
+version = "0.9.11+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
+dependencies = [
+ "indexmap 2.13.0",
+ "serde_core",
+ "serde_spanned 1.0.4",
+ "toml_datetime 0.7.5+spec-1.1.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow 0.7.14",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap 2.13.0",
+ "toml_datetime 0.6.3",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
+dependencies = [
+ "indexmap 2.13.0",
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.3",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.23.10+spec-1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
+dependencies = [
+ "indexmap 2.13.0",
+ "toml_datetime 0.7.5+spec-1.1.0",
+ "toml_parser",
+ "winnow 0.7.14",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
+dependencies = [
+ "winnow 0.7.14",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.0.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
+dependencies = [
+ "bitflags 2.10.0",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "iri-string",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "transpose"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e"
+dependencies = [
+ "num-integer",
+ "strength_reduce",
+]
+
+[[package]]
+name = "tray-icon"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c"
+dependencies = [
+ "crossbeam-channel",
+ "dirs",
+ "libappindicator",
+ "muda",
+ "objc2 0.6.3",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation 0.3.2",
+ "once_cell",
+ "png 0.17.16",
+ "serde",
+ "thiserror 2.0.17",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "triple_buffer"
+version = "8.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "420466259f9fa5decc654c490b9ab538400e5420df8237f84ecbe20368bcf72b"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "ttf-parser"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
+
+[[package]]
+name = "tungstenite"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
+dependencies = [
+ "bytes",
+ "data-encoding",
+ "http",
+ "httparse",
+ "log",
+ "rand 0.9.2",
+ "sha1",
+ "thiserror 2.0.17",
+ "utf-8",
+]
+
+[[package]]
+name = "type-map"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
+dependencies = [
+ "rustc-hash",
+]
+
+[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
+[[package]]
+name = "typenum"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+
+[[package]]
+name = "ug"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90b70b37e9074642bc5f60bb23247fd072a84314ca9e71cdf8527593406a0dd3"
+dependencies = [
+ "gemm 0.18.2",
+ "half",
+ "libloading 0.8.9",
+ "memmap2",
+ "num",
+ "num-traits",
+ "num_cpus",
+ "rayon",
+ "safetensors 0.4.5",
+ "serde",
+ "thiserror 1.0.69",
+ "tracing",
+ "yoke 0.7.5",
+]
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-langid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05"
+dependencies = [
+ "unic-langid-impl",
+]
+
+[[package]]
+name = "unic-langid-impl"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658"
+dependencies = [
+ "tinystr",
+]
+
+[[package]]
+name = "unic-ucd-ident"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+[[package]]
+name = "unicode-normalization-alignments"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "ureq"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc"
+dependencies = [
+ "base64 0.22.1",
+ "der",
+ "log",
+ "native-tls",
+ "percent-encoding",
+ "rustls-pki-types",
+ "socks",
+ "ureq-proto",
+ "utf-8",
+ "webpki-root-certs",
+]
+
+[[package]]
+name = "ureq-proto"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f"
+dependencies = [
+ "base64 0.22.1",
+ "http",
+ "httparse",
+ "log",
+]
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "urlpattern"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d"
+dependencies = [
+ "regex",
+ "serde",
+ "unic-ucd-ident",
+ "url",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
+dependencies = [
+ "getrandom 0.3.4",
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "v_frame"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2"
+dependencies = [
+ "aligned-vec",
+ "num-traits",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version-compare"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "vosk"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae9e2a552b3fb49f097849cfed3d4a2a7163bb7ea507268666980605a676c627"
+dependencies = [
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+ "vosk-sys",
+]
+
+[[package]]
+name = "vosk-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ab2937e19c7d32c1a077a4a220011cbbb2b6ab83bb35635ffb65a82b7e43f6"
+
+[[package]]
+name = "vswhom"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
+dependencies = [
+ "libc",
+ "vswhom-sys",
+]
+
+[[package]]
+name = "vswhom-sys"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-streams"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix 1.1.3",
+ "scoped-tls",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec"
+dependencies = [
+ "bitflags 2.10.0",
+ "rustix 1.1.3",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-csd-frame"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
+dependencies = [
+ "bitflags 2.10.0",
+ "cursor-icon",
+ "wayland-backend",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.31.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5864c4b5b6064b06b1e8b74ead4a98a6c45a285fe7a0e784d24735f011fdb078"
+dependencies = [
+ "rustix 1.1.3",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.32.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3"
+dependencies = [
+ "bitflags 2.10.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-plasma"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa98634619300a535a9a97f338aed9a5ff1e01a461943e8346ff4ae26007306b"
+dependencies = [
+ "bitflags 2.10.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3"
+dependencies = [
+ "bitflags 2.10.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd"
+dependencies = [
+ "dlib",
+ "log",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webkit2gtk"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a"
+dependencies = [
+ "bitflags 1.3.2",
+ "cairo-rs",
+ "gdk",
+ "gdk-sys",
+ "gio",
+ "gio-sys 0.18.1",
+ "glib 0.18.5",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "gtk",
+ "gtk-sys",
+ "javascriptcore-rs",
+ "libc",
+ "once_cell",
+ "soup3",
+ "webkit2gtk-sys",
+]
+
+[[package]]
+name = "webkit2gtk-sys"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c"
+dependencies = [
+ "bitflags 1.3.2",
+ "cairo-sys-rs",
+ "gdk-sys",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "gtk-sys",
+ "javascriptcore-rs-sys",
+ "libc",
+ "pkg-config",
+ "soup3-sys",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "webview2-com"
+version = "0.38.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
+dependencies = [
+ "webview2-com-macros",
+ "webview2-com-sys",
+ "windows 0.61.3",
+ "windows-core 0.61.2",
+ "windows-implement",
+ "windows-interface",
+]
+
+[[package]]
+name = "webview2-com-macros"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "webview2-com-sys"
+version = "0.38.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
+dependencies = [
+ "thiserror 2.0.17",
+ "windows 0.61.3",
+ "windows-core 0.61.2",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
+
+[[package]]
+name = "which"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d"
+dependencies = [
+ "env_home",
+ "rustix 1.1.3",
+ "winsafe",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "window-vibrancy"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
+dependencies = [
+ "objc2 0.6.3",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "raw-window-handle",
+ "windows-sys 0.59.0",
+ "windows-version",
+]
+
+[[package]]
+name = "windows"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9f39345ae0c8ab072c0ac7fe8a8b411636aa34f89be19ddd0d9226544f13944"
+dependencies = [
+ "windows_i686_gnu 0.24.0",
+ "windows_i686_msvc 0.24.0",
+ "windows_x86_64_gnu 0.24.0",
+ "windows_x86_64_msvc 0.24.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.54.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
+dependencies = [
+ "windows-core 0.54.0",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows"
+version = "0.61.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
+dependencies = [
+ "windows-collections",
+ "windows-core 0.61.2",
+ "windows-future",
+ "windows-link 0.1.3",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
+dependencies = [
+ "windows-core 0.61.2",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.54.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
+dependencies = [
+ "windows-result 0.1.2",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.1.3",
+ "windows-result 0.3.4",
+ "windows-strings 0.4.2",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.2.1",
+ "windows-result 0.4.1",
+ "windows-strings 0.5.1",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
+dependencies = [
+ "windows-core 0.61.2",
+ "windows-link 0.1.3",
+ "windows-threading",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-numerics"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
+dependencies = [
+ "windows-core 0.61.2",
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
+dependencies = [
+ "windows-link 0.2.1",
+ "windows-result 0.4.1",
+ "windows-strings 0.5.1",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link 0.2.1",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-version"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0866510a3eca9aed73a077490bbbf03e5eaac4e1fd70849d89539e5830501fd"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf0ffed56b7e9369a29078d2ab3aaeceea48eb58999d2cff3aa2494a275b95c6"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384a173630588044205a2993b6864a2f56e5a8c1e7668c07b93ec18cf4888dc4"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bd8f062d8ca5446358159d79a90be12c543b3a965c847c8f3eedf14b321d399"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "winit"
+version = "0.30.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732"
+dependencies = [
+ "ahash",
+ "android-activity",
+ "atomic-waker",
+ "bitflags 2.10.0",
+ "block2 0.5.1",
+ "bytemuck",
+ "calloop",
+ "cfg_aliases",
+ "concurrent-queue",
+ "core-foundation 0.9.4",
+ "core-graphics 0.23.2",
+ "cursor-icon",
+ "dpi",
+ "js-sys",
+ "libc",
+ "memmap2",
+ "ndk",
+ "objc2 0.5.2",
+ "objc2-app-kit 0.2.2",
+ "objc2-foundation 0.2.2",
+ "objc2-ui-kit 0.2.2",
+ "orbclient",
+ "percent-encoding",
+ "pin-project",
+ "raw-window-handle",
+ "redox_syscall 0.4.1",
+ "rustix 0.38.44",
+ "sctk-adwaita",
+ "smithay-client-toolkit",
+ "smol_str",
+ "tracing",
+ "unicode-segmentation",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-protocols-plasma",
+ "web-sys",
+ "web-time",
+ "windows-sys 0.52.0",
+ "x11-dl",
+ "x11rb",
+ "xkbcommon-dl",
+]
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winnow"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winreg"
+version = "0.55.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "winrt-notification"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "007a0353840b23e0c6dc73e5b962ff58ed7f6bc9ceff3ce7fe6fbad8d496edf4"
+dependencies = [
+ "strum",
+ "windows 0.24.0",
+ "xml-rs",
+]
+
+[[package]]
+name = "winsafe"
+version = "0.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+
+[[package]]
+name = "writeable"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+
+[[package]]
+name = "wry"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2"
+dependencies = [
+ "base64 0.22.1",
+ "block2 0.6.2",
+ "cookie",
+ "crossbeam-channel",
+ "dirs",
+ "dpi",
+ "dunce",
+ "gdkx11",
+ "gtk",
+ "html5ever",
+ "http",
+ "javascriptcore-rs",
+ "jni",
+ "kuchikiki",
+ "libc",
+ "ndk",
+ "objc2 0.6.3",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "objc2-ui-kit 0.3.2",
+ "objc2-web-kit",
+ "once_cell",
+ "percent-encoding",
+ "raw-window-handle",
+ "sha2",
+ "soup3",
+ "tao-macros",
+ "thiserror 2.0.17",
+ "url",
+ "webkit2gtk",
+ "webkit2gtk-sys",
+ "webview2-com",
+ "windows 0.61.3",
+ "windows-core 0.61.2",
+ "windows-version",
+ "x11-dl",
+]
+
+[[package]]
+name = "x11"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11-dl"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
+dependencies = [
+ "libc",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11rb"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
+dependencies = [
+ "as-raw-xcb-connection",
+ "gethostname",
+ "libc",
+ "libloading 0.8.9",
+ "once_cell",
+ "rustix 1.1.3",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
+
+[[package]]
+name = "xcursor"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
+
+[[package]]
+name = "xkbcommon-dl"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
+dependencies = [
+ "bitflags 2.10.0",
+ "dlib",
+ "log",
+ "once_cell",
+ "xkeysym",
+]
+
+[[package]]
+name = "xkeysym"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
+
+[[package]]
+name = "y4m"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
+
+[[package]]
+name = "yoke"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive 0.7.5",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive 0.8.1",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+ "synstructure",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
+dependencies = [
+ "displaydoc",
+ "yoke 0.8.1",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
+dependencies = [
+ "serde",
+ "yoke 0.8.1",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
+name = "zip"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164"
+dependencies = [
+ "arbitrary",
+ "crc32fast",
+ "crossbeam-utils",
+ "displaydoc",
+ "indexmap 2.13.0",
+ "num_enum",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea"
+
+[[package]]
+name = "zune-core"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+
+[[package]]
+name = "zune-core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
+
+[[package]]
+name = "zune-inflate"
+version = "0.2.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
+dependencies = [
+ "zune-core 0.4.12",
+]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87c86acb70a85b2c16f071f171847d1945e8f44812630463cd14ec83900ad01c"
+dependencies = [
+ "zune-core 0.5.1",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..7820244
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,55 @@
+[workspace]
+members = [
+ "crates/jarvis-core",
+ "crates/jarvis-app",
+ "crates/jarvis-gui",
+ "crates/jarvis-cli"
+]
+resolver = "2"
+
+[workspace.package]
+version = "0.1.0"
+authors = ["Abraham Tugalov"]
+license = "GPL-3.0-only"
+repository = "https://github.com/Priler/jarvis"
+edition = "2021"
+
+[workspace.dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+serde_yaml = "0.9"
+once_cell = "1.19"
+log = "0.4"
+rand = "0.8"
+seqdiff = "0.3"
+hound = "3.5"
+platform-dirs = "0.3"
+rodio = "0.21"
+kira = "0.11"
+pv_recorder = { git = "https://github.com/Priler/pvrecorder" }
+vosk = "0.3"
+rustpotter = { git = "https://github.com/Priler/rustpotter" }
+image = "0.25"
+parking_lot = "0.12.5"
+toml = "0.9.8"
+sha2 = "0.10"
+nnnoiseless = "0.5"
+sysinfo = "0.37.2"
+tokio-tungstenite = "0.28.0"
+futures-util = "0.3"
+fluent = "0.17.0"
+fluent-bundle = "0.16.0"
+unic-langid = "0.9"
+chrono = "0.4"
+mlua = { version = "0.11.5", features = ["lua55", "vendored", "async", "serde"] }
+reqwest = { version = "0.13.1", features = ["blocking", "json"] }
+tempfile = "^3.24"
+winrt-notification = "0.5"
+
+fastembed = { version = "^5.8.1", default-features = false, features = ["ort-download-binaries"] }
+ort = { version = "=2.0.0-rc.11" }
+ndarray = "0.17"
+tokenizers = { version = "0.22", default-features = false }
+regex = "1"
+sys-locale = "0.3"
+
diff --git a/LICENSE.txt b/LICENSE.txt
index e72bfdd..4fae8d5 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,674 +1,437 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- Copyright (C)
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
\ No newline at end of file
+Attribution-NonCommercial-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More_considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
+Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-NonCommercial-ShareAlike 4.0 International Public License
+("Public License"). To the extent this Public License may be
+interpreted as a contract, You are granted the Licensed Rights in
+consideration of Your acceptance of these terms and conditions, and the
+Licensor grants You such rights in consideration of benefits the
+Licensor receives from making the Licensed Material available under
+these terms and conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-NC-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution, NonCommercial, and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. NonCommercial means not primarily intended for or directed towards
+ commercial advantage or monetary compensation. For purposes of
+ this Public License, the exchange of the Licensed Material for
+ other material subject to Copyright and Similar Rights by digital
+ file-sharing or similar means is NonCommercial provided there is
+ no payment of monetary compensation in connection with the
+ exchange.
+
+ l. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ m. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ n. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part, for NonCommercial purposes only; and
+
+ b. produce, reproduce, and Share Adapted Material for
+ NonCommercial purposes only.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties, including when
+ the Licensed Material is used other than for NonCommercial
+ purposes.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-NC-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database for NonCommercial purposes
+ only;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
\ No newline at end of file
diff --git a/README.md b/README.md
index b655b5b..807de10 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,65 @@
-# Kesha v3.0 very early (aka Jarvis update)
-Simple Voice Assistant made as an experiment using [Silero](https://github.com/snakers4/silero-models) & [Vosk](https://pypi.org/project/vosk/).
- Later on [Picovoice Porcupine Wake Word Detection](https://picovoice.ai/platform/porcupine/) & [ChatGPT](https://chat.openai.com/) was added.
+# JARVIS Voice Assistant (this readme is outdated)
-
+
-`The code has NOT been polished and is provided "as is". There are a lot of code that are redundant and there are tons of improvements that can be made.`
+`Jarvis` - is a voice assistant made as an experiment using neural networks for things like **STT/TTS/Wake Word/NLU** etc.
-# Installation
-First, install the requirements, the `requirements.txt` file is just an output of `pip freeze` from my test venv 'k.
-Second, check `config.py` and set required values (api key, device index).
-Next, run the `main.py` script and Voilà, as simple as that.
+The main project challenges we try to achieve is:
+ - 100% offline *(no cloud)*
+ - Open source *(full transparency)*
+ - No data collection *(we respect your privacy)*
-And don't forget to put models of Vosk to main folder.
-You can get the latest from the [official website.](https://alphacephei.com/vosk/models)
- The one I was using is `small`.
- p.s. If you don't understand how to install or where to put the Vosk model, I've made a [screenshot](https://i.imgur.com/N3bu2lC.png) for you.
+Our backend stack is 🦀 **[Rust](https://www.rust-lang.org/)** with ❤️ **[Tauri](https://tauri.app/)**.
+For the frontend we use ⚡️ **[Vite](https://vitejs.dev/)** + 🛠️ **[Svelte](https://svelte.dev/)**.
-# Python version
-I was using Python `3.8.3`, but it should work on any newer version.
+*Other libraries, tools and packages can be found in source code.*
-# ToDo
-- Адекватная архитектура кода, собрать всё и переписать from the ground up.
-- Задержка воспроизведения звука на основе реальной длительности .wav файла (прогружать при запуске?)
-- Speech to intent?
-- Отключать self listening protection во время воспроизведения с наушников.
-- Указание из списка или по имени будет реализовано позже.
+## Neural Networks
-# Author
-(2022) Abraham Tugalov
\ No newline at end of file
+This are the neural networks we are currently using:
+
+ - Speech-To-Text
+ - [Vosk Speech Recognition Toolkit](https://github.com/alphacep/vosk-api) via [Vosk-rs](https://github.com/Bear-03/vosk-rs)
+ - Text-To-Speech
+ - [~~Silero TTS~~](https://github.com/snakers4/silero-models) *(currently not used)*
+ - [~~Coqui TTS~~](https://github.com/coqui-ai/TTS) *(currently not used)*
+ - [~~WinRT~~](https://github.com/ndarilek/tts-rs) *(currently not used)*
+ - [~gTTS~](https://github.com/nightlyistaken/tts_rust) *(currently not used)*
+ - [~~SAM~~](https://github.com/s-macke/SAM) *(currently not used)*
+ - Wake Word
+ - [Rustpotter](https://github.com/GiviMAD/rustpotter) *(Partially implemented, still WIP)*
+ - [Picovoice Porcupine](https://github.com/Picovoice/porcupine) via [official SDK](https://github.com/Picovoice/porcupine#rust) *(requires API key)*
+ - [Vosk Speech Recognition Toolkit](https://github.com/alphacep/vosk-api) via [Vosk-rs](https://github.com/Bear-03/vosk-rs) *(very slow)*
+ - [~~Snowboy~~](https://github.com/Kitt-AI/snowboy) *(currently not used)*
+ - NLU
+ - Nothing yet.
+- Chat
+ - [~~ChatGPT~~](https://chat.openai.com/) (coming soon)
+
+## Supported Languages
+
+Currently, only Russian language is supported.
+But soon, Ukranian and English will be added for the interface, wake-word detection and speech recognition.
+
+## How to build?
+
+Nothing special was used to build this project.
+You need only Rust and NodeJS installed on your system.
+Other than that, all you need is to install all the dependencies and then compile the code with `cargo tauri build` command.
+Or run dev with `cargo tauri dev`.
+
+
+*Thought you might need some of the platform specific libraries for [PvRecorder](https://github.com/Picovoice/pvrecorder) and [Vosk](https://github.com/alphacep/vosk-api).*
+
+## Author
+
+Abraham Tugalov
+
+## Python version?
+Old version of Jarvis was built with Python.
+The last Python version commit can be found [here](https://github.com/Priler/jarvis/tree/943efbfbdb8aeb5889fa5e2dc7348ca4ea0b81df).
+
+## License
+
+[Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)
+See LICENSE.txt file for more details.
diff --git a/commands.yaml b/commands.yaml
deleted file mode 100644
index dc191dc..0000000
--- a/commands.yaml
+++ /dev/null
@@ -1,157 +0,0 @@
-gaming_mode_off:
-- рабочий режим
-- вернись в рабочий режим
-- переключись на компьютер
-- обратно на компьютер
-- отключи игровой режим
-- выйди из игрового режима
-- вернись на компьютер
-- выход с игрового режима
-- рабочее пространство
-- вернись в игровой режим
-- выключи игровой режим
-gaming_mode_on:
-- включи игровой режим
-- перейди в игровой режим
-- я хочу поиграть
-- переключись на телевизор
-music:
-- давай музыку
-- музыка
-- яндекс музыка
-- включи музыку
-- открой музыку
-- послушаем музыку
-- хочу музыку
-- врубай музло
-- включи что-то послушать
-- давай что-то послушаем
-music_next:
-- следующий трек
-- следующая мелодия
-- следующая песня
-- переключи трек
-- переключи музыку
-- переключи мелодию
-- переключи песню
-- следующая музыка
-music_off:
-- выключи музыку
-- отруби музыку
-- убери музыку
-- отключи музыку
-- закрой музыку
-- останови музыку
-- хватит музыки
-music_prev:
-- предыдущая музыка
-- предыдущий трек
-- предыдущая мелодия
-- предыдущая песня
-- верни трек
-- верни мелодию
-- верни песню
-- верни прошлый трек
-- верни прошлую мелодию
-- верни прошлую песню
-- верни прошлую музыку
-- верни то что играло
-- давай предыдущий трек
-- переключи музыку обратно
-- переключи на прошлый трек
-- переключи на прошлую мелодию
-- переключи на прошлую песню
-music_save:
-- сохрани трек
-- мне нравится трек
-- сохрани песню
-- мне нравится песня
-- добавь трек в избранное
-- добавь песню в избранное
-- запомни мелодию
-- запомни трек
-- запомни песню
-- добавь мелодию в избранное
-- сохрани то что сейчас играет
-- добавь то что сейчас играет в избранное
-- лайкни трек
-- лайкни мелодию
-- лайкни песню
-'off':
-- выключись
-- вырубись
-- завершить работу
-- закройся
-- отключись
-- заверши свою работу
-- на сегодня хватит
-- выгрузи себя из памяти
-- ты мне надоел
-- пора спать
-open_browser:
-- открой браузер
-- запусти браузер
-- открой гугл хром
-- гугл хром
-open_google:
-- открой гугл
-- гугл
-- запусти гугл
-open_youtube:
-- открой ютуб
-- ютуб
-- запусти ютуб
-sound_off:
-- выключи звук
-- беззвучный режим
-- режим без звука
-- отключи звук
-sound_on:
-- включи звук
-- режим со звуком
-- верни звук
-stupid:
-- ты дурак
-- ты дебил
-- ты глупый
-- ты тупой
-switch_to_dynamics:
-- переключи на динамики
-- включи динамики
-- перейди на динамики
-- давай звук в динамики
-- давай звук на динамики
-- переключи звук в динамики
-- переключи звук на динамики
-- переключи на колонки
-- включи колонки
-- перейди на колонки
-- давай звук в колонки
-- давай звук на колонки
-- переключи звук в колонки
-- переключи звук на колонки
-switch_to_headphones:
-- переключи на наушники
-- включи наушники
-- перейди на наушники
-- давай звук в наушники
-- давай звук на наушники
-- переключи звук в наушники
-- переключи звук на наушники
-- переключи на кракены
-- включи кракены
-- перейди на кракены
-- давай звук в кракены
-- давай звук на кракены
-- переключи звук в кракены
-- переключи звук на кракены
-thanks:
-- спасибо
-- молодец
-- респект
-- ты супер
-- отличная работа
-- ты крут
-- ты большой молодец
-- ты реально крут
-- ты афигенный
\ No newline at end of file
diff --git a/config.py b/config.py
deleted file mode 100644
index cac005f..0000000
--- a/config.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from dotenv import load_dotenv
-import os
-
-# Find .env file with os variables
-load_dotenv("dev.env")
-
-# Конфигурация
-VA_NAME = 'Jarvis'
-VA_VER = "3.0"
-VA_ALIAS = ('джарвис',)
-VA_TBR = ('скажи', 'покажи', 'ответь', 'произнеси', 'расскажи', 'сколько', 'слушай')
-
-# ID микрофона (можете просто менять ID пока при запуске не отобразится нужный)
-# -1 это стандартное записывающее устройство
-MICROPHONE_INDEX = -1
-
-# Путь к браузеру Google Chrome
-CHROME_PATH = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
-
-# Токен Picovoice
-PICOVOICE_TOKEN = os.getenv('PICOVOICE_TOKEN')
-
-# Токен OpenAI
-OPENAI_TOKEN = os.getenv('OPENAI_TOKEN')
diff --git a/crates/jarvis-app/.cargo/config.toml b/crates/jarvis-app/.cargo/config.toml
new file mode 100644
index 0000000..0a3dde6
--- /dev/null
+++ b/crates/jarvis-app/.cargo/config.toml
@@ -0,0 +1,16 @@
+# Cargo configuration file
+
+[build]
+jobs = 4
+rustflags = [
+ # "-Ctarget-feature=+fp16,+fhm"
+]
+
+[profile.dev]
+opt-level = 0
+debug = true
+
+[profile.release]
+opt-level = 3
+debug = false
+lto = true
\ No newline at end of file
diff --git a/crates/jarvis-app/Cargo.toml b/crates/jarvis-app/Cargo.toml
new file mode 100644
index 0000000..78353b5
--- /dev/null
+++ b/crates/jarvis-app/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "jarvis-app"
+version.workspace = true
+authors.workspace = true
+license.workspace = true
+repository.workspace = true
+edition.workspace = true
+
+[dependencies]
+jarvis-core = { path = "../jarvis-core", features = ["intent"] }
+once_cell.workspace = true
+log.workspace = true
+simple-log = "2.4"
+tray-icon = "0.21"
+winit = "0.30"
+image.workspace = true
+platform-dirs.workspace = true
+rand.workspace = true
+parking_lot.workspace = true
+
+tokio = { version = "1", features = ["rt-multi-thread"] }
+
+[target.'cfg(windows)'.dependencies.winit]
+version = "0.30"
+features = []
+
+[target.'cfg(target_os = "windows")'.dependencies]
+winapi = { version = "0.3", features = ["winuser"] }
+
+[target.'cfg(target_os = "linux")'.dependencies]
+gtk = "0.18"
+glib = "0.21.5"
\ No newline at end of file
diff --git a/crates/jarvis-app/Makefile.toml b/crates/jarvis-app/Makefile.toml
new file mode 100644
index 0000000..9946861
--- /dev/null
+++ b/crates/jarvis-app/Makefile.toml
@@ -0,0 +1,45 @@
+[tasks.format]
+install_crate = "rustfmt"
+command = "cargo"
+args = ["fmt", "--", "--emit=files"]
+
+[tasks.clean]
+command = "cargo"
+args = ["clean"]
+
+[tasks.build_debug]
+command = "cargo"
+args = ["build"]
+
+[tasks.run]
+command = "cargo"
+args = ["run"]
+
+[tasks.build_release]
+command = "cargo"
+args = ["build", "--release"]
+dependencies = ["clean"]
+
+[tasks.test]
+command = "cargo"
+args = ["test"]
+# dependencies = ["clean"]
+
+[tasks.post_build]
+script_runner = "python"
+script_extension = "py"
+script = { file = "post_build.py" }
+
+[tasks.debug]
+dependencies = [
+ "format",
+ "build_debug",
+ "post_build"
+]
+
+[tasks.release]
+dependencies = [
+ "format",
+ "build_release",
+ "post_build"
+]
\ No newline at end of file
diff --git a/crates/jarvis-app/build.rs b/crates/jarvis-app/build.rs
new file mode 100644
index 0000000..30a6d3f
--- /dev/null
+++ b/crates/jarvis-app/build.rs
@@ -0,0 +1,10 @@
+fn main() {
+ // link to Vosk lib
+ // println!("cargo:rustc-link-lib=libvosk.dll");
+
+ let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
+ let lib_path = std::path::Path::new(&manifest_dir)
+ .join("..\\..\\lib\\windows\\amd64");
+
+ println!("cargo:rustc-link-search=native={}", lib_path.display());
+}
diff --git a/crates/jarvis-app/src/_app.rs b/crates/jarvis-app/src/_app.rs
new file mode 100644
index 0000000..e29e8d1
--- /dev/null
+++ b/crates/jarvis-app/src/_app.rs
@@ -0,0 +1,337 @@
+use std::sync::mpsc::Receiver;
+use std::time::SystemTime;
+
+use jarvis_core::{audio_buffer::AudioRingBuffer, audio, audio_processing, commands, config, listener, recorder, stt, COMMANDS_LIST, intent, voices, ipc::{self, IpcEvent}};
+use rand::prelude::*;
+
+use crate::should_stop;
+
+// VAD state machine
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum VadState {
+ WaitingForVoice,
+ VoiceActive,
+}
+
+pub fn start(text_cmd_rx: Receiver) -> Result<(), ()> {
+ // start the loop
+ main_loop(text_cmd_rx)
+}
+
+fn main_loop(text_cmd_rx: Receiver) -> Result<(), ()> {
+ let rt = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
+ let mut start: SystemTime;
+ // let sounds_directory = audio::get_sound_directory().unwrap();
+ let frame_length: usize = 512; // default for every wake-word engine
+ let sample_rate: usize = 16000;
+ let mut frame_buffer: Vec = vec![0; frame_length];
+
+ // ring buffer: keep last 2 seconds of audio
+ let mut audio_buffer = AudioRingBuffer::new(2.0, frame_length, sample_rate);
+
+ // VAD state
+ let mut vad_state = VadState::WaitingForVoice;
+ let mut silence_frames: u32 = 0;
+
+ // how many frames of silence before we consider speech ended
+ // 1.5 seconds = 1.5 * (16000 / 512) ≈ 47 frames
+ // @TODO: Put this to config
+ let silence_threshold: u32 = ((1.5 * sample_rate as f32) / frame_length as f32) as u32;
+
+ // play some startup phrase
+ // audio::play_sound(&sounds_directory.join("run.wav"));
+ voices::play_greet();
+
+ // start recording
+ match recorder::start_recording() {
+ Ok(_) => info!("Recording started."),
+ Err(_) => {
+ error!("Cannot start recording.");
+ return Err(()); // quit
+ }
+ }
+
+ // notify GUI we're ready
+ ipc::send(IpcEvent::Idle);
+
+ // DEBUG counter
+ let mut frame_count: u32 = 0;
+
+ // the loop
+ 'wake_word: loop {
+ // check for stop signal
+ if should_stop() {
+ info!("Stop signal received, shutting down...");
+ voices::play_goodbye();
+ ipc::send(IpcEvent::Stopping);
+ break;
+ }
+
+ // check for text commands
+ if let Ok(text) = text_cmd_rx.try_recv() {
+ process_text_command(&text, &rt);
+ continue 'wake_word;
+ }
+
+ // read from microphone
+ recorder::read_microphone(&mut frame_buffer);
+
+ // DEBUG: check raw audio
+ frame_count += 1;
+ let raw_rms = calculate_rms(&frame_buffer);
+
+ if frame_count % 100 == 0 {
+ info!("DEBUG [{}]: raw_rms={:.0}", frame_count, raw_rms);
+ }
+
+ // check if we're getting any audio at all
+ if frame_count == 100 && raw_rms < 10.0 {
+ warn!("WARNING: Microphone appears to be silent! RMS={:.0}", raw_rms);
+ }
+
+ // process audio (gain -> noise suppression -> VAD)
+ let processed = audio_processing::process(&frame_buffer);
+
+ if frame_count % 100 == 0 {
+ info!("DEBUG [{}]: is_voice={}, vad_conf={:.2}, processed_rms={:.0}",
+ frame_count,
+ processed.is_voice,
+ processed.vad_confidence,
+ calculate_rms(&processed.samples)
+ );
+ }
+
+ // skip if no voice detected (vad)
+ if !processed.is_voice {
+ continue 'wake_word;
+ }
+
+ // DEBUG: we passed VAD
+ if frame_count % 50 == 0 {
+ info!("DEBUG: Voice detected, checking wake word...");
+ }
+
+ // recognize wake-word
+ match listener::data_callback(&frame_buffer) {
+ Some(_keyword_index) => {
+ // notify GUI
+ ipc::send(IpcEvent::WakeWordDetected);
+
+ // reset some things
+ stt::reset_wake_recognizer();
+ stt::reset_speech_recognizer();
+ audio_processing::reset();
+
+ // wake-word activated, process further commands
+ // capture current time
+ start = SystemTime::now();
+ silence_frames = 0;
+
+ // play some reply phrase
+ // @TODO. Make it via commands or upcoming events system.
+ voices::play_reply();
+
+
+ // notify GUI we're listening
+ ipc::send(IpcEvent::Listening);
+
+ // wait for voice commands
+ 'voice_recognition: loop {
+ // check for stop
+ if should_stop() {
+ break 'wake_word;
+ }
+
+ // read from microphone
+ recorder::read_microphone(&mut frame_buffer);
+
+ // process first
+ let processed = audio_processing::process(&frame_buffer);
+
+ // detect silence, return to wake-word if silence
+ if processed.is_voice {
+ silence_frames = 0;
+ } else {
+ silence_frames += 1;
+ if silence_frames > config::VAD_SILENCE_FRAMES * 2 {
+ info!("Long silence detected, returning to wake word mode.");
+ break 'voice_recognition;
+ }
+ }
+
+ // stt part (without partials)
+ if let Some(mut recognized_voice) = stt::recognize(&frame_buffer, false) {
+ // something was recognized
+ info!("Recognized voice: {}", recognized_voice);
+
+ // notify GUI
+ ipc::send(IpcEvent::SpeechRecognized {
+ text: recognized_voice.clone(),
+ });
+
+ // filter recognized voice
+ // @TODO. Better recognized voice filtration.
+ recognized_voice = recognized_voice.to_lowercase();
+
+ // answer again if it's activation phrase repeated
+ if recognized_voice.contains(config::VOSK_FETCH_PHRASE) {
+ info!("Wake word detected during chaining, reactivating...");
+
+ // play greet sound
+ // audio::play_sound(&sounds_directory.join(format!(
+ // "{}.wav",
+ // config::ASSISTANT_GREET_PHRASES
+ // .choose(&mut rand::thread_rng())
+ // .unwrap()
+ // )));
+ voices::play_reply();
+
+ // reset timer and continue listening
+ start = SystemTime::now();
+ silence_frames = 0;
+ stt::reset_speech_recognizer();
+
+ ipc::send(IpcEvent::Listening);
+ continue 'voice_recognition;
+ }
+
+ // filter out activation phrase from command
+ for tbr in config::ASSISTANT_PHRASES_TBR {
+ recognized_voice = recognized_voice.replace(tbr, "");
+ }
+ recognized_voice = recognized_voice.trim().into();
+
+ // skip if nothing left after filtering (*evil laugh*)
+ if recognized_voice.is_empty() {
+ continue 'voice_recognition;
+ }
+
+ // execute command (shared executor)
+ execute_command(&recognized_voice, &rt);
+
+ // return to wake-word listening after command execution (no matter successful or not)
+ break 'voice_recognition;
+ }
+
+ // only recognize voice for a certain period of time
+ match start.elapsed() {
+ Ok(elapsed) if elapsed > config::CMS_WAIT_DELAY => {
+ // return to wake-word listening after N seconds
+ break 'voice_recognition;
+ }
+ _ => (),
+ }
+
+ // reset things
+ stt::reset_wake_recognizer();
+ audio_processing::reset();
+ ipc::send(IpcEvent::Idle);
+ }
+ }
+ None => (),
+ }
+ }
+
+ // cleanup
+ recorder::stop_recording().ok();
+ ipc::send(IpcEvent::Stopping);
+
+ Ok(())
+}
+
+
+// process text command from GUI
+fn process_text_command(text: &str, rt: &tokio::runtime::Runtime) {
+ info!("Processing text command: {}", text);
+
+ ipc::send(IpcEvent::SpeechRecognized { text: text.to_string() });
+
+ // filter text same as voice
+ let mut filtered = text.to_lowercase();
+ for tbr in config::ASSISTANT_PHRASES_TBR {
+ filtered = filtered.replace(tbr, "");
+ }
+ let filtered = filtered.trim();
+
+ if filtered.is_empty() {
+ ipc::send(IpcEvent::Idle);
+ return;
+ }
+
+ execute_command(filtered, rt);
+}
+
+// shared command execution logic (manual & voice)
+fn execute_command(text: &str, rt: &tokio::runtime::Runtime) {
+ let commands_list = match COMMANDS_LIST.get() {
+ Some(c) => c,
+ None => {
+ ipc::send(IpcEvent::Error { message: "Commands not loaded".to_string() });
+ ipc::send(IpcEvent::Idle);
+ return;
+ }
+ };
+
+ // let sounds_directory = audio::get_sound_directory().unwrap();
+
+ // try intent recognition first, fallback to levenshtein
+ let cmd_result = if let Some((intent_id, confidence)) =
+ rt.block_on(intent::classify(text))
+ {
+ info!("Intent recognized: {} (confidence: {:.2})", intent_id, confidence);
+ intent::get_command_by_intent(commands_list, &intent_id)
+ } else {
+ info!("Intent not recognized, trying levenshtein fallback...");
+ commands::fetch_command(text, commands_list)
+ };
+
+ if let Some((cmd_path, cmd_config)) = cmd_result {
+ info!("Command found: {:?}", cmd_path);
+
+ match commands::execute_command(&cmd_path, &cmd_config) {
+ Ok(_) => {
+ info!("Command executed successfully");
+ voices::play_ok(); // command executed sound
+ ipc::send(IpcEvent::CommandExecuted {
+ id: cmd_config.id.clone(),
+ success: true,
+ });
+ }
+ Err(msg) => {
+ error!("Error executing command: {}", msg);
+ voices::play_error();
+ ipc::send(IpcEvent::CommandExecuted {
+ id: cmd_config.id.clone(),
+ success: false,
+ });
+ ipc::send(IpcEvent::Error { message: msg.to_string() });
+ }
+ }
+ } else {
+ info!("No command found for: {}", text);
+ // play "not understood" sound
+ // audio::play_sound(&sounds_directory.join("not_understand.wav"));
+ voices::play_not_found();
+ ipc::send(IpcEvent::Error {
+ message: format!("Command not found: {}", text)
+ });
+ }
+
+ ipc::send(IpcEvent::Idle);
+}
+
+
+fn keyword_callback(keyword_index: i32) {}
+
+pub fn close(code: i32) {
+ info!("Closing application.");
+ voices::play_goodbye();
+ ipc::send(IpcEvent::Stopping);
+ std::process::exit(code);
+}
+
+fn calculate_rms(samples: &[i16]) -> f32 {
+ if samples.is_empty() { return 0.0; }
+ let sum: f64 = samples.iter().map(|&s| (s as f64).powi(2)).sum();
+ (sum / samples.len() as f64).sqrt() as f32
+}
diff --git a/crates/jarvis-app/src/_main.rs b/crates/jarvis-app/src/_main.rs
new file mode 100644
index 0000000..6e716df
--- /dev/null
+++ b/crates/jarvis-app/src/_main.rs
@@ -0,0 +1,92 @@
+
+
+#[macro_use]
+extern crate lazy_static; // better switch to once_cell ?
+use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
+use log::{info};
+use log::LevelFilter;
+use std::sync::Mutex;
+
+// include assistant commands
+mod assistant_commands;
+use assistant_commands::AssistantCommand;
+
+// include vosk
+mod vosk;
+
+// include events
+mod events;
+
+// include recorder
+mod recorder;
+
+// init PickleDb connection
+lazy_static! {
+ static ref DB: Mutex = Mutex::new(
+ PickleDb::load(
+ format!("{}/{}", APP_CONFIG_DIR.lock().unwrap(), DB_FILE_NAME),
+ PickleDbDumpPolicy::AutoDump,
+ SerializationMethod::Json
+ )
+ .unwrap_or_else(|_x: _| {
+ info!("Creating new db file at {} ...", format!("{}/{}", APP_CONFIG_DIR.lock().unwrap(), DB_FILE_NAME));
+ PickleDb::new(
+ format!("{}/{}", APP_CONFIG_DIR.lock().unwrap(), DB_FILE_NAME),
+ PickleDbDumpPolicy::AutoDump,
+ SerializationMethod::Json,
+ )
+ })
+ );
+}
+
+fn main() {
+ // init vosk
+ vosk::init_vosk();
+
+ // run the app
+ tauri::Builder::default()
+ .setup(|app| {
+ std::fs::create_dir_all(app.path_resolver().app_config_dir().unwrap())?;
+ APP_CONFIG_DIR.lock().unwrap().push_str(app.path_resolver().app_config_dir().unwrap().to_str().unwrap());
+
+ std::fs::create_dir_all(app.path_resolver().app_log_dir().unwrap())?;
+ APP_LOG_DIR.lock().unwrap().push_str(app.path_resolver().app_log_dir().unwrap().to_str().unwrap());
+
+ // log to file
+ let log_file_path = format!("{}/{}", APP_LOG_DIR.lock().unwrap(), config::LOG_FILE_NAME);
+ println!("!!!===============!!!\nLOGGING TO {}\n!!!===============!!!\n", &log_file_path);
+ simple_logging::log_to_file(&log_file_path, LevelFilter::max()).expect("Failed to start logger ... is directory writable?");
+
+ Ok(())
+ })
+ .invoke_handler(tauri::generate_handler![
+ // db commands
+ tauri_commands::db_read,
+ tauri_commands::db_write,
+ // recorder commands
+ tauri_commands::pv_get_audio_devices,
+ tauri_commands::pv_get_audio_device_name,
+ // listener commands
+ tauri_commands::start_listening,
+ tauri_commands::stop_listening,
+ tauri_commands::is_listening,
+ // sys commands
+ tauri_commands::get_current_ram_usage,
+ tauri_commands::get_peak_ram_usage,
+ tauri_commands::get_cpu_temp,
+ tauri_commands::get_cpu_usage,
+ // sound commands
+ tauri_commands::play_sound,
+ // fs commands
+ tauri_commands::show_in_folder,
+ // etc commands
+ tauri_commands::get_app_version,
+ tauri_commands::get_author_name,
+ tauri_commands::get_repository_link,
+ tauri_commands::get_tg_official_link,
+ tauri_commands::get_feedback_link,
+ tauri_commands::get_log_file_path
+ ])
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
diff --git a/crates/jarvis-app/src/app.rs b/crates/jarvis-app/src/app.rs
new file mode 100644
index 0000000..cbc900f
--- /dev/null
+++ b/crates/jarvis-app/src/app.rs
@@ -0,0 +1,418 @@
+use std::sync::mpsc::Receiver;
+use std::time::SystemTime;
+
+use jarvis_core::{audio_buffer::AudioRingBuffer, audio_processing, commands, config, listener, recorder, stt, COMMANDS_LIST, intent, voices, ipc::{self, IpcEvent}, i18n, slots};
+use rand::seq::SliceRandom;
+
+use crate::should_stop;
+
+// VAD state machine
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum VadState {
+ WaitingForVoice,
+ VoiceActive,
+}
+
+pub fn start(text_cmd_rx: Receiver, rt: &tokio::runtime::Runtime) -> Result<(), ()> {
+ main_loop(text_cmd_rx, rt)
+}
+
+fn main_loop(text_cmd_rx: Receiver, rt: &tokio::runtime::Runtime) -> Result<(), ()> {
+ let frame_length: usize = 512;
+ let sample_rate: usize = 16000;
+ let mut frame_buffer: Vec = vec![0; frame_length];
+
+ // ring buffer: keeps last 5 seconds of audio (pre-roll)
+ let mut audio_buffer = AudioRingBuffer::new(5.0, frame_length, sample_rate);
+
+ // VAD state
+ let mut vad_state = VadState::WaitingForVoice;
+ let mut silence_frames: u32 = 0;
+
+ // how many frames of silence before we consider speech ended
+ // 1.5 seconds = 1.5 * (16000 / 512) ≈ 47 frames
+ let silence_threshold: u32 = ((1.5 * sample_rate as f32) / frame_length as f32) as u32;
+
+ voices::play_greet();
+
+ match recorder::start_recording() {
+ Ok(_) => info!("Recording started. Microphone: {}",
+ recorder::get_audio_device_name(recorder::get_selected_microphone_index())),
+ Err(_) => {
+ error!("Cannot start recording.");
+ return Err(());
+ }
+ }
+
+ ipc::send(IpcEvent::Idle);
+
+ // ### WAKE WORD DETECTION LOOP
+ 'wake_word: loop {
+ if should_stop() {
+ info!("Stop signal received, shutting down...");
+ voices::play_goodbye();
+ ipc::send(IpcEvent::Stopping);
+ break;
+ }
+
+ if let Ok(text) = text_cmd_rx.try_recv() {
+ process_text_command(&text, &rt);
+ continue 'wake_word;
+ }
+
+ recorder::read_microphone(&mut frame_buffer);
+ let processed = audio_processing::process(&frame_buffer);
+
+ match vad_state {
+ VadState::WaitingForVoice => {
+ // always buffer audio
+ audio_buffer.push(&frame_buffer);
+
+ if processed.is_voice {
+ // voice started! flush buffer to Vosk
+ info!("VAD: Voice started, flushing {} buffered frames", audio_buffer.len());
+
+ for buffered_frame in audio_buffer.drain_all() {
+ listener::data_callback(&buffered_frame);
+ }
+
+ vad_state = VadState::VoiceActive;
+ silence_frames = 0;
+ }
+ }
+
+ VadState::VoiceActive => {
+ // dual-feed: speech recognizer gets frames in parallel with wake word detector
+ let _ = stt::recognize(&frame_buffer, false);
+
+ // feed to wake word detector
+ if let Some(_keyword_index) = listener::data_callback(&frame_buffer) {
+ // WAKE WORD DETECTED!
+ info!("Wake word activated!");
+ ipc::send(IpcEvent::WakeWordDetected);
+
+ stt::reset_wake_recognizer();
+ audio_processing::reset();
+
+ // brief sniff to keep feeding STT while transitioning
+ let sniff_frames = ((0.3 * sample_rate as f32) / frame_length as f32) as u32;
+ for _ in 0..sniff_frames {
+ recorder::read_microphone(&mut frame_buffer);
+ audio_processing::process(&frame_buffer);
+ stt::recognize(&frame_buffer, false);
+ }
+
+ ipc::send(IpcEvent::Listening);
+ recognize_command(&mut frame_buffer, &rt, frame_length, sample_rate, true);
+
+ // reset state after command
+ vad_state = VadState::WaitingForVoice;
+ silence_frames = 0;
+ audio_buffer.clear();
+ stt::reset_wake_recognizer();
+ stt::reset_speech_recognizer(); // NOW reset, after command is done
+ audio_processing::reset();
+ ipc::send(IpcEvent::Idle);
+
+ continue 'wake_word;
+ }
+
+ // track silence
+ if processed.is_voice {
+ silence_frames = 0;
+ } else {
+ silence_frames += 1;
+
+ if silence_frames > silence_threshold {
+ debug!("VAD: Silence timeout, returning to wait state");
+ vad_state = VadState::WaitingForVoice;
+ silence_frames = 0;
+ stt::reset_wake_recognizer();
+ stt::reset_speech_recognizer(); // reset since we were dual-feeding
+ }
+ }
+ }
+ }
+ }
+
+ recorder::stop_recording().ok();
+ ipc::send(IpcEvent::Stopping);
+
+ Ok(())
+}
+
+
+// Voice recognition for command after wake word
+fn recognize_command(
+ frame_buffer: &mut [i16],
+ rt: &tokio::runtime::Runtime,
+ frame_length: usize,
+ sample_rate: usize,
+ prefed_audio: bool
+) {
+ let mut audio_buffer = AudioRingBuffer::new(2.0, frame_length, sample_rate);
+ let mut vad_state = if prefed_audio {
+ VadState::VoiceActive
+ } else {
+ VadState::WaitingForVoice
+ };
+ let mut silence_frames: u32 = 0;
+ let mut start = SystemTime::now();
+ let mut first_recognition = prefed_audio;
+
+ // longer silence threshold for commands (user might pause to think)
+ // 5 seconds
+ let silence_threshold: u32 = ((5.0 * sample_rate as f32) / frame_length as f32) as u32;
+
+ loop {
+ if crate::should_stop() {
+ return;
+ }
+
+ recorder::read_microphone(frame_buffer);
+ let processed = audio_processing::process(frame_buffer);
+
+ match vad_state {
+ VadState::WaitingForVoice => {
+ audio_buffer.push(frame_buffer);
+
+ if processed.is_voice {
+ // flush buffer to STT
+ for buffered_frame in audio_buffer.drain_all() {
+ stt::recognize(&buffered_frame, false);
+ }
+ vad_state = VadState::VoiceActive;
+ silence_frames = 0;
+ } else {
+ silence_frames += 1;
+
+ if silence_frames > silence_threshold {
+ info!("Long silence detected, returning to wake word mode.");
+ return;
+ }
+ }
+ }
+
+ VadState::VoiceActive => {
+ // feed to STT
+ if let Some(mut recognized_voice) = stt::recognize(frame_buffer, false) {
+ info!("Recognized voice: {}", recognized_voice);
+
+ ipc::send(IpcEvent::SpeechRecognized {
+ text: recognized_voice.clone(),
+ });
+
+ recognized_voice = recognized_voice.to_lowercase();
+
+ // check if wake word repeated (reactivate)
+ let wake_phrases = config::get_wake_phrases(&i18n::get_language());
+ let contains_wake = wake_phrases.iter().any(|wp| recognized_voice.contains(wp));
+
+ if contains_wake {
+ // strip the wake word
+ let mut remaining = recognized_voice.clone();
+ for wp in wake_phrases {
+ remaining = remaining.replace(wp, "");
+ }
+ let remaining = remaining.trim();
+
+ if remaining.is_empty() {
+ if first_recognition {
+ // leftover wake word from dual-feed, just discard it
+ info!("Discarding initial wake word from prefed audio");
+ first_recognition = false;
+ stt::reset_speech_recognizer();
+ voices::play_reply();
+ vad_state = VadState::WaitingForVoice;
+ silence_frames = 0;
+ start = SystemTime::now();
+ audio_buffer.clear();
+ continue;
+ }
+
+ // just wake word, no command - reactivate
+ info!("Wake word repeated during chaining, reactivating...");
+ voices::play_reply();
+ stt::reset_speech_recognizer();
+ ipc::send(IpcEvent::Listening);
+
+ vad_state = VadState::WaitingForVoice;
+ silence_frames = 0;
+ start = SystemTime::now();
+ audio_buffer.clear();
+ continue;
+ } else {
+ // wake word + command in one phrase - execute the command part
+ info!("Wake word + command during chaining: '{}'", remaining);
+ recognized_voice = remaining.to_string();
+ // fall through to command execution below
+ }
+ }
+
+ first_recognition = false;
+
+ // filter activation phrases
+ // for tbr in config::ASSISTANT_PHRASES_TBR {
+ // recognized_voice = recognized_voice.replace(tbr, "");
+ // }
+ for tbr in config::get_phrases_to_remove(&i18n::get_language()) {
+ recognized_voice = recognized_voice.replace(tbr, "");
+ }
+
+ recognized_voice = recognized_voice.trim().to_string();
+
+ if recognized_voice.len() < 5 {
+ debug!("Ignoring too short recognition: '{}'", recognized_voice);
+ continue;
+ }
+
+ if recognized_voice.is_empty() {
+ continue;
+ }
+
+ // execute command and check if we should chain
+ let should_chain = execute_command(&recognized_voice, rt);
+
+ if should_chain {
+ // chain: reset and continue listening
+ info!("Chaining enabled, continuing to listen...");
+ stt::reset_speech_recognizer();
+ vad_state = VadState::WaitingForVoice;
+ silence_frames = 0;
+ start = SystemTime::now();
+ audio_buffer.clear();
+ ipc::send(IpcEvent::Listening);
+ continue;
+ } else {
+ // no chain: return to wake word
+ info!("No chain, returning to wake word mode.");
+ return;
+ }
+ }
+
+ // track silence
+ if processed.is_voice {
+ silence_frames = 0;
+ } else {
+ silence_frames += 1;
+
+ if silence_frames > silence_threshold {
+ info!("Long silence detected, returning to wake word mode.");
+ return;
+ }
+ }
+ }
+ }
+
+ // timeout
+ if let Ok(elapsed) = start.elapsed() {
+ if elapsed > config::CMS_WAIT_DELAY {
+ info!("Command timeout, returning to wake word mode.");
+ return;
+ }
+ }
+ }
+}
+
+
+fn process_text_command(text: &str, rt: &tokio::runtime::Runtime) {
+ info!("Processing text command: {}", text);
+
+ ipc::send(IpcEvent::SpeechRecognized { text: text.to_string() });
+
+ let mut filtered = text.to_lowercase();
+ // for tbr in config::ASSISTANT_PHRASES_TBR {
+ // filtered = filtered.replace(tbr, "");
+ // }
+ for tbr in config::get_phrases_to_remove(&i18n::get_language()) {
+ filtered = filtered.replace(tbr, "");
+ }
+
+ let filtered = filtered.trim();
+
+ if filtered.is_empty() {
+ ipc::send(IpcEvent::Idle);
+ return;
+ }
+
+ // text commands never chain
+ execute_command(filtered, rt);
+}
+
+
+// Execute command, returns true if chaining should continue
+fn execute_command(text: &str, rt: &tokio::runtime::Runtime) -> bool {
+ let commands_list = match COMMANDS_LIST.get() {
+ Some(c) => c,
+ None => {
+ ipc::send(IpcEvent::Error { message: "Commands not loaded".to_string() });
+ ipc::send(IpcEvent::Idle);
+ return false;
+ }
+ };
+
+ let cmd_result = if let Some((intent_id, confidence)) =
+ rt.block_on(intent::classify(text))
+ {
+ info!("Intent recognized: {} (confidence: {:.2})", intent_id, confidence);
+ intent::get_command_by_intent(commands_list, &intent_id)
+ } else {
+ info!("Intent not recognized, trying levenshtein fallback...");
+ commands::fetch_command(text, commands_list)
+ };
+
+ if let Some((cmd_path, cmd_config)) = cmd_result {
+ info!("Command found: {:?}", cmd_path);
+
+ // extract slots if needed
+ let extracted_slots = if !cmd_config.slots.is_empty() {
+ let s = slots::extract(text, &cmd_config.slots);
+ if !s.is_empty() {
+ info!("Extracted slots: {:?}", s);
+ }
+ Some(s)
+ } else {
+ None
+ };
+
+ match commands::execute_command(&cmd_path, &cmd_config, Some(&text), extracted_slots.as_ref()) {
+ Ok(chain) => {
+ info!("Command executed successfully");
+ // voices::play_ok();
+ voices::play_random_from(cmd_config.get_sounds(&i18n::get_language()).as_slice());
+ ipc::send(IpcEvent::CommandExecuted {
+ id: cmd_config.id.clone(),
+ success: true,
+ });
+ ipc::send(IpcEvent::Idle);
+ return chain; // return chain status from command
+ }
+ Err(msg) => {
+ error!("Error executing command: {}", msg);
+ voices::play_error();
+ ipc::send(IpcEvent::CommandExecuted {
+ id: cmd_config.id.clone(),
+ success: false,
+ });
+ ipc::send(IpcEvent::Error { message: msg.to_string() });
+ }
+ }
+ } else {
+ info!("No command found for: {}", text);
+ voices::play_not_found();
+ ipc::send(IpcEvent::Error {
+ message: format!("Command not found: {}", text)
+ });
+ }
+
+ ipc::send(IpcEvent::Idle);
+ false // no chain on error or not found
+}
+
+
+pub fn close(code: i32) {
+ info!("Closing application.");
+ voices::play_goodbye();
+ ipc::send(IpcEvent::Stopping);
+ std::process::exit(code);
+}
\ No newline at end of file
diff --git a/crates/jarvis-app/src/log.rs b/crates/jarvis-app/src/log.rs
new file mode 100644
index 0000000..81df0e4
--- /dev/null
+++ b/crates/jarvis-app/src/log.rs
@@ -0,0 +1,25 @@
+use simple_log::LogConfigBuilder;
+
+use crate::config;
+use crate::APP_LOG_DIR;
+
+pub fn init_logging() -> Result<(), String> {
+ // configure logging
+ let config = LogConfigBuilder::builder()
+ .path(format!(
+ "{}/{}",
+ APP_LOG_DIR.get().unwrap().display(),
+ config::LOG_FILE_NAME
+ ))
+ .size(1 * 100)
+ .roll_count(10)
+ .time_format("%Y-%m-%d %H:%M:%S.%f")
+ .level("debug")?
+ .output_file()
+ .output_console()
+ .build();
+
+ simple_log::new(config)?;
+
+ Ok(())
+}
diff --git a/crates/jarvis-app/src/main.rs b/crates/jarvis-app/src/main.rs
new file mode 100644
index 0000000..7a40397
--- /dev/null
+++ b/crates/jarvis-app/src/main.rs
@@ -0,0 +1,173 @@
+use jarvis_core::slots;
+use std::sync::Arc;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::mpsc;
+
+// include core
+use jarvis_core::{
+ audio, audio_processing, commands, config, db, listener, recorder, stt, intent,
+ ipc::{self, IpcAction},
+ i18n, voices, models,
+ APP_CONFIG_DIR, APP_LOG_DIR, COMMANDS_LIST, DB,
+};
+
+// include log
+#[macro_use]
+extern crate simple_log;
+mod log;
+
+// include app
+mod app;
+
+// include tray
+// @TODO. macOS currently not supported for tray functionality.
+#[cfg(not(target_os = "macos"))]
+mod tray;
+
+static SHOULD_STOP: AtomicBool = AtomicBool::new(false);
+
+fn main() -> Result<(), String> {
+ // initialize directories
+ config::init_dirs()?;
+
+ // initialize logging
+ log::init_logging()?;
+
+ // log some base info
+ info!("Starting Jarvis v{} ...", config::APP_VERSION.unwrap());
+ info!("Config directory is: {}", APP_CONFIG_DIR.get().unwrap().display());
+ info!("Log directory is: {}", APP_LOG_DIR.get().unwrap().display());
+
+ // initialize settings
+ let settings = db::init();
+
+ // set global DB (for core modules that read settings at init time)
+ DB.set(settings.arc().clone())
+ .expect("DB already initialized");
+
+ // init voices
+ let voice_id = settings.lock().voice.clone();
+ let language = settings.lock().language.clone();
+ if let Err(e) = voices::init(&voice_id, &language) {
+ warn!("Failed to init voices: {}", e);
+ }
+
+ // init i18n
+ i18n::init(&settings.lock().language);
+
+ // init recorder
+ if recorder::init().is_err() {
+ app::close(1);
+ }
+
+ // init models registry (scans available AI models)
+ if let Err(e) = models::init() {
+ warn!("Models registry init failed: {}", e);
+ }
+
+ // init stt engine
+ if stt::init().is_err() {
+ // @TODO. Allow continuing even without STT, if commands is using keywords or smthng?
+ app::close(1); // cannot continue without stt
+ }
+
+ // init commands
+ info!("Initializing commands.");
+ let cmds = match commands::parse_commands() {
+ Ok(c) => c,
+ Err(e) => {
+ warn!("Failed to parse commands: {}. Starting with empty command list.", e);
+ Vec::new()
+ }
+ };
+ info!("Commands initialized. Count: {}, List: {:?}", cmds.len(), commands::list_paths(&cmds));
+ COMMANDS_LIST.set(cmds).unwrap();
+
+ // init audio
+ if audio::init().is_err() {
+ // @TODO. Allow continuing even without audio?
+ app::close(1); // cannot continue without audio
+ }
+
+ // init wake-word engine
+ if let Err(e) = listener::init() {
+ error!("Wake-word engine init failed: {}", e);
+ app::close(1);
+ }
+
+ // shared async runtime for intent classification, IPC, etc.
+ let rt = Arc::new(
+ tokio::runtime::Runtime::new().expect("Failed to create tokio runtime")
+ );
+
+ // init intent-recognition engine
+ rt.block_on(async {
+ if let Err(e) = intent::init(COMMANDS_LIST.get().unwrap()).await {
+ error!("Failed to initialize intent classifier: {}", e);
+ app::close(1);
+ }
+ });
+
+ // init slots parsing engine
+ slots::init().map_err(|e| error!("Slot extraction init failed: {}", e)).ok();
+
+ // init audio processing
+ info!("Initializing audio processing...");
+ if let Err(e) = audio_processing::init() {
+ warn!("Audio processing init failed: {}", e);
+ }
+
+ // init IPC
+ info!("Initializing IPC...");
+ ipc::init();
+
+ // channel for text commands (manually written in the GUI)
+ let (text_cmd_tx, text_cmd_rx) = mpsc::channel::();
+
+ ipc::set_action_handler(move |action| {
+ match action {
+ IpcAction::Stop => {
+ info!("Received stop command from GUI");
+ SHOULD_STOP.store(true, Ordering::SeqCst);
+ }
+ IpcAction::ReloadCommands => {
+ info!("Received reload commands request");
+ // TODO: implement reload
+ }
+ IpcAction::SetMuted { muted } => {
+ info!("Received mute request: {}", muted);
+ // TODO: implement mute
+ }
+ IpcAction::TextCommand { text } => {
+ info!("Received text command: {}", text);
+ if let Err(e) = text_cmd_tx.send(text) {
+ error!("Failed to send text command to app: {}", e);
+ }
+ }
+ IpcAction::Ping => {
+ // handled internally by server
+ }
+ _ => {}
+ }
+ });
+
+ // start WebSocket server on the shared runtime
+ let ipc_rt = Arc::clone(&rt);
+ std::thread::spawn(move || {
+ ipc_rt.block_on(ipc::start_server());
+ });
+
+ // start the app (in the background thread)
+ let app_rt = Arc::clone(&rt);
+ std::thread::spawn(move || {
+ let _ = app::start(text_cmd_rx, &app_rt);
+ });
+
+ tray::init_blocking(settings);
+
+ Ok(())
+}
+
+pub fn should_stop() -> bool {
+ SHOULD_STOP.load(Ordering::SeqCst)
+}
diff --git a/crates/jarvis-app/src/tray.rs b/crates/jarvis-app/src/tray.rs
new file mode 100644
index 0000000..499d278
--- /dev/null
+++ b/crates/jarvis-app/src/tray.rs
@@ -0,0 +1,226 @@
+mod menu;
+
+use tray_icon::{
+ menu::MenuEvent,
+ TrayIconBuilder,
+};
+use image;
+use std::process::Command;
+
+#[cfg(target_os="windows")]
+use winit::platform::windows::EventLoopBuilderExtWindows;
+
+use jarvis_core::{config, i18n, voices, ipc::{self, IpcEvent}, SettingsManager};
+
+const TRAY_ICON_BYTES: &[u8] = include_bytes!("../../../resources/icons/32x32.png");
+
+pub fn init_blocking(settings: SettingsManager) {
+ let icon = load_icon_from_bytes(TRAY_ICON_BYTES);
+
+ // build menu with settings submenus
+ let tray_menu = menu::build(&settings);
+ let menu::TrayMenu { menu, state: tray_state } = tray_menu;
+
+ let _tray_icon = TrayIconBuilder::new()
+ .with_menu(Box::new(menu))
+ .with_tooltip(i18n::t("tray-tooltip"))
+ .with_icon(icon)
+ .build()
+ .unwrap();
+
+ let menu_channel = MenuEvent::receiver();
+
+ #[cfg(target_os = "linux")]
+ {
+ gtk::init().unwrap();
+ glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
+ if let Ok(event) = menu_channel.try_recv() {
+ handle_menu_event(&event, &settings, &tray_state);
+ }
+ glib::ControlFlow::Continue
+ });
+ gtk::main();
+ }
+
+ #[cfg(target_os = "macos")]
+ {
+ use winit::event_loop::{EventLoop, ControlFlow};
+ let event_loop = EventLoop::new().unwrap();
+ event_loop.run(move |_event, elwt| {
+ elwt.set_control_flow(ControlFlow::Wait);
+ if let Ok(event) = menu_channel.try_recv() {
+ handle_menu_event(&event, &settings, &tray_state);
+ }
+ }).unwrap();
+ }
+
+ #[cfg(target_os = "windows")]
+ {
+ loop {
+ if let Ok(event) = menu_channel.try_recv() {
+ handle_menu_event(&event, &settings, &tray_state);
+ }
+
+ // pump Windows messages
+ unsafe {
+ let mut msg: winapi::um::winuser::MSG = std::mem::zeroed();
+ while winapi::um::winuser::PeekMessageW(
+ &mut msg,
+ std::ptr::null_mut(),
+ 0, 0,
+ winapi::um::winuser::PM_REMOVE
+ ) != 0 {
+ winapi::um::winuser::TranslateMessage(&msg);
+ winapi::um::winuser::DispatchMessageW(&msg);
+ }
+ }
+ std::thread::sleep(std::time::Duration::from_millis(50));
+ }
+ }
+
+ info!("Tray initialized.");
+}
+
+fn handle_menu_event(event: &MenuEvent, settings: &SettingsManager, tray_state: &menu::TrayState) {
+ let id = event.id.0.as_str();
+
+ // -- radio group: "set:key:value"
+ if let Some(rest) = id.strip_prefix("set:") {
+ if let Some((key, value)) = rest.split_once(':') {
+ match settings.write(key, value) {
+ Ok(()) => {
+ info!("Tray: {} = {}", key, value);
+
+ // update check marks in the radio group
+ for group in &tray_state.radio_groups {
+ if group.setting_key == key {
+ group.select(value);
+ break;
+ }
+ }
+
+ // apply side effects
+ match key {
+ "language" => {
+ i18n::set_language(value);
+ }
+ "assistant_voice" => {
+ voices::set_current_voice(value);
+ }
+ _ => {}
+ }
+ }
+ Err(e) => {
+ warn!("Tray: failed to set {} = {}: {}", key, value, e);
+ }
+ }
+ return;
+ }
+ }
+
+ // -- toggle: "toggle:key"
+ if let Some(key) = id.strip_prefix("toggle:") {
+ match key {
+ "gain_normalizer" => {
+ // CheckMenuItem auto-toggles on click, just read the new state
+ let new_val = tray_state.gain_toggle.is_checked();
+ let val_str = if new_val { "true" } else { "false" };
+ if let Err(e) = settings.write(key, val_str) {
+ warn!("Tray: failed to toggle {}: {}", key, e);
+ // revert visual state on error
+ tray_state.gain_toggle.set_checked(!new_val);
+ } else {
+ info!("Tray: {} = {}", key, val_str);
+ }
+ }
+ _ => {}
+ }
+ return;
+ }
+
+ // -- action items
+ match id {
+ "exit" => std::process::exit(0),
+ "restart" => {
+ info!("Restarting from tray menu...");
+ restart_app();
+ }
+ "settings" => {
+ info!("Opening settings from tray menu...");
+ open_settings();
+ }
+ _ => {}
+ }
+}
+
+// HELPERS
+
+fn load_icon_from_bytes(bytes: &[u8]) -> tray_icon::Icon {
+ let image = image::load_from_memory(bytes)
+ .expect("Failed to load icon")
+ .into_rgba8();
+ let (width, height) = image.dimensions();
+ let rgba = image.into_raw();
+ tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to create icon")
+}
+
+fn restart_app() {
+ let exe_path = match std::env::current_exe() {
+ Ok(path) => path,
+ Err(e) => {
+ error!("Failed to get executable path: {}", e);
+ return;
+ }
+ };
+
+ match Command::new(&exe_path).spawn() {
+ Ok(_) => {
+ info!("Spawned new instance, exiting current...");
+ std::process::exit(0);
+ }
+ Err(e) => {
+ error!("Failed to restart: {}", e);
+ }
+ }
+}
+
+fn open_settings() {
+ if ipc::has_clients() {
+ info!("GUI is connected, sending reveal event");
+ ipc::send(IpcEvent::RevealWindow);
+ } else {
+ info!("GUI not connected, launching jarvis-gui");
+ launch_gui();
+ }
+}
+
+fn launch_gui() {
+ let exe_path = match std::env::current_exe() {
+ Ok(path) => path,
+ Err(e) => {
+ error!("Failed to get executable path: {}", e);
+ return;
+ }
+ };
+
+ let gui_path = exe_path.parent()
+ .map(|p| p.join(get_gui_executable_name()))
+ .unwrap_or_else(|| get_gui_executable_name().into());
+
+ info!("Launching GUI: {:?}", gui_path);
+
+ match Command::new(&gui_path).spawn() {
+ Ok(_) => info!("Launched jarvis-gui"),
+ Err(e) => error!("Failed to launch jarvis-gui: {}", e),
+ }
+}
+
+#[cfg(target_os = "windows")]
+fn get_gui_executable_name() -> &'static str {
+ "jarvis-gui.exe"
+}
+
+#[cfg(not(target_os = "windows"))]
+fn get_gui_executable_name() -> &'static str {
+ "jarvis-gui"
+}
diff --git a/crates/jarvis-app/src/tray/menu.rs b/crates/jarvis-app/src/tray/menu.rs
new file mode 100644
index 0000000..fe023a0
--- /dev/null
+++ b/crates/jarvis-app/src/tray/menu.rs
@@ -0,0 +1,182 @@
+use tray_icon::menu::{CheckMenuItem, Menu, MenuItem, PredefinedMenuItem, Submenu};
+
+use jarvis_core::{i18n, voices, SettingsManager};
+use jarvis_core::config::structs::{WakeWordEngine, NoiseSuppressionBackend};
+
+// RADIO GROUP
+
+// a group of check menu items where only one can be active at a time.
+// stores (menu_item, setting_value) pairs.
+pub struct RadioGroup {
+ pub setting_key: String,
+ pub items: Vec<(CheckMenuItem, String)>,
+}
+
+impl RadioGroup {
+ pub fn select(&self, value: &str) {
+ for (item, val) in &self.items {
+ item.set_checked(val == value);
+ }
+ }
+}
+
+// TRAY MENU STATE
+
+pub struct TrayMenu {
+ pub menu: Menu,
+ pub state: TrayState,
+}
+
+// holds references to menu items for updating check marks after build
+pub struct TrayState {
+ pub radio_groups: Vec,
+ pub gain_toggle: CheckMenuItem,
+}
+
+// BUILD
+
+pub fn build(settings: &SettingsManager) -> TrayMenu {
+ let menu = Menu::new();
+
+ let mut radio_groups = Vec::new();
+
+ // -- language submenu
+ let lang_sub = Submenu::new(i18n::t("tray-language"), true);
+ let current_lang = settings.read("language").unwrap_or_default();
+ let mut lang_items = Vec::new();
+ for &lang in i18n::SUPPORTED_LANGUAGES {
+ let label = match lang {
+ "ru" => "Русский",
+ "en" => "English",
+ "ua" => "Українська",
+ _ => lang,
+ };
+ let item = CheckMenuItem::with_id(
+ format!("set:language:{}", lang),
+ label,
+ true,
+ lang == current_lang,
+ None,
+ );
+ let _ = lang_sub.append(&item);
+ lang_items.push((item, lang.to_string()));
+ }
+ radio_groups.push(RadioGroup {
+ setting_key: "language".to_string(),
+ items: lang_items,
+ });
+
+ // -- voice submenu
+ let voice_sub = Submenu::new(i18n::t("tray-voice"), true);
+ let current_voice = voices::get_current_voice()
+ .map(|v| v.voice.id.clone())
+ .unwrap_or_default();
+ let mut voice_items = Vec::new();
+ for voice in voices::list_voices() {
+ let item = CheckMenuItem::with_id(
+ format!("set:assistant_voice:{}", voice.voice.id),
+ &voice.voice.name,
+ true,
+ voice.voice.id == current_voice,
+ None,
+ );
+ let _ = voice_sub.append(&item);
+ voice_items.push((item, voice.voice.id.clone()));
+ }
+ radio_groups.push(RadioGroup {
+ setting_key: "assistant_voice".to_string(),
+ items: voice_items,
+ });
+
+ // -- wake word engine submenu
+ let ww_sub = Submenu::new(i18n::t("tray-wake-word"), true);
+ let current_ww = settings.read("selected_wake_word_engine").unwrap_or_default();
+ let mut ww_items = Vec::new();
+ for (label, value) in &[("Rustpotter", "Rustpotter"), ("Vosk", "Vosk")] {
+ let item = CheckMenuItem::with_id(
+ format!("set:selected_wake_word_engine:{}", value.to_lowercase()),
+ *label,
+ true,
+ current_ww == *label,
+ None,
+ );
+ let _ = ww_sub.append(&item);
+ ww_items.push((item, value.to_lowercase()));
+ }
+ radio_groups.push(RadioGroup {
+ setting_key: "selected_wake_word_engine".to_string(),
+ items: ww_items,
+ });
+
+ // -- noise suppression submenu
+ let ns_sub = Submenu::new(i18n::t("tray-noise-suppression"), true);
+ let current_ns = settings.read("noise_suppression").unwrap_or_default();
+ let mut ns_items = Vec::new();
+ for (label, value) in &[("None", "none"), ("Nnnoiseless", "nnnoiseless")] {
+ let item = CheckMenuItem::with_id(
+ format!("set:noise_suppression:{}", value),
+ *label,
+ true,
+ current_ns.to_lowercase() == *value,
+ None,
+ );
+ let _ = ns_sub.append(&item);
+ ns_items.push((item, value.to_string()));
+ }
+ radio_groups.push(RadioGroup {
+ setting_key: "noise_suppression".to_string(),
+ items: ns_items,
+ });
+
+ // -- vad submenu
+ let vad_sub = Submenu::new(i18n::t("tray-vad"), true);
+ let current_vad = settings.read("vad_backend").unwrap_or_default();
+ let mut vad_items = Vec::new();
+ for (label, value) in &[("None", "none"), ("Energy", "energy"), ("Nnnoiseless", "nnnoiseless")] {
+ let item = CheckMenuItem::with_id(
+ format!("set:vad_backend:{}", value),
+ *label,
+ true,
+ current_vad == *value,
+ None,
+ );
+ let _ = vad_sub.append(&item);
+ vad_items.push((item, value.to_string()));
+ }
+ radio_groups.push(RadioGroup {
+ setting_key: "vad_backend".to_string(),
+ items: vad_items,
+ });
+
+ // -- gain normalizer toggle
+ let gain_on = settings.read("gain_normalizer")
+ .map(|v| v == "true")
+ .unwrap_or(true);
+ let gain_toggle = CheckMenuItem::with_id(
+ "toggle:gain_normalizer",
+ i18n::t("tray-gain-normalizer"),
+ true,
+ gain_on,
+ None,
+ );
+
+ // -- assemble main menu
+ let _ = menu.append(&lang_sub);
+ let _ = menu.append(&voice_sub);
+ let _ = menu.append(&ww_sub);
+ let _ = menu.append(&ns_sub);
+ let _ = menu.append(&vad_sub);
+ let _ = menu.append(&gain_toggle);
+ let _ = menu.append(&PredefinedMenuItem::separator());
+ let _ = menu.append(&MenuItem::with_id("restart", i18n::t("tray-restart"), true, None));
+ let _ = menu.append(&MenuItem::with_id("settings", i18n::t("tray-settings"), true, None));
+ let _ = menu.append(&MenuItem::with_id("exit", i18n::t("tray-exit"), true, None));
+
+ TrayMenu {
+ menu,
+ state: TrayState {
+ radio_groups,
+ gain_toggle,
+ },
+ }
+}
diff --git a/crates/jarvis-cli/Cargo.toml b/crates/jarvis-cli/Cargo.toml
new file mode 100644
index 0000000..64644a9
--- /dev/null
+++ b/crates/jarvis-cli/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "jarvis-cli"
+version.workspace = true
+authors.workspace = true
+license.workspace = true
+repository.workspace = true
+edition.workspace = true
+
+[dependencies]
+jarvis-core = { path = "../jarvis-core", default-features = false, features = ["intent"]}
+tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros"] }
+log.workspace = true
+env_logger = "0.11"
+parking_lot.workspace = true
\ No newline at end of file
diff --git a/crates/jarvis-cli/src/main.rs b/crates/jarvis-cli/src/main.rs
new file mode 100644
index 0000000..d161b5d
--- /dev/null
+++ b/crates/jarvis-cli/src/main.rs
@@ -0,0 +1,203 @@
+use std::io::{self, Write};
+
+use jarvis_core::{COMMANDS_LIST, DB, JCommandsList, commands, config, db, intent};
+
+fn print_help() {
+ println!("
+--## Jarvis CLI - Testing Tool ##--
+
+Commands:
+ classify - Test intent classification
+ execute - Simulate voice input and execute command
+ list - List all loaded commands
+ phrases - List all training phrases
+ hash - Show commands hash
+ settings - Dump all settings
+ help - Show this help
+ exit - Exit the CLI
+");
+}
+
+fn list_commands(commands: &[JCommandsList]) {
+ println!("\n[ Loaded Commands ]");
+ for cmd_list in commands {
+ println!(" 📁 {}", cmd_list.path.display());
+ for cmd in &cmd_list.commands {
+ println!(" ├─ id: {}", cmd.id);
+ println!(" ├─ type: {}", cmd.cmd_type);
+ println!(" └─ phrases: {} languages", cmd.phrases.len());
+ }
+ }
+ println!();
+}
+
+fn list_phrases(commands: &[JCommandsList]) {
+ println!("\n[ Training Phrases ]");
+ for cmd_list in commands {
+ for cmd in &cmd_list.commands {
+ println!(" [{}]", cmd.id);
+ for (lang, phrases) in &cmd.phrases {
+ println!(" lang: {}", lang);
+ for phrase in phrases {
+ println!(" - {}", phrase);
+ }
+ }
+ }
+ }
+ println!();
+}
+
+async fn classify_text(text: &str) {
+ match intent::classify(text).await {
+ Some((intent_id, confidence)) => {
+ println!(" ✓ Intent: {} (confidence: {:.2}%)", intent_id, confidence * 100.0);
+ }
+ None => {
+ println!(" ✗ No intent matched (below threshold)");
+ }
+ }
+}
+
+async fn execute_text(commands: &[JCommandsList], text: &str) {
+ // try intent classification first
+ if let Some((intent_id, confidence)) = intent::classify(text).await {
+ println!(" Intent: {} (confidence: {:.2}%)", intent_id, confidence * 100.0);
+
+ if let Some((cmd_path, cmd)) = intent::get_command_by_intent(commands, &intent_id) {
+ println!(" Command: {:?}", cmd_path);
+ println!(" Type: {}", cmd.cmd_type);
+ println!(" Executing...");
+
+ match commands::execute_command(cmd_path, cmd, Some(text), None) {
+ Ok(chain) => println!(" ✓ Success (chain: {})", chain),
+ Err(e) => println!(" ✗ Error: {}", e),
+ }
+ return;
+ }
+ }
+
+ // fallback to levenshtein
+ println!(" Intent not matched, trying levenshtein fallback...");
+ if let Some((cmd_path, cmd)) = commands::fetch_command(text, commands) {
+ println!(" Command: {:?}", cmd_path);
+ println!(" Type: {}", cmd.cmd_type);
+ println!(" Executing...");
+
+ match commands::execute_command(cmd_path, cmd, Some(text), None) {
+ Ok(chain) => println!(" ✓ Success (chain: {})", chain),
+ Err(e) => println!(" ✗ Error: {}", e),
+ }
+ } else {
+ println!(" ✗ No command matched");
+ }
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ // init logging
+ env_logger::Builder::from_env(
+ env_logger::Env::default().default_filter_or("info")
+ ).init();
+
+ println!("Jarvis CLI v{}", config::APP_VERSION.unwrap_or("unknown"));
+
+ // init dirs
+ config::init_dirs()?;
+
+ // init settings
+ let settings = db::init();
+ DB.set(settings.arc().clone())
+ .expect("DB already initialized");
+
+ // parse commands
+ println!("\n[*] Loading commands...");
+ let cmds = match commands::parse_commands() {
+ Ok(c) => {
+ println!(" Loaded {} command groups", c.len());
+ c
+ }
+ Err(e) => {
+ println!(" Warning: {}", e);
+ Vec::new()
+ }
+ };
+ COMMANDS_LIST.set(cmds).expect("Failed to set commands list");
+
+ // init intent classifier
+ println!("[*] Initializing intent classifier...");
+ match intent::init(COMMANDS_LIST.get().unwrap()).await {
+ Ok(_) => println!(" Intent classifier ready"),
+ Err(e) => println!(" Warning: {}", e),
+ }
+
+ // init sound
+ println!("[*] Initializing audio...");
+ if let Err(e) = jarvis_core::audio::init() {
+ println!(" Warning: Audio init failed: {:?}", e);
+ }
+
+ print_help();
+
+ // REPL loop
+ let mut input = String::new();
+ loop {
+ print!("jarvis> ");
+ io::stdout().flush()?;
+
+ input.clear();
+ io::stdin().read_line(&mut input)?;
+ let input = input.trim();
+
+ if input.is_empty() {
+ continue;
+ }
+
+ let parts: Vec<&str> = input.splitn(2, ' ').collect();
+ let cmd = parts[0];
+ let arg = parts.get(1).copied().unwrap_or("");
+
+ match cmd {
+ "exit" | "quit" | "q" => {
+ println!("Bye!");
+ break;
+ }
+ "help" | "h" | "?" => print_help(),
+ "list" | "ls" => list_commands(COMMANDS_LIST.get().unwrap()),
+ "phrases" => list_phrases(COMMANDS_LIST.get().unwrap()),
+ "hash" => {
+ let hash = commands::commands_hash(COMMANDS_LIST.get().unwrap());
+ println!(" Commands hash: {}", hash);
+ }
+ "settings" => {
+ println!("\n[ Current Settings ]");
+ for (key, val) in settings.dump() {
+ println!(" {} = {}", key, val);
+ }
+ println!();
+ }
+ "classify" | "c" => {
+ if arg.is_empty() {
+ println!(" Usage: classify ");
+ } else {
+ classify_text(arg).await;
+ }
+ }
+ "execute" | "exec" | "e" => {
+ if arg.is_empty() {
+ println!(" Usage: execute ");
+ } else {
+ execute_text(COMMANDS_LIST.get().unwrap(), arg).await;
+ }
+ }
+ "reload" => {
+ println!(" Note: Reload requires app restart (statics can't be reset)");
+ }
+ _ => {
+ // treat unknown commands as text to classify
+ classify_text(input).await;
+ }
+ }
+ }
+
+ Ok(())
+}
diff --git a/crates/jarvis-core/Cargo.toml b/crates/jarvis-core/Cargo.toml
new file mode 100644
index 0000000..0033a9f
--- /dev/null
+++ b/crates/jarvis-core/Cargo.toml
@@ -0,0 +1,64 @@
+[package]
+name = "jarvis-core"
+version.workspace = true
+authors.workspace = true
+license.workspace = true
+repository.workspace = true
+edition.workspace = true
+
+[dependencies]
+serde.workspace = true
+serde_json.workspace = true
+serde_yaml.workspace = true
+once_cell.workspace = true
+log.workspace = true
+rand.workspace = true
+seqdiff.workspace = true
+hound.workspace = true
+platform-dirs.workspace = true
+rodio.workspace = true
+kira.workspace = true
+pv_recorder.workspace = true
+rustpotter.workspace = true
+parking_lot.workspace = true
+toml.workspace = true
+sha2.workspace = true
+nnnoiseless = { workspace = true, optional = true }
+tokio-tungstenite = { workspace = true, optional = true }
+futures-util = { workspace = true, optional = true }
+fluent.workspace = true
+fluent-bundle.workspace = true
+unic-langid.workspace = true
+chrono.workspace = true
+sys-locale.workspace = true
+
+# pv_recorder = { workspace = true, optional = true }
+vosk = { version = "0.3.1", optional = true }
+intent-classifier = { version = "0.1.0", optional = true }
+# rustpotter = { workspace = true, optional = true }
+
+tokio = { version = "1", features = ["sync"], optional = true }
+
+mlua = { workspace = true, optional = true }
+reqwest = { workspace = true, optional = true }
+tempfile.workspace = true
+
+fastembed = { workspace = true, optional = true }
+ort = { workspace = true, optional = true }
+ndarray = { workspace = true, optional = true }
+tokenizers = { workspace = true, optional = true }
+regex = { workspace = true, optional = true }
+
+[target.'cfg(windows)'.dependencies]
+winrt-notification = { workspace = true, optional = true }
+
+[features]
+default = ["jarvis_app"]
+jarvis_app = [
+ "vosk", "intent-classifier", "fastembed", "tokio", "nnnoiseless", "tokio-tungstenite", "futures-util",
+ "lua",
+ "ort", "ndarray", "tokenizers", "regex",]
+
+intent = ["intent-classifier", "tokio"]
+lua = ["mlua", "reqwest", "winrt-notification"]
+lua_only = ["lua", "tokio"]
\ No newline at end of file
diff --git a/crates/jarvis-core/src/audio.rs b/crates/jarvis-core/src/audio.rs
new file mode 100644
index 0000000..da9302e
--- /dev/null
+++ b/crates/jarvis-core/src/audio.rs
@@ -0,0 +1,92 @@
+mod kira;
+mod rodio;
+
+use once_cell::sync::OnceCell;
+use std::path::PathBuf;
+
+use crate::config::structs::AudioType;
+use crate::{config, DB, SOUND_DIR};
+
+static AUDIO_TYPE: OnceCell = OnceCell::new();
+
+pub fn init() -> Result<(), ()> {
+ if AUDIO_TYPE.get().is_some() {
+ return Ok(());
+ } // already initialized
+
+ // set default audio type
+ // @TODO. Make it configurable?
+ AUDIO_TYPE.set(config::DEFAULT_AUDIO_TYPE).unwrap();
+
+ // load given audio backend
+ match AUDIO_TYPE.get().unwrap() {
+ AudioType::Rodio => {
+ // Init Rodio
+ info!("Initializing Rodio audio backend.");
+
+ match rodio::init() {
+ Ok(_) => {
+ info!("Successfully initialized Rodio audio backend.");
+ }
+ Err(()) => {
+ error!("Failed to initialize Rodio audio backend.");
+
+ return Err(());
+ }
+ }
+ }
+ AudioType::Kira => {
+ // Init Kira
+ info!("Initializing Kira audio backend.");
+
+ match kira::init() {
+ Ok(_) => {
+ info!("Successfully initialized Kira audio backend.");
+ }
+ Err(_msg) => {
+ error!("Failed to initialize Kira audio backend.");
+
+ return Err(());
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
+
+pub fn play_sound(filename: &PathBuf) {
+ let audio_type = match AUDIO_TYPE.get() {
+ Some(t) => t,
+ None => {
+ warn!("Audio not initialized, cannot play: {}", filename.display());
+ return;
+ }
+ };
+
+ info!("Playing {}", filename.display());
+
+ match audio_type {
+ AudioType::Rodio => {
+ rodio::play_sound(filename, true);
+ }
+ AudioType::Kira => kira::play_sound(filename),
+ }
+}
+
+pub fn get_sound_directory() -> Option {
+ let db = DB.get()?;
+
+ let voice_path = {
+ let s = db.read();
+ SOUND_DIR.join(&s.voice)
+ };
+
+ match voice_path.exists() {
+ true => Some(voice_path),
+ _ => {
+ error!("No sounds folder found. Search path - {:?}", voice_path);
+ None
+ }
+ }
+}
diff --git a/crates/jarvis-core/src/audio/kira.rs b/crates/jarvis-core/src/audio/kira.rs
new file mode 100644
index 0000000..10f927c
--- /dev/null
+++ b/crates/jarvis-core/src/audio/kira.rs
@@ -0,0 +1,62 @@
+use once_cell::sync::OnceCell;
+use std::path::PathBuf;
+use std::sync::Mutex;
+
+// use kira::{
+// manager::{backend::DefaultBackend, AudioManager, AudioManagerSettings},
+// sound::static_sound::{StaticSoundData, StaticSoundSettings},
+// };
+
+use kira::{
+ AudioManager, AudioManagerSettings, DefaultBackend,
+ sound::static_sound::StaticSoundData,
+};
+
+static MANAGER: OnceCell> = OnceCell::new();
+
+pub fn init() -> Result<(), ()> {
+ if MANAGER.get().is_some() {
+ return Ok(());
+ } // already initialized
+
+ // Create an audio manager. This plays sounds and manages resources.
+ match AudioManager::::new(AudioManagerSettings::default()) {
+ Ok(manager) => {
+ // store
+ MANAGER.set(Mutex::new(manager)).ok();
+
+ // success
+ Ok(())
+ }
+ Err(msg) => {
+ error!("Failed to initialize audio stream.\nError details: {}", msg);
+
+ // failed
+ Err(())
+ }
+ }
+}
+
+// @TODO. Cache sounds in memory? With a pool of a certain size, for instance.
+pub fn play_sound(filename: &PathBuf) {
+ // load the file
+ match StaticSoundData::from_file(filename) {
+ Ok(sound_data) => {
+ // sound_data.duration() can be used in order to sleep, if (for some reason) blocking behaviour is required
+
+ // play it (non-blocking)
+ if let Some(manager) = MANAGER.get() {
+ if let Ok(mut audio_manager) = manager.lock() {
+ if let Err(e) = audio_manager.play(sound_data) {
+ warn!("Failed to play sound: {}", e);
+ }
+ }
+ } else {
+ warn!("Audio manager not initialized");
+ }
+ }
+ Err(err) => {
+ warn!("Cannot find sound file: {} (err: {})", filename.display(), err);
+ }
+ }
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/audio/rodio.rs b/crates/jarvis-core/src/audio/rodio.rs
new file mode 100644
index 0000000..340a85c
--- /dev/null
+++ b/crates/jarvis-core/src/audio/rodio.rs
@@ -0,0 +1,65 @@
+/*
+ Abandoned temporary.
+ Problems with blocking behaviour.
+ Possible fixes are running rodio in a separate thread or smthng.
+*/
+
+use once_cell::sync::OnceCell;
+use std::fs::File;
+use std::io::BufReader;
+use std::path::PathBuf;
+
+// use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink};
+use rodio::{Decoder, OutputStream, Sink};
+
+// static STREAM: OnceCell = OnceCell::new();
+static STREAM_HANDLE: OnceCell = OnceCell::new();
+static SINK: OnceCell = OnceCell::new();
+
+pub fn init() -> Result<(), ()> {
+ if STREAM_HANDLE.get().is_some() {
+ return Ok(());
+ } // already initialized
+
+ // get output stream handle to the default physical sound device
+ match rodio::OutputStreamBuilder::open_default_stream() {
+ Ok(stream_handle) => {
+ // create sink
+ let sink = Sink::connect_new(&stream_handle.mixer());
+ info!("Sink initialized.");
+
+ // store
+ // STREAM.set(_stream).unwrap();
+ let _ = STREAM_HANDLE.set(stream_handle);
+ let _ = SINK.set(sink);
+
+ // success
+ Ok(())
+ }
+ Err(msg) => {
+ error!("Failed to initialize audio stream.\nError details: {}", msg);
+
+ // failed
+ Err(())
+ }
+ }
+}
+
+pub fn play_sound(filename: &PathBuf, sleep: bool) {
+ // Load a sound from a file, using a path relative to Cargo.toml
+ // let filepath = format!("{PUBLIC_PATH}/sound/{filename}.wav");
+ let file = BufReader::new(File::open(&filename).unwrap());
+
+ // Decode that sound file into a source
+ let source = Decoder::new(file).unwrap();
+
+ // Play the sound directly on the device
+ // STREAM_HANDLE.get().unwrap().play_raw(source.convert_samples());
+ SINK.get().unwrap().append(source);
+
+ if sleep {
+ // The sound plays in a separate thread. This call will block the current thread until the sink
+ // has finished playing all its queued sounds.
+ SINK.get().unwrap().sleep_until_end();
+ }
+}
diff --git a/crates/jarvis-core/src/audio_buffer.rs b/crates/jarvis-core/src/audio_buffer.rs
new file mode 100644
index 0000000..ad8f915
--- /dev/null
+++ b/crates/jarvis-core/src/audio_buffer.rs
@@ -0,0 +1,41 @@
+use std::collections::VecDeque;
+
+pub struct AudioRingBuffer {
+ buffer: VecDeque>,
+ max_frames: usize,
+}
+
+impl AudioRingBuffer {
+ // Create buffer that holds `seconds` worth of audio at given frame_size and sample_rate
+ pub fn new(seconds: f32, frame_size: usize, sample_rate: usize) -> Self {
+ let frames_per_second = sample_rate / frame_size;
+ let max_frames = (frames_per_second as f32 * seconds) as usize;
+
+ Self {
+ buffer: VecDeque::with_capacity(max_frames),
+ max_frames,
+ }
+ }
+
+ // Push a frame, dropping oldest if full
+ pub fn push(&mut self, frame: &[i16]) {
+ if self.buffer.len() >= self.max_frames {
+ self.buffer.pop_front();
+ }
+ self.buffer.push_back(frame.to_vec());
+ }
+
+ // Drain all buffered frames into a single vec
+ pub fn drain_all(&mut self) -> Vec> {
+ self.buffer.drain(..).collect()
+ }
+
+ // Get frame count
+ pub fn len(&self) -> usize {
+ self.buffer.len()
+ }
+
+ pub fn clear(&mut self) {
+ self.buffer.clear();
+ }
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/audio_processing.rs b/crates/jarvis-core/src/audio_processing.rs
new file mode 100644
index 0000000..52c09ef
--- /dev/null
+++ b/crates/jarvis-core/src/audio_processing.rs
@@ -0,0 +1,117 @@
+pub mod noise_suppression;
+pub mod vad;
+pub mod gain_normalizer;
+
+use once_cell::sync::OnceCell;
+use parking_lot::Mutex;
+
+use crate::config::structs::NoiseSuppressionBackend;
+use crate::DB;
+
+static PROCESSOR: OnceCell> = OnceCell::new();
+
+#[derive(Debug, Clone)]
+pub struct ProcessedAudio {
+ pub samples: Vec,
+ pub is_voice: bool,
+ pub vad_confidence: f32,
+}
+
+struct AudioProcessor {
+ has_gain: bool,
+ has_ns: bool,
+}
+
+impl AudioProcessor {
+ fn new(ns: NoiseSuppressionBackend, gain: bool) -> Self {
+ noise_suppression::init(ns);
+ vad::init();
+ if gain {
+ gain_normalizer::init();
+ }
+
+ Self {
+ has_gain: gain,
+ has_ns: !matches!(ns, NoiseSuppressionBackend::None),
+ }
+ }
+
+ fn process(&mut self, input: &[i16]) -> ProcessedAudio {
+ let gained: Vec;
+ let after_gain: &[i16] = if self.has_gain {
+ gained = gain_normalizer::normalize(input);
+ &gained
+ } else {
+ input
+ };
+
+ let suppressed: Vec;
+ let after_ns: &[i16] = if self.has_ns {
+ suppressed = noise_suppression::process(after_gain);
+ &suppressed
+ } else {
+ after_gain
+ };
+
+ let (is_voice, confidence) = vad::detect(after_ns);
+
+ ProcessedAudio {
+ samples: after_ns.to_vec(),
+ is_voice,
+ vad_confidence: confidence,
+ }
+ }
+
+ fn reset(&mut self) {
+ noise_suppression::reset();
+ vad::reset();
+ gain_normalizer::reset();
+ }
+}
+
+pub fn init() -> Result<(), String> {
+ if PROCESSOR.get().is_some() {
+ return Ok(());
+ }
+
+ let (ns, gain) = get_settings();
+ info!("Initializing audio processing: NS={:?}, Gain={}", ns, gain);
+
+ let processor = AudioProcessor::new(ns, gain);
+ PROCESSOR
+ .set(Mutex::new(processor))
+ .map_err(|_| "Audio processor already initialized".to_string())?;
+
+ info!("Audio processing initialized.");
+ Ok(())
+}
+
+pub fn process(input: &[i16]) -> ProcessedAudio {
+ match PROCESSOR.get() {
+ Some(p) => p.lock().process(input),
+ None => ProcessedAudio {
+ samples: input.to_vec(),
+ is_voice: true,
+ vad_confidence: 1.0,
+ },
+ }
+}
+
+pub fn reset() {
+ if let Some(p) = PROCESSOR.get() {
+ p.lock().reset();
+ }
+}
+
+fn get_settings() -> (NoiseSuppressionBackend, bool) {
+ match DB.get() {
+ Some(db) => {
+ let settings = db.read();
+ (settings.noise_suppression, settings.gain_normalizer)
+ }
+ None => (
+ crate::config::DEFAULT_NOISE_SUPPRESSION,
+ crate::config::DEFAULT_GAIN_NORMALIZER,
+ ),
+ }
+}
diff --git a/crates/jarvis-core/src/audio_processing/gain_normalizer.rs b/crates/jarvis-core/src/audio_processing/gain_normalizer.rs
new file mode 100644
index 0000000..2278ca8
--- /dev/null
+++ b/crates/jarvis-core/src/audio_processing/gain_normalizer.rs
@@ -0,0 +1,28 @@
+mod simple;
+
+use once_cell::sync::OnceCell;
+use parking_lot::Mutex;
+
+static NORMALIZER: OnceCell> = OnceCell::new();
+
+pub fn init() {
+ if NORMALIZER.get().is_some() {
+ return;
+ }
+
+ NORMALIZER.set(Mutex::new(simple::GainNormalizer::new())).ok();
+ info!("Gain normalizer: enabled");
+}
+
+pub fn normalize(input: &[i16]) -> Vec {
+ match NORMALIZER.get() {
+ Some(n) => n.lock().normalize(input),
+ None => input.to_vec(),
+ }
+}
+
+pub fn reset() {
+ if let Some(n) = NORMALIZER.get() {
+ n.lock().reset();
+ }
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/audio_processing/gain_normalizer/simple.rs b/crates/jarvis-core/src/audio_processing/gain_normalizer/simple.rs
new file mode 100644
index 0000000..b58da1f
--- /dev/null
+++ b/crates/jarvis-core/src/audio_processing/gain_normalizer/simple.rs
@@ -0,0 +1,47 @@
+use crate::config;
+
+pub struct GainNormalizer {
+ current_gain: f32,
+}
+
+impl GainNormalizer {
+ pub fn new() -> Self {
+ Self { current_gain: 1.0 }
+ }
+
+ pub fn normalize(&mut self, input: &[i16]) -> Vec {
+ let rms = self.calculate_rms(input);
+
+ if rms < 1.0 {
+ return input.to_vec();
+ }
+
+ let target_gain = config::GAIN_TARGET_RMS / rms;
+ let clamped_gain = target_gain.clamp(config::GAIN_MIN, config::GAIN_MAX);
+
+ self.current_gain = self.current_gain * 0.9 + clamped_gain * 0.1;
+
+ input.iter()
+ .map(|&s| {
+ let amplified = (s as f32) * self.current_gain;
+ amplified.clamp(i16::MIN as f32, i16::MAX as f32) as i16
+ })
+ .collect()
+ }
+
+ pub fn reset(&mut self) {
+ self.current_gain = 1.0;
+ }
+
+ fn calculate_rms(&self, samples: &[i16]) -> f32 {
+ if samples.is_empty() {
+ return 0.0;
+ }
+
+ let sum: f64 = samples.iter()
+ .map(|&s| (s as f64).powi(2))
+ .sum();
+
+ (sum / samples.len() as f64).sqrt() as f32
+ }
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/audio_processing/noise_suppression.rs b/crates/jarvis-core/src/audio_processing/noise_suppression.rs
new file mode 100644
index 0000000..a5ca879
--- /dev/null
+++ b/crates/jarvis-core/src/audio_processing/noise_suppression.rs
@@ -0,0 +1,65 @@
+mod none;
+
+use once_cell::sync::OnceCell;
+use parking_lot::Mutex;
+
+use crate::config::structs::NoiseSuppressionBackend;
+
+static BACKEND: OnceCell = OnceCell::new();
+
+#[cfg(feature = "nnnoiseless")]
+static NNNOISELESS_STATE: OnceCell> = OnceCell::new();
+
+pub fn init(backend: NoiseSuppressionBackend) {
+ if BACKEND.get().is_some() {
+ return;
+ }
+
+ // fallback if nnnoiseless not compiled in
+ #[cfg(not(feature = "nnnoiseless"))]
+ if matches!(backend, NoiseSuppressionBackend::Nnnoiseless) {
+ warn!("Nnnoiseless not compiled in, falling back to None");
+ backend = NoiseSuppressionBackend::None;
+ }
+
+ BACKEND.set(backend).ok();
+
+ match backend {
+ NoiseSuppressionBackend::None => {
+ info!("Noise suppression: disabled");
+ }
+ #[cfg(feature = "nnnoiseless")]
+ NoiseSuppressionBackend::Nnnoiseless => {
+ NNNOISELESS_STATE.set(Mutex::new(crate::models::nnnoiseless::NnnoiselessNS::new())).ok();
+ info!("Noise suppression: Nnnoiseless");
+ }
+ #[cfg(not(feature = "nnnoiseless"))]
+ _ => {}
+ }
+}
+
+pub fn process(input: &[i16]) -> Vec {
+ match BACKEND.get() {
+ #[cfg(feature = "nnnoiseless")]
+ Some(NoiseSuppressionBackend::Nnnoiseless) => {
+ if let Some(state) = NNNOISELESS_STATE.get() {
+ state.lock().process(input)
+ } else {
+ none::process(input)
+ }
+ }
+ _ => none::process(input),
+ }
+}
+
+pub fn reset() {
+ match BACKEND.get() {
+ #[cfg(feature = "nnnoiseless")]
+ Some(NoiseSuppressionBackend::Nnnoiseless) => {
+ if let Some(state) = NNNOISELESS_STATE.get() {
+ state.lock().reset();
+ }
+ }
+ _ => {}
+ }
+}
diff --git a/crates/jarvis-core/src/audio_processing/noise_suppression/none.rs b/crates/jarvis-core/src/audio_processing/noise_suppression/none.rs
new file mode 100644
index 0000000..87174f9
--- /dev/null
+++ b/crates/jarvis-core/src/audio_processing/noise_suppression/none.rs
@@ -0,0 +1,4 @@
+// return unprocessed input
+pub fn process(input: &[i16]) -> Vec {
+ input.to_vec()
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/audio_processing/vad.rs b/crates/jarvis-core/src/audio_processing/vad.rs
new file mode 100644
index 0000000..a2b57c5
--- /dev/null
+++ b/crates/jarvis-core/src/audio_processing/vad.rs
@@ -0,0 +1,72 @@
+mod none;
+mod energy;
+
+use once_cell::sync::OnceCell;
+use parking_lot::Mutex;
+
+use crate::DB;
+
+static BACKEND: OnceCell = OnceCell::new();
+
+#[cfg(feature = "nnnoiseless")]
+static NNNOISELESS_STATE: OnceCell> = OnceCell::new();
+
+pub fn init() {
+ if BACKEND.get().is_some() {
+ return;
+ }
+
+ let backend = DB.get()
+ .map(|db| db.read().vad_backend.clone())
+ .unwrap_or_else(|| "energy".to_string());
+
+ BACKEND.set(backend.clone()).ok();
+
+ match backend.as_str() {
+ "none" => {
+ info!("VAD: disabled");
+ }
+ "energy" => {
+ info!("VAD: Energy-based");
+ }
+ #[cfg(feature = "nnnoiseless")]
+ "nnnoiseless" => {
+ NNNOISELESS_STATE.set(Mutex::new(crate::models::nnnoiseless::NnnoiselessVAD::new())).ok();
+ info!("VAD: Nnnoiseless");
+ }
+ other => {
+ warn!("Unknown VAD backend '{}', falling back to energy", other);
+ // overwrite with energy
+ // (BACKEND already set, so energy::detect will be used via fallthrough)
+ }
+ }
+}
+
+// returns (is_voice, confidence)
+pub fn detect(input: &[i16]) -> (bool, f32) {
+ match BACKEND.get().map(|s| s.as_str()) {
+ Some("none") | None => none::detect(input),
+ Some("energy") => energy::detect(input),
+ #[cfg(feature = "nnnoiseless")]
+ Some("nnnoiseless") => {
+ if let Some(state) = NNNOISELESS_STATE.get() {
+ state.lock().detect(input)
+ } else {
+ energy::detect(input)
+ }
+ }
+ _ => energy::detect(input),
+ }
+}
+
+pub fn reset() {
+ match BACKEND.get().map(|s| s.as_str()) {
+ #[cfg(feature = "nnnoiseless")]
+ Some("nnnoiseless") => {
+ if let Some(state) = NNNOISELESS_STATE.get() {
+ state.lock().reset();
+ }
+ }
+ _ => {}
+ }
+}
diff --git a/crates/jarvis-core/src/audio_processing/vad/energy.rs b/crates/jarvis-core/src/audio_processing/vad/energy.rs
new file mode 100644
index 0000000..9fd1011
--- /dev/null
+++ b/crates/jarvis-core/src/audio_processing/vad/energy.rs
@@ -0,0 +1,24 @@
+use crate::config;
+
+// Simple energy-based VAD
+pub fn detect(input: &[i16]) -> (bool, f32) {
+ let rms = calculate_rms(input);
+ let is_voice = rms > config::VAD_ENERGY_THRESHOLD;
+
+ // normalize confidence to 0-1 range (rough approximation)
+ let confidence = (rms / (config::VAD_ENERGY_THRESHOLD * 2.0)).min(1.0);
+
+ (is_voice, confidence)
+}
+
+fn calculate_rms(samples: &[i16]) -> f32 {
+ if samples.is_empty() {
+ return 0.0;
+ }
+
+ let sum: f64 = samples.iter()
+ .map(|&s| (s as f64).powi(2))
+ .sum();
+
+ (sum / samples.len() as f64).sqrt() as f32
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/audio_processing/vad/none.rs b/crates/jarvis-core/src/audio_processing/vad/none.rs
new file mode 100644
index 0000000..f4567c5
--- /dev/null
+++ b/crates/jarvis-core/src/audio_processing/vad/none.rs
@@ -0,0 +1,4 @@
+// Always returns voice detected (no vad)
+pub fn detect(_input: &[i16]) -> (bool, f32) {
+ (true, 1.0)
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/commands.rs b/crates/jarvis-core/src/commands.rs
new file mode 100644
index 0000000..c250cd0
--- /dev/null
+++ b/crates/jarvis-core/src/commands.rs
@@ -0,0 +1,319 @@
+use std::collections::HashMap;
+use std::path::{Path, PathBuf};
+use std::fs;
+use std::time::Duration;
+use std::process::{Child, Command};
+
+use seqdiff::ratio;
+
+mod structs;
+pub use structs::*;
+
+use crate::{config, i18n, APP_DIR};
+
+#[cfg(feature = "lua")]
+use crate::lua::{self, SandboxLevel, CommandContext};
+
+pub fn parse_commands() -> Result, String> {
+ let mut commands: Vec = Vec::new();
+
+ let commands_path = APP_DIR.join(config::COMMANDS_PATH);
+ let cmd_dirs = fs::read_dir(&commands_path)
+ .map_err(|e| format!("Error reading commands directory {:?}: {}", commands_path, e))?;
+
+ for entry in cmd_dirs.flatten() {
+ let cmd_path = entry.path();
+ let toml_file = cmd_path.join("command.toml");
+
+ if !toml_file.exists() {
+ continue;
+ }
+
+ let content = match fs::read_to_string(&toml_file) {
+ Ok(c) => c,
+ Err(e) => {
+ warn!("Failed to read {}: {}", toml_file.display(), e);
+ continue;
+ }
+ };
+
+ let file: JCommandsList = match toml::from_str(&content) {
+ Ok(f) => f,
+ Err(e) => {
+ warn!("Failed to parse {}: {}", toml_file.display(), e);
+ continue;
+ }
+ };
+
+ commands.push(JCommandsList {
+ path: cmd_path,
+ commands: file.commands,
+ });
+ }
+
+ if commands.is_empty() {
+ Err("No commands found".into())
+ } else {
+ info!("Loaded {} command pack(s)", commands.len());
+ Ok(commands)
+ }
+}
+
+
+pub fn commands_hash(commands: &[JCommandsList]) -> String {
+ use sha2::{Sha256, Digest};
+
+ let mut hasher = Sha256::new();
+
+ let lang = i18n::get_language();
+ hasher.update(lang.as_bytes());
+ hasher.update(b"|");
+
+ // collect all command ids and phrases for current language, sorted
+ let mut all_data: Vec<(&str, _)> = commands.iter()
+ .flat_map(|ac| ac.commands.iter().map(|c| (c.id.as_str(), c.get_phrases(&lang))))
+ .collect();
+ all_data.sort_by_key(|(id, _)| *id);
+
+ for (id, phrases) in all_data {
+ hasher.update(id.as_bytes());
+ for phrase in phrases.iter() {
+ hasher.update(phrase.as_bytes());
+ }
+ }
+
+ format!("{:x}", hasher.finalize())
+}
+
+
+pub fn fetch_command<'a>(
+ phrase: &str,
+ commands: &'a [JCommandsList],
+) -> Option<(&'a PathBuf, &'a JCommand)> {
+ let lang = i18n::get_language();
+
+ let phrase = phrase.trim().to_lowercase();
+ if phrase.is_empty() {
+ return None;
+ }
+
+ let phrase_chars: Vec = phrase.chars().collect();
+ let phrase_words: Vec<&str> = phrase.split_whitespace().collect();
+
+ let mut result: Option<(&PathBuf, &JCommand)> = None;
+ let mut best_score = config::CMD_RATIO_THRESHOLD;
+
+ for cmd_list in commands {
+ for cmd in &cmd_list.commands {
+ let cmd_phrases = cmd.get_phrases(&lang);
+
+ for cmd_phrase in cmd_phrases.iter() {
+ let cmd_phrase_lower = cmd_phrase.trim().to_lowercase();
+ let cmd_phrase_chars: Vec = cmd_phrase_lower.chars().collect();
+
+ // character-level similarity
+ let char_ratio = ratio(&phrase_chars, &cmd_phrase_chars);
+
+ // word-level similarity
+ let cmd_words: Vec<&str> = cmd_phrase_lower.split_whitespace().collect();
+ let word_score = word_overlap_score(&phrase_words, &cmd_words);
+
+ // combined score
+ let score = (char_ratio * 0.6) + (word_score * 0.4);
+
+ // early exit on perfect match
+ if score >= 99.0 {
+ debug!("Perfect match: '{}' -> '{}'", phrase, cmd_phrase_lower);
+ return Some((&cmd_list.path, cmd));
+ }
+
+ if score > best_score {
+ best_score = score;
+ result = Some((&cmd_list.path, cmd));
+ }
+ }
+ }
+ }
+
+ if let Some((_, cmd)) = result {
+ info!("Fuzzy match: '{}' -> cmd '{}' (score: {:.1}%)", phrase, cmd.id, best_score);
+ } else {
+ debug!("No match for '{}' (best: {:.1}%)", phrase, best_score);
+ }
+
+ result
+}
+
+
+fn word_overlap_score(input_words: &[&str], cmd_words: &[&str]) -> f64 {
+ if input_words.is_empty() || cmd_words.is_empty() {
+ return 0.0;
+ }
+
+ let mut matched = 0.0;
+
+ // pre-compute cmd word chars to avoid repeated allocations
+ let cmd_word_chars: Vec> = cmd_words
+ .iter()
+ .map(|w| w.chars().collect())
+ .collect();
+
+ for input_word in input_words {
+ let input_chars: Vec = input_word.chars().collect();
+
+ let best_word_match = cmd_word_chars
+ .iter()
+ .map(|cw| ratio(&input_chars, cw))
+ .fold(0.0_f64, f64::max);
+
+ if best_word_match > 70.0 {
+ matched += best_word_match / 100.0;
+ }
+ }
+
+ let max_words = input_words.len().max(cmd_words.len()) as f64;
+ (matched / max_words) * 100.0
+}
+
+
+
+
+pub fn execute_exe(exe: &str, args: &[String]) -> std::io::Result {
+ Command::new(exe).args(args).spawn()
+}
+
+pub fn execute_cli(cmd: &str, args: &[String]) -> std::io::Result {
+ debug!("Spawning: cmd /C {} {:?}", cmd, args);
+
+ if cfg!(target_os = "windows") {
+ Command::new("cmd").arg("/C").arg(cmd).args(args).spawn()
+ } else {
+ Command::new("sh").arg("-c").arg(cmd).args(args).spawn()
+ }
+}
+
+pub fn execute_command(cmd_path: &PathBuf, cmd_config: &JCommand, phrase: Option<&str>, slots: Option<&HashMap>) -> Result {
+ // execute command by the type
+ match cmd_config.cmd_type.as_str() {
+
+ // BRUH
+ "voice" => Ok(true),
+
+ // LUA command
+ #[cfg(feature = "lua")]
+ "lua" => {
+ execute_lua_command(cmd_path, cmd_config, phrase, slots)
+ }
+
+ // AutoHotkey command
+ // @TODO: Consider adding ahk source files execution?
+ "ahk" => {
+ let exe_path_absolute = Path::new(&cmd_config.exe_path);
+ let exe_path_local = cmd_path.join(&cmd_config.exe_path);
+
+ let exe_path = if exe_path_absolute.exists() {
+ exe_path_absolute
+ } else {
+ exe_path_local.as_path()
+ };
+
+ execute_exe(exe_path.to_str().unwrap(), &cmd_config.exe_args)
+ .map(|_| true)
+ .map_err(|e| format!("AHK process spawn error: {}", e))
+ }
+
+ // CLI command type
+ // @TODO: Consider security restrictions
+ "cli" => {
+ execute_cli(&cmd_config.cli_cmd, &cmd_config.cli_args)
+ .map(|_| true)
+ .map_err(|e| format!("CLI command error: {}", e))
+ }
+
+ // TERMINATOR command (T1000)
+ "terminate" => {
+ std::thread::sleep(Duration::from_secs(2));
+ std::process::exit(0);
+ }
+
+ // STOP CHANING
+ "stop_chaining" => Ok(false),
+
+ // other
+ _ => {
+ error!("Command type unknown: {}", cmd_config.cmd_type);
+ Err(format!("Command type unknown: {}", cmd_config.cmd_type).into())
+ }
+ }
+}
+
+// look up a command by its ID
+pub fn get_command_by_id<'a>(
+ commands: &'a [JCommandsList],
+ id: &str,
+) -> Option<(&'a PathBuf, &'a JCommand)> {
+ for cmd_list in commands {
+ for cmd in &cmd_list.commands {
+ if cmd.id == id {
+ return Some((&cmd_list.path, cmd));
+ }
+ }
+ }
+ None
+}
+
+pub fn list_paths(commands: &[JCommandsList]) -> Vec<&Path> {
+ commands.iter().map(|x| x.path.as_path()).collect()
+}
+
+#[cfg(feature = "lua")]
+fn execute_lua_command(
+ cmd_path: &PathBuf,
+ cmd_config: &JCommand,
+ phrase: Option<&str>,
+ slots: Option<&HashMap>
+) -> Result {
+ // get script path
+
+ let script_name = if cmd_config.script.is_empty() {
+ "script.lua"
+ } else {
+ &cmd_config.script
+ };
+
+ let script_path = cmd_path.join(script_name);
+
+ if !script_path.exists() {
+ return Err(format!("Lua script not found: {}", script_path.display()));
+ }
+
+ // parse sandbox level
+ let sandbox = SandboxLevel::from_str(&cmd_config.sandbox);
+
+ // create context
+ let context = CommandContext {
+ phrase: phrase.unwrap_or("").to_string(),
+ command_id: cmd_config.id.clone(),
+ command_path: cmd_path.clone(),
+ language: i18n::get_language(),
+ slots: slots.map(|s| s.clone()),
+ };
+
+ // get timeout
+ let timeout = Duration::from_millis(cmd_config.timeout);
+
+ info!("Executing Lua command: {} (sandbox: {:?}, timeout: {:?})",
+ cmd_config.id, sandbox, timeout);
+
+ // execute
+ match lua::execute(&script_path, context, sandbox, timeout) {
+ Ok(result) => {
+ info!("Lua command {} completed (chain: {})", cmd_config.id, result.chain);
+ Ok(result.chain)
+ }
+ Err(e) => {
+ error!("Lua command {} failed: {}", cmd_config.id, e);
+ Err(e.to_string())
+ }
+ }
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/commands/structs.rs b/crates/jarvis-core/src/commands/structs.rs
new file mode 100644
index 0000000..18a182d
--- /dev/null
+++ b/crates/jarvis-core/src/commands/structs.rs
@@ -0,0 +1,179 @@
+use std::{collections::HashMap, path::PathBuf, sync::Arc};
+use serde::{Serialize, Deserialize};
+use parking_lot::RwLock;
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct JCommandsList {
+ #[serde(skip)]
+ pub path: PathBuf,
+
+ pub commands: Vec,
+}
+
+
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct JCommand {
+ pub id: String,
+
+ // Available command types are: "lua", "ahk", "cli", "voice", "terminate", "stop_chaining"
+ #[serde(rename = "type")]
+ pub cmd_type: String,
+
+ #[serde(default)]
+ pub description: String,
+
+ // for "ahk" type
+ #[serde(default)]
+ pub exe_path: String,
+ #[serde(default)]
+ pub exe_args: Vec,
+
+ // for "cli" type
+ #[serde(default)]
+ pub cli_cmd: String,
+ #[serde(default)]
+ pub cli_args: Vec,
+
+ // #[serde(default)]
+ // pub sounds: Vec,
+
+ // for "lua" type
+ #[serde(default)]
+ pub script: String,
+
+ // Lua sandbox level: "minimal", "standard", "full"
+ // basically this is an access level
+ #[serde(default)]
+ pub sandbox: String,
+
+ // Script timeout in milliseconds (default 10000 = 10s)
+ #[serde(default)]
+ pub timeout: u64,
+
+ // Multi-language sounds
+ #[serde(default)]
+ pub sounds: HashMap>,
+
+ // Multi-language phrases
+ #[serde(default)]
+ pub phrases: HashMap>,
+
+ // Slot definitions: slot_name -> how to extract it
+ #[serde(default)]
+ pub slots: HashMap,
+
+ // CACHE
+ #[serde(skip, default)]
+ sounds_cache: RwLock>>>,
+
+ #[serde(skip, default)]
+ phrases_cache: RwLock>>>,
+}
+
+// custom Clone
+impl Clone for JCommand {
+ fn clone(&self) -> Self {
+ Self {
+ id: self.id.clone(),
+
+ cmd_type: self.cmd_type.clone(),
+ description: self.description.clone(),
+
+ exe_path: self.exe_path.clone(),
+ exe_args: self.exe_args.clone(),
+
+ cli_cmd: self.cli_cmd.clone(),
+ cli_args: self.cli_args.clone(),
+
+ script: self.script.clone(),
+ sandbox: self.sandbox.clone(),
+ timeout: self.timeout.clone(),
+
+ sounds: self.sounds.clone(),
+ phrases: self.phrases.clone(),
+
+ slots: self.slots.clone(),
+
+ // empty caches for cloned instance
+ sounds_cache: RwLock::new(HashMap::new()),
+ phrases_cache: RwLock::new(HashMap::new()),
+ }
+ }
+}
+
+impl JCommand {
+ // get phrases for current language
+ pub fn get_phrases(&self, lang: &str) -> Arc> {
+ if let Some(cached) = self.phrases_cache.read().get(lang) {
+ return Arc::clone(cached);
+ }
+
+ let result = Arc::new(self.resolve_localized(&self.phrases, lang));
+ self.phrases_cache.write().insert(lang.to_string(), Arc::clone(&result));
+
+ result
+ }
+
+ // get all phrases (for backwards compat)
+ pub fn get_all_phrases(&self) -> Vec {
+ self.phrases.values().flatten().cloned().collect()
+ }
+
+ // get sounds for current language
+ pub fn get_sounds(&self, lang: &str) -> Arc> {
+ if let Some(cached) = self.sounds_cache.read().get(lang) {
+ return Arc::clone(cached);
+ }
+
+ let result = Arc::new(self.resolve_localized(&self.sounds, lang));
+ self.sounds_cache.write().insert(lang.to_string(), Arc::clone(&result));
+
+ result
+ }
+
+ // get all sounds (for backwards compat)
+ pub fn get_all_sounds(&self) -> Vec {
+ self.sounds.values().flatten().cloned().collect()
+ }
+
+
+ // shared fallback
+ fn resolve_localized(&self, map: &HashMap>, lang: &str) -> Vec {
+ // exact match
+ if let Some(values) = map.get(lang) {
+ return values.clone();
+ }
+
+ // fallback to "en"
+ if lang != "en" {
+ if let Some(values) = map.get("en") {
+ return values.clone();
+ }
+ }
+
+ // fallback to first available
+ map.values().next().cloned().unwrap_or_default()
+ }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct SlotDefinition {
+ // Entity label for GLiNER (e.g. "city name", "song title", "number")
+ // This is a free-form description - GLiNER matches it semantically
+ #[serde(default)]
+ pub entity: String,
+
+ // Optional: fallback context words for template-based extraction
+ // e.g. ["in", "for", "at"] for a city slot
+ #[serde(default)]
+ pub context: Vec,
+}
+
+// Extracted slot value passed to commands
+#[derive(Debug, Clone, Serialize)]
+#[serde(untagged)]
+pub enum SlotValue {
+ Text(String),
+ Number(f64),
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/config.rs b/crates/jarvis-core/src/config.rs
new file mode 100644
index 0000000..d363366
--- /dev/null
+++ b/crates/jarvis-core/src/config.rs
@@ -0,0 +1,259 @@
+pub mod structs;
+use structs::AudioType;
+use structs::RecorderType;
+use structs::SpeechToTextEngine;
+use structs::WakeWordEngine;
+
+use once_cell::sync::Lazy;
+use std::env;
+use std::fs;
+use std::path::PathBuf;
+
+use platform_dirs::AppDirs;
+
+#[cfg(feature="jarvis_app")]
+use rustpotter::{
+ AudioFmt, BandPassConfig, DetectorConfig, FiltersConfig, GainNormalizationConfig,
+ RustpotterConfig, ScoreMode,
+};
+
+use crate::config::structs::NoiseSuppressionBackend;
+use crate::{APP_CONFIG_DIR, APP_DIRS, APP_LOG_DIR};
+
+#[allow(dead_code)]
+pub fn init_dirs() -> Result<(), String> {
+ // infer app dirs
+ if APP_DIRS.get().is_some() {
+ return Ok(());
+ }
+
+ // cache_dir, config_dir, data_dir, state_dir
+ APP_DIRS
+ .set(AppDirs::new(Some(BUNDLE_IDENTIFIER), false).unwrap())
+ .unwrap();
+
+ // setup directories
+ let mut config_dir = PathBuf::from(&APP_DIRS.get().unwrap().config_dir);
+ let mut log_dir = PathBuf::from(&APP_DIRS.get().unwrap().config_dir);
+
+ // create dirs, if required
+ if !config_dir.exists() {
+ if fs::create_dir_all(&config_dir).is_err() {
+ config_dir = env::current_dir().expect("Cannot infer the config directory");
+ fs::create_dir_all(&config_dir)
+ .expect("Cannot create config directory, access denied?");
+ }
+ }
+
+ if !log_dir.exists() {
+ if fs::create_dir_all(&log_dir).is_err() {
+ log_dir = env::current_dir().expect("Cannot infer the log directory");
+ fs::create_dir_all(&log_dir).expect("Cannot create log directory, access denied?");
+ }
+ }
+
+ // store inferred paths
+ APP_CONFIG_DIR.set(config_dir).unwrap();
+ APP_LOG_DIR.set(log_dir).unwrap();
+
+ Ok(())
+}
+
+/*
+ Defaults.
+*/
+pub const DEFAULT_AUDIO_TYPE: AudioType = AudioType::Kira;
+pub const DEFAULT_RECORDER_TYPE: RecorderType = RecorderType::PvRecorder;
+pub const DEFAULT_WAKE_WORD_ENGINE: WakeWordEngine = WakeWordEngine::Vosk;
+pub const DEFAULT_SPEECH_TO_TEXT_ENGINE: SpeechToTextEngine = SpeechToTextEngine::Vosk;
+
+// backend defaults (string IDs)
+pub const DEFAULT_INTENT_BACKEND: &str = "intent-classifier";
+pub const DEFAULT_SLOTS_BACKEND: &str = "none";
+pub const DEFAULT_VAD_BACKEND: &str = "energy";
+
+pub const DEFAULT_VOICE: &str = "jarvis-remaster";
+pub const SOUND_PATH: &str = "resources/sound"; // extended from SOUND_DIR (resources/sound)
+pub const VOICES_PATH: &str = "voices"; // extended from SOUND_PATH (resources/sound)
+
+pub const BUNDLE_IDENTIFIER: &str = "com.priler.jarvis";
+pub const DB_FILE_NAME: &str = "app.db";
+pub const LOG_FILE_NAME: &str = "log.txt";
+pub const APP_VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
+pub const AUTHOR_NAME: Option<&str> = option_env!("CARGO_PKG_AUTHORS");
+pub const REPOSITORY_LINK: Option<&str> = option_env!("CARGO_PKG_REPOSITORY");
+pub const TG_OFFICIAL_LINK: Option<&str> = Some("https://t.me/howdyho_official");
+pub const FEEDBACK_LINK: Option<&str> = Some("https://t.me/jarvis_feedback_bot");
+pub const SUPPORT_BOOSTY_LINK: Option<&str> = Some("https://boosty.to/howdyho");
+pub const SUPPORT_PATREON_LINK: Option<&str> = Some("https://www.patreon.com/c/priler");
+
+/*
+ Tray.
+*/
+pub const TRAY_ICON: &str = "32x32.png";
+pub const TRAY_TOOLTIP: &str = "Jarvis Voice Assistant";
+
+// RUSPOTTER
+pub const RUSPOTTER_MIN_SCORE: f32 = 0.62;
+
+#[cfg(feature="jarvis_app")]
+pub const RUSTPOTTER_DEFAULT_CONFIG: Lazy = Lazy::new(|| {
+ RustpotterConfig {
+ fmt: AudioFmt::default(),
+ detector: DetectorConfig {
+ avg_threshold: 0.,
+ threshold: 0.5,
+ min_scores: 15,
+ score_ref: 0.22,
+ band_size: 5,
+ vad_mode: None,
+ score_mode: ScoreMode::Max,
+ eager: false,
+ // comparator_band_size: 5,
+ // comparator_ref: 0.22
+ },
+ filters: FiltersConfig {
+ gain_normalizer: GainNormalizationConfig {
+ // enabled: true,
+ // gain_ref: None,
+ // min_gain: 0.7,
+ // max_gain: 1.0,
+ enabled: false, // disable, now we have separate gain normalizer implementation
+ gain_ref: None,
+ min_gain: 0.7,
+ max_gain: 1.0,
+ },
+ band_pass: BandPassConfig {
+ enabled: true,
+ low_cutoff: 80.,
+ high_cutoff: 400.,
+ },
+ },
+ }
+});
+
+// PICOVOICE
+pub const COMMANDS_PATH: &str = "resources/commands/";
+pub const KEYWORDS_PATH: &str = "resources/picovoice/keywords/";
+pub const DEFAULT_KEYWORD: &str = "jarvis_windows.ppn";
+pub const DEFAULT_SENSITIVITY: f32 = 1.0;
+
+// VOSK
+// pub const VOSK_MODEL_PATH: &str = const_concat!(PUBLIC_PATH, "/vosk/model_small");
+pub const VOSK_MODELS_PATH: &str = "resources/vosk";
+pub const VOSK_MODEL_PATH: &str = "resources/vosk/model_small";
+pub const VOSK_FETCH_PHRASE: &str = "джарвис";
+pub const VOSK_MIN_RATIO: f64 = 70.0;
+
+// 0.7 lenient, expect false positives
+// 0.8 balanced
+// 0.9 strict
+// etc
+pub const VOSK_WAKE_CONFIDENCE: f32 = 0.9;
+
+pub const VOSK_SPEECH_RECOGNIZER_MAX_ALTERNATIVES: u16 = 3;
+pub const VOSK_SPEECH_RECOGNIZER_WORDS: bool = false;
+pub const VOSK_SPEECH_PARTIAL_WORDS: bool = false;
+
+// IRE (intents recognition)
+pub const INTENT_CLASSIFIER_MIN_CONFIDENCE: f64 = 0.75;
+
+
+// embedding classifier
+pub const EMBEDDING_MIN_CONFIDENCE: f64 = 0.70;
+
+// AUDIO PROCESSING DEFAULTS
+pub const DEFAULT_NOISE_SUPPRESSION: NoiseSuppressionBackend = NoiseSuppressionBackend::None;
+pub const DEFAULT_GAIN_NORMALIZER: bool = false;
+
+// VAD settings
+pub const VAD_ENERGY_THRESHOLD: f32 = 100.0; // RMS threshold for energy-based VAD
+pub const VAD_NNNOISELESS_THRESHOLD: f32 = 0.8; // probability threshold for nnnoiseless
+pub const VAD_SILENCE_FRAMES: u32 = 15; // frames of silence before speech end (~480ms)
+
+// gain normalizer settings
+pub const GAIN_TARGET_RMS: f32 = 3000.0; // target RMS level
+pub const GAIN_MIN: f32 = 0.5; // minimum gain multiplier
+pub const GAIN_MAX: f32 = 3.0; // maximum gain multiplier
+
+// nnnoiseless frame size (fixed by library)
+pub const NNNOISELESS_FRAME_SIZE: usize = 480;
+
+// LUA
+pub const DEFAULT_LUA_SANDBOX: &str = "standard";
+pub const DEFAULT_LUA_TIMEOUT: u64 = 10000; // ms
+
+// ETC
+pub const CMD_RATIO_THRESHOLD: f64 = 75f64;
+pub const CMS_WAIT_DELAY: std::time::Duration = std::time::Duration::from_secs(15);
+
+// pub const ASSISTANT_GREET_PHRASES: [&str; 3] = ["greet1", "greet2", "greet3"];
+// pub const ASSISTANT_PHRASES_TBR: [&str; 17] = [
+// "джарвис",
+// "сэр",
+// "слушаю сэр",
+// "всегда к услугам",
+// "произнеси",
+// "ответь",
+// "покажи",
+// "скажи",
+// "давай",
+// "да сэр",
+// "к вашим услугам сэр",
+// "всегда к вашим услугам сэр",
+// "запрос выполнен сэр",
+// "выполнен сэр",
+// "есть",
+// "загружаю сэр",
+// "очень тонкое замечание сэр",
+// ];
+
+
+
+pub fn get_wake_phrases(lang: &str) -> &'static [&'static str] {
+ match lang {
+ "ru" => &["джарвис", "джервис", "гарвис", "джарви", "гарви"],
+ "ua" => &["джарвіс", "джервіс"],
+ "en" => &["jarvis", "jervis"],
+ _ => &["jarvis"],
+ }
+}
+
+pub fn get_phrases_to_remove(lang: &str) -> &'static [&'static str] {
+ match lang {
+ "ru" => &[
+ "джарвис", "джервис", "гарвис", "джарви", "гарви",
+ "сэр", "слушаю сэр", "всегда к услугам",
+ "произнеси", "ответь", "покажи", "скажи", "давай",
+ "да сэр", "к вашим услугам сэр", "загружаю сэр",
+ ],
+ "ua" => &[
+ "джарвіс", "джервіс", "сер", "слухаю сер", "завжди до послуг",
+ "скажи", "покажи", "відповідай", "давай",
+ "так сер", "до ваших послуг сер",
+ ],
+ "en" => &[
+ "jarvis", "jervis", "sir", "yes sir", "at your service",
+ "please", "say", "show", "tell", "hey",
+ ],
+ _ => &["jarvis"],
+ }
+}
+
+pub fn get_wake_grammar(lang: &str) -> &'static [&'static str] {
+ match lang {
+ "ru" => &[
+ "джарвис", "[unk]", "джон", "джони", "джей",
+ "джонстон", "привет", "давай",
+ ],
+ "ua" => &[
+ "джарвіс", "[unk]", "джон", "джоні", "джей",
+ "привіт", "давай",
+ ],
+ "en" => &[
+ "jarvis", "[unk]", "john", "johnny", "jay",
+ "hello", "hey", "hi",
+ ],
+ _ => &["jarvis", "[unk]"],
+ }
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/config/structs.rs b/crates/jarvis-core/src/config/structs.rs
new file mode 100644
index 0000000..0dd7189
--- /dev/null
+++ b/crates/jarvis-core/src/config/structs.rs
@@ -0,0 +1,51 @@
+use std::fmt;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
+pub enum WakeWordEngine {
+ Rustpotter,
+ Vosk,
+ Porcupine,
+}
+
+#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
+pub enum NoiseSuppressionBackend {
+ None,
+ Nnnoiseless,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub enum SpeechToTextEngine {
+ Vosk,
+}
+
+#[derive(PartialEq, Debug)]
+pub enum RecorderType {
+ Cpal,
+ PvRecorder,
+ PortAudio,
+}
+
+#[derive(PartialEq, Debug)]
+pub enum AudioType {
+ Rodio,
+ Kira,
+}
+
+impl fmt::Display for WakeWordEngine {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl fmt::Display for SpeechToTextEngine {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl fmt::Display for NoiseSuppressionBackend {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
diff --git a/crates/jarvis-core/src/db.rs b/crates/jarvis-core/src/db.rs
new file mode 100644
index 0000000..a218679
--- /dev/null
+++ b/crates/jarvis-core/src/db.rs
@@ -0,0 +1,59 @@
+pub mod structs;
+pub mod manager;
+
+use crate::{config, APP_CONFIG_DIR};
+
+use log::info;
+use std::fs::File;
+use std::io::BufReader;
+use std::path::PathBuf;
+
+pub use manager::SettingsManager;
+
+fn get_db_file_path() -> PathBuf {
+ PathBuf::from(format!(
+ "{}/{}",
+ APP_CONFIG_DIR.get().unwrap().display(),
+ config::DB_FILE_NAME
+ ))
+}
+
+pub fn init_settings() -> structs::Settings {
+ let db_file_path = get_db_file_path();
+
+ info!(
+ "Loading settings db file located at: {}",
+ db_file_path.display()
+ );
+
+ if db_file_path.exists() {
+ if let Ok(db_file) = File::open(&db_file_path) {
+ let reader = BufReader::new(db_file);
+ if let Ok(settings) = serde_json::from_reader(reader) {
+ info!("Settings loaded.");
+ return settings;
+ }
+ }
+ }
+
+ warn!("No settings file found or there was an error parsing it. Creating default struct.");
+ structs::Settings::default()
+}
+
+/// init settings and return a SettingsManager ready to use
+pub fn init() -> SettingsManager {
+ let settings = init_settings();
+ SettingsManager::new(settings)
+}
+
+pub fn save_settings(settings: &structs::Settings) -> Result<(), std::io::Error> {
+ let db_file_path = get_db_file_path();
+
+ std::fs::write(
+ &db_file_path,
+ serde_json::to_string_pretty(&settings).unwrap(),
+ )?;
+
+ info!("Settings saved to: {:#}", db_file_path.display());
+ Ok(())
+}
diff --git a/crates/jarvis-core/src/db/manager.rs b/crates/jarvis-core/src/db/manager.rs
new file mode 100644
index 0000000..02a37dd
--- /dev/null
+++ b/crates/jarvis-core/src/db/manager.rs
@@ -0,0 +1,87 @@
+use std::sync::Arc;
+use parking_lot::RwLock;
+
+use super::structs::Settings;
+use super::save_settings;
+
+// centralized settings manager.
+// wraps Arc> and handles locking + auto-save
+// can be used anywhere, ex. from GUI, tray, IPC, CLI, etc.
+#[derive(Clone)]
+pub struct SettingsManager {
+ inner: Arc>,
+}
+
+impl SettingsManager {
+ pub fn new(settings: Settings) -> Self {
+ Self {
+ inner: Arc::new(RwLock::new(settings)),
+ }
+ }
+
+ // wrap an existing Arc>
+ pub fn from_arc(arc: Arc>) -> Self {
+ Self { inner: arc }
+ }
+
+ // read a setting by key
+ pub fn read(&self, key: &str) -> Option {
+ self.inner.read().get(key)
+ }
+
+ // write a setting by key, auto-saves to disk
+ pub fn write(&self, key: &str, val: &str) -> Result<(), String> {
+ let snapshot = {
+ let mut settings = self.inner.write();
+ settings.set(key, val)?;
+ settings.clone()
+ };
+
+ save_settings(&snapshot)
+ .map_err(|e| format!("failed to save settings: {}", e))?;
+
+ Ok(())
+ }
+
+ // write multiple settings at once, single save
+ pub fn write_many(&self, pairs: &[(&str, &str)]) -> Result<(), String> {
+ let snapshot = {
+ let mut settings = self.inner.write();
+ for (key, val) in pairs {
+ settings.set(key, val)?;
+ }
+ settings.clone()
+ };
+
+ save_settings(&snapshot)
+ .map_err(|e| format!("failed to save settings: {}", e))?;
+
+ Ok(())
+ }
+
+ // direct read access to the full Settings struct (for init code that
+ // needs to read multiple fields at once without key-based access)
+ pub fn lock(&self) -> parking_lot::RwLockReadGuard<'_, Settings> {
+ self.inner.read()
+ }
+
+ // direct write access (for bulk operations not covered by set())
+ pub fn lock_mut(&self) -> parking_lot::RwLockWriteGuard<'_, Settings> {
+ self.inner.write()
+ }
+
+ // get the underlying Arc
+ pub fn arc(&self) -> &Arc> {
+ &self.inner
+ }
+
+ // dump all settings as key-value pairs (for debugging)
+ pub fn dump(&self) -> Vec<(String, String)> {
+ let settings = self.inner.read();
+ Settings::keys().iter()
+ .filter_map(|&key| {
+ settings.get(key).map(|val| (key.to_string(), val))
+ })
+ .collect()
+ }
+}
diff --git a/crates/jarvis-core/src/db/structs.rs b/crates/jarvis-core/src/db/structs.rs
new file mode 100644
index 0000000..dd63b0d
--- /dev/null
+++ b/crates/jarvis-core/src/db/structs.rs
@@ -0,0 +1,184 @@
+use crate::config;
+use serde::{Deserialize, Serialize};
+
+use crate::config::structs::SpeechToTextEngine;
+use crate::config::structs::WakeWordEngine;
+use crate::config::structs::NoiseSuppressionBackend;
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct Settings {
+ pub microphone: i32,
+ pub voice: String,
+
+ pub wake_word_engine: WakeWordEngine,
+
+ // backend selections (string IDs matching model or code backend IDs)
+ #[serde(default = "default_intent_backend")]
+ pub intent_backend: String,
+ #[serde(default = "default_slots_backend")]
+ pub slots_backend: String,
+ #[serde(default = "default_vad_backend")]
+ pub vad_backend: String,
+
+ pub gliner_model: String,
+
+ pub speech_to_text_engine: SpeechToTextEngine,
+ pub vosk_model: String,
+
+ // audio processing
+ pub noise_suppression: NoiseSuppressionBackend,
+ pub gain_normalizer: bool,
+
+ #[serde(default = "default_language")]
+ pub language: String,
+
+ pub api_keys: ApiKeys,
+}
+
+fn default_intent_backend() -> String { config::DEFAULT_INTENT_BACKEND.to_string() }
+fn default_slots_backend() -> String { config::DEFAULT_SLOTS_BACKEND.to_string() }
+fn default_vad_backend() -> String { config::DEFAULT_VAD_BACKEND.to_string() }
+fn default_language() -> String { crate::i18n::detect_system_language().to_string() }
+
+// ### KEY-VALUE ACCESS
+
+impl Settings {
+ /// read a setting by key. returns None for unknown keys.
+ pub fn get(&self, key: &str) -> Option {
+ match key {
+ "selected_microphone" => Some(self.microphone.to_string()),
+ "assistant_voice" => Some(self.voice.clone()),
+ "selected_wake_word_engine" => Some(format!("{:?}", self.wake_word_engine)),
+ "intent_backend" => Some(self.intent_backend.clone()),
+ "slots_backend" => Some(self.slots_backend.clone()),
+ "vad_backend" => Some(self.vad_backend.clone()),
+ "selected_gliner_model" => Some(self.gliner_model.clone()),
+ "selected_vosk_model" => Some(self.vosk_model.clone()),
+ "speech_to_text_engine" => Some(format!("{:?}", self.speech_to_text_engine)),
+ "noise_suppression" => Some(format!("{:?}", self.noise_suppression)),
+ "gain_normalizer" => Some(self.gain_normalizer.to_string()),
+ "language" => Some(self.language.clone()),
+ "api_key__picovoice" => Some(self.api_keys.picovoice.clone()),
+ "api_key__openai" => Some(self.api_keys.openai.clone()),
+ _ => None,
+ }
+ }
+
+ /// write a setting by key. returns Err for unknown keys or invalid values.
+ pub fn set(&mut self, key: &str, val: &str) -> Result<(), String> {
+ match key {
+ "selected_microphone" => {
+ self.microphone = val.parse::()
+ .map_err(|_| format!("invalid integer: '{}'", val))?;
+ }
+ "assistant_voice" => {
+ self.voice = val.to_string();
+ }
+ "selected_wake_word_engine" => {
+ self.wake_word_engine = match val.to_lowercase().as_str() {
+ "rustpotter" => WakeWordEngine::Rustpotter,
+ "vosk" => WakeWordEngine::Vosk,
+ "porcupine" => WakeWordEngine::Porcupine,
+ _ => return Err(format!("unknown wake word engine: '{}'", val)),
+ };
+ }
+ "intent_backend" => {
+ self.intent_backend = val.to_string();
+ }
+ "slots_backend" => {
+ self.slots_backend = val.to_string();
+ }
+ "vad_backend" => {
+ self.vad_backend = val.to_string();
+ }
+ "selected_gliner_model" => {
+ self.gliner_model = val.to_string();
+ }
+ "selected_vosk_model" => {
+ self.vosk_model = val.to_string();
+ }
+ "noise_suppression" => {
+ self.noise_suppression = match val.to_lowercase().as_str() {
+ "none" => NoiseSuppressionBackend::None,
+ "nnnoiseless" => NoiseSuppressionBackend::Nnnoiseless,
+ _ => return Err(format!("unknown noise suppression backend: '{}'", val)),
+ };
+ }
+ "gain_normalizer" => {
+ self.gain_normalizer = match val.to_lowercase().as_str() {
+ "true" => true,
+ "false" => false,
+ _ => return Err(format!("expected 'true' or 'false', got: '{}'", val)),
+ };
+ }
+ "language" => {
+ self.language = val.to_string();
+ }
+ "api_key__picovoice" => {
+ self.api_keys.picovoice = val.to_string();
+ }
+ "api_key__openai" => {
+ self.api_keys.openai = val.to_string();
+ }
+ _ => return Err(format!("unknown setting: '{}'", key)),
+ }
+ Ok(())
+ }
+
+ /// all valid setting keys (for enumeration, debugging, etc.)
+ pub fn keys() -> &'static [&'static str] {
+ &[
+ "selected_microphone",
+ "assistant_voice",
+ "selected_wake_word_engine",
+ "intent_backend",
+ "slots_backend",
+ "vad_backend",
+ "selected_gliner_model",
+ "selected_vosk_model",
+ "speech_to_text_engine",
+ "noise_suppression",
+ "gain_normalizer",
+ "language",
+ "api_key__picovoice",
+ "api_key__openai",
+ ]
+ }
+}
+
+// ### DEFAULT
+
+impl Default for Settings {
+ fn default() -> Settings {
+ Settings {
+ microphone: -1,
+ voice: String::from(""),
+
+ wake_word_engine: config::DEFAULT_WAKE_WORD_ENGINE,
+
+ intent_backend: config::DEFAULT_INTENT_BACKEND.to_string(),
+ slots_backend: config::DEFAULT_SLOTS_BACKEND.to_string(),
+ vad_backend: config::DEFAULT_VAD_BACKEND.to_string(),
+
+ gliner_model: String::new(),
+ speech_to_text_engine: config::DEFAULT_SPEECH_TO_TEXT_ENGINE,
+ vosk_model: String::from(""),
+
+ noise_suppression: config::DEFAULT_NOISE_SUPPRESSION,
+ gain_normalizer: config::DEFAULT_GAIN_NORMALIZER,
+
+ language: crate::i18n::detect_system_language().to_string(),
+
+ api_keys: ApiKeys {
+ picovoice: String::from(""),
+ openai: String::from(""),
+ },
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ApiKeys {
+ pub picovoice: String,
+ pub openai: String,
+}
diff --git a/crates/jarvis-core/src/i18n.rs b/crates/jarvis-core/src/i18n.rs
new file mode 100644
index 0000000..24f7576
--- /dev/null
+++ b/crates/jarvis-core/src/i18n.rs
@@ -0,0 +1,202 @@
+use fluent_bundle::{FluentBundle, FluentResource, FluentArgs, FluentValue};
+use fluent_bundle::concurrent::FluentBundle as ConcurrentFluentBundle;
+use once_cell::sync::OnceCell;
+use parking_lot::RwLock;
+use std::collections::HashMap;
+use unic_langid::LanguageIdentifier;
+
+// locale files embedded at compile time
+const LOCALE_RU: &str = include_str!("i18n/locales/ru.ftl");
+const LOCALE_EN: &str = include_str!("i18n/locales/en.ftl");
+const LOCALE_UA: &str = include_str!("i18n/locales/ua.ftl");
+
+pub const SUPPORTED_LANGUAGES: &[&str] = &["ru", "en", "ua"];
+pub const DEFAULT_LANGUAGE: &str = "en";
+
+// detect the OS language and map it to a supported language.
+// falls back to DEFAULT_LANGUAGE if not supported.
+pub fn detect_system_language() -> &'static str {
+ if let Some(locale) = sys_locale::get_locale() {
+ // locale can be "en-US", "ru-RU", "uk-UA", etc.
+ let lang_code = locale.split(&['-', '_'][..]).next().unwrap_or("");
+
+ // map OS locale codes to our supported languages
+ let mapped = match lang_code {
+ "uk" => "ua", // ISO 639-1 "uk" (ukrainian) -> our "ua"
+ other => other,
+ };
+
+ if SUPPORTED_LANGUAGES.contains(&mapped) {
+ info!("Detected system language: {} (from locale '{}')", mapped, locale);
+ return SUPPORTED_LANGUAGES.iter()
+ .find(|&&l| l == mapped)
+ .unwrap();
+ }
+
+ info!("System locale '{}' not supported, using default '{}'", locale, DEFAULT_LANGUAGE);
+ }
+
+ DEFAULT_LANGUAGE
+}
+
+// use concurrent bundle (thread-safe)
+type Bundle = ConcurrentFluentBundle;
+
+static BUNDLES: OnceCell> = OnceCell::new();
+static CURRENT_LANG: OnceCell> = OnceCell::new();
+
+// Initialize i18n system
+pub fn init(lang: &str) {
+ let bundles = create_bundles();
+ BUNDLES.set(bundles).ok();
+
+ let lang = if SUPPORTED_LANGUAGES.contains(&lang) { lang } else { DEFAULT_LANGUAGE };
+ CURRENT_LANG.set(RwLock::new(lang.to_string())).ok();
+
+ info!("i18n initialized with language: {}", lang);
+}
+
+fn create_bundles() -> HashMap {
+ let mut bundles = HashMap::new();
+
+ bundles.insert("ru".to_string(), create_bundle("ru", LOCALE_RU));
+ bundles.insert("en".to_string(), create_bundle("en", LOCALE_EN));
+ bundles.insert("ua".to_string(), create_bundle("ua", LOCALE_UA));
+
+ bundles
+}
+
+fn create_bundle(lang: &str, source: &str) -> Bundle {
+ let langid: LanguageIdentifier = lang.parse().expect("Invalid language identifier");
+ let mut bundle = ConcurrentFluentBundle::new_concurrent(vec![langid]);
+
+ let resource = FluentResource::try_new(source.to_string())
+ .expect("Failed to parse FTL resource");
+
+ bundle.add_resource(resource).expect("Failed to add resource");
+ bundle
+}
+
+// Set current language
+pub fn set_language(lang: &str) {
+ if let Some(current) = CURRENT_LANG.get() {
+ let lang = if SUPPORTED_LANGUAGES.contains(&lang) { lang } else { DEFAULT_LANGUAGE };
+ *current.write() = lang.to_string();
+ info!("Language changed to: {}", lang);
+ }
+}
+
+// Get current language
+pub fn get_language() -> String {
+ CURRENT_LANG.get()
+ .map(|l| l.read().clone())
+ .unwrap_or_else(|| DEFAULT_LANGUAGE.to_string())
+}
+
+// Translate a key
+pub fn t(key: &str) -> String {
+ t_with_args(key, None)
+}
+
+// Translate a key with arguments
+pub fn t_with_args(key: &str, args: Option<&FluentArgs>) -> String {
+ let lang = get_language();
+
+ let bundles = match BUNDLES.get() {
+ Some(b) => b,
+ None => return key.to_string(),
+ };
+
+ let bundle = match bundles.get(&lang) {
+ Some(b) => b,
+ None => bundles.get(DEFAULT_LANGUAGE).unwrap(),
+ };
+
+ let msg = match bundle.get_message(key) {
+ Some(m) => m,
+ None => return key.to_string(),
+ };
+
+ let pattern = match msg.value() {
+ Some(p) => p,
+ None => return key.to_string(),
+ };
+
+ let mut errors = vec![];
+ let result = bundle.format_pattern(pattern, args, &mut errors);
+
+ if !errors.is_empty() {
+ warn!("i18n errors for key '{}': {:?}", key, errors);
+ }
+
+ result.to_string()
+}
+
+// Translate with a single argument
+pub fn t_arg(key: &str, arg_name: &str, arg_value: &str) -> String {
+ let mut args = FluentArgs::new();
+ args.set(arg_name, FluentValue::from(arg_value));
+ t_with_args(key, Some(&args))
+}
+
+// Translate with numeric argument
+pub fn t_count(key: &str, count: i64) -> String {
+ let mut args = FluentArgs::new();
+ args.set("count", FluentValue::from(count));
+ t_with_args(key, Some(&args))
+}
+
+// Get all translations for current language (for frontend)
+pub fn get_all_translations() -> HashMap {
+ let lang = get_language();
+ get_translations_for(&lang)
+}
+
+// Get all translations for a specific language
+pub fn get_translations_for(lang: &str) -> HashMap {
+ let mut result = HashMap::new();
+
+ let bundles = match BUNDLES.get() {
+ Some(b) => b,
+ None => return result,
+ };
+
+ let bundle = match bundles.get(lang) {
+ Some(b) => b,
+ None => match bundles.get(DEFAULT_LANGUAGE) {
+ Some(b) => b,
+ None => return result,
+ },
+ };
+
+ // get source for this language and extract all keys
+ let source = match lang {
+ "ru" => LOCALE_RU,
+ "en" => LOCALE_EN,
+ "ua" => LOCALE_UA,
+ _ => LOCALE_RU,
+ };
+
+ // parse keys from FTL source (lines that have "=" and don't start with "#" or "-")
+ for line in source.lines() {
+ let line = line.trim();
+ if line.is_empty() || line.starts_with('#') || line.starts_with('-') {
+ continue;
+ }
+
+ if let Some(key) = line.split('=').next() {
+ let key = key.trim();
+ if !key.is_empty() && !key.contains(' ') {
+ if let Some(msg) = bundle.get_message(key) {
+ if let Some(pattern) = msg.value() {
+ let mut errors = vec![];
+ let value = bundle.format_pattern(pattern, None, &mut errors);
+ result.insert(key.to_string(), value.to_string());
+ }
+ }
+ }
+ }
+ }
+
+ result
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/i18n/locales/en.ftl b/crates/jarvis-core/src/i18n/locales/en.ftl
new file mode 100644
index 0000000..49161b3
--- /dev/null
+++ b/crates/jarvis-core/src/i18n/locales/en.ftl
@@ -0,0 +1,143 @@
+# ### APP INFO
+app-name = JARVIS
+app-description = Voice Assistant
+
+# ### TRAY MENU
+tray-restart = Restart
+tray-settings = Settings
+tray-exit = Exit
+tray-tooltip = JARVIS - Voice Assistant
+tray-language = Language
+tray-voice = Voice
+tray-wake-word = Wake Word Engine
+tray-noise-suppression = Noise Suppression
+tray-vad = Voice Activity Detection
+tray-gain-normalizer = Gain Normalizer
+
+# ### HEADER
+header-commands = COMMANDS
+header-settings = SETTINGS
+
+# ### SEARCH
+search-placeholder = Enter a command manually or say «Jarvis» ...
+
+# ### MAIN PAGE
+assistant-not-running = ASSISTANT NOT RUNNING
+assistant-offline-hint = You can configure it without starting.
+btn-start = START
+btn-starting = STARTING...
+
+# ### STATUS
+status-disconnected = Disconnected
+status-standby = Standby
+status-listening = Listening...
+status-processing = Processing...
+
+# ### STATS
+stats-microphone = MICROPHONE
+stats-neural-networks = NEURAL NETWORKS
+stats-resources = RESOURCES
+stats-system-default = System Default
+stats-not-selected = Not selected
+stats-loading = Loading...
+
+# ### FOOTER
+footer-author = Project author
+footer-telegram = Our Telegram channel
+footer-github = Github repository
+footer-support = Support the project on
+
+# ### SETTINGS
+settings-title = Settings
+settings-general = General
+settings-devices = Devices
+settings-neural-networks = Neural Networks
+settings-audio = Audio
+settings-recognition = Recognition
+settings-about = About
+settings-language = Language
+settings-microphone = Microphone
+settings-microphone-desc = The assistant will listen to this microphone.
+settings-mic-default = Default (System)
+settings-voice = Assistant voice
+settings-voice-desc =
+ Not all commands work with all sound packs.
+ Click to listen the preview of sound.
+settings-wake-word-engine = Wake word engine
+settings-wake-word-desc = Choose the engine for wake word recognition.
+settings-stt-engine = Speech recognition
+settings-intent-engine = Intent recognition
+settings-intent-engine-desc = Select neural network for command recognition.
+settings-noise-suppression = Noise suppression
+settings-noise-suppression-desc = Reduces background noise. May negatively affect recognition.
+settings-vad = Voice detection (VAD)
+settings-vad-desc = Skips silence, saves CPU resources.
+settings-gain-normalizer = Gain normalizer
+settings-gain-normalizer-desc = Automatically adjusts volume level.
+settings-api-keys = API Keys
+settings-save = Save
+settings-cancel = Cancel
+settings-back = Back
+settings-enabled = Enabled
+settings-disabled = Disabled
+
+# settings - beta notice
+settings-beta-title = BETA version!
+settings-beta-desc = Some features may not work correctly.
+settings-beta-feedback = Report all bugs to
+settings-beta-bot = our Telegram bot
+settings-open-logs = Open logs folder
+
+# settings - picovoice
+settings-attention = Attention!
+settings-picovoice-warning = This neural network doesn't work for everyone!
+settings-picovoice-waiting = We are waiting for an official patch from the developers.
+settings-picovoice-key-desc = Enter your Picovoice key here. It is issued for free upon registration at
+settings-picovoice-key = Picovoice Key
+
+# settings - vosk
+settings-auto-detect = Auto-detect
+settings-vosk-model = Speech recognition model (Vosk)
+settings-vosk-model-desc =
+ Select Vosk model for speech recognition.
+ You can download models here: https://alphacephei.com/vosk/models
+settings-models-not-found = Models not found
+settings-models-hint = Place Vosk models in resources/vosk folder
+
+# settings - openai
+settings-openai-key = OpenAI Key
+settings-openai-not-supported = ChatGPT is not currently supported. It will be added in future updates.
+
+# ### COMMANDS PAGE
+commands-title = Commands
+commands-search = Search commands...
+commands-count = { $count } commands
+commands-wip-title = [404] This section is under development!
+commands-wip-desc = Here will be a list of commands + full-featured command editor.
+commands-wip-follow = Follow updates in
+commands-wip-channel = our Telegram channel
+
+# ### ERRORS
+error-generic = An error occurred
+error-connection = Connection error
+error-not-found = Not found
+
+# ### NOTIFICATIONS
+notification-saved = Settings saved!
+notification-error = Error
+notification-assistant-started = Assistant started
+notification-assistant-stopped = Assistant stopped
+
+# SLOTS EXTRACTION
+settings-slot-engine = Slot extraction
+settings-slot-engine-desc = Extract parameters from voice commands (e.g. city name, number).
+settings-gliner-model = GLiNER ONNX model
+settings-gliner-model-desc =
+ Select model variant.
+ Smaller quantized models (int8, uint8) are faster but less accurate.
+settings-gliner-models-hint = No GLiNER models found.
+
+# ETC
+search-error-not-running = Assistant is not running
+search-error-failed = Failed to execute command
+settings-no-voices = No voices found
\ No newline at end of file
diff --git a/crates/jarvis-core/src/i18n/locales/ru.ftl b/crates/jarvis-core/src/i18n/locales/ru.ftl
new file mode 100644
index 0000000..82f55bc
--- /dev/null
+++ b/crates/jarvis-core/src/i18n/locales/ru.ftl
@@ -0,0 +1,143 @@
+# APP INFO
+app-name = JARVIS
+app-description = Голосовой ассистент
+
+# TRAY MENU
+tray-restart = Перезапустить
+tray-settings = Настройки
+tray-exit = Выход
+tray-tooltip = JARVIS - Голосовой ассистент
+tray-language = Язык
+tray-voice = Голос
+tray-wake-word = Движок wake-word
+tray-noise-suppression = Шумоподавление
+tray-vad = Детекция голоса (VAD)
+tray-gain-normalizer = Нормализация громкости
+
+# HEADER
+header-commands = КОМАНДЫ
+header-settings = НАСТРОЙКИ
+
+# SEARCH
+search-placeholder = Введите команду вручную или произнесите «Джарвис» ...
+
+# MAIN PAGE
+assistant-not-running = АССИСТЕНТ НЕ ЗАПУЩЕН
+assistant-offline-hint = Настроить его можно не запуская.
+btn-start = ЗАПУСТИТЬ
+btn-starting = ЗАПУСК...
+
+# STATUS
+status-disconnected = Отключен
+status-standby = Ожидание
+status-listening = Слушаю...
+status-processing = Обработка...
+
+# STATS
+stats-microphone = МИКРОФОН
+stats-neural-networks = НЕЙРОСЕТИ
+stats-resources = РЕСУРСЫ
+stats-system-default = Системный
+stats-not-selected = Не выбран
+stats-loading = Загрузка...
+
+# FOOTER
+footer-author = Автор проекта
+footer-telegram = Наш телеграм канал
+footer-github = Github репозиторий проекта
+footer-support = Поддержать проект на
+
+# SETTINGS
+settings-title = Настройки
+settings-general = Основные
+settings-devices = Устройства
+settings-neural-networks = Нейросети
+settings-audio = Аудио
+settings-recognition = Распознавание
+settings-about = О программе
+settings-language = Язык
+settings-microphone = Микрофон
+settings-microphone-desc = Его будет слушать ассистент.
+settings-mic-default = По умолчанию (Система)
+settings-voice = Голос ассистента
+settings-voice-desc =
+ Не все команды работают со всеми звуковыми пакетами.
+ Кликните, чтобы прослушать как звучит голос.
+settings-wake-word-engine = Движок активации
+settings-wake-word-desc = Выберите нейросеть для распознавания активационной фразы.
+settings-stt-engine = Распознавание речи
+settings-intent-engine = Определение намерения
+settings-intent-engine-desc = Выберите нейросеть для распознавания команд.
+settings-noise-suppression = Шумоподавление
+settings-noise-suppression-desc = Уменьшает фоновый шум. Может негативно влиять на распознавание.
+settings-vad = Определение голоса (VAD)
+settings-vad-desc = Пропускает тишину, экономит ресурсы CPU.
+settings-gain-normalizer = Нормализация громкости
+settings-gain-normalizer-desc = Автоматически регулирует уровень громкости.
+settings-api-keys = API Ключи
+settings-save = Сохранить
+settings-cancel = Отмена
+settings-back = Назад
+settings-enabled = Включено
+settings-disabled = Отключено
+
+# settings - beta notice
+settings-beta-title = БЕТА версия!
+settings-beta-desc = Часть функций может работать некорректно.
+settings-beta-feedback = Сообщайте обо всех найденных багах в
+settings-beta-bot = наш телеграм бот
+settings-open-logs = Открыть папку с логами
+
+# settings - picovoice
+settings-attention = Внимание!
+settings-picovoice-warning = Эта нейросеть работает не у всех!
+settings-picovoice-waiting = Мы ждем официального патча от разработчиков.
+settings-picovoice-key-desc = Введите сюда свой ключ Picovoice. Он выдается бесплатно при регистрации в
+settings-picovoice-key = Ключ Picovoice
+
+# settings - vosk
+settings-auto-detect = Авто-определение
+settings-vosk-model = Модель распознавания речи (Vosk)
+settings-vosk-model-desc =
+ Выберите модель Vosk для распознавания речи.
+ Вы можете скачать модели здесь: https://alphacephei.com/vosk/models
+settings-models-not-found = Модели не найдены
+settings-models-hint = Поместите модели Vosk в папку resources/vosk
+
+# settings - openai
+settings-openai-key = Ключ OpenAI
+settings-openai-not-supported = В данный момент ChatGPT не поддерживается. Он будет добавлен в ближайших обновлениях.
+
+# COMMANDS PAGE
+commands-title = Команды
+commands-search = Поиск команд...
+commands-count = { $count } команд
+commands-wip-title = [404] Этот раздел еще находится в разработке!
+commands-wip-desc = Тут будет список команд + полноценный редактор команд.
+commands-wip-follow = Следите за обновлениями в
+commands-wip-channel = нашем телеграм канале
+
+# ERRORS
+error-generic = Произошла ошибка
+error-connection = Ошибка подключения
+error-not-found = Не найдено
+
+# NOTIFICATIONS
+notification-saved = Настройки сохранены!
+notification-error = Ошибка
+notification-assistant-started = Ассистент запущен
+notification-assistant-stopped = Ассистент остановлен
+
+# SLOTS EXTRACTION
+settings-slot-engine = Извлечение параметров
+settings-slot-engine-desc = Извлекает параметры из голосовых команд (напр. название города, число).
+settings-gliner-model = Модель GLiNER ONNX
+settings-gliner-model-desc =
+ Выберите вариант модели.
+ Квантизированные модели (int8, uint8) быстрее, но менее точны.
+settings-gliner-models-hint = Модели GLiNER не найдены.
+
+# ETC
+search-error-not-running = Ассистент не запущен
+search-error-failed = Не удалось выполнить команду
+settings-no-voices = Голоса не найдены
\ No newline at end of file
diff --git a/crates/jarvis-core/src/i18n/locales/ua.ftl b/crates/jarvis-core/src/i18n/locales/ua.ftl
new file mode 100644
index 0000000..dcafc8a
--- /dev/null
+++ b/crates/jarvis-core/src/i18n/locales/ua.ftl
@@ -0,0 +1,143 @@
+# ### APP INFO
+app-name = JARVIS
+app-description = Голосовий асистент
+
+# ### TRAY MENU
+tray-restart = Перезапустити
+tray-settings = Налаштування
+tray-exit = Вихід
+tray-tooltip = JARVIS - Голосовий асистент
+tray-language = Мова
+tray-voice = Голос
+tray-wake-word = Рушій детекції
+tray-noise-suppression = Шумозаглушення
+tray-vad = Детекцiя голосу (VAD)
+tray-gain-normalizer = Нормалізація гучності
+
+# ### HEADER
+header-commands = КОМАНДИ
+header-settings = НАЛАШТУВАННЯ
+
+# ### SEARCH
+search-placeholder = Введіть команду вручну або скажіть «Джарвіс» ...
+
+# ### MAIN PAGE
+assistant-not-running = АСИСТЕНТ НЕ ЗАПУЩЕНО
+assistant-offline-hint = Налаштувати його можна не запускаючи.
+btn-start = ЗАПУСТИТИ
+btn-starting = ЗАПУСК...
+
+# ### STATUS
+status-disconnected = Відключено
+status-standby = Очікування
+status-listening = Слухаю...
+status-processing = Обробка...
+
+# ### STATS
+stats-microphone = МІКРОФОН
+stats-neural-networks = НЕЙРОМЕРЕЖІ
+stats-resources = РЕСУРСИ
+stats-system-default = Системний
+stats-not-selected = Не вибрано
+stats-loading = Завантаження...
+
+# ### FOOTER
+footer-author = Автор проєкту
+footer-telegram = Наш телеграм канал
+footer-github = Github репозиторій проєкту
+footer-support = Підтримати проєкт на
+
+# ### SETTINGS
+settings-title = Налаштування
+settings-general = Основні
+settings-devices = Пристрої
+settings-neural-networks = Нейромережі
+settings-audio = Аудіо
+settings-recognition = Розпізнавання
+settings-about = Про програму
+settings-language = Мова
+settings-microphone = Мікрофон
+settings-microphone-desc = Його буде слухати асистент.
+settings-mic-default = За замовчуванням (Система)
+settings-voice = Голос асистента
+settings-voice-desc =
+ Не всі команди працюють з усіма звуковими пакетами.
+ Натисніть, щоб прослухати як звучить голос.
+settings-wake-word-engine = Рушій активації
+settings-wake-word-desc = Виберіть нейромережу для розпізнавання активаційної фрази.
+settings-stt-engine = Розпізнавання мовлення
+settings-intent-engine = Визначення наміру
+settings-intent-engine-desc = Виберіть нейромережу для розпізнавання команд.
+settings-noise-suppression = Шумозаглушення
+settings-noise-suppression-desc = Зменшує фоновий шум. Може негативно впливати на розпізнавання.
+settings-vad = Визначення голосу (VAD)
+settings-vad-desc = Пропускає тишу, економить ресурси CPU.
+settings-gain-normalizer = Нормалізація гучності
+settings-gain-normalizer-desc = Автоматично регулює рівень гучності.
+settings-api-keys = API Ключі
+settings-save = Зберегти
+settings-cancel = Скасувати
+settings-back = Назад
+settings-enabled = Увімкнено
+settings-disabled = Вимкнено
+
+# settings - beta notice
+settings-beta-title = БЕТА версія!
+settings-beta-desc = Частина функцій може працювати некоректно.
+settings-beta-feedback = Повідомляйте про всі знайдені баги в
+settings-beta-bot = наш телеграм бот
+settings-open-logs = Відкрити папку з логами
+
+# settings - picovoice
+settings-attention = Увага!
+settings-picovoice-warning = Ця нейромережа працює не у всіх!
+settings-picovoice-waiting = Ми чекаємо офіційного патча від розробників.
+settings-picovoice-key-desc = Введіть сюди свій ключ Picovoice. Він видається безкоштовно при реєстрації в
+settings-picovoice-key = Ключ Picovoice
+
+# settings - vosk
+settings-auto-detect = Авто-визначення
+settings-vosk-model = Модель розпізнавання мовлення (Vosk)
+settings-vosk-model-desc =
+ Виберіть модель Vosk для розпізнавання мовлення.
+ Ви можете завантажити моделі тут: https://alphacephei.com/vosk/models
+settings-models-not-found = Моделі не знайдено
+settings-models-hint = Помістіть моделі Vosk в папку resources/vosk
+
+# settings - openai
+settings-openai-key = Ключ OpenAI
+settings-openai-not-supported = Наразі ChatGPT не підтримується. Він буде доданий у наступних оновленнях.
+
+# ### COMMANDS PAGE
+commands-title = Команди
+commands-search = Пошук команд...
+commands-count = { $count } команд
+commands-wip-title = [404] Цей розділ ще в розробці!
+commands-wip-desc = Тут буде список команд + повноцінний редактор команд.
+commands-wip-follow = Слідкуйте за оновленнями в
+commands-wip-channel = нашому телеграм каналі
+
+# ### ERRORS
+error-generic = Сталася помилка
+error-connection = Помилка підключення
+error-not-found = Не знайдено
+
+# ### NOTIFICATIONS
+notification-saved = Налаштування збережено!
+notification-error = Помилка
+notification-assistant-started = Асистент запущено
+notification-assistant-stopped = Асистент зупинено
+
+# SLOTS EXTRACTION
+settings-slot-engine = Витяг параметрів
+settings-slot-engine-desc = Витягує параметри з голосових команд (напр. назва міста, число).
+settings-gliner-model = Модель GLiNER ONNX
+settings-gliner-model-desc =
+ Оберіть варіант моделі.
+ Квантизовані моделі (int8, uint8) швидші, але менш точні.
+settings-gliner-models-hint = Моделі GLiNER не знайдено.
+
+# ETC
+search-error-not-running = Асистент не запущено
+search-error-failed = Не вдалося виконати команду
+settings-no-voices = Голоси не знайдено
\ No newline at end of file
diff --git a/crates/jarvis-core/src/intent.rs b/crates/jarvis-core/src/intent.rs
new file mode 100644
index 0000000..6306b01
--- /dev/null
+++ b/crates/jarvis-core/src/intent.rs
@@ -0,0 +1,89 @@
+mod intentclassifier;
+mod embeddingclassifier;
+
+use std::path::PathBuf;
+
+use crate::{commands::{self, JCommandsList, JCommand}, config, models};
+use once_cell::sync::OnceCell;
+
+use crate::DB;
+
+static BACKEND: OnceCell = OnceCell::new();
+
+pub async fn init(commands: &Vec) -> Result<(), String> {
+ if BACKEND.get().is_some() {
+ return Ok(());
+ }
+
+ let backend = DB.get().unwrap().read().intent_backend.clone();
+
+ BACKEND.set(backend.clone()).map_err(|_| "Backend already set")?;
+
+ match backend.as_str() {
+ "none" => {
+ info!("Intent recognition disabled");
+ }
+ "intent-classifier" => {
+ info!("Initializing IntentClassifier backend.");
+ intentclassifier::init(&commands).await?;
+ info!("IntentClassifier backend initialized.");
+ }
+ // any other value is treated as a model ID for embedding classification
+ model_id => {
+ info!("Initializing EmbeddingClassifier with model '{}'.", model_id);
+ let model = models::embedding::load(models::registry(), model_id)?;
+ embeddingclassifier::init_with_model(model, &commands)?;
+ info!("EmbeddingClassifier backend initialized.");
+ }
+ }
+
+ Ok(())
+}
+
+pub async fn classify(text: &str) -> Option<(String, f64)> {
+ match BACKEND.get()?.as_str() {
+ "none" => None,
+ "intent-classifier" => {
+ match intentclassifier::classify(text).await {
+ Ok(prediction) => {
+ let confidence = prediction.confidence.value();
+ if confidence >= config::INTENT_CLASSIFIER_MIN_CONFIDENCE {
+ Some((prediction.intent.to_string(), confidence))
+ } else {
+ None
+ }
+ }
+ Err(e) => {
+ error!("Intent classification error: {}", e);
+ None
+ }
+ }
+ }
+ _ => {
+ match embeddingclassifier::classify(text) {
+ Ok((intent_id, confidence)) => {
+ if confidence >= config::EMBEDDING_MIN_CONFIDENCE {
+ Some((intent_id, confidence))
+ } else {
+ None
+ }
+ }
+ Err(e) => {
+ error!("Embedding classification error: {}", e);
+ None
+ }
+ }
+ }
+ }
+}
+
+// unified command lookup by intent ID - works for all backends
+pub fn get_command_by_intent<'a>(
+ commands: &'a [JCommandsList],
+ intent_id: &str,
+) -> Option<(&'a PathBuf, &'a JCommand)> {
+ if matches!(BACKEND.get().map(|s| s.as_str()), Some("none")) {
+ return None;
+ }
+ commands::get_command_by_id(commands, intent_id)
+}
diff --git a/crates/jarvis-core/src/intent/embeddingclassifier.rs b/crates/jarvis-core/src/intent/embeddingclassifier.rs
new file mode 100644
index 0000000..d9f8e08
--- /dev/null
+++ b/crates/jarvis-core/src/intent/embeddingclassifier.rs
@@ -0,0 +1,195 @@
+use std::path::PathBuf;
+use std::sync::Arc;
+use std::fs;
+
+use once_cell::sync::OnceCell;
+
+use crate::commands::JCommandsList;
+use crate::i18n;
+use crate::APP_CONFIG_DIR;
+use crate::models::embedding::EmbeddingModel;
+
+// no outer Mutex needed - state is immutable after init.
+// the embedding model has its own internal Mutex.
+static CLASSIFIER: OnceCell = OnceCell::new();
+
+struct IntentVector {
+ id: String,
+ vector: Vec,
+}
+
+struct EmbeddingClassifierState {
+ model: Arc,
+ intents: Vec,
+}
+
+// model is Arc (Send+Sync), intents are read-only after init
+unsafe impl Send for EmbeddingClassifierState {}
+unsafe impl Sync for EmbeddingClassifierState {}
+
+const CACHE_FILE: &str = "embedding_intents.json";
+const HASH_FILE: &str = "embedding_hash.txt";
+
+// init with a model loaded through the registry
+pub fn init_with_model(model: Arc, commands: &[JCommandsList]) -> Result<(), String> {
+ if CLASSIFIER.get().is_some() {
+ return Ok(());
+ }
+
+ info!("Initializing embedding classifier...");
+
+ let current_hash = crate::commands::commands_hash(commands);
+ let config_dir = APP_CONFIG_DIR.get().ok_or("Config dir not set")?;
+ let hash_path = config_dir.join(HASH_FILE);
+ let cache_path = config_dir.join(CACHE_FILE);
+
+ // check if cached vectors are still valid
+ let should_retrain = if hash_path.exists() && cache_path.exists() {
+ let stored_hash = fs::read_to_string(&hash_path).unwrap_or_default();
+ stored_hash.trim() != current_hash
+ } else {
+ true
+ };
+
+ let intents = if should_retrain {
+ info!("Building intent vectors from commands...");
+ let intents = build_intent_vectors(&model, commands)?;
+
+ // cache to disk
+ if let Ok(json) = serde_json::to_string(&intents_to_cache(&intents)) {
+ let _ = fs::write(&cache_path, json);
+ let _ = fs::write(&hash_path, ¤t_hash);
+ info!("Intent vectors cached");
+ }
+
+ intents
+ } else {
+ info!("Loading cached intent vectors...");
+ load_cached_intents(&cache_path)?
+ };
+
+ info!("Embedding classifier ready with {} intents", intents.len());
+
+ CLASSIFIER.set(EmbeddingClassifierState { model, intents })
+ .map_err(|_| "Classifier already set".to_string())?;
+
+ Ok(())
+}
+
+fn build_intent_vectors(
+ model: &EmbeddingModel,
+ commands: &[JCommandsList],
+) -> Result, String> {
+ let lang = i18n::get_language();
+ let mut intents = Vec::new();
+
+ for cmd_list in commands {
+ for cmd in &cmd_list.commands {
+ let phrases = cmd.get_phrases(&lang);
+ if phrases.is_empty() {
+ continue;
+ }
+
+ let texts: Vec<&str> = phrases.iter().map(|s| s.as_str()).collect();
+
+ let embeddings = model.embedding.lock().embed(texts, None)
+ .map_err(|e| format!("Embedding failed for '{}': {}", cmd.id, e))?;
+
+ // average all phrase vectors into one intent vector
+ let dim = embeddings[0].len();
+ let mut avg = vec![0.0f32; dim];
+
+ for emb in &embeddings {
+ for (i, val) in emb.iter().enumerate() {
+ avg[i] += val;
+ }
+ }
+
+ let count = embeddings.len() as f32;
+ for val in &mut avg {
+ *val /= count;
+ }
+
+ // normalize
+ let norm: f32 = avg.iter().map(|v| v * v).sum::().sqrt();
+ if norm > 0.0 {
+ for val in &mut avg {
+ *val /= norm;
+ }
+ }
+
+ intents.push(IntentVector {
+ id: cmd.id.clone(),
+ vector: avg,
+ });
+ }
+ }
+
+ Ok(intents)
+}
+
+pub fn classify(text: &str) -> Result<(String, f64), String> {
+ let state = CLASSIFIER.get().ok_or("Classifier not initialized")?;
+
+ // only the embedding model needs locking, intents are read-only
+ let embeddings = state.model.embedding.lock().embed(vec![text], None)
+ .map_err(|e| format!("Failed to embed query: {}", e))?;
+
+ let mut query_vec = embeddings.into_iter().next()
+ .ok_or("Empty embedding result")?;
+
+ // normalize query
+ let norm: f32 = query_vec.iter().map(|v| v * v).sum::().sqrt();
+ if norm > 0.0 {
+ for val in &mut query_vec {
+ *val /= norm;
+ }
+ }
+
+ // cosine similarity - track index, clone only the winner
+ let mut best_idx: usize = 0;
+ let mut best_score: f64 = -1.0;
+
+ for (i, intent) in state.intents.iter().enumerate() {
+ let score: f64 = query_vec.iter()
+ .zip(intent.vector.iter())
+ .map(|(a, b)| (*a as f64) * (*b as f64))
+ .sum();
+
+ if score > best_score {
+ best_score = score;
+ best_idx = i;
+ }
+ }
+
+ let best_id = state.intents[best_idx].id.clone();
+ debug!("Embedding classify: '{}' -> '{}' ({:.2}%)", text, best_id, best_score * 100.0);
+
+ Ok((best_id, best_score))
+}
+
+#[derive(serde::Serialize, serde::Deserialize)]
+struct CachedIntent {
+ id: String,
+ vector: Vec,
+}
+
+fn intents_to_cache(intents: &[IntentVector]) -> Vec {
+ intents.iter().map(|i| CachedIntent {
+ id: i.id.clone(),
+ vector: i.vector.clone(),
+ }).collect()
+}
+
+fn load_cached_intents(path: &PathBuf) -> Result, String> {
+ let json = fs::read_to_string(path)
+ .map_err(|e| format!("Failed to read cache: {}", e))?;
+
+ let cached: Vec = serde_json::from_str(&json)
+ .map_err(|e| format!("Failed to parse cache: {}", e))?;
+
+ Ok(cached.into_iter().map(|c| IntentVector {
+ id: c.id,
+ vector: c.vector,
+ }).collect())
+}
diff --git a/crates/jarvis-core/src/intent/intentclassifier.rs b/crates/jarvis-core/src/intent/intentclassifier.rs
new file mode 100644
index 0000000..1e490fe
--- /dev/null
+++ b/crates/jarvis-core/src/intent/intentclassifier.rs
@@ -0,0 +1,96 @@
+use intent_classifier::{
+ IntentPrediction, IntentError,
+ TrainingExample, TrainingSource, IntentId
+};
+
+use std::sync::Arc;
+use std::fs;
+
+use crate::commands::{self, JCommandsList};
+use crate::models;
+use crate::models::intent_classifier::IntentClassifierModel;
+use crate::{APP_CONFIG_DIR, i18n};
+
+use once_cell::sync::OnceCell;
+
+static MODEL: OnceCell> = OnceCell::new();
+
+const TRAINING_CACHE_FILE: &str = "intent_training.json";
+const COMMANDS_HASH_FILE: &str = "commands_hash.txt";
+
+pub async fn init(commands: &[JCommandsList]) -> Result<(), String> {
+ let current_hash = commands::commands_hash(&commands);
+
+ let model = models::intent_classifier::load(models::registry(), "intent-classifier").await?;
+
+ // check if we can use cached training data
+ let config_dir = APP_CONFIG_DIR.get().ok_or("Config dir not set")?;
+ let hash_path = config_dir.join(COMMANDS_HASH_FILE);
+ let cache_path = config_dir.join(TRAINING_CACHE_FILE);
+
+ let should_retrain = if hash_path.exists() && cache_path.exists() {
+ let stored_hash = fs::read_to_string(&hash_path).unwrap_or_default();
+ stored_hash.trim() != current_hash
+ } else {
+ true
+ };
+
+ if should_retrain {
+ info!("Training intent classifier with {} commands...", commands.len());
+ train_classifier(&model.classifier, &commands).await?;
+
+ if let Ok(export) = model.classifier.export_training_data().await {
+ let _ = fs::write(&cache_path, export);
+ let _ = fs::write(&hash_path, ¤t_hash);
+ info!("Training data cached.");
+ }
+ } else {
+ info!("Loading cached training data...");
+ if let Ok(data) = fs::read_to_string(&cache_path) {
+ model.classifier.import_training_data(&data).await
+ .map_err(|e| format!("Failed to import training data: {}", e))?;
+ }
+ }
+
+ MODEL.set(model).map_err(|_| "Model already set")?;
+
+ Ok(())
+}
+
+pub async fn classify(text: &str) -> Result {
+ let model = MODEL.get().expect("IntentClassifier not initialized");
+ model.classifier.predict_intent(text).await
+}
+
+async fn train_classifier(
+ classifier: &intent_classifier::IntentClassifier,
+ commands: &[JCommandsList]
+) -> Result<(), String> {
+ let lang = i18n::get_language();
+ info!("Training intent classifier for language: {}", lang);
+
+ let mut total_examples = 0;
+
+ for assistant_cmd in commands {
+ for cmd in &assistant_cmd.commands {
+ let phrases = cmd.get_phrases(&lang);
+
+ for phrase in phrases.iter() {
+ let example = TrainingExample {
+ text: phrase.clone(),
+ intent: IntentId::from(cmd.id.as_str()),
+ confidence: 1.0,
+ source: TrainingSource::Programmatic,
+ };
+
+ classifier.add_training_example(example).await
+ .map_err(|e| format!("Failed to add training example: {}", e))?;
+
+ total_examples += 1;
+ }
+ }
+ }
+
+ info!("Added {} training examples for language '{}'", total_examples, lang);
+ Ok(())
+}
diff --git a/crates/jarvis-core/src/ipc.rs b/crates/jarvis-core/src/ipc.rs
new file mode 100644
index 0000000..1768c1d
--- /dev/null
+++ b/crates/jarvis-core/src/ipc.rs
@@ -0,0 +1,5 @@
+mod events;
+mod server;
+
+pub use events::{IpcAction, IpcEvent};
+pub use server::{init, send, set_action_handler, start_server, has_clients, IPC_ADDR, IPC_PORT};
\ No newline at end of file
diff --git a/crates/jarvis-core/src/ipc/events.rs b/crates/jarvis-core/src/ipc/events.rs
new file mode 100644
index 0000000..e372d40
--- /dev/null
+++ b/crates/jarvis-core/src/ipc/events.rs
@@ -0,0 +1,56 @@
+use serde::{Deserialize, Serialize};
+
+// Events sent from jarvis-app to GUI
+#[derive(Clone, Debug, Serialize)]
+#[serde(tag = "event", rename_all = "snake_case")]
+pub enum IpcEvent {
+ // Wake word detected, starting to listen
+ WakeWordDetected,
+
+ // Actively listening for command
+ Listening,
+
+ // Speech recognized
+ SpeechRecognized { text: String },
+
+ // Command was executed
+ CommandExecuted { id: String, success: bool },
+
+ // Returned to idle state
+ Idle,
+
+ // Error occurred
+ Error { message: String },
+
+ // App started
+ Started,
+
+ // App is shutting down
+ Stopping,
+
+ // Pong response
+ Pong,
+
+ // request GUI to reveal/focus window
+ RevealWindow,
+}
+
+// Actions sent from GUI to jarvis-app
+#[derive(Clone, Debug, Deserialize)]
+#[serde(tag = "action", rename_all = "snake_case")]
+pub enum IpcAction {
+ // Request graceful shutdown
+ Stop,
+
+ // Reload commands from disk
+ ReloadCommands,
+
+ // Ping to check connection
+ Ping,
+
+ // Mute/unmute listening
+ SetMuted { muted: bool },
+
+ // Execute text command
+ TextCommand { text: String },
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/ipc/server.rs b/crates/jarvis-core/src/ipc/server.rs
new file mode 100644
index 0000000..6e9072c
--- /dev/null
+++ b/crates/jarvis-core/src/ipc/server.rs
@@ -0,0 +1,198 @@
+use std::net::SocketAddr;
+use std::sync::Arc;
+
+use futures_util::{SinkExt, StreamExt};
+use once_cell::sync::OnceCell;
+use parking_lot::RwLock;
+use tokio::net::{TcpListener, TcpStream};
+use tokio::sync::broadcast;
+use tokio_tungstenite::{accept_async, tungstenite::Message};
+
+use super::events::{IpcAction, IpcEvent};
+
+pub const IPC_PORT: u16 = 9712;
+pub const IPC_ADDR: &str = "127.0.0.1";
+
+static BROADCAST_TX: OnceCell> = OnceCell::new();
+static ACTION_HANDLER: OnceCell>>>> = OnceCell::new();
+
+// Initialize the IPC broadcast channel
+pub fn init() -> broadcast::Sender {
+ if let Some(tx) = BROADCAST_TX.get() {
+ return tx.clone();
+ }
+
+ let (tx, _) = broadcast::channel::(32);
+ BROADCAST_TX.set(tx.clone()).ok();
+ ACTION_HANDLER.set(Arc::new(RwLock::new(None))).ok();
+
+ info!("IPC: Broadcast channel initialized");
+ tx
+}
+
+// Send event to all connected clients
+pub fn send(event: IpcEvent) {
+ if let Some(tx) = BROADCAST_TX.get() {
+ match tx.send(event.clone()) {
+ Ok(n) => {
+ if n > 0 {
+ debug!("IPC: Sent {:?} to {} client(s)", event, n);
+ }
+ }
+ Err(_) => {
+ // no receivers, that's fine
+ }
+ }
+ }
+}
+
+// Register handler for incoming actions from GUI
+pub fn set_action_handler(handler: F)
+where
+ F: Fn(IpcAction) + Send + Sync + 'static,
+{
+ if let Some(h) = ACTION_HANDLER.get() {
+ *h.write() = Some(Box::new(handler));
+ }
+}
+
+fn handle_action(action: IpcAction) {
+ info!("IPC: Received action {:?}", action);
+
+ // handle ping internally
+ if matches!(action, IpcAction::Ping) {
+ send(IpcEvent::Pong);
+ return;
+ }
+
+ // forward to registered handler
+ if let Some(handler_lock) = ACTION_HANDLER.get() {
+ let handler = handler_lock.read();
+ if let Some(ref h) = *handler {
+ h(action);
+ }
+ }
+}
+
+// Start the WebSocket server (blocking)
+pub async fn start_server() {
+ let addr = format!("{}:{}", IPC_ADDR, IPC_PORT);
+ let socket_addr: SocketAddr = addr.parse().expect("Invalid IPC address");
+
+ let listener = match TcpListener::bind(&socket_addr).await {
+ Ok(l) => {
+ info!("IPC: WebSocket server listening on ws://{}", addr);
+ l
+ }
+ Err(e) => {
+ error!("IPC: Failed to bind to {}: {}", addr, e);
+ return;
+ }
+ };
+
+ // notify that we're ready
+ send(IpcEvent::Started);
+
+ while let Ok((stream, peer_addr)) = listener.accept().await {
+ info!("IPC: Client connecting from {}", peer_addr);
+
+ let rx = BROADCAST_TX
+ .get()
+ .map(|tx| tx.subscribe())
+ .expect("IPC not initialized");
+
+ tokio::spawn(handle_client(stream, peer_addr, rx));
+ }
+}
+
+async fn handle_client(
+ stream: TcpStream,
+ peer_addr: SocketAddr,
+ mut event_rx: broadcast::Receiver,
+) {
+ let ws_stream = match accept_async(stream).await {
+ Ok(ws) => {
+ info!("IPC: Client connected: {}", peer_addr);
+ ws
+ }
+ Err(e) => {
+ error!("IPC: WebSocket handshake failed for {}: {}", peer_addr, e);
+ return;
+ }
+ };
+
+ let (mut ws_tx, mut ws_rx) = ws_stream.split();
+
+ loop {
+ tokio::select! {
+ // forward events to client
+ event_result = event_rx.recv() => {
+ match event_result {
+ Ok(event) => {
+ let json = match serde_json::to_string(&event) {
+ Ok(j) => j,
+ Err(e) => {
+ error!("IPC: Failed to serialize event: {}", e);
+ continue;
+ }
+ };
+
+ if ws_tx.send(Message::Text(json.into())).await.is_err() {
+ info!("IPC: Client {} disconnected (send failed)", peer_addr);
+ break;
+ }
+ }
+ Err(broadcast::error::RecvError::Lagged(n)) => {
+ warn!("IPC: Client {} lagged {} events", peer_addr, n);
+ }
+ Err(broadcast::error::RecvError::Closed) => {
+ info!("IPC: Broadcast channel closed");
+ break;
+ }
+ }
+ }
+
+ // receive messages from client
+ msg_result = ws_rx.next() => {
+ match msg_result {
+ Some(Ok(Message::Text(text))) => {
+ match serde_json::from_str::(&text) {
+ Ok(action) => handle_action(action),
+ Err(e) => {
+ warn!("IPC: Invalid action from {}: {} ({})", peer_addr, text, e);
+ }
+ }
+ }
+ Some(Ok(Message::Ping(data))) => {
+ if ws_tx.send(Message::Pong(data)).await.is_err() {
+ break;
+ }
+ }
+ Some(Ok(Message::Close(_))) => {
+ info!("IPC: Client {} sent close frame", peer_addr);
+ break;
+ }
+ Some(Err(e)) => {
+ error!("IPC: Error receiving from {}: {}", peer_addr, e);
+ break;
+ }
+ None => {
+ info!("IPC: Client {} stream ended", peer_addr);
+ break;
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+
+ info!("IPC: Client disconnected: {}", peer_addr);
+}
+
+pub fn has_clients() -> bool {
+ if let Some(tx) = BROADCAST_TX.get() {
+ tx.receiver_count() > 0
+ } else {
+ false
+ }
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lib.rs b/crates/jarvis-core/src/lib.rs
new file mode 100644
index 0000000..41f8c04
--- /dev/null
+++ b/crates/jarvis-core/src/lib.rs
@@ -0,0 +1,72 @@
+use once_cell::sync::{Lazy, OnceCell};
+use parking_lot::RwLock;
+use std::{sync::Arc};
+use platform_dirs::AppDirs;
+use std::path::PathBuf;
+
+#[macro_use]
+extern crate log;
+
+pub mod time;
+
+pub mod audio;
+pub mod commands;
+pub mod config;
+pub mod db;
+pub mod i18n;
+
+#[cfg(feature = "jarvis_app")]
+pub mod listener;
+
+pub mod recorder;
+
+#[cfg(feature = "jarvis_app")]
+pub mod stt;
+
+#[cfg(feature = "intent")]
+pub mod intent;
+
+#[cfg(feature = "jarvis_app")]
+pub mod slots;
+
+pub mod models;
+
+// re-exported from models/
+pub use models::vosk_models;
+pub use models::gliner_models;
+
+#[cfg(feature = "jarvis_app")]
+pub mod audio_processing;
+
+#[cfg(feature = "jarvis_app")]
+pub mod ipc;
+
+pub mod voices;
+
+pub mod audio_buffer;
+
+#[cfg(feature = "lua")]
+pub mod lua;
+
+// shared statics
+// pub static APP_DIR: Lazy = Lazy::new(|| std::env::current_dir().unwrap());
+pub static APP_DIR: Lazy = Lazy::new(|| {
+ std::env::current_exe()
+ .ok()
+ .and_then(|p| p.parent().map(|p| p.to_path_buf()))
+ .unwrap_or_else(|| std::env::current_dir().unwrap())
+});
+pub static SOUND_DIR: Lazy = Lazy::new(|| APP_DIR.clone().join(config::SOUND_PATH));
+pub static APP_DIRS: OnceCell = OnceCell::new();
+pub static APP_CONFIG_DIR: OnceCell = OnceCell::new();
+pub static APP_LOG_DIR: OnceCell = OnceCell::new();
+pub static DB: OnceCell>> = OnceCell::new();
+pub static COMMANDS_LIST: OnceCell> = OnceCell::new();
+
+// re-exports
+pub use commands::JCommandsList;
+pub use config::structs::*;
+pub use db::structs::Settings;
+pub use db::SettingsManager;
+
+// use crate::commands::{JComandsList, JCommand};
\ No newline at end of file
diff --git a/crates/jarvis-core/src/listener.rs b/crates/jarvis-core/src/listener.rs
new file mode 100644
index 0000000..e038a85
--- /dev/null
+++ b/crates/jarvis-core/src/listener.rs
@@ -0,0 +1,46 @@
+mod rustpotter;
+mod vosk;
+
+use once_cell::sync::OnceCell;
+
+use crate::config::structs::WakeWordEngine;
+
+use crate::DB;
+
+static WAKE_WORD_ENGINE: OnceCell = OnceCell::new();
+
+pub fn init() -> Result<(), String> {
+ if WAKE_WORD_ENGINE.get().is_some() {
+ return Ok(());
+ }
+
+ let engine = DB.get().unwrap().read().wake_word_engine;
+
+ WAKE_WORD_ENGINE.set(engine)
+ .map_err(|_| "Wake word engine already set".to_string())?;
+
+ match engine {
+ WakeWordEngine::Porcupine => {
+ Err("Porcupine wake-word engine is not supported".to_string())
+ }
+ WakeWordEngine::Rustpotter => {
+ info!("Initializing Rustpotter wake-word engine.");
+ rustpotter::init()
+ .map_err(|_| "Failed to init Rustpotter".to_string())
+ }
+ WakeWordEngine::Vosk => {
+ info!("Initializing Vosk as wake-word engine.");
+ warn!("Using Vosk as wake-word engine is highly not recommended, because it's very slow for this task.");
+ vosk::init()
+ .map_err(|_| "Failed to init Vosk wake-word".to_string())
+ }
+ }
+}
+
+pub fn data_callback(frame_buffer: &[i16]) -> Option {
+ match WAKE_WORD_ENGINE.get()? {
+ WakeWordEngine::Porcupine => None,
+ WakeWordEngine::Rustpotter => rustpotter::data_callback(frame_buffer),
+ WakeWordEngine::Vosk => vosk::data_callback(frame_buffer),
+ }
+}
diff --git a/crates/jarvis-core/src/listener/porcupine.rs b/crates/jarvis-core/src/listener/porcupine.rs
new file mode 100644
index 0000000..1ebb3f0
--- /dev/null
+++ b/crates/jarvis-core/src/listener/porcupine.rs
@@ -0,0 +1,56 @@
+use std::path::Path;
+
+use once_cell::sync::OnceCell;
+use porcupine::{Porcupine, PorcupineBuilder};
+
+use crate::config;
+use crate::DB;
+
+// store porcupine instance
+static PORCUPINE: OnceCell = OnceCell::new();
+
+pub fn init() -> Result<(), ()> {
+ let picovoice_api_key: String;
+
+ // retrieve picovoice api key
+ picovoice_api_key = DB.get().unwrap().api_keys.picovoice.clone();
+ if picovoice_api_key.trim().is_empty() {
+ warn!("Picovoice API key is not set.");
+ return Err(());
+ }
+
+ // create porcupine instance with the given API key
+ match PorcupineBuilder::new_with_keyword_paths(
+ picovoice_api_key,
+ &[Path::new(config::KEYWORDS_PATH).join(config::DEFAULT_KEYWORD)],
+ )
+ .sensitivities(&[config::DEFAULT_SENSITIVITY]) // set sensitivity
+ .init()
+ {
+ Ok(pinstance) => {
+ // success
+ info!("Porcupine successfully initialized with the given API key.");
+
+ // store
+ PORCUPINE.set(pinstance);
+ }
+ Err(msg) => {
+ error!("Porcupine failed to initialize, either API key is not valid or there is no internet connection.");
+ error!("Error details: {}", msg);
+
+ return Err(());
+ }
+ }
+
+ Ok(())
+}
+
+pub fn data_callback(frame_buffer: &[i16]) -> Option {
+ if let Ok(keyword_index) = PORCUPINE.get().unwrap().process(&frame_buffer) {
+ if keyword_index >= 0 {
+ return Some(keyword_index);
+ }
+ }
+
+ None
+}
diff --git a/crates/jarvis-core/src/listener/rustpotter.rs b/crates/jarvis-core/src/listener/rustpotter.rs
new file mode 100644
index 0000000..f0ca5b2
--- /dev/null
+++ b/crates/jarvis-core/src/listener/rustpotter.rs
@@ -0,0 +1,69 @@
+use std::sync::Mutex;
+
+use once_cell::sync::OnceCell;
+use rustpotter::Rustpotter;
+
+use crate::config;
+
+// store rustpotter instance
+static RUSTPOTTER: OnceCell> = OnceCell::new();
+
+pub fn init() -> Result<(), ()> {
+ let rustpotter_config = config::RUSTPOTTER_DEFAULT_CONFIG;
+
+ // create rustpotter instance
+ match Rustpotter::new(&rustpotter_config) {
+ Ok(mut rinstance) => {
+ // success
+ // wake word files list
+ // @TODO. Make it configurable via GUI for custom user voice.
+ let rustpotter_wake_word_files: [&str; 1] = [
+ "resources/rustpotter/jarvis-default.rpw",
+ // "rustpotter/jarvis-community-1.rpw",
+ // "rustpotter/jarvis-community-2.rpw",
+ // "rustpotter/jarvis-community-3.rpw",
+ // "rustpotter/jarvis-community-4.rpw",
+ // "rustpotter/jarvis-community-5.rpw",
+ ];
+
+ // load wake word files
+ for rpw in rustpotter_wake_word_files {
+ // @TODO: Change wakeword key to something else?
+ if let Err(e) = rinstance.add_wakeword_from_file(rpw, rpw) {
+ error!("Failed to load wakeword file '{}': {}", rpw, e);
+ }
+ }
+
+ // store
+ let _ = RUSTPOTTER.set(Mutex::new(rinstance));
+ }
+ Err(msg) => {
+ error!("Rustpotter failed to initialize.\nError details: {}", msg);
+
+ return Err(());
+ }
+ }
+
+ Ok(())
+}
+
+pub fn data_callback(frame_buffer: &[i16]) -> Option {
+ let mut lock = RUSTPOTTER.get().unwrap().lock();
+ let rustpotter = lock.as_mut().unwrap();
+ // let detection = rustpotter.process_samples(frame_buffer.to_vec()); // @TODO. Temp crutch. Fix optimization issue, frame_buffer should not be copied to a new vector!
+ let detection = rustpotter.process_samples(frame_buffer);
+
+ // info!("Ruspotter data callback");
+
+ if let Some(detection) = detection {
+ if detection.score > config::RUSPOTTER_MIN_SCORE {
+ info!("Rustpotter detection info:\n{:?}", detection);
+
+ return Some(0);
+ } else {
+ info!("Rustpotter detection info:\n{:?}", detection)
+ }
+ }
+
+ None
+}
diff --git a/crates/jarvis-core/src/listener/vosk.rs b/crates/jarvis-core/src/listener/vosk.rs
new file mode 100644
index 0000000..22160aa
--- /dev/null
+++ b/crates/jarvis-core/src/listener/vosk.rs
@@ -0,0 +1,76 @@
+use crate::{config, stt, i18n};
+
+pub fn init() -> Result<(), ()> {
+ Ok(()) // nothing to init for Vosk
+}
+
+pub fn data_callback(frame_buffer: &[i16]) -> Option {
+ if let Some((recognized, _confidence)) = stt::recognize_wake_word(frame_buffer) {
+ let recognized = recognized.trim().to_lowercase();
+
+ // skip unknown/empty
+ if recognized.is_empty() || recognized == "[unk]" {
+ return None;
+ }
+
+ info!("Wake word candidate: '{}'", recognized);
+
+ // language-specific wake phrase
+ let lang = i18n::get_language();
+ let wake_phrases = config::get_wake_phrases(&lang);
+
+ // verify with seqdiff ratio
+ for word in recognized.split_whitespace() {
+ if word == "[unk]" {
+ continue;
+ }
+
+ let word_chars: Vec = word.chars().collect();
+
+ for wake_phrase in wake_phrases {
+ let wake_chars: Vec = wake_phrase.chars().collect();
+ let similarity = seqdiff::ratio(&wake_chars, &word_chars);
+
+ if similarity >= config::VOSK_MIN_RATIO {
+ info!("Wake word match: '{}' ~ '{}' ({:.1}%)", word, wake_phrase, similarity);
+ return Some(0);
+ }
+ }
+ }
+
+ // info!("Similarity: {:.1}% ('{}' vs '{}')", similarity, recognized, config::VOSK_FETCH_PHRASE);
+ }
+
+ None
+}
+
+// @TODO. Make it better somehow (more accurate or with higher sensitivity).
+// pub fn data_callback(frame_buffer: &[i16]) -> Option {
+// // recognize & convert to sequence
+// let recognized_phrase = stt::recognize(&frame_buffer, true).unwrap_or("".into());
+
+// if !recognized_phrase.trim().is_empty() {
+// info!("Vosk wake-word debug info:");
+// info!("rec: {}", recognized_phrase);
+// let recognized_phrases = recognized_phrase.split_whitespace();
+// for phrase in recognized_phrases {
+// let recognized_phrase_chars = phrase.trim().to_lowercase().chars().collect::>();
+
+// // compare
+// let compare_ratio = seqdiff::ratio(
+// &config::VOSK_FETCH_PHRASE.chars().collect::>(),
+// &recognized_phrase_chars,
+// );
+// info!("og phrase: {:?}", &config::VOSK_FETCH_PHRASE);
+// info!("recognized phrase: {:?}", &recognized_phrase_chars);
+// info!("compare ratio: {}", compare_ratio);
+
+// if compare_ratio >= config::VOSK_MIN_RATIO {
+// info!("Phrase activated.");
+// return Some(0);
+// }
+// }
+// }
+
+// None
+// }
diff --git a/crates/jarvis-core/src/lua.rs b/crates/jarvis-core/src/lua.rs
new file mode 100644
index 0000000..9653867
--- /dev/null
+++ b/crates/jarvis-core/src/lua.rs
@@ -0,0 +1,28 @@
+mod engine;
+mod sandbox;
+mod error;
+mod api;
+
+mod structs;
+pub use structs::*;
+
+pub use engine::LuaEngine;
+pub use sandbox::SandboxLevel;
+pub use error::LuaError;
+
+use std::path::PathBuf;
+use std::time::Duration;
+
+#[cfg(test)]
+mod tests;
+
+// Execute a Lua command script
+pub fn execute(
+ script_path: &PathBuf,
+ context: CommandContext,
+ sandbox: SandboxLevel,
+ timeout: Duration,
+) -> Result {
+ let engine = LuaEngine::new(sandbox)?;
+ engine.execute(script_path, context, timeout)
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/api.rs b/crates/jarvis-core/src/lua/api.rs
new file mode 100644
index 0000000..8bda5a4
--- /dev/null
+++ b/crates/jarvis-core/src/lua/api.rs
@@ -0,0 +1,7 @@
+pub mod core;
+pub mod audio;
+pub mod context;
+pub mod http;
+pub mod fs;
+pub mod state;
+pub mod system;
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/api/_new_api_module_example.rs b/crates/jarvis-core/src/lua/api/_new_api_module_example.rs
new file mode 100644
index 0000000..7c2112b
--- /dev/null
+++ b/crates/jarvis-core/src/lua/api/_new_api_module_example.rs
@@ -0,0 +1,37 @@
+// QUICK HELP ON HOW TO ADD NEW LUA API MODULE
+//
+// # 1. DEFINE NEW API MODULE FILE
+
+use mlua::{Lua, Table};
+use crate::lua::error::LuaError;
+
+pub fn register(lua: &Lua, jarvis: &Table) -> mlua::Result<()> {
+ let mymodule = lua.create_table()?;
+
+ // add functions
+ let my_fn = lua.create_function(|_, arg: String| {
+ // implementation
+ Ok(format!("Result: {}", arg))
+ })?;
+ mymodule.set("my_function", my_fn)?;
+
+ jarvis.set("mymodule", mymodule)?;
+
+ Ok(())
+}
+
+// # 2. ADD NEW API MODULE TO mod.rs
+
+pub mod mymodule;
+
+
+// # 3. REGISTER NEW MODULE IN engine.rs
+// in register_api()
+api::mymodule::register(&self.lua, &jarvis)?;
+
+// # 4. YOU CAN ALSO DEFINE ASYNC FUNCTIONS INSTEAD
+let async_fn = lua.create_async_function(|_, url: String| async move {
+ let response = reqwest::get(&url).await
+ .map_err(|e| mlua::Error::runtime(e.to_string()))?;
+ Ok(response.text().await.unwrap_or_default())
+})?;
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/api/audio.rs b/crates/jarvis-core/src/lua/api/audio.rs
new file mode 100644
index 0000000..706f4df
--- /dev/null
+++ b/crates/jarvis-core/src/lua/api/audio.rs
@@ -0,0 +1,77 @@
+// Audio Lua API: something sound related, apparently :3
+
+use mlua::{Lua, Table};
+use crate::voices::{self, Reaction};
+
+pub fn register(lua: &Lua, jarvis: &Table) -> mlua::Result<()> {
+ let audio = lua.create_table()?;
+
+ // jarvis.audio.play(reaction)
+ // reactions: "ok", "reply", "greet", "not_found", "error", "goodbye", "thanks"
+ let play_fn = lua.create_function(|_, reaction: String| {
+ let reaction_enum = match reaction.to_lowercase().as_str() {
+ "ok" => Reaction::Ok,
+ "reply" => Reaction::Reply,
+ "greet" => Reaction::Greet,
+ "not_found" => Reaction::NotFound,
+ "error" => Reaction::Error,
+ "goodbye" => Reaction::Goodbye,
+ "thanks" => Reaction::Thanks,
+ // "joke" => Reaction::Joke, NO PUN INTENDED :3
+ _ => {
+ log::warn!("[Lua] Unknown reaction: {}", reaction);
+ return Ok(false);
+ }
+ };
+
+ voices::play(reaction_enum);
+ Ok(true)
+ })?;
+ audio.set("play", play_fn)?;
+
+ // jarvis.audio.play_ok()
+ let play_ok_fn = lua.create_function(|_, ()| {
+ voices::play_ok();
+ Ok(())
+ })?;
+ audio.set("play_ok", play_ok_fn)?;
+
+ // jarvis.audio.play_reply()
+ let play_reply_fn = lua.create_function(|_, ()| {
+ voices::play_reply();
+ Ok(())
+ })?;
+ audio.set("play_reply", play_reply_fn)?;
+
+ // jarvis.audio.play_error()
+ let play_error_fn = lua.create_function(|_, ()| {
+ voices::play_error();
+ Ok(())
+ })?;
+ audio.set("play_error", play_error_fn)?;
+
+ // jarvis.audio.play_not_found()
+ let play_not_found_fn = lua.create_function(|_, ()| {
+ voices::play_not_found();
+ Ok(())
+ })?;
+ audio.set("play_not_found", play_not_found_fn)?;
+
+ // jarvis.audio.play_greet()
+ let play_greet_fn = lua.create_function(|_, ()| {
+ voices::play_greet();
+ Ok(())
+ })?;
+ audio.set("play_greet", play_greet_fn)?;
+
+ // jarvis.audio.play_goodbye()
+ let play_goodbye_fn = lua.create_function(|_, ()| {
+ voices::play_goodbye();
+ Ok(())
+ })?;
+ audio.set("play_goodbye", play_goodbye_fn)?;
+
+ jarvis.set("audio", audio)?;
+
+ Ok(())
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/api/context.rs b/crates/jarvis-core/src/lua/api/context.rs
new file mode 100644
index 0000000..2d75b8f
--- /dev/null
+++ b/crates/jarvis-core/src/lua/api/context.rs
@@ -0,0 +1,45 @@
+// Context Lua API: read-only command context
+
+use mlua::{Lua, Table};
+use crate::lua::{CommandContext};
+
+use crate::commands::SlotValue;
+
+pub fn register(lua: &Lua, jarvis: &Table, ctx: &CommandContext) -> mlua::Result<()> {
+ let context = lua.create_table()?;
+
+ // read-only context values
+ context.set("phrase", ctx.phrase.clone())?;
+ context.set("command_id", ctx.command_id.clone())?;
+ context.set("command_path", ctx.command_path.to_string_lossy().to_string())?;
+ context.set("language", ctx.language.clone())?;
+
+ // time info
+ let time = lua.create_table()?;
+ let now = chrono::Local::now();
+ time.set("year", now.format("%Y").to_string())?;
+ time.set("month", now.format("%m").to_string())?;
+ time.set("day", now.format("%d").to_string())?;
+ time.set("hour", now.format("%H").to_string())?;
+ time.set("minute", now.format("%M").to_string())?;
+ time.set("second", now.format("%S").to_string())?;
+ time.set("weekday", now.format("%A").to_string())?;
+ time.set("timestamp", now.timestamp())?;
+ context.set("time", time)?;
+
+ // slots
+ let slots_table = lua.create_table()?;
+ if let Some(ref slots) = ctx.slots {
+ for (name, value) in slots {
+ match value {
+ SlotValue::Text(t) => slots_table.set(name.as_str(), t.as_str())?,
+ SlotValue::Number(n) => slots_table.set(name.as_str(), *n)?,
+ }
+ }
+ }
+ context.set("slots", slots_table)?;
+
+ jarvis.set("context", context)?;
+
+ Ok(())
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/api/core.rs b/crates/jarvis-core/src/lua/api/core.rs
new file mode 100644
index 0000000..fa05bae
--- /dev/null
+++ b/crates/jarvis-core/src/lua/api/core.rs
@@ -0,0 +1,50 @@
+// Core Lua API: log, sleep, print, etc.
+
+use mlua::{Lua, Table, MultiValue};
+
+pub fn register(lua: &Lua, jarvis: &Table) -> mlua::Result<()> {
+
+ // @ jarvis.log(level, message)
+ // log something
+ let log_fn = lua.create_function(|_, (level, message): (String, String)| {
+ match level.to_lowercase().as_str() {
+ "debug" => log::debug!("[Lua] {}", message),
+ "info" => log::info!("[Lua] {}", message),
+ "warn" => log::warn!("[Lua] {}", message),
+ "error" => log::error!("[Lua] {}", message),
+ _ => log::info!("[Lua] {}", message),
+ }
+ Ok(())
+ })?;
+ jarvis.set("log", log_fn)?;
+
+ // @ jarvis.print(...)
+ // simple print
+ let print_fn = lua.create_function(|_, args: MultiValue| {
+ let parts: Vec = args.iter()
+ .map(|v| format!("{:?}", v))
+ .collect();
+ log::info!("[Lua] {}", parts.join(" "));
+ Ok(())
+ })?;
+ jarvis.set("print", print_fn)?;
+
+ // @ jarvis.sleep(ms)
+ // ..zZZ
+ let sleep_fn = lua.create_function(|_, ms: u64| {
+ std::thread::sleep(std::time::Duration::from_millis(ms));
+ Ok(())
+ })?;
+ jarvis.set("sleep", sleep_fn)?;
+
+ // @ jarvis.speak(text)
+ // @TODO: update when TTS will be implemented
+ let speak_fn = lua.create_function(|_, text: String| {
+ log::info!("[Lua] SPEAK: {}", text);
+ // pass
+ Ok(())
+ })?;
+ jarvis.set("speak", speak_fn)?;
+
+ Ok(())
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/api/fs.rs b/crates/jarvis-core/src/lua/api/fs.rs
new file mode 100644
index 0000000..23e002e
--- /dev/null
+++ b/crates/jarvis-core/src/lua/api/fs.rs
@@ -0,0 +1,212 @@
+// File System Lua API: read, write, list (sandboxed)
+
+use mlua::{Lua, Table};
+use std::path::{Path, PathBuf};
+use std::fs;
+
+use crate::lua::sandbox::SandboxLevel;
+
+pub fn register(
+ lua: &Lua,
+ jarvis: &Table,
+ command_path: &PathBuf,
+ sandbox: SandboxLevel,
+) -> mlua::Result<()> {
+ let fs_table = lua.create_table()?;
+
+ let cmd_path = command_path.clone();
+ let sandbox_level = sandbox;
+
+ // jarvis.fs.read(path)
+ let cmd_path_read = cmd_path.clone();
+ let read_fn = lua.create_function(move |_, path: String| {
+ let full_path = resolve_path(&cmd_path_read, &path, sandbox_level)?;
+
+ fs::read_to_string(&full_path)
+ .map_err(|e| mlua::Error::runtime(format!("Read error: {}", e)))
+ })?;
+ fs_table.set("read", read_fn)?;
+
+ // jarvis.fs.read_bytes(path)
+ let cmd_path_read_bytes = cmd_path.clone();
+ let read_bytes_fn = lua.create_function(move |lua, path: String| {
+ let full_path = resolve_path(&cmd_path_read_bytes, &path, sandbox_level)?;
+
+ let bytes = fs::read(&full_path)
+ .map_err(|e| mlua::Error::runtime(format!("Read error: {}", e)))?;
+
+ Ok(lua.create_string(&bytes)?)
+ })?;
+ fs_table.set("read_bytes", read_bytes_fn)?;
+
+ // jarvis.fs.write(path, content)
+ let cmd_path_write = cmd_path.clone();
+ let write_fn = lua.create_function(move |_, (path, content): (String, String)| {
+ if !sandbox_level.allows_fs_write() {
+ return Err(mlua::Error::runtime("Write not allowed in this sandbox"));
+ }
+
+ let full_path = resolve_path(&cmd_path_write, &path, sandbox_level)?;
+
+ // ensure parent directory exists
+ if let Some(parent) = full_path.parent() {
+ fs::create_dir_all(parent)
+ .map_err(|e| mlua::Error::runtime(format!("Create dir error: {}", e)))?;
+ }
+
+ fs::write(&full_path, content)
+ .map_err(|e| mlua::Error::runtime(format!("Write error: {}", e)))?;
+
+ Ok(true)
+ })?;
+ fs_table.set("write", write_fn)?;
+
+ // jarvis.fs.append(path, content)
+ let cmd_path_append = cmd_path.clone();
+ let append_fn = lua.create_function(move |_, (path, content): (String, String)| {
+ if !sandbox_level.allows_fs_write() {
+ return Err(mlua::Error::runtime("Write not allowed in this sandbox"));
+ }
+
+ let full_path = resolve_path(&cmd_path_append, &path, sandbox_level)?;
+
+ use std::io::Write;
+ let mut file = fs::OpenOptions::new()
+ .create(true)
+ .append(true)
+ .open(&full_path)
+ .map_err(|e| mlua::Error::runtime(format!("Open error: {}", e)))?;
+
+ file.write_all(content.as_bytes())
+ .map_err(|e| mlua::Error::runtime(format!("Write error: {}", e)))?;
+
+ Ok(true)
+ })?;
+ fs_table.set("append", append_fn)?;
+
+ // jarvis.fs.exists(path)
+ let cmd_path_exists = cmd_path.clone();
+ let exists_fn = lua.create_function(move |_, path: String| {
+ let full_path = resolve_path(&cmd_path_exists, &path, sandbox_level)?;
+ Ok(full_path.exists())
+ })?;
+ fs_table.set("exists", exists_fn)?;
+
+ // jarvis.fs.is_file(path)
+ let cmd_path_is_file = cmd_path.clone();
+ let is_file_fn = lua.create_function(move |_, path: String| {
+ let full_path = resolve_path(&cmd_path_is_file, &path, sandbox_level)?;
+ Ok(full_path.is_file())
+ })?;
+ fs_table.set("is_file", is_file_fn)?;
+
+ // jarvis.fs.is_dir(path)
+ let cmd_path_is_dir = cmd_path.clone();
+ let is_dir_fn = lua.create_function(move |_, path: String| {
+ let full_path = resolve_path(&cmd_path_is_dir, &path, sandbox_level)?;
+ Ok(full_path.is_dir())
+ })?;
+ fs_table.set("is_dir", is_dir_fn)?;
+
+ // jarvis.fs.list(path)
+ let cmd_path_list = cmd_path.clone();
+ let list_fn = lua.create_function(move |lua, path: Option| {
+ let full_path = if let Some(p) = path {
+ resolve_path(&cmd_path_list, &p, sandbox_level)?
+ } else {
+ cmd_path_list.clone()
+ };
+
+ let result = lua.create_table()?;
+
+ let entries = fs::read_dir(&full_path)
+ .map_err(|e| mlua::Error::runtime(format!("List error: {}", e)))?;
+
+ let mut idx = 1;
+ for entry in entries {
+ if let Ok(entry) = entry {
+ let item = lua.create_table()?;
+ item.set("name", entry.file_name().to_string_lossy().to_string())?;
+ item.set("path", entry.path().to_string_lossy().to_string())?;
+ item.set("is_file", entry.path().is_file())?;
+ item.set("is_dir", entry.path().is_dir())?;
+
+ result.set(idx, item)?;
+ idx += 1;
+ }
+ }
+
+ Ok(result)
+ })?;
+ fs_table.set("list", list_fn)?;
+
+ // jarvis.fs.mkdir(path)
+ let cmd_path_mkdir = cmd_path.clone();
+ let mkdir_fn = lua.create_function(move |_, path: String| {
+ if !sandbox_level.allows_fs_write() {
+ return Err(mlua::Error::runtime("Write not allowed in this sandbox"));
+ }
+
+ let full_path = resolve_path(&cmd_path_mkdir, &path, sandbox_level)?;
+
+ fs::create_dir_all(&full_path)
+ .map_err(|e| mlua::Error::runtime(format!("Mkdir error: {}", e)))?;
+
+ Ok(true)
+ })?;
+ fs_table.set("mkdir", mkdir_fn)?;
+
+ // jarvis.fs.remove(path)
+ let cmd_path_remove = cmd_path.clone();
+ let remove_fn = lua.create_function(move |_, path: String| {
+ if !sandbox_level.allows_fs_write() {
+ return Err(mlua::Error::runtime("Write not allowed in this sandbox"));
+ }
+
+ let full_path = resolve_path(&cmd_path_remove, &path, sandbox_level)?;
+
+ if full_path.is_dir() {
+ fs::remove_dir_all(&full_path)
+ .map_err(|e| mlua::Error::runtime(format!("Remove error: {}", e)))?;
+ } else {
+ fs::remove_file(&full_path)
+ .map_err(|e| mlua::Error::runtime(format!("Remove error: {}", e)))?;
+ }
+
+ Ok(true)
+ })?;
+ fs_table.set("remove", remove_fn)?;
+
+ jarvis.set("fs", fs_table)?;
+
+ Ok(())
+}
+
+// Resolve path relative to command folder, with sandbox checks
+fn resolve_path(command_path: &PathBuf, path: &str, sandbox: SandboxLevel) -> mlua::Result {
+ let path = Path::new(path);
+
+ // if absolute path, check sandbox allows it
+ if path.is_absolute() {
+ if !sandbox.allows_expanded_paths() {
+ return Err(mlua::Error::runtime("Absolute paths not allowed in this sandbox"));
+ }
+ return Ok(path.to_path_buf());
+ }
+
+ // relative path - resolve against command folder
+ let resolved = command_path.join(path);
+
+ // canonicalize to resolve ../ etc and check it's still within command folder
+ let canonical = resolved.canonicalize()
+ .unwrap_or_else(|_| resolved.clone());
+
+ let cmd_canonical = command_path.canonicalize()
+ .unwrap_or_else(|_| command_path.clone());
+
+ if !sandbox.allows_expanded_paths() && !canonical.starts_with(&cmd_canonical) {
+ return Err(mlua::Error::runtime("Path escapes command folder"));
+ }
+
+ Ok(resolved)
+}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/api/http.rs b/crates/jarvis-core/src/lua/api/http.rs
new file mode 100644
index 0000000..30a1e5c
--- /dev/null
+++ b/crates/jarvis-core/src/lua/api/http.rs
@@ -0,0 +1,226 @@
+// HTTP Lua API: GET, POST, JSON requests
+
+use mlua::{Lua, Table, Value};
+use std::collections::HashMap;
+use std::time::Duration;
+
+pub fn register(lua: &Lua, jarvis: &Table) -> mlua::Result<()> {
+ let http = lua.create_table()?;
+
+ // jarvis.http.get(url, headers?)
+ let get_fn = lua.create_function(|lua, (url, headers): (String, Option