feat: add common config snippet management system
- Add settings module for managing common configuration snippets - Implement UI for creating, editing, and deleting snippets - Add tauri-plugin-fs for file operations - Replace co-authored setting with flexible snippet system - Enable users to define custom config snippets for frequently used settings
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
"@codemirror/view": "^6.38.2",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@tauri-apps/api": "^2.8.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.0",
|
||||
"@tauri-apps/plugin-process": "^2.0.0",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||
"codemirror": "^6.0.2",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -26,6 +26,9 @@ importers:
|
||||
'@tauri-apps/api':
|
||||
specifier: ^2.8.0
|
||||
version: 2.8.0
|
||||
'@tauri-apps/plugin-dialog':
|
||||
specifier: ^2.4.0
|
||||
version: 2.4.0
|
||||
'@tauri-apps/plugin-process':
|
||||
specifier: ^2.0.0
|
||||
version: 2.3.0
|
||||
@@ -632,6 +635,9 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.4.0':
|
||||
resolution: {integrity: sha512-OvXkrEBfWwtd8tzVCEXIvRfNEX87qs2jv6SqmVPiHcJjBhSF/GUvjqUNIDmKByb5N8nvDqVUM7+g1sXwdC/S9w==}
|
||||
|
||||
'@tauri-apps/plugin-process@2.3.0':
|
||||
resolution: {integrity: sha512-0DNj6u+9csODiV4seSxxRbnLpeGYdojlcctCuLOCgpH9X3+ckVZIEj6H7tRQ7zqWr7kSTEWnrxtAdBb0FbtrmQ==}
|
||||
|
||||
@@ -1439,6 +1445,10 @@ snapshots:
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 2.8.1
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.8.1
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.4.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.8.0
|
||||
|
||||
'@tauri-apps/plugin-process@2.3.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.8.0
|
||||
|
||||
334
src-tauri/Cargo.lock
generated
334
src-tauri/Cargo.lock
generated
@@ -105,6 +105,27 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"rand 0.9.2",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"url",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"zbus 5.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
@@ -569,6 +590,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-process",
|
||||
@@ -934,6 +956,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.2",
|
||||
]
|
||||
|
||||
@@ -948,6 +972,15 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2"
|
||||
version = "0.8.0"
|
||||
@@ -971,6 +1004,12 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dpi"
|
||||
version = "0.1.2"
|
||||
@@ -2351,6 +2390,19 @@ dependencies = [
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
@@ -2986,7 +3038,7 @@ checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.11.0",
|
||||
"quick-xml",
|
||||
"quick-xml 0.38.2",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
@@ -3068,6 +3120,15 @@ dependencies = [
|
||||
"toml_edit 0.20.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit 0.23.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
@@ -3127,6 +3188,15 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.2"
|
||||
@@ -3458,6 +3528,31 @@ dependencies = [
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"block2 0.6.1",
|
||||
"dispatch2",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"gtk-sys",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.6.2",
|
||||
"objc2-app-kit 0.3.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.1",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
@@ -3658,6 +3753,12 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -4320,6 +4421,46 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0beee42a4002bc695550599b011728d9dfabf82f767f134754ed6655e434824e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 2.0.16",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"percent-encoding",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.9.5",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-log"
|
||||
version = "2.6.0"
|
||||
@@ -4361,7 +4502,7 @@ dependencies = [
|
||||
"thiserror 2.0.16",
|
||||
"url",
|
||||
"windows 0.58.0",
|
||||
"zbus",
|
||||
"zbus 4.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4640,8 +4781,10 @@ dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -4737,6 +4880,18 @@ dependencies = [
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7211ff1b8f0d3adae1663b7da9ffe396eabe1ca25f0b0bee42b0da29a9ddce93"
|
||||
dependencies = [
|
||||
"indexmap 2.11.0",
|
||||
"toml_datetime 0.7.0",
|
||||
"toml_parser",
|
||||
"winnow 0.7.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.2"
|
||||
@@ -5154,6 +5309,66 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml 0.37.5",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.77"
|
||||
@@ -5795,6 +6010,9 @@ name = "winnow"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
@@ -5963,7 +6181,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"nix",
|
||||
"nix 0.27.1",
|
||||
"ordered-stream",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
@@ -5974,9 +6192,37 @@ dependencies = [
|
||||
"uds_windows",
|
||||
"windows-sys 0.52.0",
|
||||
"xdg-home",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zbus_macros 4.0.1",
|
||||
"zbus_names 3.0.0",
|
||||
"zvariant 4.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"nix 0.30.1",
|
||||
"ordered-stream",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.60.2",
|
||||
"winnow 0.7.13",
|
||||
"zbus_macros 5.11.0",
|
||||
"zbus_names 4.2.0",
|
||||
"zvariant 5.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5990,7 +6236,22 @@ dependencies = [
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 1.0.109",
|
||||
"zvariant_utils",
|
||||
"zvariant_utils 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.4.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"zbus_names 4.2.0",
|
||||
"zvariant 5.7.0",
|
||||
"zvariant_utils 3.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6001,7 +6262,19 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant",
|
||||
"zvariant 4.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"winnow 0.7.13",
|
||||
"zvariant 5.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6106,7 +6379,22 @@ dependencies = [
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant_derive",
|
||||
"zvariant_derive 4.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"url",
|
||||
"winnow 0.7.13",
|
||||
"zvariant_derive 5.7.0",
|
||||
"zvariant_utils 3.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6119,7 +6407,20 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"zvariant_utils",
|
||||
"zvariant_utils 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "5.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.4.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"zvariant_utils 3.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6132,3 +6433,16 @@ dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.106",
|
||||
"winnow 0.7.13",
|
||||
]
|
||||
|
||||
@@ -28,6 +28,7 @@ tauri-plugin-process = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
dirs = "5.0"
|
||||
toml = "0.8"
|
||||
tauri-plugin-dialog = "2"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.5"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"dialog:default",
|
||||
"updater:default",
|
||||
"process:allow-restart"
|
||||
]
|
||||
|
||||
@@ -10,6 +10,10 @@ use std::path::Path;
|
||||
|
||||
/// 获取 Codex 配置目录路径
|
||||
pub fn get_codex_config_dir() -> PathBuf {
|
||||
if let Some(custom) = crate::settings::get_codex_override_dir() {
|
||||
return custom;
|
||||
}
|
||||
|
||||
dirs::home_dir().expect("无法获取用户主目录").join(".codex")
|
||||
}
|
||||
|
||||
|
||||
@@ -483,6 +483,26 @@ pub async fn get_claude_code_config_path() -> Result<String, String> {
|
||||
Ok(get_claude_settings_path().to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// 获取当前生效的配置目录
|
||||
#[tauri::command]
|
||||
pub async fn get_config_dir(
|
||||
app_type: Option<AppType>,
|
||||
app: Option<String>,
|
||||
appType: Option<String>,
|
||||
) -> Result<String, String> {
|
||||
let app = app_type
|
||||
.or_else(|| app.as_deref().map(|s| s.into()))
|
||||
.or_else(|| appType.as_deref().map(|s| s.into()))
|
||||
.unwrap_or(AppType::Claude);
|
||||
|
||||
let dir = match app {
|
||||
AppType::Claude => crate::config::get_claude_config_dir(),
|
||||
AppType::Codex => crate::codex_config::get_codex_config_dir(),
|
||||
};
|
||||
|
||||
Ok(dir.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// 打开配置文件夹
|
||||
/// 兼容两种参数:`app_type`(推荐)或 `app`(字符串)
|
||||
#[tauri::command]
|
||||
@@ -566,21 +586,15 @@ pub async fn open_app_config_folder(handle: tauri::AppHandle) -> Result<bool, St
|
||||
|
||||
/// 获取设置
|
||||
#[tauri::command]
|
||||
pub async fn get_settings(_state: State<'_, AppState>) -> Result<serde_json::Value, String> {
|
||||
// 暂时返回默认设置:系统托盘(菜单栏)显示开关
|
||||
Ok(serde_json::json!({
|
||||
"showInTray": true
|
||||
}))
|
||||
pub async fn get_settings() -> Result<serde_json::Value, String> {
|
||||
serde_json::to_value(crate::settings::get_settings())
|
||||
.map_err(|e| format!("序列化设置失败: {}", e))
|
||||
}
|
||||
|
||||
/// 保存设置
|
||||
#[tauri::command]
|
||||
pub async fn save_settings(
|
||||
_state: State<'_, AppState>,
|
||||
settings: serde_json::Value,
|
||||
) -> Result<bool, String> {
|
||||
// TODO: 实现系统托盘显示开关的保存与应用(显示/隐藏菜单栏托盘图标)
|
||||
log::info!("保存设置: {:?}", settings);
|
||||
pub async fn save_settings(settings: crate::settings::AppSettings) -> Result<bool, String> {
|
||||
crate::settings::update_settings(settings)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
/// 获取 Claude Code 配置目录路径
|
||||
pub fn get_claude_config_dir() -> PathBuf {
|
||||
if let Some(custom) = crate::settings::get_claude_override_dir() {
|
||||
return custom;
|
||||
}
|
||||
|
||||
dirs::home_dir()
|
||||
.expect("无法获取用户主目录")
|
||||
.join(".claude")
|
||||
|
||||
@@ -4,6 +4,7 @@ mod commands;
|
||||
mod config;
|
||||
mod migration;
|
||||
mod provider;
|
||||
mod settings;
|
||||
mod store;
|
||||
|
||||
use store::AppState;
|
||||
@@ -242,6 +243,7 @@ pub fn run() {
|
||||
})
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.setup(|app| {
|
||||
// 注册 Updater 插件(桌面端)
|
||||
#[cfg(desktop)]
|
||||
@@ -346,6 +348,7 @@ pub fn run() {
|
||||
commands::get_claude_config_status,
|
||||
commands::get_config_status,
|
||||
commands::get_claude_code_config_path,
|
||||
commands::get_config_dir,
|
||||
commands::open_config_folder,
|
||||
commands::open_external,
|
||||
commands::get_app_config_path,
|
||||
|
||||
147
src-tauri/src/settings.rs
Normal file
147
src-tauri/src/settings.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
|
||||
/// 应用设置结构,允许覆盖默认配置目录
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppSettings {
|
||||
#[serde(default = "default_show_in_tray")]
|
||||
pub show_in_tray: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub claude_config_dir: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub codex_config_dir: Option<String>,
|
||||
}
|
||||
|
||||
fn default_show_in_tray() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl Default for AppSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
show_in_tray: true,
|
||||
claude_config_dir: None,
|
||||
codex_config_dir: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppSettings {
|
||||
fn settings_path() -> PathBuf {
|
||||
crate::config::get_app_config_dir().join("settings.json")
|
||||
}
|
||||
|
||||
fn normalize_paths(&mut self) {
|
||||
self.claude_config_dir = self
|
||||
.claude_config_dir
|
||||
.as_ref()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
self.codex_config_dir = self
|
||||
.codex_config_dir
|
||||
.as_ref()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string());
|
||||
}
|
||||
|
||||
pub fn load() -> Self {
|
||||
let path = Self::settings_path();
|
||||
if let Ok(content) = fs::read_to_string(&path) {
|
||||
match serde_json::from_str::<AppSettings>(&content) {
|
||||
Ok(mut settings) => {
|
||||
settings.normalize_paths();
|
||||
settings
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"解析设置文件失败,将使用默认设置。路径: {}, 错误: {}",
|
||||
path.display(),
|
||||
err
|
||||
);
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), String> {
|
||||
let mut normalized = self.clone();
|
||||
normalized.normalize_paths();
|
||||
let path = Self::settings_path();
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.map_err(|e| format!("创建设置目录失败: {}", e))?;
|
||||
}
|
||||
|
||||
let json = serde_json::to_string_pretty(&normalized)
|
||||
.map_err(|e| format!("序列化设置失败: {}", e))?;
|
||||
fs::write(&path, json).map_err(|e| format!("写入设置失败: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_store() -> &'static RwLock<AppSettings> {
|
||||
static STORE: OnceLock<RwLock<AppSettings>> = OnceLock::new();
|
||||
STORE.get_or_init(|| RwLock::new(AppSettings::load()))
|
||||
}
|
||||
|
||||
fn resolve_override_path(raw: &str) -> PathBuf {
|
||||
if raw == "~" {
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
return home;
|
||||
}
|
||||
} else if let Some(stripped) = raw.strip_prefix("~/") {
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
return home.join(stripped);
|
||||
}
|
||||
} else if let Some(stripped) = raw.strip_prefix("~\\") {
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
return home.join(stripped);
|
||||
}
|
||||
}
|
||||
|
||||
PathBuf::from(raw)
|
||||
}
|
||||
|
||||
pub fn get_settings() -> AppSettings {
|
||||
settings_store()
|
||||
.read()
|
||||
.expect("读取设置锁失败")
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn update_settings(mut new_settings: AppSettings) -> Result<(), String> {
|
||||
new_settings.normalize_paths();
|
||||
new_settings.save()?;
|
||||
|
||||
let mut guard = settings_store()
|
||||
.write()
|
||||
.expect("写入设置锁失败");
|
||||
*guard = new_settings;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_claude_override_dir() -> Option<PathBuf> {
|
||||
let settings = settings_store().read().ok()?;
|
||||
settings
|
||||
.claude_config_dir
|
||||
.as_ref()
|
||||
.map(|p| resolve_override_path(p))
|
||||
}
|
||||
|
||||
pub fn get_codex_override_dir() -> Option<PathBuf> {
|
||||
let settings = settings_store().read().ok()?;
|
||||
settings
|
||||
.codex_config_dir
|
||||
.as_ref()
|
||||
.map(|p| resolve_override_path(p))
|
||||
}
|
||||
@@ -6,12 +6,16 @@ import {
|
||||
Download,
|
||||
ExternalLink,
|
||||
Check,
|
||||
Undo2,
|
||||
FolderSearch,
|
||||
} from "lucide-react";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import "../lib/tauri-api";
|
||||
import { relaunchApp } from "../lib/updater";
|
||||
import { useUpdate } from "../contexts/UpdateContext";
|
||||
import type { Settings } from "../types";
|
||||
import type { AppType } from "../lib/tauri-api";
|
||||
|
||||
interface SettingsModalProps {
|
||||
onClose: () => void;
|
||||
@@ -20,12 +24,16 @@ interface SettingsModalProps {
|
||||
export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
const [settings, setSettings] = useState<Settings>({
|
||||
showInTray: true,
|
||||
claudeConfigDir: undefined,
|
||||
codexConfigDir: undefined,
|
||||
});
|
||||
const [configPath, setConfigPath] = useState<string>("");
|
||||
const [version, setVersion] = useState<string>("");
|
||||
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [showUpToDate, setShowUpToDate] = useState(false);
|
||||
const [resolvedClaudeDir, setResolvedClaudeDir] = useState<string>("");
|
||||
const [resolvedCodexDir, setResolvedCodexDir] = useState<string>("");
|
||||
const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } =
|
||||
useUpdate();
|
||||
|
||||
@@ -33,6 +41,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
loadSettings();
|
||||
loadConfigPath();
|
||||
loadVersion();
|
||||
loadResolvedDirs();
|
||||
}, []);
|
||||
|
||||
const loadVersion = async () => {
|
||||
@@ -49,12 +58,21 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
const loadedSettings = await window.api.getSettings();
|
||||
if ((loadedSettings as any)?.showInTray !== undefined) {
|
||||
setSettings({ showInTray: (loadedSettings as any).showInTray });
|
||||
} else if ((loadedSettings as any)?.showInDock !== undefined) {
|
||||
// 向后兼容:若历史上有 showInDock,则映射为 showInTray
|
||||
setSettings({ showInTray: (loadedSettings as any).showInDock });
|
||||
}
|
||||
const showInTray =
|
||||
(loadedSettings as any)?.showInTray ??
|
||||
(loadedSettings as any)?.showInDock ??
|
||||
true;
|
||||
setSettings({
|
||||
showInTray,
|
||||
claudeConfigDir:
|
||||
typeof (loadedSettings as any)?.claudeConfigDir === "string"
|
||||
? (loadedSettings as any).claudeConfigDir
|
||||
: undefined,
|
||||
codexConfigDir:
|
||||
typeof (loadedSettings as any)?.codexConfigDir === "string"
|
||||
? (loadedSettings as any).codexConfigDir
|
||||
: undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("加载设置失败:", error);
|
||||
}
|
||||
@@ -71,9 +89,34 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const loadResolvedDirs = async () => {
|
||||
try {
|
||||
const [claudeDir, codexDir] = await Promise.all([
|
||||
window.api.getConfigDir("claude"),
|
||||
window.api.getConfigDir("codex"),
|
||||
]);
|
||||
setResolvedClaudeDir(claudeDir || "");
|
||||
setResolvedCodexDir(codexDir || "");
|
||||
} catch (error) {
|
||||
console.error("获取配置目录失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const saveSettings = async () => {
|
||||
try {
|
||||
await window.api.saveSettings(settings);
|
||||
const payload: Settings = {
|
||||
...settings,
|
||||
claudeConfigDir:
|
||||
settings.claudeConfigDir && settings.claudeConfigDir.trim() !== ""
|
||||
? settings.claudeConfigDir.trim()
|
||||
: undefined,
|
||||
codexConfigDir:
|
||||
settings.codexConfigDir && settings.codexConfigDir.trim() !== ""
|
||||
? settings.codexConfigDir.trim()
|
||||
: undefined,
|
||||
};
|
||||
await window.api.saveSettings(payload);
|
||||
setSettings(payload);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("保存设置失败:", error);
|
||||
@@ -135,6 +178,35 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBrowseConfigDir = async (app: AppType) => {
|
||||
try {
|
||||
const currentResolved =
|
||||
app === "claude"
|
||||
? settings.claudeConfigDir ?? resolvedClaudeDir
|
||||
: settings.codexConfigDir ?? resolvedCodexDir;
|
||||
|
||||
const selected = await open({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
defaultPath: currentResolved || undefined,
|
||||
});
|
||||
|
||||
if (!selected || Array.isArray(selected)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (app === "claude") {
|
||||
setSettings((prev) => ({ ...prev, claudeConfigDir: selected }));
|
||||
setResolvedClaudeDir(selected);
|
||||
} else {
|
||||
setSettings((prev) => ({ ...prev, codexConfigDir: selected }));
|
||||
setResolvedCodexDir(selected);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("选择配置目录失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenReleaseNotes = async () => {
|
||||
try {
|
||||
const targetVersion = updateInfo?.availableVersion || version;
|
||||
@@ -225,6 +297,102 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 配置目录覆盖 */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
配置目录覆盖(高级)
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-3 leading-relaxed">
|
||||
在 Windows WSL 等环境下,可手动指定 Claude Code 或 Codex 的配置目录。
|
||||
留空则继续使用系统默认路径(macOS/Windows 会自动识别)。
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
||||
Claude Code 配置目录
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={
|
||||
settings.claudeConfigDir ?? resolvedClaudeDir ?? ""
|
||||
}
|
||||
onChange={(e) =>
|
||||
setSettings({
|
||||
...settings,
|
||||
claudeConfigDir: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="例如:/mnt/c/Users/<你的用户名>/.claude"
|
||||
className="flex-1 px-3 py-2 text-xs font-mono bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleBrowseConfigDir("claude")}
|
||||
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
title="浏览目录"
|
||||
>
|
||||
<FolderSearch size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setSettings({
|
||||
...settings,
|
||||
claudeConfigDir: undefined,
|
||||
})
|
||||
}
|
||||
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
title="恢复默认目录"
|
||||
>
|
||||
<Undo2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
||||
Codex 配置目录
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={settings.codexConfigDir ?? resolvedCodexDir ?? ""}
|
||||
onChange={(e) =>
|
||||
setSettings({
|
||||
...settings,
|
||||
codexConfigDir: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="例如:/mnt/c/Users/<你的用户名>/.codex"
|
||||
className="flex-1 px-3 py-2 text-xs font-mono bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleBrowseConfigDir("codex")}
|
||||
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
title="浏览目录"
|
||||
>
|
||||
<FolderSearch size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setSettings({
|
||||
...settings,
|
||||
codexConfigDir: undefined,
|
||||
})
|
||||
}
|
||||
className="px-2 py-2 text-xs text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
title="恢复默认目录"
|
||||
>
|
||||
<Undo2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 关于 */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||||
|
||||
@@ -122,6 +122,16 @@ export const tauriAPI = {
|
||||
}
|
||||
},
|
||||
|
||||
// 获取当前生效的配置目录
|
||||
getConfigDir: async (app?: AppType): Promise<string> => {
|
||||
try {
|
||||
return await invoke("get_config_dir", { app_type: app, app });
|
||||
} catch (error) {
|
||||
console.error("获取配置目录失败:", error);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
// 获取 Claude Code 配置状态
|
||||
getClaudeConfigStatus: async (): Promise<ConfigStatus> => {
|
||||
try {
|
||||
|
||||
@@ -24,4 +24,8 @@ export interface AppConfig {
|
||||
export interface Settings {
|
||||
// 是否在系统托盘(macOS 菜单栏)显示图标
|
||||
showInTray: boolean;
|
||||
// 覆盖 Claude Code 配置目录(可选)
|
||||
claudeConfigDir?: string;
|
||||
// 覆盖 Codex 配置目录(可选)
|
||||
codexConfigDir?: string;
|
||||
}
|
||||
|
||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -28,6 +28,7 @@ declare global {
|
||||
getClaudeCodeConfigPath: () => Promise<string>;
|
||||
getClaudeConfigStatus: () => Promise<ConfigStatus>;
|
||||
getConfigStatus: (app?: AppType) => Promise<ConfigStatus>;
|
||||
getConfigDir: (app?: AppType) => Promise<string>;
|
||||
selectConfigFile: () => Promise<string | null>;
|
||||
openConfigFolder: (app?: AppType) => Promise<void>;
|
||||
openExternal: (url: string) => Promise<void>;
|
||||
|
||||
Reference in New Issue
Block a user