feat: integrate Google semantic search and branded desktop launcher
Core CI / core (ubuntu-latest) (push) Failing after 3m27s
Core CI / core (macos-latest) (push) Canceled after 0s
Core CI / core (windows-latest) (push) Canceled after 0s
Core CI / desktop (ubuntu-latest) (push) Successful in 5m45s
Core CI / desktop (macos-latest) (push) Canceled after 0s
Core CI / desktop (windows-latest) (push) Canceled after 0s
Core CI / core (ubuntu-latest) (push) Failing after 3m27s
Core CI / core (macos-latest) (push) Canceled after 0s
Core CI / core (windows-latest) (push) Canceled after 0s
Core CI / desktop (ubuntu-latest) (push) Successful in 5m45s
Core CI / desktop (macos-latest) (push) Canceled after 0s
Core CI / desktop (windows-latest) (push) Canceled after 0s
This commit is contained in:
1 parent
a90df15503
commit
caf6a2f82e
80 files changed
+14971
-837
No files matched your search
Generated
+630
-3
@@ -79,6 +79,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.104"
|
||||
@@ -535,6 +541,12 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.2"
|
||||
@@ -568,6 +580,43 @@ dependencies = [
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.8"
|
||||
@@ -745,6 +794,68 @@ version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d"
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.205"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8a02bfeba9074b2bbb39492c9b0908ec854f455a3cccdcde7a1de55e2315e40"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxx-build",
|
||||
"cxxbridge-cmd",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"foldhash",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.205"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbc2ac76997619d2d888b58ad79488f1c835de41ca4dd3dd7082acf4ecb0a50f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"indexmap 2.14.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn 3.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-cmd"
|
||||
version = "1.0.205"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a486ed16a3f2a053fe7f5b98db4c1a59846a23e8cb9e49f9d1bd48830d51a2"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codespan-reporting",
|
||||
"indexmap 2.14.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.205"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c72569be94e2283764d334cd285980b747dbe221e27e315484eb6bcd93fa808a"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.205"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "423e795284a7f7b3f843c956a5322c4bc497b60c2dd1a6339da10313cb18d575"
|
||||
dependencies = [
|
||||
"indexmap 2.14.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.24.1"
|
||||
@@ -872,6 +983,15 @@ dependencies = [
|
||||
"crypto-common 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "7.0.0"
|
||||
@@ -1209,6 +1329,16 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.34"
|
||||
@@ -1216,6 +1346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1402,6 +1533,29 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
@@ -1421,9 +1575,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1511,6 +1667,24 @@ version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
|
||||
|
||||
[[package]]
|
||||
name = "global-hotkey"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c386b0a4a70cb2d39fffd74480f985b6f0bfbcb934b6a6b6b7e630e448f242e"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"keyboard-types 0.7.0",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"thiserror 2.0.21",
|
||||
"windows-sys 0.59.0",
|
||||
"x11rb",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.18.0"
|
||||
@@ -1705,6 +1879,22 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.21"
|
||||
@@ -2100,6 +2290,17 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyboard-types"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"serde",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyboard-types"
|
||||
version = "0.8.3"
|
||||
@@ -2179,6 +2380,15 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
@@ -2233,6 +2443,12 @@ dependencies = [
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.39.0"
|
||||
@@ -2315,7 +2531,7 @@ dependencies = [
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
"gtk",
|
||||
"keyboard-types",
|
||||
"keyboard-types 0.8.3",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
@@ -2415,6 +2631,15 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numkong"
|
||||
version = "7.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53bcc7469814b82ddffa194dbf3418fc28fa49015392261926f85c5f1cae0625"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.5"
|
||||
@@ -2941,6 +3166,62 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror 2.0.21",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.4.3",
|
||||
"lru-slab",
|
||||
"rand",
|
||||
"rand_pcg",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.21",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
@@ -2979,6 +3260,15 @@ version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rangemap"
|
||||
version = "1.8.0"
|
||||
@@ -3059,6 +3349,46 @@ version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.5"
|
||||
@@ -3117,6 +3447,20 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.32.1"
|
||||
@@ -3159,12 +3503,53 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -3231,6 +3616,25 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2"
|
||||
|
||||
[[package]]
|
||||
name = "searchi-assets"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"directories",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "searchi-core"
|
||||
version = "0.1.0"
|
||||
@@ -3248,15 +3652,43 @@ dependencies = [
|
||||
name = "searchi-desktop"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"searchi-assets",
|
||||
"searchi-core",
|
||||
"searchi-service",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-global-shortcut",
|
||||
"tauri-plugin-opener",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "searchi-semantic"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "searchi-service"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fs2",
|
||||
"rusqlite",
|
||||
"searchi-core",
|
||||
"searchi-semantic",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"usearch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.38.0"
|
||||
@@ -3381,6 +3813,18 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.24.0"
|
||||
@@ -3618,6 +4062,12 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.9"
|
||||
@@ -3781,7 +4231,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
"reqwest",
|
||||
"reqwest 0.13.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@@ -3922,6 +4372,21 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-global-shortcut"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93ff17919fe09852d269bd37b1d3d2e993b9dbb514afe7acbf3346c1d3627e2d"
|
||||
dependencies = [
|
||||
"global-hotkey",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.7.1"
|
||||
@@ -4066,6 +4531,15 @@ dependencies = [
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
@@ -4166,6 +4640,16 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.20"
|
||||
@@ -4441,6 +4925,18 @@ version = "1.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -4466,6 +4962,17 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usearch"
|
||||
version = "2.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1086fc5e6b4ee87a5bac4fd80fcf66670526ca21b77d5f95b68de01b15329c67"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
"numkong",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
@@ -4701,6 +5208,15 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.39.1"
|
||||
@@ -4898,6 +5414,24 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
@@ -4931,6 +5465,22 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
@@ -4941,7 +5491,7 @@ dependencies = [
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
@@ -4972,6 +5522,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
@@ -4984,6 +5540,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
@@ -4996,12 +5558,24 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
@@ -5014,6 +5588,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
@@ -5026,6 +5606,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
@@ -5038,6 +5624,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
@@ -5050,6 +5642,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
@@ -5161,6 +5759,29 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"rustix",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb-protocol"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "xkeysym"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.3"
|
||||
@@ -5295,6 +5916,12 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.5"
|
||||
|
||||
@@ -17,8 +17,10 @@ tauri-build = { version = "2", features = [] }
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-global-shortcut = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
searchi-core = { path = "../../../crates/searchi-core" }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
tempfile = "3"
|
||||
searchi-core = { path = "../../../crates/searchi-core" }
|
||||
searchi-service = { path = "../../../crates/searchi-service" }
|
||||
searchi-assets = { path = "../../../crates/searchi-assets" }
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023-2026 The ggml authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,3 @@
|
||||
Searchi's native Google embedding worker is staged here during application builds.
|
||||
The installer ships that worker; Google model files are automatically verified
|
||||
and downloaded to the local model cache on first launch when not bundled.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) Inter-Italic[opsz,wght].ttf: Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,21 +1,75 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod model_download;
|
||||
mod semantic_commands;
|
||||
mod settings;
|
||||
|
||||
use searchi_core::{IndexReport, IndexStats, SearchIndex, SearchOptions, SearchResult};
|
||||
use serde::Deserialize;
|
||||
use semantic_commands::{
|
||||
hybrid_search, index_embeddings, semantic_status, setup_model, SemanticState,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
use tauri::{Manager, State};
|
||||
use tauri::{Emitter, Manager, State};
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
use tauri_plugin_global_shortcut::{GlobalShortcutExt, ShortcutState};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
struct AppState {
|
||||
database: PathBuf,
|
||||
lexical: Arc<Mutex<SearchIndex>>,
|
||||
indexing: Arc<AtomicBool>,
|
||||
settings: Arc<Mutex<SettingsStore>>,
|
||||
shortcut_status: String,
|
||||
semantic: Arc<SemanticState>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DesktopStatus {
|
||||
settings: Settings,
|
||||
indexing: bool,
|
||||
shortcut_status: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn desktop_status(state: State<'_, AppState>) -> Result<DesktopStatus, String> {
|
||||
Ok(DesktopStatus {
|
||||
settings: state
|
||||
.settings
|
||||
.lock()
|
||||
.map_err(|_| "Settings are unavailable.")?
|
||||
.value
|
||||
.clone(),
|
||||
indexing: state.indexing.load(Ordering::Acquire),
|
||||
shortcut_status: state.shortcut_status.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn set_cpu_threads(threads: usize, state: State<'_, AppState>) -> Result<Settings, String> {
|
||||
if state.indexing.load(Ordering::Acquire) {
|
||||
return Err("Wait for indexing to finish before changing resource settings.".into());
|
||||
}
|
||||
let mut store = state
|
||||
.settings
|
||||
.lock()
|
||||
.map_err(|_| "Settings are unavailable.")?;
|
||||
let mut next = store.value.clone();
|
||||
next.cpu_threads = threads;
|
||||
next.validate()?;
|
||||
state.semantic.reset_session()?;
|
||||
store.save(next)?;
|
||||
state
|
||||
.semantic
|
||||
.begin_automatic_setup(state.database.clone(), threads);
|
||||
Ok(store.value.clone())
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
@@ -48,12 +102,12 @@ async fn search(
|
||||
filters: Option<Filters>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<SearchResult>, String> {
|
||||
let database = state.database.clone();
|
||||
let lexical = state.lexical.clone();
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
SearchIndex::open(database)
|
||||
.and_then(|index| {
|
||||
index.search_with_options(&query, 50, &filters.unwrap_or_default().into())
|
||||
})
|
||||
lexical
|
||||
.try_lock()
|
||||
.map_err(|_| "Keyword worker is busy; try the latest query again.".to_owned())?
|
||||
.search_with_options(&query, 50, &filters.unwrap_or_default().into())
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
@@ -150,11 +204,23 @@ async fn index_folder(root: String, state: State<'_, AppState>) -> Result<IndexR
|
||||
}
|
||||
let guard = IndexGuard(state.indexing.clone());
|
||||
let database = state.database.clone();
|
||||
let settings = state.settings.clone();
|
||||
let lexical = state.lexical.clone();
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let _guard = guard;
|
||||
SearchIndex::open(database)
|
||||
.and_then(|mut index| index.index_folder(root))
|
||||
.map_err(|e| e.to_string())
|
||||
settings
|
||||
.lock()
|
||||
.map_err(|_| "Settings are unavailable.")?
|
||||
.add_root(std::path::Path::new(&root))?;
|
||||
let mut index = SearchIndex::open(database).map_err(|error| error.to_string())?;
|
||||
let report = index
|
||||
.index_folder(root)
|
||||
.map_err(|error| error.to_string())?;
|
||||
index.prewarm().map_err(|error| error.to_string())?;
|
||||
*lexical
|
||||
.lock()
|
||||
.map_err(|_| "Keyword engine is unavailable.")? = index;
|
||||
Ok(report)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
@@ -164,14 +230,78 @@ fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_global_shortcut::Builder::new().with_handler(|app, _, event| {
|
||||
if event.state == ShortcutState::Pressed {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let result = window.show().and_then(|_| window.unminimize()).and_then(|_| window.set_focus());
|
||||
if let Err(error) = result { eprintln!("Could not focus Searchi: {error}"); }
|
||||
let _ = window.emit("focus-search", ());
|
||||
}
|
||||
}
|
||||
}).build())
|
||||
.setup(|app| {
|
||||
let directory = app.path().app_data_dir()?;
|
||||
std::fs::create_dir_all(&directory)?;
|
||||
let database = directory.join("searchi.sqlite");
|
||||
SearchIndex::open(&database)?;
|
||||
let lexical = Arc::new(Mutex::new(SearchIndex::open(&database)?));
|
||||
let warm_database = database.clone();
|
||||
let warm_target = lexical.clone();
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
// Prepare a separate reader and swap it in briefly; foreground
|
||||
// queries never wait on the long statistics-preparation lock.
|
||||
if let Ok(mut index) = SearchIndex::open(warm_database) {
|
||||
if index.prewarm().is_ok() {
|
||||
if let Ok(mut current) = warm_target.lock() { *current = index; }
|
||||
}
|
||||
}
|
||||
});
|
||||
let settings = SettingsStore::load(directory.join("settings.json")).map_err(std::io::Error::other)?;
|
||||
let development_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../..");
|
||||
let development_models = development_root.join(".searchi/models/embeddinggemma2");
|
||||
let bundled_models = app.path().resource_dir()?.join("embeddinggemma2");
|
||||
let model_directory = settings.value.model_directory.clone().unwrap_or_else(|| {
|
||||
if cfg!(debug_assertions) && development_models.is_dir() {
|
||||
development_models.canonicalize().unwrap_or(development_models)
|
||||
} else if bundled_models.is_dir() {
|
||||
bundled_models
|
||||
} else {
|
||||
searchi_assets::default_model_directory().unwrap_or_else(|_| directory.join("Models"))
|
||||
}
|
||||
});
|
||||
let worker_name = if cfg!(windows) { "searchi-gemma-worker.exe" } else { "searchi-gemma-worker" };
|
||||
let development_worker = development_root.join(".searchi/runtime/cpu")
|
||||
.join(if cfg!(windows) { "Release" } else { "" }).join(worker_name);
|
||||
let worker = std::env::var_os("SEARCHI_EMBEDDING_WORKER").map(PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
if cfg!(debug_assertions) && development_worker.is_file() {
|
||||
development_worker.canonicalize().unwrap_or(development_worker)
|
||||
} else {
|
||||
app.path().resource_dir().unwrap_or_else(|_| directory.clone()).join(worker_name)
|
||||
}
|
||||
});
|
||||
let semantic = Arc::new(SemanticState::new(model_directory, worker));
|
||||
semantic.begin_automatic_setup(database.clone(), settings.value.cpu_threads);
|
||||
#[cfg(target_os = "macos")]
|
||||
let shortcut = "Shift+Super+Space";
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let shortcut = "Control+Shift+Space";
|
||||
let wayland = cfg!(target_os = "linux") &&
|
||||
(std::env::var("XDG_SESSION_TYPE").is_ok_and(|value| value == "wayland") || std::env::var_os("WAYLAND_DISPLAY").is_some());
|
||||
let shortcut_status = if wayland {
|
||||
"Global shortcut unavailable on Wayland. Open Searchi from your application launcher.".into()
|
||||
} else {
|
||||
match app.global_shortcut().register(shortcut) {
|
||||
Ok(()) => format!("Global shortcut: {shortcut}"),
|
||||
Err(error) => format!("Global shortcut unavailable: {error}"),
|
||||
}
|
||||
};
|
||||
app.manage(AppState {
|
||||
database,
|
||||
lexical,
|
||||
indexing: Arc::new(AtomicBool::new(false)),
|
||||
settings: Arc::new(Mutex::new(settings)),
|
||||
shortcut_status,
|
||||
semantic,
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
@@ -180,7 +310,13 @@ fn main() {
|
||||
stats,
|
||||
index_folder,
|
||||
pick_folder,
|
||||
open_result
|
||||
open_result,
|
||||
desktop_status,
|
||||
set_cpu_threads,
|
||||
semantic_status,
|
||||
setup_model,
|
||||
index_embeddings,
|
||||
hybrid_search
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("Searchi desktop could not start");
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
//! Thin desktop adapter over the GUI-free, shared model provisioner.
|
||||
pub use searchi_assets::TOTAL_BYTES;
|
||||
use std::{path::Path, sync::atomic::AtomicU64};
|
||||
|
||||
pub fn provision(directory: &Path, progress: &AtomicU64) -> Result<(), String> {
|
||||
searchi_assets::provision(directory, progress).map_err(|error| format!("{error:#}"))
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
//! Desktop-owned orchestration: managed model setup and persistent hybrid queries.
|
||||
use crate::{model_download, AppState, Filters};
|
||||
use searchi_core::SearchResult;
|
||||
use searchi_service::{EmbeddingReport, HybridSearch};
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
Mutex,
|
||||
},
|
||||
};
|
||||
use tauri::State;
|
||||
|
||||
pub struct SemanticState {
|
||||
pub model_directory: PathBuf,
|
||||
pub worker: PathBuf,
|
||||
verified: AtomicBool,
|
||||
downloading: AtomicBool,
|
||||
embedding: AtomicBool,
|
||||
querying: AtomicBool,
|
||||
warmed: AtomicBool,
|
||||
warming: AtomicBool,
|
||||
downloaded_bytes: AtomicU64,
|
||||
error: Mutex<Option<String>>,
|
||||
service: Mutex<Option<HybridSearch>>,
|
||||
}
|
||||
|
||||
impl SemanticState {
|
||||
pub fn new(model_directory: PathBuf, worker: PathBuf) -> Self {
|
||||
Self {
|
||||
model_directory,
|
||||
worker,
|
||||
verified: AtomicBool::new(false),
|
||||
downloading: AtomicBool::new(false),
|
||||
embedding: AtomicBool::new(false),
|
||||
querying: AtomicBool::new(false),
|
||||
warmed: AtomicBool::new(false),
|
||||
warming: AtomicBool::new(false),
|
||||
downloaded_bytes: AtomicU64::new(0),
|
||||
error: Mutex::new(None),
|
||||
service: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_session(&self) -> Result<(), String> {
|
||||
*self
|
||||
.service
|
||||
.try_lock()
|
||||
.map_err(|_| "Wait for the current semantic query before changing resources.")? = None;
|
||||
self.warmed.store(false, Ordering::Release);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_query_session(&self, database: PathBuf, threads: usize) -> Result<(), String> {
|
||||
self.warming.store(true, Ordering::Release);
|
||||
let _guard = WorkGuard(&self.warming);
|
||||
if !self.worker.is_file() {
|
||||
return Err(
|
||||
"The installed embedding worker is missing. Repair or reinstall Searchi.".into(),
|
||||
);
|
||||
}
|
||||
let mut service =
|
||||
HybridSearch::open(database, &self.model_directory, &self.worker, threads)
|
||||
.map_err(|error| error.to_string())?;
|
||||
service.warmup().map_err(|error| error.to_string())?;
|
||||
*self
|
||||
.service
|
||||
.lock()
|
||||
.map_err(|_| "Semantic session is unavailable.")? = Some(service);
|
||||
self.warmed.store(true, Ordering::Release);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// First-launch setup is authorized product behavior; inference remains offline.
|
||||
pub fn begin_automatic_setup(self: &std::sync::Arc<Self>, database: PathBuf, threads: usize) {
|
||||
if self
|
||||
.downloading
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let semantic = self.clone();
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let _guard = WorkGuard(&semantic.downloading);
|
||||
semantic.record_error(None);
|
||||
let result =
|
||||
model_download::provision(&semantic.model_directory, &semantic.downloaded_bytes);
|
||||
semantic.verified.store(result.is_ok(), Ordering::Release);
|
||||
let result = result.and_then(|_| semantic.prepare_query_session(database, threads));
|
||||
semantic.record_error(result.err());
|
||||
});
|
||||
}
|
||||
|
||||
fn record_error(&self, value: Option<String>) {
|
||||
if let Ok(mut error) = self.error.lock() {
|
||||
*error = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct SemanticStatus {
|
||||
model: &'static str,
|
||||
ready: bool,
|
||||
model_directory: String,
|
||||
worker_ready: bool,
|
||||
downloading: bool,
|
||||
embedding: bool,
|
||||
device_policy: &'static str,
|
||||
error: Option<String>,
|
||||
downloaded_bytes: u64,
|
||||
total_bytes: u64,
|
||||
generation: Option<i64>,
|
||||
chunks: usize,
|
||||
stale: bool,
|
||||
pending: usize,
|
||||
warming: bool,
|
||||
worker_info: Option<searchi_service::WorkerInfo>,
|
||||
}
|
||||
|
||||
struct WorkGuard<'a>(&'a AtomicBool);
|
||||
impl Drop for WorkGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0.store(false, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn semantic_status(state: State<'_, AppState>) -> Result<SemanticStatus, String> {
|
||||
let semantic = &state.semantic;
|
||||
let worker_ready = semantic.worker.is_file();
|
||||
let index =
|
||||
HybridSearch::embedding_status(&state.database).map_err(|error| error.to_string())?;
|
||||
Ok(SemanticStatus {
|
||||
model: "Google EmbeddingGemma 2",
|
||||
ready: worker_ready && semantic.warmed.load(Ordering::Acquire),
|
||||
model_directory: semantic.model_directory.to_string_lossy().into_owned(),
|
||||
worker_ready,
|
||||
downloading: semantic.downloading.load(Ordering::Acquire),
|
||||
embedding: semantic.embedding.load(Ordering::Acquire),
|
||||
device_policy: if std::env::var("SEARCHI_FORCE_CPU").as_deref() == Ok("1") {
|
||||
"cpu"
|
||||
} else {
|
||||
"auto"
|
||||
},
|
||||
error: semantic.error.lock().ok().and_then(|value| value.clone()),
|
||||
downloaded_bytes: semantic.downloaded_bytes.load(Ordering::Acquire),
|
||||
total_bytes: model_download::TOTAL_BYTES,
|
||||
generation: index.generation,
|
||||
chunks: index.chunks,
|
||||
stale: index.stale,
|
||||
pending: index.pending,
|
||||
warming: semantic.warming.load(Ordering::Acquire),
|
||||
worker_info: semantic
|
||||
.service
|
||||
.try_lock()
|
||||
.ok()
|
||||
.and_then(|service| service.as_ref().and_then(HybridSearch::worker_info)),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn setup_model(state: State<'_, AppState>) -> Result<(), String> {
|
||||
let semantic = state.semantic.clone();
|
||||
let database = state.database.clone();
|
||||
let threads = state
|
||||
.settings
|
||||
.lock()
|
||||
.map_err(|_| "Settings are unavailable.")?
|
||||
.value
|
||||
.cpu_threads;
|
||||
if semantic
|
||||
.downloading
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Google model setup is already running.".into());
|
||||
}
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let _guard = WorkGuard(&semantic.downloading);
|
||||
semantic.record_error(None);
|
||||
let result =
|
||||
model_download::provision(&semantic.model_directory, &semantic.downloaded_bytes);
|
||||
semantic.verified.store(result.is_ok(), Ordering::Release);
|
||||
let result = result.and_then(|_| semantic.prepare_query_session(database, threads));
|
||||
semantic.record_error(result.as_ref().err().cloned());
|
||||
result
|
||||
})
|
||||
.await
|
||||
.map_err(|error| error.to_string())?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn index_embeddings(state: State<'_, AppState>) -> Result<EmbeddingReport, String> {
|
||||
let semantic = state.semantic.clone();
|
||||
if !semantic.verified.load(Ordering::Acquire) || !semantic.worker.is_file() {
|
||||
return Err("Set up the Google model and native worker first.".into());
|
||||
}
|
||||
let threads = state
|
||||
.settings
|
||||
.lock()
|
||||
.map_err(|_| "Settings are unavailable.")?
|
||||
.value
|
||||
.cpu_threads;
|
||||
if semantic
|
||||
.embedding
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Semantic indexing is already running.".into());
|
||||
}
|
||||
let database = state.database.clone();
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let _guard = WorkGuard(&semantic.embedding);
|
||||
semantic.record_error(None);
|
||||
// Indexing uses its own isolated worker; interactive queries keep their own session.
|
||||
let result = HybridSearch::open(
|
||||
database,
|
||||
&semantic.model_directory,
|
||||
&semantic.worker,
|
||||
threads,
|
||||
)
|
||||
.and_then(|mut service| service.index_embeddings())
|
||||
.map_err(|error| error.to_string());
|
||||
semantic.record_error(result.as_ref().err().cloned());
|
||||
result
|
||||
})
|
||||
.await
|
||||
.map_err(|error| error.to_string())?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn hybrid_search(
|
||||
query: String,
|
||||
filters: Option<Filters>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<SearchResult>, String> {
|
||||
let semantic = state.semantic.clone();
|
||||
if !semantic.warmed.load(Ordering::Acquire) {
|
||||
return Err(
|
||||
"Semantic search is preparing in the background; keyword results are available.".into(),
|
||||
);
|
||||
}
|
||||
let database = state.database.clone();
|
||||
let threads = state
|
||||
.settings
|
||||
.lock()
|
||||
.map_err(|_| "Settings are unavailable.")?
|
||||
.value
|
||||
.cpu_threads;
|
||||
if semantic
|
||||
.querying
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_err()
|
||||
{
|
||||
return Err("Semantic worker is busy; keyword results remain available.".into());
|
||||
}
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let _guard = WorkGuard(&semantic.querying);
|
||||
let mut cached = semantic
|
||||
.service
|
||||
.lock()
|
||||
.map_err(|_| "Semantic session is unavailable.")?;
|
||||
if cached.is_none() {
|
||||
*cached = Some(
|
||||
HybridSearch::open(
|
||||
database,
|
||||
&semantic.model_directory,
|
||||
&semantic.worker,
|
||||
threads,
|
||||
)
|
||||
.map_err(|error| error.to_string())?,
|
||||
);
|
||||
}
|
||||
cached
|
||||
.as_mut()
|
||||
.ok_or("Semantic session is unavailable.")?
|
||||
.search(&query, 50, &filters.unwrap_or_default().into())
|
||||
.map_err(|error| error.to_string())
|
||||
})
|
||||
.await
|
||||
.map_err(|error| error.to_string())?
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
//! Versioned local settings. Writes replace the destination atomically; failed
|
||||
//! saves leave both the previous file and in-memory settings intact.
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fs,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
const SETTINGS_VERSION: u32 = 1;
|
||||
const MAX_SETTINGS_BYTES: u64 = 128 * 1024;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Settings {
|
||||
pub version: u32,
|
||||
pub roots: Vec<PathBuf>,
|
||||
pub cpu_threads: usize,
|
||||
/// Managed local Google EmbeddingGemma2 artifacts. Older settings default
|
||||
/// to unconfigured; model acquisition is an explicit desktop action.
|
||||
#[serde(default)]
|
||||
pub model_directory: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: SETTINGS_VERSION,
|
||||
roots: Vec::new(),
|
||||
cpu_threads: 1,
|
||||
model_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
if self.version != SETTINGS_VERSION {
|
||||
return Err(format!("Unsupported settings version {}", self.version));
|
||||
}
|
||||
if !(1..=8).contains(&self.cpu_threads) {
|
||||
return Err("CPU threads must be between 1 and 8.".into());
|
||||
}
|
||||
if self.roots.len() > 64 {
|
||||
return Err("At most 64 indexed folders are supported.".into());
|
||||
}
|
||||
if let Some(directory) = &self.model_directory {
|
||||
if !directory.is_absolute() || directory.to_string_lossy().len() > 32768 {
|
||||
return Err("Model directory must be an absolute bounded path.".into());
|
||||
}
|
||||
}
|
||||
for (i, root) in self.roots.iter().enumerate() {
|
||||
if !root.is_absolute() {
|
||||
return Err("Indexed folder paths must be absolute.".into());
|
||||
}
|
||||
if root.to_string_lossy().len() > 32768 {
|
||||
return Err("Indexed folder path is too long.".into());
|
||||
}
|
||||
if self.roots[..i].contains(root) {
|
||||
return Err("Indexed folder paths must be unique.".into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SettingsStore {
|
||||
path: PathBuf,
|
||||
pub value: Settings,
|
||||
}
|
||||
|
||||
impl SettingsStore {
|
||||
pub fn load(path: PathBuf) -> Result<Self, String> {
|
||||
let value = match fs::metadata(&path) {
|
||||
Ok(metadata) => {
|
||||
if metadata.len() > MAX_SETTINGS_BYTES {
|
||||
return Err("Settings file exceeds the size limit.".into());
|
||||
}
|
||||
serde_json::from_slice(&fs::read(&path).map_err(|e| e.to_string())?)
|
||||
.map_err(|e| format!("Invalid settings file: {e}"))?
|
||||
}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Settings::default(),
|
||||
Err(error) => return Err(error.to_string()),
|
||||
};
|
||||
let store = Self { path, value };
|
||||
store.value.validate()?;
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
pub fn save(&mut self, next: Settings) -> Result<(), String> {
|
||||
next.validate()?;
|
||||
let parent = self.path.parent().ok_or("Settings path has no parent.")?;
|
||||
fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||
let mut temporary = tempfile::NamedTempFile::new_in(parent).map_err(|e| e.to_string())?;
|
||||
let bytes = serde_json::to_vec_pretty(&next).map_err(|e| e.to_string())?;
|
||||
if bytes.len() as u64 > MAX_SETTINGS_BYTES {
|
||||
return Err("Settings exceed the size limit.".into());
|
||||
}
|
||||
temporary.write_all(&bytes).map_err(|e| e.to_string())?;
|
||||
temporary.as_file().sync_all().map_err(|e| e.to_string())?;
|
||||
temporary.persist(&self.path).map_err(|e| e.to_string())?;
|
||||
self.value = next;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_root(&mut self, root: &Path) -> Result<(), String> {
|
||||
let canonical = fs::canonicalize(root).map_err(|e| e.to_string())?;
|
||||
if !canonical.is_dir() {
|
||||
return Err("Indexed folder must be a directory.".into());
|
||||
}
|
||||
let mut next = self.value.clone();
|
||||
if !next.roots.contains(&canonical) {
|
||||
next.roots.push(canonical);
|
||||
self.save(next)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn settings_round_trip_and_atomic_replacement() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("settings.json");
|
||||
let mut store = SettingsStore::load(path.clone()).unwrap();
|
||||
assert_eq!(store.value.cpu_threads, 1);
|
||||
store.add_root(directory.path()).unwrap();
|
||||
store.add_root(directory.path()).unwrap();
|
||||
let mut next = store.value.clone();
|
||||
next.cpu_threads = 2;
|
||||
store.save(next.clone()).unwrap();
|
||||
assert_eq!(SettingsStore::load(path).unwrap().value, next);
|
||||
assert_eq!(store.value.roots.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_save_preserves_previous_file_and_memory() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("settings.json");
|
||||
let mut store = SettingsStore::load(path.clone()).unwrap();
|
||||
store.save(Settings::default()).unwrap();
|
||||
let original = fs::read(&path).unwrap();
|
||||
let mut invalid = store.value.clone();
|
||||
invalid.cpu_threads = 0;
|
||||
assert!(store.save(invalid).is_err());
|
||||
assert_eq!(store.value, Settings::default());
|
||||
assert_eq!(fs::read(&path).unwrap(), original);
|
||||
let mut invalid = store.value.clone();
|
||||
invalid.roots.push(PathBuf::from("relative"));
|
||||
assert!(store.save(invalid).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_corruption_unknown_version_and_excessive_threads() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("settings.json");
|
||||
fs::write(&path, "broken").unwrap();
|
||||
assert!(SettingsStore::load(path.clone()).is_err());
|
||||
fs::write(&path, r#"{"version":99,"roots":[],"cpu_threads":1}"#).unwrap();
|
||||
assert!(SettingsStore::load(path).is_err());
|
||||
let invalid = Settings {
|
||||
cpu_threads: 9,
|
||||
..Settings::default()
|
||||
};
|
||||
assert!(invalid.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loads_older_settings_with_unconfigured_google_model() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("settings.json");
|
||||
fs::write(&path, r#"{"version":1,"roots":[],"cpu_threads":1}"#).unwrap();
|
||||
let store = SettingsStore::load(path).unwrap();
|
||||
assert!(store.value.model_directory.is_none());
|
||||
let invalid = Settings {
|
||||
model_directory: Some(PathBuf::from("relative")),
|
||||
..Settings::default()
|
||||
};
|
||||
assert!(invalid.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oversized_save_leaves_existing_settings_readable() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("settings.json");
|
||||
let mut store = SettingsStore::load(path.clone()).unwrap();
|
||||
store.save(Settings::default()).unwrap();
|
||||
let mut oversized = store.value.clone();
|
||||
oversized.roots = (0..64)
|
||||
.map(|i| directory.path().join(format!("{i}{}", "x".repeat(3000))))
|
||||
.collect();
|
||||
assert!(store.save(oversized).is_err());
|
||||
assert_eq!(
|
||||
SettingsStore::load(path).unwrap().value,
|
||||
Settings::default()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Searchi",
|
||||
"productName": "searchi",
|
||||
"version": "0.1.0",
|
||||
"identifier": "dev.searchi.desktop",
|
||||
"build": {
|
||||
@@ -12,7 +12,7 @@
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Searchi",
|
||||
"title": "searchi",
|
||||
"width": 1080,
|
||||
"height": 760,
|
||||
"minWidth": 680,
|
||||
@@ -23,5 +23,15 @@
|
||||
"csp": "default-src 'self'; connect-src ipc: http://ipc.localhost; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
||||
}
|
||||
},
|
||||
"bundle": { "active": false }
|
||||
"bundle": {
|
||||
"active": false,
|
||||
"resources": { "resources/": "" },
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/[email protected]",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user