@nextclaw/ui 0.14.0 → 0.14.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/assets/{api-B50yVdT8.js → api-C6EDHBDR.js} +1 -1
- package/dist/assets/channels-list-page-DKj6m1Hc.js +8 -0
- package/dist/assets/chat-page-BXngLJY6.js +105 -0
- package/dist/assets/config-split-page-CjlmJy8X.js +1 -0
- package/dist/assets/{confirm-dialog-DRSelLdI.js → confirm-dialog-B39X0pYp.js} +1 -1
- package/dist/assets/desktop-update-config-DfUQ8nly.js +1 -0
- package/dist/assets/{dist-NZRV-BxD.js → dist-BIYV2-Ia.js} +1 -1
- package/dist/assets/{dist-D19wiQwB.js → dist-CmMro3nV.js} +1 -1
- package/dist/assets/doc-browser-B6Wc_TIY.js +1 -0
- package/dist/assets/doc-browser-CDtUtcim.js +1 -0
- package/dist/assets/{doc-browser-context-C8KPbj-p.js → doc-browser-context-DZbC9zVB.js} +1 -1
- package/dist/assets/{ellipsis-BYy8TP0A.js → ellipsis-DDpgTzgx.js} +1 -1
- package/dist/assets/{external-link-528k6MDD.js → external-link-D2cBOCqn.js} +1 -1
- package/dist/assets/index-DTb_XVIH.js +103 -0
- package/dist/assets/index-ayiPYhf1.css +1 -0
- package/dist/assets/mcp-marketplace-page-DZ9H0v05.js +1 -0
- package/dist/assets/mcp-marketplace-page-cnVWf7QQ.js +40 -0
- package/dist/assets/model-config-page-Bcb35AVv.js +1 -0
- package/dist/assets/plus-CYE1onU2.js +1 -0
- package/dist/assets/provider-scoped-model-input-kdoNp4HG.js +1 -0
- package/dist/assets/providers-config-page-CQSibSwO.js +1 -0
- package/dist/assets/{react-Dsotw5vM.js → react-DYQXXGbH.js} +1 -1
- package/dist/assets/remote-BlJDbXOI.js +1 -0
- package/dist/assets/rotate-cw-BWbvFDAX.js +1 -0
- package/dist/assets/runtime-config-page-Tn2yLQ0c.js +1 -0
- package/dist/assets/{save-CHDnDTok.js → save-BNVTgBVn.js} +1 -1
- package/dist/assets/search-CKCzwLId.js +1 -0
- package/dist/assets/search-config-page-DG5Q2hF1.js +1 -0
- package/dist/assets/{secrets-config-page-95yaH1fa.js → secrets-config-page-BXn5Uuwp.js} +2 -2
- package/dist/assets/select-CIRwTNP4.js +41 -0
- package/dist/assets/{tag-chip-Cr8GwpRE.js → tag-chip-DKhVFtTE.js} +1 -1
- package/dist/assets/{use-config-TzwzTgg0.js → use-config-DgKM9VIU.js} +1 -1
- package/dist/assets/x-Cm3JjYut.js +1 -0
- package/dist/index.html +14 -27
- package/package.json +9 -9
- package/src/app/presenters/app.presenter.ts +2 -0
- package/src/features/account/components/account-panel.tsx +2 -2
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +1 -0
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +39 -80
- package/src/features/chat/features/input/hooks/use-chat-input-bar-query-state.ts +118 -0
- package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +2 -17
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +1 -24
- package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +0 -27
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +89 -0
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +9 -1
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +115 -0
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +4 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +119 -1
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +51 -0
- package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +28 -5
- package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +4 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ui-show-content-event.test.tsx +1 -0
- package/src/features/chat/features/ncp/utils/ncp-chat-query-derived.utils.ts +11 -3
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +27 -0
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +19 -1
- package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +46 -0
- package/src/features/chat/features/session/hooks/__tests__/use-selected-session-context-window-indicator.test.tsx +63 -0
- package/src/features/chat/features/session/hooks/use-selected-session-context-window-indicator.ts +7 -10
- package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +8 -0
- package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +2 -0
- package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +1 -0
- package/src/features/chat/features/session-type/utils/chat-session-type.utils.ts +4 -0
- package/src/features/chat/index.ts +1 -0
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +28 -0
- package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +129 -0
- package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +34 -0
- package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +143 -1
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +30 -0
- package/src/features/chat/managers/__tests__/recent-selection.manager.test.ts +37 -14
- package/src/features/chat/managers/chat-draft-intent.manager.ts +46 -0
- package/src/features/chat/managers/chat-input.manager.ts +13 -13
- package/src/features/chat/managers/chat-run.manager.ts +3 -0
- package/src/features/chat/managers/chat-session-preference-sync.manager.ts +19 -2
- package/src/features/chat/managers/chat-thread.manager.ts +4 -0
- package/src/features/chat/managers/chat-ui.manager.ts +2 -6
- package/src/features/chat/managers/recent-selection.manager.ts +21 -67
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +47 -1
- package/src/features/chat/pages/ncp-chat-page.tsx +18 -0
- package/src/features/chat/stores/chat-input.store.ts +7 -1
- package/src/features/chat/stores/recent-selection.store.ts +85 -0
- package/src/features/chat/types/chat-input-bar.types.ts +0 -1
- package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +137 -0
- package/src/features/panel-apps/components/panel-apps-list.tsx +119 -5
- package/src/features/panel-apps/index.ts +2 -0
- package/src/features/panel-apps/utils/panel-app-entry-match.utils.ts +17 -0
- package/src/shared/lib/api/chat-session-type.types.ts +7 -0
- package/src/shared/lib/api/ncp-session.types.ts +1 -0
- package/src/shared/lib/api/types.ts +3 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +4 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +10 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +4 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +10 -0
- package/dist/assets/app-presenter-provider-BI-ewLbO.js +0 -3
- package/dist/assets/arrow-left-BOCJyLbQ.js +0 -1
- package/dist/assets/book-open-Bd2Xbrrc.js +0 -1
- package/dist/assets/channels-list-page-moJkpxtU.js +0 -8
- package/dist/assets/chat-page-CO27cl4v.js +0 -105
- package/dist/assets/config-split-page-CsQ2rIdq.js +0 -1
- package/dist/assets/desktop-update-config-qa-SynOc.js +0 -1
- package/dist/assets/doc-browser-2ddUJCA0.js +0 -1
- package/dist/assets/doc-browser-BjHUczQb.js +0 -1
- package/dist/assets/es2015-BUIIKCNh.js +0 -41
- package/dist/assets/host-capabilities-9bB_998Z.js +0 -1
- package/dist/assets/index-CMNrRhnG.js +0 -101
- package/dist/assets/index-UTmTW1eU.css +0 -1
- package/dist/assets/key-round-D3vD9dj7.js +0 -1
- package/dist/assets/loader-circle-DV9TSRns.js +0 -1
- package/dist/assets/mcp-marketplace-page-CS5Lh3oA.js +0 -1
- package/dist/assets/mcp-marketplace-page-DEOMcWb0.js +0 -40
- package/dist/assets/model-config-page-BSc4o7pb.js +0 -1
- package/dist/assets/notice-card-qqlbsOoR.js +0 -1
- package/dist/assets/plus-Bf9qhPDO.js +0 -1
- package/dist/assets/popover-BnmPOXe3.js +0 -1
- package/dist/assets/provider-scoped-model-input-Ca56s475.js +0 -1
- package/dist/assets/providers-config-page-D8Kf4U3u.js +0 -1
- package/dist/assets/refresh-cw-Bmx4wc9P.js +0 -1
- package/dist/assets/remote-DNMEy_c4.js +0 -1
- package/dist/assets/rotate-cw-CL0sVBel.js +0 -1
- package/dist/assets/runtime-config-page-CUFtKr0U.js +0 -1
- package/dist/assets/search-D7L-TCH-.js +0 -1
- package/dist/assets/search-config-page-CA3RVAef.js +0 -1
- package/dist/assets/status-dot-Dm4h-bjn.js +0 -1
- package/dist/assets/tabs-CJvw5Lqp.js +0 -1
- package/dist/assets/tooltip-Bxzi6aDL.js +0 -1
- package/dist/assets/trash-2-CSfX-BvU.js +0 -1
- package/dist/assets/x-fkIWvi07.js +0 -1
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-bar-controller.test.tsx +0 -152
- package/src/features/chat/features/input/hooks/use-chat-input-bar-controller.ts +0 -142
- /package/dist/assets/{config-hints-Cl2XAhzV.js → config-hints-CTqBnCDp.js} +0 -0
package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts
CHANGED
|
@@ -42,6 +42,12 @@ function readPurpose(value: unknown): ChatUiShowContentRequest["purpose"] {
|
|
|
42
42
|
: undefined;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function readPlacement(value: unknown): ChatUiShowContentRequest["placement"] {
|
|
46
|
+
return value === "inline" || value === "side_panel"
|
|
47
|
+
? value
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
function readShowContentRequest(value: unknown): ChatUiShowContentRequest | null {
|
|
46
52
|
if (!isRecord(value) || !isRecord(value.target)) {
|
|
47
53
|
return null;
|
|
@@ -57,6 +63,7 @@ function readShowContentRequest(value: unknown): ChatUiShowContentRequest | null
|
|
|
57
63
|
}
|
|
58
64
|
const title = readOptionalString(value.title);
|
|
59
65
|
const purpose = readPurpose(value.purpose);
|
|
66
|
+
const placement = readPlacement(value.placement);
|
|
60
67
|
|
|
61
68
|
if (targetType === "file") {
|
|
62
69
|
const path = readOptionalString(payload.path);
|
|
@@ -74,6 +81,7 @@ function readShowContentRequest(value: unknown): ChatUiShowContentRequest | null
|
|
|
74
81
|
},
|
|
75
82
|
title,
|
|
76
83
|
purpose,
|
|
84
|
+
placement,
|
|
77
85
|
};
|
|
78
86
|
}
|
|
79
87
|
|
|
@@ -91,6 +99,7 @@ function readShowContentRequest(value: unknown): ChatUiShowContentRequest | null
|
|
|
91
99
|
},
|
|
92
100
|
title,
|
|
93
101
|
purpose,
|
|
102
|
+
placement,
|
|
94
103
|
};
|
|
95
104
|
}
|
|
96
105
|
|
|
@@ -107,6 +116,7 @@ function readShowContentRequest(value: unknown): ChatUiShowContentRequest | null
|
|
|
107
116
|
},
|
|
108
117
|
title,
|
|
109
118
|
purpose,
|
|
119
|
+
placement,
|
|
110
120
|
};
|
|
111
121
|
}
|
|
112
122
|
|
|
@@ -147,6 +157,22 @@ export function buildShowContentToolCard(params: {
|
|
|
147
157
|
if (!request) {
|
|
148
158
|
return null;
|
|
149
159
|
}
|
|
160
|
+
const sidePanelRequest: ChatUiShowContentRequest = {
|
|
161
|
+
...request,
|
|
162
|
+
placement: "side_panel",
|
|
163
|
+
};
|
|
164
|
+
const showContentAction = {
|
|
165
|
+
kind: "show-content" as const,
|
|
166
|
+
label: actionLabel,
|
|
167
|
+
request: sidePanelRequest,
|
|
168
|
+
};
|
|
169
|
+
const panelApp = request.target.type === "panel_app" && request.placement === "inline"
|
|
170
|
+
? {
|
|
171
|
+
appId: request.target.payload.appId,
|
|
172
|
+
title: request.title,
|
|
173
|
+
action: showContentAction,
|
|
174
|
+
}
|
|
175
|
+
: undefined;
|
|
150
176
|
return {
|
|
151
177
|
kind: "result",
|
|
152
178
|
name: SHOW_CONTENT_TOOL_NAME,
|
|
@@ -156,10 +182,7 @@ export function buildShowContentToolCard(params: {
|
|
|
156
182
|
hasResult: true,
|
|
157
183
|
statusTone: "success",
|
|
158
184
|
statusLabel,
|
|
159
|
-
action:
|
|
160
|
-
|
|
161
|
-
label: actionLabel,
|
|
162
|
-
request,
|
|
163
|
-
},
|
|
185
|
+
action: showContentAction,
|
|
186
|
+
...(panelApp ? { panelApp } : {}),
|
|
164
187
|
};
|
|
165
188
|
}
|
|
@@ -15,6 +15,7 @@ export type ToolCardViewSource = ToolCard & {
|
|
|
15
15
|
action?: ChatToolPartViewModel["action"];
|
|
16
16
|
fileOperation?: ChatToolPartViewModel["fileOperation"];
|
|
17
17
|
outputData?: unknown;
|
|
18
|
+
panelApp?: ChatToolPartViewModel["panelApp"];
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -133,6 +134,9 @@ export function buildToolCard(
|
|
|
133
134
|
...("fileOperation" in toolCard && toolCard.fileOperation
|
|
134
135
|
? { fileOperation: toolCard.fileOperation }
|
|
135
136
|
: {}),
|
|
137
|
+
...("panelApp" in toolCard && toolCard.panelApp
|
|
138
|
+
? { panelApp: toolCard.panelApp }
|
|
139
|
+
: {}),
|
|
136
140
|
};
|
|
137
141
|
}
|
|
138
142
|
|
|
@@ -6,13 +6,16 @@ import type { ConfigView, ProvidersView, ProviderTemplatesView } from '@/shared/
|
|
|
6
6
|
import type { ChatModelOption } from '@/features/chat/types/chat-input.types';
|
|
7
7
|
import { buildProviderModelCatalog, composeProviderModel, resolveModelThinkingCapability } from '@/shared/lib/provider-models';
|
|
8
8
|
|
|
9
|
-
function buildRuntimeDefaultModelOption(
|
|
9
|
+
function buildRuntimeDefaultModelOption(
|
|
10
|
+
label: string,
|
|
11
|
+
thinkingCapability?: ChatModelOption['thinkingCapability'],
|
|
12
|
+
): ChatModelOption {
|
|
10
13
|
return {
|
|
11
14
|
value: RUNTIME_DEFAULT_MODEL_VALUE,
|
|
12
15
|
modelLabel: label,
|
|
13
16
|
providerLabel: '',
|
|
14
17
|
isRuntimeDefault: true,
|
|
15
|
-
thinkingCapability: null,
|
|
18
|
+
thinkingCapability: thinkingCapability ?? null,
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -50,16 +53,21 @@ export function buildNcpChatProviderModelOptions(params: {
|
|
|
50
53
|
export function filterNcpChatModelOptionsBySessionType(params: {
|
|
51
54
|
modelOptions: ChatModelOption[];
|
|
52
55
|
modelSelectionMode?: RuntimeModelSelectionMode;
|
|
56
|
+
runtimeDefaultThinkingCapability?: ChatModelOption['thinkingCapability'];
|
|
53
57
|
runtimeDefaultModelLabel?: string;
|
|
54
58
|
supportedModels?: string[];
|
|
55
59
|
}): ChatModelOption[] {
|
|
56
60
|
const {
|
|
57
61
|
modelOptions,
|
|
58
62
|
modelSelectionMode,
|
|
63
|
+
runtimeDefaultThinkingCapability,
|
|
59
64
|
runtimeDefaultModelLabel = 'Runtime default',
|
|
60
65
|
supportedModels,
|
|
61
66
|
} = params;
|
|
62
|
-
const runtimeDefaultOption = buildRuntimeDefaultModelOption(
|
|
67
|
+
const runtimeDefaultOption = buildRuntimeDefaultModelOption(
|
|
68
|
+
runtimeDefaultModelLabel,
|
|
69
|
+
runtimeDefaultThinkingCapability,
|
|
70
|
+
);
|
|
63
71
|
if (modelSelectionMode === 'runtime-default') {
|
|
64
72
|
return [runtimeDefaultOption];
|
|
65
73
|
}
|
|
@@ -45,6 +45,7 @@ describe('ChatSessionHeaderActions', () => {
|
|
|
45
45
|
|
|
46
46
|
expect(screen.getByText('Set Project Directory')).toBeTruthy();
|
|
47
47
|
expect(screen.queryByText('Clear Project Directory')).toBeNull();
|
|
48
|
+
expect(screen.getByText('View Metadata')).toBeTruthy();
|
|
48
49
|
expect(screen.getByText('Delete Session')).toBeTruthy();
|
|
49
50
|
});
|
|
50
51
|
|
|
@@ -67,6 +68,32 @@ describe('ChatSessionHeaderActions', () => {
|
|
|
67
68
|
|
|
68
69
|
expect(screen.getByText('Set Project Directory')).toBeTruthy();
|
|
69
70
|
expect(screen.queryByText('Clear Project Directory')).toBeNull();
|
|
71
|
+
expect(screen.getByText('View Metadata')).toBeTruthy();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('opens the session metadata dialog from the more-actions menu', async () => {
|
|
75
|
+
const user = userEvent.setup();
|
|
76
|
+
|
|
77
|
+
render(
|
|
78
|
+
<ChatSessionHeaderActions
|
|
79
|
+
sessionKey="session-codex"
|
|
80
|
+
canDeleteSession
|
|
81
|
+
isDeletePending={false}
|
|
82
|
+
projectRoot={null}
|
|
83
|
+
metadata={{
|
|
84
|
+
codex_thread_id: 'thread-123',
|
|
85
|
+
runtime: 'codex',
|
|
86
|
+
}}
|
|
87
|
+
onDeleteSession={mocks.onDeleteSession}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
await user.click(screen.getByRole('button', { name: 'More actions' }));
|
|
92
|
+
await user.click(screen.getByRole('button', { name: 'View Metadata' }));
|
|
93
|
+
|
|
94
|
+
expect(screen.getByRole('dialog', { name: 'Session Metadata' })).toBeTruthy();
|
|
95
|
+
expect(screen.getByText(/codex_thread_id/)).toBeTruthy();
|
|
96
|
+
expect(screen.getByText(/thread-123/)).toBeTruthy();
|
|
70
97
|
});
|
|
71
98
|
|
|
72
99
|
it('shows a dedicated child-session entry button when the current session has child sessions', async () => {
|
package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { AlarmClock, FolderOpen, GitBranch, MoreVertical, Trash2 } from 'lucide-react';
|
|
2
|
+
import { AlarmClock, Braces, FolderOpen, GitBranch, MoreVertical, Trash2 } from 'lucide-react';
|
|
3
3
|
import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
|
|
4
4
|
import { Popover, PopoverContent, PopoverTrigger } from '@/shared/components/ui/popover';
|
|
5
5
|
import { useChatSessionProject } from '@/features/chat/features/session/hooks/use-chat-session-project';
|
|
6
6
|
import { ChatSessionHeaderMenuItem } from './chat-session-header-menu-item';
|
|
7
|
+
import { ChatSessionMetadataDialog } from './chat-session-metadata-dialog';
|
|
7
8
|
import { ChatSessionProjectDialog } from './chat-session-project-dialog';
|
|
8
9
|
import { t } from '@/shared/lib/i18n';
|
|
9
10
|
|
|
@@ -15,6 +16,7 @@ type ChatSessionHeaderActionsProps = {
|
|
|
15
16
|
canDeleteSession: boolean;
|
|
16
17
|
isDeletePending: boolean;
|
|
17
18
|
projectRoot?: string | null;
|
|
19
|
+
metadata?: Record<string, unknown> | null;
|
|
18
20
|
childSessionCount?: number;
|
|
19
21
|
sessionCronJobCount?: number;
|
|
20
22
|
onOpenChildSessions?: () => void;
|
|
@@ -27,6 +29,7 @@ export function ChatSessionHeaderActions({
|
|
|
27
29
|
canDeleteSession,
|
|
28
30
|
isDeletePending,
|
|
29
31
|
projectRoot,
|
|
32
|
+
metadata,
|
|
30
33
|
childSessionCount = 0,
|
|
31
34
|
sessionCronJobCount = 0,
|
|
32
35
|
onOpenChildSessions,
|
|
@@ -36,6 +39,7 @@ export function ChatSessionHeaderActions({
|
|
|
36
39
|
const updateSessionProject = useChatSessionProject();
|
|
37
40
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
38
41
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
42
|
+
const [isMetadataDialogOpen, setIsMetadataDialogOpen] = useState(false);
|
|
39
43
|
const [isProjectPending, setIsProjectPending] = useState(false);
|
|
40
44
|
const isBusy = isDeletePending || isProjectPending;
|
|
41
45
|
|
|
@@ -96,6 +100,15 @@ export function ChatSessionHeaderActions({
|
|
|
96
100
|
}}
|
|
97
101
|
disabled={isBusy}
|
|
98
102
|
/>
|
|
103
|
+
<ChatSessionHeaderMenuItem
|
|
104
|
+
icon={Braces}
|
|
105
|
+
label={t('chatSessionViewMetadata')}
|
|
106
|
+
onClick={() => {
|
|
107
|
+
setIsMenuOpen(false);
|
|
108
|
+
setIsMetadataDialogOpen(true);
|
|
109
|
+
}}
|
|
110
|
+
disabled={isBusy}
|
|
111
|
+
/>
|
|
99
112
|
<ChatSessionHeaderMenuItem
|
|
100
113
|
icon={Trash2}
|
|
101
114
|
label={t('chatDeleteSession')}
|
|
@@ -117,6 +130,11 @@ export function ChatSessionHeaderActions({
|
|
|
117
130
|
onOpenChange={setIsDialogOpen}
|
|
118
131
|
onSave={runProjectUpdate}
|
|
119
132
|
/>
|
|
133
|
+
<ChatSessionMetadataDialog
|
|
134
|
+
open={isMetadataDialogOpen}
|
|
135
|
+
metadata={metadata}
|
|
136
|
+
onOpenChange={setIsMetadataDialogOpen}
|
|
137
|
+
/>
|
|
120
138
|
</div>
|
|
121
139
|
);
|
|
122
140
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dialog,
|
|
3
|
+
DialogContent,
|
|
4
|
+
DialogDescription,
|
|
5
|
+
DialogHeader,
|
|
6
|
+
DialogTitle,
|
|
7
|
+
} from '@/shared/components/ui/dialog';
|
|
8
|
+
import { t } from '@/shared/lib/i18n';
|
|
9
|
+
|
|
10
|
+
type ChatSessionMetadataDialogProps = {
|
|
11
|
+
open: boolean;
|
|
12
|
+
metadata?: Record<string, unknown> | null;
|
|
13
|
+
onOpenChange: (open: boolean) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function formatSessionMetadata(metadata?: Record<string, unknown> | null): string {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.stringify(metadata ?? {}, null, 2);
|
|
19
|
+
} catch {
|
|
20
|
+
return '{}';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ChatSessionMetadataDialog({
|
|
25
|
+
open,
|
|
26
|
+
metadata,
|
|
27
|
+
onOpenChange,
|
|
28
|
+
}: ChatSessionMetadataDialogProps) {
|
|
29
|
+
return (
|
|
30
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
31
|
+
<DialogContent className="max-w-2xl gap-0 overflow-hidden p-0">
|
|
32
|
+
<DialogHeader className="border-b border-gray-200 px-5 py-4 pr-12">
|
|
33
|
+
<DialogTitle className="text-base">{t('chatSessionMetadataDialogTitle')}</DialogTitle>
|
|
34
|
+
<DialogDescription className="sr-only">
|
|
35
|
+
{t('chatSessionMetadataDialogDescription')}
|
|
36
|
+
</DialogDescription>
|
|
37
|
+
</DialogHeader>
|
|
38
|
+
<div className="px-5 pb-5 pt-4">
|
|
39
|
+
<pre className="max-h-[60vh] overflow-auto rounded-lg border border-gray-200 bg-gray-950 p-4 text-xs leading-relaxed text-gray-100">
|
|
40
|
+
{formatSessionMetadata(metadata)}
|
|
41
|
+
</pre>
|
|
42
|
+
</div>
|
|
43
|
+
</DialogContent>
|
|
44
|
+
</Dialog>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import { useSelectedSessionContextWindowIndicator } from '@/features/chat/features/session/hooks/use-selected-session-context-window-indicator';
|
|
4
|
+
import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
|
|
5
|
+
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
6
|
+
|
|
7
|
+
describe('useSelectedSessionContextWindowIndicator', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
useChatSessionListStore.getState().setSnapshot({
|
|
10
|
+
selectedSessionKey: null,
|
|
11
|
+
});
|
|
12
|
+
useChatThreadStore.getState().setSnapshot({
|
|
13
|
+
sessionKey: null,
|
|
14
|
+
contextWindow: null,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('shows the current thread context window once the thread snapshot has it', () => {
|
|
19
|
+
useChatSessionListStore.getState().setSnapshot({
|
|
20
|
+
selectedSessionKey: 'session-other',
|
|
21
|
+
});
|
|
22
|
+
useChatThreadStore.getState().setSnapshot({
|
|
23
|
+
sessionKey: 'session-current',
|
|
24
|
+
contextWindow: {
|
|
25
|
+
usedContextTokens: 25,
|
|
26
|
+
totalContextTokens: 100,
|
|
27
|
+
availableContextTokens: 75,
|
|
28
|
+
prunedUsedContextTokens: 25,
|
|
29
|
+
droppedHistoryCount: 0,
|
|
30
|
+
truncatedToolResultCount: 0,
|
|
31
|
+
truncatedSystemPrompt: false,
|
|
32
|
+
truncatedUserMessage: false,
|
|
33
|
+
compacted: false,
|
|
34
|
+
compactedUsedContextTokens: 25,
|
|
35
|
+
compactedMessageCount: 0,
|
|
36
|
+
updatedAt: '2026-06-10T00:00:00.000Z',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const { result } = renderHook(() =>
|
|
41
|
+
useSelectedSessionContextWindowIndicator(),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(result.current).toMatchObject({
|
|
45
|
+
percentLabel: '25%',
|
|
46
|
+
ratio: 0.25,
|
|
47
|
+
tone: 'neutral',
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('hides the indicator when the current thread snapshot has no context window', () => {
|
|
52
|
+
useChatThreadStore.getState().setSnapshot({
|
|
53
|
+
sessionKey: 'session-current',
|
|
54
|
+
contextWindow: null,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const { result } = renderHook(() =>
|
|
58
|
+
useSelectedSessionContextWindowIndicator(),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(result.current).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
});
|
package/src/features/chat/features/session/hooks/use-selected-session-context-window-indicator.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import type { ChatContextWindowIndicator } from '@nextclaw/agent-chat-ui';
|
|
3
|
-
import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
|
|
4
3
|
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
5
4
|
import { buildChatContextWindowIndicator } from '@/features/chat/features/session/utils/chat-context-window-indicator.utils';
|
|
6
5
|
|
|
7
6
|
export function useSelectedSessionContextWindowIndicator(): ChatContextWindowIndicator | null {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const liveContextWindow = useChatThreadStore(
|
|
8
|
+
(state) => state.snapshot.contextWindow,
|
|
9
|
+
);
|
|
11
10
|
|
|
12
|
-
return useMemo(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return null;
|
|
17
|
-
}, [liveContextWindow, liveSessionKey, selectedSessionKey]);
|
|
11
|
+
return useMemo(
|
|
12
|
+
() => buildChatContextWindowIndicator(liveContextWindow),
|
|
13
|
+
[liveContextWindow],
|
|
14
|
+
);
|
|
18
15
|
}
|
package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts
CHANGED
|
@@ -43,6 +43,14 @@ describe('adaptNcpSessionSummary', () => {
|
|
|
43
43
|
projectRoot: '/Users/demo/workspace/project-alpha',
|
|
44
44
|
workingDir: '/Users/demo/workspace/project-alpha',
|
|
45
45
|
projectName: 'project-alpha',
|
|
46
|
+
metadata: {
|
|
47
|
+
label: 'NCP Planning Thread',
|
|
48
|
+
model: 'openai/gpt-5',
|
|
49
|
+
preferred_thinking: 'medium',
|
|
50
|
+
project_root: '/Users/demo/workspace/project-alpha',
|
|
51
|
+
session_type: 'native',
|
|
52
|
+
ui_last_read_at: '2026-03-17T23:59:00.000Z'
|
|
53
|
+
},
|
|
46
54
|
lastMessageAt: '2026-03-18T00:00:00.000Z',
|
|
47
55
|
readAt: '2026-03-17T23:59:00.000Z',
|
|
48
56
|
sessionType: 'native',
|
|
@@ -308,6 +308,7 @@ export function adaptNcpMessagesToUiMessages(messages: readonly NcpMessageView[]
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionEntryView {
|
|
311
|
+
const metadata = readMetadata(summary);
|
|
311
312
|
const label = readNcpSessionLabel(summary);
|
|
312
313
|
const preferredModel = readNcpSessionPreferredModel(summary);
|
|
313
314
|
const preferredThinking = readNcpSessionPreferredThinking(summary);
|
|
@@ -335,6 +336,7 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
|
|
|
335
336
|
...(projectRoot ? { projectRoot } : {}),
|
|
336
337
|
...(workingDir ? { workingDir } : {}),
|
|
337
338
|
...(projectName ? { projectName } : {}),
|
|
339
|
+
...(metadata ? { metadata } : {}),
|
|
338
340
|
sessionType: readNcpSessionType(summary),
|
|
339
341
|
sessionTypeMutable: false,
|
|
340
342
|
status: summary.status ?? 'idle',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AgentProfileView,
|
|
3
3
|
ChatSessionTypeOptionView,
|
|
4
|
+
RuntimeDefaultThinkingView,
|
|
4
5
|
SessionTypeIconView,
|
|
5
6
|
} from "@/shared/lib/api";
|
|
6
7
|
import type { RuntimeModelSelectionMode } from "@nextclaw/shared";
|
|
@@ -18,6 +19,7 @@ export type ChatSessionTypeOption = {
|
|
|
18
19
|
supportedModels?: string[];
|
|
19
20
|
recommendedModel?: string | null;
|
|
20
21
|
modelSelectionMode?: RuntimeModelSelectionMode;
|
|
22
|
+
runtimeDefaultThinking?: RuntimeDefaultThinkingView | null;
|
|
21
23
|
cta?: {
|
|
22
24
|
kind: string;
|
|
23
25
|
label?: string;
|
|
@@ -76,6 +78,7 @@ export function buildSessionTypeOptions(
|
|
|
76
78
|
supportedModels: option.supportedModels,
|
|
77
79
|
recommendedModel: option.recommendedModel ?? null,
|
|
78
80
|
modelSelectionMode: option.modelSelectionMode ?? "nextclaw",
|
|
81
|
+
runtimeDefaultThinking: option.runtimeDefaultThinking ?? null,
|
|
79
82
|
cta: option.cta ?? null,
|
|
80
83
|
});
|
|
81
84
|
}
|
|
@@ -90,6 +93,7 @@ export function buildSessionTypeOptions(
|
|
|
90
93
|
supportedModels: undefined,
|
|
91
94
|
recommendedModel: null,
|
|
92
95
|
modelSelectionMode: "nextclaw",
|
|
96
|
+
runtimeDefaultThinking: null,
|
|
93
97
|
cta: null,
|
|
94
98
|
});
|
|
95
99
|
}
|
|
@@ -3,6 +3,7 @@ export { ChatSidebar } from "./components/layout/chat-sidebar";
|
|
|
3
3
|
export { ChatPresenterProvider } from "./components/providers/chat-presenter.provider";
|
|
4
4
|
export { usePresenter } from "./components/providers/chat-presenter.provider";
|
|
5
5
|
export { ChatPresenter } from "./presenters/chat.presenter";
|
|
6
|
+
export { ChatDraftIntentManager } from "./managers/chat-draft-intent.manager";
|
|
6
7
|
export { useChatInputStore } from "./stores/chat-input.store";
|
|
7
8
|
export { useChatSessionListStore } from "./stores/chat-session-list.store";
|
|
8
9
|
export { useChatThreadStore } from "./stores/chat-thread.store";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createChatComposerTextNode } from '@nextclaw/agent-chat-ui';
|
|
2
|
+
import { RUNTIME_DEFAULT_MODEL_VALUE } from '@nextclaw/shared';
|
|
2
3
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
4
|
import { ChatInputManager } from '@/features/chat/managers/chat-input.manager';
|
|
4
5
|
import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
|
|
@@ -322,6 +323,33 @@ describe('ChatInputManager configuration sync', () => {
|
|
|
322
323
|
});
|
|
323
324
|
});
|
|
324
325
|
|
|
326
|
+
it('sends the runtime-default sentinel so the backend can ignore stale session model metadata', async () => {
|
|
327
|
+
useChatInputStore.setState({
|
|
328
|
+
snapshot: {
|
|
329
|
+
...useChatInputStore.getState().snapshot,
|
|
330
|
+
selectedSessionType: 'codex',
|
|
331
|
+
selectedModel: RUNTIME_DEFAULT_MODEL_VALUE,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
const chatRunManager = {
|
|
335
|
+
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
336
|
+
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
337
|
+
} as unknown as ConstructorParameters<typeof ChatInputManager>[0];
|
|
338
|
+
const sessionListManager = {
|
|
339
|
+
ensureDraftSession: vi.fn(() => 'draft-session'),
|
|
340
|
+
} as unknown as ConstructorParameters<typeof ChatInputManager>[1];
|
|
341
|
+
const manager = new ChatInputManager(chatRunManager, sessionListManager);
|
|
342
|
+
|
|
343
|
+
await manager.send();
|
|
344
|
+
|
|
345
|
+
expect(chatRunManager.sendMessage).toHaveBeenCalledWith(
|
|
346
|
+
expect.objectContaining({
|
|
347
|
+
sessionType: 'codex',
|
|
348
|
+
model: RUNTIME_DEFAULT_MODEL_VALUE,
|
|
349
|
+
}),
|
|
350
|
+
);
|
|
351
|
+
});
|
|
352
|
+
|
|
325
353
|
it('resolves and clears pending project root overrides inside the input manager', () => {
|
|
326
354
|
useChatInputStore.setState({
|
|
327
355
|
snapshot: {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { NcpMessage } from '@nextclaw/ncp';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
|
|
4
|
+
import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
|
|
5
|
+
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
6
|
+
|
|
7
|
+
function createChatRunManager() {
|
|
8
|
+
const uiManager = {
|
|
9
|
+
isAtChatRoot: vi.fn(() => true),
|
|
10
|
+
goToSession: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
return new ChatRunManager(
|
|
13
|
+
uiManager as unknown as ConstructorParameters<typeof ChatRunManager>[0],
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('ChatRunManager run snapshots', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
useChatInputStore.setState({
|
|
20
|
+
snapshot: {
|
|
21
|
+
...useChatInputStore.getState().snapshot,
|
|
22
|
+
canStopGeneration: false,
|
|
23
|
+
sendError: null,
|
|
24
|
+
isSending: false,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
useChatThreadStore.setState({
|
|
28
|
+
snapshot: {
|
|
29
|
+
...useChatThreadStore.getState().snapshot,
|
|
30
|
+
sessionKey: null,
|
|
31
|
+
isHistoryLoading: false,
|
|
32
|
+
messages: [],
|
|
33
|
+
isSending: false,
|
|
34
|
+
isAwaitingAssistantOutput: false,
|
|
35
|
+
contextWindow: null,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('syncs the run snapshot into chat input and thread state', () => {
|
|
41
|
+
const manager = createChatRunManager();
|
|
42
|
+
const message: NcpMessage = {
|
|
43
|
+
id: 'message-1',
|
|
44
|
+
sessionId: 'session-1',
|
|
45
|
+
role: 'assistant',
|
|
46
|
+
status: 'final',
|
|
47
|
+
parts: [{ type: 'text', text: 'done' }],
|
|
48
|
+
timestamp: '2026-06-10T00:00:00.000Z',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
manager.applyRunSnapshot({
|
|
52
|
+
routeSessionKey: 'session-1',
|
|
53
|
+
isHydrating: true,
|
|
54
|
+
isSending: false,
|
|
55
|
+
isRunning: true,
|
|
56
|
+
visibleMessages: [message],
|
|
57
|
+
contextWindow: {
|
|
58
|
+
usedContextTokens: 10,
|
|
59
|
+
totalContextTokens: 100,
|
|
60
|
+
availableContextTokens: 90,
|
|
61
|
+
prunedUsedContextTokens: 10,
|
|
62
|
+
droppedHistoryCount: 0,
|
|
63
|
+
truncatedToolResultCount: 0,
|
|
64
|
+
truncatedSystemPrompt: false,
|
|
65
|
+
truncatedUserMessage: false,
|
|
66
|
+
compacted: false,
|
|
67
|
+
compactedUsedContextTokens: 10,
|
|
68
|
+
compactedMessageCount: 0,
|
|
69
|
+
updatedAt: '2026-06-10T00:00:00.000Z',
|
|
70
|
+
},
|
|
71
|
+
sendErrorMessage: 'failed',
|
|
72
|
+
materializedSessionKey: null,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(useChatInputStore.getState().snapshot).toMatchObject({
|
|
76
|
+
canStopGeneration: true,
|
|
77
|
+
sendError: 'failed',
|
|
78
|
+
isSending: true,
|
|
79
|
+
});
|
|
80
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
81
|
+
sessionKey: 'session-1',
|
|
82
|
+
isHistoryLoading: true,
|
|
83
|
+
messages: [message],
|
|
84
|
+
isSending: true,
|
|
85
|
+
isAwaitingAssistantOutput: true,
|
|
86
|
+
contextWindow: {
|
|
87
|
+
usedContextTokens: 10,
|
|
88
|
+
totalContextTokens: 100,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('resets stale context window state when switching sessions', () => {
|
|
94
|
+
const manager = createChatRunManager();
|
|
95
|
+
useChatThreadStore.getState().setSnapshot({
|
|
96
|
+
sessionKey: 'session-previous',
|
|
97
|
+
contextWindow: {
|
|
98
|
+
usedContextTokens: 80,
|
|
99
|
+
totalContextTokens: 100,
|
|
100
|
+
availableContextTokens: 20,
|
|
101
|
+
prunedUsedContextTokens: 80,
|
|
102
|
+
droppedHistoryCount: 0,
|
|
103
|
+
truncatedToolResultCount: 0,
|
|
104
|
+
truncatedSystemPrompt: false,
|
|
105
|
+
truncatedUserMessage: false,
|
|
106
|
+
compacted: false,
|
|
107
|
+
compactedUsedContextTokens: 80,
|
|
108
|
+
compactedMessageCount: 0,
|
|
109
|
+
updatedAt: '2026-06-10T00:00:00.000Z',
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
manager.applyRunSnapshot({
|
|
114
|
+
routeSessionKey: 'session-next',
|
|
115
|
+
isHydrating: true,
|
|
116
|
+
isSending: false,
|
|
117
|
+
isRunning: false,
|
|
118
|
+
visibleMessages: [],
|
|
119
|
+
contextWindow: null,
|
|
120
|
+
sendErrorMessage: null,
|
|
121
|
+
materializedSessionKey: null,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
125
|
+
sessionKey: 'session-next',
|
|
126
|
+
contextWindow: null,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|