From c891a8be58cbab77aee42e16c739f9dba167ffbf Mon Sep 17 00:00:00 2001 From: zxq5 Date: Mon, 18 Aug 2025 01:06:30 +0100 Subject: [PATCH] ui improvements and feature flags for AI integration --- .vscode/settings.json | 9 +- Cargo.lock | 328 +++++++++---------- Cargo.toml | 13 +- PKGBUILD | 4 +- src/editors/content_editor.rs | 93 +++--- src/explorer.rs | 36 +-- src/index.rs | 125 ++++++++ src/llm_integration/content_llm.rs | 495 +++++++++++++++++------------ src/main.rs | 3 + 9 files changed, 669 insertions(+), 437 deletions(-) create mode 100644 src/index.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 97bf104..ffd7239 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,14 @@ { "rust-analyzer.check.command": "clippy", "editor.formatOnSave": true, - "rust-analyzer.cargo.features": "all", "files.eol": "\n", "files.insertFinalNewline": 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 } diff --git a/Cargo.lock b/Cargo.lock index fa85873..a76646d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.30" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea" +checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "accesskit" @@ -194,15 +194,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arboard" @@ -213,7 +213,7 @@ dependencies = [ "clipboard-win", "image", "log", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-app-kit 0.3.1", "objc2-core-foundation", "objc2-core-graphics", @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" dependencies = [ "async-lock", "cfg-if", @@ -304,17 +304,16 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.0.7", + "rustix 1.0.8", "slab", - "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ "event-listener", "event-listener-strategy", @@ -323,9 +322,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" dependencies = [ "async-channel", "async-io", @@ -336,8 +335,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.0.7", - "tracing", + "rustix 1.0.8", ] [[package]] @@ -353,9 +351,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" dependencies = [ "async-io", "async-lock", @@ -363,10 +361,10 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.0.7", + "rustix 1.0.8", "signal-hook-registry", "slab", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -377,9 +375,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -464,9 +462,9 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" dependencies = [ "arrayvec", ] @@ -570,18 +568,18 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" dependencies = [ "proc-macro2", "quote", @@ -628,9 +626,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.29" +version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ "jobserver", "libc", @@ -691,9 +689,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" dependencies = [ "error-code", ] @@ -786,9 +784,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -849,7 +847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.9.1", - "objc2 0.6.1", + "objc2 0.6.2", ] [[package]] @@ -895,9 +893,9 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ecolor" @@ -1225,9 +1223,9 @@ checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -1371,9 +1369,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -1508,7 +1506,7 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-app-kit 0.3.1", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -1562,9 +1560,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -1592,9 +1590,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] @@ -1968,6 +1966,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -2036,9 +2043,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libfuzzer-sys" @@ -2057,7 +2064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -2068,13 +2075,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags 2.9.1", "libc", - "redox_syscall 0.5.13", + "redox_syscall 0.5.17", ] [[package]] @@ -2097,9 +2104,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -2144,9 +2151,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] @@ -2225,7 +2232,7 @@ dependencies = [ "once_cell", "rustc-hash 1.1.0", "strum", - "thiserror 2.0.12", + "thiserror 2.0.14", "unicode-ident", ] @@ -2408,9 +2415,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" dependencies = [ "objc2-encode", ] @@ -2438,7 +2445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags 2.9.1", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.1", @@ -2488,7 +2495,7 @@ checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags 2.9.1", "dispatch2", - "objc2 0.6.1", + "objc2 0.6.2", ] [[package]] @@ -2499,7 +2506,7 @@ checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ "bitflags 2.9.1", "dispatch2", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-core-foundation", "objc2-io-surface", ] @@ -2554,7 +2561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags 2.9.1", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-core-foundation", ] @@ -2565,7 +2572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" dependencies = [ "bitflags 2.9.1", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-core-foundation", ] @@ -2741,9 +2748,9 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" dependencies = [ "ttf-parser", ] @@ -2772,7 +2779,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.13", + "redox_syscall 0.5.17", "smallvec", "windows-targets 0.52.6", ] @@ -2897,17 +2904,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.8.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.7", - "tracing", - "windows-sys 0.59.0", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] @@ -2945,9 +2951,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] @@ -3075,7 +3081,7 @@ dependencies = [ "built", "cfg-if", "interpolate_name", - "itertools", + "itertools 0.12.1", "libc", "libfuzzer-sys", "log", @@ -3119,9 +3125,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -3129,9 +3135,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -3148,9 +3154,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] @@ -3163,9 +3169,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", @@ -3205,9 +3211,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.51" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" [[package]] name = "ring" @@ -3225,9 +3231,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -3256,22 +3262,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "rustls-pki-types", @@ -3302,9 +3308,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -3400,9 +3406,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -3450,9 +3456,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -3480,9 +3486,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" @@ -3602,9 +3608,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" dependencies = [ "proc-macro2", "quote", @@ -3680,7 +3686,7 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -3704,11 +3710,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.14", ] [[package]] @@ -3724,9 +3730,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", @@ -3781,9 +3787,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -3818,9 +3824,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -3991,9 +3997,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.14" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "untrusted" @@ -4020,9 +4026,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -4165,13 +4171,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.44", + "rustix 1.0.8", "scoped-tls", "smallvec", "wayland-sys", @@ -4179,12 +4185,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.9.1", - "rustix 0.38.44", + "rustix 1.0.8", "wayland-backend", "wayland-scanner", ] @@ -4202,20 +4208,20 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 0.38.44", + "rustix 1.0.8", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.8" +version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -4225,9 +4231,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -4238,9 +4244,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -4251,9 +4257,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", "quick-xml 0.37.5", @@ -4262,9 +4268,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", "log", @@ -4302,7 +4308,7 @@ dependencies = [ "jni", "log", "ndk-context", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-foundation 0.3.1", "url", "web-sys", @@ -4363,7 +4369,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.14", "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-types", @@ -4393,7 +4399,7 @@ dependencies = [ "portable-atomic", "raw-window-handle", "renderdoc-sys", - "thiserror 2.0.12", + "thiserror 2.0.14", "wgpu-types", ] @@ -4407,7 +4413,7 @@ dependencies = [ "bytemuck", "js-sys", "log", - "thiserror 2.0.12", + "thiserror 2.0.14", "web-sys", ] @@ -4588,7 +4594,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -4639,10 +4645,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4844,9 +4851,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winit" -version = "0.30.11" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ "ahash", "android-activity", @@ -4896,9 +4903,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -4923,12 +4930,13 @@ dependencies = [ "egui_extras", "egui_file", "image", + "itertools 0.14.0", "reqwest", "serde", "serde_json", - "thiserror 2.0.12", + "tempfile", + "thiserror 2.0.14", "uuid", - "walkdir", ] [[package]] @@ -5026,9 +5034,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.8.0" +version = "5.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f45e98bc7e6f0988276012797855613cd8269e23b5be62cc4e5d28b7e515d" +checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad" dependencies = [ "async-broadcast", "async-executor", @@ -5083,9 +5091,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.8.0" +version = "5.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c8e4e14dcdd9d97a98b189cd1220f30e8394ad271e8c987da84f73693862c2" +checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -5181,9 +5189,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -5218,9 +5226,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" +checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 54eba04..e1ccb28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,15 @@ image = { version = "0.25.6", features = ["jpeg", "png"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.141" chrono = { version = "0.4.41", features = ["serde"] } -thiserror = "2.0.12" +thiserror = "2.0.14" egui_commonmark = { version = "0.21.1", features = ["embedded_image"] } -walkdir = "2.5.0" 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 = [] diff --git a/PKGBUILD b/PKGBUILD index 18e1cb0..37e3ea1 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,11 +1,9 @@ pkgname=worldcoder pkgver=0.1.1 -pkgrel=1 +pkgrel=3 makedepends=('rust' 'cargo') 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() { cargo fetch --locked --target "$CARCH-unknown-linux-gnu" } diff --git a/src/editors/content_editor.rs b/src/editors/content_editor.rs index edc0f88..a1114a9 100644 --- a/src/editors/content_editor.rs +++ b/src/editors/content_editor.rs @@ -1,34 +1,42 @@ use egui::TextEdit; use egui_commonmark::{CommonMarkCache, CommonMarkViewer}; use serde::{self, Deserialize, Serialize}; -use std::sync::{Arc, Mutex}; use crate::{ PROJECT_FOLDER, editors::{settings_editor::ProjectSettings, tags::Tag}, - llm_integration::content_llm::{ContentAI, ReadyState, ReasoningEffort}, util, }; +#[cfg(feature = "llm")] +use crate::llm_integration::content_llm::{ContentAI, ReadyState}; + pub struct MainEditor { pub content: ContentSection, pub show_editor: bool, pub editor_separate_window: bool, pub show_preview: bool, preview_cache: CommonMarkCache, - dialog: Option, + + #[cfg(feature = "llm")] + dialog: ContentAI, + #[cfg(feature = "llm")] + pub show_ai: bool, } impl Clone for MainEditor { fn clone(&self) -> Self { Self { content: self.content.clone(), - show_editor: self.show_editor, editor_separate_window: self.editor_separate_window, show_preview: self.show_preview, preview_cache: CommonMarkCache::default(), + + #[cfg(feature = "llm")] dialog: self.dialog.clone(), + #[cfg(feature = "llm")] + show_ai: self.show_ai, } } } @@ -108,7 +116,11 @@ impl MainEditor { show_preview: false, editor_separate_window: false, preview_cache: CommonMarkCache::default(), - dialog: None, + + #[cfg(feature = "llm")] + show_ai: false, + #[cfg(feature = "llm")] + dialog: ContentAI::new(String::new()), } } @@ -119,7 +131,11 @@ impl MainEditor { show_preview: false, editor_separate_window: false, preview_cache: CommonMarkCache::default(), - dialog: None, + + #[cfg(feature = "llm")] + show_ai: false, + #[cfg(feature = "llm")] + dialog: ContentAI::new(String::new()), } } @@ -177,6 +193,10 @@ impl MainEditor { // preview toggle ui.checkbox(&mut self.show_preview, "Preview"); + // assistant toggle + #[cfg(feature = "llm")] + ui.checkbox(&mut self.show_ai, "AI Assistant"); + // editor toggle ui.checkbox(&mut self.editor_separate_window, "Pop out editor"); }); @@ -222,10 +242,13 @@ impl MainEditor { ui.separator(); - if let Some(dialog) = &mut self.dialog { - dialog.ui(ui, project); + #[cfg(feature = "llm")] + if self.show_ai { + let dialog = &mut self.dialog; dialog.content = self.content.content.clone(); + dialog.ui(ui, project); + if *dialog.ready.lock().unwrap() == ReadyState::Ready { self.content .content @@ -319,50 +342,16 @@ impl MainEditor { ui.set_min_width(max_width as f32); - let text_edit = TextEdit::multiline(&mut self.content.content) - .id_source("MainEditor_editor") - .font(egui::TextStyle::Monospace) - .interactive(true) - .frame(false) - .lock_focus(true) - .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; - } - } + ui.add( + TextEdit::multiline(&mut self.content.content) + .id_source("MainEditor_editor") + .font(egui::TextStyle::Monospace) + .interactive(true) + .frame(false) + .lock_focus(true) + .hint_text("Type here...") + .desired_width(max_width as f32), + ); }); }); }); diff --git a/src/explorer.rs b/src/explorer.rs index de4c089..ec762ed 100644 --- a/src/explorer.rs +++ b/src/explorer.rs @@ -1,4 +1,7 @@ -use walkdir::{DirEntry, WalkDir}; +use itertools::Itertools; +use std::fs::{self, DirEntry}; + +// use walkdir::{DirEntry, WalkDir}; use crate::{ PROJECT_FOLDER, RightPanelContent, @@ -249,24 +252,22 @@ impl Explorer { }); }) .body(|ui| { - let entries: Vec<_> = WalkDir::new(PROJECT_FOLDER.join("assets")) - .min_depth(1) - .max_depth(1) // Only immediate children - .sort_by(|a, b| { + let entries = fs::read_dir(PROJECT_FOLDER.join("assets")) + .unwrap() + .filter_map(Result::ok) + .sorted_by(|a, b| { // Directories first, then files - let a_is_dir = a.file_type().is_dir(); - let b_is_dir = b.file_type().is_dir(); + let a_is_dir = a.file_type().unwrap().is_dir(); + let b_is_dir = b.file_type().unwrap().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 { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater } }) - .into_iter() - .filter_map(Result::ok) - .collect(); + .collect::>(); for entry in entries { Self::render_entry(ui, to_load, &entry); @@ -275,19 +276,16 @@ impl Explorer { } fn render_entry(ui: &mut egui::Ui, to_load: &mut Option, entry: &DirEntry) { - let file_type = entry.file_type(); + let file_type = entry.file_type().unwrap(); 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(); if is_dir { - let entries: Vec<_> = WalkDir::new(path) - .min_depth(1) - .max_depth(1) - .sort_by(|a, b| a.file_name().cmp(b.file_name())) - .into_iter() + let entries = fs::read_dir(path) + .unwrap() .filter_map(Result::ok) - .collect(); + .collect::>(); egui::collapsing_header::CollapsingState::load_with_default_open( ui.ctx(), diff --git a/src/index.rs b/src/index.rs new file mode 100644 index 0000000..90d8fc3 --- /dev/null +++ b/src/index.rs @@ -0,0 +1,125 @@ +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; + +/// Platform-agnostic file system operations +trait FileSystem { + fn read_file(&self, path: &Path) -> io::Result>; + fn write_file(&self, path: &Path, contents: &[u8]) -> io::Result<()>; + fn create_dir_all(&self, path: &Path) -> io::Result<()>; + fn read_dir(&self, path: &Path) -> io::Result>; + fn exists(&self, path: &Path) -> bool; +} + +/// Native filesystem implementation +#[cfg(feature = "native")] +struct NativeFileSystem; + +#[cfg(feature = "native")] +impl FileSystem for NativeFileSystem { + fn read_file(&self, path: &Path) -> io::Result> { + std::fs::read(path) + } + + fn write_file(&self, path: &Path, contents: &[u8]) -> io::Result<()> { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(path, contents) + } + + fn create_dir_all(&self, path: &Path) -> io::Result<()> { + std::fs::create_dir_all(path) + } + + fn read_dir(&self, path: &Path) -> io::Result> { + Ok(std::fs::read_dir(path)? + .filter_map(Result::ok) + .map(|entry| entry.path()) + .collect()) + } + + fn exists(&self, path: &Path) -> bool { + path.exists() + } +} + +/// Web filesystem implementation +#[cfg(feature = "web")] +struct WebFileSystem; + +#[cfg(feature = "web")] +impl WebFileSystem { + fn new() -> Self { + // Initialize web-specific storage if needed + Self + } +} + +#[cfg(feature = "web")] +impl FileSystem for WebFileSystem { + fn read_file(&self, path: &Path) -> io::Result> { + // In a real implementation, this would use web_sys and IndexedDB + // This is a simplified version that won't actually work + Err(io::Error::new( + io::ErrorKind::Other, + "Web filesystem not implemented", + )) + } + + fn write_file(&self, path: &Path, contents: &[u8]) -> io::Result<()> { + // In a real implementation, this would use web_sys and IndexedDB + // This is a simplified version that won't actually work + Err(io::Error::new( + io::ErrorKind::Other, + "Web filesystem not implemented", + )) + } + + fn create_dir_all(&self, _path: &Path) -> io::Result<()> { + // In web, directories are virtual and created automatically + Ok(()) + } + + fn read_dir(&self, _path: &Path) -> io::Result> { + // In a real implementation, this would list files from IndexedDB + // This is a simplified version that returns an empty list + Ok(Vec::new()) + } + + fn exists(&self, _path: &Path) -> bool { + // In a real implementation, this would check IndexedDB + false + } +} + +#[cfg(feature = "web")] +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use tempfile::tempdir; + + #[test] + fn test_native_fs() { + let temp_dir = tempdir().unwrap(); + let file_path = temp_dir.path().join("test.txt"); + + let fs = NativeFileSystem; + + // Test write and read + let test_data = b"Hello, world!"; + fs.write_file(&file_path, test_data).unwrap(); + let read_data = fs.read_file(&file_path).unwrap(); + assert_eq!(read_data, test_data); + + // Test exists + assert!(fs.exists(&file_path)); + assert!(!fs.exists(&temp_dir.path().join("nonexistent"))); + + // Test create_dir_all and read_dir + let dir_path = temp_dir.path().join("subdir"); + fs.create_dir_all(&dir_path).unwrap(); + let entries = fs.read_dir(temp_dir.path()).unwrap(); + assert_eq!(entries.len(), 2); // Should contain both the file and the subdirectory + } +} diff --git a/src/llm_integration/content_llm.rs b/src/llm_integration/content_llm.rs index 61a0888..6ed00fe 100644 --- a/src/llm_integration/content_llm.rs +++ b/src/llm_integration/content_llm.rs @@ -10,18 +10,50 @@ use crate::editors::settings_editor::ProjectSettings; #[derive(Clone)] pub struct ContentAI { pub open: bool, + + // model input pub content: String, pub instruction: String, - pub max_tokens: usize, pub context_override: String, - pub result: Arc>, - pub ready: Arc>, + pub system_prompt: String, + + // model settings + pub max_tokens: usize, pub temperature: f32, pub reasoning_effort: ReasoningEffort, pub model_override: String, + + // model output + pub reasoning: Arc>, + pub result: Arc>, + pub ready: Arc>, } 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) { let is_open = self.open; @@ -34,203 +66,266 @@ impl ContentAI { } fn ui_output_box(&mut self, ui: &mut egui::Ui, project: &mut ProjectSettings) { - egui::TopBottomPanel::bottom("llm_output") - .resizable(true) - .show_inside(ui, |ui| { - let mut ready_lock = self.ready.lock().unwrap(); + let mut ready_lock = self.ready.lock().unwrap(); - ui.horizontal(|ui| { - if *ready_lock == ReadyState::Generating { - if ui.button("Cancel").clicked() { - *ready_lock = ReadyState::Halted; + ui.horizontal(|ui| { + if *ready_lock == ReadyState::Generating { + if ui.button("Cancel").clicked() { + *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 { - let 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(); + if ui.button("Generate ").clicked() { + continue_content(); + } - 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 - }, - }; + ui.label("Idle"); + } - result.lock().unwrap().clear(); + // show regardless of state + if ui.button("Insert").clicked() { + *ready_lock = ReadyState::Ready; + } - std::thread::spawn(move || { - let result = crate::llm_integration::content_llm::continue_content( - ai_context + "\n" + &context_override, - content, - instruction, - options, - project, - result, - ready.clone(), - ); - if let Err(e) = result { - eprintln!("Error in content generation: {e}"); - } - }); - }; + if ui.button("Clear").clicked() { + self.result.lock().unwrap().clear(); + self.reasoning.lock().unwrap().clear(); + } + }); - if ui.button("Generate ").clicked() { - continue_content(); - } + ui.spacing(); - ui.label("Idle"); - } - - // show regardless of state - if ui.button("Insert").clicked() { - *self.ready.lock().unwrap() = ReadyState::Ready; - } - - if ui.button("Clear").clicked() { - self.result.lock().unwrap().clear(); - } + ui.vertical(|ui| { + egui::TopBottomPanel::top("reasoning_output") + .resizable(true) + .show_inside(ui, |ui| { + egui::ScrollArea::both() + .auto_shrink([false, true]) + .id_salt("reasoning_output") + .max_width(ui.available_width()) + // .max_height(ui.available_height() / 3.0) + .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]) - .id_salt("llm_output") - .max_width(ui.available_width()) - // .max_height(ui.available_height() / 3.0) - .show(ui, |ui| { - ui.add( - egui::TextEdit::multiline(&mut *self.result.lock().unwrap()) - .font(egui::TextStyle::Monospace) - .interactive(false) - .desired_rows(0) - .frame(false) - .desired_width(ui.available_width()) - .lock_focus(true) - .hint_text("Content will appear here..."), - ); - }); - }); + egui::ScrollArea::both() + .auto_shrink([false, false]) + .id_salt("llm_output") + .max_width(ui.available_width()) + .show(ui, |ui| { + ui.add( + egui::TextEdit::multiline(&mut *self.result.lock().unwrap()) + .font(egui::TextStyle::Monospace) + .interactive(false) + .desired_rows(0) + .frame(false) + .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) { { ui.weak("(The model will see current file content)"); - egui::Grid::new("continue_grid") - .num_columns(2) - .striped(true) + egui::CollapsingHeader::new("Settings") + .default_open(true) .show(ui, |ui| { - ui.label("Max Tokens"); - ui.add( - egui::DragValue::new(&mut self.max_tokens) - .range(128..=u32::MAX) - .speed(128), - ); - ui.end_row(); + egui::Grid::new("continue_grid") + .num_columns(2) + .striped(true) + .show(ui, |ui| { + ui.label("Max Tokens"); + ui.add( + egui::DragValue::new(&mut self.max_tokens) + .range(128..=u32::MAX) + .speed(128), + ); + ui.end_row(); - ui.label("Temperature"); - ui.add( - egui::DragValue::new(&mut self.temperature) - .range(0.0..=2.0) - .speed(0.1), - ); + ui.label("Temperature"); + ui.add( + egui::DragValue::new(&mut self.temperature) + .range(0.0..=2.0) + .speed(0.1), + ); - ui.label("Reasoning effort"); + ui.label("Reasoning effort"); - egui::ComboBox::from_id_salt("reasoning_effort") - .selected_text(self.reasoning_effort.to_string()) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.reasoning_effort, - ReasoningEffort::Minimal, - "Minimal", - ); - ui.selectable_value( - &mut self.reasoning_effort, - ReasoningEffort::Low, - "Low", - ); - ui.selectable_value( - &mut self.reasoning_effort, - ReasoningEffort::Medium, - "Medium", - ); - ui.selectable_value( - &mut self.reasoning_effort, - ReasoningEffort::High, - "High", - ); + egui::ComboBox::from_id_salt("reasoning_effort") + .selected_text(self.reasoning_effort.to_string()) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.reasoning_effort, + ReasoningEffort::Minimal, + "Minimal", + ); + ui.selectable_value( + &mut self.reasoning_effort, + ReasoningEffort::Low, + "Low", + ); + ui.selectable_value( + &mut self.reasoning_effort, + ReasoningEffort::Medium, + "Medium", + ); + ui.selectable_value( + &mut self.reasoning_effort, + ReasoningEffort::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"); - ui.add(egui::TextEdit::singleline(&mut self.model_override)); - ui.end_row(); + egui::TopBottomPanel::top("continue_context") + .resizable(true) + .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); - - 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( - context: String, - previous_content: String, - instruction: String, + ai_input: AIInput, + // context: String, + // previous_content: String, + // instruction: String, options: AIOptions, project: ProjectSettings, result: Arc>, + reasoning: Arc>, ready: Arc>, ) -> Result<(), Box> { *ready.lock().unwrap() = ReadyState::Generating; @@ -240,27 +335,15 @@ pub fn continue_content( let messages = vec![ Message { role: "system".to_string(), - content: " - 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(), + content: ai_input.system_prompt, }, Message { role: "user".to_string(), - content: format!("Context / General instructions: {context}"), + content: format!( + " {}\n\n {}\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 { @@ -288,19 +371,20 @@ pub fn continue_content( let response = if let Some(k) = api_key { client - .post(llm_api_uri + "/v1/chat/completions") + .post(llm_api_uri + "/api/v0/chat/completions") .json(&request) .bearer_auth(k) .send()? } else { client - .post(llm_api_uri + "/v1/chat/completions") + .post(llm_api_uri + "/api/v0/chat/completions") .json(&request) .send()? }; println!("success!"); + // println!("response: {}", response.text().unwrap()); let reader = BufReader::new(response); for line in reader.lines() { // initial loop to check if the user has terminated the generation @@ -309,6 +393,7 @@ pub fn continue_content( if *ready == ReadyState::Halted { result.lock().unwrap().clear(); + reasoning.lock().unwrap().clear(); } if *ready != ReadyState::Generating { @@ -324,9 +409,17 @@ pub fn continue_content( if let Some(json) = line.strip_prefix("data: ") { if let Ok(chunk) = serde_json::from_str::(json) { + println!("chunk: {chunk:?}"); + if let Some(content) = chunk.choices[0].delta.content.as_ref() { + println!("content: {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, } +pub struct AIInput { + pub system_prompt: String, + pub user_prompt: String, + pub previous_content: String, + pub structure: Option, +} + #[derive(Debug, PartialEq, Clone, Copy)] pub enum ReadyState { Idle, @@ -351,10 +451,12 @@ pub enum ReadyState { Halted, } -#[derive(Serialize, Copy, Clone, PartialEq)] +#[derive(Serialize, Copy, Clone, PartialEq, Default)] pub enum ReasoningEffort { #[serde(rename = "minimal")] Minimal, + + #[default] #[serde(rename = "low")] Low, #[serde(rename = "medium")] @@ -363,19 +465,13 @@ pub enum ReasoningEffort { High, } -impl Default for ReasoningEffort { - fn default() -> Self { - ReasoningEffort::Low - } -} - -impl ToString for ReasoningEffort { - fn to_string(&self) -> String { +impl std::fmt::Display for ReasoningEffort { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ReasoningEffort::Minimal => "Minimal".to_string(), - ReasoningEffort::Low => "Low".to_string(), - ReasoningEffort::Medium => "Medium".to_string(), - ReasoningEffort::High => "High".to_string(), + ReasoningEffort::Minimal => write!(f, "Minimal"), + ReasoningEffort::Low => write!(f, "Low"), + ReasoningEffort::Medium => write!(f, "Medium"), + ReasoningEffort::High => write!(f, "High"), } } } @@ -413,8 +509,11 @@ struct Delta { #[serde(default)] #[allow(unused)] role: Option, + #[serde(default)] content: Option, + #[serde(default)] + reasoning_content: Option, } #[derive(Serialize, Deserialize, Debug)] diff --git a/src/main.rs b/src/main.rs index 0e415b1..6663a20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,11 @@ use egui::ScrollArea; mod editors; mod explorer; + +#[cfg(feature = "llm")] mod llm_integration; +mod index; mod util; use crate::{