mirror of
https://github.com/wisdgod/cursor-api.git
synced 2025-10-05 14:46:53 +08:00
v0.1.3-rc.5
This commit is contained in:
15
.env.example
15
.env.example
@@ -22,8 +22,8 @@ SHARED_TOKEN=
|
|||||||
# 令牌文件路径(已弃用)
|
# 令牌文件路径(已弃用)
|
||||||
# TOKEN_FILE=.token
|
# TOKEN_FILE=.token
|
||||||
|
|
||||||
# 令牌列表文件路径
|
# 令牌列表文件路径(已弃用)
|
||||||
TOKEN_LIST_FILE=.tokens
|
# TOKEN_LIST_FILE=.tokens
|
||||||
|
|
||||||
# (实验性)是否启用慢速池(true/false)
|
# (实验性)是否启用慢速池(true/false)
|
||||||
ENABLE_SLOW_POOL=false
|
ENABLE_SLOW_POOL=false
|
||||||
@@ -93,8 +93,11 @@ SERVICE_TIMEOUT=30
|
|||||||
# 包含网络引用
|
# 包含网络引用
|
||||||
INCLUDE_WEB_REFERENCES=false
|
INCLUDE_WEB_REFERENCES=false
|
||||||
|
|
||||||
# 持久化日志文件路径
|
# 持久化日志文件路径(已弃用)
|
||||||
LOGS_FILE_PATH=logs.bin
|
# LOGS_FILE_PATH=logs.bin
|
||||||
|
|
||||||
# 持久化页面配置文件路径
|
# 持久化页面配置文件路径(已弃用)
|
||||||
PAGES_FILE_PATH=pages.bin
|
# PAGES_FILE_PATH=pages.bin
|
||||||
|
|
||||||
|
# 程序数据目录
|
||||||
|
DATA_DIR=data
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,7 +16,7 @@ node_modules
|
|||||||
/cursor-api
|
/cursor-api
|
||||||
/cursor-api.exe
|
/cursor-api.exe
|
||||||
/release
|
/release
|
||||||
|
/data
|
||||||
/*.py
|
/*.py
|
||||||
/logs
|
/logs
|
||||||
/dev*
|
/dev*
|
||||||
|
309
Cargo.lock
generated
309
Cargo.lock
generated
@@ -23,7 +23,7 @@ version = "0.7.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.15",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
@@ -69,9 +69,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.95"
|
version = "1.0.96"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
@@ -220,9 +220,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli-decompressor"
|
name = "brotli-decompressor"
|
||||||
version = "4.0.1"
|
version = "4.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
|
checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
"alloc-stdlib",
|
"alloc-stdlib",
|
||||||
@@ -230,9 +230,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.16.0"
|
version = "3.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecheck"
|
name = "bytecheck"
|
||||||
@@ -276,15 +276,15 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.9.0"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.10"
|
version = "1.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
|
checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
@@ -333,9 +333,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.16"
|
version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -361,7 +361,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cursor-api"
|
name = "cursor-api"
|
||||||
version = "0.1.3-rc.4.3"
|
version = "0.1.3-rc.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -378,6 +378,7 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
"prost",
|
"prost",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
|
"prost-types",
|
||||||
"rand",
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -412,7 +413,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -438,9 +439,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
@@ -460,9 +461,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "faststr"
|
name = "faststr"
|
||||||
version = "0.2.27"
|
version = "0.2.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9154486833a83cb5d99de8c4d831314b8ae810dd4ef18d89ceb7a9c7c728dd74"
|
checksum = "403ebc0cd0c6dbff1cae7098168eff6bac83fad5928b6e91f29388b8dbb61653"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"rkyv 0.8.10",
|
"rkyv 0.8.10",
|
||||||
@@ -481,9 +482,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fixedbitset"
|
name = "fixedbitset"
|
||||||
version = "0.4.2"
|
version = "0.5.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
@@ -575,7 +576,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -626,7 +627,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.13.3+wasi-0.2.2",
|
||||||
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -647,9 +660,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.7"
|
version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
|
checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -727,9 +740,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.9.5"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
|
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpdate"
|
name = "httpdate"
|
||||||
@@ -739,9 +752,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.5.2"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
|
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@@ -948,7 +961,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1017,9 +1030,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.13.0"
|
version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
@@ -1070,9 +1083,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.25"
|
version = "0.4.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchit"
|
name = "matchit"
|
||||||
@@ -1103,9 +1116,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.3"
|
version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
|
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
@@ -1118,7 +1131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1130,29 +1143,29 @@ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "munge"
|
name = "munge"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64142d38c84badf60abf06ff9bd80ad2174306a5b11bd4706535090a30a419df"
|
checksum = "8743b8dfaf66acac79aca9ff2440e8680fef745b6260e6a31d1772b14cfa2862"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"munge_macro",
|
"munge_macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "munge_macro"
|
name = "munge_macro"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bb5c1d8184f13f7d0ccbeeca0def2f9a181bce2624302793005f5ca8aa62e5e"
|
checksum = "66191390a55bb9830fa8468c12634442ea4199c6e390ddf08ddcace35b3cd5da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.12"
|
version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
|
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@@ -1194,15 +1207,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.2"
|
version = "1.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.68"
|
version = "0.10.71"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
|
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -1221,20 +1234,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.104"
|
version = "0.9.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
|
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1279,9 +1292,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "petgraph"
|
name = "petgraph"
|
||||||
version = "0.6.5"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -1324,7 +1337,7 @@ version = "0.2.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy",
|
"zerocopy 0.7.35",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1334,7 +1347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
|
checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1348,9 +1361,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost"
|
name = "prost"
|
||||||
version = "0.13.4"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
|
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"prost-derive",
|
"prost-derive",
|
||||||
@@ -1358,9 +1371,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-build"
|
name = "prost-build"
|
||||||
version = "0.13.4"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b"
|
checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"itertools",
|
"itertools",
|
||||||
@@ -1372,28 +1385,28 @@ dependencies = [
|
|||||||
"prost",
|
"prost",
|
||||||
"prost-types",
|
"prost-types",
|
||||||
"regex",
|
"regex",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-derive"
|
name = "prost-derive"
|
||||||
version = "0.13.4"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
|
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools",
|
"itertools",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-types"
|
name = "prost-types"
|
||||||
version = "0.13.4"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc"
|
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prost",
|
"prost",
|
||||||
]
|
]
|
||||||
@@ -1435,7 +1448,7 @@ checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1470,20 +1483,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
|
"zerocopy 0.8.20",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.3.1"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
@@ -1491,18 +1504,19 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.9.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.3.1",
|
||||||
|
"zerocopy 0.8.20",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.8"
|
version = "0.5.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
]
|
]
|
||||||
@@ -1524,7 +1538,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1621,15 +1635,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.8"
|
version = "0.17.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"getrandom",
|
"getrandom 0.2.15",
|
||||||
"libc",
|
"libc",
|
||||||
"spin",
|
|
||||||
"untrusted",
|
"untrusted",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
@@ -1689,7 +1702,7 @@ checksum = "246b40ac189af6c675d124b802e8ef6d5246c53e17367ce9501f8f66a81abb7a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1700,9 +1713,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.43"
|
version = "0.38.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
"errno",
|
"errno",
|
||||||
@@ -1713,9 +1726,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.21"
|
version = "0.23.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
|
checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
@@ -1735,9 +1748,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pki-types"
|
name = "rustls-pki-types"
|
||||||
version = "1.10.1"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
|
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
@@ -1758,9 +1771,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.18"
|
version = "1.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
@@ -1808,29 +1821,29 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.217"
|
version = "1.0.218"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.217"
|
version = "1.0.218"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.137"
|
version = "1.0.139"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
|
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -1909,9 +1922,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.13.2"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
@@ -1961,12 +1974,6 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spin"
|
|
||||||
version = "0.9.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -1992,9 +1999,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.96"
|
version = "2.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2018,7 +2025,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2063,13 +2070,13 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.15.0"
|
version = "3.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
|
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom",
|
"getrandom 0.3.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
@@ -2101,7 +2108,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2112,7 +2119,7 @@ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2165,7 +2172,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2296,15 +2303,15 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.17.0"
|
version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.14"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
@@ -2337,11 +2344,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.12.1"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
|
checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2371,6 +2378,15 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.13.3+wasi-0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.100"
|
version = "0.2.100"
|
||||||
@@ -2393,7 +2409,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2428,7 +2444,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@@ -2532,7 +2548,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2543,7 +2559,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2667,6 +2683,15 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.8.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "write16"
|
name = "write16"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -2708,7 +2733,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2719,7 +2744,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"zerocopy-derive",
|
"zerocopy-derive 0.7.35",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive 0.8.20",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2730,7 +2764,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2750,7 +2795,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2779,7 +2824,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
71
Cargo.toml
71
Cargo.toml
@@ -1,47 +1,48 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cursor-api"
|
name = "cursor-api"
|
||||||
version = "0.1.3-rc.4.3"
|
version = "0.1.3-rc.5"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
authors = ["wisdgod <nav@wisdgod.com>"]
|
authors = ["wisdgod <nav@wisdgod.com>"]
|
||||||
description = "OpenAI format compatibility layer for the Cursor API"
|
description = "OpenAI format compatibility layer for the Cursor API"
|
||||||
repository = "https://github.com/wisdgod/cursor-api"
|
repository = "https://github.com/wisdgod/cursor-api"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.13.4"
|
prost-build = "^0.13"
|
||||||
sha2 = { version = "0.10.8", default-features = false }
|
sha2 = { version = "^0.10.8", default-features = false }
|
||||||
serde_json = "1.0.134"
|
serde_json = "^1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8.1", features = ["json"] }
|
axum = { version = "^0.8", features = ["json"] }
|
||||||
base64 = { version = "0.22.1", default-features = false, features = ["std"] }
|
base64 = { version = "^0.22", default-features = false, features = ["std"] }
|
||||||
# brotli = { version = "7.0.0", default-features = false, features = ["std"] }
|
# brotli = { version = "^7.0", default-features = false, features = ["std"] }
|
||||||
bytes = "1.9.0"
|
bytes = "^1.10"
|
||||||
chrono = { version = "0.4.39", default-features = false, features = ["std", "clock", "now", "serde", "rkyv-64"] }
|
chrono = { version = "^0.4", default-features = false, features = ["std", "clock", "now", "serde", "rkyv-64"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "^0.15"
|
||||||
flate2 = { version = "1.0.35", default-features = false, features = ["rust_backend"] }
|
flate2 = { version = "^1.0", default-features = false, features = ["rust_backend"] }
|
||||||
futures = { version = "0.3.31", default-features = false, features = ["std"] }
|
futures = { version = "^0.3", default-features = false, features = ["std"] }
|
||||||
gif = { version = "0.13.1", default-features = false, features = ["std"] }
|
gif = { version = "^0.13", default-features = false, features = ["std"] }
|
||||||
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
hex = { version = "^0.4", default-features = false, features = ["std"] }
|
||||||
image = { version = "0.25.5", default-features = false, features = ["jpeg", "png", "gif", "webp"] }
|
image = { version = "^0.25", default-features = false, features = ["jpeg", "png", "gif", "webp"] }
|
||||||
memmap2 = "0.9.5"
|
memmap2 = "^0.9"
|
||||||
# openssl = { version = "0.10.68", features = ["vendored"] }
|
# openssl = { version = "^0.10", features = ["vendored"] }
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "^0.12"
|
||||||
paste = "1.0.15"
|
paste = "^1.0"
|
||||||
prost = "0.13.4"
|
prost = "^0.13"
|
||||||
rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] }
|
prost-types = "^0.13"
|
||||||
regex = { version = "1.11.1", default-features = false, features = ["std", "perf"] }
|
rand = { version = "^0.9", default-features = false, features = ["thread_rng"] }
|
||||||
reqwest = { version = "0.12.12", default-features = false, features = ["gzip", "brotli", "json", "stream", "socks", "__tls", "charset", "default-tls", "h2", "http2", "macos-system-configuration"] }
|
regex = { version = "^1.11", default-features = false, features = ["std", "perf"] }
|
||||||
rkyv = { version = "0.7.45", default-features = false, features = ["alloc", "std", "bytecheck", "size_64", "validation", "std"] }
|
reqwest = { version = "^0.12", default-features = false, features = ["gzip", "brotli", "json", "stream", "socks", "__tls", "charset", "default-tls", "h2", "http2", "macos-system-configuration"] }
|
||||||
serde = { version = "1.0.217", default-features = false, features = ["std", "derive"] }
|
rkyv = { version = "^0.7", default-features = false, features = ["alloc", "std", "bytecheck", "size_64", "validation", "std"] }
|
||||||
serde_json = { package = "sonic-rs", version = "0.3.17" }
|
serde = { version = "^1.0", default-features = false, features = ["std", "derive"] }
|
||||||
# serde_json = "1.0.137"
|
serde_json = { package = "sonic-rs", version = "^0.3" }
|
||||||
sha2 = { version = "0.10.8", default-features = false }
|
# serde_json = "^1.0"
|
||||||
sysinfo = { version = "0.33.1", default-features = false, features = ["system"] }
|
sha2 = { version = "^0.10", default-features = false }
|
||||||
tokio = { version = "1.43.0", features = ["rt-multi-thread", "macros", "net", "sync", "time", "fs", "signal"] }
|
sysinfo = { version = "^0.33", default-features = false, features = ["system"] }
|
||||||
tokio-stream = { version = "0.1.17", features = ["time"] }
|
tokio = { version = "^1.43", features = ["rt-multi-thread", "macros", "net", "sync", "time", "fs", "signal"] }
|
||||||
tower-http = { version = "0.6.2", features = ["cors", "limit"] }
|
tokio-stream = { version = "^0.1", features = ["time"] }
|
||||||
url = { version = "2.5.4", default-features = false }
|
tower-http = { version = "^0.6", features = ["cors", "limit"] }
|
||||||
uuid = { version = "1.12.1", features = ["v4"] }
|
url = { version = "^2.5", default-features = false }
|
||||||
|
uuid = { version = "^1.14", features = ["v4"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
10
Dockerfile
10
Dockerfile
@@ -1,5 +1,5 @@
|
|||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
FROM --platform=linux/${TARGETARCH} rust:1.84.0-slim-bookworm as builder
|
FROM --platform=linux/${TARGETARCH} rust:1-slim-bookworm as builder
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
@@ -10,13 +10,7 @@ RUN apt-get update && \
|
|||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN case "$TARGETARCH" in \
|
RUN case "$TARGETARCH" in amd64) TARGET_CPU="x86-64-v3" ;; arm64) TARGET_CPU="neoverse-n1" ;; *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; esac && RUSTFLAGS="-C link-arg=-s -C target-cpu=$TARGET_CPU" cargo build --release && cp target/release/cursor-api /app/cursor-api
|
||||||
amd64) TARGET_CPU="x86-64-v3" ;; \
|
|
||||||
arm64) TARGET_CPU="neoverse-n1" ;; \
|
|
||||||
*) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \
|
|
||||||
esac && \
|
|
||||||
RUSTFLAGS="-C link-arg=-s -C target-cpu=$TARGET_CPU" cargo build --release && \
|
|
||||||
cp target/release/cursor-api /app/cursor-api
|
|
||||||
|
|
||||||
# 运行阶段
|
# 运行阶段
|
||||||
FROM --platform=linux/${TARGETARCH} debian:bookworm-slim
|
FROM --platform=linux/${TARGETARCH} debian:bookworm-slim
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Dockerfile.cross
|
# Dockerfile.cross
|
||||||
|
|
||||||
FROM --platform=linux/amd64 rust:1.84.0-slim-bookworm
|
FROM --platform=linux/amd64 rust:1-slim-bookworm
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Dockerfile.cross
|
# Dockerfile.cross
|
||||||
|
|
||||||
FROM --platform=linux/arm64 rust:1.84.0-slim-bookworm
|
FROM --platform=linux/arm64 rust:1-slim-bookworm
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
94
README.md
94
README.md
@@ -59,17 +59,18 @@ gpt-4o-128k
|
|||||||
gemini-1.5-flash-500k
|
gemini-1.5-flash-500k
|
||||||
claude-3-haiku-200k
|
claude-3-haiku-200k
|
||||||
claude-3-5-sonnet-200k
|
claude-3-5-sonnet-200k
|
||||||
claude-3-5-sonnet-20241022
|
|
||||||
gpt-4o-mini
|
gpt-4o-mini
|
||||||
o1-mini
|
o1-mini
|
||||||
o1-preview
|
o1-preview
|
||||||
o1
|
o1
|
||||||
claude-3.5-haiku
|
claude-3.5-haiku
|
||||||
gemini-exp-1206
|
gemini-2.0-pro-exp
|
||||||
gemini-2.0-flash-thinking-exp
|
gemini-2.0-flash-thinking-exp
|
||||||
gemini-2.0-flash-exp
|
gemini-2.0-flash
|
||||||
deepseek-v3
|
deepseek-v3
|
||||||
deepseek-r1
|
deepseek-r1
|
||||||
|
o3-mini
|
||||||
|
grok-2
|
||||||
```
|
```
|
||||||
|
|
||||||
## 接口说明
|
## 接口说明
|
||||||
@@ -148,6 +149,32 @@ data: {"id":"string","object":"chat.completion.chunk","created":number,"model":"
|
|||||||
data: [DONE]
|
data: [DONE]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 获取模型列表
|
||||||
|
|
||||||
|
* 接口地址: `/v1/models`
|
||||||
|
* 请求方法: GET
|
||||||
|
* 认证方式: Bearer Token
|
||||||
|
|
||||||
|
#### 响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"object": "list",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"object": "model",
|
||||||
|
"created": number,
|
||||||
|
"owned_by": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 更新模型列表说明
|
||||||
|
|
||||||
|
每次携带Token时都会拉取最新的模型列表,与上次更新需距离至少30分钟。
|
||||||
|
|
||||||
### Token管理接口
|
### Token管理接口
|
||||||
|
|
||||||
#### 简易Token信息管理页面
|
#### 简易Token信息管理页面
|
||||||
@@ -194,7 +221,8 @@ data: [DONE]
|
|||||||
"tokens": number,
|
"tokens": number,
|
||||||
"max_requests": number,
|
"max_requests": number,
|
||||||
"max_tokens": number
|
"max_tokens": number
|
||||||
}
|
},
|
||||||
|
"start_of_month": "string"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"email": "string",
|
"email": "string",
|
||||||
@@ -260,12 +288,15 @@ data: [DONE]
|
|||||||
* 请求格式:
|
* 请求格式:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
{
|
||||||
|
"tokens": [
|
||||||
{
|
{
|
||||||
"token": "string",
|
"token": "string",
|
||||||
"checksum": "string" // 可选,如果不提供将自动生成
|
"checksum": "string" // 可选,如果不提供将自动生成
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"tags": ["string"]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* 响应格式:
|
* 响应格式:
|
||||||
@@ -308,6 +339,29 @@ data: [DONE]
|
|||||||
- failed_tokens: 返回未找到的token列表
|
- failed_tokens: 返回未找到的token列表
|
||||||
- detailed: 返回完整信息(包括updated_tokens和failed_tokens)
|
- detailed: 返回完整信息(包括updated_tokens和failed_tokens)
|
||||||
|
|
||||||
|
#### 更新Tokens标签
|
||||||
|
|
||||||
|
* 接口地址: `/tokens/tags/update`
|
||||||
|
* 请求方法: POST
|
||||||
|
* 认证方式: Bearer Token
|
||||||
|
* 请求格式:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tokens": ["string"],
|
||||||
|
"tags": ["string"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* 响应格式:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"message": "string" // "标签更新成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### 构建API Key
|
#### 构建API Key
|
||||||
|
|
||||||
* 接口地址: `/build-key`
|
* 接口地址: `/build-key`
|
||||||
@@ -354,7 +408,7 @@ data: [DONE]
|
|||||||
|------|------|
|
|------|------|
|
||||||
| 提取关键信息,生成更短的密钥 | 可能存在版本兼容性问题 |
|
| 提取关键信息,生成更短的密钥 | 可能存在版本兼容性问题 |
|
||||||
| 支持携带自定义配置 | 增加了程序复杂度 |
|
| 支持携带自定义配置 | 增加了程序复杂度 |
|
||||||
| 采用非常规编码方式,提升安全性 | |
|
| 采用非常规编码方式,提升安全性 | 项目是开源的,安全性的提升相当于没有 |
|
||||||
| 更容易验证Key的合法性 | |
|
| 更容易验证Key的合法性 | |
|
||||||
| 取消预校验带来轻微性能提升 | |
|
| 取消预校验带来轻微性能提升 | |
|
||||||
|
|
||||||
@@ -470,26 +524,6 @@ data: [DONE]
|
|||||||
|
|
||||||
### 其他接口
|
### 其他接口
|
||||||
|
|
||||||
#### 获取模型列表
|
|
||||||
|
|
||||||
* 接口地址: `/v1/models`
|
|
||||||
* 请求方法: GET
|
|
||||||
* 响应格式:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"object": "list",
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "string",
|
|
||||||
"object": "model",
|
|
||||||
"created": number,
|
|
||||||
"owned_by": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取一个随机hash
|
#### 获取一个随机hash
|
||||||
|
|
||||||
* 接口地址: `/get-hash`
|
* 接口地址: `/get-hash`
|
||||||
@@ -605,7 +639,8 @@ string
|
|||||||
"tokens": number,
|
"tokens": number,
|
||||||
"max_requests": number,
|
"max_requests": number,
|
||||||
"max_tokens": number
|
"max_tokens": number
|
||||||
}
|
},
|
||||||
|
"start_of_month": "string"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"email": "string",
|
"email": "string",
|
||||||
@@ -673,7 +708,8 @@ string
|
|||||||
"tokens": number,
|
"tokens": number,
|
||||||
"max_requests": number,
|
"max_requests": number,
|
||||||
"max_tokens": number
|
"max_tokens": number
|
||||||
}
|
},
|
||||||
|
"start_of_month": "string"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"email": "string",
|
"email": "string",
|
||||||
|
21
build.rs
21
build.rs
@@ -116,7 +116,7 @@ fn minify_assets() -> Result<()> {
|
|||||||
let files_to_update: Vec<_> = current_hashes
|
let files_to_update: Vec<_> = current_hashes
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(path, current_hash)| {
|
.filter(|(path, current_hash)| {
|
||||||
let is_readme = path.file_name().map_or(false, |f| f == "README.md");
|
let is_readme = path.file_name().is_some_and(|f| f == "README.md");
|
||||||
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
|
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
|
||||||
|
|
||||||
// 为 README.md 和其他文件使用不同的输出路径检查
|
// 为 README.md 和其他文件使用不同的输出路径检查
|
||||||
@@ -136,9 +136,7 @@ fn minify_assets() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查原始文件是否发生变化
|
// 检查原始文件是否发生变化
|
||||||
saved_hashes
|
saved_hashes.get(*path) != Some(*current_hash)
|
||||||
.get(*path)
|
|
||||||
.map_or(true, |saved_hash| saved_hash != *current_hash)
|
|
||||||
})
|
})
|
||||||
.map(|(path, _)| path.file_name().unwrap().to_string_lossy().into_owned())
|
.map(|(path, _)| path.file_name().unwrap().to_string_lossy().into_owned())
|
||||||
.collect();
|
.collect();
|
||||||
@@ -168,7 +166,7 @@ fn minify_assets() -> Result<()> {
|
|||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
// Proto 文件处理
|
// Proto 文件处理
|
||||||
println!("cargo:rerun-if-changed=src/chat/aiserver/v1/lite.proto");
|
// println!("cargo:rerun-if-changed=src/chat/aiserver/v1/lite.proto");
|
||||||
println!("cargo:rerun-if-changed=src/chat/config/key.proto");
|
println!("cargo:rerun-if-changed=src/chat/config/key.proto");
|
||||||
// 获取环境变量 PROTOC
|
// 获取环境变量 PROTOC
|
||||||
let protoc_path = match std::env::var_os("PROTOC") {
|
let protoc_path = match std::env::var_os("PROTOC") {
|
||||||
@@ -185,12 +183,13 @@ fn main() -> Result<()> {
|
|||||||
config.protoc_executable(protoc_path);
|
config.protoc_executable(protoc_path);
|
||||||
}
|
}
|
||||||
// config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
|
// config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
|
||||||
config
|
// config.enum_attribute(".aiserver.v1", "#[allow(clippy::enum_variant_names)]");
|
||||||
.compile_protos(
|
// config
|
||||||
&["src/chat/aiserver/v1/lite.proto"],
|
// .compile_protos(
|
||||||
&["src/chat/aiserver/v1/"],
|
// &["src/chat/aiserver/v1/lite.proto"],
|
||||||
)
|
// &["src/chat/aiserver/v1/"],
|
||||||
.unwrap();
|
// )
|
||||||
|
// .unwrap();
|
||||||
config
|
config
|
||||||
.compile_protos(&["src/chat/config/key.proto"], &["src/chat/config/"])
|
.compile_protos(&["src/chat/config/key.proto"], &["src/chat/config/"])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@@ -2,3 +2,4 @@ pub mod config;
|
|||||||
pub mod constant;
|
pub mod constant;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod lazy;
|
pub mod lazy;
|
||||||
|
// pub mod rule;
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
use super::{constant::AUTHORIZATION_BEARER_PREFIX, lazy::AUTH_TOKEN, model::AppConfig};
|
use super::{constant::AUTHORIZATION_BEARER_PREFIX, lazy::AUTH_TOKEN, model::AppConfig};
|
||||||
use crate::common::model::{
|
use crate::common::model::{
|
||||||
config::{ConfigData, ConfigUpdateRequest},
|
|
||||||
ApiStatus, ErrorResponse, NormalResponse,
|
ApiStatus, ErrorResponse, NormalResponse,
|
||||||
|
config::{ConfigData, ConfigUpdateRequest},
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
http::{header::AUTHORIZATION, HeaderMap, StatusCode},
|
|
||||||
Json,
|
Json,
|
||||||
|
http::{HeaderMap, StatusCode, header::AUTHORIZATION},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定义处理更新操作的宏
|
// 定义处理更新操作的宏
|
||||||
@@ -41,7 +41,7 @@ pub async fn handle_config_update(
|
|||||||
.ok_or((
|
.ok_or((
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
Json(ErrorResponse {
|
Json(ErrorResponse {
|
||||||
status: ApiStatus::Failed,
|
status: ApiStatus::Failure,
|
||||||
code: Some(401),
|
code: Some(401),
|
||||||
error: Some("未提供认证令牌".to_string()),
|
error: Some("未提供认证令牌".to_string()),
|
||||||
message: None,
|
message: None,
|
||||||
@@ -52,7 +52,7 @@ pub async fn handle_config_update(
|
|||||||
return Err((
|
return Err((
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
Json(ErrorResponse {
|
Json(ErrorResponse {
|
||||||
status: ApiStatus::Failed,
|
status: ApiStatus::Failure,
|
||||||
code: Some(401),
|
code: Some(401),
|
||||||
error: Some("无效的认证令牌".to_string()),
|
error: Some("无效的认证令牌".to_string()),
|
||||||
message: None,
|
message: None,
|
||||||
@@ -85,7 +85,7 @@ pub async fn handle_config_update(
|
|||||||
return Err((
|
return Err((
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(ErrorResponse {
|
Json(ErrorResponse {
|
||||||
status: ApiStatus::Failed,
|
status: ApiStatus::Failure,
|
||||||
code: Some(500),
|
code: Some(500),
|
||||||
error: Some(format!("更新页面内容失败: {}", e)),
|
error: Some(format!("更新页面内容失败: {}", e)),
|
||||||
message: None,
|
message: None,
|
||||||
@@ -119,7 +119,7 @@ pub async fn handle_config_update(
|
|||||||
return Err((
|
return Err((
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(ErrorResponse {
|
Json(ErrorResponse {
|
||||||
status: ApiStatus::Failed,
|
status: ApiStatus::Failure,
|
||||||
code: Some(500),
|
code: Some(500),
|
||||||
error: Some(format!("重置页面内容失败: {}", e)),
|
error: Some(format!("重置页面内容失败: {}", e)),
|
||||||
message: None,
|
message: None,
|
||||||
@@ -149,7 +149,7 @@ pub async fn handle_config_update(
|
|||||||
_ => Err((
|
_ => Err((
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(ErrorResponse {
|
Json(ErrorResponse {
|
||||||
status: ApiStatus::Failed,
|
status: ApiStatus::Failure,
|
||||||
code: Some(400),
|
code: Some(400),
|
||||||
error: Some("无效的操作类型".to_string()),
|
error: Some("无效的操作类型".to_string()),
|
||||||
message: None,
|
message: None,
|
||||||
|
@@ -1,83 +1,122 @@
|
|||||||
macro_rules! def_pub_const {
|
macro_rules! def_pub_const {
|
||||||
($name:ident, $value:expr) => {
|
// 单个常量定义
|
||||||
|
// ($name:ident, $value:expr) => {
|
||||||
|
// pub const $name: &'static str = $value;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 批量常量定义
|
||||||
|
($($name:ident => $value:expr),+ $(,)?) => {
|
||||||
|
$(
|
||||||
pub const $name: &'static str = $value;
|
pub const $name: &'static str = $value;
|
||||||
|
)+
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const COMMA: char = ',';
|
pub const COMMA: char = ',';
|
||||||
|
|
||||||
def_pub_const!(PKG_VERSION, env!("CARGO_PKG_VERSION"));
|
// Package related constants
|
||||||
// def_pub_const!(PKG_NAME, env!("CARGO_PKG_NAME"));
|
|
||||||
// def_pub_const!(PKG_DESCRIPTION, env!("CARGO_PKG_DESCRIPTION"));
|
|
||||||
// def_pub_const!(PKG_AUTHORS, env!("CARGO_PKG_AUTHORS"));
|
|
||||||
// def_pub_const!(PKG_REPOSITORY, env!("CARGO_PKG_REPOSITORY"));
|
|
||||||
|
|
||||||
def_pub_const!(EMPTY_STRING, "");
|
|
||||||
|
|
||||||
def_pub_const!(COMMA_STRING, ",");
|
|
||||||
|
|
||||||
def_pub_const!(ROUTE_ROOT_PATH, "/");
|
|
||||||
def_pub_const!(ROUTE_HEALTH_PATH, "/health");
|
|
||||||
def_pub_const!(ROUTE_GET_HASH, "/get-hash");
|
|
||||||
def_pub_const!(ROUTE_GET_CHECKSUM, "/get-checksum");
|
|
||||||
def_pub_const!(ROUTE_GET_TIMESTAMP_HEADER, "/get-tsheader");
|
|
||||||
def_pub_const!(ROUTE_USER_INFO_PATH, "/userinfo");
|
|
||||||
def_pub_const!(ROUTE_API_PATH, "/api");
|
|
||||||
def_pub_const!(ROUTE_LOGS_PATH, "/logs");
|
|
||||||
def_pub_const!(ROUTE_CONFIG_PATH, "/config");
|
|
||||||
def_pub_const!(ROUTE_TOKENS_PATH, "/tokens");
|
|
||||||
def_pub_const!(ROUTE_TOKENS_GET_PATH, "/tokens/get");
|
|
||||||
def_pub_const!(ROUTE_TOKENS_RELOAD_PATH, "/tokens/reload");
|
|
||||||
def_pub_const!(ROUTE_TOKENS_UPDATE_PATH, "/tokens/update");
|
|
||||||
def_pub_const!(ROUTE_TOKENS_ADD_PATH, "/tokens/add");
|
|
||||||
def_pub_const!(ROUTE_TOKENS_DELETE_PATH, "/tokens/delete");
|
|
||||||
def_pub_const!(ROUTE_ENV_EXAMPLE_PATH, "/env-example");
|
|
||||||
def_pub_const!(ROUTE_STATIC_PATH, "/static/{path}");
|
|
||||||
def_pub_const!(ROUTE_SHARED_STYLES_PATH, "/static/shared-styles.css");
|
|
||||||
def_pub_const!(ROUTE_SHARED_JS_PATH, "/static/shared.js");
|
|
||||||
def_pub_const!(ROUTE_ABOUT_PATH, "/about");
|
|
||||||
def_pub_const!(ROUTE_README_PATH, "/readme");
|
|
||||||
def_pub_const!(ROUTE_BASIC_CALIBRATION_PATH, "/basic-calibration");
|
|
||||||
def_pub_const!(ROUTE_BUILD_KEY_PATH, "/build-key");
|
|
||||||
|
|
||||||
def_pub_const!(DEFAULT_TOKEN_LIST_FILE_NAME, ".tokens");
|
|
||||||
|
|
||||||
def_pub_const!(STATUS_PENDING, "pending");
|
|
||||||
def_pub_const!(STATUS_SUCCESS, "success");
|
|
||||||
def_pub_const!(STATUS_FAILED, "failed");
|
|
||||||
|
|
||||||
def_pub_const!(HEADER_NAME_GHOST_MODE, "x-ghost-mode");
|
|
||||||
|
|
||||||
def_pub_const!(TRUE, "true");
|
|
||||||
def_pub_const!(FALSE, "false");
|
|
||||||
|
|
||||||
// def_pub_const!(CONTENT_TYPE_PROTO, "application/proto");
|
|
||||||
def_pub_const!(CONTENT_TYPE_CONNECT_PROTO, "application/connect+proto");
|
|
||||||
def_pub_const!(CONTENT_TYPE_TEXT_HTML_WITH_UTF8, "text/html;charset=utf-8");
|
|
||||||
def_pub_const!(
|
def_pub_const!(
|
||||||
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8,
|
PKG_VERSION => env!("CARGO_PKG_VERSION")
|
||||||
"text/plain;charset=utf-8"
|
// PKG_NAME => env!("CARGO_PKG_NAME"),
|
||||||
);
|
// PKG_DESCRIPTION => env!("CARGO_PKG_DESCRIPTION"),
|
||||||
def_pub_const!(CONTENT_TYPE_TEXT_CSS_WITH_UTF8, "text/css;charset=utf-8");
|
// PKG_AUTHORS => env!("CARGO_PKG_AUTHORS"),
|
||||||
def_pub_const!(
|
// PKG_REPOSITORY => env!("CARGO_PKG_REPOSITORY")
|
||||||
CONTENT_TYPE_TEXT_JS_WITH_UTF8,
|
|
||||||
"text/javascript;charset=utf-8"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
def_pub_const!(AUTHORIZATION_BEARER_PREFIX, "Bearer ");
|
// Basic string constants
|
||||||
|
def_pub_const!(
|
||||||
|
EMPTY_STRING => "",
|
||||||
|
COMMA_STRING => ","
|
||||||
|
);
|
||||||
|
|
||||||
def_pub_const!(CURSOR_API2_HOST, "api2.cursor.sh");
|
// Route related constants
|
||||||
def_pub_const!(CURSOR_HOST, "www.cursor.com");
|
def_pub_const!(
|
||||||
def_pub_const!(CURSOR_SETTINGS_URL, "https://www.cursor.com/settings");
|
ROUTE_ROOT_PATH => "/",
|
||||||
|
ROUTE_HEALTH_PATH => "/health",
|
||||||
|
ROUTE_GET_HASH => "/get-hash",
|
||||||
|
ROUTE_GET_CHECKSUM => "/get-checksum",
|
||||||
|
ROUTE_GET_TIMESTAMP_HEADER => "/get-tsheader",
|
||||||
|
ROUTE_USER_INFO_PATH => "/userinfo",
|
||||||
|
ROUTE_API_PATH => "/api",
|
||||||
|
ROUTE_LOGS_PATH => "/logs",
|
||||||
|
ROUTE_CONFIG_PATH => "/config",
|
||||||
|
ROUTE_TOKENS_PATH => "/tokens",
|
||||||
|
ROUTE_TOKENS_GET_PATH => "/tokens/get",
|
||||||
|
ROUTE_TOKENS_UPDATE_PATH => "/tokens/update",
|
||||||
|
ROUTE_TOKENS_ADD_PATH => "/tokens/add",
|
||||||
|
ROUTE_TOKENS_DELETE_PATH => "/tokens/delete",
|
||||||
|
ROUTE_TOKEN_TAGS_UPDATE_PATH => "/tokens/tags/update",
|
||||||
|
ROUTE_ENV_EXAMPLE_PATH => "/env-example",
|
||||||
|
ROUTE_STATIC_PATH => "/static/{path}",
|
||||||
|
ROUTE_SHARED_STYLES_PATH => "/static/shared-styles.css",
|
||||||
|
ROUTE_SHARED_JS_PATH => "/static/shared.js",
|
||||||
|
ROUTE_ABOUT_PATH => "/about",
|
||||||
|
ROUTE_README_PATH => "/readme",
|
||||||
|
ROUTE_BASIC_CALIBRATION_PATH => "/basic-calibration",
|
||||||
|
ROUTE_BUILD_KEY_PATH => "/build-key"
|
||||||
|
);
|
||||||
|
|
||||||
def_pub_const!(OBJECT_CHAT_COMPLETION, "chat.completion");
|
// def_pub_const!(DEFAULT_TOKEN_LIST_FILE_NAME => ".tokens");
|
||||||
def_pub_const!(OBJECT_CHAT_COMPLETION_CHUNK, "chat.completion.chunk");
|
|
||||||
|
|
||||||
// def_pub_const!(CURSOR_API2_STREAM_CHAT, "StreamChat");
|
// Status constants
|
||||||
// def_pub_const!(CURSOR_API2_GET_USER_INFO, "GetUserInfo");
|
def_pub_const!(
|
||||||
|
STATUS_PENDING => "pending",
|
||||||
|
STATUS_SUCCESS => "success",
|
||||||
|
STATUS_FAILED => "failed"
|
||||||
|
);
|
||||||
|
|
||||||
def_pub_const!(FINISH_REASON_STOP, "stop");
|
// Header constants
|
||||||
|
def_pub_const!(
|
||||||
|
HEADER_NAME_GHOST_MODE => "x-ghost-mode"
|
||||||
|
);
|
||||||
|
|
||||||
def_pub_const!(ERR_INVALID_PATH, "无效的路径");
|
// Boolean constants
|
||||||
|
def_pub_const!(
|
||||||
|
TRUE => "true",
|
||||||
|
FALSE => "false"
|
||||||
|
);
|
||||||
|
|
||||||
// def_pub_const!(ERR_CHECKSUM_NO_GOOD, "checksum no good");
|
// Content type constants
|
||||||
|
def_pub_const!(
|
||||||
|
CONTENT_TYPE_PROTO => "application/proto",
|
||||||
|
CONTENT_TYPE_CONNECT_PROTO => "application/connect+proto",
|
||||||
|
CONTENT_TYPE_TEXT_HTML_WITH_UTF8 => "text/html;charset=utf-8",
|
||||||
|
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8 => "text/plain;charset=utf-8",
|
||||||
|
CONTENT_TYPE_TEXT_CSS_WITH_UTF8 => "text/css;charset=utf-8",
|
||||||
|
CONTENT_TYPE_TEXT_JS_WITH_UTF8 => "text/javascript;charset=utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Authorization constants
|
||||||
|
def_pub_const!(
|
||||||
|
AUTHORIZATION_BEARER_PREFIX => "Bearer "
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cursor related constants
|
||||||
|
def_pub_const!(
|
||||||
|
CURSOR_API2_HOST => "api2.cursor.sh",
|
||||||
|
CURSOR_HOST => "www.cursor.com",
|
||||||
|
CURSOR_SETTINGS_URL => "https://www.cursor.com/settings"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Object type constants
|
||||||
|
def_pub_const!(
|
||||||
|
OBJECT_CHAT_COMPLETION => "chat.completion",
|
||||||
|
OBJECT_CHAT_COMPLETION_CHUNK => "chat.completion.chunk"
|
||||||
|
);
|
||||||
|
|
||||||
|
// def_pub_const!(
|
||||||
|
// CURSOR_API2_STREAM_CHAT => "StreamChat",
|
||||||
|
// CURSOR_API2_GET_USER_INFO => "GetUserInfo"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Finish reason constants
|
||||||
|
def_pub_const!(
|
||||||
|
FINISH_REASON_STOP => "stop"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Error message constants
|
||||||
|
def_pub_const!(
|
||||||
|
ERR_INVALID_PATH => "无效的路径"
|
||||||
|
);
|
||||||
|
|
||||||
|
// def_pub_const!(ERR_CHECKSUM_NO_GOOD => "checksum no good");
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
use super::constant::{
|
use super::constant::{COMMA, CURSOR_API2_HOST, CURSOR_HOST, EMPTY_STRING};
|
||||||
COMMA, CURSOR_API2_HOST, CURSOR_HOST, DEFAULT_TOKEN_LIST_FILE_NAME, EMPTY_STRING,
|
|
||||||
};
|
|
||||||
use crate::common::utils::{
|
use crate::common::utils::{
|
||||||
parse_ascii_char_from_env, parse_bool_from_env, parse_string_from_env, parse_usize_from_env,
|
parse_ascii_char_from_env, parse_bool_from_env, parse_string_from_env, parse_usize_from_env,
|
||||||
};
|
};
|
||||||
use std::sync::LazyLock;
|
use chrono::Local;
|
||||||
|
use std::{path::PathBuf, sync::LazyLock};
|
||||||
use tokio::sync::{Mutex, OnceCell};
|
use tokio::sync::{Mutex, OnceCell};
|
||||||
|
|
||||||
macro_rules! def_pub_static {
|
macro_rules! def_pub_static {
|
||||||
@@ -32,17 +31,15 @@ macro_rules! def_pub_static {
|
|||||||
|
|
||||||
def_pub_static!(ROUTE_PREFIX, env: "ROUTE_PREFIX", default: EMPTY_STRING);
|
def_pub_static!(ROUTE_PREFIX, env: "ROUTE_PREFIX", default: EMPTY_STRING);
|
||||||
def_pub_static!(AUTH_TOKEN, env: "AUTH_TOKEN", default: EMPTY_STRING);
|
def_pub_static!(AUTH_TOKEN, env: "AUTH_TOKEN", default: EMPTY_STRING);
|
||||||
def_pub_static!(TOKEN_LIST_FILE, env: "TOKEN_LIST_FILE", default: DEFAULT_TOKEN_LIST_FILE_NAME);
|
|
||||||
def_pub_static!(ROUTE_MODELS_PATH, format!("{}/v1/models", *ROUTE_PREFIX));
|
def_pub_static!(ROUTE_MODELS_PATH, format!("{}/v1/models", *ROUTE_PREFIX));
|
||||||
def_pub_static!(
|
def_pub_static!(
|
||||||
ROUTE_CHAT_PATH,
|
ROUTE_CHAT_PATH,
|
||||||
format!("{}/v1/chat/completions", *ROUTE_PREFIX)
|
format!("{}/v1/chat/completions", *ROUTE_PREFIX)
|
||||||
);
|
);
|
||||||
|
|
||||||
pub static START_TIME: LazyLock<chrono::DateTime<chrono::Local>> =
|
pub static START_TIME: LazyLock<chrono::DateTime<Local>> = LazyLock::new(Local::now);
|
||||||
LazyLock::new(chrono::Local::now);
|
|
||||||
|
|
||||||
pub fn get_start_time() -> chrono::DateTime<chrono::Local> {
|
pub fn get_start_time() -> chrono::DateTime<Local> {
|
||||||
*START_TIME
|
*START_TIME
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +111,12 @@ def_cursor_api_url!(
|
|||||||
"/aiserver.v1.AiService/StreamChatWeb"
|
"/aiserver.v1.AiService/StreamChatWeb"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
def_cursor_api_url!(
|
||||||
|
CURSOR_API2_CHAT_MODELS_URL,
|
||||||
|
CURSOR_API2_HOST,
|
||||||
|
"/aiserver.v1.AiService/AvailableModels"
|
||||||
|
);
|
||||||
|
|
||||||
def_cursor_api_url!(
|
def_cursor_api_url!(
|
||||||
CURSOR_API2_STRIPE_URL,
|
CURSOR_API2_STRIPE_URL,
|
||||||
CURSOR_API2_HOST,
|
CURSOR_API2_HOST,
|
||||||
@@ -124,11 +127,26 @@ def_cursor_api_url!(CURSOR_USAGE_API_URL, CURSOR_HOST, "/api/usage");
|
|||||||
|
|
||||||
def_cursor_api_url!(CURSOR_USER_API_URL, CURSOR_HOST, "/api/auth/me");
|
def_cursor_api_url!(CURSOR_USER_API_URL, CURSOR_HOST, "/api/auth/me");
|
||||||
|
|
||||||
pub(super) static LOGS_FILE_PATH: LazyLock<String> =
|
static DATA_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||||
LazyLock::new(|| parse_string_from_env("LOGS_FILE_PATH", "logs.bin"));
|
let data_dir = parse_string_from_env("DATA_DIR", "data");
|
||||||
|
let path = std::env::current_exe()
|
||||||
|
.ok()
|
||||||
|
.and_then(|exe_path| exe_path.parent().map(|p| p.to_path_buf()))
|
||||||
|
.unwrap_or_else(|| PathBuf::from("."))
|
||||||
|
.join(data_dir);
|
||||||
|
if !path.exists() {
|
||||||
|
std::fs::create_dir_all(&path).expect("无法创建数据目录");
|
||||||
|
}
|
||||||
|
path
|
||||||
|
});
|
||||||
|
|
||||||
pub(super) static PAGES_FILE_PATH: LazyLock<String> =
|
pub(super) static CONFIG_FILE_PATH: LazyLock<PathBuf> =
|
||||||
LazyLock::new(|| parse_string_from_env("PAGES_FILE_PATH", "pages.bin"));
|
LazyLock::new(|| DATA_DIR.join("config.bin"));
|
||||||
|
|
||||||
|
pub(super) static LOGS_FILE_PATH: LazyLock<PathBuf> = LazyLock::new(|| DATA_DIR.join("logs.bin"));
|
||||||
|
|
||||||
|
pub(super) static TOKENS_FILE_PATH: LazyLock<PathBuf> =
|
||||||
|
LazyLock::new(|| DATA_DIR.join("tokens.bin"));
|
||||||
|
|
||||||
pub static DEBUG: LazyLock<bool> = LazyLock::new(|| parse_bool_from_env("DEBUG", false));
|
pub static DEBUG: LazyLock<bool> = LazyLock::new(|| parse_bool_from_env("DEBUG", false));
|
||||||
|
|
||||||
@@ -157,14 +175,14 @@ pub(crate) async fn get_log_file() -> &'static Mutex<tokio::fs::File> {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! debug_println {
|
macro_rules! debug_println {
|
||||||
($($arg:tt)*) => {
|
($($arg:tt)*) => {
|
||||||
if *crate::app::lazy::DEBUG {
|
if *$crate::app::lazy::DEBUG {
|
||||||
let time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
let time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||||
let log_message = format!("{} - {}", time, format!($($arg)*));
|
let log_message = format!("{} - {}", time, format!($($arg)*));
|
||||||
use tokio::io::AsyncWriteExt as _;
|
use tokio::io::AsyncWriteExt as _;
|
||||||
|
|
||||||
// 使用 tokio 的 spawn 在后台异步写入日志
|
// 使用 tokio 的 spawn 在后台异步写入日志
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let log_file = crate::app::lazy::get_log_file().await;
|
let log_file = $crate::app::lazy::get_log_file().await;
|
||||||
// 使用 MutexGuard 获取可变引用
|
// 使用 MutexGuard 获取可变引用
|
||||||
let mut file = log_file.lock().await;
|
let mut file = log_file.lock().await;
|
||||||
if let Err(err) = file.write_all(log_message.as_bytes()).await {
|
if let Err(err) = file.write_all(log_message.as_bytes()).await {
|
||||||
@@ -183,6 +201,8 @@ macro_rules! debug_println {
|
|||||||
pub static REQUEST_LOGS_LIMIT: LazyLock<usize> =
|
pub static REQUEST_LOGS_LIMIT: LazyLock<usize> =
|
||||||
LazyLock::new(|| std::cmp::min(parse_usize_from_env("REQUEST_LOGS_LIMIT", 100), 2000));
|
LazyLock::new(|| std::cmp::min(parse_usize_from_env("REQUEST_LOGS_LIMIT", 100), 2000));
|
||||||
|
|
||||||
|
pub static IS_UNLIMITED_REQUEST_LOGS: LazyLock<bool> = LazyLock::new(|| *REQUEST_LOGS_LIMIT == 0);
|
||||||
|
|
||||||
pub static SERVICE_TIMEOUT: LazyLock<u64> = LazyLock::new(|| {
|
pub static SERVICE_TIMEOUT: LazyLock<u64> = LazyLock::new(|| {
|
||||||
let timeout = parse_usize_from_env("SERVICE_TIMEOUT", 30);
|
let timeout = parse_usize_from_env("SERVICE_TIMEOUT", 30);
|
||||||
u64::try_from(timeout).map(|t| t.min(600)).unwrap_or(30)
|
u64::try_from(timeout).map(|t| t.min(600)).unwrap_or(30)
|
||||||
|
491
src/app/model.rs
491
src/app/model.rs
@@ -1,30 +1,30 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::constant::{
|
|
||||||
EMPTY_STRING, ERR_INVALID_PATH, ROUTE_ABOUT_PATH, ROUTE_API_PATH, ROUTE_BUILD_KEY_PATH,
|
|
||||||
ROUTE_CONFIG_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH,
|
|
||||||
ROUTE_SHARED_JS_PATH, ROUTE_SHARED_STYLES_PATH, ROUTE_TOKENS_PATH,
|
|
||||||
},
|
|
||||||
chat::model::Message,
|
chat::model::Message,
|
||||||
common::{
|
common::{
|
||||||
client::rebuild_http_client,
|
model::{ApiStatus, userinfo::TokenProfile},
|
||||||
model::{userinfo::TokenProfile, ApiStatus},
|
utils::{generate_checksum_with_repair, get_token_profile},
|
||||||
utils::{generate_checksum_with_repair, parse_bool_from_env, parse_string_from_env},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use parking_lot::RwLock;
|
use memmap2::{MmapMut, MmapOptions};
|
||||||
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::LazyLock;
|
use std::{collections::HashSet, fs::OpenOptions};
|
||||||
|
|
||||||
mod usage_check;
|
mod usage_check;
|
||||||
pub use usage_check::UsageCheck;
|
pub use usage_check::UsageCheck;
|
||||||
|
mod vision_ability;
|
||||||
|
pub use vision_ability::VisionAbility;
|
||||||
mod config;
|
mod config;
|
||||||
|
pub use config::AppConfig;
|
||||||
mod proxies;
|
mod proxies;
|
||||||
pub use proxies::Proxies;
|
pub use proxies::Proxies;
|
||||||
mod build_key;
|
mod build_key;
|
||||||
pub use build_key::*;
|
pub use build_key::*;
|
||||||
|
|
||||||
use super::constant::{STATUS_FAILED, STATUS_PENDING, STATUS_SUCCESS};
|
use super::{
|
||||||
|
constant::{STATUS_FAILED, STATUS_PENDING, STATUS_SUCCESS},
|
||||||
|
lazy::{LOGS_FILE_PATH, TOKENS_FILE_PATH},
|
||||||
|
};
|
||||||
|
|
||||||
// 页面内容类型枚举
|
// 页面内容类型枚举
|
||||||
#[derive(Clone, Serialize, Deserialize, Archive, RkyvDeserialize, RkyvSerialize)]
|
#[derive(Clone, Serialize, Deserialize, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||||
@@ -44,52 +44,6 @@ impl Default for PageContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 静态配置
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
pub struct AppConfig {
|
|
||||||
vision_ability: VisionAbility,
|
|
||||||
slow_pool: bool,
|
|
||||||
allow_claude: bool,
|
|
||||||
pages: Pages,
|
|
||||||
usage_check: UsageCheck,
|
|
||||||
dynamic_key: bool,
|
|
||||||
share_token: String,
|
|
||||||
is_share: bool,
|
|
||||||
proxies: Proxies,
|
|
||||||
web_refs: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq)]
|
|
||||||
pub enum VisionAbility {
|
|
||||||
#[serde(rename = "none", alias = "disabled")]
|
|
||||||
None,
|
|
||||||
#[serde(rename = "base64", alias = "base64-only")]
|
|
||||||
Base64,
|
|
||||||
#[serde(rename = "all", alias = "base64-http")]
|
|
||||||
All,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisionAbility {
|
|
||||||
pub fn from_str(s: &str) -> Self {
|
|
||||||
match s.to_lowercase().as_str() {
|
|
||||||
"none" | "disabled" => Self::None,
|
|
||||||
"base64" | "base64-only" => Self::Base64,
|
|
||||||
"all" | "base64-http" => Self::All,
|
|
||||||
_ => Self::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_none(&self) -> bool {
|
|
||||||
matches!(self, VisionAbility::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for VisionAbility {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Base64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Archive, RkyvDeserialize, RkyvSerialize)]
|
#[derive(Clone, Default, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||||
pub struct Pages {
|
pub struct Pages {
|
||||||
pub root_content: PageContent,
|
pub root_content: PageContent,
|
||||||
@@ -104,232 +58,140 @@ pub struct Pages {
|
|||||||
pub build_key_content: PageContent,
|
pub build_key_content: PageContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 运行时状态
|
#[derive(Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||||
pub struct AppState {
|
pub struct TokenGroup {
|
||||||
|
pub index: u16,
|
||||||
|
pub name: String,
|
||||||
|
pub tokens: Vec<TokenInfo>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token管理器
|
||||||
|
#[derive(Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||||
|
pub struct TokenManager {
|
||||||
|
pub tokens: Vec<TokenInfo>,
|
||||||
|
pub tags: HashSet<String>, // 存储所有已使用的标签
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求统计管理器
|
||||||
|
#[derive(Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||||
|
pub struct RequestStatsManager {
|
||||||
pub total_requests: u64,
|
pub total_requests: u64,
|
||||||
pub active_requests: u64,
|
pub active_requests: u64,
|
||||||
pub error_requests: u64,
|
pub error_requests: u64,
|
||||||
pub request_logs: Vec<RequestLog>,
|
pub request_logs: Vec<RequestLog>,
|
||||||
pub token_infos: Vec<TokenInfo>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局配置实例
|
#[derive(Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||||
pub static APP_CONFIG: LazyLock<RwLock<AppConfig>> =
|
pub struct AppState {
|
||||||
LazyLock::new(|| RwLock::new(AppConfig::default()));
|
pub token_manager: TokenManager,
|
||||||
|
pub request_manager: RequestStatsManager,
|
||||||
macro_rules! config_methods {
|
|
||||||
($($field:ident: $type:ty, $default:expr;)*) => {
|
|
||||||
$(
|
|
||||||
paste::paste! {
|
|
||||||
pub fn [<get_ $field>]() -> $type
|
|
||||||
where
|
|
||||||
$type: Copy + PartialEq,
|
|
||||||
{
|
|
||||||
APP_CONFIG.read().$field
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<update_ $field>](value: $type)
|
|
||||||
where
|
|
||||||
$type: Copy + PartialEq,
|
|
||||||
{
|
|
||||||
let current = Self::[<get_ $field>]();
|
|
||||||
if current != value {
|
|
||||||
APP_CONFIG.write().$field = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<reset_ $field>]()
|
|
||||||
where
|
|
||||||
$type: Copy + PartialEq,
|
|
||||||
{
|
|
||||||
let default_value = $default;
|
|
||||||
let current = Self::[<get_ $field>]();
|
|
||||||
if current != default_value {
|
|
||||||
APP_CONFIG.write().$field = default_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! config_methods_clone {
|
impl TokenManager {
|
||||||
($($field:ident: $type:ty, $default:expr;)*) => {
|
pub fn new(tokens: Vec<TokenInfo>) -> Self {
|
||||||
$(
|
let mut tags = HashSet::new();
|
||||||
paste::paste! {
|
for token in &tokens {
|
||||||
pub fn [<get_ $field>]() -> $type
|
if let Some(token_tags) = &token.tags {
|
||||||
where
|
tags.extend(token_tags.iter().cloned());
|
||||||
$type: Clone + PartialEq,
|
|
||||||
{
|
|
||||||
APP_CONFIG.read().$field.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn [<update_ $field>](value: $type)
|
|
||||||
where
|
|
||||||
$type: Clone + PartialEq,
|
|
||||||
{
|
|
||||||
let current = Self::[<get_ $field>]();
|
|
||||||
if current != value {
|
|
||||||
APP_CONFIG.write().$field = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn [<reset_ $field>]()
|
Self { tokens, tags }
|
||||||
where
|
|
||||||
$type: Clone + PartialEq,
|
|
||||||
{
|
|
||||||
let default_value = $default;
|
|
||||||
let current = Self::[<get_ $field>]();
|
|
||||||
if current != default_value {
|
|
||||||
APP_CONFIG.write().$field = default_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppConfig {
|
|
||||||
pub fn init() {
|
|
||||||
let mut config = APP_CONFIG.write();
|
|
||||||
config.vision_ability =
|
|
||||||
VisionAbility::from_str(&parse_string_from_env("VISION_ABILITY", EMPTY_STRING));
|
|
||||||
config.slow_pool = parse_bool_from_env("ENABLE_SLOW_POOL", false);
|
|
||||||
config.allow_claude = parse_bool_from_env("PASS_ANY_CLAUDE", false);
|
|
||||||
config.usage_check =
|
|
||||||
UsageCheck::from_str(&parse_string_from_env("USAGE_CHECK", EMPTY_STRING));
|
|
||||||
config.dynamic_key = parse_bool_from_env("DYNAMIC_KEY", false);
|
|
||||||
config.share_token = parse_string_from_env("SHARED_TOKEN", EMPTY_STRING);
|
|
||||||
config.is_share = !config.share_token.is_empty();
|
|
||||||
config.proxies = match std::env::var("PROXIES") {
|
|
||||||
Ok(proxies) => Proxies::from_str(proxies.as_str()),
|
|
||||||
Err(_) => Proxies::default(),
|
|
||||||
};
|
|
||||||
config.web_refs = parse_bool_from_env("INCLUDE_WEB_REFERENCES", false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config_methods! {
|
pub fn update_global_tags(&mut self, new_tags: &[String]) {
|
||||||
slow_pool: bool, false;
|
// 将新标签添加到全局标签集合中
|
||||||
allow_claude: bool, false;
|
self.tags.extend(new_tags.iter().cloned());
|
||||||
dynamic_key: bool, false;
|
|
||||||
web_refs: bool, false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config_methods_clone! {
|
pub fn update_tokens_tags(
|
||||||
vision_ability: VisionAbility, VisionAbility::default();
|
&mut self,
|
||||||
usage_check: UsageCheck, UsageCheck::default();
|
tokens: Vec<String>,
|
||||||
}
|
new_tags: Vec<String>,
|
||||||
|
) -> Result<(), &'static str> {
|
||||||
|
// 创建tokens的HashSet用于快速查找
|
||||||
|
let tokens_set: HashSet<_> = tokens.iter().collect();
|
||||||
|
|
||||||
pub fn get_share_token() -> String {
|
// 更新指定tokens的标签
|
||||||
APP_CONFIG.read().share_token.clone()
|
for token_info in &mut self.tokens {
|
||||||
}
|
if tokens_set.contains(&token_info.token) {
|
||||||
|
token_info.tags = Some(new_tags.clone());
|
||||||
pub fn update_share_token(value: String) {
|
|
||||||
let current = Self::get_share_token();
|
|
||||||
if current != value {
|
|
||||||
let mut config = APP_CONFIG.write();
|
|
||||||
config.share_token = value;
|
|
||||||
config.is_share = !config.share_token.is_empty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_share_token() {
|
// 更新全局标签集合
|
||||||
let current = Self::get_share_token();
|
self.tags = self
|
||||||
if !current.is_empty() {
|
.tokens
|
||||||
let mut config = APP_CONFIG.write();
|
.iter()
|
||||||
config.share_token = String::new();
|
.filter_map(|t| t.tags.clone())
|
||||||
config.is_share = false;
|
.flatten()
|
||||||
}
|
.collect();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_proxies() -> Proxies {
|
|
||||||
APP_CONFIG.read().proxies.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_proxies(value: Proxies) {
|
|
||||||
let current = Self::get_proxies();
|
|
||||||
if current != value {
|
|
||||||
let mut config = APP_CONFIG.write();
|
|
||||||
config.proxies = value;
|
|
||||||
rebuild_http_client();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_proxies() {
|
|
||||||
let default_value = Proxies::default();
|
|
||||||
let current = Self::get_proxies();
|
|
||||||
if current != default_value {
|
|
||||||
let mut config = APP_CONFIG.write();
|
|
||||||
config.proxies = default_value;
|
|
||||||
rebuild_http_client();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_page_content(path: &str) -> Option<PageContent> {
|
|
||||||
match path {
|
|
||||||
ROUTE_ROOT_PATH => Some(APP_CONFIG.read().pages.root_content.clone()),
|
|
||||||
ROUTE_LOGS_PATH => Some(APP_CONFIG.read().pages.logs_content.clone()),
|
|
||||||
ROUTE_CONFIG_PATH => Some(APP_CONFIG.read().pages.config_content.clone()),
|
|
||||||
ROUTE_TOKENS_PATH => Some(APP_CONFIG.read().pages.tokeninfo_content.clone()),
|
|
||||||
ROUTE_SHARED_STYLES_PATH => Some(APP_CONFIG.read().pages.shared_styles_content.clone()),
|
|
||||||
ROUTE_SHARED_JS_PATH => Some(APP_CONFIG.read().pages.shared_js_content.clone()),
|
|
||||||
ROUTE_ABOUT_PATH => Some(APP_CONFIG.read().pages.about_content.clone()),
|
|
||||||
ROUTE_README_PATH => Some(APP_CONFIG.read().pages.readme_content.clone()),
|
|
||||||
ROUTE_API_PATH => Some(APP_CONFIG.read().pages.api_content.clone()),
|
|
||||||
ROUTE_BUILD_KEY_PATH => Some(APP_CONFIG.read().pages.build_key_content.clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_page_content(path: &str, content: PageContent) -> Result<(), &'static str> {
|
|
||||||
let mut config = APP_CONFIG.write();
|
|
||||||
match path {
|
|
||||||
ROUTE_ROOT_PATH => config.pages.root_content = content,
|
|
||||||
ROUTE_LOGS_PATH => config.pages.logs_content = content,
|
|
||||||
ROUTE_CONFIG_PATH => config.pages.config_content = content,
|
|
||||||
ROUTE_TOKENS_PATH => config.pages.tokeninfo_content = content,
|
|
||||||
ROUTE_SHARED_STYLES_PATH => config.pages.shared_styles_content = content,
|
|
||||||
ROUTE_SHARED_JS_PATH => config.pages.shared_js_content = content,
|
|
||||||
ROUTE_ABOUT_PATH => config.pages.about_content = content,
|
|
||||||
ROUTE_README_PATH => config.pages.readme_content = content,
|
|
||||||
ROUTE_API_PATH => config.pages.api_content = content,
|
|
||||||
ROUTE_BUILD_KEY_PATH => config.pages.build_key_content = content,
|
|
||||||
_ => return Err(ERR_INVALID_PATH),
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_page_content(path: &str) -> Result<(), &'static str> {
|
pub fn get_tokens_by_tag(&self, tag: &str) -> Vec<&TokenInfo> {
|
||||||
let mut config = APP_CONFIG.write();
|
self.tokens
|
||||||
match path {
|
.iter()
|
||||||
ROUTE_ROOT_PATH => config.pages.root_content = PageContent::default(),
|
.filter(|t| {
|
||||||
ROUTE_LOGS_PATH => config.pages.logs_content = PageContent::default(),
|
t.tags
|
||||||
ROUTE_CONFIG_PATH => config.pages.config_content = PageContent::default(),
|
.as_ref()
|
||||||
ROUTE_TOKENS_PATH => config.pages.tokeninfo_content = PageContent::default(),
|
.is_some_and(|tags| tags.contains(&tag.to_string()))
|
||||||
ROUTE_SHARED_STYLES_PATH => config.pages.shared_styles_content = PageContent::default(),
|
})
|
||||||
ROUTE_SHARED_JS_PATH => config.pages.shared_js_content = PageContent::default(),
|
.collect()
|
||||||
ROUTE_ABOUT_PATH => config.pages.about_content = PageContent::default(),
|
|
||||||
ROUTE_README_PATH => config.pages.readme_content = PageContent::default(),
|
|
||||||
ROUTE_API_PATH => config.pages.api_content = PageContent::default(),
|
|
||||||
ROUTE_BUILD_KEY_PATH => config.pages.build_key_content = PageContent::default(),
|
|
||||||
_ => return Err(ERR_INVALID_PATH),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_checksum(&mut self) {
|
||||||
|
for token_info in self.tokens.iter_mut() {
|
||||||
|
token_info.checksum = generate_checksum_with_repair(&token_info.checksum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn save_tokens(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let bytes = rkyv::to_bytes::<_, 256>(self)?;
|
||||||
|
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(&*TOKENS_FILE_PATH)?;
|
||||||
|
|
||||||
|
if bytes.len() > usize::MAX / 2 {
|
||||||
|
return Err("Token数据过大".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
file.set_len(bytes.len() as u64)?;
|
||||||
|
let mut mmap = unsafe { MmapMut::map_mut(&file)? };
|
||||||
|
mmap.copy_from_slice(&bytes);
|
||||||
|
mmap.flush()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_share() -> bool {
|
pub async fn load_tokens() -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
APP_CONFIG.read().is_share
|
let file = match OpenOptions::new().read(true).open(&*TOKENS_FILE_PATH) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
return Ok(Self::new(Vec::new()));
|
||||||
|
}
|
||||||
|
Err(e) => return Err(Box::new(e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if file.metadata()?.len() > usize::MAX as u64 {
|
||||||
|
return Err("Token文件过大".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
||||||
|
let archived = unsafe { rkyv::archived_root::<Self>(&mmap) };
|
||||||
|
Ok(archived.deserialize(&mut rkyv::Infallible)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl RequestStatsManager {
|
||||||
pub fn new(token_infos: Vec<TokenInfo>) -> Self {
|
pub fn new(request_logs: Vec<RequestLog>) -> Self {
|
||||||
// 尝试加载保存的日志
|
|
||||||
let request_logs = tokio::task::block_in_place(|| {
|
|
||||||
tokio::runtime::Handle::current()
|
|
||||||
.block_on(async { Self::load_saved_logs().await.unwrap_or_default() })
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
total_requests: request_logs.len() as u64,
|
total_requests: request_logs.len() as u64,
|
||||||
active_requests: 0,
|
active_requests: 0,
|
||||||
@@ -338,14 +200,96 @@ impl AppState {
|
|||||||
.filter(|log| matches!(log.status, LogStatus::Failed))
|
.filter(|log| matches!(log.status, LogStatus::Failed))
|
||||||
.count() as u64,
|
.count() as u64,
|
||||||
request_logs,
|
request_logs,
|
||||||
token_infos,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_checksum(&mut self) {
|
pub async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
for token_info in self.token_infos.iter_mut() {
|
let bytes = rkyv::to_bytes::<_, 256>(&self.request_logs)?;
|
||||||
token_info.checksum = generate_checksum_with_repair(&token_info.checksum);
|
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(&*LOGS_FILE_PATH)?;
|
||||||
|
|
||||||
|
if bytes.len() > usize::MAX / 2 {
|
||||||
|
return Err("日志数据过大".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.set_len(bytes.len() as u64)?;
|
||||||
|
let mut mmap = unsafe { MmapMut::map_mut(&file)? };
|
||||||
|
mmap.copy_from_slice(&bytes);
|
||||||
|
mmap.flush()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load_logs() -> Result<Vec<RequestLog>, Box<dyn std::error::Error>> {
|
||||||
|
let file = match OpenOptions::new().read(true).open(&*LOGS_FILE_PATH) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
Err(e) => return Err(Box::new(e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if file.metadata()?.len() > usize::MAX as u64 {
|
||||||
|
return Err("日志文件过大".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
||||||
|
let archived = unsafe { rkyv::archived_root::<Vec<RequestLog>>(&mmap) };
|
||||||
|
Ok(archived.deserialize(&mut rkyv::Infallible)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AppState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
// 尝试加载保存的数据
|
||||||
|
let (request_logs, mut token_manager) = tokio::task::block_in_place(|| {
|
||||||
|
tokio::runtime::Handle::current().block_on(async {
|
||||||
|
let logs = RequestStatsManager::load_logs().await.unwrap_or_default();
|
||||||
|
let token_manager = TokenManager::load_tokens()
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|_| TokenManager::new(Vec::new()));
|
||||||
|
(logs, token_manager)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 查询缺失的 token profiles
|
||||||
|
tokio::task::block_in_place(|| {
|
||||||
|
tokio::runtime::Handle::current().block_on(async {
|
||||||
|
for token_info in token_manager.tokens.iter_mut() {
|
||||||
|
if token_info.profile.is_none() {
|
||||||
|
token_info.profile = get_token_profile(&token_info.token).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
token_manager,
|
||||||
|
request_manager: RequestStatsManager::new(request_logs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn save_state(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// 并行保存 logs 和 tokens
|
||||||
|
let (logs_result, tokens_result) = tokio::join!(
|
||||||
|
self.request_manager.save_logs(),
|
||||||
|
self.token_manager.save_tokens()
|
||||||
|
);
|
||||||
|
|
||||||
|
logs_result?;
|
||||||
|
tokens_result?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,12 +361,13 @@ pub struct ChatRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 用于存储 token 信息
|
// 用于存储 token 信息
|
||||||
#[derive(Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
#[derive(Clone, Serialize, Archive, RkyvSerialize, RkyvDeserialize)]
|
||||||
pub struct TokenInfo {
|
pub struct TokenInfo {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub checksum: String,
|
pub checksum: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub profile: Option<TokenProfile>,
|
pub profile: Option<TokenProfile>,
|
||||||
|
pub tags: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenUpdateRequest 结构体
|
// TokenUpdateRequest 结构体
|
||||||
@@ -431,6 +376,13 @@ pub struct TokenUpdateRequest {
|
|||||||
pub tokens: String,
|
pub tokens: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct TokenAddRequest {
|
||||||
|
pub tokens: Vec<TokenAddRequestTokenInfo>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub tags: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct TokenAddRequestTokenInfo {
|
pub struct TokenAddRequestTokenInfo {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
@@ -484,3 +436,26 @@ pub struct TokensDeleteResponse {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub failed_tokens: Option<Vec<String>>,
|
pub failed_tokens: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct TokenInfoResponse {
|
||||||
|
pub status: ApiStatus,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub tokens: Option<Vec<TokenInfo>>,
|
||||||
|
pub tokens_count: usize,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签相关的请求/响应结构体
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct TokenTagsUpdateRequest {
|
||||||
|
pub tokens: Vec<String>,
|
||||||
|
pub tags: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct TokenTagsResponse {
|
||||||
|
pub status: ApiStatus,
|
||||||
|
pub message: Option<String>,
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{app::constant::COMMA, chat::constant::AVAILABLE_MODELS};
|
use crate::{app::constant::COMMA, chat::constant::Models};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct BuildKeyRequest {
|
pub struct BuildKeyRequest {
|
||||||
@@ -16,7 +16,7 @@ pub struct BuildKeyRequest {
|
|||||||
}
|
}
|
||||||
pub struct UsageCheckModelConfig {
|
pub struct UsageCheckModelConfig {
|
||||||
pub model_type: UsageCheckModelType,
|
pub model_type: UsageCheckModelType,
|
||||||
pub model_ids: Vec<&'static str>,
|
pub model_ids: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for UsageCheckModelConfig {
|
impl<'de> Deserialize<'de> for UsageCheckModelConfig {
|
||||||
@@ -42,10 +42,7 @@ impl<'de> Deserialize<'de> for UsageCheckModelConfig {
|
|||||||
.split(COMMA)
|
.split(COMMA)
|
||||||
.filter_map(|model| {
|
.filter_map(|model| {
|
||||||
let model = model.trim();
|
let model = model.trim();
|
||||||
AVAILABLE_MODELS
|
Models::find_id(model)
|
||||||
.iter()
|
|
||||||
.find(|m| m.id == model)
|
|
||||||
.map(|m| m.id)
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
@@ -1,69 +1,248 @@
|
|||||||
use memmap2::{MmapMut, MmapOptions};
|
use memmap2::{MmapMut, MmapOptions};
|
||||||
use rkyv::{archived_root, Deserialize as _};
|
use parking_lot::RwLock;
|
||||||
use std::fs::OpenOptions;
|
use rkyv::{Deserialize as _, archived_root};
|
||||||
|
use std::{fs::OpenOptions, sync::LazyLock};
|
||||||
|
|
||||||
use crate::app::lazy::{LOGS_FILE_PATH, PAGES_FILE_PATH};
|
use crate::{
|
||||||
|
app::{
|
||||||
|
constant::{
|
||||||
|
EMPTY_STRING, ERR_INVALID_PATH, ROUTE_ABOUT_PATH, ROUTE_API_PATH, ROUTE_BUILD_KEY_PATH,
|
||||||
|
ROUTE_CONFIG_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH,
|
||||||
|
ROUTE_SHARED_JS_PATH, ROUTE_SHARED_STYLES_PATH, ROUTE_TOKENS_PATH,
|
||||||
|
},
|
||||||
|
lazy::CONFIG_FILE_PATH,
|
||||||
|
},
|
||||||
|
common::{
|
||||||
|
client::rebuild_http_client,
|
||||||
|
utils::{parse_bool_from_env, parse_string_from_env},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{AppConfig, AppState, Pages, RequestLog, APP_CONFIG};
|
use super::{PageContent, Pages, Proxies, UsageCheck, VisionAbility};
|
||||||
|
|
||||||
impl AppState {
|
// 静态配置
|
||||||
// 保存日志的方法
|
#[derive(Default, Clone)]
|
||||||
pub(crate) async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
|
pub struct AppConfig {
|
||||||
// 序列化日志
|
vision_ability: VisionAbility,
|
||||||
let bytes = rkyv::to_bytes::<_, 256>(&self.request_logs)?;
|
slow_pool: bool,
|
||||||
|
allow_claude: bool,
|
||||||
|
pages: Pages,
|
||||||
|
usage_check: UsageCheck,
|
||||||
|
dynamic_key: bool,
|
||||||
|
share_token: String,
|
||||||
|
is_share: bool,
|
||||||
|
proxies: Proxies,
|
||||||
|
web_refs: bool,
|
||||||
|
}
|
||||||
|
|
||||||
// 创建或打开文件
|
// 全局配置实例
|
||||||
let file = OpenOptions::new()
|
static APP_CONFIG: LazyLock<RwLock<AppConfig>> =
|
||||||
.read(true)
|
LazyLock::new(|| RwLock::new(AppConfig::default()));
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(LOGS_FILE_PATH.as_str())?;
|
|
||||||
|
|
||||||
// 添加大小检查
|
macro_rules! config_methods {
|
||||||
if bytes.len() > usize::MAX / 2 {
|
($($field:ident: $type:ty, $default:expr;)*) => {
|
||||||
return Err("日志数据过大".into());
|
$(
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<get_ $field>]() -> $type
|
||||||
|
where
|
||||||
|
$type: Copy + PartialEq,
|
||||||
|
{
|
||||||
|
APP_CONFIG.read().$field
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置文件大小
|
pub fn [<update_ $field>](value: $type)
|
||||||
file.set_len(bytes.len() as u64)?;
|
where
|
||||||
|
$type: Copy + PartialEq,
|
||||||
// 创建可写入的内存映射
|
{
|
||||||
let mut mmap = unsafe { MmapMut::map_mut(&file)? };
|
let current = Self::[<get_ $field>]();
|
||||||
|
if current != value {
|
||||||
// 写入数据
|
APP_CONFIG.write().$field = value;
|
||||||
mmap.copy_from_slice(&bytes);
|
}
|
||||||
|
|
||||||
// 同步到磁盘
|
|
||||||
mmap.flush()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载日志的方法
|
pub fn [<reset_ $field>]()
|
||||||
pub(super) async fn load_saved_logs() -> Result<Vec<RequestLog>, Box<dyn std::error::Error>> {
|
where
|
||||||
let file = match OpenOptions::new().read(true).open(LOGS_FILE_PATH.as_str()) {
|
$type: Copy + PartialEq,
|
||||||
Ok(file) => file,
|
{
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
let default_value = $default;
|
||||||
return Ok(Vec::new());
|
let current = Self::[<get_ $field>]();
|
||||||
|
if current != default_value {
|
||||||
|
APP_CONFIG.write().$field = default_value;
|
||||||
}
|
}
|
||||||
Err(e) => return Err(Box::new(e)),
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 添加文件大小检查
|
macro_rules! config_methods_clone {
|
||||||
if file.metadata()?.len() > usize::MAX as u64 {
|
($($field:ident: $type:ty, $default:expr;)*) => {
|
||||||
return Err("日志文件过大".into());
|
$(
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<get_ $field>]() -> $type
|
||||||
|
where
|
||||||
|
$type: Clone + PartialEq,
|
||||||
|
{
|
||||||
|
APP_CONFIG.read().$field.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建只读内存映射
|
pub fn [<update_ $field>](value: $type)
|
||||||
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
where
|
||||||
|
$type: Clone + PartialEq,
|
||||||
// 验证并反序列化数据
|
{
|
||||||
let archived = unsafe { archived_root::<Vec<RequestLog>>(&mmap) };
|
let current = Self::[<get_ $field>]();
|
||||||
Ok(archived.deserialize(&mut rkyv::Infallible)?)
|
if current != value {
|
||||||
|
APP_CONFIG.write().$field = value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn [<reset_ $field>]()
|
||||||
|
where
|
||||||
|
$type: Clone + PartialEq,
|
||||||
|
{
|
||||||
|
let default_value = $default;
|
||||||
|
let current = Self::[<get_ $field>]();
|
||||||
|
if current != default_value {
|
||||||
|
APP_CONFIG.write().$field = default_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
|
pub fn init() {
|
||||||
|
let mut config = APP_CONFIG.write();
|
||||||
|
config.vision_ability =
|
||||||
|
VisionAbility::from_str(&parse_string_from_env("VISION_ABILITY", EMPTY_STRING));
|
||||||
|
config.slow_pool = parse_bool_from_env("ENABLE_SLOW_POOL", false);
|
||||||
|
config.allow_claude = parse_bool_from_env("PASS_ANY_CLAUDE", false);
|
||||||
|
config.usage_check =
|
||||||
|
UsageCheck::from_str(&parse_string_from_env("USAGE_CHECK", EMPTY_STRING));
|
||||||
|
config.dynamic_key = parse_bool_from_env("DYNAMIC_KEY", false);
|
||||||
|
config.share_token = parse_string_from_env("SHARED_TOKEN", EMPTY_STRING);
|
||||||
|
config.is_share = !config.share_token.is_empty();
|
||||||
|
config.proxies = match std::env::var("PROXIES") {
|
||||||
|
Ok(proxies) => Proxies::from_str(proxies.as_str()),
|
||||||
|
Err(_) => Proxies::default(),
|
||||||
|
};
|
||||||
|
config.web_refs = parse_bool_from_env("INCLUDE_WEB_REFERENCES", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
config_methods! {
|
||||||
|
slow_pool: bool, false;
|
||||||
|
allow_claude: bool, false;
|
||||||
|
dynamic_key: bool, false;
|
||||||
|
web_refs: bool, false;
|
||||||
|
}
|
||||||
|
|
||||||
|
config_methods_clone! {
|
||||||
|
vision_ability: VisionAbility, VisionAbility::default();
|
||||||
|
usage_check: UsageCheck, UsageCheck::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_share_token() -> String {
|
||||||
|
APP_CONFIG.read().share_token.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_share_token(value: String) {
|
||||||
|
let current = Self::get_share_token();
|
||||||
|
if current != value {
|
||||||
|
let mut config = APP_CONFIG.write();
|
||||||
|
config.share_token = value;
|
||||||
|
config.is_share = !config.share_token.is_empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_share_token() {
|
||||||
|
let current = Self::get_share_token();
|
||||||
|
if !current.is_empty() {
|
||||||
|
let mut config = APP_CONFIG.write();
|
||||||
|
config.share_token = String::new();
|
||||||
|
config.is_share = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_proxies() -> Proxies {
|
||||||
|
APP_CONFIG.read().proxies.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_proxies(value: Proxies) {
|
||||||
|
let current = Self::get_proxies();
|
||||||
|
if current != value {
|
||||||
|
let mut config = APP_CONFIG.write();
|
||||||
|
config.proxies = value;
|
||||||
|
rebuild_http_client();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_proxies() {
|
||||||
|
let default_value = Proxies::default();
|
||||||
|
let current = Self::get_proxies();
|
||||||
|
if current != default_value {
|
||||||
|
let mut config = APP_CONFIG.write();
|
||||||
|
config.proxies = default_value;
|
||||||
|
rebuild_http_client();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_page_content(path: &str) -> Option<PageContent> {
|
||||||
|
match path {
|
||||||
|
ROUTE_ROOT_PATH => Some(APP_CONFIG.read().pages.root_content.clone()),
|
||||||
|
ROUTE_LOGS_PATH => Some(APP_CONFIG.read().pages.logs_content.clone()),
|
||||||
|
ROUTE_CONFIG_PATH => Some(APP_CONFIG.read().pages.config_content.clone()),
|
||||||
|
ROUTE_TOKENS_PATH => Some(APP_CONFIG.read().pages.tokeninfo_content.clone()),
|
||||||
|
ROUTE_SHARED_STYLES_PATH => Some(APP_CONFIG.read().pages.shared_styles_content.clone()),
|
||||||
|
ROUTE_SHARED_JS_PATH => Some(APP_CONFIG.read().pages.shared_js_content.clone()),
|
||||||
|
ROUTE_ABOUT_PATH => Some(APP_CONFIG.read().pages.about_content.clone()),
|
||||||
|
ROUTE_README_PATH => Some(APP_CONFIG.read().pages.readme_content.clone()),
|
||||||
|
ROUTE_API_PATH => Some(APP_CONFIG.read().pages.api_content.clone()),
|
||||||
|
ROUTE_BUILD_KEY_PATH => Some(APP_CONFIG.read().pages.build_key_content.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_page_content(path: &str, content: PageContent) -> Result<(), &'static str> {
|
||||||
|
let mut config = APP_CONFIG.write();
|
||||||
|
match path {
|
||||||
|
ROUTE_ROOT_PATH => config.pages.root_content = content,
|
||||||
|
ROUTE_LOGS_PATH => config.pages.logs_content = content,
|
||||||
|
ROUTE_CONFIG_PATH => config.pages.config_content = content,
|
||||||
|
ROUTE_TOKENS_PATH => config.pages.tokeninfo_content = content,
|
||||||
|
ROUTE_SHARED_STYLES_PATH => config.pages.shared_styles_content = content,
|
||||||
|
ROUTE_SHARED_JS_PATH => config.pages.shared_js_content = content,
|
||||||
|
ROUTE_ABOUT_PATH => config.pages.about_content = content,
|
||||||
|
ROUTE_README_PATH => config.pages.readme_content = content,
|
||||||
|
ROUTE_API_PATH => config.pages.api_content = content,
|
||||||
|
ROUTE_BUILD_KEY_PATH => config.pages.build_key_content = content,
|
||||||
|
_ => return Err(ERR_INVALID_PATH),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_page_content(path: &str) -> Result<(), &'static str> {
|
||||||
|
let mut config = APP_CONFIG.write();
|
||||||
|
match path {
|
||||||
|
ROUTE_ROOT_PATH => config.pages.root_content = PageContent::default(),
|
||||||
|
ROUTE_LOGS_PATH => config.pages.logs_content = PageContent::default(),
|
||||||
|
ROUTE_CONFIG_PATH => config.pages.config_content = PageContent::default(),
|
||||||
|
ROUTE_TOKENS_PATH => config.pages.tokeninfo_content = PageContent::default(),
|
||||||
|
ROUTE_SHARED_STYLES_PATH => config.pages.shared_styles_content = PageContent::default(),
|
||||||
|
ROUTE_SHARED_JS_PATH => config.pages.shared_js_content = PageContent::default(),
|
||||||
|
ROUTE_ABOUT_PATH => config.pages.about_content = PageContent::default(),
|
||||||
|
ROUTE_README_PATH => config.pages.readme_content = PageContent::default(),
|
||||||
|
ROUTE_API_PATH => config.pages.api_content = PageContent::default(),
|
||||||
|
ROUTE_BUILD_KEY_PATH => config.pages.build_key_content = PageContent::default(),
|
||||||
|
_ => return Err(ERR_INVALID_PATH),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_share() -> bool {
|
||||||
|
APP_CONFIG.read().is_share
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_config() -> Result<(), Box<dyn std::error::Error>> {
|
pub fn save_config() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let pages = APP_CONFIG.read().pages.clone();
|
let pages = APP_CONFIG.read().pages.clone();
|
||||||
let bytes = rkyv::to_bytes::<_, 256>(&pages)?;
|
let bytes = rkyv::to_bytes::<_, 256>(&pages)?;
|
||||||
@@ -72,7 +251,8 @@ impl AppConfig {
|
|||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(PAGES_FILE_PATH.as_str())?;
|
.truncate(true)
|
||||||
|
.open(&*CONFIG_FILE_PATH)?;
|
||||||
|
|
||||||
// 添加大小检查
|
// 添加大小检查
|
||||||
if bytes.len() > usize::MAX / 2 {
|
if bytes.len() > usize::MAX / 2 {
|
||||||
@@ -89,7 +269,7 @@ impl AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_saved_config() -> Result<(), Box<dyn std::error::Error>> {
|
pub fn load_saved_config() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let file = match OpenOptions::new().read(true).open(PAGES_FILE_PATH.as_str()) {
|
let file = match OpenOptions::new().read(true).open(&*CONFIG_FILE_PATH) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
use reqwest::{Client, Proxy};
|
use reqwest::{Client, Proxy};
|
||||||
use serde::{Serialize, Serializer};
|
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use serde::{Serialize, Serializer};
|
||||||
// use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
// use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||||
|
|
||||||
use crate::app::constant::COMMA_STRING;
|
use crate::app::constant::COMMA_STRING;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::constant::{COMMA, COMMA_STRING},
|
app::constant::{COMMA, COMMA_STRING},
|
||||||
chat::{config::key_config, constant::AVAILABLE_MODELS},
|
chat::{config::key_config, constant::Models},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
// use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
// use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||||
@@ -10,7 +10,7 @@ pub enum UsageCheck {
|
|||||||
None,
|
None,
|
||||||
Default,
|
Default,
|
||||||
All,
|
All,
|
||||||
Custom(Vec<&'static str>),
|
Custom(Vec<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsageCheck {
|
impl UsageCheck {
|
||||||
@@ -21,10 +21,10 @@ impl UsageCheck {
|
|||||||
Type::Default | Type::Disabled => Self::None,
|
Type::Default | Type::Disabled => Self::None,
|
||||||
Type::All => Self::All,
|
Type::All => Self::All,
|
||||||
Type::Custom => {
|
Type::Custom => {
|
||||||
let models: Vec<&'static str> = model
|
let models: Vec<_> = model
|
||||||
.model_ids
|
.model_ids
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|id| AVAILABLE_MODELS.iter().find(|m| m.id == id).map(|m| m.id))
|
.filter_map(|id| Models::find_id(id))
|
||||||
.collect();
|
.collect();
|
||||||
if models.is_empty() {
|
if models.is_empty() {
|
||||||
Self::None
|
Self::None
|
||||||
@@ -119,14 +119,11 @@ impl<'de> Deserialize<'de> for UsageCheck {
|
|||||||
return Ok(UsageCheck::None);
|
return Ok(UsageCheck::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let models: Vec<&'static str> = list
|
let models: Vec<_> = list
|
||||||
.split(COMMA)
|
.split(COMMA)
|
||||||
.filter_map(|model| {
|
.filter_map(|model| {
|
||||||
let model = model.trim();
|
let model = model.trim();
|
||||||
AVAILABLE_MODELS
|
Models::find_id(model)
|
||||||
.iter()
|
|
||||||
.find(|m| m.id == model)
|
|
||||||
.map(|m| m.id)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -150,14 +147,11 @@ impl UsageCheck {
|
|||||||
if list.is_empty() {
|
if list.is_empty() {
|
||||||
return Self::default();
|
return Self::default();
|
||||||
}
|
}
|
||||||
let models: Vec<&'static str> = list
|
let models: Vec<_> = list
|
||||||
.split(COMMA)
|
.split(COMMA)
|
||||||
.filter_map(|model| {
|
.filter_map(|model| {
|
||||||
let model = model.trim();
|
let model = model.trim();
|
||||||
AVAILABLE_MODELS
|
Models::find_id(model)
|
||||||
.iter()
|
|
||||||
.find(|m| m.id == model)
|
|
||||||
.map(|m| m.id)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
32
src/app/model/vision_ability.rs
Normal file
32
src/app/model/vision_ability.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq)]
|
||||||
|
pub enum VisionAbility {
|
||||||
|
#[serde(rename = "none", alias = "disabled")]
|
||||||
|
None,
|
||||||
|
#[serde(rename = "base64", alias = "base64-only")]
|
||||||
|
Base64,
|
||||||
|
#[serde(rename = "all", alias = "base64-http")]
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisionAbility {
|
||||||
|
pub fn from_str(s: &str) -> Self {
|
||||||
|
match s.to_lowercase().as_str() {
|
||||||
|
"none" | "disabled" => Self::None,
|
||||||
|
"base64" | "base64-only" => Self::Base64,
|
||||||
|
"all" | "base64-http" => Self::All,
|
||||||
|
_ => Self::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
matches!(self, VisionAbility::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VisionAbility {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Base64
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
|
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||||||
use image::guess_format;
|
use image::guess_format;
|
||||||
use prost::Message as _;
|
use rand::Rng as _;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -10,17 +10,79 @@ use crate::{
|
|||||||
lazy::DEFAULT_INSTRUCTIONS,
|
lazy::DEFAULT_INSTRUCTIONS,
|
||||||
model::{AppConfig, VisionAbility},
|
model::{AppConfig, VisionAbility},
|
||||||
},
|
},
|
||||||
common::client::HTTP_CLIENT,
|
common::{client::HTTP_CLIENT, utils::encode_message},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
aiserver::v1::{
|
aiserver::v1::{
|
||||||
conversation_message, image_proto, AzureState, ChatExternalLink, ConversationMessage, ExplicitContext, GetChatRequest, ImageProto, ModelDetails
|
AzureState, ChatExternalLink, ConversationMessage, ExplicitContext, GetChatRequest,
|
||||||
|
ImageProto, ModelDetails, WebReference, conversation_message, image_proto,
|
||||||
},
|
},
|
||||||
constant::{ERR_UNSUPPORTED_GIF, ERR_UNSUPPORTED_IMAGE_FORMAT, LONG_CONTEXT_MODELS},
|
constant::{ERR_UNSUPPORTED_GIF, ERR_UNSUPPORTED_IMAGE_FORMAT, LONG_CONTEXT_MODELS},
|
||||||
model::{Message, MessageContent, Role},
|
model::{Message, MessageContent, Role},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn parse_web_references(text: &str) -> Vec<WebReference> {
|
||||||
|
let mut web_refs = Vec::new();
|
||||||
|
let lines = text.lines().skip(1); // 跳过 "WebReferences:" 行
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过序号和空格
|
||||||
|
let mut chars = line.chars();
|
||||||
|
for c in chars.by_ref() {
|
||||||
|
if c == '.' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let remaining = chars.as_str().trim_start();
|
||||||
|
|
||||||
|
// 解析 [title](url) 部分
|
||||||
|
if !remaining.starts_with('[') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut title = String::new();
|
||||||
|
let mut url = String::new();
|
||||||
|
let mut chunk = String::new();
|
||||||
|
let mut current = &mut title;
|
||||||
|
let mut state = 0; // 0: title, 1: url, 2: chunk
|
||||||
|
|
||||||
|
let mut chars = remaining.chars();
|
||||||
|
chars.next(); // 跳过 '['
|
||||||
|
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
match (state, c) {
|
||||||
|
(0, ']') => {
|
||||||
|
state = 1;
|
||||||
|
if chars.next() != Some('(') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current = &mut url;
|
||||||
|
}
|
||||||
|
(1, ')') => {
|
||||||
|
state = 2;
|
||||||
|
if chars.next() == Some('<') {
|
||||||
|
current = &mut chunk;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(2, '>') => break,
|
||||||
|
(_, c) => current.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
web_refs.push(WebReference { title, url, chunk });
|
||||||
|
}
|
||||||
|
|
||||||
|
web_refs
|
||||||
|
}
|
||||||
|
|
||||||
async fn process_chat_inputs(
|
async fn process_chat_inputs(
|
||||||
inputs: Vec<Message>,
|
inputs: Vec<Message>,
|
||||||
disable_vision: bool,
|
disable_vision: bool,
|
||||||
@@ -96,6 +158,17 @@ async fn process_chat_inputs(
|
|||||||
is_agentic: false,
|
is_agentic: false,
|
||||||
file_diff_trajectories: vec![],
|
file_diff_trajectories: vec![],
|
||||||
conversation_summary: None,
|
conversation_summary: None,
|
||||||
|
existed_subsequent_terminal_command: false,
|
||||||
|
existed_previous_terminal_command: false,
|
||||||
|
docs_references: vec![],
|
||||||
|
web_references: vec![],
|
||||||
|
git_context: None,
|
||||||
|
attached_folders_list_dir_results: vec![],
|
||||||
|
cached_conversation_summary: None,
|
||||||
|
human_changes: vec![],
|
||||||
|
attached_human_changes: false,
|
||||||
|
summarized_composers: vec![],
|
||||||
|
cursor_rules: vec![],
|
||||||
}],
|
}],
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
@@ -119,7 +192,7 @@ async fn process_chat_inputs(
|
|||||||
// 如果第一条是 assistant,插入空的 user 消息
|
// 如果第一条是 assistant,插入空的 user 消息
|
||||||
if chat_inputs
|
if chat_inputs
|
||||||
.first()
|
.first()
|
||||||
.map_or(false, |input| input.role == Role::Assistant)
|
.is_some_and(|input| input.role == Role::Assistant)
|
||||||
{
|
{
|
||||||
chat_inputs.insert(
|
chat_inputs.insert(
|
||||||
0,
|
0,
|
||||||
@@ -153,7 +226,7 @@ async fn process_chat_inputs(
|
|||||||
// 确保最后一条是 user
|
// 确保最后一条是 user
|
||||||
if chat_inputs
|
if chat_inputs
|
||||||
.last()
|
.last()
|
||||||
.map_or(false, |input| input.role == Role::Assistant)
|
.is_some_and(|input| input.role == Role::Assistant)
|
||||||
{
|
{
|
||||||
chat_inputs.push(Message {
|
chat_inputs.push(Message {
|
||||||
role: Role::User,
|
role: Role::User,
|
||||||
@@ -201,6 +274,21 @@ async fn process_chat_inputs(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (text, web_references) =
|
||||||
|
if input.role == Role::Assistant && text.starts_with("WebReferences:") {
|
||||||
|
if let Some(pos) = text.find("\n\n") {
|
||||||
|
let (web_refs_text, content_text) = text.split_at(pos);
|
||||||
|
(
|
||||||
|
content_text[2..].to_string(), // 跳过 "\n\n"
|
||||||
|
parse_web_references(web_refs_text),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(text, vec![])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(text, vec![])
|
||||||
|
};
|
||||||
|
|
||||||
messages.push(ConversationMessage {
|
messages.push(ConversationMessage {
|
||||||
text,
|
text,
|
||||||
r#type: if input.role == Role::User {
|
r#type: if input.role == Role::User {
|
||||||
@@ -238,6 +326,17 @@ async fn process_chat_inputs(
|
|||||||
is_agentic: false,
|
is_agentic: false,
|
||||||
file_diff_trajectories: vec![],
|
file_diff_trajectories: vec![],
|
||||||
conversation_summary: None,
|
conversation_summary: None,
|
||||||
|
existed_subsequent_terminal_command: false,
|
||||||
|
existed_previous_terminal_command: false,
|
||||||
|
docs_references: vec![],
|
||||||
|
web_references,
|
||||||
|
git_context: None,
|
||||||
|
attached_folders_list_dir_results: vec![],
|
||||||
|
cached_conversation_summary: None,
|
||||||
|
human_changes: vec![],
|
||||||
|
attached_human_changes: false,
|
||||||
|
summarized_composers: vec![],
|
||||||
|
cursor_rules: vec![],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,13 +486,7 @@ pub async fn encode_chat_message(
|
|||||||
is_search: bool,
|
is_search: bool,
|
||||||
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// 在进入异步操作前获取并释放锁
|
// 在进入异步操作前获取并释放锁
|
||||||
let enable_slow_pool = {
|
let enable_slow_pool = { if enable_slow_pool { Some(true) } else { None } };
|
||||||
if enable_slow_pool {
|
|
||||||
Some(true)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (instructions, messages, urls) = process_chat_inputs(inputs, disable_vision).await;
|
let (instructions, messages, urls) = process_chat_inputs(inputs, disable_vision).await;
|
||||||
|
|
||||||
@@ -401,19 +494,24 @@ pub async fn encode_chat_message(
|
|||||||
Some(ExplicitContext {
|
Some(ExplicitContext {
|
||||||
context: instructions,
|
context: instructions,
|
||||||
repo_context: None,
|
repo_context: None,
|
||||||
|
rules: vec![],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let base_uuid = rand::random::<u16>();
|
let base_uuid = rand::rng().random::<u16>();
|
||||||
let external_links = urls.into_iter().enumerate().map(|(i, url)| {
|
let external_links = urls
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, url)| {
|
||||||
let uuid = base_uuid.wrapping_add(i as u16);
|
let uuid = base_uuid.wrapping_add(i as u16);
|
||||||
ChatExternalLink {
|
ChatExternalLink {
|
||||||
url,
|
url,
|
||||||
uuid: uuid.to_string(),
|
uuid: uuid.to_string(),
|
||||||
}
|
}
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let chat = GetChatRequest {
|
let chat = GetChatRequest {
|
||||||
current_file: None,
|
current_file: None,
|
||||||
@@ -461,13 +559,9 @@ pub async fn encode_chat_message(
|
|||||||
is_composer: None,
|
is_composer: None,
|
||||||
runnable_code_blocks: Some(false),
|
runnable_code_blocks: Some(false),
|
||||||
should_cache: Some(false),
|
should_cache: Some(false),
|
||||||
|
allow_model_fallbacks: None,
|
||||||
|
number_of_times_shown_fallback_model_warning: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut encoded = Vec::new();
|
encode_message(&chat, true)
|
||||||
chat.encode(&mut encoded)?;
|
|
||||||
|
|
||||||
let len_prefix = format!("{:010x}", encoded.len()).to_uppercase();
|
|
||||||
let content = hex::encode_upper(&encoded);
|
|
||||||
|
|
||||||
Ok(hex::decode(len_prefix + &content)?)
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
include!(concat!(env!("OUT_DIR"), "/aiserver.v1.rs"));
|
// include!(concat!(env!("OUT_DIR"), "/aiserver.v1.rs"));
|
||||||
|
include!("v1/aiserver.v1.rs");
|
||||||
use error_details::Error;
|
use error_details::Error;
|
||||||
|
|
||||||
impl ErrorDetails {
|
impl ErrorDetails {
|
||||||
@@ -7,6 +8,7 @@ impl ErrorDetails {
|
|||||||
Ok(error) => match error {
|
Ok(error) => match error {
|
||||||
Error::Unspecified => 500,
|
Error::Unspecified => 500,
|
||||||
Error::BadApiKey
|
Error::BadApiKey
|
||||||
|
| Error::BadUserApiKey
|
||||||
| Error::InvalidAuthId
|
| Error::InvalidAuthId
|
||||||
| Error::AuthTokenNotFound
|
| Error::AuthTokenNotFound
|
||||||
| Error::AuthTokenExpired
|
| Error::AuthTokenExpired
|
||||||
@@ -33,7 +35,9 @@ impl ErrorDetails {
|
|||||||
| Error::BadModelName
|
| Error::BadModelName
|
||||||
| Error::SlashEditFileTooLong
|
| Error::SlashEditFileTooLong
|
||||||
| Error::FileUnsupported
|
| Error::FileUnsupported
|
||||||
| Error::ClaudeImageTooLarge => 400,
|
| Error::ClaudeImageTooLarge
|
||||||
|
| Error::ConversationTooLong => 400,
|
||||||
|
Error::Timeout => 504,
|
||||||
Error::Deprecated
|
Error::Deprecated
|
||||||
| Error::FreeUserUsageLimit
|
| Error::FreeUserUsageLimit
|
||||||
| Error::ProUserUsageLimit
|
| Error::ProUserUsageLimit
|
||||||
|
4149
src/chat/aiserver/v1/aiserver.v1.rs
Normal file
4149
src/chat/aiserver/v1/aiserver.v1.rs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
144
src/chat/aiserver/v1/timestamp.proto
Normal file
144
src/chat/aiserver/v1/timestamp.proto
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
// https://developers.google.com/protocol-buffers/
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package google.protobuf;
|
||||||
|
|
||||||
|
option cc_enable_arenas = true;
|
||||||
|
option go_package = "google.golang.org/protobuf/types/known/timestamppb";
|
||||||
|
option java_package = "com.google.protobuf";
|
||||||
|
option java_outer_classname = "TimestampProto";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option objc_class_prefix = "GPB";
|
||||||
|
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
|
||||||
|
|
||||||
|
// A Timestamp represents a point in time independent of any time zone or local
|
||||||
|
// calendar, encoded as a count of seconds and fractions of seconds at
|
||||||
|
// nanosecond resolution. The count is relative to an epoch at UTC midnight on
|
||||||
|
// January 1, 1970, in the proleptic Gregorian calendar which extends the
|
||||||
|
// Gregorian calendar backwards to year one.
|
||||||
|
//
|
||||||
|
// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
|
||||||
|
// second table is needed for interpretation, using a [24-hour linear
|
||||||
|
// smear](https://developers.google.com/time/smear).
|
||||||
|
//
|
||||||
|
// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
|
||||||
|
// restricting to that range, we ensure that we can convert to and from [RFC
|
||||||
|
// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
|
||||||
|
//
|
||||||
|
// # Examples
|
||||||
|
//
|
||||||
|
// Example 1: Compute Timestamp from POSIX `time()`.
|
||||||
|
//
|
||||||
|
// Timestamp timestamp;
|
||||||
|
// timestamp.set_seconds(time(NULL));
|
||||||
|
// timestamp.set_nanos(0);
|
||||||
|
//
|
||||||
|
// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
|
||||||
|
//
|
||||||
|
// struct timeval tv;
|
||||||
|
// gettimeofday(&tv, NULL);
|
||||||
|
//
|
||||||
|
// Timestamp timestamp;
|
||||||
|
// timestamp.set_seconds(tv.tv_sec);
|
||||||
|
// timestamp.set_nanos(tv.tv_usec * 1000);
|
||||||
|
//
|
||||||
|
// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
|
||||||
|
//
|
||||||
|
// FILETIME ft;
|
||||||
|
// GetSystemTimeAsFileTime(&ft);
|
||||||
|
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
|
||||||
|
//
|
||||||
|
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
|
||||||
|
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
|
||||||
|
// Timestamp timestamp;
|
||||||
|
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
|
||||||
|
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
|
||||||
|
//
|
||||||
|
// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
|
||||||
|
//
|
||||||
|
// long millis = System.currentTimeMillis();
|
||||||
|
//
|
||||||
|
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
|
||||||
|
// .setNanos((int) ((millis % 1000) * 1000000)).build();
|
||||||
|
//
|
||||||
|
// Example 5: Compute Timestamp from Java `Instant.now()`.
|
||||||
|
//
|
||||||
|
// Instant now = Instant.now();
|
||||||
|
//
|
||||||
|
// Timestamp timestamp =
|
||||||
|
// Timestamp.newBuilder().setSeconds(now.getEpochSecond())
|
||||||
|
// .setNanos(now.getNano()).build();
|
||||||
|
//
|
||||||
|
// Example 6: Compute Timestamp from current time in Python.
|
||||||
|
//
|
||||||
|
// timestamp = Timestamp()
|
||||||
|
// timestamp.GetCurrentTime()
|
||||||
|
//
|
||||||
|
// # JSON Mapping
|
||||||
|
//
|
||||||
|
// In JSON format, the Timestamp type is encoded as a string in the
|
||||||
|
// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
|
||||||
|
// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
|
||||||
|
// where {year} is always expressed using four digits while {month}, {day},
|
||||||
|
// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
|
||||||
|
// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
|
||||||
|
// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
|
||||||
|
// is required. A proto3 JSON serializer should always use UTC (as indicated by
|
||||||
|
// "Z") when printing the Timestamp type and a proto3 JSON parser should be
|
||||||
|
// able to accept both UTC and other timezones (as indicated by an offset).
|
||||||
|
//
|
||||||
|
// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
|
||||||
|
// 01:30 UTC on January 15, 2017.
|
||||||
|
//
|
||||||
|
// In JavaScript, one can convert a Date object to this format using the
|
||||||
|
// standard
|
||||||
|
// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
|
||||||
|
// method. In Python, a standard `datetime.datetime` object can be converted
|
||||||
|
// to this format using
|
||||||
|
// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
|
||||||
|
// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
|
||||||
|
// the Joda Time's [`ISODateTimeFormat.dateTime()`](
|
||||||
|
// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()
|
||||||
|
// ) to obtain a formatter capable of generating timestamps in this format.
|
||||||
|
//
|
||||||
|
message Timestamp {
|
||||||
|
// Represents seconds of UTC time since Unix epoch
|
||||||
|
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||||
|
// 9999-12-31T23:59:59Z inclusive.
|
||||||
|
int64 seconds = 1;
|
||||||
|
|
||||||
|
// Non-negative fractions of a second at nanosecond resolution. Negative
|
||||||
|
// second values with fractions must still have non-negative nanos values
|
||||||
|
// that count forward in time. Must be from 0 to 999,999,999
|
||||||
|
// inclusive.
|
||||||
|
int32 nanos = 2;
|
||||||
|
}
|
@@ -1,183 +1,205 @@
|
|||||||
|
use parking_lot::RwLock;
|
||||||
|
use std::{sync::Arc, time::{Duration, Instant}};
|
||||||
|
|
||||||
use super::model::Model;
|
use super::model::Model;
|
||||||
|
|
||||||
macro_rules! def_pub_const {
|
macro_rules! def_pub_const {
|
||||||
|
// 单个常量定义分支
|
||||||
($name:ident, $value:expr) => {
|
($name:ident, $value:expr) => {
|
||||||
pub const $name: &'static str = $value;
|
pub const $name: &'static str = $value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 批量定义分支
|
||||||
|
($($name:ident => $value:expr),+ $(,)?) => {
|
||||||
|
$(
|
||||||
|
pub const $name: &'static str = $value;
|
||||||
|
)+
|
||||||
|
};
|
||||||
}
|
}
|
||||||
def_pub_const!(ERR_UNSUPPORTED_GIF, "不支持动态 GIF");
|
|
||||||
|
// 错误信息
|
||||||
def_pub_const!(
|
def_pub_const!(
|
||||||
ERR_UNSUPPORTED_IMAGE_FORMAT,
|
ERR_UNSUPPORTED_GIF => "不支持动态 GIF",
|
||||||
"不支持的图片格式,仅支持 PNG、JPEG、WEBP 和非动态 GIF"
|
ERR_UNSUPPORTED_IMAGE_FORMAT => "不支持的图片格式,仅支持 PNG、JPEG、WEBP 和非动态 GIF",
|
||||||
|
ERR_NODATA => "No data",
|
||||||
);
|
);
|
||||||
def_pub_const!(ERR_NODATA, "No data");
|
|
||||||
|
|
||||||
const MODEL_OBJECT: &str = "model";
|
// 系统常量
|
||||||
const CREATED: &i64 = &1706659200;
|
pub const MODEL_OBJECT: &str = "model";
|
||||||
|
pub const CREATED: &i64 = &1706659200;
|
||||||
|
|
||||||
def_pub_const!(ANTHROPIC, "anthropic");
|
// AI 服务商
|
||||||
def_pub_const!(CURSOR, "cursor");
|
|
||||||
def_pub_const!(GOOGLE, "google");
|
|
||||||
def_pub_const!(OPENAI, "openai");
|
|
||||||
def_pub_const!(DEEPSEEK, "deepseek");
|
|
||||||
|
|
||||||
def_pub_const!(CLAUDE_3_5_SONNET, "claude-3.5-sonnet");
|
|
||||||
def_pub_const!(GPT_4, "gpt-4");
|
|
||||||
def_pub_const!(GPT_4O, "gpt-4o");
|
|
||||||
def_pub_const!(CLAUDE_3_OPUS, "claude-3-opus");
|
|
||||||
def_pub_const!(CURSOR_FAST, "cursor-fast");
|
|
||||||
def_pub_const!(CURSOR_SMALL, "cursor-small");
|
|
||||||
def_pub_const!(GPT_3_5_TURBO, "gpt-3.5-turbo");
|
|
||||||
def_pub_const!(GPT_4_TURBO_2024_04_09, "gpt-4-turbo-2024-04-09");
|
|
||||||
def_pub_const!(GPT_4O_128K, "gpt-4o-128k");
|
|
||||||
def_pub_const!(GEMINI_1_5_FLASH_500K, "gemini-1.5-flash-500k");
|
|
||||||
def_pub_const!(CLAUDE_3_HAIKU_200K, "claude-3-haiku-200k");
|
|
||||||
def_pub_const!(CLAUDE_3_5_SONNET_200K, "claude-3-5-sonnet-200k");
|
|
||||||
def_pub_const!(CLAUDE_3_5_SONNET_20241022, "claude-3-5-sonnet-20241022");
|
|
||||||
def_pub_const!(GPT_4O_MINI, "gpt-4o-mini");
|
|
||||||
def_pub_const!(O1_MINI, "o1-mini");
|
|
||||||
def_pub_const!(O1_PREVIEW, "o1-preview");
|
|
||||||
def_pub_const!(O1, "o1");
|
|
||||||
def_pub_const!(CLAUDE_3_5_HAIKU, "claude-3.5-haiku");
|
|
||||||
def_pub_const!(GEMINI_EXP_1206, "gemini-exp-1206");
|
|
||||||
def_pub_const!(
|
def_pub_const!(
|
||||||
GEMINI_2_0_FLASH_THINKING_EXP,
|
ANTHROPIC => "anthropic",
|
||||||
"gemini-2.0-flash-thinking-exp"
|
CURSOR => "cursor",
|
||||||
|
GOOGLE => "google",
|
||||||
|
OPENAI => "openai",
|
||||||
|
DEEPSEEK => "deepseek",
|
||||||
|
XAI => "xai",
|
||||||
|
UNKNOWN => "unknown",
|
||||||
);
|
);
|
||||||
def_pub_const!(GEMINI_2_0_FLASH_EXP, "gemini-2.0-flash-exp");
|
|
||||||
def_pub_const!(DEEPSEEK_V3, "deepseek-v3");
|
|
||||||
def_pub_const!(DEEPSEEK_R1, "deepseek-r1");
|
|
||||||
|
|
||||||
// #[derive(Clone, PartialEq, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
// AI 模型
|
||||||
// pub enum ModelType {
|
def_pub_const!(
|
||||||
// Claude35Sonnet,
|
// Anthropic 模型
|
||||||
// Gpt4,
|
CLAUDE_3_OPUS => "claude-3-opus",
|
||||||
// Gpt4o,
|
CLAUDE_3_5_SONNET => "claude-3.5-sonnet",
|
||||||
// Claude3Opus,
|
CLAUDE_3_HAIKU_200K => "claude-3-haiku-200k",
|
||||||
// CursorFast,
|
CLAUDE_3_5_SONNET_200K => "claude-3-5-sonnet-200k",
|
||||||
// CursorSmall,
|
CLAUDE_3_5_SONNET_20241022 => "claude-3-5-sonnet-20241022",
|
||||||
// Gpt35Turbo,
|
CLAUDE_3_5_HAIKU => "claude-3.5-haiku",
|
||||||
// Gpt4Turbo202404,
|
|
||||||
// Gpt4o128k,
|
|
||||||
// Gemini15Flash500k,
|
|
||||||
// Claude3Haiku200k,
|
|
||||||
// Claude35Sonnet200k,
|
|
||||||
// Claude35Sonnet20241022,
|
|
||||||
// Gpt4oMini,
|
|
||||||
// O1Mini,
|
|
||||||
// O1Preview,
|
|
||||||
// O1,
|
|
||||||
// Claude35Haiku,
|
|
||||||
// GeminiExp1206,
|
|
||||||
// Gemini20FlashThinkingExp,
|
|
||||||
// Gemini20FlashExp,
|
|
||||||
// DeepseekV3,
|
|
||||||
// DeepseekR1,
|
|
||||||
// }
|
|
||||||
|
|
||||||
macro_rules! create_model {
|
// OpenAI 模型
|
||||||
($($id:expr, $owner:expr),* $(,)?) => {
|
GPT_4 => "gpt-4",
|
||||||
pub const AVAILABLE_MODELS: [Model; count!($( ($id, $owner) )*)] = [
|
GPT_4O => "gpt-4o",
|
||||||
|
GPT_3_5_TURBO => "gpt-3.5-turbo",
|
||||||
|
GPT_4_TURBO_2024_04_09 => "gpt-4-turbo-2024-04-09",
|
||||||
|
GPT_4O_128K => "gpt-4o-128k",
|
||||||
|
GPT_4O_MINI => "gpt-4o-mini",
|
||||||
|
O1_MINI => "o1-mini",
|
||||||
|
O1_PREVIEW => "o1-preview",
|
||||||
|
O1 => "o1",
|
||||||
|
O3_MINI => "o3-mini",
|
||||||
|
|
||||||
|
// Cursor 模型
|
||||||
|
CURSOR_FAST => "cursor-fast",
|
||||||
|
CURSOR_SMALL => "cursor-small",
|
||||||
|
|
||||||
|
// Google 模型
|
||||||
|
GEMINI_1_5_FLASH_500K => "gemini-1.5-flash-500k",
|
||||||
|
GEMINI_EXP_1206 => "gemini-exp-1206",
|
||||||
|
GEMINI_2_0_PRO_EXP => "gemini-2.0-pro-exp",
|
||||||
|
GEMINI_2_0_FLASH_THINKING_EXP => "gemini-2.0-flash-thinking-exp",
|
||||||
|
GEMINI_2_0_FLASH => "gemini-2.0-flash",
|
||||||
|
|
||||||
|
// Deepseek 模型
|
||||||
|
DEEPSEEK_V3 => "deepseek-v3",
|
||||||
|
DEEPSEEK_R1 => "deepseek-r1",
|
||||||
|
|
||||||
|
// XAI 模型
|
||||||
|
GROK_2 => "grok-2",
|
||||||
|
);
|
||||||
|
|
||||||
|
macro_rules! create_models {
|
||||||
|
($($model:expr => $owner:expr),* $(,)?) => {
|
||||||
|
static INSTANCE: std::sync::LazyLock<RwLock<Models>> = std::sync::LazyLock::new(|| {
|
||||||
|
RwLock::new(Models {
|
||||||
|
models: Arc::new(vec![
|
||||||
$(
|
$(
|
||||||
Model {
|
Model {
|
||||||
id: $id,
|
id: $model.into(),
|
||||||
created: CREATED,
|
created: CREATED,
|
||||||
object: MODEL_OBJECT,
|
object: MODEL_OBJECT,
|
||||||
owned_by: $owner,
|
owned_by: $owner,
|
||||||
},
|
},
|
||||||
)*
|
)*
|
||||||
];
|
]),
|
||||||
|
last_update: Instant::now() - Duration::from_secs(30 * 60),
|
||||||
|
})
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! count {
|
pub struct Models {
|
||||||
() => (0);
|
pub models: Arc<Vec<Model>>,
|
||||||
(($id:expr, $owner:expr) $( ($id2:expr, $owner2:expr) )*) => (1 + count!($( ($id2, $owner2) )*));
|
last_update: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl ModelType {
|
impl Models {
|
||||||
// pub fn as_str_name(&self) -> &'static str {
|
// 返回读锁
|
||||||
// match self {
|
pub fn read() -> parking_lot::RwLockReadGuard<'static, Models> {
|
||||||
// ModelType::Claude35Sonnet => CLAUDE_3_5_SONNET,
|
INSTANCE.read()
|
||||||
// ModelType::Gpt4 => GPT_4,
|
}
|
||||||
// ModelType::Gpt4o => GPT_4O,
|
|
||||||
// ModelType::Claude3Opus => CLAUDE_3_OPUS,
|
// 返回 Arc 的克隆
|
||||||
// ModelType::CursorFast => CURSOR_FAST,
|
pub fn to_arc() -> Arc<Vec<Model>> {
|
||||||
// ModelType::CursorSmall => CURSOR_SMALL,
|
INSTANCE.read().models.clone()
|
||||||
// ModelType::Gpt35Turbo => GPT_3_5_TURBO,
|
}
|
||||||
// ModelType::Gpt4Turbo202404 => GPT_4_TURBO_2024_04_09,
|
|
||||||
// ModelType::Gpt4o128k => GPT_4O_128K,
|
// 克隆所有模型
|
||||||
// ModelType::Gemini15Flash500k => GEMINI_1_5_FLASH_500K,
|
// pub fn cloned() -> Vec<Model> {
|
||||||
// ModelType::Claude3Haiku200k => CLAUDE_3_HAIKU_200K,
|
// INSTANCE.read().models.as_ref().clone()
|
||||||
// ModelType::Claude35Sonnet200k => CLAUDE_3_5_SONNET_200K,
|
// }
|
||||||
// ModelType::Claude35Sonnet20241022 => CLAUDE_3_5_SONNET_20241022,
|
|
||||||
// ModelType::Gpt4oMini => GPT_4O_MINI,
|
// 检查模型是否存在
|
||||||
// ModelType::O1Mini => O1_MINI,
|
pub fn exists(model_id: &str) -> bool {
|
||||||
// ModelType::O1Preview => O1_PREVIEW,
|
Self::read().models.iter().any(|m| m.id == model_id)
|
||||||
// ModelType::O1 => O1,
|
}
|
||||||
// ModelType::Claude35Haiku => CLAUDE_3_5_HAIKU,
|
|
||||||
// ModelType::GeminiExp1206 => GEMINI_EXP_1206,
|
// 查找模型并返回其 ID
|
||||||
// ModelType::Gemini20FlashThinkingExp => GEMINI_2_0_FLASH_THINKING_EXP,
|
pub fn find_id(model: &str) -> Option<String> {
|
||||||
// ModelType::Gemini20FlashExp => GEMINI_2_0_FLASH_EXP,
|
Self::read()
|
||||||
// ModelType::DeepseekV3 => DEEPSEEK_V3,
|
.models
|
||||||
// ModelType::DeepseekR1 => DEEPSEEK_R1,
|
.iter()
|
||||||
// }
|
.find(|m| m.id == model)
|
||||||
|
.map(|m| m.id.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回所有模型 ID 的列表
|
||||||
|
pub fn ids() -> Vec<String> {
|
||||||
|
Self::read()
|
||||||
|
.models
|
||||||
|
.iter()
|
||||||
|
.map(|m| m.id.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入方法
|
||||||
|
pub fn update(new_models: Vec<Model>) -> Result<(), &'static str> {
|
||||||
|
if new_models.is_empty() {
|
||||||
|
return Err("Models list cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = INSTANCE.write();
|
||||||
|
|
||||||
|
// 检查时间间隔(30分钟)
|
||||||
|
if data.last_update.elapsed() < Duration::from_secs(30 * 60) {
|
||||||
|
return Err("Cannot update models more frequently than every 30 minutes");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查内容是否有变化
|
||||||
|
if *data.models == new_models {
|
||||||
|
return Err("No changes in models");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新数据和时间戳
|
||||||
|
data.models = Arc::new(new_models);
|
||||||
|
data.last_update = Instant::now();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// macro_rules! count {
|
||||||
|
// () => (0);
|
||||||
|
// (($id:expr, $owner:expr) $( ($id2:expr, $owner2:expr) )*) => (1 + count!($( ($id2, $owner2) )*));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// pub fn from_str_name(id :&str) -> Option<ModelType> {
|
create_models!(
|
||||||
// match id {
|
CLAUDE_3_5_SONNET => ANTHROPIC,
|
||||||
// CLAUDE_3_5_SONNET => Some(ModelType::Claude35Sonnet),
|
GPT_4 => OPENAI,
|
||||||
// GPT_4 => Some(ModelType::Gpt4),
|
GPT_4O => OPENAI,
|
||||||
// GPT_4O => Some(ModelType::Gpt4o),
|
CLAUDE_3_OPUS => ANTHROPIC,
|
||||||
// CLAUDE_3_OPUS => Some(ModelType::Claude3Opus),
|
CURSOR_FAST => CURSOR,
|
||||||
// CURSOR_FAST => Some(ModelType::CursorFast),
|
CURSOR_SMALL => CURSOR,
|
||||||
// CURSOR_SMALL => Some(ModelType::CursorSmall),
|
GPT_3_5_TURBO => OPENAI,
|
||||||
// GPT_3_5_TURBO => Some(ModelType::Gpt35Turbo),
|
GPT_4_TURBO_2024_04_09 => OPENAI,
|
||||||
// GPT_4_TURBO_2024_04_09 => Some(ModelType::Gpt4Turbo202404),
|
GPT_4O_128K => OPENAI,
|
||||||
// GPT_4O_128K => Some(ModelType::Gpt4o128k),
|
GEMINI_1_5_FLASH_500K => GOOGLE,
|
||||||
// GEMINI_1_5_FLASH_500K => Some(ModelType::Gemini15Flash500k),
|
CLAUDE_3_HAIKU_200K => ANTHROPIC,
|
||||||
// CLAUDE_3_HAIKU_200K => Some(ModelType::Claude3Haiku200k),
|
CLAUDE_3_5_SONNET_200K => ANTHROPIC,
|
||||||
// CLAUDE_3_5_SONNET_200K => Some(ModelType::Claude35Sonnet200k),
|
GPT_4O_MINI => OPENAI,
|
||||||
// CLAUDE_3_5_SONNET_20241022 => Some(ModelType::Claude35Sonnet20241022),
|
O1_MINI => OPENAI,
|
||||||
// GPT_4O_MINI => Some(ModelType::Gpt4oMini),
|
O1_PREVIEW => OPENAI,
|
||||||
// O1_MINI => Some(ModelType::O1Mini),
|
O1 => OPENAI,
|
||||||
// O1_PREVIEW => Some(ModelType::O1Preview),
|
CLAUDE_3_5_HAIKU => ANTHROPIC,
|
||||||
// O1 => Some(ModelType::O1),
|
GEMINI_2_0_PRO_EXP => GOOGLE,
|
||||||
// CLAUDE_3_5_HAIKU => Some(ModelType::Claude35Haiku),
|
GEMINI_2_0_FLASH_THINKING_EXP => GOOGLE,
|
||||||
// GEMINI_EXP_1206 => Some(ModelType::GeminiExp1206),
|
GEMINI_2_0_FLASH => GOOGLE,
|
||||||
// GEMINI_2_0_FLASH_THINKING_EXP => Some(ModelType::Gemini20FlashThinkingExp),
|
DEEPSEEK_V3 => DEEPSEEK,
|
||||||
// GEMINI_2_0_FLASH_EXP => Some(ModelType::Gemini20FlashExp),
|
DEEPSEEK_R1 => DEEPSEEK,
|
||||||
// DEEPSEEK_V3 => Some(ModelType::DeepseekV3),
|
O3_MINI => OPENAI,
|
||||||
// DEEPSEEK_R1 => Some(ModelType::DeepseekR1),
|
GROK_2 => XAI,
|
||||||
// _ => None,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
create_model!(
|
|
||||||
CLAUDE_3_5_SONNET, ANTHROPIC,
|
|
||||||
GPT_4, OPENAI,
|
|
||||||
GPT_4O, OPENAI,
|
|
||||||
CLAUDE_3_OPUS, ANTHROPIC,
|
|
||||||
CURSOR_FAST, CURSOR,
|
|
||||||
CURSOR_SMALL, CURSOR,
|
|
||||||
GPT_3_5_TURBO, OPENAI,
|
|
||||||
GPT_4_TURBO_2024_04_09, OPENAI,
|
|
||||||
GPT_4O_128K, OPENAI,
|
|
||||||
GEMINI_1_5_FLASH_500K, GOOGLE,
|
|
||||||
CLAUDE_3_HAIKU_200K, ANTHROPIC,
|
|
||||||
CLAUDE_3_5_SONNET_200K, ANTHROPIC,
|
|
||||||
CLAUDE_3_5_SONNET_20241022, ANTHROPIC,
|
|
||||||
GPT_4O_MINI, OPENAI,
|
|
||||||
O1_MINI, OPENAI,
|
|
||||||
O1_PREVIEW, OPENAI,
|
|
||||||
O1, OPENAI,
|
|
||||||
CLAUDE_3_5_HAIKU, ANTHROPIC,
|
|
||||||
GEMINI_EXP_1206, GOOGLE,
|
|
||||||
GEMINI_2_0_FLASH_THINKING_EXP, GOOGLE,
|
|
||||||
GEMINI_2_0_FLASH_EXP, GOOGLE,
|
|
||||||
DEEPSEEK_V3, DEEPSEEK,
|
|
||||||
DEEPSEEK_R1, DEEPSEEK,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
pub const USAGE_CHECK_MODELS: [&str; 11] = [
|
pub const USAGE_CHECK_MODELS: [&str; 11] = [
|
||||||
@@ -200,5 +222,3 @@ pub const LONG_CONTEXT_MODELS: [&str; 4] = [
|
|||||||
CLAUDE_3_HAIKU_200K,
|
CLAUDE_3_HAIKU_200K,
|
||||||
CLAUDE_3_5_SONNET_200K,
|
CLAUDE_3_5_SONNET_200K,
|
||||||
];
|
];
|
||||||
|
|
||||||
// include!("constant/models.rs");
|
|
||||||
|
@@ -1,118 +0,0 @@
|
|||||||
pub struct DefaultModel {
|
|
||||||
pub default_on: bool,
|
|
||||||
pub is_long_context_only: Option<bool>,
|
|
||||||
pub name: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const AVAILABLE_MODELS2: [DefaultModel; 22] = [
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: Some(false),
|
|
||||||
name: CLAUDE_3_5_SONNET,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: GPT_4,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: GPT_4O,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: CLAUDE_3_OPUS,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: CURSOR_FAST,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: CURSOR_SMALL,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: GPT_3_5_TURBO,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: GPT_4_TURBO_2024_04_09,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: Some(true),
|
|
||||||
name: GPT_4O_128K,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: Some(true),
|
|
||||||
name: GEMINI_1_5_FLASH_500K,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: Some(true),
|
|
||||||
name: CLAUDE_3_HAIKU_200K,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: Some(true),
|
|
||||||
name: CLAUDE_3_5_SONNET_200K,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: Some(false),
|
|
||||||
name: CLAUDE_3_5_SONNET_20241022,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: Some(false),
|
|
||||||
name: GPT_4O_MINI,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: Some(false),
|
|
||||||
name: O1_MINI,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: Some(false),
|
|
||||||
name: O1_PREVIEW,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: true,
|
|
||||||
is_long_context_only: Some(false),
|
|
||||||
name: O1,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: Some(false),
|
|
||||||
name: CLAUDE_3_5_HAIKU,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: GEMINI_EXP_1206,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: GEMINI_2_0_FLASH_THINKING_EXP,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: GEMINI_2_0_FLASH_EXP,
|
|
||||||
},
|
|
||||||
DefaultModel {
|
|
||||||
default_on: false,
|
|
||||||
is_long_context_only: None,
|
|
||||||
name: DEEPSEEK_V3,
|
|
||||||
},
|
|
||||||
];
|
|
@@ -1,6 +1,6 @@
|
|||||||
use super::aiserver::v1::ErrorDetails;
|
use super::aiserver::v1::ErrorDetails;
|
||||||
use crate::common::model::{ApiStatus, ErrorResponse as CommonErrorResponse};
|
use crate::common::model::{ApiStatus, ErrorResponse as CommonErrorResponse};
|
||||||
use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _};
|
use base64::{Engine as _, engine::general_purpose::STANDARD_NO_PAD};
|
||||||
use prost::Message as _;
|
use prost::Message as _;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -42,7 +42,7 @@ pub struct ErrorDetail {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
impl ChatError {
|
impl ChatError {
|
||||||
pub fn to_error_response(self) -> ErrorResponse {
|
pub fn into_error_response(self) -> ErrorResponse {
|
||||||
if self.error.details.is_empty() {
|
if self.error.details.is_empty() {
|
||||||
return ErrorResponse {
|
return ErrorResponse {
|
||||||
status: 500,
|
status: 500,
|
||||||
@@ -108,7 +108,7 @@ impl ErrorResponse {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_common(self) -> CommonErrorResponse {
|
pub fn into_common(self) -> CommonErrorResponse {
|
||||||
CommonErrorResponse {
|
CommonErrorResponse {
|
||||||
status: ApiStatus::Error,
|
status: ApiStatus::Error,
|
||||||
code: Some(self.status),
|
code: Some(self.status),
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
use crate::app::{constant::AUTHORIZATION_BEARER_PREFIX, lazy::AUTH_TOKEN};
|
use crate::app::{constant::AUTHORIZATION_BEARER_PREFIX, lazy::AUTH_TOKEN};
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
http::{header::AUTHORIZATION, Request, StatusCode},
|
http::{Request, StatusCode, header::AUTHORIZATION},
|
||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::Response,
|
response::Response,
|
||||||
};
|
};
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -80,22 +82,28 @@ pub struct Usage {
|
|||||||
// 模型定义
|
// 模型定义
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
pub id: &'static str,
|
pub id: String,
|
||||||
pub created: &'static i64,
|
pub created: &'static i64,
|
||||||
pub object: &'static str,
|
pub object: &'static str,
|
||||||
pub owned_by: &'static str,
|
pub owned_by: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::constant::USAGE_CHECK_MODELS;
|
impl PartialEq for Model {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use super::constant::{Models, USAGE_CHECK_MODELS};
|
||||||
use crate::app::model::{AppConfig, UsageCheck};
|
use crate::app::model::{AppConfig, UsageCheck};
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn is_usage_check(&self, usage_check: Option<UsageCheck>) -> bool {
|
pub fn is_usage_check(model_id: &String, usage_check: Option<UsageCheck>) -> bool {
|
||||||
match usage_check.unwrap_or(AppConfig::get_usage_check()) {
|
match usage_check.unwrap_or(AppConfig::get_usage_check()) {
|
||||||
UsageCheck::None => false,
|
UsageCheck::None => false,
|
||||||
UsageCheck::Default => USAGE_CHECK_MODELS.contains(&self.id),
|
UsageCheck::Default => USAGE_CHECK_MODELS.contains(&model_id.as_str()),
|
||||||
UsageCheck::All => true,
|
UsageCheck::All => true,
|
||||||
UsageCheck::Custom(models) => models.contains(&self.id),
|
UsageCheck::Custom(models) => models.contains(model_id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,5 +111,18 @@ impl Model {
|
|||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ModelsResponse {
|
pub struct ModelsResponse {
|
||||||
pub object: &'static str,
|
pub object: &'static str,
|
||||||
pub data: &'static [Model],
|
pub data: Arc<Vec<Model>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModelsResponse {
|
||||||
|
pub(super) fn new(data: Arc<Vec<Model>>) -> Self {
|
||||||
|
Self {
|
||||||
|
object: "list",
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn with_default_models() -> Self {
|
||||||
|
Self::new(Models::to_arc())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,12 +2,15 @@ mod logs;
|
|||||||
pub use logs::{handle_logs, handle_logs_post};
|
pub use logs::{handle_logs, handle_logs_post};
|
||||||
mod health;
|
mod health;
|
||||||
pub use health::{handle_health, handle_root};
|
pub use health::{handle_health, handle_root};
|
||||||
|
mod token;
|
||||||
|
pub use token::{handle_basic_calibration, handle_tokens_page};
|
||||||
mod tokens;
|
mod tokens;
|
||||||
pub use tokens::{
|
pub use tokens::{
|
||||||
handle_add_tokens, handle_basic_calibration, handle_delete_tokens, handle_get_checksum,
|
handle_add_tokens, handle_delete_tokens, handle_get_tokens, handle_update_token_tags,
|
||||||
handle_get_hash, handle_get_timestamp_header, handle_get_tokens, handle_reload_tokens,
|
handle_update_tokens,
|
||||||
handle_tokens_page, handle_update_tokens,
|
|
||||||
};
|
};
|
||||||
|
mod checksum;
|
||||||
|
pub use checksum::{handle_get_checksum, handle_get_hash, handle_get_timestamp_header};
|
||||||
mod profile;
|
mod profile;
|
||||||
pub use profile::handle_user_info;
|
pub use profile::handle_user_info;
|
||||||
mod config;
|
mod config;
|
||||||
|
@@ -1,26 +1,29 @@
|
|||||||
use axum::response::{IntoResponse, Response};
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
use reqwest::header::CONTENT_TYPE;
|
use reqwest::header::CONTENT_TYPE;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
AppConfig, PageContent,
|
||||||
app::constant::{
|
app::constant::{
|
||||||
CONTENT_TYPE_TEXT_HTML_WITH_UTF8, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_API_PATH,
|
CONTENT_TYPE_TEXT_HTML_WITH_UTF8, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_API_PATH,
|
||||||
},
|
},
|
||||||
AppConfig, PageContent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_api_page() -> impl IntoResponse {
|
pub async fn handle_api_page() -> impl IntoResponse {
|
||||||
match AppConfig::get_page_content(ROUTE_API_PATH).unwrap_or_default() {
|
match AppConfig::get_page_content(ROUTE_API_PATH).unwrap_or_default() {
|
||||||
PageContent::Default => Response::builder()
|
PageContent::Default => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(include_str!("../../../static/api.min.html").to_string())
|
.body(Body::from(include_str!("../../../static/api.min.html")))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Text(content) => Response::builder()
|
PageContent::Text(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Html(content) => Response::builder()
|
PageContent::Html(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
59
src/chat/route/checksum.rs
Normal file
59
src/chat/route/checksum.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::Query,
|
||||||
|
http::{HeaderMap, header::CONTENT_TYPE},
|
||||||
|
response::{IntoResponse as _, Response},
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::constant::CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8,
|
||||||
|
common::utils::{
|
||||||
|
generate_checksum_with_default, generate_checksum_with_repair, generate_hash,
|
||||||
|
generate_timestamp_header,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handle_get_hash() -> Response {
|
||||||
|
let hash = generate_hash();
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(
|
||||||
|
CONTENT_TYPE,
|
||||||
|
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
(headers, hash).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ChecksumQuery {
|
||||||
|
#[serde(default)]
|
||||||
|
pub checksum: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_get_checksum(Query(query): Query<ChecksumQuery>) -> Response {
|
||||||
|
let checksum = match query.checksum {
|
||||||
|
None => generate_checksum_with_default(),
|
||||||
|
Some(checksum) => generate_checksum_with_repair(&checksum),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(
|
||||||
|
CONTENT_TYPE,
|
||||||
|
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
(headers, checksum).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_get_timestamp_header() -> Response {
|
||||||
|
let timestamp_header = generate_timestamp_header();
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(
|
||||||
|
CONTENT_TYPE,
|
||||||
|
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
(headers, timestamp_header).into_response()
|
||||||
|
}
|
@@ -1,30 +1,33 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
constant::{
|
constant::{
|
||||||
AUTHORIZATION_BEARER_PREFIX, CONTENT_TYPE_TEXT_CSS_WITH_UTF8, CONTENT_TYPE_TEXT_HTML_WITH_UTF8, CONTENT_TYPE_TEXT_JS_WITH_UTF8, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_ABOUT_PATH, ROUTE_BUILD_KEY_PATH, ROUTE_CONFIG_PATH, ROUTE_README_PATH, ROUTE_SHARED_JS_PATH, ROUTE_SHARED_STYLES_PATH
|
AUTHORIZATION_BEARER_PREFIX, CONTENT_TYPE_TEXT_CSS_WITH_UTF8,
|
||||||
|
CONTENT_TYPE_TEXT_HTML_WITH_UTF8, CONTENT_TYPE_TEXT_JS_WITH_UTF8,
|
||||||
|
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_ABOUT_PATH, ROUTE_BUILD_KEY_PATH,
|
||||||
|
ROUTE_CONFIG_PATH, ROUTE_README_PATH, ROUTE_SHARED_JS_PATH, ROUTE_SHARED_STYLES_PATH,
|
||||||
},
|
},
|
||||||
lazy::{AUTH_TOKEN, KEY_PREFIX},
|
lazy::{AUTH_TOKEN, KEY_PREFIX},
|
||||||
model::{AppConfig, BuildKeyRequest, BuildKeyResponse, PageContent, UsageCheckModelType},
|
model::{AppConfig, BuildKeyRequest, BuildKeyResponse, PageContent, UsageCheckModelType},
|
||||||
},
|
},
|
||||||
chat::config::{key_config, KeyConfig},
|
chat::config::{KeyConfig, key_config},
|
||||||
common::utils::{to_base64, token_to_tokeninfo},
|
common::utils::{to_base64, token_to_tokeninfo},
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
|
Json,
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::Path,
|
extract::Path,
|
||||||
http::{
|
http::{
|
||||||
header::{AUTHORIZATION, CONTENT_TYPE, LOCATION},
|
|
||||||
HeaderMap, StatusCode,
|
HeaderMap, StatusCode,
|
||||||
|
header::{AUTHORIZATION, CONTENT_TYPE, LOCATION},
|
||||||
},
|
},
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
Json,
|
|
||||||
};
|
};
|
||||||
use prost::Message as _;
|
use prost::Message as _;
|
||||||
|
|
||||||
pub async fn handle_env_example() -> impl IntoResponse {
|
pub async fn handle_env_example() -> impl IntoResponse {
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
||||||
.body(include_str!("../../../.env.example").to_string())
|
.body(Body::from(include_str!("../../../.env.example")))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,15 +36,15 @@ pub async fn handle_config_page() -> impl IntoResponse {
|
|||||||
match AppConfig::get_page_content(ROUTE_CONFIG_PATH).unwrap_or_default() {
|
match AppConfig::get_page_content(ROUTE_CONFIG_PATH).unwrap_or_default() {
|
||||||
PageContent::Default => Response::builder()
|
PageContent::Default => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(include_str!("../../../static/config.min.html").to_string())
|
.body(Body::from(include_str!("../../../static/config.min.html")))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Text(content) => Response::builder()
|
PageContent::Text(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Html(content) => Response::builder()
|
PageContent::Html(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,11 +55,13 @@ pub async fn handle_static(Path(path): Path<String>) -> impl IntoResponse {
|
|||||||
match AppConfig::get_page_content(ROUTE_SHARED_STYLES_PATH).unwrap_or_default() {
|
match AppConfig::get_page_content(ROUTE_SHARED_STYLES_PATH).unwrap_or_default() {
|
||||||
PageContent::Default => Response::builder()
|
PageContent::Default => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_CSS_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_CSS_WITH_UTF8)
|
||||||
.body(include_str!("../../../static/shared-styles.min.css").to_string())
|
.body(Body::from(include_str!(
|
||||||
|
"../../../static/shared-styles.min.css"
|
||||||
|
)))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Text(content) | PageContent::Html(content) => Response::builder()
|
PageContent::Text(content) | PageContent::Html(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_CSS_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_CSS_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,17 +69,19 @@ pub async fn handle_static(Path(path): Path<String>) -> impl IntoResponse {
|
|||||||
match AppConfig::get_page_content(ROUTE_SHARED_JS_PATH).unwrap_or_default() {
|
match AppConfig::get_page_content(ROUTE_SHARED_JS_PATH).unwrap_or_default() {
|
||||||
PageContent::Default => Response::builder()
|
PageContent::Default => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_JS_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_JS_WITH_UTF8)
|
||||||
.body(include_str!("../../../static/shared.min.js").to_string())
|
.body(Body::from(
|
||||||
|
include_str!("../../../static/shared.min.js").to_string(),
|
||||||
|
))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Text(content) | PageContent::Html(content) => Response::builder()
|
PageContent::Text(content) | PageContent::Html(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_JS_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_JS_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Response::builder()
|
_ => Response::builder()
|
||||||
.status(StatusCode::NOT_FOUND)
|
.status(StatusCode::NOT_FOUND)
|
||||||
.body("Not found".to_string())
|
.body(Body::from("Not found"))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,15 +90,15 @@ pub async fn handle_readme() -> impl IntoResponse {
|
|||||||
match AppConfig::get_page_content(ROUTE_README_PATH).unwrap_or_default() {
|
match AppConfig::get_page_content(ROUTE_README_PATH).unwrap_or_default() {
|
||||||
PageContent::Default => Response::builder()
|
PageContent::Default => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(include_str!("../../../static/readme.min.html").to_string())
|
.body(Body::from(include_str!("../../../static/readme.min.html")))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Text(content) => Response::builder()
|
PageContent::Text(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Html(content) => Response::builder()
|
PageContent::Html(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,11 +112,11 @@ pub async fn handle_about() -> impl IntoResponse {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Text(content) => Response::builder()
|
PageContent::Text(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
||||||
.body(Body::from(content.clone()))
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Html(content) => Response::builder()
|
PageContent::Html(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(Body::from(content.clone()))
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,15 +125,17 @@ pub async fn handle_build_key_page() -> impl IntoResponse {
|
|||||||
match AppConfig::get_page_content(ROUTE_BUILD_KEY_PATH).unwrap_or_default() {
|
match AppConfig::get_page_content(ROUTE_BUILD_KEY_PATH).unwrap_or_default() {
|
||||||
PageContent::Default => Response::builder()
|
PageContent::Default => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(include_str!("../../../static/build_key.min.html").to_string())
|
.body(Body::from(include_str!(
|
||||||
|
"../../../static/build_key.min.html"
|
||||||
|
)))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Text(content) => Response::builder()
|
PageContent::Text(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Html(content) => Response::builder()
|
PageContent::Html(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(content.clone())
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +151,9 @@ pub async fn handle_build_key(
|
|||||||
.and_then(|h| h.to_str().ok())
|
.and_then(|h| h.to_str().ok())
|
||||||
.and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX));
|
.and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX));
|
||||||
|
|
||||||
if auth_header.map_or(true, |h| h != AppConfig::get_share_token().as_str() && h != AUTH_TOKEN.as_str()) {
|
if auth_header
|
||||||
|
.is_none_or(|h| h != AppConfig::get_share_token().as_str() && h != AUTH_TOKEN.as_str())
|
||||||
|
{
|
||||||
return (
|
return (
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
Json(BuildKeyResponse::Error("Unauthorized".to_owned())),
|
Json(BuildKeyResponse::Error("Unauthorized".to_owned())),
|
||||||
@@ -157,7 +168,7 @@ pub async fn handle_build_key(
|
|||||||
return (
|
return (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(BuildKeyResponse::Error("Invalid auth token".to_owned())),
|
Json(BuildKeyResponse::Error("Invalid auth token".to_owned())),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -173,9 +184,7 @@ pub async fn handle_build_key(
|
|||||||
if let Some(usage_check_models) = request.usage_check_models {
|
if let Some(usage_check_models) = request.usage_check_models {
|
||||||
let usage_check = key_config::UsageCheckModel {
|
let usage_check = key_config::UsageCheckModel {
|
||||||
r#type: match usage_check_models.model_type {
|
r#type: match usage_check_models.model_type {
|
||||||
UsageCheckModelType::Default => {
|
UsageCheckModelType::Default => key_config::usage_check_model::Type::Default as i32,
|
||||||
key_config::usage_check_model::Type::Default as i32
|
|
||||||
}
|
|
||||||
UsageCheckModelType::Disabled => {
|
UsageCheckModelType::Disabled => {
|
||||||
key_config::usage_check_model::Type::Disabled as i32
|
key_config::usage_check_model::Type::Disabled as i32
|
||||||
}
|
}
|
||||||
|
@@ -6,28 +6,28 @@ use crate::{
|
|||||||
ROUTE_BASIC_CALIBRATION_PATH, ROUTE_BUILD_KEY_PATH, ROUTE_CONFIG_PATH,
|
ROUTE_BASIC_CALIBRATION_PATH, ROUTE_BUILD_KEY_PATH, ROUTE_CONFIG_PATH,
|
||||||
ROUTE_ENV_EXAMPLE_PATH, ROUTE_GET_CHECKSUM, ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER,
|
ROUTE_ENV_EXAMPLE_PATH, ROUTE_GET_CHECKSUM, ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER,
|
||||||
ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH,
|
ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH,
|
||||||
ROUTE_STATIC_PATH, ROUTE_TOKENS_ADD_PATH, ROUTE_TOKENS_DELETE_PATH,
|
ROUTE_STATIC_PATH, ROUTE_TOKEN_TAGS_UPDATE_PATH, ROUTE_TOKENS_ADD_PATH,
|
||||||
ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH, ROUTE_TOKENS_UPDATE_PATH,
|
ROUTE_TOKENS_DELETE_PATH, ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH,
|
||||||
ROUTE_USER_INFO_PATH,
|
ROUTE_TOKENS_UPDATE_PATH, ROUTE_USER_INFO_PATH,
|
||||||
},
|
},
|
||||||
lazy::{get_start_time, AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH},
|
lazy::{AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH, get_start_time},
|
||||||
model::{AppConfig, AppState, PageContent},
|
model::{AppConfig, AppState, PageContent},
|
||||||
},
|
},
|
||||||
chat::constant::AVAILABLE_MODELS,
|
chat::constant::Models,
|
||||||
common::model::{
|
common::model::{
|
||||||
health::{CpuInfo, HealthCheckResponse, MemoryInfo, SystemInfo, SystemStats},
|
|
||||||
ApiStatus,
|
ApiStatus,
|
||||||
|
health::{CpuInfo, HealthCheckResponse, MemoryInfo, SystemInfo, SystemStats},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
|
Json,
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::State,
|
extract::State,
|
||||||
http::{
|
http::{
|
||||||
header::{CONTENT_TYPE, LOCATION},
|
|
||||||
HeaderMap, StatusCode,
|
HeaderMap, StatusCode,
|
||||||
|
header::{CONTENT_TYPE, LOCATION},
|
||||||
},
|
},
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
Json,
|
|
||||||
};
|
};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use reqwest::header::AUTHORIZATION;
|
use reqwest::header::AUTHORIZATION;
|
||||||
@@ -44,11 +44,11 @@ pub async fn handle_root() -> impl IntoResponse {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Text(content) => Response::builder()
|
PageContent::Text(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
||||||
.body(Body::from(content.clone()))
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Html(content) => Response::builder()
|
PageContent::Html(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(Body::from(content.clone()))
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ pub async fn handle_health(
|
|||||||
.get(AUTHORIZATION)
|
.get(AUTHORIZATION)
|
||||||
.and_then(|h| h.to_str().ok())
|
.and_then(|h| h.to_str().ok())
|
||||||
.and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
|
.and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
|
||||||
.map_or(false, |token| token == AUTH_TOKEN.as_str())
|
.is_some_and(|token| token == AUTH_TOKEN.as_str())
|
||||||
{
|
{
|
||||||
// 只有在需要系统信息时才创建实例
|
// 只有在需要系统信息时才创建实例
|
||||||
let mut sys = System::new_with_specifics(
|
let mut sys = System::new_with_specifics(
|
||||||
@@ -93,8 +93,8 @@ pub async fn handle_health(
|
|||||||
|
|
||||||
Some(SystemStats {
|
Some(SystemStats {
|
||||||
started: start_time.to_string(),
|
started: start_time.to_string(),
|
||||||
total_requests: state.total_requests,
|
total_requests: state.request_manager.total_requests,
|
||||||
active_requests: state.active_requests,
|
active_requests: state.request_manager.active_requests,
|
||||||
system: SystemInfo {
|
system: SystemInfo {
|
||||||
memory: MemoryInfo {
|
memory: MemoryInfo {
|
||||||
rss: memory, // 物理内存使用量(字节)
|
rss: memory, // 物理内存使用量(字节)
|
||||||
@@ -113,7 +113,7 @@ pub async fn handle_health(
|
|||||||
version: PKG_VERSION,
|
version: PKG_VERSION,
|
||||||
uptime,
|
uptime,
|
||||||
stats,
|
stats,
|
||||||
models: AVAILABLE_MODELS.iter().map(|m| m.id).collect::<Vec<_>>(),
|
models: Models::ids(),
|
||||||
endpoints: vec![
|
endpoints: vec![
|
||||||
ROUTE_CHAT_PATH.as_str(),
|
ROUTE_CHAT_PATH.as_str(),
|
||||||
ROUTE_MODELS_PATH.as_str(),
|
ROUTE_MODELS_PATH.as_str(),
|
||||||
@@ -122,6 +122,7 @@ pub async fn handle_health(
|
|||||||
ROUTE_TOKENS_UPDATE_PATH,
|
ROUTE_TOKENS_UPDATE_PATH,
|
||||||
ROUTE_TOKENS_ADD_PATH,
|
ROUTE_TOKENS_ADD_PATH,
|
||||||
ROUTE_TOKENS_DELETE_PATH,
|
ROUTE_TOKENS_DELETE_PATH,
|
||||||
|
ROUTE_TOKEN_TAGS_UPDATE_PATH,
|
||||||
ROUTE_LOGS_PATH,
|
ROUTE_LOGS_PATH,
|
||||||
ROUTE_ENV_EXAMPLE_PATH,
|
ROUTE_ENV_EXAMPLE_PATH,
|
||||||
ROUTE_CONFIG_PATH,
|
ROUTE_CONFIG_PATH,
|
||||||
|
@@ -10,14 +10,14 @@ use crate::{
|
|||||||
common::{model::ApiStatus, utils::extract_token},
|
common::{model::ApiStatus, utils::extract_token},
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
|
Json,
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::State,
|
extract::State,
|
||||||
http::{
|
http::{
|
||||||
header::{AUTHORIZATION, CONTENT_TYPE},
|
|
||||||
HeaderMap, StatusCode,
|
HeaderMap, StatusCode,
|
||||||
|
header::{AUTHORIZATION, CONTENT_TYPE},
|
||||||
},
|
},
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
Json,
|
|
||||||
};
|
};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -28,17 +28,15 @@ pub async fn handle_logs() -> impl IntoResponse {
|
|||||||
match AppConfig::get_page_content(ROUTE_LOGS_PATH).unwrap_or_default() {
|
match AppConfig::get_page_content(ROUTE_LOGS_PATH).unwrap_or_default() {
|
||||||
PageContent::Default => Response::builder()
|
PageContent::Default => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(Body::from(
|
.body(Body::from(include_str!("../../../static/logs.min.html")))
|
||||||
include_str!("../../../static/logs.min.html").to_string(),
|
|
||||||
))
|
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Text(content) => Response::builder()
|
PageContent::Text(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
||||||
.body(Body::from(content.clone()))
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
PageContent::Html(content) => Response::builder()
|
PageContent::Html(content) => Response::builder()
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
.body(Body::from(content.clone()))
|
.body(Body::from(content))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,10 +60,10 @@ pub async fn handle_logs_post(
|
|||||||
if auth_header == auth_token {
|
if auth_header == auth_token {
|
||||||
return Ok(Json(LogsResponse {
|
return Ok(Json(LogsResponse {
|
||||||
status: ApiStatus::Success,
|
status: ApiStatus::Success,
|
||||||
total: state.total_requests,
|
total: state.request_manager.total_requests,
|
||||||
active: Some(state.active_requests),
|
active: Some(state.request_manager.active_requests),
|
||||||
error: Some(state.error_requests),
|
error: Some(state.request_manager.error_requests),
|
||||||
logs: state.request_logs.clone(),
|
logs: state.request_manager.request_logs.clone(),
|
||||||
timestamp: Local::now().to_string(),
|
timestamp: Local::now().to_string(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -75,6 +73,7 @@ pub async fn handle_logs_post(
|
|||||||
|
|
||||||
// 否则筛选出token匹配的日志
|
// 否则筛选出token匹配的日志
|
||||||
let filtered_logs: Vec<RequestLog> = state
|
let filtered_logs: Vec<RequestLog> = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|log| log.token_info.token == token_part)
|
.filter(|log| log.token_info.token == token_part)
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
chat::constant::ERR_NODATA,
|
chat::constant::ERR_NODATA,
|
||||||
common::{model::userinfo::GetUserInfo, utils::{extract_token, get_token_profile}},
|
common::{
|
||||||
|
model::userinfo::GetUserInfo,
|
||||||
|
utils::{extract_token, get_token_profile},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
|
|
||||||
use super::tokens::TokenRequest;
|
use super::token::TokenRequest;
|
||||||
|
|
||||||
pub async fn handle_user_info(Json(request): Json<TokenRequest>) -> Json<GetUserInfo> {
|
pub async fn handle_user_info(Json(request): Json<TokenRequest>) -> Json<GetUserInfo> {
|
||||||
let auth_token = match request.token {
|
let auth_token = match request.token {
|
||||||
@@ -12,7 +15,7 @@ pub async fn handle_user_info(Json(request): Json<TokenRequest>) -> Json<GetUser
|
|||||||
None => {
|
None => {
|
||||||
return Json(GetUserInfo::Error {
|
return Json(GetUserInfo::Error {
|
||||||
error: ERR_NODATA.to_string(),
|
error: ERR_NODATA.to_string(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,12 +24,12 @@ pub async fn handle_user_info(Json(request): Json<TokenRequest>) -> Json<GetUser
|
|||||||
None => {
|
None => {
|
||||||
return Json(GetUserInfo::Error {
|
return Json(GetUserInfo::Error {
|
||||||
error: ERR_NODATA.to_string(),
|
error: ERR_NODATA.to_string(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match get_token_profile(&token).await {
|
match get_token_profile(&token).await {
|
||||||
Some(usage) => Json(GetUserInfo::Usage(usage)),
|
Some(usage) => Json(GetUserInfo::Usage(Box::new(usage))),
|
||||||
None => Json(GetUserInfo::Error {
|
None => Json(GetUserInfo::Error {
|
||||||
error: ERR_NODATA.to_string(),
|
error: ERR_NODATA.to_string(),
|
||||||
}),
|
}),
|
||||||
|
99
src/chat/route/token.rs
Normal file
99
src/chat/route/token.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
use crate::{
|
||||||
|
app::{
|
||||||
|
constant::{
|
||||||
|
CONTENT_TYPE_TEXT_HTML_WITH_UTF8, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_TOKENS_PATH,
|
||||||
|
},
|
||||||
|
model::{AppConfig, PageContent},
|
||||||
|
},
|
||||||
|
common::{
|
||||||
|
model::ApiStatus,
|
||||||
|
utils::{extract_time, extract_time_ks, extract_user_id, validate_token_and_checksum},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use axum::{
|
||||||
|
Json,
|
||||||
|
body::Body,
|
||||||
|
http::header::CONTENT_TYPE,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub async fn handle_tokens_page() -> impl IntoResponse {
|
||||||
|
match AppConfig::get_page_content(ROUTE_TOKENS_PATH).unwrap_or_default() {
|
||||||
|
PageContent::Default => Response::builder()
|
||||||
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
|
.body(Body::from(include_str!("../../../static/tokens.min.html")))
|
||||||
|
.unwrap(),
|
||||||
|
PageContent::Text(content) => Response::builder()
|
||||||
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
||||||
|
.body(Body::from(content))
|
||||||
|
.unwrap(),
|
||||||
|
PageContent::Html(content) => Response::builder()
|
||||||
|
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
||||||
|
.body(Body::from(content))
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct TokenRequest {
|
||||||
|
pub token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct BasicCalibrationResponse {
|
||||||
|
pub status: ApiStatus,
|
||||||
|
pub message: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub user_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub create_at: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub checksum_time: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_basic_calibration(
|
||||||
|
Json(request): Json<TokenRequest>,
|
||||||
|
) -> Json<BasicCalibrationResponse> {
|
||||||
|
// 从请求头中获取并验证 auth token
|
||||||
|
let auth_token = match request.token {
|
||||||
|
Some(token) => token,
|
||||||
|
None => {
|
||||||
|
return Json(BasicCalibrationResponse {
|
||||||
|
status: ApiStatus::Error,
|
||||||
|
message: Some("未提供授权令牌".to_string()),
|
||||||
|
user_id: None,
|
||||||
|
create_at: None,
|
||||||
|
checksum_time: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 校验 token 和 checksum
|
||||||
|
let (token, checksum) = match validate_token_and_checksum(&auth_token) {
|
||||||
|
Some(parts) => parts,
|
||||||
|
None => {
|
||||||
|
return Json(BasicCalibrationResponse {
|
||||||
|
status: ApiStatus::Error,
|
||||||
|
message: Some("无效令牌或无效校验和".to_string()),
|
||||||
|
user_id: None,
|
||||||
|
create_at: None,
|
||||||
|
checksum_time: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提取用户ID和创建时间
|
||||||
|
let user_id = extract_user_id(&token);
|
||||||
|
let create_at = extract_time(&token).map(|dt| dt.to_string());
|
||||||
|
let checksum_time = extract_time_ks(&checksum[..8]);
|
||||||
|
|
||||||
|
// 返回校验结果
|
||||||
|
Json(BasicCalibrationResponse {
|
||||||
|
status: ApiStatus::Success,
|
||||||
|
message: Some("校验成功".to_string()),
|
||||||
|
user_id,
|
||||||
|
create_at,
|
||||||
|
checksum_time,
|
||||||
|
})
|
||||||
|
}
|
@@ -1,83 +1,29 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
constant::{
|
constant::AUTHORIZATION_BEARER_PREFIX,
|
||||||
AUTHORIZATION_BEARER_PREFIX, CONTENT_TYPE_TEXT_HTML_WITH_UTF8,
|
lazy::AUTH_TOKEN,
|
||||||
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_TOKENS_PATH,
|
|
||||||
},
|
|
||||||
lazy::{AUTH_TOKEN, TOKEN_LIST_FILE},
|
|
||||||
model::{
|
model::{
|
||||||
AppConfig, AppState, PageContent, TokenAddRequestTokenInfo, TokenInfo,
|
AppState, TokenAddRequest, TokenInfo, TokenInfoResponse, TokenManager,
|
||||||
TokenUpdateRequest, TokensDeleteRequest, TokensDeleteResponse,
|
TokenTagsResponse, TokenTagsUpdateRequest, TokenUpdateRequest, TokensDeleteRequest,
|
||||||
|
TokensDeleteResponse,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
common::{
|
common::{
|
||||||
model::{error::ChatError, ApiStatus, ErrorResponse},
|
model::{ApiStatus, ErrorResponse, error::ChatError, userinfo::TokenProfile},
|
||||||
utils::{
|
utils::{
|
||||||
extract_time, extract_time_ks, extract_user_id, generate_checksum_with_default,
|
generate_checksum_with_default, generate_checksum_with_repair,
|
||||||
generate_checksum_with_repair, generate_hash, generate_timestamp_header, load_tokens,
|
load_tokens_from_content, parse_token, validate_token,
|
||||||
parse_token, validate_token, validate_token_and_checksum, write_tokens,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Query, State},
|
|
||||||
http::{
|
|
||||||
header::{AUTHORIZATION, CONTENT_TYPE},
|
|
||||||
HeaderMap,
|
|
||||||
},
|
|
||||||
response::{IntoResponse, Response},
|
|
||||||
Json,
|
Json,
|
||||||
|
extract::State,
|
||||||
|
http::{HeaderMap, StatusCode, header::AUTHORIZATION},
|
||||||
};
|
};
|
||||||
use reqwest::StatusCode;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
pub async fn handle_get_hash() -> Response {
|
|
||||||
let hash = generate_hash();
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(
|
|
||||||
CONTENT_TYPE,
|
|
||||||
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
(headers, hash).into_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ChecksumQuery {
|
|
||||||
#[serde(default)]
|
|
||||||
pub checksum: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_get_checksum(Query(query): Query<ChecksumQuery>) -> Response {
|
|
||||||
let checksum = match query.checksum {
|
|
||||||
None => generate_checksum_with_default(),
|
|
||||||
Some(checksum) => generate_checksum_with_repair(&checksum),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(
|
|
||||||
CONTENT_TYPE,
|
|
||||||
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
(headers, checksum).into_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_get_timestamp_header() -> Response {
|
|
||||||
let timestamp_header = generate_timestamp_header();
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(
|
|
||||||
CONTENT_TYPE,
|
|
||||||
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
(headers, timestamp_header).into_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_get_tokens(
|
pub async fn handle_get_tokens(
|
||||||
State(state): State<Arc<Mutex<AppState>>>,
|
State(state): State<Arc<Mutex<AppState>>>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
@@ -93,7 +39,8 @@ pub async fn handle_get_tokens(
|
|||||||
return Err(StatusCode::UNAUTHORIZED);
|
return Err(StatusCode::UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tokens = state.lock().await.token_infos.clone();
|
let state = state.lock().await;
|
||||||
|
let tokens = state.token_manager.tokens.clone();
|
||||||
let tokens_count = tokens.len();
|
let tokens_count = tokens.len();
|
||||||
|
|
||||||
Ok(Json(TokenInfoResponse {
|
Ok(Json(TokenInfoResponse {
|
||||||
@@ -104,49 +51,6 @@ pub async fn handle_get_tokens(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct TokenInfoResponse {
|
|
||||||
pub status: ApiStatus,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub tokens: Option<Vec<TokenInfo>>,
|
|
||||||
pub tokens_count: usize,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub message: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_reload_tokens(
|
|
||||||
State(state): State<Arc<Mutex<AppState>>>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
) -> Result<Json<TokenInfoResponse>, StatusCode> {
|
|
||||||
// 验证 AUTH_TOKEN
|
|
||||||
let auth_header = headers
|
|
||||||
.get(AUTHORIZATION)
|
|
||||||
.and_then(|h| h.to_str().ok())
|
|
||||||
.and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
|
|
||||||
.ok_or(StatusCode::UNAUTHORIZED)?;
|
|
||||||
|
|
||||||
if auth_header != AUTH_TOKEN.as_str() {
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新加载 tokens
|
|
||||||
let tokens = load_tokens();
|
|
||||||
let tokens_count = tokens.len();
|
|
||||||
|
|
||||||
// 更新应用状态
|
|
||||||
{
|
|
||||||
let mut state = state.lock().await;
|
|
||||||
state.token_infos = tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Json(TokenInfoResponse {
|
|
||||||
status: ApiStatus::Success,
|
|
||||||
tokens: None,
|
|
||||||
tokens_count,
|
|
||||||
message: Some("Token list has been reloaded".to_string()),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_update_tokens(
|
pub async fn handle_update_tokens(
|
||||||
State(state): State<Arc<Mutex<AppState>>>,
|
State(state): State<Arc<Mutex<AppState>>>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
@@ -163,19 +67,50 @@ pub async fn handle_update_tokens(
|
|||||||
return Err(StatusCode::UNAUTHORIZED);
|
return Err(StatusCode::UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_list_file = TOKEN_LIST_FILE.as_str();
|
// 获取当前的 token_manager 以保留现有 token 的 profile 和 tags
|
||||||
|
let current_token_manager = {
|
||||||
|
let state = state.lock().await;
|
||||||
|
state.token_manager.clone()
|
||||||
|
};
|
||||||
|
|
||||||
std::fs::write(&token_list_file, &request.tokens)
|
// 创建 token -> (profile, tags) 映射
|
||||||
|
let token_info_map: HashMap<String, (Option<TokenProfile>, Option<Vec<String>>)> =
|
||||||
|
current_token_manager
|
||||||
|
.tokens
|
||||||
|
.iter()
|
||||||
|
.map(|token| {
|
||||||
|
(
|
||||||
|
token.token.clone(),
|
||||||
|
(token.profile.clone(), token.tags.clone()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// 从请求内容加载新的 tokens
|
||||||
|
let mut new_tokens = load_tokens_from_content(&request.tokens);
|
||||||
|
|
||||||
|
// 为相同的 token 保留原有的 profile 和 tags
|
||||||
|
for token_info in &mut new_tokens {
|
||||||
|
if let Some((profile, tags)) = token_info_map.get(&token_info.token) {
|
||||||
|
token_info.profile = profile.clone();
|
||||||
|
token_info.tags = tags.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的 TokenManager
|
||||||
|
let token_manager = TokenManager::new(new_tokens);
|
||||||
|
let tokens_count = token_manager.tokens.len();
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
token_manager
|
||||||
|
.save_tokens()
|
||||||
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
// 重新加载 tokens
|
|
||||||
let token_infos = load_tokens();
|
|
||||||
let tokens_count = token_infos.len();
|
|
||||||
|
|
||||||
// 更新应用状态
|
// 更新应用状态
|
||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
state.token_infos = token_infos;
|
state.token_manager = token_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(TokenInfoResponse {
|
Ok(Json(TokenInfoResponse {
|
||||||
@@ -189,7 +124,7 @@ pub async fn handle_update_tokens(
|
|||||||
pub async fn handle_add_tokens(
|
pub async fn handle_add_tokens(
|
||||||
State(state): State<Arc<Mutex<AppState>>>,
|
State(state): State<Arc<Mutex<AppState>>>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Json(request): Json<Vec<TokenAddRequestTokenInfo>>,
|
Json(request): Json<TokenAddRequest>,
|
||||||
) -> Result<Json<TokenInfoResponse>, (StatusCode, Json<ErrorResponse>)> {
|
) -> Result<Json<TokenInfoResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||||
// 验证 AUTH_TOKEN
|
// 验证 AUTH_TOKEN
|
||||||
let auth_header = headers
|
let auth_header = headers
|
||||||
@@ -208,64 +143,65 @@ pub async fn handle_add_tokens(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_list_file = TOKEN_LIST_FILE.as_str();
|
// 获取当前的 token_manager
|
||||||
|
let mut token_manager = {
|
||||||
// 获取当前的 tokens 并创建新的 token_infos
|
|
||||||
let mut token_infos = {
|
|
||||||
let state = state.lock().await;
|
let state = state.lock().await;
|
||||||
state.token_infos.clone()
|
state.token_manager.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建现有token的集合
|
// 创建现有token的集合
|
||||||
let existing_tokens: std::collections::HashSet<_> =
|
let existing_tokens: std::collections::HashSet<_> = token_manager
|
||||||
token_infos.iter().map(|info| info.token.as_str()).collect();
|
.tokens
|
||||||
|
.iter()
|
||||||
// 预分配容量
|
.map(|info| info.token.as_str())
|
||||||
let mut new_tokens = Vec::with_capacity(request.len());
|
.collect();
|
||||||
|
|
||||||
// 处理新的tokens
|
// 处理新的tokens
|
||||||
for token_info in request {
|
let mut new_tokens = Vec::with_capacity(request.tokens.len());
|
||||||
|
for token_info in request.tokens {
|
||||||
let parsed_token = parse_token(&token_info.token);
|
let parsed_token = parse_token(&token_info.token);
|
||||||
if !existing_tokens.contains(parsed_token.as_str()) && validate_token(&parsed_token) {
|
if !existing_tokens.contains(parsed_token.as_str()) && validate_token(&parsed_token) {
|
||||||
new_tokens.push(TokenInfo {
|
new_tokens.push(TokenInfo {
|
||||||
token: parsed_token,
|
token: parsed_token,
|
||||||
// 如果提供了checksum就使用提供的,否则生成新的
|
|
||||||
checksum: token_info
|
checksum: token_info
|
||||||
.checksum
|
.checksum
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(generate_checksum_with_repair)
|
.map(generate_checksum_with_repair)
|
||||||
.unwrap_or_else(generate_checksum_with_default),
|
.unwrap_or_else(generate_checksum_with_default),
|
||||||
profile: None,
|
profile: None,
|
||||||
|
tags: request.tags.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有新tokens才进行后续操作
|
// 如果有新tokens才进行后续操作
|
||||||
if !new_tokens.is_empty() {
|
if !new_tokens.is_empty() {
|
||||||
// 预分配足够的容量
|
// 添加新tokens
|
||||||
token_infos.reserve(new_tokens.len());
|
token_manager.tokens.extend(new_tokens);
|
||||||
token_infos.extend(new_tokens);
|
let tokens_count = token_manager.tokens.len();
|
||||||
|
|
||||||
// 写入文件
|
// 更新全局标签
|
||||||
write_tokens(&token_infos, token_list_file).map_err(|_| {
|
if let Some(ref tags) = request.tags {
|
||||||
|
token_manager.update_global_tags(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
token_manager.save_tokens().await.map_err(|_| {
|
||||||
(
|
(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(ErrorResponse {
|
Json(ErrorResponse {
|
||||||
status: ApiStatus::Error,
|
status: ApiStatus::Error,
|
||||||
code: None,
|
code: None,
|
||||||
error: Some("Failed to update token list file".to_string()),
|
error: Some("Failed to save token list".to_string()),
|
||||||
message: Some("无法更新token list文件".to_string()),
|
message: Some("无法保存token list".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// 获取最终的tokens数量(在更新状态之前)
|
|
||||||
let tokens_count = token_infos.len();
|
|
||||||
|
|
||||||
// 更新应用状态
|
// 更新应用状态
|
||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
state.token_infos = token_infos;
|
state.token_manager = token_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(TokenInfoResponse {
|
Ok(Json(TokenInfoResponse {
|
||||||
@@ -275,12 +211,13 @@ pub async fn handle_add_tokens(
|
|||||||
message: Some("New tokens have been added and reloaded".to_string()),
|
message: Some("New tokens have been added and reloaded".to_string()),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
// 如果没有新tokens,使用原始数量
|
// 如果没有新tokens,返回当前状态
|
||||||
let tokens_count = token_infos.len();
|
let tokens = token_manager.tokens.clone();
|
||||||
|
let tokens_count = tokens.len();
|
||||||
|
|
||||||
Ok(Json(TokenInfoResponse {
|
Ok(Json(TokenInfoResponse {
|
||||||
status: ApiStatus::Success,
|
status: ApiStatus::Success,
|
||||||
tokens: None,
|
tokens: Some(tokens),
|
||||||
tokens_count,
|
tokens_count,
|
||||||
message: Some("No new tokens were added".to_string()),
|
message: Some("No new tokens were added".to_string()),
|
||||||
}))
|
}))
|
||||||
@@ -309,11 +246,11 @@ pub async fn handle_delete_tokens(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_infos = state.lock().await.token_infos.clone();
|
// 获取当前的 token_manager
|
||||||
let original_count = token_infos.len(); // 提前存储原始长度
|
let mut token_manager = {
|
||||||
|
let state = state.lock().await;
|
||||||
// 获取token_list文件路径
|
state.token_manager.clone()
|
||||||
let token_list_file = TOKEN_LIST_FILE.as_str();
|
};
|
||||||
|
|
||||||
// 创建要删除的tokens的HashSet,提高查找效率
|
// 创建要删除的tokens的HashSet,提高查找效率
|
||||||
let tokens_to_delete: std::collections::HashSet<_> = request.tokens.iter().collect();
|
let tokens_to_delete: std::collections::HashSet<_> = request.tokens.iter().collect();
|
||||||
@@ -324,7 +261,12 @@ pub async fn handle_delete_tokens(
|
|||||||
request
|
request
|
||||||
.tokens
|
.tokens
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|token| !token_infos.iter().any(|info| &info.token == *token))
|
.filter(|token| {
|
||||||
|
!token_manager
|
||||||
|
.tokens
|
||||||
|
.iter()
|
||||||
|
.any(|token_info| token_info.token == **token)
|
||||||
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<String>>(),
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
@@ -332,28 +274,26 @@ pub async fn handle_delete_tokens(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// 预分配容量并过滤掉要删除的tokens
|
let original_count: usize = token_manager.tokens.len();
|
||||||
let estimated_capacity = original_count.saturating_sub(tokens_to_delete.len());
|
|
||||||
let mut filtered_token_infos = Vec::with_capacity(estimated_capacity);
|
|
||||||
|
|
||||||
// 一次性过滤tokens
|
// 从每个分组中删除指定的tokens
|
||||||
for info in token_infos {
|
token_manager
|
||||||
if !tokens_to_delete.contains(&info.token) {
|
.tokens
|
||||||
filtered_token_infos.push(info);
|
.retain(|token_info| !tokens_to_delete.contains(&token_info.token));
|
||||||
}
|
|
||||||
}
|
let new_count: usize = token_manager.tokens.len();
|
||||||
|
|
||||||
// 如果有tokens被删除才进行更新操作
|
// 如果有tokens被删除才进行更新操作
|
||||||
if filtered_token_infos.len() < original_count {
|
if new_count < original_count {
|
||||||
// 写入文件
|
// 保存到文件
|
||||||
write_tokens(&filtered_token_infos, token_list_file).map_err(|_| {
|
token_manager.save_tokens().await.map_err(|_| {
|
||||||
(
|
(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(ErrorResponse {
|
Json(ErrorResponse {
|
||||||
status: ApiStatus::Error,
|
status: ApiStatus::Error,
|
||||||
code: None,
|
code: None,
|
||||||
error: Some("Failed to update token list file".to_string()),
|
error: Some("Failed to save token list".to_string()),
|
||||||
message: Some("无法更新token list文件".to_string()),
|
message: Some("无法保存token list".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
@@ -361,9 +301,10 @@ pub async fn handle_delete_tokens(
|
|||||||
// 如果需要的话计算 updated_tokens
|
// 如果需要的话计算 updated_tokens
|
||||||
let updated_tokens = if request.expectation.needs_updated_tokens() {
|
let updated_tokens = if request.expectation.needs_updated_tokens() {
|
||||||
Some(
|
Some(
|
||||||
filtered_token_infos
|
token_manager
|
||||||
|
.tokens
|
||||||
.iter()
|
.iter()
|
||||||
.map(|info| info.token.clone())
|
.map(|t| t.token.clone())
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -373,7 +314,7 @@ pub async fn handle_delete_tokens(
|
|||||||
// 更新状态
|
// 更新状态
|
||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
state.token_infos = filtered_token_infos;
|
state.token_manager = token_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(TokensDeleteResponse {
|
Ok(Json(TokensDeleteResponse {
|
||||||
@@ -387,9 +328,10 @@ pub async fn handle_delete_tokens(
|
|||||||
status: ApiStatus::Success,
|
status: ApiStatus::Success,
|
||||||
updated_tokens: if request.expectation.needs_updated_tokens() {
|
updated_tokens: if request.expectation.needs_updated_tokens() {
|
||||||
Some(
|
Some(
|
||||||
filtered_token_infos
|
token_manager
|
||||||
|
.tokens
|
||||||
.iter()
|
.iter()
|
||||||
.map(|info| info.token.clone())
|
.map(|t| t.token.clone())
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -400,82 +342,62 @@ pub async fn handle_delete_tokens(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_tokens_page() -> impl IntoResponse {
|
pub async fn handle_update_token_tags(
|
||||||
match AppConfig::get_page_content(ROUTE_TOKENS_PATH).unwrap_or_default() {
|
State(state): State<Arc<Mutex<AppState>>>,
|
||||||
PageContent::Default => Response::builder()
|
headers: HeaderMap,
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
Json(request): Json<TokenTagsUpdateRequest>,
|
||||||
.body(include_str!("../../../static/tokens.min.html").to_string())
|
) -> Result<Json<TokenTagsResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||||
.unwrap(),
|
// 验证 AUTH_TOKEN
|
||||||
PageContent::Text(content) => Response::builder()
|
let auth_header = headers
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
|
.get(AUTHORIZATION)
|
||||||
.body(content.clone())
|
.and_then(|h| h.to_str().ok())
|
||||||
.unwrap(),
|
.and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
|
||||||
PageContent::Html(content) => Response::builder()
|
.ok_or((
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
|
StatusCode::UNAUTHORIZED,
|
||||||
.body(content.clone())
|
Json(ChatError::Unauthorized.to_json()),
|
||||||
.unwrap(),
|
))?;
|
||||||
|
|
||||||
|
if auth_header != AUTH_TOKEN.as_str() {
|
||||||
|
return Err((
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
Json(ChatError::Unauthorized.to_json()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
// 获取并更新 token_manager
|
||||||
pub struct TokenRequest {
|
{
|
||||||
pub token: Option<String>,
|
let mut state = state.lock().await;
|
||||||
}
|
if let Err(e) = state
|
||||||
|
.token_manager
|
||||||
#[derive(Serialize)]
|
.update_tokens_tags(request.tokens, request.tags)
|
||||||
pub struct BasicCalibrationResponse {
|
{
|
||||||
pub status: ApiStatus,
|
return Err((
|
||||||
pub message: Option<String>,
|
StatusCode::BAD_REQUEST,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
Json(ErrorResponse {
|
||||||
pub user_id: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub create_at: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub checksum_time: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_basic_calibration(
|
|
||||||
Json(request): Json<TokenRequest>,
|
|
||||||
) -> Json<BasicCalibrationResponse> {
|
|
||||||
// 从请求头中获取并验证 auth token
|
|
||||||
let auth_token = match request.token {
|
|
||||||
Some(token) => token,
|
|
||||||
None => {
|
|
||||||
return Json(BasicCalibrationResponse {
|
|
||||||
status: ApiStatus::Error,
|
status: ApiStatus::Error,
|
||||||
message: Some("未提供授权令牌".to_string()),
|
code: None,
|
||||||
user_id: None,
|
error: Some(e.to_string()),
|
||||||
create_at: None,
|
message: Some("更新标签失败".to_string()),
|
||||||
checksum_time: None,
|
}),
|
||||||
})
|
));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// 校验 token 和 checksum
|
// 保存更改
|
||||||
let (token, checksum) = match validate_token_and_checksum(&auth_token) {
|
if (state.token_manager.save_tokens().await).is_err() {
|
||||||
Some(parts) => parts,
|
return Err((
|
||||||
None => {
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
return Json(BasicCalibrationResponse {
|
Json(ErrorResponse {
|
||||||
status: ApiStatus::Error,
|
status: ApiStatus::Error,
|
||||||
message: Some("无效令牌或无效校验和".to_string()),
|
code: None,
|
||||||
user_id: None,
|
error: Some("Failed to save token tags".to_string()),
|
||||||
create_at: None,
|
message: Some("无法保存标签信息".to_string()),
|
||||||
checksum_time: None,
|
}),
|
||||||
})
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// 提取用户ID和创建时间
|
Ok(Json(TokenTagsResponse {
|
||||||
let user_id = extract_user_id(&token);
|
|
||||||
let create_at = extract_time(&token).map(|dt| dt.to_string());
|
|
||||||
let checksum_time = extract_time_ks(&checksum[..8]);
|
|
||||||
|
|
||||||
// 返回校验结果
|
|
||||||
Json(BasicCalibrationResponse {
|
|
||||||
status: ApiStatus::Success,
|
status: ApiStatus::Success,
|
||||||
message: Some("校验成功".to_string()),
|
message: Some("标签更新成功".to_string()),
|
||||||
user_id,
|
}))
|
||||||
create_at,
|
|
||||||
checksum_time,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,10 @@ use crate::{
|
|||||||
AUTHORIZATION_BEARER_PREFIX, FINISH_REASON_STOP, OBJECT_CHAT_COMPLETION,
|
AUTHORIZATION_BEARER_PREFIX, FINISH_REASON_STOP, OBJECT_CHAT_COMPLETION,
|
||||||
OBJECT_CHAT_COMPLETION_CHUNK,
|
OBJECT_CHAT_COMPLETION_CHUNK,
|
||||||
},
|
},
|
||||||
lazy::{AUTH_TOKEN, KEY_PREFIX, KEY_PREFIX_LEN, REQUEST_LOGS_LIMIT, SERVICE_TIMEOUT},
|
lazy::{
|
||||||
|
AUTH_TOKEN, CURSOR_API2_CHAT_URL, CURSOR_API2_CHAT_WEB_URL, IS_UNLIMITED_REQUEST_LOGS,
|
||||||
|
KEY_PREFIX, KEY_PREFIX_LEN, REQUEST_LOGS_LIMIT, SERVICE_TIMEOUT,
|
||||||
|
},
|
||||||
model::{
|
model::{
|
||||||
AppConfig, AppState, ChatRequest, LogStatus, RequestLog, TimingInfo, TokenInfo,
|
AppConfig, AppState, ChatRequest, LogStatus, RequestLog, TimingInfo, TokenInfo,
|
||||||
UsageCheck,
|
UsageCheck,
|
||||||
@@ -12,7 +15,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
chat::{
|
chat::{
|
||||||
config::KeyConfig,
|
config::KeyConfig,
|
||||||
constant::{AVAILABLE_MODELS, USAGE_CHECK_MODELS},
|
constant::{Models, USAGE_CHECK_MODELS},
|
||||||
error::StreamError,
|
error::StreamError,
|
||||||
model::{
|
model::{
|
||||||
ChatResponse, Choice, Delta, Message, MessageContent, ModelsResponse, Role, Usage,
|
ChatResponse, Choice, Delta, Message, MessageContent, ModelsResponse, Role, Usage,
|
||||||
@@ -21,22 +24,22 @@ use crate::{
|
|||||||
},
|
},
|
||||||
common::{
|
common::{
|
||||||
client::build_client,
|
client::build_client,
|
||||||
model::{error::ChatError, userinfo::MembershipType, ApiStatus, ErrorResponse},
|
model::{ApiStatus, ErrorResponse, error::ChatError, userinfo::MembershipType},
|
||||||
utils::{
|
utils::{
|
||||||
format_time_ms, from_base64, get_token_profile, tokeninfo_to_token,
|
TrimNewlines as _, format_time_ms, from_base64, get_available_models,
|
||||||
validate_token_and_checksum, TrimNewlines as _,
|
get_token_profile, tokeninfo_to_token, validate_token_and_checksum,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
|
Json,
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::State,
|
extract::State,
|
||||||
http::{
|
http::{
|
||||||
header::{AUTHORIZATION, CONTENT_TYPE},
|
|
||||||
HeaderMap, StatusCode,
|
HeaderMap, StatusCode,
|
||||||
|
header::{AUTHORIZATION, CONTENT_TYPE},
|
||||||
},
|
},
|
||||||
response::Response,
|
response::Response,
|
||||||
Json,
|
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
@@ -44,17 +47,129 @@ use prost::Message as _;
|
|||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{Arc, atomic::AtomicBool},
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::{constant::LONG_CONTEXT_MODELS, model::Model};
|
||||||
|
|
||||||
|
// 辅助函数:提取认证token
|
||||||
|
fn extract_auth_token(headers: &HeaderMap) -> Result<&str, (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
headers
|
||||||
|
.get(AUTHORIZATION)
|
||||||
|
.and_then(|h| h.to_str().ok())
|
||||||
|
.and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
|
||||||
|
.ok_or((
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
Json(ChatError::Unauthorized.to_json()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:解析token信息
|
||||||
|
async fn resolve_token_info(
|
||||||
|
auth_header: &str,
|
||||||
|
state: &Arc<Mutex<AppState>>,
|
||||||
|
) -> Result<(String, String), (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
match auth_header {
|
||||||
|
// 管理员Token处理
|
||||||
|
token if is_admin_token(token) => resolve_admin_token(state).await,
|
||||||
|
|
||||||
|
// 动态密钥处理
|
||||||
|
token if is_dynamic_key(token) => resolve_dynamic_key(token),
|
||||||
|
|
||||||
|
// 普通用户Token处理
|
||||||
|
token => validate_token_and_checksum(token).ok_or((
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
Json(ChatError::Unauthorized.to_json()),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:检查是否为管理员token
|
||||||
|
fn is_admin_token(token: &str) -> bool {
|
||||||
|
token == AUTH_TOKEN.as_str()
|
||||||
|
|| (AppConfig::is_share() && token == AppConfig::get_share_token().as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:检查是否为动态密钥
|
||||||
|
fn is_dynamic_key(token: &str) -> bool {
|
||||||
|
AppConfig::get_dynamic_key() && token.starts_with(&*KEY_PREFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:处理管理员token
|
||||||
|
async fn resolve_admin_token(
|
||||||
|
state: &Arc<Mutex<AppState>>,
|
||||||
|
) -> Result<(String, String), (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
let state_guard = state.lock().await;
|
||||||
|
let token_infos = &state_guard.token_manager.tokens;
|
||||||
|
|
||||||
|
if token_infos.is_empty() {
|
||||||
|
return Err((
|
||||||
|
StatusCode::SERVICE_UNAVAILABLE,
|
||||||
|
Json(ChatError::NoTokens.to_json()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = CURRENT_KEY_INDEX.fetch_add(1, Ordering::SeqCst) % token_infos.len();
|
||||||
|
let token_info = &token_infos[index];
|
||||||
|
|
||||||
|
Ok((token_info.token.clone(), token_info.checksum.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:处理动态密钥
|
||||||
|
fn resolve_dynamic_key(token: &str) -> Result<(String, String), (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
from_base64(&token[*KEY_PREFIX_LEN..])
|
||||||
|
.and_then(|decoded_bytes| KeyConfig::decode(&decoded_bytes[..]).ok())
|
||||||
|
.and_then(|key_config| key_config.auth_token)
|
||||||
|
.and_then(|token_info| tokeninfo_to_token(&token_info))
|
||||||
|
.ok_or((
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
Json(ChatError::Unauthorized.to_json()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// 模型列表处理
|
// 模型列表处理
|
||||||
pub async fn handle_models() -> Json<ModelsResponse> {
|
pub async fn handle_models(
|
||||||
Json(ModelsResponse {
|
State(state): State<Arc<Mutex<AppState>>>,
|
||||||
object: "list",
|
headers: HeaderMap,
|
||||||
data: &AVAILABLE_MODELS,
|
) -> Result<Json<ModelsResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||||
})
|
// 如果没有认证头,返回默认可用模型
|
||||||
|
if headers.get(AUTHORIZATION).is_none() {
|
||||||
|
return Ok(Json(ModelsResponse::with_default_models()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取和验证认证token
|
||||||
|
let auth_token = extract_auth_token(&headers)?;
|
||||||
|
let (token, checksum) = resolve_token_info(auth_token, &state).await?;
|
||||||
|
|
||||||
|
// 获取可用模型列表
|
||||||
|
let models = get_available_models(&token, &checksum).await.ok_or((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(ErrorResponse {
|
||||||
|
status: ApiStatus::Failure,
|
||||||
|
code: Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16()),
|
||||||
|
error: Some("Failed to fetch available models".to_string()),
|
||||||
|
message: Some("Unable to get available models".to_string()),
|
||||||
|
}),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
// 更新模型列表
|
||||||
|
if let Err(e) = Models::update(models) {
|
||||||
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(ErrorResponse {
|
||||||
|
status: ApiStatus::Failure,
|
||||||
|
code: Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16()),
|
||||||
|
error: Some("Failed to update models".to_string()),
|
||||||
|
message: Some(e.to_string()),
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(ModelsResponse::new(Models::to_arc())))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 聊天处理函数的签名
|
// 聊天处理函数的签名
|
||||||
@@ -73,15 +188,15 @@ pub async fn handle_chat(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 验证模型是否支持并获取模型信息
|
// 验证模型是否支持并获取模型信息
|
||||||
let model = AVAILABLE_MODELS.iter().find(|m| m.id == model_name);
|
let model =
|
||||||
let model_supported = model.is_some();
|
if Models::exists(&model_name) || (allow_claude && request.model.starts_with("claude")) {
|
||||||
|
Some(&model_name)
|
||||||
if !(model_supported || allow_claude && request.model.starts_with("claude")) {
|
} else {
|
||||||
return Err((
|
return Err((
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(ChatError::ModelNotSupported(request.model).to_json()),
|
Json(ChatError::ModelNotSupported(request.model).to_json()),
|
||||||
));
|
));
|
||||||
}
|
};
|
||||||
|
|
||||||
let request_time = chrono::Local::now();
|
let request_time = chrono::Local::now();
|
||||||
|
|
||||||
@@ -114,7 +229,7 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
|
static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
|
||||||
let state_guard = state.lock().await;
|
let state_guard = state.lock().await;
|
||||||
let token_infos = &state_guard.token_infos;
|
let token_infos = &state_guard.token_manager.tokens;
|
||||||
|
|
||||||
// 检查是否存在可用的token
|
// 检查是否存在可用的token
|
||||||
if token_infos.is_empty() {
|
if token_infos.is_empty() {
|
||||||
@@ -159,56 +274,85 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
let state_clone = state.clone();
|
let state_clone = state.clone();
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
state.total_requests += 1;
|
state.request_manager.total_requests += 1;
|
||||||
state.active_requests += 1;
|
state.request_manager.active_requests += 1;
|
||||||
|
|
||||||
// 查找最新的相同token的日志,检查使用情况
|
let mut found_count: u32 = 0;
|
||||||
let need_profile_check = state
|
let mut no_prompt_count: u32 = 0;
|
||||||
.request_logs
|
let mut need_profile_check = false;
|
||||||
.iter()
|
|
||||||
.rev()
|
for log in state.request_manager.request_logs.iter().rev() {
|
||||||
.find(|log| log.token_info.token == auth_token && log.token_info.profile.is_some())
|
if log.token_info.token == auth_token {
|
||||||
.and_then(|log| log.token_info.profile.as_ref())
|
if !LONG_CONTEXT_MODELS.contains(&log.model.as_str()) {
|
||||||
.map(|profile| {
|
found_count += 1;
|
||||||
if profile.stripe.membership_type != MembershipType::Free {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if log.prompt.is_none() {
|
||||||
|
no_prompt_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if found_count == 1 && log.token_info.profile.is_some() {
|
||||||
|
if let Some(profile) = &log.token_info.profile {
|
||||||
|
if profile.stripe.membership_type == MembershipType::Free {
|
||||||
let is_premium = USAGE_CHECK_MODELS.contains(&model_name.as_str());
|
let is_premium = USAGE_CHECK_MODELS.contains(&model_name.as_str());
|
||||||
let standard = &profile.usage.standard;
|
need_profile_check =
|
||||||
let premium = &profile.usage.premium;
|
|
||||||
|
|
||||||
if is_premium {
|
if is_premium {
|
||||||
premium
|
profile.usage.premium.max_requests.is_some_and(|max| {
|
||||||
.max_requests
|
profile.usage.premium.num_requests >= max
|
||||||
.map_or(false, |max| premium.num_requests >= max)
|
|
||||||
} else {
|
|
||||||
standard
|
|
||||||
.max_requests
|
|
||||||
.map_or(false, |max| standard.num_requests >= max)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
} else {
|
||||||
|
profile.usage.standard.max_requests.is_some_and(|max| {
|
||||||
|
profile.usage.standard.num_requests >= max
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果达到限制,直接返回未授权错误
|
if found_count == 2 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found_count == 2 && no_prompt_count == 2 {
|
||||||
|
state.request_manager.active_requests -= 1;
|
||||||
|
state.request_manager.error_requests += 1;
|
||||||
|
return Err((
|
||||||
|
StatusCode::TOO_MANY_REQUESTS,
|
||||||
|
Json(ErrorResponse {
|
||||||
|
status: ApiStatus::Error,
|
||||||
|
code: Some(429),
|
||||||
|
error: Some("rate_limit_exceeded".to_string()),
|
||||||
|
message: Some("Too many requests without prompt".to_string()),
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理检查结果
|
||||||
if need_profile_check {
|
if need_profile_check {
|
||||||
state.active_requests -= 1;
|
state.request_manager.active_requests -= 1;
|
||||||
state.error_requests += 1;
|
state.request_manager.error_requests += 1;
|
||||||
return Err((
|
return Err((
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
Json(ChatError::Unauthorized.to_json()),
|
Json(ChatError::Unauthorized.to_json()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_id = state.request_logs.last().map_or(1, |log| log.id + 1);
|
let next_id = state
|
||||||
|
.request_manager
|
||||||
|
.request_logs
|
||||||
|
.last()
|
||||||
|
.map_or(1, |log| log.id + 1);
|
||||||
current_id = next_id;
|
current_id = next_id;
|
||||||
|
|
||||||
// 如果需要获取用户使用情况,创建后台任务获取profile
|
// 如果需要获取用户使用情况,创建后台任务获取profile
|
||||||
if model
|
if model
|
||||||
.map(|m| {
|
.map(|m| {
|
||||||
m.is_usage_check(UsageCheck::from_proto(
|
Model::is_usage_check(
|
||||||
current_config.usage_check_models.as_ref(),
|
m,
|
||||||
))
|
UsageCheck::from_proto(current_config.usage_check_models.as_ref()),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
@@ -222,30 +366,35 @@ pub async fn handle_chat(
|
|||||||
|
|
||||||
// 先找到所有需要更新的位置的索引
|
// 先找到所有需要更新的位置的索引
|
||||||
let token_info_idx = state
|
let token_info_idx = state
|
||||||
.token_infos
|
.token_manager
|
||||||
|
.tokens
|
||||||
.iter()
|
.iter()
|
||||||
.position(|info| info.token == auth_token_clone);
|
.position(|info| info.token == auth_token_clone);
|
||||||
|
|
||||||
let log_idx = state.request_logs.iter().rposition(|log| log.id == log_id);
|
let log_idx = state
|
||||||
|
.request_manager
|
||||||
|
.request_logs
|
||||||
|
.iter()
|
||||||
|
.rposition(|log| log.id == log_id);
|
||||||
|
|
||||||
// 根据索引更新
|
// 根据索引更新
|
||||||
match (token_info_idx, log_idx) {
|
match (token_info_idx, log_idx) {
|
||||||
(Some(t_idx), Some(l_idx)) => {
|
(Some(t_idx), Some(l_idx)) => {
|
||||||
state.token_infos[t_idx].profile = profile.clone();
|
state.token_manager.tokens[t_idx].profile = profile.clone();
|
||||||
state.request_logs[l_idx].token_info.profile = profile;
|
state.request_manager.request_logs[l_idx].token_info.profile = profile;
|
||||||
}
|
}
|
||||||
(Some(t_idx), None) => {
|
(Some(t_idx), None) => {
|
||||||
state.token_infos[t_idx].profile = profile;
|
state.token_manager.tokens[t_idx].profile = profile;
|
||||||
}
|
}
|
||||||
(None, Some(l_idx)) => {
|
(None, Some(l_idx)) => {
|
||||||
state.request_logs[l_idx].token_info.profile = profile;
|
state.request_manager.request_logs[l_idx].token_info.profile = profile;
|
||||||
}
|
}
|
||||||
(None, None) => {}
|
(None, None) => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
state.request_logs.push(RequestLog {
|
state.request_manager.request_logs.push(RequestLog {
|
||||||
id: next_id,
|
id: next_id,
|
||||||
timestamp: request_time,
|
timestamp: request_time,
|
||||||
model: request.model.clone(),
|
model: request.model.clone(),
|
||||||
@@ -253,6 +402,7 @@ pub async fn handle_chat(
|
|||||||
token: auth_token.clone(),
|
token: auth_token.clone(),
|
||||||
checksum: checksum.clone(),
|
checksum: checksum.clone(),
|
||||||
profile: None,
|
profile: None,
|
||||||
|
tags: None,
|
||||||
},
|
},
|
||||||
prompt: None,
|
prompt: None,
|
||||||
timing: TimingInfo {
|
timing: TimingInfo {
|
||||||
@@ -264,8 +414,10 @@ pub async fn handle_chat(
|
|||||||
error: None,
|
error: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
if state.request_logs.len() > *REQUEST_LOGS_LIMIT {
|
if !*IS_UNLIMITED_REQUEST_LOGS
|
||||||
state.request_logs.remove(0);
|
&& state.request_manager.request_logs.len() > *REQUEST_LOGS_LIMIT
|
||||||
|
{
|
||||||
|
state.request_manager.request_logs.remove(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +435,7 @@ pub async fn handle_chat(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -291,8 +444,8 @@ pub async fn handle_chat(
|
|||||||
log.status = LogStatus::Failed;
|
log.status = LogStatus::Failed;
|
||||||
log.error = Some(e.to_string());
|
log.error = Some(e.to_string());
|
||||||
}
|
}
|
||||||
state.active_requests -= 1;
|
state.request_manager.active_requests -= 1;
|
||||||
state.error_requests += 1;
|
state.request_manager.error_requests += 1;
|
||||||
return Err((
|
return Err((
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(
|
Json(
|
||||||
@@ -303,7 +456,16 @@ pub async fn handle_chat(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 构建请求客户端
|
// 构建请求客户端
|
||||||
let client = build_client(&auth_token, &checksum, is_search);
|
let client = build_client(
|
||||||
|
&auth_token,
|
||||||
|
&checksum,
|
||||||
|
if is_search {
|
||||||
|
&CURSOR_API2_CHAT_WEB_URL
|
||||||
|
} else {
|
||||||
|
&CURSOR_API2_CHAT_URL
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
// 添加超时设置
|
// 添加超时设置
|
||||||
let response = tokio::time::timeout(
|
let response = tokio::time::timeout(
|
||||||
std::time::Duration::from_secs(*SERVICE_TIMEOUT),
|
std::time::Duration::from_secs(*SERVICE_TIMEOUT),
|
||||||
@@ -319,6 +481,7 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -334,6 +497,7 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -342,8 +506,8 @@ pub async fn handle_chat(
|
|||||||
log.status = LogStatus::Failed;
|
log.status = LogStatus::Failed;
|
||||||
log.error = Some(e.to_string());
|
log.error = Some(e.to_string());
|
||||||
}
|
}
|
||||||
state.active_requests -= 1;
|
state.request_manager.active_requests -= 1;
|
||||||
state.error_requests += 1;
|
state.request_manager.error_requests += 1;
|
||||||
}
|
}
|
||||||
return Err((
|
return Err((
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
@@ -356,6 +520,7 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -364,8 +529,8 @@ pub async fn handle_chat(
|
|||||||
log.status = LogStatus::Failed;
|
log.status = LogStatus::Failed;
|
||||||
log.error = Some("Request timeout".to_string());
|
log.error = Some("Request timeout".to_string());
|
||||||
}
|
}
|
||||||
state.active_requests -= 1;
|
state.request_manager.active_requests -= 1;
|
||||||
state.error_requests += 1;
|
state.request_manager.error_requests += 1;
|
||||||
}
|
}
|
||||||
return Err((
|
return Err((
|
||||||
StatusCode::GATEWAY_TIMEOUT,
|
StatusCode::GATEWAY_TIMEOUT,
|
||||||
@@ -377,7 +542,7 @@ pub async fn handle_chat(
|
|||||||
// 释放活动请求计数
|
// 释放活动请求计数
|
||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
state.active_requests -= 1;
|
state.request_manager.active_requests -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let convert_web_ref = current_config.include_web_references();
|
let convert_web_ref = current_config.include_web_references();
|
||||||
@@ -460,6 +625,7 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
let mut state = ctx.state.lock().await;
|
let mut state = ctx.state.lock().await;
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -494,6 +660,7 @@ pub async fn handle_chat(
|
|||||||
StreamMessage::Debug(debug_prompt) => {
|
StreamMessage::Debug(debug_prompt) => {
|
||||||
if let Ok(mut state) = ctx.state.try_lock() {
|
if let Ok(mut state) = ctx.state.try_lock() {
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -518,11 +685,12 @@ pub async fn handle_chat(
|
|||||||
if let Err(StreamError::ChatError(error)) =
|
if let Err(StreamError::ChatError(error)) =
|
||||||
decoder.lock().await.decode(&chunk, convert_web_ref)
|
decoder.lock().await.decode(&chunk, convert_web_ref)
|
||||||
{
|
{
|
||||||
let error_response = error.to_error_response();
|
let error_response = error.into_error_response();
|
||||||
// 更新请求日志为失败
|
// 更新请求日志为失败
|
||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -532,12 +700,12 @@ pub async fn handle_chat(
|
|||||||
log.error = Some(error_response.native_code());
|
log.error = Some(error_response.native_code());
|
||||||
log.timing.total =
|
log.timing.total =
|
||||||
format_time_ms(start_time.elapsed().as_secs_f64());
|
format_time_ms(start_time.elapsed().as_secs_f64());
|
||||||
state.error_requests += 1;
|
state.request_manager.error_requests += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Err((
|
return Err((
|
||||||
error_response.status_code(),
|
error_response.status_code(),
|
||||||
Json(error_response.to_common()),
|
Json(error_response.into_common()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,6 +721,7 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -560,7 +729,7 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
log.status = LogStatus::Failed;
|
log.status = LogStatus::Failed;
|
||||||
log.error = Some("Empty stream response".to_string());
|
log.error = Some("Empty stream response".to_string());
|
||||||
state.error_requests += 1;
|
state.request_manager.error_requests += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Err((
|
return Err((
|
||||||
@@ -667,6 +836,7 @@ pub async fn handle_chat(
|
|||||||
StreamMessage::Debug(debug_prompt) => {
|
StreamMessage::Debug(debug_prompt) => {
|
||||||
if let Ok(mut state) = state.try_lock() {
|
if let Ok(mut state) = state.try_lock() {
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -681,10 +851,10 @@ pub async fn handle_chat(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(StreamError::ChatError(error)) => {
|
Err(StreamError::ChatError(error)) => {
|
||||||
let error_response = error.to_error_response();
|
let error_response = error.into_error_response();
|
||||||
return Err((
|
return Err((
|
||||||
error_response.status_code(),
|
error_response.status_code(),
|
||||||
Json(error_response.to_common()),
|
Json(error_response.into_common()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -705,6 +875,7 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -712,7 +883,7 @@ pub async fn handle_chat(
|
|||||||
{
|
{
|
||||||
log.status = LogStatus::Failed;
|
log.status = LogStatus::Failed;
|
||||||
log.error = Some("Empty response received".to_string());
|
log.error = Some("Empty response received".to_string());
|
||||||
state.error_requests += 1;
|
state.request_manager.error_requests += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Err((
|
return Err((
|
||||||
@@ -747,6 +918,7 @@ pub async fn handle_chat(
|
|||||||
let total_time = format_time_ms(start_time.elapsed().as_secs_f64());
|
let total_time = format_time_ms(start_time.elapsed().as_secs_f64());
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
if let Some(log) = state
|
if let Some(log) = state
|
||||||
|
.request_manager
|
||||||
.request_logs
|
.request_logs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
use crate::chat::{
|
use crate::chat::{
|
||||||
aiserver::v1::StreamChatResponse,
|
aiserver::v1::{StreamChatResponse, WebReference},
|
||||||
error::{ChatError, StreamError},
|
error::{ChatError, StreamError},
|
||||||
};
|
};
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use std::{collections::BTreeMap, io::Read};
|
use std::io::Read;
|
||||||
|
|
||||||
// 解压gzip数据
|
// 解压gzip数据
|
||||||
fn decompress_gzip(data: &[u8]) -> Option<Vec<u8>> {
|
fn decompress_gzip(data: &[u8]) -> Option<Vec<u8>> {
|
||||||
@@ -24,17 +24,23 @@ pub trait ToMarkdown {
|
|||||||
fn to_markdown(&self) -> String;
|
fn to_markdown(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToMarkdown for BTreeMap<String, String> {
|
impl ToMarkdown for Vec<WebReference> {
|
||||||
fn to_markdown(&self) -> String {
|
fn to_markdown(&self) -> String {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = String::from("WebReferences:\n");
|
let mut result = String::from("WebReferences:\n");
|
||||||
for (i, (url, title)) in self.iter().enumerate() {
|
for (i, web_ref) in self.iter().enumerate() {
|
||||||
result.push_str(&format!("{}. [{}]({})\n", i + 1, title, url));
|
result.push_str(&format!(
|
||||||
|
"{}. [{}]({})<{}>\n",
|
||||||
|
i + 1,
|
||||||
|
web_ref.title,
|
||||||
|
web_ref.url,
|
||||||
|
web_ref.chunk
|
||||||
|
));
|
||||||
}
|
}
|
||||||
result.push_str("\n");
|
result.push('\n');
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,7 +50,7 @@ pub enum StreamMessage {
|
|||||||
// 调试
|
// 调试
|
||||||
Debug(String),
|
Debug(String),
|
||||||
// 网络引用
|
// 网络引用
|
||||||
WebReference(BTreeMap<String, String>),
|
WebReference(Vec<WebReference>),
|
||||||
// 内容开始标志
|
// 内容开始标志
|
||||||
ContentStart,
|
ContentStart,
|
||||||
// 消息内容
|
// 消息内容
|
||||||
@@ -98,11 +104,15 @@ impl StreamDecoder {
|
|||||||
self.first_result_ready
|
self.first_result_ready
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode(&mut self, data: &[u8], convert_web_ref: bool) -> Result<Vec<StreamMessage>, StreamError> {
|
pub fn decode(
|
||||||
|
&mut self,
|
||||||
|
data: &[u8],
|
||||||
|
convert_web_ref: bool,
|
||||||
|
) -> Result<Vec<StreamMessage>, StreamError> {
|
||||||
self.buffer.extend_from_slice(data);
|
self.buffer.extend_from_slice(data);
|
||||||
|
|
||||||
if self.buffer.len() < 5 {
|
if self.buffer.len() < 5 {
|
||||||
if self.buffer.len() == 0 {
|
if self.buffer.is_empty() {
|
||||||
return Err(StreamError::EmptyStream);
|
return Err(StreamError::EmptyStream);
|
||||||
}
|
}
|
||||||
crate::debug_println!("数据长度小于5字节,当前数据: {}", hex::encode(&self.buffer));
|
crate::debug_println!("数据长度小于5字节,当前数据: {}", hex::encode(&self.buffer));
|
||||||
@@ -133,16 +143,13 @@ impl StreamDecoder {
|
|||||||
|
|
||||||
let msg_data = &self.buffer[offset + 5..offset + 5 + msg_len];
|
let msg_data = &self.buffer[offset + 5..offset + 5 + msg_len];
|
||||||
|
|
||||||
match self.process_message(msg_type, msg_data)? {
|
if let Some(msg) = self.process_message(msg_type, msg_data)? {
|
||||||
Some(msg) => {
|
|
||||||
if convert_web_ref {
|
if convert_web_ref {
|
||||||
messages.push(msg.convert_web_ref_to_content());
|
messages.push(msg.convert_web_ref_to_content());
|
||||||
} else {
|
} else {
|
||||||
messages.push(msg);
|
messages.push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += 5 + msg_len;
|
offset += 5 + msg_len;
|
||||||
}
|
}
|
||||||
@@ -157,7 +164,8 @@ impl StreamDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !self.first_result_ready {
|
if !self.first_result_ready {
|
||||||
self.first_result_ready = self.first_result.is_some() && self.buffer.is_empty() && !self.first_result_taken;
|
self.first_result_ready =
|
||||||
|
self.first_result.is_some() && self.buffer.is_empty() && !self.first_result_taken;
|
||||||
}
|
}
|
||||||
Ok(messages)
|
Ok(messages)
|
||||||
}
|
}
|
||||||
@@ -182,17 +190,13 @@ impl StreamDecoder {
|
|||||||
|
|
||||||
fn handle_text_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
|
fn handle_text_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
|
||||||
if let Ok(response) = StreamChatResponse::decode(msg_data) {
|
if let Ok(response) = StreamChatResponse::decode(msg_data) {
|
||||||
// crate::debug_println!("[text] StreamChatResponse [hex: {}]: {:?}", hex::encode(msg_data), response);
|
// println!("[text] StreamChatResponse [hex: {}]: {:?}", hex::encode(msg_data), response);
|
||||||
if !response.text.is_empty() {
|
if !response.text.is_empty() {
|
||||||
Ok(Some(StreamMessage::Content(response.text)))
|
Ok(Some(StreamMessage::Content(response.text)))
|
||||||
} else if let Some(filled_prompt) = response.filled_prompt {
|
} else if let Some(filled_prompt) = response.filled_prompt {
|
||||||
Ok(Some(StreamMessage::Debug(filled_prompt)))
|
Ok(Some(StreamMessage::Debug(filled_prompt)))
|
||||||
} else if let Some(web_citation) = response.web_citation {
|
} else if let Some(web_citation) = response.web_citation {
|
||||||
let mut refs = BTreeMap::new();
|
Ok(Some(StreamMessage::WebReference(web_citation.references)))
|
||||||
for reference in web_citation.references {
|
|
||||||
refs.insert(reference.url, reference.title);
|
|
||||||
}
|
|
||||||
Ok(Some(StreamMessage::WebReference(refs)))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -204,17 +208,13 @@ impl StreamDecoder {
|
|||||||
fn handle_gzip_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
|
fn handle_gzip_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
|
||||||
if let Some(text) = decompress_gzip(msg_data) {
|
if let Some(text) = decompress_gzip(msg_data) {
|
||||||
if let Ok(response) = StreamChatResponse::decode(&text[..]) {
|
if let Ok(response) = StreamChatResponse::decode(&text[..]) {
|
||||||
// crate::debug_println!("[gzip] StreamChatResponse [hex: {}]: {:?}", hex::encode(msg_data), response);
|
// println!("[gzip] StreamChatResponse [hex: {}]: {:?}", hex::encode(msg_data), response);
|
||||||
if !response.text.is_empty() {
|
if !response.text.is_empty() {
|
||||||
Ok(Some(StreamMessage::Content(response.text)))
|
Ok(Some(StreamMessage::Content(response.text)))
|
||||||
} else if let Some(filled_prompt) = response.filled_prompt {
|
} else if let Some(filled_prompt) = response.filled_prompt {
|
||||||
Ok(Some(StreamMessage::Debug(filled_prompt)))
|
Ok(Some(StreamMessage::Debug(filled_prompt)))
|
||||||
} else if let Some(web_citation) = response.web_citation {
|
} else if let Some(web_citation) = response.web_citation {
|
||||||
let mut refs = BTreeMap::new();
|
Ok(Some(StreamMessage::WebReference(web_citation.references)))
|
||||||
for reference in web_citation.references {
|
|
||||||
refs.insert(reference.url, reference.title);
|
|
||||||
}
|
|
||||||
Ok(Some(StreamMessage::WebReference(refs)))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ impl StreamDecoder {
|
|||||||
return Ok(Some(StreamMessage::StreamEnd));
|
return Ok(Some(StreamMessage::StreamEnd));
|
||||||
}
|
}
|
||||||
if let Ok(text) = String::from_utf8(msg_data.to_vec()) {
|
if let Ok(text) = String::from_utf8(msg_data.to_vec()) {
|
||||||
// println!("JSON消息: {}", text);
|
// println!("[text] JSON消息 [hex: {}]: {}", hex::encode(msg_data), text);
|
||||||
if let Ok(error) = serde_json::from_str::<ChatError>(&text) {
|
if let Ok(error) = serde_json::from_str::<ChatError>(&text) {
|
||||||
return Err(StreamError::ChatError(error));
|
return Err(StreamError::ChatError(error));
|
||||||
}
|
}
|
||||||
@@ -248,7 +248,7 @@ impl StreamDecoder {
|
|||||||
return Ok(Some(StreamMessage::StreamEnd));
|
return Ok(Some(StreamMessage::StreamEnd));
|
||||||
}
|
}
|
||||||
if let Ok(text) = String::from_utf8(text) {
|
if let Ok(text) = String::from_utf8(text) {
|
||||||
// println!("JSON消息: {}", text);
|
// println!("[gzip] JSON消息 [hex: {}]: {}", hex::encode(msg_data), text);
|
||||||
if let Ok(error) = serde_json::from_str::<ChatError>(&text) {
|
if let Ok(error) = serde_json::from_str::<ChatError>(&text) {
|
||||||
return Err(StreamError::ChatError(error));
|
return Err(StreamError::ChatError(error));
|
||||||
}
|
}
|
||||||
@@ -293,8 +293,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
StreamMessage::WebReference(refs) => {
|
StreamMessage::WebReference(refs) => {
|
||||||
println!("网页引用:");
|
println!("网页引用:");
|
||||||
for (i, (url, title)) in refs.iter().enumerate() {
|
for (i, web_ref) in refs.iter().enumerate() {
|
||||||
println!("{}. {} - {}", i, url, title);
|
println!(
|
||||||
|
"{}. {} - {} - {}",
|
||||||
|
i, web_ref.url, web_ref.title, web_ref.chunk
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StreamMessage::Debug(prompt) => {
|
StreamMessage::Debug(prompt) => {
|
||||||
@@ -375,8 +378,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
StreamMessage::WebReference(refs) => {
|
StreamMessage::WebReference(refs) => {
|
||||||
println!("网页引用 [hex: {}]:", hex_str);
|
println!("网页引用 [hex: {}]:", hex_str);
|
||||||
for (i, (url, title)) in refs.iter().enumerate() {
|
for (i, web_ref) in refs.iter().enumerate() {
|
||||||
println!("{}. {} - {}", i, url, title);
|
println!(
|
||||||
|
"{}. {} - {} - {}",
|
||||||
|
i, web_ref.url, web_ref.title, web_ref.chunk
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StreamMessage::Debug(prompt) => {
|
StreamMessage::Debug(prompt) => {
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
|
pub mod client;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod client;
|
|
||||||
|
@@ -1,17 +1,21 @@
|
|||||||
use super::utils::generate_hash;
|
use super::utils::generate_hash;
|
||||||
use crate::{app::{
|
use crate::{
|
||||||
|
AppConfig,
|
||||||
|
app::{
|
||||||
constant::{
|
constant::{
|
||||||
CONTENT_TYPE_CONNECT_PROTO, CURSOR_API2_HOST, CURSOR_HOST, CURSOR_SETTINGS_URL,
|
CONTENT_TYPE_CONNECT_PROTO, CONTENT_TYPE_PROTO, CURSOR_API2_HOST, CURSOR_HOST,
|
||||||
HEADER_NAME_GHOST_MODE, TRUE,
|
CURSOR_SETTINGS_URL, HEADER_NAME_GHOST_MODE, TRUE,
|
||||||
},
|
},
|
||||||
lazy::{
|
lazy::{
|
||||||
CURSOR_API2_CHAT_URL, CURSOR_API2_CHAT_WEB_URL, CURSOR_API2_STRIPE_URL, CURSOR_USAGE_API_URL, CURSOR_USER_API_URL, REVERSE_PROXY_HOST, USE_REVERSE_PROXY
|
CURSOR_API2_STRIPE_URL, CURSOR_USAGE_API_URL, CURSOR_USER_API_URL, REVERSE_PROXY_HOST,
|
||||||
|
USE_REVERSE_PROXY,
|
||||||
},
|
},
|
||||||
}, AppConfig};
|
},
|
||||||
|
};
|
||||||
use reqwest::header::{
|
use reqwest::header::{
|
||||||
ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CACHE_CONTROL, CONNECTION, CONTENT_TYPE, COOKIE,
|
ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CACHE_CONTROL, CONNECTION, CONTENT_TYPE, COOKIE, DNT,
|
||||||
DNT, HOST, ORIGIN, PRAGMA, REFERER, TE, TRANSFER_ENCODING, USER_AGENT,
|
HOST, ORIGIN, PRAGMA, REFERER, TE, TRANSFER_ENCODING, USER_AGENT,
|
||||||
};
|
};
|
||||||
use reqwest::{Client, RequestBuilder};
|
use reqwest::{Client, RequestBuilder};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@@ -35,7 +39,10 @@ def_const!(VALUE_LANGUAGE, "zh-CN");
|
|||||||
def_const!(EMPTY, "empty");
|
def_const!(EMPTY, "empty");
|
||||||
def_const!(CORS, "cors");
|
def_const!(CORS, "cors");
|
||||||
def_const!(NO_CACHE, "no-cache");
|
def_const!(NO_CACHE, "no-cache");
|
||||||
def_const!(UA_WIN, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36");
|
def_const!(
|
||||||
|
UA_WIN,
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||||
|
);
|
||||||
def_const!(SAME_ORIGIN, "same-origin");
|
def_const!(SAME_ORIGIN, "same-origin");
|
||||||
def_const!(KEEP_ALIVE, "keep-alive");
|
def_const!(KEEP_ALIVE, "keep-alive");
|
||||||
def_const!(TRAILERS, "trailers");
|
def_const!(TRAILERS, "trailers");
|
||||||
@@ -66,13 +73,13 @@ pub fn rebuild_http_client() {
|
|||||||
/// # 返回
|
/// # 返回
|
||||||
///
|
///
|
||||||
/// * `reqwest::RequestBuilder` - 配置好的请求构建器
|
/// * `reqwest::RequestBuilder` - 配置好的请求构建器
|
||||||
pub fn build_client(auth_token: &str, checksum: &str, is_search: bool) -> RequestBuilder {
|
pub fn build_client(
|
||||||
|
auth_token: &str,
|
||||||
|
checksum: &str,
|
||||||
|
url: &str,
|
||||||
|
is_stream: bool,
|
||||||
|
) -> RequestBuilder {
|
||||||
let trace_id = Uuid::new_v4().to_string();
|
let trace_id = Uuid::new_v4().to_string();
|
||||||
let url = if is_search {
|
|
||||||
&*CURSOR_API2_CHAT_WEB_URL
|
|
||||||
} else {
|
|
||||||
&*CURSOR_API2_CHAT_URL
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = if *USE_REVERSE_PROXY {
|
let client = if *USE_REVERSE_PROXY {
|
||||||
HTTP_CLIENT
|
HTTP_CLIENT
|
||||||
@@ -81,14 +88,18 @@ pub fn build_client(auth_token: &str, checksum: &str, is_search: bool) -> Reques
|
|||||||
.header(HOST, &*REVERSE_PROXY_HOST)
|
.header(HOST, &*REVERSE_PROXY_HOST)
|
||||||
.header(PROXY_HOST, CURSOR_API2_HOST)
|
.header(PROXY_HOST, CURSOR_API2_HOST)
|
||||||
} else {
|
} else {
|
||||||
HTTP_CLIENT
|
HTTP_CLIENT.read().post(url).header(HOST, CURSOR_API2_HOST)
|
||||||
.read()
|
|
||||||
.post(url)
|
|
||||||
.header(HOST, CURSOR_API2_HOST)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
client
|
client
|
||||||
.header(CONTENT_TYPE, CONTENT_TYPE_CONNECT_PROTO)
|
.header(
|
||||||
|
CONTENT_TYPE,
|
||||||
|
if is_stream {
|
||||||
|
CONTENT_TYPE_CONNECT_PROTO
|
||||||
|
} else {
|
||||||
|
CONTENT_TYPE_PROTO
|
||||||
|
},
|
||||||
|
)
|
||||||
.bearer_auth(auth_token)
|
.bearer_auth(auth_token)
|
||||||
.header("connect-accept-encoding", ENCODINGS)
|
.header("connect-accept-encoding", ENCODINGS)
|
||||||
.header("connect-protocol-version", ONE)
|
.header("connect-protocol-version", ONE)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
|
pub mod config;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod health;
|
pub mod health;
|
||||||
pub mod config;
|
|
||||||
pub mod token;
|
pub mod token;
|
||||||
pub mod userinfo;
|
pub mod userinfo;
|
||||||
|
|
||||||
@@ -16,8 +16,8 @@ pub enum ApiStatus {
|
|||||||
Success,
|
Success,
|
||||||
#[serde(rename = "error")]
|
#[serde(rename = "error")]
|
||||||
Error,
|
Error,
|
||||||
#[serde(rename = "failed")]
|
#[serde(rename = "failure")]
|
||||||
Failed,
|
Failure,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Serialize)]
|
// #[derive(Serialize)]
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::app::model::{PageContent, UsageCheck, VisionAbility, Proxies};
|
use crate::app::model::{PageContent, Proxies, UsageCheck, VisionAbility};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ConfigData {
|
pub struct ConfigData {
|
||||||
|
@@ -9,7 +9,7 @@ pub struct HealthCheckResponse {
|
|||||||
pub uptime: i64,
|
pub uptime: i64,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub stats: Option<SystemStats>,
|
pub stats: Option<SystemStats>,
|
||||||
pub models: Vec<&'static str>,
|
pub models: Vec<String>,
|
||||||
pub endpoints: Vec<&'static str>,
|
pub endpoints: Vec<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum GetUserInfo {
|
pub enum GetUserInfo {
|
||||||
Usage(TokenProfile),
|
Usage(Box<TokenProfile>),
|
||||||
Error { error: String },
|
Error { error: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ pub struct ModelUsage {
|
|||||||
default,
|
default,
|
||||||
skip_serializing_if = "Option::is_none"
|
skip_serializing_if = "Option::is_none"
|
||||||
)]
|
)]
|
||||||
pub requests_total: Option<u32>,
|
pub total_requests: Option<u32>,
|
||||||
#[serde(rename(deserialize = "numTokens", serialize = "tokens"))]
|
#[serde(rename(deserialize = "numTokens", serialize = "tokens"))]
|
||||||
pub num_tokens: u32,
|
pub num_tokens: u32,
|
||||||
#[serde(
|
#[serde(
|
||||||
@@ -74,6 +74,8 @@ pub struct UsageProfile {
|
|||||||
pub standard: ModelUsage,
|
pub standard: ModelUsage,
|
||||||
#[serde(rename(deserialize = "gpt-4-32k"))]
|
#[serde(rename(deserialize = "gpt-4-32k"))]
|
||||||
pub unknown: ModelUsage,
|
pub unknown: ModelUsage,
|
||||||
|
#[serde(rename(deserialize = "startOfMonth"))]
|
||||||
|
pub start_of_month: DateTime<Local>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
#[derive(Deserialize, Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||||
|
@@ -1,15 +1,28 @@
|
|||||||
mod checksum;
|
mod checksum;
|
||||||
use ::base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
use ::base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||||
pub use checksum::*;
|
pub use checksum::*;
|
||||||
mod token;
|
mod token;
|
||||||
|
use prost::Message as _;
|
||||||
pub use token::*;
|
pub use token::*;
|
||||||
mod base64;
|
mod base64;
|
||||||
pub use base64::*;
|
pub use base64::*;
|
||||||
|
|
||||||
use super::model::{token::TokenPayload, userinfo::{StripeProfile, TokenProfile, UsageProfile, UserProfile}};
|
use super::model::{
|
||||||
use crate::app::{
|
token::TokenPayload,
|
||||||
|
userinfo::{StripeProfile, TokenProfile, UsageProfile, UserProfile},
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
app::{
|
||||||
constant::{COMMA, FALSE, TRUE},
|
constant::{COMMA, FALSE, TRUE},
|
||||||
lazy::{TOKEN_DELIMITER, USE_COMMA_DELIMITER},
|
lazy::{CURSOR_API2_CHAT_MODELS_URL, TOKEN_DELIMITER, USE_COMMA_DELIMITER},
|
||||||
|
},
|
||||||
|
chat::{
|
||||||
|
aiserver::v1::{AvailableModelsRequest, AvailableModelsResponse},
|
||||||
|
constant::{
|
||||||
|
ANTHROPIC, CREATED, CURSOR, DEEPSEEK, GOOGLE, MODEL_OBJECT, OPENAI, UNKNOWN, XAI,
|
||||||
|
},
|
||||||
|
model::Model,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse_bool_from_env(key: &str, default: bool) -> bool {
|
pub fn parse_bool_from_env(key: &str, default: bool) -> bool {
|
||||||
@@ -124,6 +137,59 @@ pub async fn get_user_profile(auth_token: &str) -> Option<UserProfile> {
|
|||||||
Some(user_profile)
|
Some(user_profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_available_models(auth_token: &str, checksum: &str) -> Option<Vec<Model>> {
|
||||||
|
let client =
|
||||||
|
super::client::build_client(auth_token, checksum, &CURSOR_API2_CHAT_MODELS_URL, false);
|
||||||
|
let request = AvailableModelsRequest {
|
||||||
|
is_nightly: true,
|
||||||
|
include_long_context_models: true,
|
||||||
|
};
|
||||||
|
let response = client
|
||||||
|
.body(encode_message(&request, false).unwrap())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.ok()?
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
let available_models = AvailableModelsResponse::decode(response.as_ref()).ok()?;
|
||||||
|
Some(
|
||||||
|
available_models
|
||||||
|
.models
|
||||||
|
.into_iter()
|
||||||
|
.map(|model| Model {
|
||||||
|
id: model.name.clone(),
|
||||||
|
created: CREATED,
|
||||||
|
object: MODEL_OBJECT,
|
||||||
|
owned_by: {
|
||||||
|
let mut chars = model.name.chars();
|
||||||
|
match chars.next() {
|
||||||
|
Some('g') => match chars.next() {
|
||||||
|
Some('p') => OPENAI, // g + p → "gp" (gpt)
|
||||||
|
Some('e') => GOOGLE, // g + e → "ge" (gemini)
|
||||||
|
Some('r') => XAI, // g + r → "ge" (grok)
|
||||||
|
_ => UNKNOWN,
|
||||||
|
},
|
||||||
|
Some('o') => match chars.next() {
|
||||||
|
// o 开头需要二次判断
|
||||||
|
Some('1') | Some('3') => OPENAI, // o1/o3 系列
|
||||||
|
_ => UNKNOWN,
|
||||||
|
},
|
||||||
|
Some('c') => match chars.next() {
|
||||||
|
Some('l') => ANTHROPIC, // c + l → "cl" (claude)
|
||||||
|
Some('u') => CURSOR, // c + u → "cu" (cursor)
|
||||||
|
_ => UNKNOWN,
|
||||||
|
},
|
||||||
|
Some('d') if chars.next() == Some('e') => DEEPSEEK, // d + e → "de" (deepseek)
|
||||||
|
// 其他情况
|
||||||
|
_ => UNKNOWN,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn validate_token_and_checksum(auth_token: &str) -> Option<(String, String)> {
|
pub fn validate_token_and_checksum(auth_token: &str) -> Option<(String, String)> {
|
||||||
// 尝试使用自定义分隔符查找
|
// 尝试使用自定义分隔符查找
|
||||||
let mut delimiter_pos = auth_token.rfind(*TOKEN_DELIMITER);
|
let mut delimiter_pos = auth_token.rfind(*TOKEN_DELIMITER);
|
||||||
@@ -277,5 +343,33 @@ pub fn tokeninfo_to_token(info: &key_config::TokenInfo) -> Option<(String, Strin
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 组合 token
|
// 组合 token
|
||||||
Some((format!("{}.{}.{}", HEADER_B64, payload_b64, info.signature), generate_checksum(&device_id, mac_addr.as_deref())))
|
Some((
|
||||||
|
format!("{}.{}.{}", HEADER_B64, payload_b64, info.signature),
|
||||||
|
generate_checksum(&device_id, mac_addr.as_deref()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_message(
|
||||||
|
message: &impl prost::Message,
|
||||||
|
with_gzip: bool,
|
||||||
|
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let mut encoded = Vec::new();
|
||||||
|
message.encode(&mut encoded)?;
|
||||||
|
|
||||||
|
if !with_gzip {
|
||||||
|
return Ok(encoded);
|
||||||
|
}
|
||||||
|
// 构造 5 字节头部 [0x00, len_be_bytes...]
|
||||||
|
let mut header = Vec::with_capacity(5);
|
||||||
|
header.push(0x00); // 压缩标记位
|
||||||
|
|
||||||
|
// 将长度转换为 u32 大端字节(显式长度检查)
|
||||||
|
let len = u32::try_from(encoded.len()).map_err(|_| "Message length exceeds u32::MAX")?; // 明确错误类型
|
||||||
|
header.extend_from_slice(&len.to_be_bytes());
|
||||||
|
|
||||||
|
// 组合最终数据
|
||||||
|
let mut result = header;
|
||||||
|
result.extend(encoded);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
|
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||||||
use rand::Rng;
|
use rand::Rng as _;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
pub fn generate_hash() -> String {
|
pub fn generate_hash() -> String {
|
||||||
let random_bytes = rand::thread_rng().gen::<[u8; 32]>();
|
let random_bytes = rand::rng().random::<[u8; 32]>();
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(random_bytes);
|
hasher.update(random_bytes);
|
||||||
hex::encode(hasher.finalize())
|
hex::encode(hasher.finalize())
|
||||||
|
@@ -1,24 +1,9 @@
|
|||||||
use super::generate_checksum_with_repair;
|
use super::generate_checksum_with_repair;
|
||||||
use crate::app::{
|
use crate::app::{constant::COMMA, model::TokenInfo};
|
||||||
constant::{COMMA, EMPTY_STRING},
|
|
||||||
lazy::TOKEN_LIST_FILE,
|
|
||||||
model::TokenInfo,
|
|
||||||
};
|
|
||||||
use crate::common::model::token::TokenPayload;
|
use crate::common::model::token::TokenPayload;
|
||||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||||
use chrono::{DateTime, Local, TimeZone};
|
use chrono::{DateTime, Local, TimeZone};
|
||||||
|
|
||||||
// 规范化文件内容并写入
|
|
||||||
fn normalize_and_write(content: &str, file_path: &str) -> String {
|
|
||||||
let normalized = content.replace("\r\n", "\n");
|
|
||||||
if normalized != content {
|
|
||||||
if let Err(e) = std::fs::write(file_path, &normalized) {
|
|
||||||
eprintln!("警告: 无法更新规范化的文件: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
normalized
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析token
|
// 解析token
|
||||||
pub fn parse_token(token_part: &str) -> String {
|
pub fn parse_token(token_part: &str) -> String {
|
||||||
// 查找最后一个:或%3A的位置
|
// 查找最后一个:或%3A的位置
|
||||||
@@ -38,23 +23,9 @@ pub fn parse_token(token_part: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token 加载函数
|
// Token 加载函数,支持从字符串内容加载
|
||||||
pub fn load_tokens() -> Vec<TokenInfo> {
|
pub fn load_tokens_from_content(content: &str) -> Vec<TokenInfo> {
|
||||||
let token_list_file = TOKEN_LIST_FILE.as_str();
|
let token_map: std::collections::HashMap<String, String> = content
|
||||||
|
|
||||||
// 确保文件存在
|
|
||||||
if !std::path::Path::new(&token_list_file).exists() {
|
|
||||||
if let Err(e) = std::fs::write(&token_list_file, EMPTY_STRING) {
|
|
||||||
eprintln!("警告: 无法创建文件 '{}': {}", &token_list_file, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取和规范化 token-list 文件
|
|
||||||
let token_map: std::collections::HashMap<String, String> =
|
|
||||||
match std::fs::read_to_string(&token_list_file) {
|
|
||||||
Ok(content) => {
|
|
||||||
let normalized = normalize_and_write(&content, &token_list_file);
|
|
||||||
normalized
|
|
||||||
.lines()
|
.lines()
|
||||||
.filter_map(|line| {
|
.filter_map(|line| {
|
||||||
let line = line.trim();
|
let line = line.trim();
|
||||||
@@ -74,46 +45,19 @@ pub fn load_tokens() -> Vec<TokenInfo> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect();
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("警告: 无法读取token-list文件: {}", e);
|
|
||||||
std::collections::HashMap::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 更新 token-list 文件
|
|
||||||
let token_list_content = token_map
|
|
||||||
.iter()
|
|
||||||
.map(|(token, checksum)| format!("{},{}", token, checksum))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
if let Err(e) = std::fs::write(&token_list_file, token_list_content) {
|
|
||||||
eprintln!("警告: 无法更新token-list文件: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转换为 TokenInfo vector
|
|
||||||
token_map
|
token_map
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(token, checksum)| TokenInfo {
|
.map(|(token, checksum)| TokenInfo {
|
||||||
token: token.clone(),
|
token,
|
||||||
checksum,
|
checksum,
|
||||||
profile: None,
|
profile: None,
|
||||||
|
tags: None,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_tokens(token_infos: &[TokenInfo], file_path: &str) -> std::io::Result<()> {
|
|
||||||
let content = token_infos
|
|
||||||
.iter()
|
|
||||||
.map(|info| format!("{},{}", info.token, info.checksum))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
std::fs::write(file_path, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) const HEADER_B64: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
pub(super) const HEADER_B64: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||||
pub(super) const ISSUER: &str = "https://authentication.cursor.sh";
|
pub(super) const ISSUER: &str = "https://authentication.cursor.sh";
|
||||||
pub(super) const SCOPE: &str = "openid profile email offline_access";
|
pub(super) const SCOPE: &str = "openid profile email offline_access";
|
||||||
|
31
src/main.rs
31
src/main.rs
@@ -8,16 +8,16 @@ use app::{
|
|||||||
PKG_VERSION, ROUTE_ABOUT_PATH, ROUTE_API_PATH, ROUTE_BASIC_CALIBRATION_PATH,
|
PKG_VERSION, ROUTE_ABOUT_PATH, ROUTE_API_PATH, ROUTE_BASIC_CALIBRATION_PATH,
|
||||||
ROUTE_BUILD_KEY_PATH, ROUTE_CONFIG_PATH, ROUTE_ENV_EXAMPLE_PATH, ROUTE_GET_CHECKSUM,
|
ROUTE_BUILD_KEY_PATH, ROUTE_CONFIG_PATH, ROUTE_ENV_EXAMPLE_PATH, ROUTE_GET_CHECKSUM,
|
||||||
ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER, ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH,
|
ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER, ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH,
|
||||||
ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, ROUTE_TOKENS_ADD_PATH,
|
ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, ROUTE_TOKEN_TAGS_UPDATE_PATH,
|
||||||
ROUTE_TOKENS_DELETE_PATH, ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH,
|
ROUTE_TOKENS_ADD_PATH, ROUTE_TOKENS_DELETE_PATH, ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH,
|
||||||
ROUTE_TOKENS_RELOAD_PATH, ROUTE_TOKENS_UPDATE_PATH, ROUTE_USER_INFO_PATH,
|
ROUTE_TOKENS_UPDATE_PATH, ROUTE_USER_INFO_PATH,
|
||||||
},
|
},
|
||||||
lazy::{AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH},
|
lazy::{AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH},
|
||||||
model::*,
|
model::*,
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
routing::{get, post},
|
|
||||||
Router,
|
Router,
|
||||||
|
routing::{get, post},
|
||||||
};
|
};
|
||||||
use chat::{
|
use chat::{
|
||||||
route::{
|
route::{
|
||||||
@@ -25,12 +25,12 @@ use chat::{
|
|||||||
handle_build_key, handle_build_key_page, handle_config_page, handle_delete_tokens,
|
handle_build_key, handle_build_key_page, handle_config_page, handle_delete_tokens,
|
||||||
handle_env_example, handle_get_checksum, handle_get_hash, handle_get_timestamp_header,
|
handle_env_example, handle_get_checksum, handle_get_hash, handle_get_timestamp_header,
|
||||||
handle_get_tokens, handle_health, handle_logs, handle_logs_post, handle_readme,
|
handle_get_tokens, handle_health, handle_logs, handle_logs_post, handle_readme,
|
||||||
handle_reload_tokens, handle_root, handle_static, handle_tokens_page, handle_update_tokens,
|
handle_root, handle_static, handle_tokens_page, handle_update_token_tags,
|
||||||
handle_user_info,
|
handle_update_tokens, handle_user_info,
|
||||||
},
|
},
|
||||||
service::{handle_chat, handle_models},
|
service::{handle_chat, handle_models},
|
||||||
};
|
};
|
||||||
use common::utils::{load_tokens, parse_string_from_env, parse_usize_from_env};
|
use common::utils::{parse_string_from_env, parse_usize_from_env};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -58,11 +58,8 @@ async fn main() {
|
|||||||
// 初始化全局配置
|
// 初始化全局配置
|
||||||
AppConfig::init();
|
AppConfig::init();
|
||||||
|
|
||||||
// 加载 tokens
|
|
||||||
let token_infos = load_tokens();
|
|
||||||
|
|
||||||
// 初始化应用状态
|
// 初始化应用状态
|
||||||
let state = Arc::new(Mutex::new(AppState::new(token_infos)));
|
let state = Arc::new(Mutex::new(AppState::new()));
|
||||||
|
|
||||||
// 尝试加载保存的配置
|
// 尝试加载保存的配置
|
||||||
if let Err(e) = AppConfig::load_saved_config() {
|
if let Err(e) = AppConfig::load_saved_config() {
|
||||||
@@ -89,7 +86,7 @@ async fn main() {
|
|||||||
tokio::time::sleep(std::time::Duration::from_secs(wait_duration)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(wait_duration)).await;
|
||||||
|
|
||||||
let mut app_state = state_for_reload.lock().await;
|
let mut app_state = state_for_reload.lock().await;
|
||||||
app_state.update_checksum();
|
app_state.token_manager.update_checksum();
|
||||||
// debug_println!("checksum 自动刷新: {}", next_reload);
|
// debug_println!("checksum 自动刷新: {}", next_reload);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -130,12 +127,12 @@ async fn main() {
|
|||||||
println!("配置已保存");
|
println!("配置已保存");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存日志
|
// 保存状态
|
||||||
let state = state_for_shutdown.lock().await;
|
let state = state_for_shutdown.lock().await;
|
||||||
if let Err(e) = state.save_logs().await {
|
if let Err(e) = state.save_state().await {
|
||||||
eprintln!("保存日志失败: {}", e);
|
eprintln!("保存状态失败: {}", e);
|
||||||
} else {
|
} else {
|
||||||
println!("日志已保存");
|
println!("状态已保存");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,7 +143,6 @@ async fn main() {
|
|||||||
.route(ROUTE_TOKENS_PATH, get(handle_tokens_page))
|
.route(ROUTE_TOKENS_PATH, get(handle_tokens_page))
|
||||||
.route(ROUTE_MODELS_PATH.as_str(), get(handle_models))
|
.route(ROUTE_MODELS_PATH.as_str(), get(handle_models))
|
||||||
.route(ROUTE_TOKENS_GET_PATH, post(handle_get_tokens))
|
.route(ROUTE_TOKENS_GET_PATH, post(handle_get_tokens))
|
||||||
.route(ROUTE_TOKENS_RELOAD_PATH, post(handle_reload_tokens))
|
|
||||||
.route(ROUTE_TOKENS_UPDATE_PATH, post(handle_update_tokens))
|
.route(ROUTE_TOKENS_UPDATE_PATH, post(handle_update_tokens))
|
||||||
.route(ROUTE_TOKENS_ADD_PATH, post(handle_add_tokens))
|
.route(ROUTE_TOKENS_ADD_PATH, post(handle_add_tokens))
|
||||||
.route(ROUTE_TOKENS_DELETE_PATH, post(handle_delete_tokens))
|
.route(ROUTE_TOKENS_DELETE_PATH, post(handle_delete_tokens))
|
||||||
@@ -167,6 +163,7 @@ async fn main() {
|
|||||||
.route(ROUTE_USER_INFO_PATH, post(handle_user_info))
|
.route(ROUTE_USER_INFO_PATH, post(handle_user_info))
|
||||||
.route(ROUTE_BUILD_KEY_PATH, get(handle_build_key_page))
|
.route(ROUTE_BUILD_KEY_PATH, get(handle_build_key_page))
|
||||||
.route(ROUTE_BUILD_KEY_PATH, post(handle_build_key))
|
.route(ROUTE_BUILD_KEY_PATH, post(handle_build_key))
|
||||||
|
.route(ROUTE_TOKEN_TAGS_UPDATE_PATH, post(handle_update_token_tags))
|
||||||
.layer(RequestBodyLimitLayer::new(
|
.layer(RequestBodyLimitLayer::new(
|
||||||
1024 * 1024 * parse_usize_from_env("REQUEST_BODY_LIMIT_MB", 2),
|
1024 * 1024 * parse_usize_from_env("REQUEST_BODY_LIMIT_MB", 2),
|
||||||
))
|
))
|
||||||
|
108
static/logs.html
108
static/logs.html
@@ -376,6 +376,23 @@
|
|||||||
background: var(--error-color);
|
background: var(--error-color);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 图表样式 */
|
||||||
|
.chart-container {
|
||||||
|
background: var(--card-background);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.chart-container {
|
||||||
|
height: 200px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -418,6 +435,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加图表容器 -->
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="requestsChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table id="logsTable">
|
<table id="logsTable">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -529,8 +551,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加 Chart.js 库 -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.js" integrity="sha512-0im+NZpDrlsC+p6iSc13cqlMNPqdT6e0hUF8NAaxdaGOmPuV9DdVpWYOCHHrMQNVDb2TByQoDbHx34MT6g16ZA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let refreshInterval;
|
let refreshInterval;
|
||||||
|
let requestsChart;
|
||||||
|
|
||||||
function updateStats(data) {
|
function updateStats(data) {
|
||||||
document.getElementById('totalRequests').textContent = data.total || 0;
|
document.getElementById('totalRequests').textContent = data.total || 0;
|
||||||
@@ -676,9 +702,91 @@
|
|||||||
return rows.map(([label, value]) => `<div class="tooltip-info-row"><span class="label">${label}:</span><span class="value">${value}</span></div>`).join('');
|
return rows.map(([label, value]) => `<div class="tooltip-info-row"><span class="label">${label}:</span><span class="value">${value}</span></div>`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initChart() {
|
||||||
|
const ctx = document.getElementById('requestsChart').getContext('2d');
|
||||||
|
requestsChart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: '每小时请求数',
|
||||||
|
data: [],
|
||||||
|
borderColor: 'rgb(75, 192, 192)',
|
||||||
|
tension: 0.1,
|
||||||
|
fill: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
stepSize: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: '24小时请求统计'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图表数据
|
||||||
|
function updateChart(data) {
|
||||||
|
if (!requestsChart) {
|
||||||
|
initChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按小时统计请求数量
|
||||||
|
const hourlyStats = new Map();
|
||||||
|
const now = new Date();
|
||||||
|
const past24Hours = new Date(now - 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// 初始化24小时的时间段
|
||||||
|
for (let i = 0; i < 24; i++) {
|
||||||
|
const hour = new Date(now - i * 60 * 60 * 1000);
|
||||||
|
const hourKey = hour.toLocaleString('zh-CN', {
|
||||||
|
month: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric'
|
||||||
|
});
|
||||||
|
hourlyStats.set(hourKey, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计每小时的请求数
|
||||||
|
data.logs.forEach(log => {
|
||||||
|
const logTime = new Date(log.timestamp);
|
||||||
|
if (logTime >= past24Hours) {
|
||||||
|
const hourKey = logTime.toLocaleString('zh-CN', {
|
||||||
|
month: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric'
|
||||||
|
});
|
||||||
|
if (hourlyStats.has(hourKey)) {
|
||||||
|
hourlyStats.set(hourKey, hourlyStats.get(hourKey) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 转换为图表数据
|
||||||
|
const sortedHours = Array.from(hourlyStats.keys()).reverse();
|
||||||
|
const counts = sortedHours.map(hour => hourlyStats.get(hour));
|
||||||
|
|
||||||
|
requestsChart.data.labels = sortedHours;
|
||||||
|
requestsChart.data.datasets[0].data = counts;
|
||||||
|
requestsChart.update();
|
||||||
|
}
|
||||||
|
|
||||||
function updateTable(data) {
|
function updateTable(data) {
|
||||||
const tbody = document.getElementById('logsBody');
|
const tbody = document.getElementById('logsBody');
|
||||||
updateStats(data);
|
updateStats(data);
|
||||||
|
updateChart(data);
|
||||||
|
|
||||||
tbody.innerHTML = data.logs.map(log => `<tr><td>${log.id}</td><td>${new Date(log.timestamp).toLocaleString()}</td><td>${log.model}</td><td><div class="token-info-tooltip"><button class="info-button" onclick='showTokenModal(${JSON.stringify(log.token_info)})'>查看详情<div class="tooltip-content">${formatSimpleTokenInfo(log.token_info)}</div></button></div></td><td>${log.prompt ? `<div class="token-info-tooltip prompt-preview"><button class="info-button" onclick="showPromptModal(decodeURIComponent('${encodeURIComponent(log.prompt).replace(/'/g, "\\'")}'))">查看对话<div class="tooltip-content">${formatPromptPreview(log.prompt)}</div></button></div>` : '-'}</td><td>${formatTiming(log.timing.total, log.timing.first)}</td><td>${log.stream ? '是' : '否'}</td><td>${log.status}</td><td>${log.error || '-'}</td></tr>`).join('');
|
tbody.innerHTML = data.logs.map(log => `<tr><td>${log.id}</td><td>${new Date(log.timestamp).toLocaleString()}</td><td>${log.model}</td><td><div class="token-info-tooltip"><button class="info-button" onclick='showTokenModal(${JSON.stringify(log.token_info)})'>查看详情<div class="tooltip-content">${formatSimpleTokenInfo(log.token_info)}</div></button></div></td><td>${log.prompt ? `<div class="token-info-tooltip prompt-preview"><button class="info-button" onclick="showPromptModal(decodeURIComponent('${encodeURIComponent(log.prompt).replace(/'/g, "\\'")}'))">查看对话<div class="tooltip-content">${formatPromptPreview(log.prompt)}</div></button></div>` : '-'}</td><td>${formatTiming(log.timing.total, log.timing.first)}</td><td>${log.stream ? '是' : '否'}</td><td>${log.status}</td><td>${log.error || '-'}</td></tr>`).join('');
|
||||||
}
|
}
|
||||||
|
@@ -469,7 +469,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await makeAuthenticatedRequest('/tokens/add', {
|
const data = await makeAuthenticatedRequest('/tokens/add', {
|
||||||
body: JSON.stringify(tokenList)
|
body: JSON.stringify({
|
||||||
|
tokens: tokenList,
|
||||||
|
tags: []
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
84
tools/get-token/Cargo.lock
generated
84
tools/get-token/Cargo.lock
generated
@@ -15,19 +15,16 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "base64"
|
||||||
version = "2.6.0"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "bitflags"
|
||||||
version = "1.2.5"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
|
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
||||||
dependencies = [
|
|
||||||
"shlex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@@ -51,7 +48,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
|||||||
name = "get-token"
|
name = "get-token"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -72,17 +71,28 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.30.1"
|
version = "0.30.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.2"
|
version = "1.20.2"
|
||||||
@@ -97,18 +107,18 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.92"
|
version = "1.0.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.37"
|
version = "1.0.38"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -128,10 +138,42 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "ryu"
|
||||||
version = "1.3.0"
|
version = "1.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.217"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.217"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.138"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
@@ -141,9 +183,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.91"
|
version = "2.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
|
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -152,9 +194,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.14"
|
version = "1.0.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
|
@@ -4,7 +4,10 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rusqlite = { version = "0.32.1", default-features = false, features = ["bundled"] }
|
base64 = "0.22.1"
|
||||||
|
# rusqlite = { version = "0.32.1", default-features = false, features = ["bundled"] }
|
||||||
|
rusqlite = "0.32.1"
|
||||||
|
serde_json = "1.0.138"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
@@ -1,28 +1,150 @@
|
|||||||
|
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn get_machine_id() -> String {
|
||||||
|
if cfg!(windows) {
|
||||||
|
let output = Command::new("REG")
|
||||||
|
.args(&[
|
||||||
|
"QUERY",
|
||||||
|
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography",
|
||||||
|
"/v",
|
||||||
|
"MachineGuid",
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.expect("无法执行 REG 命令");
|
||||||
|
String::from_utf8_lossy(&output.stdout)
|
||||||
|
.lines()
|
||||||
|
.find(|line| line.contains("MachineGuid"))
|
||||||
|
.and_then(|line| line.split_whitespace().last())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string()
|
||||||
|
} else if cfg!(target_os = "macos") {
|
||||||
|
let output = Command::new("ioreg")
|
||||||
|
.args(&["-rd1", "-c", "IOPlatformExpertDevice"])
|
||||||
|
.output()
|
||||||
|
.expect("无法执行 ioreg 命令");
|
||||||
|
String::from_utf8_lossy(&output.stdout)
|
||||||
|
.lines()
|
||||||
|
.find(|line| line.contains("IOPlatformUUID"))
|
||||||
|
.and_then(|line| line.split("\"").nth(3))
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string()
|
||||||
|
} else if cfg!(target_os = "linux") {
|
||||||
|
let output = Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg("( cat /var/lib/dbus/machine-id /etc/machine-id 2> /dev/null || hostname ) | head -n 1 || :")
|
||||||
|
.output()
|
||||||
|
.expect("无法获取 machine-id");
|
||||||
|
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
||||||
|
} else if cfg!(target_os = "freebsd") {
|
||||||
|
let output = Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg("kenv -q smbios.system.uuid || sysctl -n kern.hostuuid")
|
||||||
|
.output()
|
||||||
|
.expect("无法获取 UUID");
|
||||||
|
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
||||||
|
} else {
|
||||||
|
"unknown".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn obfuscate_bytes(bytes: &mut [u8]) {
|
||||||
|
let mut prev: u8 = 165;
|
||||||
|
for (idx, byte) in bytes.iter_mut().enumerate() {
|
||||||
|
let old_value = *byte;
|
||||||
|
*byte = (old_value ^ prev).wrapping_add((idx % 256) as u8);
|
||||||
|
prev = *byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_timestamp_header() -> String {
|
||||||
|
let timestamp = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs()
|
||||||
|
/ 1_000;
|
||||||
|
|
||||||
|
let mut timestamp_bytes = vec![
|
||||||
|
((timestamp >> 8) & 0xFF) as u8,
|
||||||
|
(timestamp & 0xFF) as u8,
|
||||||
|
((timestamp >> 24) & 0xFF) as u8,
|
||||||
|
((timestamp >> 16) & 0xFF) as u8,
|
||||||
|
((timestamp >> 8) & 0xFF) as u8,
|
||||||
|
(timestamp & 0xFF) as u8,
|
||||||
|
];
|
||||||
|
|
||||||
|
obfuscate_bytes(&mut timestamp_bytes);
|
||||||
|
BASE64.encode(×tamp_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let home_dir = env::var("HOME")
|
let db_path = if cfg!(windows) {
|
||||||
.or_else(|_| env::var("USERPROFILE"))
|
let app_data = env::var("APPDATA").unwrap_or_else(|_| {
|
||||||
.unwrap();
|
let profile = env::var("USERPROFILE").expect("未找到 USERPROFILE 环境变量");
|
||||||
let db_path = if cfg!(target_os = "windows") {
|
PathBuf::from(profile)
|
||||||
PathBuf::from(home_dir).join(r"AppData\Roaming\Cursor\User\globalStorage\state.vscdb")
|
.join("AppData")
|
||||||
} else if cfg!(target_os = "linux") {
|
.join("Roaming")
|
||||||
PathBuf::from(home_dir).join(".config/Cursor/User/globalStorage/state.vscdb")
|
.to_string_lossy()
|
||||||
} else {
|
.to_string()
|
||||||
PathBuf::from(home_dir)
|
});
|
||||||
|
PathBuf::from(app_data).join(r"Cursor\User\globalStorage\state.vscdb")
|
||||||
|
} else if cfg!(target_os = "macos") {
|
||||||
|
let home = env::var("HOME").expect("未找到 HOME 环境变量");
|
||||||
|
PathBuf::from(home)
|
||||||
.join("Library/Application Support/Cursor/User/globalStorage/state.vscdb")
|
.join("Library/Application Support/Cursor/User/globalStorage/state.vscdb")
|
||||||
|
} else if cfg!(target_os = "linux") {
|
||||||
|
let config_home = env::var("XDG_CONFIG_HOME").unwrap_or_else(|_| {
|
||||||
|
let home = env::var("HOME").expect("未找到 HOME 环境变量");
|
||||||
|
format!("{}/.config", home)
|
||||||
|
});
|
||||||
|
PathBuf::from(config_home).join("Cursor/User/globalStorage/state.vscdb")
|
||||||
|
} else {
|
||||||
|
panic!("不支持的操作系统平台")
|
||||||
};
|
};
|
||||||
|
|
||||||
match Connection::open(&db_path) {
|
match Connection::open(&db_path) {
|
||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
match conn.query_row(
|
let token = conn.query_row(
|
||||||
"SELECT value FROM ItemTable WHERE key = 'cursorAuth/accessToken'",
|
"SELECT value FROM ItemTable WHERE key = 'cursorAuth/accessToken'",
|
||||||
[],
|
[],
|
||||||
|row| row.get::<_, String>(0),
|
|row| row.get::<_, String>(0),
|
||||||
|
);
|
||||||
|
|
||||||
|
let storage_path = db_path.parent().unwrap().join("storage.json");
|
||||||
|
let storage_content = std::fs::read_to_string(storage_path).unwrap_or_default();
|
||||||
|
let storage_json: serde_json::Value =
|
||||||
|
serde_json::from_str(&storage_content).unwrap_or_default();
|
||||||
|
|
||||||
|
match token {
|
||||||
|
Ok(token) => {
|
||||||
|
println!("访问令牌: {}", token.trim());
|
||||||
|
|
||||||
|
// if let Some(machine_id) = storage_json["telemetry.machineId"].as_str() {
|
||||||
|
// println!("machineId: {}", machine_id);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(mac_machine_id) = storage_json["telemetry.macMachineId"].as_str() {
|
||||||
|
// println!("macMachineId: {}", mac_machine_id);
|
||||||
|
// }
|
||||||
|
|
||||||
|
let sys_machine_id = get_machine_id();
|
||||||
|
println!("系统 machine-id: {}", sys_machine_id);
|
||||||
|
|
||||||
|
if let (Some(machine_id), Some(mac_machine_id)) = (
|
||||||
|
storage_json["telemetry.machineId"].as_str(),
|
||||||
|
storage_json["telemetry.macMachineId"].as_str(),
|
||||||
) {
|
) {
|
||||||
Ok(token) => println!("访问令牌: {}", token.trim()),
|
println!(
|
||||||
|
"校验和: {}{}/{}",
|
||||||
|
generate_timestamp_header(),
|
||||||
|
machine_id,
|
||||||
|
mac_machine_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(err) => eprintln!("获取令牌时出错: {}", err),
|
Err(err) => eprintln!("获取令牌时出错: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
219
tools/set-token/Cargo.lock
generated
Normal file
219
tools/set-token/Cargo.lock
generated
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-streaming-iterator"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashlink"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libsqlite3-sys"
|
||||||
|
version = "0.31.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad8935b44e7c13394a179a438e0cebba0fe08fe01b54f152e29a93b5cf993fd4"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusqlite"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c6d5e5acb6f6129fe3f7ba0a7fc77bca1942cb568535e18e7bc40262baf3110"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fallible-iterator",
|
||||||
|
"fallible-streaming-iterator",
|
||||||
|
"hashlink",
|
||||||
|
"libsqlite3-sys",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.217"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.217"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.138"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "set-token"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
"rusqlite",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.96"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
16
tools/set-token/Cargo.toml
Normal file
16
tools/set-token/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "set-token"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
regex = "1.11.1"
|
||||||
|
rusqlite = "0.33.0"
|
||||||
|
serde_json = "1.0.138"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
panic = 'abort'
|
||||||
|
strip = true
|
||||||
|
opt-level = 3
|
538
tools/set-token/src/main.rs
Normal file
538
tools/set-token/src/main.rs
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
use rusqlite::{Connection, Result};
|
||||||
|
use serde_json::{Value, from_str, to_string_pretty};
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn get_cursor_path() -> PathBuf {
|
||||||
|
let home = if cfg!(windows) {
|
||||||
|
env::var("USERPROFILE").unwrap_or_else(|_| env::var("HOME").unwrap())
|
||||||
|
} else {
|
||||||
|
env::var("HOME").unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let base_path = PathBuf::from(home);
|
||||||
|
|
||||||
|
if cfg!(windows) {
|
||||||
|
base_path.join("AppData\\Roaming\\Cursor")
|
||||||
|
} else if cfg!(target_os = "macos") {
|
||||||
|
base_path.join("Library/Application Support/Cursor")
|
||||||
|
} else {
|
||||||
|
base_path.join(".config/Cursor")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_sqlite_tokens(
|
||||||
|
refresh_token: &str,
|
||||||
|
access_token: &str,
|
||||||
|
email: &str,
|
||||||
|
signup_type: &str,
|
||||||
|
membership_type: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let db_path = get_cursor_path().join("User/globalStorage/state.vscdb");
|
||||||
|
let conn = Connection::open(db_path)?;
|
||||||
|
|
||||||
|
// 获取原始值
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT key, value FROM ItemTable WHERE key IN (
|
||||||
|
'cursorAuth/refreshToken',
|
||||||
|
'cursorAuth/accessToken',
|
||||||
|
'cursorAuth/cachedEmail',
|
||||||
|
'cursorAuth/cachedSignUpType',
|
||||||
|
'cursorAuth/stripeMembershipType'
|
||||||
|
)",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
println!("\n原始值:");
|
||||||
|
let rows = stmt.query_map([], |row| {
|
||||||
|
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
||||||
|
})?;
|
||||||
|
for row in rows {
|
||||||
|
let (key, value) = row?;
|
||||||
|
println!("{}: {}", key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新值
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE ItemTable SET value = ? WHERE key = 'cursorAuth/refreshToken'",
|
||||||
|
[refresh_token],
|
||||||
|
)?;
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE ItemTable SET value = ? WHERE key = 'cursorAuth/accessToken'",
|
||||||
|
[access_token],
|
||||||
|
)?;
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE ItemTable SET value = ? WHERE key = 'cursorAuth/cachedEmail'",
|
||||||
|
[email],
|
||||||
|
)?;
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE ItemTable SET value = ? WHERE key = 'cursorAuth/cachedSignUpType'",
|
||||||
|
[signup_type],
|
||||||
|
)?;
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE ItemTable SET value = ? WHERE key = 'cursorAuth/stripeMembershipType'",
|
||||||
|
[membership_type],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
println!("\n更新后的值:");
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT key, value FROM ItemTable WHERE key IN (
|
||||||
|
'cursorAuth/refreshToken',
|
||||||
|
'cursorAuth/accessToken',
|
||||||
|
'cursorAuth/cachedEmail',
|
||||||
|
'cursorAuth/cachedSignUpType',
|
||||||
|
'cursorAuth/stripeMembershipType'
|
||||||
|
)",
|
||||||
|
)?;
|
||||||
|
let rows = stmt.query_map([], |row| {
|
||||||
|
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
||||||
|
})?;
|
||||||
|
for row in rows {
|
||||||
|
let (key, value) = row?;
|
||||||
|
println!("{}: {}", key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_storage_json(machine_ids: &[String; 4]) -> io::Result<()> {
|
||||||
|
let storage_path = get_cursor_path().join("User/globalStorage/storage.json");
|
||||||
|
let content = fs::read_to_string(&storage_path)?;
|
||||||
|
let mut json: Value = from_str(&content)?;
|
||||||
|
|
||||||
|
if let Value::Object(ref mut map) = json {
|
||||||
|
map.insert(
|
||||||
|
"telemetry.macMachineId".to_string(),
|
||||||
|
Value::String(machine_ids[0].clone()),
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
"telemetry.sqmId".to_string(),
|
||||||
|
Value::String(machine_ids[1].clone()),
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
"telemetry.machineId".to_string(),
|
||||||
|
Value::String(machine_ids[2].clone()),
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
"telemetry.devDeviceId".to_string(),
|
||||||
|
Value::String(machine_ids[3].clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::write(storage_path, to_string_pretty(&json)?)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_jwt(token: &str) -> bool {
|
||||||
|
let parts: Vec<&str> = token.split('.').collect();
|
||||||
|
if parts.len() != 3 {
|
||||||
|
println!("警告: Token 格式不正确,应该包含3个由'.'分隔的部分");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否以 "ey" 开头
|
||||||
|
if !token.starts_with("ey") {
|
||||||
|
println!("警告: Token 应该以'ey'开头");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_sha256(id: &str) -> bool {
|
||||||
|
// SHA256 哈希是64个十六进制字符
|
||||||
|
if id.len() != 64 {
|
||||||
|
println!("警告: ID 长度应为64个字符");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否都是有效的十六进制字符
|
||||||
|
if !id.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||||
|
println!("警告: ID 应只包含十六进制字符(0-9, a-f)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_sqm_id(id: &str) -> bool {
|
||||||
|
// 格式应为 {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} (大写)
|
||||||
|
if id.len() != 38 {
|
||||||
|
println!("警告: SQM ID 格式不正确");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !id.starts_with('{') || !id.ends_with('}') {
|
||||||
|
println!("警告: SQM ID 应该被花括号包围");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid = &id[1..37];
|
||||||
|
if !uuid
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '-')
|
||||||
|
{
|
||||||
|
println!("警告: UUID 部分应为大写字母、数字和连字符");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_device_id(id: &str) -> bool {
|
||||||
|
// 格式应为 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if id.len() != 36 {
|
||||||
|
println!("警告: Device ID 格式不正确");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !id
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
|
||||||
|
{
|
||||||
|
println!("警告: Device ID 应为小写字母、数字和连字符");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_email(email: &str) -> bool {
|
||||||
|
if !email.contains('@') || !email.contains('.') {
|
||||||
|
println!("警告: 邮箱格式不正确");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let parts: Vec<&str> = email.split('@').collect();
|
||||||
|
if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
|
||||||
|
println!("警告: 邮箱格式不正确");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_uuid(uuid: &str) -> bool {
|
||||||
|
// UUID格式应为: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if uuid.len() != 36 {
|
||||||
|
println!("警告: UUID 格式不正确");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parts: Vec<&str> = uuid.split('-').collect();
|
||||||
|
if parts.len() != 5
|
||||||
|
|| parts[0].len() != 8
|
||||||
|
|| parts[1].len() != 4
|
||||||
|
|| parts[2].len() != 4
|
||||||
|
|| parts[3].len() != 4
|
||||||
|
|| parts[4].len() != 12
|
||||||
|
{
|
||||||
|
println!("警告: UUID 格式不正确");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !uuid.chars().all(|c| c.is_ascii_hexdigit() || c == '-') {
|
||||||
|
println!("警告: UUID 应只包含十六进制字符(0-9, a-f)和连字符");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_uuid_launcher(uuid: &str) -> io::Result<()> {
|
||||||
|
let tools_dir = get_cursor_path().join("tools/set-token");
|
||||||
|
fs::create_dir_all(&tools_dir)?;
|
||||||
|
|
||||||
|
// 创建 inject.js
|
||||||
|
let inject_js = format!(
|
||||||
|
r#"// 保存原始 require
|
||||||
|
const originalRequire = module.constructor.prototype.require;
|
||||||
|
|
||||||
|
// 重写 require 函数
|
||||||
|
module.constructor.prototype.require = function(path) {{
|
||||||
|
const result = originalRequire.apply(this, arguments);
|
||||||
|
|
||||||
|
// 检测目标模块
|
||||||
|
if (path.includes('main.js')) {{
|
||||||
|
// 保存原始函数
|
||||||
|
const originalModule = result;
|
||||||
|
|
||||||
|
// 创建代理对象
|
||||||
|
return new Proxy(originalModule, {{
|
||||||
|
get(target, prop) {{
|
||||||
|
// 拦截 execSync 调用
|
||||||
|
if (prop === 'execSync') {{
|
||||||
|
return function() {{
|
||||||
|
// 返回自定义的 UUID
|
||||||
|
const platform = process.platform;
|
||||||
|
switch (platform) {{
|
||||||
|
case 'darwin':
|
||||||
|
return 'IOPlatformUUID="{}"';
|
||||||
|
case 'win32':
|
||||||
|
return ' HARDWARE\\DESCRIPTION\\System\\BIOS SystemProductID REG_SZ {}';
|
||||||
|
case 'linux':
|
||||||
|
case 'freebsd':
|
||||||
|
return '{}';
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported platform: ${{platform}}`);
|
||||||
|
}}
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
return target[prop];
|
||||||
|
}}
|
||||||
|
}});
|
||||||
|
}}
|
||||||
|
return result;
|
||||||
|
}};"#,
|
||||||
|
uuid, uuid, uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
// 写入 inject.js
|
||||||
|
fs::write(tools_dir.join("inject.js"), inject_js)?;
|
||||||
|
|
||||||
|
if cfg!(windows) {
|
||||||
|
// 创建 Windows CMD 脚本
|
||||||
|
let cmd_script = format!(
|
||||||
|
"@echo off\r\n\
|
||||||
|
set NODE_OPTIONS=--require \"%~dp0inject.js\"\r\n\
|
||||||
|
start \"\" \"%LOCALAPPDATA%\\Programs\\Cursor\\Cursor.exe\""
|
||||||
|
);
|
||||||
|
fs::write(tools_dir.join("start-cursor.cmd"), cmd_script)?;
|
||||||
|
|
||||||
|
// 创建 Windows PowerShell 脚本
|
||||||
|
let ps_script = format!(
|
||||||
|
"$env:NODE_OPTIONS = \"--require `\"$PSScriptRoot\\inject.js`\"\"\r\n\
|
||||||
|
Start-Process -FilePath \"$env:LOCALAPPDATA\\Programs\\Cursor\\Cursor.exe\""
|
||||||
|
);
|
||||||
|
fs::write(tools_dir.join("start-cursor.ps1"), ps_script)?;
|
||||||
|
} else {
|
||||||
|
// 创建 Shell 脚本
|
||||||
|
let shell_script = format!(
|
||||||
|
"#!/bin/bash\n\
|
||||||
|
SCRIPT_DIR=\"$(cd \"$(dirname \"${{BASH_SOURCE[0]}}\")\" && pwd)\"\n\
|
||||||
|
export NODE_OPTIONS=\"--require $SCRIPT_DIR/inject.js\"\n\
|
||||||
|
if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n\
|
||||||
|
open -a Cursor\n\
|
||||||
|
else\n\
|
||||||
|
cursor # Linux,根据实际安装路径调整\n\
|
||||||
|
fi"
|
||||||
|
);
|
||||||
|
let script_path = tools_dir.join("start-cursor.sh");
|
||||||
|
fs::write(&script_path, shell_script)?;
|
||||||
|
|
||||||
|
// 在类Unix系统上设置可执行权限
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let mut perms = fs::metadata(&script_path)?.permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
fs::set_permissions(&script_path, perms)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n注入脚本已创建在: {}", tools_dir.display());
|
||||||
|
println!("\n使用方法:");
|
||||||
|
if cfg!(windows) {
|
||||||
|
println!(
|
||||||
|
"方法1: 双击运行 {}",
|
||||||
|
tools_dir.join("start-cursor.cmd").display()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"方法2: 在 PowerShell 中运行 {}",
|
||||||
|
tools_dir.join("start-cursor.ps1").display()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"在终端中运行: {}",
|
||||||
|
tools_dir.join("start-cursor.sh").display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!("\n注意:每次启动 Cursor 时都需要使用这个脚本。");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
loop {
|
||||||
|
println!("\n请选择操作:");
|
||||||
|
println!("0. 退出");
|
||||||
|
println!("1. 更新 Token");
|
||||||
|
println!("2. 更新设备 ID");
|
||||||
|
println!("3. 创建自定义UUID启动脚本");
|
||||||
|
|
||||||
|
print!("请输入选项 (0-3): ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
|
||||||
|
let mut choice = String::new();
|
||||||
|
io::stdin().read_line(&mut choice).unwrap();
|
||||||
|
|
||||||
|
match choice.trim() {
|
||||||
|
"0" => break,
|
||||||
|
"1" => {
|
||||||
|
let mut refresh_token = String::new();
|
||||||
|
loop {
|
||||||
|
print!("请输入 Refresh Token: ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
refresh_token.clear();
|
||||||
|
io::stdin().read_line(&mut refresh_token).unwrap();
|
||||||
|
refresh_token = refresh_token.trim().to_string();
|
||||||
|
|
||||||
|
if is_valid_jwt(&refresh_token) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
println!("请重新输入正确格式的 Token");
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("Access Token 是否与 Refresh Token 相同? (y/n): ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
let mut same = String::new();
|
||||||
|
io::stdin().read_line(&mut same).unwrap();
|
||||||
|
|
||||||
|
let access_token = if same.trim().eq_ignore_ascii_case("y") {
|
||||||
|
refresh_token.clone()
|
||||||
|
} else {
|
||||||
|
let mut access_token = String::new();
|
||||||
|
loop {
|
||||||
|
print!("请输入 Access Token: ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
access_token.clear();
|
||||||
|
io::stdin().read_line(&mut access_token).unwrap();
|
||||||
|
access_token = access_token.trim().to_string();
|
||||||
|
|
||||||
|
if is_valid_jwt(&access_token) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
println!("请重新输入正确格式的 Token");
|
||||||
|
}
|
||||||
|
access_token
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut email = String::new();
|
||||||
|
loop {
|
||||||
|
print!("请输入邮箱: ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
email.clear();
|
||||||
|
io::stdin().read_line(&mut email).unwrap();
|
||||||
|
email = email.trim().to_string();
|
||||||
|
|
||||||
|
if is_valid_email(&email) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
println!("请重新输入正确格式的邮箱");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut signup_type = String::new();
|
||||||
|
loop {
|
||||||
|
println!("\n可选的注册类型:");
|
||||||
|
println!("1. Auth_0");
|
||||||
|
println!("2. Github");
|
||||||
|
println!("3. Google");
|
||||||
|
println!("4. unknown");
|
||||||
|
println!("(WorkOS - 仅供展示,不可选择)");
|
||||||
|
print!("请选择注册类型 (1-4): ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
signup_type.clear();
|
||||||
|
io::stdin().read_line(&mut signup_type).unwrap();
|
||||||
|
|
||||||
|
let signup_type_str = match signup_type.trim() {
|
||||||
|
"1" => "Auth_0",
|
||||||
|
"2" => "Github",
|
||||||
|
"3" => "Google",
|
||||||
|
"4" => "unknown",
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
signup_type = signup_type_str;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut membership_type = String::new();
|
||||||
|
loop {
|
||||||
|
println!("\n可选的会员类型:");
|
||||||
|
println!("1. free");
|
||||||
|
println!("2. pro");
|
||||||
|
println!("3. enterprise");
|
||||||
|
println!("4. free_trial");
|
||||||
|
print!("请选择会员类型 (1-4): ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
membership_type.clear();
|
||||||
|
io::stdin().read_line(&mut membership_type).unwrap();
|
||||||
|
|
||||||
|
let membership_type_str = match membership_type.trim() {
|
||||||
|
"1" => "free",
|
||||||
|
"2" => "pro",
|
||||||
|
"3" => "enterprise",
|
||||||
|
"4" => "free_trial",
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
membership_type = membership_type_str;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
match update_sqlite_tokens(
|
||||||
|
&refresh_token,
|
||||||
|
&access_token,
|
||||||
|
&email,
|
||||||
|
&signup_type,
|
||||||
|
&membership_type,
|
||||||
|
) {
|
||||||
|
Ok(_) => println!("所有信息更新成功!"),
|
||||||
|
Err(e) => println!("更新失败: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"2" => {
|
||||||
|
let mut ids = Vec::new();
|
||||||
|
let validators: [(Box<dyn Fn(&str) -> bool>, &str); 4] = [
|
||||||
|
(Box::new(is_valid_sha256), "macMachineId"),
|
||||||
|
(Box::new(is_valid_sqm_id), "sqmId"),
|
||||||
|
(Box::new(is_valid_sha256), "machineId"),
|
||||||
|
(Box::new(is_valid_device_id), "devDeviceId"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (validator, name) in validators.iter() {
|
||||||
|
loop {
|
||||||
|
print!("请输入 {}: ", name);
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
let mut id = String::new();
|
||||||
|
io::stdin().read_line(&mut id).unwrap();
|
||||||
|
let id = id.trim().to_string();
|
||||||
|
|
||||||
|
if validator(&id) {
|
||||||
|
ids.push(id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
println!("请重新输入正确格式的 ID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match update_storage_json(&ids.try_into().unwrap()) {
|
||||||
|
Ok(_) => println!("设备 ID 更新成功!"),
|
||||||
|
Err(e) => println!("更新失败: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"3" => {
|
||||||
|
let mut uuid = String::new();
|
||||||
|
loop {
|
||||||
|
print!("请输入自定义 UUID (格式: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx): ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
uuid.clear();
|
||||||
|
io::stdin().read_line(&mut uuid).unwrap();
|
||||||
|
uuid = uuid.trim().to_string();
|
||||||
|
|
||||||
|
if is_valid_uuid(&uuid) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
println!("请重新输入正确格式的 UUID");
|
||||||
|
}
|
||||||
|
|
||||||
|
match create_uuid_launcher(&uuid) {
|
||||||
|
Ok(_) => println!("启动脚本创建成功!"),
|
||||||
|
Err(e) => println!("创建失败: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => println!("无效选项,请重试"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user