feat(ui): redesign settings page and update sidebar navigation (#383)
This commit is contained in:
committed by
GitHub
Unverified
parent
19b5b2d540
commit
d9ae0f3263
158
pnpm-lock.yaml
generated
158
pnpm-lock.yaml
generated
@@ -65,7 +65,7 @@ importers:
|
|||||||
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
'@sliverp/qqbot':
|
'@sliverp/qqbot':
|
||||||
specifier: ^1.5.4
|
specifier: ^1.5.4
|
||||||
version: 1.5.4(clawdbot@2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@types/express@5.0.6)(devtools-protocol@0.0.1595872)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3))(moltbot@0.1.0)(openclaw@2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.16.2(typescript@5.9.3)))
|
version: 1.5.4(clawdbot@2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@types/express@5.0.6)(devtools-protocol@0.0.1596832)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3))(moltbot@0.1.0)(openclaw@2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.16.2(typescript@5.9.3)))
|
||||||
'@soimy/dingtalk':
|
'@soimy/dingtalk':
|
||||||
specifier: ^3.1.4
|
specifier: ^3.1.4
|
||||||
version: 3.1.4(openclaw@2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.16.2(typescript@5.9.3)))
|
version: 3.1.4(openclaw@2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.16.2(typescript@5.9.3)))
|
||||||
@@ -1384,6 +1384,34 @@ packages:
|
|||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@node-llama-cpp/linux-x64-cuda-ext@3.15.0':
|
||||||
|
resolution: {integrity: sha512-wQwgSl7Qm8vH56oBt7IuWWDNNsDECkVMS000C92wl3PkbzjwZFiWzehwa+kF8Lr2BBMiCJNkI5nEabhYH3RN2Q==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@node-llama-cpp/linux-x64-cuda-ext@3.16.2':
|
||||||
|
resolution: {integrity: sha512-47d9myCJauZyzAlN7IK1eIt/4CcBMslF+yHy4q+yJotD/RV/S6qRpK2kGn+ybtdVjkPGNCoPkHKcyla9iIVjbw==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@node-llama-cpp/linux-x64-cuda@3.15.0':
|
||||||
|
resolution: {integrity: sha512-mDjyVulCTRYilm9Emm3lDMx7dbI1vzGqk28Pj28shartjERTUu8aUNDYOmVKNMLpUKS1akw7vy0lMF8t4qswxQ==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@node-llama-cpp/linux-x64-cuda@3.16.2':
|
||||||
|
resolution: {integrity: sha512-LTBQFqjin7tyrLNJz0XWTB5QAHDsZV71/qiiRRjXdBKSZHVVaPLfdgxypGu7ggPeBNsv+MckRXdlH5C7yMtE4A==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@node-llama-cpp/linux-x64-vulkan@3.15.0':
|
'@node-llama-cpp/linux-x64-vulkan@3.15.0':
|
||||||
resolution: {integrity: sha512-htVIthQKq/rr8v5e7NiVtcHsstqTBAAC50kUymmDMbrzAu6d/EHacCJpNbU57b1UUa1nKN5cBqr6Jr+QqEalMA==}
|
resolution: {integrity: sha512-htVIthQKq/rr8v5e7NiVtcHsstqTBAAC50kUymmDMbrzAu6d/EHacCJpNbU57b1UUa1nKN5cBqr6Jr+QqEalMA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -1448,6 +1476,42 @@ packages:
|
|||||||
cpu: [arm64, x64]
|
cpu: [arm64, x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-cuda-ext@3.15.0':
|
||||||
|
resolution: {integrity: sha512-KQoNH9KsVtqGVXaRdPrnHPrg5w3KOM7CfynPmG1m16gmjmDSIspdPg/Dbg6DgHBfkdAzt+duRZEBk8Bg8KbDHw==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-cuda-ext@3.16.2':
|
||||||
|
resolution: {integrity: sha512-sdv4Kzn9bOQWNBRvw6B/zcn8dQRfZhjIHv5AfDBIOfRlSCgjebFpBeYUoU4wZPpjr3ISwcqO5MEWsw+AbUdV3Q==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-cuda@3.15.0':
|
||||||
|
resolution: {integrity: sha512-2Kyu1roDwXwFLaJgGZQISIXP9lCDZtJCx/DRcmrYRHcSUFCzo5ikOuAECyliSSQmRUAvvlRCuD+GrTcegbhojA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-cuda@3.16.2':
|
||||||
|
resolution: {integrity: sha512-jStDELHrU3rKQMOk5Hs5bWEazyjE2hzHwpNf6SblOpaGkajM/HJtxEZoL0mLHJx5qeXs4oOVkr7AzuLy0WPpNA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-vulkan@3.15.0':
|
||||||
|
resolution: {integrity: sha512-sH+K7lO49WrUvCCC3RPddCBrn2ZQwKCXKL90P/NZicMRgxTPFZEVSU2jXR/bu1K8B+4lNN+z5OEbjSYs7cKEcA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-vulkan@3.16.2':
|
||||||
|
resolution: {integrity: sha512-9xuHFCOhCQjZgQSFrk79EuSKn9nGWt/SAq/3wujQSQLtgp8jGdtZgwcmuDUoemInf10en2dcOmEt7t8dQdC3XA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
'@node-llama-cpp/win-x64@3.15.0':
|
'@node-llama-cpp/win-x64@3.15.0':
|
||||||
resolution: {integrity: sha512-gWhtc8l3HOry5guO46YfFohLQnF0NfL4On0GAO8E27JiYYxHO9nHSCfFif4+U03+FfHquZXL0znJ1qPVOiwOPw==}
|
resolution: {integrity: sha512-gWhtc8l3HOry5guO46YfFohLQnF0NfL4On0GAO8E27JiYYxHO9nHSCfFif4+U03+FfHquZXL0znJ1qPVOiwOPw==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -3597,8 +3661,8 @@ packages:
|
|||||||
devlop@1.1.0:
|
devlop@1.1.0:
|
||||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||||
|
|
||||||
devtools-protocol@0.0.1595872:
|
devtools-protocol@0.0.1596832:
|
||||||
resolution: {integrity: sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg==}
|
resolution: {integrity: sha512-IwRVIiCa4mpaKeLcZ2cmGpG0hP8ls3zj3zg87Z/JwULm2xYmhOcMrwdeHos6xaANQHGEXzSCzji+6kEuZu873A==}
|
||||||
|
|
||||||
didyoumean@1.2.2:
|
didyoumean@1.2.2:
|
||||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||||
@@ -4141,6 +4205,7 @@ packages:
|
|||||||
glob@11.1.0:
|
glob@11.1.0:
|
||||||
resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
|
resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
|
||||||
engines: {node: 20 || >=22}
|
engines: {node: 20 || >=22}
|
||||||
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
glob@13.0.6:
|
glob@13.0.6:
|
||||||
@@ -4353,11 +4418,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
|
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
ipull@3.9.3:
|
|
||||||
resolution: {integrity: sha512-ZMkxaopfwKHwmEuGDYx7giNBdLxbHbRCWcQVA1D2eqE4crUguupfxej6s7UqbidYEwT69dkyumYkY8DPHIxF9g==}
|
|
||||||
engines: {node: '>=18.0.0'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
ipull@3.9.5:
|
ipull@3.9.5:
|
||||||
resolution: {integrity: sha512-5w/yZB5lXmTfsvNawmvkCjYo4SJNuKQz/av8TC1UiOyfOHyaM+DReqbpU2XpWYfmY+NIUbRRH8PUAWsxaS+IfA==}
|
resolution: {integrity: sha512-5w/yZB5lXmTfsvNawmvkCjYo4SJNuKQz/av8TC1UiOyfOHyaM+DReqbpU2XpWYfmY+NIUbRRH8PUAWsxaS+IfA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -6125,6 +6185,7 @@ packages:
|
|||||||
tar@7.5.4:
|
tar@7.5.4:
|
||||||
resolution: {integrity: sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==}
|
resolution: {integrity: sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
|
|
||||||
tar@7.5.9:
|
tar@7.5.9:
|
||||||
resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==}
|
resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==}
|
||||||
@@ -8273,6 +8334,18 @@ snapshots:
|
|||||||
'@node-llama-cpp/linux-armv7l@3.16.2':
|
'@node-llama-cpp/linux-armv7l@3.16.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/linux-x64-cuda-ext@3.15.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/linux-x64-cuda-ext@3.16.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/linux-x64-cuda@3.15.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/linux-x64-cuda@3.16.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@node-llama-cpp/linux-x64-vulkan@3.15.0':
|
'@node-llama-cpp/linux-x64-vulkan@3.15.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -8303,6 +8376,24 @@ snapshots:
|
|||||||
'@node-llama-cpp/win-arm64@3.16.2':
|
'@node-llama-cpp/win-arm64@3.16.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-cuda-ext@3.15.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-cuda-ext@3.16.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-cuda@3.15.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-cuda@3.16.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-vulkan@3.15.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@node-llama-cpp/win-x64-vulkan@3.16.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@node-llama-cpp/win-x64@3.15.0':
|
'@node-llama-cpp/win-x64@3.15.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -9145,9 +9236,9 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
|
||||||
'@sliverp/qqbot@1.5.4(clawdbot@2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@types/express@5.0.6)(devtools-protocol@0.0.1595872)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3))(moltbot@0.1.0)(openclaw@2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.16.2(typescript@5.9.3)))':
|
'@sliverp/qqbot@1.5.4(clawdbot@2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@types/express@5.0.6)(devtools-protocol@0.0.1596832)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3))(moltbot@0.1.0)(openclaw@2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.16.2(typescript@5.9.3)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
clawdbot: 2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@types/express@5.0.6)(devtools-protocol@0.0.1595872)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3)
|
clawdbot: 2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@types/express@5.0.6)(devtools-protocol@0.0.1596832)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3)
|
||||||
moltbot: 0.1.0
|
moltbot: 0.1.0
|
||||||
openclaw: 2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.16.2(typescript@5.9.3))
|
openclaw: 2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.16.2(typescript@5.9.3))
|
||||||
|
|
||||||
@@ -10373,9 +10464,9 @@ snapshots:
|
|||||||
|
|
||||||
chownr@3.0.0: {}
|
chownr@3.0.0: {}
|
||||||
|
|
||||||
chromium-bidi@13.0.1(devtools-protocol@0.0.1595872):
|
chromium-bidi@13.0.1(devtools-protocol@0.0.1596832):
|
||||||
dependencies:
|
dependencies:
|
||||||
devtools-protocol: 0.0.1595872
|
devtools-protocol: 0.0.1596832
|
||||||
mitt: 3.0.1
|
mitt: 3.0.1
|
||||||
zod: 3.25.76
|
zod: 3.25.76
|
||||||
|
|
||||||
@@ -10389,7 +10480,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
|
|
||||||
clawdbot@2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@types/express@5.0.6)(devtools-protocol@0.0.1595872)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3):
|
clawdbot@2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@types/express@5.0.6)(devtools-protocol@0.0.1596832)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@agentclientprotocol/sdk': 0.13.1(zod@4.3.6)
|
'@agentclientprotocol/sdk': 0.13.1(zod@4.3.6)
|
||||||
'@aws-sdk/client-bedrock': 3.1000.0
|
'@aws-sdk/client-bedrock': 3.1000.0
|
||||||
@@ -10413,7 +10504,7 @@ snapshots:
|
|||||||
body-parser: 2.2.2
|
body-parser: 2.2.2
|
||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
chokidar: 5.0.0
|
chokidar: 5.0.0
|
||||||
chromium-bidi: 13.0.1(devtools-protocol@0.0.1595872)
|
chromium-bidi: 13.0.1(devtools-protocol@0.0.1596832)
|
||||||
cli-highlight: 2.1.11
|
cli-highlight: 2.1.11
|
||||||
commander: 14.0.3
|
commander: 14.0.3
|
||||||
croner: 9.1.0
|
croner: 9.1.0
|
||||||
@@ -10751,7 +10842,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
|
|
||||||
devtools-protocol@0.0.1595872: {}
|
devtools-protocol@0.0.1596832: {}
|
||||||
|
|
||||||
didyoumean@1.2.2: {}
|
didyoumean@1.2.2: {}
|
||||||
|
|
||||||
@@ -11710,31 +11801,6 @@ snapshots:
|
|||||||
|
|
||||||
ipaddr.js@2.3.0: {}
|
ipaddr.js@2.3.0: {}
|
||||||
|
|
||||||
ipull@3.9.3:
|
|
||||||
dependencies:
|
|
||||||
'@tinyhttp/content-disposition': 2.2.4
|
|
||||||
async-retry: 1.3.3
|
|
||||||
chalk: 5.6.2
|
|
||||||
ci-info: 4.4.0
|
|
||||||
cli-spinners: 2.9.2
|
|
||||||
commander: 10.0.1
|
|
||||||
eventemitter3: 5.0.4
|
|
||||||
filenamify: 6.0.0
|
|
||||||
fs-extra: 11.3.3
|
|
||||||
is-unicode-supported: 2.1.0
|
|
||||||
lifecycle-utils: 2.1.0
|
|
||||||
lodash.debounce: 4.0.8
|
|
||||||
lowdb: 7.0.1
|
|
||||||
pretty-bytes: 6.1.1
|
|
||||||
pretty-ms: 8.0.0
|
|
||||||
sleep-promise: 9.1.0
|
|
||||||
slice-ansi: 7.1.2
|
|
||||||
stdout-update: 4.0.1
|
|
||||||
strip-ansi: 7.1.2
|
|
||||||
optionalDependencies:
|
|
||||||
'@reflink/reflink': 0.1.19
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
ipull@3.9.5:
|
ipull@3.9.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tinyhttp/content-disposition': 2.2.4
|
'@tinyhttp/content-disposition': 2.2.4
|
||||||
@@ -12685,7 +12751,7 @@ snapshots:
|
|||||||
filenamify: 6.0.0
|
filenamify: 6.0.0
|
||||||
fs-extra: 11.3.3
|
fs-extra: 11.3.3
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
ipull: 3.9.3
|
ipull: 3.9.5
|
||||||
is-unicode-supported: 2.1.0
|
is-unicode-supported: 2.1.0
|
||||||
lifecycle-utils: 3.1.1
|
lifecycle-utils: 3.1.1
|
||||||
log-symbols: 7.0.1
|
log-symbols: 7.0.1
|
||||||
@@ -12707,11 +12773,16 @@ snapshots:
|
|||||||
'@node-llama-cpp/linux-arm64': 3.15.0
|
'@node-llama-cpp/linux-arm64': 3.15.0
|
||||||
'@node-llama-cpp/linux-armv7l': 3.15.0
|
'@node-llama-cpp/linux-armv7l': 3.15.0
|
||||||
'@node-llama-cpp/linux-x64': 3.15.0
|
'@node-llama-cpp/linux-x64': 3.15.0
|
||||||
|
'@node-llama-cpp/linux-x64-cuda': 3.15.0
|
||||||
|
'@node-llama-cpp/linux-x64-cuda-ext': 3.15.0
|
||||||
'@node-llama-cpp/linux-x64-vulkan': 3.15.0
|
'@node-llama-cpp/linux-x64-vulkan': 3.15.0
|
||||||
'@node-llama-cpp/mac-arm64-metal': 3.15.0
|
'@node-llama-cpp/mac-arm64-metal': 3.15.0
|
||||||
'@node-llama-cpp/mac-x64': 3.15.0
|
'@node-llama-cpp/mac-x64': 3.15.0
|
||||||
'@node-llama-cpp/win-arm64': 3.15.0
|
'@node-llama-cpp/win-arm64': 3.15.0
|
||||||
'@node-llama-cpp/win-x64': 3.15.0
|
'@node-llama-cpp/win-x64': 3.15.0
|
||||||
|
'@node-llama-cpp/win-x64-cuda': 3.15.0
|
||||||
|
'@node-llama-cpp/win-x64-cuda-ext': 3.15.0
|
||||||
|
'@node-llama-cpp/win-x64-vulkan': 3.15.0
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -12752,11 +12823,16 @@ snapshots:
|
|||||||
'@node-llama-cpp/linux-arm64': 3.16.2
|
'@node-llama-cpp/linux-arm64': 3.16.2
|
||||||
'@node-llama-cpp/linux-armv7l': 3.16.2
|
'@node-llama-cpp/linux-armv7l': 3.16.2
|
||||||
'@node-llama-cpp/linux-x64': 3.16.2
|
'@node-llama-cpp/linux-x64': 3.16.2
|
||||||
|
'@node-llama-cpp/linux-x64-cuda': 3.16.2
|
||||||
|
'@node-llama-cpp/linux-x64-cuda-ext': 3.16.2
|
||||||
'@node-llama-cpp/linux-x64-vulkan': 3.16.2
|
'@node-llama-cpp/linux-x64-vulkan': 3.16.2
|
||||||
'@node-llama-cpp/mac-arm64-metal': 3.16.2
|
'@node-llama-cpp/mac-arm64-metal': 3.16.2
|
||||||
'@node-llama-cpp/mac-x64': 3.16.2
|
'@node-llama-cpp/mac-x64': 3.16.2
|
||||||
'@node-llama-cpp/win-arm64': 3.16.2
|
'@node-llama-cpp/win-arm64': 3.16.2
|
||||||
'@node-llama-cpp/win-x64': 3.16.2
|
'@node-llama-cpp/win-x64': 3.16.2
|
||||||
|
'@node-llama-cpp/win-x64-cuda': 3.16.2
|
||||||
|
'@node-llama-cpp/win-x64-cuda-ext': 3.16.2
|
||||||
|
'@node-llama-cpp/win-x64-vulkan': 3.16.2
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { Toaster } from 'sonner';
|
|||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
import { MainLayout } from './components/layout/MainLayout';
|
import { MainLayout } from './components/layout/MainLayout';
|
||||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||||
import { Dashboard } from './pages/Dashboard';
|
|
||||||
import { Models } from './pages/Models';
|
import { Models } from './pages/Models';
|
||||||
import { Chat } from './pages/Chat';
|
import { Chat } from './pages/Chat';
|
||||||
import { Channels } from './pages/Channels';
|
import { Channels } from './pages/Channels';
|
||||||
@@ -166,7 +165,6 @@ function App() {
|
|||||||
<Route element={<MainLayout />}>
|
<Route element={<MainLayout />}>
|
||||||
<Route path="/" element={<Chat />} />
|
<Route path="/" element={<Chat />} />
|
||||||
<Route path="/models" element={<Models />} />
|
<Route path="/models" element={<Models />} />
|
||||||
<Route path="/dashboard" element={<Dashboard />} />
|
|
||||||
<Route path="/channels" element={<Channels />} />
|
<Route path="/channels" element={<Channels />} />
|
||||||
<Route path="/skills" element={<Skills />} />
|
<Route path="/skills" element={<Skills />} />
|
||||||
<Route path="/cron" element={<Cron />} />
|
<Route path="/cron" element={<Cron />} />
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
ExternalLink,
|
ExternalLink,
|
||||||
Trash2,
|
Trash2,
|
||||||
Cpu,
|
Cpu,
|
||||||
LayoutDashboard,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
@@ -105,7 +104,6 @@ const INITIAL_NOW_MS = Date.now();
|
|||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
|
const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
|
||||||
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
|
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
|
||||||
const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked);
|
|
||||||
|
|
||||||
const sessions = useChatStore((s) => s.sessions);
|
const sessions = useChatStore((s) => s.sessions);
|
||||||
const currentSessionKey = useChatStore((s) => s.currentSessionKey);
|
const currentSessionKey = useChatStore((s) => s.currentSessionKey);
|
||||||
@@ -173,7 +171,6 @@ export function Sidebar() {
|
|||||||
{ to: '/channels', icon: <Network className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.channels') },
|
{ to: '/channels', icon: <Network className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.channels') },
|
||||||
{ to: '/skills', icon: <Puzzle className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.skills') },
|
{ to: '/skills', icon: <Puzzle className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.skills') },
|
||||||
{ to: '/cron', icon: <Clock className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.cronTasks') },
|
{ to: '/cron', icon: <Clock className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.cronTasks') },
|
||||||
{ to: '/dashboard', icon: <LayoutDashboard className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.dashboard') },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -286,21 +283,6 @@ export function Sidebar() {
|
|||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="p-2 mt-auto">
|
<div className="p-2 mt-auto">
|
||||||
{devModeUnlocked && !sidebarCollapsed && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="w-full justify-start mb-1 hover:bg-black/5 dark:hover:bg-white/5"
|
|
||||||
onClick={openDevConsole}
|
|
||||||
>
|
|
||||||
<div className="flex shrink-0 items-center justify-center text-muted-foreground">
|
|
||||||
<Terminal className="h-[18px] w-[18px] mr-2.5" strokeWidth={2} />
|
|
||||||
</div>
|
|
||||||
<span className="font-medium text-[14px] flex-1 text-left text-foreground/80 overflow-hidden text-ellipsis whitespace-nowrap">{t('sidebar.devConsole')}</span>
|
|
||||||
<ExternalLink className="h-3 w-3 shrink-0 ml-auto opacity-50 text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/settings"
|
to="/settings"
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
@@ -321,6 +303,26 @@ export function Sidebar() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-2.5 rounded-lg px-2.5 py-2 h-auto text-[14px] font-medium transition-colors w-full mt-1',
|
||||||
|
'hover:bg-black/5 dark:hover:bg-white/5 text-foreground/80',
|
||||||
|
sidebarCollapsed ? 'justify-center px-0' : 'justify-start'
|
||||||
|
)}
|
||||||
|
onClick={openDevConsole}
|
||||||
|
>
|
||||||
|
<div className="flex shrink-0 items-center justify-center text-muted-foreground">
|
||||||
|
<Terminal className="h-[18px] w-[18px]" strokeWidth={2} />
|
||||||
|
</div>
|
||||||
|
{!sidebarCollapsed && (
|
||||||
|
<>
|
||||||
|
<span className="flex-1 text-left overflow-hidden text-ellipsis whitespace-nowrap">OpenClaw Page</span>
|
||||||
|
<ExternalLink className="h-3 w-3 shrink-0 ml-auto opacity-50 text-muted-foreground" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Textarea } from '@/components/ui/textarea';
|
|||||||
import { hostApiFetch } from '@/lib/host-api';
|
import { hostApiFetch } from '@/lib/host-api';
|
||||||
import { invokeIpc } from '@/lib/api-client';
|
import { invokeIpc } from '@/lib/api-client';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useGatewayStore } from '@/stores/gateway';
|
||||||
|
|
||||||
// ── Types ────────────────────────────────────────────────────────
|
// ── Types ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -84,6 +85,7 @@ export function ChatInput({ onSend, onStop, disabled = false, sending = false, i
|
|||||||
const [attachments, setAttachments] = useState<FileAttachment[]>([]);
|
const [attachments, setAttachments] = useState<FileAttachment[]>([]);
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const isComposingRef = useRef(false);
|
const isComposingRef = useRef(false);
|
||||||
|
const gatewayStatus = useGatewayStore((s) => s.status);
|
||||||
|
|
||||||
// Auto-resize textarea
|
// Auto-resize textarea
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -407,7 +409,12 @@ export function ChatInput({ onSend, onStop, disabled = false, sending = false, i
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2.5 flex items-center justify-between gap-2 text-[11px] text-muted-foreground/60 px-4">
|
<div className="mt-2.5 flex items-center justify-between gap-2 text-[11px] text-muted-foreground/60 px-4">
|
||||||
<span>Tip: switch sessions from the sidebar to keep context clean.</span>
|
<div className="flex items-center gap-1.5">
|
||||||
|
<div className={cn("w-1.5 h-1.5 rounded-full", gatewayStatus.state === 'running' ? "bg-green-500/80" : "bg-red-500/80")} />
|
||||||
|
<span>
|
||||||
|
gateway {gatewayStatus.state === 'running' ? 'connected' : gatewayStatus.state} | port: {gatewayStatus.port} {gatewayStatus.pid ? `| pid: ${gatewayStatus.pid}` : ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{hasFailedAttachments && (
|
{hasFailedAttachments && (
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
|
|||||||
@@ -1,324 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dashboard Page
|
|
||||||
* Main overview page showing system status and quick actions
|
|
||||||
*/
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import {
|
|
||||||
Activity,
|
|
||||||
MessageSquare,
|
|
||||||
Radio,
|
|
||||||
Puzzle,
|
|
||||||
Clock,
|
|
||||||
Settings,
|
|
||||||
Plus,
|
|
||||||
Terminal,
|
|
||||||
Wrench,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
import { useGatewayStore } from '@/stores/gateway';
|
|
||||||
import { useChannelsStore } from '@/stores/channels';
|
|
||||||
import { useSkillsStore } from '@/stores/skills';
|
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
|
||||||
import { StatusBadge } from '@/components/common/StatusBadge';
|
|
||||||
import { FeedbackState } from '@/components/common/FeedbackState';
|
|
||||||
import { hostApiFetch } from '@/lib/host-api';
|
|
||||||
import { trackUiEvent } from '@/lib/telemetry';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export function Dashboard() {
|
|
||||||
const { t } = useTranslation('dashboard');
|
|
||||||
const gatewayStatus = useGatewayStore((state) => state.status);
|
|
||||||
const { channels, fetchChannels } = useChannelsStore();
|
|
||||||
const { skills, fetchSkills } = useSkillsStore();
|
|
||||||
const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked);
|
|
||||||
|
|
||||||
const isGatewayRunning = gatewayStatus.state === 'running';
|
|
||||||
const [uptime, setUptime] = useState(0);
|
|
||||||
|
|
||||||
// Track page view on mount only.
|
|
||||||
useEffect(() => {
|
|
||||||
trackUiEvent('dashboard.page_viewed');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Fetch data only when gateway is running.
|
|
||||||
useEffect(() => {
|
|
||||||
if (isGatewayRunning) {
|
|
||||||
fetchChannels();
|
|
||||||
fetchSkills();
|
|
||||||
}
|
|
||||||
}, [fetchChannels, fetchSkills, isGatewayRunning]);
|
|
||||||
|
|
||||||
// Calculate statistics safely
|
|
||||||
const connectedChannels = Array.isArray(channels) ? channels.filter((c) => c.status === 'connected').length : 0;
|
|
||||||
const enabledSkills = Array.isArray(skills) ? skills.filter((s) => s.enabled).length : 0;
|
|
||||||
|
|
||||||
// Update uptime periodically
|
|
||||||
useEffect(() => {
|
|
||||||
const updateUptime = () => {
|
|
||||||
if (gatewayStatus.connectedAt) {
|
|
||||||
setUptime(Math.floor((Date.now() - gatewayStatus.connectedAt) / 1000));
|
|
||||||
} else {
|
|
||||||
setUptime(0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update immediately
|
|
||||||
updateUptime();
|
|
||||||
|
|
||||||
// Update every second
|
|
||||||
const interval = setInterval(updateUptime, 1000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [gatewayStatus.connectedAt]);
|
|
||||||
|
|
||||||
const openDevConsole = async () => {
|
|
||||||
try {
|
|
||||||
const result = await hostApiFetch<{
|
|
||||||
success: boolean;
|
|
||||||
url?: string;
|
|
||||||
error?: string;
|
|
||||||
}>('/api/gateway/control-ui');
|
|
||||||
if (result.success && result.url) {
|
|
||||||
trackUiEvent('dashboard.quick_action', { action: 'dev_console' });
|
|
||||||
window.electron.openExternal(result.url);
|
|
||||||
} else {
|
|
||||||
console.error('Failed to get Dev Console URL:', result.error);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error opening Dev Console:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Status Cards */}
|
|
||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
||||||
{/* Gateway Status */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
||||||
<CardTitle className="text-sm font-medium">{t('gateway')}</CardTitle>
|
|
||||||
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<StatusBadge status={gatewayStatus.state} />
|
|
||||||
</div>
|
|
||||||
{gatewayStatus.state === 'running' && (
|
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
|
||||||
{t('port', { port: gatewayStatus.port })} | {t('pid', { pid: gatewayStatus.pid || 'N/A' })}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Channels */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
||||||
<CardTitle className="text-sm font-medium">{t('channels')}</CardTitle>
|
|
||||||
<Radio className="h-4 w-4 text-muted-foreground" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="text-2xl font-bold">{connectedChannels}</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t('connectedOf', { connected: connectedChannels, total: channels.length })}
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Skills */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
||||||
<CardTitle className="text-sm font-medium">{t('skills')}</CardTitle>
|
|
||||||
<Puzzle className="h-4 w-4 text-muted-foreground" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="text-2xl font-bold">{enabledSkills}</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t('enabledOf', { enabled: enabledSkills, total: skills.length })}
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Uptime */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
||||||
<CardTitle className="text-sm font-medium">{t('uptime')}</CardTitle>
|
|
||||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="text-2xl font-bold">
|
|
||||||
{uptime > 0 ? formatUptime(uptime) : '—'}
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{gatewayStatus.state === 'running' ? t('sinceRestart') : t('gatewayNotRunning')}
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Quick Actions */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>{t('quickActions.title')}</CardTitle>
|
|
||||||
<CardDescription>{t('quickActions.description')}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-6">
|
|
||||||
<Button variant="outline" className="h-auto flex-col gap-2 py-4" asChild>
|
|
||||||
<Link to="/settings" onClick={() => trackUiEvent('dashboard.quick_action', { action: 'add_provider' })}>
|
|
||||||
<Wrench className="h-5 w-5" />
|
|
||||||
<span>{t('quickActions.addProvider')}</span>
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="h-auto flex-col gap-2 py-4" asChild>
|
|
||||||
<Link to="/channels" onClick={() => trackUiEvent('dashboard.quick_action', { action: 'add_channel' })}>
|
|
||||||
<Plus className="h-5 w-5" />
|
|
||||||
<span>{t('quickActions.addChannel')}</span>
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="h-auto flex-col gap-2 py-4" asChild>
|
|
||||||
<Link to="/cron" onClick={() => trackUiEvent('dashboard.quick_action', { action: 'create_cron' })}>
|
|
||||||
<Clock className="h-5 w-5" />
|
|
||||||
<span>{t('quickActions.createCron')}</span>
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="h-auto flex-col gap-2 py-4" asChild>
|
|
||||||
<Link to="/skills" onClick={() => trackUiEvent('dashboard.quick_action', { action: 'install_skill' })}>
|
|
||||||
<Puzzle className="h-5 w-5" />
|
|
||||||
<span>{t('quickActions.installSkill')}</span>
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="h-auto flex-col gap-2 py-4" asChild>
|
|
||||||
<Link to="/" onClick={() => trackUiEvent('dashboard.quick_action', { action: 'open_chat' })}>
|
|
||||||
<MessageSquare className="h-5 w-5" />
|
|
||||||
<span>{t('quickActions.openChat')}</span>
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="h-auto flex-col gap-2 py-4" asChild>
|
|
||||||
<Link to="/settings" onClick={() => trackUiEvent('dashboard.quick_action', { action: 'open_settings' })}>
|
|
||||||
<Settings className="h-5 w-5" />
|
|
||||||
<span>{t('quickActions.settings')}</span>
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
{devModeUnlocked && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="h-auto flex-col gap-2 py-4"
|
|
||||||
onClick={openDevConsole}
|
|
||||||
>
|
|
||||||
<Terminal className="h-5 w-5" />
|
|
||||||
<span>{t('quickActions.devConsole')}</span>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Recent Activity */}
|
|
||||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
||||||
{/* Connected Channels */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">{t('connectedChannels')}</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{(!Array.isArray(channels) || channels.length === 0) ? (
|
|
||||||
<FeedbackState
|
|
||||||
state="empty"
|
|
||||||
title={t('noChannels')}
|
|
||||||
action={(
|
|
||||||
<Button variant="link" asChild className="mt-2">
|
|
||||||
<Link to="/channels">{t('addFirst')}</Link>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-3">
|
|
||||||
{channels.slice(0, 5).map((channel) => (
|
|
||||||
<div
|
|
||||||
key={channel.id}
|
|
||||||
className="flex items-center justify-between rounded-lg border p-3"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="text-lg">
|
|
||||||
{channel.type === 'whatsapp' && '📱'}
|
|
||||||
{channel.type === 'telegram' && '✈️'}
|
|
||||||
{channel.type === 'discord' && '🎮'}
|
|
||||||
</span>
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">{channel.name}</p>
|
|
||||||
<p className="text-xs text-muted-foreground capitalize">
|
|
||||||
{channel.type}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<StatusBadge status={channel.status} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Enabled Skills */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">{t('activeSkills')}</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{(!Array.isArray(skills) || skills.filter((s) => s.enabled).length === 0) ? (
|
|
||||||
<FeedbackState
|
|
||||||
state="empty"
|
|
||||||
title={t('noSkills')}
|
|
||||||
action={(
|
|
||||||
<Button variant="link" asChild className="mt-2">
|
|
||||||
<Link to="/skills">{t('enableSome')}</Link>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{skills
|
|
||||||
.filter((s) => s.enabled)
|
|
||||||
.slice(0, 12)
|
|
||||||
.map((skill) => (
|
|
||||||
<Badge key={skill.id} variant="secondary">
|
|
||||||
{skill.icon && <span className="mr-1">{skill.icon}</span>}
|
|
||||||
{skill.name}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
{skills.filter((s) => s.enabled).length > 12 && (
|
|
||||||
<Badge variant="outline">
|
|
||||||
{t('more', { count: skills.filter((s) => s.enabled).length - 12 })}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format uptime in human-readable format
|
|
||||||
*/
|
|
||||||
function formatUptime(seconds: number): string {
|
|
||||||
const days = Math.floor(seconds / 86400);
|
|
||||||
const hours = Math.floor((seconds % 86400) / 3600);
|
|
||||||
const minutes = Math.floor((seconds % 3600) / 60);
|
|
||||||
|
|
||||||
if (days > 0) {
|
|
||||||
return `${days}d ${hours}h`;
|
|
||||||
} else if (hours > 0) {
|
|
||||||
return `${hours}h ${minutes}m`;
|
|
||||||
} else {
|
|
||||||
return `${minutes}m`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Dashboard;
|
|
||||||
@@ -8,16 +8,11 @@ import {
|
|||||||
Moon,
|
Moon,
|
||||||
Monitor,
|
Monitor,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Terminal,
|
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Download,
|
|
||||||
Copy,
|
Copy,
|
||||||
ChevronDown,
|
|
||||||
ChevronRight,
|
|
||||||
FileText,
|
FileText,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
@@ -44,6 +39,7 @@ import {
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SUPPORTED_LANGUAGES } from '@/i18n';
|
import { SUPPORTED_LANGUAGES } from '@/i18n';
|
||||||
import { hostApiFetch } from '@/lib/host-api';
|
import { hostApiFetch } from '@/lib/host-api';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
type ControlUiInfo = {
|
type ControlUiInfo = {
|
||||||
url: string;
|
url: string;
|
||||||
token: string;
|
token: string;
|
||||||
@@ -91,7 +87,6 @@ export function Settings() {
|
|||||||
const [proxyAllServerDraft, setProxyAllServerDraft] = useState('');
|
const [proxyAllServerDraft, setProxyAllServerDraft] = useState('');
|
||||||
const [proxyBypassRulesDraft, setProxyBypassRulesDraft] = useState('');
|
const [proxyBypassRulesDraft, setProxyBypassRulesDraft] = useState('');
|
||||||
const [proxyEnabledDraft, setProxyEnabledDraft] = useState(false);
|
const [proxyEnabledDraft, setProxyEnabledDraft] = useState(false);
|
||||||
const [showAdvancedProxy, setShowAdvancedProxy] = useState(false);
|
|
||||||
const [savingProxy, setSavingProxy] = useState(false);
|
const [savingProxy, setSavingProxy] = useState(false);
|
||||||
const [wsDiagnosticEnabled, setWsDiagnosticEnabled] = useState(false);
|
const [wsDiagnosticEnabled, setWsDiagnosticEnabled] = useState(false);
|
||||||
const [showTelemetryViewer, setShowTelemetryViewer] = useState(false);
|
const [showTelemetryViewer, setShowTelemetryViewer] = useState(false);
|
||||||
@@ -124,27 +119,7 @@ export function Settings() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Open developer console
|
|
||||||
const openDevConsole = async () => {
|
|
||||||
try {
|
|
||||||
const result = await hostApiFetch<{
|
|
||||||
success: boolean;
|
|
||||||
url?: string;
|
|
||||||
token?: string;
|
|
||||||
port?: number;
|
|
||||||
error?: string;
|
|
||||||
}>('/api/gateway/control-ui');
|
|
||||||
if (result.success && result.url && result.token && typeof result.port === 'number') {
|
|
||||||
setControlUiInfo({ url: result.url, token: result.token, port: result.port });
|
|
||||||
trackUiEvent('settings.open_dev_console');
|
|
||||||
window.electron.openExternal(result.url);
|
|
||||||
} else {
|
|
||||||
console.error('Failed to get Dev Console URL:', result.error);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error opening Dev Console:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshControlUiInfo = async () => {
|
const refreshControlUiInfo = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -390,43 +365,52 @@ export function Settings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6 p-6">
|
<div className="flex flex-col -m-6 dark:bg-background h-[calc(100vh-2.5rem)] overflow-hidden">
|
||||||
|
<div className="w-full max-w-4xl mx-auto flex flex-col h-full p-10 pt-16">
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-start justify-between mb-12 shrink-0 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">{t('title')}</h1>
|
<h1 className="text-5xl md:text-6xl font-serif text-foreground mb-3 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
<p className="text-muted-foreground">
|
{t('title')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-[17px] text-foreground/80 font-medium">
|
||||||
{t('subtitle')}
|
{t('subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Area */}
|
||||||
|
<div className="flex-1 overflow-y-auto pr-2 pb-10 min-h-0 -mr-2 space-y-12">
|
||||||
|
|
||||||
{/* Appearance */}
|
{/* Appearance */}
|
||||||
<Card className="order-2">
|
<div>
|
||||||
<CardHeader>
|
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
<CardTitle>{t('appearance.title')}</CardTitle>
|
{t('appearance.title')}
|
||||||
<CardDescription>{t('appearance.description')}</CardDescription>
|
</h2>
|
||||||
</CardHeader>
|
<div className="space-y-6">
|
||||||
<CardContent className="space-y-4">
|
<div className="space-y-3">
|
||||||
<div className="space-y-2">
|
<Label className="text-[15px] font-medium text-foreground/80">{t('appearance.theme')}</Label>
|
||||||
<Label>{t('appearance.theme')}</Label>
|
<div className="flex flex-wrap gap-2">
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
<Button
|
||||||
variant={theme === 'light' ? 'default' : 'outline'}
|
variant={theme === 'light' ? 'secondary' : 'outline'}
|
||||||
size="sm"
|
className={cn("rounded-full px-5 h-10 border-black/10 dark:border-white/10", theme === 'light' ? "bg-black/5 dark:bg-white/10 text-foreground" : "bg-transparent text-muted-foreground hover:bg-black/5 dark:hover:bg-white/5")}
|
||||||
onClick={() => setTheme('light')}
|
onClick={() => setTheme('light')}
|
||||||
>
|
>
|
||||||
<Sun className="h-4 w-4 mr-2" />
|
<Sun className="h-4 w-4 mr-2" />
|
||||||
{t('appearance.light')}
|
{t('appearance.light')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={theme === 'dark' ? 'default' : 'outline'}
|
variant={theme === 'dark' ? 'secondary' : 'outline'}
|
||||||
size="sm"
|
className={cn("rounded-full px-5 h-10 border-black/10 dark:border-white/10", theme === 'dark' ? "bg-black/5 dark:bg-white/10 text-foreground" : "bg-transparent text-muted-foreground hover:bg-black/5 dark:hover:bg-white/5")}
|
||||||
onClick={() => setTheme('dark')}
|
onClick={() => setTheme('dark')}
|
||||||
>
|
>
|
||||||
<Moon className="h-4 w-4 mr-2" />
|
<Moon className="h-4 w-4 mr-2" />
|
||||||
{t('appearance.dark')}
|
{t('appearance.dark')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={theme === 'system' ? 'default' : 'outline'}
|
variant={theme === 'system' ? 'secondary' : 'outline'}
|
||||||
size="sm"
|
className={cn("rounded-full px-5 h-10 border-black/10 dark:border-white/10", theme === 'system' ? "bg-black/5 dark:bg-white/10 text-foreground" : "bg-transparent text-muted-foreground hover:bg-black/5 dark:hover:bg-white/5")}
|
||||||
onClick={() => setTheme('system')}
|
onClick={() => setTheme('system')}
|
||||||
>
|
>
|
||||||
<Monitor className="h-4 w-4 mr-2" />
|
<Monitor className="h-4 w-4 mr-2" />
|
||||||
@@ -434,14 +418,14 @@ export function Settings() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
<Label>{t('appearance.language')}</Label>
|
<Label className="text-[15px] font-medium text-foreground/80">{t('appearance.language')}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{SUPPORTED_LANGUAGES.map((lang) => (
|
{SUPPORTED_LANGUAGES.map((lang) => (
|
||||||
<Button
|
<Button
|
||||||
key={lang.code}
|
key={lang.code}
|
||||||
variant={language === lang.code ? 'default' : 'outline'}
|
variant={language === lang.code ? 'secondary' : 'outline'}
|
||||||
size="sm"
|
className={cn("rounded-full px-5 h-10 border-black/10 dark:border-white/10", language === lang.code ? "bg-black/5 dark:bg-white/10 text-foreground" : "bg-transparent text-muted-foreground hover:bg-black/5 dark:hover:bg-white/5")}
|
||||||
onClick={() => setLanguage(lang.code)}
|
onClick={() => setLanguage(lang.code)}
|
||||||
>
|
>
|
||||||
{lang.label}
|
{lang.label}
|
||||||
@@ -449,72 +433,72 @@ export function Settings() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
|
<Separator className="bg-black/5 dark:bg-white/5" />
|
||||||
|
|
||||||
{/* Gateway */}
|
{/* Gateway */}
|
||||||
<Card className="order-1">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>{t('gateway.title')}</CardTitle>
|
|
||||||
<CardDescription>{t('gateway.description')}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t('gateway.status')}</Label>
|
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
<p className="text-sm text-muted-foreground">
|
{t('gateway.title')}
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<Label className="text-[15px] font-medium text-foreground">{t('gateway.status')}</Label>
|
||||||
|
<p className="text-[13px] text-muted-foreground mt-1">
|
||||||
{t('gateway.port')}: {gatewayStatus.port}
|
{t('gateway.port')}: {gatewayStatus.port}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Badge
|
<div className={cn(
|
||||||
variant={
|
"flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[13px] font-medium border",
|
||||||
gatewayStatus.state === 'running'
|
gatewayStatus.state === 'running' ? "bg-green-500/10 text-green-600 dark:text-green-500 border-green-500/20" :
|
||||||
? 'success'
|
gatewayStatus.state === 'error' ? "bg-red-500/10 text-red-600 dark:text-red-500 border-red-500/20" :
|
||||||
: gatewayStatus.state === 'error'
|
"bg-black/5 dark:bg-white/5 text-muted-foreground border-transparent"
|
||||||
? 'destructive'
|
)}>
|
||||||
: 'secondary'
|
<div className={cn("w-1.5 h-1.5 rounded-full",
|
||||||
}
|
gatewayStatus.state === 'running' ? "bg-green-500" :
|
||||||
>
|
gatewayStatus.state === 'error' ? "bg-red-500" : "bg-muted-foreground"
|
||||||
|
)} />
|
||||||
{gatewayStatus.state}
|
{gatewayStatus.state}
|
||||||
</Badge>
|
</div>
|
||||||
<Button variant="outline" size="sm" onClick={restartGateway}>
|
<Button variant="outline" size="sm" onClick={restartGateway} className="rounded-full h-8 px-4 border-black/10 dark:border-white/10 bg-transparent hover:bg-black/5 dark:hover:bg-white/5">
|
||||||
<RefreshCw className="h-4 w-4 mr-2" />
|
<RefreshCw className="h-3.5 w-3.5 mr-1.5" />
|
||||||
{t('common:actions.restart')}
|
{t('common:actions.restart')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" size="sm" onClick={handleShowLogs}>
|
<Button variant="outline" size="sm" onClick={handleShowLogs} className="rounded-full h-8 px-4 border-black/10 dark:border-white/10 bg-transparent hover:bg-black/5 dark:hover:bg-white/5">
|
||||||
<FileText className="h-4 w-4 mr-2" />
|
<FileText className="h-3.5 w-3.5 mr-1.5" />
|
||||||
{t('gateway.logs')}
|
{t('gateway.logs')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showLogs && (
|
{showLogs && (
|
||||||
<div className="mt-4 p-4 rounded-lg bg-black/10 dark:bg-black/40 border border-border">
|
<div className="p-4 rounded-2xl bg-black/5 dark:bg-white/5 border border-black/5 dark:border-white/5">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<p className="font-medium text-sm">{t('gateway.appLogs')}</p>
|
<p className="font-medium text-[14px]">{t('gateway.appLogs')}</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={handleOpenLogDir}>
|
<Button variant="ghost" size="sm" className="h-7 text-[12px] rounded-full hover:bg-black/5 dark:hover:bg-white/10" onClick={handleOpenLogDir}>
|
||||||
<ExternalLink className="h-3 w-3 mr-1" />
|
<ExternalLink className="h-3 w-3 mr-1.5" />
|
||||||
{t('gateway.openFolder')}
|
{t('gateway.openFolder')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setShowLogs(false)}>
|
<Button variant="ghost" size="sm" className="h-7 text-[12px] rounded-full hover:bg-black/5 dark:hover:bg-white/10" onClick={() => setShowLogs(false)}>
|
||||||
{t('common:actions.close')}
|
{t('common:actions.close')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<pre className="text-xs text-muted-foreground bg-background/50 p-3 rounded max-h-60 overflow-auto whitespace-pre-wrap font-mono">
|
<pre className="text-[12px] text-muted-foreground bg-white dark:bg-[#1a1a19] p-4 rounded-xl max-h-60 overflow-auto whitespace-pre-wrap font-mono border border-black/5 dark:border-white/5 shadow-inner">
|
||||||
{logContent || t('chat:noLogs')}
|
{logContent || t('chat:noLogs')}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<Label>{t('gateway.autoStart')}</Label>
|
<Label className="text-[15px] font-medium text-foreground">{t('gateway.autoStart')}</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-[13px] text-muted-foreground mt-1">
|
||||||
{t('gateway.autoStartDesc')}
|
{t('gateway.autoStartDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -524,30 +508,39 @@ export function Settings() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
{devModeUnlocked ? (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="rounded-md border border-border/60 p-3">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="w-full justify-start"
|
|
||||||
onClick={() => setShowAdvancedProxy((prev) => !prev)}
|
|
||||||
>
|
|
||||||
{showAdvancedProxy ? (
|
|
||||||
<ChevronDown className="h-4 w-4 mr-2" />
|
|
||||||
) : (
|
|
||||||
<ChevronRight className="h-4 w-4 mr-2" />
|
|
||||||
)}
|
|
||||||
{showAdvancedProxy ? t('gateway.hideAdvancedProxy') : t('gateway.showAdvancedProxy')}
|
|
||||||
</Button>
|
|
||||||
{showAdvancedProxy && (
|
|
||||||
<div className="mt-3 space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<Label>{t('gateway.proxyTitle')}</Label>
|
<Label className="text-[15px] font-medium text-foreground">{t('advanced.devMode')}</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-[13px] text-muted-foreground mt-1">
|
||||||
|
{t('advanced.devModeDesc')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={devModeUnlocked}
|
||||||
|
onCheckedChange={setDevModeUnlocked}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Developer */}
|
||||||
|
{devModeUnlocked && (
|
||||||
|
<>
|
||||||
|
<Separator className="bg-black/5 dark:bg-white/5" />
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
|
{t('developer.title')}
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-8">
|
||||||
|
{/* Gateway Proxy */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Label className="text-[14px] font-medium text-foreground/80">Gateway Proxy</Label>
|
||||||
|
<p className="text-[13px] text-muted-foreground">
|
||||||
{t('gateway.proxyDesc')}
|
{t('gateway.proxyDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -557,201 +550,115 @@ export function Settings() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{proxyEnabledDraft && (
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="proxy-server">{t('gateway.proxyServer')}</Label>
|
<Label htmlFor="proxy-server" className="text-[13px] text-foreground/80">{t('gateway.proxyServer')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="proxy-server"
|
id="proxy-server"
|
||||||
value={proxyServerDraft}
|
value={proxyServerDraft}
|
||||||
onChange={(event) => setProxyServerDraft(event.target.value)}
|
onChange={(event) => setProxyServerDraft(event.target.value)}
|
||||||
placeholder="http://127.0.0.1:7890"
|
placeholder="http://127.0.0.1:7890"
|
||||||
|
className="h-10 rounded-xl bg-black/5 dark:bg-white/5 border-transparent font-mono text-[13px]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
{t('gateway.proxyServerHelp')}
|
{t('gateway.proxyServerHelp')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="proxy-http-server">{t('gateway.proxyHttpServer')}</Label>
|
<Label htmlFor="proxy-http-server" className="text-[13px] text-foreground/80">{t('gateway.proxyHttpServer')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="proxy-http-server"
|
id="proxy-http-server"
|
||||||
value={proxyHttpServerDraft}
|
value={proxyHttpServerDraft}
|
||||||
onChange={(event) => setProxyHttpServerDraft(event.target.value)}
|
onChange={(event) => setProxyHttpServerDraft(event.target.value)}
|
||||||
placeholder={proxyServerDraft || 'http://127.0.0.1:7890'}
|
placeholder={proxyServerDraft || 'http://127.0.0.1:7890'}
|
||||||
|
className="h-10 rounded-xl bg-black/5 dark:bg-white/5 border-transparent font-mono text-[13px]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
{t('gateway.proxyHttpServerHelp')}
|
{t('gateway.proxyHttpServerHelp')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="proxy-https-server">{t('gateway.proxyHttpsServer')}</Label>
|
<Label htmlFor="proxy-https-server" className="text-[13px] text-foreground/80">{t('gateway.proxyHttpsServer')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="proxy-https-server"
|
id="proxy-https-server"
|
||||||
value={proxyHttpsServerDraft}
|
value={proxyHttpsServerDraft}
|
||||||
onChange={(event) => setProxyHttpsServerDraft(event.target.value)}
|
onChange={(event) => setProxyHttpsServerDraft(event.target.value)}
|
||||||
placeholder={proxyServerDraft || 'http://127.0.0.1:7890'}
|
placeholder={proxyServerDraft || 'http://127.0.0.1:7890'}
|
||||||
|
className="h-10 rounded-xl bg-black/5 dark:bg-white/5 border-transparent font-mono text-[13px]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
{t('gateway.proxyHttpsServerHelp')}
|
{t('gateway.proxyHttpsServerHelp')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="proxy-all-server">{t('gateway.proxyAllServer')}</Label>
|
<Label htmlFor="proxy-all-server" className="text-[13px] text-foreground/80">{t('gateway.proxyAllServer')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="proxy-all-server"
|
id="proxy-all-server"
|
||||||
value={proxyAllServerDraft}
|
value={proxyAllServerDraft}
|
||||||
onChange={(event) => setProxyAllServerDraft(event.target.value)}
|
onChange={(event) => setProxyAllServerDraft(event.target.value)}
|
||||||
placeholder={proxyServerDraft || 'socks5://127.0.0.1:7891'}
|
placeholder={proxyServerDraft || 'socks5://127.0.0.1:7891'}
|
||||||
|
className="h-10 rounded-xl bg-black/5 dark:bg-white/5 border-transparent font-mono text-[13px]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
{t('gateway.proxyAllServerHelp')}
|
{t('gateway.proxyAllServerHelp')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="proxy-bypass">{t('gateway.proxyBypass')}</Label>
|
<Label htmlFor="proxy-bypass" className="text-[13px] text-foreground/80">{t('gateway.proxyBypass')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="proxy-bypass"
|
id="proxy-bypass"
|
||||||
value={proxyBypassRulesDraft}
|
value={proxyBypassRulesDraft}
|
||||||
onChange={(event) => setProxyBypassRulesDraft(event.target.value)}
|
onChange={(event) => setProxyBypassRulesDraft(event.target.value)}
|
||||||
placeholder="<local>;localhost;127.0.0.1;::1"
|
placeholder="<local>;localhost;127.0.0.1;::1"
|
||||||
|
className="h-10 rounded-xl bg-black/5 dark:bg-white/5 border-transparent font-mono text-[13px]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
{t('gateway.proxyBypassHelp')}
|
{t('gateway.proxyBypassHelp')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between gap-3 rounded-lg border border-border/60 bg-background/40 p-3">
|
<div className="flex items-center gap-4 pt-2">
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('gateway.proxyRestartNote')}
|
|
||||||
</p>
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleSaveProxySettings}
|
onClick={handleSaveProxySettings}
|
||||||
disabled={savingProxy}
|
disabled={savingProxy}
|
||||||
|
className="rounded-xl h-10 px-5 bg-transparent border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
<RefreshCw className={`h-4 w-4 mr-2${savingProxy ? ' animate-spin' : ''}`} />
|
<RefreshCw className={`h-4 w-4 mr-2${savingProxy ? ' animate-spin' : ''}`} />
|
||||||
{savingProxy ? t('common:status.saving') : t('common:actions.save')}
|
{savingProxy ? t('common:status.saving') : t('common:actions.save')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<p className="text-[12px] text-muted-foreground">
|
||||||
|
{t('gateway.proxyRestartNote')}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="space-y-4 pt-4">
|
||||||
) : (
|
<Label className="text-[14px] font-medium text-foreground/80">{t('developer.gatewayToken')}</Label>
|
||||||
<div className="rounded-md border border-border/60 bg-muted/30 p-4 text-sm text-muted-foreground">
|
<p className="text-[13px] text-muted-foreground">
|
||||||
{t('advanced.devModeDesc')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Updates */}
|
|
||||||
<Card className="order-2">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Download className="h-5 w-5" />
|
|
||||||
{t('updates.title')}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>{t('updates.description')}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<UpdateSettings />
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>{t('updates.autoCheck')}</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('updates.autoCheckDesc')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={autoCheckUpdate}
|
|
||||||
onCheckedChange={setAutoCheckUpdate}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>{t('updates.autoDownload')}</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('updates.autoDownloadDesc')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={autoDownloadUpdate}
|
|
||||||
onCheckedChange={(value) => {
|
|
||||||
setAutoDownloadUpdate(value);
|
|
||||||
updateSetAutoDownload(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Advanced */}
|
|
||||||
<Card className="order-2">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>{t('advanced.title')}</CardTitle>
|
|
||||||
<CardDescription>{t('advanced.description')}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>{t('advanced.devMode')}</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('advanced.devModeDesc')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={devModeUnlocked}
|
|
||||||
onCheckedChange={setDevModeUnlocked}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Developer */}
|
|
||||||
{devModeUnlocked && (
|
|
||||||
<Card className="order-2">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>{t('developer.title')}</CardTitle>
|
|
||||||
<CardDescription>{t('developer.description')}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>{t('developer.console')}</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('developer.consoleDesc')}
|
|
||||||
</p>
|
|
||||||
<Button variant="outline" onClick={openDevConsole}>
|
|
||||||
<Terminal className="h-4 w-4 mr-2" />
|
|
||||||
{t('developer.openConsole')}
|
|
||||||
<ExternalLink className="h-3 w-3 ml-2" />
|
|
||||||
</Button>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t('developer.consoleNote')}
|
|
||||||
</p>
|
|
||||||
<div className="space-y-2 pt-2">
|
|
||||||
<Label>{t('developer.gatewayToken')}</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('developer.gatewayTokenDesc')}
|
{t('developer.gatewayTokenDesc')}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
value={controlUiInfo?.token || ''}
|
value={controlUiInfo?.token || ''}
|
||||||
placeholder={t('developer.tokenUnavailable')}
|
placeholder={t('developer.tokenUnavailable')}
|
||||||
className="font-mono"
|
className="font-mono text-[13px] h-10 rounded-xl bg-black/5 dark:bg-white/5 border-transparent flex-1 min-w-[200px]"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={refreshControlUiInfo}
|
onClick={refreshControlUiInfo}
|
||||||
disabled={!devModeUnlocked}
|
disabled={!devModeUnlocked}
|
||||||
|
className="rounded-xl h-10 px-4 bg-transparent border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-4 w-4 mr-2" />
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
{t('common:actions.load')}
|
{t('common:actions.load')}
|
||||||
@@ -761,53 +668,51 @@ export function Settings() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleCopyGatewayToken}
|
onClick={handleCopyGatewayToken}
|
||||||
disabled={!controlUiInfo?.token}
|
disabled={!controlUiInfo?.token}
|
||||||
|
className="rounded-xl h-10 px-4 bg-transparent border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
<Copy className="h-4 w-4 mr-2" />
|
<Copy className="h-4 w-4 mr-2" />
|
||||||
{t('common:actions.copy')}
|
{t('common:actions.copy')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{showCliTools && (
|
{showCliTools && (
|
||||||
<>
|
<div className="space-y-3">
|
||||||
<Separator />
|
<Label className="text-[15px] font-medium text-foreground">{t('developer.cli')}</Label>
|
||||||
<div className="space-y-2">
|
<p className="text-[13px] text-muted-foreground">
|
||||||
<Label>{t('developer.cli')}</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('developer.cliDesc')}
|
{t('developer.cliDesc')}
|
||||||
</p>
|
</p>
|
||||||
{isWindows && (
|
{isWindows && (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-[12px] text-muted-foreground">
|
||||||
{t('developer.cliPowershell')}
|
{t('developer.cliPowershell')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
value={openclawCliCommand}
|
value={openclawCliCommand}
|
||||||
placeholder={openclawCliError || t('developer.cmdUnavailable')}
|
placeholder={openclawCliError || t('developer.cmdUnavailable')}
|
||||||
className="font-mono"
|
className="font-mono text-[13px] h-10 rounded-xl bg-black/5 dark:bg-white/5 border-transparent flex-1 min-w-[200px]"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleCopyCliCommand}
|
onClick={handleCopyCliCommand}
|
||||||
disabled={!openclawCliCommand}
|
disabled={!openclawCliCommand}
|
||||||
|
className="rounded-xl h-10 px-4 bg-transparent border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
<Copy className="h-4 w-4 mr-2" />
|
<Copy className="h-4 w-4 mr-2" />
|
||||||
{t('common:actions.copy')}
|
{t('common:actions.copy')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Separator />
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="flex items-center justify-between rounded-2xl border border-black/10 dark:border-white/10 p-5 bg-transparent">
|
||||||
<div className="flex items-center justify-between rounded-md border border-border/60 p-3">
|
|
||||||
<div>
|
<div>
|
||||||
<Label>{t('developer.wsDiagnostic')}</Label>
|
<Label className="text-[14px] font-medium text-foreground">{t('developer.wsDiagnostic')}</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-[13px] text-muted-foreground mt-1">
|
||||||
{t('developer.wsDiagnosticDesc')}
|
{t('developer.wsDiagnosticDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -819,8 +724,8 @@ export function Settings() {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<Label>{t('developer.telemetryViewer')}</Label>
|
<Label className="text-[14px] font-medium text-foreground">{t('developer.telemetryViewer')}</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-[13px] text-muted-foreground mt-1">
|
||||||
{t('developer.telemetryViewerDesc')}
|
{t('developer.telemetryViewerDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -829,6 +734,7 @@ export function Settings() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setShowTelemetryViewer((prev) => !prev)}
|
onClick={() => setShowTelemetryViewer((prev) => !prev)}
|
||||||
|
className="rounded-full px-5 h-9 bg-transparent border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
{showTelemetryViewer
|
{showTelemetryViewer
|
||||||
? t('common:actions.hide')
|
? t('common:actions.hide')
|
||||||
@@ -837,37 +743,37 @@ export function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showTelemetryViewer && (
|
{showTelemetryViewer && (
|
||||||
<div className="space-y-3 rounded-lg border border-border/60 p-3">
|
<div className="space-y-4 rounded-2xl border border-black/10 dark:border-white/10 p-5 bg-black/5 dark:bg-white/5">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Badge variant="secondary">{t('developer.telemetryTotal')}: {telemetryStats.total}</Badge>
|
<Badge variant="secondary" className="rounded-full px-3 py-1 bg-white dark:bg-[#1a1a19] border border-black/5 dark:border-white/5">{t('developer.telemetryTotal')}: {telemetryStats.total}</Badge>
|
||||||
<Badge variant={telemetryStats.errorCount > 0 ? 'destructive' : 'secondary'}>
|
<Badge variant={telemetryStats.errorCount > 0 ? 'destructive' : 'secondary'} className={cn("rounded-full px-3 py-1", telemetryStats.errorCount === 0 && "bg-white dark:bg-[#1a1a19] border border-black/5 dark:border-white/5")}>
|
||||||
{t('developer.telemetryErrors')}: {telemetryStats.errorCount}
|
{t('developer.telemetryErrors')}: {telemetryStats.errorCount}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant={telemetryStats.slowCount > 0 ? 'secondary' : 'outline'}>
|
<Badge variant={telemetryStats.slowCount > 0 ? 'secondary' : 'outline'} className={cn("rounded-full px-3 py-1", telemetryStats.slowCount === 0 && "bg-white dark:bg-[#1a1a19] border border-black/5 dark:border-white/5")}>
|
||||||
{t('developer.telemetrySlow')}: {telemetryStats.slowCount}
|
{t('developer.telemetrySlow')}: {telemetryStats.slowCount}
|
||||||
</Badge>
|
</Badge>
|
||||||
<div className="ml-auto flex gap-2">
|
<div className="ml-auto flex gap-2">
|
||||||
<Button type="button" variant="outline" size="sm" onClick={handleCopyTelemetry}>
|
<Button type="button" variant="outline" size="sm" onClick={handleCopyTelemetry} className="rounded-full h-8 px-4 bg-white dark:bg-[#1a1a19] border-black/5 dark:border-white/5 hover:bg-black/5 dark:hover:bg-white/10">
|
||||||
<Copy className="h-4 w-4 mr-2" />
|
<Copy className="h-3.5 w-3.5 mr-1.5" />
|
||||||
{t('common:actions.copy')}
|
{t('common:actions.copy')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="button" variant="outline" size="sm" onClick={handleClearTelemetry}>
|
<Button type="button" variant="outline" size="sm" onClick={handleClearTelemetry} className="rounded-full h-8 px-4 bg-white dark:bg-[#1a1a19] border-black/5 dark:border-white/5 hover:bg-black/5 dark:hover:bg-white/10">
|
||||||
{t('common:actions.clear')}
|
{t('common:actions.clear')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-h-72 overflow-auto rounded-md border border-border/50 bg-muted/20">
|
<div className="max-h-80 overflow-auto rounded-xl border border-black/10 dark:border-white/10 bg-white dark:bg-[#1a1a19] shadow-inner">
|
||||||
{telemetryByEvent.length > 0 && (
|
{telemetryByEvent.length > 0 && (
|
||||||
<div className="border-b border-border/50 bg-background/70 p-2">
|
<div className="border-b border-black/5 dark:border-white/5 bg-black/5 dark:bg-white/5 p-3">
|
||||||
<p className="mb-2 text-[11px] font-semibold text-muted-foreground">
|
<p className="mb-3 text-[12px] font-semibold text-muted-foreground">
|
||||||
{t('developer.telemetryAggregated')}
|
{t('developer.telemetryAggregated')}
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-1 text-[11px]">
|
<div className="space-y-1.5 text-[12px]">
|
||||||
{telemetryByEvent.map((item) => (
|
{telemetryByEvent.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.event}
|
key={item.event}
|
||||||
className="grid grid-cols-[minmax(0,1.6fr)_0.7fr_0.9fr_0.8fr_1fr] gap-2 rounded border border-border/40 px-2 py-1"
|
className="grid grid-cols-[minmax(0,1.6fr)_0.7fr_0.9fr_0.8fr_1fr] gap-2 rounded-lg border border-black/5 dark:border-white/5 bg-white dark:bg-[#1a1a19] px-3 py-2"
|
||||||
>
|
>
|
||||||
<span className="truncate font-medium" title={item.event}>{item.event}</span>
|
<span className="truncate font-medium" title={item.event}>{item.event}</span>
|
||||||
<span className="text-muted-foreground">n={item.count}</span>
|
<span className="text-muted-foreground">n={item.count}</span>
|
||||||
@@ -881,20 +787,20 @@ export function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="space-y-1 p-2 font-mono text-xs">
|
<div className="space-y-2 p-3 font-mono text-[12px]">
|
||||||
{telemetryEntries.length === 0 ? (
|
{telemetryEntries.length === 0 ? (
|
||||||
<div className="text-muted-foreground">{t('developer.telemetryEmpty')}</div>
|
<div className="text-muted-foreground text-center py-4">{t('developer.telemetryEmpty')}</div>
|
||||||
) : (
|
) : (
|
||||||
telemetryEntries
|
telemetryEntries
|
||||||
.slice()
|
.slice()
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((entry) => (
|
.map((entry) => (
|
||||||
<div key={entry.id} className="rounded border border-border/40 bg-background/60 p-2">
|
<div key={entry.id} className="rounded-lg border border-black/5 dark:border-white/5 bg-black/5 dark:bg-white/5 p-3">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3 mb-2">
|
||||||
<span className="font-semibold">{entry.event}</span>
|
<span className="font-semibold text-foreground">{entry.event}</span>
|
||||||
<span className="text-muted-foreground">{entry.ts}</span>
|
<span className="text-muted-foreground text-[11px]">{entry.ts}</span>
|
||||||
</div>
|
</div>
|
||||||
<pre className="mt-1 whitespace-pre-wrap text-[11px] text-muted-foreground">
|
<pre className="whitespace-pre-wrap text-[11px] text-muted-foreground overflow-x-auto">
|
||||||
{JSON.stringify({ count: entry.count, ...entry.payload }, null, 2)}
|
{JSON.stringify({ count: entry.count, ...entry.payload }, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
@@ -905,39 +811,86 @@ export function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Separator className="bg-black/5 dark:bg-white/5" />
|
||||||
|
|
||||||
|
{/* Updates */}
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
|
{t('updates.title')}
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<UpdateSettings />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Label className="text-[15px] font-medium text-foreground">{t('updates.autoCheck')}</Label>
|
||||||
|
<p className="text-[13px] text-muted-foreground mt-1">
|
||||||
|
{t('updates.autoCheckDesc')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={autoCheckUpdate}
|
||||||
|
onCheckedChange={setAutoCheckUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Label className="text-[15px] font-medium text-foreground">{t('updates.autoDownload')}</Label>
|
||||||
|
<p className="text-[13px] text-muted-foreground mt-1">
|
||||||
|
{t('updates.autoDownloadDesc')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={autoDownloadUpdate}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
setAutoDownloadUpdate(value);
|
||||||
|
updateSetAutoDownload(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="bg-black/5 dark:bg-white/5" />
|
||||||
|
|
||||||
{/* About */}
|
{/* About */}
|
||||||
<Card className="order-2">
|
<div>
|
||||||
<CardHeader>
|
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
<CardTitle>{t('about.title')}</CardTitle>
|
{t('about.title')}
|
||||||
</CardHeader>
|
</h2>
|
||||||
<CardContent className="space-y-2 text-sm text-muted-foreground">
|
<div className="space-y-3 text-[14px] text-muted-foreground">
|
||||||
<p>
|
<p>
|
||||||
<strong>{t('about.appName')}</strong> - {t('about.tagline')}
|
<strong className="text-foreground font-semibold">{t('about.appName')}</strong> - {t('about.tagline')}
|
||||||
</p>
|
</p>
|
||||||
<p>{t('about.basedOn')}</p>
|
<p>{t('about.basedOn')}</p>
|
||||||
<p>{t('about.version', { version: currentVersion })}</p>
|
<p>{t('about.version', { version: currentVersion })}</p>
|
||||||
<div className="flex gap-4 pt-2">
|
<div className="flex gap-4 pt-3">
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0"
|
className="h-auto p-0 text-[14px] text-blue-500 hover:text-blue-600 font-medium"
|
||||||
onClick={() => window.electron.openExternal('https://claw-x.com')}
|
onClick={() => window.electron.openExternal('https://claw-x.com')}
|
||||||
>
|
>
|
||||||
{t('about.docs')}
|
{t('about.docs')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0"
|
className="h-auto p-0 text-[14px] text-blue-500 hover:text-blue-600 font-medium"
|
||||||
onClick={() => window.electron.openExternal('https://github.com/ValueCell-ai/ClawX')}
|
onClick={() => window.electron.openExternal('https://github.com/ValueCell-ai/ClawX')}
|
||||||
>
|
>
|
||||||
{t('about.github')}
|
{t('about.github')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user