From ef9e8e852d5442f19c3faa1505ced59f5f4cce48 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:54:34 -0500 Subject: [PATCH 01/18] initial commit --- .gitignore | 3 +- run.sh | 11 + vui/.env.example | 2 + vui/Cargo.lock | 5502 +++++++++++++++++++++++++++++++++++++ vui/Cargo.toml | 14 + vui/src/api.rs | 2 + vui/src/api/claude.rs | 90 + vui/src/api/elevenlabs.rs | 121 + vui/src/audio.rs | 5 + vui/src/audio/capture.rs | 68 + vui/src/audio/playback.rs | 81 + vui/src/main.rs | 340 +++ 12 files changed, 6238 insertions(+), 1 deletion(-) create mode 100755 run.sh create mode 100644 vui/.env.example create mode 100644 vui/Cargo.lock create mode 100644 vui/Cargo.toml create mode 100644 vui/src/api.rs create mode 100644 vui/src/api/claude.rs create mode 100644 vui/src/api/elevenlabs.rs create mode 100644 vui/src/audio.rs create mode 100644 vui/src/audio/capture.rs create mode 100644 vui/src/audio/playback.rs create mode 100644 vui/src/main.rs diff --git a/.gitignore b/.gitignore index 7cfd02b..d1cb659 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store *.log -node_modules/ +.env +vui/target diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..962f58e --- /dev/null +++ b/run.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +if ! command -v cargo &> /dev/null; then + echo "Rust not installed. Install from https://rustup.rs" + exit 1 +fi + +cd vui +cargo build --release +./target/release/vui diff --git a/vui/.env.example b/vui/.env.example new file mode 100644 index 0000000..cdc9e38 --- /dev/null +++ b/vui/.env.example @@ -0,0 +1,2 @@ +ELEVENLABS_API_KEY=your_elevenlabs_api_key_here +ANTHROPIC_API_KEY=your_anthropic_api_key_here diff --git a/vui/Cargo.lock b/vui/Cargo.lock new file mode 100644 index 0000000..48cdd56 --- /dev/null +++ b/vui/Cargo.lock @@ -0,0 +1,5502 @@ +# 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 = "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", + "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 = "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" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c88dbbce13b232b26250e1e2e6ac18b6a891a646b8148285036ebce260ac5c3" +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 0.9.0", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-build" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cac4c64175d504608cf239756339c07f6384a476f97f20a7043f92920b0b8fd" +dependencies = [ + "windows-sys 0.52.0", +] + +[[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 = "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 = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.3", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.3", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.3", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[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" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[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 = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +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", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[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" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" +dependencies = [ + "bitflags 2.10.0", + "polling", + "rustix 1.1.3", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.3", + "rustix 1.1.3", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +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 = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[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 = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "clipboard_macos" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" +dependencies = [ + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "clipboard_wayland" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8" +dependencies = [ + "smithay-clipboard", +] + +[[package]] +name = "clipboard_x11" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd63e33452ffdafd39924c4f05a5dd1e94db646c779c6bd59148a3d95fff5ad4" +dependencies = [ + "thiserror 2.0.18", + "x11rb", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[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 = "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", + "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 = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "coreaudio-rs" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "coreaudio-sys", +] + +[[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 = "coreaudio-sys" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cosmic-text" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cadaea21e24c49c0c82116f2b465ae6a49d63c90e428b0f8d9ae1f638ac91f" +dependencies = [ + "bitflags 2.10.0", + "fontdb", + "harfrust", + "linebender_resource_handle", + "log", + "rangemap", + "rustc-hash 1.1.0", + "self_cell", + "skrifa 0.39.0", + "smol_str", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa 0.9.1", + "core-foundation-sys", + "coreaudio-rs 0.11.3", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2 0.4.3", + "ndk 0.8.0", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", +] + +[[package]] +name = "cpal" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1f9c7312f19fc2fa12fd7acaf38de54e8320ba10d1a02dcbe21038def51ccb" +dependencies = [ + "alsa 0.10.0", + "coreaudio-rs 0.13.0", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2 0.5.0", + "ndk 0.9.0", + "ndk-context", + "num-derive", + "num-traits", + "objc2 0.6.3", + "objc2-audio-toolbox", + "objc2-avf-audio", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.62.2", +] + +[[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 = "cryoglyph" +version = "0.1.0" +source = "git+https://github.com/iced-rs/cryoglyph.git?rev=3836ca7a17f410d30871c2254dcc0f2400876636#3836ca7a17f410d30871c2254dcc0f2400876636" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.1.1", + "wgpu", +] + +[[package]] +name = "ctor-lite" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b29fccfdaeb0f9bd90da5759b1d0eaa2f6cfcfe90960124cfc83141ed4e111fd" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[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", + "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", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[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.1" +source = "git+https://github.com/iced-rs/winit.git?rev=05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed#05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[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 = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[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 = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[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", +] + +[[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", +] + +[[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 = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[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", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[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", +] + +[[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-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[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", +] + +[[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", + "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 = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.18", + "windows 0.62.2", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.10.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[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", + "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 = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "harfrust" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0caaee032384c10dd597af4579c67dee16650d862a9ccbe1233ff1a379abc07" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "core_maths", + "read-fonts 0.36.0", + "smallvec", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[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 = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + +[[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 = "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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iced" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "iced_core", + "iced_debug", + "iced_futures", + "iced_renderer", + "iced_runtime", + "iced_widget", + "iced_winit", + "thiserror 2.0.18", +] + +[[package]] +name = "iced_core" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "glam", + "lilt", + "log", + "num-traits", + "rustc-hash 2.1.1", + "smol_str", + "thiserror 2.0.18", + "web-time", +] + +[[package]] +name = "iced_debug" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "iced_core", + "iced_futures", + "log", +] + +[[package]] +name = "iced_futures" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "futures", + "iced_core", + "log", + "rustc-hash 2.1.1", + "tokio", + "wasm-bindgen-futures", + "wasmtimer", +] + +[[package]] +name = "iced_graphics" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "cosmic-text", + "half", + "iced_core", + "iced_futures", + "log", + "lyon_path", + "raw-window-handle", + "rustc-hash 2.1.1", + "thiserror 2.0.18", + "unicode-segmentation", +] + +[[package]] +name = "iced_program" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "iced_graphics", + "iced_runtime", +] + +[[package]] +name = "iced_renderer" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "iced_graphics", + "iced_tiny_skia", + "iced_wgpu", + "log", + "thiserror 2.0.18", +] + +[[package]] +name = "iced_runtime" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "bytes", + "iced_core", + "iced_futures", + "raw-window-handle", + "thiserror 2.0.18", +] + +[[package]] +name = "iced_tiny_skia" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "bytemuck", + "cosmic-text", + "iced_debug", + "iced_graphics", + "kurbo", + "log", + "rustc-hash 2.1.1", + "softbuffer", + "tiny-skia", +] + +[[package]] +name = "iced_wgpu" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "cryoglyph", + "futures", + "glam", + "guillotiere", + "iced_debug", + "iced_graphics", + "log", + "lyon", + "rustc-hash 2.1.1", + "thiserror 2.0.18", + "wgpu", +] + +[[package]] +name = "iced_widget" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "iced_renderer", + "log", + "num-traits", + "rustc-hash 2.1.1", + "thiserror 2.0.18", + "unicode-segmentation", +] + +[[package]] +name = "iced_winit" +version = "0.15.0-dev" +source = "git+https://github.com/iced-rs/iced#1463ec84902c6939b59c461f7d74622d6df9b4b3" +dependencies = [ + "iced_debug", + "iced_program", + "log", + "mundy", + "rustc-hash 2.1.1", + "thiserror 2.0.18", + "tracing", + "wasm-bindgen-futures", + "web-sys", + "window_clipboard", + "winit", +] + +[[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", + "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", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[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 = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[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 = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[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 = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[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 = "lilt" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67562e5eff6b20553fa9be1c503356768420994e28f67e3eafe6f41910e57ad" +dependencies = [ + "web-time", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[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 = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[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" + +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lyon" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb7d54d54c8937364c9d41902d066656817dce1e03a44e5533afebd1ef4352" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c0829e28c4f336396f250d850c3987e16ce6db057ffe047ce0dd54aab6b647" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e260b6de923e6e47adfedf6243013a7a874684165a6a277594ee3906021b2343" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aeca86bcfd632a15984ba029b539ffb811e0a70bf55e814ef8b0f54f506fdeb" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f586142e1280335b1bc89539f7c97dd80f08fc43e9ab1b74ef0a42b04aa353" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[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", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" +dependencies = [ + "bitflags 2.10.0", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mundy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523813c9e194ec43693805214eb112551f99382115b67f38600d724a692e7e8b" +dependencies = [ + "android-build", + "async-io", + "cfg-if", + "dispatch", + "futures-channel", + "futures-lite", + "jni", + "ndk-context", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "pin-project-lite", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.62.2", + "zbus", +] + +[[package]] +name = "naga" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.18", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "thiserror 1.0.69", +] + +[[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 0.6.0+11769913", + "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.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[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 = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[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", +] + +[[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", + "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", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[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", +] + +[[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-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "objc2 0.6.3", + "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", + "objc2-foundation 0.3.2", +] + +[[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", + "block2 0.6.2", + "dispatch2", + "libc", + "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-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-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-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-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-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 = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk 0.8.0", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[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 = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[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 = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[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", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[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", +] + +[[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 = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[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", + "pin-project-lite", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +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 = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[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 2.1.1", + "rustls", + "socket2", + "thiserror 2.0.18", + "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", + "ring", + "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +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.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[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 = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "read-fonts" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "read-fonts" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eaa2941a4c05443ee3a7b26ab076a553c343ad5995230cc2b1d3e993bdc6345" +dependencies = [ + "bytemuck", + "core_maths", + "font-types", +] + +[[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 = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "reqwest" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "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 = "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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb" +dependencies = [ + "claxon", + "cpal 0.15.3", + "hound", + "lewton", + "symphonia", + "thiserror 1.0.69", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[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", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[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", + "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 = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[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 = "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 0.19.2", + "tiny-skia", +] + +[[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 = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + +[[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_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", +] + +[[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", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[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 = "skrifa" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" +dependencies = [ + "bytemuck", + "read-fonts 0.35.0", +] + +[[package]] +name = "skrifa" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9eb0b904a04d09bd68c65d946617b8ff733009999050f3b851c32fb3cfb60e" +dependencies = [ + "bytemuck", + "read-fonts 0.36.0", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[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 0.13.0", + "calloop-wayland-source 0.3.0", + "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 = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.10.0", + "calloop 0.14.3", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.3", + "thiserror 2.0.18", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "fastrand", + "js-sys", + "memmap2", + "ndk 0.9.0", + "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", + "rustix 1.1.3", + "tiny-xlib", + "tracing", + "wasm-bindgen", + "wayland-backend", + "wayland-client", + "wayland-sys", + "web-sys", + "windows-sys 0.61.2", + "x11rb", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.10.0", +] + +[[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 = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "swash" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47846491253e976bdd07d0f9cc24b7daf24720d11309302ccbbc6e6b6e53550a" +dependencies = [ + "skrifa 0.37.0", + "yazi", + "zeno", +] + +[[package]] +name = "symphonia" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" +dependencies = [ + "lazy_static", + "symphonia-bundle-mp3", + "symphonia-core", + "symphonia-metadata", +] + +[[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-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-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 = "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", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +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 = "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 = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[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", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "tiny-xlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +dependencies = [ + "as-raw-xcb-connection", + "ctor-lite", + "libloading", + "pkg-config", + "tracing", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "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 = "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", +] + +[[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-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_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.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[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", +] + +[[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 = [ + "log", + "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", +] + +[[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 = "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" +dependencies = [ + "core_maths", +] + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[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", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vui" +version = "0.1.0" +dependencies = [ + "cpal 0.17.1", + "dotenvy", + "iced", + "reqwest", + "rodio", + "serde", + "serde_json", + "tokio", +] + +[[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.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", + "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 = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + +[[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-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791c58fdeec5406aa37169dd815327d1e47f334219b523444bc26d70ceb4c34e" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "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 = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wgpu" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f" +dependencies = [ + "arrayvec", + "bitflags 2.10.0", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb4c8b5db5f00e56f1f08869d870a0dff7c8bc7ebc01091fec140b0cf0211a9" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.10.0", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.18", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "293080d77fdd14d6b08a67c5487dfddbf874534bb7921526db56a7b75d7e3bef" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.10.0", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types 0.2.0", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys 0.6.0+11769913", + "objc", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.18", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "wgpu-types" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "js-sys", + "log", + "web-sys", +] + +[[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_clipboard" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5654226305eaf2dde8853fb482861d28e5dcecbbd40cb88e8393d94bb80d733" +dependencies = [ + "clipboard-win", + "clipboard_macos", + "clipboard_wayland", + "clipboard_x11", + "raw-window-handle", + "thiserror 2.0.18", +] + +[[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.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.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.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "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", +] + +[[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", +] + +[[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.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + +[[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.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[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", +] + +[[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", + "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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[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.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.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.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.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.8" +source = "git+https://github.com/iced-rs/winit.git?rev=05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed#05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.10.0", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk 0.9.0", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit 0.19.2", + "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.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[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 = "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", + "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 = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[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", + "zerofrom", +] + +[[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", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.3", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +dependencies = [ + "serde", + "winnow", + "zvariant", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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", + "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", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "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", +] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" + +[[package]] +name = "zvariant" +version = "5.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", + "winnow", +] diff --git a/vui/Cargo.toml b/vui/Cargo.toml new file mode 100644 index 0000000..f0de570 --- /dev/null +++ b/vui/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "vui" +version = "0.1.0" +edition = "2024" + +[dependencies] +cpal = "0.17.1" +dotenvy = "0.15.7" +iced = { git = "https://github.com/iced-rs/iced", version = "0.15.0-dev", features = ["canvas", "tokio"] } +reqwest = { version = "0.13.1", features = ["json", "multipart"] } +rodio = "0.19" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +tokio = { version = "1.49.0", features = ["full"] } diff --git a/vui/src/api.rs b/vui/src/api.rs new file mode 100644 index 0000000..1580798 --- /dev/null +++ b/vui/src/api.rs @@ -0,0 +1,2 @@ +pub mod claude; +pub mod elevenlabs; diff --git a/vui/src/api/claude.rs b/vui/src/api/claude.rs new file mode 100644 index 0000000..69d3519 --- /dev/null +++ b/vui/src/api/claude.rs @@ -0,0 +1,90 @@ +use serde::{Deserialize, Serialize}; + +pub struct Client { + api_key: String, + http: reqwest::Client, +} + +#[derive(Serialize)] +struct Request { + model: String, + max_tokens: u32, + system: String, + messages: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct Message { + pub role: String, + pub content: String, +} + +#[derive(Deserialize)] +struct Response { + content: Vec, +} + +#[derive(Deserialize)] +struct Content { + text: String, +} + +const SYSTEM: &str = include_str!("../../../prompt.txt"); + +impl Client { + pub fn new(api_key: String) -> Self { + let http = reqwest::Client::new(); + Self { api_key, http } + } + + pub async fn send(&self, messages: &[Message]) -> Result { + let request = Request { + model: "claude-sonnet-4-20250514".to_string(), + max_tokens: 256, + system: SYSTEM.to_string(), + messages: messages.to_vec(), + }; + + let res = self + .http + .post("https://api.anthropic.com/v1/messages") + .header("x-api-key", &self.api_key) + .header("anthropic-version", "2023-06-01") + .header("Content-Type", "application/json") + .json(&request) + .send() + .await + .map_err(|e| e.to_string())?; + + if !res.status().is_success() { + let error_text = res.text().await.unwrap_or_default(); + return Err(format!("Claude error: {}", error_text)); + } + + let response: Response = res.json().await.map_err(|e| e.to_string())?; + + let text = response + .content + .first() + .map(|b| b.text.clone()) + .unwrap_or_default(); + + Ok(text) + } +} + +impl Message { + pub fn user(content: &str) -> Self { + Self { + role: "user".to_string(), + content: content.to_string(), + } + } + + pub fn assistant(content: &str) -> Self { + Self { + role: "assistant".to_string(), + content: content.to_string(), + } + } +} diff --git a/vui/src/api/elevenlabs.rs b/vui/src/api/elevenlabs.rs new file mode 100644 index 0000000..16515d2 --- /dev/null +++ b/vui/src/api/elevenlabs.rs @@ -0,0 +1,121 @@ +use serde::Deserialize; + +pub struct Client { + api_key: String, + http: reqwest::Client, + voice_id: String, +} + +#[derive(Deserialize)] +struct TranscriptionResponse { + text: String, +} + +impl Client { + pub fn new(api_key: String) -> Self { + let http = reqwest::Client::new(); + Self { + api_key, + http, + voice_id: "21m00Tcm4TlvDq8ikWAM".to_string(), // Rachel + } + } + + pub async fn synthesize(&self, text: &str) -> Result, String> { + let url = format!( + "https://api.elevenlabs.io/v1/text-to-speech/{}", + self.voice_id + ); + + let body = serde_json::json!({ + "text": text, + "model_id": "eleven_monolingual_v1", + "voice_settings": { + "stability": 0.5, + "similarity_boost": 0.75 + } + }); + + let res = self + .http + .post(&url) + .header("xi-api-key", &self.api_key) + .header("Content-Type", "application/json") + .header("Accept", "audio/mpeg") + .json(&body) + .send() + .await + .map_err(|e| e.to_string())?; + + if !res.status().is_success() { + let error_text = res.text().await.unwrap_or_default(); + return Err(format!("ElevenLabs TTS error: {}", error_text)); + } + + let bytes = res.bytes().await.map_err(|e| e.to_string())?; + Ok(bytes.to_vec()) + } + + pub async fn transcribe(&self, audio_samples: &[f32], sample_rate: u32) -> Result { + let wav_data = encode_wav(audio_samples, sample_rate); + + let part = reqwest::multipart::Part::bytes(wav_data) + .file_name("audio.wav") + .mime_str("audio/wav") + .map_err(|e| e.to_string())?; + + let form = reqwest::multipart::Form::new() + .part("audio", part) + .text("model_id", "scribe_v1"); + + let res = self + .http + .post("https://api.elevenlabs.io/v1/speech-to-text") + .header("xi-api-key", &self.api_key) + .multipart(form) + .send() + .await + .map_err(|e| e.to_string())?; + + if !res.status().is_success() { + let error_text = res.text().await.unwrap_or_default(); + return Err(format!("ElevenLabs STT error: {}", error_text)); + } + + let response: TranscriptionResponse = res.json().await.map_err(|e| e.to_string())?; + Ok(response.text) + } +} + +fn encode_wav(samples: &[f32], sample_rate: u32) -> Vec { + let num_channels: u16 = 1; + let bits_per_sample: u16 = 16; + let byte_rate = sample_rate * u32::from(num_channels) * u32::from(bits_per_sample) / 8; + let block_align = num_channels * bits_per_sample / 8; + let data_size = (samples.len() * 2) as u32; + + let mut wav = Vec::with_capacity(44 + samples.len() * 2); + + wav.extend_from_slice(b"RIFF"); + wav.extend_from_slice(&(36 + data_size).to_le_bytes()); + wav.extend_from_slice(b"WAVE"); + + wav.extend_from_slice(b"fmt "); + wav.extend_from_slice(&16u32.to_le_bytes()); + wav.extend_from_slice(&1u16.to_le_bytes()); + wav.extend_from_slice(&num_channels.to_le_bytes()); + wav.extend_from_slice(&sample_rate.to_le_bytes()); + wav.extend_from_slice(&byte_rate.to_le_bytes()); + wav.extend_from_slice(&block_align.to_le_bytes()); + wav.extend_from_slice(&bits_per_sample.to_le_bytes()); + + wav.extend_from_slice(b"data"); + wav.extend_from_slice(&data_size.to_le_bytes()); + + for sample in samples { + let s = (sample.clamp(-1.0, 1.0) * 32767.0) as i16; + wav.extend_from_slice(&s.to_le_bytes()); + } + + wav +} diff --git a/vui/src/audio.rs b/vui/src/audio.rs new file mode 100644 index 0000000..ce7c9ce --- /dev/null +++ b/vui/src/audio.rs @@ -0,0 +1,5 @@ +pub mod capture; +pub mod playback; + +pub use capture::Capture; +pub use playback::Playback; diff --git a/vui/src/audio/capture.rs b/vui/src/audio/capture.rs new file mode 100644 index 0000000..20ff4c2 --- /dev/null +++ b/vui/src/audio/capture.rs @@ -0,0 +1,68 @@ +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::Sample; +use std::sync::mpsc; + +pub struct Capture { + #[allow(dead_code)] + stream: cpal::Stream, + receiver: mpsc::Receiver>, +} + +impl Capture { + pub fn new() -> Result { + let host = cpal::default_host(); + let device = host + .default_input_device() + .ok_or("No input device available")?; + + let config = device + .default_input_config() + .map_err(|e| e.to_string())?; + + let (sender, receiver) = mpsc::channel(); + + let stream = match config.sample_format() { + cpal::SampleFormat::F32 => build_stream::(&device, &config.into(), sender), + cpal::SampleFormat::I16 => build_stream::(&device, &config.into(), sender), + cpal::SampleFormat::U16 => build_stream::(&device, &config.into(), sender), + _ => return Err("Unsupported sample format".to_string()), + } + .map_err(|e| e.to_string())?; + + stream.play().map_err(|e| e.to_string())?; + + Ok(Self { stream, receiver }) + } + + pub fn try_recv(&self) -> Option> { + self.receiver.try_recv().ok() + } + + pub fn sample_rate() -> u32 { + let host = cpal::default_host(); + host.default_input_device() + .and_then(|d| d.default_input_config().ok()) + .map(|c| c.sample_rate()) + .unwrap_or(16000) + } +} + +fn build_stream( + device: &cpal::Device, + config: &cpal::StreamConfig, + sender: mpsc::Sender>, +) -> Result +where + T: cpal::Sample + cpal::SizedSample, + f32: cpal::FromSample, +{ + device.build_input_stream( + config, + move |data: &[T], _: &cpal::InputCallbackInfo| { + let samples: Vec = data.iter().map(|s| Sample::from_sample(*s)).collect(); + let _ = sender.send(samples); + }, + |err| eprintln!("Audio capture error: {}", err), + None, + ) +} diff --git a/vui/src/audio/playback.rs b/vui/src/audio/playback.rs new file mode 100644 index 0000000..ff66f70 --- /dev/null +++ b/vui/src/audio/playback.rs @@ -0,0 +1,81 @@ +use rodio::{Decoder, OutputStream, Sink}; +use std::io::Cursor; +use std::sync::mpsc; +use std::thread; + +pub struct Playback { + sender: mpsc::Sender, + status_receiver: mpsc::Receiver, +} + +#[allow(dead_code)] +enum Command { + Play(Vec), + Stop, +} + +#[derive(Debug, Clone)] +pub enum Status { + Playing, + Finished, + Error(String), +} + +impl Playback { + pub fn new() -> Result { + let (sender, receiver) = mpsc::channel::(); + let (status_sender, status_receiver) = mpsc::channel(); + + thread::spawn(move || { + let (_stream, stream_handle) = match OutputStream::try_default() { + Ok(s) => s, + Err(e) => { + let _ = status_sender.send(Status::Error(e.to_string())); + return; + } + }; + + let sink = Sink::try_new(&stream_handle).unwrap(); + + while let Ok(cmd) = receiver.recv() { + match cmd { + Command::Play(data) => { + let cursor = Cursor::new(data); + match Decoder::new(cursor) { + Ok(source) => { + let _ = status_sender.send(Status::Playing); + sink.append(source); + sink.sleep_until_end(); + let _ = status_sender.send(Status::Finished); + } + Err(e) => { + let _ = status_sender.send(Status::Error(e.to_string())); + } + } + } + Command::Stop => { + sink.stop(); + } + } + } + }); + + Ok(Self { + sender, + status_receiver, + }) + } + + pub fn play(&self, audio_data: Vec) { + let _ = self.sender.send(Command::Play(audio_data)); + } + + #[allow(dead_code)] + pub fn stop(&self) { + let _ = self.sender.send(Command::Stop); + } + + pub fn try_recv_status(&self) -> Option { + self.status_receiver.try_recv().ok() + } +} diff --git a/vui/src/main.rs b/vui/src/main.rs new file mode 100644 index 0000000..614b363 --- /dev/null +++ b/vui/src/main.rs @@ -0,0 +1,340 @@ +mod api; +mod audio; + +use api::claude::Message as ChatMessage; +use iced::mouse; +use iced::time::{self, milliseconds}; +use iced::widget::{canvas, center, column, container, text}; +use iced::{Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme}; +use std::sync::Arc; +use std::time::Instant; + +fn main() -> iced::Result { + dotenvy::dotenv().ok(); + + iced::application(App::new, App::update, App::view) + .subscription(App::subscription) + .theme(App::theme) + .run() +} + +#[derive(Debug, Clone)] +enum Message { + Tick, + TtsReady(Result, String>), + TtsFinished, + TranscriptionReady(Result), + ClaudeReady(Result), + StartListening, + StopListening, +} + +#[allow(dead_code)] +#[derive(Debug, Clone)] +enum State { + Idle, + Speaking { text: String }, + Listening, + Processing, + Done, +} + +struct App { + state: State, + start: Instant, + cache: canvas::Cache, + messages: Vec, + subtitle: String, + elevenlabs: Option>, + chat: Option>, + playback: Option, + capture: Option, + audio_buffer: Vec, + silence_frames: usize, +} + +impl App { + fn new() -> (Self, Task) { + let elevenlabs_key = std::env::var("ELEVENLABS_API_KEY").unwrap_or_default(); + let anthropic_key = std::env::var("ANTHROPIC_API_KEY").unwrap_or_default(); + + if elevenlabs_key.is_empty() { + eprintln!("WARNING: ELEVENLABS_API_KEY not set"); + } + if anthropic_key.is_empty() { + eprintln!("WARNING: ANTHROPIC_API_KEY not set"); + } + + let elevenlabs = if !elevenlabs_key.is_empty() { + Some(Arc::new(api::elevenlabs::Client::new(elevenlabs_key))) + } else { + None + }; + + let chat = if !anthropic_key.is_empty() { + Some(Arc::new(api::claude::Client::new(anthropic_key))) + } else { + None + }; + + let playback = audio::Playback::new().ok(); + + let app = Self { + state: State::Idle, + start: Instant::now(), + cache: canvas::Cache::default(), + messages: Vec::new(), + subtitle: "Starting...".to_string(), + elevenlabs, + chat, + playback, + capture: None, + audio_buffer: Vec::new(), + silence_frames: 0, + }; + + // Get initial greeting from Claude + let task = if let Some(chat) = &app.chat { + let chat = chat.clone(); + Task::perform( + async move { chat.send(&[]).await }, + Message::ClaudeReady, + ) + } else { + Task::none() + }; + + (app, task) + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::Tick => { + self.cache.clear(); + + // Check playback status + if let Some(ref playback) = self.playback + && let Some(status) = playback.try_recv_status() + { + match status { + audio::playback::Status::Finished => { + return Task::done(Message::TtsFinished); + } + audio::playback::Status::Error(e) => { + eprintln!("Playback error: {}", e); + return Task::done(Message::TtsFinished); + } + _ => {} + } + } + + // Check for audio input while listening + if matches!(self.state, State::Listening) + && let Some(ref capture) = self.capture + { + while let Some(samples) = capture.try_recv() { + let rms: f32 = (samples.iter().map(|s| s * s).sum::() + / samples.len() as f32) + .sqrt(); + + if rms < 0.01 { + self.silence_frames += 1; + } else { + self.silence_frames = 0; + } + + self.audio_buffer.extend(samples); + + // Stop after ~1.5 seconds of silence + if self.audio_buffer.len() > 16000 && self.silence_frames > 90 { + return Task::done(Message::StopListening); + } + } + } + + Task::none() + } + + Message::ClaudeReady(result) => match result { + Ok(response) => { + // Check if conversation is complete + let is_complete = response.contains("APPLICATION_COMPLETE"); + let display_text = response.replace("APPLICATION_COMPLETE", "").trim().to_string(); + + self.messages.push(ChatMessage::assistant(&display_text)); + self.subtitle = display_text.clone(); + + if is_complete { + self.state = State::Done; + } + + // Speak the response + if let Some(tts) = &self.elevenlabs { + let tts = tts.clone(); + Task::perform( + async move { tts.synthesize(&display_text).await }, + Message::TtsReady, + ) + } else { + Task::none() + } + } + Err(e) => { + eprintln!("Claude error: {}", e); + self.subtitle = format!("Error: {}", e); + self.state = State::Done; + Task::none() + } + }, + + Message::TtsReady(result) => match result { + Ok(audio_data) => { + if let Some(ref playback) = self.playback { + playback.play(audio_data); + self.state = State::Speaking { + text: self.subtitle.clone(), + }; + } + Task::none() + } + Err(e) => { + eprintln!("TTS error: {}", e); + self.subtitle = format!("Error: {}", e); + self.state = State::Done; + Task::none() + } + }, + + Message::TtsFinished => { + if matches!(self.state, State::Done) { + Task::none() + } else { + Task::done(Message::StartListening) + } + } + + Message::StartListening => { + self.state = State::Listening; + self.subtitle = "Listening...".to_string(); + self.audio_buffer.clear(); + self.silence_frames = 0; + self.capture = audio::Capture::new().ok(); + Task::none() + } + + Message::StopListening => { + self.capture = None; + self.state = State::Processing; + self.subtitle = "Processing...".to_string(); + + let buffer = std::mem::take(&mut self.audio_buffer); + let sample_rate = audio::Capture::sample_rate(); + + if let Some(stt) = &self.elevenlabs { + let stt = stt.clone(); + Task::perform( + async move { stt.transcribe(&buffer, sample_rate).await }, + Message::TranscriptionReady, + ) + } else { + Task::done(Message::TranscriptionReady(Ok("test response".to_string()))) + } + } + + Message::TranscriptionReady(result) => match result { + Ok(transcript) => { + eprintln!("User said: {}", transcript); + self.messages.push(ChatMessage::user(&transcript)); + self.subtitle = format!("You: {}", transcript); + + if let Some(chat) = &self.chat { + let chat = chat.clone(); + let messages = self.messages.clone(); + Task::perform( + async move { chat.send(&messages).await }, + Message::ClaudeReady, + ) + } else { + Task::none() + } + } + Err(e) => { + eprintln!("Transcription error: {}", e); + self.subtitle = format!("Error: {}", e); + Task::done(Message::StartListening) + } + }, + } + } + + fn view(&self) -> Element<'_, Message> { + let circle = canvas(self as &Self).width(Fill).height(Fill); + + let subtitle = text(&self.subtitle).size(24).color(Color::WHITE); + + let content = column![ + container(circle).width(400).height(400), + container(subtitle).padding(20), + ] + .align_x(iced::Alignment::Center); + + center(content).into() + } + + fn subscription(&self) -> Subscription { + time::every(milliseconds(16)).map(|_| Message::Tick) + } + + fn theme(&self) -> Theme { + Theme::Dark + } +} + +impl canvas::Program for App { + type State = (); + + fn draw( + &self, + _state: &Self::State, + renderer: &Renderer, + _theme: &Theme, + bounds: Rectangle, + _cursor: mouse::Cursor, + ) -> Vec { + let circle = self.cache.draw(renderer, bounds.size(), |frame| { + let center = frame.center(); + let t = self.start.elapsed().as_secs_f32(); + + let pulse = match &self.state { + State::Speaking { .. } => 25.0 * (t * 5.0).sin().abs(), + State::Listening => 15.0 * (t * 3.0).sin().abs(), + State::Processing => 10.0 * (t * 8.0).sin().abs(), + State::Idle | State::Done => 5.0 * (t * 1.5).sin().abs(), + }; + + let base_radius = 80.0; + let radius = base_radius + pulse; + + let color = match &self.state { + State::Speaking { .. } => Color::from_rgb(0.2, 0.6, 1.0), + State::Listening => Color::from_rgb(0.2, 0.8, 0.4), + State::Processing => Color::from_rgb(1.0, 0.6, 0.2), + State::Idle => Color::from_rgb(0.5, 0.5, 0.5), + State::Done => Color::from_rgb(0.6, 0.3, 0.8), + }; + + let path = canvas::Path::circle(Point::new(center.x, center.y), radius); + frame.fill(&path, color); + + let inner_radius = radius * 0.6; + let inner_color = Color { + a: 0.3, + ..Color::WHITE + }; + let inner_path = canvas::Path::circle(Point::new(center.x, center.y), inner_radius); + frame.fill(&inner_path, inner_color); + }); + + vec![circle] + } +} From 9df3f23bd489a45119c16c906aa4e2b574a09391 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:58:40 -0500 Subject: [PATCH 02/18] working API --- vui/src/api/elevenlabs.rs | 7 ++++--- vui/src/main.rs | 16 +++------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/vui/src/api/elevenlabs.rs b/vui/src/api/elevenlabs.rs index 16515d2..2ab8b44 100644 --- a/vui/src/api/elevenlabs.rs +++ b/vui/src/api/elevenlabs.rs @@ -29,7 +29,7 @@ impl Client { let body = serde_json::json!({ "text": text, - "model_id": "eleven_monolingual_v1", + "model_id": "eleven_turbo_v2_5", "voice_settings": { "stability": 0.5, "similarity_boost": 0.75 @@ -65,8 +65,9 @@ impl Client { .map_err(|e| e.to_string())?; let form = reqwest::multipart::Form::new() - .part("audio", part) - .text("model_id", "scribe_v1"); + .part("file", part) + .text("model_id", "scribe_v1") + .text("language_code", "en"); let res = self .http diff --git a/vui/src/main.rs b/vui/src/main.rs index 614b363..754d488 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -84,7 +84,7 @@ impl App { start: Instant::now(), cache: canvas::Cache::default(), messages: Vec::new(), - subtitle: "Starting...".to_string(), + subtitle: "Say something to begin...".to_string(), elevenlabs, chat, playback, @@ -93,18 +93,8 @@ impl App { silence_frames: 0, }; - // Get initial greeting from Claude - let task = if let Some(chat) = &app.chat { - let chat = chat.clone(); - Task::perform( - async move { chat.send(&[]).await }, - Message::ClaudeReady, - ) - } else { - Task::none() - }; - - (app, task) + // Start listening for the user + (app, Task::done(Message::StartListening)) } fn update(&mut self, message: Message) -> Task { From 56449c6b984f6ee45737d77847556c63bf7b2af7 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Wed, 4 Feb 2026 22:44:56 -0500 Subject: [PATCH 03/18] better ui and theme --- vui/src/main.rs | 182 ++++++++++++++++++++++++++++++++++++++--------- vui/src/theme.rs | 25 +++++++ 2 files changed, 172 insertions(+), 35 deletions(-) create mode 100644 vui/src/theme.rs diff --git a/vui/src/main.rs b/vui/src/main.rs index 754d488..6e7aa47 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -1,11 +1,12 @@ mod api; mod audio; +mod theme; use api::claude::Message as ChatMessage; use iced::mouse; use iced::time::{self, milliseconds}; -use iced::widget::{canvas, center, column, container, text}; -use iced::{Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme}; +use iced::widget::{canvas, center, column, container, scrollable, text}; +use iced::{color, system, Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme}; use std::sync::Arc; use std::time::Instant; @@ -27,6 +28,7 @@ enum Message { ClaudeReady(Result), StartListening, StopListening, + ThemeChanged(iced::theme::Mode), } #[allow(dead_code)] @@ -39,18 +41,32 @@ enum State { Done, } +#[derive(Debug, Clone)] +enum Role { + User, + Assistant, +} + +#[derive(Debug, Clone)] +struct Line { + role: Role, + text: String, +} + struct App { state: State, start: Instant, cache: canvas::Cache, messages: Vec, - subtitle: String, + transcript: Vec, elevenlabs: Option>, chat: Option>, playback: Option, capture: Option, audio_buffer: Vec, + audio_level: f32, silence_frames: usize, + theme_mode: iced::theme::Mode, } impl App { @@ -84,17 +100,23 @@ impl App { start: Instant::now(), cache: canvas::Cache::default(), messages: Vec::new(), - subtitle: "Say something to begin...".to_string(), + transcript: Vec::new(), elevenlabs, chat, playback, capture: None, audio_buffer: Vec::new(), + audio_level: 0.0, silence_frames: 0, + theme_mode: iced::theme::Mode::Dark, }; - // Start listening for the user - (app, Task::done(Message::StartListening)) + let task = Task::batch([ + Task::done(Message::StartListening), + system::theme().map(Message::ThemeChanged), + ]); + + (app, task) } fn update(&mut self, message: Message) -> Task { @@ -127,6 +149,10 @@ impl App { / samples.len() as f32) .sqrt(); + // Smooth audio level for visual display + let target_level = (rms * 10.0).min(1.0); + self.audio_level = self.audio_level * 0.8 + target_level * 0.2; + if rms < 0.01 { self.silence_frames += 1; } else { @@ -145,20 +171,26 @@ impl App { Task::none() } + Message::ThemeChanged(mode) => { + self.theme_mode = mode; + Task::none() + } + Message::ClaudeReady(result) => match result { Ok(response) => { - // Check if conversation is complete let is_complete = response.contains("APPLICATION_COMPLETE"); let display_text = response.replace("APPLICATION_COMPLETE", "").trim().to_string(); self.messages.push(ChatMessage::assistant(&display_text)); - self.subtitle = display_text.clone(); + self.transcript.push(Line { + role: Role::Assistant, + text: display_text.clone(), + }); if is_complete { self.state = State::Done; } - // Speak the response if let Some(tts) = &self.elevenlabs { let tts = tts.clone(); Task::perform( @@ -171,7 +203,10 @@ impl App { } Err(e) => { eprintln!("Claude error: {}", e); - self.subtitle = format!("Error: {}", e); + self.transcript.push(Line { + role: Role::Assistant, + text: format!("Error: {}", e), + }); self.state = State::Done; Task::none() } @@ -182,14 +217,17 @@ impl App { if let Some(ref playback) = self.playback { playback.play(audio_data); self.state = State::Speaking { - text: self.subtitle.clone(), + text: self.transcript.last().map(|l| l.text.clone()).unwrap_or_default(), }; } Task::none() } Err(e) => { eprintln!("TTS error: {}", e); - self.subtitle = format!("Error: {}", e); + self.transcript.push(Line { + role: Role::Assistant, + text: format!("Error: {}", e), + }); self.state = State::Done; Task::none() } @@ -205,8 +243,8 @@ impl App { Message::StartListening => { self.state = State::Listening; - self.subtitle = "Listening...".to_string(); self.audio_buffer.clear(); + self.audio_level = 0.0; self.silence_frames = 0; self.capture = audio::Capture::new().ok(); Task::none() @@ -215,7 +253,7 @@ impl App { Message::StopListening => { self.capture = None; self.state = State::Processing; - self.subtitle = "Processing...".to_string(); + self.audio_level = 0.0; let buffer = std::mem::take(&mut self.audio_buffer); let sample_rate = audio::Capture::sample_rate(); @@ -235,7 +273,10 @@ impl App { Ok(transcript) => { eprintln!("User said: {}", transcript); self.messages.push(ChatMessage::user(&transcript)); - self.subtitle = format!("You: {}", transcript); + self.transcript.push(Line { + role: Role::User, + text: transcript, + }); if let Some(chat) = &self.chat { let chat = chat.clone(); @@ -250,7 +291,10 @@ impl App { } Err(e) => { eprintln!("Transcription error: {}", e); - self.subtitle = format!("Error: {}", e); + self.transcript.push(Line { + role: Role::Assistant, + text: format!("Error: {}", e), + }); Task::done(Message::StartListening) } }, @@ -260,23 +304,84 @@ impl App { fn view(&self) -> Element<'_, Message> { let circle = canvas(self as &Self).width(Fill).height(Fill); - let subtitle = text(&self.subtitle).size(24).color(Color::WHITE); + let text_color = self.theme().palette().text; + let dim_color = Color { + a: 0.5, + ..text_color + }; + + let transcript_lines: Vec> = self + .transcript + .iter() + .enumerate() + .map(|(i, line)| { + let is_latest = i == self.transcript.len() - 1; + let color = if is_latest { text_color } else { dim_color }; + + let prefix = match line.role { + Role::User => "You: ", + Role::Assistant => "", + }; + + text(format!("{}{}", prefix, line.text)) + .size(18) + .color(color) + .into() + }) + .collect(); + + let transcript_view: Element<'_, Message> = if transcript_lines.is_empty() { + text("Say something to begin...") + .size(18) + .color(dim_color) + .into() + } else { + scrollable( + column(transcript_lines) + .spacing(8) + .padding(10), + ) + .height(150) + .into() + }; + + // Status indicator + let status = match &self.state { + State::Listening => "Listening...", + State::Processing => "Processing...", + State::Speaking { .. } => "Speaking...", + State::Done => "Done", + State::Idle => "", + }; + + let status_color = match &self.state { + State::Listening => color!(0xcc3e28), // Red for recording + _ => dim_color, + }; let content = column![ - container(circle).width(400).height(400), - container(subtitle).padding(20), + container(circle).width(300).height(300), + container(text(status).size(14).color(status_color)).padding(5), + container(transcript_view).width(500).padding(10), ] + .spacing(10) .align_x(iced::Alignment::Center); center(content).into() } fn subscription(&self) -> Subscription { - time::every(milliseconds(16)).map(|_| Message::Tick) + Subscription::batch([ + time::every(milliseconds(16)).map(|_| Message::Tick), + system::theme_changes().map(Message::ThemeChanged), + ]) } fn theme(&self) -> Theme { - Theme::Dark + match self.theme_mode { + iced::theme::Mode::Light => theme::paper(), + iced::theme::Mode::Dark | iced::theme::Mode::None => theme::paper_dark(), + } } } @@ -295,27 +400,34 @@ impl canvas::Program for App { let center = frame.center(); let t = self.start.elapsed().as_secs_f32(); - let pulse = match &self.state { - State::Speaking { .. } => 25.0 * (t * 5.0).sin().abs(), - State::Listening => 15.0 * (t * 3.0).sin().abs(), - State::Processing => 10.0 * (t * 8.0).sin().abs(), - State::Idle | State::Done => 5.0 * (t * 1.5).sin().abs(), + let (pulse, color) = match &self.state { + State::Speaking { .. } => ( + 25.0 * (t * 5.0).sin().abs(), + Color::from_rgb(0.2, 0.6, 1.0), // Blue + ), + State::Listening => { + // Red with audio level response + let audio_pulse = self.audio_level * 40.0; + let base_pulse = 10.0 * (t * 2.0).sin().abs(); + (base_pulse + audio_pulse, Color::from_rgb(0.85, 0.2, 0.2)) + } + State::Processing => ( + 10.0 * (t * 8.0).sin().abs(), + Color::from_rgb(1.0, 0.6, 0.2), // Orange + ), + State::Idle | State::Done => ( + 5.0 * (t * 1.5).sin().abs(), + Color::from_rgb(0.5, 0.5, 0.5), // Gray + ), }; - let base_radius = 80.0; + let base_radius = 60.0; let radius = base_radius + pulse; - let color = match &self.state { - State::Speaking { .. } => Color::from_rgb(0.2, 0.6, 1.0), - State::Listening => Color::from_rgb(0.2, 0.8, 0.4), - State::Processing => Color::from_rgb(1.0, 0.6, 0.2), - State::Idle => Color::from_rgb(0.5, 0.5, 0.5), - State::Done => Color::from_rgb(0.6, 0.3, 0.8), - }; - let path = canvas::Path::circle(Point::new(center.x, center.y), radius); frame.fill(&path, color); + // Inner glow let inner_radius = radius * 0.6; let inner_color = Color { a: 0.3, diff --git a/vui/src/theme.rs b/vui/src/theme.rs new file mode 100644 index 0000000..c33ac26 --- /dev/null +++ b/vui/src/theme.rs @@ -0,0 +1,25 @@ +use iced::theme::{Custom, Palette}; +use iced::{color, Theme}; +use std::sync::Arc; + +pub fn paper() -> Theme { + Theme::Custom(Arc::new(Custom::new("Paper".into(), Palette { + background: color!(0xf2eede), + text: color!(0x555555), + primary: color!(0x1a1a1a), + success: color!(0x1e6fcc), + warning: color!(0x216609), + danger: color!(0xcc3e28), + }))) +} + +pub fn paper_dark() -> Theme { + Theme::Custom(Arc::new(Custom::new("Paper Dark".into(), Palette { + background: color!(0x1f1e1a), + text: color!(0xd4c8b0), + primary: color!(0xe8dcc0), + success: color!(0x1e6fcc), + warning: color!(0x216609), + danger: color!(0xcc3e28), + }))) +} From 4e7c01d80a24e3365866b7a9f5249d108c30b97f Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Wed, 4 Feb 2026 22:52:47 -0500 Subject: [PATCH 04/18] feat: filter noise --- vui/src/audio/playback.rs | 82 +++++++++++++++++++++++++++++++++++++-- vui/src/main.rs | 67 +++++++++++++++++++++++--------- 2 files changed, 128 insertions(+), 21 deletions(-) diff --git a/vui/src/audio/playback.rs b/vui/src/audio/playback.rs index ff66f70..2484f3b 100644 --- a/vui/src/audio/playback.rs +++ b/vui/src/audio/playback.rs @@ -1,7 +1,10 @@ -use rodio::{Decoder, OutputStream, Sink}; +use rodio::{Decoder, OutputStream, Sink, Source}; use std::io::Cursor; +use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::mpsc; +use std::sync::Arc; use std::thread; +use std::time::Duration; pub struct Playback { sender: mpsc::Sender, @@ -17,10 +20,66 @@ enum Command { #[derive(Debug, Clone)] pub enum Status { Playing, + Level(f32), Finished, Error(String), } +/// A source wrapper that computes RMS levels as f32 audio plays through +struct Level> { + source: S, + level: Arc, + buffer: Vec, +} + +impl> Level { + fn new(source: S, level: Arc) -> Self { + Self { + source, + level, + buffer: Vec::with_capacity(1024), + } + } +} + +impl> Iterator for Level { + type Item = f32; + + fn next(&mut self) -> Option { + let sample = self.source.next()?; + + self.buffer.push(sample); + + if self.buffer.len() >= 1024 { + let rms = (self.buffer.iter().map(|s| s * s).sum::() + / self.buffer.len() as f32) + .sqrt(); + self.level.store(rms.to_bits(), Ordering::Relaxed); + self.buffer.clear(); + } + + Some(sample) + } +} + +impl> Source for Level { + fn current_frame_len(&self) -> Option { + self.source.current_frame_len() + } + + fn channels(&self) -> u16 { + self.source.channels() + } + + fn sample_rate(&self) -> u32 { + self.source.sample_rate() + } + + fn total_duration(&self) -> Option { + self.source.total_duration() + } +} + impl Playback { pub fn new() -> Result { let (sender, receiver) = mpsc::channel::(); @@ -44,8 +103,25 @@ impl Playback { match Decoder::new(cursor) { Ok(source) => { let _ = status_sender.send(Status::Playing); - sink.append(source); - sink.sleep_until_end(); + + let level = Arc::new(AtomicU32::new(0)); + let level_clone = level.clone(); + + // Convert to f32 and wrap with level tracker + let f32_source = source.convert_samples::(); + let tracked_source = Level::new(f32_source, level); + + sink.append(tracked_source); + + // Poll level while playing + while !sink.empty() { + let bits = level_clone.load(Ordering::Relaxed); + let rms = f32::from_bits(bits); + let _ = status_sender.send(Status::Level(rms)); + thread::sleep(Duration::from_millis(16)); + } + + let _ = status_sender.send(Status::Level(0.0)); let _ = status_sender.send(Status::Finished); } Err(e) => { diff --git a/vui/src/main.rs b/vui/src/main.rs index 6e7aa47..fd90f04 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -65,6 +65,7 @@ struct App { capture: Option, audio_buffer: Vec, audio_level: f32, + playback_level: f32, silence_frames: usize, theme_mode: iced::theme::Mode, } @@ -107,6 +108,7 @@ impl App { capture: None, audio_buffer: Vec::new(), audio_level: 0.0, + playback_level: 0.0, silence_frames: 0, theme_mode: iced::theme::Mode::Dark, }; @@ -124,19 +126,25 @@ impl App { Message::Tick => { self.cache.clear(); - // Check playback status - if let Some(ref playback) = self.playback - && let Some(status) = playback.try_recv_status() - { - match status { - audio::playback::Status::Finished => { - return Task::done(Message::TtsFinished); - } - audio::playback::Status::Error(e) => { - eprintln!("Playback error: {}", e); - return Task::done(Message::TtsFinished); + // Check playback status (drain all pending messages) + if let Some(ref playback) = self.playback { + while let Some(status) = playback.try_recv_status() { + match status { + audio::playback::Status::Finished => { + self.playback_level = 0.0; + return Task::done(Message::TtsFinished); + } + audio::playback::Status::Error(e) => { + eprintln!("Playback error: {}", e); + self.playback_level = 0.0; + return Task::done(Message::TtsFinished); + } + audio::playback::Status::Level(level) => { + let target = (level * 5.0).min(1.0); + self.playback_level = self.playback_level * 0.7 + target * 0.3; + } + _ => {} } - _ => {} } } @@ -271,7 +279,20 @@ impl App { Message::TranscriptionReady(result) => match result { Ok(transcript) => { + let transcript = transcript.trim().to_string(); eprintln!("User said: {}", transcript); + + // Filter out noise/static + let is_noise = transcript.is_empty() + || transcript.len() < 3 + || transcript.starts_with('(') + || transcript.to_lowercase().contains("static"); + + if is_noise { + eprintln!("Filtered noise: {:?}", transcript); + return Task::done(Message::StartListening); + } + self.messages.push(ChatMessage::user(&transcript)); self.transcript.push(Line { role: Role::User, @@ -401,14 +422,24 @@ impl canvas::Program for App { let t = self.start.elapsed().as_secs_f32(); let (pulse, color) = match &self.state { - State::Speaking { .. } => ( - 25.0 * (t * 5.0).sin().abs(), - Color::from_rgb(0.2, 0.6, 1.0), // Blue - ), + State::Speaking { .. } => { + // Blue circle reacts to actual playback volume + let audio_pulse = self.playback_level * 40.0; + let base_pulse = if self.playback_level > 0.05 { + 10.0 * (t * 4.0).sin().abs() + } else { + 0.0 + }; + (base_pulse + audio_pulse, Color::from_rgb(0.2, 0.6, 1.0)) + } State::Listening => { - // Red with audio level response + // Red, still when silent, pulses with voice let audio_pulse = self.audio_level * 40.0; - let base_pulse = 10.0 * (t * 2.0).sin().abs(); + let base_pulse = if self.audio_level > 0.05 { + 10.0 * (t * 2.0).sin().abs() + } else { + 0.0 + }; (base_pulse + audio_pulse, Color::from_rgb(0.85, 0.2, 0.2)) } State::Processing => ( From 39a1a73a975d36b033dbc628e24a618df3e41a11 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Wed, 4 Feb 2026 23:15:57 -0500 Subject: [PATCH 05/18] style: various UI tweaks --- vui/Cargo.lock | 123 ++++++++++++++++++++++++++++++++++ vui/Cargo.toml | 2 + vui/src/main.rs | 172 +++++++++++++++++++++++++++++++++--------------- 3 files changed, 245 insertions(+), 52 deletions(-) diff --git a/vui/Cargo.lock b/vui/Cargo.lock index 48cdd56..0e12f37 100644 --- a/vui/Cargo.lock +++ b/vui/Cargo.lock @@ -125,6 +125,56 @@ 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 = "arrayref" version = "0.3.9" @@ -609,6 +659,12 @@ dependencies = [ "unicode-width", ] +[[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" @@ -961,6 +1017,29 @@ dependencies = [ "syn", ] +[[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_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 = "equivalent" version = "1.0.2" @@ -1854,6 +1933,12 @@ dependencies = [ "serde", ] +[[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.13.0" @@ -1869,6 +1954,30 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[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", +] + [[package]] name = "jni" version = "0.21.1" @@ -2819,6 +2928,12 @@ 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 = "openssl-probe" version = "0.2.1" @@ -4266,6 +4381,12 @@ 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.20.0" @@ -4289,7 +4410,9 @@ version = "0.1.0" dependencies = [ "cpal 0.17.1", "dotenvy", + "env_logger", "iced", + "log", "reqwest", "rodio", "serde", diff --git a/vui/Cargo.toml b/vui/Cargo.toml index f0de570..ad37cb5 100644 --- a/vui/Cargo.toml +++ b/vui/Cargo.toml @@ -11,4 +11,6 @@ reqwest = { version = "0.13.1", features = ["json", "multipart"] } rodio = "0.19" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" +log = "0.4" +env_logger = "0.11" tokio = { version = "1.49.0", features = ["full"] } diff --git a/vui/src/main.rs b/vui/src/main.rs index fd90f04..d3d903f 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -2,16 +2,19 @@ mod api; mod audio; mod theme; -use api::claude::Message as ChatMessage; -use iced::mouse; +use api::claude; use iced::time::{self, milliseconds}; -use iced::widget::{canvas, center, column, container, scrollable, text}; -use iced::{color, system, Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme}; +use iced::widget::{bottom, canvas, center, column, container, scrollable, stack, text}; +use iced::{ + Center, Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme, color, + mouse, padding, system, +}; use std::sync::Arc; use std::time::Instant; fn main() -> iced::Result { dotenvy::dotenv().ok(); + env_logger::init(); iced::application(App::new, App::update, App::view) .subscription(App::subscription) @@ -37,7 +40,7 @@ enum State { Idle, Speaking { text: String }, Listening, - Processing, + Processing { pending_text: Option }, Done, } @@ -57,7 +60,7 @@ struct App { state: State, start: Instant, cache: canvas::Cache, - messages: Vec, + messages: Vec, transcript: Vec, elevenlabs: Option>, chat: Option>, @@ -67,6 +70,7 @@ struct App { audio_level: f32, playback_level: f32, silence_frames: usize, + has_speech: bool, theme_mode: iced::theme::Mode, } @@ -76,10 +80,10 @@ impl App { let anthropic_key = std::env::var("ANTHROPIC_API_KEY").unwrap_or_default(); if elevenlabs_key.is_empty() { - eprintln!("WARNING: ELEVENLABS_API_KEY not set"); + log::error!("WARNING: ELEVENLABS_API_KEY not set"); } if anthropic_key.is_empty() { - eprintln!("WARNING: ANTHROPIC_API_KEY not set"); + log::error!("WARNING: ANTHROPIC_API_KEY not set"); } let elevenlabs = if !elevenlabs_key.is_empty() { @@ -110,6 +114,7 @@ impl App { audio_level: 0.0, playback_level: 0.0, silence_frames: 0, + has_speech: false, theme_mode: iced::theme::Mode::Dark, }; @@ -135,7 +140,7 @@ impl App { return Task::done(Message::TtsFinished); } audio::playback::Status::Error(e) => { - eprintln!("Playback error: {}", e); + log::error!("Playback error: {}", e); self.playback_level = 0.0; return Task::done(Message::TtsFinished); } @@ -161,17 +166,24 @@ impl App { let target_level = (rms * 10.0).min(1.0); self.audio_level = self.audio_level * 0.8 + target_level * 0.2; - if rms < 0.01 { - self.silence_frames += 1; - } else { + if rms >= 0.01 { + self.has_speech = true; self.silence_frames = 0; + } else { + self.silence_frames += 1; } self.audio_buffer.extend(samples); // Stop after ~1.5 seconds of silence if self.audio_buffer.len() > 16000 && self.silence_frames > 90 { - return Task::done(Message::StopListening); + if self.has_speech { + return Task::done(Message::StopListening); + } else { + // No speech detected, just reset and keep listening + self.audio_buffer.clear(); + self.silence_frames = 0; + } } } } @@ -187,16 +199,25 @@ impl App { Message::ClaudeReady(result) => match result { Ok(response) => { let is_complete = response.contains("APPLICATION_COMPLETE"); - let display_text = response.replace("APPLICATION_COMPLETE", "").trim().to_string(); + let display_text = response + .replace("APPLICATION_COMPLETE", "") + .trim() + .to_string(); - self.messages.push(ChatMessage::assistant(&display_text)); - self.transcript.push(Line { - role: Role::Assistant, - text: display_text.clone(), - }); + self.messages + .push(claude::Message::assistant(&display_text)); if is_complete { + // Show text immediately when done since there's no next interaction + self.transcript.push(Line { + role: Role::Assistant, + text: display_text.clone(), + }); self.state = State::Done; + } else { + self.state = State::Processing { + pending_text: Some(display_text.clone()), + }; } if let Some(tts) = &self.elevenlabs { @@ -206,11 +227,25 @@ impl App { Message::TtsReady, ) } else { + // No TTS — show text immediately as fallback + if !is_complete { + if let State::Processing { + ref mut pending_text, + } = self.state + { + if let Some(text) = pending_text.take() { + self.transcript.push(Line { + role: Role::Assistant, + text, + }); + } + } + } Task::none() } } Err(e) => { - eprintln!("Claude error: {}", e); + log::error!("Claude error: {}", e); self.transcript.push(Line { role: Role::Assistant, text: format!("Error: {}", e), @@ -222,20 +257,45 @@ impl App { Message::TtsReady(result) => match result { Ok(audio_data) => { + // Reveal the assistant's response now that audio is ready + let response_text = if let State::Processing { + ref mut pending_text, + } = self.state + { + pending_text.take() + } else { + None + }; + + if let Some(text) = &response_text { + self.transcript.push(Line { + role: Role::Assistant, + text: text.clone(), + }); + } + if let Some(ref playback) = self.playback { playback.play(audio_data); self.state = State::Speaking { - text: self.transcript.last().map(|l| l.text.clone()).unwrap_or_default(), + text: response_text.unwrap_or_default(), }; } Task::none() } Err(e) => { - eprintln!("TTS error: {}", e); - self.transcript.push(Line { - role: Role::Assistant, - text: format!("Error: {}", e), - }); + log::error!("TTS error: {}", e); + // TTS failed — show pending text as fallback + if let State::Processing { + ref mut pending_text, + } = self.state + { + if let Some(text) = pending_text.take() { + self.transcript.push(Line { + role: Role::Assistant, + text, + }); + } + } self.state = State::Done; Task::none() } @@ -254,13 +314,14 @@ impl App { self.audio_buffer.clear(); self.audio_level = 0.0; self.silence_frames = 0; + self.has_speech = false; self.capture = audio::Capture::new().ok(); Task::none() } Message::StopListening => { self.capture = None; - self.state = State::Processing; + self.state = State::Processing { pending_text: None }; self.audio_level = 0.0; let buffer = std::mem::take(&mut self.audio_buffer); @@ -280,7 +341,7 @@ impl App { Message::TranscriptionReady(result) => match result { Ok(transcript) => { let transcript = transcript.trim().to_string(); - eprintln!("User said: {}", transcript); + log::debug!("User said: {}", transcript); // Filter out noise/static let is_noise = transcript.is_empty() @@ -289,11 +350,11 @@ impl App { || transcript.to_lowercase().contains("static"); if is_noise { - eprintln!("Filtered noise: {:?}", transcript); + log::debug!("Filtered noise: {:?}", transcript); return Task::done(Message::StartListening); } - self.messages.push(ChatMessage::user(&transcript)); + self.messages.push(claude::Message::user(&transcript)); self.transcript.push(Line { role: Role::User, text: transcript, @@ -311,7 +372,7 @@ impl App { } } Err(e) => { - eprintln!("Transcription error: {}", e); + log::debug!("Transcription error: {}", e); self.transcript.push(Line { role: Role::Assistant, text: format!("Error: {}", e), @@ -351,25 +412,26 @@ impl App { }) .collect(); - let transcript_view: Element<'_, Message> = if transcript_lines.is_empty() { - text("Say something to begin...") - .size(18) - .color(dim_color) - .into() + let transcript_view = if transcript_lines.is_empty() { + bottom(text("Say something to begin...").size(18).color(dim_color)).center_x(Fill) } else { - scrollable( - column(transcript_lines) - .spacing(8) - .padding(10), + bottom( + scrollable(column(transcript_lines).spacing(8).padding(10)) + .spacing(0) + .direction(scrollable::Direction::Vertical( + scrollable::Scrollbar::new() + .width(1) + .margin(1) + .scroller_width(3), + )) + .anchor_bottom(), ) - .height(150) - .into() }; // Status indicator let status = match &self.state { State::Listening => "Listening...", - State::Processing => "Processing...", + State::Processing { .. } => "Processing...", State::Speaking { .. } => "Speaking...", State::Done => "Done", State::Idle => "", @@ -380,13 +442,19 @@ impl App { _ => dim_color, }; - let content = column![ - container(circle).width(300).height(300), - container(text(status).size(14).color(status_color)).padding(5), - container(transcript_view).width(500).padding(10), - ] - .spacing(10) - .align_x(iced::Alignment::Center); + let content = stack![ + container( + column![ + container(circle).width(300).height(300), + container(text(status).size(14).color(status_color)).padding(5), + ] + .padding(padding::bottom(300)) + .spacing(10) + .align_x(Center) + ) + .center_y(Fill), + bottom(transcript_view).width(600).padding(10), + ]; center(content).into() } @@ -401,7 +469,7 @@ impl App { fn theme(&self) -> Theme { match self.theme_mode { iced::theme::Mode::Light => theme::paper(), - iced::theme::Mode::Dark | iced::theme::Mode::None => theme::paper_dark(), + _ => theme::paper_dark(), } } } @@ -442,7 +510,7 @@ impl canvas::Program for App { }; (base_pulse + audio_pulse, Color::from_rgb(0.85, 0.2, 0.2)) } - State::Processing => ( + State::Processing { .. } => ( 10.0 * (t * 8.0).sin().abs(), Color::from_rgb(1.0, 0.6, 0.2), // Orange ), From c5ced243a9b3bac6729fbe0a758d1798118639c8 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Wed, 4 Feb 2026 23:28:02 -0500 Subject: [PATCH 06/18] style: align bottom --- vui/src/main.rs | 156 +++++++++++++++++++++++++++++++----------------- 1 file changed, 100 insertions(+), 56 deletions(-) diff --git a/vui/src/main.rs b/vui/src/main.rs index d3d903f..c4c3567 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -3,8 +3,11 @@ mod audio; mod theme; use api::claude; +use iced::Length::FillPortion; use iced::time::{self, milliseconds}; -use iced::widget::{bottom, canvas, center, column, container, scrollable, stack, text}; +use iced::widget::{ + bottom, canvas, center, column, container, responsive, row, scrollable, space, stack, text, +}; use iced::{ Center, Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme, color, mouse, padding, system, @@ -416,15 +419,25 @@ impl App { bottom(text("Say something to begin...").size(18).color(dim_color)).center_x(Fill) } else { bottom( - scrollable(column(transcript_lines).spacing(8).padding(10)) - .spacing(0) - .direction(scrollable::Direction::Vertical( - scrollable::Scrollbar::new() - .width(1) - .margin(1) - .scroller_width(3), - )) - .anchor_bottom(), + scrollable( + row![ + space().width(FillPortion(1)), + column(transcript_lines) + .spacing(8) + .padding(10) + .width(FillPortion(4)), + space().width(FillPortion(1)), + ] + .width(Fill), + ) + .spacing(0) + .direction(scrollable::Direction::Vertical( + scrollable::Scrollbar::new() + .width(1) + .margin(1) + .scroller_width(3), + )) + .anchor_bottom(), ) }; @@ -451,9 +464,10 @@ impl App { .padding(padding::bottom(300)) .spacing(10) .align_x(Center) + .width(Fill) ) .center_y(Fill), - bottom(transcript_view).width(600).padding(10), + bottom(transcript_view).width(Fill).padding(10), ]; center(content).into() @@ -489,51 +503,81 @@ impl canvas::Program for App { let center = frame.center(); let t = self.start.elapsed().as_secs_f32(); - let (pulse, color) = match &self.state { - State::Speaking { .. } => { - // Blue circle reacts to actual playback volume - let audio_pulse = self.playback_level * 40.0; - let base_pulse = if self.playback_level > 0.05 { - 10.0 * (t * 4.0).sin().abs() - } else { - 0.0 - }; - (base_pulse + audio_pulse, Color::from_rgb(0.2, 0.6, 1.0)) - } - State::Listening => { - // Red, still when silent, pulses with voice - let audio_pulse = self.audio_level * 40.0; - let base_pulse = if self.audio_level > 0.05 { - 10.0 * (t * 2.0).sin().abs() - } else { - 0.0 - }; - (base_pulse + audio_pulse, Color::from_rgb(0.85, 0.2, 0.2)) - } - State::Processing { .. } => ( - 10.0 * (t * 8.0).sin().abs(), - Color::from_rgb(1.0, 0.6, 0.2), // Orange - ), - State::Idle | State::Done => ( - 5.0 * (t * 1.5).sin().abs(), - Color::from_rgb(0.5, 0.5, 0.5), // Gray - ), - }; - - let base_radius = 60.0; - let radius = base_radius + pulse; - - let path = canvas::Path::circle(Point::new(center.x, center.y), radius); - frame.fill(&path, color); - - // Inner glow - let inner_radius = radius * 0.6; - let inner_color = Color { - a: 0.3, - ..Color::WHITE - }; - let inner_path = canvas::Path::circle(Point::new(center.x, center.y), inner_radius); - frame.fill(&inner_path, inner_color); + // Processing draws a spinning arc instead of a filled circle + if matches!(&self.state, State::Processing { .. }) { + let radius = 40.0; + let sweep = std::f32::consts::FRAC_PI_2; // 90° arc + let start_angle = t * 4.0; + let arc = canvas::path::Arc { + center: Point::new(center.x, center.y), + radius, + start_angle: iced::Radians(start_angle), + end_angle: iced::Radians(start_angle + sweep), + }; + let path = canvas::Path::new(|b| b.arc(arc)); + frame.stroke( + &path, + canvas::Stroke::default() + .with_color(Color::from_rgb(1.0, 0.6, 0.2)) + .with_width(4.0), + ); + } else { + let (base_radius, pulse, color) = match &self.state { + State::Speaking { .. } => { + // Blue circle reacts to actual playback volume + let audio_pulse = self.playback_level * 40.0; + let base_pulse = if self.playback_level > 0.05 { + 10.0 * (t * 4.0).sin().abs() + } else { + 0.0 + }; + ( + 60.0, + base_pulse + audio_pulse, + Color::from_rgb(0.2, 0.6, 1.0), + ) + } + State::Listening => { + if self.has_speech { + // Reactive red circle + let audio_pulse = self.audio_level * 40.0; + let base_pulse = if self.audio_level > 0.05 { + 10.0 * (t * 2.0).sin().abs() + } else { + 0.0 + }; + ( + 60.0, + base_pulse + audio_pulse, + Color::from_rgb(0.85, 0.2, 0.2), + ) + } else { + // Small idle dot + (12.0, 0.0, Color::from_rgb(0.85, 0.2, 0.2)) + } + } + State::Idle | State::Done => ( + 60.0, + 5.0 * (t * 1.5).sin().abs(), + Color::from_rgb(0.5, 0.5, 0.5), + ), + State::Processing { .. } => unreachable!(), + }; + + let radius = base_radius + pulse; + + let path = canvas::Path::circle(Point::new(center.x, center.y), radius); + frame.fill(&path, color); + + // Inner glow + let inner_radius = radius * 0.6; + let inner_color = Color { + a: 0.3, + ..Color::WHITE + }; + let inner_path = canvas::Path::circle(Point::new(center.x, center.y), inner_radius); + frame.fill(&inner_path, inner_color); + } }); vec![circle] From ea277e7ef450fc49ca5eb61927a828425b943ce2 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Wed, 4 Feb 2026 23:36:27 -0500 Subject: [PATCH 07/18] style: speech bubbles --- vui/src/main.rs | 26 +++++++++++--------------- vui/src/theme.rs | 23 ++++++++++++++++++++++- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/vui/src/main.rs b/vui/src/main.rs index c4c3567..e574782 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -6,7 +6,8 @@ use api::claude; use iced::Length::FillPortion; use iced::time::{self, milliseconds}; use iced::widget::{ - bottom, canvas, center, column, container, responsive, row, scrollable, space, stack, text, + bottom, canvas, center, column, container, right, row, scrollable, space, stack, + text, }; use iced::{ Center, Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme, color, @@ -398,20 +399,15 @@ impl App { let transcript_lines: Vec> = self .transcript .iter() - .enumerate() - .map(|(i, line)| { - let is_latest = i == self.transcript.len() - 1; - let color = if is_latest { text_color } else { dim_color }; - - let prefix = match line.role { - Role::User => "You: ", - Role::Assistant => "", - }; - - text(format!("{}{}", prefix, line.text)) - .size(18) - .color(color) - .into() + .map(|line| { + let bubble = container(text(&line.text).size(18)) + .padding(padding::all(10).left(14).right(14)) + .max_width(500); + + match line.role { + Role::User => right(bubble.style(theme::user)).into(), + Role::Assistant => bubble.style(theme::assistant).into(), + } }) .collect(); diff --git a/vui/src/theme.rs b/vui/src/theme.rs index c33ac26..ce294f1 100644 --- a/vui/src/theme.rs +++ b/vui/src/theme.rs @@ -1,5 +1,6 @@ use iced::theme::{Custom, Palette}; -use iced::{color, Theme}; +use iced::widget::container; +use iced::{border, color, Theme}; use std::sync::Arc; pub fn paper() -> Theme { @@ -23,3 +24,23 @@ pub fn paper_dark() -> Theme { danger: color!(0xcc3e28), }))) } + +pub fn user(theme: &Theme) -> container::Style { + let palette = theme.extended_palette(); + container::Style { + background: Some(palette.primary.weak.color.into()), + text_color: Some(palette.primary.weak.text), + border: border::rounded(12), + ..container::Style::default() + } +} + +pub fn assistant(theme: &Theme) -> container::Style { + let palette = theme.extended_palette(); + container::Style { + background: Some(palette.background.weak.color.into()), + text_color: Some(palette.background.weak.text), + border: border::rounded(12), + ..container::Style::default() + } +} From b6fb26de23230dc03f9b6aea81b995fca2e9a255 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Wed, 4 Feb 2026 23:39:05 -0500 Subject: [PATCH 08/18] linter --- vui/src/api/elevenlabs.rs | 6 +++++- vui/src/audio/capture.rs | 6 ++---- vui/src/audio/playback.rs | 7 +++---- vui/src/main.rs | 18 ++++++------------ vui/src/theme.rs | 40 ++++++++++++++++++++++----------------- 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/vui/src/api/elevenlabs.rs b/vui/src/api/elevenlabs.rs index 2ab8b44..11d39b1 100644 --- a/vui/src/api/elevenlabs.rs +++ b/vui/src/api/elevenlabs.rs @@ -56,7 +56,11 @@ impl Client { Ok(bytes.to_vec()) } - pub async fn transcribe(&self, audio_samples: &[f32], sample_rate: u32) -> Result { + pub async fn transcribe( + &self, + audio_samples: &[f32], + sample_rate: u32, + ) -> Result { let wav_data = encode_wav(audio_samples, sample_rate); let part = reqwest::multipart::Part::bytes(wav_data) diff --git a/vui/src/audio/capture.rs b/vui/src/audio/capture.rs index 20ff4c2..5197a0f 100644 --- a/vui/src/audio/capture.rs +++ b/vui/src/audio/capture.rs @@ -1,5 +1,5 @@ -use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::Sample; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use std::sync::mpsc; pub struct Capture { @@ -15,9 +15,7 @@ impl Capture { .default_input_device() .ok_or("No input device available")?; - let config = device - .default_input_config() - .map_err(|e| e.to_string())?; + let config = device.default_input_config().map_err(|e| e.to_string())?; let (sender, receiver) = mpsc::channel(); diff --git a/vui/src/audio/playback.rs b/vui/src/audio/playback.rs index 2484f3b..cec2e98 100644 --- a/vui/src/audio/playback.rs +++ b/vui/src/audio/playback.rs @@ -1,8 +1,8 @@ use rodio::{Decoder, OutputStream, Sink, Source}; use std::io::Cursor; +use std::sync::Arc; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::mpsc; -use std::sync::Arc; use std::thread; use std::time::Duration; @@ -51,9 +51,8 @@ impl> Iterator for Level { self.buffer.push(sample); if self.buffer.len() >= 1024 { - let rms = (self.buffer.iter().map(|s| s * s).sum::() - / self.buffer.len() as f32) - .sqrt(); + let rms = + (self.buffer.iter().map(|s| s * s).sum::() / self.buffer.len() as f32).sqrt(); self.level.store(rms.to_bits(), Ordering::Relaxed); self.buffer.clear(); } diff --git a/vui/src/main.rs b/vui/src/main.rs index e574782..8ee7287 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -6,8 +6,7 @@ use api::claude; use iced::Length::FillPortion; use iced::time::{self, milliseconds}; use iced::widget::{ - bottom, canvas, center, column, container, right, row, scrollable, space, stack, - text, + bottom, canvas, center, column, container, right, row, scrollable, space, stack, text, }; use iced::{ Center, Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme, color, @@ -232,19 +231,16 @@ impl App { ) } else { // No TTS — show text immediately as fallback - if !is_complete { - if let State::Processing { + if !is_complete + && let State::Processing { ref mut pending_text, } = self.state - { - if let Some(text) = pending_text.take() { + && let Some(text) = pending_text.take() { self.transcript.push(Line { role: Role::Assistant, text, }); } - } - } Task::none() } } @@ -292,14 +288,12 @@ impl App { if let State::Processing { ref mut pending_text, } = self.state - { - if let Some(text) = pending_text.take() { + && let Some(text) = pending_text.take() { self.transcript.push(Line { role: Role::Assistant, text, }); } - } self.state = State::Done; Task::none() } @@ -452,6 +446,7 @@ impl App { }; let content = stack![ + bottom(transcript_view).width(Fill).padding(10), container( column![ container(circle).width(300).height(300), @@ -463,7 +458,6 @@ impl App { .width(Fill) ) .center_y(Fill), - bottom(transcript_view).width(Fill).padding(10), ]; center(content).into() diff --git a/vui/src/theme.rs b/vui/src/theme.rs index ce294f1..3516a39 100644 --- a/vui/src/theme.rs +++ b/vui/src/theme.rs @@ -1,28 +1,34 @@ use iced::theme::{Custom, Palette}; use iced::widget::container; -use iced::{border, color, Theme}; +use iced::{Theme, border, color}; use std::sync::Arc; pub fn paper() -> Theme { - Theme::Custom(Arc::new(Custom::new("Paper".into(), Palette { - background: color!(0xf2eede), - text: color!(0x555555), - primary: color!(0x1a1a1a), - success: color!(0x1e6fcc), - warning: color!(0x216609), - danger: color!(0xcc3e28), - }))) + Theme::Custom(Arc::new(Custom::new( + "Paper".into(), + Palette { + background: color!(0xf2eede), + text: color!(0x555555), + primary: color!(0x1a1a1a), + success: color!(0x1e6fcc), + warning: color!(0x216609), + danger: color!(0xcc3e28), + }, + ))) } pub fn paper_dark() -> Theme { - Theme::Custom(Arc::new(Custom::new("Paper Dark".into(), Palette { - background: color!(0x1f1e1a), - text: color!(0xd4c8b0), - primary: color!(0xe8dcc0), - success: color!(0x1e6fcc), - warning: color!(0x216609), - danger: color!(0xcc3e28), - }))) + Theme::Custom(Arc::new(Custom::new( + "Paper Dark".into(), + Palette { + background: color!(0x1f1e1a), + text: color!(0xd4c8b0), + primary: color!(0xe8dcc0), + success: color!(0x1e6fcc), + warning: color!(0x216609), + danger: color!(0xcc3e28), + }, + ))) } pub fn user(theme: &Theme) -> container::Style { From 6160a118264e98b5a502f190102a71eb4026f55d Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Wed, 4 Feb 2026 23:48:38 -0500 Subject: [PATCH 09/18] fade messages --- vui/src/main.rs | 29 ++++++++++++++++------------- vui/src/theme.rs | 11 ++++++++++- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/vui/src/main.rs b/vui/src/main.rs index 8ee7287..0a57487 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -235,12 +235,13 @@ impl App { && let State::Processing { ref mut pending_text, } = self.state - && let Some(text) = pending_text.take() { - self.transcript.push(Line { - role: Role::Assistant, - text, - }); - } + && let Some(text) = pending_text.take() + { + self.transcript.push(Line { + role: Role::Assistant, + text, + }); + } Task::none() } } @@ -288,12 +289,13 @@ impl App { if let State::Processing { ref mut pending_text, } = self.state - && let Some(text) = pending_text.take() { - self.transcript.push(Line { - role: Role::Assistant, - text, - }); - } + && let Some(text) = pending_text.take() + { + self.transcript.push(Line { + role: Role::Assistant, + text, + }); + } self.state = State::Done; Task::none() } @@ -457,7 +459,8 @@ impl App { .align_x(Center) .width(Fill) ) - .center_y(Fill), + .center_y(Fill) + .style(theme::fade), ]; center(content).into() diff --git a/vui/src/theme.rs b/vui/src/theme.rs index 3516a39..2f811d5 100644 --- a/vui/src/theme.rs +++ b/vui/src/theme.rs @@ -1,6 +1,6 @@ use iced::theme::{Custom, Palette}; use iced::widget::container; -use iced::{Theme, border, color}; +use iced::{Color, Theme, border, color, gradient}; use std::sync::Arc; pub fn paper() -> Theme { @@ -50,3 +50,12 @@ pub fn assistant(theme: &Theme) -> container::Style { ..container::Style::default() } } + +pub fn fade(theme: &Theme) -> container::Style { + let bg = theme.palette().background; + let bg_transparent = Color { a: 0.0, ..bg }; + gradient::Linear::new(std::f32::consts::PI) + .add_stop(0.6, bg) + .add_stop(0.8, bg_transparent) + .into() +} From 23b142339de4c0897b26f4269cd3993e830fa661 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:28:36 -0500 Subject: [PATCH 10/18] feat: add form --- vui/src/api/claude.rs | 102 ++++++++++++++++++++++----- vui/src/form.rs | 158 ++++++++++++++++++++++++++++++++++++++++++ vui/src/main.rs | 142 ++++++++++++++++++++++++++++--------- vui/src/theme.rs | 15 +++- 4 files changed, 365 insertions(+), 52 deletions(-) create mode 100644 vui/src/form.rs diff --git a/vui/src/api/claude.rs b/vui/src/api/claude.rs index 69d3519..b2e1d0a 100644 --- a/vui/src/api/claude.rs +++ b/vui/src/api/claude.rs @@ -11,22 +11,60 @@ struct Request { max_tokens: u32, system: String, messages: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + tools: Vec, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct Message { pub role: String, - pub content: String, + pub content: Content, +} + +/// Content can be a plain string or an array of content blocks +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(untagged)] +pub enum Content { + Text(String), + Blocks(Vec), +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "type")] +pub enum ContentBlock { + #[serde(rename = "text")] + Text { text: String }, + #[serde(rename = "tool_use")] + ToolUse { + id: String, + name: String, + input: serde_json::Value, + }, + #[serde(rename = "tool_result")] + ToolResult { + tool_use_id: String, + content: String, + }, } #[derive(Deserialize)] struct Response { - content: Vec, + content: Vec, + stop_reason: Option, } -#[derive(Deserialize)] -struct Content { - text: String, +#[derive(Debug, Clone)] +pub struct SendResult { + pub text: String, + pub tool_uses: Vec, + pub stop_reason: String, +} + +#[derive(Debug, Clone)] +pub struct ToolUse { + pub id: String, + pub name: String, + pub input: serde_json::Value, } const SYSTEM: &str = include_str!("../../../prompt.txt"); @@ -37,12 +75,17 @@ impl Client { Self { api_key, http } } - pub async fn send(&self, messages: &[Message]) -> Result { + pub async fn send( + &self, + messages: &[Message], + tools: &[serde_json::Value], + ) -> Result { let request = Request { model: "claude-sonnet-4-20250514".to_string(), - max_tokens: 256, + max_tokens: 512, system: SYSTEM.to_string(), messages: messages.to_vec(), + tools: tools.to_vec(), }; let res = self @@ -63,13 +106,28 @@ impl Client { let response: Response = res.json().await.map_err(|e| e.to_string())?; - let text = response - .content - .first() - .map(|b| b.text.clone()) - .unwrap_or_default(); + let mut text_parts = Vec::new(); + let mut tool_uses = Vec::new(); - Ok(text) + for block in &response.content { + match block { + ContentBlock::Text { text } => text_parts.push(text.clone()), + ContentBlock::ToolUse { id, name, input } => { + tool_uses.push(ToolUse { + id: id.clone(), + name: name.clone(), + input: input.clone(), + }); + } + ContentBlock::ToolResult { .. } => {} + } + } + + Ok(SendResult { + text: text_parts.join(""), + tool_uses, + stop_reason: response.stop_reason.unwrap_or_else(|| "end_turn".into()), + }) } } @@ -77,14 +135,24 @@ impl Message { pub fn user(content: &str) -> Self { Self { role: "user".to_string(), - content: content.to_string(), + content: Content::Text(content.to_string()), } } - pub fn assistant(content: &str) -> Self { + pub fn assistant_blocks(blocks: Vec) -> Self { Self { role: "assistant".to_string(), - content: content.to_string(), + content: Content::Blocks(blocks), + } + } + + pub fn tool_result(tool_use_id: &str, result: &str) -> Self { + Self { + role: "user".to_string(), + content: Content::Blocks(vec![ContentBlock::ToolResult { + tool_use_id: tool_use_id.to_string(), + content: result.to_string(), + }]), } } } diff --git a/vui/src/form.rs b/vui/src/form.rs new file mode 100644 index 0000000..ca6b715 --- /dev/null +++ b/vui/src/form.rs @@ -0,0 +1,158 @@ +use iced::widget::{bottom_right, button, column, container, scrollable, stack, text}; +use iced::{Element, Fill}; +use serde::Deserialize; + +use crate::theme; + +/// The form fields for a Root Ventures application +#[derive(Debug, Clone, Default)] +pub struct Form { + pub name: String, + pub email: String, + pub linkedin: String, + pub github: String, + pub notes: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ToolInput { + pub field: String, + pub action: String, + #[serde(default)] + pub value: String, +} + +impl Form { + /// Returns true when required fields are filled + pub fn is_ready(&self) -> bool { + !self.name.trim().is_empty() && !self.email.trim().is_empty() + } + + /// Apply a tool invocation, return a result string for the tool_result + pub fn apply(&mut self, input: &ToolInput) -> String { + match input.action.as_str() { + "write" => { + match input.field.as_str() { + "name" => self.name = input.value.clone(), + "email" => self.email = input.value.clone(), + "linkedin" => self.linkedin = input.value.clone(), + "github" => self.github = input.value.clone(), + "notes" => self.notes = input.value.clone(), + _ => return format!("Unknown field: {}", input.field), + } + format!("Set {} to {:?}", input.field, input.value) + } + "read" => { + if input.field == "form" { + format!( + "name: {:?}\nemail: {:?}\nlinkedin: {:?}\ngithub: {:?}\nnotes: {:?}", + self.name, self.email, self.linkedin, self.github, self.notes + ) + } else { + match input.field.as_str() { + "name" => format!("{:?}", self.name), + "email" => format!("{:?}", self.email), + "linkedin" => format!("{:?}", self.linkedin), + "github" => format!("{:?}", self.github), + "notes" => format!("{:?}", self.notes), + _ => format!("Unknown field: {}", input.field), + } + } + } + "clear" => { + if input.field == "form" { + *self = Form::default(); + "Form cleared".to_string() + } else { + match input.field.as_str() { + "name" => self.name.clear(), + "email" => self.email.clear(), + "linkedin" => self.linkedin.clear(), + "github" => self.github.clear(), + "notes" => self.notes.clear(), + _ => return format!("Unknown field: {}", input.field), + } + format!("Cleared {}", input.field) + } + } + "submit" => { + if self.is_ready() { + "Form submitted".to_string() + } else { + "Cannot submit: name and email are required".to_string() + } + } + _ => format!("Unknown action: {}", input.action), + } + } + + /// Returns the tool definition JSON for the Claude API request + pub fn tool_definition() -> serde_json::Value { + serde_json::json!({ + "name": "update_form", + "description": "Update the job application form. Use this to write, read, or clear form fields. Fields: name (required), email (required), linkedin, github, notes. Use field 'form' with action 'clear' to reset all fields, 'read' to see all values, or 'submit' to submit the application.", + "input_schema": { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": ["name", "email", "linkedin", "github", "notes", "form"], + "description": "Which field to act on. 'form' targets the whole form." + }, + "action": { + "type": "string", + "enum": ["write", "read", "clear", "submit"], + "description": "What to do: write a value, read the current value, clear it, or submit." + }, + "value": { + "type": "string", + "description": "The value to write (only used with action 'write')." + } + }, + "required": ["field", "action"] + } + }) + } + + /// Render the form as a sidebar Element + pub fn view<'a, Message: Clone + 'a>( + &self, + on_submit: Option, + ) -> Element<'a, Message> { + let dim = iced::Color::from_rgb(0.5, 0.5, 0.5); + + let field = |label: &'static str, value: String| -> Element<'a, Message> { + column![ + text(label).size(12).color(dim), + if value.is_empty() { + text("—").size(16) + } else { + text(value).size(16) + }, + ] + .spacing(2) + .into() + }; + + let submit = button("Submit").on_press_maybe(on_submit); + + let fields = scrollable( + column![ + text("Application").size(20), + field("Name *", self.name.clone()), + field("Email *", self.email.clone()), + field("LinkedIn", self.linkedin.clone()), + field("GitHub", self.github.clone()), + field("Notes", self.notes.clone()), + ] + .spacing(12) + .padding(16), + ); + + container(stack![bottom_right(submit).padding(16), fields]) + .style(theme::sidebar) + .width(280) + .height(Fill) + .into() + } +} diff --git a/vui/src/main.rs b/vui/src/main.rs index 0a57487..f4c7ee1 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -1,12 +1,13 @@ mod api; mod audio; +mod form; mod theme; use api::claude; use iced::Length::FillPortion; use iced::time::{self, milliseconds}; use iced::widget::{ - bottom, canvas, center, column, container, right, row, scrollable, space, stack, text, + bottom, canvas, column, container, right, row, scrollable, space, stack, text, }; use iced::{ Center, Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme, color, @@ -31,7 +32,8 @@ enum Message { TtsReady(Result, String>), TtsFinished, TranscriptionReady(Result), - ClaudeReady(Result), + ClaudeReady(Result), + SubmitForm, StartListening, StopListening, ThemeChanged(iced::theme::Mode), @@ -65,6 +67,7 @@ struct App { cache: canvas::Cache, messages: Vec, transcript: Vec, + form: form::Form, elevenlabs: Option>, chat: Option>, playback: Option, @@ -109,6 +112,7 @@ impl App { cache: canvas::Cache::default(), messages: Vec::new(), transcript: Vec::new(), + form: form::Form::default(), elevenlabs, chat, playback, @@ -200,29 +204,85 @@ impl App { } Message::ClaudeReady(result) => match result { - Ok(response) => { - let is_complete = response.contains("APPLICATION_COMPLETE"); - let display_text = response - .replace("APPLICATION_COMPLETE", "") - .trim() - .to_string(); - - self.messages - .push(claude::Message::assistant(&display_text)); - - if is_complete { - // Show text immediately when done since there's no next interaction - self.transcript.push(Line { - role: Role::Assistant, - text: display_text.clone(), + Ok(result) => { + // Build the assistant content blocks for message history + let mut blocks = Vec::new(); + if !result.text.is_empty() { + blocks.push(claude::ContentBlock::Text { + text: result.text.clone(), }); - self.state = State::Done; - } else { - self.state = State::Processing { - pending_text: Some(display_text.clone()), - }; + } + for tu in &result.tool_uses { + blocks.push(claude::ContentBlock::ToolUse { + id: tu.id.clone(), + name: tu.name.clone(), + input: tu.input.clone(), + }); + } + if !blocks.is_empty() { + self.messages.push(claude::Message::assistant_blocks(blocks)); } + // Process tool uses if stop_reason is "tool_use" + if result.stop_reason == "tool_use" && !result.tool_uses.is_empty() { + let mut submit_requested = false; + for tu in &result.tool_uses { + if tu.name == "update_form" { + if let Ok(tool_input) = + serde_json::from_value::(tu.input.clone()) + { + let tool_result = self.form.apply(&tool_input); + if tool_input.action == "submit" && self.form.is_ready() { + submit_requested = true; + } + self.messages.push(claude::Message::tool_result( + &tu.id, + &tool_result, + )); + } else { + self.messages.push(claude::Message::tool_result( + &tu.id, + "Error: invalid tool input", + )); + } + } + } + + if submit_requested { + self.state = State::Done; + if !result.text.is_empty() { + self.transcript.push(Line { + role: Role::Assistant, + text: result.text.clone(), + }); + } + return Task::done(Message::SubmitForm); + } + + // Re-send to Claude to continue the conversation + if let Some(chat) = &self.chat { + let chat = chat.clone(); + let messages = self.messages.clone(); + let tools = vec![form::Form::tool_definition()]; + return Task::perform( + async move { chat.send(&messages, &tools).await }, + Message::ClaudeReady, + ); + } + return Task::none(); + } + + // Normal end_turn — display text and TTS + let display_text = result.text.clone(); + + if display_text.is_empty() { + return Task::done(Message::StartListening); + } + + self.state = State::Processing { + pending_text: Some(display_text.clone()), + }; + if let Some(tts) = &self.elevenlabs { let tts = tts.clone(); Task::perform( @@ -230,11 +290,9 @@ impl App { Message::TtsReady, ) } else { - // No TTS — show text immediately as fallback - if !is_complete - && let State::Processing { - ref mut pending_text, - } = self.state + if let State::Processing { + ref mut pending_text, + } = self.state && let Some(text) = pending_text.take() { self.transcript.push(Line { @@ -242,7 +300,7 @@ impl App { text, }); } - Task::none() + Task::done(Message::StartListening) } } Err(e) => { @@ -256,6 +314,12 @@ impl App { } }, + Message::SubmitForm => { + log::info!("Form submitted: {:?}", self.form); + self.state = State::Done; + Task::none() + } + Message::TtsReady(result) => match result { Ok(audio_data) => { // Reveal the assistant's response now that audio is ready @@ -363,8 +427,9 @@ impl App { if let Some(chat) = &self.chat { let chat = chat.clone(); let messages = self.messages.clone(); + let tools = vec![form::Form::tool_definition()]; Task::perform( - async move { chat.send(&messages).await }, + async move { chat.send(&messages, &tools).await }, Message::ClaudeReady, ) } else { @@ -396,7 +461,7 @@ impl App { .transcript .iter() .map(|line| { - let bubble = container(text(&line.text).size(18)) + let bubble = container(text(&line.text).size(16)) .padding(padding::all(10).left(14).right(14)) .max_width(500); @@ -408,7 +473,7 @@ impl App { .collect(); let transcript_view = if transcript_lines.is_empty() { - bottom(text("Say something to begin...").size(18).color(dim_color)).center_x(Fill) + bottom(text("Say something to begin...").size(16).color(dim_color)).center_x(Fill) } else { bottom( scrollable( @@ -447,7 +512,7 @@ impl App { _ => dim_color, }; - let content = stack![ + let voice_area = stack![ bottom(transcript_view).width(Fill).padding(10), container( column![ @@ -461,9 +526,18 @@ impl App { ) .center_y(Fill) .style(theme::fade), - ]; + ] + .width(Fill); + + let sidebar = self.form.view( + if self.form.is_ready() { + Some(Message::SubmitForm) + } else { + None + }, + ); - center(content).into() + row![voice_area, sidebar].into() } fn subscription(&self) -> Subscription { diff --git a/vui/src/theme.rs b/vui/src/theme.rs index 2f811d5..18e5d44 100644 --- a/vui/src/theme.rs +++ b/vui/src/theme.rs @@ -1,6 +1,6 @@ use iced::theme::{Custom, Palette}; use iced::widget::container; -use iced::{Color, Theme, border, color, gradient}; +use iced::{Border, Color, Theme, border, color, gradient}; use std::sync::Arc; pub fn paper() -> Theme { @@ -51,6 +51,19 @@ pub fn assistant(theme: &Theme) -> container::Style { } } +pub fn sidebar(theme: &Theme) -> container::Style { + let palette = theme.extended_palette(); + container::Style { + background: Some(palette.background.weak.color.into()), + border: Border { + color: palette.background.strong.color, + width: 1.0, + radius: 0.0.into(), + }, + ..container::Style::default() + } +} + pub fn fade(theme: &Theme) -> container::Style { let bg = theme.palette().background; let bg_transparent = Color { a: 0.0, ..bg }; From eed0d83d633a835c14b1e95920fe01f9c03737bc Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:40:52 -0500 Subject: [PATCH 11/18] fix: revamped prompt to enable tool use --- vui/prompt.txt | 42 ++++++++++++++++++++++++++++++++++++++++++ vui/src/api/claude.rs | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 vui/prompt.txt diff --git a/vui/prompt.txt b/vui/prompt.txt new file mode 100644 index 0000000..d0d8760 --- /dev/null +++ b/vui/prompt.txt @@ -0,0 +1,42 @@ +You are the Root Ventures application system. This is a voice-based kiosk for people to apply for the Venture Capital Associate position at Root Ventures, a deep tech seed fund in San Francisco. + +Your ONLY purpose is to conduct this application interview. Do not discuss anything else. If the user asks about unrelated topics, politely redirect them back to the application. You are not a general assistant — you exist solely to help people apply to Root Ventures. + +## Starting the Conversation + +Greet the user warmly and let them know this is the Root Ventures associate application. Briefly describe the role — Root Ventures is a deep tech seed fund looking for technical talent with curiosity about startups — then begin collecting their information. + +## Collecting Information + +Collect the following through natural conversation: +- Name (required) +- Email (required) +- LinkedIn profile (ask for this) +- GitHub username (ask for this) +- Notes about why they're interested (built from several interview questions) + +Ask for each piece one at a time. If the user doesn't have or doesn't want to provide LinkedIn, GitHub, or notes, accept that gracefully — but always ask. + +The Notes should come from 3-4 casual interview questions. Don't ask a generic "what else do you want us to know". We are trying to understand what motivates the applicant. Everyone at Root Ventures loves technology and startups. We are looking for people who are passionate about building. Find out what they do in their time away from work that reflects their shared love of technology and building. The subsequent questions should demonstrate interest in the projects the applicant is working on, what motivated them, and how it reflects their self-starter potential. Additional questions can also try to understand their social nature — do they like going to or hosting events? Do they like building community online? We'd like an applicant that can quickly find great potential founders to fund. + +Give them a sense of how many questions are left when getting close to the end. + +## Style + +- Be conversational and natural. It's ok to be funny. Don't use so many exclamation points. +- Don't make it feel like a form — have a real conversation. +- Keep responses short and suitable for voice — no more than 2-3 sentences at a time. +- After successful submission, encourage them to explore root.vc. + + +You have access to an `update_form` tool that writes to a visible application form displayed alongside this conversation. + +As you collect information from the applicant, IMMEDIATELY call `update_form` to write each piece to the form. Do not wait until the end — fill fields the moment the user provides them. For example, if the user says "I'm Jane Doe", immediately call update_form with field "name", action "write", value "Jane Doe". + +Available fields: name, email, linkedin, github, notes +Available actions: write, read, clear, submit + +When you have collected all the information and the applicant confirms, use action "submit" with field "form" to submit the application. + +For the "notes" field, concatenate the interview questions and the user's raw answers rather than summarizing. + diff --git a/vui/src/api/claude.rs b/vui/src/api/claude.rs index b2e1d0a..796b6b5 100644 --- a/vui/src/api/claude.rs +++ b/vui/src/api/claude.rs @@ -67,7 +67,7 @@ pub struct ToolUse { pub input: serde_json::Value, } -const SYSTEM: &str = include_str!("../../../prompt.txt"); +const SYSTEM: &str = include_str!("../../prompt.txt"); impl Client { pub fn new(api_key: String) -> Self { From 712f264ac66d0530347a000675e43558a09f36f8 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:11:40 -0500 Subject: [PATCH 12/18] fix: hide form while empty --- vui/src/form.rs | 9 +++++++-- vui/src/main.rs | 40 ++++++++++++++++++++++++---------------- vui/src/theme.rs | 4 ++-- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/vui/src/form.rs b/vui/src/form.rs index ca6b715..5ada41c 100644 --- a/vui/src/form.rs +++ b/vui/src/form.rs @@ -5,7 +5,7 @@ use serde::Deserialize; use crate::theme; /// The form fields for a Root Ventures application -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct Form { pub name: String, pub email: String, @@ -23,6 +23,11 @@ pub struct ToolInput { } impl Form { + /// Returns true when the form is empty + pub fn is_empty(&self) -> bool { + self == &Form::default() + } + /// Returns true when required fields are filled pub fn is_ready(&self) -> bool { !self.name.trim().is_empty() && !self.email.trim().is_empty() @@ -151,7 +156,7 @@ impl Form { container(stack![bottom_right(submit).padding(16), fields]) .style(theme::sidebar) - .width(280) + .width(Fill) .height(Fill) .into() } diff --git a/vui/src/main.rs b/vui/src/main.rs index f4c7ee1..5f084b9 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -6,9 +6,7 @@ mod theme; use api::claude; use iced::Length::FillPortion; use iced::time::{self, milliseconds}; -use iced::widget::{ - bottom, canvas, column, container, right, row, scrollable, space, stack, text, -}; +use iced::widget::{bottom, canvas, column, container, right, row, scrollable, space, stack, text}; use iced::{ Center, Color, Element, Fill, Point, Rectangle, Renderer, Subscription, Task, Theme, color, mouse, padding, system, @@ -220,7 +218,8 @@ impl App { }); } if !blocks.is_empty() { - self.messages.push(claude::Message::assistant_blocks(blocks)); + self.messages + .push(claude::Message::assistant_blocks(blocks)); } // Process tool uses if stop_reason is "tool_use" @@ -235,10 +234,8 @@ impl App { if tool_input.action == "submit" && self.form.is_ready() { submit_requested = true; } - self.messages.push(claude::Message::tool_result( - &tu.id, - &tool_result, - )); + self.messages + .push(claude::Message::tool_result(&tu.id, &tool_result)); } else { self.messages.push(claude::Message::tool_result( &tu.id, @@ -529,15 +526,26 @@ impl App { ] .width(Fill); - let sidebar = self.form.view( - if self.form.is_ready() { - Some(Message::SubmitForm) - } else { - None - }, - ); + // let sidebar = (!self.form.is_empty()).then(|| { + // self.form.view(if self.form.is_ready() { + // Some(Message::SubmitForm) + // } else { + // None + // }) + // }); + + let Some(sidebar) = (!self.form.is_empty()).then(|| { + self.form + .view(self.form.is_ready().then(|| Message::SubmitForm)) + }) else { + return voice_area.into(); + }; - row![voice_area, sidebar].into() + stack![ + row![voice_area, space().width(280)], + right(container(sidebar).width(280)) + ] + .into() } fn subscription(&self) -> Subscription { diff --git a/vui/src/theme.rs b/vui/src/theme.rs index 18e5d44..59c2674 100644 --- a/vui/src/theme.rs +++ b/vui/src/theme.rs @@ -68,7 +68,7 @@ pub fn fade(theme: &Theme) -> container::Style { let bg = theme.palette().background; let bg_transparent = Color { a: 0.0, ..bg }; gradient::Linear::new(std::f32::consts::PI) - .add_stop(0.6, bg) - .add_stop(0.8, bg_transparent) + .add_stop(0.4, bg) + .add_stop(0.7, bg_transparent) .into() } From 470c2e76c1b6d0c2307803c6ee993895279e9024 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:11:48 -0500 Subject: [PATCH 13/18] fix: talk less. --- vui/prompt.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vui/prompt.txt b/vui/prompt.txt index d0d8760..616337e 100644 --- a/vui/prompt.txt +++ b/vui/prompt.txt @@ -4,7 +4,7 @@ Your ONLY purpose is to conduct this application interview. Do not discuss anyth ## Starting the Conversation -Greet the user warmly and let them know this is the Root Ventures associate application. Briefly describe the role — Root Ventures is a deep tech seed fund looking for technical talent with curiosity about startups — then begin collecting their information. +Keep your greeting short — one sentence is fine. Don't dump a description of the role unprompted. Just say hi, let them know this is the Root Ventures associate application, and ask their name. Save details about the role for when they ask or when it comes up naturally. ## Collecting Information @@ -25,7 +25,8 @@ Give them a sense of how many questions are left when getting close to the end. - Be conversational and natural. It's ok to be funny. Don't use so many exclamation points. - Don't make it feel like a form — have a real conversation. -- Keep responses short and suitable for voice — no more than 2-3 sentences at a time. +- Keep responses SHORT. One sentence is usually enough. Two max. This is voice — long responses feel like a lecture. Ask one question, then wait. +- Let the applicant do most of the talking. You're the interviewer, not the presenter. - After successful submission, encourage them to explore root.vc. From 48b7d958827e79a50e4e05f3c7399c72b86b19ea Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:28:58 -0500 Subject: [PATCH 14/18] feat: barge in --- vui/prompt.txt | 15 ++- vui/src/audio/playback.rs | 41 +++++++- vui/src/form.rs | 49 +++++++++ vui/src/main.rs | 208 +++++++++++++++++++++++++++++++++----- 4 files changed, 282 insertions(+), 31 deletions(-) diff --git a/vui/prompt.txt b/vui/prompt.txt index 616337e..0723f83 100644 --- a/vui/prompt.txt +++ b/vui/prompt.txt @@ -21,6 +21,19 @@ The Notes should come from 3-4 casual interview questions. Don't ask a generic " Give them a sense of how many questions are left when getting close to the end. +## Field Formatting + +When writing to the form, clean and format the values — don't just paste what the user said verbatim: +- **name**: Full name (first and last). If you only got a first name, ask for the last name before writing. +- **email**: Must look like a valid email. If STT garbled it, ask the user to repeat or spell it. +- **linkedin**: Full URL (e.g., linkedin.com/in/username). If the user gives just a name, construct the URL or ask for confirmation. +- **github**: Full URL (e.g., github.com/username). If user says "my GitHub is jackson codes", write "github.com/jacksoncodes" — normalize casing, remove spaces. +- **notes**: Write a brief narrative summary of what you learned about the applicant from the interview questions. NOT a Q&A transcript — a paragraph or two that captures who they are and what drives them. + +## Clarification + +This is voice input with speech-to-text. Some words WILL be garbled or misheard. If anything the user said doesn't make sense — especially names, emails, URLs, or technical terms — ask them to repeat or spell it out. Better to double-check than to submit wrong information. Read back emails and URLs to confirm. + ## Style - Be conversational and natural. It's ok to be funny. Don't use so many exclamation points. @@ -39,5 +52,5 @@ Available actions: write, read, clear, submit When you have collected all the information and the applicant confirms, use action "submit" with field "form" to submit the application. -For the "notes" field, concatenate the interview questions and the user's raw answers rather than summarizing. +For the "notes" field, write a narrative summary after completing the interview questions. Don't write notes until you've asked all the questions. diff --git a/vui/src/audio/playback.rs b/vui/src/audio/playback.rs index cec2e98..b738a64 100644 --- a/vui/src/audio/playback.rs +++ b/vui/src/audio/playback.rs @@ -11,10 +11,11 @@ pub struct Playback { status_receiver: mpsc::Receiver, } -#[allow(dead_code)] enum Command { Play(Vec), Stop, + Pause, + Resume, } #[derive(Debug, Clone)] @@ -22,6 +23,7 @@ pub enum Status { Playing, Level(f32), Finished, + Paused, Error(String), } @@ -106,17 +108,37 @@ impl Playback { let level = Arc::new(AtomicU32::new(0)); let level_clone = level.clone(); - // Convert to f32 and wrap with level tracker let f32_source = source.convert_samples::(); let tracked_source = Level::new(f32_source, level); sink.append(tracked_source); - // Poll level while playing + // Poll level while playing, also check for commands while !sink.empty() { + match receiver.try_recv() { + Ok(Command::Pause) => { + sink.pause(); + let _ = status_sender.send(Status::Paused); + } + Ok(Command::Resume) => { + sink.play(); + } + Ok(Command::Stop) => { + sink.stop(); + } + Ok(Command::Play(_)) => {} + Err(_) => {} + } + + if sink.empty() { + break; + } + let bits = level_clone.load(Ordering::Relaxed); let rms = f32::from_bits(bits); - let _ = status_sender.send(Status::Level(rms)); + let _ = status_sender.send(Status::Level( + if sink.is_paused() { 0.0 } else { rms }, + )); thread::sleep(Duration::from_millis(16)); } @@ -131,6 +153,8 @@ impl Playback { Command::Stop => { sink.stop(); } + Command::Pause => {} + Command::Resume => {} } } }); @@ -145,11 +169,18 @@ impl Playback { let _ = self.sender.send(Command::Play(audio_data)); } - #[allow(dead_code)] pub fn stop(&self) { let _ = self.sender.send(Command::Stop); } + pub fn pause(&self) { + let _ = self.sender.send(Command::Pause); + } + + pub fn resume(&self) { + let _ = self.sender.send(Command::Resume); + } + pub fn try_recv_status(&self) -> Option { self.status_receiver.try_recv().ok() } diff --git a/vui/src/form.rs b/vui/src/form.rs index 5ada41c..3783268 100644 --- a/vui/src/form.rs +++ b/vui/src/form.rs @@ -91,6 +91,17 @@ impl Form { } } + /// Returns the form data as JSON for the webhook POST + pub fn to_json(&self) -> serde_json::Value { + serde_json::json!({ + "name": self.name, + "email": self.email, + "linkedin": self.linkedin, + "github": self.github, + "notes": self.notes + }) + } + /// Returns the tool definition JSON for the Claude API request pub fn tool_definition() -> serde_json::Value { serde_json::json!({ @@ -160,4 +171,42 @@ impl Form { .height(Fill) .into() } + + /// Render the form as submitted (read-only, no submit button) + pub fn view_submitted<'a, Message: Clone + 'a>(&self) -> Element<'a, Message> { + let dim = iced::Color::from_rgb(0.5, 0.5, 0.5); + let green = iced::Color::from_rgb(0.2, 0.65, 0.3); + + let field = |label: &'static str, value: String| -> Element<'a, Message> { + column![ + text(label).size(12).color(dim), + if value.is_empty() { + text("—").size(16) + } else { + text(value).size(16) + }, + ] + .spacing(2) + .into() + }; + + let fields = scrollable( + column![ + text("Application Submitted").size(20).color(green), + field("Name", self.name.clone()), + field("Email", self.email.clone()), + field("LinkedIn", self.linkedin.clone()), + field("GitHub", self.github.clone()), + field("Notes", self.notes.clone()), + ] + .spacing(12) + .padding(16), + ); + + container(fields) + .style(theme::sidebar) + .width(Fill) + .height(Fill) + .into() + } } diff --git a/vui/src/main.rs b/vui/src/main.rs index 5f084b9..e20fa19 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -14,6 +14,14 @@ use iced::{ use std::sync::Arc; use std::time::Instant; +/// RMS threshold for barge-in detection during TTS playback. +/// 5x normal speech threshold (0.01) to avoid TTS echo triggering false barge-ins. +const BARGE_IN_THRESHOLD: f32 = 0.05; + +/// Consecutive audio chunks above threshold required to trigger barge-in. +/// ~5 chunks ≈ 80-160ms of sustained loud speech. +const BARGE_IN_FRAMES: usize = 5; + fn main() -> iced::Result { dotenvy::dotenv().ok(); env_logger::init(); @@ -32,8 +40,10 @@ enum Message { TranscriptionReady(Result), ClaudeReady(Result), SubmitForm, + SubmitResult(Result), StartListening, StopListening, + ResumePlayback, ThemeChanged(iced::theme::Mode), } @@ -42,8 +52,10 @@ enum Message { enum State { Idle, Speaking { text: String }, + BargedIn, Listening, Processing { pending_text: Option }, + Submitted, Done, } @@ -75,6 +87,8 @@ struct App { playback_level: f32, silence_frames: usize, has_speech: bool, + barge_in_frames: usize, + tts_paused: bool, theme_mode: iced::theme::Mode, } @@ -103,6 +117,7 @@ impl App { }; let playback = audio::Playback::new().ok(); + let capture = audio::Capture::new().ok(); let app = Self { state: State::Idle, @@ -114,12 +129,14 @@ impl App { elevenlabs, chat, playback, - capture: None, + capture, audio_buffer: Vec::new(), audio_level: 0.0, playback_level: 0.0, silence_frames: 0, has_speech: false, + barge_in_frames: 0, + tts_paused: false, theme_mode: iced::theme::Mode::Dark, }; @@ -142,22 +159,92 @@ impl App { match status { audio::playback::Status::Finished => { self.playback_level = 0.0; - return Task::done(Message::TtsFinished); + if matches!(self.state, State::Speaking { .. }) { + return Task::done(Message::TtsFinished); + } + // TTS ended during barge-in flow — mark not resumable + self.tts_paused = false; } audio::playback::Status::Error(e) => { log::error!("Playback error: {}", e); self.playback_level = 0.0; + self.tts_paused = false; return Task::done(Message::TtsFinished); } audio::playback::Status::Level(level) => { let target = (level * 5.0).min(1.0); self.playback_level = self.playback_level * 0.7 + target * 0.3; } + audio::playback::Status::Paused => { + self.playback_level = 0.0; + } _ => {} } } } + // Barge-in detection during Speaking + if matches!(self.state, State::Speaking { .. }) + && let Some(ref capture) = self.capture + { + while let Some(samples) = capture.try_recv() { + let rms: f32 = (samples.iter().map(|s| s * s).sum::() + / samples.len() as f32) + .sqrt(); + + if rms >= BARGE_IN_THRESHOLD { + self.barge_in_frames += 1; + } else { + self.barge_in_frames = 0; + } + + if self.barge_in_frames >= BARGE_IN_FRAMES { + log::debug!("Barge-in detected, pausing TTS"); + if let Some(ref playback) = self.playback { + playback.pause(); + } + self.tts_paused = true; + self.state = State::BargedIn; + self.audio_buffer.clear(); + self.has_speech = true; + self.silence_frames = 0; + self.barge_in_frames = 0; + break; + } + } + } + + // Capture during BargedIn (same silence detection as Listening) + if matches!(self.state, State::BargedIn) + && let Some(ref capture) = self.capture + { + while let Some(samples) = capture.try_recv() { + let rms: f32 = (samples.iter().map(|s| s * s).sum::() + / samples.len() as f32) + .sqrt(); + + let target_level = (rms * 10.0).min(1.0); + self.audio_level = self.audio_level * 0.8 + target_level * 0.2; + + if rms >= 0.01 { + self.has_speech = true; + self.silence_frames = 0; + } else { + self.silence_frames += 1; + } + + self.audio_buffer.extend(samples); + + if self.audio_buffer.len() > 16000 && self.silence_frames > 90 { + if self.has_speech { + return Task::done(Message::StopListening); + } else { + return Task::done(Message::ResumePlayback); + } + } + } + } + // Check for audio input while listening if matches!(self.state, State::Listening) && let Some(ref capture) = self.capture @@ -313,7 +400,30 @@ impl App { Message::SubmitForm => { log::info!("Form submitted: {:?}", self.form); - self.state = State::Done; + println!("\n=== APPLICATION SUBMITTED ===\n{}\n", serde_json::to_string_pretty(&self.form.to_json()).unwrap()); + self.state = State::Submitted; + let payload = self.form.to_json(); + let http = reqwest::Client::new(); + Task::perform( + async move { + http.post("https://hooks.attio.com/w/8b9f7dfe-0c44-4531-86e5-70f0ad1ec853/fffbdf49-989b-40bc-af97-fc602fd5bce9") + .json(&payload) + .send() + .await + .map_err(|e| e.to_string())? + .text() + .await + .map_err(|e| e.to_string()) + }, + Message::SubmitResult, + ) + } + + Message::SubmitResult(result) => { + match &result { + Ok(body) => log::info!("Submitted to Attio: {}", body), + Err(e) => log::error!("Attio webhook error: {}", e), + } Task::none() } @@ -338,6 +448,11 @@ impl App { if let Some(ref playback) = self.playback { playback.play(audio_data); + // Don't start capture here — opening a CoreAudio input stream + // causes the audio graph to reconfigure and glitches playback. + // Capture is started lazily in the Tick handler after a short delay. + self.barge_in_frames = 0; + self.tts_paused = false; self.state = State::Speaking { text: response_text.unwrap_or_default(), }; @@ -362,13 +477,10 @@ impl App { } }, - Message::TtsFinished => { - if matches!(self.state, State::Done) { - Task::none() - } else { - Task::done(Message::StartListening) - } - } + Message::TtsFinished => match self.state { + State::Speaking { .. } => Task::done(Message::StartListening), + _ => Task::none(), + }, Message::StartListening => { self.state = State::Listening; @@ -376,12 +488,10 @@ impl App { self.audio_level = 0.0; self.silence_frames = 0; self.has_speech = false; - self.capture = audio::Capture::new().ok(); Task::none() } Message::StopListening => { - self.capture = None; self.state = State::Processing { pending_text: None }; self.audio_level = 0.0; @@ -399,6 +509,26 @@ impl App { } } + Message::ResumePlayback => { + if self.tts_paused { + log::debug!("Resuming TTS after noise barge-in"); + if let Some(ref playback) = self.playback { + playback.resume(); + } + self.state = State::Speaking { + text: String::new(), + }; + self.audio_buffer.clear(); + self.barge_in_frames = 0; + self.has_speech = false; + self.silence_frames = 0; + Task::none() + } else { + // TTS finished while we were transcribing — nothing to resume + Task::done(Message::StartListening) + } + } + Message::TranscriptionReady(result) => match result { Ok(transcript) => { let transcript = transcript.trim().to_string(); @@ -412,9 +542,20 @@ impl App { if is_noise { log::debug!("Filtered noise: {:?}", transcript); + if self.tts_paused { + return Task::done(Message::ResumePlayback); + } return Task::done(Message::StartListening); } + // Real speech — if we barged in, stop TTS permanently + if self.tts_paused { + if let Some(ref playback) = self.playback { + playback.stop(); + } + self.tts_paused = false; + } + self.messages.push(claude::Message::user(&transcript)); self.transcript.push(Line { role: Role::User, @@ -435,6 +576,9 @@ impl App { } Err(e) => { log::debug!("Transcription error: {}", e); + if self.tts_paused { + return Task::done(Message::ResumePlayback); + } self.transcript.push(Line { role: Role::Assistant, text: format!("Error: {}", e), @@ -497,15 +641,16 @@ impl App { // Status indicator let status = match &self.state { - State::Listening => "Listening...", + State::Listening | State::BargedIn => "Listening...", State::Processing { .. } => "Processing...", State::Speaking { .. } => "Speaking...", + State::Submitted => "Submitted", State::Done => "Done", State::Idle => "", }; let status_color = match &self.state { - State::Listening => color!(0xcc3e28), // Red for recording + State::Listening | State::BargedIn => color!(0xcc3e28), _ => dim_color, }; @@ -526,19 +671,14 @@ impl App { ] .width(Fill); - // let sidebar = (!self.form.is_empty()).then(|| { - // self.form.view(if self.form.is_ready() { - // Some(Message::SubmitForm) - // } else { - // None - // }) - // }); - - let Some(sidebar) = (!self.form.is_empty()).then(|| { + let sidebar = if matches!(self.state, State::Submitted) { + self.form.view_submitted() + } else { + if self.form.is_empty() { + return voice_area.into(); + } self.form .view(self.form.is_ready().then(|| Message::SubmitForm)) - }) else { - return voice_area.into(); }; stack![ @@ -631,6 +771,24 @@ impl canvas::Program for App { (12.0, 0.0, Color::from_rgb(0.85, 0.2, 0.2)) } } + State::BargedIn => { + let audio_pulse = self.audio_level * 40.0; + let base_pulse = if self.audio_level > 0.05 { + 10.0 * (t * 2.0).sin().abs() + } else { + 0.0 + }; + ( + 60.0, + base_pulse + audio_pulse, + Color::from_rgb(0.85, 0.2, 0.2), + ) + } + State::Submitted => ( + 60.0, + 3.0 * (t * 1.0).sin().abs(), + Color::from_rgb(0.2, 0.65, 0.3), + ), State::Idle | State::Done => ( 60.0, 5.0 * (t * 1.5).sin().abs(), From 0c70cce83bb5eb92cf38196be871c371819d7294 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:46:04 -0500 Subject: [PATCH 15/18] fix: start with greeting --- vui/src/audio/playback.rs | 1 + vui/src/main.rs | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/vui/src/audio/playback.rs b/vui/src/audio/playback.rs index b738a64..0d47ce9 100644 --- a/vui/src/audio/playback.rs +++ b/vui/src/audio/playback.rs @@ -112,6 +112,7 @@ impl Playback { let tracked_source = Level::new(f32_source, level); sink.append(tracked_source); + sink.play(); // Ensure sink is playing (might be paused from previous barge-in) // Poll level while playing, also check for commands while !sink.empty() { diff --git a/vui/src/main.rs b/vui/src/main.rs index e20fa19..6b8a3af 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -120,10 +120,10 @@ impl App { let capture = audio::Capture::new().ok(); let app = Self { - state: State::Idle, + state: State::Processing { pending_text: None }, start: Instant::now(), cache: canvas::Cache::default(), - messages: Vec::new(), + messages: vec![claude::Message::user("Hi!")], // Initial greeting trigger (not shown in transcript) transcript: Vec::new(), form: form::Form::default(), elevenlabs, @@ -140,8 +140,21 @@ impl App { theme_mode: iced::theme::Mode::Dark, }; + // Start with a greeting from Claude (send "Hi!" to trigger first response) + let greeting_task = if let Some(chat) = &app.chat { + let chat = chat.clone(); + let messages = vec![claude::Message::user("Hi!")]; + let tools = vec![form::Form::tool_definition()]; + Task::perform( + async move { chat.send(&messages, &tools).await }, + Message::ClaudeReady, + ) + } else { + Task::none() + }; + let task = Task::batch([ - Task::done(Message::StartListening), + greeting_task, system::theme().map(Message::ThemeChanged), ]); @@ -235,7 +248,7 @@ impl App { self.audio_buffer.extend(samples); - if self.audio_buffer.len() > 16000 && self.silence_frames > 90 { + if self.audio_buffer.len() > 16000 && self.silence_frames > 150 { if self.has_speech { return Task::done(Message::StopListening); } else { @@ -268,7 +281,7 @@ impl App { self.audio_buffer.extend(samples); // Stop after ~1.5 seconds of silence - if self.audio_buffer.len() > 16000 && self.silence_frames > 90 { + if self.audio_buffer.len() > 16000 && self.silence_frames > 150 { if self.has_speech { return Task::done(Message::StopListening); } else { @@ -531,7 +544,14 @@ impl App { Message::TranscriptionReady(result) => match result { Ok(transcript) => { - let transcript = transcript.trim().to_string(); + // Strip trailing parentheticals like "(laughs)" or "(background noise)" + let transcript = transcript + .trim() + .trim_end_matches(|c: char| c == ')' || c.is_whitespace()) + .rsplit_once('(') + .map(|(before, _)| before.trim()) + .unwrap_or(transcript.trim()) + .to_string(); log::debug!("User said: {}", transcript); // Filter out noise/static @@ -614,7 +634,7 @@ impl App { .collect(); let transcript_view = if transcript_lines.is_empty() { - bottom(text("Say something to begin...").size(16).color(dim_color)).center_x(Fill) + bottom(text("").size(16)).center_x(Fill) // Empty while waiting for greeting } else { bottom( scrollable( @@ -733,7 +753,7 @@ impl canvas::Program for App { frame.stroke( &path, canvas::Stroke::default() - .with_color(Color::from_rgb(1.0, 0.6, 0.2)) + .with_color(Color::from_rgb(0.35, 0.33, 0.30)) .with_width(4.0), ); } else { From eea95200c7f6aacceb62810271ce4ed3c7755d64 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Thu, 5 Feb 2026 17:06:56 -0500 Subject: [PATCH 16/18] refactor: fold our voice script into `install.sh` --- install.sh | 239 +++++++++++++++++++++++++++++++++++------------------ run.sh | 11 --- 2 files changed, 159 insertions(+), 91 deletions(-) delete mode 100755 run.sh diff --git a/install.sh b/install.sh index 651c835..3ace7fd 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,6 @@ #!/bin/bash -# Root Ventures Apply Skill - One-line installer for Claude CLI +# Root Ventures Apply Skill - One-line installer +# Supports both text mode (Claude CLI) and voice mode (native app) set -e @@ -7,103 +8,181 @@ SKILL_DIR="$HOME/.claude/skills/root-ventures-apply" REPO_URL="https://raw.githubusercontent.com/rootvc/claude-apply-skill/main" echo "" -echo "🚀 Installing Root Ventures Apply Skill..." -echo "" - -# Create skills directory if it doesn't exist -mkdir -p "$SKILL_DIR" - -# Download files -echo "📥 Downloading skill files..." -curl -fsSL "$REPO_URL/skill.json" -o "$SKILL_DIR/skill.json" -curl -fsSL "$REPO_URL/prompt.txt" -o "$SKILL_DIR/prompt.txt" -curl -fsSL "$REPO_URL/apply.sh" -o "$SKILL_DIR/apply.sh" -curl -fsSL "$REPO_URL/README.md" -o "$SKILL_DIR/README.md" - -# Make apply.sh executable -chmod +x "$SKILL_DIR/apply.sh" - -echo "" -echo "✅ Root Ventures Apply Skill installed successfully!" +echo "🚀 Root Ventures Apply" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" -echo " Launching Claude..." +echo " Choose your mode:" +echo "" +echo " [1] 🎙️ Voice mode (recommended)" +echo " Native app with speech recognition and text-to-speech" echo "" -echo " Claude will confirm it's ready, then just say:" -echo " 'I want to apply to Root Ventures'" +echo " [2] 💬 Text mode" +echo " Claude CLI chat interface" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" -# Check if Claude CLI is installed -# Try multiple ways to find claude (handles aliases, PATH, and direct installation) -CLAUDE_CMD="" - -if command -v claude &> /dev/null; then - CLAUDE_CMD="claude" -elif [ -f "$HOME/.claude/local/claude" ]; then - CLAUDE_CMD="$HOME/.claude/local/claude" -elif [ -f "/usr/local/bin/claude" ]; then - CLAUDE_CMD="/usr/local/bin/claude" -fi - -if [ -z "$CLAUDE_CMD" ]; then - echo "⚠️ Claude CLI not found." - echo "" - echo "📦 Installing Claude CLI..." - echo "" - - # Install Claude CLI using official installer - echo "Installing Claude CLI..." - curl -fsSL https://claude.ai/install.sh | bash - - # Add to PATH if not already there - if ! command -v claude &> /dev/null; then - export PATH="$HOME/.claude/local:$PATH" - - # Add to shell profile - SHELL_PROFILE="" - if [ -f "$HOME/.zshrc" ]; then - SHELL_PROFILE="$HOME/.zshrc" - elif [ -f "$HOME/.bashrc" ]; then - SHELL_PROFILE="$HOME/.bashrc" - elif [ -f "$HOME/.bash_profile" ]; then - SHELL_PROFILE="$HOME/.bash_profile" +read -p "Enter choice (1 or 2): " choice + +case $choice in + 1) + # Voice mode - build and run VUI + echo "" + echo "🎙️ Starting voice mode..." + echo "" + + # Check for Rust + if ! command -v cargo &> /dev/null; then + echo "📦 Rust not found. Installing via rustup..." + echo "" + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source "$HOME/.cargo/env" + echo "" + echo "✅ Rust installed successfully!" + echo "" fi - if [ -n "$SHELL_PROFILE" ]; then - if ! grep -q "/.claude/local" "$SHELL_PROFILE"; then - echo 'export PATH="$HOME/.claude/local:$PATH"' >> "$SHELL_PROFILE" - echo "✅ Added Claude CLI to PATH in $SHELL_PROFILE" - fi + # Clone repo if needed, or use current dir if already in it + if [ -f "vui/Cargo.toml" ]; then + cd vui + elif [ -f "Cargo.toml" ] && grep -q 'name = "vui"' Cargo.toml; then + : # already in vui directory + else + TEMP_DIR=$(mktemp -d) + echo "📥 Downloading voice app..." + git clone --depth 1 https://github.com/rootvc/claude-apply-skill.git "$TEMP_DIR" + cd "$TEMP_DIR/vui" + echo "" fi - fi - # Verify installation - if command -v claude &> /dev/null; then - CLAUDE_CMD="claude" + echo "🔨 Building voice app (this may take a minute)..." echo "" - echo "✅ Claude CLI installed successfully!" + cargo build --release + echo "" - elif [ -f "$HOME/.claude/local/claude" ]; then - CLAUDE_CMD="$HOME/.claude/local/claude" + echo "✅ Build complete!" echo "" - echo "✅ Claude CLI installed successfully!" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" - else + echo " Launching voice app..." echo "" - echo "❌ Claude CLI installation failed." - echo "Please install manually from: https://claude.ai/download" + echo " Just start talking when you see the circle!" echo "" - echo "After installing, run this to apply:" - echo " claude 'Read ~/.claude/skills/root-ventures-apply/prompt.txt then I want to apply'" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" - exit 1 - fi -fi + sleep 1 + ./target/release/vui + ;; + + 2) + # Text mode - install Claude CLI skill + echo "" + echo "💬 Installing text mode skill..." + echo "" + + # Create skills directory if it doesn't exist + mkdir -p "$SKILL_DIR" + + # Download files + echo "📥 Downloading skill files..." + curl -fsSL "$REPO_URL/skill.json" -o "$SKILL_DIR/skill.json" + curl -fsSL "$REPO_URL/prompt.txt" -o "$SKILL_DIR/prompt.txt" + curl -fsSL "$REPO_URL/apply.sh" -o "$SKILL_DIR/apply.sh" + curl -fsSL "$REPO_URL/README.md" -o "$SKILL_DIR/README.md" -# Launch Claude with the skill pre-loaded -sleep 1 -exec "$CLAUDE_CMD" "Read ~/.claude/skills/root-ventures-apply/prompt.txt" + # Make apply.sh executable + chmod +x "$SKILL_DIR/apply.sh" + echo "" + echo "✅ Root Ventures Apply Skill installed successfully!" + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo " Launching Claude..." + echo "" + echo " Claude will confirm it's ready, then just say:" + echo " 'I want to apply to Root Ventures'" + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + # Check if Claude CLI is installed + # Try multiple ways to find claude (handles aliases, PATH, and direct installation) + CLAUDE_CMD="" + + if command -v claude &> /dev/null; then + CLAUDE_CMD="claude" + elif [ -f "$HOME/.claude/local/claude" ]; then + CLAUDE_CMD="$HOME/.claude/local/claude" + elif [ -f "/usr/local/bin/claude" ]; then + CLAUDE_CMD="/usr/local/bin/claude" + fi + + if [ -z "$CLAUDE_CMD" ]; then + echo "⚠️ Claude CLI not found." + echo "" + echo "📦 Installing Claude CLI..." + echo "" + + # Install Claude CLI using official installer + echo "Installing Claude CLI..." + curl -fsSL https://claude.ai/install.sh | bash + + # Add to PATH if not already there + if ! command -v claude &> /dev/null; then + export PATH="$HOME/.claude/local:$PATH" + + # Add to shell profile + SHELL_PROFILE="" + if [ -f "$HOME/.zshrc" ]; then + SHELL_PROFILE="$HOME/.zshrc" + elif [ -f "$HOME/.bashrc" ]; then + SHELL_PROFILE="$HOME/.bashrc" + elif [ -f "$HOME/.bash_profile" ]; then + SHELL_PROFILE="$HOME/.bash_profile" + fi + + if [ -n "$SHELL_PROFILE" ]; then + if ! grep -q "/.claude/local" "$SHELL_PROFILE"; then + echo 'export PATH="$HOME/.claude/local:$PATH"' >> "$SHELL_PROFILE" + echo "✅ Added Claude CLI to PATH in $SHELL_PROFILE" + fi + fi + fi + + # Verify installation + if command -v claude &> /dev/null; then + CLAUDE_CMD="claude" + echo "" + echo "✅ Claude CLI installed successfully!" + echo "" + elif [ -f "$HOME/.claude/local/claude" ]; then + CLAUDE_CMD="$HOME/.claude/local/claude" + echo "" + echo "✅ Claude CLI installed successfully!" + echo "" + else + echo "" + echo "❌ Claude CLI installation failed." + echo "Please install manually from: https://claude.ai/download" + echo "" + echo "After installing, run this to apply:" + echo " claude 'Read ~/.claude/skills/root-ventures-apply/prompt.txt then I want to apply'" + echo "" + exit 1 + fi + fi + + # Launch Claude with the skill pre-loaded + sleep 1 + exec "$CLAUDE_CMD" "Read ~/.claude/skills/root-ventures-apply/prompt.txt" + ;; + + *) + echo "" + echo "❌ Invalid choice. Please run again and enter 1 or 2." + echo "" + exit 1 + ;; +esac diff --git a/run.sh b/run.sh deleted file mode 100755 index 962f58e..0000000 --- a/run.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -set -e - -if ! command -v cargo &> /dev/null; then - echo "Rust not installed. Install from https://rustup.rs" - exit 1 -fi - -cd vui -cargo build --release -./target/release/vui From faadcadcef348b544a209a880341f0949d660477 Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Thu, 5 Feb 2026 17:07:08 -0500 Subject: [PATCH 17/18] fix: cleaner state for barging in --- vui/src/main.rs | 188 +++++++++++++++++++++++------------------------- 1 file changed, 89 insertions(+), 99 deletions(-) diff --git a/vui/src/main.rs b/vui/src/main.rs index 6b8a3af..ba11d6e 100644 --- a/vui/src/main.rs +++ b/vui/src/main.rs @@ -29,6 +29,7 @@ fn main() -> iced::Result { iced::application(App::new, App::update, App::view) .subscription(App::subscription) .theme(App::theme) + .title("root.vc • application system") .run() } @@ -51,9 +52,9 @@ enum Message { #[derive(Debug, Clone)] enum State { Idle, - Speaking { text: String }, - BargedIn, - Listening, + Speaking { text: String, barge_in_frames: usize }, + BargedIn { has_speech: bool, silence_frames: usize }, + Listening { has_speech: bool, silence_frames: usize }, Processing { pending_text: Option }, Submitted, Done, @@ -85,9 +86,6 @@ struct App { audio_buffer: Vec, audio_level: f32, playback_level: f32, - silence_frames: usize, - has_speech: bool, - barge_in_frames: usize, tts_paused: bool, theme_mode: iced::theme::Mode, } @@ -133,9 +131,6 @@ impl App { audio_buffer: Vec::new(), audio_level: 0.0, playback_level: 0.0, - silence_frames: 0, - has_speech: false, - barge_in_frames: 0, tts_paused: false, theme_mode: iced::theme::Mode::Dark, }; @@ -197,97 +192,98 @@ impl App { } // Barge-in detection during Speaking - if matches!(self.state, State::Speaking { .. }) - && let Some(ref capture) = self.capture - { - while let Some(samples) = capture.try_recv() { - let rms: f32 = (samples.iter().map(|s| s * s).sum::() - / samples.len() as f32) - .sqrt(); - - if rms >= BARGE_IN_THRESHOLD { - self.barge_in_frames += 1; - } else { - self.barge_in_frames = 0; - } + // Barge-in detection during Speaking + if let State::Speaking { ref mut barge_in_frames, .. } = self.state { + if let Some(ref capture) = self.capture { + while let Some(samples) = capture.try_recv() { + // Buffer samples so we capture speech that triggers barge-in + self.audio_buffer.extend(&samples); + + let rms: f32 = (samples.iter().map(|s| s * s).sum::() + / samples.len() as f32) + .sqrt(); + + if rms >= BARGE_IN_THRESHOLD { + *barge_in_frames += 1; + } else { + *barge_in_frames = 0; + } - if self.barge_in_frames >= BARGE_IN_FRAMES { - log::debug!("Barge-in detected, pausing TTS"); - if let Some(ref playback) = self.playback { - playback.pause(); + if *barge_in_frames >= BARGE_IN_FRAMES { + log::debug!("Barge-in detected, pausing TTS"); + if let Some(ref playback) = self.playback { + playback.pause(); + } + self.tts_paused = true; + // Don't clear audio_buffer - it has the speech! + self.state = State::BargedIn { has_speech: false, silence_frames: 0 }; + break; } - self.tts_paused = true; - self.state = State::BargedIn; - self.audio_buffer.clear(); - self.has_speech = true; - self.silence_frames = 0; - self.barge_in_frames = 0; - break; } } } // Capture during BargedIn (same silence detection as Listening) - if matches!(self.state, State::BargedIn) - && let Some(ref capture) = self.capture - { - while let Some(samples) = capture.try_recv() { - let rms: f32 = (samples.iter().map(|s| s * s).sum::() - / samples.len() as f32) - .sqrt(); - - let target_level = (rms * 10.0).min(1.0); - self.audio_level = self.audio_level * 0.8 + target_level * 0.2; - - if rms >= 0.01 { - self.has_speech = true; - self.silence_frames = 0; - } else { - self.silence_frames += 1; - } + if let State::BargedIn { ref mut has_speech, ref mut silence_frames } = self.state { + if let Some(ref capture) = self.capture { + while let Some(samples) = capture.try_recv() { + let rms: f32 = (samples.iter().map(|s| s * s).sum::() + / samples.len() as f32) + .sqrt(); + + let target_level = (rms * 10.0).min(1.0); + self.audio_level = self.audio_level * 0.8 + target_level * 0.2; + + if rms >= 0.01 { + *has_speech = true; + *silence_frames = 0; + } else { + *silence_frames += 1; + } - self.audio_buffer.extend(samples); + self.audio_buffer.extend(samples); - if self.audio_buffer.len() > 16000 && self.silence_frames > 150 { - if self.has_speech { - return Task::done(Message::StopListening); - } else { - return Task::done(Message::ResumePlayback); + if self.audio_buffer.len() > 16000 && *silence_frames > 150 { + if *has_speech { + return Task::done(Message::StopListening); + } else { + return Task::done(Message::ResumePlayback); + } } } } } // Check for audio input while listening - if matches!(self.state, State::Listening) - && let Some(ref capture) = self.capture - { - while let Some(samples) = capture.try_recv() { - let rms: f32 = (samples.iter().map(|s| s * s).sum::() - / samples.len() as f32) - .sqrt(); - - // Smooth audio level for visual display - let target_level = (rms * 10.0).min(1.0); - self.audio_level = self.audio_level * 0.8 + target_level * 0.2; - - if rms >= 0.01 { - self.has_speech = true; - self.silence_frames = 0; - } else { - self.silence_frames += 1; - } + if let State::Listening { ref mut has_speech, ref mut silence_frames } = self.state { + if let Some(ref capture) = self.capture { + while let Some(samples) = capture.try_recv() { + let rms: f32 = (samples.iter().map(|s| s * s).sum::() + / samples.len() as f32) + .sqrt(); + + // Smooth audio level for visual display + let target_level = (rms * 10.0).min(1.0); + self.audio_level = self.audio_level * 0.8 + target_level * 0.2; + + if rms >= 0.01 { + *has_speech = true; + *silence_frames = 0; + } else { + *silence_frames += 1; + } - self.audio_buffer.extend(samples); + self.audio_buffer.extend(samples); - // Stop after ~1.5 seconds of silence - if self.audio_buffer.len() > 16000 && self.silence_frames > 150 { - if self.has_speech { - return Task::done(Message::StopListening); - } else { - // No speech detected, just reset and keep listening - self.audio_buffer.clear(); - self.silence_frames = 0; + // Stop after ~1.5 seconds of silence + if self.audio_buffer.len() > 16000 && *silence_frames > 150 { + if *has_speech { + return Task::done(Message::StopListening); + } else { + // No speech detected, just reset and keep listening + self.audio_buffer.clear(); + *silence_frames = 0; + } } } } @@ -419,7 +415,7 @@ impl App { let http = reqwest::Client::new(); Task::perform( async move { - http.post("https://hooks.attio.com/w/8b9f7dfe-0c44-4531-86e5-70f0ad1ec853/fffbdf49-989b-40bc-af97-fc602fd5bce9") + http.post("https://hooks.attio.com/w/1d456d59-a7ac-4211-ac1d-fac612f7f491/5fc14931-0124-4121-b281-1dbfb64dceb2") .json(&payload) .send() .await @@ -461,13 +457,11 @@ impl App { if let Some(ref playback) = self.playback { playback.play(audio_data); - // Don't start capture here — opening a CoreAudio input stream - // causes the audio graph to reconfigure and glitches playback. - // Capture is started lazily in the Tick handler after a short delay. - self.barge_in_frames = 0; + self.audio_buffer.clear(); self.tts_paused = false; self.state = State::Speaking { text: response_text.unwrap_or_default(), + barge_in_frames: 0, }; } Task::none() @@ -496,11 +490,9 @@ impl App { }, Message::StartListening => { - self.state = State::Listening; + self.state = State::Listening { has_speech: false, silence_frames: 0 }; self.audio_buffer.clear(); self.audio_level = 0.0; - self.silence_frames = 0; - self.has_speech = false; Task::none() } @@ -530,11 +522,9 @@ impl App { } self.state = State::Speaking { text: String::new(), + barge_in_frames: 0, }; self.audio_buffer.clear(); - self.barge_in_frames = 0; - self.has_speech = false; - self.silence_frames = 0; Task::none() } else { // TTS finished while we were transcribing — nothing to resume @@ -661,7 +651,7 @@ impl App { // Status indicator let status = match &self.state { - State::Listening | State::BargedIn => "Listening...", + State::Listening { .. } | State::BargedIn { .. } => "Listening...", State::Processing { .. } => "Processing...", State::Speaking { .. } => "Speaking...", State::Submitted => "Submitted", @@ -670,7 +660,7 @@ impl App { }; let status_color = match &self.state { - State::Listening | State::BargedIn => color!(0xcc3e28), + State::Listening { .. } | State::BargedIn { .. } => color!(0xcc3e28), _ => dim_color, }; @@ -772,8 +762,8 @@ impl canvas::Program for App { Color::from_rgb(0.2, 0.6, 1.0), ) } - State::Listening => { - if self.has_speech { + State::Listening { has_speech, .. } => { + if *has_speech { // Reactive red circle let audio_pulse = self.audio_level * 40.0; let base_pulse = if self.audio_level > 0.05 { @@ -791,7 +781,7 @@ impl canvas::Program for App { (12.0, 0.0, Color::from_rgb(0.85, 0.2, 0.2)) } } - State::BargedIn => { + State::BargedIn { .. } => { let audio_pulse = self.audio_level * 40.0; let base_pulse = if self.audio_level > 0.05 { 10.0 * (t * 2.0).sin().abs() From 9429461adae3589ebe1a9b13d244d0ee1781fe6b Mon Sep 17 00:00:00 2001 From: Andy Terra <152812+airstrike@users.noreply.github.com> Date: Thu, 5 Feb 2026 17:24:16 -0500 Subject: [PATCH 18/18] docs: update README for voice/text modes, fix curl pipe --- README.md | 91 +++++++++++++++++------------------------------------- install.sh | 2 +- 2 files changed, 29 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 8e66928..fa4d446 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,50 @@ -# Root Ventures Application Skill +# Root Ventures Application -Apply to Root Ventures positions directly through Claude CLI. +Apply to Root Ventures directly through voice or text conversation with Claude. -## Installation & Usage - -Just run this one command: +## Quick Start ```bash curl -fsSL https://raw.githubusercontent.com/rootvc/claude-apply-skill/main/install.sh | bash ``` -The installer will: -- Download the skill files -- Automatically launch Claude CLI -- Pre-load the application skill - -Then simply say to Claude: -``` -I want to apply to Root Ventures -``` - -Claude will then: -- Guide you through the application -- Collect your information conversationally -- Submit directly to Root Ventures - -## What You'll Provide - -- Your name (required) -- Your email (required) -- LinkedIn profile (optional) -- GitHub username (optional) -- Why you're interested in Root (optional) - -## How It Works +You'll be prompted to choose: -1. You run the one-line install command -2. The installer downloads skill files and launches Claude -3. Claude loads the skill instructions and waits for you -4. You say "I want to apply to Root Ventures" -5. Claude starts the conversational application process -6. You provide your information naturally -7. Claude submits your application to Attio -8. You receive immediate confirmation +1. **Voice mode** (recommended) — Native app with speech recognition and text-to-speech +2. **Text mode** — Claude CLI chat interface -## Example +## Voice Mode -```bash -$ curl -fsSL https://raw.githubusercontent.com/rootvc/claude-apply-skill/main/install.sh | bash - -🚀 Installing Root Ventures Apply Skill... -📥 Downloading skill files... -✅ Root Ventures Apply Skill installed successfully! - -Launching Claude... - -Once Claude opens, just say: 'I want to apply to Root Ventures' - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +A native macOS app that lets you have a spoken conversation with Claude. Just talk naturally — Claude will interview you, fill out the application form in real-time, and submit when you're ready. -[Claude launches] +**Requirements:** +- macOS +- `ANTHROPIC_API_KEY` and `ELEVENLABS_API_KEY` in environment or `.env` -Claude: Root Ventures application skill installed. Claude can now help you apply for a job at Root Ventures +The installer will install Rust if needed and build the app (~1 min first time). -You: I want to apply to Root Ventures +**Features:** +- Natural voice conversation +- Barge-in support — interrupt Claude mid-sentence +- Live form updates as you speak +- Automatic submission to Attio CRM -Claude: Great! Root Ventures is looking for a technical associate in SF. - Let me collect some information. What's your name? +## Text Mode -You: Jane Doe +Uses Claude CLI for a text-based chat. The installer downloads the skill files and launches Claude automatically. -Claude: Thanks Jane! What's your email address? - - [... continues conversationally ...] - -Claude: ✅ Application submitted successfully! +Then just say: +``` +I want to apply to Root Ventures ``` -## Source Tracking +## What You'll Provide -Applications submitted through this skill are tagged with `source: "Claude Skill"` in Attio, and "Applied using claude skill" is added to your notes. +- Name (required) +- Email (required) +- LinkedIn profile +- GitHub username +- Why you're interested (collected through interview questions) ## Other Ways to Apply diff --git a/install.sh b/install.sh index 3ace7fd..8d933e7 100755 --- a/install.sh +++ b/install.sh @@ -23,7 +23,7 @@ echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" -read -p "Enter choice (1 or 2): " choice +read -p "Enter choice (1 or 2): " choice