@nextclaw/ui 0.12.19 → 0.12.20-beta.0
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 +27 -0
- package/dist/assets/api-C412zuay.js +15 -0
- package/dist/assets/app-manager-provider-Cm-KiZZG.js +1 -0
- package/dist/assets/app-navigation.config-BORqHkbN.js +1 -0
- package/dist/assets/{book-open-CVEuA0y5.js → book-open-DgLqYpNY.js} +1 -1
- package/dist/assets/{channels-list-page-BqhqaBf1.js → channels-list-page-sISO_4Yj.js} +2 -2
- package/dist/assets/{chat-D4KecKjB.js → chat-ChCu7LQD.js} +13 -12
- package/dist/assets/chat-page-BCaNZJGT.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-24FLdHl7.js → chunk-JZWAC4HX-DvbcIVPf.js} +1 -1
- package/dist/assets/{config-split-page-BGjVACdO.js → config-split-page-BMRGuCJQ.js} +1 -1
- package/dist/assets/{createLucideIcon-PPrXCGK8.js → createLucideIcon-BZkY6emz.js} +1 -1
- package/dist/assets/desktop-update-config-BfJ5iSeY.js +1 -0
- package/dist/assets/{dialog-CTCX7oLf.js → dialog-B-CXiFPZ.js} +1 -1
- package/dist/assets/{dist-FL5e8mMi.js → dist-DYVfg3q5.js} +1 -1
- package/dist/assets/{doc-browser-C02neCIE.js → doc-browser-BUlCkZo2.js} +1 -1
- package/dist/assets/doc-browser-CzCV73NJ.js +1 -0
- package/dist/assets/doc-browser-Doh2541x.js +1 -0
- package/dist/assets/{doc-browser-context-C-WPOji4.js → doc-browser-context-DfLHAWbG.js} +1 -1
- package/dist/assets/{es2015-BNy4R8AC.js → es2015-BXroVnPi.js} +1 -1
- package/dist/assets/{external-link-BNtqJE01.js → external-link-Sw3ah_JD.js} +1 -1
- package/dist/assets/{folder-QyJHVUNz.js → folder-D7-VTnkz.js} +1 -1
- package/dist/assets/{hash-BGYUE-zr.js → hash-zajSTDXZ.js} +1 -1
- package/dist/assets/i18n-C5Mibli1.js +1 -0
- package/dist/assets/index-CUmk8xFK.css +1 -0
- package/dist/assets/index-CqPDhosM.js +2 -0
- package/dist/assets/{key-round-DenCfA2w.js → key-round-CnI1mc9F.js} +1 -1
- package/dist/assets/loader-circle-B5i8oMMY.js +1 -0
- package/dist/assets/{logo-badge-CKAxvQFc.js → logo-badge-BQgKnVtz.js} +1 -1
- package/dist/assets/{logos-CqXnaJIm.js → logos-CqVm0q0W.js} +1 -1
- package/dist/assets/marketplace-page-C8uaWkfd.js +1 -0
- package/dist/assets/{marketplace-page-XnDa2ulT.js → marketplace-page-C9oZ01rM.js} +2 -2
- package/dist/assets/mcp-marketplace-page-DuEixgSs.js +40 -0
- package/dist/assets/mcp-marketplace-page-rNqr6ZpD.js +1 -0
- package/dist/assets/message-square-D6Z4NwpG.js +1 -0
- package/dist/assets/{model-config-ByeL6Toe.js → model-config-mfhqEZBG.js} +1 -1
- package/dist/assets/{notice-card-D00-02yg.js → notice-card-CozHB03G.js} +1 -1
- package/dist/assets/play-D8WJLnJe.js +1 -0
- package/dist/assets/plus-Di0KAkiO.js +1 -0
- package/dist/assets/{popover-AmJkxio3.js → popover-CPUPma-w.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-CfFJsJp-.js → provider-scoped-model-input-CL9sti2I.js} +1 -1
- package/dist/assets/{providers-list-HMQzW2WV.js → providers-list-HPmL2akJ.js} +1 -1
- package/dist/assets/{refresh-ccw-B-dhb3yS.js → refresh-ccw-Bii4w8aB.js} +1 -1
- package/dist/assets/refresh-cw-BxojR62w.js +1 -0
- package/dist/assets/remote-oDlAdgVA.js +1 -0
- package/dist/assets/{rotate-cw-BWqAG3Fv.js → rotate-cw-1Xqa7LZ8.js} +1 -1
- package/dist/assets/runtime-config-page-BCshTAAE.js +1 -0
- package/dist/assets/{save-DpdkGieJ.js → save--BVI5wZX.js} +1 -1
- package/dist/assets/search-config-Bcnk9VlL.js +1 -0
- package/dist/assets/{search-CQUdr7j_.js → search-vChioOoe.js} +1 -1
- package/dist/assets/{secrets-config-YCsGd1am.js → secrets-config-Dde-5Y1w.js} +2 -2
- package/dist/assets/{select-DVUtSFHZ.js → select-BELPuXLW.js} +1 -1
- package/dist/assets/{sessions-config-page-BKN-XdKr.js → sessions-config-page-CG49_0Z6.js} +2 -2
- package/dist/assets/{setting-row-Cb5-lFs-.js → setting-row-D5DtT6Ny.js} +1 -1
- package/dist/assets/{settings-DgtZZlnF.js → settings-CiRChctQ.js} +1 -1
- package/dist/assets/skeleton-CFQRIUzt.js +1 -0
- package/dist/assets/{sparkles-DNSCyDhL.js → sparkles-D1ZKWdm4.js} +1 -1
- package/dist/assets/{status-dot-X_j51OfA.js → status-dot-Dv_hiUVa.js} +1 -1
- package/dist/assets/{tabs-custom-CcWmekaF.js → tabs-custom-CsACkVji.js} +1 -1
- package/dist/assets/{tag-chip-fdbK2wE6.js → tag-chip-D9BWWgYg.js} +1 -1
- package/dist/assets/theme-provider-DeBrTglS.js +1 -0
- package/dist/assets/{tooltip-BkZCQcKw.js → tooltip-CI0rpNee.js} +1 -1
- package/dist/assets/{trash-2-CqciSCsg.js → trash-2-rY9ZteZX.js} +1 -1
- package/dist/assets/use-config-CrWZ_TSF.js +1 -0
- package/dist/assets/{use-confirm-dialog-DSrb9205.js → use-confirm-dialog-hbynwWf2.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-DmowtyTI.js → use-infinite-scroll-loader-Cw5qQr3-.js} +1 -1
- package/dist/assets/{use-viewport-layout-CaALCA51.js → use-viewport-layout-CWHVDC6z.js} +1 -1
- package/dist/assets/x-DpTzXQcX.js +1 -0
- package/dist/index.html +40 -39
- package/package.json +7 -6
- package/src/app/index.tsx +7 -1
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +1 -1
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -0
- package/src/features/chat/components/conversation/chat-input-bar.container.tsx +9 -4
- package/src/features/chat/components/conversation/chat-message-list.container.test.tsx +64 -6
- package/src/features/chat/components/conversation/chat-message-list.container.tsx +185 -17
- package/src/features/chat/components/session/session-context-icon.tsx +1 -4
- package/src/features/chat/hooks/use-ncp-chat-derived-state.ts +3 -1
- package/src/features/chat/hooks/use-ncp-session-conversation.test.tsx +74 -2
- package/src/features/chat/hooks/use-ncp-session-conversation.ts +32 -10
- package/src/features/chat/hooks/use-selected-session-context-window-indicator.ts +20 -0
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +25 -0
- package/src/features/chat/managers/ncp-chat-input.manager.ts +5 -1
- package/src/features/chat/pages/ncp-chat-page.tsx +15 -11
- package/src/features/chat/stores/chat-thread.store.ts +8 -2
- package/src/features/chat/utils/chat-context-window-indicator.utils.ts +50 -0
- package/src/features/chat/utils/chat-runtime.utils.ts +1 -1
- package/src/features/chat/utils/ncp-chat-runtime-availability.utils.test.ts +165 -0
- package/src/features/chat/utils/ncp-chat-runtime-availability.utils.ts +50 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +27 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +6 -4
- package/src/features/chat/utils/ncp-session-context-metadata.utils.ts +121 -0
- package/src/features/chat/utils/session-context.utils.ts +1 -2
- package/src/features/system-status/components/config/runtime-config-editor.tsx +6 -0
- package/src/features/system-status/components/config/runtime-settings-card.tsx +12 -0
- package/src/features/system-status/components/desktop-update-config.test.tsx +17 -7
- package/src/features/system-status/components/desktop-update-config.tsx +75 -30
- package/src/features/system-status/hooks/use-system-status.ts +0 -11
- package/src/features/system-status/index.ts +4 -1
- package/src/features/system-status/managers/runtime-update.manager.ts +330 -0
- package/src/features/system-status/managers/system-status.manager.test.ts +0 -25
- package/src/features/system-status/managers/system-status.manager.ts +1 -30
- package/src/features/system-status/stores/runtime-update.store.ts +24 -0
- package/src/features/system-status/types/system-status.types.ts +0 -2
- package/src/features/system-status/utils/runtime-config-agent.utils.ts +6 -1
- package/src/features/system-status/utils/system-status.utils.test.ts +1 -85
- package/src/features/system-status/utils/system-status.utils.ts +1 -23
- package/src/platforms/desktop/managers/desktop-update.manager.ts +6 -0
- package/src/platforms/desktop/types/desktop-update.types.ts +21 -19
- package/src/shared/components/common/brand-header.test.tsx +142 -0
- package/src/shared/components/common/brand-header.tsx +93 -0
- package/src/shared/components/cron-config.tsx +1 -1
- package/src/shared/components/doc-browser/doc-browser-context.test.tsx +1 -1
- package/src/shared/components/doc-browser/doc-browser.tsx +1 -1
- package/src/shared/components/search-config.tsx +3 -3
- package/src/shared/lib/api/README.md +3 -0
- package/src/shared/lib/api/index.ts +2 -0
- package/src/shared/lib/api/ncp-attachments.ts +2 -2
- package/src/shared/lib/api/ncp-session.types.ts +92 -0
- package/src/shared/lib/api/runtime-update.service.ts +50 -0
- package/src/shared/lib/api/types.ts +9 -74
- package/src/shared/lib/i18n/{chat.ts → chat-labels.utils.ts} +13 -1
- package/src/shared/lib/i18n/desktop-update-labels.utils.ts +65 -0
- package/src/shared/lib/i18n/index.ts +4 -5
- package/src/shared/lib/i18n/runtime/i18n-language-owner.ts +5 -5
- package/src/shared/lib/transport/remote-transport.service.ts +1 -1
- package/src/shared/lib/ui-document-title/index.ts +1 -1
- package/tsconfig.json +1 -0
- package/dist/assets/api-BurjmW4A.js +0 -15
- package/dist/assets/app-manager-provider-DhxUmyTv.js +0 -1
- package/dist/assets/app-navigation.config-Bpd16Pem.js +0 -1
- package/dist/assets/chat-page-Cc7n80lW.js +0 -1
- package/dist/assets/desktop-update-config-fMLlSStv.js +0 -1
- package/dist/assets/doc-browser-COj7x090.js +0 -1
- package/dist/assets/doc-browser-fyn7eDTp.js +0 -1
- package/dist/assets/i18n-CM4y8Mw9.js +0 -1
- package/dist/assets/index-CtVSzMPM.js +0 -2
- package/dist/assets/index-N3hjuljD.css +0 -1
- package/dist/assets/loader-circle-R23uEPkM.js +0 -1
- package/dist/assets/marketplace-page-mF-M5mku.js +0 -1
- package/dist/assets/mcp-marketplace-page-BArKWcRZ.js +0 -40
- package/dist/assets/mcp-marketplace-page-DBUcIIHJ.js +0 -1
- package/dist/assets/message-square-Dm34zD6k.js +0 -1
- package/dist/assets/play-ul4L6MWm.js +0 -1
- package/dist/assets/plus-D14303DH.js +0 -1
- package/dist/assets/remote-B4ELSd3u.js +0 -1
- package/dist/assets/runtime-config-page-N4FP6H0M.js +0 -1
- package/dist/assets/search-config-B62TY-z2.js +0 -1
- package/dist/assets/skeleton-BCPi52jT.js +0 -1
- package/dist/assets/theme-provider-WTWq_jYq.js +0 -1
- package/dist/assets/use-config-CyvhbRhf.js +0 -1
- package/dist/assets/x-tYcSDsrY.js +0 -1
- /package/dist/assets/{config-hints-CPNzbMEp.js → config-hints-MogHYQ8G.js} +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { UpdateSnapshot } from '@nextclaw/kernel';
|
|
2
|
+
import { create } from 'zustand';
|
|
3
|
+
|
|
4
|
+
export type RuntimeUpdateBusyAction =
|
|
5
|
+
| 'checking'
|
|
6
|
+
| 'downloading'
|
|
7
|
+
| 'applying'
|
|
8
|
+
| 'saving-preferences'
|
|
9
|
+
| 'switching-channel'
|
|
10
|
+
| null;
|
|
11
|
+
|
|
12
|
+
type RuntimeUpdateStoreState = {
|
|
13
|
+
supported: boolean;
|
|
14
|
+
initialized: boolean;
|
|
15
|
+
busyAction: RuntimeUpdateBusyAction;
|
|
16
|
+
snapshot: UpdateSnapshot | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const useRuntimeUpdateStore = create<RuntimeUpdateStoreState>(() => ({
|
|
20
|
+
supported: false,
|
|
21
|
+
initialized: false,
|
|
22
|
+
busyAction: null,
|
|
23
|
+
snapshot: null
|
|
24
|
+
}));
|
|
@@ -46,8 +46,6 @@ export type SystemStatusState = {
|
|
|
46
46
|
export type SystemStatusView = SystemStatusState & {
|
|
47
47
|
phase: SystemStatusPhase;
|
|
48
48
|
connectionStatus: SystemConnectionStatus;
|
|
49
|
-
isChatBlocked: boolean;
|
|
50
|
-
chatMessage: string | null;
|
|
51
49
|
};
|
|
52
50
|
|
|
53
51
|
export type RuntimeStatusTone = 'healthy' | 'attention' | 'inactive';
|
|
@@ -4,7 +4,7 @@ import { t } from '@/shared/lib/i18n';
|
|
|
4
4
|
export type DmScope = 'main' | 'per-peer' | 'per-channel-peer' | 'per-account-channel-peer';
|
|
5
5
|
export type PeerKind = '' | 'direct' | 'group' | 'channel';
|
|
6
6
|
export type RuntimeEntryDraft = RuntimeEntryView & { id: string; configText: string };
|
|
7
|
-
export type RuntimeConfigEditorState = { agents: AgentProfileView[]; bindings: AgentBindingView[]; runtimeEntries: RuntimeEntryDraft[]; dmScope: DmScope; defaultContextTokens: number; defaultEngine: string };
|
|
7
|
+
export type RuntimeConfigEditorState = { companionEnabled: boolean; agents: AgentProfileView[]; bindings: AgentBindingView[]; runtimeEntries: RuntimeEntryDraft[]; dmScope: DmScope; defaultContextTokens: number; defaultEngine: string };
|
|
8
8
|
|
|
9
9
|
const DEFAULT_NARP_STDIO_ENTRY_CONFIG = {
|
|
10
10
|
wireDialect: 'acp',
|
|
@@ -56,6 +56,7 @@ export function parseOptionalInt(value: string): number | undefined {
|
|
|
56
56
|
|
|
57
57
|
export function createRuntimeConfigEditorState(config: ConfigView): RuntimeConfigEditorState {
|
|
58
58
|
return {
|
|
59
|
+
companionEnabled: config.companion?.enabled === true,
|
|
59
60
|
agents: (config.agents.list ?? []).map(hydrateRuntimeAgent),
|
|
60
61
|
bindings: (config.bindings ?? []).map(hydrateRuntimeBinding),
|
|
61
62
|
runtimeEntries: Object.entries(config.agents.runtimes?.entries ?? {}).map(([id, entry]) => ({ id, enabled: entry.enabled !== false, label: entry.label ?? '', type: entry.type, config: entry.config ?? {}, configText: JSON.stringify(entry.config ?? {}, null, 2) })),
|
|
@@ -81,6 +82,7 @@ export function toPersistedRuntimeAgent(agent: AgentProfileView): AgentProfileVi
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
export function createRuntimeConfigUpdatePayload(input: {
|
|
85
|
+
companionEnabled: boolean;
|
|
84
86
|
agents: AgentProfileView[];
|
|
85
87
|
bindings: AgentBindingView[];
|
|
86
88
|
runtimeEntries: RuntimeEntryDraft[];
|
|
@@ -134,6 +136,9 @@ export function createRuntimeConfigUpdatePayload(input: {
|
|
|
134
136
|
return entries;
|
|
135
137
|
}, {});
|
|
136
138
|
return {
|
|
139
|
+
companion: {
|
|
140
|
+
enabled: input.companionEnabled
|
|
141
|
+
},
|
|
137
142
|
agents: {
|
|
138
143
|
defaults: {
|
|
139
144
|
contextTokens: Math.max(1000, input.defaultContextTokens),
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { t } from '@/shared/lib/i18n';
|
|
3
2
|
import {
|
|
4
|
-
resolveChatRuntimeMessage,
|
|
5
3
|
resolveSystemConnectionStatus,
|
|
6
4
|
toSystemStatusView,
|
|
7
5
|
} from './system-status.utils';
|
|
@@ -26,88 +24,8 @@ describe('resolveSystemConnectionStatus', () => {
|
|
|
26
24
|
});
|
|
27
25
|
});
|
|
28
26
|
|
|
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
27
|
describe('toSystemStatusView', () => {
|
|
110
|
-
it('
|
|
28
|
+
it('maps stalled to factual connection and lifecycle view fields', () => {
|
|
111
29
|
expect(
|
|
112
30
|
toSystemStatusView({
|
|
113
31
|
lifecyclePhase: 'stalled',
|
|
@@ -123,8 +41,6 @@ describe('toSystemStatusView', () => {
|
|
|
123
41
|
lastSystemActionError: null,
|
|
124
42
|
})
|
|
125
43
|
).toMatchObject({
|
|
126
|
-
isChatBlocked: true,
|
|
127
|
-
chatMessage: null,
|
|
128
44
|
connectionStatus: 'disconnected',
|
|
129
45
|
phase: 'stalled',
|
|
130
46
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { t } from '@/shared/lib/i18n';
|
|
2
1
|
import type {
|
|
3
2
|
RuntimeControlAction,
|
|
4
3
|
RuntimeLifecycleState,
|
|
5
4
|
RuntimeServiceState,
|
|
6
5
|
} from '@/shared/lib/api';
|
|
6
|
+
import { t } from '@/shared/lib/i18n';
|
|
7
7
|
import type {
|
|
8
8
|
RuntimeControlPanelView,
|
|
9
9
|
RuntimeStatusBadgeView,
|
|
@@ -29,26 +29,6 @@ export function resolveSystemConnectionStatus(
|
|
|
29
29
|
return 'connecting';
|
|
30
30
|
}
|
|
31
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
32
|
export function toSystemStatusView(
|
|
53
33
|
state: SystemStatusState
|
|
54
34
|
): SystemStatusView {
|
|
@@ -57,8 +37,6 @@ export function toSystemStatusView(
|
|
|
57
37
|
...state,
|
|
58
38
|
phase,
|
|
59
39
|
connectionStatus: resolveSystemConnectionStatus(phase),
|
|
60
|
-
isChatBlocked: phase !== 'ready',
|
|
61
|
-
chatMessage: resolveChatRuntimeMessage(state),
|
|
62
40
|
};
|
|
63
41
|
}
|
|
64
42
|
|
|
@@ -12,8 +12,10 @@ type DesktopUpdateBusyAction = 'checking' | 'downloading' | 'applying' | 'saving
|
|
|
12
12
|
|
|
13
13
|
export class DesktopUpdateManager {
|
|
14
14
|
private unsubscribe: (() => void) | null = null;
|
|
15
|
+
private subscriptionCount = 0;
|
|
15
16
|
|
|
16
17
|
start = async () => {
|
|
18
|
+
this.subscriptionCount += 1;
|
|
17
19
|
const desktopApi = this.getDesktopApi();
|
|
18
20
|
if (!desktopApi) {
|
|
19
21
|
useDesktopUpdateStore.setState({
|
|
@@ -56,6 +58,10 @@ export class DesktopUpdateManager {
|
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
stop = () => {
|
|
61
|
+
this.subscriptionCount = Math.max(0, this.subscriptionCount - 1);
|
|
62
|
+
if (this.subscriptionCount > 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
59
65
|
this.unsubscribe?.();
|
|
60
66
|
this.unsubscribe = null;
|
|
61
67
|
};
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import type {
|
|
2
|
+
InstallationKind,
|
|
3
|
+
UpdateBlockReason,
|
|
4
|
+
UpdatePreferences,
|
|
5
|
+
UpdateProgress,
|
|
6
|
+
UpdateSnapshot,
|
|
7
|
+
UpdateStatus,
|
|
8
|
+
} from '@nextclaw/kernel';
|
|
9
|
+
|
|
10
|
+
export type DesktopUpdateStatus = Extract<
|
|
11
|
+
UpdateStatus,
|
|
12
|
+
'idle' | 'checking' | 'update-available' | 'downloading' | 'downloaded' | 'blocked' | 'up-to-date' | 'failed'
|
|
13
|
+
>;
|
|
9
14
|
|
|
10
15
|
export type DesktopReleaseChannel = 'stable' | 'beta';
|
|
11
16
|
|
|
12
|
-
export type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
export type DesktopInstallationKind = InstallationKind;
|
|
18
|
+
|
|
19
|
+
export type DesktopUpdateBlockReason = UpdateBlockReason;
|
|
20
|
+
|
|
21
|
+
export type DesktopUpdateProgress = UpdateProgress;
|
|
22
|
+
|
|
23
|
+
export type DesktopUpdatePreferences = UpdatePreferences;
|
|
16
24
|
|
|
17
|
-
export type DesktopUpdateSnapshot = {
|
|
25
|
+
export type DesktopUpdateSnapshot = UpdateSnapshot & {
|
|
18
26
|
status: DesktopUpdateStatus;
|
|
19
27
|
channel: DesktopReleaseChannel;
|
|
20
28
|
launcherVersion: string;
|
|
21
|
-
currentVersion: string | null;
|
|
22
|
-
availableVersion: string | null;
|
|
23
|
-
downloadedVersion: string | null;
|
|
24
|
-
releaseNotesUrl: string | null;
|
|
25
|
-
lastCheckedAt: string | null;
|
|
26
|
-
errorMessage: string | null;
|
|
27
29
|
preferences: DesktopUpdatePreferences;
|
|
28
30
|
};
|
|
29
31
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
5
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { useRuntimeUpdateStore } from '@/features/system-status';
|
|
7
|
+
import { BrandHeader } from '@/shared/components/common/brand-header';
|
|
8
|
+
import { setLanguage } from '@/shared/lib/i18n';
|
|
9
|
+
|
|
10
|
+
const mocks = vi.hoisted(() => ({
|
|
11
|
+
applyDownloadedUpdate: vi.fn(),
|
|
12
|
+
downloadUpdate: vi.fn()
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('@/features/system-status', async () => {
|
|
16
|
+
const actual = await vi.importActual<typeof import('@/features/system-status')>(
|
|
17
|
+
'@/features/system-status'
|
|
18
|
+
);
|
|
19
|
+
return {
|
|
20
|
+
...actual,
|
|
21
|
+
runtimeUpdateManager: {
|
|
22
|
+
...actual.runtimeUpdateManager,
|
|
23
|
+
applyDownloadedUpdate: mocks.applyDownloadedUpdate,
|
|
24
|
+
downloadUpdate: mocks.downloadUpdate
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function renderBrandHeader() {
|
|
30
|
+
const queryClient = new QueryClient({
|
|
31
|
+
defaultOptions: {
|
|
32
|
+
queries: {
|
|
33
|
+
retry: false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
queryClient.setQueryData(['app-meta'], {
|
|
38
|
+
name: 'NextClaw',
|
|
39
|
+
productVersion: '0.18.11'
|
|
40
|
+
});
|
|
41
|
+
return render(
|
|
42
|
+
<QueryClientProvider client={queryClient}>
|
|
43
|
+
<MemoryRouter>
|
|
44
|
+
<BrandHeader suffix={null} />
|
|
45
|
+
</MemoryRouter>
|
|
46
|
+
</QueryClientProvider>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('BrandHeader', () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
setLanguage('zh');
|
|
53
|
+
useRuntimeUpdateStore.setState({
|
|
54
|
+
supported: false,
|
|
55
|
+
initialized: false,
|
|
56
|
+
busyAction: null,
|
|
57
|
+
snapshot: null
|
|
58
|
+
});
|
|
59
|
+
mocks.applyDownloadedUpdate.mockReset();
|
|
60
|
+
mocks.downloadUpdate.mockReset();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('shows update progress next to the product version', () => {
|
|
64
|
+
useRuntimeUpdateStore.setState({
|
|
65
|
+
supported: true,
|
|
66
|
+
initialized: true,
|
|
67
|
+
busyAction: null,
|
|
68
|
+
snapshot: {
|
|
69
|
+
status: 'downloading',
|
|
70
|
+
installationKind: 'desktop-bundle',
|
|
71
|
+
channel: 'stable',
|
|
72
|
+
hostVersion: '0.0.138',
|
|
73
|
+
currentVersion: '0.18.11',
|
|
74
|
+
availableVersion: '0.18.12',
|
|
75
|
+
downloadedVersion: null,
|
|
76
|
+
minimumHostVersion: null,
|
|
77
|
+
releaseNotesUrl: null,
|
|
78
|
+
lastCheckedAt: null,
|
|
79
|
+
progress: {
|
|
80
|
+
downloadedBytes: 50,
|
|
81
|
+
totalBytes: 100,
|
|
82
|
+
percent: 50
|
|
83
|
+
},
|
|
84
|
+
canAutoDownload: true,
|
|
85
|
+
canApplyInApp: false,
|
|
86
|
+
requiresRestart: false,
|
|
87
|
+
blockReason: null,
|
|
88
|
+
recoveryCommand: null,
|
|
89
|
+
errorMessage: null,
|
|
90
|
+
preferences: {
|
|
91
|
+
automaticChecks: true,
|
|
92
|
+
autoDownload: true
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
renderBrandHeader();
|
|
98
|
+
|
|
99
|
+
expect(screen.getByText('v0.18.11')).toBeTruthy();
|
|
100
|
+
expect(screen.getByText('下载 50%')).toBeTruthy();
|
|
101
|
+
expect(screen.queryByRole('button', { name: '更新' })).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('applies the downloaded update from the version-adjacent update button', async () => {
|
|
105
|
+
const user = userEvent.setup();
|
|
106
|
+
useRuntimeUpdateStore.setState({
|
|
107
|
+
supported: true,
|
|
108
|
+
initialized: true,
|
|
109
|
+
busyAction: null,
|
|
110
|
+
snapshot: {
|
|
111
|
+
status: 'downloaded',
|
|
112
|
+
installationKind: 'desktop-bundle',
|
|
113
|
+
channel: 'stable',
|
|
114
|
+
hostVersion: '0.0.138',
|
|
115
|
+
currentVersion: '0.18.11',
|
|
116
|
+
availableVersion: null,
|
|
117
|
+
downloadedVersion: '0.18.12',
|
|
118
|
+
minimumHostVersion: null,
|
|
119
|
+
releaseNotesUrl: null,
|
|
120
|
+
lastCheckedAt: null,
|
|
121
|
+
progress: null,
|
|
122
|
+
canAutoDownload: true,
|
|
123
|
+
canApplyInApp: true,
|
|
124
|
+
requiresRestart: false,
|
|
125
|
+
blockReason: null,
|
|
126
|
+
recoveryCommand: null,
|
|
127
|
+
errorMessage: null,
|
|
128
|
+
preferences: {
|
|
129
|
+
automaticChecks: true,
|
|
130
|
+
autoDownload: true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
renderBrandHeader();
|
|
136
|
+
|
|
137
|
+
await user.click(screen.getByRole('button', { name: '更新' }));
|
|
138
|
+
|
|
139
|
+
expect(mocks.applyDownloadedUpdate).toHaveBeenCalledTimes(1);
|
|
140
|
+
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import type { UpdateSnapshot } from '@nextclaw/kernel';
|
|
2
|
+
import { runtimeUpdateManager, useRuntimeUpdateStore } from '@/features/system-status';
|
|
1
3
|
import { useAppMeta } from '@/shared/hooks/use-config';
|
|
2
4
|
import type { ReactNode } from 'react';
|
|
3
5
|
import { RuntimeStatusEntry } from '@/app/components/layout/runtime-status-entry';
|
|
6
|
+
import { cn } from '@/shared/lib/utils';
|
|
7
|
+
import { t } from '@/shared/lib/i18n';
|
|
4
8
|
|
|
5
9
|
type BrandHeaderProps = {
|
|
6
10
|
className?: string;
|
|
@@ -21,8 +25,97 @@ export function BrandHeader({ className, suffix }: BrandHeaderProps) {
|
|
|
21
25
|
<div className="flex items-baseline gap-2 min-w-0">
|
|
22
26
|
<span className="truncate text-[15px] font-semibold tracking-[-0.01em] text-gray-800">{productName}</span>
|
|
23
27
|
{productVersion ? <span className="text-[13px] font-medium text-gray-500">v{productVersion}</span> : null}
|
|
28
|
+
<RuntimeUpdateInlineStatus />
|
|
24
29
|
{resolvedSuffix ? <span className="inline-flex items-center shrink-0">{resolvedSuffix}</span> : null}
|
|
25
30
|
</div>
|
|
26
31
|
</div>
|
|
27
32
|
);
|
|
28
33
|
}
|
|
34
|
+
|
|
35
|
+
function RuntimeUpdateInlineStatus() {
|
|
36
|
+
const { supported, busyAction, snapshot } = useRuntimeUpdateStore();
|
|
37
|
+
if (!supported || !snapshot) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (snapshot.status === 'downloading' || snapshot.status === 'blocked' || snapshot.status === 'failed') {
|
|
41
|
+
return <RuntimeUpdateInlineBadge snapshot={snapshot} />;
|
|
42
|
+
}
|
|
43
|
+
if (snapshot.status === 'downloaded') {
|
|
44
|
+
return (
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
className={cn(
|
|
48
|
+
'inline-flex h-5 shrink-0 items-center rounded-full px-2 text-[11px] font-semibold leading-none ring-1 transition-colors',
|
|
49
|
+
resolveInlineUpdateTone(snapshot.status)
|
|
50
|
+
)}
|
|
51
|
+
disabled={busyAction === 'applying'}
|
|
52
|
+
onClick={() => void runtimeUpdateManager.applyDownloadedUpdate()}
|
|
53
|
+
>
|
|
54
|
+
{busyAction === 'applying' ? t('desktopUpdatesInlineApplying') : t('desktopUpdatesInlineReady')}
|
|
55
|
+
</button>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (snapshot.status === 'update-available') {
|
|
59
|
+
return (
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
className={cn(
|
|
63
|
+
'inline-flex h-5 shrink-0 items-center rounded-full px-2 text-[11px] font-semibold leading-none ring-1 transition-colors',
|
|
64
|
+
resolveInlineUpdateTone(snapshot.status)
|
|
65
|
+
)}
|
|
66
|
+
disabled={busyAction === 'downloading'}
|
|
67
|
+
onClick={() => void runtimeUpdateManager.downloadUpdate()}
|
|
68
|
+
>
|
|
69
|
+
{busyAction === 'downloading' ? t('desktopUpdatesInlineDownloading') : t('desktopUpdatesInlineDownload')}
|
|
70
|
+
</button>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function RuntimeUpdateInlineBadge({ snapshot }: { snapshot: UpdateSnapshot }) {
|
|
77
|
+
const label = resolveInlineUpdateLabel(snapshot);
|
|
78
|
+
if (!label) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return (
|
|
82
|
+
<span
|
|
83
|
+
className={cn(
|
|
84
|
+
'inline-flex h-5 shrink-0 items-center rounded-full px-2 text-[11px] font-semibold leading-none ring-1 transition-colors',
|
|
85
|
+
resolveInlineUpdateTone(snapshot.status)
|
|
86
|
+
)}
|
|
87
|
+
title={t('updates')}
|
|
88
|
+
>
|
|
89
|
+
{label}
|
|
90
|
+
</span>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function resolveInlineUpdateLabel(snapshot: UpdateSnapshot): string | null {
|
|
95
|
+
if (snapshot.status === 'downloading') {
|
|
96
|
+
const percent = snapshot.progress?.percent;
|
|
97
|
+
return percent === null || percent === undefined
|
|
98
|
+
? t('desktopUpdatesInlineDownloading')
|
|
99
|
+
: t('desktopUpdatesInlineDownloadingPercent').replace('{percent}', String(percent));
|
|
100
|
+
}
|
|
101
|
+
if (snapshot.status === 'downloaded') {
|
|
102
|
+
return t('desktopUpdatesInlineReady');
|
|
103
|
+
}
|
|
104
|
+
if (snapshot.status === 'update-available') {
|
|
105
|
+
return t('desktopUpdatesInlineDownload');
|
|
106
|
+
}
|
|
107
|
+
if (snapshot.status === 'blocked' || snapshot.status === 'failed') {
|
|
108
|
+
return t('desktopUpdatesInlineAttention');
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function resolveInlineUpdateTone(status: UpdateSnapshot['status']): string {
|
|
114
|
+
if (status === 'downloaded') {
|
|
115
|
+
return 'bg-emerald-50 text-emerald-700 ring-emerald-100 hover:bg-emerald-100 disabled:opacity-70';
|
|
116
|
+
}
|
|
117
|
+
if (status === 'blocked' || status === 'failed') {
|
|
118
|
+
return 'bg-red-50 text-red-700 ring-red-100';
|
|
119
|
+
}
|
|
120
|
+
return 'bg-amber-50 text-amber-700 ring-amber-100 hover:bg-amber-100 disabled:opacity-70';
|
|
121
|
+
}
|
|
@@ -39,7 +39,7 @@ function formatEveryDuration(ms?: number | null): string {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function describeSchedule(job: CronJobView): string {
|
|
42
|
-
const schedule = job
|
|
42
|
+
const { schedule } = job;
|
|
43
43
|
if (schedule.kind === 'cron') {
|
|
44
44
|
return schedule.expr ? `cron ${schedule.expr}` : 'cron';
|
|
45
45
|
}
|
|
@@ -203,7 +203,7 @@ export function DocBrowser({ displayMode = 'desktop' }: DocBrowserProps) {
|
|
|
203
203
|
e.preventDefault();
|
|
204
204
|
e.stopPropagation();
|
|
205
205
|
setIsResizing(true);
|
|
206
|
-
const axis = (e.currentTarget as HTMLElement).dataset
|
|
206
|
+
const { axis } = (e.currentTarget as HTMLElement).dataset;
|
|
207
207
|
resizeRef.current = {
|
|
208
208
|
startX: e.clientX,
|
|
209
209
|
startY: e.clientY,
|
|
@@ -179,7 +179,7 @@ function SearchProviderFields(props: {
|
|
|
179
179
|
const { draft, provider, search, selectedDocsUrl, updateProviderDraft } = props;
|
|
180
180
|
|
|
181
181
|
if (provider === "bocha") {
|
|
182
|
-
const bocha = draft.providers
|
|
182
|
+
const { bocha } = draft.providers;
|
|
183
183
|
return (
|
|
184
184
|
<>
|
|
185
185
|
<SearchTextField
|
|
@@ -214,7 +214,7 @@ function SearchProviderFields(props: {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
if (provider === "tavily") {
|
|
217
|
-
const tavily = draft.providers
|
|
217
|
+
const { tavily } = draft.providers;
|
|
218
218
|
return (
|
|
219
219
|
<>
|
|
220
220
|
<SearchTextField
|
|
@@ -252,7 +252,7 @@ function SearchProviderFields(props: {
|
|
|
252
252
|
);
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
const brave = draft.providers
|
|
255
|
+
const { brave } = draft.providers;
|
|
256
256
|
return (
|
|
257
257
|
<>
|
|
258
258
|
<SearchTextField
|
|
@@ -10,11 +10,13 @@ export * from './marketplace';
|
|
|
10
10
|
export * from './mcp-marketplace';
|
|
11
11
|
export * from './ncp-attachments';
|
|
12
12
|
export * from './ncp-session';
|
|
13
|
+
export * from './ncp-session.types';
|
|
13
14
|
export * from './ncp-session-query-cache';
|
|
14
15
|
export * from './raw-client.utils';
|
|
15
16
|
export * from './remote';
|
|
16
17
|
export * from './remote.types';
|
|
17
18
|
export * from './runtime-control';
|
|
18
19
|
export * from './runtime-control.types';
|
|
20
|
+
export * from './runtime-update.service';
|
|
19
21
|
export * from './server-path';
|
|
20
22
|
export * from './types';
|
|
@@ -6,11 +6,11 @@ function readErrorMessage(payload: unknown, fallback: string): string {
|
|
|
6
6
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
7
7
|
return fallback;
|
|
8
8
|
}
|
|
9
|
-
const error = (payload as { error?: unknown })
|
|
9
|
+
const { error } = (payload as { error?: unknown });
|
|
10
10
|
if (!error || typeof error !== "object" || Array.isArray(error)) {
|
|
11
11
|
return fallback;
|
|
12
12
|
}
|
|
13
|
-
const message = (error as { message?: unknown })
|
|
13
|
+
const { message } = (error as { message?: unknown });
|
|
14
14
|
return typeof message === "string" && message.trim().length > 0 ? message : fallback;
|
|
15
15
|
}
|
|
16
16
|
|