@nextclaw/ui 0.12.24 → 0.12.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -29
- package/dist/assets/api-DGD9_Bg4.js +15 -0
- package/dist/assets/app-manager-provider-oYdeYPSv.js +1 -0
- package/dist/assets/{book-open-DDlN5MvX.js → book-open-BcnAiKde.js} +1 -1
- package/dist/assets/channels-list-page-HgLgrEg4.js +8 -0
- package/dist/assets/chat-page-DAKMFDrS.js +1 -0
- package/dist/assets/config-split-page-CcrEUtwu.js +1 -0
- package/dist/assets/cpu-DPPwMzoC.js +3 -0
- package/dist/assets/{createLucideIcon-BLMK3QUd.js → createLucideIcon-DzY6wN61.js} +1 -1
- package/dist/assets/desktop-DVUbOWbR.js +3 -0
- package/dist/assets/desktop-update-config-CP8dFYXK.js +1 -0
- package/dist/assets/{dialog-C3D7Be0p.js → dialog-BKo0RItd.js} +1 -1
- package/dist/assets/{dist-CPlbUgwU.js → dist-CFiwgaLs.js} +1 -1
- package/dist/assets/doc-browser-CAhfnm0D.js +1 -0
- package/dist/assets/{doc-browser-context-BJuMaI3o.js → doc-browser-context-FukQHvyo.js} +1 -1
- package/dist/assets/doc-browser-p9DDNPWB.js +1 -0
- package/dist/assets/doc-browser-rZIQIjuw.js +1 -0
- package/dist/assets/download-CMM8po31.js +1 -0
- package/dist/assets/{es2015-xqN1slyW.js → es2015-BhznEEyJ.js} +1 -1
- package/dist/assets/{external-link-DwfSfTLB.js → external-link-CpEvG65F.js} +1 -1
- package/dist/assets/i18n-D1144VAA.js +1 -0
- package/dist/assets/index-Cuwst6cc.js +100 -0
- package/dist/assets/index-dlcqieQ0.css +1 -0
- package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
- package/dist/assets/marketplace-page-BeFbwxR-.js +105 -0
- package/dist/assets/marketplace-page-CR4xq-TM.js +1 -0
- package/dist/assets/mcp-marketplace-page-DlRrSCj3.js +1 -0
- package/dist/assets/mcp-marketplace-page-DwnaLNTx.js +40 -0
- package/dist/assets/model-config-L2l6YAlQ.js +1 -0
- package/dist/assets/{notice-card-BFDbKQDA.js → notice-card-Dr6xCwva.js} +1 -1
- package/dist/assets/play-AqrNslHI.js +1 -0
- package/dist/assets/plus-B-YHtTNC.js +1 -0
- package/dist/assets/{popover-B86Dbfhf.js → popover-BDFNiLlg.js} +1 -1
- package/dist/assets/provider-scoped-model-input-BMTp4BEH.js +1 -0
- package/dist/assets/providers-list-DYAEunOp.js +1 -0
- package/dist/assets/refresh-cw-CrbD8EkT.js +1 -0
- package/dist/assets/remote-Dr3jcfWP.js +1 -0
- package/dist/assets/{rotate-cw-BZ2JObNs.js → rotate-cw-BN9yjccP.js} +1 -1
- package/dist/assets/runtime-config-page-BdeU8PEK.js +1 -0
- package/dist/assets/{save-euRxl8pI.js → save-CO_4qf6b.js} +1 -1
- package/dist/assets/{search-CLd7m0M7.js → search-CRtQwr-h.js} +1 -1
- package/dist/assets/search-config-CQUhd5RU.js +1 -0
- package/dist/assets/secrets-config-D-NWlW9q.js +3 -0
- package/dist/assets/{select-CJ0wbo3D.js → select-BUTwE_lC.js} +1 -1
- package/dist/assets/{setting-row-D1Yygqp7.js → setting-row-BavcnXw1.js} +1 -1
- package/dist/assets/settings-MWL2SMyk.js +1 -0
- package/dist/assets/{sparkles-DVfeSVJQ.js → sparkles-BmgOD4nY.js} +1 -1
- package/dist/assets/{status-dot-ChvPCib9.js → status-dot-l3kPFdq_.js} +1 -1
- package/dist/assets/{tabs-custom-Hia_ong0.js → tabs-custom-D48zdZoc.js} +1 -1
- package/dist/assets/{tag-chip-FrkmkT8r.js → tag-chip-Dm2Lqnpu.js} +1 -1
- package/dist/assets/use-config-Cyv5IuSt.js +1 -0
- package/dist/assets/use-infinite-scroll-loader-CFVdPpNv.js +1 -0
- package/dist/assets/x-BeyYA_h6.js +1 -0
- package/dist/index.html +29 -40
- package/package.json +9 -9
- package/src/app/components/layout/sidebar.layout.test.tsx +2 -4
- package/src/app/components/theme-provider.tsx +1 -0
- package/src/app/configs/app-navigation.config.ts +0 -6
- package/src/app/index.tsx +4 -7
- package/src/features/agents/components/agents-page.test.tsx +25 -15
- package/src/features/agents/components/agents-page.tsx +133 -172
- package/src/features/channels/components/config/channel-form.test.tsx +1 -0
- package/src/features/channels/components/config/channel-form.tsx +4 -3
- package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +38 -1
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +137 -40
- package/src/features/channels/index.ts +1 -1
- package/src/features/channels/utils/channel-form-fields.utils.test.ts +26 -0
- package/src/features/channels/utils/channel-form-fields.utils.ts +32 -18
- package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +23 -4
- package/src/features/chat/components/chat-session-workspace-panel.tsx +53 -35
- package/src/features/chat/components/chat-sidebar-session-item.tsx +16 -12
- package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +74 -0
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +8 -2
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +262 -114
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +210 -174
- package/src/features/chat/components/conversation/chat-input-bar.container.tsx +11 -1
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.test.tsx +24 -0
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +27 -6
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +174 -0
- package/src/features/chat/components/layout/chat-sidebar.test.tsx +45 -8
- package/src/features/chat/components/layout/chat-sidebar.tsx +29 -46
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +4 -0
- package/src/features/chat/components/workspace/session-cron-job-content.tsx +103 -0
- package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +153 -80
- package/src/features/chat/hooks/use-ncp-chat-page-data.test.tsx +70 -0
- package/src/features/chat/hooks/use-ncp-chat-page-data.ts +1 -1
- package/src/features/chat/hooks/use-ncp-child-session-tabs-view.ts +2 -8
- package/src/features/chat/hooks/use-ncp-session-list-view.ts +1 -2
- package/src/features/chat/managers/chat-session-list.manager.test.ts +7 -9
- package/src/features/chat/managers/chat-session-list.manager.ts +5 -10
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +20 -2
- package/src/features/chat/managers/ncp-chat-input.manager.ts +18 -0
- package/src/features/chat/managers/ncp-chat-presenter.manager.ts +7 -0
- package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +52 -1
- package/src/features/chat/managers/ncp-chat-thread.manager.ts +21 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +9 -5
- package/src/features/chat/stores/chat-input.store.ts +3 -1
- package/src/features/chat/stores/chat-session-list.store.ts +0 -2
- package/src/features/chat/stores/chat-thread.store.ts +4 -0
- package/src/features/chat/utils/chat-session-display.utils.test.ts +83 -1
- package/src/features/chat/utils/chat-session-display.utils.ts +73 -0
- package/src/features/chat/utils/ncp-chat-input-availability.utils.test.ts +1 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +22 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +32 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +235 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config.ts +162 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +355 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +118 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +201 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc.test.ts +40 -0
- package/src/features/marketplace/components/marketplace-catalog-grid.tsx +114 -0
- package/src/features/marketplace/components/marketplace-detail-doc.ts +73 -24
- package/src/features/marketplace/components/marketplace-item-icon.tsx +45 -0
- package/src/features/marketplace/components/marketplace-list-card.tsx +177 -93
- package/src/features/marketplace/components/marketplace-page-detail.test.tsx +9 -2
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/marketplace/components/marketplace-page.test.tsx +25 -6
- package/src/features/marketplace/components/marketplace-page.tsx +154 -132
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +97 -0
- package/src/features/marketplace/hooks/use-marketplace.ts +59 -3
- package/src/features/system-status/components/config/runtime-agent-list-card.tsx +4 -8
- package/src/features/system-status/components/config/runtime-binding-list-card.tsx +5 -7
- package/src/features/system-status/components/config/runtime-config-editor.tsx +1 -19
- package/src/features/system-status/components/config/runtime-entry-list-card.tsx +10 -11
- package/src/features/system-status/components/config/runtime-settings-card.tsx +15 -23
- package/src/features/system-status/components/runtime-control-card.test.tsx +8 -6
- package/src/features/system-status/components/runtime-control-card.tsx +7 -6
- package/src/features/system-status/pages/runtime-config-page.test.tsx +19 -9
- package/src/features/system-status/pages/runtime-config-page.tsx +2 -3
- package/src/features/system-status/utils/runtime-config-agent.utils.ts +4 -4
- package/src/features/system-status/utils/system-status.utils.ts +31 -6
- package/src/index.css +8 -0
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +68 -0
- package/src/platforms/desktop/components/desktop-app-shell.tsx +46 -18
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +30 -0
- package/src/platforms/desktop/index.ts +6 -0
- package/src/platforms/desktop/types/desktop-update.types.ts +3 -0
- package/src/platforms/desktop/utils/desktop-host.utils.ts +56 -0
- package/src/shared/components/common/brand-header.tsx +36 -16
- package/src/shared/components/config/provider-form-support.ts +2 -22
- package/src/shared/components/cron-config.tsx +12 -58
- package/src/shared/components/doc-browser/doc-browser.tsx +4 -4
- package/src/shared/components/ui/select.tsx +19 -7
- package/src/shared/lib/api/channel-auth.types.ts +1 -0
- package/src/shared/lib/api/ncp-session.types.ts +9 -0
- package/src/shared/lib/api/types.ts +12 -1
- package/src/shared/lib/api/utils/marketplace.utils.ts +7 -1
- package/src/shared/lib/cron/cron-job-view.utils.ts +59 -0
- package/src/shared/lib/cron/index.ts +1 -0
- package/src/shared/lib/i18n/{channel-auth.ts → channel-auth.constants.ts} +31 -0
- package/src/shared/lib/i18n/chat-labels.utils.ts +3 -2
- package/src/shared/lib/i18n/index.ts +20 -59
- package/src/shared/lib/i18n/{runtime-control.ts → runtime-control-labels.utils.ts} +30 -1
- package/src/shared/lib/provider-models/index.test.ts +39 -0
- package/src/shared/lib/provider-models/index.ts +1 -3
- package/src/shared/lib/ui-document-title/index.ts +0 -1
- package/tsconfig.json +1 -0
- package/vite.config.ts +1 -1
- package/vitest.config.ts +1 -1
- package/dist/assets/api-D2xRKmZd.js +0 -15
- package/dist/assets/app-manager-provider-CNaZboG4.js +0 -1
- package/dist/assets/app-navigation.config-Ihhrrt--.js +0 -1
- package/dist/assets/channels-list-page-p26lgxLk.js +0 -8
- package/dist/assets/chat-Dkh2qtuz.js +0 -61
- package/dist/assets/chat-page-DoTmE2wx.js +0 -1
- package/dist/assets/chunk-JZWAC4HX-Kydj4yEz.js +0 -3
- package/dist/assets/config-split-page-DIOCjj2Q.js +0 -1
- package/dist/assets/desktop-update-config-DlpzDfKM.js +0 -1
- package/dist/assets/doc-browser-C8FM5fC0.js +0 -1
- package/dist/assets/doc-browser-RJUOL_GO.js +0 -1
- package/dist/assets/doc-browser-p82AdNO-.js +0 -1
- package/dist/assets/folder-CeJKPx5P.js +0 -1
- package/dist/assets/hash-BqxRTZW5.js +0 -1
- package/dist/assets/i18n-DnTGDIRw.js +0 -1
- package/dist/assets/index-D8MKmXtO.css +0 -1
- package/dist/assets/index-pBvbJ5Mt.js +0 -2
- package/dist/assets/loader-circle-fd-vQKtW.js +0 -1
- package/dist/assets/logo-badge-KAe-7d8c.js +0 -1
- package/dist/assets/logos-C4sYP1Vl.js +0 -1
- package/dist/assets/marketplace-page-Cql0kDi-.js +0 -1
- package/dist/assets/marketplace-page-m4P5g_Ht.js +0 -49
- package/dist/assets/mcp-marketplace-page-9WVKl1m1.js +0 -1
- package/dist/assets/mcp-marketplace-page-ByzBQZcx.js +0 -40
- package/dist/assets/message-square-z_osm9c0.js +0 -1
- package/dist/assets/model-config-Dbr_0APb.js +0 -1
- package/dist/assets/play-Dv6Nr1Ew.js +0 -1
- package/dist/assets/plus-D8eKFY7h.js +0 -1
- package/dist/assets/provider-scoped-model-input-DFm6N2f7.js +0 -1
- package/dist/assets/providers-list-BJcLOjun.js +0 -1
- package/dist/assets/refresh-ccw-ByVwmnN_.js +0 -1
- package/dist/assets/refresh-cw-PcqoYB3K.js +0 -1
- package/dist/assets/remote-BOxo9iwd.js +0 -1
- package/dist/assets/runtime-config-page-CjLhnbSl.js +0 -1
- package/dist/assets/search-config-J4Htco-P.js +0 -1
- package/dist/assets/secrets-config-CUdERjco.js +0 -3
- package/dist/assets/sessions-config-page-DpK991fs.js +0 -2
- package/dist/assets/settings-drbWqzA4.js +0 -1
- package/dist/assets/skeleton-BK1SOSRA.js +0 -1
- package/dist/assets/theme-provider-0hxjiPc_.js +0 -2
- package/dist/assets/tooltip-Cj4yA0gH.js +0 -1
- package/dist/assets/trash-2-CBsHCfqq.js +0 -1
- package/dist/assets/use-config-38Ur-89i.js +0 -1
- package/dist/assets/use-confirm-dialog-DPQThaeU.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-5Gf1xQi7.js +0 -1
- package/dist/assets/use-viewport-layout-D1XzKeip.js +0 -1
- package/dist/assets/x-CM-XDMpk.js +0 -1
- package/src/features/chat/components/config/sessions-config-detail-pane.tsx +0 -244
- package/src/features/chat/pages/sessions-config-page.test.tsx +0 -152
- package/src/features/chat/pages/sessions-config-page.tsx +0 -192
- /package/dist/assets/{config-hints-MogHYQ8G.js → config-hints-BNfpOL4J.js} +0 -0
|
@@ -3,6 +3,7 @@ import { appQueryClient } from '@/app-query-client';
|
|
|
3
3
|
import { NcpChatThreadManager } from '@/features/chat/managers/ncp-chat-thread.manager';
|
|
4
4
|
import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
|
|
5
5
|
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
6
|
+
import type * as SharedApi from '@/shared/lib/api';
|
|
6
7
|
|
|
7
8
|
const { deleteNcpSessionMock, deleteSummaryMock } = vi.hoisted(() => ({
|
|
8
9
|
deleteNcpSessionMock: vi.fn(async () => ({ deleted: true, sessionId: 'parent-session-1' })),
|
|
@@ -10,7 +11,7 @@ const { deleteNcpSessionMock, deleteSummaryMock } = vi.hoisted(() => ({
|
|
|
10
11
|
}));
|
|
11
12
|
|
|
12
13
|
vi.mock('@/shared/lib/api', async (importOriginal) => {
|
|
13
|
-
const actual = await importOriginal<typeof
|
|
14
|
+
const actual = await importOriginal<typeof SharedApi>();
|
|
14
15
|
return {
|
|
15
16
|
...actual,
|
|
16
17
|
deleteNcpSession: deleteNcpSessionMock,
|
|
@@ -97,6 +98,56 @@ describe('NcpChatThreadManager', () => {
|
|
|
97
98
|
expect(uiManager.goToSession).toHaveBeenCalledWith('parent-session-1');
|
|
98
99
|
});
|
|
99
100
|
|
|
101
|
+
it('opens the session cron panel without changing route when already selected', () => {
|
|
102
|
+
const uiManager = {
|
|
103
|
+
goToSession: vi.fn(),
|
|
104
|
+
goToChatRoot: vi.fn(),
|
|
105
|
+
goToProviders: vi.fn(),
|
|
106
|
+
confirm: vi.fn(),
|
|
107
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
|
|
108
|
+
|
|
109
|
+
const manager = new NcpChatThreadManager(
|
|
110
|
+
uiManager,
|
|
111
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
112
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
manager.openSessionCronPanel('parent-session-1');
|
|
116
|
+
|
|
117
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
118
|
+
workspacePanelParentKey: 'parent-session-1',
|
|
119
|
+
activeWorkspacePanelKind: 'cron',
|
|
120
|
+
activeChildSessionKey: null,
|
|
121
|
+
activeWorkspaceFileKey: null,
|
|
122
|
+
});
|
|
123
|
+
expect(uiManager.goToSession).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('routes to the session before opening its cron panel when needed', () => {
|
|
127
|
+
useChatSessionListStore.setState({
|
|
128
|
+
snapshot: {
|
|
129
|
+
...useChatSessionListStore.getState().snapshot,
|
|
130
|
+
selectedSessionKey: 'another-session',
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
const uiManager = {
|
|
134
|
+
goToSession: vi.fn(),
|
|
135
|
+
goToChatRoot: vi.fn(),
|
|
136
|
+
goToProviders: vi.fn(),
|
|
137
|
+
confirm: vi.fn(),
|
|
138
|
+
} as unknown as ConstructorParameters<typeof NcpChatThreadManager>[0];
|
|
139
|
+
|
|
140
|
+
const manager = new NcpChatThreadManager(
|
|
141
|
+
uiManager,
|
|
142
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[1],
|
|
143
|
+
{} as ConstructorParameters<typeof NcpChatThreadManager>[2],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
manager.openSessionCronPanel('parent-session-1');
|
|
147
|
+
|
|
148
|
+
expect(uiManager.goToSession).toHaveBeenCalledWith('parent-session-1');
|
|
149
|
+
});
|
|
150
|
+
|
|
100
151
|
it('keeps preview and diff for the same file as separate workspace tabs', () => {
|
|
101
152
|
const uiManager = {
|
|
102
153
|
goToSession: vi.fn(),
|
|
@@ -62,6 +62,7 @@ export class NcpChatThreadManager {
|
|
|
62
62
|
parentSessionKey: null,
|
|
63
63
|
parentSessionLabel: null,
|
|
64
64
|
workspacePanelParentKey: null,
|
|
65
|
+
activeWorkspacePanelKind: null,
|
|
65
66
|
childSessionTabs: [],
|
|
66
67
|
activeChildSessionKey: null,
|
|
67
68
|
workspaceFileTabs: [],
|
|
@@ -156,6 +157,7 @@ export class NcpChatThreadManager {
|
|
|
156
157
|
const activeChildSessionKey = params.activeChildSessionKey?.trim() || null;
|
|
157
158
|
useChatThreadStore.getState().setSnapshot({
|
|
158
159
|
workspacePanelParentKey: parentSessionKey,
|
|
160
|
+
activeWorkspacePanelKind: 'child-session',
|
|
159
161
|
activeChildSessionKey,
|
|
160
162
|
activeWorkspaceFileKey: null,
|
|
161
163
|
});
|
|
@@ -170,6 +172,7 @@ export class NcpChatThreadManager {
|
|
|
170
172
|
}
|
|
171
173
|
useChatThreadStore.getState().setSnapshot({
|
|
172
174
|
workspacePanelParentKey: parentSessionKey,
|
|
175
|
+
activeWorkspacePanelKind: 'file',
|
|
173
176
|
workspaceFileTabs: this.upsertWorkspaceFileTab(nextTab),
|
|
174
177
|
activeWorkspaceFileKey: nextTab.key,
|
|
175
178
|
activeChildSessionKey: null,
|
|
@@ -196,6 +199,7 @@ export class NcpChatThreadManager {
|
|
|
196
199
|
}
|
|
197
200
|
useChatThreadStore.getState().setSnapshot({
|
|
198
201
|
workspacePanelParentKey: null,
|
|
202
|
+
activeWorkspacePanelKind: null,
|
|
199
203
|
activeChildSessionKey: null,
|
|
200
204
|
activeWorkspaceFileKey: null,
|
|
201
205
|
});
|
|
@@ -214,6 +218,7 @@ export class NcpChatThreadManager {
|
|
|
214
218
|
useChatThreadStore.getState().setSnapshot({
|
|
215
219
|
activeChildSessionKey: normalizedSessionKey,
|
|
216
220
|
activeWorkspaceFileKey: null,
|
|
221
|
+
activeWorkspacePanelKind: 'child-session',
|
|
217
222
|
});
|
|
218
223
|
};
|
|
219
224
|
|
|
@@ -229,6 +234,7 @@ export class NcpChatThreadManager {
|
|
|
229
234
|
useChatThreadStore.getState().setSnapshot({
|
|
230
235
|
activeWorkspaceFileKey: normalizedFileKey,
|
|
231
236
|
activeChildSessionKey: null,
|
|
237
|
+
activeWorkspacePanelKind: 'file',
|
|
232
238
|
});
|
|
233
239
|
};
|
|
234
240
|
|
|
@@ -254,11 +260,26 @@ export class NcpChatThreadManager {
|
|
|
254
260
|
closeWorkspacePanel = () => {
|
|
255
261
|
useChatThreadStore.getState().setSnapshot({
|
|
256
262
|
workspacePanelParentKey: null,
|
|
263
|
+
activeWorkspacePanelKind: null,
|
|
257
264
|
activeChildSessionKey: null,
|
|
258
265
|
activeWorkspaceFileKey: null,
|
|
259
266
|
});
|
|
260
267
|
};
|
|
261
268
|
|
|
269
|
+
openSessionCronPanel = (sessionKey: string) => {
|
|
270
|
+
const parentSessionKey = sessionKey.trim();
|
|
271
|
+
if (!parentSessionKey) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
useChatThreadStore.getState().setSnapshot({
|
|
275
|
+
workspacePanelParentKey: parentSessionKey,
|
|
276
|
+
activeWorkspacePanelKind: 'cron',
|
|
277
|
+
activeChildSessionKey: null,
|
|
278
|
+
activeWorkspaceFileKey: null,
|
|
279
|
+
});
|
|
280
|
+
this.ensureWorkspaceParentRoute(parentSessionKey);
|
|
281
|
+
};
|
|
282
|
+
|
|
262
283
|
closeChildSessionDetail = () => {
|
|
263
284
|
this.closeWorkspacePanel();
|
|
264
285
|
};
|
|
@@ -219,15 +219,16 @@ function useNcpChatPageState(presenter: NcpChatPresenter) {
|
|
|
219
219
|
selectedSessionType,
|
|
220
220
|
sessionTypeOptions,
|
|
221
221
|
});
|
|
222
|
+
const currentSessionRunning = agent.isRunning || selectedSession?.status === "running";
|
|
222
223
|
return {
|
|
223
224
|
...baseState,
|
|
224
225
|
availableAgents,
|
|
225
226
|
effectiveSessionProjectRoot,
|
|
226
227
|
effectiveSessionProjectName,
|
|
227
|
-
isSending: agent.isSending ||
|
|
228
|
-
isAwaitingAssistantOutput:
|
|
229
|
-
canStopCurrentRun:
|
|
230
|
-
stopDisabledReason:
|
|
228
|
+
isSending: agent.isSending || currentSessionRunning,
|
|
229
|
+
isAwaitingAssistantOutput: currentSessionRunning,
|
|
230
|
+
canStopCurrentRun: currentSessionRunning,
|
|
231
|
+
stopDisabledReason: currentSessionRunning ? null : "__preparing__",
|
|
231
232
|
lastSendError:
|
|
232
233
|
isRuntimeBlocked
|
|
233
234
|
? null
|
|
@@ -280,7 +281,10 @@ function useNcpChatStreamBindings(params: ReturnType<typeof useNcpChatPageState>
|
|
|
280
281
|
return;
|
|
281
282
|
}
|
|
282
283
|
try {
|
|
283
|
-
await agent.send(envelope);
|
|
284
|
+
const handle = await agent.send(envelope);
|
|
285
|
+
if (!payload.sessionKey && handle?.sessionId) {
|
|
286
|
+
presenter.chatSessionListManager.materializeRootSessionRoute(handle.sessionId);
|
|
287
|
+
}
|
|
284
288
|
} catch (error) {
|
|
285
289
|
if (payload.restoreDraftOnError) {
|
|
286
290
|
if (payload.composerNodes && payload.composerNodes.length > 0) {
|
|
@@ -44,6 +44,7 @@ export type ChatInputSnapshot = {
|
|
|
44
44
|
skillRecords: SessionSkillEntryView[];
|
|
45
45
|
isSkillsLoading: boolean;
|
|
46
46
|
selectedSkills: string[];
|
|
47
|
+
composerFocusRequest: { id: number; placement: 'end' } | null;
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
type ChatInputStore = {
|
|
@@ -75,7 +76,8 @@ const initialSnapshot: ChatInputSnapshot = {
|
|
|
75
76
|
sessionTypeUnavailable: false,
|
|
76
77
|
skillRecords: [],
|
|
77
78
|
isSkillsLoading: false,
|
|
78
|
-
selectedSkills: []
|
|
79
|
+
selectedSkills: [],
|
|
80
|
+
composerFocusRequest: null
|
|
79
81
|
};
|
|
80
82
|
|
|
81
83
|
export const useChatInputStore = create<ChatInputStore>((set) => ({
|
|
@@ -3,7 +3,6 @@ import type { SessionRunStatus } from '@/features/chat/types/session-run-status.
|
|
|
3
3
|
export type ChatSessionListMode = 'time-first' | 'project-first';
|
|
4
4
|
export type ChatSessionListSnapshot = {
|
|
5
5
|
selectedSessionKey: string | null;
|
|
6
|
-
draftSessionKey: string | null;
|
|
7
6
|
selectedAgentId: string;
|
|
8
7
|
query: string;
|
|
9
8
|
listMode: ChatSessionListMode;
|
|
@@ -49,7 +48,6 @@ type ChatSessionListStoreSet = Parameters<StateCreator<ChatSessionListStore>>[0]
|
|
|
49
48
|
|
|
50
49
|
const initialSnapshot: ChatSessionListSnapshot = {
|
|
51
50
|
selectedSessionKey: null,
|
|
52
|
-
draftSessionKey: null,
|
|
53
51
|
selectedAgentId: 'main',
|
|
54
52
|
query: '',
|
|
55
53
|
listMode: 'time-first'
|
|
@@ -55,9 +55,11 @@ export type ChatThreadSnapshot = {
|
|
|
55
55
|
messages: readonly NcpMessage[];
|
|
56
56
|
isSending: boolean;
|
|
57
57
|
isAwaitingAssistantOutput: boolean;
|
|
58
|
+
hasSubmittedDraftMessage: boolean;
|
|
58
59
|
parentSessionKey?: string | null;
|
|
59
60
|
parentSessionLabel?: string | null;
|
|
60
61
|
workspacePanelParentKey?: string | null;
|
|
62
|
+
activeWorkspacePanelKind?: "child-session" | "file" | "cron" | null;
|
|
61
63
|
childSessionTabs: ChatChildSessionTab[];
|
|
62
64
|
activeChildSessionKey?: string | null;
|
|
63
65
|
workspaceFileTabs: ChatWorkspaceFileTab[];
|
|
@@ -92,9 +94,11 @@ const initialSnapshot: ChatThreadSnapshot = {
|
|
|
92
94
|
messages: [],
|
|
93
95
|
isSending: false,
|
|
94
96
|
isAwaitingAssistantOutput: false,
|
|
97
|
+
hasSubmittedDraftMessage: false,
|
|
95
98
|
parentSessionKey: null,
|
|
96
99
|
parentSessionLabel: null,
|
|
97
100
|
workspacePanelParentKey: null,
|
|
101
|
+
activeWorkspacePanelKind: null,
|
|
98
102
|
childSessionTabs: [],
|
|
99
103
|
activeChildSessionKey: null,
|
|
100
104
|
workspaceFileTabs: [],
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import type { SessionEntryView } from '@/shared/lib/api';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
formatSessionListTime,
|
|
5
|
+
sessionActivityPreviewText,
|
|
6
|
+
sessionDisplayName,
|
|
7
|
+
sessionMatchesQuery
|
|
8
|
+
} from './chat-session-display.utils';
|
|
4
9
|
|
|
5
10
|
function createSession(overrides: Partial<SessionEntryView> = {}): SessionEntryView {
|
|
6
11
|
return {
|
|
@@ -52,4 +57,81 @@ describe('chat-session-display', () => {
|
|
|
52
57
|
it('treats an empty query as a match', () => {
|
|
53
58
|
expect(sessionMatchesQuery(createSession({ label: 'Anything' }), ' ')).toBe(true);
|
|
54
59
|
});
|
|
60
|
+
|
|
61
|
+
it('shows running activity before the previous reply preview', () => {
|
|
62
|
+
expect(
|
|
63
|
+
sessionActivityPreviewText(
|
|
64
|
+
createSession({
|
|
65
|
+
activityPreview: {
|
|
66
|
+
state: 'running',
|
|
67
|
+
statusText: '正在调用工具:shell',
|
|
68
|
+
replyText: '之前的回复',
|
|
69
|
+
timestamp: '2026-05-16T01:00:00.000Z'
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
)
|
|
73
|
+
).toBe('正在调用工具:shell');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('shows the final assistant reply after completion', () => {
|
|
77
|
+
expect(
|
|
78
|
+
sessionActivityPreviewText(
|
|
79
|
+
createSession({
|
|
80
|
+
activityPreview: {
|
|
81
|
+
state: 'completed',
|
|
82
|
+
statusText: '工具调用完成',
|
|
83
|
+
replyText: '最终回复内容',
|
|
84
|
+
timestamp: '2026-05-16T01:00:00.000Z'
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
)
|
|
88
|
+
).toBe('最终回复内容');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('formats today activity as time like WeChat session lists', () => {
|
|
92
|
+
expect(
|
|
93
|
+
formatSessionListTime(
|
|
94
|
+
new Date(2026, 4, 16, 9, 5),
|
|
95
|
+
'zh',
|
|
96
|
+
new Date(2026, 4, 16, 12, 0)
|
|
97
|
+
)
|
|
98
|
+
).toBe('09:05');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('formats yesterday activity as yesterday like WeChat session lists', () => {
|
|
102
|
+
expect(
|
|
103
|
+
formatSessionListTime(
|
|
104
|
+
new Date(2026, 4, 15, 23, 30),
|
|
105
|
+
'zh',
|
|
106
|
+
new Date(2026, 4, 16, 12, 0)
|
|
107
|
+
)
|
|
108
|
+
).toBe('昨天');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('formats recent activity within a week as weekday', () => {
|
|
112
|
+
expect(
|
|
113
|
+
formatSessionListTime(
|
|
114
|
+
new Date(2026, 4, 12, 18, 30),
|
|
115
|
+
'zh',
|
|
116
|
+
new Date(2026, 4, 16, 12, 0)
|
|
117
|
+
)
|
|
118
|
+
).toBe('星期二');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('formats older activity as date and keeps year only across years', () => {
|
|
122
|
+
expect(
|
|
123
|
+
formatSessionListTime(
|
|
124
|
+
new Date(2026, 4, 1, 18, 30),
|
|
125
|
+
'zh',
|
|
126
|
+
new Date(2026, 4, 16, 12, 0)
|
|
127
|
+
)
|
|
128
|
+
).toBe('5月1日');
|
|
129
|
+
expect(
|
|
130
|
+
formatSessionListTime(
|
|
131
|
+
new Date(2025, 11, 31, 18, 30),
|
|
132
|
+
'zh',
|
|
133
|
+
new Date(2026, 4, 16, 12, 0)
|
|
134
|
+
)
|
|
135
|
+
).toBe('2025年12月31日');
|
|
136
|
+
});
|
|
55
137
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SessionEntryView } from '@/shared/lib/api';
|
|
2
|
+
import { getLanguage, getLocale, t, type I18nLanguage } from '@/shared/lib/i18n';
|
|
2
3
|
|
|
3
4
|
export function sessionDisplayName(session: SessionEntryView): string {
|
|
4
5
|
const label = session.label?.trim();
|
|
@@ -9,6 +10,78 @@ export function sessionDisplayName(session: SessionEntryView): string {
|
|
|
9
10
|
return chunks[chunks.length - 1] || session.key;
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
export function sessionActivityPreviewText(session: SessionEntryView): string | null {
|
|
14
|
+
const preview = session.activityPreview;
|
|
15
|
+
if (!preview) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
if (preview.state === 'failed' || preview.state === 'running') {
|
|
19
|
+
return preview.statusText ?? preview.replyText ?? null;
|
|
20
|
+
}
|
|
21
|
+
if (preview.state === 'completed') {
|
|
22
|
+
return preview.replyText ?? preview.statusText ?? null;
|
|
23
|
+
}
|
|
24
|
+
return preview.statusText ?? preview.replyText ?? null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function startOfLocalDate(date: Date): number {
|
|
28
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isSameLocalYear(left: Date, right: Date): boolean {
|
|
32
|
+
return left.getFullYear() === right.getFullYear();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatChineseSessionDate(date: Date, now: Date): string {
|
|
36
|
+
const monthDay = `${date.getMonth() + 1}月${date.getDate()}日`;
|
|
37
|
+
return isSameLocalYear(date, now) ? monthDay : `${date.getFullYear()}年${monthDay}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function formatSessionListTime(
|
|
41
|
+
value?: string | Date,
|
|
42
|
+
lang: I18nLanguage = getLanguage(),
|
|
43
|
+
now: Date = new Date()
|
|
44
|
+
): string {
|
|
45
|
+
if (!value) {
|
|
46
|
+
return '-';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
50
|
+
if (Number.isNaN(date.getTime())) {
|
|
51
|
+
return typeof value === 'string' ? value : '-';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const locale = getLocale(lang);
|
|
55
|
+
const dateStart = startOfLocalDate(date);
|
|
56
|
+
const todayStart = startOfLocalDate(now);
|
|
57
|
+
const daysAgo = Math.floor((todayStart - dateStart) / 86_400_000);
|
|
58
|
+
|
|
59
|
+
if (daysAgo === 0) {
|
|
60
|
+
return new Intl.DateTimeFormat(locale, {
|
|
61
|
+
hour: '2-digit',
|
|
62
|
+
minute: '2-digit',
|
|
63
|
+
hour12: false
|
|
64
|
+
}).format(date);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (daysAgo === 1) {
|
|
68
|
+
return t('chatSidebarYesterday', lang);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (daysAgo > 1 && daysAgo < 7) {
|
|
72
|
+
return new Intl.DateTimeFormat(locale, { weekday: 'long' }).format(date);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (lang === 'zh') {
|
|
76
|
+
return formatChineseSessionDate(date, now);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const options: Intl.DateTimeFormatOptions = isSameLocalYear(date, now)
|
|
80
|
+
? { month: 'numeric', day: 'numeric' }
|
|
81
|
+
: { year: 'numeric', month: 'numeric', day: 'numeric' };
|
|
82
|
+
return new Intl.DateTimeFormat(locale, options).format(date);
|
|
83
|
+
}
|
|
84
|
+
|
|
12
85
|
function normalizeSessionSearchValue(value: string): string {
|
|
13
86
|
return value.trim().toLowerCase();
|
|
14
87
|
}
|
|
@@ -70,6 +70,28 @@ describe('adaptNcpSessionSummary', () => {
|
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
it('maps session activity preview metadata into the session entry', () => {
|
|
74
|
+
const adapted = adaptNcpSessionSummary(
|
|
75
|
+
createSummary({
|
|
76
|
+
metadata: {
|
|
77
|
+
last_activity_preview: {
|
|
78
|
+
state: 'completed',
|
|
79
|
+
replyText: '已经整理好方案',
|
|
80
|
+
statusText: '工具调用完成',
|
|
81
|
+
timestamp: '2026-05-16T01:00:00.000Z',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(adapted.activityPreview).toEqual({
|
|
88
|
+
state: 'completed',
|
|
89
|
+
replyText: '已经整理好方案',
|
|
90
|
+
statusText: '工具调用完成',
|
|
91
|
+
timestamp: '2026-05-16T01:00:00.000Z',
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
73
95
|
it('does not hydrate context window metadata from persisted session summaries', () => {
|
|
74
96
|
const adapted = adaptNcpSessionSummary(
|
|
75
97
|
createSummary({
|
|
@@ -3,6 +3,7 @@ import type { NcpMessagePart } from '@nextclaw/ncp';
|
|
|
3
3
|
import type {
|
|
4
4
|
NcpMessageView,
|
|
5
5
|
NcpSessionSummaryView,
|
|
6
|
+
SessionActivityPreviewView,
|
|
6
7
|
SessionEntryView,
|
|
7
8
|
ThinkingLevel
|
|
8
9
|
} from '@/shared/lib/api';
|
|
@@ -13,6 +14,12 @@ import {
|
|
|
13
14
|
} from '@/shared/lib/session-project';
|
|
14
15
|
|
|
15
16
|
const THINKING_LEVEL_SET = new Set<string>(['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh']);
|
|
17
|
+
const SESSION_ACTIVITY_PREVIEW_STATE_SET = new Set<SessionActivityPreviewView['state']>([
|
|
18
|
+
'running',
|
|
19
|
+
'completed',
|
|
20
|
+
'failed',
|
|
21
|
+
'idle'
|
|
22
|
+
]);
|
|
16
23
|
|
|
17
24
|
function stringifyUnknown(value: unknown): string {
|
|
18
25
|
if (typeof value === 'string') {
|
|
@@ -46,6 +53,28 @@ function readMetadata(summary: NcpSessionSummaryView): Record<string, unknown> |
|
|
|
46
53
|
return metadata as Record<string, unknown>;
|
|
47
54
|
}
|
|
48
55
|
|
|
56
|
+
function readNcpSessionActivityPreview(summary: NcpSessionSummaryView): SessionActivityPreviewView | null {
|
|
57
|
+
const metadata = readMetadata(summary);
|
|
58
|
+
const preview = metadata?.last_activity_preview;
|
|
59
|
+
if (!preview || typeof preview !== 'object' || Array.isArray(preview)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const previewRecord = preview as Record<string, unknown>;
|
|
63
|
+
const { state } = previewRecord;
|
|
64
|
+
const timestamp = readOptionalString(previewRecord.timestamp);
|
|
65
|
+
if (!SESSION_ACTIVITY_PREVIEW_STATE_SET.has(state as SessionActivityPreviewView['state']) || !timestamp) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const statusText = readOptionalString(previewRecord.statusText);
|
|
69
|
+
const replyText = readOptionalString(previewRecord.replyText);
|
|
70
|
+
return {
|
|
71
|
+
state: state as SessionActivityPreviewView['state'],
|
|
72
|
+
timestamp,
|
|
73
|
+
...(statusText ? { statusText } : {}),
|
|
74
|
+
...(replyText ? { replyText } : {})
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
49
78
|
export function readNcpSessionPreferredModel(summary: NcpSessionSummaryView): string | null {
|
|
50
79
|
const metadata = readMetadata(summary);
|
|
51
80
|
if (!metadata) {
|
|
@@ -286,6 +315,7 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
|
|
|
286
315
|
const parentSessionId = readNcpParentSessionId(summary);
|
|
287
316
|
const spawnedByRequestId = readNcpSpawnedByRequestId(summary);
|
|
288
317
|
const isPromotedChildSession = readPromotedChildSession(summary);
|
|
318
|
+
const activityPreview = readNcpSessionActivityPreview(summary);
|
|
289
319
|
return {
|
|
290
320
|
key: summary.sessionId,
|
|
291
321
|
createdAt: summary.createdAt ?? summary.updatedAt,
|
|
@@ -301,10 +331,12 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
|
|
|
301
331
|
...(projectName ? { projectName } : {}),
|
|
302
332
|
sessionType: readNcpSessionType(summary),
|
|
303
333
|
sessionTypeMutable: false,
|
|
334
|
+
status: summary.status ?? 'idle',
|
|
304
335
|
isChildSession: Boolean(parentSessionId),
|
|
305
336
|
...(isPromotedChildSession ? { isPromotedChildSession } : {}),
|
|
306
337
|
...(parentSessionId ? { parentSessionId } : {}),
|
|
307
338
|
...(spawnedByRequestId ? { spawnedByRequestId } : {}),
|
|
339
|
+
...(activityPreview ? { activityPreview } : {}),
|
|
308
340
|
messageCount: summary.messageCount
|
|
309
341
|
};
|
|
310
342
|
}
|