@nextclaw/ui 0.12.9 → 0.12.11
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 +102 -0
- package/dist/assets/ChannelsList-SQ7Oxotv.js +8 -0
- package/dist/assets/DocBrowser-BCO2k6XD.js +1 -0
- package/dist/assets/{DocBrowser-6ReNjvzF.js → DocBrowser-rDOjI3ga.js} +1 -1
- package/dist/assets/{DocBrowserContext-B6SpA7Qs.js → DocBrowserContext-BUq3Wo8O.js} +1 -1
- package/dist/assets/{LogoBadge-ByNLYg65.js → LogoBadge-DP8Ye7wJ.js} +1 -1
- package/dist/assets/ModelConfig-C77Ae9ru.js +1 -0
- package/dist/assets/ProviderScopedModelInput-CEnK61uo.js +1 -0
- package/dist/assets/ProvidersList-BCupBayq.js +1 -0
- package/dist/assets/RuntimeConfig-Ad-CAcmy.js +1 -0
- package/dist/assets/SearchConfig-BfCz4wJ4.js +1 -0
- package/dist/assets/SecretsConfig-DjmBIhyy.js +3 -0
- package/dist/assets/{SessionsConfig-ChHQ7M5c.js → SessionsConfig-CvjxU40H.js} +2 -2
- package/dist/assets/{book-open-BdcxxoQu.js → book-open-BE8M56IM.js} +1 -1
- package/dist/assets/chat-page-JKC6ln-y.js +58 -0
- package/dist/assets/chat-session-display-YcRMrAMa.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-DK5HPmIK.js → chunk-JZWAC4HX-erTUn3b8.js} +1 -1
- package/dist/assets/client-CszWMVKi.js +7 -0
- package/dist/assets/config-split-page-BAGSzUR3.js +1 -0
- package/dist/assets/{createLucideIcon-BSeTgkZW.js → createLucideIcon-CCiTGX8L.js} +1 -1
- package/dist/assets/desktop-DfkLlkG2.js +1 -0
- package/dist/assets/desktop-update-config-BXeGlqHD.js +1 -0
- package/dist/assets/dialog-BghZFPch.js +5 -0
- package/dist/assets/{dist-6TrrnPCR.js → dist-Dd9cr-kz.js} +1 -1
- package/dist/assets/dist-ZwoAXs46.js +9 -0
- package/dist/assets/{download-BhDxnyvU.js → download-D7LOizcW.js} +1 -1
- package/dist/assets/es2015-CEAreese.js +41 -0
- package/dist/assets/{external-link-BgErLCNT.js → external-link-qsnCMhw1.js} +1 -1
- package/dist/assets/{hash-Bl7dr_UG.js → hash-0zjWsNl-.js} +1 -1
- package/dist/assets/{i18n-eDHeDY0n.js → i18n-DvzXOGQX.js} +1 -1
- package/dist/assets/index-DvVTC9FF.css +1 -0
- package/dist/assets/index-lr6rQUSd.js +2 -0
- package/dist/assets/key-round-BLe9D8ND.js +1 -0
- package/dist/assets/loader-circle-wj7kARHv.js +1 -0
- package/dist/assets/{logos-x89HbrZ4.js → logos-_v5b2SdG.js} +1 -1
- package/dist/assets/marketplace-page-CAAk1Khc.js +1 -0
- package/dist/assets/marketplace-page-CfCiq90S.js +49 -0
- package/dist/assets/mcp-marketplace-page-D0Pp9Hs-.js +40 -0
- package/dist/assets/play-o6NmwGTi.js +1 -0
- package/dist/assets/plus-I9pBS4Fl.js +1 -0
- package/dist/assets/{refresh-cw-C47QSEwg.js → refresh-cw-MNqgR3LZ.js} +1 -1
- package/dist/assets/remote-C9fXm4V5.js +1 -0
- package/dist/assets/{save-3S6-H3Xw.js → save-D4bObrmH.js} +1 -1
- package/dist/assets/search-DxmL3IWE.js +1 -0
- package/dist/assets/security-config-BUm6FFfl.js +1 -0
- package/dist/assets/select-BILPf7zs.js +1 -0
- package/dist/assets/setting-row-BATDgg4r.js +1 -0
- package/dist/assets/skeleton-COKMAnJy.js +1 -0
- package/dist/assets/{switch-BsLtHOH-.js → switch-CBOzecWS.js} +1 -1
- package/dist/assets/{tabs-custom-D3HYMt6k.js → tabs-custom-Bx3cNhD-.js} +1 -1
- package/dist/assets/tag-chip-zUaDE2-H.js +1 -0
- package/dist/assets/{trash-2-G48scll7.js → trash-2-CQUgYyRn.js} +1 -1
- package/dist/assets/use-infinite-scroll-loader-B5V2Klve.js +1 -0
- package/dist/assets/useConfirmDialog-patAnl1g.js +1 -0
- package/dist/assets/{useMutation-CBWjE2uj.js → useMutation-__AYv-Pz.js} +1 -1
- package/dist/assets/x-BHUGQIUv.js +1 -0
- package/dist/index.html +22 -22
- package/dist/runtime-icons/claude.ico +0 -0
- package/dist/runtime-icons/codex-openai.svg +6 -0
- package/dist/runtime-icons/hermes-agent.png +0 -0
- package/module-structure.config.json +7 -0
- package/package.json +6 -6
- package/public/runtime-icons/claude.ico +0 -0
- package/public/runtime-icons/codex-openai.svg +6 -0
- package/public/runtime-icons/hermes-agent.png +0 -0
- package/src/api/chat-session-type.types.ts +7 -0
- package/src/api/config.ts +10 -0
- package/src/api/raw-client.test.ts +1 -1
- package/src/api/{raw-client.ts → raw-client.utils.ts} +2 -0
- package/src/api/runtime-control.types.ts +8 -0
- package/src/api/types.ts +48 -0
- package/src/app/components/app-manager-provider.tsx +20 -0
- package/src/app/managers/app.manager.ts +12 -0
- package/src/app.tsx +223 -59
- package/src/components/agents/agent-dialogs.tsx +499 -0
- package/src/components/agents/agents-page.test.tsx +238 -0
- package/src/components/agents/agents-page.tsx +435 -0
- package/src/components/chat/chat-conversation-panel.test.tsx +30 -0
- package/src/components/chat/chat-conversation-panel.tsx +83 -13
- package/src/components/chat/chat-input/ncp-chat-input-availability.utils.test.ts +92 -0
- package/src/components/chat/chat-input/ncp-chat-input-availability.utils.ts +45 -0
- package/src/components/chat/chat-page-shell.tsx +19 -13
- package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
- package/src/components/chat/chat-session-type-option-item.tsx +68 -0
- package/src/components/chat/chat-session-workspace-file-preview.test.tsx +87 -0
- package/src/components/chat/chat-session-workspace-file-preview.tsx +14 -43
- package/src/components/chat/chat-session-workspace-panel-nav.tsx +8 -2
- package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
- package/src/components/chat/containers/chat-input-bar.container.tsx +24 -12
- package/src/components/chat/{ChatSidebar.test.tsx → containers/chat-sidebar.test.tsx} +5 -4
- package/src/components/chat/{ChatSidebar.tsx → containers/chat-sidebar.tsx} +24 -72
- package/src/components/chat/hooks/use-chat-sidebar-session-label-editor.ts +49 -0
- package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
- package/src/components/chat/ncp/ncp-app-client-fetch.ts +3 -0
- package/src/components/chat/ncp/ncp-chat-input.manager.ts +13 -5
- package/src/components/chat/ncp/ncp-chat-page.tsx +23 -2
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +1 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +3 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +10 -4
- package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.test.tsx +48 -4
- package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.ts +43 -5
- package/src/components/chat/ncp/tests/ncp-chat-input.manager.test.ts +51 -1
- package/src/components/chat/stores/chat-input.store.ts +2 -1
- package/src/components/chat/stores/chat-thread.store.ts +3 -1
- package/src/components/chat/useChatSessionTypeState.ts +10 -1
- package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
- package/src/components/common/BrandHeader.tsx +3 -1
- package/src/components/common/session-context-icon.tsx +15 -2
- package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
- package/src/components/config/ChannelForm.test.tsx +89 -3
- package/src/components/config/ChannelForm.tsx +157 -188
- package/src/components/config/ChannelsList.test.tsx +163 -119
- package/src/components/config/ChannelsList.tsx +90 -101
- package/src/components/config/ProviderForm.tsx +108 -146
- package/src/components/config/ProvidersList.tsx +100 -123
- package/src/components/config/SearchConfig.tsx +423 -393
- package/src/components/config/channel-form-fields-section.tsx +70 -37
- package/src/components/config/config-split-page.tsx +109 -0
- package/src/components/config/desktop-update-config.test.tsx +10 -4
- package/src/components/config/desktop-update-config.tsx +5 -3
- package/src/components/config/provider-enabled-field.tsx +17 -10
- package/src/components/config/runtime-control-card.test.tsx +136 -158
- package/src/components/config/runtime-control-card.tsx +43 -68
- package/src/components/config/runtime-presence-card.test.tsx +10 -14
- package/src/components/config/runtime-presence-card.tsx +97 -81
- package/src/components/layout/AppLayout.tsx +25 -37
- package/src/components/layout/Sidebar.tsx +4 -4
- package/src/components/layout/app-layout.test.tsx +46 -14
- package/src/components/layout/runtime-status-entry.test.tsx +101 -0
- package/src/components/layout/runtime-status-entry.tsx +95 -0
- package/src/components/layout/sidebar.layout.test.tsx +11 -5
- package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
- package/src/components/marketplace/marketplace-list-card.tsx +288 -0
- package/src/components/marketplace/marketplace-page-data.ts +129 -0
- package/src/components/marketplace/marketplace-page.test.tsx +339 -0
- package/src/components/marketplace/marketplace-page.tsx +596 -0
- package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
- package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
- package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
- package/src/components/ui/notice-card.tsx +129 -0
- package/src/components/ui/setting-row.tsx +51 -0
- package/src/components/ui/tag-chip.tsx +39 -0
- package/src/components/ui/textarea.tsx +19 -0
- package/src/features/account/components/account-panel.tsx +255 -0
- package/src/features/account/index.ts +6 -0
- package/src/{account → features/account}/managers/account.manager.ts +6 -5
- package/src/features/remote/components/remote-access-page.test.tsx +104 -0
- package/src/features/remote/components/remote-access-page.tsx +250 -0
- package/src/{hooks/useRemoteAccess.ts → features/remote/hooks/use-remote-access.ts} +1 -1
- package/src/features/remote/index.ts +27 -0
- package/src/{remote → features/remote}/managers/remote-access.manager.ts +3 -4
- package/src/{remote → features/remote/services}/remote-access-feedback.service.test.ts +1 -1
- package/src/features/system-status/hooks/use-system-status.ts +104 -0
- package/src/features/system-status/index.ts +12 -0
- package/src/features/system-status/managers/system-status.manager.bootstrap-polling.test.ts +126 -0
- package/src/features/system-status/managers/system-status.manager.test.ts +142 -0
- package/src/features/system-status/managers/system-status.manager.ts +511 -0
- package/src/features/system-status/stores/system-status.store.ts +32 -0
- package/src/features/system-status/types/system-status.types.ts +73 -0
- package/src/features/system-status/utils/system-status.utils.test.ts +132 -0
- package/src/features/system-status/utils/system-status.utils.ts +202 -0
- package/src/hooks/use-realtime-query-bridge.ts +34 -18
- package/src/hooks/useConfig.ts +2 -1
- package/src/index.css +24 -0
- package/src/lib/app-resource-uri.test.ts +20 -0
- package/src/lib/app-resource-uri.ts +29 -0
- package/src/lib/i18n.chat.ts +8 -0
- package/src/lib/i18n.remote.ts +1 -1
- package/src/lib/i18n.runtime-control.ts +31 -0
- package/src/lib/i18n.ts +5 -8
- package/src/lib/session-context.utils.test.ts +71 -0
- package/src/lib/session-context.utils.ts +28 -3
- package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
- package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
- package/src/platforms/desktop/index.ts +20 -0
- package/src/{desktop → platforms/desktop}/managers/desktop-presence.manager.ts +2 -2
- package/src/{desktop → platforms/desktop}/managers/desktop-update.manager.ts +2 -2
- package/src/{desktop → platforms/desktop}/stores/desktop-presence.store.ts +1 -1
- package/src/{desktop → platforms/desktop}/stores/desktop-update.store.ts +1 -1
- package/src/stores/ui.store.ts +0 -9
- package/src/transport/{app-client.ts → app-client.service.ts} +9 -9
- package/src/transport/app-client.test.ts +9 -5
- package/src/transport/index.ts +1 -1
- package/src/transport/{local.transport.ts → local-transport.service.ts} +14 -12
- package/dist/assets/ChannelsList-Ita2Zm1_.js +0 -8
- package/dist/assets/DocBrowser-BNwbPHf4.js +0 -1
- package/dist/assets/MarketplacePage-CjX2MWww.js +0 -1
- package/dist/assets/MarketplacePage-D0sDlYX4.js +0 -49
- package/dist/assets/McpMarketplacePage-BGKJm1sJ.js +0 -40
- package/dist/assets/ModelConfig-BzZenCH-.js +0 -1
- package/dist/assets/ProviderScopedModelInput-Da7khnBA.js +0 -1
- package/dist/assets/ProvidersList-BbVzRxjY.js +0 -1
- package/dist/assets/RemoteAccessPage-BaDH_X1Q.js +0 -1
- package/dist/assets/RuntimeConfig-F_XKGgLm.js +0 -1
- package/dist/assets/SearchConfig-BGkzXQP-.js +0 -1
- package/dist/assets/SecretsConfig-D281Rotl.js +0 -3
- package/dist/assets/app-query-client-VnFElj4E.js +0 -1
- package/dist/assets/chat-page-Doe0yTtB.js +0 -58
- package/dist/assets/chat-session-display-cw78aiI_.js +0 -1
- package/dist/assets/client-_i4MU2bB.js +0 -7
- package/dist/assets/config-DtIQwrHF.js +0 -1
- package/dist/assets/config-layout-CHs0mAaR.js +0 -1
- package/dist/assets/desktop-update-config-Dpcf4BKG.js +0 -1
- package/dist/assets/dist-ccBFUi-o.js +0 -9
- package/dist/assets/index-CF9xve0E.js +0 -6
- package/dist/assets/index-FgA52VBt.css +0 -1
- package/dist/assets/infiniteQueryBehavior-ZDS92Qpp.js +0 -1
- package/dist/assets/loader-circle-ACM1s51e.js +0 -1
- package/dist/assets/page-layout-vZnghcFy.js +0 -1
- package/dist/assets/play-CFUwCA2E.js +0 -1
- package/dist/assets/plus-rYsv72JG.js +0 -1
- package/dist/assets/popover-Bg1VoTZ6.js +0 -1
- package/dist/assets/refresh-ccw-DT98i__E.js +0 -1
- package/dist/assets/rotate-cw-JtFzpNn6.js +0 -1
- package/dist/assets/search-3kFR_zh9.js +0 -1
- package/dist/assets/security-config-BWaiARNk.js +0 -1
- package/dist/assets/select-DJ2MUjBB.js +0 -41
- package/dist/assets/skeleton-ByQepn0M.js +0 -1
- package/dist/assets/status-dot-vbanNPFU.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-DkNhD-42.js +0 -1
- package/dist/assets/useConfirmDialog-BkvTN-vd.js +0 -1
- package/dist/assets/x-ByDbItbq.js +0 -1
- package/src/account/components/account-panel.tsx +0 -135
- package/src/components/agents/AgentDialogs.tsx +0 -400
- package/src/components/agents/AgentsPage.test.tsx +0 -217
- package/src/components/agents/AgentsPage.tsx +0 -352
- package/src/components/config/config-layout.ts +0 -10
- package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
- package/src/components/marketplace/MarketplacePage.tsx +0 -827
- package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
- package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
- package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
- package/src/components/remote/RemoteAccessPage.tsx +0 -144
- package/src/hooks/use-runtime-control.ts +0 -24
- package/src/presenter/app-presenter-context.tsx +0 -20
- package/src/presenter/app.presenter.ts +0 -12
- package/src/runtime-control/runtime-control.manager.ts +0 -118
- /package/dist/assets/{config-hints-BhTmc9P1.js → config-hints-DSQQbeOA.js} +0 -0
- /package/src/{account → features/account}/stores/account.store.ts +0 -0
- /package/src/{remote → features/remote/services}/remote-access-feedback.service.ts +0 -0
- /package/src/{remote/remote-access.query.ts → features/remote/services/remote-access-query.service.ts} +0 -0
- /package/src/{remote → features/remote}/stores/remote-access.store.ts +0 -0
- /package/src/{desktop → platforms/desktop/types}/desktop-update.types.ts +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { t } from '@/lib/i18n';
|
|
3
|
+
import {
|
|
4
|
+
resolveChatRuntimeMessage,
|
|
5
|
+
resolveSystemConnectionStatus,
|
|
6
|
+
toSystemStatusView,
|
|
7
|
+
} from './system-status.utils';
|
|
8
|
+
|
|
9
|
+
describe('resolveSystemConnectionStatus', () => {
|
|
10
|
+
it('maps cold-starting to connecting', () => {
|
|
11
|
+
expect(resolveSystemConnectionStatus('cold-starting')).toBe('connecting');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('maps ready to connected', () => {
|
|
15
|
+
expect(resolveSystemConnectionStatus('ready')).toBe('connected');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('maps stalled to disconnected', () => {
|
|
19
|
+
expect(resolveSystemConnectionStatus('stalled')).toBe('disconnected');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('maps service-transitioning to connecting', () => {
|
|
23
|
+
expect(resolveSystemConnectionStatus('service-transitioning')).toBe(
|
|
24
|
+
'connecting'
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('resolveChatRuntimeMessage', () => {
|
|
30
|
+
it('uses the startup message during cold start', () => {
|
|
31
|
+
expect(
|
|
32
|
+
resolveChatRuntimeMessage({
|
|
33
|
+
lifecyclePhase: 'cold-starting',
|
|
34
|
+
hasReachedReady: false,
|
|
35
|
+
lastReadyAt: null,
|
|
36
|
+
recoveryStartedAt: null,
|
|
37
|
+
bootstrapStatus: null,
|
|
38
|
+
lastError: null,
|
|
39
|
+
lastTransportError: null,
|
|
40
|
+
runtimeControlView: null,
|
|
41
|
+
runtimeControlError: null,
|
|
42
|
+
activeSystemAction: null,
|
|
43
|
+
lastSystemActionError: null,
|
|
44
|
+
})
|
|
45
|
+
).toBe(t('chatRuntimeInitializing'));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('uses the bootstrap error when startup failed', () => {
|
|
49
|
+
expect(
|
|
50
|
+
resolveChatRuntimeMessage({
|
|
51
|
+
lifecyclePhase: 'startup-failed',
|
|
52
|
+
hasReachedReady: false,
|
|
53
|
+
lastReadyAt: null,
|
|
54
|
+
recoveryStartedAt: null,
|
|
55
|
+
bootstrapStatus: {
|
|
56
|
+
phase: 'error',
|
|
57
|
+
ncpAgent: {
|
|
58
|
+
state: 'error',
|
|
59
|
+
error: 'boom',
|
|
60
|
+
},
|
|
61
|
+
pluginHydration: {
|
|
62
|
+
state: 'pending',
|
|
63
|
+
loadedPluginCount: 0,
|
|
64
|
+
totalPluginCount: 0,
|
|
65
|
+
},
|
|
66
|
+
channels: {
|
|
67
|
+
state: 'pending',
|
|
68
|
+
enabled: [],
|
|
69
|
+
},
|
|
70
|
+
remote: {
|
|
71
|
+
state: 'pending',
|
|
72
|
+
},
|
|
73
|
+
lastError: 'boom',
|
|
74
|
+
},
|
|
75
|
+
lastError: null,
|
|
76
|
+
lastTransportError: null,
|
|
77
|
+
runtimeControlView: null,
|
|
78
|
+
runtimeControlError: null,
|
|
79
|
+
activeSystemAction: null,
|
|
80
|
+
lastSystemActionError: null,
|
|
81
|
+
})
|
|
82
|
+
).toBe('boom');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('prefers the centralized action message while a system action is running', () => {
|
|
86
|
+
expect(
|
|
87
|
+
resolveChatRuntimeMessage({
|
|
88
|
+
lifecyclePhase: 'ready',
|
|
89
|
+
hasReachedReady: true,
|
|
90
|
+
lastReadyAt: Date.now(),
|
|
91
|
+
recoveryStartedAt: null,
|
|
92
|
+
bootstrapStatus: null,
|
|
93
|
+
lastError: null,
|
|
94
|
+
lastTransportError: null,
|
|
95
|
+
runtimeControlView: null,
|
|
96
|
+
runtimeControlError: null,
|
|
97
|
+
activeSystemAction: {
|
|
98
|
+
action: 'restart-service',
|
|
99
|
+
lifecycle: 'recovering',
|
|
100
|
+
serviceState: null,
|
|
101
|
+
message: 'NextClaw 正在恢复连接',
|
|
102
|
+
},
|
|
103
|
+
lastSystemActionError: null,
|
|
104
|
+
})
|
|
105
|
+
).toBe('NextClaw 正在恢复连接');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('toSystemStatusView', () => {
|
|
110
|
+
it('keeps stalled chat blocked without surfacing a timeout banner', () => {
|
|
111
|
+
expect(
|
|
112
|
+
toSystemStatusView({
|
|
113
|
+
lifecyclePhase: 'stalled',
|
|
114
|
+
hasReachedReady: true,
|
|
115
|
+
lastReadyAt: Date.now(),
|
|
116
|
+
recoveryStartedAt: Date.now(),
|
|
117
|
+
bootstrapStatus: null,
|
|
118
|
+
lastError: 'Failed to fetch',
|
|
119
|
+
lastTransportError: 'Failed to fetch',
|
|
120
|
+
runtimeControlView: null,
|
|
121
|
+
runtimeControlError: null,
|
|
122
|
+
activeSystemAction: null,
|
|
123
|
+
lastSystemActionError: null,
|
|
124
|
+
})
|
|
125
|
+
).toMatchObject({
|
|
126
|
+
isChatBlocked: true,
|
|
127
|
+
chatMessage: null,
|
|
128
|
+
connectionStatus: 'disconnected',
|
|
129
|
+
phase: 'stalled',
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { t } from '@/lib/i18n';
|
|
2
|
+
import type {
|
|
3
|
+
RuntimeControlAction,
|
|
4
|
+
RuntimeLifecycleState,
|
|
5
|
+
RuntimeServiceState,
|
|
6
|
+
} from '@/api/runtime-control.types';
|
|
7
|
+
import type {
|
|
8
|
+
RuntimeControlPanelView,
|
|
9
|
+
RuntimeStatusBadgeView,
|
|
10
|
+
SystemConnectionStatus,
|
|
11
|
+
SystemStatusPhase,
|
|
12
|
+
SystemStatusState,
|
|
13
|
+
SystemStatusView,
|
|
14
|
+
} from '@/features/system-status/types/system-status.types';
|
|
15
|
+
|
|
16
|
+
function resolveSystemStatusPhase(state: SystemStatusState): SystemStatusPhase {
|
|
17
|
+
return state.activeSystemAction ? 'service-transitioning' : state.lifecyclePhase;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveSystemConnectionStatus(
|
|
21
|
+
phase: SystemStatusPhase
|
|
22
|
+
): SystemConnectionStatus {
|
|
23
|
+
if (phase === 'ready') {
|
|
24
|
+
return 'connected';
|
|
25
|
+
}
|
|
26
|
+
if (phase === 'startup-failed' || phase === 'stalled') {
|
|
27
|
+
return 'disconnected';
|
|
28
|
+
}
|
|
29
|
+
return 'connecting';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveChatRuntimeMessage(
|
|
33
|
+
state: SystemStatusState
|
|
34
|
+
): string | null {
|
|
35
|
+
if (state.activeSystemAction?.message?.trim()) {
|
|
36
|
+
return state.activeSystemAction.message.trim();
|
|
37
|
+
}
|
|
38
|
+
if (state.lifecyclePhase === 'cold-starting') {
|
|
39
|
+
return t('chatRuntimeInitializing');
|
|
40
|
+
}
|
|
41
|
+
if (state.lifecyclePhase === 'startup-failed') {
|
|
42
|
+
return (
|
|
43
|
+
state.bootstrapStatus?.ncpAgent.error?.trim() ||
|
|
44
|
+
state.bootstrapStatus?.lastError?.trim() ||
|
|
45
|
+
state.lastError?.trim() ||
|
|
46
|
+
t('chatRuntimeInitializationFailed')
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function toSystemStatusView(
|
|
53
|
+
state: SystemStatusState
|
|
54
|
+
): SystemStatusView {
|
|
55
|
+
const phase = resolveSystemStatusPhase(state);
|
|
56
|
+
return {
|
|
57
|
+
...state,
|
|
58
|
+
phase,
|
|
59
|
+
connectionStatus: resolveSystemConnectionStatus(phase),
|
|
60
|
+
isChatBlocked: phase !== 'ready',
|
|
61
|
+
chatMessage: resolveChatRuntimeMessage(state),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveActionLifecycleLabel(
|
|
66
|
+
action: RuntimeControlAction
|
|
67
|
+
): RuntimeLifecycleState {
|
|
68
|
+
if (action === 'start-service') {
|
|
69
|
+
return 'starting-service';
|
|
70
|
+
}
|
|
71
|
+
if (action === 'stop-service') {
|
|
72
|
+
return 'stopping-service';
|
|
73
|
+
}
|
|
74
|
+
if (action === 'restart-service') {
|
|
75
|
+
return 'restarting-service';
|
|
76
|
+
}
|
|
77
|
+
return 'restarting-app';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveActionServiceState(
|
|
81
|
+
action: RuntimeControlAction
|
|
82
|
+
): RuntimeServiceState | null {
|
|
83
|
+
if (action === 'start-service') {
|
|
84
|
+
return 'starting';
|
|
85
|
+
}
|
|
86
|
+
if (action === 'stop-service') {
|
|
87
|
+
return 'stopping';
|
|
88
|
+
}
|
|
89
|
+
if (action === 'restart-service') {
|
|
90
|
+
return 'restarting';
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function buildActiveSystemActionState(params: {
|
|
96
|
+
action: RuntimeControlAction;
|
|
97
|
+
message: string | null;
|
|
98
|
+
}): SystemStatusState['activeSystemAction'] {
|
|
99
|
+
const { action, message } = params;
|
|
100
|
+
return {
|
|
101
|
+
action,
|
|
102
|
+
lifecycle: resolveActionLifecycleLabel(action),
|
|
103
|
+
serviceState: resolveActionServiceState(action),
|
|
104
|
+
message,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function toRuntimeStatusBadgeView(
|
|
109
|
+
state: SystemStatusState
|
|
110
|
+
): RuntimeStatusBadgeView {
|
|
111
|
+
if (state.runtimeControlError) {
|
|
112
|
+
return {
|
|
113
|
+
tone: 'inactive',
|
|
114
|
+
title: t('runtimeControlLoadFailed'),
|
|
115
|
+
description: state.runtimeControlError,
|
|
116
|
+
reasonLines: [],
|
|
117
|
+
actionLabel: null,
|
|
118
|
+
isBusy: false,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!state.runtimeControlView) {
|
|
123
|
+
return {
|
|
124
|
+
tone: 'inactive',
|
|
125
|
+
title: t('runtimeStatusLoadingTitle'),
|
|
126
|
+
description: t('runtimeStatusLoadingDescription'),
|
|
127
|
+
reasonLines: [],
|
|
128
|
+
actionLabel: null,
|
|
129
|
+
isBusy: Boolean(state.activeSystemAction),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (state.activeSystemAction) {
|
|
134
|
+
return {
|
|
135
|
+
tone: 'attention',
|
|
136
|
+
title: t('runtimeControlTitle'),
|
|
137
|
+
description:
|
|
138
|
+
state.activeSystemAction.message ||
|
|
139
|
+
state.runtimeControlView.message ||
|
|
140
|
+
t('runtimeControlDescription'),
|
|
141
|
+
reasonLines: [],
|
|
142
|
+
actionLabel: null,
|
|
143
|
+
isBusy: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const view = state.runtimeControlView;
|
|
148
|
+
if (view.pendingRestart) {
|
|
149
|
+
return {
|
|
150
|
+
tone: 'attention',
|
|
151
|
+
title: t('runtimeStatusPendingRestartTitle'),
|
|
152
|
+
description: t('runtimeStatusPendingRestartDescription'),
|
|
153
|
+
reasonLines:
|
|
154
|
+
view.pendingRestart.changedPaths.length > 0
|
|
155
|
+
? view.pendingRestart.changedPaths.map((path: string) =>
|
|
156
|
+
t('runtimeStatusPendingRestartReasonItem').replace('{path}', path)
|
|
157
|
+
)
|
|
158
|
+
: [view.pendingRestart.message],
|
|
159
|
+
actionLabel: view.canRestartService.available
|
|
160
|
+
? t('runtimeStatusRestartAction')
|
|
161
|
+
: null,
|
|
162
|
+
isBusy: false,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
tone: view.lifecycle === 'healthy' ? 'healthy' : 'inactive',
|
|
168
|
+
title: t('runtimeStatusHealthyTitle'),
|
|
169
|
+
description: t('runtimeStatusHealthyDescription'),
|
|
170
|
+
reasonLines: [],
|
|
171
|
+
actionLabel: null,
|
|
172
|
+
isBusy: false,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function toRuntimeControlPanelView(
|
|
177
|
+
state: SystemStatusState
|
|
178
|
+
): RuntimeControlPanelView {
|
|
179
|
+
const action = state.activeSystemAction;
|
|
180
|
+
const controlView = state.runtimeControlView;
|
|
181
|
+
const visibleLifecycle =
|
|
182
|
+
action?.lifecycle ?? controlView?.lifecycle ?? 'healthy';
|
|
183
|
+
const visibleServiceState =
|
|
184
|
+
action?.serviceState ?? controlView?.serviceState ?? 'unknown';
|
|
185
|
+
const visibleMessage =
|
|
186
|
+
action?.message ||
|
|
187
|
+
state.lastSystemActionError ||
|
|
188
|
+
controlView?.message ||
|
|
189
|
+
t('runtimeControlDescription');
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
controlView,
|
|
193
|
+
visibleLifecycle,
|
|
194
|
+
visibleServiceState,
|
|
195
|
+
visibleMessage,
|
|
196
|
+
busyAction: action?.action ?? null,
|
|
197
|
+
busy: Boolean(action),
|
|
198
|
+
pendingRestart: controlView?.pendingRestart ?? null,
|
|
199
|
+
errorMessage:
|
|
200
|
+
state.lastSystemActionError || state.runtimeControlError || null,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { applyNcpSessionRealtimeEvent } from '@/api/ncp-session-query-cache';
|
|
3
|
+
import { systemStatusManager } from '@/features/system-status';
|
|
3
4
|
import { appClient } from '@/transport';
|
|
4
|
-
import { useUiStore } from '@/stores/ui.store';
|
|
5
5
|
import type { QueryClient } from '@tanstack/react-query';
|
|
6
6
|
|
|
7
|
-
type ConnectionStatus = 'connected' | 'disconnected' | 'connecting';
|
|
8
|
-
type SetConnectionStatus = (status: ConnectionStatus) => void;
|
|
9
|
-
|
|
10
7
|
function shouldInvalidateConfigQuery(configPath: string) {
|
|
11
8
|
const normalized = configPath.trim().toLowerCase();
|
|
12
9
|
if (!normalized) {
|
|
@@ -39,22 +36,34 @@ function handleConfigUpdatedEvent(queryClient: QueryClient | undefined, path: st
|
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
function handleRealtimeEvent(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
params: {
|
|
40
|
+
queryClient: QueryClient | undefined;
|
|
41
|
+
shouldResyncSessions: boolean;
|
|
42
|
+
clearShouldResyncSessions: () => void;
|
|
43
|
+
markShouldResyncSessions: () => void;
|
|
44
|
+
event: Parameters<Parameters<typeof appClient.subscribe>[0]>[0];
|
|
45
|
+
}
|
|
46
46
|
): void {
|
|
47
|
+
const {
|
|
48
|
+
queryClient,
|
|
49
|
+
shouldResyncSessions,
|
|
50
|
+
clearShouldResyncSessions,
|
|
51
|
+
markShouldResyncSessions,
|
|
52
|
+
event,
|
|
53
|
+
} = params;
|
|
47
54
|
if (event.type === 'connection.open') {
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
55
|
+
systemStatusManager.handleConnectionRestored();
|
|
56
|
+
if (shouldResyncSessions) {
|
|
57
|
+
clearShouldResyncSessions();
|
|
51
58
|
queryClient?.invalidateQueries({ queryKey: ['ncp-sessions'] });
|
|
52
59
|
}
|
|
53
60
|
return;
|
|
54
61
|
}
|
|
55
62
|
if (event.type === 'connection.close' || event.type === 'connection.error') {
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
systemStatusManager.handleConnectionInterrupted(
|
|
64
|
+
event.type === 'connection.error' ? event.payload?.message : null
|
|
65
|
+
);
|
|
66
|
+
markShouldResyncSessions();
|
|
58
67
|
return;
|
|
59
68
|
}
|
|
60
69
|
if (event.type === 'config.updated') {
|
|
@@ -76,14 +85,21 @@ function handleRealtimeEvent(
|
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
export function useRealtimeQueryBridge(queryClient?: QueryClient) {
|
|
79
|
-
const { setConnectionStatus } = useUiStore();
|
|
80
88
|
const shouldResyncSessionsRef = useRef(false);
|
|
81
89
|
|
|
82
90
|
useEffect(() => {
|
|
83
|
-
setConnectionStatus('connecting');
|
|
84
|
-
|
|
85
91
|
return appClient.subscribe((event) =>
|
|
86
|
-
handleRealtimeEvent(
|
|
92
|
+
handleRealtimeEvent({
|
|
93
|
+
queryClient,
|
|
94
|
+
shouldResyncSessions: shouldResyncSessionsRef.current,
|
|
95
|
+
clearShouldResyncSessions: () => {
|
|
96
|
+
shouldResyncSessionsRef.current = false;
|
|
97
|
+
},
|
|
98
|
+
markShouldResyncSessions: () => {
|
|
99
|
+
shouldResyncSessionsRef.current = true;
|
|
100
|
+
},
|
|
101
|
+
event,
|
|
102
|
+
})
|
|
87
103
|
);
|
|
88
|
-
}, [queryClient
|
|
104
|
+
}, [queryClient]);
|
|
89
105
|
}
|
package/src/hooks/useConfig.ts
CHANGED
|
@@ -185,7 +185,8 @@ export function useUpdateChannel() {
|
|
|
185
185
|
updateChannel(channel, data as Parameters<typeof updateChannel>[1]),
|
|
186
186
|
onSuccess: () => {
|
|
187
187
|
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
188
|
-
|
|
188
|
+
queryClient.invalidateQueries({ queryKey: ['config-meta'] });
|
|
189
|
+
toast.success(t('configSavedApplying'));
|
|
189
190
|
},
|
|
190
191
|
onError: (error: Error) => {
|
|
191
192
|
toast.error(t('configSaveFailed') + ': ' + error.message);
|
package/src/index.css
CHANGED
|
@@ -59,6 +59,30 @@
|
|
|
59
59
|
background: hsl(var(--gray-400));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
.workspace-horizontal-scrollbar {
|
|
63
|
+
scrollbar-width: thin;
|
|
64
|
+
scrollbar-color: hsl(var(--gray-300) / 0.38) transparent;
|
|
65
|
+
scrollbar-gutter: stable;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.workspace-horizontal-scrollbar::-webkit-scrollbar {
|
|
69
|
+
width: 3px;
|
|
70
|
+
height: 2px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.workspace-horizontal-scrollbar::-webkit-scrollbar-track {
|
|
74
|
+
background: transparent;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.workspace-horizontal-scrollbar::-webkit-scrollbar-thumb {
|
|
78
|
+
background: hsl(var(--gray-300) / 0.38);
|
|
79
|
+
border-radius: 999px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.workspace-horizontal-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
83
|
+
background: hsl(var(--gray-400) / 0.48);
|
|
84
|
+
}
|
|
85
|
+
|
|
62
86
|
/* ========================================
|
|
63
87
|
GLASSMORPHISM
|
|
64
88
|
======================================== */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { resolveAppResourceUri } from "@/lib/app-resource-uri";
|
|
3
|
+
|
|
4
|
+
describe("resolveAppResourceUri", () => {
|
|
5
|
+
it("maps app resource uris to public app paths", () => {
|
|
6
|
+
expect(resolveAppResourceUri("app://runtime-icons/codex-openai.svg")).toBe(
|
|
7
|
+
"/runtime-icons/codex-openai.svg",
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("passes through ordinary image src values for compatibility", () => {
|
|
12
|
+
expect(resolveAppResourceUri("https://example.com/icon.png")).toBe(
|
|
13
|
+
"https://example.com/icon.png",
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("rejects app resource uris that escape the app resource directory", () => {
|
|
18
|
+
expect(resolveAppResourceUri("app://../icon.png")).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const APP_RESOURCE_URI_PREFIX = "app://";
|
|
2
|
+
|
|
3
|
+
function normalizeAppResourcePath(value: string): string | null {
|
|
4
|
+
const normalized = value.trim().replace(/^\/+/, "");
|
|
5
|
+
if (!normalized) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const segments = normalized.split("/");
|
|
9
|
+
if (
|
|
10
|
+
segments.some((segment) => segment.trim().length === 0 || segment === "." || segment === "..")
|
|
11
|
+
) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return segments.join("/");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveAppResourceUri(uri: string): string | null {
|
|
18
|
+
const normalized = uri.trim();
|
|
19
|
+
if (!normalized) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (!normalized.startsWith(APP_RESOURCE_URI_PREFIX)) {
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
const appResourcePath = normalizeAppResourcePath(
|
|
26
|
+
normalized.slice(APP_RESOURCE_URI_PREFIX.length),
|
|
27
|
+
);
|
|
28
|
+
return appResourcePath ? `/${appResourcePath}` : null;
|
|
29
|
+
}
|
package/src/lib/i18n.chat.ts
CHANGED
|
@@ -59,6 +59,14 @@ export const CHAT_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
59
59
|
chatWorkspacePreviewTruncated: { zh: '内容已截断', en: 'Preview truncated' },
|
|
60
60
|
chatSessionUnread: { zh: '会话有未读更新', en: 'Session has unread updates' },
|
|
61
61
|
chatTyping: { zh: 'Agent 正在思考...', en: 'Agent is thinking...' },
|
|
62
|
+
chatRuntimeInitializing: {
|
|
63
|
+
zh: '聊天能力正在初始化。你可以先输入内容,完成后即可发送。',
|
|
64
|
+
en: 'Chat is still initializing. You can keep typing and send once startup finishes.'
|
|
65
|
+
},
|
|
66
|
+
chatRuntimeInitializationFailed: {
|
|
67
|
+
zh: '聊天能力启动失败,请稍后重试或检查服务日志。',
|
|
68
|
+
en: 'Chat startup failed. Please retry in a moment or inspect the service logs.'
|
|
69
|
+
},
|
|
62
70
|
chatInputPlaceholder: { zh: '输入消息,输入 / 选择技能,Enter 发送,Shift + Enter 换行', en: 'Type a message, type / to select skills, Enter to send, Shift + Enter for newline' },
|
|
63
71
|
chatInputHint: { zh: '支持多轮上下文,默认走当前会话。', en: 'Multi-turn context is preserved in the current session.' },
|
|
64
72
|
chatSlashSectionCommands: { zh: '命令', en: 'Commands' },
|
package/src/lib/i18n.remote.ts
CHANGED
|
@@ -5,7 +5,7 @@ export const REMOTE_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
5
5
|
en: 'Make this device appear in your NextClaw Platform device list and open it from the web.'
|
|
6
6
|
},
|
|
7
7
|
remoteOpenWeb: { zh: '前往 NextClaw Web', en: 'Open NextClaw Web' },
|
|
8
|
-
remoteOpenDeviceList: { zh: '
|
|
8
|
+
remoteOpenDeviceList: { zh: '前往 NextClaw Web', en: 'Open NextClaw Web' },
|
|
9
9
|
remoteOpenWebHint: {
|
|
10
10
|
zh: '开启后,这台设备会出现在 NextClaw Web 中,你可以在那里点击打开并继续使用。',
|
|
11
11
|
en: 'Once enabled, this device appears in NextClaw Web, where you can open it and keep working.'
|
|
@@ -75,6 +75,37 @@ export const RUNTIME_CONTROL_LABELS: Record<string, { zh: string; en: string }>
|
|
|
75
75
|
runtimeControlRestartService: { zh: '重启服务', en: 'Restart Service' },
|
|
76
76
|
runtimeControlStopService: { zh: '停止服务', en: 'Stop Service' },
|
|
77
77
|
runtimeControlRestartApp: { zh: '重启应用', en: 'Restart App' },
|
|
78
|
+
runtimeControlPendingRestartTitle: { zh: '待重启', en: 'Pending Restart' },
|
|
79
|
+
runtimeControlPendingRestartDescription: {
|
|
80
|
+
zh: '这次改动已经保存,但系统不会自动重启。请在你方便的时候手动重启,重启完成后该提示会自动清空。',
|
|
81
|
+
en: 'These changes are saved, but the system will not restart automatically. Restart manually when you are ready, and this notice clears after the restart finishes.'
|
|
82
|
+
},
|
|
83
|
+
runtimeControlPendingRestartPaths: { zh: '待生效项', en: 'Changes Waiting For Restart' },
|
|
84
|
+
runtimeStatusLoadingTitle: { zh: '读取状态中', en: 'Loading status' },
|
|
85
|
+
runtimeStatusLoadingDescription: {
|
|
86
|
+
zh: '正在读取当前系统状态。',
|
|
87
|
+
en: 'Loading the current system status.'
|
|
88
|
+
},
|
|
89
|
+
runtimeStatusHealthyTitle: { zh: '系统正常', en: 'System healthy' },
|
|
90
|
+
runtimeStatusHealthyDescription: {
|
|
91
|
+
zh: '当前没有需要你立即处理的系统动作。',
|
|
92
|
+
en: 'There is no system action that needs your attention right now.'
|
|
93
|
+
},
|
|
94
|
+
runtimeStatusPendingRestartTitle: { zh: '待重启', en: 'Restart required' },
|
|
95
|
+
runtimeStatusPendingRestartDescription: {
|
|
96
|
+
zh: '这些改动已经保存,但不会自动重启。你可以在这里查看原因,并在方便的时候手动重启。',
|
|
97
|
+
en: 'These changes are saved, but the system will not restart automatically. Review the reason here and restart when you are ready.'
|
|
98
|
+
},
|
|
99
|
+
runtimeStatusPendingRestartReasonItem: {
|
|
100
|
+
zh: '{path} 改动将在重启后生效。',
|
|
101
|
+
en: 'Changes in {path} will apply after restart.'
|
|
102
|
+
},
|
|
103
|
+
runtimeStatusActionHint: {
|
|
104
|
+
zh: '准备好时再执行',
|
|
105
|
+
en: 'Run when you are ready'
|
|
106
|
+
},
|
|
107
|
+
runtimeStatusRestartAction: { zh: '立即重启', en: 'Restart now' },
|
|
108
|
+
runtimeStatusRestartingAction: { zh: '重启中...', en: 'Restarting...' },
|
|
78
109
|
runtimeControlStartingServiceHelp: {
|
|
79
110
|
zh: '正在启动 NextClaw 服务,页面可能会在服务恢复后重新连接。',
|
|
80
111
|
en: 'Starting the NextClaw service. The page may reconnect after the service becomes available.'
|
package/src/lib/i18n.ts
CHANGED
|
@@ -497,22 +497,19 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
497
497
|
|
|
498
498
|
// Remote & Status
|
|
499
499
|
...REMOTE_LABELS,
|
|
500
|
-
|
|
501
|
-
// Action labels
|
|
502
500
|
actionConfigure: { zh: '配置', en: 'Configure' },
|
|
503
501
|
actionAddProvider: { zh: '添加提供商', en: 'Add Provider' },
|
|
504
502
|
actionEnable: { zh: '启用', en: 'Enable' },
|
|
505
|
-
|
|
506
|
-
// Messages
|
|
507
503
|
configSaved: { zh: '配置已保存', en: 'Configuration saved' },
|
|
504
|
+
configSavedApplying: { zh: '配置已保存,正在应用', en: 'Configuration saved, applying changes' },
|
|
508
505
|
configSavedApplied: { zh: '配置已保存并已应用', en: 'Configuration saved and applied' },
|
|
509
506
|
configSaveFailed: { zh: '保存配置失败', en: 'Failed to save configuration' },
|
|
510
507
|
configReloaded: { zh: '配置已重载', en: 'Configuration reloaded' },
|
|
511
508
|
configReloadFailed: { zh: '重载配置失败', en: 'Failed to reload configuration' },
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
},
|
|
509
|
+
channelConfigApplying: { zh: '渠道配置正在应用', en: 'Channel configuration is applying' },
|
|
510
|
+
channelConfigApplied: { zh: '渠道配置已应用', en: 'Channel configuration applied' },
|
|
511
|
+
channelConfigApplyFailed: { zh: '渠道配置应用失败', en: 'Failed to apply channel configuration' },
|
|
512
|
+
feishuVerifySuccess: { zh: '验证成功,请到飞书开放平台完成事件订阅与发布后再开始使用。', en: 'Verified. Please finish Feishu event subscription and app publishing before using.' },
|
|
516
513
|
feishuVerifyFailed: { zh: '验证失败', en: 'Verification failed' },
|
|
517
514
|
enterTag: { zh: '输入后按回车...', en: 'Type and press Enter...' },
|
|
518
515
|
headerName: { zh: 'Header 名称', en: 'Header Name' },
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { resolveSessionContextView } from "@/lib/session-context.utils";
|
|
3
|
+
|
|
4
|
+
vi.mock("@/lib/logos", () => ({
|
|
5
|
+
getChannelLogo: vi.fn(() => null),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
vi.mock("@/lib/i18n", () => ({
|
|
9
|
+
t: (key: string) => key,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe("resolveSessionContextView", () => {
|
|
13
|
+
it("prefers a declared runtime icon before falling back to a text label", () => {
|
|
14
|
+
const view = resolveSessionContextView(
|
|
15
|
+
{
|
|
16
|
+
key: "session-1",
|
|
17
|
+
createdAt: "2026-04-18T00:00:00.000Z",
|
|
18
|
+
updatedAt: "2026-04-18T00:00:00.000Z",
|
|
19
|
+
sessionType: "hermes",
|
|
20
|
+
sessionTypeMutable: true,
|
|
21
|
+
messageCount: 1,
|
|
22
|
+
},
|
|
23
|
+
[
|
|
24
|
+
{
|
|
25
|
+
value: "hermes",
|
|
26
|
+
label: "Hermes",
|
|
27
|
+
icon: {
|
|
28
|
+
kind: "image",
|
|
29
|
+
src: "app://runtime-icons/hermes-agent.png",
|
|
30
|
+
alt: "Hermes",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(view).toEqual({
|
|
37
|
+
icon: {
|
|
38
|
+
kind: "runtime-image",
|
|
39
|
+
src: "app://runtime-icons/hermes-agent.png",
|
|
40
|
+
alt: "Hermes",
|
|
41
|
+
name: "Hermes",
|
|
42
|
+
},
|
|
43
|
+
label: null,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("falls back to the resolved runtime label when no runtime icon is available", () => {
|
|
48
|
+
const view = resolveSessionContextView(
|
|
49
|
+
{
|
|
50
|
+
key: "session-2",
|
|
51
|
+
createdAt: "2026-04-18T00:00:00.000Z",
|
|
52
|
+
updatedAt: "2026-04-18T00:00:00.000Z",
|
|
53
|
+
sessionType: "custom-runtime",
|
|
54
|
+
sessionTypeMutable: true,
|
|
55
|
+
messageCount: 2,
|
|
56
|
+
},
|
|
57
|
+
[
|
|
58
|
+
{
|
|
59
|
+
value: "custom-runtime",
|
|
60
|
+
label: "Custom Runtime",
|
|
61
|
+
icon: null,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(view).toEqual({
|
|
67
|
+
icon: null,
|
|
68
|
+
label: "Custom Runtime",
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|