fix: stabilize zhihu export and dashboard flow
This commit is contained in:
334
Cargo.lock
generated
334
Cargo.lock
generated
@@ -26,18 +26,6 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
@@ -47,12 +35,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -118,15 +100,6 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "ar_archive_writer"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
|
||||
dependencies = [
|
||||
"object",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.9.0"
|
||||
@@ -220,9 +193,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.39.0"
|
||||
version = "0.39.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a"
|
||||
checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
@@ -328,6 +301,12 @@ version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
@@ -342,9 +321,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.57"
|
||||
version = "1.2.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
||||
checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -421,16 +400,6 @@ dependencies = [
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@@ -493,9 +462,9 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.57"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -772,9 +741,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
@@ -1012,16 +981,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
@@ -1039,10 +998,11 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "hashify"
|
||||
version = "0.2.7"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "149e3ea90eb5a26ad354cfe3cb7f7401b9329032d0235f2687d03a35f30e5d4c"
|
||||
checksum = "dd1246c0e5493286aeb2dde35b1f4eb9c4ce00e628641210a5e553fc001a1f26"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1136,9 +1096,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.1"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
|
||||
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -1151,7 +1111,6 @@ dependencies = [
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
@@ -1223,12 +1182,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
|
||||
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"utf8_iter",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
@@ -1236,9 +1196,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
|
||||
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
@@ -1249,9 +1209,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
|
||||
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
@@ -1263,15 +1223,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
|
||||
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
|
||||
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
@@ -1283,15 +1243,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
|
||||
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
|
||||
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
@@ -1355,9 +1315,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
@@ -1395,9 +1355,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||
|
||||
[[package]]
|
||||
name = "iri-string"
|
||||
version = "0.7.11"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb"
|
||||
checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
@@ -1427,10 +1387,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.91"
|
||||
version = "0.3.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
||||
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -1449,12 +1411,11 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lettre"
|
||||
version = "0.11.19"
|
||||
version = "0.11.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f"
|
||||
checksum = "dabda5859ee7c06b995b9d1165aa52c39110e079ef609db97178d86aeb051fa7"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chumsky",
|
||||
"email-encoding",
|
||||
"email_address",
|
||||
"fastrand",
|
||||
@@ -1473,9 +1434,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
version = "0.2.184"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -1518,9 +1479,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@@ -1607,9 +1568,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
@@ -1669,15 +1630,6 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
@@ -1831,9 +1783,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
|
||||
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
@@ -1866,16 +1818,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
|
||||
dependencies = [
|
||||
"ar_archive_writer",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.28"
|
||||
@@ -2204,9 +2146,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
@@ -2328,9 +2270,9 @@ checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
@@ -2399,9 +2341,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98"
|
||||
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@@ -2420,7 +2362,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sgclaw"
|
||||
version = "0.1.0"
|
||||
version = "0.1.0-2026.4.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -2437,6 +2379,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"uuid",
|
||||
"zeroclawlabs",
|
||||
"zip 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2503,9 +2446,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
@@ -2541,20 +2484,6 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stop-token"
|
||||
version = "0.7.0"
|
||||
@@ -2685,9 +2614,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
|
||||
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
@@ -2710,9 +2639,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.50.0"
|
||||
version = "1.51.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
||||
checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -2726,9 +2655,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.1"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
|
||||
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2812,9 +2741,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.0+spec-1.1.0"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc"
|
||||
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
@@ -2822,32 +2751,32 @@ dependencies = [
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow 1.0.0",
|
||||
"winnow 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.1.0+spec-1.1.0"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f"
|
||||
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.0+spec-1.1.0"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow 1.0.0",
|
||||
"winnow 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.0+spec-1.1.0"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed"
|
||||
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
@@ -3068,9 +2997,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.22.0"
|
||||
version = "1.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
||||
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
|
||||
dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"js-sys",
|
||||
@@ -3134,9 +3063,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.114"
|
||||
version = "0.2.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
||||
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -3147,23 +3076,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.64"
|
||||
version = "0.4.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8"
|
||||
checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.114"
|
||||
version = "0.2.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
||||
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -3171,9 +3096,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.114"
|
||||
version = "0.2.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
||||
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -3184,9 +3109,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.114"
|
||||
version = "0.2.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
||||
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -3240,9 +3165,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.91"
|
||||
version = "0.3.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
|
||||
checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -3362,15 +3287,6 @@ 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"
|
||||
@@ -3529,9 +3445,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
|
||||
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
@@ -3623,9 +3539,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
@@ -3639,9 +3555,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
|
||||
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
@@ -3650,9 +3566,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
||||
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3729,23 +3645,23 @@ dependencies = [
|
||||
"uuid",
|
||||
"webpki-roots 1.0.6",
|
||||
"which",
|
||||
"zip",
|
||||
"zip 8.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.47"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.47"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3754,18 +3670,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3781,9 +3697,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
|
||||
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
@@ -3792,9 +3708,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.5"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
|
||||
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
@@ -3803,9 +3719,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.2"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
||||
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3814,9 +3730,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "8.4.0"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7756d0206d058333667493c4014f545f4b9603c4330ccd6d9b3f86dcab59f7d9"
|
||||
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "8.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcab981e19633ebcf0b001ddd37dd802996098bc1864f90b7c5d970ce76c1d59"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
@@ -3839,9 +3767,9 @@ checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.14"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6"
|
||||
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sgclaw"
|
||||
version = "0.1.0"
|
||||
version = "0.1.0-2026.4.9"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -18,4 +18,5 @@ sha2 = "0.10"
|
||||
thiserror = "1"
|
||||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
zip = { version = "0.6.6", default-features = false, features = ["deflate"] }
|
||||
zeroclaw = { package = "zeroclawlabs", path = "third_party/zeroclaw", default-features = false }
|
||||
|
||||
637
resources/zhihu-hotlist-echarts.html
Normal file
637
resources/zhihu-hotlist-echarts.html
Normal file
@@ -0,0 +1,637 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>知乎热榜图表驾驶舱</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #06111f;
|
||||
--bg-2: #0a1f37;
|
||||
--panel: rgba(8, 25, 42, 0.88);
|
||||
--panel-strong: rgba(10, 32, 55, 0.95);
|
||||
--line: rgba(101, 187, 255, 0.18);
|
||||
--line-strong: rgba(236, 186, 81, 0.26);
|
||||
--text: #eef6ff;
|
||||
--muted: #8ea6c2;
|
||||
--accent: #62d0ff;
|
||||
--accent-2: #ecba51;
|
||||
--accent-3: #6df0c2;
|
||||
--danger: #ff8b7e;
|
||||
--shadow: 0 20px 48px rgba(0, 0, 0, 0.34);
|
||||
--font-heading: "DIN Alternate", "Bahnschrift", "Microsoft YaHei UI", sans-serif;
|
||||
--font-body: "Segoe UI Variable Text", "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
background:
|
||||
radial-gradient(circle at 16% 10%, rgba(98, 208, 255, 0.18), transparent 22%),
|
||||
radial-gradient(circle at 86% 12%, rgba(236, 186, 81, 0.14), transparent 18%),
|
||||
linear-gradient(145deg, var(--bg) 0%, var(--bg-2) 42%, #030910 100%);
|
||||
color: var(--text);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-image:
|
||||
linear-gradient(rgba(101, 187, 255, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(101, 187, 255, 0.05) 1px, transparent 1px);
|
||||
background-size: 44px 44px;
|
||||
mask-image: radial-gradient(circle at center, black 34%, rgba(0, 0, 0, 0.22) 88%, transparent 100%);
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
padding: 18px;
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr auto;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.01)),
|
||||
linear-gradient(145deg, rgba(9, 30, 51, 0.97), rgba(6, 20, 34, 0.92));
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 22px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.panel::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
right: 18px;
|
||||
top: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, var(--accent), var(--accent-2), transparent);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 18px 24px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 360px;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
color: var(--accent);
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 38px;
|
||||
line-height: 1.08;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#snapshot-meta {
|
||||
margin: 10px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hero-notes {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.note-card {
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, rgba(98, 208, 255, 0.08), rgba(236, 186, 81, 0.08));
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.note-card strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.note-card span {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.metric {
|
||||
padding: 18px 18px 16px;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
margin-top: 10px;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 34px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.metric-sub {
|
||||
margin-top: 8px;
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.charts {
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 1fr 0.95fr;
|
||||
grid-template-rows: 360px 320px;
|
||||
gap: 14px;
|
||||
grid-template-areas:
|
||||
"bar top pie"
|
||||
"bubble table table";
|
||||
}
|
||||
|
||||
.chart-panel {
|
||||
padding: 14px 16px 12px;
|
||||
}
|
||||
|
||||
.bar-panel { grid-area: bar; }
|
||||
.top-panel { grid-area: top; }
|
||||
.pie-panel { grid-area: pie; }
|
||||
.bubble-panel { grid-area: bubble; }
|
||||
.table-panel { grid-area: table; padding: 14px 16px 10px; }
|
||||
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.section-head h2 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-family: var(--font-heading);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.section-head span {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: calc(100% - 42px);
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
height: calc(100% - 42px);
|
||||
overflow: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background: rgba(6, 19, 32, 0.96);
|
||||
padding: 10px 8px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid var(--line-strong);
|
||||
}
|
||||
|
||||
tbody td {
|
||||
padding: 11px 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
font-size: 13px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
background: rgba(255, 255, 255, 0.016);
|
||||
}
|
||||
|
||||
.rank {
|
||||
font-family: var(--font-heading);
|
||||
color: var(--accent-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.heat {
|
||||
color: var(--accent-3);
|
||||
font-family: var(--font-heading);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(98, 208, 255, 0.12);
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 10px 16px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 1440px) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.metrics {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.charts {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 320px 320px 320px 320px 420px;
|
||||
grid-template-areas:
|
||||
"bar"
|
||||
"top"
|
||||
"pie"
|
||||
"bubble"
|
||||
"table";
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.metrics {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<section class="panel hero">
|
||||
<div>
|
||||
<div class="eyebrow">Zhihu Hotlist Visual Command Center</div>
|
||||
<h1>知乎热榜图表驾驶舱</h1>
|
||||
<p id="snapshot-meta">由 sgClaw screen_html_export 生成的本地静态展示页</p>
|
||||
</div>
|
||||
<div class="hero-notes">
|
||||
<div class="note-card">
|
||||
<strong>图表表达</strong>
|
||||
<span>同一份热榜数据同时映射为分类热度、头部热点、结构占比和热度散点,适合现场讲解图表能力。</span>
|
||||
</div>
|
||||
<div class="note-card">
|
||||
<strong>演示建议</strong>
|
||||
<span id="lead-summary">优先讲解榜首热点、分类分布与热度层级,再向下展开全量榜单细节。</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="metrics">
|
||||
<article class="panel metric">
|
||||
<div class="metric-label">热榜条目数</div>
|
||||
<div id="metric-total" class="metric-value">0</div>
|
||||
<div class="metric-sub">Tracked items</div>
|
||||
</article>
|
||||
<article class="panel metric">
|
||||
<div class="metric-label">主题分类数</div>
|
||||
<div id="metric-categories" class="metric-value">0</div>
|
||||
<div class="metric-sub">Topic groups</div>
|
||||
</article>
|
||||
<article class="panel metric">
|
||||
<div class="metric-label">累计热度</div>
|
||||
<div id="metric-heat" class="metric-value">0</div>
|
||||
<div class="metric-sub">Total heat</div>
|
||||
</article>
|
||||
<article class="panel metric">
|
||||
<div class="metric-label">头部峰值</div>
|
||||
<div id="metric-peak" class="metric-value">0</div>
|
||||
<div class="metric-sub">Peak topic heat</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="charts">
|
||||
<section class="panel chart-panel bar-panel">
|
||||
<div class="section-head">
|
||||
<h2>分类总热度</h2>
|
||||
<span>横向对比</span>
|
||||
</div>
|
||||
<div id="bar-chart" class="chart"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel chart-panel top-panel">
|
||||
<div class="section-head">
|
||||
<h2>Top10 热点</h2>
|
||||
<span>柱状排行</span>
|
||||
</div>
|
||||
<div id="top-chart" class="chart"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel chart-panel pie-panel">
|
||||
<div class="section-head">
|
||||
<h2>分类占比</h2>
|
||||
<span>环形结构</span>
|
||||
</div>
|
||||
<div id="pie-chart" class="chart"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel chart-panel bubble-panel">
|
||||
<div class="section-head">
|
||||
<h2>热度分层</h2>
|
||||
<span>散点气泡</span>
|
||||
</div>
|
||||
<div id="bubble-chart" class="chart"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel table-panel">
|
||||
<div class="section-head">
|
||||
<h2>热榜明细</h2>
|
||||
<span id="table-note">按原始顺序保留</span>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>排名</th>
|
||||
<th>标题</th>
|
||||
<th>分类</th>
|
||||
<th>热度</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="panel footer">
|
||||
本页由 `screen_html_export` 生成,适合在系统浏览器中直接打开进行展示。
|
||||
</section>
|
||||
</div>
|
||||
<script>
|
||||
const defaultPayload = {
|
||||
"snapshot_id": "template-snapshot",
|
||||
"generated_at_ms": 0,
|
||||
"categories": [],
|
||||
"table": []
|
||||
}
|
||||
|
||||
const themeMeta = {
|
||||
title: "知乎热榜图表驾驶舱",
|
||||
renderer: "screen_html_export"
|
||||
};
|
||||
|
||||
const chartColors = ["#62d0ff", "#ecba51", "#6df0c2", "#7f8cff", "#ff8b7e", "#9fcbff", "#58a6ff"];
|
||||
const charts = {};
|
||||
|
||||
function formatNumber(value) {
|
||||
return new Intl.NumberFormat("zh-CN").format(Number(value || 0));
|
||||
}
|
||||
|
||||
function getTotalHeat(categories) {
|
||||
return (categories || []).reduce((sum, item) => sum + Number(item.total_heat || 0), 0);
|
||||
}
|
||||
|
||||
function getPeakHeat(table) {
|
||||
return (table || []).reduce((max, row) => Math.max(max, Number(row.heat_value || 0)), 0);
|
||||
}
|
||||
|
||||
function buildLeadSummary(table, categories) {
|
||||
const top = (table || [])[0];
|
||||
const category = (categories || []).slice().sort((a, b) => (b.total_heat || 0) - (a.total_heat || 0))[0];
|
||||
const parts = [];
|
||||
if (top) {
|
||||
parts.push(`榜首是“${top.title}”`);
|
||||
}
|
||||
if (category) {
|
||||
parts.push(`主导分类为“${category.category_label}”`);
|
||||
}
|
||||
parts.push(`共覆盖 ${(table || []).length} 条热点`);
|
||||
return parts.join(",");
|
||||
}
|
||||
|
||||
function ensureCharts() {
|
||||
if (!window.echarts) {
|
||||
return;
|
||||
}
|
||||
charts.bar = charts.bar || echarts.init(document.getElementById("bar-chart"));
|
||||
charts.top = charts.top || echarts.init(document.getElementById("top-chart"));
|
||||
charts.pie = charts.pie || echarts.init(document.getElementById("pie-chart"));
|
||||
charts.bubble = charts.bubble || echarts.init(document.getElementById("bubble-chart"));
|
||||
}
|
||||
|
||||
function renderBarChart(categories) {
|
||||
const sorted = (categories || []).slice().sort((a, b) => Number(a.total_heat || 0) - Number(b.total_heat || 0));
|
||||
charts.bar.setOption({
|
||||
animationDuration: 700,
|
||||
grid: {left: 90, right: 18, top: 10, bottom: 20},
|
||||
xAxis: {
|
||||
type: "value",
|
||||
axisLabel: {color: "#8ea6c2"},
|
||||
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
|
||||
},
|
||||
yAxis: {
|
||||
type: "category",
|
||||
data: sorted.map((item) => item.category_label),
|
||||
axisLabel: {color: "#eef6ff"},
|
||||
axisLine: {lineStyle: {color: "rgba(255,255,255,0.1)"}}
|
||||
},
|
||||
tooltip: {trigger: "axis", axisPointer: {type: "shadow"}},
|
||||
series: [{
|
||||
type: "bar",
|
||||
data: sorted.map((item, index) => ({
|
||||
value: Number(item.total_heat || 0),
|
||||
itemStyle: {color: chartColors[index % chartColors.length], borderRadius: [0, 8, 8, 0]}
|
||||
})),
|
||||
label: {show: true, position: "right", color: "#dfeeff"}
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
function renderTopChart(table) {
|
||||
const top = (table || []).slice(0, 10);
|
||||
charts.top.setOption({
|
||||
animationDuration: 700,
|
||||
grid: {left: 42, right: 12, top: 26, bottom: 46},
|
||||
tooltip: {trigger: "axis", axisPointer: {type: "shadow"}},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: top.map((row) => `#${row.rank}`),
|
||||
axisLabel: {color: "#8ea6c2"},
|
||||
axisLine: {lineStyle: {color: "rgba(255,255,255,0.1)"}}
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
axisLabel: {color: "#8ea6c2"},
|
||||
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
|
||||
},
|
||||
series: [{
|
||||
type: "bar",
|
||||
data: top.map((row, index) => ({
|
||||
value: Number(row.heat_value || 0),
|
||||
itemStyle: {color: chartColors[index % chartColors.length], borderRadius: [8, 8, 0, 0]}
|
||||
})),
|
||||
label: {show: true, position: "top", color: "#eef6ff", formatter: ({dataIndex}) => top[dataIndex].heat_text}
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
function renderPieChart(categories) {
|
||||
charts.pie.setOption({
|
||||
animationDuration: 700,
|
||||
color: chartColors,
|
||||
tooltip: {trigger: "item"},
|
||||
legend: {
|
||||
bottom: 2,
|
||||
textStyle: {color: "#8ea6c2", fontSize: 11},
|
||||
itemWidth: 12,
|
||||
itemHeight: 8
|
||||
},
|
||||
series: [{
|
||||
type: "pie",
|
||||
radius: ["44%", "72%"],
|
||||
center: ["50%", "44%"],
|
||||
itemStyle: {borderColor: "#081a2c", borderWidth: 2},
|
||||
label: {
|
||||
color: "#eef6ff",
|
||||
formatter: "{b}\n{d}%"
|
||||
},
|
||||
data: (categories || []).map((item) => ({
|
||||
name: item.category_label,
|
||||
value: Number(item.total_heat || 0)
|
||||
}))
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
function renderBubbleChart(table) {
|
||||
const top = (table || []).slice(0, 12);
|
||||
charts.bubble.setOption({
|
||||
animationDuration: 700,
|
||||
color: chartColors,
|
||||
grid: {left: 44, right: 18, top: 16, bottom: 36},
|
||||
xAxis: {
|
||||
type: "value",
|
||||
name: "排名",
|
||||
inverse: true,
|
||||
min: 0,
|
||||
max: Math.max(...top.map((row) => Number(row.rank || 0)), 10) + 1,
|
||||
nameTextStyle: {color: "#8ea6c2"},
|
||||
axisLabel: {color: "#8ea6c2"},
|
||||
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
name: "热度值",
|
||||
nameTextStyle: {color: "#8ea6c2"},
|
||||
axisLabel: {color: "#8ea6c2"},
|
||||
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
|
||||
},
|
||||
tooltip: {
|
||||
formatter: (params) => {
|
||||
const row = params.data.raw;
|
||||
return `${row.title}<br/>排名 #${row.rank}<br/>热度 ${row.heat_text}<br/>分类 ${row.category_label}`;
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: "scatter",
|
||||
symbolSize: (value) => Math.max(18, Math.min(56, value[2] / 80000)),
|
||||
data: top.map((row, index) => ({
|
||||
value: [Number(row.rank || 0), Number(row.heat_value || 0), Number(row.heat_value || 0)],
|
||||
raw: row,
|
||||
itemStyle: {color: chartColors[index % chartColors.length], opacity: 0.82}
|
||||
}))
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(table) {
|
||||
document.getElementById("table-body").innerHTML = (table || []).map((row) => `
|
||||
<tr>
|
||||
<td class="rank">#${row.rank}</td>
|
||||
<td>${row.title}</td>
|
||||
<td><span class="tag">${row.category_label}</span></td>
|
||||
<td class="heat">${row.heat_text}</td>
|
||||
</tr>
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function render(payload) {
|
||||
const data = payload || defaultPayload;
|
||||
const categories = data.categories || [];
|
||||
const table = data.table || [];
|
||||
|
||||
document.title = themeMeta.title;
|
||||
document.getElementById("snapshot-meta").textContent =
|
||||
`${data.snapshot_id} | 生成时间 ${new Date(data.generated_at_ms || 0).toLocaleString()}`;
|
||||
document.getElementById("metric-total").textContent = formatNumber(table.length);
|
||||
document.getElementById("metric-categories").textContent = formatNumber(categories.length);
|
||||
document.getElementById("metric-heat").textContent = formatNumber(getTotalHeat(categories));
|
||||
document.getElementById("metric-peak").textContent = formatNumber(getPeakHeat(table));
|
||||
document.getElementById("lead-summary").textContent = buildLeadSummary(table, categories);
|
||||
document.getElementById("table-note").textContent =
|
||||
table.length > 0 ? `当前展示 ${table.length} 条热点` : "暂无热榜数据";
|
||||
|
||||
renderTable(table);
|
||||
ensureCharts();
|
||||
if (window.echarts) {
|
||||
renderBarChart(categories);
|
||||
renderTopChart(table);
|
||||
renderPieChart(categories);
|
||||
renderBubbleChart(table);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
Object.values(charts).forEach((chart) => chart && chart.resize());
|
||||
});
|
||||
|
||||
render(defaultPayload);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,10 +4,13 @@ use serde_json::{json, Value};
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use zeroclaw::tools::{Tool, ToolResult};
|
||||
use zip::write::FileOptions;
|
||||
use zip::{CompressionMethod, ZipWriter};
|
||||
|
||||
const OPENXML_OFFICE_TOOL_NAME: &str = "openxml_office";
|
||||
const DEFAULT_SHEET_NAME: &str = "知乎热榜";
|
||||
@@ -280,13 +283,8 @@ fn run_openxml_cli(request_path: &Path) -> anyhow::Result<Value> {
|
||||
.parent()
|
||||
.map(|path| path.join("openxml_cli").join("Cargo.toml"))
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to resolve openxml_cli manifest path"))?;
|
||||
let binary_path = manifest_path
|
||||
.parent()
|
||||
.map(|path| path.join("target").join("debug").join("openxml-cli"))
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to resolve openxml_cli binary path"))?;
|
||||
|
||||
let output = if binary_path.exists() {
|
||||
Command::new(&binary_path)
|
||||
let output = if let Some(binary_path) = resolve_openxml_cli_binary(&manifest_path) {
|
||||
Command::new(binary_path)
|
||||
.args([
|
||||
"template",
|
||||
"render",
|
||||
@@ -325,6 +323,34 @@ fn run_openxml_cli(request_path: &Path) -> anyhow::Result<Value> {
|
||||
Ok(serde_json::from_str(&stdout)?)
|
||||
}
|
||||
|
||||
fn resolve_openxml_cli_binary(manifest_path: &Path) -> Option<PathBuf> {
|
||||
let cli_dir = manifest_path.parent()?;
|
||||
openxml_cli_candidate_paths(cli_dir)
|
||||
.into_iter()
|
||||
.find(|path| path.exists())
|
||||
}
|
||||
|
||||
fn openxml_cli_candidate_paths(cli_dir: &Path) -> Vec<PathBuf> {
|
||||
let mut paths = Vec::new();
|
||||
for profile in ["release", "debug"] {
|
||||
paths.push(
|
||||
cli_dir
|
||||
.join("target")
|
||||
.join(profile)
|
||||
.join(openxml_cli_binary_name()),
|
||||
);
|
||||
}
|
||||
paths
|
||||
}
|
||||
|
||||
fn openxml_cli_binary_name() -> &'static str {
|
||||
if cfg!(windows) {
|
||||
"openxml-cli.exe"
|
||||
} else {
|
||||
"openxml-cli"
|
||||
}
|
||||
}
|
||||
|
||||
fn value_to_string(value: &Value) -> String {
|
||||
match value {
|
||||
Value::String(text) => text.clone(),
|
||||
@@ -363,22 +389,81 @@ fn write_hotlist_template(path: &Path, row_count: usize) -> anyhow::Result<()> {
|
||||
fs::remove_file(path)?;
|
||||
}
|
||||
|
||||
let zip = Command::new("zip")
|
||||
.current_dir(&build_root)
|
||||
.args(["-q", "-r", path.to_string_lossy().as_ref(), "."])
|
||||
.output()?;
|
||||
if !zip.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&zip.stderr);
|
||||
return Err(anyhow::anyhow!(format!(
|
||||
"failed to create xlsx template: {}",
|
||||
stderr.trim()
|
||||
)));
|
||||
}
|
||||
zip_directory(&build_root, path)?;
|
||||
|
||||
let _ = fs::remove_dir_all(&build_root);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{openxml_cli_binary_name, openxml_cli_candidate_paths, zip_entry_name};
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn openxml_cli_candidates_prefer_release_before_debug() {
|
||||
let paths = openxml_cli_candidate_paths(Path::new("E:\\coding\\codex\\openxml_cli"));
|
||||
assert_eq!(paths.len(), 2);
|
||||
assert_eq!(
|
||||
paths[0],
|
||||
Path::new("E:\\coding\\codex\\openxml_cli")
|
||||
.join("target")
|
||||
.join("release")
|
||||
.join(openxml_cli_binary_name())
|
||||
);
|
||||
assert_eq!(
|
||||
paths[1],
|
||||
Path::new("E:\\coding\\codex\\openxml_cli")
|
||||
.join("target")
|
||||
.join("debug")
|
||||
.join(openxml_cli_binary_name())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zip_entry_name_normalizes_windows_separators() {
|
||||
let rel = Path::new("xl\\worksheets\\sheet1.xml");
|
||||
assert_eq!(zip_entry_name(rel), "xl/worksheets/sheet1.xml");
|
||||
}
|
||||
}
|
||||
|
||||
fn zip_directory(source_root: &Path, zip_path: &Path) -> anyhow::Result<()> {
|
||||
let file = fs::File::create(zip_path)?;
|
||||
let mut writer = ZipWriter::new(file);
|
||||
let options = FileOptions::default().compression_method(CompressionMethod::Stored);
|
||||
add_directory_to_zip(&mut writer, source_root, source_root, options)?;
|
||||
writer.finish()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_directory_to_zip<W: Write + std::io::Seek>(
|
||||
writer: &mut ZipWriter<W>,
|
||||
source_root: &Path,
|
||||
current_dir: &Path,
|
||||
options: FileOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
for entry in fs::read_dir(current_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
add_directory_to_zip(writer, source_root, &path, options)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let relative_path = path.strip_prefix(source_root)?;
|
||||
writer.start_file(zip_entry_name(relative_path), options)?;
|
||||
let mut input = fs::File::open(&path)?;
|
||||
let mut buffer = Vec::new();
|
||||
input.read_to_end(&mut buffer)?;
|
||||
writer.write_all(&buffer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn zip_entry_name(path: &Path) -> String {
|
||||
path.to_string_lossy().replace('\\', "/")
|
||||
}
|
||||
|
||||
fn worksheet_xml(row_count: usize) -> String {
|
||||
let mut rows = Vec::new();
|
||||
rows.push(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::path::Path;
|
||||
|
||||
use crate::compat::config_adapter::resolve_skills_dir_from_sgclaw_settings;
|
||||
use crate::compat::runtime::CompatTaskContext;
|
||||
use crate::config::SgClawSettings;
|
||||
use crate::pipe::{BrowserPipeTool, PipeError, Transport};
|
||||
@@ -34,6 +35,7 @@ pub fn execute_task_with_sgclaw_settings<T: Transport + 'static>(
|
||||
workspace_root: &Path,
|
||||
settings: &SgClawSettings,
|
||||
) -> Result<String, PipeError> {
|
||||
let skills_dir = resolve_skills_dir_from_sgclaw_settings(workspace_root, settings);
|
||||
let route = crate::compat::workflow_executor::detect_route(
|
||||
instruction,
|
||||
task_context.page_url.as_deref(),
|
||||
@@ -45,6 +47,7 @@ pub fn execute_task_with_sgclaw_settings<T: Transport + 'static>(
|
||||
transport,
|
||||
&browser_tool,
|
||||
workspace_root,
|
||||
&skills_dir,
|
||||
instruction,
|
||||
task_context,
|
||||
route,
|
||||
@@ -70,6 +73,7 @@ pub fn execute_task_with_sgclaw_settings<T: Transport + 'static>(
|
||||
transport,
|
||||
&browser_tool,
|
||||
workspace_root,
|
||||
&skills_dir,
|
||||
instruction,
|
||||
task_context,
|
||||
route,
|
||||
@@ -80,6 +84,7 @@ pub fn execute_task_with_sgclaw_settings<T: Transport + 'static>(
|
||||
transport,
|
||||
&browser_tool,
|
||||
workspace_root,
|
||||
&skills_dir,
|
||||
instruction,
|
||||
task_context,
|
||||
route,
|
||||
|
||||
@@ -12,7 +12,7 @@ const SCREEN_HTML_EXPORT_TOOL_NAME: &str = "screen_html_export";
|
||||
const DEFAULT_SCREEN_TITLE: &str = "知乎热榜主题分类分析大屏";
|
||||
const TEMPLATE: &str = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../skill_lib/skills/zhihu-hotlist-screen/assets/zhihu-hotlist-echarts.html"
|
||||
"/resources/zhihu-hotlist-echarts.html"
|
||||
));
|
||||
const PAYLOAD_START_MARKER: &str = " const defaultPayload = ";
|
||||
const PAYLOAD_END_MARKER: &str = "\n\n const themeMeta = {";
|
||||
|
||||
@@ -117,6 +117,7 @@ pub fn execute_route<T: Transport + 'static>(
|
||||
transport: &T,
|
||||
browser_tool: &BrowserPipeTool<T>,
|
||||
workspace_root: &Path,
|
||||
skills_dir: &Path,
|
||||
instruction: &str,
|
||||
task_context: &CompatTaskContext,
|
||||
route: WorkflowRoute,
|
||||
@@ -124,7 +125,13 @@ pub fn execute_route<T: Transport + 'static>(
|
||||
match route {
|
||||
WorkflowRoute::ZhihuHotlistExportXlsx | WorkflowRoute::ZhihuHotlistScreen => {
|
||||
let top_n = extract_top_n(instruction);
|
||||
let items = collect_hotlist_items(transport, browser_tool, top_n, task_context)?;
|
||||
let items = collect_hotlist_items(
|
||||
transport,
|
||||
browser_tool,
|
||||
skills_dir,
|
||||
top_n,
|
||||
task_context,
|
||||
)?;
|
||||
if items.is_empty() {
|
||||
return Err(PipeError::Protocol(
|
||||
"知乎热榜采集失败:未能从页面文本中解析到热榜条目".to_string(),
|
||||
@@ -141,13 +148,27 @@ pub fn execute_route<T: Transport + 'static>(
|
||||
}
|
||||
}
|
||||
WorkflowRoute::ZhihuArticleEntry => {
|
||||
execute_zhihu_article_entry_route(transport, browser_tool)
|
||||
execute_zhihu_article_entry_route(transport, browser_tool, skills_dir)
|
||||
}
|
||||
WorkflowRoute::ZhihuArticleDraft => {
|
||||
execute_zhihu_article_route(transport, browser_tool, instruction, task_context, false)
|
||||
execute_zhihu_article_route(
|
||||
transport,
|
||||
browser_tool,
|
||||
skills_dir,
|
||||
instruction,
|
||||
task_context,
|
||||
false,
|
||||
)
|
||||
}
|
||||
WorkflowRoute::ZhihuArticlePublish => {
|
||||
execute_zhihu_article_route(transport, browser_tool, instruction, task_context, true)
|
||||
execute_zhihu_article_route(
|
||||
transport,
|
||||
browser_tool,
|
||||
skills_dir,
|
||||
instruction,
|
||||
task_context,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,10 +176,13 @@ pub fn execute_route<T: Transport + 'static>(
|
||||
fn collect_hotlist_items<T: Transport + 'static>(
|
||||
transport: &T,
|
||||
browser_tool: &BrowserPipeTool<T>,
|
||||
skills_dir: &Path,
|
||||
top_n: usize,
|
||||
task_context: &CompatTaskContext,
|
||||
) -> Result<Vec<HotlistItem>, PipeError> {
|
||||
if let Some(items) = ensure_hotlist_page_ready(transport, browser_tool, top_n, task_context)? {
|
||||
if let Some(items) =
|
||||
ensure_hotlist_page_ready(transport, browser_tool, skills_dir, top_n, task_context)?
|
||||
{
|
||||
return Ok(items);
|
||||
}
|
||||
transport.send(&AgentMessage::LogEntry {
|
||||
@@ -167,7 +191,7 @@ fn collect_hotlist_items<T: Transport + 'static>(
|
||||
})?;
|
||||
let response = browser_tool.invoke(
|
||||
Action::Eval,
|
||||
json!({ "script": load_hotlist_extractor_script(top_n)? }),
|
||||
json!({ "script": load_hotlist_extractor_script(skills_dir, top_n)? }),
|
||||
ZHIHU_DOMAIN,
|
||||
)?;
|
||||
if !response.success {
|
||||
@@ -188,6 +212,7 @@ fn collect_hotlist_items<T: Transport + 'static>(
|
||||
fn ensure_hotlist_page_ready<T: Transport + 'static>(
|
||||
transport: &T,
|
||||
browser_tool: &BrowserPipeTool<T>,
|
||||
skills_dir: &Path,
|
||||
top_n: usize,
|
||||
task_context: &CompatTaskContext,
|
||||
) -> Result<Option<Vec<HotlistItem>>, PipeError> {
|
||||
@@ -204,7 +229,7 @@ fn ensure_hotlist_page_ready<T: Transport + 'static>(
|
||||
return Ok(None);
|
||||
}
|
||||
if starts_on_hotlist {
|
||||
if let Some(items) = probe_hotlist_extractor(transport, browser_tool, top_n)? {
|
||||
if let Some(items) = probe_hotlist_extractor(transport, browser_tool, skills_dir, top_n)? {
|
||||
return Ok(Some(items));
|
||||
}
|
||||
}
|
||||
@@ -215,7 +240,7 @@ fn ensure_hotlist_page_ready<T: Transport + 'static>(
|
||||
if poll_for_hotlist_readiness(browser_tool)? {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(items) = probe_hotlist_extractor(transport, browser_tool, top_n)? {
|
||||
if let Some(items) = probe_hotlist_extractor(transport, browser_tool, skills_dir, top_n)? {
|
||||
return Ok(Some(items));
|
||||
}
|
||||
last_error = Some(PipeError::Protocol(format!(
|
||||
@@ -230,6 +255,7 @@ fn ensure_hotlist_page_ready<T: Transport + 'static>(
|
||||
fn probe_hotlist_extractor<T: Transport + 'static>(
|
||||
transport: &T,
|
||||
browser_tool: &BrowserPipeTool<T>,
|
||||
skills_dir: &Path,
|
||||
top_n: usize,
|
||||
) -> Result<Option<Vec<HotlistItem>>, PipeError> {
|
||||
transport.send(&AgentMessage::LogEntry {
|
||||
@@ -238,7 +264,7 @@ fn probe_hotlist_extractor<T: Transport + 'static>(
|
||||
})?;
|
||||
let response = browser_tool.invoke(
|
||||
Action::Eval,
|
||||
json!({ "script": load_hotlist_extractor_script(top_n)? }),
|
||||
json!({ "script": load_hotlist_extractor_script(skills_dir, top_n)? }),
|
||||
ZHIHU_DOMAIN,
|
||||
)?;
|
||||
if !response.success {
|
||||
@@ -379,6 +405,7 @@ fn export_screen<T: Transport>(
|
||||
fn execute_zhihu_article_route<T: Transport + 'static>(
|
||||
transport: &T,
|
||||
browser_tool: &BrowserPipeTool<T>,
|
||||
skills_dir: &Path,
|
||||
instruction: &str,
|
||||
task_context: &CompatTaskContext,
|
||||
publish_mode: bool,
|
||||
@@ -401,6 +428,7 @@ fn execute_zhihu_article_route<T: Transport + 'static>(
|
||||
})?;
|
||||
let creator_state = execute_browser_skill_script(
|
||||
browser_tool,
|
||||
skills_dir,
|
||||
"zhihu-navigate",
|
||||
"open_creator_entry.js",
|
||||
json!({ "desired_target": "article_editor" }),
|
||||
@@ -424,6 +452,7 @@ fn execute_zhihu_article_route<T: Transport + 'static>(
|
||||
})?;
|
||||
let editor_state = execute_browser_skill_script(
|
||||
browser_tool,
|
||||
skills_dir,
|
||||
"zhihu-write",
|
||||
"prepare_article_editor.js",
|
||||
json!({ "desired_mode": if publish_mode { "publish" } else { "draft" } }),
|
||||
@@ -446,6 +475,7 @@ fn execute_zhihu_article_route<T: Transport + 'static>(
|
||||
})?;
|
||||
let fill_result = execute_browser_skill_script(
|
||||
browser_tool,
|
||||
skills_dir,
|
||||
"zhihu-write",
|
||||
"fill_article_draft.js",
|
||||
json!({
|
||||
@@ -482,6 +512,7 @@ fn execute_zhihu_article_route<T: Transport + 'static>(
|
||||
fn execute_zhihu_article_entry_route<T: Transport + 'static>(
|
||||
transport: &T,
|
||||
browser_tool: &BrowserPipeTool<T>,
|
||||
skills_dir: &Path,
|
||||
) -> Result<String, PipeError> {
|
||||
navigate_zhihu_page(transport, browser_tool, ZHIHU_CREATOR_URL)?;
|
||||
transport.send(&AgentMessage::LogEntry {
|
||||
@@ -490,6 +521,7 @@ fn execute_zhihu_article_entry_route<T: Transport + 'static>(
|
||||
})?;
|
||||
let creator_state = execute_browser_skill_script(
|
||||
browser_tool,
|
||||
skills_dir,
|
||||
"zhihu-navigate",
|
||||
"open_creator_entry.js",
|
||||
json!({ "desired_target": "article_editor" }),
|
||||
@@ -513,6 +545,7 @@ fn execute_zhihu_article_entry_route<T: Transport + 'static>(
|
||||
})?;
|
||||
let editor_state = execute_browser_skill_script(
|
||||
browser_tool,
|
||||
skills_dir,
|
||||
"zhihu-write",
|
||||
"prepare_article_editor.js",
|
||||
json!({ "desired_mode": "draft" }),
|
||||
@@ -532,8 +565,9 @@ fn execute_zhihu_article_entry_route<T: Transport + 'static>(
|
||||
)))
|
||||
}
|
||||
|
||||
fn load_hotlist_extractor_script(top_n: usize) -> Result<String, PipeError> {
|
||||
fn load_hotlist_extractor_script(skills_dir: &Path, top_n: usize) -> Result<String, PipeError> {
|
||||
load_browser_skill_script(
|
||||
skills_dir,
|
||||
"zhihu-hotlist",
|
||||
"extract_hotlist.js",
|
||||
json!({ "top_n": top_n.to_string() }),
|
||||
@@ -618,12 +652,14 @@ fn navigate_zhihu_page<T: Transport + 'static>(
|
||||
|
||||
fn execute_browser_skill_script<T: Transport + 'static>(
|
||||
browser_tool: &BrowserPipeTool<T>,
|
||||
skills_dir: &Path,
|
||||
skill_name: &str,
|
||||
script_name: &str,
|
||||
args: Value,
|
||||
expected_domain: &str,
|
||||
) -> Result<Value, PipeError> {
|
||||
let wrapped_script = load_browser_skill_script(skill_name, script_name, args)?;
|
||||
let wrapped_script =
|
||||
load_browser_skill_script(skills_dir, skill_name, script_name, args)?;
|
||||
let response = browser_tool.invoke(
|
||||
Action::Eval,
|
||||
json!({ "script": wrapped_script }),
|
||||
@@ -977,15 +1013,12 @@ mod tests {
|
||||
}
|
||||
|
||||
fn load_browser_skill_script(
|
||||
skills_dir: &Path,
|
||||
skill_name: &str,
|
||||
script_name: &str,
|
||||
args: Value,
|
||||
) -> Result<String, PipeError> {
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(env!("CARGO_MANIFEST_DIR")))
|
||||
.join("skill_lib")
|
||||
.join("skills")
|
||||
let script_path = skills_dir
|
||||
.join(skill_name)
|
||||
.join("scripts")
|
||||
.join(script_name);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command as ProcessCommand;
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
use serde_json::json;
|
||||
use sgclaw::compat::openxml_office_tool::OpenXmlOfficeTool;
|
||||
use uuid::Uuid;
|
||||
use zeroclaw::tools::Tool;
|
||||
use zip::ZipArchive;
|
||||
|
||||
fn temp_workspace_root() -> PathBuf {
|
||||
let root = std::env::temp_dir().join(format!("sgclaw-openxml-office-{}", Uuid::new_v4()));
|
||||
@@ -12,6 +13,15 @@ fn temp_workspace_root() -> PathBuf {
|
||||
root
|
||||
}
|
||||
|
||||
fn read_sheet_xml(output_path: &std::path::Path) -> String {
|
||||
let file = File::open(output_path).unwrap();
|
||||
let mut archive = ZipArchive::new(file).unwrap();
|
||||
let mut entry = archive.by_name("xl/worksheets/sheet1.xml").unwrap();
|
||||
let mut xml = String::new();
|
||||
entry.read_to_string(&mut xml).unwrap();
|
||||
xml
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn openxml_office_tool_renders_hotlist_xlsx_from_rows() {
|
||||
let workspace_root = temp_workspace_root();
|
||||
@@ -33,19 +43,12 @@ async fn openxml_office_tool_renders_hotlist_xlsx_from_rows() {
|
||||
|
||||
assert!(result.success, "{result:?}");
|
||||
assert!(output_path.exists());
|
||||
assert!(result.output.contains(output_path.to_str().unwrap()));
|
||||
let output_json: serde_json::Value = serde_json::from_str(&result.output).unwrap();
|
||||
assert_eq!(output_json["row_count"], 2);
|
||||
assert_eq!(output_json["renderer"], "openxml_office");
|
||||
assert!(!output_json["output_path"].as_str().unwrap().is_empty());
|
||||
|
||||
let unzip = ProcessCommand::new("unzip")
|
||||
.args([
|
||||
"-p",
|
||||
output_path.to_str().unwrap(),
|
||||
"xl/worksheets/sheet1.xml",
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(unzip.status.success());
|
||||
|
||||
let xml = String::from_utf8(unzip.stdout).unwrap();
|
||||
let xml = read_sheet_xml(&output_path);
|
||||
assert!(xml.contains("问题一"));
|
||||
assert!(xml.contains("344万"));
|
||||
assert!(xml.contains("问题二"));
|
||||
@@ -74,17 +77,7 @@ async fn openxml_office_tool_accepts_reordered_columns_when_rows_are_structured(
|
||||
assert!(result.success, "{result:?}");
|
||||
assert!(output_path.exists());
|
||||
|
||||
let unzip = ProcessCommand::new("unzip")
|
||||
.args([
|
||||
"-p",
|
||||
output_path.to_str().unwrap(),
|
||||
"xl/worksheets/sheet1.xml",
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(unzip.status.success());
|
||||
|
||||
let xml = String::from_utf8(unzip.stdout).unwrap();
|
||||
let xml = read_sheet_xml(&output_path);
|
||||
assert!(xml.contains("问题一"));
|
||||
assert!(xml.contains("344万"));
|
||||
assert!(xml.contains(">1<"));
|
||||
@@ -112,17 +105,7 @@ async fn openxml_office_tool_accepts_localized_hotlist_column_aliases() {
|
||||
assert!(result.success, "{result:?}");
|
||||
assert!(output_path.exists());
|
||||
|
||||
let unzip = ProcessCommand::new("unzip")
|
||||
.args([
|
||||
"-p",
|
||||
output_path.to_str().unwrap(),
|
||||
"xl/worksheets/sheet1.xml",
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(unzip.status.success());
|
||||
|
||||
let xml = String::from_utf8(unzip.stdout).unwrap();
|
||||
let xml = read_sheet_xml(&output_path);
|
||||
assert!(xml.contains("问题一"));
|
||||
assert!(xml.contains("344万"));
|
||||
assert!(xml.contains(">1<"));
|
||||
|
||||
@@ -111,10 +111,15 @@ fn write_skill_script(skill_dir: &std::path::Path, relative_path: &str, body: &s
|
||||
}
|
||||
|
||||
fn real_skill_lib_root() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
let repo_parent = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("skill_lib")
|
||||
.to_path_buf();
|
||||
let hyphenated = repo_parent.join("skill-lib");
|
||||
if hyphenated.exists() {
|
||||
return hyphenated;
|
||||
}
|
||||
repo_parent.join("skill_lib")
|
||||
}
|
||||
|
||||
fn success_browser_response(seq: u64, data: Value) -> BrowserMessage {
|
||||
@@ -2274,6 +2279,7 @@ fn handle_browser_message_chains_hotlist_skill_into_screen_export_tool() {
|
||||
}
|
||||
}),
|
||||
),
|
||||
success_browser_response(4, json!({ "navigated": true })),
|
||||
]));
|
||||
let browser_tool = BrowserPipeTool::new(
|
||||
transport.clone(),
|
||||
|
||||
@@ -43,6 +43,7 @@ async fn screen_html_export_tool_renders_dashboard_html_with_presentation_contra
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.starts_with("file://"));
|
||||
assert!(html.contains("知乎热榜态势驾驶舱"));
|
||||
assert!(html.contains("snapshot-20260329"));
|
||||
assert!(html.contains("问题一"));
|
||||
assert!(html.contains("344万"));
|
||||
|
||||
Reference in New Issue
Block a user