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)
|
||||
'@sliverp/qqbot':
|
||||
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':
|
||||
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)))
|
||||
@@ -1384,6 +1384,34 @@ packages:
|
||||
os: [linux]
|
||||
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':
|
||||
resolution: {integrity: sha512-htVIthQKq/rr8v5e7NiVtcHsstqTBAAC50kUymmDMbrzAu6d/EHacCJpNbU57b1UUa1nKN5cBqr6Jr+QqEalMA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -1448,6 +1476,42 @@ packages:
|
||||
cpu: [arm64, x64]
|
||||
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':
|
||||
resolution: {integrity: sha512-gWhtc8l3HOry5guO46YfFohLQnF0NfL4On0GAO8E27JiYYxHO9nHSCfFif4+U03+FfHquZXL0znJ1qPVOiwOPw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -3597,8 +3661,8 @@ packages:
|
||||
devlop@1.1.0:
|
||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||
|
||||
devtools-protocol@0.0.1595872:
|
||||
resolution: {integrity: sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg==}
|
||||
devtools-protocol@0.0.1596832:
|
||||
resolution: {integrity: sha512-IwRVIiCa4mpaKeLcZ2cmGpG0hP8ls3zj3zg87Z/JwULm2xYmhOcMrwdeHos6xaANQHGEXzSCzji+6kEuZu873A==}
|
||||
|
||||
didyoumean@1.2.2:
|
||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||
@@ -4141,6 +4205,7 @@ packages:
|
||||
glob@11.1.0:
|
||||
resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
|
||||
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
|
||||
|
||||
glob@13.0.6:
|
||||
@@ -4353,11 +4418,6 @@ packages:
|
||||
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
ipull@3.9.3:
|
||||
resolution: {integrity: sha512-ZMkxaopfwKHwmEuGDYx7giNBdLxbHbRCWcQVA1D2eqE4crUguupfxej6s7UqbidYEwT69dkyumYkY8DPHIxF9g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
ipull@3.9.5:
|
||||
resolution: {integrity: sha512-5w/yZB5lXmTfsvNawmvkCjYo4SJNuKQz/av8TC1UiOyfOHyaM+DReqbpU2XpWYfmY+NIUbRRH8PUAWsxaS+IfA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -6125,6 +6185,7 @@ packages:
|
||||
tar@7.5.4:
|
||||
resolution: {integrity: sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==}
|
||||
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:
|
||||
resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==}
|
||||
@@ -8273,6 +8334,18 @@ snapshots:
|
||||
'@node-llama-cpp/linux-armv7l@3.16.2':
|
||||
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':
|
||||
optional: true
|
||||
|
||||
@@ -8303,6 +8376,24 @@ snapshots:
|
||||
'@node-llama-cpp/win-arm64@3.16.2':
|
||||
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':
|
||||
optional: true
|
||||
|
||||
@@ -9145,9 +9236,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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:
|
||||
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
|
||||
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: {}
|
||||
|
||||
chromium-bidi@13.0.1(devtools-protocol@0.0.1595872):
|
||||
chromium-bidi@13.0.1(devtools-protocol@0.0.1596832):
|
||||
dependencies:
|
||||
devtools-protocol: 0.0.1595872
|
||||
devtools-protocol: 0.0.1596832
|
||||
mitt: 3.0.1
|
||||
zod: 3.25.76
|
||||
|
||||
@@ -10389,7 +10480,7 @@ snapshots:
|
||||
dependencies:
|
||||
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:
|
||||
'@agentclientprotocol/sdk': 0.13.1(zod@4.3.6)
|
||||
'@aws-sdk/client-bedrock': 3.1000.0
|
||||
@@ -10413,7 +10504,7 @@ snapshots:
|
||||
body-parser: 2.2.2
|
||||
chalk: 5.6.2
|
||||
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
|
||||
commander: 14.0.3
|
||||
croner: 9.1.0
|
||||
@@ -10751,7 +10842,7 @@ snapshots:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
devtools-protocol@0.0.1595872: {}
|
||||
devtools-protocol@0.0.1596832: {}
|
||||
|
||||
didyoumean@1.2.2: {}
|
||||
|
||||
@@ -11710,31 +11801,6 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
'@tinyhttp/content-disposition': 2.2.4
|
||||
@@ -12685,7 +12751,7 @@ snapshots:
|
||||
filenamify: 6.0.0
|
||||
fs-extra: 11.3.3
|
||||
ignore: 7.0.5
|
||||
ipull: 3.9.3
|
||||
ipull: 3.9.5
|
||||
is-unicode-supported: 2.1.0
|
||||
lifecycle-utils: 3.1.1
|
||||
log-symbols: 7.0.1
|
||||
@@ -12707,11 +12773,16 @@ snapshots:
|
||||
'@node-llama-cpp/linux-arm64': 3.15.0
|
||||
'@node-llama-cpp/linux-armv7l': 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/mac-arm64-metal': 3.15.0
|
||||
'@node-llama-cpp/mac-x64': 3.15.0
|
||||
'@node-llama-cpp/win-arm64': 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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -12752,11 +12823,16 @@ snapshots:
|
||||
'@node-llama-cpp/linux-arm64': 3.16.2
|
||||
'@node-llama-cpp/linux-armv7l': 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/mac-arm64-metal': 3.16.2
|
||||
'@node-llama-cpp/mac-x64': 3.16.2
|
||||
'@node-llama-cpp/win-arm64': 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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Toaster } from 'sonner';
|
||||
import i18n from './i18n';
|
||||
import { MainLayout } from './components/layout/MainLayout';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { Dashboard } from './pages/Dashboard';
|
||||
import { Models } from './pages/Models';
|
||||
import { Chat } from './pages/Chat';
|
||||
import { Channels } from './pages/Channels';
|
||||
@@ -166,7 +165,6 @@ function App() {
|
||||
<Route element={<MainLayout />}>
|
||||
<Route path="/" element={<Chat />} />
|
||||
<Route path="/models" element={<Models />} />
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/channels" element={<Channels />} />
|
||||
<Route path="/skills" element={<Skills />} />
|
||||
<Route path="/cron" element={<Cron />} />
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
ExternalLink,
|
||||
Trash2,
|
||||
Cpu,
|
||||
LayoutDashboard,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
@@ -105,7 +104,6 @@ const INITIAL_NOW_MS = Date.now();
|
||||
export function Sidebar() {
|
||||
const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
|
||||
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
|
||||
const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked);
|
||||
|
||||
const sessions = useChatStore((s) => s.sessions);
|
||||
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: '/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: '/dashboard', icon: <LayoutDashboard className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.dashboard') },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -286,21 +283,6 @@ export function Sidebar() {
|
||||
|
||||
{/* Footer */}
|
||||
<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
|
||||
to="/settings"
|
||||
className={({ isActive }) =>
|
||||
@@ -321,6 +303,26 @@ export function Sidebar() {
|
||||
</>
|
||||
)}
|
||||
</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>
|
||||
|
||||
<ConfirmDialog
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Textarea } from '@/components/ui/textarea';
|
||||
import { hostApiFetch } from '@/lib/host-api';
|
||||
import { invokeIpc } from '@/lib/api-client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useGatewayStore } from '@/stores/gateway';
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -84,6 +85,7 @@ export function ChatInput({ onSend, onStop, disabled = false, sending = false, i
|
||||
const [attachments, setAttachments] = useState<FileAttachment[]>([]);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const isComposingRef = useRef(false);
|
||||
const gatewayStatus = useGatewayStore((s) => s.status);
|
||||
|
||||
// Auto-resize textarea
|
||||
useEffect(() => {
|
||||
@@ -407,7 +409,12 @@ export function ChatInput({ onSend, onStop, disabled = false, sending = false, i
|
||||
</Button>
|
||||
</div>
|
||||
<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 && (
|
||||
<Button
|
||||
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;
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user