@nextclaw/ui 0.14.1 → 0.14.2
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 +29 -0
- package/dist/assets/{api-DPcvCLfa.js → api-a14tR_z1.js} +7 -7
- package/dist/assets/channels-list-page-CFGkm4DT.js +8 -0
- package/dist/assets/chat-page-CEdavJXc.js +105 -0
- package/dist/assets/{config-split-page-BCJhqdK9.js → config-split-page-B5b9B0uE.js} +1 -1
- package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-ClH_Ec7n.js} +1 -1
- package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-DPNDnVpk.js} +1 -1
- package/dist/assets/{dist-DPrgcEC0.js → dist-BtCS2Ucq.js} +1 -1
- package/dist/assets/{dist-DsE4OsSS.js → dist-CLGGLm8b.js} +1 -1
- package/dist/assets/doc-browser-5nMWYKBD.js +1 -0
- package/dist/assets/doc-browser-BJRLhztv.js +1 -0
- package/dist/assets/doc-browser-context-D4JwMMxK.js +1 -0
- package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-CtJXjIol.js} +1 -1
- package/dist/assets/{external-link-C_dmG-WJ.js → external-link-CdJiG13p.js} +1 -1
- package/dist/assets/index-BDN1zzMQ.js +105 -0
- package/dist/assets/index-g3uSZXoc.css +1 -0
- package/dist/assets/mcp-marketplace-page-BU0kIjhR.js +40 -0
- package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +1 -0
- package/dist/assets/middleware-CkFT3R4v.js +1 -0
- package/dist/assets/model-config-page-D6hYXROd.js +1 -0
- package/dist/assets/plus-DPjb0piO.js +1 -0
- package/dist/assets/provider-scoped-model-input-DAX2HgmR.js +1 -0
- package/dist/assets/providers-config-page-BzAeaACA.js +1 -0
- package/dist/assets/{react-sZz5HSCm.js → react-C6cZgP44.js} +1 -1
- package/dist/assets/remote-B0G80uPr.js +1 -0
- package/dist/assets/rotate-cw-DnEB1Rju.js +1 -0
- package/dist/assets/runtime-config-page-C5xKaV3i.js +1 -0
- package/dist/assets/{save-MzjoiHri.js → save-DKA0x4zk.js} +1 -1
- package/dist/assets/{search-B29dE4GN.js → search-BNlcoj5c.js} +1 -1
- package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-Cq9uJ-N8.js} +1 -1
- package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page-CxSbZLBo.js} +2 -2
- package/dist/assets/{select-BKmh05hd.js → select-CvTlDZTQ.js} +1 -1
- package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-8ffF2y2C.js} +1 -1
- package/dist/assets/use-config-DmT0o1gv.js +1 -0
- package/dist/assets/x-BiSSpgqB.js +1 -0
- package/dist/index.html +15 -14
- package/package.json +9 -9
- package/src/app/components/app-presenter-provider.tsx +11 -2
- package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
- package/src/app/components/layout/app-layout.tsx +18 -4
- package/src/app/components/layout/sidebar-items.tsx +194 -71
- package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
- package/src/app/components/layout/sidebar.tsx +340 -83
- package/src/app/managers/viewport-layout.manager.ts +10 -0
- package/src/app/stores/viewport-layout.store.ts +39 -3
- package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
- package/src/features/agents/components/agents-page.tsx +10 -6
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +14 -15
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +18 -18
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +16 -14
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +80 -7
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +82 -25
- package/src/features/chat/components/layout/chat-sidebar.tsx +59 -92
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +289 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +427 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +204 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
- package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +32 -17
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
- package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
- package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
- package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
- package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
- package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +93 -12
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
- package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
- package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
- package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
- package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +2 -2
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
- package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
- package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
- package/src/features/chat/index.ts +0 -1
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
- package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
- package/src/features/chat/managers/chat-thread.manager.ts +108 -34
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
- package/src/features/chat/presenters/chat.presenter.ts +2 -19
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
- package/src/features/chat/stores/chat-thread.store.ts +42 -26
- package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
- package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
- package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
- package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
- package/src/shared/lib/ui-document-title/index.ts +50 -21
- package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
- package/dist/assets/chat-page-BBnNJoKG.js +0 -105
- package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
- package/dist/assets/doc-browser-DgQedeAn.js +0 -1
- package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
- package/dist/assets/index-7o1WdUbU.js +0 -103
- package/dist/assets/index-DQWmEWzk.css +0 -1
- package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
- package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
- package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
- package/dist/assets/plus-Bnt56oqG.js +0 -1
- package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
- package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
- package/dist/assets/remote-B0LMs8u7.js +0 -1
- package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
- package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
- package/dist/assets/use-config-DZkBWIdL.js +0 -1
- package/dist/assets/x-CW2KO-Hz.js +0 -1
- package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
- package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
- package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
- package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
- package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
- package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
- package/src/features/chat/managers/chat-input.manager.ts +0 -430
- package/src/features/chat/managers/chat-run.manager.ts +0 -110
- package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
- package/src/features/chat/stores/chat-input.store.ts +0 -104
- /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
|
|
2
|
-
import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
|
|
3
2
|
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
4
3
|
import type { ChatUiManager } from '@/features/chat/managers/chat-ui.manager';
|
|
5
4
|
import type { SetStateAction } from 'react';
|
|
6
|
-
import type { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
|
|
7
5
|
import { normalizeSessionProjectRootValue } from '@/shared/lib/session-project';
|
|
8
6
|
import { updateNcpSession } from '@/shared/lib/api';
|
|
9
7
|
import { CHAT_DRAFT_SESSION_PATH } from '@/features/chat/features/session/utils/chat-session-route.utils';
|
|
8
|
+
import { DEFAULT_SESSION_TYPE, normalizeSessionType } from '@/features/chat/features/session-type/utils/chat-session-type.utils';
|
|
10
9
|
|
|
11
10
|
type WorkspaceChildReadState = {
|
|
12
11
|
sessionKey: string | null | undefined;
|
|
@@ -15,21 +14,13 @@ type WorkspaceChildReadState = {
|
|
|
15
14
|
runStatus?: string | null;
|
|
16
15
|
};
|
|
17
16
|
export class ChatSessionListManager {
|
|
18
|
-
constructor(
|
|
19
|
-
private uiManager: ChatUiManager,
|
|
20
|
-
private chatRunManager: ChatRunManager
|
|
21
|
-
) {}
|
|
17
|
+
constructor(private uiManager: ChatUiManager) {}
|
|
22
18
|
|
|
23
|
-
private syncDraftThreadState = (
|
|
19
|
+
private syncDraftThreadState = () => {
|
|
24
20
|
useChatThreadStore.getState().setSnapshot({
|
|
25
21
|
sessionKey: null,
|
|
26
22
|
sessionDisplayName: undefined,
|
|
27
23
|
canDeleteSession: false,
|
|
28
|
-
isHistoryLoading: false,
|
|
29
|
-
messages: [],
|
|
30
|
-
isSending: false,
|
|
31
|
-
isAwaitingAssistantOutput: false,
|
|
32
|
-
hasSubmittedDraftMessage,
|
|
33
24
|
parentSessionKey: null,
|
|
34
25
|
parentSessionLabel: null,
|
|
35
26
|
workspacePanelParentKey: null,
|
|
@@ -107,7 +98,6 @@ export class ChatSessionListManager {
|
|
|
107
98
|
}
|
|
108
99
|
if (selectedSessionKey !== null) {
|
|
109
100
|
this.setSelectedSessionKey(null);
|
|
110
|
-
this.chatRunManager.clearRunState();
|
|
111
101
|
}
|
|
112
102
|
};
|
|
113
103
|
|
|
@@ -142,26 +132,21 @@ export class ChatSessionListManager {
|
|
|
142
132
|
};
|
|
143
133
|
|
|
144
134
|
createSession = (sessionType?: string, projectRoot?: string | null): void => {
|
|
145
|
-
const
|
|
146
|
-
const { defaultSessionType: configuredDefaultSessionType } = snapshot;
|
|
147
|
-
const defaultSessionType = configuredDefaultSessionType || 'native';
|
|
148
|
-
const nextSessionType =
|
|
149
|
-
typeof sessionType === 'string' && sessionType.trim().length > 0
|
|
150
|
-
? sessionType.trim()
|
|
151
|
-
: defaultSessionType;
|
|
135
|
+
const nextSessionType = normalizeSessionType(sessionType ?? DEFAULT_SESSION_TYPE);
|
|
152
136
|
const normalizedProjectRoot = normalizeSessionProjectRootValue(projectRoot);
|
|
153
|
-
this.chatRunManager.clearRunState();
|
|
154
137
|
useChatSessionListStore.getState().setSnapshot({
|
|
155
138
|
selectedSessionKey: null,
|
|
156
139
|
});
|
|
157
140
|
this.syncDraftThreadState();
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
141
|
+
this.uiManager.navigateTo(CHAT_DRAFT_SESSION_PATH, {
|
|
142
|
+
replace: this.uiManager.isAtChatRoot(),
|
|
143
|
+
state: {
|
|
144
|
+
chatDraft: {
|
|
145
|
+
sessionType: nextSessionType,
|
|
146
|
+
projectRoot: normalizedProjectRoot,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
163
149
|
});
|
|
164
|
-
this.uiManager.navigateTo(CHAT_DRAFT_SESSION_PATH);
|
|
165
150
|
};
|
|
166
151
|
|
|
167
152
|
startAgentDraftChat = (agentId: string, sessionType: string): void => {
|
|
@@ -170,22 +155,6 @@ export class ChatSessionListManager {
|
|
|
170
155
|
this.setSelectedAgentId(normalizedAgentId);
|
|
171
156
|
};
|
|
172
157
|
|
|
173
|
-
ensureDraftSession = (sessionType?: string): string | null => {
|
|
174
|
-
const { snapshot } = useChatSessionListStore.getState();
|
|
175
|
-
if (snapshot.selectedSessionKey) {
|
|
176
|
-
return snapshot.selectedSessionKey;
|
|
177
|
-
}
|
|
178
|
-
const normalizedSessionType =
|
|
179
|
-
typeof sessionType === 'string' && sessionType.trim().length > 0
|
|
180
|
-
? sessionType.trim()
|
|
181
|
-
: null;
|
|
182
|
-
this.syncDraftThreadState(true);
|
|
183
|
-
if (normalizedSessionType) {
|
|
184
|
-
useChatInputStore.getState().setSnapshot({ pendingSessionType: normalizedSessionType });
|
|
185
|
-
}
|
|
186
|
-
return null;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
158
|
selectSession = (sessionKey: string) => {
|
|
190
159
|
this.uiManager.goToSession(sessionKey);
|
|
191
160
|
};
|
|
@@ -10,37 +10,33 @@ import type {
|
|
|
10
10
|
} from '@nextclaw/agent-chat-ui';
|
|
11
11
|
import type { UiShowContentEventPayload } from '@nextclaw/shared';
|
|
12
12
|
import type { ChatSessionListManager } from '@/features/chat/managers/chat-session-list.manager';
|
|
13
|
-
import type { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
|
|
14
13
|
import type { ChatUiManager } from '@/features/chat/managers/chat-ui.manager';
|
|
15
14
|
import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
|
|
16
15
|
import type {
|
|
16
|
+
ChatChildSessionTab,
|
|
17
17
|
ChatThreadSnapshot,
|
|
18
18
|
ChatWorkspaceNavigationEntry,
|
|
19
19
|
ChatWorkspaceFileTab,
|
|
20
20
|
} from '@/features/chat/stores/chat-thread.store';
|
|
21
21
|
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
22
22
|
import { t } from '@/shared/lib/i18n';
|
|
23
|
+
import {
|
|
24
|
+
areWorkspaceNavigationEntriesEqual,
|
|
25
|
+
createSideChatDraft,
|
|
26
|
+
materializeSideChatDraftSnapshot,
|
|
27
|
+
upsertChildSessionTab,
|
|
28
|
+
} from '@/features/chat/features/workspace/utils/chat-thread-workspace-session.utils';
|
|
23
29
|
import {
|
|
24
30
|
filterNavigationHistoryEntries,
|
|
25
31
|
pushNavigationHistoryEntry,
|
|
26
32
|
stepNavigationHistory,
|
|
27
33
|
} from '@/shared/lib/navigation-history';
|
|
28
34
|
|
|
29
|
-
function areWorkspaceNavigationEntriesEqual(
|
|
30
|
-
current: ChatWorkspaceNavigationEntry,
|
|
31
|
-
next: ChatWorkspaceNavigationEntry,
|
|
32
|
-
): boolean {
|
|
33
|
-
if (current.kind !== next.kind) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
if (current.kind === 'cron') {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
return next.kind !== 'cron' && current.key === next.key;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
35
|
type WorkspaceChildReadState = Parameters<ChatSessionListManager['markVisibleWorkspaceChildRead']>[0];
|
|
43
|
-
export type ChatVisibleWorkspaceSelection =
|
|
36
|
+
export type ChatVisibleWorkspaceSelection =
|
|
37
|
+
| { kind: 'child-session'; tab: WorkspaceChildReadState }
|
|
38
|
+
| { kind: 'file' | 'side-chat-draft' | 'cron' }
|
|
39
|
+
| null;
|
|
44
40
|
|
|
45
41
|
export class ChatThreadManager {
|
|
46
42
|
private readonly handledUiShowContentEventIds = new Set<string>();
|
|
@@ -48,7 +44,6 @@ export class ChatThreadManager {
|
|
|
48
44
|
constructor(
|
|
49
45
|
private uiManager: ChatUiManager,
|
|
50
46
|
private sessionListManager: ChatSessionListManager,
|
|
51
|
-
private chatRunManager: ChatRunManager,
|
|
52
47
|
) {}
|
|
53
48
|
|
|
54
49
|
private hasSnapshotChanges = (patch: Partial<ChatThreadSnapshot>): boolean => {
|
|
@@ -80,16 +75,13 @@ export class ChatThreadManager {
|
|
|
80
75
|
sessionProjectRoot: null,
|
|
81
76
|
sessionProjectName: null,
|
|
82
77
|
canDeleteSession: false,
|
|
83
|
-
isHistoryLoading: false,
|
|
84
|
-
messages: [],
|
|
85
|
-
isSending: false,
|
|
86
|
-
isAwaitingAssistantOutput: false,
|
|
87
78
|
parentSessionKey: null,
|
|
88
79
|
parentSessionLabel: null,
|
|
89
80
|
workspacePanelParentKey: null,
|
|
90
81
|
activeWorkspacePanelKind: null,
|
|
91
82
|
childSessionTabs: [],
|
|
92
83
|
activeChildSessionKey: null,
|
|
84
|
+
activeSideChatDraft: null,
|
|
93
85
|
workspaceFileTabs: [],
|
|
94
86
|
activeWorkspaceFileKey: null,
|
|
95
87
|
workspaceNavigationHistory: [],
|
|
@@ -98,6 +90,10 @@ export class ChatThreadManager {
|
|
|
98
90
|
};
|
|
99
91
|
|
|
100
92
|
private resolveWorkspaceParentSessionKey = (): string | null => {
|
|
93
|
+
const selectedSessionKey = useChatSessionListStore.getState().snapshot.selectedSessionKey?.trim();
|
|
94
|
+
if (selectedSessionKey) {
|
|
95
|
+
return selectedSessionKey;
|
|
96
|
+
}
|
|
101
97
|
const threadSessionKey = useChatThreadStore.getState().snapshot.sessionKey?.trim();
|
|
102
98
|
if (threadSessionKey) {
|
|
103
99
|
return threadSessionKey;
|
|
@@ -169,6 +165,7 @@ export class ChatThreadManager {
|
|
|
169
165
|
return {
|
|
170
166
|
activeWorkspacePanelKind: 'cron',
|
|
171
167
|
activeChildSessionKey: null,
|
|
168
|
+
activeSideChatDraft: null,
|
|
172
169
|
activeWorkspaceFileKey: null,
|
|
173
170
|
};
|
|
174
171
|
}
|
|
@@ -179,15 +176,29 @@ export class ChatThreadManager {
|
|
|
179
176
|
return {
|
|
180
177
|
activeWorkspacePanelKind: 'file',
|
|
181
178
|
activeChildSessionKey: null,
|
|
179
|
+
activeSideChatDraft: null,
|
|
182
180
|
activeWorkspaceFileKey: entry.key,
|
|
183
181
|
};
|
|
184
182
|
}
|
|
183
|
+
if (entry.kind === 'side-chat-draft') {
|
|
184
|
+
const { activeSideChatDraft } = snapshot;
|
|
185
|
+
if (activeSideChatDraft?.draftKey !== entry.key) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
activeWorkspacePanelKind: 'side-chat-draft',
|
|
190
|
+
activeChildSessionKey: null,
|
|
191
|
+
activeSideChatDraft,
|
|
192
|
+
activeWorkspaceFileKey: null,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
185
195
|
if (!entry.key) {
|
|
186
196
|
return null;
|
|
187
197
|
}
|
|
188
198
|
return {
|
|
189
199
|
activeWorkspacePanelKind: 'child-session',
|
|
190
200
|
activeChildSessionKey: entry.key,
|
|
201
|
+
activeSideChatDraft: null,
|
|
191
202
|
activeWorkspaceFileKey: null,
|
|
192
203
|
};
|
|
193
204
|
};
|
|
@@ -215,18 +226,36 @@ export class ChatThreadManager {
|
|
|
215
226
|
openChildSessionPanel = (params: {
|
|
216
227
|
parentSessionKey: string;
|
|
217
228
|
activeChildSessionKey?: string | null;
|
|
229
|
+
childSessionTab?: Pick<ChatChildSessionTab, 'label' | 'agentId'> | null;
|
|
218
230
|
}) => {
|
|
219
|
-
const
|
|
231
|
+
const {
|
|
232
|
+
activeChildSessionKey: rawActiveChildSessionKey,
|
|
233
|
+
childSessionTab,
|
|
234
|
+
parentSessionKey: rawParentSessionKey,
|
|
235
|
+
} = params;
|
|
236
|
+
const parentSessionKey = rawParentSessionKey.trim();
|
|
220
237
|
if (!parentSessionKey) {
|
|
221
238
|
return;
|
|
222
239
|
}
|
|
223
|
-
const activeChildSessionKey =
|
|
240
|
+
const activeChildSessionKey = rawActiveChildSessionKey?.trim() || null;
|
|
224
241
|
const patch: Partial<ChatThreadSnapshot> = {
|
|
225
242
|
workspacePanelParentKey: parentSessionKey,
|
|
226
243
|
activeWorkspacePanelKind: 'child-session',
|
|
227
244
|
activeChildSessionKey,
|
|
245
|
+
activeSideChatDraft: null,
|
|
228
246
|
activeWorkspaceFileKey: null,
|
|
229
247
|
};
|
|
248
|
+
if (activeChildSessionKey && childSessionTab) {
|
|
249
|
+
patch.childSessionTabs = upsertChildSessionTab(
|
|
250
|
+
useChatThreadStore.getState().snapshot.childSessionTabs,
|
|
251
|
+
{
|
|
252
|
+
sessionKey: activeChildSessionKey,
|
|
253
|
+
parentSessionKey,
|
|
254
|
+
label: childSessionTab.label?.trim() || null,
|
|
255
|
+
agentId: childSessionTab.agentId?.trim() || null,
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
}
|
|
230
259
|
if (activeChildSessionKey) {
|
|
231
260
|
this.setWorkspaceSelection(patch, {
|
|
232
261
|
kind: 'child-session',
|
|
@@ -238,6 +267,43 @@ export class ChatThreadManager {
|
|
|
238
267
|
this.ensureWorkspaceParentRoute(parentSessionKey);
|
|
239
268
|
};
|
|
240
269
|
|
|
270
|
+
openSideChatDraft = (parentSessionKey?: string | null) => {
|
|
271
|
+
const resolvedParentSessionKey =
|
|
272
|
+
parentSessionKey?.trim() || this.resolveWorkspaceParentSessionKey()?.trim();
|
|
273
|
+
if (!resolvedParentSessionKey) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const activeSideChatDraft = createSideChatDraft(resolvedParentSessionKey);
|
|
277
|
+
this.setWorkspaceSelection({
|
|
278
|
+
workspacePanelParentKey: resolvedParentSessionKey,
|
|
279
|
+
activeWorkspacePanelKind: 'side-chat-draft',
|
|
280
|
+
activeChildSessionKey: null,
|
|
281
|
+
activeSideChatDraft,
|
|
282
|
+
activeWorkspaceFileKey: null,
|
|
283
|
+
}, {
|
|
284
|
+
kind: 'side-chat-draft',
|
|
285
|
+
key: activeSideChatDraft.draftKey,
|
|
286
|
+
});
|
|
287
|
+
this.ensureWorkspaceParentRoute(resolvedParentSessionKey);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
materializeSideChatDraft = (params: {
|
|
291
|
+
draftKey: string;
|
|
292
|
+
sessionKey: string;
|
|
293
|
+
label?: string | null;
|
|
294
|
+
agentId?: string | null;
|
|
295
|
+
}) => {
|
|
296
|
+
const materialized = materializeSideChatDraftSnapshot({
|
|
297
|
+
snapshot: useChatThreadStore.getState().snapshot,
|
|
298
|
+
...params,
|
|
299
|
+
});
|
|
300
|
+
if (!materialized) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
useChatThreadStore.getState().setSnapshot(materialized.patch);
|
|
304
|
+
this.ensureWorkspaceParentRoute(materialized.parentSessionKey);
|
|
305
|
+
};
|
|
306
|
+
|
|
241
307
|
openFilePreview = (action: ChatFileOpenActionViewModel) => {
|
|
242
308
|
const parentSessionKey = this.resolveWorkspaceParentSessionKey();
|
|
243
309
|
const nextTab = this.buildWorkspaceFileTab(action, parentSessionKey);
|
|
@@ -250,6 +316,7 @@ export class ChatThreadManager {
|
|
|
250
316
|
workspaceFileTabs: this.upsertWorkspaceFileTab(nextTab),
|
|
251
317
|
activeWorkspaceFileKey: nextTab.key,
|
|
252
318
|
activeChildSessionKey: null,
|
|
319
|
+
activeSideChatDraft: null,
|
|
253
320
|
}, {
|
|
254
321
|
kind: 'file',
|
|
255
322
|
key: nextTab.key,
|
|
@@ -261,6 +328,10 @@ export class ChatThreadManager {
|
|
|
261
328
|
if (action.kind !== 'open-session') {
|
|
262
329
|
return;
|
|
263
330
|
}
|
|
331
|
+
const sessionId = action.sessionId.trim();
|
|
332
|
+
if (!sessionId) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
264
335
|
if (action.sessionKind === 'child' && !this.uiManager.isCompactViewport()) {
|
|
265
336
|
const parentSessionKey =
|
|
266
337
|
action.parentSessionId?.trim() ||
|
|
@@ -269,13 +340,17 @@ export class ChatThreadManager {
|
|
|
269
340
|
if (parentSessionKey) {
|
|
270
341
|
this.openChildSessionPanel({
|
|
271
342
|
parentSessionKey,
|
|
272
|
-
activeChildSessionKey:
|
|
343
|
+
activeChildSessionKey: sessionId,
|
|
344
|
+
childSessionTab: {
|
|
345
|
+
label: action.label,
|
|
346
|
+
agentId: action.agentId,
|
|
347
|
+
},
|
|
273
348
|
});
|
|
274
349
|
return;
|
|
275
350
|
}
|
|
276
351
|
}
|
|
277
352
|
this.closeWorkspacePanel();
|
|
278
|
-
this.uiManager.goToSession(
|
|
353
|
+
this.uiManager.goToSession(sessionId);
|
|
279
354
|
};
|
|
280
355
|
|
|
281
356
|
handleToolAction = async (action: ChatToolActionViewModel): Promise<void> => {
|
|
@@ -331,14 +406,11 @@ export class ChatThreadManager {
|
|
|
331
406
|
if (!normalizedSessionKey) {
|
|
332
407
|
return;
|
|
333
408
|
}
|
|
334
|
-
const { childSessionTabs } = useChatThreadStore.getState().snapshot;
|
|
335
|
-
if (!childSessionTabs.some((tab) => tab.sessionKey === normalizedSessionKey)) {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
409
|
this.setWorkspaceSelection({
|
|
339
410
|
activeChildSessionKey: normalizedSessionKey,
|
|
340
411
|
activeWorkspaceFileKey: null,
|
|
341
412
|
activeWorkspacePanelKind: 'child-session',
|
|
413
|
+
activeSideChatDraft: null,
|
|
342
414
|
}, {
|
|
343
415
|
kind: 'child-session',
|
|
344
416
|
key: normalizedSessionKey,
|
|
@@ -358,6 +430,7 @@ export class ChatThreadManager {
|
|
|
358
430
|
activeWorkspaceFileKey: normalizedFileKey,
|
|
359
431
|
activeChildSessionKey: null,
|
|
360
432
|
activeWorkspacePanelKind: 'file',
|
|
433
|
+
activeSideChatDraft: null,
|
|
361
434
|
}, {
|
|
362
435
|
kind: 'file',
|
|
363
436
|
key: normalizedFileKey,
|
|
@@ -397,6 +470,7 @@ export class ChatThreadManager {
|
|
|
397
470
|
: null;
|
|
398
471
|
Object.assign(nextPatch, restorePatch ?? {
|
|
399
472
|
activeWorkspacePanelKind: null,
|
|
473
|
+
activeSideChatDraft: null,
|
|
400
474
|
activeWorkspaceFileKey: null,
|
|
401
475
|
});
|
|
402
476
|
}
|
|
@@ -408,6 +482,7 @@ export class ChatThreadManager {
|
|
|
408
482
|
workspacePanelParentKey: null,
|
|
409
483
|
activeWorkspacePanelKind: null,
|
|
410
484
|
activeChildSessionKey: null,
|
|
485
|
+
activeSideChatDraft: null,
|
|
411
486
|
activeWorkspaceFileKey: null,
|
|
412
487
|
workspaceNavigationHistory: [],
|
|
413
488
|
workspaceNavigationHistoryIndex: 0,
|
|
@@ -423,6 +498,7 @@ export class ChatThreadManager {
|
|
|
423
498
|
workspacePanelParentKey: parentSessionKey,
|
|
424
499
|
activeWorkspacePanelKind: 'cron',
|
|
425
500
|
activeChildSessionKey: null,
|
|
501
|
+
activeSideChatDraft: null,
|
|
426
502
|
activeWorkspaceFileKey: null,
|
|
427
503
|
}, {
|
|
428
504
|
kind: 'cron',
|
|
@@ -461,13 +537,10 @@ export class ChatThreadManager {
|
|
|
461
537
|
});
|
|
462
538
|
};
|
|
463
539
|
|
|
464
|
-
closeChildSessionDetail = () => {
|
|
465
|
-
this.closeWorkspacePanel();
|
|
466
|
-
};
|
|
467
|
-
|
|
468
540
|
goToParentSession = () => {
|
|
469
541
|
const {
|
|
470
542
|
parentSessionKey,
|
|
543
|
+
activeSideChatDraft,
|
|
471
544
|
childSessionTabs,
|
|
472
545
|
activeChildSessionKey,
|
|
473
546
|
} = useChatThreadStore.getState().snapshot;
|
|
@@ -475,7 +548,9 @@ export class ChatThreadManager {
|
|
|
475
548
|
childSessionTabs.find((tab) => tab.sessionKey === activeChildSessionKey)
|
|
476
549
|
?.parentSessionKey ?? null;
|
|
477
550
|
const resolvedParentSessionKey =
|
|
478
|
-
parentSessionKey ??
|
|
551
|
+
parentSessionKey ??
|
|
552
|
+
activeSideChatDraft?.parentSessionKey ??
|
|
553
|
+
activeChildParentSessionKey;
|
|
479
554
|
if (!resolvedParentSessionKey) {
|
|
480
555
|
return;
|
|
481
556
|
}
|
|
@@ -503,7 +578,6 @@ export class ChatThreadManager {
|
|
|
503
578
|
await deleteNcpSessionApi(selectedSessionKey);
|
|
504
579
|
deleteNcpSessionSummaryInQueryClient(appQueryClient, selectedSessionKey);
|
|
505
580
|
appQueryClient.removeQueries({ queryKey: ['ncp-session-messages', selectedSessionKey] });
|
|
506
|
-
this.chatRunManager.clearRunState();
|
|
507
581
|
this.clearDeletedSessionState(selectedSessionKey);
|
|
508
582
|
this.uiManager.goToChatRoot({ replace: true });
|
|
509
583
|
} finally {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { createChatComposerTokenNode } from '@nextclaw/agent-chat-ui';
|
|
3
|
+
import {
|
|
4
|
+
CHAT_SESSION_MATERIALIZATION_METADATA_KEY,
|
|
5
|
+
RUNTIME_DEFAULT_MODEL_VALUE,
|
|
6
|
+
} from '@nextclaw/shared';
|
|
3
7
|
import type { SessionEntryView, ThinkingLevel } from '@/shared/lib/api';
|
|
4
8
|
import type { ChatModelOption } from '@/features/chat/types/chat-input.types';
|
|
5
9
|
import {
|
|
@@ -185,6 +189,49 @@ describe('buildChatRunMetadata', () => {
|
|
|
185
189
|
requested_skill_refs: ['project:/tmp/project-alpha/.agents/skills/review'],
|
|
186
190
|
});
|
|
187
191
|
});
|
|
192
|
+
|
|
193
|
+
it('serializes panel app references as text without run metadata', () => {
|
|
194
|
+
expect(
|
|
195
|
+
buildChatRunMetadata({
|
|
196
|
+
composerNodes: [
|
|
197
|
+
createChatComposerTokenNode({
|
|
198
|
+
tokenKind: 'panel_app',
|
|
199
|
+
tokenKey: 'task-board',
|
|
200
|
+
label: 'Task Board',
|
|
201
|
+
}),
|
|
202
|
+
],
|
|
203
|
+
}),
|
|
204
|
+
).not.toHaveProperty('referenced_panel_apps');
|
|
205
|
+
expect(
|
|
206
|
+
buildChatRunMetadata({
|
|
207
|
+
composerNodes: [
|
|
208
|
+
createChatComposerTokenNode({
|
|
209
|
+
tokenKind: 'panel_app',
|
|
210
|
+
tokenKey: 'task-board',
|
|
211
|
+
label: 'Task Board',
|
|
212
|
+
}),
|
|
213
|
+
],
|
|
214
|
+
}),
|
|
215
|
+
).not.toHaveProperty('ui_inline_tokens');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('serializes child session materialization metadata for side chat drafts', () => {
|
|
219
|
+
expect(
|
|
220
|
+
buildChatRunMetadata({
|
|
221
|
+
sessionMaterialization: {
|
|
222
|
+
kind: 'child',
|
|
223
|
+
parentSessionId: 'parent-session-1',
|
|
224
|
+
inheritContext: true,
|
|
225
|
+
},
|
|
226
|
+
}),
|
|
227
|
+
).toMatchObject({
|
|
228
|
+
[CHAT_SESSION_MATERIALIZATION_METADATA_KEY]: {
|
|
229
|
+
kind: 'child',
|
|
230
|
+
parentSessionId: 'parent-session-1',
|
|
231
|
+
inheritContext: true,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
});
|
|
188
235
|
});
|
|
189
236
|
|
|
190
237
|
function createSession(overrides: Partial<SessionEntryView> & Pick<SessionEntryView, 'key'>): SessionEntryView {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { AppPresenterProvider } from '@/app/components/app-presenter-provider';
|
|
6
|
+
import { NcpChatPage } from '@/features/chat/pages/ncp-chat-page';
|
|
7
|
+
|
|
8
|
+
const mocks = vi.hoisted(() => ({
|
|
9
|
+
confirm: vi.fn(),
|
|
10
|
+
consumePending: vi.fn(() => null),
|
|
11
|
+
markConsumed: vi.fn(),
|
|
12
|
+
subscribe: vi.fn(() => vi.fn()),
|
|
13
|
+
useChatSessionSync: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock('@/app/presenters/app.presenter', () => ({
|
|
17
|
+
getAppPresenter: () => ({
|
|
18
|
+
docBrowserManager: {},
|
|
19
|
+
chatDraftIntentManager: {
|
|
20
|
+
consumePending: mocks.consumePending,
|
|
21
|
+
markConsumed: mocks.markConsumed,
|
|
22
|
+
subscribe: mocks.subscribe,
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock('@/shared/hooks/use-confirm-dialog', () => ({
|
|
28
|
+
useConfirmDialog: () => ({
|
|
29
|
+
confirm: mocks.confirm,
|
|
30
|
+
ConfirmDialog: () => <div data-testid="confirm-dialog" />,
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock('@/features/chat/components/layout/chat-page-shell', () => ({
|
|
35
|
+
ChatPageLayout: () => <div data-testid="chat-page-layout" />,
|
|
36
|
+
useChatSessionSync: (params: {
|
|
37
|
+
syncRouteSessionSelection: (value: {
|
|
38
|
+
isChatView: boolean;
|
|
39
|
+
routeSessionKey: string | null;
|
|
40
|
+
}) => void;
|
|
41
|
+
}) => {
|
|
42
|
+
mocks.useChatSessionSync(params);
|
|
43
|
+
const { syncRouteSessionSelection } = params;
|
|
44
|
+
syncRouteSessionSelection({
|
|
45
|
+
isChatView: true,
|
|
46
|
+
routeSessionKey: null,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
vi.mock('@/features/chat/features/ncp/hooks/use-ncp-chat-query-store-sync', () => ({
|
|
52
|
+
useChatQueryStoreSync: () => undefined,
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
vi.mock('@/features/chat/features/ncp/hooks/use-ui-show-content-event', () => ({
|
|
56
|
+
useUiShowContentEvent: () => undefined,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
describe('NcpChatPage render boundary', () => {
|
|
60
|
+
it('creates its chat presenter from the global app presenter provider', () => {
|
|
61
|
+
render(
|
|
62
|
+
<MemoryRouter initialEntries={['/chat']}>
|
|
63
|
+
<AppPresenterProvider>
|
|
64
|
+
<NcpChatPage view="chat" />
|
|
65
|
+
</AppPresenterProvider>
|
|
66
|
+
</MemoryRouter>,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(screen.getByTestId('chat-page-layout')).toBeTruthy();
|
|
70
|
+
expect(mocks.useChatSessionSync).toHaveBeenCalledOnce();
|
|
71
|
+
});
|
|
72
|
+
});
|