feat: finalize image search workflow and release v0.76.0

This commit is contained in:
coso
2026-03-02 02:30:38 +08:00
parent 5cd3eda653
commit dabb5c87b6
88 changed files with 10474 additions and 2272 deletions
+515 -21
View File
@@ -2,6 +2,163 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "actix-codec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
dependencies = [
"bitflags 2.10.0",
"bytes",
"futures-core",
"futures-sink",
"memchr",
"pin-project-lite",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "actix-http"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e"
dependencies = [
"actix-codec",
"actix-rt",
"actix-service",
"actix-utils",
"bitflags 2.10.0",
"bytes",
"bytestring",
"derive_more 2.1.1",
"encoding_rs",
"foldhash 0.1.5",
"futures-core",
"http 0.2.12",
"httparse",
"httpdate",
"itoa",
"language-tags",
"mime",
"percent-encoding",
"pin-project-lite",
"smallvec",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "actix-router"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7"
dependencies = [
"bytestring",
"cfg-if",
"http 0.2.12",
"regex-lite",
"serde",
"tracing",
]
[[package]]
name = "actix-rt"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63"
dependencies = [
"futures-core",
"tokio",
]
[[package]]
name = "actix-server"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502"
dependencies = [
"actix-rt",
"actix-service",
"actix-utils",
"futures-core",
"futures-util",
"mio 1.1.1",
"socket2 0.5.10",
"tokio",
"tracing",
]
[[package]]
name = "actix-service"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f"
dependencies = [
"futures-core",
"pin-project-lite",
]
[[package]]
name = "actix-utils"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8"
dependencies = [
"local-waker",
"pin-project-lite",
]
[[package]]
name = "actix-web"
version = "4.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf"
dependencies = [
"actix-codec",
"actix-http",
"actix-router",
"actix-rt",
"actix-server",
"actix-service",
"actix-utils",
"bytes",
"bytestring",
"cfg-if",
"derive_more 2.1.1",
"encoding_rs",
"foldhash 0.1.5",
"futures-core",
"futures-util",
"impl-more",
"itoa",
"language-tags",
"log",
"mime",
"once_cell",
"pin-project-lite",
"regex-lite",
"serde",
"serde_json",
"serde_urlencoded",
"smallvec",
"socket2 0.6.2",
"time",
"tracing",
"url",
]
[[package]]
name = "addr2line"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.1"
@@ -1083,6 +1240,21 @@ dependencies = [
"tower-service",
]
[[package]]
name = "backtrace"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-link 0.2.1",
]
[[package]]
name = "base64"
version = "0.21.7"
@@ -1518,6 +1690,15 @@ dependencies = [
"either",
]
[[package]]
name = "bytestring"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289"
dependencies = [
"bytes",
]
[[package]]
name = "bzip2"
version = "0.4.4"
@@ -1923,6 +2104,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "convert_case"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cookie"
version = "0.18.1"
@@ -2416,6 +2606,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "debugid"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
dependencies = [
"serde",
"uuid",
]
[[package]]
name = "der"
version = "0.7.10"
@@ -2474,13 +2674,36 @@ version = "0.99.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
dependencies = [
"convert_case",
"convert_case 0.4.0",
"proc-macro2",
"quote",
"rustc_version 0.4.1",
"syn 2.0.114",
]
[[package]]
name = "derive_more"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case 0.10.0",
"proc-macro2",
"quote",
"rustc_version 0.4.1",
"syn 2.0.114",
"unicode-xid",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -3030,6 +3253,18 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "findshlibs"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64"
dependencies = [
"cc",
"lazy_static",
"libc",
"winapi",
]
[[package]]
name = "fixedbitset"
version = "0.5.7"
@@ -3503,6 +3738,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "gimli"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "gio"
version = "0.18.4"
@@ -4085,7 +4326,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.57.0",
"windows-core 0.62.2",
]
[[package]]
@@ -4273,6 +4514,12 @@ dependencies = [
"tiff",
]
[[package]]
name = "impl-more"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
[[package]]
name = "include_dir"
version = "0.7.4"
@@ -4665,6 +4912,12 @@ dependencies = [
"selectors 0.24.0",
]
[[package]]
name = "language-tags"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -4818,6 +5071,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "local-waker"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -5422,7 +5681,7 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
dependencies = [
"proc-macro-crate 2.0.2",
"proc-macro-crate 3.4.0",
"proc-macro2",
"quote",
"syn 2.0.114",
@@ -5609,6 +5868,16 @@ dependencies = [
"objc2-foundation 0.3.2",
]
[[package]]
name = "objc2-core-location"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
dependencies = [
"objc2 0.6.3",
"objc2-foundation 0.3.2",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
@@ -5750,8 +6019,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags 2.10.0",
"block2 0.6.2",
"objc2 0.6.3",
"objc2-cloud-kit",
"objc2-core-data 0.3.2",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image 0.3.2",
"objc2-core-location",
"objc2-core-text",
"objc2-foundation 0.3.2",
"objc2-quartz-core 0.3.2",
"objc2-user-notifications",
]
[[package]]
name = "objc2-user-notifications"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
dependencies = [
"objc2 0.6.3",
"objc2-foundation 0.3.2",
]
@@ -5771,6 +6059,15 @@ dependencies = [
"objc2-security",
]
[[package]]
name = "object"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
[[package]]
name = "oboe"
version = "0.6.1"
@@ -6003,6 +6300,22 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "os_info"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
dependencies = [
"android_system_properties",
"log",
"nix 0.30.1",
"objc2 0.6.3",
"objc2-foundation 0.3.2",
"objc2-ui-kit",
"serde",
"windows-sys 0.61.2",
]
[[package]]
name = "os_pipe"
version = "1.2.3"
@@ -6677,7 +6990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
dependencies = [
"anyhow",
"itertools 0.13.0",
"itertools 0.14.0",
"proc-macro2",
"quote",
"syn 2.0.114",
@@ -6685,7 +6998,7 @@ dependencies = [
[[package]]
name = "proxycast"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"anyhow",
"arboard",
@@ -6745,6 +7058,7 @@ dependencies = [
"rusqlite",
"rustls-pemfile 2.2.0",
"scopeguard",
"sentry",
"serde",
"serde_json",
"serde_urlencoded",
@@ -6785,7 +7099,7 @@ dependencies = [
[[package]]
name = "proxycast-agent"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"aster-core",
"async-trait",
@@ -6809,7 +7123,7 @@ dependencies = [
[[package]]
name = "proxycast-config"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"async-trait",
"parking_lot",
@@ -6825,7 +7139,7 @@ dependencies = [
[[package]]
name = "proxycast-core"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"aster-models",
"async-trait",
@@ -6865,7 +7179,7 @@ dependencies = [
[[package]]
name = "proxycast-credential"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"axum 0.7.9",
"base64 0.22.1",
@@ -6900,7 +7214,7 @@ dependencies = [
[[package]]
name = "proxycast-infra"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"chrono",
"dashmap 5.5.3",
@@ -6920,7 +7234,7 @@ dependencies = [
[[package]]
name = "proxycast-mcp"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"async-trait",
"glob",
@@ -6951,7 +7265,7 @@ dependencies = [
[[package]]
name = "proxycast-processor"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"async-trait",
"parking_lot",
@@ -6970,7 +7284,7 @@ dependencies = [
[[package]]
name = "proxycast-providers"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"anyhow",
"async-stream",
@@ -7022,7 +7336,7 @@ dependencies = [
[[package]]
name = "proxycast-server"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"async-stream",
"axum 0.7.9",
@@ -7065,7 +7379,7 @@ dependencies = [
[[package]]
name = "proxycast-server-utils"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"axum 0.7.9",
"futures",
@@ -7080,7 +7394,7 @@ dependencies = [
[[package]]
name = "proxycast-services"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"anyhow",
"aster-core",
@@ -7121,7 +7435,7 @@ dependencies = [
[[package]]
name = "proxycast-skills"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"async-trait",
"dirs 5.0.1",
@@ -7137,7 +7451,7 @@ dependencies = [
[[package]]
name = "proxycast-terminal"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"async-trait",
"base64 0.22.1",
@@ -7164,7 +7478,7 @@ dependencies = [
[[package]]
name = "proxycast-websocket"
version = "0.75.0"
version = "0.76.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -7813,6 +8127,12 @@ dependencies = [
"ordered-multimap",
]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]]
name = "rustc-hash"
version = "1.1.0"
@@ -8147,7 +8467,7 @@ checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416"
dependencies = [
"bitflags 1.3.2",
"cssparser 0.29.6",
"derive_more",
"derive_more 0.99.20",
"fxhash",
"log",
"phf 0.8.0",
@@ -8165,7 +8485,7 @@ checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06"
dependencies = [
"bitflags 2.10.0",
"cssparser 0.31.2",
"derive_more",
"derive_more 0.99.20",
"fxhash",
"log",
"new_debug_unreachable",
@@ -8207,6 +8527,127 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "sentry"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc02afbba55340d6394968cb621384dbb9a0ad72d1b45e3e403099702a8bcf61"
dependencies = [
"httpdate",
"native-tls",
"reqwest 0.12.28",
"sentry-actix",
"sentry-backtrace",
"sentry-contexts",
"sentry-core",
"sentry-debug-images",
"sentry-panic",
"sentry-tracing",
"tokio",
"ureq",
]
[[package]]
name = "sentry-actix"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5c9138631299216c4b85be2e54b9ca3c40e54dabeac4fa71ee4a680d536e083"
dependencies = [
"actix-http",
"actix-web",
"bytes",
"futures-util",
"sentry-core",
]
[[package]]
name = "sentry-backtrace"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f288792b92dfb3e8887554d2d11418863d5fabcd1cfad6756a8f88a5b963b76"
dependencies = [
"backtrace",
"regex",
"sentry-core",
]
[[package]]
name = "sentry-contexts"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b28e6208b0f6e28a3d08a6e46b1127fefe75185decb969b8e1e0bf8cce6e06d"
dependencies = [
"hostname",
"libc",
"os_info",
"rustc_version 0.4.1",
"sentry-core",
"uname",
]
[[package]]
name = "sentry-core"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67600a47b5aa005cb701454d56a289135a80654eebf1c1d9f076c3817a249bb8"
dependencies = [
"rand 0.9.2",
"sentry-types",
"serde",
"serde_json",
"url",
]
[[package]]
name = "sentry-debug-images"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb27e7e12008f0c80446089efe050f3333a333aafc9eb16fe9800b7a183cf20c"
dependencies = [
"findshlibs",
"sentry-core",
]
[[package]]
name = "sentry-panic"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce0bae293ecf6afaaa00dc63de3cc89b49ef774f06b9cf18dfd9c652009d3461"
dependencies = [
"sentry-backtrace",
"sentry-core",
]
[[package]]
name = "sentry-tracing"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c044617771f460a3c2ca7cf8f516c3341ba886ca38d53cb550997d8bf636a3c"
dependencies = [
"bitflags 2.10.0",
"sentry-backtrace",
"sentry-core",
"tracing-core",
"tracing-subscriber",
]
[[package]]
name = "sentry-types"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa369b6dc823ba5bddd69d16620f19229eb5df865b9483cf4dabb608891bb9c2"
dependencies = [
"debugid",
"hex",
"rand 0.9.2",
"serde",
"serde_json",
"thiserror 2.0.18",
"time",
"url",
"uuid",
]
[[package]]
name = "serde"
version = "1.0.228"
@@ -10262,6 +10703,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "uname"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
dependencies = [
"libc",
]
[[package]]
name = "unarray"
version = "0.1.4"
@@ -10366,6 +10816,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unicode_categories"
version = "0.1.1"
@@ -10394,6 +10850,35 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc"
dependencies = [
"base64 0.22.1",
"der",
"log",
"native-tls",
"percent-encoding",
"rustls-pki-types",
"ureq-proto",
"utf-8",
"webpki-root-certs",
]
[[package]]
name = "ureq-proto"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f"
dependencies = [
"base64 0.22.1",
"http 1.4.0",
"httparse",
"log",
]
[[package]]
name = "url"
version = "2.5.8"
@@ -10792,6 +11277,15 @@ dependencies = [
"system-deps",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "0.25.4"
+4 -2
View File
@@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2"
[workspace.package]
version = "0.75.0"
version = "0.76.0"
edition = "2021"
authors = ["coso"]
repository = "https://github.com/aiclientproxy/proxycast"
@@ -50,6 +50,7 @@ thiserror = "1"
# 日志
tracing = "0.1"
tracing-subscriber = "0.3"
sentry = "0.43"
# HTTP 服务器
axum = { version = "0.7", features = ["ws"] }
@@ -189,7 +190,7 @@ version = "2.4"
[package]
name = "proxycast"
version = "0.75.0"
version = "0.76.0"
description = "AI API Proxy Desktop App"
authors = ["you"]
edition = "2021"
@@ -252,6 +253,7 @@ thiserror.workspace = true
# 日志
tracing.workspace = true
tracing-subscriber.workspace = true
sentry.workspace = true
# HTTP 服务器
axum.workspace = true
+8 -8
View File
@@ -22,14 +22,14 @@ pub use path_utils::{collapse_tilde, contains_tilde, expand_tilde};
pub use types::{
generate_secure_api_key, AmpConfig, AmpModelMapping, ApiKeyEntry, AsrCredentialEntry,
AsrProviderType, AssistantConfig, AssistantProfile, BaiduConfig, ChannelsConfig,
ChatAppearanceConfig, Config, ContentCreatorConfig, ConversationSettings, CredentialEntry,
CredentialPoolConfig, CustomProviderConfig, DeliveryConfig, EndpointProvidersConfig,
ExperimentalFeatures, GeminiApiKeyEntry, HeartbeatExecutionMode, HeartbeatSecurityConfig,
HeartbeatSettings, HintRouteSettingsEntry, HintRouterSettings, ImageGenConfig,
InjectionRuleConfig, InjectionSettings, LoggingConfig, MemoryAutoConfig, MemoryConfig,
MemoryProfileConfig, MemoryResolveConfig, MemorySourcesConfig, ModelInfo, ModelsConfig,
NativeAgentConfig, NavigationConfig, OpenAIAsrConfig, PairingSettings, ProviderConfig,
ProviderModelsConfig, ProvidersConfig, QuotaExceededConfig, RateLimitSettings,
ChatAppearanceConfig, Config, ContentCreatorConfig, ConversationSettings, CrashReportingConfig,
CredentialEntry, CredentialPoolConfig, CustomProviderConfig, DeliveryConfig,
EndpointProvidersConfig, ExperimentalFeatures, GeminiApiKeyEntry, HeartbeatExecutionMode,
HeartbeatSecurityConfig, HeartbeatSettings, HintRouteSettingsEntry, HintRouterSettings,
ImageGenConfig, InjectionRuleConfig, InjectionSettings, LoggingConfig, MemoryAutoConfig,
MemoryConfig, MemoryProfileConfig, MemoryResolveConfig, MemorySourcesConfig, ModelInfo,
ModelsConfig, NativeAgentConfig, NavigationConfig, OpenAIAsrConfig, PairingSettings,
ProviderConfig, ProviderModelsConfig, ProvidersConfig, QuotaExceededConfig, RateLimitSettings,
RemoteManagementConfig, RetrySettings, RoutingConfig, ScreenshotChatConfig, SearchEngine,
ServerConfig, TaskSchedule, TlsConfig, UpdateCheckConfig, UserProfile, VertexApiKeyEntry,
VertexModelAlias, VoiceConfig, VoiceInputConfig, VoiceInstruction, VoiceOutputConfig,
+59
View File
@@ -429,6 +429,9 @@ pub struct Config {
/// 速率限制配置
#[serde(default)]
pub rate_limit: RateLimitSettings,
/// 崩溃上报配置(Sentry 协议兼容)
#[serde(default)]
pub crash_reporting: CrashReportingConfig,
/// 对话管理配置
#[serde(default)]
pub conversation: ConversationSettings,
@@ -1285,6 +1288,50 @@ pub struct LoggingConfig {
pub include_request_body: bool,
}
/// 崩溃上报配置
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CrashReportingConfig {
/// 是否启用崩溃上报
#[serde(default = "default_crash_reporting_enabled")]
pub enabled: bool,
/// Sentry DSN(为空时不发送远端)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dsn: Option<String>,
/// 上报环境(如 prod/dev)
#[serde(default = "default_crash_reporting_environment")]
pub environment: String,
/// 采样率(0.0 - 1.0)
#[serde(default = "default_crash_reporting_sample_rate")]
pub sample_rate: f64,
/// 是否发送可能包含 PII 的默认字段
#[serde(default)]
pub send_pii: bool,
}
fn default_crash_reporting_enabled() -> bool {
true
}
fn default_crash_reporting_environment() -> String {
"production".to_string()
}
fn default_crash_reporting_sample_rate() -> f64 {
1.0
}
impl Default for CrashReportingConfig {
fn default() -> Self {
Self {
enabled: default_crash_reporting_enabled(),
dsn: None,
environment: default_crash_reporting_environment(),
sample_rate: default_crash_reporting_sample_rate(),
send_pii: false,
}
}
}
fn default_logging_enabled() -> bool {
true
}
@@ -1788,6 +1835,7 @@ impl Default for Config {
assistant: AssistantConfig::default(),
user_profile: UserProfile::default(),
rate_limit: RateLimitSettings::default(),
crash_reporting: CrashReportingConfig::default(),
conversation: ConversationSettings::default(),
hint_router: HintRouterSettings::default(),
pairing: PairingSettings::default(),
@@ -2054,6 +2102,12 @@ pub struct ImageGenConfig {
/// 自动下载生成的图像
#[serde(default)]
pub auto_download: Option<bool>,
/// 图片搜索(Pexels)API Key
#[serde(default)]
pub image_search_pexels_api_key: Option<String>,
/// 图片搜索(Pixabay)API Key
#[serde(default)]
pub image_search_pixabay_api_key: Option<String>,
}
/// 助理配置
@@ -2139,6 +2193,11 @@ mod unit_tests {
assert_eq!(config.auth_dir, "~/.proxycast/auth");
assert!(config.credential_pool.kiro.is_empty());
assert!(config.credential_pool.openai.is_empty());
assert!(config.crash_reporting.enabled);
assert!(config.crash_reporting.dsn.is_none());
assert_eq!(config.crash_reporting.environment, "production");
assert_eq!(config.crash_reporting.sample_rate, 1.0);
assert!(!config.crash_reporting.send_pii);
}
#[test]
+19 -5
View File
@@ -6,7 +6,7 @@ use crate::app::types::{AppState, LogState};
use crate::app::utils::is_valid_bind_host;
use crate::config::{
self,
observer::{ConfigChangeEvent, RoutingChangeEvent},
observer::{ConfigChangeEvent, FullReloadEvent, RoutingChangeEvent},
ConfigChangeSource, GlobalConfigManagerState,
};
@@ -21,6 +21,7 @@ pub async fn get_config(state: tauri::State<'_, AppState>) -> Result<config::Con
#[tauri::command]
pub async fn save_config(
state: tauri::State<'_, AppState>,
config_manager: tauri::State<'_, GlobalConfigManagerState>,
config: config::Config,
) -> Result<(), String> {
let host = config.server.host.to_lowercase();
@@ -46,17 +47,30 @@ pub async fn save_config(
return Err("安全限制:不允许开启远程管理功能".to_string());
}
let mut s = state.write().await;
s.config = config.clone();
{
let mut s = state.write().await;
s.config = config.clone();
}
match config::save_config(&config) {
let save_result = config::save_config(&config).map_err(|e| e.to_string());
match save_result {
Ok(()) => {
let full_reload_event = ConfigChangeEvent::FullReload(FullReloadEvent {
timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
source: ConfigChangeSource::FrontendUI,
});
config_manager
.0
.subject()
.notify_event(full_reload_event)
.await;
tracing::info!("[CONFIG] 配置保存成功: host={}", config.server.host);
Ok(())
}
Err(e) => {
tracing::error!("[CONFIG] 配置保存失败: {}", e);
Err(e.to_string())
Err(e)
}
}
}
+91
View File
@@ -4,6 +4,26 @@
use crate::app::types::LogState;
use crate::logger;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
/// 前端异常上报参数
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrontendCrashReport {
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stack: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub component: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub workflow_step: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub creation_mode: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context: Option<serde_json::Value>,
}
/// 获取日志
#[tauri::command]
@@ -17,3 +37,74 @@ pub async fn clear_logs(logs: tauri::State<'_, LogState>) -> Result<(), String>
logs.write().await.clear();
Ok(())
}
/// 写入前端异常到本地日志并同步到崩溃上报后端
#[tauri::command]
pub async fn report_frontend_crash(
logs: tauri::State<'_, LogState>,
report: FrontendCrashReport,
) -> Result<(), String> {
let sanitized_message = logger::sanitize_log_message(&report.message);
let sanitized_component = report
.component
.as_deref()
.map(logger::sanitize_log_message)
.unwrap_or_else(|| "unknown".to_string());
let sanitized_step = report
.workflow_step
.as_deref()
.map(logger::sanitize_log_message)
.unwrap_or_else(|| "unknown".to_string());
let sanitized_mode = report
.creation_mode
.as_deref()
.map(logger::sanitize_log_message)
.unwrap_or_else(|| "unknown".to_string());
let stack_preview = report
.stack
.as_deref()
.map(logger::sanitize_log_message)
.map(|stack| stack.lines().take(3).collect::<Vec<_>>().join(" | "))
.unwrap_or_default();
logs.write().await.add(
"error",
&format!(
"[FrontendCrash] component={sanitized_component} step={sanitized_step} mode={sanitized_mode} message={sanitized_message} stack={stack_preview}"
),
);
let mut merged_context = match report.context {
Some(Value::Object(context)) => context,
Some(other) => {
let mut context = Map::new();
context.insert("raw_context".to_string(), other);
context
}
None => Map::new(),
};
if let Some(name) = report.name.as_deref() {
merged_context.insert(
"error_name".to_string(),
Value::String(logger::sanitize_log_message(name)),
);
}
if let Some(stack) = report.stack.as_deref() {
merged_context.insert(
"error_stack".to_string(),
Value::String(logger::sanitize_log_message(stack)),
);
}
crate::crash_reporting::capture_frontend_report(
&sanitized_message,
report.component.as_deref(),
report.workflow_step.as_deref(),
report.creation_mode.as_deref(),
Some(Value::Object(merged_context)),
);
Ok(())
}
+7
View File
@@ -35,6 +35,9 @@ pub fn run() {
}
};
// 初始化崩溃上报(保持 guard 生命周期直到应用退出)
let _crash_reporting_guard = crate::crash_reporting::init_from_config(&config);
// 初始化所有应用状态
let states = match bootstrap::init_states(&config) {
Ok(s) => s,
@@ -765,6 +768,7 @@ pub fn run() {
// Log commands (from app::commands)
app_commands::get_logs,
app_commands::clear_logs,
app_commands::report_frontend_crash,
// API test commands (from app::commands)
app_commands::test_api,
app_commands::get_available_models,
@@ -1301,6 +1305,9 @@ pub fn run() {
commands::material_cmd::get_material_content,
commands::material_cmd::get_material_count,
commands::material_cmd::get_materials_content,
// Image search commands
commands::image_search_cmd::search_pixabay_images,
commands::image_search_cmd::search_web_images,
// Video generation commands
commands::video_generation_cmd::create_video_generation_task,
commands::video_generation_cmd::get_video_generation_task,
+481
View File
@@ -0,0 +1,481 @@
//! 图片搜索相关的 Tauri 命令
//!
//! 提供在线图片搜索功能,支持 Pixabay API 和联网搜索(Pexels)。
use crate::app::AppState;
use serde::{Deserialize, Serialize};
use tauri::State;
/// Pixabay 搜索请求
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PixabaySearchRequest {
/// 搜索关键词
pub query: String,
/// 页码(从 1 开始)
pub page: u32,
/// 每页数量
#[serde(alias = "per_page")]
pub per_page: u32,
/// 方向筛选:horizontal, vertical
pub orientation: Option<String>,
}
/// Pixabay 搜索响应
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PixabaySearchResponse {
/// 总结果数
pub total: u32,
/// 总命中数
pub total_hits: u32,
/// 搜索结果
pub hits: Vec<PixabayHit>,
}
/// Pixabay 图片信息
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PixabayHit {
/// 图片 ID
pub id: u64,
/// 预览图 URL
pub preview_url: String,
/// 大图 URL
pub large_image_url: String,
/// 图片宽度
pub image_width: u32,
/// 图片高度
pub image_height: u32,
/// 标签(逗号分隔)
pub tags: String,
/// 来源页面 URL
pub page_url: String,
/// 作者名称
pub user: String,
}
/// 联网图片搜索请求
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WebImageSearchRequest {
/// 搜索关键词
pub query: String,
/// 页码(从 1 开始)
pub page: u32,
/// 每页数量
#[serde(alias = "per_page")]
pub per_page: u32,
/// 方向筛选:landscape, portrait, square
pub aspect: Option<String>,
}
/// 联网图片搜索响应(统一格式)
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WebImageSearchResponse {
/// 总结果数
pub total: u32,
/// 搜索结果
pub hits: Vec<WebImageHit>,
/// 来源提供者标识
pub provider: String,
}
/// 联网图片信息
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WebImageHit {
/// 图片 ID
pub id: String,
/// 缩略图 URL
pub thumbnail_url: String,
/// 原图 URL
pub content_url: String,
/// 图片宽度
pub width: u32,
/// 图片高度
pub height: u32,
/// 标题/描述
pub name: String,
/// 来源页面 URL
pub host_page_url: String,
}
#[derive(Debug, Clone, Deserialize)]
struct PexelsSearchResponse {
total_results: u32,
photos: Vec<PexelsPhoto>,
}
#[derive(Debug, Clone, Deserialize)]
struct PexelsPhoto {
id: u64,
width: u32,
height: u32,
url: String,
alt: Option<String>,
src: PexelsPhotoSrc,
}
#[derive(Debug, Clone, Deserialize)]
struct PexelsPhotoSrc {
tiny: Option<String>,
small: Option<String>,
medium: Option<String>,
large: Option<String>,
large2x: Option<String>,
landscape: Option<String>,
portrait: Option<String>,
original: Option<String>,
}
/// 获取 Pixabay API Key(优先配置,其次环境变量)
async fn get_pixabay_api_key(app_state: State<'_, AppState>) -> Option<String> {
let key_from_config = {
let state = app_state.read().await;
state.config.image_gen.image_search_pixabay_api_key.clone()
};
key_from_config
.and_then(|key| {
let trimmed = key.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
})
.or_else(|| {
std::env::var("PIXABAY_API_KEY").ok().and_then(|key| {
let trimmed = key.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
})
})
}
/// 获取 Pexels API Key(优先配置,其次环境变量)
async fn get_pexels_api_key(app_state: State<'_, AppState>) -> Option<String> {
let key_from_config = {
let state = app_state.read().await;
state.config.image_gen.image_search_pexels_api_key.clone()
};
key_from_config
.and_then(|key| {
let trimmed = key.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
})
.or_else(|| {
std::env::var("PEXELS_API_KEY").ok().and_then(|key| {
let trimmed = key.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
})
})
}
fn map_aspect_to_pexels_orientation(aspect: Option<&str>) -> Option<&'static str> {
match aspect.unwrap_or_default() {
"landscape" => Some("landscape"),
"portrait" => Some("portrait"),
"square" => Some("square"),
_ => None,
}
}
fn map_pexels_to_web_response(resp: PexelsSearchResponse) -> WebImageSearchResponse {
let hits = resp
.photos
.into_iter()
.filter_map(|photo| {
let content_url = photo
.src
.large2x
.clone()
.or(photo.src.large.clone())
.or(photo.src.original.clone())
.or(photo.src.landscape.clone())
.or(photo.src.portrait.clone())
.or(photo.src.medium.clone())
.or(photo.src.small.clone())
.or(photo.src.tiny.clone())?;
let thumbnail_url = photo
.src
.medium
.clone()
.or(photo.src.small.clone())
.or(photo.src.tiny.clone())
.or(photo.src.landscape.clone())
.or(photo.src.portrait.clone())
.or(photo.src.large.clone())
.or(photo.src.original.clone())
.unwrap_or_else(|| content_url.clone());
let name = photo
.alt
.as_deref()
.map(str::trim)
.filter(|name| !name.is_empty())
.map(str::to_string)
.unwrap_or_else(|| "Pexels Image".to_string());
Some(WebImageHit {
id: photo.id.to_string(),
thumbnail_url,
content_url,
width: photo.width,
height: photo.height,
name,
host_page_url: photo.url,
})
})
.collect::<Vec<_>>();
WebImageSearchResponse {
total: resp.total_results,
hits,
provider: "pexels".to_string(),
}
}
/// 搜索 Pixabay 图片
///
/// 通过 Pixabay API 搜索在线图片。
///
/// # 参数
/// - `req`: 搜索请求,包含关键词、页码、每页数量等
///
/// # 返回
/// - 成功返回搜索结果
/// - 失败返回错误信息
#[tauri::command]
pub async fn search_pixabay_images(
app_state: State<'_, AppState>,
req: PixabaySearchRequest,
) -> Result<PixabaySearchResponse, String> {
let api_key = get_pixabay_api_key(app_state)
.await
.ok_or_else(|| "未配置 Pixabay API Key,请先在设置 → 系统 → 网络搜索中配置".to_string())?;
// 构建 URL
let url = format!(
"https://pixabay.com/api/?key={}&q={}&page={}&per_page={}&image_type=photo&safesearch=true",
api_key,
urlencoding::encode(&req.query),
req.page,
req.per_page
);
// 添加 orientation 参数
let url = if let Some(orientation) = &req.orientation {
if !orientation.is_empty() {
format!("{}&orientation={}", url, orientation)
} else {
url
}
} else {
url
};
// 发起请求
let response = reqwest::get(&url)
.await
.map_err(|e| format!("请求失败: {}", e))?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
let detail = serde_json::from_str::<serde_json::Value>(&body)
.ok()
.and_then(|value| {
value
.get("error")
.and_then(|error| error.as_str())
.map(str::to_string)
})
.unwrap_or_else(|| body.chars().take(120).collect::<String>());
return Err(format!("Pixabay API 返回错误: HTTP {} {}", status, detail));
}
let json: serde_json::Value = response
.json()
.await
.map_err(|e| format!("解析响应失败: {}", e))?;
// 映射响应
let total = json["total"].as_u64().map(|v| v as u32).unwrap_or(0);
let total_hits = json["totalHits"].as_u64().map(|v| v as u32).unwrap_or(0);
let hits = json["hits"]
.as_array()
.unwrap_or(&vec![])
.iter()
.filter_map(|hit| {
Some(PixabayHit {
id: hit["id"].as_u64()? as u64,
preview_url: hit["previewURL"].as_str()?.to_string(),
large_image_url: hit["largeImageURL"].as_str()?.to_string(),
image_width: hit["imageWidth"].as_u64()? as u32,
image_height: hit["imageHeight"].as_u64()? as u32,
tags: hit["tags"].as_str().unwrap_or("").to_string(),
page_url: hit["pageURL"].as_str().unwrap_or("").to_string(),
user: hit["user"].as_str().unwrap_or("").to_string(),
})
})
.collect();
Ok(PixabaySearchResponse {
total,
total_hits,
hits,
})
}
/// 联网搜索图片(Pexels)
#[tauri::command]
pub async fn search_web_images(
app_state: State<'_, AppState>,
req: WebImageSearchRequest,
) -> Result<WebImageSearchResponse, String> {
let api_key = get_pexels_api_key(app_state)
.await
.ok_or_else(|| "未配置 Pexels API Key,请先在设置 → 系统 → 网络搜索中配置".to_string())?;
let client = reqwest::Client::new();
let mut query = vec![
("query", req.query.clone()),
("page", req.page.to_string()),
("per_page", req.per_page.to_string()),
];
if let Some(orientation) = map_aspect_to_pexels_orientation(req.aspect.as_deref()) {
query.push(("orientation", orientation.to_string()));
}
let response = client
.get("https://api.pexels.com/v1/search")
.header("Authorization", api_key)
.header(
reqwest::header::USER_AGENT,
"Mozilla/5.0 (compatible; ProxyCast/0.75; +https://github.com/aiclientproxy/proxycast)",
)
.header(reqwest::header::ACCEPT, "application/json")
.query(&query)
.send()
.await
.map_err(|e| format!("请求 Pexels 失败: {e}"))?;
if !response.status().is_success() {
let status = response.status();
if status.as_u16() == 401 || status.as_u16() == 403 {
return Err("Pexels API Key 无效或无权限,请检查设置".to_string());
}
return Err(format!("Pexels API 返回错误: HTTP {status}"));
}
let body: PexelsSearchResponse = response
.json()
.await
.map_err(|e| format!("解析 Pexels 响应失败: {e}"))?;
Ok(map_pexels_to_web_response(body))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pixabay_request_serialization() {
let req = PixabaySearchRequest {
query: "nature".to_string(),
page: 1,
per_page: 20,
orientation: Some("horizontal".to_string()),
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("nature"));
assert!(json.contains("horizontal"));
}
#[test]
fn test_web_request_serialization() {
let req = WebImageSearchRequest {
query: "city".to_string(),
page: 2,
per_page: 30,
aspect: Some("landscape".to_string()),
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("city"));
assert!(json.contains("landscape"));
assert!(json.contains("\"page\":2"));
}
#[test]
fn test_map_aspect_to_pexels_orientation() {
assert_eq!(
map_aspect_to_pexels_orientation(Some("landscape")),
Some("landscape")
);
assert_eq!(
map_aspect_to_pexels_orientation(Some("portrait")),
Some("portrait")
);
assert_eq!(
map_aspect_to_pexels_orientation(Some("square")),
Some("square")
);
assert_eq!(map_aspect_to_pexels_orientation(Some("all")), None);
assert_eq!(map_aspect_to_pexels_orientation(None), None);
}
#[test]
fn test_map_pexels_to_web_response() {
let resp = PexelsSearchResponse {
total_results: 2,
photos: vec![PexelsPhoto {
id: 123,
width: 1920,
height: 1080,
url: "https://www.pexels.com/photo/test".to_string(),
alt: Some(" 城市夜景 ".to_string()),
src: PexelsPhotoSrc {
tiny: None,
small: None,
medium: Some("https://images.pexels.com/medium.jpg".to_string()),
large: Some("https://images.pexels.com/large.jpg".to_string()),
large2x: Some("https://images.pexels.com/large2x.jpg".to_string()),
landscape: None,
portrait: None,
original: None,
},
}],
};
let mapped = map_pexels_to_web_response(resp);
assert_eq!(mapped.provider, "pexels");
assert_eq!(mapped.total, 2);
assert_eq!(mapped.hits.len(), 1);
let hit = &mapped.hits[0];
assert_eq!(hit.id, "123");
assert_eq!(hit.name, "城市夜景");
assert_eq!(hit.content_url, "https://images.pexels.com/large2x.jpg");
assert_eq!(hit.thumbnail_url, "https://images.pexels.com/medium.jpg");
assert_eq!(hit.width, 1920);
assert_eq!(hit.height, 1080);
}
}
+1
View File
@@ -16,6 +16,7 @@ pub mod external_tools_cmd;
pub mod file_upload_cmd;
pub mod general_chat_cmd;
pub mod heartbeat_cmd;
pub mod image_search_cmd;
pub mod injection_cmd;
pub mod kiro_local;
pub mod machine_id_cmd;
+2 -1
View File
@@ -8,7 +8,8 @@ mod tauri_observer;
// 从 proxycast-config crate 重新导出被使用的类型
pub use proxycast_config::observer::events::{
ConfigChangeEvent, ConfigChangeSource, EndpointProvidersChangeEvent, RoutingChangeEvent,
ConfigChangeEvent, ConfigChangeSource, EndpointProvidersChangeEvent, FullReloadEvent,
RoutingChangeEvent,
};
// Tauri 相关实现
+3
View File
@@ -205,6 +205,7 @@ fn arb_config() -> impl Strategy<Value = Config> {
pairing: proxycast_core::config::PairingSettings::default(),
heartbeat: proxycast_core::config::HeartbeatSettings::default(),
channels: proxycast_core::config::ChannelsConfig::default(),
crash_reporting: proxycast_core::config::CrashReportingConfig::default(),
})
}
@@ -458,6 +459,7 @@ fn arb_valid_config() -> impl Strategy<Value = Config> {
pairing: proxycast_core::config::PairingSettings::default(),
heartbeat: proxycast_core::config::HeartbeatSettings::default(),
channels: proxycast_core::config::ChannelsConfig::default(),
crash_reporting: proxycast_core::config::CrashReportingConfig::default(),
})
}
@@ -522,6 +524,7 @@ fn arb_invalid_config() -> impl Strategy<Value = Config> {
pairing: proxycast_core::config::PairingSettings::default(),
heartbeat: proxycast_core::config::HeartbeatSettings::default(),
channels: proxycast_core::config::ChannelsConfig::default(),
crash_reporting: proxycast_core::config::CrashReportingConfig::default(),
};
// 根据类型使配置无效
match invalid_type {
+91
View File
@@ -0,0 +1,91 @@
//! 崩溃上报初始化与上报辅助(Sentry 协议兼容)
use proxycast_core::config::{Config, CrashReportingConfig};
use serde_json::Value;
/// 根据配置初始化 Sentry 客户端
pub fn init_from_config(config: &Config) -> Option<sentry::ClientInitGuard> {
init(&config.crash_reporting)
}
fn init(config: &CrashReportingConfig) -> Option<sentry::ClientInitGuard> {
if !config.enabled {
tracing::info!("[CrashReporting] 已禁用崩溃上报");
return None;
}
let dsn = config
.dsn
.as_ref()
.map(|value| value.trim())
.filter(|value| !value.is_empty())?;
let sample_rate = config.sample_rate.clamp(0.0, 1.0) as f32;
let environment = config.environment.trim();
let environment = if environment.is_empty() {
"production"
} else {
environment
};
let guard = sentry::init((
dsn,
sentry::ClientOptions {
release: sentry::release_name!(),
environment: Some(environment.to_string().into()),
sample_rate,
send_default_pii: config.send_pii,
attach_stacktrace: true,
..Default::default()
},
));
sentry::configure_scope(|scope| {
scope.set_tag("platform", std::env::consts::OS);
scope.set_tag("arch", std::env::consts::ARCH);
scope.set_tag("app", "proxycast");
scope.set_tag("app_version", env!("CARGO_PKG_VERSION"));
});
tracing::info!(
"[CrashReporting] Sentry 已初始化: env={}, sample_rate={}",
environment,
sample_rate
);
Some(guard)
}
/// 记录来自前端的崩溃/异常事件(在已初始化时发送到 Sentry)
pub fn capture_frontend_report(
message: &str,
component: Option<&str>,
workflow_step: Option<&str>,
creation_mode: Option<&str>,
metadata: Option<Value>,
) {
if message.trim().is_empty() {
return;
}
sentry::with_scope(
|scope| {
scope.set_tag("origin", "frontend");
if let Some(component_name) = component.filter(|value| !value.trim().is_empty()) {
scope.set_tag("component", component_name.to_string());
}
if let Some(step) = workflow_step.filter(|value| !value.trim().is_empty()) {
scope.set_tag("workflow_step", step.to_string());
}
if let Some(mode) = creation_mode.filter(|value| !value.trim().is_empty()) {
scope.set_tag("creation_mode", mode.to_string());
}
if let Some(extra) = metadata {
scope.set_extra("frontend_report", sentry::protocol::Value::from(extra));
}
},
|| {
sentry::capture_message(message, sentry::Level::Error);
},
);
}
+1
View File
@@ -46,6 +46,7 @@ pub mod voice;
// 内部模块
mod commands;
mod config;
mod crash_reporting;
mod data;
#[cfg(debug_assertions)]
#[allow(dead_code)]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ProxyCast",
"version": "0.75.0",
"version": "0.76.0",
"identifier": "com.proxycast.app",
"build": {
"beforeDevCommand": "npm run dev",