diff --git a/Cargo.lock b/Cargo.lock index e50c318e..0ab2eaef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # 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 = "actix" version = "0.13.5" @@ -367,6 +383,49 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.11.0", + "cc", + "jni 0.22.4", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.18", +] + +[[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 = "anstream" version = "1.0.0" @@ -461,6 +520,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + [[package]] name = "as-slice" version = "0.2.1" @@ -470,6 +535,15 @@ dependencies = [ "stable_deref_trait", ] +[[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 = "askama" version = "0.14.0" @@ -494,7 +568,7 @@ dependencies = [ "memchr", "proc-macro2", "quote", - "rustc-hash", + "rustc-hash 2.1.2", "serde", "serde_derive", "syn", @@ -509,7 +583,121 @@ dependencies = [ "memchr", "serde", "serde_derive", - "winnow", + "winnow 0.7.15", +] + +[[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.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +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.4", + "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.4", +] + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", ] [[package]] @@ -534,6 +722,12 @@ dependencies = [ "syn", ] +[[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" @@ -572,7 +766,7 @@ dependencies = [ "num-traits", "pastey 0.1.1", "rayon", - "thiserror", + "thiserror 2.0.18", "v_frame", "y4m", ] @@ -630,7 +824,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.2", "shlex", "syn", ] @@ -677,6 +871,12 @@ dependencies = [ "core2", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -686,6 +886,37 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[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 = "borrow-or-share" version = "0.2.4" @@ -736,6 +967,20 @@ 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" @@ -783,6 +1028,57 @@ dependencies = [ "crossbeam-channel", ] +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.11.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.11.0", + "polling", + "rustix 1.1.4", + "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.4", + "rustix 1.1.4", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cc" version = "1.2.59" @@ -795,6 +1091,12 @@ dependencies = [ "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" @@ -867,6 +1169,45 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[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.58" @@ -876,6 +1217,17 @@ 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 = "color_quant" version = "1.1.0" @@ -888,6 +1240,25 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes 1.11.1", + "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 = "console" version = "0.15.11" @@ -948,39 +1319,98 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "core2" -version = "0.4.0" +name = "core-graphics" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ - "memchr", + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", ] [[package]] -name = "core_maths" -version = "0.1.1" +name = "core-graphics-types" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ - "libm", + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", ] [[package]] -name = "cpufeatures" -version = "0.2.17" +name = "core-graphics-types" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", "libc", ] [[package]] -name = "crc32fast" -version = "1.5.0" +name = "core2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" dependencies = [ - "cfg-if", + "memchr", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cosmic-text" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173852283a9a57a3cbe365d86e74dc428a09c50421477d5ad6fe9d9509e37737" +dependencies = [ + "bitflags 2.11.0", + "fontdb", + "harfrust", + "linebender_resource_handle", + "log", + "rangemap", + "rustc-hash 1.1.0", + "self_cell", + "skrifa 0.37.0", + "smol_str", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", ] [[package]] @@ -1023,6 +1453,19 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "cryoglyph" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc795bdbccdbd461736fb163930a009da6597b226d6f6fce33e7a8eb6ec519" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.1.2", + "wgpu", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -1054,6 +1497,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor-lite" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e162d0c2e2068eb736b71e5597eff0b9944e6b973cd9f37b6a288ab9bf20e300" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + [[package]] name = "dashmap" version = "6.1.0" @@ -1150,6 +1605,22 @@ dependencies = [ "windows-sys 0.61.2", ] +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1161,6 +1632,36 @@ dependencies = [ "syn", ] +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +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 = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + [[package]] name = "either" version = "1.15.0" @@ -1192,6 +1693,12 @@ dependencies = [ "serde", ] +[[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" @@ -1199,6 +1706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ "enumflags2_derive", + "serde", ] [[package]] @@ -1271,6 +1779,12 @@ dependencies = [ "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 = "esbuild-metafile" version = "0.5.2" @@ -1288,6 +1802,16 @@ dependencies = [ "serde_json", ] +[[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.14" @@ -1297,6 +1821,27 @@ 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 = "exr" version = "1.74.0" @@ -1418,6 +1963,24 @@ 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 = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + [[package]] name = "fontconfig-parser" version = "0.5.8" @@ -1447,7 +2010,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1456,6 +2040,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1539,6 +2129,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[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.32" @@ -1590,6 +2193,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -1628,6 +2241,16 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gif" version = "0.14.1" @@ -1638,12 +2261,111 @@ dependencies = [ "weezl", ] +[[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-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.11.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows 0.58.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.11.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.11.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.3.27" @@ -1690,9 +2412,23 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "num-traits", "zerocopy", ] +[[package]] +name = "harfrust" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c020db12c71d8a12a3fe7607873cade3a01a6287e29d540c8723276221b9d8" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "core_maths", + "read-fonts 0.35.0", + "smallvec", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1731,6 +2467,18 @@ 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 = "hf-hub" version = "0.4.3" @@ -1749,7 +2497,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", "ureq", "windows-sys 0.60.2", @@ -1889,6 +2637,196 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "iced" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000e01026c93ba643f8357a3db3ada0e6555265a377f6f9291c472f6dd701fb3" +dependencies = [ + "iced_core", + "iced_debug", + "iced_futures", + "iced_renderer", + "iced_runtime", + "iced_widget", + "iced_winit", + "image", + "thiserror 2.0.18", +] + +[[package]] +name = "iced_core" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ab1937d699403e7e69252ae743a902bcee9f4ab2052cc4c9a46fcf34729d85" +dependencies = [ + "bitflags 2.11.0", + "bytes 1.11.1", + "glam", + "lilt", + "log", + "num-traits", + "rustc-hash 2.1.2", + "smol_str", + "thiserror 2.0.18", + "web-time", +] + +[[package]] +name = "iced_debug" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25035ab0215a620e53f4103e36fc4e59a1fb2817e4bfc38a30ad27b4202ea0be" +dependencies = [ + "iced_core", + "iced_futures", + "log", +] + +[[package]] +name = "iced_futures" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c0c85ccad42dfbec7293c36c018af0ea0dbcc52d137a4a9a0b0f6822a3fdf0a" +dependencies = [ + "futures 0.3.32", + "iced_core", + "log", + "rustc-hash 2.1.2", + "tokio", + "wasm-bindgen-futures", + "wasmtimer", +] + +[[package]] +name = "iced_graphics" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234ca1c2cec4155055f68fa5fad1b5242c496ac8238d80a259bca382fb44a102" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "cosmic-text", + "half", + "iced_core", + "iced_futures", + "image", + "kamadak-exif", + "log", + "raw-window-handle", + "rustc-hash 2.1.2", + "thiserror 2.0.18", + "unicode-segmentation", +] + +[[package]] +name = "iced_program" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dfafec2947cda688d8eb00dac337ba11aa60f9ef6335aed343e189d26e4a673" +dependencies = [ + "iced_graphics", + "iced_runtime", +] + +[[package]] +name = "iced_renderer" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250cc0802408e8c077986ec56c7d07c65f423ee658a4b9fd795a1f2aae5dac05" +dependencies = [ + "iced_graphics", + "iced_tiny_skia", + "iced_wgpu", + "log", + "thiserror 2.0.18", +] + +[[package]] +name = "iced_runtime" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1889b819ce4c06674183242e336c8d49465665441396914dc07cc86f44fa8d4" +dependencies = [ + "bytes 1.11.1", + "iced_core", + "iced_futures", + "raw-window-handle", + "thiserror 2.0.18", +] + +[[package]] +name = "iced_tiny_skia" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0acf8b75a3bc914aff5f2329fdffc1b36eeaea29dda0e4bd232f1c62e9cc3d" +dependencies = [ + "bytemuck", + "cosmic-text", + "iced_debug", + "iced_graphics", + "kurbo 0.10.4", + "log", + "resvg 0.45.1", + "rustc-hash 2.1.2", + "softbuffer", + "tiny-skia", +] + +[[package]] +name = "iced_wgpu" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff144a999b0ca0f8a10257934500060240825c42e950ec0ebee9c8ae30561c13" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "cryoglyph", + "futures 0.3.32", + "glam", + "guillotiere", + "iced_debug", + "iced_graphics", + "log", + "resvg 0.45.1", + "rustc-hash 2.1.2", + "thiserror 2.0.18", + "wgpu", +] + +[[package]] +name = "iced_widget" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1596afa0d3109c2618e8bc12bae6c11d3064df8f95c42dfce570397dbe957ab" +dependencies = [ + "iced_renderer", + "log", + "num-traits", + "rustc-hash 2.1.2", + "thiserror 2.0.18", + "unicode-segmentation", +] + +[[package]] +name = "iced_winit" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7dbedc47562d1de3b9707d939f678b88c382004b7ab5a18f7a7dd723162d75" +dependencies = [ + "iced_debug", + "iced_program", + "log", + "mundy", + "rustc-hash 2.1.2", + "thiserror 2.0.18", + "tracing", + "wasm-bindgen-futures", + "web-sys", + "window_clipboard", + "winit", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -1998,6 +2936,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "if-addrs" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b2eeee38fef3aa9b4cc5f1beea8a2444fc00e7377cafae396de3f5c2065e24" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "image" version = "0.25.10" @@ -2008,7 +2956,7 @@ dependencies = [ "byteorder-lite", "color_quant", "exr", - "gif", + "gif 0.14.1", "image-webp", "moxcms", "num-traits", @@ -2018,8 +2966,8 @@ dependencies = [ "rayon", "rgb", "tiff", - "zune-core", - "zune-jpeg", + "zune-core 0.5.1", + "zune-jpeg 0.5.15", ] [[package]] @@ -2032,6 +2980,12 @@ dependencies = [ "quick-error", ] +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + [[package]] name = "imagesize" version = "0.14.0" @@ -2180,6 +3134,80 @@ dependencies = [ "syn", ] +[[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 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -2229,6 +3257,53 @@ dependencies = [ "uuid-simd", ] +[[package]] +name = "kamadak-exif" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837" +dependencies = [ + "mutate_once", +] + +[[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 = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + [[package]] name = "kurbo" version = "0.13.0" @@ -2302,9 +3377,53 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ + "bitflags 2.11.0", "libc", + "plain", + "redox_syscall 0.7.4", +] + +[[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 = "linkme" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e3283ed2d0e50c06dd8602e0ab319bb048b6325d0bba739db64ed8205179898" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5cec0ec4228b4853bb129c84dbf093a27e6c7a20526da046defc334a1b017f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] +[[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.12.1" @@ -2317,6 +3436,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "llama-cpp-bindings" version = "0.4.1" @@ -2326,7 +3451,7 @@ dependencies = [ "encoding_rs", "enumflags2", "llama-cpp-bindings-sys", - "thiserror", + "thiserror 2.0.18", "tracing", "tracing-core", ] @@ -2395,6 +3520,31 @@ dependencies = [ "imgref", ] +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" + +[[package]] +name = "macro_registry" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c03fc749d06e1000766283015673e91aea121f30c60b7445681f2248e4994c" +dependencies = [ + "module_path_extractor", + "syn", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -2427,13 +3577,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" [[package]] -name = "mime" -version = "0.3.17" +name = "memoffset" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] [[package]] -name = "mime_guess" +name = "metal" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" +dependencies = [ + "bitflags 2.11.0", + "block", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "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" @@ -2494,6 +3668,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moddef" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e519fd9c6131c1c9a4a67f8bdc4f32eb4105b16c1468adea1b8e68c98c85ec4" + +[[package]] +name = "module_path_extractor" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ec8ded8dfe4b676fb3dc4341830262395371d2113117bec13b40e830a1dea7" + [[package]] name = "moxcms" version = "0.8.1" @@ -2504,6 +3690,63 @@ dependencies = [ "pxfm", ] +[[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 0.21.1", + "ndk-context", + "objc2 0.6.4", + "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 = "mutate_once" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" + +[[package]] +name = "naga" +version = "27.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.11.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 = "nanoid" version = "0.4.0" @@ -2530,6 +3773,36 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -2667,6 +3940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2679,6 +3953,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -2686,197 +3982,645 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] -name = "once_cell" -version = "1.21.4" +name = "objc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "objc-sys" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] -name = "openssl" -version = "0.10.76" +name = "objc2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "objc-sys", + "objc2-encode", ] [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "objc2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ - "proc-macro2", - "quote", - "syn", + "objc2-encode", ] [[package]] -name = "openssl-probe" -version = "0.2.1" +name = "objc2-app-kit" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.11.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 = "openssl-sys" -version = "0.9.112" +name = "objc2-app-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "cc", + "bitflags 2.11.0", + "block2 0.6.2", "libc", - "pkg-config", - "vcpkg", + "objc2 0.6.4", + "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 = "option-ext" -version = "0.2.0" +name = "objc2-cloud-kit" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] [[package]] -name = "outref" -version = "0.5.2" +name = "objc2-cloud-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" - -[[package]] -name = "paddler" -version = "3.0.1" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "actix", - "actix-cors", - "actix-rt", - "actix-web", - "actix-web-lab", - "actix-ws", - "anyhow", - "askama", - "async-stream", - "async-trait", - "base64", - "bytes 1.11.1", - "cadence", - "clap", - "dashmap", - "encoding_rs", - "env_logger", - "esbuild-metafile", - "futures 0.3.32", - "futures-util", - "hf-hub", - "image", - "indoc", - "jsonschema", - "llama-cpp-bindings", - "llama-cpp-bindings-sys", - "log", - "mime_guess", - "minijinja", - "minijinja-contrib", - "nanoid", - "nix", - "paddler_types", - "rand 0.9.2", - "reqwest", - "resvg", - "rust-embed", - "serde", - "serde_json", - "shellexpand", - "tempfile", - "thiserror", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "url", + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-foundation 0.3.2", ] [[package]] -name = "paddler_client" -version = "3.0.1" +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ - "anyhow", - "dashmap", - "futures-util", - "log", - "nanoid", - "paddler_types", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "url", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] -name = "paddler_integration_tests" -version = "3.0.1" +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "anyhow", - "base64", - "futures-util", - "nix", - "paddler_client", - "paddler_types", - "reqwest", - "serde", - "serde_json", - "serial_test", - "tempfile", - "tokio", - "url", + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] -name = "paddler_model_tests" -version = "3.0.1" +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ - "actix-web", - "anyhow", - "base64", - "llama-cpp-bindings", - "paddler", - "paddler_types", - "pastey 0.2.1", - "serde_json", - "tokio", + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-foundation 0.3.2", ] [[package]] -name = "paddler_types" -version = "3.0.1" +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "anyhow", - "jsonschema", - "serde", - "serde_json", + "bitflags 2.11.0", + "dispatch2", + "objc2 0.6.4", ] [[package]] -name = "parking_lot" -version = "0.12.5" +name = "objc2-core-graphics" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "lock_api", - "parking_lot_core", + "bitflags 2.11.0", + "dispatch2", + "objc2 0.6.4", + "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.4", + "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.11.0", + "objc2 0.6.4", + "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.11.0", + "objc2 0.6.4", + "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.11.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.11.0", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "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.11.0", + "objc2 0.6.4", + "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.11.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.11.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.11.0", + "objc2 0.6.4", + "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.11.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.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orbclient" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +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 = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[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 = "paddler" +version = "3.0.1" +dependencies = [ + "actix", + "actix-cors", + "actix-rt", + "actix-web", + "actix-web-lab", + "actix-ws", + "anyhow", + "askama", + "async-stream", + "async-trait", + "base64", + "bytes 1.11.1", + "cadence", + "clap", + "dashmap", + "encoding_rs", + "env_logger", + "esbuild-metafile", + "futures 0.3.32", + "futures-util", + "hf-hub", + "image", + "indoc", + "jsonschema", + "llama-cpp-bindings", + "llama-cpp-bindings-sys", + "log", + "mime_guess", + "minijinja", + "minijinja-contrib", + "nanoid", + "paddler_types", + "rand 0.9.2", + "reqwest", + "resvg 0.46.0", + "rust-embed", + "serde", + "serde_json", + "shellexpand", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "url", +] + +[[package]] +name = "paddler_bootstrap" +version = "3.0.1" +dependencies = [ + "anyhow", + "nanoid", + "paddler", + "paddler_types", + "tokio", +] + +[[package]] +name = "paddler_cli" +version = "3.0.1" +dependencies = [ + "actix-rt", + "actix-web", + "anyhow", + "async-trait", + "clap", + "env_logger", + "esbuild-metafile", + "log", + "nanoid", + "paddler", + "paddler_bootstrap", + "tokio", +] + +[[package]] +name = "paddler_client" +version = "3.0.1" +dependencies = [ + "anyhow", + "dashmap", + "futures-util", + "log", + "nanoid", + "paddler_types", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "url", +] + +[[package]] +name = "paddler_gui" +version = "3.0.1" +dependencies = [ + "actix-web", + "anyhow", + "env_logger", + "iced", + "if-addrs", + "log", + "paddler", + "paddler_bootstrap", + "paddler_types", + "statum", + "tokio", +] + +[[package]] +name = "paddler_integration_tests" +version = "3.0.1" +dependencies = [ + "anyhow", + "base64", + "futures-util", + "nix", + "paddler_client", + "paddler_types", + "reqwest", + "serde", + "serde_json", + "serial_test", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "paddler_model_tests" +version = "3.0.1" +dependencies = [ + "actix-web", + "anyhow", + "base64", + "llama-cpp-bindings", + "paddler", + "paddler_types", + "pastey 0.2.1", + "serde_json", + "tokio", +] + +[[package]] +name = "paddler_types" +version = "3.0.1" +dependencies = [ + "anyhow", + "jsonschema", + "serde", + "serde_json", +] + +[[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]] @@ -2887,7 +4631,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] @@ -2922,18 +4666,61 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +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 = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "png" version = "0.17.16" @@ -2960,6 +4747,20 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -2999,6 +4800,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -3019,6 +4826,15 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -3068,6 +4884,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.45" @@ -3148,6 +4973,18 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + [[package]] name = "rav1e" version = "0.8.1" @@ -3178,7 +5015,7 @@ dependencies = [ "rand 0.9.2", "rand_chacha 0.9.0", "simd_helpers", - "thiserror", + "thiserror 2.0.18", "v_frame", "wasm-bindgen", ] @@ -3198,6 +5035,12 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "rayon" version = "1.11.0" @@ -3218,6 +5061,36 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +dependencies = [ + "bytemuck", + "core_maths", + "font-types 0.10.1", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "font-types 0.11.3", +] + +[[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" @@ -3227,6 +5100,15 @@ dependencies = [ "bitflags 2.11.0", ] +[[package]] +name = "redox_syscall" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -3235,7 +5117,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -3306,7 +5188,13 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" name = "regex-syntax" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" @@ -3351,21 +5239,38 @@ dependencies = [ "web-sys", ] +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "gif 0.13.3", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes 0.15.3", + "tiny-skia", + "usvg 0.45.1", + "zune-jpeg 0.4.21", +] + [[package]] name = "resvg" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" dependencies = [ - "gif", + "gif 0.14.1", "image-webp", "log", "pico-args", "rgb", - "svgtypes", + "svgtypes 0.16.1", "tiny-skia", - "usvg", - "zune-jpeg", + "usvg 0.46.0", + "zune-jpeg 0.5.15", ] [[package]] @@ -3441,6 +5346,12 @@ dependencies = [ "walkdir", ] +[[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.2" @@ -3456,6 +5367,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.4" @@ -3465,7 +5389,7 @@ dependencies = [ "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -3561,12 +5485,31 @@ 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 = "sdd" version = "3.0.10" @@ -3596,6 +5539,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + [[package]] name = "semver" version = "1.0.27" @@ -3669,6 +5618,17 @@ dependencies = [ "serde_core", ] +[[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 = "serde_urlencoded" version = "0.7.1" @@ -3761,6 +5721,16 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + [[package]] name = "simd_helpers" version = "0.1.0" @@ -3770,6 +5740,12 @@ dependencies = [ "quote", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "simplecss" version = "0.2.2" @@ -3785,6 +5761,26 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +[[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.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts 0.37.0", +] + [[package]] name = "slab" version = "0.4.12" @@ -3812,6 +5808,78 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.11.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.11.0", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.4", + "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.5.10" @@ -3843,12 +5911,91 @@ dependencies = [ "winapi", ] +[[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", + "objc2 0.6.4", + "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.4", + "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.11.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 = "statum" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b355d3d23dd91652df5621cc0a41b3e831e40a8376a50b4e06020cf4922f01" +dependencies = [ + "statum-core", + "statum-macros", +] + +[[package]] +name = "statum-core" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e80c81d094f726bee8a08b3acb08119c56ee9e03e9c96dc62bd0685757fc6b04" +dependencies = [ + "futures 0.3.32", + "linkme", +] + +[[package]] +name = "statum-macros" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5843996c8d6017fb8046be3ccb3779c69ad41940b8d5c53421200688e18ca7" +dependencies = [ + "macro_registry", + "moddef", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "strict-num" version = "0.1.1" @@ -3870,16 +6017,43 @@ 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 = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo 0.11.3", + "siphasher", +] + [[package]] name = "svgtypes" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" dependencies = [ - "kurbo", + "kurbo 0.13.0", "siphasher", ] +[[package]] +name = "swash" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" +dependencies = [ + "skrifa 0.40.0", + "yazi", + "zeno", +] + [[package]] name = "syn" version = "2.0.117" @@ -3911,6 +6085,15 @@ dependencies = [ "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" @@ -3941,10 +6124,19 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix", + "rustix 1.1.4", "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 = "textwrap" version = "0.16.2" @@ -3954,13 +6146,33 @@ dependencies = [ "smawk", ] +[[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", + "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]] @@ -3985,7 +6197,7 @@ dependencies = [ "half", "quick-error", "weezl", - "zune-jpeg", + "zune-jpeg 0.5.15", ] [[package]] @@ -4045,6 +6257,19 @@ dependencies = [ "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.3" @@ -4166,6 +6391,36 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow 1.0.1", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.1", +] + [[package]] name = "tower" version = "0.5.3" @@ -4272,7 +6527,7 @@ dependencies = [ "log", "rand 0.9.2", "sha1", - "thiserror", + "thiserror 2.0.18", "utf-8", ] @@ -4282,6 +6537,17 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + [[package]] name = "unicase" version = "2.9.0" @@ -4318,6 +6584,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-properties" version = "0.1.4" @@ -4399,6 +6671,33 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64", + "data-url", + "flate2", + "fontdb", + "imagesize 0.13.0", + "kurbo 0.11.3", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes 0.15.3", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "usvg" version = "0.46.0" @@ -4409,8 +6708,8 @@ dependencies = [ "data-url", "flate2", "fontdb", - "imagesize", - "kurbo", + "imagesize 0.14.0", + "kurbo 0.13.0", "log", "pico-args", "roxmltree 0.21.1", @@ -4418,7 +6717,7 @@ dependencies = [ "simplecss", "siphasher", "strict-num", - "svgtypes", + "svgtypes 0.16.1", "tiny-skia-path", "unicode-bidi", "unicode-script", @@ -4444,6 +6743,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "uuid-simd" version = "0.8.0" @@ -4634,6 +6944,155 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures 0.3.32", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.11.0", + "rustix 1.1.4", + "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.11.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +dependencies = [ + "bitflags 2.11.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.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.94" @@ -4658,56 +7117,333 @@ dependencies = [ name = "webpki-roots" version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" +dependencies = [ + "arrayvec", + "bitflags 2.11.0", + "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 = "27.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.11.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 = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "27.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.11.0", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types 0.2.0", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys", + "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.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "wgpu-types" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "js-sys", + "log", + "thiserror 2.0.18", + "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.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.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.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "webpki-roots 1.0.6", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", ] [[package]] -name = "webpki-roots" -version = "1.0.6" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "rustls-pki-types", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] -name = "weezl" -version = "0.1.12" +name = "windows-future" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-implement" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "winapi-util" -version = "0.1.11" +name = "windows-interface" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ - "windows-sys 0.61.2", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "windows-link" @@ -4715,6 +7451,16 @@ 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" @@ -4722,8 +7468,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ "windows-link", - "windows-result", - "windows-strings", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -4735,6 +7490,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.5.1" @@ -4744,6 +7509,15 @@ 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" @@ -4780,6 +7554,21 @@ 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" @@ -4813,6 +7602,21 @@ dependencies = [ "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" @@ -4825,6 +7629,12 @@ 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" @@ -4837,6 +7647,12 @@ 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" @@ -4861,6 +7677,12 @@ 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" @@ -4873,6 +7695,12 @@ 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" @@ -4885,6 +7713,12 @@ 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" @@ -4897,6 +7731,12 @@ 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" @@ -4909,6 +7749,58 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.11.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", + "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.15" @@ -4918,6 +7810,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -5012,6 +7913,69 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[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.4", + "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.11.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 = "xmlwriter" version = "0.1.0" @@ -5030,6 +7994,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[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.2" @@ -5053,6 +8023,73 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +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.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 0.7.15", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +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 0.7.15", + "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.48" @@ -5167,6 +8204,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-core" version = "0.5.1" @@ -5182,11 +8225,60 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + [[package]] name = "zune-jpeg" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ - "zune-core", + "zune-core 0.5.1", +] + +[[package]] +name = "zvariant" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.15", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +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 0.7.15", ] diff --git a/Cargo.toml b/Cargo.toml index e31a313b..5ca15625 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["paddler_integration_tests", "paddler", "paddler_client", "paddler_model_tests", "paddler_types"] +members = ["paddler", "paddler_bootstrap", "paddler_cli", "paddler_client", "paddler_gui", "paddler_integration_tests", "paddler_model_tests", "paddler_types"] resolver = "2" [workspace.package] @@ -53,6 +53,9 @@ serial_test = { version = "3", features = ["file_locks"] } serde = { version = "1", features = ["derive"] } serde_json = "1" shellexpand = "3" +iced = { version = "0.14", features = ["image", "svg", "tokio"] } +if-addrs = "0.13" +statum = "0.6" tempfile = "3.20.0" tokio = { version = "1.48", features = ["full"] } tokio-stream = { version = "0.1.17", features = ["sync"] } @@ -60,6 +63,7 @@ tokio-tungstenite = "0.28" thiserror = "2" url = { version = "2.5", features = ["serde"] } paddler = { version = "3.0.1", path = "paddler" } +paddler_bootstrap = { version = "3.0.1", path = "paddler_bootstrap" } paddler_client = { version = "3.0.1", path = "paddler_client" } paddler_types = { version = "3.0.1", path = "paddler_types" } diff --git a/Makefile b/Makefile index 97a705e9..a0658319 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,8 @@ node_modules: package-lock.json npm install --from-lockfile touch node_modules -target/debug/paddler: $(shell find paddler/src paddler_types/src paddler_client/src -name '*.rs') - cargo build -p paddler +target/debug/paddler_cli: $(shell find paddler/src paddler_bootstrap/src paddler_cli/src paddler_client/src paddler_types/src -name '*.rs') + cargo build -p paddler_cli # ----------------------------------------------------------------------------- # Phony targets @@ -41,42 +41,42 @@ jarmuz-static: node_modules .PHONY: build build: jarmuz-static - cargo build -p paddler --features web_admin_panel + cargo build -p paddler_cli --features web_admin_panel .PHONY: build.cuda build.cuda: jarmuz-static - cargo build -p paddler --features cuda,web_admin_panel + cargo build -p paddler_cli --features cuda,web_admin_panel .PHONY: release release: jarmuz-static - cargo build --release -p paddler --features web_admin_panel + cargo build --release -p paddler_cli --features web_admin_panel .PHONY: release.cuda release.cuda: jarmuz-static - cargo build --release -p paddler --features web_admin_panel,cuda + cargo build --release -p paddler_cli --features web_admin_panel,cuda .PHONY: release.vulkan release.vulkan: jarmuz-static - cargo build --release -p paddler --features web_admin_panel,vulkan + cargo build --release -p paddler_cli --features web_admin_panel,vulkan .PHONY: test test: test.unit test.models test.integration .PHONY: test.models test.models: - timeout 300 cargo test -p paddler_model_tests --features tests_that_use_llms -- --nocapture --test-threads=1 + cargo test -p paddler_model_tests --features tests_that_use_llms -- --nocapture --test-threads=1 .PHONY: test.cuda test.cuda: - timeout 1800 cargo test -p paddler_model_tests --features tests_that_use_llms,cuda -- --nocapture --test-threads=1 + cargo test -p paddler_model_tests --features tests_that_use_llms,cuda -- --nocapture --test-threads=1 .PHONY: test.unit test.unit: jarmuz-static - timeout 300 cargo test --features web_admin_panel + cargo test --features web_admin_panel .PHONY: test.integration -test.integration: target/debug/paddler - timeout 300 cargo test -p paddler_integration_tests --features tests_that_use_compiled_paddler,tests_that_use_llms -- --nocapture --test-threads=1 +test.integration: target/debug/paddler_cli + cargo test -p paddler_integration_tests --features tests_that_use_compiled_paddler,tests_that_use_llms -- --nocapture --test-threads=1 .PHONY: watch watch: node_modules diff --git a/jarmuz/worker-paddler.mjs b/jarmuz/worker-paddler.mjs index f7a9f348..89ebd062 100644 --- a/jarmuz/worker-paddler.mjs +++ b/jarmuz/worker-paddler.mjs @@ -17,7 +17,7 @@ spawner(async function ({ buildId, command }) { const results = await Promise.all([ command(` - target/debug/paddler balancer + target/debug/paddler_cli balancer --compat-openai-addr 127.0.0.1:8063 --inference-addr 127.0.0.1:8061 --inference-item-timeout 30000 @@ -26,19 +26,19 @@ spawner(async function ({ buildId, command }) { --web-admin-panel-addr 127.0.0.1:8062 `), command(` - target/debug/paddler agent + target/debug/paddler_cli agent --management-addr 127.0.0.1:8060 --name agent-1 --slots 4 `), command(` - target/debug/paddler agent + target/debug/paddler_cli agent --management-addr 127.0.0.1:8060 --name agent-2 --slots 4 `), command(` - target/debug/paddler agent + target/debug/paddler_cli agent --management-addr 127.0.0.1:8060 --name agent-3 --slots 2 diff --git a/package-lock.json b/package-lock.json index cf1988f2..447776ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -222,6 +222,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -245,6 +246,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -268,6 +270,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1296,6 +1299,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1362,6 +1366,7 @@ "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.39.1", "@typescript-eslint/types": "8.39.1", @@ -1763,6 +1768,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2868,6 +2874,7 @@ "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -5426,6 +5433,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5538,6 +5546,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -5588,6 +5597,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -5677,6 +5687,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5686,6 +5697,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -7180,6 +7192,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/paddler/Cargo.toml b/paddler/Cargo.toml index 10d1c85f..cec19fe3 100644 --- a/paddler/Cargo.toml +++ b/paddler/Cargo.toml @@ -39,7 +39,6 @@ minijinja-contrib = { workspace = true } nanoid = { workspace = true } paddler_types = { workspace = true } thiserror = { workspace = true } -nix = { workspace = true } rand = { workspace = true } reqwest = { workspace = true } resvg = { workspace = true } diff --git a/paddler/src/cmd/agent.rs b/paddler/src/cmd/agent.rs deleted file mode 100644 index 99817ab1..00000000 --- a/paddler/src/cmd/agent.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::sync::Arc; - -use anyhow::Result; -use async_trait::async_trait; -use clap::Parser; -use nanoid::nanoid; -use tokio::sync::mpsc; -use tokio::sync::oneshot; - -use super::handler::Handler; -use super::value_parser::parse_socket_addr; -use crate::agent::continue_from_conversation_history_request::ContinueFromConversationHistoryRequest; -use crate::agent::continue_from_raw_prompt_request::ContinueFromRawPromptRequest; -use crate::agent::generate_embedding_batch_request::GenerateEmbeddingBatchRequest; -use crate::agent::llamacpp_arbiter_service::LlamaCppArbiterService; -use crate::agent::management_socket_client_service::ManagementSocketClientService; -use crate::agent::model_metadata_holder::ModelMetadataHolder; -use crate::agent::reconciliation_service::ReconciliationService; -use crate::agent_applicable_state_holder::AgentApplicableStateHolder; -use crate::agent_desired_state::AgentDesiredState; -use crate::resolved_socket_addr::ResolvedSocketAddr; -use crate::service_manager::ServiceManager; -use crate::slot_aggregated_status_manager::SlotAggregatedStatusManager; - -#[derive(Parser)] -pub struct Agent { - #[arg(long, value_parser = parse_socket_addr)] - /// Address of the management server that the agent will connect to - management_addr: ResolvedSocketAddr, - - #[arg(long)] - /// Name of the agent (optional) - name: Option, - - #[arg(long)] - /// Number of parallel requests of any kind that the agent can handle at once - slots: i32, -} - -#[async_trait] -impl Handler for Agent { - async fn handle(&self, shutdown_rx: oneshot::Receiver<()>) -> Result<()> { - let (agent_desired_state_tx, agent_desired_state_rx) = - mpsc::unbounded_channel::(); - let ( - continue_from_conversation_history_request_tx, - continue_from_conversation_history_request_rx, - ) = mpsc::unbounded_channel::(); - let (continue_from_raw_prompt_request_tx, continue_from_raw_prompt_request_rx) = - mpsc::unbounded_channel::(); - let (generate_embedding_batch_request_tx, generate_embedding_batch_request_rx) = - mpsc::unbounded_channel::(); - - let agent_applicable_state_holder = Arc::new(AgentApplicableStateHolder::default()); - let model_metadata_holder = Arc::new(ModelMetadataHolder::default()); - let mut service_manager = ServiceManager::default(); - let slot_aggregated_status_manager = Arc::new(SlotAggregatedStatusManager::new(self.slots)); - - service_manager.add_service(LlamaCppArbiterService { - agent_applicable_state: None, - agent_applicable_state_holder: agent_applicable_state_holder.clone(), - agent_name: self.name.clone(), - continue_from_conversation_history_request_rx, - continue_from_raw_prompt_request_rx, - desired_slots_total: self.slots, - generate_embedding_batch_request_rx, - continuous_batch_arbiter_handle: None, - model_metadata_holder: model_metadata_holder.clone(), - slot_aggregated_status_manager: slot_aggregated_status_manager.clone(), - }); - - service_manager.add_service(ManagementSocketClientService { - agent_applicable_state_holder: agent_applicable_state_holder.clone(), - agent_desired_state_tx, - continue_from_conversation_history_request_tx, - continue_from_raw_prompt_request_tx, - generate_embedding_batch_request_tx, - model_metadata_holder, - name: self.name.clone(), - receive_stream_stopper_collection: Arc::default(), - slot_aggregated_status: slot_aggregated_status_manager - .slot_aggregated_status - .clone(), - socket_url: format!( - "ws://{}/api/v1/agent_socket/{}", - self.management_addr.socket_addr, - nanoid!() - ), - }); - - service_manager.add_service(ReconciliationService { - agent_applicable_state_holder, - agent_desired_state: None, - agent_desired_state_rx, - is_converted_to_applicable_state: false, - slot_aggregated_status: slot_aggregated_status_manager - .slot_aggregated_status - .clone(), - }); - - service_manager.run_forever(shutdown_rx).await - } -} diff --git a/paddler/src/lib.rs b/paddler/src/lib.rs index caf8373c..57e93688 100644 --- a/paddler/src/lib.rs +++ b/paddler/src/lib.rs @@ -10,7 +10,6 @@ pub mod balancer_applicable_state; pub mod balancer_applicable_state_holder; pub mod balancer_desired_state; pub mod chat_template_renderer; -pub mod cmd; pub mod continuation_decision; pub mod continuation_stop_parameters; pub mod controls_session; diff --git a/paddler_bootstrap/Cargo.toml b/paddler_bootstrap/Cargo.toml new file mode 100644 index 00000000..d91fa2c2 --- /dev/null +++ b/paddler_bootstrap/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "paddler_bootstrap" +version.workspace = true +edition.workspace = true +authors.workspace = true +description = "Shared service wiring for Paddler CLI and desktop app" +license.workspace = true + +[dependencies] +anyhow = { workspace = true } +nanoid = { workspace = true } +paddler = { workspace = true } +paddler_types = { workspace = true } +tokio = { workspace = true } + +[lints] +workspace = true + +[features] +default = [] +web_admin_panel = ["paddler/web_admin_panel"] diff --git a/paddler_bootstrap/src/bootstrap_agent_params.rs b/paddler_bootstrap/src/bootstrap_agent_params.rs new file mode 100644 index 00000000..899aa987 --- /dev/null +++ b/paddler_bootstrap/src/bootstrap_agent_params.rs @@ -0,0 +1,5 @@ +pub struct BootstrapAgentParams { + pub agent_name: Option, + pub management_address: String, + pub slots: i32, +} diff --git a/paddler_bootstrap/src/bootstrap_balancer_params.rs b/paddler_bootstrap/src/bootstrap_balancer_params.rs new file mode 100644 index 00000000..374c3739 --- /dev/null +++ b/paddler_bootstrap/src/bootstrap_balancer_params.rs @@ -0,0 +1,20 @@ +use std::time::Duration; + +use paddler::balancer::compatibility::openai_service::configuration::Configuration as OpenAIServiceConfiguration; +use paddler::balancer::inference_service::configuration::Configuration as InferenceServiceConfiguration; +use paddler::balancer::management_service::configuration::Configuration as ManagementServiceConfiguration; +use paddler::balancer::state_database_type::StateDatabaseType; +#[cfg(feature = "web_admin_panel")] +use paddler::balancer::web_admin_panel_service::configuration::Configuration as WebAdminPanelServiceConfiguration; + +pub struct BootstrapBalancerParams { + pub buffered_request_timeout: Duration, + pub inference_service_configuration: InferenceServiceConfiguration, + pub management_service_configuration: ManagementServiceConfiguration, + pub max_buffered_requests: i32, + pub openai_service_configuration: Option, + pub state_database_type: StateDatabaseType, + pub statsd_prefix: String, + #[cfg(feature = "web_admin_panel")] + pub web_admin_panel_service_configuration: Option, +} diff --git a/paddler_bootstrap/src/bootstrapped_agent_handle.rs b/paddler_bootstrap/src/bootstrapped_agent_handle.rs new file mode 100644 index 00000000..b3a63212 --- /dev/null +++ b/paddler_bootstrap/src/bootstrapped_agent_handle.rs @@ -0,0 +1,96 @@ +use std::sync::Arc; + +use nanoid::nanoid; +use paddler::agent::continue_from_conversation_history_request::ContinueFromConversationHistoryRequest; +use paddler::agent::continue_from_raw_prompt_request::ContinueFromRawPromptRequest; +use paddler::agent::generate_embedding_batch_request::GenerateEmbeddingBatchRequest; +use paddler::agent::llamacpp_arbiter_service::LlamaCppArbiterService; +use paddler::agent::management_socket_client_service::ManagementSocketClientService; +use paddler::agent::model_metadata_holder::ModelMetadataHolder; +use paddler::agent::reconciliation_service::ReconciliationService; +use paddler::agent_applicable_state_holder::AgentApplicableStateHolder; +use paddler::agent_desired_state::AgentDesiredState; +use paddler::service_manager::ServiceManager; +use paddler::slot_aggregated_status::SlotAggregatedStatus; +use paddler::slot_aggregated_status_manager::SlotAggregatedStatusManager; +use tokio::sync::mpsc; + +use super::bootstrap_agent_params::BootstrapAgentParams; + +pub struct BootstrappedAgentHandle { + pub service_manager: ServiceManager, + pub slot_aggregated_status: Arc, +} + +pub fn bootstrap_agent( + BootstrapAgentParams { + agent_name, + management_address, + slots, + }: BootstrapAgentParams, +) -> BootstrappedAgentHandle { + let (agent_desired_state_tx, agent_desired_state_rx) = + mpsc::unbounded_channel::(); + let ( + continue_from_conversation_history_request_tx, + continue_from_conversation_history_request_rx, + ) = mpsc::unbounded_channel::(); + let (continue_from_raw_prompt_request_tx, continue_from_raw_prompt_request_rx) = + mpsc::unbounded_channel::(); + let (generate_embedding_batch_request_tx, generate_embedding_batch_request_rx) = + mpsc::unbounded_channel::(); + + let agent_applicable_state_holder = Arc::new(AgentApplicableStateHolder::default()); + let model_metadata_holder = Arc::new(ModelMetadataHolder::default()); + let mut service_manager = ServiceManager::default(); + let slot_aggregated_status_manager = Arc::new(SlotAggregatedStatusManager::new(slots)); + + service_manager.add_service(LlamaCppArbiterService { + agent_applicable_state: None, + agent_applicable_state_holder: agent_applicable_state_holder.clone(), + agent_name: agent_name.clone(), + continue_from_conversation_history_request_rx, + continue_from_raw_prompt_request_rx, + desired_slots_total: slots, + generate_embedding_batch_request_rx, + continuous_batch_arbiter_handle: None, + model_metadata_holder: model_metadata_holder.clone(), + slot_aggregated_status_manager: slot_aggregated_status_manager.clone(), + }); + + service_manager.add_service(ManagementSocketClientService { + agent_applicable_state_holder: agent_applicable_state_holder.clone(), + agent_desired_state_tx, + continue_from_conversation_history_request_tx, + continue_from_raw_prompt_request_tx, + generate_embedding_batch_request_tx, + model_metadata_holder, + name: agent_name, + receive_stream_stopper_collection: Arc::default(), + slot_aggregated_status: slot_aggregated_status_manager + .slot_aggregated_status + .clone(), + socket_url: format!( + "ws://{}/api/v1/agent_socket/{}", + management_address, + nanoid!() + ), + }); + + service_manager.add_service(ReconciliationService { + agent_applicable_state_holder, + agent_desired_state: None, + agent_desired_state_rx, + is_converted_to_applicable_state: false, + slot_aggregated_status: slot_aggregated_status_manager + .slot_aggregated_status + .clone(), + }); + + BootstrappedAgentHandle { + service_manager, + slot_aggregated_status: slot_aggregated_status_manager + .slot_aggregated_status + .clone(), + } +} diff --git a/paddler_bootstrap/src/bootstrapped_balancer_handle.rs b/paddler_bootstrap/src/bootstrapped_balancer_handle.rs new file mode 100644 index 00000000..ee85d0cd --- /dev/null +++ b/paddler_bootstrap/src/bootstrapped_balancer_handle.rs @@ -0,0 +1,110 @@ +use std::sync::Arc; + +use paddler::balancer::agent_controller_pool::AgentControllerPool; +use paddler::balancer::buffered_request_manager::BufferedRequestManager; +use paddler::balancer::chat_template_override_sender_collection::ChatTemplateOverrideSenderCollection; +use paddler::balancer::compatibility::openai_service::OpenAIService; +use paddler::balancer::embedding_sender_collection::EmbeddingSenderCollection; +use paddler::balancer::generate_tokens_sender_collection::GenerateTokensSenderCollection; +use paddler::balancer::inference_service::InferenceService; +use paddler::balancer::management_service::ManagementService; +use paddler::balancer::model_metadata_sender_collection::ModelMetadataSenderCollection; +use paddler::balancer::reconciliation_service::ReconciliationService; +use paddler::balancer::state_database::File; +use paddler::balancer::state_database::Memory; +use paddler::balancer::state_database::StateDatabase; +use paddler::balancer::state_database_type::StateDatabaseType; +use paddler::balancer_applicable_state_holder::BalancerApplicableStateHolder; +use paddler::service_manager::ServiceManager; +use tokio::sync::broadcast; + +use super::bootstrap_balancer_params::BootstrapBalancerParams; + +pub struct BootstrappedBalancerHandle { + pub agent_controller_pool: Arc, + pub buffered_request_manager: Arc, + pub service_manager: ServiceManager, + pub state_database: Arc, +} + +pub async fn bootstrap_balancer( + BootstrapBalancerParams { + buffered_request_timeout, + inference_service_configuration, + management_service_configuration, + max_buffered_requests, + openai_service_configuration, + state_database_type, + statsd_prefix, + #[cfg(feature = "web_admin_panel")] + web_admin_panel_service_configuration, + }: BootstrapBalancerParams, +) -> anyhow::Result { + let (balancer_desired_state_tx, balancer_desired_state_rx) = broadcast::channel(100); + + let agent_controller_pool = Arc::new(AgentControllerPool::default()); + let balancer_applicable_state_holder = Arc::new(BalancerApplicableStateHolder::default()); + let buffered_request_manager = Arc::new(BufferedRequestManager::new( + agent_controller_pool.clone(), + buffered_request_timeout, + max_buffered_requests, + )); + let chat_template_override_sender_collection = + Arc::new(ChatTemplateOverrideSenderCollection::default()); + let embedding_sender_collection = Arc::new(EmbeddingSenderCollection::default()); + let generate_tokens_sender_collection = Arc::new(GenerateTokensSenderCollection::default()); + let model_metadata_sender_collection = Arc::new(ModelMetadataSenderCollection::default()); + let mut service_manager = ServiceManager::default(); + let state_database: Arc = match state_database_type { + StateDatabaseType::File(path) => { + Arc::new(File::new(balancer_desired_state_tx.clone(), path)) + } + StateDatabaseType::Memory => Arc::new(Memory::new(balancer_desired_state_tx.clone())), + }; + + service_manager.add_service(InferenceService { + balancer_applicable_state_holder: balancer_applicable_state_holder.clone(), + buffered_request_manager: buffered_request_manager.clone(), + configuration: inference_service_configuration.clone(), + #[cfg(feature = "web_admin_panel")] + web_admin_panel_service_configuration: web_admin_panel_service_configuration.clone(), + }); + + service_manager.add_service(ManagementService { + agent_controller_pool: agent_controller_pool.clone(), + balancer_applicable_state_holder: balancer_applicable_state_holder.clone(), + buffered_request_manager: buffered_request_manager.clone(), + chat_template_override_sender_collection, + configuration: management_service_configuration, + embedding_sender_collection, + generate_tokens_sender_collection, + model_metadata_sender_collection, + state_database: state_database.clone(), + statsd_prefix, + #[cfg(feature = "web_admin_panel")] + web_admin_panel_service_configuration, + }); + + service_manager.add_service(ReconciliationService { + agent_controller_pool: agent_controller_pool.clone(), + balancer_applicable_state_holder, + balancer_desired_state: state_database.read_balancer_desired_state().await?, + balancer_desired_state_rx, + is_converted_to_applicable_state: false, + }); + + if let Some(openai_configuration) = openai_service_configuration { + service_manager.add_service(OpenAIService { + buffered_request_manager: buffered_request_manager.clone(), + inference_service_configuration, + openai_service_configuration: openai_configuration, + }); + } + + Ok(BootstrappedBalancerHandle { + agent_controller_pool, + buffered_request_manager, + service_manager, + state_database, + }) +} diff --git a/paddler_bootstrap/src/lib.rs b/paddler_bootstrap/src/lib.rs new file mode 100644 index 00000000..5916f33f --- /dev/null +++ b/paddler_bootstrap/src/lib.rs @@ -0,0 +1,4 @@ +pub mod bootstrap_agent_params; +pub mod bootstrap_balancer_params; +pub mod bootstrapped_agent_handle; +pub mod bootstrapped_balancer_handle; diff --git a/paddler_cli/Cargo.toml b/paddler_cli/Cargo.toml new file mode 100644 index 00000000..0002faff --- /dev/null +++ b/paddler_cli/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "paddler_cli" +version.workspace = true +edition.workspace = true +authors.workspace = true +description = "CLI binary for Paddler" +license.workspace = true + +[dependencies] +actix-rt = { workspace = true } +actix-web = { workspace = true } +anyhow = { workspace = true } +async-trait = { workspace = true } +clap = { workspace = true } +env_logger = { workspace = true } +log = { workspace = true } +nanoid = { workspace = true } +paddler = { workspace = true } +paddler_bootstrap = { workspace = true } +tokio = { workspace = true } + +# web dashboard deps +esbuild-metafile = { workspace = true, optional = true } + +[lints] +workspace = true + +[features] +default = [] +cuda = ["paddler/cuda"] +vulkan = ["paddler/vulkan"] +web_admin_panel = [ + "dep:esbuild-metafile", + "paddler/web_admin_panel", + "paddler_bootstrap/web_admin_panel", +] diff --git a/paddler_cli/src/cmd/agent.rs b/paddler_cli/src/cmd/agent.rs new file mode 100644 index 00000000..303795ee --- /dev/null +++ b/paddler_cli/src/cmd/agent.rs @@ -0,0 +1,38 @@ +use anyhow::Result; +use async_trait::async_trait; +use clap::Parser; +use paddler::resolved_socket_addr::ResolvedSocketAddr; +use paddler_bootstrap::bootstrap_agent_params::BootstrapAgentParams; +use paddler_bootstrap::bootstrapped_agent_handle::bootstrap_agent; +use tokio::sync::oneshot; + +use super::handler::Handler; +use super::value_parser::parse_socket_addr; + +#[derive(Parser)] +pub struct Agent { + #[arg(long, value_parser = parse_socket_addr)] + /// Address of the management server that the agent will connect to + management_addr: ResolvedSocketAddr, + + #[arg(long)] + /// Name of the agent (optional) + name: Option, + + #[arg(long)] + /// Number of parallel requests of any kind that the agent can handle at once + slots: i32, +} + +#[async_trait] +impl Handler for Agent { + async fn handle(&self, shutdown_rx: oneshot::Receiver<()>) -> Result<()> { + let bootstrapped = bootstrap_agent(BootstrapAgentParams { + agent_name: self.name.clone(), + management_address: self.management_addr.socket_addr.to_string(), + slots: self.slots, + }); + + bootstrapped.service_manager.run_forever(shutdown_rx).await + } +} diff --git a/paddler/src/cmd/balancer.rs b/paddler_cli/src/cmd/balancer.rs similarity index 50% rename from paddler/src/cmd/balancer.rs rename to paddler_cli/src/cmd/balancer.rs index bc22ab19..599be5fb 100644 --- a/paddler/src/cmd/balancer.rs +++ b/paddler_cli/src/cmd/balancer.rs @@ -1,43 +1,28 @@ -use std::sync::Arc; use std::time::Duration; use anyhow::Result; use async_trait::async_trait; use clap::Parser; -use tokio::sync::broadcast; +use paddler::balancer::compatibility::openai_service::configuration::Configuration as OpenAIServiceConfiguration; +use paddler::balancer::inference_service::configuration::Configuration as InferenceServiceConfiguration; +use paddler::balancer::management_service::configuration::Configuration as ManagementServiceConfiguration; +use paddler::balancer::state_database_type::StateDatabaseType; +use paddler::balancer::statsd_service::StatsdService; +use paddler::balancer::statsd_service::configuration::Configuration as StatsdServiceConfiguration; +#[cfg(feature = "web_admin_panel")] +use paddler::balancer::web_admin_panel_service::WebAdminPanelService; +#[cfg(feature = "web_admin_panel")] +use paddler::balancer::web_admin_panel_service::configuration::Configuration as WebAdminPanelServiceConfiguration; +#[cfg(feature = "web_admin_panel")] +use paddler::balancer::web_admin_panel_service::template_data::TemplateData; +use paddler::resolved_socket_addr::ResolvedSocketAddr; +use paddler_bootstrap::bootstrap_balancer_params::BootstrapBalancerParams; +use paddler_bootstrap::bootstrapped_balancer_handle::bootstrap_balancer; use tokio::sync::oneshot; use super::handler::Handler; use super::value_parser::parse_duration; use super::value_parser::parse_socket_addr; -use crate::balancer::agent_controller_pool::AgentControllerPool; -use crate::balancer::buffered_request_manager::BufferedRequestManager; -use crate::balancer::chat_template_override_sender_collection::ChatTemplateOverrideSenderCollection; -use crate::balancer::compatibility::openai_service::OpenAIService; -use crate::balancer::compatibility::openai_service::configuration::Configuration as OpenAIServiceConfiguration; -use crate::balancer::embedding_sender_collection::EmbeddingSenderCollection; -use crate::balancer::generate_tokens_sender_collection::GenerateTokensSenderCollection; -use crate::balancer::inference_service::InferenceService; -use crate::balancer::inference_service::configuration::Configuration as InferenceServiceConfiguration; -use crate::balancer::management_service::ManagementService; -use crate::balancer::management_service::configuration::Configuration as ManagementServiceConfiguration; -use crate::balancer::model_metadata_sender_collection::ModelMetadataSenderCollection; -use crate::balancer::reconciliation_service::ReconciliationService; -use crate::balancer::state_database::File; -use crate::balancer::state_database::Memory; -use crate::balancer::state_database::StateDatabase; -use crate::balancer::state_database_type::StateDatabaseType; -use crate::balancer::statsd_service::StatsdService; -use crate::balancer::statsd_service::configuration::Configuration as StatsdServiceConfiguration; -#[cfg(feature = "web_admin_panel")] -use crate::balancer::web_admin_panel_service::WebAdminPanelService; -#[cfg(feature = "web_admin_panel")] -use crate::balancer::web_admin_panel_service::configuration::Configuration as WebAdminPanelServiceConfiguration; -#[cfg(feature = "web_admin_panel")] -use crate::balancer::web_admin_panel_service::template_data::TemplateData; -use crate::balancer_applicable_state_holder::BalancerApplicableStateHolder; -use crate::resolved_socket_addr::ResolvedSocketAddr; -use crate::service_manager::ServiceManager; #[derive(Parser)] pub struct Balancer { @@ -119,6 +104,14 @@ impl Balancer { } } + fn get_openai_service_configuration(&self) -> Option { + self.compat_openai_addr + .clone() + .map(|compat_openai_addr| OpenAIServiceConfiguration { + addr: compat_openai_addr.socket_addr, + }) + } + #[cfg(feature = "web_admin_panel")] fn get_web_admin_panel_service_configuration( &self, @@ -144,64 +137,23 @@ impl Balancer { #[async_trait] impl Handler for Balancer { async fn handle(&self, shutdown_rx: oneshot::Receiver<()>) -> Result<()> { - let (balancer_desired_state_tx, balancer_desired_state_rx) = broadcast::channel(100); - - let agent_controller_pool = Arc::new(AgentControllerPool::default()); - let balancer_applicable_state_holder = Arc::new(BalancerApplicableStateHolder::default()); - let buffered_request_manager = Arc::new(BufferedRequestManager::new( - agent_controller_pool.clone(), - self.buffered_request_timeout, - self.max_buffered_requests, - )); - let chat_template_override_sender_collection = - Arc::new(ChatTemplateOverrideSenderCollection::default()); - let embedding_sender_collection = Arc::new(EmbeddingSenderCollection::default()); - let generate_tokens_sender_collection = Arc::new(GenerateTokensSenderCollection::default()); - let model_metadata_sender_collection = Arc::new(ModelMetadataSenderCollection::default()); - let mut service_manager = ServiceManager::default(); - let state_database: Arc = match &self.state_database { - StateDatabaseType::File(path) => Arc::new(File::new( - balancer_desired_state_tx.clone(), - path.to_owned(), - )), - StateDatabaseType::Memory => Arc::new(Memory::new(balancer_desired_state_tx.clone())), - }; - - service_manager.add_service(InferenceService { - balancer_applicable_state_holder: balancer_applicable_state_holder.clone(), - buffered_request_manager: buffered_request_manager.clone(), - configuration: self.get_inference_service_configuration(), - #[cfg(feature = "web_admin_panel")] - web_admin_panel_service_configuration: self.get_web_admin_panel_service_configuration(), - }); - - service_manager.add_service(ManagementService { - agent_controller_pool: agent_controller_pool.clone(), - balancer_applicable_state_holder: balancer_applicable_state_holder.clone(), - buffered_request_manager: buffered_request_manager.clone(), - chat_template_override_sender_collection, - configuration: self.get_management_service_configuration(), - embedding_sender_collection, - generate_tokens_sender_collection, - model_metadata_sender_collection, - state_database: state_database.clone(), + let mut bootstrapped = bootstrap_balancer(BootstrapBalancerParams { + buffered_request_timeout: self.buffered_request_timeout, + inference_service_configuration: self.get_inference_service_configuration(), + management_service_configuration: self.get_management_service_configuration(), + max_buffered_requests: self.max_buffered_requests, + openai_service_configuration: self.get_openai_service_configuration(), + state_database_type: self.state_database.clone(), statsd_prefix: self.statsd_prefix.clone(), #[cfg(feature = "web_admin_panel")] web_admin_panel_service_configuration: self.get_web_admin_panel_service_configuration(), - }); - - service_manager.add_service(ReconciliationService { - agent_controller_pool: agent_controller_pool.clone(), - balancer_applicable_state_holder, - balancer_desired_state: state_database.read_balancer_desired_state().await?, - balancer_desired_state_rx, - is_converted_to_applicable_state: false, - }); + }) + .await?; if let Some(statsd_addr) = self.statsd_addr.clone() { - service_manager.add_service(StatsdService { - agent_controller_pool, - buffered_request_manager: buffered_request_manager.clone(), + bootstrapped.service_manager.add_service(StatsdService { + agent_controller_pool: bootstrapped.agent_controller_pool.clone(), + buffered_request_manager: bootstrapped.buffered_request_manager.clone(), configuration: StatsdServiceConfiguration { statsd_addr: statsd_addr.socket_addr, statsd_prefix: self.statsd_prefix.clone(), @@ -212,19 +164,11 @@ impl Handler for Balancer { #[cfg(feature = "web_admin_panel")] if let Some(configuration) = self.get_web_admin_panel_service_configuration() { - service_manager.add_service(WebAdminPanelService { configuration }); - } - - if let Some(compat_openai_addr) = self.compat_openai_addr.clone() { - service_manager.add_service(OpenAIService { - buffered_request_manager, - inference_service_configuration: self.get_inference_service_configuration(), - openai_service_configuration: OpenAIServiceConfiguration { - addr: compat_openai_addr.socket_addr, - }, - }); + bootstrapped + .service_manager + .add_service(WebAdminPanelService { configuration }); } - service_manager.run_forever(shutdown_rx).await + bootstrapped.service_manager.run_forever(shutdown_rx).await } } diff --git a/paddler/src/cmd/handler.rs b/paddler_cli/src/cmd/handler.rs similarity index 100% rename from paddler/src/cmd/handler.rs rename to paddler_cli/src/cmd/handler.rs diff --git a/paddler/src/cmd/mod.rs b/paddler_cli/src/cmd/mod.rs similarity index 100% rename from paddler/src/cmd/mod.rs rename to paddler_cli/src/cmd/mod.rs diff --git a/paddler/src/cmd/value_parser/mod.rs b/paddler_cli/src/cmd/value_parser/mod.rs similarity index 100% rename from paddler/src/cmd/value_parser/mod.rs rename to paddler_cli/src/cmd/value_parser/mod.rs diff --git a/paddler/src/cmd/value_parser/parse_duration.rs b/paddler_cli/src/cmd/value_parser/parse_duration.rs similarity index 100% rename from paddler/src/cmd/value_parser/parse_duration.rs rename to paddler_cli/src/cmd/value_parser/parse_duration.rs diff --git a/paddler/src/cmd/value_parser/parse_socket_addr.rs b/paddler_cli/src/cmd/value_parser/parse_socket_addr.rs similarity index 97% rename from paddler/src/cmd/value_parser/parse_socket_addr.rs rename to paddler_cli/src/cmd/value_parser/parse_socket_addr.rs index 5b23e0fa..f8cad109 100644 --- a/paddler/src/cmd/value_parser/parse_socket_addr.rs +++ b/paddler_cli/src/cmd/value_parser/parse_socket_addr.rs @@ -4,8 +4,7 @@ use std::net::ToSocketAddrs; use anyhow::Result; use anyhow::anyhow; use log::warn; - -use crate::resolved_socket_addr::ResolvedSocketAddr; +use paddler::resolved_socket_addr::ResolvedSocketAddr; fn resolve_socket_addr(input_addr: &str) -> Result { let addrs: Vec = input_addr.to_socket_addrs()?.collect(); diff --git a/paddler/src/main.rs b/paddler_cli/src/main.rs similarity index 96% rename from paddler/src/main.rs rename to paddler_cli/src/main.rs index 99c348fb..8dc0e2d3 100644 --- a/paddler/src/main.rs +++ b/paddler_cli/src/main.rs @@ -4,9 +4,11 @@ use clap::Subcommand; #[cfg(feature = "web_admin_panel")] use esbuild_metafile::instance::initialize_instance; use log::info; -use paddler::cmd::agent::Agent; -use paddler::cmd::balancer::Balancer; -use paddler::cmd::handler::Handler as _; +mod cmd; + +use cmd::agent::Agent; +use cmd::balancer::Balancer; +use cmd::handler::Handler as _; use tokio::signal::unix::SignalKind; use tokio::signal::unix::signal; use tokio::sync::oneshot; diff --git a/paddler_gui/Cargo.toml b/paddler_gui/Cargo.toml new file mode 100644 index 00000000..65834965 --- /dev/null +++ b/paddler_gui/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "paddler_gui" +version.workspace = true +edition.workspace = true +authors.workspace = true +description = "Paddler desktop application" +license.workspace = true + +[dependencies] +actix-web = { workspace = true } +anyhow = { workspace = true } +env_logger = { workspace = true } +iced = { workspace = true } +if-addrs = { workspace = true } +log = { workspace = true } +paddler = { workspace = true } +paddler_bootstrap = { workspace = true } +paddler_types = { workspace = true } +statum = { workspace = true } +tokio = { workspace = true } + +[lints] +workspace = true + +[features] +default = [] +web_admin_panel = ["paddler/web_admin_panel"] diff --git a/paddler_gui/src/agent_running_data.rs b/paddler_gui/src/agent_running_data.rs new file mode 100644 index 00000000..454ac4aa --- /dev/null +++ b/paddler_gui/src/agent_running_data.rs @@ -0,0 +1,28 @@ +use paddler_types::agent_controller_snapshot::AgentControllerSnapshot; +use paddler_types::slot_aggregated_status_snapshot::SlotAggregatedStatusSnapshot; + +pub struct AgentRunningData { + pub cluster_address: String, + pub connected: bool, + pub snapshot: AgentControllerSnapshot, +} + +impl AgentRunningData { + pub fn apply_status(&mut self, status: SlotAggregatedStatusSnapshot) { + self.connected = true; + self.snapshot = AgentControllerSnapshot { + desired_slots_total: status.desired_slots_total, + download_current: status.download_current, + download_filename: status.download_filename, + download_total: status.download_total, + id: String::new(), + issues: status.issues, + model_path: status.model_path, + name: self.snapshot.name.clone(), + slots_processing: status.slots_processing, + slots_total: status.slots_total, + state_application_status: status.state_application_status, + uses_chat_template_override: status.uses_chat_template_override, + }; + } +} diff --git a/paddler_gui/src/agent_running_handler.rs b/paddler_gui/src/agent_running_handler.rs new file mode 100644 index 00000000..78bfb814 --- /dev/null +++ b/paddler_gui/src/agent_running_handler.rs @@ -0,0 +1,27 @@ +use paddler_types::slot_aggregated_status_snapshot::SlotAggregatedStatusSnapshot; + +use crate::agent_running_data::AgentRunningData; + +#[derive(Debug, Clone)] +pub enum Message { + AgentStatusUpdated(SlotAggregatedStatusSnapshot), + Disconnect, +} + +pub enum Action { + None, + Disconnect, +} + +impl AgentRunningData { + pub fn update(&mut self, message: Message) -> Action { + match message { + Message::AgentStatusUpdated(status) => { + self.apply_status(status); + + Action::None + } + Message::Disconnect => Action::Disconnect, + } + } +} diff --git a/paddler_gui/src/app.rs b/paddler_gui/src/app.rs new file mode 100644 index 00000000..bbba254f --- /dev/null +++ b/paddler_gui/src/app.rs @@ -0,0 +1,512 @@ +use std::mem; +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::Duration; + +use iced::Center; +use iced::Element; +use iced::Fill; +use iced::Subscription; +use iced::Task; +use iced::futures::SinkExt; +use iced::keyboard; +use iced::widget::column; +use iced::widget::container; +use iced::widget::operation; +use paddler::balancer::agent_controller_pool::AgentControllerPool; +use paddler::balancer::inference_service::configuration::Configuration as InferenceServiceConfiguration; +use paddler::balancer::management_service::configuration::Configuration as ManagementServiceConfiguration; +use paddler::balancer::state_database_type::StateDatabaseType; +use paddler::produces_snapshot::ProducesSnapshot; +use paddler::slot_aggregated_status::SlotAggregatedStatus; +use paddler_bootstrap::bootstrap_agent_params::BootstrapAgentParams; +use paddler_bootstrap::bootstrap_balancer_params::BootstrapBalancerParams; +use paddler_bootstrap::bootstrapped_agent_handle::bootstrap_agent; +use paddler_bootstrap::bootstrapped_balancer_handle::bootstrap_balancer; +use paddler_types::balancer_desired_state::BalancerDesiredState; +use tokio::sync::oneshot; +use tokio::sync::watch; + +use crate::agent_running_handler; +use crate::current_screen::CurrentScreen; +use crate::home_data::HomeData; +use crate::home_handler; +use crate::join_cluster_config_handler; +use crate::message::Message; +use crate::running_cluster_handler; +use crate::screen::AgentRunning; +use crate::screen::Screen; +use crate::start_cluster_config_handler; +use crate::ui::variables::SPACING_2X; +use crate::ui::variables::SPACING_BASE; +use crate::ui::view_agent_running::view_agent_running; +use crate::ui::view_home::view_home; +use crate::ui::view_join_cluster_config::view_join_cluster_config; +use crate::ui::view_running_cluster::view_running_cluster; +use crate::ui::view_start_cluster_config::view_start_cluster_config; + +fn send_shutdown(sender: &mut Option>, label: &str) { + if let Some(shutdown_tx) = sender.take() + && let Err(unsent_signal) = shutdown_tx.send(()) + { + log::error!("Failed to send {label} shutdown signal: {unsent_signal:?}"); + } +} + +fn collect_sorted_agent_snapshots( + pool: &AgentControllerPool, +) -> anyhow::Result> { + let pool_snapshot = pool.make_snapshot()?; + let mut agents = pool_snapshot.agents; + + agents.sort_by(|current_agent, other_agent| { + let current_name = current_agent.name.as_deref().unwrap_or(¤t_agent.id); + let other_name = other_agent.name.as_deref().unwrap_or(&other_agent.id); + + current_name.cmp(other_name) + }); + + Ok(agents) +} + +pub struct App { + agent_shutdown_tx: Option>, + screen: CurrentScreen, + shutdown_tx: Option>, +} + +impl Drop for App { + fn drop(&mut self) { + send_shutdown(&mut self.shutdown_tx, "cluster"); + send_shutdown(&mut self.agent_shutdown_tx, "agent"); + } +} + +impl App { + pub fn new() -> (Self, Task) { + let app = Self { + agent_shutdown_tx: None, + screen: CurrentScreen::default(), + shutdown_tx: None, + }; + + (app, Task::none()) + } + + pub fn update(&mut self, message: Message) -> Task { + let screen = mem::take(&mut self.screen); + + match (screen, message) { + (CurrentScreen::Home(home), Message::Home(msg)) => { + let action = HomeData::update(msg); + + match action { + home_handler::Action::StartCluster => { + self.screen = CurrentScreen::StartClusterConfig(home.start_cluster()); + + Task::none() + } + home_handler::Action::JoinCluster => { + self.screen = CurrentScreen::JoinClusterConfig(home.join_cluster()); + + Task::none() + } + } + } + (CurrentScreen::JoinClusterConfig(mut config), Message::JoinClusterConfig(msg)) => { + let action = config.state_data.update(msg); + + match action { + join_cluster_config_handler::Action::None => { + self.screen = CurrentScreen::JoinClusterConfig(config); + + Task::none() + } + join_cluster_config_handler::Action::Cancel => { + self.screen = CurrentScreen::Home(config.cancel()); + + Task::none() + } + join_cluster_config_handler::Action::ConnectAgent { + agent_name, + management_address, + slots, + } => self.spawn_agent(config.connect(), agent_name, management_address, slots), + } + } + (CurrentScreen::StartClusterConfig(mut config), Message::StartClusterConfig(msg)) => { + let action = config.state_data.update(msg); + + match action { + start_cluster_config_handler::Action::None => { + self.screen = CurrentScreen::StartClusterConfig(config); + + Task::none() + } + start_cluster_config_handler::Action::Cancel => { + send_shutdown(&mut self.shutdown_tx, "cluster"); + self.screen = CurrentScreen::Home(config.cancel()); + + Task::none() + } + start_cluster_config_handler::Action::StartCluster { + management_addr, + inference_addr, + desired_state, + } => { + self.screen = CurrentScreen::StartClusterConfig(config); + + self.spawn_cluster(management_addr, inference_addr, desired_state) + } + } + } + (CurrentScreen::StartClusterConfig(config), Message::ClusterStarted) => { + self.screen = CurrentScreen::RunningCluster(config.cluster_started()); + + Task::none() + } + (CurrentScreen::StartClusterConfig(config), Message::ClusterFailed(error)) => { + log::error!("Cluster failed to start: {error}"); + self.shutdown_tx = None; + self.screen = CurrentScreen::Home(config.cluster_failed(error)); + + Task::none() + } + (CurrentScreen::RunningCluster(mut running), Message::RunningCluster(msg)) => { + let action = running.state_data.update(msg); + + match action { + running_cluster_handler::Action::None => { + self.screen = CurrentScreen::RunningCluster(running); + + Task::none() + } + running_cluster_handler::Action::Stop => { + send_shutdown(&mut self.shutdown_tx, "cluster"); + self.screen = CurrentScreen::RunningCluster(running); + + Task::none() + } + running_cluster_handler::Action::CopyToClipboard(content) => { + self.screen = CurrentScreen::RunningCluster(running); + + iced::clipboard::write::(content).discard() + } + } + } + (CurrentScreen::RunningCluster(running), Message::ClusterStopped) => { + self.screen = CurrentScreen::Home(running.cluster_stopped()); + + Task::none() + } + (CurrentScreen::RunningCluster(running), Message::ClusterFailed(error)) => { + log::error!("Cluster failed unexpectedly: {error}"); + self.shutdown_tx = None; + self.screen = CurrentScreen::Home(running.cluster_failed(error)); + + Task::none() + } + (CurrentScreen::AgentRunning(mut running), Message::AgentRunning(msg)) => { + let action = running.state_data.update(msg); + + match action { + agent_running_handler::Action::None => { + self.screen = CurrentScreen::AgentRunning(running); + + Task::none() + } + agent_running_handler::Action::Disconnect => { + send_shutdown(&mut self.agent_shutdown_tx, "agent"); + self.screen = CurrentScreen::Home(running.disconnect()); + + Task::none() + } + } + } + (CurrentScreen::AgentRunning(running), Message::AgentStopped) => { + log::info!("Agent stopped"); + self.agent_shutdown_tx = None; + self.screen = CurrentScreen::Home(running.disconnect()); + + Task::none() + } + (CurrentScreen::AgentRunning(running), Message::AgentFailed(error)) => { + log::error!("Agent failed: {error}"); + self.agent_shutdown_tx = None; + self.screen = CurrentScreen::Home(running.agent_failed(error)); + + Task::none() + } + (screen, Message::TabPressed { shift }) => { + self.screen = screen; + + if shift { + operation::focus_previous() + } else { + operation::focus_next() + } + } + (screen, message) => { + log::warn!("Unhandled message {message:?} for current screen"); + self.screen = screen; + + Task::none() + } + } + } + + #[expect( + clippy::unused_self, + reason = "signature required by iced application API" + )] + pub fn subscription(&self) -> Subscription { + keyboard::listen().filter_map(|event| match event { + keyboard::Event::KeyPressed { + key: keyboard::Key::Named(keyboard::key::Named::Tab), + modifiers, + .. + } => Some(Message::TabPressed { + shift: modifiers.shift(), + }), + _ => None, + }) + } + + pub fn view(&self) -> Element<'_, Message> { + let screen_content = match &self.screen { + CurrentScreen::AgentRunning(screen) => { + view_agent_running(&screen.state_data).map(Message::AgentRunning) + } + CurrentScreen::Home(screen) => view_home(&screen.state_data).map(Message::Home), + CurrentScreen::JoinClusterConfig(screen) => { + view_join_cluster_config(&screen.state_data).map(Message::JoinClusterConfig) + } + CurrentScreen::StartClusterConfig(screen) => { + view_start_cluster_config(&screen.state_data).map(Message::StartClusterConfig) + } + CurrentScreen::RunningCluster(screen) => { + view_running_cluster(&screen.state_data).map(Message::RunningCluster) + } + }; + + let content_column = column![screen_content] + .max_width(700) + .padding([SPACING_2X * 2.0, SPACING_BASE]) + .spacing(SPACING_BASE) + .align_x(Center); + + container(content_column).center_x(Fill).into() + } + + fn spawn_agent( + &mut self, + screen: Screen, + agent_name: Option, + management_address: String, + slots: i32, + ) -> Task { + let (agent_shutdown_tx, agent_shutdown_rx) = oneshot::channel::<()>(); + let (status_watch_tx, status_watch_rx) = + watch::channel::>>(None); + + self.agent_shutdown_tx = Some(agent_shutdown_tx); + self.screen = CurrentScreen::AgentRunning(screen); + + Task::batch([ + Task::perform( + async move { + tokio::task::spawn_blocking(move || { + actix_web::rt::System::new().block_on(async { + let bootstrapped = bootstrap_agent(BootstrapAgentParams { + agent_name, + management_address, + slots, + }); + + if status_watch_tx + .send(Some(bootstrapped.slot_aggregated_status.clone())) + .is_err() + { + return Err(anyhow::anyhow!( + "Monitor stream was dropped before receiving status" + )); + } + + bootstrapped + .service_manager + .run_forever(agent_shutdown_rx) + .await + }) + }) + .await + .map_err(|error| anyhow::anyhow!("Agent task panicked: {error}"))? + }, + |result: Result<(), anyhow::Error>| match result { + Ok(()) => Message::AgentStopped, + Err(error) => Message::AgentFailed(error.to_string()), + }, + ), + Task::stream(iced::stream::channel(1, async move |mut output| { + let mut watch_rx = status_watch_rx; + + let slot_aggregated_status = loop { + if watch_rx.changed().await.is_err() { + return; + } + let borrowed = watch_rx.borrow_and_update().clone(); + if let Some(status) = borrowed { + break status; + } + }; + + loop { + match slot_aggregated_status.make_snapshot() { + Ok(snapshot) => { + if output + .send(Message::AgentRunning( + agent_running_handler::Message::AgentStatusUpdated(snapshot), + )) + .await + .is_err() + { + return; + } + } + Err(error) => { + log::error!("Failed to make agent status snapshot: {error}"); + + return; + } + } + + tokio::select! { + () = slot_aggregated_status.update_notifier.notified() => {} + changed_result = watch_rx.changed() => { + if changed_result.is_err() { + return; + } + } + } + } + })), + ]) + } + + fn spawn_cluster( + &mut self, + management_addr: SocketAddr, + inference_addr: SocketAddr, + desired_state: BalancerDesiredState, + ) -> Task { + let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); + let (pool_watch_tx, pool_watch_rx) = + watch::channel::>>(None); + + self.shutdown_tx = Some(shutdown_tx); + + Task::batch([ + Task::perform( + async move { + tokio::task::spawn_blocking(move || { + actix_web::rt::System::new().block_on(async { + let bootstrapped = bootstrap_balancer(BootstrapBalancerParams { + buffered_request_timeout: Duration::from_secs(10), + inference_service_configuration: InferenceServiceConfiguration { + addr: inference_addr, + cors_allowed_hosts: vec![], + inference_item_timeout: Duration::from_secs(30), + }, + management_service_configuration: ManagementServiceConfiguration { + addr: management_addr, + cors_allowed_hosts: vec![], + }, + max_buffered_requests: 30, + openai_service_configuration: None, + state_database_type: StateDatabaseType::Memory, + statsd_prefix: "paddler_".to_owned(), + #[cfg(feature = "web_admin_panel")] + web_admin_panel_service_configuration: None, + }) + .await?; + + if pool_watch_tx + .send(Some(bootstrapped.agent_controller_pool.clone())) + .is_err() + { + return Err(anyhow::anyhow!( + "Monitor stream was dropped before receiving pool" + )); + } + + let state_database = bootstrapped.state_database.clone(); + + let service_handle = actix_web::rt::spawn( + bootstrapped.service_manager.run_forever(shutdown_rx), + ); + + state_database + .store_balancer_desired_state(&desired_state) + .await?; + + service_handle.await.map_err(|error| { + anyhow::anyhow!("Service manager task failed: {error}") + })? + }) + }) + .await + .map_err(|error| anyhow::anyhow!("Balancer task panicked: {error}"))? + }, + |result: Result<(), anyhow::Error>| match result { + Ok(()) => Message::ClusterStopped, + Err(error) => Message::ClusterFailed(error.to_string()), + }, + ), + Task::done(Message::ClusterStarted), + Task::stream(iced::stream::channel(1, async move |mut output| { + let mut watch_rx = pool_watch_rx; + + let agent_controller_pool = loop { + if watch_rx.changed().await.is_err() { + return; + } + let borrowed = watch_rx.borrow_and_update().clone(); + if let Some(pool) = borrowed { + break pool; + } + }; + + loop { + match collect_sorted_agent_snapshots(&agent_controller_pool) { + Ok(snapshots) => { + if output + .send(Message::RunningCluster( + running_cluster_handler::Message::AgentSnapshotsUpdated( + snapshots, + ), + )) + .await + .is_err() + { + return; + } + } + Err(error) => { + log::error!("Failed to collect agent snapshots: {error}"); + + return; + } + } + + tokio::select! { + () = agent_controller_pool.update_notifier.notified() => {} + changed_result = watch_rx.changed() => { + if changed_result.is_err() { + return; + } + } + } + } + })), + ]) + } +} diff --git a/paddler_gui/src/current_screen.rs b/paddler_gui/src/current_screen.rs new file mode 100644 index 00000000..c3175f39 --- /dev/null +++ b/paddler_gui/src/current_screen.rs @@ -0,0 +1,26 @@ +use crate::screen::AgentRunning; +use crate::screen::Home; +use crate::screen::JoinClusterConfig; +use crate::screen::RunningCluster; +use crate::screen::Screen; +use crate::screen::StartClusterConfig; + +pub enum CurrentScreen { + AgentRunning(Screen), + Home(Screen), + JoinClusterConfig(Screen), + StartClusterConfig(Screen), + RunningCluster(Screen), +} + +impl Default for CurrentScreen { + fn default() -> Self { + use crate::home_data::HomeData; + + Self::Home( + Screen::::builder() + .state_data(HomeData { error: None }) + .build(), + ) + } +} diff --git a/paddler_gui/src/detect_network_interfaces.rs b/paddler_gui/src/detect_network_interfaces.rs new file mode 100644 index 00000000..c80a52ff --- /dev/null +++ b/paddler_gui/src/detect_network_interfaces.rs @@ -0,0 +1,26 @@ +use crate::network_interface_address::NetworkInterfaceAddress; + +pub fn detect_network_interfaces() -> Vec { + let interfaces = match if_addrs::get_if_addrs() { + Ok(interfaces) => interfaces, + Err(error) => { + log::error!("Failed to detect network interfaces: {error}"); + + return Vec::new(); + } + }; + + interfaces + .into_iter() + .filter(|interface| !interface.is_loopback()) + .filter(|interface| interface.ip().is_ipv4()) + .map(|interface| { + let ip_address = interface.ip(); + + NetworkInterfaceAddress { + interface_name: interface.name, + ip_address, + } + }) + .collect() +} diff --git a/paddler_gui/src/home_data.rs b/paddler_gui/src/home_data.rs new file mode 100644 index 00000000..ed431f1b --- /dev/null +++ b/paddler_gui/src/home_data.rs @@ -0,0 +1,3 @@ +pub struct HomeData { + pub error: Option, +} diff --git a/paddler_gui/src/home_handler.rs b/paddler_gui/src/home_handler.rs new file mode 100644 index 00000000..6611b9d5 --- /dev/null +++ b/paddler_gui/src/home_handler.rs @@ -0,0 +1,21 @@ +use crate::home_data::HomeData; + +#[derive(Debug, Clone, Copy)] +pub enum Message { + StartCluster, + JoinCluster, +} + +pub enum Action { + StartCluster, + JoinCluster, +} + +impl HomeData { + pub const fn update(message: Message) -> Action { + match message { + Message::StartCluster => Action::StartCluster, + Message::JoinCluster => Action::JoinCluster, + } + } +} diff --git a/paddler_gui/src/join_cluster_config_data.rs b/paddler_gui/src/join_cluster_config_data.rs new file mode 100644 index 00000000..87516d83 --- /dev/null +++ b/paddler_gui/src/join_cluster_config_data.rs @@ -0,0 +1,8 @@ +#[derive(Default)] +pub struct JoinClusterConfigData { + pub agent_name: String, + pub cluster_address: String, + pub cluster_address_error: Option, + pub slots_count: String, + pub slots_error: Option, +} diff --git a/paddler_gui/src/join_cluster_config_handler.rs b/paddler_gui/src/join_cluster_config_handler.rs new file mode 100644 index 00000000..eb386d8e --- /dev/null +++ b/paddler_gui/src/join_cluster_config_handler.rs @@ -0,0 +1,110 @@ +use std::net::SocketAddr; + +use crate::join_cluster_config_data::JoinClusterConfigData; + +#[derive(Debug, Clone)] +pub enum Message { + SetAgentName(String), + SetClusterAddress(String), + SetSlotsCount(String), + Connect, + Cancel, +} + +pub enum Action { + None, + Cancel, + ConnectAgent { + agent_name: Option, + management_address: String, + slots: i32, + }, +} + +impl JoinClusterConfigData { + pub fn update(&mut self, message: Message) -> Action { + match message { + Message::SetAgentName(name) => { + self.agent_name = name; + + Action::None + } + Message::SetClusterAddress(address) => { + self.cluster_address = address; + self.cluster_address_error = None; + + Action::None + } + Message::SetSlotsCount(slots) => { + if slots.is_empty() || slots.chars().all(|character| character.is_ascii_digit()) { + self.slots_count = slots; + self.slots_error = None; + } + + Action::None + } + Message::Connect => self.validate_and_connect(), + Message::Cancel => Action::Cancel, + } + } + + fn validate_and_connect(&mut self) -> Action { + self.cluster_address_error = None; + self.slots_error = None; + + if self.cluster_address.is_empty() { + self.cluster_address_error = Some("Cluster address is required.".to_owned()); + } else if self.cluster_address.parse::().is_err() { + self.cluster_address_error = + Some("Invalid address, expected format: IP:port".to_owned()); + } + + let slots = if self.slots_count.is_empty() { + self.slots_error = Some("Number of slots is required.".to_owned()); + None + } else { + match self.slots_count.parse::() { + Ok(slots) if slots > 0 => Some(slots), + Ok(non_positive_slots) => { + log::debug!("User entered non-positive slot count: {non_positive_slots}"); + self.slots_error = Some( + "Invalid number of slots (the number should be greater than zero)." + .to_owned(), + ); + None + } + Err(error) => { + let message = match error.kind() { + std::num::IntErrorKind::PosOverflow => "Number of slots is too large.", + unexpected_kind => { + log::error!("Unexpected slots parse error: {unexpected_kind:?}"); + "Invalid number of slots." + } + }; + self.slots_error = Some(message.to_owned()); + None + } + } + }; + + if self.cluster_address_error.is_some() || self.slots_error.is_some() { + return Action::None; + } + + let Some(slots) = slots else { + return Action::None; + }; + + let agent_name = if self.agent_name.is_empty() { + None + } else { + Some(self.agent_name.clone()) + }; + + Action::ConnectAgent { + agent_name, + management_address: self.cluster_address.clone(), + slots, + } + } +} diff --git a/paddler_gui/src/main.rs b/paddler_gui/src/main.rs new file mode 100644 index 00000000..104e8bb0 --- /dev/null +++ b/paddler_gui/src/main.rs @@ -0,0 +1,39 @@ +mod agent_running_data; +mod agent_running_handler; +mod app; +mod current_screen; +mod detect_network_interfaces; +mod home_data; +mod home_handler; +mod join_cluster_config_data; +mod join_cluster_config_handler; +mod message; +mod model_preset; +mod network_interface_address; +mod running_cluster_data; +mod running_cluster_handler; +#[expect(unsafe_code, reason = "statum macros generate link_section statics")] +mod screen; +mod start_cluster_config_data; +mod start_cluster_config_handler; +mod ui; + +use app::App; +use iced::Size; +use iced::Theme; + +fn main() -> iced::Result { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + + iced::application(App::new, App::update, App::view) + .font(include_bytes!( + "../../resources/fonts/JetBrainsMono-Regular.ttf" + )) + .font(include_bytes!( + "../../resources/fonts/JetBrainsMono-Bold.ttf" + )) + .theme(Theme::Light) + .window_size(Size::new(800.0, 800.0)) + .subscription(App::subscription) + .run() +} diff --git a/paddler_gui/src/message.rs b/paddler_gui/src/message.rs new file mode 100644 index 00000000..8d47dc5f --- /dev/null +++ b/paddler_gui/src/message.rs @@ -0,0 +1,20 @@ +use crate::agent_running_handler; +use crate::home_handler; +use crate::join_cluster_config_handler; +use crate::running_cluster_handler; +use crate::start_cluster_config_handler; + +#[derive(Debug, Clone)] +pub enum Message { + Home(home_handler::Message), + StartClusterConfig(start_cluster_config_handler::Message), + JoinClusterConfig(join_cluster_config_handler::Message), + RunningCluster(running_cluster_handler::Message), + AgentRunning(agent_running_handler::Message), + ClusterStarted, + ClusterStopped, + ClusterFailed(String), + AgentStopped, + AgentFailed(String), + TabPressed { shift: bool }, +} diff --git a/paddler_gui/src/model_preset.rs b/paddler_gui/src/model_preset.rs new file mode 100644 index 00000000..923d7ef0 --- /dev/null +++ b/paddler_gui/src/model_preset.rs @@ -0,0 +1,68 @@ +use std::fmt; + +use paddler_types::agent_desired_model::AgentDesiredModel; +use paddler_types::balancer_desired_state::BalancerDesiredState; +use paddler_types::huggingface_model_reference::HuggingFaceModelReference; +use paddler_types::inference_parameters::InferenceParameters; + +#[derive(Clone, Debug, PartialEq)] +pub struct ModelPreset { + pub display_name: String, + pub model: HuggingFaceModelReference, + pub multimodal_projection: Option, + pub inference_parameters: InferenceParameters, +} + +impl ModelPreset { + pub fn available_presets() -> Vec { + vec![ + Self { + display_name: "Qwen 3 0.6B".to_owned(), + model: HuggingFaceModelReference { + repo_id: "unsloth/Qwen3-0.6B-GGUF".to_owned(), + filename: "Qwen3-0.6B-Q8_0.gguf".to_owned(), + revision: "main".to_owned(), + }, + multimodal_projection: None, + inference_parameters: InferenceParameters::default(), + }, + Self { + display_name: "Qwen 3.5 0.8B".to_owned(), + model: HuggingFaceModelReference { + repo_id: "unsloth/Qwen3.5-0.8B-GGUF".to_owned(), + filename: "Qwen3.5-0.8B-Q4_K_M.gguf".to_owned(), + revision: "main".to_owned(), + }, + multimodal_projection: Some(HuggingFaceModelReference { + repo_id: "unsloth/Qwen3.5-0.8B-GGUF".to_owned(), + filename: "mmproj-F16.gguf".to_owned(), + revision: "main".to_owned(), + }), + inference_parameters: InferenceParameters::default(), + }, + ] + } + + pub fn to_balancer_desired_state(&self) -> BalancerDesiredState { + let multimodal_projection = self + .multimodal_projection + .as_ref() + .map_or(AgentDesiredModel::None, |reference| { + AgentDesiredModel::HuggingFace(reference.clone()) + }); + + BalancerDesiredState { + chat_template_override: None, + inference_parameters: self.inference_parameters.clone(), + model: AgentDesiredModel::HuggingFace(self.model.clone()), + multimodal_projection, + use_chat_template_override: false, + } + } +} + +impl fmt::Display for ModelPreset { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "{}", self.display_name) + } +} diff --git a/paddler_gui/src/network_interface_address.rs b/paddler_gui/src/network_interface_address.rs new file mode 100644 index 00000000..f8eeae47 --- /dev/null +++ b/paddler_gui/src/network_interface_address.rs @@ -0,0 +1,7 @@ +use std::net::IpAddr; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NetworkInterfaceAddress { + pub interface_name: String, + pub ip_address: IpAddr, +} diff --git a/paddler_gui/src/running_cluster_data.rs b/paddler_gui/src/running_cluster_data.rs new file mode 100644 index 00000000..b70d90d9 --- /dev/null +++ b/paddler_gui/src/running_cluster_data.rs @@ -0,0 +1,7 @@ +use paddler_types::agent_controller_snapshot::AgentControllerSnapshot; + +pub struct RunningClusterData { + pub agent_snapshots: Vec, + pub cluster_address: String, + pub stopping: bool, +} diff --git a/paddler_gui/src/running_cluster_handler.rs b/paddler_gui/src/running_cluster_handler.rs new file mode 100644 index 00000000..32a29061 --- /dev/null +++ b/paddler_gui/src/running_cluster_handler.rs @@ -0,0 +1,34 @@ +use paddler_types::agent_controller_snapshot::AgentControllerSnapshot; + +use crate::running_cluster_data::RunningClusterData; + +#[derive(Debug, Clone)] +pub enum Message { + AgentSnapshotsUpdated(Vec), + Stop, + CopyToClipboard(String), +} + +pub enum Action { + None, + Stop, + CopyToClipboard(String), +} + +impl RunningClusterData { + pub fn update(&mut self, message: Message) -> Action { + match message { + Message::AgentSnapshotsUpdated(snapshots) => { + self.agent_snapshots = snapshots; + + Action::None + } + Message::Stop => { + self.stopping = true; + + Action::Stop + } + Message::CopyToClipboard(content) => Action::CopyToClipboard(content), + } + } +} diff --git a/paddler_gui/src/screen.rs b/paddler_gui/src/screen.rs new file mode 100644 index 00000000..d3ae1145 --- /dev/null +++ b/paddler_gui/src/screen.rs @@ -0,0 +1,127 @@ +use std::collections::BTreeSet; + +use paddler_types::agent_controller_snapshot::AgentControllerSnapshot; +use paddler_types::agent_state_application_status::AgentStateApplicationStatus; +use statum::machine; +use statum::state; +use statum::transition; + +use crate::agent_running_data::AgentRunningData; +use crate::detect_network_interfaces::detect_network_interfaces; +use crate::home_data::HomeData; +use crate::join_cluster_config_data::JoinClusterConfigData; +use crate::running_cluster_data::RunningClusterData; +use crate::start_cluster_config_data::StartClusterConfigData; + +#[state] +pub enum ScreenState { + AgentRunning(AgentRunningData), + Home(HomeData), + JoinClusterConfig(JoinClusterConfigData), + StartClusterConfig(StartClusterConfigData), + RunningCluster(RunningClusterData), +} + +#[machine] +pub struct Screen {} + +#[transition] +impl Screen { + pub fn join_cluster(self) -> Screen { + self.transition_with(JoinClusterConfigData::default()) + } + + pub fn start_cluster(self) -> Screen { + let suggested_address = detect_network_interfaces() + .first() + .map(|interface| interface.ip_address.to_string()) + .unwrap_or_default(); + + self.transition_with(StartClusterConfigData { + cluster_address: format!("{suggested_address}:8060"), + cluster_address_error: None, + inference_address: format!("{suggested_address}:8061"), + inference_address_error: None, + model_error: None, + selected_model: None, + starting: false, + }) + } +} + +#[transition] +impl Screen { + pub fn cancel(self) -> Screen { + self.transition_with(HomeData { error: None }) + } + + pub fn connect(self) -> Screen { + self.transition_map(|config_data: JoinClusterConfigData| { + let name = if config_data.agent_name.is_empty() { + None + } else { + Some(config_data.agent_name) + }; + + AgentRunningData { + cluster_address: config_data.cluster_address, + connected: false, + snapshot: AgentControllerSnapshot { + desired_slots_total: 0, + download_current: 0, + download_filename: None, + download_total: 0, + id: String::new(), + issues: BTreeSet::new(), + model_path: None, + name, + slots_processing: 0, + slots_total: 0, + state_application_status: AgentStateApplicationStatus::Fresh, + uses_chat_template_override: false, + }, + } + }) + } +} + +#[transition] +impl Screen { + pub fn disconnect(self) -> Screen { + self.transition_with(HomeData { error: None }) + } + + pub fn agent_failed(self, error: String) -> Screen { + self.transition_with(HomeData { error: Some(error) }) + } +} + +#[transition] +impl Screen { + pub fn cancel(self) -> Screen { + self.transition_with(HomeData { error: None }) + } + + pub fn cluster_started(self) -> Screen { + self.transition_map(|config_data: StartClusterConfigData| RunningClusterData { + agent_snapshots: vec![], + cluster_address: config_data.cluster_address, + stopping: false, + }) + } + + pub fn cluster_failed(self, error: String) -> Screen { + self.transition_with(HomeData { error: Some(error) }) + } +} + +#[transition] +impl Screen { + pub fn cluster_stopped(self) -> Screen { + self.transition_with(HomeData { error: None }) + } + + pub fn cluster_failed(self, error: String) -> Screen { + self.transition_with(HomeData { error: Some(error) }) + } +} diff --git a/paddler_gui/src/start_cluster_config_data.rs b/paddler_gui/src/start_cluster_config_data.rs new file mode 100644 index 00000000..8a302add --- /dev/null +++ b/paddler_gui/src/start_cluster_config_data.rs @@ -0,0 +1,11 @@ +use crate::model_preset::ModelPreset; + +pub struct StartClusterConfigData { + pub cluster_address: String, + pub cluster_address_error: Option, + pub inference_address: String, + pub inference_address_error: Option, + pub model_error: Option, + pub selected_model: Option, + pub starting: bool, +} diff --git a/paddler_gui/src/start_cluster_config_handler.rs b/paddler_gui/src/start_cluster_config_handler.rs new file mode 100644 index 00000000..8013a0a8 --- /dev/null +++ b/paddler_gui/src/start_cluster_config_handler.rs @@ -0,0 +1,137 @@ +use std::net::SocketAddr; +use std::net::TcpListener; + +use paddler_types::balancer_desired_state::BalancerDesiredState; + +use crate::model_preset::ModelPreset; +use crate::start_cluster_config_data::StartClusterConfigData; + +fn is_port_in_use(address: &SocketAddr) -> bool { + TcpListener::bind(address).is_err() +} + +#[derive(Debug, Clone)] +pub enum Message { + SetClusterAddress(String), + SetInferenceAddress(String), + SelectModel(ModelPreset), + Confirm, + Cancel, +} + +#[expect( + clippy::large_enum_variant, + reason = "ephemeral value, immediately consumed" +)] +pub enum Action { + None, + Cancel, + StartCluster { + management_addr: SocketAddr, + inference_addr: SocketAddr, + desired_state: BalancerDesiredState, + }, +} + +impl StartClusterConfigData { + pub fn update(&mut self, message: Message) -> Action { + match message { + Message::SelectModel(preset) => { + self.selected_model = Some(preset); + self.model_error = None; + + Action::None + } + Message::SetClusterAddress(address) => { + self.cluster_address = address; + self.cluster_address_error = None; + + Action::None + } + Message::SetInferenceAddress(address) => { + self.inference_address = address; + self.inference_address_error = None; + + Action::None + } + Message::Confirm => self.validate_and_confirm(), + Message::Cancel => Action::Cancel, + } + } + + fn validate_and_confirm(&mut self) -> Action { + self.cluster_address_error = None; + self.inference_address_error = None; + self.model_error = None; + + if self.selected_model.is_none() { + self.model_error = Some("Please select a model.".to_owned()); + } + + let management_addr = if self.cluster_address.is_empty() { + self.cluster_address_error = Some("Cluster address is required.".to_owned()); + None + } else if let Ok(addr) = self.cluster_address.parse::() { + Some(addr) + } else { + self.cluster_address_error = + Some("Invalid address, expected format: IP:port".to_owned()); + None + }; + + let inference_addr = if self.inference_address.is_empty() { + self.inference_address_error = Some("Inference address is required.".to_owned()); + None + } else if let Ok(addr) = self.inference_address.parse::() { + Some(addr) + } else { + self.inference_address_error = + Some("Invalid address, expected format: IP:port".to_owned()); + None + }; + + let management_addr = match management_addr { + Some(addr) if is_port_in_use(&addr) => { + self.cluster_address_error = + Some(format!("Port {} is already in use", addr.port())); + None + } + other => other, + }; + + let inference_addr = match inference_addr { + Some(addr) if is_port_in_use(&addr) => { + self.inference_address_error = + Some(format!("Port {} is already in use", addr.port())); + None + } + other => other, + }; + + if self.model_error.is_some() + || self.cluster_address_error.is_some() + || self.inference_address_error.is_some() + { + return Action::None; + } + + let (Some(management_addr), Some(inference_addr)) = (management_addr, inference_addr) + else { + return Action::None; + }; + + let desired_state = self + .selected_model + .as_ref() + .map(ModelPreset::to_balancer_desired_state) + .unwrap_or_default(); + + self.starting = true; + + Action::StartCluster { + management_addr, + inference_addr, + desired_state, + } + } +} diff --git a/paddler_gui/src/ui/font.rs b/paddler_gui/src/ui/font.rs new file mode 100644 index 00000000..322584f9 --- /dev/null +++ b/paddler_gui/src/ui/font.rs @@ -0,0 +1,17 @@ +use iced::Font; +use iced::font::Family; +use iced::font::Weight; + +pub const REGULAR: Font = Font { + family: Family::Name("JetBrains Mono"), + weight: Weight::Normal, + stretch: iced::font::Stretch::Normal, + style: iced::font::Style::Normal, +}; + +pub const BOLD: Font = Font { + family: Family::Name("JetBrains Mono"), + weight: Weight::Bold, + stretch: iced::font::Stretch::Normal, + style: iced::font::Style::Normal, +}; diff --git a/paddler_gui/src/ui/mod.rs b/paddler_gui/src/ui/mod.rs new file mode 100644 index 00000000..cab22249 --- /dev/null +++ b/paddler_gui/src/ui/mod.rs @@ -0,0 +1,20 @@ +pub mod variables; +pub mod view_agent_running; +pub mod view_home; +pub mod view_join_cluster_config; +pub mod view_running_cluster; +pub mod view_start_cluster_config; + +mod font; +mod style_agent_container; +mod style_button_disconnect; +mod style_button_primary; +mod style_card_container; +mod style_download_progress_bar; +mod style_field_container; +mod style_field_pick_list; +mod style_field_pick_list_menu; +mod style_field_text_input; +mod style_status_indicator; +mod view_agent_card; +mod view_form_field; diff --git a/paddler_gui/src/ui/style_agent_container.rs b/paddler_gui/src/ui/style_agent_container.rs new file mode 100644 index 00000000..72609cc4 --- /dev/null +++ b/paddler_gui/src/ui/style_agent_container.rs @@ -0,0 +1,21 @@ +use iced::Background; +use iced::Border; +use iced::Theme; +use iced::widget::container; + +use super::variables::COLOR_AGENT_BACKGROUND; +use super::variables::COLOR_BORDER; + +pub fn style_agent_container(theme: &Theme) -> container::Style { + let base = container::transparent(theme); + + container::Style { + background: Some(Background::Color(COLOR_AGENT_BACKGROUND)), + border: Border { + color: COLOR_BORDER, + width: 2.0, + radius: 0.into(), + }, + ..base + } +} diff --git a/paddler_gui/src/ui/style_button_disconnect.rs b/paddler_gui/src/ui/style_button_disconnect.rs new file mode 100644 index 00000000..7973c5ca --- /dev/null +++ b/paddler_gui/src/ui/style_button_disconnect.rs @@ -0,0 +1,22 @@ +use iced::Background; +use iced::Border; +use iced::Color; +use iced::Theme; +use iced::widget::button; + +use super::variables::COLOR_ERROR; + +pub fn style_button_disconnect(theme: &Theme, status: button::Status) -> button::Style { + let base = button::primary(theme, status); + + button::Style { + background: Some(Background::Color(COLOR_ERROR)), + text_color: Color::WHITE, + border: Border { + color: COLOR_ERROR, + width: 0.0, + radius: 0.into(), + }, + ..base + } +} diff --git a/paddler_gui/src/ui/style_button_primary.rs b/paddler_gui/src/ui/style_button_primary.rs new file mode 100644 index 00000000..9ed1bcad --- /dev/null +++ b/paddler_gui/src/ui/style_button_primary.rs @@ -0,0 +1,22 @@ +use iced::Background; +use iced::Border; +use iced::Theme; +use iced::widget::button; + +use super::variables::COLOR_BODY_BACKGROUND; +use super::variables::COLOR_BORDER; + +pub fn style_button_primary(theme: &Theme, status: button::Status) -> button::Style { + let base = button::primary(theme, status); + + button::Style { + background: Some(Background::Color(COLOR_BORDER)), + text_color: COLOR_BODY_BACKGROUND, + border: Border { + color: COLOR_BORDER, + width: 0.0, + radius: 0.into(), + }, + ..base + } +} diff --git a/paddler_gui/src/ui/style_card_container.rs b/paddler_gui/src/ui/style_card_container.rs new file mode 100644 index 00000000..66a5a254 --- /dev/null +++ b/paddler_gui/src/ui/style_card_container.rs @@ -0,0 +1,21 @@ +use iced::Background; +use iced::Border; +use iced::Theme; +use iced::widget::container; + +use super::variables::COLOR_BODY_BACKGROUND; +use super::variables::COLOR_BORDER; + +pub fn style_card_container(theme: &Theme) -> container::Style { + let base = container::transparent(theme); + + container::Style { + background: Some(Background::Color(COLOR_BODY_BACKGROUND)), + border: Border { + color: COLOR_BORDER, + width: 2.0, + radius: 0.into(), + }, + ..base + } +} diff --git a/paddler_gui/src/ui/style_download_progress_bar.rs b/paddler_gui/src/ui/style_download_progress_bar.rs new file mode 100644 index 00000000..915291d7 --- /dev/null +++ b/paddler_gui/src/ui/style_download_progress_bar.rs @@ -0,0 +1,19 @@ +use iced::Background; +use iced::Border; +use iced::Theme; +use iced::widget::progress_bar; + +use super::variables::COLOR_BODY_BACKGROUND; +use super::variables::COLOR_BORDER; + +pub fn style_download_progress_bar(_theme: &Theme) -> progress_bar::Style { + progress_bar::Style { + background: Background::Color(COLOR_BODY_BACKGROUND), + bar: Background::Color(COLOR_BORDER), + border: Border { + color: COLOR_BORDER, + width: 2.0, + radius: 0.into(), + }, + } +} diff --git a/paddler_gui/src/ui/style_field_container.rs b/paddler_gui/src/ui/style_field_container.rs new file mode 100644 index 00000000..df18be3d --- /dev/null +++ b/paddler_gui/src/ui/style_field_container.rs @@ -0,0 +1,19 @@ +use iced::Shadow; +use iced::Theme; +use iced::Vector; +use iced::widget::container; + +use super::variables::COLOR_BORDER; + +pub fn style_field_container(theme: &Theme) -> container::Style { + let base = container::transparent(theme); + + container::Style { + shadow: Shadow { + color: COLOR_BORDER, + offset: Vector::new(4.0, 4.0), + blur_radius: 0.0, + }, + ..base + } +} diff --git a/paddler_gui/src/ui/style_field_pick_list.rs b/paddler_gui/src/ui/style_field_pick_list.rs new file mode 100644 index 00000000..b9fe77b8 --- /dev/null +++ b/paddler_gui/src/ui/style_field_pick_list.rs @@ -0,0 +1,24 @@ +use iced::Background; +use iced::Border; +use iced::Theme; +use iced::widget::pick_list; + +use super::variables::COLOR_BODY_BACKGROUND; +use super::variables::COLOR_BODY_FONT; +use super::variables::COLOR_BORDER; + +pub fn style_field_pick_list(theme: &Theme, status: pick_list::Status) -> pick_list::Style { + let base = pick_list::default(theme, status); + + pick_list::Style { + text_color: COLOR_BODY_FONT, + placeholder_color: base.placeholder_color, + handle_color: COLOR_BODY_FONT, + background: Background::Color(COLOR_BODY_BACKGROUND), + border: Border { + color: COLOR_BORDER, + width: 2.0, + radius: 0.into(), + }, + } +} diff --git a/paddler_gui/src/ui/style_field_pick_list_menu.rs b/paddler_gui/src/ui/style_field_pick_list_menu.rs new file mode 100644 index 00000000..bce4cb4a --- /dev/null +++ b/paddler_gui/src/ui/style_field_pick_list_menu.rs @@ -0,0 +1,26 @@ +use iced::Background; +use iced::Border; +use iced::Color; +use iced::Theme; +use iced::overlay::menu; + +use super::variables::COLOR_BODY_BACKGROUND; +use super::variables::COLOR_BODY_FONT; +use super::variables::COLOR_BORDER; + +pub fn style_field_pick_list_menu(theme: &Theme) -> menu::Style { + let base = menu::default(theme); + + menu::Style { + background: Background::Color(COLOR_BODY_BACKGROUND), + border: Border { + color: COLOR_BORDER, + width: 1.0, + radius: 0.into(), + }, + text_color: COLOR_BODY_FONT, + selected_text_color: COLOR_BODY_FONT, + selected_background: Background::Color(Color::from_rgb(0.9, 0.9, 0.9)), + ..base + } +} diff --git a/paddler_gui/src/ui/style_field_text_input.rs b/paddler_gui/src/ui/style_field_text_input.rs new file mode 100644 index 00000000..ff0e4613 --- /dev/null +++ b/paddler_gui/src/ui/style_field_text_input.rs @@ -0,0 +1,25 @@ +use iced::Background; +use iced::Border; +use iced::Theme; +use iced::widget::text_input; + +use super::variables::COLOR_BODY_BACKGROUND; +use super::variables::COLOR_BODY_FONT; +use super::variables::COLOR_BORDER; + +pub fn style_field_text_input(theme: &Theme, status: text_input::Status) -> text_input::Style { + let base = text_input::default(theme, status); + + text_input::Style { + background: Background::Color(COLOR_BODY_BACKGROUND), + border: Border { + color: COLOR_BORDER, + width: 2.0, + radius: 0.into(), + }, + icon: COLOR_BODY_FONT, + placeholder: base.placeholder, + value: COLOR_BODY_FONT, + ..base + } +} diff --git a/paddler_gui/src/ui/style_status_indicator.rs b/paddler_gui/src/ui/style_status_indicator.rs new file mode 100644 index 00000000..b80b49c7 --- /dev/null +++ b/paddler_gui/src/ui/style_status_indicator.rs @@ -0,0 +1,19 @@ +use iced::Background; +use iced::Border; +use iced::Color; +use iced::Theme; +use iced::widget::container; + +pub fn style_status_indicator(theme: &Theme) -> container::Style { + let base = container::transparent(theme); + + container::Style { + background: Some(Background::Color(Color::from_rgb8(0xEE, 0xFF, 0xEE))), + border: Border { + color: Color::from_rgb8(0xCC, 0xDD, 0xCC), + width: 2.0, + radius: 8.into(), + }, + ..base + } +} diff --git a/paddler_gui/src/ui/variables.rs b/paddler_gui/src/ui/variables.rs new file mode 100644 index 00000000..8585804b --- /dev/null +++ b/paddler_gui/src/ui/variables.rs @@ -0,0 +1,40 @@ +use iced::Color; + +// Font sizes + +const FONT_SIZE_BASE: f32 = 14.0; +const FONT_SIZE_L1: f32 = 1.5 * FONT_SIZE_BASE; +pub const FONT_SIZE_L2: f32 = 1.5 * FONT_SIZE_L1; + +// Sizing + +pub const FORM_WIDTH: f32 = 400.0; + +// Spacing + +pub const SPACING_BASE: f32 = 16.0; +pub const SPACING_2X: f32 = 2.0 * SPACING_BASE; +pub const SPACING_HALF: f32 = 0.5 * SPACING_BASE; + +// Colors + +pub const COLOR_BODY_BACKGROUND: Color = Color::WHITE; +pub const COLOR_BODY_FONT: Color = Color { + r: 0.067, + g: 0.067, + b: 0.067, + a: 1.0, +}; +pub const COLOR_AGENT_BACKGROUND: Color = Color { + r: 250.0 / 255.0, + g: 240.0 / 255.0, + b: 230.0 / 255.0, + a: 1.0, +}; +pub const COLOR_BORDER: Color = Color::BLACK; +pub const COLOR_ERROR: Color = Color { + r: 204.0 / 255.0, + g: 51.0 / 255.0, + b: 51.0 / 255.0, + a: 1.0, +}; diff --git a/paddler_gui/src/ui/view_agent_card.rs b/paddler_gui/src/ui/view_agent_card.rs new file mode 100644 index 00000000..c680f0ed --- /dev/null +++ b/paddler_gui/src/ui/view_agent_card.rs @@ -0,0 +1,111 @@ +use iced::Element; +use iced::Fill; +use iced::widget::column; +use iced::widget::container; +use iced::widget::progress_bar; +use iced::widget::row; +use iced::widget::text; +use paddler_types::agent_controller_snapshot::AgentControllerSnapshot; +use paddler_types::agent_state_application_status::AgentStateApplicationStatus; + +use super::font::BOLD; +use super::font::REGULAR; +use super::style_agent_container::style_agent_container; +use super::style_download_progress_bar::style_download_progress_bar; +use super::variables::SPACING_BASE; +use super::variables::SPACING_HALF; +fn display_last_path_part(path: &str) -> String { + std::path::Path::new(path) + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or(path) + .to_owned() +} + +pub fn view_agent_card( + snapshot: &AgentControllerSnapshot, +) -> Element<'_, TMessage> { + let is_downloading = + snapshot.download_total > 0 && snapshot.download_current < snapshot.download_total; + + let mut name_row = row![]; + + match &snapshot.name { + Some(agent_name) => { + name_row = name_row.push(container(text(agent_name.clone()).font(BOLD)).width(Fill)); + } + None => { + name_row = name_row.push(container("").width(Fill)); + } + } + + if is_downloading { + name_row = name_row.push( + #[expect( + clippy::cast_precision_loss, + reason = "download sizes fit in f32 mantissa" + )] + progress_bar( + 0.0..=snapshot.download_total as f32, + snapshot.download_current as f32, + ) + .girth(12) + .style(style_download_progress_bar), + ); + } else { + let model_label = snapshot.model_path.as_ref().map_or_else( + || "No model loaded".to_owned(), + |path| display_last_path_part(path), + ); + + name_row = name_row.push(text(model_label).font(REGULAR)); + } + + let status_label = if is_downloading { + #[expect( + clippy::cast_precision_loss, + reason = "download sizes fit in f32 mantissa" + )] + let percentage = + (snapshot.download_current as f32 / snapshot.download_total as f32) * 100.0; + + format!("Downloading ({percentage:.0}%)") + } else if snapshot.model_path.is_none() { + "Waiting for model...".to_owned() + } else { + match &snapshot.state_application_status { + AgentStateApplicationStatus::Applied => "OK".to_owned(), + AgentStateApplicationStatus::Fresh => "Pending".to_owned(), + AgentStateApplicationStatus::AttemptedAndRetrying => "Retrying".to_owned(), + AgentStateApplicationStatus::Stuck => "Retrying, but seems stuck?".to_owned(), + AgentStateApplicationStatus::AttemptedAndNotAppliable => "Needs your help".to_owned(), + } + }; + + let mut status_row_left = column![].spacing(SPACING_HALF); + + status_row_left = status_row_left.push(text(format!("Status: {status_label}")).font(REGULAR)); + + if !snapshot.issues.is_empty() { + status_row_left = + status_row_left.push(text(format!("{} issues", snapshot.issues.len())).font(REGULAR)); + } + + let slots_label = format!( + "{}/{}/{}", + snapshot.slots_processing, snapshot.slots_total, snapshot.desired_slots_total, + ); + + let status_row_content = row![ + container(status_row_left).width(Fill), + text(format!("Slots: {slots_label}")).font(REGULAR), + ]; + + let card_content = column![name_row, status_row_content,].spacing(SPACING_BASE); + + container(card_content) + .width(Fill) + .padding(SPACING_BASE) + .style(style_agent_container) + .into() +} diff --git a/paddler_gui/src/ui/view_agent_running.rs b/paddler_gui/src/ui/view_agent_running.rs new file mode 100644 index 00000000..0f918ac7 --- /dev/null +++ b/paddler_gui/src/ui/view_agent_running.rs @@ -0,0 +1,62 @@ +use iced::Center; +use iced::Element; +use iced::Fill; +use iced::widget::button; +use iced::widget::column; +use iced::widget::container; +use iced::widget::row; +use iced::widget::svg; +use iced::widget::svg::Handle as SvgHandle; +use iced::widget::text; + +use super::font::BOLD; +use super::font::REGULAR; +use super::style_button_disconnect::style_button_disconnect; +use super::variables::FONT_SIZE_L2; +use super::variables::SPACING_2X; +use super::variables::SPACING_BASE; +use super::variables::SPACING_HALF; +use super::view_agent_card::view_agent_card; +use crate::agent_running_data::AgentRunningData; +use crate::agent_running_handler::Message; + +pub fn view_agent_running(data: &AgentRunningData) -> Element<'_, Message> { + let stop_icon = svg(SvgHandle::from_memory( + include_bytes!("../../../resources/icons/stop.svg").as_slice(), + )) + .width(16) + .height(16); + + let disconnect_button = button( + row![stop_icon, text("Disconnect").font(BOLD)] + .spacing(SPACING_HALF) + .align_y(Center), + ) + .padding([SPACING_HALF, SPACING_BASE]) + .style(style_button_disconnect) + .on_press(Message::Disconnect); + + let connection_status = if data.connected { + text(format!( + "Connected to the cluster at {}", + data.cluster_address + )) + .font(REGULAR) + } else { + text("Connecting to the cluster...").font(REGULAR) + }; + + let status_row = container( + row![container(connection_status).width(Fill), disconnect_button,].align_y(Center), + ) + .padding([0.0, SPACING_BASE]); + + column![ + container(text("Your agent").size(FONT_SIZE_L2).font(BOLD)).padding([0.0, SPACING_BASE]), + container(text("Agent details").font(BOLD)).padding([0.0, SPACING_BASE]), + view_agent_card(&data.snapshot), + status_row, + ] + .spacing(SPACING_2X) + .into() +} diff --git a/paddler_gui/src/ui/view_form_field.rs b/paddler_gui/src/ui/view_form_field.rs new file mode 100644 index 00000000..b85934d5 --- /dev/null +++ b/paddler_gui/src/ui/view_form_field.rs @@ -0,0 +1,32 @@ +use iced::Element; +use iced::widget::column; +use iced::widget::container; +use iced::widget::text; + +use super::font::BOLD; +use super::font::REGULAR; +use super::style_field_container::style_field_container; +use super::variables::COLOR_ERROR; +use super::variables::SPACING_BASE; +use super::variables::SPACING_HALF; + +pub fn view_form_field<'element, TMessage: 'static>( + label: &str, + input: Element<'element, TMessage>, + error: Option<&String>, +) -> Element<'element, TMessage> { + let mut field = column![ + container(text(label.to_owned()).font(BOLD)).padding([0.0, SPACING_BASE]), + container(input).style(style_field_container), + ] + .spacing(SPACING_HALF); + + if let Some(error) = error { + field = field.push( + container(text(error.clone()).font(REGULAR).color(COLOR_ERROR)) + .padding([0.0, SPACING_BASE]), + ); + } + + field.into() +} diff --git a/paddler_gui/src/ui/view_home.rs b/paddler_gui/src/ui/view_home.rs new file mode 100644 index 00000000..a760b6f4 --- /dev/null +++ b/paddler_gui/src/ui/view_home.rs @@ -0,0 +1,75 @@ +use std::sync::LazyLock; + +use iced::Center; +use iced::Element; +use iced::widget::button; +use iced::widget::column; +use iced::widget::container; +use iced::widget::image; +use iced::widget::image::Handle as ImageHandle; +use iced::widget::row; +use iced::widget::text; + +use super::font::BOLD; +use super::font::REGULAR; +use super::style_button_primary::style_button_primary; +use super::variables::COLOR_ERROR; +use super::variables::FONT_SIZE_L2; +use super::variables::SPACING_2X; +use super::variables::SPACING_BASE; +use super::variables::SPACING_HALF; +use crate::home_data::HomeData; +use crate::home_handler::Message; + +static CREATE_CLUSTER_IMAGE: LazyLock = LazyLock::new(|| { + ImageHandle::from_bytes( + include_bytes!("../../../resources/images/create_a_cluster.png").as_slice(), + ) +}); + +static JOIN_CLUSTER_IMAGE: LazyLock = LazyLock::new(|| { + ImageHandle::from_bytes( + include_bytes!("../../../resources/images/join_a_cluster.png").as_slice(), + ) +}); + +pub fn view_home(data: &HomeData) -> Element<'_, Message> { + let create_image = image(CREATE_CLUSTER_IMAGE.clone()).width(200).height(200); + + let join_image = image(JOIN_CLUSTER_IMAGE.clone()).width(200).height(200); + + let start_button = button(text("Start a cluster").font(BOLD)) + .padding([SPACING_HALF, SPACING_BASE]) + .style(style_button_primary) + .on_press(Message::StartCluster); + + let join_button = button(text("Join a cluster").font(BOLD)) + .padding([SPACING_HALF, SPACING_BASE]) + .style(style_button_primary) + .on_press(Message::JoinCluster); + + let start_column = column![create_image, start_button] + .spacing(SPACING_BASE) + .align_x(Center); + + let join_column = column![join_image, join_button] + .spacing(SPACING_BASE) + .align_x(Center); + + let options_row = row![start_column, join_column].spacing(SPACING_2X); + + let mut content = column![ + container(text("Paddler App").size(FONT_SIZE_L2).font(BOLD)).padding([0.0, SPACING_BASE]), + container(options_row).align_x(Center), + ] + .spacing(SPACING_2X); + + if let Some(error) = &data.error { + content = content.push( + container(text(error.clone()).font(REGULAR).color(COLOR_ERROR)) + .padding([0.0, SPACING_BASE]), + ); + } + + content.into() +} diff --git a/paddler_gui/src/ui/view_join_cluster_config.rs b/paddler_gui/src/ui/view_join_cluster_config.rs new file mode 100644 index 00000000..7246bd52 --- /dev/null +++ b/paddler_gui/src/ui/view_join_cluster_config.rs @@ -0,0 +1,76 @@ +use iced::Center; +use iced::Element; +use iced::alignment::Horizontal; +use iced::widget::button; +use iced::widget::column; +use iced::widget::container; +use iced::widget::row; +use iced::widget::text; +use iced::widget::text_input; + +use super::font::BOLD; +use super::style_button_primary::style_button_primary; +use super::style_field_text_input::style_field_text_input; +use super::variables::FONT_SIZE_L2; +use super::variables::FORM_WIDTH; +use super::variables::SPACING_2X; +use super::variables::SPACING_BASE; +use super::variables::SPACING_HALF; +use super::view_form_field::view_form_field; +use crate::join_cluster_config_data::JoinClusterConfigData; +use crate::join_cluster_config_handler::Message; + +pub fn view_join_cluster_config(data: &JoinClusterConfigData) -> Element<'_, Message> { + let confirm_button = button(text("Connect").font(BOLD)) + .padding([SPACING_HALF, SPACING_BASE]) + .style(style_button_primary) + .on_press(Message::Connect); + + let cancel_button = button(text("Cancel").font(BOLD)) + .style(button::text) + .on_press(Message::Cancel); + + let cluster_address_input = text_input("IP:port", &data.cluster_address) + .on_input(Message::SetClusterAddress) + .padding(SPACING_BASE) + .style(style_field_text_input) + .into(); + + let agent_name_input = text_input("my-agent", &data.agent_name) + .on_input(Message::SetAgentName) + .padding(SPACING_BASE) + .style(style_field_text_input) + .into(); + + let slots_input = text_input("e.g. 1", &data.slots_count) + .on_input(Message::SetSlotsCount) + .padding(SPACING_BASE) + .style(style_field_text_input) + .into(); + + column![ + container(text("Join a cluster").size(FONT_SIZE_L2).font(BOLD)) + .padding([0.0, SPACING_BASE]), + container( + column![ + view_form_field( + "Cluster address", + cluster_address_input, + data.cluster_address_error.as_ref() + ), + view_form_field("Agent name (optional)", agent_name_input, None), + view_form_field("Slots", slots_input, data.slots_error.as_ref()), + container( + row![cancel_button, confirm_button] + .align_y(Center) + .spacing(SPACING_BASE), + ) + .align_x(Horizontal::Right), + ] + .spacing(SPACING_2X), + ) + .width(FORM_WIDTH), + ] + .spacing(SPACING_2X) + .into() +} diff --git a/paddler_gui/src/ui/view_running_cluster.rs b/paddler_gui/src/ui/view_running_cluster.rs new file mode 100644 index 00000000..51b6359e --- /dev/null +++ b/paddler_gui/src/ui/view_running_cluster.rs @@ -0,0 +1,113 @@ +use iced::Center; +use iced::Element; +use iced::Fill; +use iced::widget::button; +use iced::widget::column; +use iced::widget::container; +use iced::widget::row; +use iced::widget::svg; +use iced::widget::svg::Handle as SvgHandle; +use iced::widget::text; + +use super::font::BOLD; +use super::font::REGULAR; +use super::style_button_disconnect::style_button_disconnect; +use super::style_card_container::style_card_container; +use super::style_status_indicator::style_status_indicator; +use super::variables::FONT_SIZE_L2; +use super::variables::SPACING_2X; +use super::variables::SPACING_BASE; +use super::variables::SPACING_HALF; +use super::view_agent_card::view_agent_card; +use crate::running_cluster_data::RunningClusterData; +use crate::running_cluster_handler::Message; + +pub fn view_running_cluster(data: &RunningClusterData) -> Element<'_, Message> { + let copy_icon = svg(SvgHandle::from_memory( + include_bytes!("../../../resources/icons/copy.svg").as_slice(), + )) + .width(16) + .height(16); + + let address_row = container( + row![ + container(text(format!("Cluster address: {}", data.cluster_address)).font(REGULAR)) + .width(Fill), + button( + row![copy_icon, text("Copy address").font(BOLD)] + .spacing(SPACING_HALF) + .align_y(Center), + ) + .style(button::text) + .on_press(Message::CopyToClipboard(data.cluster_address.clone())), + ] + .align_y(Center) + .padding(SPACING_BASE), + ) + .style(style_card_container); + + let stop_icon = svg(SvgHandle::from_memory( + include_bytes!("../../../resources/icons/stop.svg").as_slice(), + )) + .width(16) + .height(16); + + let status_indicator = container("") + .width(16) + .height(16) + .style(style_status_indicator); + + let stop_button = if data.stopping { + button( + row![stop_icon, text("Stopping...").font(BOLD)] + .spacing(SPACING_HALF) + .align_y(Center), + ) + .padding([SPACING_HALF, SPACING_BASE]) + .style(style_button_disconnect) + } else { + button( + row![stop_icon, text("Stop cluster").font(BOLD)] + .spacing(SPACING_HALF) + .align_y(Center), + ) + .padding([SPACING_HALF, SPACING_BASE]) + .style(style_button_disconnect) + .on_press(Message::Stop) + }; + + let status_row = container( + row![ + container( + row![text("Cluster is running").font(REGULAR), status_indicator,] + .spacing(SPACING_HALF) + .align_y(Center), + ) + .width(Fill), + stop_button, + ] + .align_y(Center), + ) + .padding([SPACING_HALF, SPACING_BASE]); + + let mut content = column![ + container(text("Your cluster").size(FONT_SIZE_L2).font(BOLD)).padding([0.0, SPACING_BASE]), + address_row, + status_row, + container(text("Connected agents").font(BOLD)).padding([0.0, SPACING_BASE]), + ] + .spacing(SPACING_2X); + + if data.agent_snapshots.is_empty() { + content = content.push( + container(text("Waiting for agents to connect...").font(REGULAR)) + .padding([0.0, SPACING_BASE]), + ); + } else { + for agent_snapshot in &data.agent_snapshots { + content = content.push(view_agent_card(agent_snapshot)); + } + } + + content.into() +} diff --git a/paddler_gui/src/ui/view_start_cluster_config.rs b/paddler_gui/src/ui/view_start_cluster_config.rs new file mode 100644 index 00000000..ced10e30 --- /dev/null +++ b/paddler_gui/src/ui/view_start_cluster_config.rs @@ -0,0 +1,98 @@ +use iced::Center; +use iced::Element; +use iced::Fill; +use iced::alignment::Horizontal; +use iced::widget::button; +use iced::widget::column; +use iced::widget::container; +use iced::widget::pick_list; +use iced::widget::row; +use iced::widget::text; +use iced::widget::text_input; + +use super::font::BOLD; +use super::style_button_primary::style_button_primary; +use super::style_field_pick_list::style_field_pick_list; +use super::style_field_pick_list_menu::style_field_pick_list_menu; +use super::style_field_text_input::style_field_text_input; +use super::variables::FONT_SIZE_L2; +use super::variables::FORM_WIDTH; +use super::variables::SPACING_2X; +use super::variables::SPACING_BASE; +use super::variables::SPACING_HALF; +use super::view_form_field::view_form_field; +use crate::model_preset::ModelPreset; +use crate::start_cluster_config_data::StartClusterConfigData; +use crate::start_cluster_config_handler::Message; + +pub fn view_start_cluster_config(data: &StartClusterConfigData) -> Element<'_, Message> { + let available_models = ModelPreset::available_presets(); + + let confirm_button = if data.starting { + button(text("Starting...").font(BOLD)) + .padding([SPACING_HALF, SPACING_BASE]) + .style(style_button_primary) + } else { + button(text("Start a cluster").font(BOLD)) + .padding([SPACING_HALF, SPACING_BASE]) + .style(style_button_primary) + .on_press(Message::Confirm) + }; + + let cancel_button = button(text("Cancel").font(BOLD)) + .style(button::text) + .on_press(Message::Cancel); + + let cluster_address_input = text_input("IP:port", &data.cluster_address) + .on_input(Message::SetClusterAddress) + .padding(SPACING_BASE) + .style(style_field_text_input) + .into(); + + let inference_address_input = text_input("IP:port", &data.inference_address) + .on_input(Message::SetInferenceAddress) + .padding(SPACING_BASE) + .style(style_field_text_input) + .into(); + + let model_input = pick_list( + available_models, + data.selected_model.as_ref(), + Message::SelectModel, + ) + .width(Fill) + .padding(SPACING_BASE) + .style(style_field_pick_list) + .menu_style(style_field_pick_list_menu) + .into(); + + column![ + container(text("Start a cluster").size(FONT_SIZE_L2).font(BOLD)) + .padding([0.0, SPACING_BASE]), + container( + column![ + view_form_field( + "Cluster address", + cluster_address_input, + data.cluster_address_error.as_ref() + ), + view_form_field( + "Inference address", + inference_address_input, + data.inference_address_error.as_ref() + ), + view_form_field("Select a model", model_input, data.model_error.as_ref()), + container( + row![cancel_button, confirm_button] + .align_y(Center) + .spacing(SPACING_BASE), + ) + .align_x(Horizontal::Right), + ] + .spacing(SPACING_2X), + ) + .width(FORM_WIDTH), + ] + .spacing(SPACING_2X) + .into() +} diff --git a/paddler_integration_tests/src/lib.rs b/paddler_integration_tests/src/lib.rs index 6a3c65f2..05408155 100644 --- a/paddler_integration_tests/src/lib.rs +++ b/paddler_integration_tests/src/lib.rs @@ -23,7 +23,8 @@ pub const WAIT_FOR_STATE_CHANGE_TIMEOUT: Duration = Duration::from_secs(30); pub const WAIT_FOR_STATE_CHANGE_POLL_INTERVAL: Duration = Duration::from_millis(10); static PADDLER_BINARY_PATH: LazyLock = LazyLock::new(|| { - std::env::var("PADDLER_BINARY_PATH").unwrap_or_else(|_| "../target/debug/paddler".to_owned()) + std::env::var("PADDLER_BINARY_PATH") + .unwrap_or_else(|_| "../target/debug/paddler_cli".to_owned()) }); pub fn paddler_command() -> Command { diff --git a/paddler_types/src/agent_controller_snapshot.rs b/paddler_types/src/agent_controller_snapshot.rs index e5818dc2..d28b0fe0 100644 --- a/paddler_types/src/agent_controller_snapshot.rs +++ b/paddler_types/src/agent_controller_snapshot.rs @@ -6,7 +6,7 @@ use serde::Serialize; use crate::agent_issue::AgentIssue; use crate::agent_state_application_status::AgentStateApplicationStatus; -#[derive(Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct AgentControllerSnapshot { pub desired_slots_total: i32, diff --git a/resources/css/_fonts.css b/resources/css/_fonts.css index e6e42047..b11ef5f0 100644 --- a/resources/css/_fonts.css +++ b/resources/css/_fonts.css @@ -1,7 +1,17 @@ @font-face { font-display: swap; font-family: "JetBrains Mono"; - src: url(https://fonts.gstatic.com/s/jetbrainsmono/v23/tDbX2o-flEEny0FZhsfKu5WU4xD-Cw6nSHrV.woff2) - format("woff2"); + src: url("../fonts/JetBrainsMono-Regular.ttf") format("truetype"); + /*src: url(https://fonts.gstatic.com/s/jetbrainsmono/v23/tDbX2o-flEEny0FZhsfKu5WU4xD-Cw6nSHrV.woff2) + format("woff2");*/ font-style: normal; + font-weight: 400; +} + +@font-face { + font-display: swap; + font-family: "JetBrains Mono"; + src: url("../fonts/JetBrainsMono-Bold.ttf") format("truetype"); + font-style: normal; + font-weight: 700; } diff --git a/resources/fonts/JetBrainsMono-Bold.ttf b/resources/fonts/JetBrainsMono-Bold.ttf new file mode 100644 index 00000000..1926c804 Binary files /dev/null and b/resources/fonts/JetBrainsMono-Bold.ttf differ diff --git a/resources/fonts/JetBrainsMono-Regular.ttf b/resources/fonts/JetBrainsMono-Regular.ttf new file mode 100644 index 00000000..436c982f Binary files /dev/null and b/resources/fonts/JetBrainsMono-Regular.ttf differ diff --git a/resources/icons/copy.svg b/resources/icons/copy.svg new file mode 100644 index 00000000..513e589c --- /dev/null +++ b/resources/icons/copy.svg @@ -0,0 +1 @@ + diff --git a/resources/icons/stop.svg b/resources/icons/stop.svg new file mode 100644 index 00000000..afaf0f26 --- /dev/null +++ b/resources/icons/stop.svg @@ -0,0 +1 @@ + diff --git a/resources/images/create_a_cluster.png b/resources/images/create_a_cluster.png new file mode 100644 index 00000000..03a28a53 Binary files /dev/null and b/resources/images/create_a_cluster.png differ diff --git a/resources/images/join_a_cluster.png b/resources/images/join_a_cluster.png new file mode 100644 index 00000000..16b1ace8 Binary files /dev/null and b/resources/images/join_a_cluster.png differ