@nextclaw/ui 0.12.19 → 0.12.20-beta.1
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 +39 -0
- package/dist/assets/api-BcqDx0tm.js +15 -0
- package/dist/assets/app-manager-provider-DVYBjif-.js +1 -0
- package/dist/assets/app-navigation.config-CMoWvFEI.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-CsoI4OJm.js} +2 -2
- package/dist/assets/{chat-D4KecKjB.js → chat-CA3aRmhx.js} +13 -12
- package/dist/assets/chat-page-gdSN6Pr6.js +1 -0
- package/dist/assets/chunk-JZWAC4HX-u4uYphxM.js +3 -0
- 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-CD6-2PfI.js +1 -0
- package/dist/assets/{dialog-CTCX7oLf.js → dialog-csshWetU.js} +1 -1
- package/dist/assets/{dist-FL5e8mMi.js → dist-Bl94Ahwx.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-JCM5-KtW.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-BTDFuKka.js +2 -0
- package/dist/assets/index-CUmk8xFK.css +1 -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-DJGDpTAo.js +1 -0
- package/dist/assets/{marketplace-page-XnDa2ulT.js → marketplace-page-DxlxHCFm.js} +2 -2
- package/dist/assets/mcp-marketplace-page-5UjYRWOR.js +40 -0
- package/dist/assets/mcp-marketplace-page-C1XaHZZO.js +1 -0
- package/dist/assets/message-square-D6Z4NwpG.js +1 -0
- package/dist/assets/{model-config-ByeL6Toe.js → model-config-PccJ9XyH.js} +1 -1
- package/dist/assets/{notice-card-D00-02yg.js → notice-card-CCgk6FvF.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-YAsxDBhY.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-CfFJsJp-.js → provider-scoped-model-input-CzpF7cug.js} +1 -1
- package/dist/assets/{providers-list-HMQzW2WV.js → providers-list-8qDMER8o.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-D4TtLPAp.js +1 -0
- package/dist/assets/{rotate-cw-BWqAG3Fv.js → rotate-cw-1Xqa7LZ8.js} +1 -1
- package/dist/assets/runtime-config-page-D-4c5H5z.js +1 -0
- package/dist/assets/{save-DpdkGieJ.js → save--BVI5wZX.js} +1 -1
- package/dist/assets/search-config-D3a65l3r.js +1 -0
- package/dist/assets/{search-CQUdr7j_.js → search-vChioOoe.js} +1 -1
- package/dist/assets/{secrets-config-YCsGd1am.js → secrets-config-CoMlR_7i.js} +2 -2
- package/dist/assets/{select-DVUtSFHZ.js → select-DIZrwsKU.js} +1 -1
- package/dist/assets/{sessions-config-page-BKN-XdKr.js → sessions-config-page-Cc0TJStn.js} +2 -2
- package/dist/assets/{setting-row-Cb5-lFs-.js → setting-row-DiQyrE81.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-C3wDBe_-.js} +1 -1
- package/dist/assets/theme-provider-aOmrJ9J6.js +1 -0
- package/dist/assets/{tooltip-BkZCQcKw.js → tooltip-Dq5Xehpk.js} +1 -1
- package/dist/assets/{trash-2-CqciSCsg.js → trash-2-rY9ZteZX.js} +1 -1
- package/dist/assets/use-config-BQJjq1mP.js +1 -0
- package/dist/assets/{use-confirm-dialog-DSrb9205.js → use-confirm-dialog-DBoV5n5P.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-DmowtyTI.js → use-infinite-scroll-loader-JAicqVC5.js} +1 -1
- package/dist/assets/{use-viewport-layout-CaALCA51.js → use-viewport-layout-BX3XqzJ4.js} +1 -1
- package/dist/assets/x-DpTzXQcX.js +1 -0
- package/dist/index.html +40 -39
- package/package.json +9 -6
- package/src/app/hooks/use-realtime-query-bridge.ts +5 -5
- package/src/app/index.tsx +7 -1
- package/src/features/channels/components/config/channel-form.tsx +3 -3
- 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-chat-page-data.ts +7 -6
- 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.test.ts +22 -8
- 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/chat-session-preference-governance.utils.test.tsx +114 -0
- package/src/features/chat/utils/chat-session-preference-governance.utils.ts +30 -36
- 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 +13 -11
- package/src/shared/lib/api/ncp-session.test.ts +17 -18
- package/src/shared/lib/api/ncp-session.types.ts +92 -0
- package/src/shared/lib/api/raw-client.utils.ts +3 -126
- package/src/shared/lib/api/services/agents.service.ts +18 -0
- package/src/shared/lib/api/services/channel-auth.service.ts +21 -0
- package/src/shared/lib/api/{client.ts → services/client.service.ts} +45 -1
- package/src/shared/lib/api/services/config.service.ts +171 -0
- package/src/shared/lib/api/services/marketplace.service.ts +66 -0
- package/src/shared/lib/api/services/mcp-marketplace.service.ts +70 -0
- package/src/shared/lib/api/services/ncp-attachments.service.ts +14 -0
- package/src/shared/lib/api/services/ncp-session.service.ts +39 -0
- package/src/shared/lib/api/services/remote.service.ts +50 -0
- package/src/shared/lib/api/services/runtime-control.service.ts +18 -0
- package/src/shared/lib/api/services/runtime-update.service.ts +26 -0
- package/src/shared/lib/api/services/server-path.service.ts +16 -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/index.ts +1 -0
- package/src/shared/lib/transport/local-transport.service.ts +24 -4
- package/src/shared/lib/transport/remote-transport.service.ts +2 -2
- package/src/shared/lib/transport/request-raw-api-response.utils.ts +133 -0
- package/src/shared/lib/transport/transport.types.ts +8 -2
- 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/chunk-JZWAC4HX-24FLdHl7.js +0 -3
- 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/src/shared/lib/api/agents.ts +0 -34
- package/src/shared/lib/api/channel-auth.ts +0 -35
- package/src/shared/lib/api/config.ts +0 -362
- package/src/shared/lib/api/marketplace.ts +0 -156
- package/src/shared/lib/api/mcp-marketplace.ts +0 -138
- package/src/shared/lib/api/ncp-attachments.ts +0 -41
- package/src/shared/lib/api/ncp-session.ts +0 -78
- package/src/shared/lib/api/remote.ts +0 -86
- package/src/shared/lib/api/runtime-control.ts +0 -34
- package/src/shared/lib/api/server-path.ts +0 -46
- /package/dist/assets/{config-hints-CPNzbMEp.js → config-hints-MogHYQ8G.js} +0 -0
|
@@ -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
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
export * from './agents';
|
|
1
|
+
export * from './services/agents.service';
|
|
2
2
|
export * from './api-base';
|
|
3
3
|
export * from './auth.types';
|
|
4
|
-
export * from './channel-auth';
|
|
4
|
+
export * from './services/channel-auth.service';
|
|
5
5
|
export * from './channel-auth.types';
|
|
6
6
|
export * from './chat-session-type.types';
|
|
7
|
-
export * from './client';
|
|
8
|
-
export * from './config';
|
|
9
|
-
export * from './marketplace';
|
|
10
|
-
export * from './mcp-marketplace';
|
|
11
|
-
export * from './ncp-attachments';
|
|
12
|
-
export * from './ncp-session';
|
|
7
|
+
export * from './services/client.service';
|
|
8
|
+
export * from './services/config.service';
|
|
9
|
+
export * from './services/marketplace.service';
|
|
10
|
+
export * from './services/mcp-marketplace.service';
|
|
11
|
+
export * from './services/ncp-attachments.service';
|
|
12
|
+
export * from './services/ncp-session.service';
|
|
13
|
+
export * from './ncp-session.types';
|
|
13
14
|
export * from './ncp-session-query-cache';
|
|
14
15
|
export * from './raw-client.utils';
|
|
15
|
-
export * from './remote';
|
|
16
|
+
export * from './services/remote.service';
|
|
16
17
|
export * from './remote.types';
|
|
17
|
-
export * from './runtime-control';
|
|
18
|
+
export * from './services/runtime-control.service';
|
|
18
19
|
export * from './runtime-control.types';
|
|
19
|
-
export * from './
|
|
20
|
+
export * from './services/runtime-update.service';
|
|
21
|
+
export * from './services/server-path.service';
|
|
20
22
|
export * from './types';
|
|
@@ -1,37 +1,36 @@
|
|
|
1
|
-
import { fetchNcpSessionSkills } from './ncp-session';
|
|
2
|
-
import {
|
|
1
|
+
import { fetchNcpSessionSkills } from './services/ncp-session.service';
|
|
2
|
+
import { nextclawClient } from './services/client.service';
|
|
3
3
|
|
|
4
|
-
vi.mock('./client', () => ({
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
vi.mock('./services/client.service', () => ({
|
|
5
|
+
nextclawClient: {
|
|
6
|
+
sessions: {
|
|
7
|
+
listSkills: vi.fn()
|
|
8
|
+
}
|
|
7
9
|
}
|
|
8
10
|
}));
|
|
9
11
|
|
|
10
12
|
describe('api/ncp-session', () => {
|
|
11
13
|
beforeEach(() => {
|
|
12
|
-
vi.mocked(
|
|
13
|
-
vi.mocked(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
refs: [],
|
|
19
|
-
records: []
|
|
20
|
-
}
|
|
14
|
+
vi.mocked(nextclawClient.sessions.listSkills).mockReset();
|
|
15
|
+
vi.mocked(nextclawClient.sessions.listSkills).mockResolvedValue({
|
|
16
|
+
sessionId: 'session-1',
|
|
17
|
+
total: 0,
|
|
18
|
+
refs: [],
|
|
19
|
+
records: []
|
|
21
20
|
});
|
|
22
21
|
});
|
|
23
22
|
|
|
24
23
|
it('does not send an empty projectRoot query when no override is provided', async () => {
|
|
25
24
|
await fetchNcpSessionSkills('session-1', { projectRoot: null });
|
|
26
25
|
|
|
27
|
-
expect(
|
|
26
|
+
expect(nextclawClient.sessions.listSkills).toHaveBeenCalledWith('session-1', { projectRoot: null });
|
|
28
27
|
});
|
|
29
28
|
|
|
30
29
|
it('sends projectRoot only when the override is non-empty', async () => {
|
|
31
30
|
await fetchNcpSessionSkills('session-1', { projectRoot: ' /tmp/project-alpha ' });
|
|
32
31
|
|
|
33
|
-
expect(
|
|
34
|
-
'/
|
|
35
|
-
);
|
|
32
|
+
expect(nextclawClient.sessions.listSkills).toHaveBeenCalledWith('session-1', {
|
|
33
|
+
projectRoot: ' /tmp/project-alpha '
|
|
34
|
+
});
|
|
36
35
|
});
|
|
37
36
|
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { NcpMessage, NcpSessionStatus, NcpSessionSummary } from '@nextclaw/ncp';
|
|
2
|
+
import type { ThinkingLevel } from './types';
|
|
3
|
+
|
|
4
|
+
export type SessionTypeIconView = {
|
|
5
|
+
kind: "image";
|
|
6
|
+
src: string;
|
|
7
|
+
alt?: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type RuntimeEntryView = {
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
label?: string;
|
|
13
|
+
icon?: SessionTypeIconView | null;
|
|
14
|
+
type: string;
|
|
15
|
+
config?: Record<string, unknown>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type SessionContextWindowView = {
|
|
19
|
+
usedContextTokens: number;
|
|
20
|
+
totalContextTokens: number;
|
|
21
|
+
prunedUsedContextTokens: number;
|
|
22
|
+
availableContextTokens: number;
|
|
23
|
+
droppedHistoryCount: number;
|
|
24
|
+
truncatedToolResultCount: number;
|
|
25
|
+
truncatedSystemPrompt: boolean;
|
|
26
|
+
truncatedUserMessage: boolean;
|
|
27
|
+
compacted: boolean;
|
|
28
|
+
checkpointId?: string;
|
|
29
|
+
compactedMessageCount: number;
|
|
30
|
+
compactedUsedContextTokens?: number;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type SessionEntryView = {
|
|
35
|
+
key: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt: string;
|
|
38
|
+
lastMessageAt?: string;
|
|
39
|
+
readAt?: string;
|
|
40
|
+
agentId?: string;
|
|
41
|
+
label?: string;
|
|
42
|
+
channel?: string;
|
|
43
|
+
type?: string;
|
|
44
|
+
preferredModel?: string;
|
|
45
|
+
preferredThinking?: ThinkingLevel | null;
|
|
46
|
+
projectRoot?: string | null;
|
|
47
|
+
projectName?: string | null;
|
|
48
|
+
sessionType: string;
|
|
49
|
+
sessionTypeMutable: boolean;
|
|
50
|
+
isChildSession?: boolean;
|
|
51
|
+
isPromotedChildSession?: boolean;
|
|
52
|
+
parentSessionId?: string | null;
|
|
53
|
+
spawnedByRequestId?: string | null;
|
|
54
|
+
contextWindow?: SessionContextWindowView | null;
|
|
55
|
+
messageCount: number;
|
|
56
|
+
lastRole?: string;
|
|
57
|
+
lastTimestamp?: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type SessionMessageView = {
|
|
61
|
+
role: string;
|
|
62
|
+
content: unknown;
|
|
63
|
+
timestamp: string;
|
|
64
|
+
name?: string;
|
|
65
|
+
tool_call_id?: string;
|
|
66
|
+
tool_calls?: Array<Record<string, unknown>>;
|
|
67
|
+
reasoning_content?: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type SessionEventView = {
|
|
71
|
+
seq: number;
|
|
72
|
+
type: string;
|
|
73
|
+
timestamp: string;
|
|
74
|
+
message?: SessionMessageView;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type NcpSessionSummaryView = NcpSessionSummary;
|
|
78
|
+
|
|
79
|
+
export type NcpSessionsListView = {
|
|
80
|
+
sessions: NcpSessionSummaryView[];
|
|
81
|
+
total: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type NcpMessageView = NcpMessage;
|
|
85
|
+
|
|
86
|
+
export type NcpSessionMessagesView = {
|
|
87
|
+
sessionId: string;
|
|
88
|
+
status: NcpSessionStatus;
|
|
89
|
+
messages: NcpMessageView[];
|
|
90
|
+
contextWindow?: SessionContextWindowView | null;
|
|
91
|
+
total: number;
|
|
92
|
+
};
|
|
@@ -1,132 +1,9 @@
|
|
|
1
1
|
import { API_BASE } from './api-base';
|
|
2
|
-
import
|
|
3
|
-
import { systemStatusManager } from '@/features/system-status';
|
|
4
|
-
|
|
5
|
-
function compactSnippet(text: string) {
|
|
6
|
-
return text.replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function inferNonJsonHint(endpoint: string, status: number): string | undefined {
|
|
10
|
-
if (
|
|
11
|
-
status === 404 &&
|
|
12
|
-
endpoint.startsWith('/api/config/providers/') &&
|
|
13
|
-
endpoint.endsWith('/test')
|
|
14
|
-
) {
|
|
15
|
-
return 'Provider test endpoint is missing. This usually means nextclaw runtime version is outdated.';
|
|
16
|
-
}
|
|
17
|
-
if (status === 401 || status === 403) {
|
|
18
|
-
return 'Authentication failed. Check apiKey and custom headers.';
|
|
19
|
-
}
|
|
20
|
-
if (status === 429) {
|
|
21
|
-
return 'Rate limited by upstream provider. Retry later or switch model/provider.';
|
|
22
|
-
}
|
|
23
|
-
if (status >= 500) {
|
|
24
|
-
return 'Upstream service error. Retry later and inspect server logs if it persists.';
|
|
25
|
-
}
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function formatUnknownFetchError(error: unknown): {
|
|
30
|
-
summary: string;
|
|
31
|
-
details: Record<string, unknown>;
|
|
32
|
-
} {
|
|
33
|
-
if (error instanceof Error) {
|
|
34
|
-
const name = error.name?.trim() || 'Error';
|
|
35
|
-
const message = error.message?.trim() || 'Unknown error';
|
|
36
|
-
return {
|
|
37
|
-
summary: `${name}: ${message}`,
|
|
38
|
-
details: {
|
|
39
|
-
errorName: name,
|
|
40
|
-
errorMessage: message,
|
|
41
|
-
...(error.stack?.trim() ? { errorStack: error.stack.trim() } : {})
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
summary: String(error ?? 'Unknown error'),
|
|
47
|
-
details: {
|
|
48
|
-
errorName: 'NonError',
|
|
49
|
-
errorMessage: String(error ?? 'Unknown error')
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
2
|
+
import { requestRawApiResponse as requestRawTransportApiResponse } from '@/shared/lib/transport';
|
|
53
3
|
|
|
54
4
|
export async function requestRawApiResponse<T>(
|
|
55
5
|
endpoint: string,
|
|
56
6
|
options: RequestInit = {}
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
const method = (options.method || 'GET').toUpperCase();
|
|
60
|
-
|
|
61
|
-
let response: Response;
|
|
62
|
-
try {
|
|
63
|
-
response = await fetch(url, {
|
|
64
|
-
credentials: 'include',
|
|
65
|
-
headers: {
|
|
66
|
-
'Content-Type': 'application/json',
|
|
67
|
-
...options.headers
|
|
68
|
-
},
|
|
69
|
-
...options
|
|
70
|
-
});
|
|
71
|
-
} catch (error) {
|
|
72
|
-
const formatted = formatUnknownFetchError(error);
|
|
73
|
-
systemStatusManager.reportTransportFailure(formatted.summary);
|
|
74
|
-
return {
|
|
75
|
-
ok: false,
|
|
76
|
-
error: {
|
|
77
|
-
code: 'NETWORK_ERROR',
|
|
78
|
-
message: `Fetch failed on ${method} ${endpoint} | ${formatted.summary}`,
|
|
79
|
-
details: {
|
|
80
|
-
method,
|
|
81
|
-
endpoint,
|
|
82
|
-
url,
|
|
83
|
-
...formatted.details
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const text = await response.text();
|
|
90
|
-
let data: ApiResponse<T> | null = null;
|
|
91
|
-
if (text) {
|
|
92
|
-
try {
|
|
93
|
-
data = JSON.parse(text) as ApiResponse<T>;
|
|
94
|
-
} catch {
|
|
95
|
-
// fall through to build a synthetic error response
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!data) {
|
|
100
|
-
const snippet = text ? compactSnippet(text) : '';
|
|
101
|
-
const hint = inferNonJsonHint(endpoint, response.status);
|
|
102
|
-
const parts = [`Non-JSON response (${response.status} ${response.statusText}) on ${method} ${endpoint}`];
|
|
103
|
-
if (snippet) {
|
|
104
|
-
parts.push(`body=${snippet}`);
|
|
105
|
-
}
|
|
106
|
-
if (hint) {
|
|
107
|
-
parts.push(`hint=${hint}`);
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
ok: false,
|
|
111
|
-
error: {
|
|
112
|
-
code: 'INVALID_RESPONSE',
|
|
113
|
-
message: parts.join(' | '),
|
|
114
|
-
details: {
|
|
115
|
-
status: response.status,
|
|
116
|
-
statusText: response.statusText,
|
|
117
|
-
method,
|
|
118
|
-
endpoint,
|
|
119
|
-
url,
|
|
120
|
-
bodySnippet: snippet || undefined,
|
|
121
|
-
hint
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!response.ok) {
|
|
128
|
-
return data as ApiResponse<T>;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return data as ApiResponse<T>;
|
|
7
|
+
) {
|
|
8
|
+
return await requestRawTransportApiResponse<T>(API_BASE, endpoint, options);
|
|
132
9
|
}
|