@nextclaw/ui 0.12.23 → 0.12.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/dist/assets/api-DGD9_Bg4.js +15 -0
- package/dist/assets/app-manager-provider-oYdeYPSv.js +1 -0
- package/dist/assets/{book-open-DDlN5MvX.js → book-open-BcnAiKde.js} +1 -1
- package/dist/assets/channels-list-page-FJDuPwU6.js +8 -0
- package/dist/assets/chat-page-D1fMNBrT.js +1 -0
- package/dist/assets/config-split-page-CcrEUtwu.js +1 -0
- package/dist/assets/cpu-DPPwMzoC.js +3 -0
- package/dist/assets/{createLucideIcon-BLMK3QUd.js → createLucideIcon-DzY6wN61.js} +1 -1
- package/dist/assets/desktop-kk7qvZ-v.js +3 -0
- package/dist/assets/desktop-update-config-CP8dFYXK.js +1 -0
- package/dist/assets/{dialog-dxsKz7jJ.js → dialog-BKo0RItd.js} +1 -1
- package/dist/assets/{dist-DsYTOyq7.js → dist-CFiwgaLs.js} +1 -1
- package/dist/assets/doc-browser-CAhfnm0D.js +1 -0
- package/dist/assets/{doc-browser-context-BJuMaI3o.js → doc-browser-context-FukQHvyo.js} +1 -1
- package/dist/assets/doc-browser-p9DDNPWB.js +1 -0
- package/dist/assets/doc-browser-rZIQIjuw.js +1 -0
- package/dist/assets/download-CMM8po31.js +1 -0
- package/dist/assets/{es2015-V75WQJ2s.js → es2015-BhznEEyJ.js} +1 -1
- package/dist/assets/{external-link-DwfSfTLB.js → external-link-CpEvG65F.js} +1 -1
- package/dist/assets/i18n-D1144VAA.js +1 -0
- package/dist/assets/index-D-AAMKCt.js +103 -0
- package/dist/assets/index-DnBeV2Xm.css +1 -0
- package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
- package/dist/assets/marketplace-page-BrCLRIc4.js +105 -0
- package/dist/assets/marketplace-page-odDpPYEs.js +1 -0
- package/dist/assets/mcp-marketplace-page-CfbOBgKK.js +1 -0
- package/dist/assets/mcp-marketplace-page-DIq_SpMe.js +40 -0
- package/dist/assets/model-config-Bc6VVnxy.js +1 -0
- package/dist/assets/{notice-card-D1RNsTn_.js → notice-card-Dr6xCwva.js} +1 -1
- package/dist/assets/play-AqrNslHI.js +1 -0
- package/dist/assets/plus-B-YHtTNC.js +1 -0
- package/dist/assets/{popover-BMyiifTA.js → popover-BDFNiLlg.js} +1 -1
- package/dist/assets/provider-scoped-model-input-BMTp4BEH.js +1 -0
- package/dist/assets/providers-list-DN0tvISH.js +1 -0
- package/dist/assets/refresh-cw-CrbD8EkT.js +1 -0
- package/dist/assets/remote-Dr3jcfWP.js +1 -0
- package/dist/assets/{rotate-cw-BZ2JObNs.js → rotate-cw-BN9yjccP.js} +1 -1
- package/dist/assets/runtime-config-page-CRWOwBbl.js +1 -0
- package/dist/assets/{save-euRxl8pI.js → save-CO_4qf6b.js} +1 -1
- package/dist/assets/{search-CLd7m0M7.js → search-CRtQwr-h.js} +1 -1
- package/dist/assets/search-config-C4c1yZSP.js +1 -0
- package/dist/assets/secrets-config-zAF30YfO.js +3 -0
- package/dist/assets/{select-DTdzR8j8.js → select-BUTwE_lC.js} +1 -1
- package/dist/assets/{setting-row-CvKngoNI.js → setting-row-BavcnXw1.js} +1 -1
- package/dist/assets/settings-MWL2SMyk.js +1 -0
- package/dist/assets/{sparkles-DVfeSVJQ.js → sparkles-BmgOD4nY.js} +1 -1
- package/dist/assets/{status-dot-ChvPCib9.js → status-dot-l3kPFdq_.js} +1 -1
- package/dist/assets/{tabs-custom-Hia_ong0.js → tabs-custom-D48zdZoc.js} +1 -1
- package/dist/assets/{tag-chip-BywQeHJj.js → tag-chip-Dm2Lqnpu.js} +1 -1
- package/dist/assets/use-config-Cyv5IuSt.js +1 -0
- package/dist/assets/use-infinite-scroll-loader-Cvz8ZteY.js +1 -0
- package/dist/assets/x-BeyYA_h6.js +1 -0
- package/dist/index.html +29 -40
- package/package.json +9 -9
- package/src/app/components/layout/sidebar.layout.test.tsx +2 -4
- package/src/app/components/theme-provider.tsx +1 -0
- package/src/app/configs/app-navigation.config.ts +0 -6
- package/src/app/index.tsx +4 -7
- package/src/features/agents/components/agents-page.test.tsx +25 -15
- package/src/features/agents/components/agents-page.tsx +133 -172
- package/src/features/channels/components/config/channel-form.test.tsx +1 -0
- package/src/features/channels/components/config/channel-form.tsx +4 -3
- package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +38 -1
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +137 -40
- package/src/features/channels/index.ts +1 -1
- package/src/features/channels/utils/channel-form-fields.utils.test.ts +26 -0
- package/src/features/channels/utils/channel-form-fields.utils.ts +32 -18
- package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +23 -4
- package/src/features/chat/components/chat-session-workspace-panel.tsx +34 -2
- package/src/features/chat/components/chat-sidebar-session-item.tsx +9 -3
- package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +71 -0
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +6 -0
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +181 -61
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +56 -25
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.test.tsx +24 -0
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +26 -5
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +174 -0
- package/src/features/chat/components/layout/chat-sidebar.test.tsx +119 -8
- package/src/features/chat/components/layout/chat-sidebar.tsx +57 -75
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
- package/src/features/chat/components/workspace/session-cron-job-content.tsx +103 -0
- package/src/features/chat/hooks/use-hydrated-ncp-agent.test.tsx +6 -0
- package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +172 -69
- package/src/features/chat/hooks/use-ncp-chat-derived-state.ts +2 -2
- package/src/features/chat/hooks/use-ncp-chat-page-data.test.tsx +70 -0
- package/src/features/chat/hooks/use-ncp-chat-page-data.ts +7 -7
- package/src/features/chat/hooks/use-ncp-child-session-tabs-view.ts +2 -8
- package/src/features/chat/hooks/use-ncp-session-conversation.test.tsx +10 -0
- package/src/features/chat/hooks/use-ncp-session-conversation.ts +2 -1
- package/src/features/chat/hooks/use-ncp-session-list-view.ts +1 -2
- package/src/features/chat/hooks/use-selected-session-context-window-indicator.ts +2 -4
- package/src/features/chat/managers/chat-session-list.manager.test.ts +21 -20
- package/src/features/chat/managers/chat-session-list.manager.ts +15 -24
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +22 -13
- package/src/features/chat/managers/ncp-chat-input.manager.ts +4 -2
- package/src/features/chat/managers/ncp-chat-presenter.manager.ts +6 -0
- package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +52 -1
- package/src/features/chat/managers/ncp-chat-thread.manager.ts +21 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +28 -17
- package/src/features/chat/stores/chat-session-list.store.ts +0 -3
- package/src/features/chat/stores/chat-thread.store.ts +4 -0
- package/src/features/chat/types/chat-stream.types.ts +1 -1
- package/src/features/chat/utils/chat-session-display.utils.test.ts +83 -1
- package/src/features/chat/utils/chat-session-display.utils.ts +73 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +22 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +33 -1
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +235 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config.ts +162 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +355 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +118 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +201 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc.test.ts +40 -0
- package/src/features/marketplace/components/marketplace-catalog-grid.tsx +114 -0
- package/src/features/marketplace/components/marketplace-detail-doc.ts +73 -24
- package/src/features/marketplace/components/marketplace-item-icon.tsx +45 -0
- package/src/features/marketplace/components/marketplace-list-card.tsx +177 -93
- package/src/features/marketplace/components/marketplace-page-detail.test.tsx +9 -2
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/marketplace/components/marketplace-page.test.tsx +25 -6
- package/src/features/marketplace/components/marketplace-page.tsx +154 -132
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +97 -0
- package/src/features/marketplace/hooks/use-marketplace.ts +59 -3
- package/src/features/system-status/components/config/runtime-agent-list-card.tsx +4 -8
- package/src/features/system-status/components/config/runtime-binding-list-card.tsx +5 -7
- package/src/features/system-status/components/config/runtime-config-editor.tsx +1 -19
- package/src/features/system-status/components/config/runtime-entry-list-card.tsx +10 -11
- package/src/features/system-status/components/config/runtime-settings-card.tsx +15 -23
- package/src/features/system-status/components/runtime-control-card.test.tsx +8 -6
- package/src/features/system-status/components/runtime-control-card.tsx +7 -6
- package/src/features/system-status/pages/runtime-config-page.test.tsx +19 -9
- package/src/features/system-status/pages/runtime-config-page.tsx +2 -3
- package/src/features/system-status/utils/runtime-config-agent.utils.ts +4 -4
- package/src/features/system-status/utils/system-status.utils.ts +31 -6
- package/src/index.css +8 -0
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +67 -0
- package/src/platforms/desktop/components/desktop-app-shell.tsx +46 -18
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +30 -0
- package/src/platforms/desktop/index.ts +6 -0
- package/src/platforms/desktop/types/desktop-update.types.ts +3 -0
- package/src/platforms/desktop/utils/desktop-host.utils.ts +56 -0
- package/src/shared/components/common/brand-header.tsx +36 -16
- package/src/shared/components/config/provider-form-support.ts +2 -22
- package/src/shared/components/cron-config.tsx +12 -58
- package/src/shared/components/doc-browser/doc-browser.tsx +4 -4
- package/src/shared/components/ui/select.tsx +19 -7
- package/src/shared/lib/api/channel-auth.types.ts +1 -0
- package/src/shared/lib/api/ncp-session-query-cache.test.ts +26 -1
- package/src/shared/lib/api/ncp-session-query-cache.ts +5 -1
- package/src/shared/lib/api/ncp-session.types.ts +9 -0
- package/src/shared/lib/api/types.ts +12 -1
- package/src/shared/lib/api/utils/marketplace.utils.ts +7 -1
- package/src/shared/lib/cron/cron-job-view.utils.ts +59 -0
- package/src/shared/lib/cron/index.ts +1 -0
- package/src/shared/lib/i18n/{channel-auth.ts → channel-auth.constants.ts} +31 -0
- package/src/shared/lib/i18n/chat-labels.utils.ts +3 -2
- package/src/shared/lib/i18n/index.ts +20 -59
- package/src/shared/lib/i18n/{runtime-control.ts → runtime-control-labels.utils.ts} +30 -1
- package/src/shared/lib/provider-models/index.test.ts +39 -0
- package/src/shared/lib/provider-models/index.ts +1 -3
- package/src/shared/lib/ui-document-title/index.ts +0 -1
- package/tsconfig.json +1 -0
- package/vite.config.ts +1 -1
- package/vitest.config.ts +1 -1
- package/dist/assets/api-BGd3rgv_.js +0 -15
- package/dist/assets/app-manager-provider-BuJ_U9eC.js +0 -1
- package/dist/assets/app-navigation.config-BTdUuqXS.js +0 -1
- package/dist/assets/channels-list-page-BrwymXPe.js +0 -8
- package/dist/assets/chat-DGM6K3Qs.js +0 -61
- package/dist/assets/chat-page-DpmXMWNS.js +0 -1
- package/dist/assets/chunk-JZWAC4HX-Kydj4yEz.js +0 -3
- package/dist/assets/config-split-page-DIOCjj2Q.js +0 -1
- package/dist/assets/desktop-update-config-BGKiqc6q.js +0 -1
- package/dist/assets/doc-browser-C8FM5fC0.js +0 -1
- package/dist/assets/doc-browser-RJUOL_GO.js +0 -1
- package/dist/assets/doc-browser-p82AdNO-.js +0 -1
- package/dist/assets/folder-CeJKPx5P.js +0 -1
- package/dist/assets/hash-BqxRTZW5.js +0 -1
- package/dist/assets/i18n-DnTGDIRw.js +0 -1
- package/dist/assets/index-BrEdR78s.js +0 -2
- package/dist/assets/index-D8MKmXtO.css +0 -1
- package/dist/assets/loader-circle-fd-vQKtW.js +0 -1
- package/dist/assets/logo-badge-KAe-7d8c.js +0 -1
- package/dist/assets/logos-C4sYP1Vl.js +0 -1
- package/dist/assets/marketplace-page-B2Pm2RDJ.js +0 -1
- package/dist/assets/marketplace-page-CPHxlYL8.js +0 -49
- package/dist/assets/mcp-marketplace-page-BcjVmw36.js +0 -1
- package/dist/assets/mcp-marketplace-page-CswPXSjf.js +0 -40
- package/dist/assets/message-square-z_osm9c0.js +0 -1
- package/dist/assets/model-config-Cmruiqdx.js +0 -1
- package/dist/assets/play-Dv6Nr1Ew.js +0 -1
- package/dist/assets/plus-D8eKFY7h.js +0 -1
- package/dist/assets/provider-scoped-model-input-D7ACiMAO.js +0 -1
- package/dist/assets/providers-list-gg7LrfuB.js +0 -1
- package/dist/assets/refresh-ccw-ByVwmnN_.js +0 -1
- package/dist/assets/refresh-cw-PcqoYB3K.js +0 -1
- package/dist/assets/remote-Db2M39Cv.js +0 -1
- package/dist/assets/runtime-config-page-BT_VV41p.js +0 -1
- package/dist/assets/search-config-0VTPpz-w.js +0 -1
- package/dist/assets/secrets-config-DwQbLLEy.js +0 -3
- package/dist/assets/sessions-config-page-CAG7Zevv.js +0 -2
- package/dist/assets/settings-drbWqzA4.js +0 -1
- package/dist/assets/skeleton-BK1SOSRA.js +0 -1
- package/dist/assets/theme-provider-COAwWFv8.js +0 -2
- package/dist/assets/tooltip-BOYp8Ue7.js +0 -1
- package/dist/assets/trash-2-CBsHCfqq.js +0 -1
- package/dist/assets/use-config-DTwhNDQE.js +0 -1
- package/dist/assets/use-confirm-dialog-oeSqhmrx.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-X3KGuME8.js +0 -1
- package/dist/assets/use-viewport-layout-C0NJAVXs.js +0 -1
- package/dist/assets/x-CM-XDMpk.js +0 -1
- package/src/features/chat/components/config/sessions-config-detail-pane.tsx +0 -244
- package/src/features/chat/pages/sessions-config-page.test.tsx +0 -152
- package/src/features/chat/pages/sessions-config-page.tsx +0 -192
- /package/dist/assets/{config-hints-MogHYQ8G.js → config-hints-BNfpOL4J.js} +0 -0
|
@@ -13,6 +13,21 @@ import type {
|
|
|
13
13
|
SystemStatusView,
|
|
14
14
|
} from '@/features/system-status/types/system-status.types';
|
|
15
15
|
|
|
16
|
+
const RUNTIME_CONTROL_MESSAGE_LABELS: Record<string, string> = {
|
|
17
|
+
'Use this page to manage the local NextClaw service. Closing the browser does not stop the service.': 'runtimeControlManagedLocalMessage',
|
|
18
|
+
'This page is served by the running local service. Closing the browser does not stop it.': 'runtimeControlManagedLocalHint',
|
|
19
|
+
'This page is served by the running local service.': 'runtimeControlManagedLocalHintShort',
|
|
20
|
+
'This page is already hosted by the running local service.': 'runtimeControlStartUnavailableHosted',
|
|
21
|
+
'App restart is only available in the desktop shell.': 'runtimeControlRestartAppDesktopOnly',
|
|
22
|
+
'The local service is not running.': 'runtimeControlServiceNotRunning',
|
|
23
|
+
'The local service is already stopped.': 'runtimeControlServiceAlreadyStopped',
|
|
24
|
+
'Managed service started.': 'runtimeControlManagedServiceStarted',
|
|
25
|
+
'Managed service start scheduled.': 'runtimeControlManagedServiceStartScheduled',
|
|
26
|
+
'Restart scheduled. This page may disconnect for a few seconds.': 'runtimeControlRestartScheduled',
|
|
27
|
+
'Stop scheduled. This page will disconnect shortly.': 'runtimeControlStopScheduled',
|
|
28
|
+
'runtime healthy': 'runtimeControlHealthy'
|
|
29
|
+
};
|
|
30
|
+
|
|
16
31
|
function resolveSystemStatusPhase(state: SystemStatusState): SystemStatusPhase {
|
|
17
32
|
return state.activeSystemAction ? 'service-transitioning' : state.lifecyclePhase;
|
|
18
33
|
}
|
|
@@ -70,6 +85,14 @@ function resolveActionServiceState(
|
|
|
70
85
|
return null;
|
|
71
86
|
}
|
|
72
87
|
|
|
88
|
+
export function localizeRuntimeControlMessage(message: string | null | undefined): string | null {
|
|
89
|
+
if (!message) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const labelKey = RUNTIME_CONTROL_MESSAGE_LABELS[message.trim()];
|
|
93
|
+
return labelKey ? t(labelKey) : message;
|
|
94
|
+
}
|
|
95
|
+
|
|
73
96
|
export function buildActiveSystemActionState(params: {
|
|
74
97
|
action: RuntimeControlAction;
|
|
75
98
|
message: string | null;
|
|
@@ -113,8 +136,8 @@ export function toRuntimeStatusBadgeView(
|
|
|
113
136
|
tone: 'attention',
|
|
114
137
|
title: t('runtimeControlTitle'),
|
|
115
138
|
description:
|
|
116
|
-
state.activeSystemAction.message ||
|
|
117
|
-
state.runtimeControlView.message ||
|
|
139
|
+
localizeRuntimeControlMessage(state.activeSystemAction.message) ||
|
|
140
|
+
localizeRuntimeControlMessage(state.runtimeControlView.message) ||
|
|
118
141
|
t('runtimeControlDescription'),
|
|
119
142
|
reasonLines: [],
|
|
120
143
|
actionLabel: null,
|
|
@@ -161,9 +184,9 @@ export function toRuntimeControlPanelView(
|
|
|
161
184
|
const visibleServiceState =
|
|
162
185
|
action?.serviceState ?? controlView?.serviceState ?? 'unknown';
|
|
163
186
|
const visibleMessage =
|
|
164
|
-
action?.message ||
|
|
165
|
-
state.lastSystemActionError ||
|
|
166
|
-
controlView?.message ||
|
|
187
|
+
localizeRuntimeControlMessage(action?.message) ||
|
|
188
|
+
localizeRuntimeControlMessage(state.lastSystemActionError) ||
|
|
189
|
+
localizeRuntimeControlMessage(controlView?.message) ||
|
|
167
190
|
t('runtimeControlDescription');
|
|
168
191
|
|
|
169
192
|
return {
|
|
@@ -175,6 +198,8 @@ export function toRuntimeControlPanelView(
|
|
|
175
198
|
busy: Boolean(action),
|
|
176
199
|
pendingRestart: controlView?.pendingRestart ?? null,
|
|
177
200
|
errorMessage:
|
|
178
|
-
state.lastSystemActionError ||
|
|
201
|
+
localizeRuntimeControlMessage(state.lastSystemActionError) ||
|
|
202
|
+
localizeRuntimeControlMessage(state.runtimeControlError) ||
|
|
203
|
+
null,
|
|
179
204
|
};
|
|
180
205
|
}
|
package/src/index.css
CHANGED
|
@@ -38,6 +38,14 @@
|
|
|
38
38
|
|
|
39
39
|
@layer utilities {
|
|
40
40
|
|
|
41
|
+
.desktop-window-drag {
|
|
42
|
+
-webkit-app-region: drag;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.desktop-window-no-drag {
|
|
46
|
+
-webkit-app-region: no-drag;
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
/* ========================================
|
|
42
50
|
SCROLLBAR
|
|
43
51
|
======================================== */
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { DesktopAppShell } from "@/platforms/desktop/components/desktop-app-shell";
|
|
5
|
+
|
|
6
|
+
vi.mock("@/app/components/layout/sidebar", () => ({
|
|
7
|
+
Sidebar: () => <aside data-testid="settings-sidebar">Settings Sidebar</aside>,
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
function setDesktopPlatform(platform: string | null): void {
|
|
11
|
+
window.nextclawDesktop = platform
|
|
12
|
+
? ({
|
|
13
|
+
platform,
|
|
14
|
+
} as typeof window.nextclawDesktop)
|
|
15
|
+
: undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderDesktopShell(platform: string | null) {
|
|
19
|
+
setDesktopPlatform(platform);
|
|
20
|
+
const queryClient = new QueryClient({
|
|
21
|
+
defaultOptions: {
|
|
22
|
+
queries: {
|
|
23
|
+
retry: false,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return render(
|
|
29
|
+
<QueryClientProvider client={queryClient}>
|
|
30
|
+
<DesktopAppShell
|
|
31
|
+
pathname="/chat"
|
|
32
|
+
isDocBrowserOpen={false}
|
|
33
|
+
docBrowserMode="floating"
|
|
34
|
+
>
|
|
35
|
+
<div data-testid="app-content">App Content</div>
|
|
36
|
+
</DesktopAppShell>
|
|
37
|
+
</QueryClientProvider>,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("DesktopAppShell", () => {
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
setDesktopPlatform(null);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("renders the reserved Windows chrome row above app content", () => {
|
|
47
|
+
renderDesktopShell("win32");
|
|
48
|
+
|
|
49
|
+
const chrome = screen.getByTestId("desktop-window-chrome");
|
|
50
|
+
const sidebarChrome = screen.getByTestId("desktop-window-chrome-sidebar");
|
|
51
|
+
const mainChrome = screen.getByTestId("desktop-window-chrome-main");
|
|
52
|
+
|
|
53
|
+
expect(chrome).toBeTruthy();
|
|
54
|
+
expect(sidebarChrome.className).toContain("w-[var(--desktop-sidebar-width)]");
|
|
55
|
+
expect(sidebarChrome.className).not.toContain("border-b");
|
|
56
|
+
expect(mainChrome.className).toContain("border-b");
|
|
57
|
+
expect(mainChrome.className).toContain("pr-[var(--desktop-caption-safe-right)]");
|
|
58
|
+
expect(screen.getByTestId("app-content")).toBeTruthy();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("keeps non-Windows desktop hosts on the existing shell shape", () => {
|
|
62
|
+
renderDesktopShell("darwin");
|
|
63
|
+
|
|
64
|
+
expect(screen.queryByTestId("desktop-window-chrome")).toBeNull();
|
|
65
|
+
expect(screen.getByTestId("app-content")).toBeTruthy();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { lazy, Suspense } from "react";
|
|
2
|
+
import type { CSSProperties } from "react";
|
|
2
3
|
import { isMainWorkspaceRoute } from "@/app/configs/app-navigation.config";
|
|
3
4
|
import { Sidebar } from "@/app/components/layout/sidebar";
|
|
5
|
+
import { DesktopWindowChrome } from "@/platforms/desktop/components/desktop-window-chrome";
|
|
6
|
+
import { isWindowsDesktopHost } from "@/platforms/desktop/utils/desktop-host.utils";
|
|
7
|
+
import { cn } from "@/shared/lib/utils";
|
|
4
8
|
|
|
5
9
|
const DocBrowser = lazy(async () => ({
|
|
6
10
|
default: (await import("@/shared/components/doc-browser/doc-browser")).DocBrowser,
|
|
@@ -13,6 +17,20 @@ type DesktopAppShellProps = {
|
|
|
13
17
|
children: React.ReactNode;
|
|
14
18
|
};
|
|
15
19
|
|
|
20
|
+
type DesktopShellStyle = CSSProperties & {
|
|
21
|
+
"--desktop-titlebar-height"?: string;
|
|
22
|
+
"--desktop-caption-safe-right"?: string;
|
|
23
|
+
"--desktop-sidebar-width"?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function createWindowsDesktopShellStyle(isMainRoute: boolean): DesktopShellStyle {
|
|
27
|
+
return {
|
|
28
|
+
"--desktop-titlebar-height": "34px",
|
|
29
|
+
"--desktop-caption-safe-right": "140px",
|
|
30
|
+
"--desktop-sidebar-width": isMainRoute ? "280px" : "240px",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
export function DesktopAppShell({
|
|
17
35
|
pathname,
|
|
18
36
|
isDocBrowserOpen,
|
|
@@ -20,27 +38,37 @@ export function DesktopAppShell({
|
|
|
20
38
|
children,
|
|
21
39
|
}: DesktopAppShellProps) {
|
|
22
40
|
const isMainRoute = isMainWorkspaceRoute(pathname);
|
|
41
|
+
const shouldUseWindowsChrome = isWindowsDesktopHost();
|
|
23
42
|
|
|
24
43
|
return (
|
|
25
|
-
<div
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
<div
|
|
45
|
+
className={cn(
|
|
46
|
+
"h-screen flex flex-col overflow-hidden bg-background font-sans text-foreground",
|
|
47
|
+
shouldUseWindowsChrome ? "rounded-[10px]" : null,
|
|
48
|
+
)}
|
|
49
|
+
style={shouldUseWindowsChrome ? createWindowsDesktopShellStyle(isMainRoute) : undefined}
|
|
50
|
+
>
|
|
51
|
+
{shouldUseWindowsChrome ? <DesktopWindowChrome /> : null}
|
|
52
|
+
<div className="flex min-h-0 flex-1 overflow-hidden">
|
|
53
|
+
{!isMainRoute && <Sidebar mode="settings" />}
|
|
54
|
+
<div className="flex-1 flex min-w-0 overflow-hidden relative">
|
|
55
|
+
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
|
56
|
+
{isMainRoute ? (
|
|
57
|
+
<div className="flex-1 h-full overflow-hidden">{children}</div>
|
|
58
|
+
) : (
|
|
59
|
+
<main className="flex-1 overflow-auto p-8 pb-16 custom-scrollbar">
|
|
60
|
+
<div className="mx-auto h-full max-w-6xl animate-fade-in">
|
|
61
|
+
{children}
|
|
62
|
+
</div>
|
|
63
|
+
</main>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
{isDocBrowserOpen && docBrowserMode === "docked" ? (
|
|
67
|
+
<Suspense fallback={null}>
|
|
68
|
+
<DocBrowser />
|
|
69
|
+
</Suspense>
|
|
70
|
+
) : null}
|
|
38
71
|
</div>
|
|
39
|
-
{isDocBrowserOpen && docBrowserMode === "docked" ? (
|
|
40
|
-
<Suspense fallback={null}>
|
|
41
|
-
<DocBrowser />
|
|
42
|
-
</Suspense>
|
|
43
|
-
) : null}
|
|
44
72
|
</div>
|
|
45
73
|
{isDocBrowserOpen && docBrowserMode === "floating" ? (
|
|
46
74
|
<Suspense fallback={null}>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BrandHeader } from "@/shared/components/common/brand-header";
|
|
2
|
+
|
|
3
|
+
export function DesktopWindowChrome() {
|
|
4
|
+
return (
|
|
5
|
+
<header
|
|
6
|
+
className="flex h-[var(--desktop-titlebar-height)] shrink-0 bg-background"
|
|
7
|
+
data-testid="desktop-window-chrome"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
className="desktop-window-drag flex h-full w-[var(--desktop-sidebar-width)] shrink-0 items-center bg-secondary pl-4 pr-3 text-secondary-foreground"
|
|
11
|
+
data-testid="desktop-window-chrome-sidebar"
|
|
12
|
+
>
|
|
13
|
+
<div className="desktop-window-no-drag flex min-w-0 shrink-0 items-center">
|
|
14
|
+
<BrandHeader
|
|
15
|
+
className="flex min-w-0 items-center gap-2.5"
|
|
16
|
+
density="chrome"
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
<div className="min-w-0 flex-1 self-stretch" aria-hidden="true" />
|
|
20
|
+
</div>
|
|
21
|
+
<div
|
|
22
|
+
className="desktop-window-drag flex min-w-0 flex-1 items-center border-b border-[#ebe7dc]/80 bg-secondary pr-[var(--desktop-caption-safe-right)]"
|
|
23
|
+
aria-hidden="true"
|
|
24
|
+
data-testid="desktop-window-chrome-main"
|
|
25
|
+
>
|
|
26
|
+
<div className="min-w-0 flex-1 self-stretch" />
|
|
27
|
+
</div>
|
|
28
|
+
</header>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -7,8 +7,14 @@ export {
|
|
|
7
7
|
DesktopUpdateManager,
|
|
8
8
|
} from './managers/desktop-update.manager';
|
|
9
9
|
export { DesktopAppShell } from './components/desktop-app-shell';
|
|
10
|
+
export { DesktopWindowChrome } from './components/desktop-window-chrome';
|
|
10
11
|
export { useDesktopPresenceStore } from './stores/desktop-presence.store';
|
|
11
12
|
export { useDesktopUpdateStore } from './stores/desktop-update.store';
|
|
13
|
+
export {
|
|
14
|
+
getDesktopHostPlatform,
|
|
15
|
+
isMacDesktopHost,
|
|
16
|
+
isWindowsDesktopHost,
|
|
17
|
+
} from './utils/desktop-host.utils';
|
|
12
18
|
export type {
|
|
13
19
|
DesktopPresencePreferences,
|
|
14
20
|
DesktopPresenceSnapshot,
|
|
@@ -48,6 +48,8 @@ export type DesktopPresenceSnapshot = DesktopPresencePreferences & {
|
|
|
48
48
|
|
|
49
49
|
export type DesktopUiLanguagePreference = 'en' | 'zh';
|
|
50
50
|
|
|
51
|
+
export type DesktopShellTheme = 'warm' | 'cool';
|
|
52
|
+
|
|
51
53
|
export type NextClawDesktopBridge = {
|
|
52
54
|
platform: string;
|
|
53
55
|
version: string;
|
|
@@ -63,5 +65,6 @@ export type NextClawDesktopBridge = {
|
|
|
63
65
|
getPresenceState: () => Promise<DesktopPresenceSnapshot>;
|
|
64
66
|
updatePresencePreferences: (preferences: Partial<DesktopPresencePreferences>) => Promise<DesktopPresenceSnapshot>;
|
|
65
67
|
setLocalePreference?: (language: DesktopUiLanguagePreference | null) => Promise<DesktopUiLanguagePreference | null>;
|
|
68
|
+
setShellTheme?: (theme: DesktopShellTheme) => Promise<void>;
|
|
66
69
|
onUpdateStateChanged: (listener: (snapshot: DesktopUpdateSnapshot) => void) => () => void;
|
|
67
70
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type DesktopHostPlatform = string | null;
|
|
2
|
+
|
|
3
|
+
const devPlatformOverrideParam = "nextclawDesktopPlatform";
|
|
4
|
+
const devPlatformOverrideStorageKey = "nextclaw.desktopPlatformOverride";
|
|
5
|
+
|
|
6
|
+
export function getDesktopHostPlatform(): DesktopHostPlatform {
|
|
7
|
+
if (typeof window === "undefined") {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return getDevDesktopHostPlatformOverride() ?? window.nextclawDesktop?.platform ?? null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isMacDesktopHost(): boolean {
|
|
14
|
+
return getDesktopHostPlatform() === "darwin";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isWindowsDesktopHost(): boolean {
|
|
18
|
+
return getDesktopHostPlatform() === "win32";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getDevDesktopHostPlatformOverride(): DesktopHostPlatform {
|
|
22
|
+
if (!import.meta.env.DEV) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const platform = new URLSearchParams(window.location.search).get(devPlatformOverrideParam);
|
|
26
|
+
if (platform === "win32" || platform === "darwin") {
|
|
27
|
+
writeDevDesktopHostPlatformOverride(platform);
|
|
28
|
+
return platform;
|
|
29
|
+
}
|
|
30
|
+
if (platform === "clear") {
|
|
31
|
+
writeDevDesktopHostPlatformOverride(null);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return readDevDesktopHostPlatformOverride();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readDevDesktopHostPlatformOverride(): DesktopHostPlatform {
|
|
38
|
+
try {
|
|
39
|
+
const platform = window.sessionStorage.getItem(devPlatformOverrideStorageKey);
|
|
40
|
+
return platform === "win32" || platform === "darwin" ? platform : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeDevDesktopHostPlatformOverride(platform: DesktopHostPlatform): void {
|
|
47
|
+
try {
|
|
48
|
+
if (platform) {
|
|
49
|
+
window.sessionStorage.setItem(devPlatformOverrideStorageKey, platform);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
window.sessionStorage.removeItem(devPlatformOverrideStorageKey);
|
|
53
|
+
} catch {
|
|
54
|
+
// Storage can be unavailable in restricted browser contexts; URL-only override still works.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -4,28 +4,44 @@ import { useAppMeta } from '@/shared/hooks/use-config';
|
|
|
4
4
|
import { type ReactNode, useState } from 'react';
|
|
5
5
|
import { RuntimeStatusEntry } from '@/app/components/layout/runtime-status-entry';
|
|
6
6
|
import { t } from '@/shared/lib/i18n';
|
|
7
|
+
import { cn } from '@/shared/lib/utils';
|
|
7
8
|
|
|
8
9
|
type BrandHeaderProps = {
|
|
9
10
|
className?: string;
|
|
11
|
+
density?: 'sidebar' | 'chrome';
|
|
10
12
|
suffix?: ReactNode;
|
|
11
13
|
};
|
|
12
14
|
|
|
13
|
-
export function BrandHeader({ className, suffix }: BrandHeaderProps) {
|
|
15
|
+
export function BrandHeader({ className, density = 'sidebar', suffix }: BrandHeaderProps) {
|
|
14
16
|
const { data } = useAppMeta();
|
|
15
17
|
const productName = data?.name ?? 'NextClaw';
|
|
16
18
|
const productVersion = data?.productVersion?.trim();
|
|
17
19
|
const versionLabel = productVersion ? `v${productVersion}` : null;
|
|
18
20
|
const resolvedSuffix = suffix ?? <RuntimeStatusEntry />;
|
|
21
|
+
const shouldReserveMacWindowControls = typeof window !== 'undefined' && window.nextclawDesktop?.platform === 'darwin';
|
|
22
|
+
const isChromeDensity = density === 'chrome';
|
|
19
23
|
|
|
20
24
|
return (
|
|
21
|
-
<div className={className ?? 'flex items-center gap-2
|
|
22
|
-
<div
|
|
25
|
+
<div className={cn(className ?? 'flex min-w-0 items-center gap-2', shouldReserveMacWindowControls && 'pl-[58px]')}>
|
|
26
|
+
<div
|
|
27
|
+
className={cn(
|
|
28
|
+
'flex shrink-0 items-center justify-center overflow-hidden',
|
|
29
|
+
isChromeDensity ? 'h-6 w-6 rounded-md' : 'h-6 w-6 rounded-md',
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
23
32
|
<img src="/logo.svg" alt={productName} className="h-full w-full object-contain" />
|
|
24
33
|
</div>
|
|
25
|
-
<div className="flex min-w-0 items-
|
|
26
|
-
<div className="flex min-w-0 flex-1 items-baseline gap-
|
|
27
|
-
<span
|
|
28
|
-
|
|
34
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
35
|
+
<div className="flex min-w-0 flex-1 items-baseline gap-1.5">
|
|
36
|
+
<span
|
|
37
|
+
className={cn(
|
|
38
|
+
'shrink-0 font-semibold text-gray-800',
|
|
39
|
+
isChromeDensity ? 'text-[15px]' : 'text-[14px]',
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
{productName}
|
|
43
|
+
</span>
|
|
44
|
+
{versionLabel ? <BrandVersionLabel versionLabel={versionLabel} density={density} /> : null}
|
|
29
45
|
</div>
|
|
30
46
|
<RuntimeUpdateInlineStatus />
|
|
31
47
|
{resolvedSuffix ? <span className="inline-flex items-center shrink-0">{resolvedSuffix}</span> : null}
|
|
@@ -34,8 +50,15 @@ export function BrandHeader({ className, suffix }: BrandHeaderProps) {
|
|
|
34
50
|
);
|
|
35
51
|
}
|
|
36
52
|
|
|
37
|
-
function BrandVersionLabel({
|
|
53
|
+
function BrandVersionLabel({
|
|
54
|
+
versionLabel,
|
|
55
|
+
density,
|
|
56
|
+
}: {
|
|
57
|
+
versionLabel: string;
|
|
58
|
+
density: BrandHeaderProps['density'];
|
|
59
|
+
}) {
|
|
38
60
|
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
|
|
61
|
+
const isChromeDensity = density === 'chrome';
|
|
39
62
|
|
|
40
63
|
return (
|
|
41
64
|
<span
|
|
@@ -48,7 +71,10 @@ function BrandVersionLabel({ versionLabel }: { versionLabel: string }) {
|
|
|
48
71
|
<span
|
|
49
72
|
tabIndex={0}
|
|
50
73
|
aria-label={versionLabel}
|
|
51
|
-
className=
|
|
74
|
+
className={cn(
|
|
75
|
+
'block min-w-0 truncate font-medium text-gray-500 outline-none',
|
|
76
|
+
isChromeDensity ? 'text-[12px]' : 'text-[12px]',
|
|
77
|
+
)}
|
|
52
78
|
>
|
|
53
79
|
{versionLabel}
|
|
54
80
|
</span>
|
|
@@ -100,13 +126,7 @@ function RuntimeUpdateInlineBadge({ snapshot }: { snapshot: UpdateSnapshot }) {
|
|
|
100
126
|
if (snapshot.status === 'blocked' || snapshot.status === 'failed') {
|
|
101
127
|
return <RuntimeUpdateIssueIcon snapshot={snapshot} />;
|
|
102
128
|
}
|
|
103
|
-
const label = snapshot.status === 'downloading'
|
|
104
|
-
? resolveInlineDownloadLabel(snapshot)
|
|
105
|
-
: snapshot.status === 'downloaded'
|
|
106
|
-
? t('desktopUpdatesInlineReady')
|
|
107
|
-
: snapshot.status === 'update-available'
|
|
108
|
-
? t('desktopUpdatesInlineDownload')
|
|
109
|
-
: null;
|
|
129
|
+
const label = snapshot.status === 'downloading' ? resolveInlineDownloadLabel(snapshot) : null;
|
|
110
130
|
if (!label) {
|
|
111
131
|
return null;
|
|
112
132
|
}
|
|
@@ -104,31 +104,11 @@ function modelListsEqual(left: string[], right: string[]): boolean {
|
|
|
104
104
|
return left.every((item, index) => item === right[index]);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
function
|
|
108
|
-
const merged = [...base];
|
|
109
|
-
for (const item of extra) {
|
|
110
|
-
if (!merged.includes(item)) {
|
|
111
|
-
merged.push(item);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return merged;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function resolveEditableModels(defaultModels: string[], savedModels: string[]): string[] {
|
|
118
|
-
if (savedModels.length === 0) {
|
|
119
|
-
return defaultModels;
|
|
120
|
-
}
|
|
121
|
-
const looksLikeLegacyCustomList = savedModels.every((model) => !defaultModels.includes(model));
|
|
122
|
-
if (looksLikeLegacyCustomList) {
|
|
123
|
-
return mergeModelLists(defaultModels, savedModels);
|
|
124
|
-
}
|
|
107
|
+
function resolveEditableModels(_defaultModels: string[], savedModels: string[]): string[] {
|
|
125
108
|
return savedModels;
|
|
126
109
|
}
|
|
127
110
|
|
|
128
|
-
function serializeModelsForSave(models: string[],
|
|
129
|
-
if (modelListsEqual(models, defaultModels)) {
|
|
130
|
-
return [];
|
|
131
|
-
}
|
|
111
|
+
function serializeModelsForSave(models: string[], _defaultModels: string[]): string[] {
|
|
132
112
|
return models;
|
|
133
113
|
}
|
|
134
114
|
|
|
@@ -6,65 +6,19 @@ import { Button } from '@/shared/components/ui/button';
|
|
|
6
6
|
import { Input } from '@/shared/components/ui/input';
|
|
7
7
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/components/ui/select';
|
|
8
8
|
import { Card, CardContent } from '@/shared/components/ui/card';
|
|
9
|
+
import {
|
|
10
|
+
describeCronDelivery,
|
|
11
|
+
describeCronSchedule,
|
|
12
|
+
describeCronSession,
|
|
13
|
+
formatCronDate,
|
|
14
|
+
} from '@/shared/lib/cron';
|
|
9
15
|
import { cn } from '@/shared/lib/utils';
|
|
10
|
-
import {
|
|
16
|
+
import { t } from '@/shared/lib/i18n';
|
|
11
17
|
import { PageLayout, PageHeader } from '@/app/components/layout/page-layout';
|
|
12
18
|
import { AlarmClock, RefreshCw, Trash2, Play, Power } from 'lucide-react';
|
|
13
19
|
|
|
14
20
|
type StatusFilter = 'all' | 'enabled' | 'disabled';
|
|
15
21
|
|
|
16
|
-
function formatDate(value?: string | null): string {
|
|
17
|
-
return formatDateTime(value ?? undefined);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function formatDateFromMs(value?: number | null): string {
|
|
21
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
22
|
-
return '-';
|
|
23
|
-
}
|
|
24
|
-
return formatDateTime(new Date(value));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function formatEveryDuration(ms?: number | null): string {
|
|
28
|
-
if (typeof ms !== 'number' || !Number.isFinite(ms)) {
|
|
29
|
-
return '-';
|
|
30
|
-
}
|
|
31
|
-
const seconds = Math.round(ms / 1000);
|
|
32
|
-
if (seconds < 60) return `${seconds}s`;
|
|
33
|
-
const minutes = Math.round(seconds / 60);
|
|
34
|
-
if (minutes < 60) return `${minutes}m`;
|
|
35
|
-
const hours = Math.round(minutes / 60);
|
|
36
|
-
if (hours < 24) return `${hours}h`;
|
|
37
|
-
const days = Math.round(hours / 24);
|
|
38
|
-
return `${days}d`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function describeSchedule(job: CronJobView): string {
|
|
42
|
-
const { schedule } = job;
|
|
43
|
-
if (schedule.kind === 'cron') {
|
|
44
|
-
return schedule.expr ? `cron ${schedule.expr}` : 'cron';
|
|
45
|
-
}
|
|
46
|
-
if (schedule.kind === 'every') {
|
|
47
|
-
return `every ${formatEveryDuration(schedule.everyMs)}`;
|
|
48
|
-
}
|
|
49
|
-
if (schedule.kind === 'at') {
|
|
50
|
-
return `at ${formatDateFromMs(schedule.atMs)}`;
|
|
51
|
-
}
|
|
52
|
-
return '-';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function describeDelivery(job: CronJobView): string {
|
|
56
|
-
if (!job.payload.deliver) {
|
|
57
|
-
return '-';
|
|
58
|
-
}
|
|
59
|
-
const channel = job.payload.channel ?? '-';
|
|
60
|
-
const target = job.payload.to ?? '-';
|
|
61
|
-
return `${channel}:${target}`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function describeSession(job: CronJobView): string {
|
|
65
|
-
return job.payload.sessionId?.trim() || `cron:${job.id}`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
22
|
function matchQuery(job: CronJobView, query: string): boolean {
|
|
69
23
|
const q = query.trim().toLowerCase();
|
|
70
24
|
if (!q) return true;
|
|
@@ -107,14 +61,14 @@ function CronJobCard(props: {
|
|
|
107
61
|
<span className="rounded-full bg-amber-50 px-2 py-0.5 text-[10px] font-semibold text-amber-700">{t('cronOneShot')}</span>
|
|
108
62
|
) : null}
|
|
109
63
|
</div>
|
|
110
|
-
<div className="mt-2 text-xs text-gray-500">{t('cronScheduleLabel')}: {
|
|
64
|
+
<div className="mt-2 text-xs text-gray-500">{t('cronScheduleLabel')}: {describeCronSchedule(job)}</div>
|
|
111
65
|
<div className="mt-2 whitespace-pre-wrap break-words text-sm text-gray-700">{job.payload.message}</div>
|
|
112
|
-
<div className="mt-2 text-xs text-gray-500">{t('cronSessionLabel')}: {
|
|
113
|
-
<div className="mt-2 text-xs text-gray-500">{t('cronDeliverTo')}: {
|
|
66
|
+
<div className="mt-2 text-xs text-gray-500">{t('cronSessionLabel')}: {describeCronSession(job)}</div>
|
|
67
|
+
<div className="mt-2 text-xs text-gray-500">{t('cronDeliverTo')}: {describeCronDelivery(job)}</div>
|
|
114
68
|
</div>
|
|
115
69
|
<div className="min-w-[220px] space-y-2 text-xs text-gray-500">
|
|
116
|
-
<div><span className="font-medium text-gray-700">{t('cronNextRun')}:</span> {
|
|
117
|
-
<div><span className="font-medium text-gray-700">{t('cronLastRun')}:</span> {
|
|
70
|
+
<div><span className="font-medium text-gray-700">{t('cronNextRun')}:</span> {formatCronDate(job.state.nextRunAt)}</div>
|
|
71
|
+
<div><span className="font-medium text-gray-700">{t('cronLastRun')}:</span> {formatCronDate(job.state.lastRunAt)}</div>
|
|
118
72
|
<div><span className="font-medium text-gray-700">{t('cronLastStatus')}:</span> {job.state.lastStatus ?? '-'}</div>
|
|
119
73
|
{job.state.lastError ? <div className="break-words text-[11px] text-red-500">{job.state.lastError}</div> : null}
|
|
120
74
|
</div>
|
|
@@ -320,7 +320,7 @@ export function DocBrowser({ displayMode = 'desktop' }: DocBrowserProps) {
|
|
|
320
320
|
</div>
|
|
321
321
|
</div>
|
|
322
322
|
|
|
323
|
-
<div className="flex items-center gap-1.5 px-2.5 py-2 bg-
|
|
323
|
+
<div className="flex items-center gap-1.5 px-2.5 py-2 bg-background border-b border-[#f1e7d4] overflow-x-auto custom-scrollbar">
|
|
324
324
|
{tabs.map((tab) => {
|
|
325
325
|
const isActive = tab.id === activeTabId;
|
|
326
326
|
return (
|
|
@@ -329,8 +329,8 @@ export function DocBrowser({ displayMode = 'desktop' }: DocBrowserProps) {
|
|
|
329
329
|
className={cn(
|
|
330
330
|
'inline-flex items-center gap-1 h-7 px-1.5 rounded-lg text-xs border max-w-[220px] shrink-0 transition-colors',
|
|
331
331
|
isActive
|
|
332
|
-
? 'bg-
|
|
333
|
-
: 'bg-
|
|
332
|
+
? 'bg-amber-50/80 border-amber-200 text-amber-900 shadow-[0_1px_2px_rgba(30,20,10,0.04)]'
|
|
333
|
+
: 'bg-[#f9f8f5] border-[#eee3d1] text-[#78644d] hover:bg-[#fff7ea] hover:text-[#2f2212]'
|
|
334
334
|
)}
|
|
335
335
|
>
|
|
336
336
|
<button
|
|
@@ -357,7 +357,7 @@ export function DocBrowser({ displayMode = 'desktop' }: DocBrowserProps) {
|
|
|
357
357
|
})}
|
|
358
358
|
<button
|
|
359
359
|
onClick={() => openNewTab(undefined, { kind: 'docs', title: 'Docs' })}
|
|
360
|
-
className="inline-flex items-center justify-center w-7 h-7 rounded-lg border border-
|
|
360
|
+
className="inline-flex items-center justify-center w-7 h-7 rounded-lg border border-[#eee3d1] bg-white text-[#78644d] hover:bg-[#fff7ea] hover:text-[#2f2212] shrink-0"
|
|
361
361
|
title={t('docBrowserNewTab')}
|
|
362
362
|
>
|
|
363
363
|
<Plus className="w-3.5 h-3.5" />
|