diff --git a/.claude/worktrees/saas-backend b/.claude/worktrees/saas-backend new file mode 160000 index 0000000..4d8d560 --- /dev/null +++ b/.claude/worktrees/saas-backend @@ -0,0 +1 @@ +Subproject commit 4d8d560d1fdda249abf7b8875db747dd4eb161c2 diff --git a/.gitignore b/.gitignore index 2d8faef..7d7ef41 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,10 @@ build/ .env.local .env.*.local +# SaaS config (contains database credentials) +saas-config.toml +!saas-config.toml.example + # Logs logs/ *.log diff --git a/CLAUDE.md b/CLAUDE.md index f92edde..e50cfc3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,16 +37,20 @@ ZCLAW/ │ ├── zclaw-skills/ # 技能系统 (SKILL.md解析, 执行器) │ ├── zclaw-hands/ # 自主能力 (Hand/Trigger 注册管理) │ ├── zclaw-channels/ # 通道适配器 (仅 ConsoleChannel 测试适配器) -│ └── zclaw-protocols/ # 协议支持 (MCP, A2A) +│ ├── zclaw-protocols/ # 协议支持 (MCP, A2A) +│ └── zclaw-saas/ # SaaS 后端 (账号, 模型配置, 中转, 配置同步) +├── admin/ # Next.js 管理后台 ├── desktop/ # Tauri 桌面应用 │ ├── src/ -│ │ ├── components/ # React UI 组件 -│ │ ├── store/ # Zustand 状态管理 -│ │ └── lib/ # 客户端通信 / 工具函数 +│ │ ├── components/ # React UI 组件 (含 SaaS 集成) +│ │ ├── store/ # Zustand 状态管理 (含 saasStore) +│ │ └── lib/ # 客户端通信 / 工具函数 (含 saas-client) │ └── src-tauri/ # Tauri Rust 后端 (集成 Kernel) ├── skills/ # SKILL.md 技能定义 ├── hands/ # HAND.toml 自主能力配置 ├── config/ # TOML 配置文件 +├── saas-config.toml # SaaS 后端配置 (PostgreSQL 连接等) +├── docker-compose.yml # PostgreSQL 容器配置 ├── docs/ # 架构文档和知识库 └── tests/ # Vitest 回归测试 ``` @@ -66,7 +70,9 @@ ZCLAW/ | 桌面框架 | Tauri 2.x | | 样式方案 | Tailwind CSS | | 配置格式 | TOML | -| 后端核心 | Rust Workspace (8 crates) | +| 后端核心 | Rust Workspace (9 crates) | +| SaaS 后端 | Axum + PostgreSQL (zclaw-saas) | +| 管理后台 | Next.js (admin/) | ### 2.3 Crate 依赖关系 @@ -79,6 +85,8 @@ zclaw-runtime (→ types, memory) ↑ zclaw-kernel (→ types, memory, runtime) ↑ +zclaw-saas (→ types, 独立运行于 8080 端口) + ↑ desktop/src-tauri (→ kernel, skills, hands, channels, protocols) ``` @@ -260,6 +268,18 @@ docs/ - **面向未来** - 文档要帮助未来的开发者快速理解 - **中文优先** - 所有面向用户的文档使用中文 +### 8.3 完成工作后的文档同步(强制) + +每次完成功能实现、架构变更、问题修复后,**必须**同步更新以下文档: + +1. **CLAUDE.md** — 如果涉及项目结构、技术栈、工作流程、命令的变化 +2. **docs/features/** — 如果涉及新功能、功能变更、功能状态更新 +3. **docs/knowledge-base/** — 如果涉及新知识、故障排查经验、配置说明 +4. **saas-config.toml 注释** — 如果涉及 SaaS 配置项变更 +5. **CHANGELOG** — 如果涉及对外可见的行为变化 + +**执行时机:** 代码编译通过且验证成功后,在标记任务完成之前,立即执行文档更新。文档更新是任务完成的必要条件,不是可选步骤。 + *** ## 9. 常见问题排查 diff --git a/Cargo.lock b/Cargo.lock index 04d1761..58f9819 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9698bf0769c641b18618039fe2ebd41eb3541f98433000f64e663fab7cea2c87" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -86,6 +95,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -173,7 +188,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -204,7 +219,7 @@ dependencies = [ "cfg-if", "event-listener 5.4.1", "futures-lite", - "rustix", + "rustix 1.1.4", ] [[package]] @@ -230,7 +245,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -530,6 +545,9 @@ name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytemuck" @@ -586,6 +604,84 @@ dependencies = [ "serde_core", ] +[[package]] +name = "cap-fs-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "cap-net-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" +dependencies = [ + "cap-primitives", + "cap-std", + "rustix 1.1.4", + "smallvec", +] + +[[package]] +name = "cap-primitives" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix 1.1.4", + "rustix-linux-procfs", + "windows-sys 0.59.0", + "winx", +] + +[[package]] +name = "cap-rand" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" +dependencies = [ + "ambient-authority", + "rand 0.8.5", +] + +[[package]] +name = "cap-std" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix 1.1.4", +] + +[[package]] +name = "cap-time-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" +dependencies = [ + "ambient-authority", + "cap-primitives", + "iana-time-zone", + "once_cell", + "rustix 1.1.4", + "winx", +] + [[package]] name = "cargo-platform" version = "0.1.9" @@ -653,7 +749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", - "target-lexicon", + "target-lexicon 0.12.16", ] [[package]] @@ -692,6 +788,15 @@ dependencies = [ "inout", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "combine" version = "4.6.7" @@ -799,6 +904,148 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-assembler-x64" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f248321c6a7d4de5dcf2939368e96a397ad3f53b6a076e38d0104d1da326d37" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab6d78ff1f7d9bf8b7e1afbedbf78ba49e38e9da479d4c8a2db094e22f64e2bc" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b6005ba640213a5b95382aeaf6b82bf028309581c8d7349778d66f27dc1180b" +dependencies = [ + "cranelift-entity", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-bitset" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fb5b134a12b559ff0c0f5af0fcd755ad380723b5016c4e0d36f74d39485340" +dependencies = [ + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85837de8be7f17a4034a6b08816f05a3144345d2091937b39d415990daca28f4" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.16.1", + "libm", + "log", + "pulley-interpreter", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon 0.13.5", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e433faa87d38e5b8ff469e44a26fea4f93e58abd7a7c10bad9810056139700c9" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck 0.5.0", + "pulley-interpreter", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5397ba61976e13944ca71230775db13ee1cb62849701ed35b753f4761ed0a9b7" + +[[package]] +name = "cranelift-control" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc81c88765580720eb30f4fc2c1bfdb75fcbf3094f87b3cd69cecca79d77a245" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463feed5d46cf8763f3ba3045284cf706dd161496e20ec9c14afbb4ba09b9e66" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-frontend" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c5eca7696c1c04ab4c7ed8d18eadbb47d6cc9f14ec86fe0881bf1d7e97e261" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon 0.13.5", +] + +[[package]] +name = "cranelift-isle" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1153844610cc9c6da8cf10ce205e45da1a585b7688ed558aa808bbe2e4e6d77" + +[[package]] +name = "cranelift-native" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97b583fe9a60f06b0464cee6be5a17f623fd91b217aaac99b51b339d19911af" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon 0.13.5", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8594dc6bb4860fa8292f1814c76459dbfb933e1978d8222de6380efce45c7cee" + [[package]] name = "crc" version = "3.4.0" @@ -1046,6 +1293,7 @@ dependencies = [ "axum", "base64 0.22.1", "chrono", + "dashmap", "dirs", "fantoccini", "futures", @@ -1243,6 +1491,18 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1374,6 +1634,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.1.4", + "windows-sys 0.59.0", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1489,6 +1760,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes", + "rustix 1.1.4", + "windows-sys 0.59.0", +] + [[package]] name = "futf" version = "0.1.5" @@ -1790,6 +2072,18 @@ dependencies = [ "polyval", ] +[[package]] +name = "gimli" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +dependencies = [ + "fnv", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "stable_deref_trait", +] + [[package]] name = "gio" version = "0.18.4" @@ -1968,6 +2262,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] [[package]] name = "hashlink" @@ -2401,6 +2700,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-extras" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +dependencies = [ + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + [[package]] name = "ipnet" version = "2.12.0" @@ -2436,6 +2751,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -2598,6 +2922,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -2673,6 +3003,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2712,6 +3048,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "markup5ever" version = "0.14.1" @@ -2769,6 +3114,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + [[package]] name = "md-5" version = "0.10.6" @@ -2785,6 +3136,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix 1.1.4", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -3150,6 +3510,18 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "object" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271638cd5fa9cca89c4c304675ca658efc4e64a66c716b7cfe1afb4b9611dbbc" +dependencies = [ + "crc32fast", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -3635,7 +4007,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -3651,6 +4023,18 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -3759,6 +4143,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pulley-interpreter" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7975f0975fa2c047bf47d617bdf716689e42ee82b159bd000ead7330d7697a1b" +dependencies = [ + "cranelift-bitset", + "log", + "pulley-macros", + "wasmtime-internal-core", +] + +[[package]] +name = "pulley-macros" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210c0386ef0ddedb337ec99b91e560ae9c341415ef75958cb39ddb537bb0c84" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "quick-xml" version = "0.38.4" @@ -4009,6 +4416,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "regalloc2" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "952ddbfc6f9f64d006c3efd8c9851a6ba2f2b944ba94730db255d55006e0ffda" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.15.5", + "log", + "rustc-hash", + "smallvec", +] + [[package]] name = "regex" version = "1.12.3" @@ -4163,6 +4584,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.4" @@ -4172,10 +4606,20 @@ dependencies = [ "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix 1.1.4", +] + [[package]] name = "rustls" version = "0.23.37" @@ -4684,6 +5128,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -4855,6 +5302,7 @@ dependencies = [ "sha2", "sqlx-core", "sqlx-mysql", + "sqlx-postgres", "sqlx-sqlite", "syn 1.0.109", "tempfile", @@ -5109,6 +5557,22 @@ dependencies = [ "version-compare", ] +[[package]] +name = "system-interface" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" +dependencies = [ + "bitflags 2.11.0", + "cap-fs-ext", + "cap-std", + "fd-lock", + "io-lifetimes", + "rustix 0.38.44", + "windows-sys 0.59.0", + "winx", +] + [[package]] name = "tao" version = "0.34.8" @@ -5164,6 +5628,12 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "tauri" version = "2.10.3" @@ -5426,7 +5896,7 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -5451,6 +5921,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -6277,7 +6756,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" +dependencies = [ + "leb128fmt", + "wasmparser 0.245.1", ] [[package]] @@ -6288,8 +6777,8 @@ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap 2.13.0", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", ] [[package]] @@ -6330,6 +6819,303 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "semver", + "serde", +] + +[[package]] +name = "wasmprinter" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41517a3716fbb8ccf46daa9c1325f760fcbff5168e75c7392288e410b91ac8" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.245.1", +] + +[[package]] +name = "wasmtime" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54fa9f298901a64ed3eae16b130f0b30c80dbb74a9e7f129a791f4e74649b917" +dependencies = [ + "addr2line", + "async-trait", + "bitflags 2.11.0", + "bumpalo", + "cc", + "cfg-if", + "encoding_rs", + "libc", + "log", + "mach2", + "memfd", + "object", + "once_cell", + "postcard", + "pulley-interpreter", + "rustix 1.1.4", + "semver", + "serde", + "serde_derive", + "smallvec", + "target-lexicon 0.13.5", + "wasmparser 0.245.1", + "wasmtime-environ", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", + "wasmtime-internal-winch", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-environ" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a3aaaa3a522f443af67a7ed8d4efa20b0c3784e1031980537fbfcb497f70a7" +dependencies = [ + "anyhow", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "log", + "object", + "postcard", + "semver", + "serde", + "serde_derive", + "sha2", + "smallvec", + "target-lexicon 0.13.5", + "wasm-encoder 0.245.1", + "wasmparser 0.245.1", + "wasmprinter", + "wasmtime-internal-component-util", + "wasmtime-internal-core", +] + +[[package]] +name = "wasmtime-internal-component-macro" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0d00d29ed90a63d2445072860a8a42d7151390157236a69bc3ae056786e9c9" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser 0.245.1", +] + +[[package]] +name = "wasmtime-internal-component-util" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acfd639ca7ab9e1cc37f053edd95bed6a7bed16370a8b2643dc7d9ef3047935" + +[[package]] +name = "wasmtime-internal-core" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e671917bb6856ae360cb59d7aaf26f1cfd042c7b924319dd06fd380739fc0b2e" +dependencies = [ + "hashbrown 0.16.1", + "libm", + "serde", +] + +[[package]] +name = "wasmtime-internal-cranelift" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dfd752e1dcf79eeeadc6f2681e2fb4a9f0b5534d18c5b9b93faccd0de2c80c" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools", + "log", + "object", + "pulley-interpreter", + "smallvec", + "target-lexicon 0.13.5", + "thiserror 2.0.18", + "wasmparser 0.245.1", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-fiber" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1e9171af643316c11d6ebe52f31f6e2a5d6d1d270de9167a7b7b6f0e3f72982" +dependencies = [ + "cc", + "cfg-if", + "libc", + "rustix 1.1.4", + "wasmtime-environ", + "wasmtime-internal-versioned-export-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-jit-debug" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe23134536b9883ffc2afcffae23f7ffbcb1791e2d9fac6d6464a37ea4c8fdd" +dependencies = [ + "cc", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3112806515fac8495883885eb8dbdde849988ae91fe6beb544c0d7c0f4c9aa" +dependencies = [ + "cfg-if", + "libc", + "wasmtime-internal-core", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-unwinder" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dafc29c6e538273fda8409335137654751bdf24beab65702b7866b0a85ee108a" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "log", + "object", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-internal-versioned-export-macros" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772f2b105b7fdd3dfb2cdf70c297baaeb96fe76a95cdc6fa516f713f04090c73" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "wasmtime-internal-winch" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d556c3b176aba3cce565b2bafcdc049e7410ac1d86bf1ef663a035d9ded0dddc" +dependencies = [ + "cranelift-codegen", + "gimli", + "log", + "object", + "target-lexicon 0.13.5", + "wasmparser 0.245.1", + "wasmtime-environ", + "wasmtime-internal-cranelift", + "winch-codegen", +] + +[[package]] +name = "wasmtime-internal-wit-bindgen" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c47507f09e68462a0ed9f351ef410584a52e01d7ec92bc588bf7fa597ce528ef" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "heck 0.5.0", + "indexmap 2.13.0", + "wit-parser 0.245.1", +] + +[[package]] +name = "wasmtime-wasi" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7fc1eb83dd0d5a368c78d2bad2660f69c03e3c07ce2dd6d1e50fc2b9ff14db" +dependencies = [ + "async-trait", + "bitflags 2.11.0", + "bytes", + "cap-fs-ext", + "cap-net-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "futures", + "io-extras", + "io-lifetimes", + "rustix 1.1.4", + "system-interface", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", + "wasmtime", + "wasmtime-wasi-io", + "wiggle", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-wasi-io" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "315fd7192148233c2c61753b5e8e2456e0ff96dd649f079148977554139ea4dc" +dependencies = [ + "async-trait", + "bytes", + "futures", + "tracing", + "wasmtime", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + [[package]] name = "web-sys" version = "0.3.91" @@ -6481,6 +7267,46 @@ dependencies = [ "wasite", ] +[[package]] +name = "wiggle" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e79079e7f5a8c034307bb5e61b2e63bc668e17d139705a7dea5afceab02510" +dependencies = [ + "bitflags 2.11.0", + "thiserror 2.0.18", + "tracing", + "wasmtime", + "wasmtime-environ", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9165e5b08a6463d247b5c1292aaab16b103d0d8f5941b60d7bc0c38125eb9ffe" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasmtime-environ", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb0a5b9476150428eead9ce1a5c83e8fd6aac29806f48c6dbf77d50a067473a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "wiggle-generate", +] + [[package]] name = "winapi" version = "0.3.9" @@ -6512,6 +7338,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winch-codegen" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca3d76763e4ddc48ede73792d067396ba5ee74c3c581db90e6638fe6b46bf52" +dependencies = [ + "cranelift-assembler-x64", + "cranelift-codegen", + "gimli", + "regalloc2", + "smallvec", + "target-lexicon 0.13.5", + "thiserror 2.0.18", + "wasmparser 0.245.1", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", +] + [[package]] name = "window-vibrancy" version = "0.6.0" @@ -7018,6 +7863,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags 2.11.0", + "windows-sys 0.59.0", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -7035,7 +7890,7 @@ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck 0.5.0", - "wit-parser", + "wit-parser 0.244.0", ] [[package]] @@ -7082,10 +7937,10 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder", + "wasm-encoder 0.244.0", "wasm-metadata", - "wasmparser", - "wit-parser", + "wasmparser 0.244.0", + "wit-parser 0.244.0", ] [[package]] @@ -7103,7 +7958,38 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser", + "wasmparser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330698718e82983499419494dd1e3d7811a457a9bf9f69734e8c5f07a2547929" +dependencies = [ + "anyhow", + "hashbrown 0.16.1", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.245.1", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror 1.0.69", + "wast", ] [[package]] @@ -7222,7 +8108,7 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix", + "rustix 1.1.4", "serde", "serde_repr", "tracing", @@ -7432,18 +8318,20 @@ dependencies = [ name = "zclaw-saas" version = "0.1.0" dependencies = [ + "aes-gcm", "anyhow", "argon2", "axum", "axum-extra", + "bytes", "chrono", "dashmap", "data-encoding", "futures", "hex", "jsonwebtoken", - "libsqlite3-sys", "rand 0.8.5", + "regex", "reqwest 0.12.28", "secrecy", "serde", @@ -7453,6 +8341,7 @@ dependencies = [ "tempfile", "thiserror 2.0.18", "tokio", + "tokio-stream", "toml 0.8.2", "totp-rs", "tower 0.4.13", @@ -7478,6 +8367,8 @@ dependencies = [ "tokio", "tracing", "uuid", + "wasmtime", + "wasmtime-wasi", "zclaw-types", ] diff --git a/Cargo.toml b/Cargo.toml index 833b7e4..da7d710 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ chrono = { version = "0.4", features = ["serde"] } uuid = { version = "1", features = ["v4", "v5", "serde"] } # Database -sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] } +sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite", "postgres"] } libsqlite3-sys = { version = "0.27", features = ["bundled"] } # HTTP client (for LLM drivers) @@ -94,6 +94,10 @@ regex = "1" # Shell parsing shlex = "1" +# WASM runtime +wasmtime = { version = "43", default-features = false, features = ["cranelift"] } +wasmtime-wasi = { version = "43" } + # Testing tempfile = "3" diff --git a/admin/next.config.js b/admin/next.config.js index 767719f..77d993c 100644 --- a/admin/next.config.js +++ b/admin/next.config.js @@ -1,4 +1,13 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + async rewrites() { + return [ + { + source: '/api/:path*', + destination: 'http://localhost:8080/api/:path*', + }, + ] + }, +} module.exports = nextConfig diff --git a/admin/package.json b/admin/package.json index c33c6ed..6197ddb 100644 --- a/admin/package.json +++ b/admin/package.json @@ -11,10 +11,10 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.2.7", - "@radix-ui/react-separator": "^1.1.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.484.0", @@ -22,6 +22,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "recharts": "^2.15.3", + "swr": "^2.4.1", "tailwind-merge": "^3.0.2" }, "devDependencies": { diff --git a/admin/pnpm-lock.yaml b/admin/pnpm-lock.yaml index 2f8b4ef..badaea8 100644 --- a/admin/pnpm-lock.yaml +++ b/admin/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: recharts: specifier: ^2.15.3 version: 2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + swr: + specifier: ^2.4.1 + version: 2.4.1(react@18.3.1) tailwind-merge: specifier: ^3.0.2 version: 3.5.0 @@ -719,6 +722,10 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} @@ -1093,6 +1100,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swr@2.4.1: + resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} @@ -1159,6 +1171,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -1744,6 +1761,8 @@ snapshots: decimal.js-light@2.5.1: {} + dequal@2.0.3: {} + detect-node-es@1.1.0: {} didyoumean@1.2.2: {} @@ -2073,6 +2092,12 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swr@2.4.1(react@18.3.1): + dependencies: + dequal: 2.0.3 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + tailwind-merge@3.5.0: {} tailwindcss@3.4.19: @@ -2151,6 +2176,10 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} victory-vendor@36.9.2: diff --git a/admin/src/app/(dashboard)/accounts/page.tsx b/admin/src/app/(dashboard)/accounts/page.tsx index 91e1a01..6d9a79d 100644 --- a/admin/src/app/(dashboard)/accounts/page.tsx +++ b/admin/src/app/(dashboard)/accounts/page.tsx @@ -1,6 +1,7 @@ 'use client' -import { useEffect, useState, useCallback } from 'react' +import { useState } from 'react' +import useSWR from 'swr' import { Search, Plus, @@ -41,6 +42,9 @@ import { import { api } from '@/lib/api-client' import { ApiRequestError } from '@/lib/api-client' import { formatDate } from '@/lib/utils' +import { ErrorBanner, EmptyState } from '@/components/ui/state' +import { TableSkeleton } from '@/components/ui/skeleton' +import { useDebounce } from '@/hooks/use-debounce' import type { AccountPublic } from '@/lib/types' const PAGE_SIZE = 20 @@ -64,14 +68,28 @@ const statusLabels: Record = { } export default function AccountsPage() { - const [accounts, setAccounts] = useState([]) - const [total, setTotal] = useState(0) const [page, setPage] = useState(1) const [search, setSearch] = useState('') const [roleFilter, setRoleFilter] = useState('all') const [statusFilter, setStatusFilter] = useState('all') - const [loading, setLoading] = useState(true) - const [error, setError] = useState('') + const [mutationError, setMutationError] = useState('') + + const debouncedSearch = useDebounce(search, 300) + + const { data, error: swrError, isLoading, mutate } = useSWR( + ['accounts', page, debouncedSearch, roleFilter, statusFilter], + () => { + const params: Record = { page, page_size: PAGE_SIZE } + if (debouncedSearch.trim()) params.search = debouncedSearch.trim() + if (roleFilter !== 'all') params.role = roleFilter + if (statusFilter !== 'all') params.status = statusFilter + return api.accounts.list(params) + }, + ) + + const accounts = data?.items ?? [] + const total = data?.total ?? 0 + const error = swrError?.message || mutationError // 编辑 Dialog const [editTarget, setEditTarget] = useState(null) @@ -82,33 +100,6 @@ export default function AccountsPage() { const [confirmTarget, setConfirmTarget] = useState<{ id: string; action: string; status: string } | null>(null) const [confirmSaving, setConfirmSaving] = useState(false) - const fetchAccounts = useCallback(async () => { - setLoading(true) - setError('') - try { - const params: Record = { page, page_size: PAGE_SIZE } - if (search.trim()) params.search = search.trim() - if (roleFilter !== 'all') params.role = roleFilter - if (statusFilter !== 'all') params.status = statusFilter - - const res = await api.accounts.list(params) - setAccounts(res.items) - setTotal(res.total) - } catch (err) { - if (err instanceof ApiRequestError) { - setError(err.body.message) - } else { - setError('加载失败') - } - } finally { - setLoading(false) - } - }, [page, search, roleFilter, statusFilter]) - - useEffect(() => { - fetchAccounts() - }, [fetchAccounts]) - const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)) function openEditDialog(account: AccountPublic) { @@ -130,10 +121,10 @@ export default function AccountsPage() { role: editForm.role as AccountPublic['role'], }) setEditTarget(null) - fetchAccounts() + mutate() } catch (err) { if (err instanceof ApiRequestError) { - setError(err.body.message) + setMutationError(err.body.message) } } finally { setEditSaving(false) @@ -157,10 +148,10 @@ export default function AccountsPage() { status: confirmTarget.status as AccountPublic['status'], }) setConfirmTarget(null) - fetchAccounts() + mutate() } catch (err) { if (err instanceof ApiRequestError) { - setError(err.body.message) + setMutationError(err.body.message) } } finally { setConfirmSaving(false) @@ -205,24 +196,13 @@ export default function AccountsPage() { {/* 错误提示 */} - {error && ( -
- {error} - -
- )} + {error && { setMutationError('') }} />} {/* 表格 */} - {loading ? ( -
- -
- ) : accounts.length === 0 ? ( -
- 暂无数据 -
+ {isLoading ? ( + + ) : error ? null : accounts.length === 0 ? ( + ) : ( <> diff --git a/admin/src/app/(dashboard)/agent-templates/page.tsx b/admin/src/app/(dashboard)/agent-templates/page.tsx new file mode 100644 index 0000000..cad5391 --- /dev/null +++ b/admin/src/app/(dashboard)/agent-templates/page.tsx @@ -0,0 +1,290 @@ +'use client' + +import { useState } from 'react' +import useSWR from 'swr' +import { api } from '@/lib/api-client' +import type { AgentTemplate } from '@/lib/types' +import { ErrorBanner, EmptyState } from '@/components/ui/state' +import { TableSkeleton } from '@/components/ui/skeleton' + +export default function AgentTemplatesPage() { + const [page, setPage] = useState(1) + const [error, setError] = useState('') + const [showCreate, setShowCreate] = useState(false) + const [editingId, setEditingId] = useState(null) + + const { data, isLoading, mutate } = useSWR( + ['agentTemplates.list', page], + () => api.agentTemplates.list({ page, page_size: 50 }), + ) + + const templates = data?.items ?? [] + const total = data?.total ?? 0 + + const handleCreate = async (e: React.FormEvent) => { + e.preventDefault() + const fd = new FormData(e.currentTarget) + try { + const tools = (fd.get('tools') as string || '').split(',').map(s => s.trim()).filter(Boolean) + const capabilities = (fd.get('capabilities') as string || '').split(',').map(s => s.trim()).filter(Boolean) + await api.agentTemplates.create({ + name: fd.get('name') as string, + description: (fd.get('description') as string) || undefined, + category: (fd.get('category') as string) || 'general', + model: (fd.get('model') as string) || undefined, + system_prompt: (fd.get('system_prompt') as string) || undefined, + tools: tools.length > 0 ? tools : undefined, + capabilities: capabilities.length > 0 ? capabilities : undefined, + temperature: (fd.get('temperature') as string) ? parseFloat(fd.get('temperature') as string) : undefined, + max_tokens: (fd.get('max_tokens') as string) ? parseInt(fd.get('max_tokens') as string, 10) : undefined, + visibility: (fd.get('visibility') as string) || 'public', + }) + setShowCreate(false) + mutate() + } catch { + setError('创建失败') + } + } + + const handleArchive = async (id: string, name: string) => { + if (!confirm(`确认归档模板 "${name}"?`)) return + try { + await api.agentTemplates.archive(id) + mutate() + } catch { + setError('归档失败') + } + } + + const statusBadge = (status: string) => { + const colors: Record = { + active: 'bg-emerald-500/20 text-emerald-400', + archived: 'bg-zinc-500/20 text-zinc-400', + } + return {status} + } + + const sourceBadge = (source: string) => { + const colors: Record = { + builtin: 'bg-blue-500/20 text-blue-400', + custom: 'bg-purple-500/20 text-purple-400', + } + return ( + + {source === 'builtin' ? '内置' : '自定义'} + + ) + } + + return ( +
+
+
+

Agent 配置模板

+

管理 Agent 配置模板,支持团队共享和一键复用

+
+ +
+ + {error && setError('')} />} + +
+
+ + + + + + + + + + + + + + + {isLoading ? ( + + + + ) : templates.length === 0 ? ( + + ) : ( + templates.map(t => ( + + + + + + + + + + + + )) + )} + +
名称分类来源模型工具数可见性状态更新时间操作
+ +
+
+ {t.name} + {t.description && ( +

{t.description}

+ )} +
+
{t.category}{sourceBadge(t.source)}{t.model || '-'}{t.tools.length}{t.visibility}{statusBadge(t.status)} + {new Date(t.updated_at).toLocaleString('zh-CN')} + + + {t.source === 'custom' && ( + + )} +
+
+ 共 {total} 个模板 +
+ + + {/* 展开详情 */} + {editingId && (() => { + const t = templates.find(t => t.id === editingId) + if (!t) return null + return ( +
+
+

{t.name} — 详情

+ +
+
+
+ 分类: + {t.category} +
+
+ 模型: + {t.model || '未指定'} +
+
+ 温度: + {t.temperature?.toFixed(2) || '默认'} +
+
+ 最大 Token: + {t.max_tokens || '未限制'} +
+
+ 工具: +
+ {t.tools.length > 0 ? t.tools.map(tool => ( + {tool} + )) : } +
+
+
+ 能力: +
+ {t.capabilities.length > 0 ? t.capabilities.map(cap => ( + {cap} + )) : } +
+
+ {t.system_prompt && ( +
+ 系统提示词: +
+                    {t.system_prompt.substring(0, 500)}{t.system_prompt.length > 500 ? '...' : ''}
+                  
+
+ )} +
+
+ ) + })()} + + {/* Create Modal */} + {showCreate && ( +
+
+

新建 Agent 模板

+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ +