feat: add config directory override support for WSL
- Add persistent app settings with custom Claude Code and Codex config directories - Add config directory override UI in settings modal with manual input, browse, and reset options - Integrate tauri-plugin-dialog for native directory picker - Support WSL and other special environments where config paths need manual specification Changes: - settings.rs: Implement settings load/save and directory override logic - SettingsModal: Add config directory override UI components - API: Add get_config_dir and pick_directory commands
This commit is contained in:
@@ -32,10 +32,11 @@
|
|||||||
"@codemirror/view": "^6.38.2",
|
"@codemirror/view": "^6.38.2",
|
||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"@tauri-apps/api": "^2.8.0",
|
"@tauri-apps/api": "^2.8.0",
|
||||||
|
"@tauri-apps/plugin-dialog": "^2.4.0",
|
||||||
"@tauri-apps/plugin-process": "^2.0.0",
|
"@tauri-apps/plugin-process": "^2.0.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||||
"jsonc-parser": "^3.2.1",
|
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
|
"jsonc-parser": "^3.2.1",
|
||||||
"lucide-react": "^0.542.0",
|
"lucide-react": "^0.542.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -26,6 +26,9 @@ importers:
|
|||||||
'@tauri-apps/api':
|
'@tauri-apps/api':
|
||||||
specifier: ^2.8.0
|
specifier: ^2.8.0
|
||||||
version: 2.8.0
|
version: 2.8.0
|
||||||
|
'@tauri-apps/plugin-dialog':
|
||||||
|
specifier: ^2.4.0
|
||||||
|
version: 2.4.0
|
||||||
'@tauri-apps/plugin-process':
|
'@tauri-apps/plugin-process':
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
@@ -635,6 +638,9 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
'@tauri-apps/plugin-dialog@2.4.0':
|
||||||
|
resolution: {integrity: sha512-OvXkrEBfWwtd8tzVCEXIvRfNEX87qs2jv6SqmVPiHcJjBhSF/GUvjqUNIDmKByb5N8nvDqVUM7+g1sXwdC/S9w==}
|
||||||
|
|
||||||
'@tauri-apps/plugin-process@2.3.0':
|
'@tauri-apps/plugin-process@2.3.0':
|
||||||
resolution: {integrity: sha512-0DNj6u+9csODiV4seSxxRbnLpeGYdojlcctCuLOCgpH9X3+ckVZIEj6H7tRQ7zqWr7kSTEWnrxtAdBb0FbtrmQ==}
|
resolution: {integrity: sha512-0DNj6u+9csODiV4seSxxRbnLpeGYdojlcctCuLOCgpH9X3+ckVZIEj6H7tRQ7zqWr7kSTEWnrxtAdBb0FbtrmQ==}
|
||||||
|
|
||||||
@@ -1445,6 +1451,10 @@ snapshots:
|
|||||||
'@tauri-apps/cli-win32-ia32-msvc': 2.8.1
|
'@tauri-apps/cli-win32-ia32-msvc': 2.8.1
|
||||||
'@tauri-apps/cli-win32-x64-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':
|
'@tauri-apps/plugin-process@2.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.8.0
|
'@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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
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]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
@@ -569,6 +590,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"tauri-plugin-process",
|
"tauri-plugin-process",
|
||||||
@@ -934,6 +956,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.3",
|
"bitflags 2.9.3",
|
||||||
|
"block2 0.6.1",
|
||||||
|
"libc",
|
||||||
"objc2 0.6.2",
|
"objc2 0.6.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -948,6 +972,15 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"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]]
|
[[package]]
|
||||||
name = "dlopen2"
|
name = "dlopen2"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -971,6 +1004,12 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"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]]
|
[[package]]
|
||||||
name = "dpi"
|
name = "dpi"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -2351,6 +2390,19 @@ dependencies = [
|
|||||||
"memoffset",
|
"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]]
|
[[package]]
|
||||||
name = "nodrop"
|
name = "nodrop"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
@@ -2986,7 +3038,7 @@ checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"indexmap 2.11.0",
|
"indexmap 2.11.0",
|
||||||
"quick-xml",
|
"quick-xml 0.38.2",
|
||||||
"serde",
|
"serde",
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
@@ -3068,6 +3120,15 @@ dependencies = [
|
|||||||
"toml_edit 0.20.2",
|
"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]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -3127,6 +3188,15 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"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]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.38.2"
|
version = "0.38.2"
|
||||||
@@ -3458,6 +3528,31 @@ dependencies = [
|
|||||||
"webpki-roots",
|
"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]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
@@ -3658,6 +3753,12 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"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]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -4320,6 +4421,46 @@ dependencies = [
|
|||||||
"walkdir",
|
"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]]
|
[[package]]
|
||||||
name = "tauri-plugin-log"
|
name = "tauri-plugin-log"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
@@ -4361,7 +4502,7 @@ dependencies = [
|
|||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
"url",
|
"url",
|
||||||
"windows 0.58.0",
|
"windows 0.58.0",
|
||||||
"zbus",
|
"zbus 4.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4640,8 +4781,10 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tracing",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4737,6 +4880,18 @@ dependencies = [
|
|||||||
"winnow 0.5.40",
|
"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]]
|
[[package]]
|
||||||
name = "toml_parser"
|
name = "toml_parser"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -5154,6 +5309,66 @@ dependencies = [
|
|||||||
"web-sys",
|
"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]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.77"
|
version = "0.3.77"
|
||||||
@@ -5795,6 +6010,9 @@ name = "winnow"
|
|||||||
version = "0.7.13"
|
version = "0.7.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
@@ -5963,7 +6181,7 @@ dependencies = [
|
|||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hex",
|
"hex",
|
||||||
"nix",
|
"nix 0.27.1",
|
||||||
"ordered-stream",
|
"ordered-stream",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5974,9 +6192,37 @@ dependencies = [
|
|||||||
"uds_windows",
|
"uds_windows",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
"xdg-home",
|
"xdg-home",
|
||||||
"zbus_macros",
|
"zbus_macros 4.0.1",
|
||||||
"zbus_names",
|
"zbus_names 3.0.0",
|
||||||
"zvariant",
|
"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]]
|
[[package]]
|
||||||
@@ -5990,7 +6236,22 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"regex",
|
"regex",
|
||||||
"syn 1.0.109",
|
"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]]
|
[[package]]
|
||||||
@@ -6001,7 +6262,19 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"static_assertions",
|
"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]]
|
[[package]]
|
||||||
@@ -6106,7 +6379,22 @@ dependencies = [
|
|||||||
"enumflags2",
|
"enumflags2",
|
||||||
"serde",
|
"serde",
|
||||||
"static_assertions",
|
"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]]
|
[[package]]
|
||||||
@@ -6119,7 +6407,20 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"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]]
|
[[package]]
|
||||||
@@ -6132,3 +6433,16 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"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",
|
||||||
|
]
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ tauri-plugin-log = "2"
|
|||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
tauri-plugin-updater = "2"
|
tauri-plugin-updater = "2"
|
||||||
|
tauri-plugin-dialog = "2"
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"core:default",
|
"core:default",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
"updater:default",
|
"updater:default",
|
||||||
"process:allow-restart"
|
"process:allow-restart",
|
||||||
|
"dialog:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ use std::path::Path;
|
|||||||
|
|
||||||
/// 获取 Codex 配置目录路径
|
/// 获取 Codex 配置目录路径
|
||||||
pub fn get_codex_config_dir() -> PathBuf {
|
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")
|
dirs::home_dir().expect("无法获取用户主目录").join(".codex")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
|
use tauri_plugin_dialog::DialogExt;
|
||||||
|
|
||||||
use crate::app_config::AppType;
|
use crate::app_config::AppType;
|
||||||
use crate::codex_config;
|
use crate::codex_config;
|
||||||
use crate::config::{get_claude_settings_path, ConfigStatus};
|
use crate::config::{self, get_claude_settings_path, ConfigStatus};
|
||||||
use crate::vscode;
|
|
||||||
use crate::config;
|
|
||||||
use crate::provider::Provider;
|
use crate::provider::Provider;
|
||||||
use crate::store::AppState;
|
use crate::store::AppState;
|
||||||
|
use crate::vscode;
|
||||||
|
|
||||||
fn validate_provider_settings(app_type: &AppType, provider: &Provider) -> Result<(), String> {
|
fn validate_provider_settings(app_type: &AppType, provider: &Provider) -> Result<(), String> {
|
||||||
match app_type {
|
match app_type {
|
||||||
@@ -520,6 +520,26 @@ pub async fn get_claude_code_config_path() -> Result<String, String> {
|
|||||||
Ok(get_claude_settings_path().to_string_lossy().to_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 => config::get_claude_config_dir(),
|
||||||
|
AppType::Codex => codex_config::get_codex_config_dir(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(dir.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// 打开配置文件夹
|
/// 打开配置文件夹
|
||||||
/// 兼容两种参数:`app_type`(推荐)或 `app`(字符串)
|
/// 兼容两种参数:`app_type`(推荐)或 `app`(字符串)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -553,6 +573,38 @@ pub async fn open_config_folder(
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 弹出系统目录选择器并返回用户选择的路径
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn pick_directory(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
default_path: Option<String>,
|
||||||
|
) -> Result<Option<String>, String> {
|
||||||
|
let initial = default_path
|
||||||
|
.map(|p| p.trim().to_string())
|
||||||
|
.filter(|p| !p.is_empty());
|
||||||
|
|
||||||
|
let result = tauri::async_runtime::spawn_blocking(move || {
|
||||||
|
let mut builder = app.dialog().file();
|
||||||
|
if let Some(path) = initial {
|
||||||
|
builder = builder.set_directory(path);
|
||||||
|
}
|
||||||
|
builder.blocking_pick_folder()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("弹出目录选择器失败: {}", e))?;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Some(file_path) => {
|
||||||
|
let resolved = file_path
|
||||||
|
.simplified()
|
||||||
|
.into_path()
|
||||||
|
.map_err(|e| format!("解析选择的目录失败: {}", e))?;
|
||||||
|
Ok(Some(resolved.to_string_lossy().to_string()))
|
||||||
|
}
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 打开外部链接
|
/// 打开外部链接
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn open_external(app: tauri::AppHandle, url: String) -> Result<bool, String> {
|
pub async fn open_external(app: tauri::AppHandle, url: String) -> Result<bool, String> {
|
||||||
@@ -603,21 +655,15 @@ pub async fn open_app_config_folder(handle: tauri::AppHandle) -> Result<bool, St
|
|||||||
|
|
||||||
/// 获取设置
|
/// 获取设置
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_settings(_state: State<'_, AppState>) -> Result<serde_json::Value, String> {
|
pub async fn get_settings() -> Result<serde_json::Value, String> {
|
||||||
// 暂时返回默认设置:系统托盘(菜单栏)显示开关
|
serde_json::to_value(crate::settings::get_settings())
|
||||||
Ok(serde_json::json!({
|
.map_err(|e| format!("序列化设置失败: {}", e))
|
||||||
"showInTray": true
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 保存设置
|
/// 保存设置
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn save_settings(
|
pub async fn save_settings(settings: crate::settings::AppSettings) -> Result<bool, String> {
|
||||||
_state: State<'_, AppState>,
|
crate::settings::update_settings(settings)?;
|
||||||
settings: serde_json::Value,
|
|
||||||
) -> Result<bool, String> {
|
|
||||||
// TODO: 实现系统托盘显示开关的保存与应用(显示/隐藏菜单栏托盘图标)
|
|
||||||
log::info!("保存设置: {:?}", settings);
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
/// 获取 Claude Code 配置目录路径
|
/// 获取 Claude Code 配置目录路径
|
||||||
pub fn get_claude_config_dir() -> PathBuf {
|
pub fn get_claude_config_dir() -> PathBuf {
|
||||||
|
if let Some(custom) = crate::settings::get_claude_override_dir() {
|
||||||
|
return custom;
|
||||||
|
}
|
||||||
|
|
||||||
dirs::home_dir()
|
dirs::home_dir()
|
||||||
.expect("无法获取用户主目录")
|
.expect("无法获取用户主目录")
|
||||||
.join(".claude")
|
.join(".claude")
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ mod app_config;
|
|||||||
mod codex_config;
|
mod codex_config;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod config;
|
mod config;
|
||||||
mod vscode;
|
|
||||||
mod migration;
|
mod migration;
|
||||||
mod provider;
|
mod provider;
|
||||||
|
mod settings;
|
||||||
mod store;
|
mod store;
|
||||||
|
mod vscode;
|
||||||
|
|
||||||
use store::AppState;
|
use store::AppState;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -246,6 +247,7 @@ pub fn run() {
|
|||||||
_ => {}
|
_ => {}
|
||||||
})
|
})
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
// 注册 Updater 插件(桌面端)
|
// 注册 Updater 插件(桌面端)
|
||||||
@@ -351,7 +353,9 @@ pub fn run() {
|
|||||||
commands::get_claude_config_status,
|
commands::get_claude_config_status,
|
||||||
commands::get_config_status,
|
commands::get_config_status,
|
||||||
commands::get_claude_code_config_path,
|
commands::get_claude_code_config_path,
|
||||||
|
commands::get_config_dir,
|
||||||
commands::open_config_folder,
|
commands::open_config_folder,
|
||||||
|
commands::pick_directory,
|
||||||
commands::open_external,
|
commands::open_external,
|
||||||
commands::get_app_config_path,
|
commands::get_app_config_path,
|
||||||
commands::open_app_config_folder,
|
commands::open_app_config_folder,
|
||||||
|
|||||||
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,
|
Download,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Check,
|
Check,
|
||||||
|
Undo2,
|
||||||
|
FolderSearch,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { getVersion } from "@tauri-apps/api/app";
|
import { getVersion } from "@tauri-apps/api/app";
|
||||||
|
import { homeDir, join } from "@tauri-apps/api/path";
|
||||||
import "../lib/tauri-api";
|
import "../lib/tauri-api";
|
||||||
import { relaunchApp } from "../lib/updater";
|
import { relaunchApp } from "../lib/updater";
|
||||||
import { useUpdate } from "../contexts/UpdateContext";
|
import { useUpdate } from "../contexts/UpdateContext";
|
||||||
import type { Settings } from "../types";
|
import type { Settings } from "../types";
|
||||||
|
import type { AppType } from "../lib/tauri-api";
|
||||||
import { isLinux } from "../lib/platform";
|
import { isLinux } from "../lib/platform";
|
||||||
|
|
||||||
interface SettingsModalProps {
|
interface SettingsModalProps {
|
||||||
@@ -21,12 +25,16 @@ interface SettingsModalProps {
|
|||||||
export default function SettingsModal({ onClose }: SettingsModalProps) {
|
export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||||
const [settings, setSettings] = useState<Settings>({
|
const [settings, setSettings] = useState<Settings>({
|
||||||
showInTray: true,
|
showInTray: true,
|
||||||
|
claudeConfigDir: undefined,
|
||||||
|
codexConfigDir: undefined,
|
||||||
});
|
});
|
||||||
const [configPath, setConfigPath] = useState<string>("");
|
const [configPath, setConfigPath] = useState<string>("");
|
||||||
const [version, setVersion] = useState<string>("");
|
const [version, setVersion] = useState<string>("");
|
||||||
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
|
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
|
||||||
const [isDownloading, setIsDownloading] = useState(false);
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
const [showUpToDate, setShowUpToDate] = useState(false);
|
const [showUpToDate, setShowUpToDate] = useState(false);
|
||||||
|
const [resolvedClaudeDir, setResolvedClaudeDir] = useState<string>("");
|
||||||
|
const [resolvedCodexDir, setResolvedCodexDir] = useState<string>("");
|
||||||
const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } =
|
const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } =
|
||||||
useUpdate();
|
useUpdate();
|
||||||
|
|
||||||
@@ -34,6 +42,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
loadSettings();
|
loadSettings();
|
||||||
loadConfigPath();
|
loadConfigPath();
|
||||||
loadVersion();
|
loadVersion();
|
||||||
|
loadResolvedDirs();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadVersion = async () => {
|
const loadVersion = async () => {
|
||||||
@@ -50,12 +59,21 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
const loadSettings = async () => {
|
const loadSettings = async () => {
|
||||||
try {
|
try {
|
||||||
const loadedSettings = await window.api.getSettings();
|
const loadedSettings = await window.api.getSettings();
|
||||||
if ((loadedSettings as any)?.showInTray !== undefined) {
|
const showInTray =
|
||||||
setSettings({ showInTray: (loadedSettings as any).showInTray });
|
(loadedSettings as any)?.showInTray ??
|
||||||
} else if ((loadedSettings as any)?.showInDock !== undefined) {
|
(loadedSettings as any)?.showInDock ??
|
||||||
// 向后兼容:若历史上有 showInDock,则映射为 showInTray
|
true;
|
||||||
setSettings({ showInTray: (loadedSettings as any).showInDock });
|
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) {
|
} catch (error) {
|
||||||
console.error("加载设置失败:", error);
|
console.error("加载设置失败:", error);
|
||||||
}
|
}
|
||||||
@@ -72,9 +90,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 () => {
|
const saveSettings = async () => {
|
||||||
try {
|
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();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("保存设置失败:", error);
|
console.error("保存设置失败:", error);
|
||||||
@@ -136,6 +179,68 @@ 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 window.api.selectConfigDirectory(currentResolved);
|
||||||
|
|
||||||
|
if (!selected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitized = selected.trim();
|
||||||
|
|
||||||
|
if (sanitized === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app === "claude") {
|
||||||
|
setSettings((prev) => ({ ...prev, claudeConfigDir: sanitized }));
|
||||||
|
setResolvedClaudeDir(sanitized);
|
||||||
|
} else {
|
||||||
|
setSettings((prev) => ({ ...prev, codexConfigDir: sanitized }));
|
||||||
|
setResolvedCodexDir(sanitized);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("选择配置目录失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const computeDefaultConfigDir = async (app: AppType) => {
|
||||||
|
try {
|
||||||
|
const home = await homeDir();
|
||||||
|
const folder = app === "claude" ? ".claude" : ".codex";
|
||||||
|
return await join(home, folder);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取默认配置目录失败:", error);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetConfigDir = async (app: AppType) => {
|
||||||
|
setSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
...(app === "claude"
|
||||||
|
? { claudeConfigDir: undefined }
|
||||||
|
: { codexConfigDir: undefined }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const defaultDir = await computeDefaultConfigDir(app);
|
||||||
|
if (!defaultDir) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app === "claude") {
|
||||||
|
setResolvedClaudeDir(defaultDir);
|
||||||
|
} else {
|
||||||
|
setResolvedCodexDir(defaultDir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleOpenReleaseNotes = async () => {
|
const handleOpenReleaseNotes = async () => {
|
||||||
try {
|
try {
|
||||||
const targetVersion = updateInfo?.availableVersion || version;
|
const targetVersion = updateInfo?.availableVersion || version;
|
||||||
@@ -232,6 +337,92 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
</div>
|
</div>
|
||||||
</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={() => handleResetConfigDir("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="恢复默认目录(需保存后生效)"
|
||||||
|
>
|
||||||
|
<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={() => handleResetConfigDir("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="恢复默认目录(需保存后生效)"
|
||||||
|
>
|
||||||
|
<Undo2 size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 关于 */}
|
{/* 关于 */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
<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 配置状态
|
// 获取 Claude Code 配置状态
|
||||||
getClaudeConfigStatus: async (): Promise<ConfigStatus> => {
|
getClaudeConfigStatus: async (): Promise<ConfigStatus> => {
|
||||||
try {
|
try {
|
||||||
@@ -189,10 +199,22 @@ export const tauriAPI = {
|
|||||||
|
|
||||||
// (保留空位,取消迁移提示)
|
// (保留空位,取消迁移提示)
|
||||||
|
|
||||||
// 选择配置文件(Tauri 暂不实现,保留接口兼容性)
|
// 选择配置目录
|
||||||
selectConfigFile: async (): Promise<string | null> => {
|
selectConfigDirectory: async (
|
||||||
console.warn("selectConfigFile 在 Tauri 版本中暂不支持");
|
defaultPath?: string,
|
||||||
return null;
|
): Promise<string | null> => {
|
||||||
|
try {
|
||||||
|
const sanitized =
|
||||||
|
defaultPath && defaultPath.trim() !== ""
|
||||||
|
? defaultPath
|
||||||
|
: undefined;
|
||||||
|
return await invoke<string | null>("pick_directory", {
|
||||||
|
defaultPath: sanitized,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("选择配置目录失败:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取设置
|
// 获取设置
|
||||||
|
|||||||
@@ -24,4 +24,8 @@ export interface AppConfig {
|
|||||||
export interface Settings {
|
export interface Settings {
|
||||||
// 是否在系统托盘(macOS 菜单栏)显示图标
|
// 是否在系统托盘(macOS 菜单栏)显示图标
|
||||||
showInTray: boolean;
|
showInTray: boolean;
|
||||||
|
// 覆盖 Claude Code 配置目录(可选)
|
||||||
|
claudeConfigDir?: string;
|
||||||
|
// 覆盖 Codex 配置目录(可选)
|
||||||
|
codexConfigDir?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/vite-env.d.ts
vendored
3
src/vite-env.d.ts
vendored
@@ -28,7 +28,8 @@ declare global {
|
|||||||
getClaudeCodeConfigPath: () => Promise<string>;
|
getClaudeCodeConfigPath: () => Promise<string>;
|
||||||
getClaudeConfigStatus: () => Promise<ConfigStatus>;
|
getClaudeConfigStatus: () => Promise<ConfigStatus>;
|
||||||
getConfigStatus: (app?: AppType) => Promise<ConfigStatus>;
|
getConfigStatus: (app?: AppType) => Promise<ConfigStatus>;
|
||||||
selectConfigFile: () => Promise<string | null>;
|
getConfigDir: (app?: AppType) => Promise<string>;
|
||||||
|
selectConfigDirectory: (defaultPath?: string) => Promise<string | null>;
|
||||||
openConfigFolder: (app?: AppType) => Promise<void>;
|
openConfigFolder: (app?: AppType) => Promise<void>;
|
||||||
openExternal: (url: string) => Promise<void>;
|
openExternal: (url: string) => Promise<void>;
|
||||||
updateTrayMenu: () => Promise<boolean>;
|
updateTrayMenu: () => Promise<boolean>;
|
||||||
|
|||||||
Reference in New Issue
Block a user