2 Commits

Author SHA1 Message Date
zxq5 7b051208f3 probably broken tbh
Continuous integration / build (push) Failing after 3m15s
2025-08-20 22:57:16 +01:00
zxq5 c891a8be58 ui improvements and feature flags for AI integration
Continuous integration / build (push) Failing after 4m9s
2025-08-18 01:06:30 +01:00
18 changed files with 1154 additions and 568 deletions
+7 -2
View File
@@ -1,9 +1,14 @@
{ {
"rust-analyzer.check.command": "clippy", "rust-analyzer.check.command": "clippy",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"rust-analyzer.cargo.features": "all",
"files.eol": "\n", "files.eol": "\n",
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
"files.trimFinalNewlines": true, "files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true "files.trimTrailingWhitespace": true,
"rust-analyzer.cargo.features": [
"llm",
"native"
],
"rust-analyzer.cargo.noDefaultFeatures": true,
"rust-analyzer.cargo.allFeatures": false
} }
Generated
+168 -160
View File
@@ -4,9 +4,9 @@ version = 4
[[package]] [[package]]
name = "ab_glyph" name = "ab_glyph"
version = "0.2.30" version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea" checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d"
dependencies = [ dependencies = [
"ab_glyph_rasterizer", "ab_glyph_rasterizer",
"owned_ttf_parser", "owned_ttf_parser",
@@ -14,9 +14,9 @@ dependencies = [
[[package]] [[package]]
name = "ab_glyph_rasterizer" name = "ab_glyph_rasterizer"
version = "0.1.9" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[package]] [[package]]
name = "accesskit" name = "accesskit"
@@ -194,15 +194,15 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.98" version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
version = "1.4.1" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
[[package]] [[package]]
name = "arboard" name = "arboard"
@@ -213,7 +213,7 @@ dependencies = [
"clipboard-win", "clipboard-win",
"image", "image",
"log", "log",
"objc2 0.6.1", "objc2 0.6.2",
"objc2-app-kit 0.3.1", "objc2-app-kit 0.3.1",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-core-graphics", "objc2-core-graphics",
@@ -293,9 +293,9 @@ dependencies = [
[[package]] [[package]]
name = "async-io" name = "async-io"
version = "2.4.1" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
dependencies = [ dependencies = [
"async-lock", "async-lock",
"cfg-if", "cfg-if",
@@ -304,17 +304,16 @@ dependencies = [
"futures-lite", "futures-lite",
"parking", "parking",
"polling", "polling",
"rustix 1.0.7", "rustix 1.0.8",
"slab", "slab",
"tracing", "windows-sys 0.60.2",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "async-lock" name = "async-lock"
version = "3.4.0" version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
dependencies = [ dependencies = [
"event-listener", "event-listener",
"event-listener-strategy", "event-listener-strategy",
@@ -323,9 +322,9 @@ dependencies = [
[[package]] [[package]]
name = "async-process" name = "async-process"
version = "2.3.1" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-io", "async-io",
@@ -336,8 +335,7 @@ dependencies = [
"cfg-if", "cfg-if",
"event-listener", "event-listener",
"futures-lite", "futures-lite",
"rustix 1.0.7", "rustix 1.0.8",
"tracing",
] ]
[[package]] [[package]]
@@ -353,9 +351,9 @@ dependencies = [
[[package]] [[package]]
name = "async-signal" name = "async-signal"
version = "0.2.11" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1"
dependencies = [ dependencies = [
"async-io", "async-io",
"async-lock", "async-lock",
@@ -363,10 +361,10 @@ dependencies = [
"cfg-if", "cfg-if",
"futures-core", "futures-core",
"futures-io", "futures-io",
"rustix 1.0.7", "rustix 1.0.8",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -377,9 +375,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.88" version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -464,9 +462,9 @@ dependencies = [
[[package]] [[package]]
name = "avif-serialize" name = "avif-serialize"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42" checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
] ]
@@ -570,18 +568,18 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.23.1" version = "1.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677"
dependencies = [ dependencies = [
"bytemuck_derive", "bytemuck_derive",
] ]
[[package]] [[package]]
name = "bytemuck_derive" name = "bytemuck_derive"
version = "1.9.3" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -628,9 +626,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.29" version = "1.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@@ -691,9 +689,9 @@ dependencies = [
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "5.4.0" version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
dependencies = [ dependencies = [
"error-code", "error-code",
] ]
@@ -786,9 +784,9 @@ dependencies = [
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@@ -849,7 +847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"objc2 0.6.1", "objc2 0.6.2",
] ]
[[package]] [[package]]
@@ -895,9 +893,9 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]] [[package]]
name = "ecolor" name = "ecolor"
@@ -1225,9 +1223,9 @@ checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.4.0" version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [ dependencies = [
"concurrent-queue", "concurrent-queue",
"parking", "parking",
@@ -1371,9 +1369,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]] [[package]]
name = "futures-lite" name = "futures-lite"
version = "2.6.0" version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"futures-core", "futures-core",
@@ -1508,7 +1506,7 @@ dependencies = [
"glutin_glx_sys", "glutin_glx_sys",
"glutin_wgl_sys", "glutin_wgl_sys",
"libloading", "libloading",
"objc2 0.6.1", "objc2 0.6.2",
"objc2-app-kit 0.3.1", "objc2-app-kit 0.3.1",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation 0.3.1", "objc2-foundation 0.3.1",
@@ -1562,9 +1560,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.11" version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -1592,9 +1590,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.4" version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [ dependencies = [
"foldhash", "foldhash",
] ]
@@ -1968,6 +1966,15 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"
@@ -2036,9 +2043,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.174" version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]] [[package]]
name = "libfuzzer-sys" name = "libfuzzer-sys"
@@ -2057,7 +2064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.53.2", "windows-targets 0.53.3",
] ]
[[package]] [[package]]
@@ -2068,13 +2075,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.4" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"libc", "libc",
"redox_syscall 0.5.13", "redox_syscall 0.5.17",
] ]
[[package]] [[package]]
@@ -2097,9 +2104,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]] [[package]]
name = "litrs" name = "litrs"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@@ -2144,9 +2151,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
version = "0.9.5" version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -2225,7 +2232,7 @@ dependencies = [
"once_cell", "once_cell",
"rustc-hash 1.1.0", "rustc-hash 1.1.0",
"strum", "strum",
"thiserror 2.0.12", "thiserror 2.0.14",
"unicode-ident", "unicode-ident",
] ]
@@ -2408,9 +2415,9 @@ dependencies = [
[[package]] [[package]]
name = "objc2" name = "objc2"
version = "0.6.1" version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc"
dependencies = [ dependencies = [
"objc2-encode", "objc2-encode",
] ]
@@ -2438,7 +2445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"objc2 0.6.1", "objc2 0.6.2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-core-graphics", "objc2-core-graphics",
"objc2-foundation 0.3.1", "objc2-foundation 0.3.1",
@@ -2488,7 +2495,7 @@ checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"dispatch2", "dispatch2",
"objc2 0.6.1", "objc2 0.6.2",
] ]
[[package]] [[package]]
@@ -2499,7 +2506,7 @@ checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"dispatch2", "dispatch2",
"objc2 0.6.1", "objc2 0.6.2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-io-surface", "objc2-io-surface",
] ]
@@ -2554,7 +2561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"objc2 0.6.1", "objc2 0.6.2",
"objc2-core-foundation", "objc2-core-foundation",
] ]
@@ -2565,7 +2572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"objc2 0.6.1", "objc2 0.6.2",
"objc2-core-foundation", "objc2-core-foundation",
] ]
@@ -2741,9 +2748,9 @@ dependencies = [
[[package]] [[package]]
name = "owned_ttf_parser" name = "owned_ttf_parser"
version = "0.25.0" version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
dependencies = [ dependencies = [
"ttf-parser", "ttf-parser",
] ]
@@ -2772,7 +2779,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall 0.5.13", "redox_syscall 0.5.17",
"smallvec", "smallvec",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
@@ -2897,17 +2904,16 @@ dependencies = [
[[package]] [[package]]
name = "polling" name = "polling"
version = "3.8.0" version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"concurrent-queue", "concurrent-queue",
"hermit-abi", "hermit-abi",
"pin-project-lite", "pin-project-lite",
"rustix 1.0.7", "rustix 1.0.8",
"tracing", "windows-sys 0.60.2",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -2945,9 +2951,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -3075,7 +3081,7 @@ dependencies = [
"built", "built",
"cfg-if", "cfg-if",
"interpolate_name", "interpolate_name",
"itertools", "itertools 0.12.1",
"libc", "libc",
"libfuzzer-sys", "libfuzzer-sys",
"log", "log",
@@ -3119,9 +3125,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.10.0" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [ dependencies = [
"either", "either",
"rayon-core", "rayon-core",
@@ -3129,9 +3135,9 @@ dependencies = [
[[package]] [[package]]
name = "rayon-core" name = "rayon-core"
version = "1.12.1" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [ dependencies = [
"crossbeam-deque", "crossbeam-deque",
"crossbeam-utils", "crossbeam-utils",
@@ -3148,9 +3154,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.13" version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
] ]
@@ -3163,9 +3169,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.22" version = "0.12.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@@ -3205,9 +3211,9 @@ dependencies = [
[[package]] [[package]]
name = "rgb" name = "rgb"
version = "0.8.51" version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
[[package]] [[package]]
name = "ring" name = "ring"
@@ -3225,9 +3231,9 @@ dependencies = [
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.25" version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
@@ -3256,22 +3262,22 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.0.7" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.9.4", "linux-raw-sys 0.9.4",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.29" version = "0.23.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"rustls-pki-types", "rustls-pki-types",
@@ -3302,9 +3308,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.21" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]] [[package]]
name = "ryu" name = "ryu"
@@ -3400,9 +3406,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.141" version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -3450,9 +3456,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.5" version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -3480,9 +3486,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.10" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]] [[package]]
name = "slotmap" name = "slotmap"
@@ -3602,9 +3608,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.104" version = "2.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3680,7 +3686,7 @@ dependencies = [
"fastrand", "fastrand",
"getrandom 0.3.3", "getrandom 0.3.3",
"once_cell", "once_cell",
"rustix 1.0.7", "rustix 1.0.8",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -3704,11 +3710,11 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.12" version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
dependencies = [ dependencies = [
"thiserror-impl 2.0.12", "thiserror-impl 2.0.14",
] ]
[[package]] [[package]]
@@ -3724,9 +3730,9 @@ dependencies = [
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.12" version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3781,9 +3787,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.47.0" version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -3818,9 +3824,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.15" version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -3991,9 +3997,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.14" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
@@ -4020,9 +4026,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.17.0" version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
dependencies = [ dependencies = [
"getrandom 0.3.3", "getrandom 0.3.3",
"js-sys", "js-sys",
@@ -4165,13 +4171,13 @@ dependencies = [
[[package]] [[package]]
name = "wayland-backend" name = "wayland-backend"
version = "0.3.10" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
dependencies = [ dependencies = [
"cc", "cc",
"downcast-rs", "downcast-rs",
"rustix 0.38.44", "rustix 1.0.8",
"scoped-tls", "scoped-tls",
"smallvec", "smallvec",
"wayland-sys", "wayland-sys",
@@ -4179,12 +4185,12 @@ dependencies = [
[[package]] [[package]]
name = "wayland-client" name = "wayland-client"
version = "0.31.10" version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"rustix 0.38.44", "rustix 1.0.8",
"wayland-backend", "wayland-backend",
"wayland-scanner", "wayland-scanner",
] ]
@@ -4202,20 +4208,20 @@ dependencies = [
[[package]] [[package]]
name = "wayland-cursor" name = "wayland-cursor"
version = "0.31.10" version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29"
dependencies = [ dependencies = [
"rustix 0.38.44", "rustix 1.0.8",
"wayland-client", "wayland-client",
"xcursor", "xcursor",
] ]
[[package]] [[package]]
name = "wayland-protocols" name = "wayland-protocols"
version = "0.32.8" version = "0.32.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"wayland-backend", "wayland-backend",
@@ -4225,9 +4231,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols-plasma" name = "wayland-protocols-plasma"
version = "0.3.8" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"wayland-backend", "wayland-backend",
@@ -4238,9 +4244,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols-wlr" name = "wayland-protocols-wlr"
version = "0.3.8" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"wayland-backend", "wayland-backend",
@@ -4251,9 +4257,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-scanner" name = "wayland-scanner"
version = "0.31.6" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quick-xml 0.37.5", "quick-xml 0.37.5",
@@ -4262,9 +4268,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-sys" name = "wayland-sys"
version = "0.31.6" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
dependencies = [ dependencies = [
"dlib", "dlib",
"log", "log",
@@ -4302,7 +4308,7 @@ dependencies = [
"jni", "jni",
"log", "log",
"ndk-context", "ndk-context",
"objc2 0.6.1", "objc2 0.6.2",
"objc2-foundation 0.3.1", "objc2-foundation 0.3.1",
"url", "url",
"web-sys", "web-sys",
@@ -4363,7 +4369,7 @@ dependencies = [
"raw-window-handle", "raw-window-handle",
"rustc-hash 1.1.0", "rustc-hash 1.1.0",
"smallvec", "smallvec",
"thiserror 2.0.12", "thiserror 2.0.14",
"wgpu-core-deps-windows-linux-android", "wgpu-core-deps-windows-linux-android",
"wgpu-hal", "wgpu-hal",
"wgpu-types", "wgpu-types",
@@ -4393,7 +4399,7 @@ dependencies = [
"portable-atomic", "portable-atomic",
"raw-window-handle", "raw-window-handle",
"renderdoc-sys", "renderdoc-sys",
"thiserror 2.0.12", "thiserror 2.0.14",
"wgpu-types", "wgpu-types",
] ]
@@ -4407,7 +4413,7 @@ dependencies = [
"bytemuck", "bytemuck",
"js-sys", "js-sys",
"log", "log",
"thiserror 2.0.12", "thiserror 2.0.14",
"web-sys", "web-sys",
] ]
@@ -4588,7 +4594,7 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [ dependencies = [
"windows-targets 0.53.2", "windows-targets 0.53.3",
] ]
[[package]] [[package]]
@@ -4639,10 +4645,11 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.53.2" version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [ dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0", "windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0", "windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0", "windows_i686_gnu 0.53.0",
@@ -4844,9 +4851,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]] [[package]]
name = "winit" name = "winit"
version = "0.30.11" version = "0.30.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732"
dependencies = [ dependencies = [
"ahash", "ahash",
"android-activity", "android-activity",
@@ -4896,9 +4903,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.11" version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -4923,12 +4930,13 @@ dependencies = [
"egui_extras", "egui_extras",
"egui_file", "egui_file",
"image", "image",
"itertools 0.14.0",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.12", "tempfile",
"thiserror 2.0.14",
"uuid", "uuid",
"walkdir",
] ]
[[package]] [[package]]
@@ -5026,9 +5034,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus" name = "zbus"
version = "5.8.0" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597f45e98bc7e6f0988276012797855613cd8269e23b5be62cc4e5d28b7e515d" checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-executor", "async-executor",
@@ -5083,9 +5091,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus_macros" name = "zbus_macros"
version = "5.8.0" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c8e4e14dcdd9d97a98b189cd1220f30e8394ad271e8c987da84f73693862c2" checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659"
dependencies = [ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
@@ -5181,9 +5189,9 @@ dependencies = [
[[package]] [[package]]
name = "zerovec" name = "zerovec"
version = "0.11.2" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
dependencies = [ dependencies = [
"yoke", "yoke",
"zerofrom", "zerofrom",
@@ -5218,9 +5226,9 @@ dependencies = [
[[package]] [[package]]
name = "zune-jpeg" name = "zune-jpeg"
version = "0.4.19" version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089"
dependencies = [ dependencies = [
"zune-core", "zune-core",
] ]
+10 -3
View File
@@ -17,8 +17,15 @@ image = { version = "0.25.6", features = ["jpeg", "png"] }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.141" serde_json = "1.0.141"
chrono = { version = "0.4.41", features = ["serde"] } chrono = { version = "0.4.41", features = ["serde"] }
thiserror = "2.0.12" thiserror = "2.0.14"
egui_commonmark = { version = "0.21.1", features = ["embedded_image"] } egui_commonmark = { version = "0.21.1", features = ["embedded_image"] }
walkdir = "2.5.0"
uuid = { version = "1.17.0", features = ["v4"] } uuid = { version = "1.17.0", features = ["v4"] }
reqwest = { version = "0.12.22", features = ["blocking", "json"] } reqwest = { version = "0.12.23", features = ["blocking", "json"] }
tempfile = "3.20.0"
itertools = "0.14.0"
[features]
default = ["native", "llm"]
web = []
native = []
llm = []
+1 -3
View File
@@ -1,11 +1,9 @@
pkgname=worldcoder pkgname=worldcoder
pkgver=0.1.1 pkgver=0.1.1
pkgrel=1 pkgrel=3
makedepends=('rust' 'cargo') makedepends=('rust' 'cargo')
arch=('i686' 'x86_64' 'armv6h' 'armv7h') arch=('i686' 'x86_64' 'armv6h' 'armv7h')
# Generated in accordance to https://wiki.archlinux.org/title/Rust_package_guidelines.
# Might require further modification depending on the package involved.
prepare() { prepare() {
cargo fetch --locked --target "$CARCH-unknown-linux-gnu" cargo fetch --locked --target "$CARCH-unknown-linux-gnu"
} }
+2 -2
View File
@@ -1,6 +1,6 @@
use egui::{TextEdit, vec2}; use egui::{TextEdit, vec2};
use crate::{PROJECT_FOLDER, util}; use crate::{PROJECT_FOLDER, filesystem::Id, util};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Asset { pub struct Asset {
@@ -48,7 +48,7 @@ impl Asset {
pub fn ui(&mut self, ui: &mut egui::Ui) { pub fn ui(&mut self, ui: &mut egui::Ui) {
ui.vertical(|ui| { ui.vertical(|ui| {
util::saved_status(ui, self.saved, &self.name, &self.new_name); util::saved_status(ui, self.saved, &Id::new(), &self.new_name);
if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl)
|| ui.button("Save").clicked() || ui.button("Save").clicked()
+87 -86
View File
@@ -1,34 +1,43 @@
use egui::TextEdit; use egui::TextEdit;
use egui_commonmark::{CommonMarkCache, CommonMarkViewer}; use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
use serde::{self, Deserialize, Serialize}; use serde::{self, Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use crate::{ use crate::{
PROJECT_FOLDER, FILESYSTEM, PROJECT_FOLDER,
editors::{settings_editor::ProjectSettings, tags::Tag}, editors::{settings_editor::ProjectSettings, tags::Tag},
llm_integration::content_llm::{ContentAI, ReadyState, ReasoningEffort}, filesystem::{FileSystem, Id},
util, util,
}; };
#[cfg(feature = "llm")]
use crate::llm_integration::content_llm::{ContentAI, ReadyState};
pub struct MainEditor { pub struct MainEditor {
pub content: ContentSection, pub content: ContentSection,
pub show_editor: bool, pub show_editor: bool,
pub editor_separate_window: bool, pub editor_separate_window: bool,
pub show_preview: bool, pub show_preview: bool,
preview_cache: CommonMarkCache, preview_cache: CommonMarkCache,
dialog: Option<ContentAI>,
#[cfg(feature = "llm")]
dialog: ContentAI,
#[cfg(feature = "llm")]
pub show_ai: bool,
} }
impl Clone for MainEditor { impl Clone for MainEditor {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
content: self.content.clone(), content: self.content.clone(),
show_editor: self.show_editor, show_editor: self.show_editor,
editor_separate_window: self.editor_separate_window, editor_separate_window: self.editor_separate_window,
show_preview: self.show_preview, show_preview: self.show_preview,
preview_cache: CommonMarkCache::default(), preview_cache: CommonMarkCache::default(),
#[cfg(feature = "llm")]
dialog: self.dialog.clone(), dialog: self.dialog.clone(),
#[cfg(feature = "llm")]
show_ai: self.show_ai,
} }
} }
} }
@@ -38,21 +47,20 @@ pub struct ContentSection {
#[serde(default)] #[serde(default)]
pub title: String, pub title: String,
#[serde(default)] pub id: Id,
pub id: String,
#[serde(default)] #[serde(default)]
pub description: String, pub description: String,
#[serde(default)] #[serde(default)]
pub tags: Vec<String>, pub tags: Vec<Id>,
#[serde(default)] #[serde(default)]
pub content: String, pub content: String,
// parent id // parent id
#[serde(default)] #[serde(default)]
pub parent: Option<String>, pub parent: Option<Id>,
#[serde(skip)] #[serde(skip)]
pub saved: bool, pub saved: bool,
@@ -62,7 +70,7 @@ impl ContentSection {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
title: String::new(), title: String::new(),
id: uuid::Uuid::new_v4().to_string(), id: Id::new(),
description: String::new(), description: String::new(),
tags: Vec::new(), tags: Vec::new(),
content: String::new(), content: String::new(),
@@ -71,24 +79,29 @@ impl ContentSection {
} }
} }
pub fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> { pub fn save<F: FileSystem>(
let path = PROJECT_FOLDER &mut self,
.join("documents") filesystem: &F,
.join(format!("{}.json", &self.id)); ) -> Result<(), Box<dyn std::error::Error>> {
let documents_dir = PROJECT_FOLDER.join("documents");
if filesystem.exists(&self.id) {
filesystem.write(&self.id, self.clone())?;
} else {
let _new_id = filesystem.create(&documents_dir, self.clone())?;
// Note: The filesystem creates its own ID, but we keep our existing ID for consistency
}
let content = serde_json::to_string_pretty(self)?;
std::fs::write(path, content)?;
self.saved = true; self.saved = true;
Ok(()) Ok(())
} }
pub fn load(id: &str) -> Result<Self, Box<dyn std::error::Error>> { pub fn load<F: FileSystem>(
let path = PROJECT_FOLDER.join("documents").join(format!("{id}.json")); filesystem: &F,
id: &Id,
let content = std::fs::read_to_string(&path)?; ) -> Result<Self, Box<dyn std::error::Error>> {
let mut section: Self = serde_json::from_str(&content)?; let mut section: Self = filesystem.read(id)?;
section.saved = true; section.saved = true;
section.id = id.to_string(); section.id = id.clone();
Ok(section) Ok(section)
} }
@@ -108,7 +121,11 @@ impl MainEditor {
show_preview: false, show_preview: false,
editor_separate_window: false, editor_separate_window: false,
preview_cache: CommonMarkCache::default(), preview_cache: CommonMarkCache::default(),
dialog: None,
#[cfg(feature = "llm")]
show_ai: false,
#[cfg(feature = "llm")]
dialog: ContentAI::new(String::new()),
} }
} }
@@ -119,15 +136,24 @@ impl MainEditor {
show_preview: false, show_preview: false,
editor_separate_window: false, editor_separate_window: false,
preview_cache: CommonMarkCache::default(), preview_cache: CommonMarkCache::default(),
dialog: None,
#[cfg(feature = "llm")]
show_ai: false,
#[cfg(feature = "llm")]
dialog: ContentAI::new(String::new()),
} }
} }
pub fn render_ui(&mut self, project: &mut ProjectSettings, ui: &mut egui::Ui) { pub fn render_ui<F: FileSystem>(
&mut self,
project: &mut ProjectSettings,
filesystem: &F,
ui: &mut egui::Ui,
) {
ui.vertical(|ui| { ui.vertical(|ui| {
// check for Ctrl+S to save // check for Ctrl+S to save
if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) {
if let Err(e) = self.content.save() { if let Err(e) = self.content.save(filesystem) {
eprintln!("Failed to save: {e}"); eprintln!("Failed to save: {e}");
} }
} }
@@ -144,7 +170,7 @@ impl MainEditor {
ui.horizontal(|ui| { ui.horizontal(|ui| {
// save button // save button
if ui.button("Save").clicked() { if ui.button("Save").clicked() {
if let Err(e) = self.content.save() { if let Err(e) = self.content.save(filesystem) {
eprintln!("Failed to save: {e}"); eprintln!("Failed to save: {e}");
} }
} }
@@ -152,31 +178,32 @@ impl MainEditor {
// create copy button // create copy button
if ui.button("Create Copy").clicked() { if ui.button("Create Copy").clicked() {
let mut copy = self.clone(); let mut copy = self.clone();
copy.content.id = uuid::Uuid::new_v4().to_string(); copy.content.id = Id::new();
copy.content.title = format!("{} (Copy)", self.content.title); copy.content.title = format!("{} (Copy)", self.content.title);
copy.content.save().unwrap();
FILESYSTEM.clone(&self.content.id, &copy.content.id);
// TODO: Fix save call to pass filesystem
// copy.content.save().unwrap();
} }
// delete button // delete button
if ui.button("Delete").clicked() { if ui.button("Delete").clicked() {
std::fs::remove_file( filesystem.delete(&self.content.id).unwrap();
PROJECT_FOLDER
.join("documents")
.join(format!("{}.json", self.content.id)),
)
.unwrap();
*self = Self::new(); *self = Self::new();
} }
// revert changes button // revert changes button
if ui.button("Revert changes").clicked() { if ui.button("Revert changes").clicked() {
self.content = ContentSection::load(&self.content.id).unwrap(); self.content = ContentSection::load(filesystem, &self.content.id).unwrap();
} }
// preview toggle // preview toggle
ui.checkbox(&mut self.show_preview, "Preview"); ui.checkbox(&mut self.show_preview, "Preview");
// assistant toggle
#[cfg(feature = "llm")]
ui.checkbox(&mut self.show_ai, "AI Assistant");
// editor toggle // editor toggle
ui.checkbox(&mut self.editor_separate_window, "Pop out editor"); ui.checkbox(&mut self.editor_separate_window, "Pop out editor");
}); });
@@ -222,10 +249,13 @@ impl MainEditor {
ui.separator(); ui.separator();
if let Some(dialog) = &mut self.dialog { #[cfg(feature = "llm")]
dialog.ui(ui, project); if self.show_ai {
let dialog = &mut self.dialog;
dialog.content = self.content.content.clone(); dialog.content = self.content.content.clone();
dialog.ui(ui, project);
if *dialog.ready.lock().unwrap() == ReadyState::Ready { if *dialog.ready.lock().unwrap() == ReadyState::Ready {
self.content self.content
.content .content
@@ -244,7 +274,12 @@ impl MainEditor {
self.editor_ui(ui, project); self.editor_ui(ui, project);
} }
pub fn ui(&mut self, ctx: &egui::Context, project: &mut ProjectSettings) { pub fn ui<F: FileSystem>(
&mut self,
ctx: &egui::Context,
project: &mut ProjectSettings,
filesystem: &F,
) {
// Show the editor window if enabled // Show the editor window if enabled
let mut show = self.show_editor; let mut show = self.show_editor;
if show { if show {
@@ -255,11 +290,11 @@ impl MainEditor {
.default_height(800.0) .default_height(800.0)
.open(&mut show) .open(&mut show)
.show(ctx, |ui| { .show(ctx, |ui| {
self.render_ui(project, ui); self.render_ui(project, filesystem, ui);
}); });
} else { } else {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
self.render_ui(project, ui); self.render_ui(project, filesystem, ui);
}); });
} }
} }
@@ -319,50 +354,16 @@ impl MainEditor {
ui.set_min_width(max_width as f32); ui.set_min_width(max_width as f32);
let text_edit = TextEdit::multiline(&mut self.content.content) ui.add(
.id_source("MainEditor_editor") TextEdit::multiline(&mut self.content.content)
.font(egui::TextStyle::Monospace) .id_source("MainEditor_editor")
.interactive(true) .font(egui::TextStyle::Monospace)
.frame(false) .interactive(true)
.lock_focus(true) .frame(false)
.hint_text("Type here...") .lock_focus(true)
.desired_width(max_width as f32); .hint_text("Type here...")
.desired_width(max_width as f32),
let mut ctx_menu = false; );
let response = ui
.add_sized(
egui::vec2(max_width as f32 - 30.0, ui.available_height()),
text_edit,
)
.on_hover_text("Right click to open context menu")
.context_menu(|ui| {
ctx_menu = true;
ui.menu_button("AI Actions", |ui| {
ui.add_enabled_ui(project.ai_enabled(), |ui| {
if ui.button("AI Assistant").clicked() {
self.dialog = Some(ContentAI {
content: self.content.content.clone(),
instruction: String::new(),
max_tokens: 1024,
reasoning_effort: ReasoningEffort::default(),
context_override: "".to_string(),
result: Arc::new(Mutex::new(String::new())),
open: true,
ready: Arc::new(Mutex::new(ReadyState::Idle)),
temperature: 0.7,
model_override: "".to_string(),
});
}
});
});
});
if let Some(response) = response {
if response.response.changed() || ctx_menu {
self.content.saved = false;
}
}
}); });
}); });
}); });
+17 -14
View File
@@ -1,9 +1,14 @@
use std::fs; use std::fs;
use egui::TextEdit; use egui::TextEdit;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize, de::DeserializeOwned};
use crate::{PROJECT_FOLDER, editors::tags::Tag, util}; use crate::{
FILESYSTEM, PROJECT_FOLDER,
editors::tags::Tag,
filesystem::{FileSystem, Id},
util,
};
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Note { pub struct Note {
@@ -14,10 +19,9 @@ pub struct Note {
pub subject: String, pub subject: String,
#[serde(default)] #[serde(default)]
pub tags: Vec<String>, pub tags: Vec<Id>,
#[serde(skip)] pub id: Id,
pub id: String,
#[serde(skip)] #[serde(skip)]
pub saved: bool, pub saved: bool,
@@ -26,7 +30,7 @@ pub struct Note {
impl Default for Note { impl Default for Note {
fn default() -> Self { fn default() -> Self {
Self { Self {
id: uuid::Uuid::new_v4().to_string(), id: Id::new(),
name: "New Note".to_string(), name: "New Note".to_string(),
subject: "".to_string(), subject: "".to_string(),
content: "".to_string(), content: "".to_string(),
@@ -39,7 +43,7 @@ impl Default for Note {
impl Note { impl Note {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
id: uuid::Uuid::new_v4().to_string(), id: Id::new(),
name: "New Note".to_string(), name: "New Note".to_string(),
subject: "".to_string(), subject: "".to_string(),
content: "".to_string(), content: "".to_string(),
@@ -50,17 +54,16 @@ impl Note {
pub fn save(&mut self) -> std::io::Result<()> { pub fn save(&mut self) -> std::io::Result<()> {
let id = &self.id; let id = &self.id;
let path = PROJECT_FOLDER.join("notes").join(format!("{id}.json")); let data = serde_json::to_string(&self)?;
fs::write(path, serde_json::to_string(&self)?)?; FILESYSTEM.write(id, data).unwrap();
self.saved = true; self.saved = true;
Ok(()) Ok(())
} }
pub fn load(id: &str) -> std::io::Result<Self> { pub fn load(id: &Id) -> std::io::Result<Self> {
let path = PROJECT_FOLDER.join("notes").join(format!("{id}.json")); let mut note: Note = FILESYSTEM.read(id).unwrap();
let content = fs::read_to_string(path)?; note.id = id.clone();
let mut note: Note = serde_json::from_str(&content)?;
note.id = id.to_string();
note.saved = true; note.saved = true;
Ok(note) Ok(note)
} }
+26 -43
View File
@@ -1,30 +1,30 @@
use core::f32; use core::f32;
use egui::{CollapsingHeader, RichText, Sense, TextEdit, Ui, UiBuilder, vec2}; use egui::{CollapsingHeader, RichText, Sense, TextEdit, Ui, UiBuilder, vec2};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::Path;
use crate::{ use crate::{
PROJECT_FOLDER, RightPanelContent, FILESYSTEM, PROJECT_FOLDER, RightPanelContent,
editors::{ editors::{
tags::Tag, tags::Tag,
template_editor::{FieldValue, Template}, template_editor::{FieldValue, Template},
}, },
filesystem::{FileSystem, Id},
util, util,
}; };
pub type ObjectId = String;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ObjectInstance { pub struct ObjectInstance {
// template info // template info
pub id: ObjectId, pub id: Id,
pub template_id: String, pub template_id: Id,
// instance info // instance info
pub name: String, pub name: String,
pub fields: std::collections::HashMap<String, FieldValue>, pub fields: std::collections::HashMap<String, FieldValue>,
#[serde(default)] #[serde(default)]
pub tags: Vec<String>, pub tags: Vec<Id>,
#[serde(skip)] #[serde(skip)]
pub saved: bool, pub saved: bool,
@@ -50,8 +50,8 @@ impl Clone for ObjectInstance {
impl Default for ObjectInstance { impl Default for ObjectInstance {
fn default() -> Self { fn default() -> Self {
Self { Self {
id: uuid::Uuid::new_v4().to_string(), id: Id::new(),
template_id: "new_template_instance".to_string(), template_id: Id::new(),
name: "new_object".to_string(), name: "new_object".to_string(),
fields: std::collections::HashMap::new(), fields: std::collections::HashMap::new(),
tags: Vec::new(), tags: Vec::new(),
@@ -69,33 +69,25 @@ impl ObjectInstance {
fields.insert(field.name.clone(), FieldValue::from_type(&field.field_type)); fields.insert(field.name.clone(), FieldValue::from_type(&field.field_type));
} }
Self { let instance = Self {
id: uuid::Uuid::new_v4().to_string(),
template_id: template.id.clone(),
name: "new_object".to_string(),
fields, fields,
tags: Vec::new(), template_id: template.id.clone(),
saved: false, ..Default::default()
dialog: None, };
}
let _ = FILESYSTEM.create(Path::new("./objects"), instance.clone());
instance
} }
pub fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> { pub fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let path = PROJECT_FOLDER FILESYSTEM.write(&self.id, self.clone())?;
.join("objects")
.join(format!("{}.json", &self.id));
let content = serde_json::to_string_pretty(self)?;
std::fs::write(&path, content)?;
self.saved = true; self.saved = true;
Ok(()) Ok(())
} }
pub fn load(id: &str) -> Result<Self, Box<dyn std::error::Error>> { pub fn load(id: &Id) -> Result<Self, Box<dyn std::error::Error>> {
let path = PROJECT_FOLDER.join("objects").join(format!("{id}.json")); let mut instance: ObjectInstance = FILESYSTEM.read(id)?;
let content = std::fs::read_to_string(&path)?;
let mut instance: ObjectInstance = serde_json::from_str(&content)?;
instance.saved = true; instance.saved = true;
Ok(instance) Ok(instance)
} }
@@ -127,23 +119,14 @@ impl ObjectInstance {
} }
if ui.button("Create Copy").clicked() { if ui.button("Create Copy").clicked() {
let mut copy = self.clone(); let new_id = Id::new();
copy.id = uuid::Uuid::new_v4().to_string(); FILESYSTEM.clone(&self.id, &new_id).unwrap();
copy.dialog = None; let copy = Self::load(&new_id).unwrap();
copy.name = format!("{} (Copy)", self.name);
copy.save().unwrap();
*right_panel = Some(RightPanelContent::Object(Box::new(copy))); *right_panel = Some(RightPanelContent::Object(Box::new(copy)));
} }
if ui.button("Delete").clicked() { if ui.button("Delete").clicked() {
std::fs::remove_file( FILESYSTEM.delete(&self.id).unwrap();
PROJECT_FOLDER
.join("objects")
.join(format!("{}.json", self.id)),
)
.unwrap();
*right_panel = Some(RightPanelContent::None); *right_panel = Some(RightPanelContent::None);
} }
}); });
@@ -299,12 +282,12 @@ impl ObjectInstance {
} }
fn selector_ui( fn selector_ui(
selected: &mut ObjectId, selected: &mut Id,
objects: &mut [ObjectInstance], objects: &mut [ObjectInstance],
ui: &mut egui::Ui, ui: &mut egui::Ui,
saved: &mut bool, saved: &mut bool,
) { ) {
if !selected.is_empty() { if !selected.to_string().is_empty() {
if let Ok(object) = ObjectInstance::load(selected) { if let Ok(object) = ObjectInstance::load(selected) {
ui.strong(&object.name); ui.strong(&object.name);
} }
@@ -340,7 +323,7 @@ impl ObjectInstance {
} }
if ui.button("Remove").clicked() { if ui.button("Remove").clicked() {
*selected = String::new(); *selected = Id::default();
*saved = false; *saved = false;
} }
}); });
+2 -2
View File
@@ -4,7 +4,7 @@ use egui_extras::DatePickerButton;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{io::Read, path::PathBuf, sync::LazyLock}; use std::{io::Read, path::PathBuf, sync::LazyLock};
use crate::{PROJECT_FOLDER, util::saved_status}; use crate::{PROJECT_FOLDER, filesystem::Id, util::saved_status};
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct ProjectSettings { pub struct ProjectSettings {
@@ -86,7 +86,7 @@ impl ProjectSettings {
#[allow(dead_code)] #[allow(dead_code)]
pub fn ui(&mut self, ui: &mut egui::Ui) { pub fn ui(&mut self, ui: &mut egui::Ui) {
// save state // save state
saved_status(ui, self.saved, "N/A", "Project Settings"); saved_status(ui, self.saved, &Id::default(), "Project Settings");
if ui.button("Save").clicked() { if ui.button("Save").clicked() {
self.save(); self.save();
} }
+13 -7
View File
@@ -1,11 +1,15 @@
use egui::{Response, RichText, TextEdit}; use egui::{Response, RichText, TextEdit};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{PROJECT_FOLDER, util}; use crate::{
FILESYSTEM, PROJECT_FOLDER,
filesystem::{FileSystem, Id},
util,
};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Tag { pub struct Tag {
pub id: String, pub id: Id,
pub name: String, pub name: String,
pub description: String, pub description: String,
pub color: egui::Color32, pub color: egui::Color32,
@@ -20,7 +24,7 @@ pub struct Tag {
impl Default for Tag { impl Default for Tag {
fn default() -> Self { fn default() -> Self {
Self { Self {
id: uuid::Uuid::new_v4().to_string(), id: Id::new(),
name: String::new(), name: String::new(),
description: String::new(), description: String::new(),
color: egui::Color32::from_rgb(20, 20, 20), color: egui::Color32::from_rgb(20, 20, 20),
@@ -128,7 +132,7 @@ impl Tag {
}); });
} }
pub fn selector_ui(tag_ids: &mut Vec<String>, ui: &mut egui::Ui, saved: Option<&mut bool>) { pub fn selector_ui(tag_ids: &mut Vec<Id>, ui: &mut egui::Ui, saved: Option<&mut bool>) {
// remove duplicate tag ids // remove duplicate tag ids
tag_ids.sort(); tag_ids.sort();
tag_ids.dedup(); tag_ids.dedup();
@@ -202,9 +206,11 @@ impl Tag {
}); });
} }
pub fn load(id: &str) -> Result<Self, Box<dyn std::error::Error>> { pub fn load(id: &Id) -> Result<Self, Box<dyn std::error::Error>> {
let path = PROJECT_FOLDER.join("tags").join(format!("{id}.json")); let mut tag: Self = FILESYSTEM.read(id)?;
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?) tag.saved = true;
tag.id = id.clone();
Ok(tag)
} }
pub fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> { pub fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> {
+13 -19
View File
@@ -4,8 +4,9 @@ use egui::ScrollArea;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
PROJECT_FOLDER, RightPanelContent, FILESYSTEM, PROJECT_FOLDER, RightPanelContent,
editors::object_editor::ObjectInstance, editors::object_editor::ObjectInstance,
filesystem::{self, FileSystem, Id},
util::{self, Error}, util::{self, Error},
}; };
@@ -16,7 +17,7 @@ pub enum FieldType {
MultiLine, MultiLine,
Date, Date,
Number, Number,
Link { template_id: Option<String> }, Link { template_id: Option<Id> },
Links, Links,
} }
@@ -47,8 +48,8 @@ pub enum FieldValue {
MultiLine(String), MultiLine(String),
Date(NaiveDate), Date(NaiveDate),
Number(f64), Number(f64),
Link(String), Link(Id),
Links(Vec<String>), Links(Vec<Id>),
} }
impl FieldValue { impl FieldValue {
@@ -59,7 +60,7 @@ impl FieldValue {
FieldType::MultiLine => Self::MultiLine(String::new()), FieldType::MultiLine => Self::MultiLine(String::new()),
FieldType::Date => Self::Date(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()), FieldType::Date => Self::Date(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()),
FieldType::Number => Self::Number(0.0), FieldType::Number => Self::Number(0.0),
FieldType::Link { template_id: None } => Self::Link(String::new()), FieldType::Link { template_id: None } => Self::Link(Id::default()),
FieldType::Link { FieldType::Link {
template_id: Some(template_id), template_id: Some(template_id),
} => Self::Link(template_id.clone()), } => Self::Link(template_id.clone()),
@@ -88,7 +89,7 @@ pub struct FieldDefinition {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Template { pub struct Template {
pub name: String, pub name: String,
pub id: String, pub id: Id,
pub description: Option<String>, pub description: Option<String>,
pub fields: Vec<FieldDefinition>, pub fields: Vec<FieldDefinition>,
@@ -154,7 +155,7 @@ impl Default for Template {
fn default() -> Self { fn default() -> Self {
Self { Self {
name: "New Template".to_string(), name: "New Template".to_string(),
id: uuid::Uuid::new_v4().to_string(), id: Id::new(),
description: Some(String::from("Placeholder description")), description: Some(String::from("Placeholder description")),
fields: Vec::new(), fields: Vec::new(),
saved: false, saved: false,
@@ -170,22 +171,15 @@ impl Default for Template {
} }
impl Template { impl Template {
pub fn load(id: &str) -> Result<Self, Box<dyn std::error::Error>> { pub fn load(id: &Id) -> Result<Self, Box<dyn std::error::Error>> {
let path = PROJECT_FOLDER.join("templates").join(format!("{id}.json")); let mut template: Self = FILESYSTEM.read(id)?;
let content = std::fs::read_to_string(&path)?;
let mut template: Self = serde_json::from_str(&content)?;
template.saved = true; template.saved = true;
template.id = id.clone();
Ok(template) Ok(template)
} }
pub fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> { pub fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let path = PROJECT_FOLDER FILESYSTEM.write(&self.id, self.clone())?;
.join("templates")
.join(format!("{}.json", &self.id));
let content = serde_json::to_string_pretty(self)?;
std::fs::write(path, content)?;
self.saved = true; self.saved = true;
Ok(()) Ok(())
} }
@@ -225,7 +219,7 @@ impl Template {
if ui.button("Create Copy").clicked() { if ui.button("Create Copy").clicked() {
let mut copy = self.clone(); let mut copy = self.clone();
copy.id = uuid::Uuid::new_v4().to_string(); copy.id = Id::new();
copy.name = format!("{} (Copy)", self.name); copy.name = format!("{} (Copy)", self.name);
copy.save().unwrap(); copy.save().unwrap();
} }
+33 -27
View File
@@ -1,4 +1,7 @@
use walkdir::{DirEntry, WalkDir}; use itertools::Itertools;
use std::fs::{self, DirEntry};
// use walkdir::{DirEntry, WalkDir};
use crate::{ use crate::{
PROJECT_FOLDER, RightPanelContent, PROJECT_FOLDER, RightPanelContent,
@@ -7,6 +10,7 @@ use crate::{
asset_editor::Asset, content_editor::ContentSection, object_editor::ObjectInstance, asset_editor::Asset, content_editor::ContentSection, object_editor::ObjectInstance,
tags::Tag, template_editor::Template, tags::Tag, template_editor::Template,
}, },
filesystem::Id,
note_editor::Note, note_editor::Note,
}; };
@@ -179,7 +183,7 @@ impl Explorer {
// Filter documents that have the current parent (or no parent if this is the root) // Filter documents that have the current parent (or no parent if this is the root)
let child_docs: Vec<&ContentSection> = documents let child_docs: Vec<&ContentSection> = documents
.iter() .iter()
.filter(|doc| doc.parent.as_deref() == parent_id) .filter(|doc| doc.parent.as_ref().map(|id| id.as_str()) == parent_id)
.collect(); .collect();
for doc in child_docs { for doc in child_docs {
@@ -204,7 +208,7 @@ impl Explorer {
}) })
.body(|ui| { .body(|ui| {
// recursive call to render the next level of documents // recursive call to render the next level of documents
Self::render_doc_branch(ui, documents, Some(&doc.id), load_doc); Self::render_doc_branch(ui, documents, Some(doc.id.as_str()), load_doc);
}); });
} }
} }
@@ -249,24 +253,22 @@ impl Explorer {
}); });
}) })
.body(|ui| { .body(|ui| {
let entries: Vec<_> = WalkDir::new(PROJECT_FOLDER.join("assets")) let entries = fs::read_dir(PROJECT_FOLDER.join("assets"))
.min_depth(1) .unwrap()
.max_depth(1) // Only immediate children .filter_map(Result::ok)
.sort_by(|a, b| { .sorted_by(|a, b| {
// Directories first, then files // Directories first, then files
let a_is_dir = a.file_type().is_dir(); let a_is_dir = a.file_type().unwrap().is_dir();
let b_is_dir = b.file_type().is_dir(); let b_is_dir = b.file_type().unwrap().is_dir();
if a_is_dir == b_is_dir { if a_is_dir == b_is_dir {
a.file_name().cmp(b.file_name()) a.file_name().cmp(&b.file_name())
} else if a_is_dir { } else if a_is_dir {
std::cmp::Ordering::Less std::cmp::Ordering::Less
} else { } else {
std::cmp::Ordering::Greater std::cmp::Ordering::Greater
} }
}) })
.into_iter() .collect::<Vec<_>>();
.filter_map(Result::ok)
.collect();
for entry in entries { for entry in entries {
Self::render_entry(ui, to_load, &entry); Self::render_entry(ui, to_load, &entry);
@@ -275,19 +277,16 @@ impl Explorer {
} }
fn render_entry(ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>, entry: &DirEntry) { fn render_entry(ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>, entry: &DirEntry) {
let file_type = entry.file_type(); let file_type = entry.file_type().unwrap();
let is_dir = file_type.is_dir(); let is_dir = file_type.is_dir();
let file_name = entry.file_name().to_string_lossy(); let file_name = entry.file_name().to_str().unwrap().to_string();
let path = entry.path(); let path = entry.path();
if is_dir { if is_dir {
let entries: Vec<_> = WalkDir::new(path) let entries = fs::read_dir(path)
.min_depth(1) .unwrap()
.max_depth(1)
.sort_by(|a, b| a.file_name().cmp(b.file_name()))
.into_iter()
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect::<Vec<_>>();
egui::collapsing_header::CollapsingState::load_with_default_open( egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(), ui.ctx(),
@@ -329,7 +328,7 @@ impl Explorer {
let mut templates = Vec::new(); let mut templates = Vec::new();
for entry in std::fs::read_dir(&templates_folder).unwrap() { for entry in std::fs::read_dir(&templates_folder).unwrap() {
let path = entry.unwrap().path(); let path = entry.unwrap().path();
match Template::load(path.file_stem().unwrap().to_str().unwrap()) { match Template::load(&Id::from_path(&path)) {
Ok(t) => templates.push(t), Ok(t) => templates.push(t),
Err(err) => eprintln!("Could not parse file {path:?}: {err}"), Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
} }
@@ -348,7 +347,7 @@ impl Explorer {
let mut objects = Vec::new(); let mut objects = Vec::new();
for entry in std::fs::read_dir(&objects_folder).unwrap() { for entry in std::fs::read_dir(&objects_folder).unwrap() {
let path = entry.unwrap().path(); let path = entry.unwrap().path();
match ObjectInstance::load(path.file_stem().unwrap().to_str().unwrap()) { match ObjectInstance::load(&Id::from_path(&path)) {
Ok(o) => objects.push(o), Ok(o) => objects.push(o),
Err(err) => eprintln!("Could not parse file {path:?}: {err}"), Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
} }
@@ -368,7 +367,7 @@ impl Explorer {
for entry in std::fs::read_dir(&notes_folder).unwrap() { for entry in std::fs::read_dir(&notes_folder).unwrap() {
let path = entry.unwrap().path(); let path = entry.unwrap().path();
match Note::load(path.file_stem().unwrap().to_str().unwrap()) { match Note::load(&Id::from_path(&path)) {
Ok(note) => notes.push(note), Ok(note) => notes.push(note),
Err(err) => eprintln!("Could not parse file {path:?}: {err}"), Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
} }
@@ -389,9 +388,16 @@ impl Explorer {
for entry in std::fs::read_dir(&documents_folder).unwrap() { for entry in std::fs::read_dir(&documents_folder).unwrap() {
let path = entry.unwrap().path(); let path = entry.unwrap().path();
match ContentSection::load(path.file_stem().unwrap().to_str().unwrap()) { // TODO: Update to use FileSystem API
Ok(document) => documents.push(MainEditor::open(document)), // For now, read files directly until we refactor the loading system
Err(err) => eprintln!("Could not parse file {path:?}: {err}"), if let Ok(content) = std::fs::read_to_string(&path) {
if let Ok(document) =
serde_json::from_str::<crate::editors::content_editor::ContentSection>(&content)
{
documents.push(MainEditor::open(document));
} else {
eprintln!("Could not parse file {path:?}");
}
} }
} }
+137
View File
@@ -0,0 +1,137 @@
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{
fmt,
ops::{Deref, DerefMut},
path::{Path, PathBuf},
};
use crate::FILESYSTEM;
#[cfg(feature = "native")]
pub mod native;
#[cfg(feature = "web")]
pub mod web;
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Serialize, Deserialize)]
pub struct Id(String);
impl Id {
pub fn new() -> Self {
Self(uuid::Uuid::new_v4().to_string())
}
}
impl AsRef<str> for Id {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Id {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn from_path(path: &Path) -> Self {
Self(path.file_name().unwrap().to_str().unwrap().to_string())
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Default for Id {
fn default() -> Self {
Id(String::new())
}
}
#[derive(Debug)]
pub enum FileSystemError {
FileNotFound(Id, String),
DirectoryNotFound(PathBuf, String),
}
impl fmt::Display for FileSystemError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FileSystemError::FileNotFound(id, message) => {
write!(f, "File not found: {} - {}", id, message)
}
FileSystemError::DirectoryNotFound(id, message) => {
write!(f, "Directory not found: {} - {}", id.display(), message)
}
}
}
}
impl std::error::Error for FileSystemError {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileTree {
pub name: String,
pub path: PathBuf,
pub is_directory: bool,
pub id: Option<Id>,
pub children: Vec<FileTree>,
}
pub trait FileSystem {
fn new(root: impl AsRef<Path>) -> Self;
fn create(&self, directory: &Path, data: impl Serialize) -> Result<Id, FileSystemError>;
fn read<T: DeserializeOwned>(&self, id: &Id) -> Result<T, FileSystemError>;
fn write(&self, id: &Id, data: impl Serialize) -> Result<(), FileSystemError>;
fn borrow<T: DeserializeOwned + Serialize>(
&self,
id: &Id,
) -> Result<FileBorrow<T>, FileSystemError> {
let file = self.read(id)?;
Ok(FileBorrow {
id: id.clone(),
file,
})
}
fn delete(&self, id: &Id) -> Result<(), FileSystemError>;
fn clone(&self, id: &Id, new_id: &Id) -> Result<(), FileSystemError>;
fn exists(&self, id: &Id) -> bool;
fn lsdir(&self, id: &Id) -> Result<Vec<String>, FileSystemError>;
fn file_tree(&self, root_path: &Path) -> Result<FileTree, FileSystemError>;
}
pub struct FileBorrow<T: DeserializeOwned + Serialize> {
pub id: Id,
pub file: T,
}
impl<T: DeserializeOwned + Serialize> Deref for FileBorrow<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.file
}
}
impl<T: DeserializeOwned + Serialize> DerefMut for FileBorrow<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.file
}
}
impl<T: DeserializeOwned + Serialize> Drop for FileBorrow<T> {
fn drop(&mut self) {
FILESYSTEM.write(&self.id, &self.file).unwrap();
}
}
+327
View File
@@ -0,0 +1,327 @@
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use image::EncodableLayout;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::to_string;
use crate::filesystem::FileSystemError;
use super::FileSystem;
pub struct NativeFileSystem {
root: PathBuf,
index: Arc<RwLock<HashMap<super::Id, PathBuf>>>,
}
impl NativeFileSystem {
/// Rebuild the entire index by scanning the filesystem
fn rebuild_index(&self) -> Result<(), std::io::Error> {
let mut index = HashMap::new();
Self::scan_directory(&self.root, &mut index)?;
if let Ok(mut idx) = self.index.write() {
*idx = index;
}
Ok(())
}
/// Recursively scan a directory and populate the index
fn scan_directory(
dir: &Path,
index: &mut HashMap<super::Id, PathBuf>,
) -> Result<(), std::io::Error> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
// Try to parse the filename as a UUID (our Id format)
if let Ok(uuid) = uuid::Uuid::parse_str(name) {
let id = super::Id(uuid.to_string());
index.insert(id, path.clone());
}
}
if path.is_dir() {
Self::scan_directory(&path, index)?;
}
}
Ok(())
}
/// Add an entry to the index
fn add_to_index(&self, id: super::Id, path: PathBuf) {
if let Ok(mut index) = self.index.write() {
index.insert(id, path);
}
}
/// Remove an entry from the index
fn remove_from_index(&self, id: &super::Id) {
if let Ok(mut index) = self.index.write() {
index.remove(id);
}
}
/// Get path from index, with fallback to filesystem scan if not found
fn find_path_by_id(&self, id: &super::Id) -> Option<PathBuf> {
// First try the index
if let Ok(index) = self.index.read() {
if let Some(path) = index.get(id) {
// Verify the file still exists
if path.exists() {
return Some(path.clone());
}
}
}
// Fallback: scan filesystem and update index
let target_name = id.to_string();
let mut stack = vec![self.root.clone()];
while let Some(dir) = stack.pop() {
let entries = match fs::read_dir(&dir) {
Ok(e) => e,
Err(_) => continue,
};
for entry in entries.flatten() {
let path = entry.path();
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
if name == target_name {
// Update index with found path
self.add_to_index(id.clone(), path.clone());
return Some(path);
}
}
if path.is_dir() {
stack.push(path);
}
}
}
None
}
/// Build a file tree recursively from a given path
fn build_file_tree(&self, path: &Path) -> Result<super::FileTree, FileSystemError> {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
let is_directory = path.is_dir();
// Check if this path corresponds to an ID in our index
let id = if let Ok(index) = self.index.read() {
index
.iter()
.find(|(_, indexed_path)| indexed_path.as_path() == path)
.map(|(id, _)| id.clone())
} else {
None
};
let mut children = Vec::new();
if is_directory {
for entry in fs::read_dir(path).map_err(|e| {
FileSystemError::DirectoryNotFound(path.to_path_buf(), e.to_string())
})? {
let entry = entry.map_err(|e| {
FileSystemError::DirectoryNotFound(path.to_path_buf(), e.to_string())
})?;
let child_path = entry.path();
match self.build_file_tree(&child_path) {
Ok(child_tree) => children.push(child_tree),
Err(e) => eprintln!(
"Warning: Failed to build tree for {}: {}",
child_path.display(),
e
),
}
}
}
Ok(super::FileTree {
name,
path: path.to_path_buf(),
is_directory,
id,
children,
})
}
}
impl FileSystem for NativeFileSystem {
fn new(root: impl AsRef<Path>) -> Self {
let fs = Self {
root: root.as_ref().to_path_buf(),
index: Arc::new(RwLock::new(HashMap::new())),
};
fs.rebuild_index().unwrap_or_else(|e| {
eprintln!("Warning: Failed to build initial index: {e}");
});
fs
}
fn create(&self, directory: &Path, data: impl Serialize) -> Result<super::Id, FileSystemError> {
let dir = if directory.is_absolute() {
directory.to_path_buf()
} else {
self.root.join(directory)
};
if !dir.exists() {
fs::create_dir_all(&dir).map_err(|e| {
FileSystemError::DirectoryNotFound(dir.to_path_buf(), e.to_string())
})?;
}
if !dir.is_dir() {
return Err(FileSystemError::DirectoryNotFound(
dir.to_path_buf(),
"".to_string(),
));
}
let id = super::Id::new();
let file_path = dir.join(id.to_string());
let mut file = File::create(&file_path)
.map_err(|e| FileSystemError::DirectoryNotFound(dir.to_path_buf(), e.to_string()))?;
file.write_all(serde_json::to_string(&data).unwrap().as_bytes())
.map_err(|e| FileSystemError::DirectoryNotFound(dir.to_path_buf(), e.to_string()))?;
// Add to index
self.add_to_index(id.clone(), file_path);
Ok(id)
}
fn read<T: DeserializeOwned>(&self, id: &super::Id) -> Result<T, FileSystemError> {
let path = self.find_path_by_id(id).ok_or_else(|| {
FileSystemError::FileNotFound(id.clone(), "No path found!".to_string())
})?;
let val = serde_json::from_reader(
File::open(path)
.map_err(|_| FileSystemError::FileNotFound(id.clone(), "".to_string()))?,
)
.map_err(|_| FileSystemError::FileNotFound(id.clone(), "".to_string()))?;
Ok(val)
}
fn write(&self, id: &super::Id, data: impl Serialize) -> Result<(), FileSystemError> {
let path = self.find_path_by_id(id).ok_or_else(|| {
FileSystemError::FileNotFound(id.clone(), "No path found!".to_string())
})?;
let mut file = File::create(path)
.map_err(|_| FileSystemError::FileNotFound(id.clone(), "".to_string()))?;
file.write_all(serde_json::to_string(&data).unwrap().as_bytes())
.map_err(|_| FileSystemError::FileNotFound(id.clone(), "".to_string()))?;
Ok(())
}
fn delete(&self, id: &super::Id) -> Result<(), FileSystemError> {
let path = self.find_path_by_id(id).ok_or_else(|| {
FileSystemError::FileNotFound(id.clone(), "No path found!".to_string())
})?;
let result = if path.is_dir() {
fs::remove_dir_all(path)
} else {
fs::remove_file(path)
};
// Remove from index if deletion was successful
if result.is_ok() {
self.remove_from_index(id);
}
result.map_err(|e| FileSystemError::FileNotFound(id.clone(), e.to_string()))
}
fn clone(&self, id: &super::Id, new_id: &super::Id) -> Result<(), FileSystemError> {
let src = self.find_path_by_id(id).ok_or_else(|| {
FileSystemError::FileNotFound(id.clone(), "No path found!".to_string())
})?;
let parent = src.parent().map(Path::to_path_buf).ok_or_else(|| {
FileSystemError::FileNotFound(id.clone(), "No parent found!".to_string())
})?;
let dst = parent.join(new_id.to_string());
let result = if src.is_dir() {
// Simple recursive dir copy
fn copy_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
let sp = entry.path();
let dp = dst.join(entry.file_name());
if ty.is_dir() {
copy_dir(&sp, &dp)?;
} else if ty.is_file() {
fs::copy(&sp, &dp)?;
}
}
Ok(())
}
copy_dir(&src, &dst)
} else {
fs::copy(&src, &dst).map(|_| ())
};
// Add cloned file/directory to index if copy was successful
if result.is_ok() {
self.add_to_index(new_id.clone(), dst);
}
result.map_err(|e| FileSystemError::FileNotFound(id.clone(), e.to_string()))
}
fn exists(&self, id: &super::Id) -> bool {
self.find_path_by_id(id).is_some()
}
fn lsdir(&self, id: &super::Id) -> Result<Vec<String>, FileSystemError> {
let path = match self.find_path_by_id(id) {
Some(p) if p.is_dir() => p,
Some(p) => p.parent().map(Path::to_path_buf).ok_or_else(|| {
FileSystemError::DirectoryNotFound(PathBuf::from(id.to_string()), "".to_string())
})?,
None => {
return Err(FileSystemError::DirectoryNotFound(
PathBuf::from(id.to_string()),
id.to_string(),
));
}
};
let mut entries = Vec::new();
for entry in fs::read_dir(&path)
.map_err(|e| FileSystemError::DirectoryNotFound(path.clone(), e.to_string()))?
{
let entry = entry
.map_err(|e| FileSystemError::DirectoryNotFound(path.clone(), e.to_string()))?;
if let Some(name) = entry.file_name().to_str() {
entries.push(name.to_string());
}
}
Ok(entries)
}
fn file_tree(&self, root_path: &Path) -> Result<super::FileTree, FileSystemError> {
self.build_file_tree(root_path)
}
}
View File
+297 -198
View File
@@ -10,18 +10,50 @@ use crate::editors::settings_editor::ProjectSettings;
#[derive(Clone)] #[derive(Clone)]
pub struct ContentAI { pub struct ContentAI {
pub open: bool, pub open: bool,
// model input
pub content: String, pub content: String,
pub instruction: String, pub instruction: String,
pub max_tokens: usize,
pub context_override: String, pub context_override: String,
pub result: Arc<Mutex<String>>, pub system_prompt: String,
pub ready: Arc<Mutex<ReadyState>>,
// model settings
pub max_tokens: usize,
pub temperature: f32, pub temperature: f32,
pub reasoning_effort: ReasoningEffort, pub reasoning_effort: ReasoningEffort,
pub model_override: String, pub model_override: String,
// model output
pub reasoning: Arc<Mutex<String>>,
pub result: Arc<Mutex<String>>,
pub ready: Arc<Mutex<ReadyState>>,
} }
impl ContentAI { impl ContentAI {
pub fn new(content: String) -> Self {
Self {
// model input
content,
instruction: String::new(),
context_override: String::new(),
system_prompt: String::new(),
// model settings
max_tokens: 2048,
reasoning_effort: ReasoningEffort::default(),
temperature: 0.7,
model_override: String::new(),
reasoning: Arc::new(Mutex::new(String::new())),
// output
result: Arc::new(Mutex::new(String::new())),
ready: Arc::new(Mutex::new(ReadyState::Idle)),
// ui
open: true,
}
}
pub fn ui(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) { pub fn ui(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) {
let is_open = self.open; let is_open = self.open;
@@ -34,203 +66,266 @@ impl ContentAI {
} }
fn ui_output_box(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) { fn ui_output_box(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) {
egui::TopBottomPanel::bottom("llm_output") let mut ready_lock = self.ready.lock().unwrap();
.resizable(true)
.show_inside(ui, |ui| {
let mut ready_lock = self.ready.lock().unwrap();
ui.horizontal(|ui| { ui.horizontal(|ui| {
if *ready_lock == ReadyState::Generating { if *ready_lock == ReadyState::Generating {
if ui.button("Cancel").clicked() { if ui.button("Cancel").clicked() {
*ready_lock = ReadyState::Halted; *ready_lock = ReadyState::Halted;
}
if ui.button("Stop").clicked() {
*ready_lock = ReadyState::Idle;
}
ui.spinner();
ui.label("Generating...");
}
if *ready_lock == ReadyState::Idle {
let continue_content = || {
let content = self.content.clone();
let project = project.clone();
let result = self.result.clone();
let reasoning = self.reasoning.clone();
let ready = self.ready.clone();
let options = AIOptions {
max_completion_tokens: self.max_tokens,
reasoning_effort: self.reasoning_effort,
temperature: self.temperature,
model_override: if !self.model_override.is_empty() {
Some(self.model_override.clone())
} else {
None
},
};
let ai_input = AIInput {
system_prompt: self.system_prompt.clone(),
user_prompt: format!(
"{}\n\n{} {}",
self.instruction.clone(),
project.ai_context.clone(),
self.context_override.clone()
),
previous_content: content.clone(),
structure: None,
};
result.lock().unwrap().clear();
std::thread::spawn(move || {
let result = crate::llm_integration::content_llm::continue_content(
ai_input,
options,
project,
result,
reasoning,
ready.clone(),
);
if let Err(e) = result {
eprintln!("Error in content generation: {e}");
} }
if ui.button("Stop").clicked() { });
*ready_lock = ReadyState::Idle; };
}
ui.spinner();
ui.label("Generating...");
}
if *ready_lock == ReadyState::Idle { if ui.button("Generate ").clicked() {
let continue_content = || { continue_content();
let context_override = self.context_override.clone(); }
let content = self.content.clone();
let instruction = self.instruction.clone();
let project = project.clone();
let ai_context = project.ai_context.clone();
let result = self.result.clone();
let ready = self.ready.clone();
let options = AIOptions { ui.label("Idle");
max_completion_tokens: self.max_tokens, }
reasoning_effort: self.reasoning_effort,
temperature: self.temperature,
model_override: if !self.model_override.is_empty() {
Some(self.model_override.clone())
} else {
None
},
};
result.lock().unwrap().clear(); // show regardless of state
if ui.button("Insert").clicked() {
*ready_lock = ReadyState::Ready;
}
std::thread::spawn(move || { if ui.button("Clear").clicked() {
let result = crate::llm_integration::content_llm::continue_content( self.result.lock().unwrap().clear();
ai_context + "\n" + &context_override, self.reasoning.lock().unwrap().clear();
content, }
instruction, });
options,
project,
result,
ready.clone(),
);
if let Err(e) = result {
eprintln!("Error in content generation: {e}");
}
});
};
if ui.button("Generate ").clicked() { ui.spacing();
continue_content();
}
ui.label("Idle"); ui.vertical(|ui| {
} egui::TopBottomPanel::top("reasoning_output")
.resizable(true)
// show regardless of state .show_inside(ui, |ui| {
if ui.button("Insert").clicked() { egui::ScrollArea::both()
*self.ready.lock().unwrap() = ReadyState::Ready; .auto_shrink([false, true])
} .id_salt("reasoning_output")
.max_width(ui.available_width())
if ui.button("Clear").clicked() { // .max_height(ui.available_height() / 3.0)
self.result.lock().unwrap().clear(); .show(ui, |ui| {
} ui.add(
egui::TextEdit::multiline(&mut *self.reasoning.lock().unwrap())
.font(egui::TextStyle::Monospace)
.interactive(false)
.desired_rows(5)
.frame(false)
.desired_width(ui.available_width())
.lock_focus(true)
.hint_text("Reasoning will appear here..."),
);
});
}); });
ui.separator(); egui::ScrollArea::both()
.auto_shrink([false, false])
egui::ScrollArea::both() .id_salt("llm_output")
.auto_shrink([false, false]) .max_width(ui.available_width())
.id_salt("llm_output") .show(ui, |ui| {
.max_width(ui.available_width()) ui.add(
// .max_height(ui.available_height() / 3.0) egui::TextEdit::multiline(&mut *self.result.lock().unwrap())
.show(ui, |ui| { .font(egui::TextStyle::Monospace)
ui.add( .interactive(false)
egui::TextEdit::multiline(&mut *self.result.lock().unwrap()) .desired_rows(0)
.font(egui::TextStyle::Monospace) .frame(false)
.interactive(false) .desired_width(ui.available_width())
.desired_rows(0) .lock_focus(true)
.frame(false) .hint_text("Content will appear here..."),
.desired_width(ui.available_width()) );
.lock_focus(true) });
.hint_text("Content will appear here..."), });
);
});
});
} }
fn ui_main(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) { fn ui_main(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) {
{ {
ui.weak("(The model will see current file content)"); ui.weak("(The model will see current file content)");
egui::Grid::new("continue_grid") egui::CollapsingHeader::new("Settings")
.num_columns(2) .default_open(true)
.striped(true)
.show(ui, |ui| { .show(ui, |ui| {
ui.label("Max Tokens"); egui::Grid::new("continue_grid")
ui.add( .num_columns(2)
egui::DragValue::new(&mut self.max_tokens) .striped(true)
.range(128..=u32::MAX) .show(ui, |ui| {
.speed(128), ui.label("Max Tokens");
); ui.add(
ui.end_row(); egui::DragValue::new(&mut self.max_tokens)
.range(128..=u32::MAX)
.speed(128),
);
ui.end_row();
ui.label("Temperature"); ui.label("Temperature");
ui.add( ui.add(
egui::DragValue::new(&mut self.temperature) egui::DragValue::new(&mut self.temperature)
.range(0.0..=2.0) .range(0.0..=2.0)
.speed(0.1), .speed(0.1),
); );
ui.label("Reasoning effort"); ui.label("Reasoning effort");
egui::ComboBox::from_id_salt("reasoning_effort") egui::ComboBox::from_id_salt("reasoning_effort")
.selected_text(self.reasoning_effort.to_string()) .selected_text(self.reasoning_effort.to_string())
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
ui.selectable_value( ui.selectable_value(
&mut self.reasoning_effort, &mut self.reasoning_effort,
ReasoningEffort::Minimal, ReasoningEffort::Minimal,
"Minimal", "Minimal",
); );
ui.selectable_value( ui.selectable_value(
&mut self.reasoning_effort, &mut self.reasoning_effort,
ReasoningEffort::Low, ReasoningEffort::Low,
"Low", "Low",
); );
ui.selectable_value( ui.selectable_value(
&mut self.reasoning_effort, &mut self.reasoning_effort,
ReasoningEffort::Medium, ReasoningEffort::Medium,
"Medium", "Medium",
); );
ui.selectable_value( ui.selectable_value(
&mut self.reasoning_effort, &mut self.reasoning_effort,
ReasoningEffort::High, ReasoningEffort::High,
"High", "High",
); );
});
ui.end_row();
ui.label("Model override");
ui.add(egui::TextEdit::singleline(&mut self.model_override));
ui.end_row();
}); });
});
ui.end_row(); egui::TopBottomPanel::top("continue_instruction")
.resizable(true)
.show_inside(ui, |ui| {
egui::CollapsingHeader::new("Instructions")
.default_open(true)
.show(ui, |ui| {
egui::ScrollArea::vertical()
.auto_shrink([false, false])
.max_height(ui.available_height())
.show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(&mut self.instruction)
.frame(false)
.desired_width(ui.available_width())
.hint_text("Writing Instructions"),
);
});
});
});
ui.label("Model override"); egui::TopBottomPanel::top("continue_context")
ui.add(egui::TextEdit::singleline(&mut self.model_override)); .resizable(true)
ui.end_row(); .show_inside(ui, |ui| {
egui::CollapsingHeader::new("Context")
.default_open(true)
.show(ui, |ui| {
egui::ScrollArea::vertical()
.auto_shrink([false, false])
.max_height(ui.available_height())
.show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(&mut self.context_override)
.frame(false)
.desired_width(ui.available_width())
.hint_text("Any additional context?"),
);
});
});
});
egui::TopBottomPanel::top("continue_system_prompt")
.resizable(true)
.show_inside(ui, |ui| {
egui::CollapsingHeader::new("System prompt")
.default_open(true)
.show(ui, |ui| {
egui::ScrollArea::vertical()
.auto_shrink([false, false])
.max_height(ui.available_height())
.show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(&mut self.system_prompt)
.frame(false)
.desired_width(ui.available_width())
.hint_text("System prompt"),
);
});
});
}); });
self.ui_output_box(ui, project); self.ui_output_box(ui, project);
ui.separator();
// Instructions
egui::ScrollArea::both()
.id_salt("continue_instruction")
.auto_shrink([true, false])
.max_height(ui.available_height() / 2.0)
.max_width(ui.available_width())
.show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(&mut self.instruction)
.frame(false)
.desired_width(ui.available_width())
.hint_text("Writing Instructions"),
);
});
ui.separator();
// Context
egui::ScrollArea::both()
.id_salt("continue_context")
.auto_shrink([true, false])
.max_height(ui.available_height())
.max_width(ui.available_width())
.show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(&mut self.context_override)
.frame(false)
.desired_width(ui.available_width())
.hint_text("Any additional context?"),
);
});
} }
} }
} }
#[allow(clippy::too_many_arguments)]
pub fn continue_content( pub fn continue_content(
context: String, ai_input: AIInput,
previous_content: String, // context: String,
instruction: String, // previous_content: String,
// instruction: String,
options: AIOptions, options: AIOptions,
project: ProjectSettings, project: ProjectSettings,
result: Arc<Mutex<String>>, result: Arc<Mutex<String>>,
reasoning: Arc<Mutex<String>>,
ready: Arc<Mutex<ReadyState>>, ready: Arc<Mutex<ReadyState>>,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
*ready.lock().unwrap() = ReadyState::Generating; *ready.lock().unwrap() = ReadyState::Generating;
@@ -240,27 +335,15 @@ pub fn continue_content(
let messages = vec![ let messages = vec![
Message { Message {
role: "system".to_string(), role: "system".to_string(),
content: " content: ai_input.system_prompt,
Please generate content that is a continuation of the given text.
Your response should be a logical next step in the content and should not repeat any of the text from the instruction or the content.
Do not generate any text that is not a direct continuation of the content.
if extra instructions are provided, follow them exactly, otherwise continue the text in a logical way.
your output should NEVER be a repeat of any previous content
".to_string(),
}, },
Message { Message {
role: "user".to_string(), role: "user".to_string(),
content: format!("Context / General instructions: {context}"), content: format!(
"<Instructions> {}\n\n<Previous content> {}\n\n",
ai_input.user_prompt, ai_input.previous_content
),
}, },
Message {
role: "user".to_string(),
content: format!("Content to continue: {previous_content}"),
},
Message {
role: "user".to_string(),
content: format!("Specific instructions: {instruction}"),
},
]; ];
let request = ChatRequest { let request = ChatRequest {
@@ -288,19 +371,20 @@ pub fn continue_content(
let response = if let Some(k) = api_key { let response = if let Some(k) = api_key {
client client
.post(llm_api_uri + "/v1/chat/completions") .post(llm_api_uri + "/api/v0/chat/completions")
.json(&request) .json(&request)
.bearer_auth(k) .bearer_auth(k)
.send()? .send()?
} else { } else {
client client
.post(llm_api_uri + "/v1/chat/completions") .post(llm_api_uri + "/api/v0/chat/completions")
.json(&request) .json(&request)
.send()? .send()?
}; };
println!("success!"); println!("success!");
// println!("response: {}", response.text().unwrap());
let reader = BufReader::new(response); let reader = BufReader::new(response);
for line in reader.lines() { for line in reader.lines() {
// initial loop to check if the user has terminated the generation // initial loop to check if the user has terminated the generation
@@ -309,6 +393,7 @@ pub fn continue_content(
if *ready == ReadyState::Halted { if *ready == ReadyState::Halted {
result.lock().unwrap().clear(); result.lock().unwrap().clear();
reasoning.lock().unwrap().clear();
} }
if *ready != ReadyState::Generating { if *ready != ReadyState::Generating {
@@ -324,9 +409,17 @@ pub fn continue_content(
if let Some(json) = line.strip_prefix("data: ") { if let Some(json) = line.strip_prefix("data: ") {
if let Ok(chunk) = serde_json::from_str::<StreamingChatResponse>(json) { if let Ok(chunk) = serde_json::from_str::<StreamingChatResponse>(json) {
println!("chunk: {chunk:?}");
if let Some(content) = chunk.choices[0].delta.content.as_ref() { if let Some(content) = chunk.choices[0].delta.content.as_ref() {
println!("content: {content}");
result.lock().unwrap().push_str(content); result.lock().unwrap().push_str(content);
} }
if let Some(reasoning_content) = chunk.choices[0].delta.reasoning_content.as_ref() {
println!("reasoning_content: {reasoning_content}");
reasoning.lock().unwrap().push_str(reasoning_content);
}
} }
} }
} }
@@ -343,6 +436,13 @@ pub struct AIOptions {
pub model_override: Option<String>, pub model_override: Option<String>,
} }
pub struct AIInput {
pub system_prompt: String,
pub user_prompt: String,
pub previous_content: String,
pub structure: Option<String>,
}
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
pub enum ReadyState { pub enum ReadyState {
Idle, Idle,
@@ -351,10 +451,12 @@ pub enum ReadyState {
Halted, Halted,
} }
#[derive(Serialize, Copy, Clone, PartialEq)] #[derive(Serialize, Copy, Clone, PartialEq, Default)]
pub enum ReasoningEffort { pub enum ReasoningEffort {
#[serde(rename = "minimal")] #[serde(rename = "minimal")]
Minimal, Minimal,
#[default]
#[serde(rename = "low")] #[serde(rename = "low")]
Low, Low,
#[serde(rename = "medium")] #[serde(rename = "medium")]
@@ -363,19 +465,13 @@ pub enum ReasoningEffort {
High, High,
} }
impl Default for ReasoningEffort { impl std::fmt::Display for ReasoningEffort {
fn default() -> Self { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
ReasoningEffort::Low
}
}
impl ToString for ReasoningEffort {
fn to_string(&self) -> String {
match self { match self {
ReasoningEffort::Minimal => "Minimal".to_string(), ReasoningEffort::Minimal => write!(f, "Minimal"),
ReasoningEffort::Low => "Low".to_string(), ReasoningEffort::Low => write!(f, "Low"),
ReasoningEffort::Medium => "Medium".to_string(), ReasoningEffort::Medium => write!(f, "Medium"),
ReasoningEffort::High => "High".to_string(), ReasoningEffort::High => write!(f, "High"),
} }
} }
} }
@@ -413,8 +509,11 @@ struct Delta {
#[serde(default)] #[serde(default)]
#[allow(unused)] #[allow(unused)]
role: Option<String>, role: Option<String>,
#[serde(default)] #[serde(default)]
content: Option<String>, content: Option<String>,
#[serde(default)]
reasoning_content: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
+11 -1
View File
@@ -6,8 +6,12 @@ use egui::ScrollArea;
mod editors; mod editors;
mod explorer; mod explorer;
#[cfg(feature = "llm")]
mod llm_integration; mod llm_integration;
mod filesystem;
mod util; mod util;
use crate::{ use crate::{
@@ -16,6 +20,7 @@ use crate::{
settings_editor::ProjectSettings, tags::Tag, template_editor::Template, settings_editor::ProjectSettings, tags::Tag, template_editor::Template,
}, },
explorer::Explorer, explorer::Explorer,
filesystem::{FileSystem, native::NativeFileSystem},
}; };
static VERSION: &str = "0.1.0"; static VERSION: &str = "0.1.0";
@@ -25,6 +30,9 @@ static PROJECT_FOLDER: LazyLock<PathBuf> = LazyLock::new(|| {
path path
}); });
pub static FILESYSTEM: LazyLock<NativeFileSystem> =
LazyLock::new(|| NativeFileSystem::new(&*PROJECT_FOLDER));
fn main() { fn main() {
let app = Interface::new(); let app = Interface::new();
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
@@ -44,6 +52,7 @@ pub struct Interface {
editor: content_editor::MainEditor, editor: content_editor::MainEditor,
explorer: Explorer, explorer: Explorer,
project: ProjectSettings, project: ProjectSettings,
filesystem: NativeFileSystem,
} }
impl eframe::App for Interface { impl eframe::App for Interface {
@@ -89,6 +98,7 @@ impl Interface {
editor: content_editor::MainEditor::new(), editor: content_editor::MainEditor::new(),
explorer: Explorer::new(), explorer: Explorer::new(),
project: ProjectSettings::load(), project: ProjectSettings::load(),
filesystem: NativeFileSystem::new(&*PROJECT_FOLDER),
} }
} }
@@ -202,7 +212,7 @@ impl Interface {
// render main content area // render main content area
fn render_main_content(&mut self, ctx: &egui::Context) { fn render_main_content(&mut self, ctx: &egui::Context) {
self.editor.ui(ctx, &mut self.project); self.editor.ui(ctx, &mut self.project, &self.filesystem);
} }
// configure appearance of UI elements // configure appearance of UI elements
+3 -1
View File
@@ -3,6 +3,8 @@ use egui::{
scroll_area::{ScrollBarVisibility, ScrollSource}, scroll_area::{ScrollBarVisibility, ScrollSource},
}; };
use crate::filesystem::Id;
pub struct Error { pub struct Error {
message: String, message: String,
visible: bool, visible: bool,
@@ -26,7 +28,7 @@ impl Error {
} }
} }
pub fn saved_status(ui: &mut egui::Ui, saved: bool, id: &str, name: &str) { pub fn saved_status(ui: &mut egui::Ui, saved: bool, id: &Id, name: &str) {
ui.group(|ui| { ui.group(|ui| {
ui.set_max_width(ui.available_width()); ui.set_max_width(ui.available_width());