diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..0bb75f7
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.onnx filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
index ca439dc..992e6b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,55 +1,196 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
+# Created by .ignore support plugin (hsz.mobi)
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
-# Editor directories and files
-.vscode
-.vscode/*
-!.vscode/extensions.json
-.idea
-.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
+# Custom
+model_small/
-# Etc
-__other
-psd
-list.py
-tree.txt
-
-# Rust / Cargo 🔧
-# Ignore Cargo build output, generated files, and common Rust artifacts
-/target/
-**/target/
-/target/doc/
-
-# Binaries and dynamic libraries
-*.exe
-# *.dll
+# C extensions
*.so
-*.dylib
-# Static libraries
-*.a
-# *.lib
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
-# Rust compilation artifacts
-*.rlib
-*.rmeta
+# 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
-# Backup files and editor temp files
-*.rs.bk
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
-# Ignore packaged crates
-*.crate
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
-# Ignore AI models
-/resources/models/*
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# IPython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# dotenv
+.env
+
+# 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
+# Local secrets (real keys go here, not in dev.env.example)
+dev.env
+
+# Misc artifacts
+.git-filter-redactions.txt
+# Experiments / voice cloning artifacts (not part of repo)
+voice_ref/
+sound/_*.wav
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7f9be7c
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarvis.iml b/.idea/jarvis.iml
new file mode 100644
index 0000000..b55f7bb
--- /dev/null
+++ b/.idea/jarvis.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..b1d0f0b
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..fbd2bfc
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
deleted file mode 100644
index 9361469..0000000
--- a/Cargo.lock
+++ /dev/null
@@ -1,9582 +0,0 @@
-# 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
deleted file mode 100644
index 7820244..0000000
--- a/Cargo.toml
+++ /dev/null
@@ -1,55 +0,0 @@
-[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 4fae8d5..e72bfdd 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,437 +1,674 @@
-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
+ 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
diff --git a/README.md b/README.md
index 807de10..12abbd5 100644
--- a/README.md
+++ b/README.md
@@ -1,65 +1,163 @@
-# JARVIS Voice Assistant (this readme is outdated)
+# J.A.R.V.I.S (Python) — Bossiara13's fork
-
+Голосовой ассистент для Windows с wake-word **«jarvis»**. Распознаёт речь локально через Vosk (русская модель), отвечает голосом через Silero TTS, а свободные вопросы (фразы, начинающиеся со слова «скажи …») отправляет в LLM на Groq и зачитывает ответ.
-`Jarvis` - is a voice assistant made as an experiment using neural networks for things like **STT/TTS/Wake Word/NLU** etc.
+## Это форк
-The main project challenges we try to achieve is:
- - 100% offline *(no cloud)*
- - Open source *(full transparency)*
- - No data collection *(we respect your privacy)*
+Форк репозитория [Priler/jarvis](https://github.com/Priler/jarvis) (автор оригинала — Abraham Tugalov, 2022). Лицензия наследуется: **CC BY-NC-SA 4.0** (см. [LICENSE.txt](LICENSE.txt)).
-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/)**.
+## Что отличается от оригинала
-*Other libraries, tools and packages can be found in source code.*
+- Из истории удалены случайно закоммиченные секреты.
+- Бэкенд LLM переключён с OpenAI на Groq (бесплатный тариф) через openai-совместимый API.
+- Код обновлён под новую версию SDK `openai` (>=1.0); старый pre-1.0 интерфейс был сломан.
+- Убрана зависимость от Picovoice Porcupine — wake-word распознаётся напрямую через Vosk с grammar-constraint (второй `KaldiRecognizer` со словарём из `WAKE_WORDS`). Ключ Picovoice больше не нужен.
+- Обновлена ветка/тэги (`dev`, `v0.0.1-import`), причёсан README и `requirements.txt`.
-## Neural Networks
+## Установка
-This are the neural networks we are currently using:
+Требуется **Python 3.11** (на 3.13 ряд зависимостей пока ставится с бубном).
- - 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)
+```bash
+git clone https://github.com/DmitryBykov-ISPO/J.A.R.V.I.S-py.git
+cd J.A.R.V.I.S-py
+py -3.11 -m venv .venv
+.venv\Scripts\activate
+pip install --upgrade pip
+pip install -r requirements.txt
+```
-## Supported Languages
+Скопируйте `dev.env.example` в `dev.env` и заполните ключ:
-Currently, only Russian language is supported.
-But soon, Ukranian and English will be added for the interface, wake-word detection and speech recognition.
+- `GROQ_TOKEN` — бесплатно на [console.groq.com](https://console.groq.com/).
-## How to build?
+Vosk-модель для русского языка лежит в `model_small/` (уже в репозитории). Запуск:
-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`.
+```bash
+python main.py
+```
-
-*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
+- `config.py`:
+ - `MICROPHONE_INDEX = -1` — индекс микрофона (`-1` означает устройство по умолчанию). Если микрофонов несколько и берётся не тот, поменяйте число.
+ - `WAKE_WORDS = ('jarvis', 'джарвис')` — варианты транскрипции wake-word, которые принимает Vosk. Если ваш голос/акцент стабильно расшифровывается как, например, `жарвис` — добавьте этот вариант сюда (всё в нижнем регистре).
+ - `GROQ_MODEL` — имя модели Groq (по умолчанию `llama-3.3-70b-versatile`).
+ - `INTENT_SIMILARITY_THRESHOLD = 0.45` — минимальная косинусная близость для срабатывания команды (см. ниже).
+ - `DENOISE_ENABLED = False` — шумоподавление перед Vosk (см. ниже).
+- `commands.yaml` — набор голосовых команд и их вариантов; матчится семантически по эмбеддингам.
+- `custom-commands/` — скомпилированные AHK-скрипты под конкретный сетап автора оригинала (переключение мониторов, управление Яндекс.Музыкой и т.п.). На вашей машине большая часть из них работать не будет, но ассистент запустится в любом случае.
-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).
+1. Скажите **«Джарвис»** (или **«jarvis»**) — ассистент даст звуковой сигнал готовности.
+2. Произнесите команду — окно записи закрывается само, как только вы замолчите (см. ниже про VAD):
+ - либо одну из фраз из `commands.yaml` (открыть браузер, выключить звук и т.д.);
+ - либо начните со слова **«скажи …»** — остаток фразы уйдёт в Groq, ответ будет зачитан вслух.
-## License
+## VAD (определение конца команды)
-[Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)
-See LICENSE.txt file for more details.
+Окно записи команды закрывается по голосовой активности (webrtcvad), а не по таймеру. Параметры в `config.py`:
+
+- `VAD_AGGRESSIVENESS` (0..3) — насколько агрессивно VAD режет шум; по умолчанию 2.
+- `COMMAND_END_SILENCE_MS` — длительность тишины, после которой окно закрывается (по умолчанию 1200 мс).
+- `COMMAND_MIN_SPEECH_MS` — минимальная длительность речи, иначе тишина не считается концом (500 мс).
+- `COMMAND_MIN_LISTEN_MS` — минимальное время записи, чтобы не закрыться мгновенно (1000 мс).
+- `COMMAND_MAX_LISTEN_MS` — жёсткий потолок (15000 мс).
+
+## Семантический матчинг команд
+
+Раньше распознанная фраза сравнивалась с фразами из `commands.yaml` через `fuzzywuzzy.fuzz.ratio` — это срабатывало только на формальное совпадение букв. Теперь используется sentence-transformer `all-MiniLM-L6-v2` (ONNX, CPU) — фраза кодируется в 384-мерный вектор, считается косинусная близость с фразами всех команд, побеждает самая близкая.
+
+Это означает, что фраза `«запусти браузер»` сматчится с командой `open_browser`, даже если в YAML записано только `«открой браузер»` — у них близкий смысл.
+
+- Модель лежит в `models/all-MiniLM-L6-v2/` (87 МБ, хранится через Git LFS — `git lfs pull` после клонирования).
+- Порог `INTENT_SIMILARITY_THRESHOLD = 0.45` (косинус, диапазон [-1; 1], для близких смыслов обычно 0.7+). Ниже порога — «команда не распознана», сработает `not_found.wav` (или ветка `«скажи …»` для LLM).
+- Все фразы из `commands.yaml` эмбеддятся один раз при старте; на каждой команде эмбеддится только распознанная пользователем фраза (~1-5 мс на CPU).
+
+## Шумоподавление перед STT
+
+Опциональный пре-процессинг аудио командного буфера через `noisereduce` (спектральное гейтирование). Помогает в шумных помещениях (кулер, кондиционер, фоновая музыка). К wake-word фазе не применяется — там важна отзывчивость.
+
+В `config.py`:
+
+- `DENOISE_ENABLED = False` — главный тумблер.
+- `DENOISE_PROP = 0.85` — доля подавления (0..1).
+- `DENOISE_STATIONARY = False` — `False` для меняющегося шума (рекомендуется), `True` для стабильного фона.
+
+## Эффекты под AI-ассистента
+
+Silero звучит естественно, но иногда хочется лёгкой «AI-окраски» в духе J.A.R.V.I.S. — узкую полосу, еле заметную реверберацию, при желании небольшой сдвиг высоты. Это тонкий постпроцессинг, голос и sample rate не меняются.
+
+По умолчанию эффекты **выключены**. Включаются в `config.py`:
+
+- `TTS_EFFECTS_ENABLED` — главный тумблер (`False` по умолчанию).
+- `TTS_BANDPASS_LOW_HZ` / `TTS_BANDPASS_HIGH_HZ` — полоса пропускания (по умолчанию 200–7000 Гц). Сужение полосы даёт ощущение «голоса по радио».
+- `TTS_REVERB_WET` (0..1) и `TTS_REVERB_DECAY_MS` — доля мокрого сигнала и длительность хвоста короткой реверберации (по умолчанию 0.20 и 100 мс).
+- `TTS_PITCH_SEMITONES` — сдвиг высоты в полутонах (по умолчанию 0, разумный диапазон ±2).
+
+Фильтры реализованы на `scipy.signal` (IIR, sosfiltfilt — нулевая фазовая задержка), реверберация — несколько затухающих задержанных копий, pitch — ресэмплинг по линейной интерполяции. Всё in-process, без временных файлов и лишних зависимостей.
+
+Быстрое A/B сравнение перед тем как переключать тумблер:
+
+```bash
+.venv\Scripts\python.exe utils\preview_effects.py
+```
+
+Скрипт сгенерирует `sound/_preview_silero_raw.wav` и `sound/_preview_silero_fx.wav` — откройте оба в плеере и решите, нравится ли эффект. Параметры fx-версии берутся из текущего `config.py` (независимо от `TTS_EFFECTS_ENABLED`).
+
+## Схема commands.yaml
+
+Каждая команда — это пара `phrases` + `action`:
+
+```yaml
+open_browser:
+ phrases:
+ - открой браузер
+ - запусти браузер
+ action: {type: exe, file: "Run browser.exe"}
+```
+
+Типы действий:
+
+- `exe` — запустить `.exe` из `custom-commands/`. Поля: `file` (имя файла), необязательные `wait: true` (ждать завершения), `delay_ms` (пауза после запуска). По умолчанию после `exe` проигрывается звук подтверждения `ok` — отключается через `confirm_sound: null`.
+- `play_sound` — проиграть WAV из `sound/`. Поле `name` (без расширения). Имена `greet` и `ok` проигрываются с рандомным выбором из вариантов `greet1/2/3.wav`, `ok1/2/3.wav`.
+- `system` — встроенные операции. `op: volume_mute`, `volume_unmute`, `exit`.
+- `multi` — последовательность шагов: `steps: [...]`.
+
+Шаги внутри `multi` — тех же типов. Пример:
+
+```yaml
+sound_off:
+ phrases: [выключи звук, отключи звук]
+ action:
+ type: multi
+ steps:
+ - {type: play_sound, name: ok}
+ - {type: system, op: volume_mute}
+```
+
+## Конструктор команд
+
+GUI для добавления команд в `commands.yaml` без ручной правки YAML.
+
+Запуск:
+
+```
+python -m tools.command_builder
+```
+
+Или из корня репо `run_builder.bat` (под Windows). Открывается окно pywebview с пошаговым мастером: фразы → тип действия → параметры → предпросмотр YAML и сохранение. На шаге «Действие» доступна вкладка «Подсказать (LLM)» — модель Groq предложит готовый блок по описанию на русском.
+
+После сохранения команды Jarvis нужно перезапустить, чтобы она подхватилась.
+
+Требования:
+
+- Windows 10/11 с установленным WebView2 Runtime (обычно уже стоит вместе с Edge — иначе [скачать у Microsoft](https://developer.microsoft.com/microsoft-edge/webview2/)).
+- Зависимости из `requirements.txt` (включая `pywebview`, `ruamel.yaml`, `pyautogui`).
+
+Подробности для разработчиков — [tools/command_builder/README.md](tools/command_builder/README.md). TODO: добавить скриншот окна.
+
+## Лицензия
+
+CC BY-NC-SA 4.0 — см. [LICENSE.txt](LICENSE.txt). Авторство оригинала — Abraham Tugalov / Priler. Изменения в этом форке — Bossiara13 (Dmitry Bykov), 2026.
diff --git a/commands.yaml b/commands.yaml
new file mode 100644
index 0000000..a80d0e3
--- /dev/null
+++ b/commands.yaml
@@ -0,0 +1,239 @@
+open_browser:
+ phrases:
+ - открой браузер
+ - запусти браузер
+ - открой яндекс
+ - яндекс браузер
+ action: {type: url, href: "about:blank", browser: "yandex"}
+
+open_youtube:
+ phrases:
+ - открой ютуб
+ - ютуб
+ - запусти ютуб
+ action: {type: url, href: "https://www.youtube.com", browser: "yandex"}
+
+open_google:
+ phrases:
+ - открой гугл
+ - гугл
+ - запусти гугл
+ action: {type: url, href: "https://www.google.com", browser: "yandex"}
+
+music:
+ phrases:
+ - давай музыку
+ - музыка
+ - яндекс музыка
+ - включи музыку
+ - открой музыку
+ - послушаем музыку
+ - хочу музыку
+ - врубай музло
+ - включи что-то послушать
+ - давай что-то послушаем
+ action: {type: exe, file: "Run music.exe"}
+
+music_off:
+ phrases:
+ - выключи музыку
+ - отруби музыку
+ - убери музыку
+ - отключи музыку
+ - закрой музыку
+ - останови музыку
+ - хватит музыки
+ action: {type: exe, file: "Stop music.exe", delay_ms: 200}
+
+music_save:
+ phrases:
+ - сохрани трек
+ - мне нравится трек
+ - сохрани песню
+ - мне нравится песня
+ - добавь трек в избранное
+ - добавь песню в избранное
+ - запомни мелодию
+ - запомни трек
+ - запомни песню
+ - добавь мелодию в избранное
+ - сохрани то что сейчас играет
+ - добавь то что сейчас играет в избранное
+ - лайкни трек
+ - лайкни мелодию
+ - лайкни песню
+ action: {type: exe, file: "Save music.exe", delay_ms: 200}
+
+music_next:
+ phrases:
+ - следующий трек
+ - следующая мелодия
+ - следующая песня
+ - переключи трек
+ - переключи музыку
+ - переключи мелодию
+ - переключи песню
+ - следующая музыка
+ action: {type: exe, file: "Next music.exe", delay_ms: 200}
+
+music_prev:
+ phrases:
+ - предыдущая музыка
+ - предыдущий трек
+ - предыдущая мелодия
+ - предыдущая песня
+ - верни трек
+ - верни мелодию
+ - верни песню
+ - верни прошлый трек
+ - верни прошлую мелодию
+ - верни прошлую песню
+ - верни прошлую музыку
+ - верни то что играло
+ - давай предыдущий трек
+ - переключи музыку обратно
+ - переключи на прошлый трек
+ - переключи на прошлую мелодию
+ - переключи на прошлую песню
+ action: {type: exe, file: "Prev music.exe", delay_ms: 200}
+
+sound_off:
+ phrases:
+ - выключи звук
+ - беззвучный режим
+ - режим без звука
+ - отключи звук
+ action:
+ type: multi
+ steps:
+ - {type: play_sound, name: ok}
+ - {type: system, op: volume_mute}
+
+sound_on:
+ phrases:
+ - включи звук
+ - режим со звуком
+ - верни звук
+ action:
+ type: multi
+ steps:
+ - {type: system, op: volume_unmute}
+ - {type: play_sound, name: ok}
+
+thanks:
+ phrases:
+ - спасибо
+ - молодец
+ - респект
+ - ты супер
+ - отличная работа
+ - ты крут
+ - ты большой молодец
+ - ты реально крут
+ - ты афигенный
+ action: {type: play_sound, name: thanks}
+
+stupid:
+ phrases:
+ - ты дурак
+ - ты дебил
+ - ты глупый
+ - ты тупой
+ action: {type: play_sound, name: stupid}
+
+gaming_mode_on:
+ phrases:
+ - включи игровой режим
+ - перейди в игровой режим
+ - я хочу поиграть
+ - переключись на телевизор
+ action:
+ type: multi
+ steps:
+ - {type: play_sound, name: ok}
+ - {type: exe, file: "Switch to gaming mode.exe", wait: true}
+ - {type: play_sound, name: ready}
+
+gaming_mode_off:
+ phrases:
+ - рабочий режим
+ - вернись в рабочий режим
+ - переключись на компьютер
+ - обратно на компьютер
+ - отключи игровой режим
+ - выйди из игрового режима
+ - вернись на компьютер
+ - выход с игрового режима
+ - рабочее пространство
+ - вернись в игровой режим
+ - выключи игровой режим
+ action:
+ type: multi
+ steps:
+ - {type: play_sound, name: ok}
+ - {type: exe, file: "Switch back to workspace.exe", wait: true}
+ - {type: play_sound, name: ready}
+
+switch_to_headphones:
+ phrases:
+ - переключи на наушники
+ - включи наушники
+ - перейди на наушники
+ - давай звук в наушники
+ - давай звук на наушники
+ - переключи звук в наушники
+ - переключи звук на наушники
+ - переключи на кракены
+ - включи кракены
+ - перейди на кракены
+ - давай звук в кракены
+ - давай звук на кракены
+ - переключи звук в кракены
+ - переключи звук на кракены
+ action:
+ type: multi
+ steps:
+ - {type: play_sound, name: ok}
+ - {type: exe, file: "Switch to headphones.exe", wait: true, delay_ms: 500}
+ - {type: play_sound, name: ready}
+
+switch_to_dynamics:
+ phrases:
+ - переключи на динамики
+ - включи динамики
+ - перейди на динамики
+ - давай звук в динамики
+ - давай звук на динамики
+ - переключи звук в динамики
+ - переключи звук на динамики
+ - переключи на колонки
+ - включи колонки
+ - перейди на колонки
+ - давай звук в колонки
+ - давай звук на колонки
+ - переключи звук в колонки
+ - переключи звук на колонки
+ action:
+ type: multi
+ steps:
+ - {type: play_sound, name: ok}
+ - {type: exe, file: "Switch to dynamics.exe", wait: true, delay_ms: 500}
+ - {type: play_sound, name: ready}
+
+'off':
+ phrases:
+ - выключись
+ - вырубись
+ - завершить работу
+ - закройся
+ - отключись
+ - заверши свою работу
+ - на сегодня хватит
+ - выгрузи себя из памяти
+ - ты мне надоел
+ - пора спать
+ action:
+ type: multi
+ steps:
+ - {type: play_sound, name: "off"}
+ - {type: system, op: exit}
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..b78f82c
--- /dev/null
+++ b/config.py
@@ -0,0 +1,60 @@
+from dotenv import load_dotenv
+import os
+
+# Find .env file with os variables
+load_dotenv("dev.env")
+
+# Конфигурация
+VA_NAME = 'Jarvis'
+VA_VER = "0.4.1"
+VA_ALIAS = ('джарвис',)
+VA_TBR = ('скажи', 'покажи', 'ответь', 'произнеси', 'расскажи', 'сколько', 'слушай')
+
+# Wake-words распознаются Vosk'ом с grammar-constraint. Добавляйте сюда
+# варианты написания — Vosk с русской моделью может расшифровать "jarvis"
+# как "джарвис", "жарвис" и т.п.
+WAKE_WORDS = ('jarvis', 'джарвис')
+
+# ID микрофона (можете просто менять ID пока при запуске не отобразится нужный)
+# -1 это стандартное записывающее устройство
+MICROPHONE_INDEX = -1
+
+BROWSER_PATHS = {
+ 'yandex': 'C:/Program Files/Yandex/YandexBrowser/Application/browser.exe',
+ 'chrome': 'C:/Program Files/Google/Chrome/Application/chrome.exe',
+ 'firefox': 'C:/Program Files/Mozilla Firefox/firefox.exe',
+ 'edge': 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
+}
+
+# Токен Groq
+GROQ_TOKEN = os.getenv('GROQ_TOKEN')
+GROQ_BASE_URL = "https://api.groq.com/openai/v1"
+GROQ_MODEL = "llama-3.3-70b-versatile"
+
+# VAD (webrtcvad) — определяет конец команды по тишине вместо фиксированных 10 сек.
+# 0..3, выше — агрессивнее режет шум (но и обрезает речь).
+VAD_AGGRESSIVENESS = 2
+COMMAND_END_SILENCE_MS = 1200
+COMMAND_MIN_SPEECH_MS = 500
+COMMAND_MIN_LISTEN_MS = 1000
+COMMAND_MAX_LISTEN_MS = 15000
+
+TTS_EFFECTS_ENABLED = False
+TTS_BANDPASS_LOW_HZ = 200
+TTS_BANDPASS_HIGH_HZ = 7000
+TTS_REVERB_WET = 0.20
+TTS_REVERB_DECAY_MS = 100
+TTS_PITCH_SEMITONES = 0
+
+# Семантический матчинг команд через эмбеддинги MiniLM-L6-v2 (ONNX, CPU).
+# Порог — косинусная близость [0..1]. На вход поступает уже отфильтрованная
+# фраза (без алиасов и vа_tbr); 0.45 эмпирически отделяет валидные команды
+# от шума, оставляя запас на разговорные перефразировки.
+INTENT_SIMILARITY_THRESHOLD = 0.45
+
+# Шумоподавление аудио перед Vosk на этапе захвата команды (после wake-word).
+# Спектральное гейтирование через noisereduce; даёт небольшую латентность,
+# поэтому выключено по умолчанию и применяется только к буферу команды.
+DENOISE_ENABLED = False
+DENOISE_PROP = 0.85
+DENOISE_STATIONARY = False
diff --git a/crates/jarvis-app/.cargo/config.toml b/crates/jarvis-app/.cargo/config.toml
deleted file mode 100644
index 0a3dde6..0000000
--- a/crates/jarvis-app/.cargo/config.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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
deleted file mode 100644
index 78353b5..0000000
--- a/crates/jarvis-app/Cargo.toml
+++ /dev/null
@@ -1,32 +0,0 @@
-[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
deleted file mode 100644
index 9946861..0000000
--- a/crates/jarvis-app/Makefile.toml
+++ /dev/null
@@ -1,45 +0,0 @@
-[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
deleted file mode 100644
index 30a6d3f..0000000
--- a/crates/jarvis-app/build.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-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
deleted file mode 100644
index e29e8d1..0000000
--- a/crates/jarvis-app/src/_app.rs
+++ /dev/null
@@ -1,337 +0,0 @@
-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
deleted file mode 100644
index 6e716df..0000000
--- a/crates/jarvis-app/src/_main.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-
-
-#[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
deleted file mode 100644
index cbc900f..0000000
--- a/crates/jarvis-app/src/app.rs
+++ /dev/null
@@ -1,418 +0,0 @@
-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
deleted file mode 100644
index 81df0e4..0000000
--- a/crates/jarvis-app/src/log.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-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
deleted file mode 100644
index 7a40397..0000000
--- a/crates/jarvis-app/src/main.rs
+++ /dev/null
@@ -1,173 +0,0 @@
-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
deleted file mode 100644
index 499d278..0000000
--- a/crates/jarvis-app/src/tray.rs
+++ /dev/null
@@ -1,226 +0,0 @@
-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
deleted file mode 100644
index fe023a0..0000000
--- a/crates/jarvis-app/src/tray/menu.rs
+++ /dev/null
@@ -1,182 +0,0 @@
-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
deleted file mode 100644
index 64644a9..0000000
--- a/crates/jarvis-cli/Cargo.toml
+++ /dev/null
@@ -1,14 +0,0 @@
-[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
deleted file mode 100644
index d161b5d..0000000
--- a/crates/jarvis-cli/src/main.rs
+++ /dev/null
@@ -1,203 +0,0 @@
-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
deleted file mode 100644
index 0033a9f..0000000
--- a/crates/jarvis-core/Cargo.toml
+++ /dev/null
@@ -1,64 +0,0 @@
-[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
deleted file mode 100644
index da9302e..0000000
--- a/crates/jarvis-core/src/audio.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-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
deleted file mode 100644
index 10f927c..0000000
--- a/crates/jarvis-core/src/audio/kira.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-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
deleted file mode 100644
index 340a85c..0000000
--- a/crates/jarvis-core/src/audio/rodio.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- 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
deleted file mode 100644
index ad8f915..0000000
--- a/crates/jarvis-core/src/audio_buffer.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-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
deleted file mode 100644
index 52c09ef..0000000
--- a/crates/jarvis-core/src/audio_processing.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-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
deleted file mode 100644
index 2278ca8..0000000
--- a/crates/jarvis-core/src/audio_processing/gain_normalizer.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-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
deleted file mode 100644
index b58da1f..0000000
--- a/crates/jarvis-core/src/audio_processing/gain_normalizer/simple.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-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
deleted file mode 100644
index a5ca879..0000000
--- a/crates/jarvis-core/src/audio_processing/noise_suppression.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-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
deleted file mode 100644
index 87174f9..0000000
--- a/crates/jarvis-core/src/audio_processing/noise_suppression/none.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-// 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
deleted file mode 100644
index a2b57c5..0000000
--- a/crates/jarvis-core/src/audio_processing/vad.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-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
deleted file mode 100644
index 9fd1011..0000000
--- a/crates/jarvis-core/src/audio_processing/vad/energy.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-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
deleted file mode 100644
index f4567c5..0000000
--- a/crates/jarvis-core/src/audio_processing/vad/none.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-// 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
deleted file mode 100644
index c250cd0..0000000
--- a/crates/jarvis-core/src/commands.rs
+++ /dev/null
@@ -1,319 +0,0 @@
-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
deleted file mode 100644
index 18a182d..0000000
--- a/crates/jarvis-core/src/commands/structs.rs
+++ /dev/null
@@ -1,179 +0,0 @@
-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
deleted file mode 100644
index d363366..0000000
--- a/crates/jarvis-core/src/config.rs
+++ /dev/null
@@ -1,259 +0,0 @@
-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
deleted file mode 100644
index 0dd7189..0000000
--- a/crates/jarvis-core/src/config/structs.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-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
deleted file mode 100644
index a218679..0000000
--- a/crates/jarvis-core/src/db.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-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
deleted file mode 100644
index 02a37dd..0000000
--- a/crates/jarvis-core/src/db/manager.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-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
deleted file mode 100644
index dd63b0d..0000000
--- a/crates/jarvis-core/src/db/structs.rs
+++ /dev/null
@@ -1,184 +0,0 @@
-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
deleted file mode 100644
index 24f7576..0000000
--- a/crates/jarvis-core/src/i18n.rs
+++ /dev/null
@@ -1,202 +0,0 @@
-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
deleted file mode 100644
index 49161b3..0000000
--- a/crates/jarvis-core/src/i18n/locales/en.ftl
+++ /dev/null
@@ -1,143 +0,0 @@
-# ### 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
deleted file mode 100644
index 82f55bc..0000000
--- a/crates/jarvis-core/src/i18n/locales/ru.ftl
+++ /dev/null
@@ -1,143 +0,0 @@
-# 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
deleted file mode 100644
index dcafc8a..0000000
--- a/crates/jarvis-core/src/i18n/locales/ua.ftl
+++ /dev/null
@@ -1,143 +0,0 @@
-# ### 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
deleted file mode 100644
index 6306b01..0000000
--- a/crates/jarvis-core/src/intent.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-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
deleted file mode 100644
index d9f8e08..0000000
--- a/crates/jarvis-core/src/intent/embeddingclassifier.rs
+++ /dev/null
@@ -1,195 +0,0 @@
-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
deleted file mode 100644
index 1e490fe..0000000
--- a/crates/jarvis-core/src/intent/intentclassifier.rs
+++ /dev/null
@@ -1,96 +0,0 @@
-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
deleted file mode 100644
index 1768c1d..0000000
--- a/crates/jarvis-core/src/ipc.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-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
deleted file mode 100644
index e372d40..0000000
--- a/crates/jarvis-core/src/ipc/events.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-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
deleted file mode 100644
index 6e9072c..0000000
--- a/crates/jarvis-core/src/ipc/server.rs
+++ /dev/null
@@ -1,198 +0,0 @@
-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
deleted file mode 100644
index 41f8c04..0000000
--- a/crates/jarvis-core/src/lib.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-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
deleted file mode 100644
index e038a85..0000000
--- a/crates/jarvis-core/src/listener.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-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
deleted file mode 100644
index 1ebb3f0..0000000
--- a/crates/jarvis-core/src/listener/porcupine.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-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
deleted file mode 100644
index f0ca5b2..0000000
--- a/crates/jarvis-core/src/listener/rustpotter.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-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
deleted file mode 100644
index 22160aa..0000000
--- a/crates/jarvis-core/src/listener/vosk.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-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
deleted file mode 100644
index 9653867..0000000
--- a/crates/jarvis-core/src/lua.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-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
deleted file mode 100644
index 8bda5a4..0000000
--- a/crates/jarvis-core/src/lua/api.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-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
deleted file mode 100644
index 7c2112b..0000000
--- a/crates/jarvis-core/src/lua/api/_new_api_module_example.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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
deleted file mode 100644
index 706f4df..0000000
--- a/crates/jarvis-core/src/lua/api/audio.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-// 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
deleted file mode 100644
index 2d75b8f..0000000
--- a/crates/jarvis-core/src/lua/api/context.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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
deleted file mode 100644
index fa05bae..0000000
--- a/crates/jarvis-core/src/lua/api/core.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-// 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
deleted file mode 100644
index 23e002e..0000000
--- a/crates/jarvis-core/src/lua/api/fs.rs
+++ /dev/null
@@ -1,212 +0,0 @@
-// 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
deleted file mode 100644
index 30a1e5c..0000000
--- a/crates/jarvis-core/src/lua/api/http.rs
+++ /dev/null
@@ -1,226 +0,0 @@
-// 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)| {
- http_request(lua, "GET", &url, None, headers)
- })?;
- http.set("get", get_fn)?;
-
- // jarvis.http.post(url, body, headers?)
- let post_fn = lua.create_function(|lua, (url, body, headers): (String, Option, Option)| {
- http_request(lua, "POST", &url, body, headers)
- })?;
- http.set("post", post_fn)?;
-
- // jarvis.http.post_json(url, data, headers?)
- let post_json_fn = lua.create_function(|lua, (url, data, headers): (String, Table, Option)| {
- // convert Lua table to JSON string
- let json_value = table_to_json(lua, data)?;
- let body = serde_json::to_string(&json_value)
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- // add content-type header
- let mut header_map: HashMap = HashMap::new();
- header_map.insert("Content-Type".to_string(), "application/json".to_string());
-
- if let Some(h) = headers {
- for pair in h.pairs::() {
- if let Ok((k, v)) = pair {
- header_map.insert(k, v);
- }
- }
- }
-
- http_request_with_headers(lua, "POST", &url, Some(body), header_map)
- })?;
- http.set("post_json", post_json_fn)?;
-
- // jarvis.http.json(url) - GET + parse JSON
- let json_fn = lua.create_function(|lua, url: String| {
- let client = reqwest::blocking::Client::builder()
- .timeout(Duration::from_secs(30))
- .build()
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- let response = client.get(&url)
- .send()
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- if response.status().is_success() {
- let json: serde_json::Value = response.json()
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
- json_to_lua(lua, json)
- } else {
- Ok(Value::Nil)
- }
- })?;
- http.set("json", json_fn)?;
-
- jarvis.set("http", http)?;
-
- Ok(())
-}
-
-fn http_request(
- lua: &Lua,
- method: &str,
- url: &str,
- body: Option,
- headers: Option,
-) -> mlua::Result {
- let header_map: HashMap = if let Some(h) = headers {
- h.pairs::()
- .filter_map(|r| r.ok())
- .collect()
- } else {
- HashMap::new()
- };
-
- http_request_with_headers(lua, method, url, body, header_map)
-}
-
-fn http_request_with_headers(
- lua: &Lua,
- method: &str,
- url: &str,
- body: Option,
- headers: HashMap,
-) -> mlua::Result {
- let client = reqwest::blocking::Client::builder()
- .timeout(Duration::from_secs(30))
- .build()
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- let mut request = match method {
- "POST" => client.post(url),
- "PUT" => client.put(url),
- "DELETE" => client.delete(url),
- _ => client.get(url),
- };
-
- for (k, v) in headers {
- request = request.header(&k, &v);
- }
-
- if let Some(b) = body {
- request = request.body(b);
- }
-
- let result = lua.create_table()?;
-
- match request.send() {
- Ok(response) => {
- result.set("ok", response.status().is_success())?;
- result.set("status", response.status().as_u16())?;
-
- // get headers
- let headers_table = lua.create_table()?;
- for (name, value) in response.headers() {
- if let Ok(v) = value.to_str() {
- headers_table.set(name.as_str(), v)?;
- }
- }
- result.set("headers", headers_table)?;
-
- // get body
- match response.text() {
- Ok(text) => result.set("body", text)?,
- Err(e) => result.set("body", format!("Error reading body: {}", e))?,
- }
- }
- Err(e) => {
- result.set("ok", false)?;
- result.set("status", 0)?;
- result.set("error", e.to_string())?;
- result.set("body", "")?;
- }
- }
-
- Ok(result)
-}
-
-// Convert Lua table to serde_json::Value
-fn table_to_json(lua: &Lua, table: Table) -> mlua::Result {
- use serde_json::{Value as JsonValue, Map};
-
- // check if it's an array (sequential integer keys starting from 1)
- let is_array = table.clone().pairs::()
- .filter_map(|r| r.ok())
- .enumerate()
- .all(|(i, (k, _))| k == (i + 1) as i64);
-
- if is_array && table.len()? > 0 {
- let arr: Vec = table.sequence_values::()
- .filter_map(|r| r.ok())
- .map(|v| lua_to_json(lua, v))
- .collect::, _>>()?;
- Ok(JsonValue::Array(arr))
- } else {
- let mut map = Map::new();
- for pair in table.pairs::() {
- let (k, v) = pair?;
- map.insert(k, lua_to_json(lua, v)?);
- }
- Ok(JsonValue::Object(map))
- }
-}
-
-// Convert Lua Value to serde_json::Value
-fn lua_to_json(lua: &Lua, value: Value) -> mlua::Result {
- use serde_json::{Value as JsonValue, Number};
-
- match value {
- Value::Nil => Ok(JsonValue::Null),
- Value::Boolean(b) => Ok(JsonValue::Bool(b)),
- Value::Integer(i) => Ok(JsonValue::Number(Number::from(i))),
- Value::Number(n) => {
- Number::from_f64(n)
- .map(JsonValue::Number)
- .ok_or_else(|| mlua::Error::runtime("Invalid float"))
- }
- Value::String(s) => Ok(JsonValue::String(s.to_str()?.to_string())),
- Value::Table(t) => table_to_json(lua, t),
- _ => Err(mlua::Error::runtime("Unsupported type for JSON")),
- }
-}
-
-// Convert serde_json::Value to Lua Value
-fn json_to_lua(lua: &Lua, json: serde_json::Value) -> mlua::Result {
- use serde_json::Value as JsonValue;
-
- match json {
- JsonValue::Null => Ok(Value::Nil),
- JsonValue::Bool(b) => Ok(Value::Boolean(b)),
- JsonValue::Number(n) => {
- if let Some(i) = n.as_i64() {
- Ok(Value::Integer(i))
- } else if let Some(f) = n.as_f64() {
- Ok(Value::Number(f))
- } else {
- Ok(Value::Nil)
- }
- }
- JsonValue::String(s) => Ok(Value::String(lua.create_string(&s)?)),
- JsonValue::Array(arr) => {
- let table = lua.create_table()?;
- for (i, v) in arr.into_iter().enumerate() {
- table.set(i + 1, json_to_lua(lua, v)?)?;
- }
- Ok(Value::Table(table))
- }
- JsonValue::Object(map) => {
- let table = lua.create_table()?;
- for (k, v) in map {
- table.set(k, json_to_lua(lua, v)?)?;
- }
- Ok(Value::Table(table))
- }
- }
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/api/state.rs b/crates/jarvis-core/src/lua/api/state.rs
deleted file mode 100644
index 7916b7d..0000000
--- a/crates/jarvis-core/src/lua/api/state.rs
+++ /dev/null
@@ -1,184 +0,0 @@
-// State Lua API: persistent key-value storage per command
-
-use mlua::{Lua, Table, Result, Value};
-use std::path::PathBuf;
-use std::fs;
-use std::collections::HashMap;
-
-const STATE_FILE: &str = ".state.json";
-
-pub fn register(lua: &Lua, jarvis: &Table, command_path: &PathBuf) -> mlua::Result<()> {
- let state = lua.create_table()?;
- let state_path = command_path.join(STATE_FILE);
-
- // jarvis.state.get(key)
- let state_path_get = state_path.clone();
- let get_fn = lua.create_function(move |lua, key: String| {
- let data = load_state(&state_path_get);
-
- if let Some(value) = data.get(&key) {
- json_to_lua_value(lua, value.clone())
- } else {
- Ok(Value::Nil)
- }
- })?;
- state.set("get", get_fn)?;
-
- // jarvis.state.set(key, value)
- let state_path_set = state_path.clone();
- let set_fn = lua.create_function(move |_, (key, value): (String, Value)| {
- let mut data = load_state(&state_path_set);
-
- let json_value = lua_to_json_value(value)?;
- data.insert(key, json_value);
-
- save_state(&state_path_set, &data)?;
- Ok(true)
- })?;
- state.set("set", set_fn)?;
-
- // jarvis.state.delete(key)
- let state_path_delete = state_path.clone();
- let delete_fn = lua.create_function(move |_, key: String| {
- let mut data = load_state(&state_path_delete);
- let existed = data.remove(&key).is_some();
- save_state(&state_path_delete, &data)?;
- Ok(existed)
- })?;
- state.set("delete", delete_fn)?;
-
- // jarvis.state.clear()
- let state_path_clear = state_path.clone();
- let clear_fn = lua.create_function(move |_, ()| {
- let data: HashMap = HashMap::new();
- save_state(&state_path_clear, &data)?;
- Ok(true)
- })?;
- state.set("clear", clear_fn)?;
-
- // jarvis.state.keys()
- let state_path_keys = state_path.clone();
- let keys_fn = lua.create_function(move |lua, ()| {
- let data = load_state(&state_path_keys);
- let table = lua.create_table()?;
-
- for (i, key) in data.keys().enumerate() {
- table.set(i + 1, key.clone())?;
- }
-
- Ok(table)
- })?;
- state.set("keys", keys_fn)?;
-
- // jarvis.state.all()
- let state_path_all = state_path.clone();
- let all_fn = lua.create_function(move |lua, ()| {
- let data = load_state(&state_path_all);
- let table = lua.create_table()?;
-
- for (key, value) in data {
- let lua_value = json_to_lua_value(lua, value)?;
- table.set(key, lua_value)?;
- }
-
- Ok(table)
- })?;
- state.set("all", all_fn)?;
-
- jarvis.set("state", state)?;
-
- Ok(())
-}
-
-fn load_state(path: &PathBuf) -> HashMap {
- if !path.exists() {
- return HashMap::new();
- }
-
- fs::read_to_string(path)
- .ok()
- .and_then(|s| serde_json::from_str(&s).ok())
- .unwrap_or_default()
-}
-
-fn save_state(path: &PathBuf, data: &HashMap) -> mlua::Result<()> {
- let json = serde_json::to_string_pretty(data)
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- fs::write(path, json)
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- Ok(())
-}
-
-fn lua_to_json_value(value: Value) -> mlua::Result {
- use serde_json::Value as JsonValue;
-
- match value {
- Value::Nil => Ok(JsonValue::Null),
- Value::Boolean(b) => Ok(JsonValue::Bool(b)),
- Value::Integer(i) => Ok(JsonValue::Number(i.into())),
- Value::Number(n) => {
- serde_json::Number::from_f64(n)
- .map(JsonValue::Number)
- .ok_or_else(|| mlua::Error::runtime("Invalid float"))
- }
- Value::String(s) => Ok(JsonValue::String(s.to_str()?.to_string())),
- Value::Table(t) => {
- // check if array
- let is_array = t.clone().pairs::()
- .filter_map(|r| r.ok())
- .enumerate()
- .all(|(i, (k, _))| k == (i + 1) as i64);
-
- if is_array && t.len().unwrap_or(0) > 0 {
- let arr: Vec = t.sequence_values::()
- .filter_map(|r| r.ok())
- .map(lua_to_json_value)
- .collect::>>()?;
- Ok(JsonValue::Array(arr))
- } else {
- let mut map = serde_json::Map::new();
- for pair in t.pairs::() {
- let (k, v) = pair?;
- map.insert(k, lua_to_json_value(v)?);
- }
- Ok(JsonValue::Object(map))
- }
- }
- _ => Err(mlua::Error::runtime("Unsupported type for state")),
- }
-}
-
-fn json_to_lua_value(lua: &Lua, json: serde_json::Value) -> mlua::Result {
- use serde_json::Value as JsonValue;
-
- match json {
- JsonValue::Null => Ok(Value::Nil),
- JsonValue::Bool(b) => Ok(Value::Boolean(b)),
- JsonValue::Number(n) => {
- if let Some(i) = n.as_i64() {
- Ok(Value::Integer(i))
- } else if let Some(f) = n.as_f64() {
- Ok(Value::Number(f))
- } else {
- Ok(Value::Nil)
- }
- }
- JsonValue::String(s) => Ok(Value::String(lua.create_string(&s)?)),
- JsonValue::Array(arr) => {
- let table = lua.create_table()?;
- for (i, v) in arr.into_iter().enumerate() {
- table.set(i + 1, json_to_lua_value(lua, v)?)?;
- }
- Ok(Value::Table(table))
- }
- JsonValue::Object(map) => {
- let table = lua.create_table()?;
- for (k, v) in map {
- table.set(k, json_to_lua_value(lua, v)?)?;
- }
- Ok(Value::Table(table))
- }
- }
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/api/system.rs b/crates/jarvis-core/src/lua/api/system.rs
deleted file mode 100644
index 2722db8..0000000
--- a/crates/jarvis-core/src/lua/api/system.rs
+++ /dev/null
@@ -1,240 +0,0 @@
-// System Lua API: exec, open, clipboard, notify
-
-use mlua::{Lua, Table};
-use std::process::Command;
-
-use crate::lua::sandbox::SandboxLevel;
-
-pub fn register(lua: &Lua, jarvis: &Table, sandbox: SandboxLevel) -> mlua::Result<()> {
- let system = lua.create_table()?;
-
- // jarvis.system.open(url_or_path) - always available
- let open_fn = lua.create_function(|_, target: String| {
- let result = if cfg!(target_os = "windows") {
- Command::new("cmd")
- .args(["/C", "start", "", &target])
- .spawn()
- } else if cfg!(target_os = "macos") {
- Command::new("open")
- .arg(&target)
- .spawn()
- } else {
- Command::new("xdg-open")
- .arg(&target)
- .spawn()
- };
-
- match result {
- Ok(_) => Ok(true),
- Err(e) => {
- log::warn!("[Lua] Failed to open {}: {}", target, e);
- Ok(false)
- }
- }
- })?;
- system.set("open", open_fn)?;
-
- // jarvis.system.exec(cmd, args?) - only in full sandbox
- if sandbox.allows_exec() {
- let exec_fn = lua.create_function(|lua, (cmd, args): (String, Option)| {
- let mut command = if cfg!(target_os = "windows") {
- let mut c = Command::new("cmd");
- c.args(["/C", &cmd]);
- c
- } else {
- let mut c = Command::new("sh");
- c.args(["-c", &cmd]);
- c
- };
-
- // add extra args if provided
- if let Some(args_table) = args {
- for pair in args_table.sequence_values::() {
- if let Ok(arg) = pair {
- command.arg(arg);
- }
- }
- }
-
- let output = command.output()
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- let result = lua.create_table()?;
- result.set("success", output.status.success())?;
- result.set("code", output.status.code().unwrap_or(-1))?;
- result.set("stdout", String::from_utf8_lossy(&output.stdout).to_string())?;
- result.set("stderr", String::from_utf8_lossy(&output.stderr).to_string())?;
-
- Ok(result)
- })?;
- system.set("exec", exec_fn)?;
- }
-
- // jarvis.system.notify(title, message) - always available
- let notify_fn = lua.create_function(|_, (title, message): (String, String)| {
- log::info!("[Lua] NOTIFY: {} - {}", title, message);
-
- // platform-specific notification
- #[cfg(target_os = "windows")]
- {
- use winrt_notification::{Toast, Duration as ToastDuration};
-
- if let Err(e) = Toast::new(Toast::POWERSHELL_APP_ID)
- .title(&title)
- .text1(&message)
- .duration(ToastDuration::Short)
- .show()
- {
- log::warn!("[Lua] Failed to show toast notification: {}", e);
- // fallback to msg.exe
- let _ = Command::new("msg")
- .args(["*", "/time:10", &format!("{}: {}", title, message)])
- .spawn();
- }
- }
-
- #[cfg(target_os = "linux")]
- {
- let _ = Command::new("notify-send")
- .args([&title, &message])
- .spawn();
- }
-
- #[cfg(target_os = "macos")]
- {
- let script = format!(
- r#"display notification "{}" with title "{}""#,
- message.replace("\"", "\\\""),
- title.replace("\"", "\\\"")
- );
- let _ = Command::new("osascript")
- .args(["-e", &script])
- .spawn();
- }
-
- Ok(true)
- })?;
- system.set("notify", notify_fn)?;
-
- // jarvis.system.clipboard - subtable
- let clipboard = lua.create_table()?;
-
- // jarvis.system.clipboard.get() - always available
- let clipboard_get_fn = lua.create_function(|_, ()| {
- #[cfg(target_os = "windows")]
- {
- let output = Command::new("powershell")
- .args(["-Command", "Get-Clipboard"])
- .output()
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
- }
-
- #[cfg(target_os = "linux")]
- {
- let output = Command::new("xclip")
- .args(["-selection", "clipboard", "-o"])
- .output()
- .or_else(|_| {
- Command::new("xsel")
- .args(["--clipboard", "--output"])
- .output()
- })
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- Ok(String::from_utf8_lossy(&output.stdout).to_string())
- }
-
- #[cfg(target_os = "macos")]
- {
- let output = Command::new("pbpaste")
- .output()
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- Ok(String::from_utf8_lossy(&output.stdout).to_string())
- }
-
- #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
- {
- Err(mlua::Error::runtime("Clipboard not supported on this platform"))
- }
- })?;
- clipboard.set("get", clipboard_get_fn)?;
-
- // jarvis.system.clipboard.set(text) - only in full sandbox
- if sandbox.allows_clipboard_write() {
- let clipboard_set_fn = lua.create_function(|_, text: String| {
- #[cfg(target_os = "windows")]
- {
- let script = format!("Set-Clipboard -Value '{}'", text.replace("'", "''"));
- Command::new("powershell")
- .args(["-Command", &script])
- .output()
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
- }
-
- #[cfg(target_os = "linux")]
- {
- use std::io::Write;
- let mut child = Command::new("xclip")
- .args(["-selection", "clipboard"])
- .stdin(std::process::Stdio::piped())
- .spawn()
- .or_else(|_| {
- Command::new("xsel")
- .args(["--clipboard", "--input"])
- .stdin(std::process::Stdio::piped())
- .spawn()
- })
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- if let Some(stdin) = child.stdin.as_mut() {
- stdin.write_all(text.as_bytes())
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
- }
- }
-
- #[cfg(target_os = "macos")]
- {
- use std::io::Write;
- let mut child = Command::new("pbcopy")
- .stdin(std::process::Stdio::piped())
- .spawn()
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
-
- if let Some(stdin) = child.stdin.as_mut() {
- stdin.write_all(text.as_bytes())
- .map_err(|e| mlua::Error::runtime(e.to_string()))?;
- }
- }
-
- Ok(true)
- })?;
- clipboard.set("set", clipboard_set_fn)?;
- }
-
- system.set("clipboard", clipboard)?;
-
- // jarvis.system.env(name) - get environment variable (always available)
- let env_fn = lua.create_function(|_, name: String| {
- Ok(std::env::var(&name).ok())
- })?;
- system.set("env", env_fn)?;
-
- // jarvis.system.platform - read-only string
- let platform = if cfg!(target_os = "windows") {
- "windows"
- } else if cfg!(target_os = "macos") {
- "macos"
- } else if cfg!(target_os = "linux") {
- "linux"
- } else {
- "unknown"
- };
- system.set("platform", platform)?;
-
- jarvis.set("system", system)?;
-
- Ok(())
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/engine.rs b/crates/jarvis-core/src/lua/engine.rs
deleted file mode 100644
index 71380e1..0000000
--- a/crates/jarvis-core/src/lua/engine.rs
+++ /dev/null
@@ -1,169 +0,0 @@
-use mlua::{Lua, Value, StdLib};
-use std::path::PathBuf;
-use std::time::Duration;
-use std::fs;
-
-use super::sandbox::SandboxLevel;
-use super::error::LuaError;
-use super::{CommandContext, CommandResult};
-use super::api;
-
-pub struct LuaEngine {
- lua: Lua,
- sandbox: SandboxLevel,
-}
-
-
-
-impl LuaEngine {
- pub fn new(sandbox: SandboxLevel) -> Result {
- // select which standard libraries to load based on sandbox access level
- let std_libs = match sandbox {
- SandboxLevel::Minimal => {
- StdLib::TABLE | StdLib::STRING | StdLib::MATH
- }
- SandboxLevel::Standard => {
- StdLib::TABLE | StdLib::STRING | StdLib::MATH | StdLib::UTF8
- }
- SandboxLevel::Full => {
- StdLib::TABLE | StdLib::STRING | StdLib::MATH | StdLib::UTF8 | StdLib::OS
- }
- };
-
- let lua = Lua::new_with(std_libs, mlua::LuaOptions::default())
- .map_err(|e| LuaError::InitError(e.to_string()))?;
-
- // remove dangerous globals regardless of sandbox
- {
- let globals = lua.globals();
-
- // always remove these
- let _ = globals.set("loadfile", Value::Nil);
- let _ = globals.set("dofile", Value::Nil);
- let _ = globals.set("load", Value::Nil);
- let _ = globals.set("loadstring", Value::Nil);
-
- // remove io unless full sandbox
- if !matches!(sandbox, SandboxLevel::Full) {
- let _ = globals.set("io", Value::Nil);
- }
-
- // remove os.execute, os.exit, os.setlocale even in full mode
- // for SECURITY REASONS!!!
- if matches!(sandbox, SandboxLevel::Full) {
- if let Ok(os) = globals.get::("os") {
- let _ = os.set("execute", Value::Nil);
- let _ = os.set("exit", Value::Nil);
- let _ = os.set("remove", Value::Nil);
- let _ = os.set("rename", Value::Nil);
- let _ = os.set("setlocale", Value::Nil);
- }
- }
- }
-
- Ok(Self { lua, sandbox })
- }
-
- // Register all jarvis APIs
- fn register_api(&self, context: &CommandContext) -> Result<(), LuaError> {
- let globals = self.lua.globals();
-
- // main jarvis table
- let jarvis = self.lua.create_table()
- .map_err(|e| LuaError::InitError(e.to_string()))?;
-
- // always register core APIs
- api::core::register(&self.lua, &jarvis)?;
- api::audio::register(&self.lua, &jarvis)?;
- api::context::register(&self.lua, &jarvis, context)?;
-
- // sandbox-controlled APIs
- if self.sandbox.allows_http() {
- api::http::register(&self.lua, &jarvis)?;
- }
-
- if self.sandbox.allows_state() {
- api::state::register(&self.lua, &jarvis, &context.command_path)?;
- }
-
- if self.sandbox.allows_fs() {
- api::fs::register(&self.lua, &jarvis, &context.command_path, self.sandbox)?;
- }
-
- api::system::register(&self.lua, &jarvis, self.sandbox)?;
-
- globals.set("jarvis", jarvis)
- .map_err(|e| LuaError::InitError(e.to_string()))?;
-
- Ok(())
- }
-
- // Main LUA executor
- pub fn execute(
- &self,
- script_path: &PathBuf,
- context: CommandContext,
- timeout: Duration,
- ) -> Result {
- // register APIs
- self.register_api(&context)?;
-
- // load script
- let script_content = fs::read_to_string(script_path)
- .map_err(|e| LuaError::LoadError(format!("{}: {}", script_path.display(), e)))?;
-
- let script_name = script_path.file_name()
- .unwrap()
- .to_string_lossy()
- .to_string();
-
-
- // set up timeout hook
- let start = std::time::Instant::now();
- self.lua.set_hook(mlua::HookTriggers {
- every_nth_instruction: Some(1000),
- ..Default::default()
- }, move |_lua, _debug| {
- if start.elapsed() > timeout {
- Err(mlua::Error::runtime("Script timeout"))
- } else {
- Ok(mlua::VmState::Continue)
- }
- }).map_err(|e| LuaError::InitError(e.to_string()))?;
-
- // execute script
- let result = self.lua.load(&script_content)
- .set_name(&script_name)
- .eval::();
-
- // remove hook
- let _ = self.lua.remove_hook();
-
- // result
- match result {
- Ok(value) => Ok(self.parse_result(value)),
- Err(e) => {
- if e.to_string().contains("timeout") {
- Err(LuaError::Timeout)
- } else {
- Err(LuaError::RuntimeError(e.to_string()))
- }
- }
- }
- }
-
- // Parse Lua return value into CommandResult
- fn parse_result(&self, value: Value) -> CommandResult {
- match value {
- // return { chain = false }
- Value::Table(t) => {
- let chain = t.get::("chain").unwrap_or(true);
- CommandResult { chain }
- }
- // return false (shorthand for no chain)
- Value::Boolean(chain) => CommandResult { chain },
- // return nil or no return = chain
- _ => CommandResult::default(),
- }
- }
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/error.rs b/crates/jarvis-core/src/lua/error.rs
deleted file mode 100644
index 2888101..0000000
--- a/crates/jarvis-core/src/lua/error.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use std::fmt;
-
-#[derive(Debug)]
-pub enum LuaError {
- // Failed to create Lua VM
- InitError(String),
-
- // Failed to load script file
- LoadError(String),
-
- // Script execution error
- RuntimeError(String),
-
- // Script exceeded timeout
- Timeout,
-
- // Sandbox violation
- SandboxViolation(String),
-
- // IO error
- IoError(std::io::Error),
-}
-
-impl fmt::Display for LuaError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- LuaError::InitError(msg) => write!(f, "Lua init error: {}", msg),
- LuaError::LoadError(msg) => write!(f, "Script load error: {}", msg),
- LuaError::RuntimeError(msg) => write!(f, "Runtime error: {}", msg),
- LuaError::Timeout => write!(f, "Script timeout"),
- LuaError::SandboxViolation(msg) => write!(f, "Sandbox violation: {}", msg),
- LuaError::IoError(e) => write!(f, "IO error: {}", e),
- }
- }
-}
-
-impl std::error::Error for LuaError {}
-
-impl From for LuaError {
- fn from(e: mlua::Error) -> Self {
- LuaError::RuntimeError(e.to_string())
- }
-}
-
-impl From for LuaError {
- fn from(e: std::io::Error) -> Self {
- LuaError::IoError(e)
- }
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/sandbox.rs b/crates/jarvis-core/src/lua/sandbox.rs
deleted file mode 100644
index 3e54b7e..0000000
--- a/crates/jarvis-core/src/lua/sandbox.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-// Sandbox level controlling what APIs are available
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
-pub enum SandboxLevel {
- // Minimal: only core APIs (log, speak, audio, context)
- Minimal,
-
- // Standard: + http, state, fs (command folder only)
- Standard,
-
- // Full: + system.exec, expanded fs access
- Full,
-}
-
-impl SandboxLevel {
- pub fn from_str(s: &str) -> Self {
- match s.to_lowercase().as_str() {
- "minimal" => SandboxLevel::Minimal,
- "full" => SandboxLevel::Full,
- _ => SandboxLevel::Standard,
- }
- }
-
- // Can use HTTP API
- pub fn allows_http(&self) -> bool {
- matches!(self, SandboxLevel::Standard | SandboxLevel::Full)
- }
-
- // Can use persistent state API
- pub fn allows_state(&self) -> bool {
- matches!(self, SandboxLevel::Standard | SandboxLevel::Full)
- }
-
- // Can use file system API
- pub fn allows_fs(&self) -> bool {
- matches!(self, SandboxLevel::Standard | SandboxLevel::Full)
- }
-
- // Can write files
- pub fn allows_fs_write(&self) -> bool {
- matches!(self, SandboxLevel::Standard | SandboxLevel::Full)
- }
-
- // Can execute system commands
- pub fn allows_exec(&self) -> bool {
- matches!(self, SandboxLevel::Full)
- }
-
- // Can access clipboard write
- pub fn allows_clipboard_write(&self) -> bool {
- matches!(self, SandboxLevel::Full)
- }
-
- // Can access paths outside command folder
- pub fn allows_expanded_paths(&self) -> bool {
- matches!(self, SandboxLevel::Full)
- }
-}
-
-impl Default for SandboxLevel {
- fn default() -> Self {
- SandboxLevel::Standard
- }
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/structs.rs b/crates/jarvis-core/src/lua/structs.rs
deleted file mode 100644
index 1e27097..0000000
--- a/crates/jarvis-core/src/lua/structs.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-use std::{collections::HashMap, path::PathBuf};
-
-use crate::commands::SlotValue;
-
-// Context passed to Lua scripts
-#[derive(Debug, Clone)]
-pub struct CommandContext {
- // The phrase that triggered the command
- pub phrase: String,
-
- // Command ID
- pub command_id: String,
-
- // Path to command folder
- pub command_path: PathBuf,
-
- // Current language
- pub language: String,
-
- // Slots
- pub slots: Option>
-}
-
-// Result returned from Lua script execution
-#[derive(Debug, Clone)]
-pub struct CommandResult {
- // Whether to continue chaining commands
- pub chain: bool,
-}
-
-impl Default for CommandResult {
- fn default() -> Self {
- Self { chain: true }
- }
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/lua/tests.rs b/crates/jarvis-core/src/lua/tests.rs
deleted file mode 100644
index f2035ee..0000000
--- a/crates/jarvis-core/src/lua/tests.rs
+++ /dev/null
@@ -1,112 +0,0 @@
-#[cfg(test)]
-mod tests {
- use crate::lua::{CommandContext, LuaError, SandboxLevel, execute};
-
- use std::path::PathBuf;
- use std::time::Duration;
- use tempfile::tempdir;
- use std::fs;
-
- fn create_test_context(cmd_path: PathBuf) -> CommandContext {
- CommandContext {
- phrase: "test phrase".to_string(),
- command_id: "test_cmd".to_string(),
- command_path: cmd_path,
- language: "en".to_string(),
- slots: None,
- }
- }
-
- #[test]
- fn test_minimal_sandbox() {
- let dir = tempdir().unwrap();
- let script_path = dir.path().join("test.lua");
-
- fs::write(&script_path, r#"
- jarvis.log("info", "test log")
- return { chain = false }
- "#).unwrap();
-
- let context = create_test_context(dir.path().to_path_buf());
- let result = execute(
- &script_path,
- context,
- SandboxLevel::Minimal,
- Duration::from_secs(5),
- );
-
- assert!(result.is_ok());
- assert_eq!(result.unwrap().chain, false);
- }
-
- #[test]
- fn test_state_persistence() {
- let dir = tempdir().unwrap();
- let script_path = dir.path().join("test.lua");
-
- // first run - set state
- fs::write(&script_path, r#"
- jarvis.state.set("key", "value")
- return true
- "#).unwrap();
-
- let context = create_test_context(dir.path().to_path_buf());
- execute(&script_path, context, SandboxLevel::Standard, Duration::from_secs(5)).unwrap();
-
- // second run - read state
- fs::write(&script_path, r#"
- local val = jarvis.state.get("key")
- if val == "value" then
- return true
- else
- error("State not persisted")
- end
- "#).unwrap();
-
- let context = create_test_context(dir.path().to_path_buf());
- let result = execute(&script_path, context, SandboxLevel::Standard, Duration::from_secs(5));
-
- assert!(result.is_ok());
- }
-
- #[test]
- fn test_timeout() {
- let dir = tempdir().unwrap();
- let script_path = dir.path().join("test.lua");
-
- fs::write(&script_path, r#"
- while true do end
- "#).unwrap();
-
- let context = create_test_context(dir.path().to_path_buf());
- let result = execute(
- &script_path,
- context,
- SandboxLevel::Minimal,
- Duration::from_millis(100),
- );
-
- assert!(matches!(result, Err(LuaError::Timeout)));
- }
-
- #[test]
- fn test_sandbox_fs_escape() {
- let dir = tempdir().unwrap();
- let script_path = dir.path().join("test.lua");
-
- fs::write(&script_path, r#"
- local ok, err = pcall(function()
- jarvis.fs.read("../../../etc/passwd")
- end)
- if ok then
- error("Should have been blocked")
- end
- return true
- "#).unwrap();
-
- let context = create_test_context(dir.path().to_path_buf());
- let result = execute(&script_path, context, SandboxLevel::Standard, Duration::from_secs(5));
-
- assert!(result.is_ok());
- }
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/models.rs b/crates/jarvis-core/src/models.rs
deleted file mode 100644
index ff49918..0000000
--- a/crates/jarvis-core/src/models.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-mod registry;
-mod catalog;
-pub mod structs;
-pub mod loaders;
-
-pub mod vosk_models;
-pub mod gliner_models;
-
-// re-export loaders
-#[cfg(feature = "jarvis_app")]
-pub use loaders::embedding;
-
-#[cfg(feature = "jarvis_app")]
-pub use loaders::gliner;
-
-#[cfg(feature = "jarvis_app")]
-pub use loaders::ort_model;
-
-#[cfg(feature = "jarvis_app")]
-pub use loaders::intent_classifier;
-
-#[cfg(feature = "vosk")]
-pub use loaders::vosk;
-
-#[cfg(feature = "nnnoiseless")]
-pub use loaders::nnnoiseless;
-
-pub use registry::ModelRegistry;
-pub use structs::{Task, ModelDef, BackendOption};
-
-use once_cell::sync::OnceCell;
-
-use crate::APP_DIR;
-
-pub const MODELS_PATH: &str = "resources/models";
-
-static REGISTRY: OnceCell = OnceCell::new();
-
-pub fn init() -> Result<(), String> {
- if REGISTRY.get().is_some() {
- return Ok(());
- }
-
- let registry = ModelRegistry::new();
-
- let models_dir = APP_DIR.join(MODELS_PATH);
- let models = catalog::scan_models(&models_dir);
- info!("Found {} model(s) in {:?}", models.len(), models_dir);
- registry.set_catalog(models);
-
- REGISTRY.set(registry)
- .map_err(|_| "Models registry already initialized".to_string())?;
-
- Ok(())
-}
-
-pub fn registry() -> &'static ModelRegistry {
- REGISTRY.get().expect("Models registry not initialized - call models::init() first")
-}
-
-pub fn get_options(task: Task) -> Vec {
- registry().with_catalog(|models| catalog::get_options(task, models))
-}
-
-pub fn is_valid_backend(task: Task, backend_id: &str) -> bool {
- registry().with_catalog(|models| catalog::is_valid_backend(task, backend_id, models))
-}
diff --git a/crates/jarvis-core/src/models/catalog.rs b/crates/jarvis-core/src/models/catalog.rs
deleted file mode 100644
index 7c736f2..0000000
--- a/crates/jarvis-core/src/models/catalog.rs
+++ /dev/null
@@ -1,140 +0,0 @@
-use std::fs;
-use std::path::Path;
-
-use super::structs::{Task, ModelDef, BackendOption};
-
-// scan the models directory for folders containing model.toml
-pub fn scan_models(models_dir: &Path) -> Vec {
- let mut models = Vec::new();
-
- if !models_dir.exists() {
- warn!("Models directory not found: {:?}", models_dir);
- return models;
- }
-
- let entries = match fs::read_dir(models_dir) {
- Ok(e) => e,
- Err(e) => {
- warn!("Failed to read models dir: {}", e);
- return models;
- }
- };
-
- for entry in entries.flatten() {
- let path = entry.path();
- if !path.is_dir() {
- continue;
- }
-
- let toml_path = path.join("model.toml");
- if !toml_path.exists() {
- continue;
- }
-
- match load_model_def(&toml_path, &path) {
- Ok(def) => {
- info!("Found model: {} ({}) - tasks: {:?}", def.name, def.id, def.tasks);
- models.push(def);
- }
- Err(e) => warn!("Failed to load model from {:?}: {}", path, e),
- }
- }
-
- models
-}
-
-fn load_model_def(toml_path: &Path, model_dir: &Path) -> Result {
- let content = fs::read_to_string(toml_path)
- .map_err(|e| format!("read error: {}", e))?;
-
- let parsed: ModelToml = toml::from_str(&content)
- .map_err(|e| format!("parse error: {}", e))?;
-
- let mut def = parsed.model;
- def.path = model_dir.to_path_buf();
-
- Ok(def)
-}
-
-#[derive(serde::Deserialize)]
-struct ModelToml {
- model: ModelDef,
-}
-
-// Code backends per task
-pub fn code_backends(task: Task) -> Vec {
- match task {
- Task::Intent => vec![
- BackendOption {
- id: "intent-classifier".into(),
- name: "Intent Classifier".into(),
- model_id: None,
- },
- ],
- Task::Slots => vec![],
- Task::Vad => vec![
- BackendOption {
- id: "energy".into(),
- name: "Energy-based".into(),
- model_id: None,
- },
- BackendOption {
- id: "nnnoiseless".into(),
- name: "Nnnoiseless".into(),
- model_id: None,
- },
- ],
- Task::NoiseSuppression => vec![
- BackendOption {
- id: "nnnoiseless".into(),
- name: "Nnnoiseless".into(),
- model_id: None,
- },
- ],
- Task::Stt => vec![
- BackendOption {
- id: "vosk".into(),
- name: "Vosk".into(),
- model_id: None,
- },
- ],
- }
-}
-
-// get all available options for a task:
-// "none" first, then code backends, then AI models from catalog
-pub fn get_options(task: Task, models: &[ModelDef]) -> Vec {
- let mut options = vec![
- BackendOption {
- id: "none".into(),
- name: "Disabled".into(),
- model_id: None,
- },
- ];
-
- options.extend(code_backends(task));
-
- for model in models {
- if model.tasks.contains(&task) {
- options.push(BackendOption {
- id: model.id.clone(),
- name: model.name.clone(),
- model_id: Some(model.id.clone()),
- });
- }
- }
-
- options
-}
-
-pub fn is_valid_backend(task: Task, backend_id: &str, models: &[ModelDef]) -> bool {
- if backend_id == "none" {
- return true;
- }
-
- if code_backends(task).iter().any(|b| b.id == backend_id) {
- return true;
- }
-
- models.iter().any(|m| m.id == backend_id && m.tasks.contains(&task))
-}
diff --git a/crates/jarvis-core/src/models/gliner_models.rs b/crates/jarvis-core/src/models/gliner_models.rs
deleted file mode 100644
index 82a88b4..0000000
--- a/crates/jarvis-core/src/models/gliner_models.rs
+++ /dev/null
@@ -1,130 +0,0 @@
-use std::collections::HashMap;
-use std::fs;
-use crate::APP_DIR;
-
-const GLINER_DIRS: &[&str] = &["gliner_small-v2.1", "gliner_multi-v2.1"];
-
-#[derive(Debug, Clone)]
-pub struct GlinerModelVariant {
- // type id stored in settings, e.g. "int8", "fp16", "full"
- pub value: String,
- // shown in dropdown, e.g. "int8 (174MB / 332MB)"
- pub display_name: String,
-}
-
-// scan both model dirs and return a deduplicated list of model types
-pub fn scan_gliner_variants() -> Vec {
- let base = APP_DIR.join("resources").join("models");
-
- // collect: type -> { dir_name -> size_mb }
- let mut types: HashMap> = HashMap::new();
-
- for dir_name in GLINER_DIRS {
- let onnx_dir = base.join(dir_name).join("onnx");
- if !onnx_dir.exists() { continue; }
-
- let entries = match fs::read_dir(&onnx_dir) {
- Ok(e) => e,
- Err(_) => continue,
- };
-
- for entry in entries.flatten() {
- let path = entry.path();
- let file_name = match path.file_name().and_then(|n| n.to_str()) {
- Some(n) if n.ends_with(".onnx") => n.to_string(),
- _ => continue,
- };
-
- let variant_type = file_name_to_type(&file_name);
- let size_mb = fs::metadata(&path).map(|m| m.len()).unwrap_or(0) / (1024 * 1024);
-
- types.entry(variant_type)
- .or_default()
- .insert(dir_name.to_string(), size_mb);
- }
- }
-
- let mut result: Vec = types.into_iter().map(|(variant, sizes)| {
- let size_str = build_size_string(&sizes);
- let label = if variant == "full" { "Full".to_string() } else { variant.clone() };
- GlinerModelVariant {
- display_name: format!("{} ({})", label, size_str),
- value: variant,
- }
- }).collect();
-
- // sort: full first, then alphabetically
- result.sort_by(|a, b| {
- let a_full = a.value == "full";
- let b_full = b.value == "full";
- b_full.cmp(&a_full).then_with(|| a.value.cmp(&b.value))
- });
-
- result
-}
-
-// "model.onnx" -> "full", "model_int8.onnx" -> "int8"
-fn file_name_to_type(name: &str) -> String {
- let stem = name.strip_suffix(".onnx").unwrap_or(name);
- if stem == "model" {
- "full".to_string()
- } else if let Some(variant) = stem.strip_prefix("model_") {
- variant.to_string()
- } else {
- stem.to_string()
- }
-}
-
-// build size display: "174MB" if only one dir, "small: 174MB / multi: 332MB" if both
-fn build_size_string(sizes: &HashMap) -> String {
- if sizes.len() == 1 {
- let (dir, mb) = sizes.iter().next().unwrap();
- let short = short_dir_name(dir);
- return format!("{}: {}MB", short, mb);
- }
-
- let mut parts: Vec = Vec::new();
- for dir_name in GLINER_DIRS {
- if let Some(mb) = sizes.get(*dir_name) {
- parts.push(format!("{}: {}MB", short_dir_name(dir_name), mb));
- }
- }
- parts.join(" / ")
-}
-
-fn short_dir_name(dir: &str) -> &str {
- if dir.contains("small") { "small" }
- else if dir.contains("multi") { "multi" }
- else { dir }
-}
-
-// resolve variant type + language into actual file path
-// returns (model_dir_path, onnx_file_name) or None
-pub fn resolve_model(variant: &str, language: &str) -> Option<(std::path::PathBuf, String)> {
- let base = APP_DIR.join("resources").join("models");
- let file_name = type_to_file_name(variant);
-
- // pick dir based on language
- let preferred: &[&str] = match language {
- "en" => &["gliner_small-v2.1", "gliner_multi-v2.1"],
- _ => &["gliner_multi-v2.1", "gliner_small-v2.1"],
- };
-
- for dir_name in preferred {
- let path = base.join(dir_name).join("onnx").join(&file_name);
- if path.exists() {
- return Some((base.join(dir_name), file_name));
- }
- }
-
- None
-}
-
-// "full" -> "model.onnx", "int8" -> "model_int8.onnx"
-fn type_to_file_name(variant: &str) -> String {
- if variant == "full" || variant.is_empty() {
- "model.onnx".to_string()
- } else {
- format!("model_{}.onnx", variant)
- }
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/models/loaders/embedding.rs b/crates/jarvis-core/src/models/loaders/embedding.rs
deleted file mode 100644
index 999dca3..0000000
--- a/crates/jarvis-core/src/models/loaders/embedding.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-// fastembed embedding model (all-MiniLM-L6-v2, paraphrase-multilingual, etc.)
-
-use std::sync::Arc;
-use parking_lot::Mutex;
-use fastembed::{TextEmbedding, UserDefinedEmbeddingModel, TokenizerFiles, Pooling, QuantizationMode, OutputKey};
-
-use crate::models::registry::ModelRegistry;
-
-pub struct EmbeddingModel {
- pub embedding: Mutex,
-}
-
-// fastembed uses ORT internally which is thread-safe
-unsafe impl Send for EmbeddingModel {}
-unsafe impl Sync for EmbeddingModel {}
-
-pub fn load(registry: &ModelRegistry, model_id: &str) -> Result, String> {
- registry.get_or_load::(model_id, |def| {
- let model_dir = &def.path;
-
- info!("Loading embedding model from: {}", model_dir.display());
-
- let user_model = UserDefinedEmbeddingModel {
- onnx_file: std::fs::read(model_dir.join("model.onnx"))
- .map_err(|e| format!("Failed to read model.onnx: {}", e))?,
- tokenizer_files: TokenizerFiles {
- tokenizer_file: std::fs::read(model_dir.join("tokenizer.json"))
- .map_err(|e| format!("Failed to read tokenizer.json: {}", e))?,
- config_file: std::fs::read(model_dir.join("config.json"))
- .map_err(|e| format!("Failed to read config.json: {}", e))?,
- special_tokens_map_file: std::fs::read(model_dir.join("special_tokens_map.json"))
- .map_err(|e| format!("Failed to read special_tokens_map.json: {}", e))?,
- tokenizer_config_file: std::fs::read(model_dir.join("tokenizer_config.json"))
- .map_err(|e| format!("Failed to read tokenizer_config.json: {}", e))?,
- },
- pooling: Some(Pooling::Mean),
- quantization: QuantizationMode::None,
- output_key: Some(OutputKey::ByName("last_hidden_state")),
- };
-
- let model = TextEmbedding::try_new_from_user_defined(user_model, Default::default())
- .map_err(|e| format!("Failed to load embedding model: {}", e))?;
-
- info!("Embedding model loaded: {}", def.name);
- Ok(EmbeddingModel { embedding: Mutex::new(model) })
- })
-}
diff --git a/crates/jarvis-core/src/models/loaders/gliner.rs b/crates/jarvis-core/src/models/loaders/gliner.rs
deleted file mode 100644
index c456005..0000000
--- a/crates/jarvis-core/src/models/loaders/gliner.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-// GLiNER model for named entity recognition / slot extraction
-
-use std::sync::Arc;
-use parking_lot::Mutex;
-use regex::Regex;
-use tokenizers::Tokenizer;
-
-use crate::models::registry::ModelRegistry;
-
-const WORD_REGEX: &str = r"\w+(?:[-_]\w+)*|\S";
-
-pub struct GlinerModel {
- pub session: Mutex,
- pub tokenizer: Tokenizer,
- pub splitter: Regex,
-}
-
-unsafe impl Send for GlinerModel {}
-unsafe impl Sync for GlinerModel {}
-
-pub fn load(registry: &ModelRegistry, model_id: &str) -> Result, String> {
- registry.get_or_load::(model_id, |def| {
- let model_dir = &def.path;
-
- // GLiNER models keep onnx files in an "onnx" subfolder
- let onnx_dir = model_dir.join("onnx");
- let model_path = if onnx_dir.exists() {
- onnx_dir.join("model.onnx")
- } else {
- model_dir.join("model.onnx")
- };
-
- let tokenizer_path = model_dir.join("tokenizer.json");
-
- info!("Loading GLiNER model from: {}", model_dir.display());
-
- let session = ort::session::Session::builder()
- .map_err(|e| format!("Failed to create ORT session builder: {}", e))?
- .commit_from_file(&model_path)
- .map_err(|e| format!("Failed to load ONNX model: {}", e))?;
-
- let tokenizer = Tokenizer::from_file(&tokenizer_path)
- .map_err(|e| format!("Failed to load tokenizer: {}", e))?;
-
- let splitter = Regex::new(WORD_REGEX)
- .map_err(|e| format!("Failed to compile word regex: {}", e))?;
-
- info!("GLiNER model loaded: {}", def.name);
- Ok(GlinerModel { session: Mutex::new(session), tokenizer, splitter })
- })
-}
diff --git a/crates/jarvis-core/src/models/loaders/intent_classifier.rs b/crates/jarvis-core/src/models/loaders/intent_classifier.rs
deleted file mode 100644
index 0c5a01a..0000000
--- a/crates/jarvis-core/src/models/loaders/intent_classifier.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-// intent-classifier crate wrapper
-
-use std::sync::Arc;
-use intent_classifier::IntentClassifier;
-
-use crate::models::registry::ModelRegistry;
-
-pub struct IntentClassifierModel {
- pub classifier: IntentClassifier,
-}
-
-unsafe impl Send for IntentClassifierModel {}
-unsafe impl Sync for IntentClassifierModel {}
-
-// init is async (IntentClassifier::new().await), so we create it
-// outside the registry and insert it after
-pub async fn load(registry: &ModelRegistry, model_id: &str) -> Result, String> {
- if let Some(existing) = registry.get::(model_id) {
- info!("IntentClassifier '{}' already loaded, reusing", model_id);
- return Ok(existing);
- }
-
- info!("Initializing IntentClassifier...");
-
- let classifier = IntentClassifier::new().await
- .map_err(|e| format!("Failed to init IntentClassifier: {}", e))?;
-
- info!("IntentClassifier initialized");
- Ok(registry.insert(model_id, IntentClassifierModel { classifier }))
-}
diff --git a/crates/jarvis-core/src/models/loaders/mod.rs b/crates/jarvis-core/src/models/loaders/mod.rs
deleted file mode 100644
index 6ed7e41..0000000
--- a/crates/jarvis-core/src/models/loaders/mod.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-#[cfg(feature = "jarvis_app")]
-pub mod embedding;
-#[cfg(feature = "jarvis_app")]
-pub mod gliner;
-#[cfg(feature = "jarvis_app")]
-pub mod ort_model;
-#[cfg(feature = "jarvis_app")]
-pub mod intent_classifier;
-#[cfg(feature = "vosk")]
-pub mod vosk;
-#[cfg(feature = "nnnoiseless")]
-pub mod nnnoiseless;
diff --git a/crates/jarvis-core/src/models/loaders/nnnoiseless.rs b/crates/jarvis-core/src/models/loaders/nnnoiseless.rs
deleted file mode 100644
index 315b031..0000000
--- a/crates/jarvis-core/src/models/loaders/nnnoiseless.rs
+++ /dev/null
@@ -1,110 +0,0 @@
-// nnnoiseless - used for both noise suppression and VAD.
-// each consumer needs its own DenoiseState (stateful per-stream),
-// so this doesn't go through the registry. just centralizes creation.
-
-use nnnoiseless::DenoiseState;
-use crate::config;
-
-// noise suppression instance
-pub struct NnnoiselessNS {
- state: Box>,
- buffer: Vec,
-}
-
-impl NnnoiselessNS {
- pub fn new() -> Self {
- Self {
- state: DenoiseState::new(),
- buffer: Vec::with_capacity(config::NNNOISELESS_FRAME_SIZE * 2),
- }
- }
-
- pub fn process(&mut self, input: &[i16]) -> Vec {
- self.buffer.extend(input.iter().map(|&s| s as f32));
-
- let frame_size = config::NNNOISELESS_FRAME_SIZE;
- let full_frames = self.buffer.len() / frame_size;
-
- if full_frames == 0 {
- return input.to_vec();
- }
-
- let mut output: Vec = Vec::with_capacity(full_frames * frame_size);
- let mut input_frame = [0.0f32; 480];
- let mut output_frame = [0.0f32; 480];
-
- let consumed = full_frames * frame_size;
- for i in 0..full_frames {
- let offset = i * frame_size;
- input_frame.copy_from_slice(&self.buffer[offset..offset + frame_size]);
-
- let _ = self.state.process_frame(&mut output_frame, &input_frame);
-
- for &sample in &output_frame {
- let clamped = sample.clamp(i16::MIN as f32, i16::MAX as f32);
- output.push(clamped as i16);
- }
- }
-
- // keep leftover samples (single drain at the end)
- self.buffer.drain(..consumed);
-
- output
- }
-
- pub fn reset(&mut self) {
- self.buffer.clear();
- }
-}
-
-// VAD instance
-pub struct NnnoiselessVAD {
- state: Box>,
- buffer: Vec,
-}
-
-impl NnnoiselessVAD {
- pub fn new() -> Self {
- Self {
- state: DenoiseState::new(),
- buffer: Vec::with_capacity(config::NNNOISELESS_FRAME_SIZE * 2),
- }
- }
-
- pub fn detect(&mut self, input: &[i16]) -> (bool, f32) {
- self.buffer.extend(input.iter().map(|&s| s as f32));
-
- let frame_size = config::NNNOISELESS_FRAME_SIZE;
- let full_frames = self.buffer.len() / frame_size;
-
- if full_frames == 0 {
- return (true, 0.5);
- }
-
- let mut total_vad = 0.0f32;
- let mut input_frame = [0.0f32; 480];
- let mut output_frame = [0.0f32; 480];
-
- let consumed = full_frames * frame_size;
- for i in 0..full_frames {
- let offset = i * frame_size;
- input_frame.copy_from_slice(&self.buffer[offset..offset + frame_size]);
-
- let vad_prob = self.state.process_frame(&mut output_frame, &input_frame);
- total_vad += vad_prob;
- }
-
- // single drain
- self.buffer.drain(..consumed);
-
- let avg_vad = total_vad / full_frames as f32;
- let is_voice = avg_vad >= config::VAD_NNNOISELESS_THRESHOLD;
-
- (is_voice, avg_vad)
- }
-
- pub fn reset(&mut self) {
- self.state = DenoiseState::new();
- self.buffer.clear();
- }
-}
diff --git a/crates/jarvis-core/src/models/loaders/ort_model.rs b/crates/jarvis-core/src/models/loaders/ort_model.rs
deleted file mode 100644
index 471f602..0000000
--- a/crates/jarvis-core/src/models/loaders/ort_model.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-// generic ORT model - session + optional tokenizer.
-// for models like BERT (tiny, distil, mini) that can serve
-// multiple tasks (intent, NER, text classification, etc.)
-
-use std::sync::Arc;
-use parking_lot::Mutex;
-use tokenizers::Tokenizer;
-
-use crate::models::registry::ModelRegistry;
-
-pub struct OrtModel {
- pub session: Mutex,
- pub tokenizer: Option,
-}
-
-unsafe impl Send for OrtModel {}
-unsafe impl Sync for OrtModel {}
-
-pub fn load(registry: &ModelRegistry, model_id: &str) -> Result, String> {
- registry.get_or_load::(model_id, |def| {
- let model_dir = &def.path;
- let onnx_path = model_dir.join("model.onnx");
-
- info!("Loading ORT model from: {}", model_dir.display());
-
- let session = ort::session::Session::builder()
- .map_err(|e| format!("ORT session builder error: {}", e))?
- .commit_from_file(&onnx_path)
- .map_err(|e| format!("Failed to load ONNX model '{}': {}", onnx_path.display(), e))?;
-
- let tokenizer_path = model_dir.join("tokenizer.json");
- let tokenizer = if tokenizer_path.exists() {
- Some(
- Tokenizer::from_file(&tokenizer_path)
- .map_err(|e| format!("Failed to load tokenizer: {}", e))?
- )
- } else {
- None
- };
-
- info!("ORT model loaded: {}", def.name);
- Ok(OrtModel { session: Mutex::new(session), tokenizer })
- })
-}
diff --git a/crates/jarvis-core/src/models/loaders/vosk.rs b/crates/jarvis-core/src/models/loaders/vosk.rs
deleted file mode 100644
index d87bfde..0000000
--- a/crates/jarvis-core/src/models/loaders/vosk.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-// vosk speech recognition model
-
-use std::sync::Arc;
-use vosk::Model;
-
-use crate::models::registry::ModelRegistry;
-
-pub struct VoskModel {
- pub model: Model,
-}
-
-unsafe impl Send for VoskModel {}
-unsafe impl Sync for VoskModel {}
-
-// load a vosk model by path through the registry.
-// vosk models aren't in the catalog (they use their own directory structure),
-// so we pass the path directly and use model_id for dedup.
-// @ToDo: Consider moving to catalog
-pub fn load(registry: &ModelRegistry, model_id: &str, model_path: &str) -> Result, String> {
- // check if already loaded
- if let Some(existing) = registry.get::(model_id) {
- info!("Vosk model '{}' already loaded, reusing", model_id);
- return Ok(existing);
- }
-
- info!("Loading Vosk model from: {}", model_path);
-
- let model = Model::new(model_path)
- .ok_or_else(|| format!("Failed to load Vosk model from: {}", model_path))?;
-
- info!("Vosk model loaded: {}", model_id);
- Ok(registry.insert(model_id, VoskModel { model }))
-}
diff --git a/crates/jarvis-core/src/models/registry.rs b/crates/jarvis-core/src/models/registry.rs
deleted file mode 100644
index 6c31323..0000000
--- a/crates/jarvis-core/src/models/registry.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-use std::any::Any;
-use std::collections::HashMap;
-use std::sync::Arc;
-use parking_lot::{Mutex, RwLock};
-
-use super::structs::ModelDef;
-
-// central model registry. loads models once and shares them between components.
-// completely type-agnostic
-pub struct ModelRegistry {
- loaded: Mutex>>,
- catalog: RwLock>,
-}
-
-impl ModelRegistry {
- pub fn new() -> Self {
- Self {
- loaded: Mutex::new(HashMap::new()),
- catalog: RwLock::new(Vec::new()),
- }
- }
-
- pub fn set_catalog(&self, defs: Vec) {
- *self.catalog.write() = defs;
- }
-
- // read access to catalog without cloning the whole vec
- pub fn with_catalog(&self, f: impl FnOnce(&[ModelDef]) -> R) -> R {
- f(&self.catalog.read())
- }
-
- pub fn get_model_def(&self, id: &str) -> Option {
- self.catalog.read().iter().find(|m| m.id == id).cloned()
- }
-
- // get a loaded model, downcasted to the expected type
- pub fn get(&self, id: &str) -> Option> {
- self.loaded.lock()
- .get(id)?
- .clone()
- .downcast::()
- .ok()
- }
-
- // get or load a model. if two components request the same id,
- // the model only loads once.
- //
- // the lock is released before calling the loader to avoid deadlocks
- // if the loader tries to load a dependency through the registry.
- pub fn get_or_load(
- &self,
- id: &str,
- loader: impl FnOnce(&ModelDef) -> Result,
- ) -> Result, String> {
- // fast path: already loaded
- if let Some(existing) = self.get::(id) {
- info!("Model '{}' already loaded, reusing", id);
- return Ok(existing);
- }
-
- // grab model def (releases catalog lock immediately)
- let def = self.get_model_def(id)
- .ok_or_else(|| format!("Model '{}' not found in catalog", id))?;
-
- // run loader without holding any lock
- info!("Loading model '{}' from {:?}...", id, def.path);
- let model = loader(&def)?;
- let arc = Arc::new(model);
-
- // insert (check again in case another thread loaded it meanwhile)
- let mut map = self.loaded.lock();
- if let Some(existing) = map.get(id) {
- if let Ok(typed) = existing.clone().downcast::() {
- info!("Model '{}' was loaded by another thread, reusing", id);
- return Ok(typed);
- }
- }
-
- map.insert(id.to_string(), arc.clone());
- info!("Model '{}' loaded and registered", id);
-
- Ok(arc)
- }
-
- // insert a model directly (for models not in the catalog,
- // or loaded through non-standard means like async init)
- pub fn insert(&self, id: &str, model: T) -> Arc {
- let arc = Arc::new(model);
- self.loaded.lock().insert(id.to_string(), arc.clone());
- arc
- }
-
- pub fn unload(&self, id: &str) -> bool {
- let removed = self.loaded.lock().remove(id).is_some();
- if removed {
- info!("Model '{}' unloaded from registry", id);
- }
- removed
- }
-
- pub fn is_loaded(&self, id: &str) -> bool {
- self.loaded.lock().contains_key(id)
- }
-
- pub fn loaded_ids(&self) -> Vec {
- self.loaded.lock().keys().cloned().collect()
- }
-}
diff --git a/crates/jarvis-core/src/models/structs.rs b/crates/jarvis-core/src/models/structs.rs
deleted file mode 100644
index 616b615..0000000
--- a/crates/jarvis-core/src/models/structs.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use std::path::PathBuf;
-use serde::{Serialize, Deserialize};
-
-// tasks that components can request a backend for
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum Task {
- Intent,
- Slots,
- Vad,
- NoiseSuppression,
- Stt,
-}
-
-// metadata about a model, parsed from model.toml on disk
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct ModelDef {
- pub id: String,
- pub name: String,
- pub tasks: Vec,
-
- #[serde(default)]
- pub description: String,
-
- // set at runtime after scanning the folder
- #[serde(skip)]
- pub path: PathBuf,
-}
-
-// a selectable option for a task (shown in UI / stored in settings)
-#[derive(Debug, Clone, Serialize)]
-pub struct BackendOption {
- pub id: String,
- pub name: String,
- // if Some, this option uses a model from the registry.
- // if None, it's a code-only backend (like energy VAD) or disabled.
- pub model_id: Option,
-}
diff --git a/crates/jarvis-core/src/models/vosk_models.rs b/crates/jarvis-core/src/models/vosk_models.rs
deleted file mode 100644
index 6de4b49..0000000
--- a/crates/jarvis-core/src/models/vosk_models.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-use std::fs;
-use std::path::{Path, PathBuf};
-
-use crate::{APP_DIR, config};
-
-#[derive(Debug, Clone)]
-pub struct VoskModelInfo {
- pub name: String, // folder name: "vosk-model-small-ru-0.22"
- pub path: PathBuf, // full path
- pub language: String, // extracted from name: "ru"
- pub size: String, // "small", "large", etc.
-}
-
-// Scan for available Vosk models
-pub fn scan_vosk_models() -> Vec {
- let models_dir = {
- APP_DIR.join(config::VOSK_MODELS_PATH)
- };
- let mut models = Vec::new();
-
- info!("VOSK MODELS DIR: {}", models_dir.display());
-
- if !models_dir.exists() {
- warn!("Vosk models directory not found: {}", models_dir.display());
- return models;
- }
-
- let entries = match fs::read_dir(models_dir) {
- Ok(e) => e,
- Err(e) => {
- warn!("Failed to read vosk models directory: {}", e);
- return models;
- }
- };
-
- for entry in entries {
- let entry = match entry {
- Ok(e) => e,
- Err(_) => continue,
- };
-
- let path = entry.path();
-
- // must be a directory
- if !path.is_dir() {
- continue;
- }
-
- // check if it looks like a vosk model (has am/conf/graph folders or similar)
- if !is_vosk_model(&path) {
- continue;
- }
-
- let name = path.file_name()
- .and_then(|n| n.to_str())
- .unwrap_or("")
- .to_string();
-
- let (language, size) = parse_model_name(&name);
-
- models.push(VoskModelInfo {
- name,
- path,
- language,
- size,
- });
- }
-
- models.sort_by(|a, b| a.name.cmp(&b.name));
- models
-}
-
-// Check if directory looks like a Vosk model
-fn is_vosk_model(path: &Path) -> bool {
- // vosk models typically have these subdirectories
- path.join("am").exists() ||
- path.join("conf").exists() ||
- path.join("graph").exists() ||
- path.join("ivector").exists()
-}
-
-// Extract language and size from model name
-// e.g., "vosk-model-small-ru-0.22" -> ("ru", "small")
-fn parse_model_name(name: &str) -> (String, String) {
- let parts: Vec<&str> = name.split('-').collect();
-
- let mut language = String::from("unknown");
- let mut size = String::from("unknown");
-
- // look for common size indicators
- for part in &parts {
- match *part {
- "small" | "big" | "large" | "lgraph" => size = part.to_string(),
- // language codes are usually 2 letters
- s if s.len() == 2 && s.chars().all(|c| c.is_alphabetic()) => {
- language = s.to_string();
- }
- _ => {}
- }
- }
-
- (language, size)
-}
-
-// Get model path by name
-pub fn get_model_path(model_name: &str) -> Option {
- let path = Path::new(config::VOSK_MODELS_PATH).join(model_name);
- if path.exists() && path.is_dir() {
- Some(path)
- } else {
- None
- }
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/recorder.rs b/crates/jarvis-core/src/recorder.rs
deleted file mode 100644
index 7bef81c..0000000
--- a/crates/jarvis-core/src/recorder.rs
+++ /dev/null
@@ -1,171 +0,0 @@
-mod pvrecorder;
-
-// mod cpal;
-// mod portaudio;
-
-use once_cell::sync::OnceCell;
-
-use crate::{config, config::structs::RecorderType, DB};
-
-static RECORDER_TYPE: OnceCell = OnceCell::new();
-static FRAME_LENGTH: OnceCell = OnceCell::new();
-
-pub fn init() -> Result<(), ()> {
- // set default recorder type
- // @TODO. Make it configurable?
- RECORDER_TYPE.set(config::DEFAULT_RECORDER_TYPE).unwrap();
-
- // some info
- info!("Loading recorder ...");
- info!("Available audio_devices are:\n{:?}", get_audio_devices());
-
- // load given recorder
- match RECORDER_TYPE.get().unwrap() {
- RecorderType::PvRecorder => {
- // Init Pv Recorder
- info!("Initializing PvRecorder recording backend.");
- FRAME_LENGTH.set(512u32).unwrap(); // pvrecorder requires frame buffer of 512
- let selected_microphone = get_selected_microphone_index();
- match pvrecorder::init_microphone(
- selected_microphone,
- FRAME_LENGTH.get().unwrap().to_owned(),
- ) {
- false => {
- error!("Recorder initialization failed.");
-
- return Err(());
- }
- _ => {
- info!(
- "Recorder initialization success. Listening to microphone ({}): {}",
- selected_microphone,
- get_audio_device_name(selected_microphone)
- );
- }
- }
- }
- RecorderType::PortAudio => {
- // Init PortAudio
- info!("Initializing PortAudio recording backend");
- todo!();
- // match portaudio::init_microphone(get_selected_microphone_index(), FRAME_LENGTH.load(Ordering::SeqCst)) {
- // false => {
- // // Switch to PortAudio recorder
- // error!("PortAudio audio backend failed.");
- // },
- // _ => ()
- // }
- }
- RecorderType::Cpal => {
- // Init CPAL
- info!("Initializing CPAL recording backend");
- todo!();
- // match cpal::init_microphone(get_selected_microphone_index(), FRAME_LENGTH.load(Ordering::SeqCst)) {
- // false => {
- // // Switch to CPAL recorder
- // error!("CPAL audio backend failed.");
- // },
- // _ => ()
- // }
- }
- }
-
- Ok(())
-}
-
-pub fn read_microphone(frame_buffer: &mut [i16]) {
- match RECORDER_TYPE.get().unwrap() {
- RecorderType::PvRecorder => {
- pvrecorder::read_microphone(frame_buffer);
- }
- RecorderType::PortAudio => {
- todo!();
- // portaudio::read_microphone(frame_buffer);
- }
- RecorderType::Cpal => {
- // cpal::read_microphone(frame_buffer);
- panic!("Cpal should be used via callback assignment");
- }
- }
-}
-
-pub fn start_recording() -> Result<(), ()> {
- match RECORDER_TYPE.get().unwrap() {
- RecorderType::PvRecorder => {
- return pvrecorder::start_recording(
- get_selected_microphone_index(),
- FRAME_LENGTH.get().unwrap().to_owned(),
- );
- }
- RecorderType::PortAudio => {
- todo!();
- // portaudio::start_recording(get_selected_microphone_index(), FRAME_LENGTH.load(Ordering::SeqCst));
- }
- RecorderType::Cpal => {
- todo!();
- // cpal::start_recording(get_selected_microphone_index(), FRAME_LENGTH.load(Ordering::SeqCst));
- }
- }
-}
-
-pub fn stop_recording() -> Result<(), ()> {
- match RECORDER_TYPE.get().unwrap() {
- RecorderType::PvRecorder => pvrecorder::stop_recording(),
- RecorderType::PortAudio => {
- todo!();
- // portaudio::stop_recording();
- }
- RecorderType::Cpal => {
- todo!();
- // cpal::stop_recording();
- }
- }
-}
-
-pub fn get_selected_microphone_index() -> i32 {
- let idx = DB.get().unwrap().read().microphone;
-
- if idx > 0 {
- // validate that this microphone is actually in the list
- let devices = get_audio_devices();
- if (idx as usize) >= devices.len() {
- warn!("Microphone index {} not found ({} available), falling back to default",
- idx, devices.len());
- return -1;
- }
- }
-
- idx
-}
-
-pub fn get_audio_devices() -> Vec {
- match RECORDER_TYPE.get() {
- Some(RecorderType::PvRecorder) => pvrecorder::list_audio_devices(),
- Some(RecorderType::PortAudio) => {
- todo!();
- }
- Some(RecorderType::Cpal) => {
- todo!();
- }
- None => {
- // not initialized yet, default to pvrecorder
- pvrecorder::list_audio_devices()
- }
- }
-}
-
-pub fn get_audio_device_name(idx: i32) -> String {
- match RECORDER_TYPE.get() {
- Some(RecorderType::PvRecorder) => pvrecorder::get_audio_device_name(idx),
- Some(RecorderType::PortAudio) => {
- todo!();
- }
- Some(RecorderType::Cpal) => {
- todo!();
- }
- None => {
- // not initialized yet, default to pvrecorder
- pvrecorder::get_audio_device_name(idx)
- }
- }
-}
diff --git a/crates/jarvis-core/src/recorder/cpal.rs b/crates/jarvis-core/src/recorder/cpal.rs
deleted file mode 100644
index 9692a9d..0000000
--- a/crates/jarvis-core/src/recorder/cpal.rs
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- Abandoned temporary.
- Problems with frame size.
-*/
-
-use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
-use cpal::{BufferSize, StreamConfig, SampleRate, Host, Device, Stream, SampleFormat};
-use log::{info, warn, error};
-
-use once_cell::sync::OnceCell;
-use std::sync::Arc;
-use arc_swap::ArcSwap;
-use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
-
-use crate::tauri_commands::cpal_data_callback;
-
-static HOST: OnceCell = OnceCell::new();
-thread_local!(static RECORDER: OnceCell> = OnceCell::new());
-static SELECTED_MICROPHONE_IDX: AtomicI32 = AtomicI32::new(0);
-static FRAME_LENGTH: AtomicU32 = AtomicU32::new(0);
-static IS_RECORDING: AtomicBool = AtomicBool::new(false);
-
-pub fn init_microphone(device_index: i32, frame_length: u32) -> bool {
- // init host & frame buffer for the callback
- if HOST.get().is_none() {
- HOST.set(cpal::default_host());
-
- // FRAME_BUFFER.set(Mutex::new(vec![0; FRAME_LENGTH.load(Ordering::SeqCst) as usize]));
- }
-
- // init microphone
- RECORDER.with(|recorder| {
- match recorder.get().is_none() {
- true => {
- if let Some(device) = get_device(device_index as usize) {
- // store
- recorder.set(ArcSwap::from_pointee(create_stream(device, frame_length)));
-
- // remember current configuration
- SELECTED_MICROPHONE_IDX.store(device_index, Ordering::SeqCst);
- FRAME_LENGTH.store(frame_length, Ordering::SeqCst);
-
- // success
- true
- } else {
- false
- }
- },
- false => {
- // check if re-initialization required (i.e. selecetd microphoneor frame-length was changed )
- if SELECTED_MICROPHONE_IDX.load(Ordering::SeqCst) != device_index
- ||
- FRAME_LENGTH.load(Ordering::SeqCst) != frame_length {
- warn!("Selected microphone or frame length was changed, re-initializing ...");
- // initialize again with new device index
- if IS_RECORDING.load(Ordering::SeqCst) {
- stop_recording();
- }
-
- // remember new configuration
- SELECTED_MICROPHONE_IDX.store(device_index, Ordering::SeqCst);
- FRAME_LENGTH.store(frame_length, Ordering::SeqCst);
-
- if let Some(device) = get_device(device_index as usize) {
- // store
- recorder.get().unwrap().store(Arc::new(create_stream(device, frame_length)));
-
- // success
- return true
- } else {
- return false
- }
- }
-
- // success
- true
- }
- }
- })
-}
-
-fn create_stream(device: Device, frame_length: u32) -> Stream {
- // get default input stream config
- // let default_config = device.default_input_config().unwrap();
-
- // create config for the stream
- // let config: StreamConfig = StreamConfig {
- // channels: default_config.channels(),
- // sample_rate: SampleRate(16000),
- // buffer_size: BufferSize::Fixed(frame_length)
- // };
-
- let config = device
- .default_input_config()
- .expect("Failed to load default input config");
-
- let channels = config.channels();
-
- let err_fn = move |err| {
- eprintln!("an error occurred on stream: {}", err);
- };
-
- match config.sample_format() {
- SampleFormat::F32 => device.build_input_stream(
- &config.into(),
- move |data: &[f32], info| {
- cpal_data_callback(data, channels);
- },
- err_fn,
- None
- ),
- SampleFormat::U16 => device.build_input_stream(
- &config.into(),
- move |data: &[u16], info| {
- cpal_data_callback(data, channels);
- },
- err_fn,
- None
- ),
- SampleFormat::I16 => device.build_input_stream(
- &config.into(),
- move |data: &[i16], info| {
- cpal_data_callback(data, channels);
- },
- err_fn,
- None
- ),
- _ => todo!()
- }.unwrap()
-}
-
-pub fn stereo_to_mono(input_data: &[i16]) -> Vec {
- let mut result = Vec::with_capacity(input_data.len() / 2);
- result.extend(
- input_data
- .chunks_exact(2)
- .map(|chunk| chunk[0] / 2 + chunk[1] / 2),
- );
-
- result
-}
-
-fn get_device(device_index: usize) -> Option {
- if let Some(device) = HOST.get().unwrap().input_devices().expect("Get devices error ...").nth(device_index) {
- Some(device)
- } else {
- if let Some(default) = HOST.get().unwrap().default_input_device() {
- Some(default)
- } else {
- error!("No default input device ...");
-
- None
- }
- }
-}
-
-pub fn start_recording(device_index: i32, frame_length: u32) {
- // ensure microphone is initialized
- init_microphone(device_index, frame_length);
-
- // start recording
- RECORDER.with(|recorder| {
- match recorder.get().unwrap().load().play() {
- Err(msg) => {
- error!("[CPAL] Audio stream PLAY error ... {:?}", msg);
- },
- _ => ()
- };
-
- IS_RECORDING.store(true, Ordering::SeqCst);
- info!("START recording from microphone ...");
- });
-}
-
-pub fn stop_recording() {
- // ensure microphone is initialized
- RECORDER.with(|recorder| {
- if recorder.get().is_some() && IS_RECORDING.load(Ordering::SeqCst) {
- // pause instead of stop
- match recorder.get().unwrap().load().pause() {
- Err(msg) => {
- error!("[CPAL] Audio stream PAUSE error ... {:?}", msg);
- },
- _ => ()
- };
-
- IS_RECORDING.store(false, Ordering::SeqCst);
- info!("STOP recording from microphone ...");
- }
- });
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/recorder/portaudio.rs b/crates/jarvis-core/src/recorder/portaudio.rs
deleted file mode 100644
index 4bb8ce6..0000000
--- a/crates/jarvis-core/src/recorder/portaudio.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- Abandoned temporary.
-*/
-
-use portaudio as pa;
-use pa::{DeviceIndex, Stream};
-use log::{info, warn, error};
-
-use once_cell::sync::OnceCell;
-use std::sync::{Arc, Mutex};
-use arc_swap::ArcSwap;
-use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
-
-thread_local!(static RECORDER: OnceCell, pa::Input>>>> = OnceCell::new());
-static SELECTED_MICROPHONE_IDX: AtomicI32 = AtomicI32::new(0);
-static FRAME_LENGTH: AtomicU32 = AtomicU32::new(0);
-static IS_RECORDING: AtomicBool = AtomicBool::new(false);
-
-const CHANNELS: i32 = 1;
-const SAMPLE_RATE: f64 = 16_000.0;
-
-pub fn init_microphone(device_index: i32, frame_length: u32) -> bool {
- RECORDER.with(|r| {
- match r.get().is_none() {
- true => {
- match create_stream(device_index, frame_length) {
- Ok(stream) => {
- // store
- r.set(ArcSwap::from_pointee(Mutex::new(stream)));
-
- // remember current configuration
- SELECTED_MICROPHONE_IDX.store(device_index, Ordering::SeqCst);
- FRAME_LENGTH.store(frame_length, Ordering::SeqCst);
-
- // success
- true
- },
- Err(msg) => {
- error!("Failed to initialize portaudio.\nError details: {:?}", msg);
-
- // fail
- false
- }
- }
- },
- _ => {
- // check if re-initialization required (i.e. selecetd microphoneor frame-length was changed )
- if SELECTED_MICROPHONE_IDX.load(Ordering::SeqCst) != device_index
- ||
- FRAME_LENGTH.load(Ordering::SeqCst) != frame_length {
- warn!("Selected microphone or frame length was changed, re-initializing ...");
- // initialize again with new device index
- if IS_RECORDING.load(Ordering::SeqCst) {
- // RECORDER.get().unwrap().load().stop().expect("Failed to start audio recording!");
- stop_recording();
- }
-
- // store
- match create_stream(device_index, frame_length) {
- Ok(stream) => {
- // store new stream
- r.get().unwrap().store(Arc::new(Mutex::new(stream)));
-
- // remember new configuration
- SELECTED_MICROPHONE_IDX.store(device_index, Ordering::SeqCst);
- FRAME_LENGTH.store(frame_length, Ordering::SeqCst);
-
- // success
- return true
- },
- Err(msg) => {
- error!("Failed to initialize portaudio.\nError details: {:?}", msg);
-
- // fail
- return false
- }
- }
- }
-
- // success
- true
- }
- }
- })
-}
-
-fn create_stream(device_index: i32, frame_length: u32) -> Result, pa::Input>, pa::Error> {
- let pa_recorder: Result = pa::PortAudio::new();
-
- match pa_recorder {
- Ok(pa) => {
- let input_settings = match get_input_settings(DeviceIndex(device_index as u32), &pa, SAMPLE_RATE, frame_length, CHANNELS) {
- Ok(settings) => settings,
- Err(error) => panic!("{}", String::from(error))
- };
-
- // Construct a stream with input and output sample types of i16
- match pa.open_blocking_stream(input_settings) {
- Ok(strm) => Ok(strm),
- Err(error) => panic!("{}", error.to_string()),
- }
- },
- Err(msg) => Err(msg)
- }
-}
-
-fn get_input_latency(audio_port: &pa::PortAudio, input_index: pa::DeviceIndex) -> Result
-{
- let input_device_information = audio_port.device_info(input_index).or_else(|error| Err(String::from(format!("{}", error))));
- Ok(input_device_information.unwrap().default_low_input_latency)
-}
-
-fn get_input_stream_parameters(input_index: pa::DeviceIndex, latency: f64, channels: i32) -> Result, String>
-{
- const INTERLEAVED: bool = true;
- Ok(pa::StreamParameters::::new(input_index, channels, INTERLEAVED, latency))
-}
-
-fn get_input_settings(input_index: pa::DeviceIndex, audio_port: &pa::PortAudio, sample_rate: f64, frames: u32, channels: i32) -> Result, String>
-{
- Ok(
- pa::InputStreamSettings::new(
- (get_input_stream_parameters(
- input_index,
- (get_input_latency(
- &audio_port,
- input_index,
- ))?,
- channels
- ))?,
- sample_rate,
- frames,
- )
- )
-}
-
-// We'll use this function to wait for read/write availability.
-fn wait_for_stream(f: F, name: &str) -> u32
-where
- F: Fn() -> Result,
-{
- loop {
- match f() {
- Ok(available) => match available {
- pa::StreamAvailable::Frames(frames) => return frames as u32,
- pa::StreamAvailable::InputOverflowed => println!("Input stream has overflowed"),
- pa::StreamAvailable::OutputUnderflowed => {
- println!("Output stream has underflowed")
- }
- },
- Err(err) => panic!(
- "An error occurred while waiting for the {} stream: {}",
- name, err
- ),
- }
- }
-}
-
-pub fn read_microphone(frame_buffer: &mut [i16]) {
- // ensure microphone is initialized
- RECORDER.with(|r| {
- if r.get().is_some() {
- let cell = r.get().unwrap().load();
- let mut lock = cell.lock();
- let stream = lock.as_mut().unwrap();
-
- // read to frame buffer
- let in_frames = wait_for_stream(|| stream.read_available(), "Read");
-
- if in_frames > 0 {
- // let input_samples = stream.read(in_frames).expect("Cannot read frames ...");
- // println!("Read {:?} frames from the input stream.", in_frames);
-
- let input_samples = stream.read(in_frames).expect("Cannot read frames ...");
- println!("Read: {} (required {})", input_samples.len(), frame_buffer.len());
- frame_buffer.copy_from_slice(input_samples.chunks(frame_buffer.len()).last().unwrap());
- }
- // r.get().unwrap().load().read(frame_buffer).expect("Failed to read audio frame");
- }
- });
-}
-
-pub fn start_recording(device_index: i32, frame_length: u32) {
- // ensure microphone is initialized
- init_microphone(device_index, frame_length);
-
- // start recording
- RECORDER.with(|r| {
- r.get().unwrap().load().lock().unwrap().start().expect("Failed to start audio recording!");
- IS_RECORDING.store(true, Ordering::SeqCst);
- info!("START recording from microphone ...");
- });
-}
-
-pub fn stop_recording() {
- RECORDER.with(|r| {
- if r.get().is_some() && IS_RECORDING.load(Ordering::SeqCst) {
- // stop recording
- let pa = r.get().unwrap().load();
- r.get().unwrap().load().lock().unwrap().stop().expect("Failed to stop audio recording!");
- IS_RECORDING.store(false, Ordering::SeqCst);
- info!("STOP recording from microphone ...");
- }
- });
-}
\ No newline at end of file
diff --git a/crates/jarvis-core/src/recorder/pvrecorder.rs b/crates/jarvis-core/src/recorder/pvrecorder.rs
deleted file mode 100644
index 276c08f..0000000
--- a/crates/jarvis-core/src/recorder/pvrecorder.rs
+++ /dev/null
@@ -1,136 +0,0 @@
-use once_cell::sync::OnceCell;
-use pv_recorder::{PvRecorder, PvRecorderBuilder};
-use std::sync::atomic::{AtomicBool, Ordering};
-
-static RECORDER: OnceCell = OnceCell::new();
-static IS_RECORDING: AtomicBool = AtomicBool::new(false);
-
-pub fn init_microphone(device_index: i32, frame_length: u32) -> bool {
- if RECORDER.get().is_some() {
- return true; // already initialized
- }
-
- // initialize
- let pv_recorder = PvRecorderBuilder::new(frame_length as i32)
- .device_index(device_index)
- // .frame_length(frame_length as i32)
- .init();
-
- match pv_recorder {
- Ok(pv) => {
- // store
- let _ = RECORDER.set(pv);
-
- // success
- true
- }
- Err(msg) => {
- error!("Failed to initialize pvrecorder.\nError details: {:?}", msg);
-
- // fail
- false
- }
- }
-}
-
-pub fn read_microphone(frame_buffer: &mut [i16]) {
- // ensure microphone is initialized
- if RECORDER.get().is_some() {
- // read to frame buffer
-
- let frame = RECORDER.get().unwrap().read();
-
- match frame {
- Ok(f) => {
- frame_buffer.copy_from_slice(f.as_slice());
- }
- Err(msg) => {
- // @TODO: Fix? PvRecorder always wait for PCM buffer size of 512.
- error!("Failed to read audio frame. {:?}", msg);
- }
- }
- }
-}
-
-pub fn start_recording(device_index: i32, frame_length: u32) -> Result<(), ()> {
- // ensure microphone is initialized
- init_microphone(device_index, frame_length);
-
- // start recording
- match RECORDER.get().unwrap().start() {
- Ok(_) => {
- info!("START recording from microphone ...");
-
- // change recording state
- IS_RECORDING.store(true, Ordering::SeqCst);
-
- // success
- Ok(())
- }
- Err(msg) => {
- error!("Failed to START audio recording: {}", msg);
-
- // fail
- Err(())
- }
- }
-}
-
-pub fn stop_recording() -> Result<(), ()> {
- // ensure microphone is initialized & recording is in process
- if RECORDER.get().is_some() && IS_RECORDING.load(Ordering::SeqCst) {
- // stop recording
- match RECORDER.get().unwrap().stop() {
- Ok(_) => {
- info!("STOP recording from microphone ...");
-
- // change recording state
- IS_RECORDING.store(false, Ordering::SeqCst);
-
- // success
- return Ok(());
- }
- Err(msg) => {
- error!("Failed to STOP audio recording: {}", msg);
-
- // fail
- return Err(());
- }
- }
- }
-
- Ok(()) // if already stopped or not yet initialized
-}
-
-pub fn list_audio_devices() -> Vec {
- let audio_devices = PvRecorderBuilder::default().get_available_devices();
- match audio_devices {
- Ok(audio_devices) => audio_devices,
- Err(err) => {
- error!("Failed to get audio devices: {}", err);
- Vec::new()
- },
- }
-}
-
-pub fn get_audio_device_name(idx: i32) -> String {
- if idx == -1 {
- return String::from("System Default");
- }
-
- let audio_devices = list_audio_devices();
- let mut first_device: String = String::new();
-
- for (_idx, device) in audio_devices.iter().enumerate() {
- if idx as usize == _idx {
- return device.to_string();
- }
-
- if _idx == 0 {
- first_device = device.to_string()
- }
- }
-
- // return first device as default, if none were matched
- first_device
-}
diff --git a/crates/jarvis-core/src/slots.rs b/crates/jarvis-core/src/slots.rs
deleted file mode 100644
index 4082eb2..0000000
--- a/crates/jarvis-core/src/slots.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-mod gliner;
-
-use std::collections::HashMap;
-use once_cell::sync::OnceCell;
-
-use crate::commands::{SlotDefinition, SlotValue};
-use crate::{models, DB};
-
-static BACKEND: OnceCell = OnceCell::new();
-
-pub fn init() -> Result<(), String> {
- if BACKEND.get().is_some() {
- return Ok(());
- }
-
- let backend = DB.get()
- .map(|db| db.read().slots_backend.clone())
- .unwrap_or_else(|| "none".to_string());
-
- BACKEND.set(backend.clone()).map_err(|_| "Slot backend already set")?;
-
- match backend.as_str() {
- "none" => {
- info!("Slot extraction disabled");
- }
- // any model ID is treated as a GLiNER model for now
- model_id => {
- info!("Initializing GLiNER slot extraction with model '{}'.", model_id);
- let model = models::gliner::load(models::registry(), model_id)?;
- gliner::init_with_model(model)?;
- info!("GLiNER slot extraction initialized.");
- }
- }
-
- Ok(())
-}
-
-pub fn extract(
- text: &str,
- slots: &HashMap,
-) -> HashMap {
- if slots.is_empty() {
- return HashMap::new();
- }
-
- match BACKEND.get().map(|s| s.as_str()).unwrap_or("none") {
- "none" => HashMap::new(),
- _ => {
- match gliner::extract(text, slots) {
- Ok(result) => result,
- Err(e) => {
- error!("GLiNER slot extraction failed: {}", e);
- HashMap::new()
- }
- }
- }
- }
-}
diff --git a/crates/jarvis-core/src/slots/gliner.rs b/crates/jarvis-core/src/slots/gliner.rs
deleted file mode 100644
index d4b68aa..0000000
--- a/crates/jarvis-core/src/slots/gliner.rs
+++ /dev/null
@@ -1,395 +0,0 @@
-// BASED ON: gline-rs crate source code
-// https://github.com/fbilhaut/gline-rs
-
-use std::collections::HashMap;
-use std::sync::Arc;
-use once_cell::sync::OnceCell;
-use ndarray::Array;
-use ort::value::Tensor;
-
-use crate::commands::{SlotDefinition, SlotValue};
-use crate::models::gliner::GlinerModel;
-
-static MODEL: OnceCell> = OnceCell::new();
-
-// GLiNER defaults
-const THRESHOLD: f32 = 0.3;
-const MAX_WIDTH: usize = 12;
-const MAX_LENGTH: usize = 512;
-const MIN_CONFIDENCE: f32 = 0.4;
-
-pub fn init_with_model(model: Arc) -> Result<(), String> {
- MODEL.set(model).map_err(|_| "GLiNER model already initialized".to_string())?;
- info!("GLiNER slot extraction ready");
- Ok(())
-}
-
-// word splitting
-
-struct WordToken<'a> {
- start: usize,
- end: usize,
- text: &'a str,
-}
-
-fn split_words<'a>(text: &'a str, model: &GlinerModel, limit: Option) -> Vec> {
- let mut tokens = Vec::new();
- for m in model.splitter.find_iter(text) {
- tokens.push(WordToken {
- start: m.start(),
- end: m.end(),
- text: m.as_str(),
- });
- if let Some(lim) = limit {
- if tokens.len() >= lim { break; }
- }
- }
- tokens
-}
-
-// prompt construction
-//
-// GLiNER prompt format:
-// [<>, label1_w1, label1_w2, <