@nextclaw/ui 0.15.5 → 0.15.6
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 +24 -0
- package/dist/assets/api-Dyb5rgh0.js +13 -0
- package/dist/assets/app-presenter-provider-7jV0nPiL.js +106 -0
- package/dist/assets/{appearance-settings-page-80fLa4-9.js → appearance-settings-page-DFm3BbEb.js} +1 -1
- package/dist/assets/{book-open-CN7Shpij.js → book-open-L6LCuqOr.js} +1 -1
- package/dist/assets/{channels-list-page-R7M6pzp6.js → channels-list-page-DOaPKNJA.js} +2 -2
- package/dist/assets/{chat-page-152YuwfU.js → chat-page-Cw9uDhD_.js} +1 -1
- package/dist/assets/{config-split-page-BlfvfmTm.js → config-split-page-CqlqjnCK.js} +1 -1
- package/dist/assets/{confirm-dialog-Dt8Kdbgp.js → confirm-dialog-bAp8NU93.js} +1 -1
- package/dist/assets/desktop-update-config-2SjMfCMl.js +1 -0
- package/dist/assets/{dist-D5ycbwCo.js → dist-CrtbGLlh.js} +1 -1
- package/dist/assets/doc-browser-A5IRyDXQ2.js +1 -0
- package/dist/assets/{doc-browser-C9u7N9vH.js → doc-browser-BmnQa30-.js} +1 -1
- package/dist/assets/{doc-browser-context-CCdW9TJf.js → doc-browser-context-BlpUUFrR.js} +1 -1
- package/dist/assets/{download-CyPgglKr.js → download-BhZZNXD8.js} +1 -1
- package/dist/assets/{external-link-BahW1sl3.js → external-link-B9Uoq5J7.js} +1 -1
- package/dist/assets/{eye-D8gGXMkD.js → eye-K5knauEM.js} +1 -1
- package/dist/assets/{folder-DGWqFDvE.js → folder-BIYZZWJv.js} +1 -1
- package/dist/assets/{form-actions-D2imZLnU.js → form-actions-CKprWFeM.js} +1 -1
- package/dist/assets/index-BVn702m4.css +1 -0
- package/dist/assets/{index-CT0iYlxV.js → index-CFxJ1Fkg.js} +2 -2
- package/dist/assets/loader-circle-Bj2RMrCR.js +1 -0
- package/dist/assets/{logo-badge-CuPetu-v.js → logo-badge-BQ-XFZod.js} +1 -1
- package/dist/assets/mcp-marketplace-page-BfA7A3n4.js +9 -0
- package/dist/assets/mcp-marketplace-page-CXHYO95j.js +1 -0
- package/dist/assets/{model-config-page-DiJcvBML.js → model-config-page-BbWbW_qM.js} +1 -1
- package/dist/assets/plus-TmFdwO3P.js +1 -0
- package/dist/assets/{popover-Btp7KVc3.js → popover-CX9zFsuc.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-DZMtoAJG.js → provider-scoped-model-input-B-9_DtIP.js} +1 -1
- package/dist/assets/providers-config-page-xwIjGur2.js +1 -0
- package/dist/assets/{react-BY-U3h3t.js → react-DsgudwpT.js} +1 -1
- package/dist/assets/{refresh-cw-Cme5qFej.js → refresh-cw-kXdvbhFf.js} +1 -1
- package/dist/assets/remote-B9F2uCk9.js +1 -0
- package/dist/assets/rotate-cw-DEc6aihr.js +1 -0
- package/dist/assets/runtime-config-page-DTsi7czJ.js +1 -0
- package/dist/assets/{save-DNg2yJTS.js → save-C9qpz2_2.js} +1 -1
- package/dist/assets/{search-3ykfrAFt.js → search-_kEi29qo.js} +1 -1
- package/dist/assets/{search-config-page-DrVKa_Ls.js → search-config-page-H7ug8f4D.js} +1 -1
- package/dist/assets/{secrets-config-page-B39uQ5kF.js → secrets-config-page-C1_6rINu.js} +2 -2
- package/dist/assets/security-config-Cr2ERKLu.js +1 -0
- package/dist/assets/{select-B5EecXJt.js → select-MJKhKAWR.js} +1 -1
- package/dist/assets/{setting-row-Qv1Ps79v.js → setting-row-DA3tJuew.js} +1 -1
- package/dist/assets/{settings-2-D-3um9Az.js → settings-2-D_y7zXJz.js} +1 -1
- package/dist/assets/{skeleton-BgkzQd4U.js → skeleton-0uTLUhm9.js} +1 -1
- package/dist/assets/{tabs-BVD8gp3y.js → tabs-h44XPocd.js} +1 -1
- package/dist/assets/{tag-chip-0qn-f3Bo.js → tag-chip-bRZ04q3C.js} +1 -1
- package/dist/assets/{trash-2-cnsMppRC.js → trash-2-BRpSE-Wt.js} +1 -1
- package/dist/assets/use-config-CwDEAl6f.js +1 -0
- package/dist/assets/{use-confirm-dialog-C_o0eQeH.js → use-confirm-dialog-BbnWw-8c.js} +1 -1
- package/dist/assets/{use-viewport-layout-BWav4KCL.js → use-viewport-layout-CqgIMDQL.js} +1 -1
- package/dist/assets/x-DdEDL6ju.js +1 -0
- package/dist/index.html +29 -29
- package/package.json +8 -8
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +84 -1
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +13 -2
- package/src/features/chat/components/layout/__tests__/chat-sidebar-test.utils.ts +22 -0
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +13 -24
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +16 -4
- package/src/features/chat/components/layout/chat-sidebar.tsx +38 -2
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -0
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +52 -2
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +22 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +8 -9
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +55 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +12 -11
- package/src/features/chat/features/conversation/hooks/use-session-conversation-preference-actions.ts +94 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +37 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +140 -63
- package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +175 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +23 -0
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +8 -1
- package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +274 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +4 -1
- package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +26 -0
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +50 -4
- package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +4 -10
- package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +29 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +66 -8
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +1 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +41 -3
- package/src/features/chat/features/project/components/__tests__/chat-project-create-dialog.test.tsx +67 -0
- package/src/features/chat/features/project/components/chat-project-create-dialog.tsx +203 -0
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +7 -7
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-dialog.test.tsx +47 -0
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +6 -3
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -0
- package/src/features/chat/features/session/components/session-header/chat-session-project-dialog.tsx +3 -0
- package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +245 -0
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +81 -127
- package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +22 -0
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +6 -6
- package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +12 -1
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +3 -0
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +1 -0
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +20 -0
- package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +15 -2
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +23 -0
- package/src/features/settings/components/config/provider-form.tsx +0 -4
- package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +7 -9
- package/src/features/settings/utils/provider-form-model.utils.ts +0 -4
- package/src/features/system-status/hooks/use-current-version-release-notes-link.ts +33 -0
- package/src/features/system-status/index.ts +9 -0
- package/src/features/system-status/utils/update-release-notes.utils.ts +44 -3
- package/src/index.css +10 -16
- package/src/shared/components/common/__tests__/brand-header.test.tsx +110 -3
- package/src/shared/components/common/brand-header.tsx +121 -35
- package/src/shared/components/path-picker/__tests__/server-path-picker-dialog.test.tsx +288 -38
- package/src/shared/components/path-picker/server-path-picker-dialog.tsx +290 -199
- package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +103 -0
- package/src/shared/components/path-picker/server-path-picker-footer.tsx +58 -0
- package/src/shared/components/path-picker/server-path-picker-locations.tsx +148 -0
- package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +198 -0
- package/src/shared/components/path-picker/server-path-picker-new-folder.tsx +59 -0
- package/src/shared/components/path-picker/server-path-picker-toolbar.tsx +25 -0
- package/src/shared/hooks/use-projects.ts +28 -0
- package/src/shared/hooks/use-server-path-create-directory.ts +6 -0
- package/src/shared/hooks/use-server-path-search.ts +40 -0
- package/src/shared/lib/api/index.ts +1 -0
- package/src/shared/lib/api/projects/project.utils.ts +14 -0
- package/src/shared/lib/api/types.ts +27 -0
- package/src/shared/lib/api/utils/server-path.utils.ts +21 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +35 -2
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -3
- package/src/shared/lib/i18n/locales/en-US/desktop-update.json +4 -0
- package/src/shared/lib/i18n/locales/en-US/path-picker.json +25 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +35 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -3
- package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +4 -0
- package/src/shared/lib/i18n/locales/zh-CN/path-picker.json +25 -0
- package/dist/assets/api-IlX_CbwU.js +0 -13
- package/dist/assets/app-presenter-provider-Bbl_MVbg.js +0 -106
- package/dist/assets/desktop-update-config-DreMJu9z.js +0 -1
- package/dist/assets/doc-browser-DJQ5XPsi2.js +0 -1
- package/dist/assets/index-0av6WO9-.css +0 -1
- package/dist/assets/loader-circle-DtKek-qG.js +0 -1
- package/dist/assets/mcp-marketplace-page-4qMwFDpz.js +0 -1
- package/dist/assets/mcp-marketplace-page-BbxN5aZF.js +0 -9
- package/dist/assets/plus-BwgE-Zwf.js +0 -1
- package/dist/assets/providers-config-page-DfiWveEF.js +0 -1
- package/dist/assets/remote-BaVECEZw.js +0 -1
- package/dist/assets/rotate-cw-CMRZxIkH.js +0 -1
- package/dist/assets/runtime-config-page-wygwvMB4.js +0 -1
- package/dist/assets/security-config-BPzet76z.js +0 -1
- package/dist/assets/use-config-D6yx0lb0.js +0 -1
- package/dist/assets/x-B3o1dWR5.js +0 -1
- package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +0 -77
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +0 -144
|
@@ -86,6 +86,7 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
86
86
|
const state = resolveChatInputSurfaceState({
|
|
87
87
|
data: {
|
|
88
88
|
isPanelAppsLoading: false,
|
|
89
|
+
isServerPathSearchLoading: false,
|
|
89
90
|
isSkillsLoading: false,
|
|
90
91
|
panelApps: [
|
|
91
92
|
createPanelApp({
|
|
@@ -94,7 +95,11 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
94
95
|
description: 'Track tasks',
|
|
95
96
|
}),
|
|
96
97
|
],
|
|
98
|
+
projectRoot: '/tmp/project',
|
|
97
99
|
recentSkillValues: [],
|
|
100
|
+
referenceMode: 'root',
|
|
101
|
+
serverPathEntries: [],
|
|
102
|
+
serverPathSearchError: null,
|
|
98
103
|
skillRecords: [
|
|
99
104
|
{
|
|
100
105
|
key: 'web-search',
|
|
@@ -125,16 +130,19 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
125
130
|
{ key: 'panel-apps', label: 'Panel Apps', sectionKeys: ['panel-apps'] },
|
|
126
131
|
]);
|
|
127
132
|
expect(state.panel?.items[0]).toMatchObject({
|
|
133
|
+
icon: 'command',
|
|
128
134
|
sectionKey: 'commands',
|
|
129
135
|
sectionLabel: 'Commands',
|
|
130
136
|
hintLabel: 'Run command',
|
|
131
137
|
});
|
|
132
138
|
expect(state.panel?.items[1]).toMatchObject({
|
|
139
|
+
icon: 'skill',
|
|
133
140
|
sectionKey: 'skills',
|
|
134
141
|
sectionLabel: 'Skills',
|
|
135
142
|
hintLabel: 'Add skill',
|
|
136
143
|
});
|
|
137
144
|
expect(state.panel?.items[2]).toMatchObject({
|
|
145
|
+
icon: 'panel-app',
|
|
138
146
|
sectionKey: 'panel-apps',
|
|
139
147
|
sectionLabel: 'Panel Apps',
|
|
140
148
|
hintLabel: 'Open panel app',
|
|
@@ -145,9 +153,14 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
145
153
|
const state = resolveChatInputSurfaceState({
|
|
146
154
|
data: {
|
|
147
155
|
isPanelAppsLoading: false,
|
|
156
|
+
isServerPathSearchLoading: false,
|
|
148
157
|
isSkillsLoading: false,
|
|
149
158
|
panelApps: [],
|
|
159
|
+
projectRoot: '/tmp/project',
|
|
150
160
|
recentSkillValues: [],
|
|
161
|
+
referenceMode: 'root',
|
|
162
|
+
serverPathEntries: [],
|
|
163
|
+
serverPathSearchError: null,
|
|
151
164
|
skillRecords: [
|
|
152
165
|
{
|
|
153
166
|
key: 'project:review',
|
|
@@ -189,9 +202,14 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
189
202
|
const state = resolveChatInputSurfaceState({
|
|
190
203
|
data: {
|
|
191
204
|
isPanelAppsLoading: false,
|
|
205
|
+
isServerPathSearchLoading: false,
|
|
192
206
|
isSkillsLoading: false,
|
|
193
207
|
panelApps: [],
|
|
208
|
+
projectRoot: '/tmp/project',
|
|
194
209
|
recentSkillValues: [],
|
|
210
|
+
referenceMode: 'root',
|
|
211
|
+
serverPathEntries: [],
|
|
212
|
+
serverPathSearchError: null,
|
|
195
213
|
skillRecords: [],
|
|
196
214
|
},
|
|
197
215
|
plugins: [createPlugin({ onSelectCommand })],
|
|
@@ -215,6 +233,7 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
215
233
|
const state = resolveChatInputSurfaceState({
|
|
216
234
|
data: {
|
|
217
235
|
isPanelAppsLoading: false,
|
|
236
|
+
isServerPathSearchLoading: false,
|
|
218
237
|
isSkillsLoading: false,
|
|
219
238
|
panelApps: [
|
|
220
239
|
createPanelApp({
|
|
@@ -223,7 +242,11 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
223
242
|
description: 'Track tasks',
|
|
224
243
|
}),
|
|
225
244
|
],
|
|
245
|
+
projectRoot: '/tmp/project',
|
|
226
246
|
recentSkillValues: [],
|
|
247
|
+
referenceMode: 'root',
|
|
248
|
+
serverPathEntries: [],
|
|
249
|
+
serverPathSearchError: null,
|
|
227
250
|
skillRecords: [],
|
|
228
251
|
},
|
|
229
252
|
plugins: [createPlugin({ onSelectPanelApp })],
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { ChatSkillRecord } from '@/features/chat/types/chat-input-bar.types';
|
|
2
|
-
import type { PanelAppEntryView } from '@/shared/lib/api';
|
|
2
|
+
import type { PanelAppEntryView, ServerPathSearchEntryView } from '@/shared/lib/api';
|
|
3
|
+
|
|
4
|
+
export type ContextReferenceMode = 'root' | 'files';
|
|
3
5
|
|
|
4
6
|
export type ChatInputProductPluginData = {
|
|
5
7
|
isPanelAppsLoading: boolean;
|
|
8
|
+
isServerPathSearchLoading: boolean;
|
|
6
9
|
isSkillsLoading: boolean;
|
|
7
10
|
panelApps: readonly PanelAppEntryView[];
|
|
11
|
+
projectRoot: string;
|
|
8
12
|
recentSkillValues: readonly string[];
|
|
13
|
+
referenceMode: ContextReferenceMode;
|
|
14
|
+
serverPathEntries: readonly ServerPathSearchEntryView[];
|
|
15
|
+
serverPathSearchError: string | null;
|
|
9
16
|
skillRecords: readonly ChatSkillRecord[];
|
|
10
17
|
};
|
package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createInputSurfaceTriggeredPanelPlugin,
|
|
3
|
+
type ChatInputSurfaceItem,
|
|
4
|
+
type ChatInputSurfaceMenuTexts,
|
|
5
|
+
type ChatInputSurfacePlugin,
|
|
6
|
+
type ChatInputSurfaceTriggerSpec,
|
|
7
|
+
} from '@nextclaw/agent-chat-ui';
|
|
8
|
+
import {
|
|
9
|
+
CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
|
|
10
|
+
CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
11
|
+
} from '@nextclaw/shared';
|
|
12
|
+
import type { PanelAppEntryView, ServerPathSearchEntryView } from '@/shared/lib/api';
|
|
13
|
+
import type {
|
|
14
|
+
ChatInputProductPluginData,
|
|
15
|
+
ContextReferenceMode,
|
|
16
|
+
} from './chat-input-product-plugin-adapters.types';
|
|
17
|
+
import {
|
|
18
|
+
scoreInputSurfaceSearchCandidate,
|
|
19
|
+
resolveInputSurfaceMatchTier,
|
|
20
|
+
} from './input-surface-search.utils';
|
|
21
|
+
|
|
22
|
+
const FILES_NAVIGATION_ITEM_KEY = 'context-reference:navigate:files';
|
|
23
|
+
const ROOT_NAVIGATION_ITEM_KEY = 'context-reference:navigate:root';
|
|
24
|
+
const WORKSPACE_SECTION_KEY = 'workspace';
|
|
25
|
+
const PANEL_APP_SECTION_KEY = 'panel-apps';
|
|
26
|
+
|
|
27
|
+
export const CONTEXT_REFERENCE_TRIGGER_SPEC: ChatInputSurfaceTriggerSpec = {
|
|
28
|
+
key: 'context-reference',
|
|
29
|
+
marker: '@',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type PanelAppInputSurfaceItemTexts = {
|
|
33
|
+
appIdLabel: string;
|
|
34
|
+
fileLabel: string;
|
|
35
|
+
noDescriptionLabel: string;
|
|
36
|
+
subtitle: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ContextReferenceInputSurfaceTexts = {
|
|
40
|
+
backLabel: string;
|
|
41
|
+
backDescription: string;
|
|
42
|
+
backHintLabel: string;
|
|
43
|
+
directoryDescription: string;
|
|
44
|
+
fileDescription: string;
|
|
45
|
+
filesDescription: string;
|
|
46
|
+
filesHintLabel: string;
|
|
47
|
+
filesLabel: string;
|
|
48
|
+
filesSubtitle: string;
|
|
49
|
+
panelAppSectionLabel: string;
|
|
50
|
+
projectRootLabel: string;
|
|
51
|
+
searchFailedLabel: string;
|
|
52
|
+
workspaceSectionLabel: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function getPanelAppActivityTime(entry: PanelAppEntryView): number {
|
|
56
|
+
return Date.parse(entry.lastOpenedAt ?? entry.updatedAt) || 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveProjectLabel(projectRoot: string): string {
|
|
60
|
+
return projectRoot.split(/[\\/]+/).filter(Boolean).at(-1) ?? projectRoot;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolvePanelAppInputSurfaceEntries(params: {
|
|
64
|
+
entries: readonly PanelAppEntryView[];
|
|
65
|
+
query: string;
|
|
66
|
+
}): PanelAppEntryView[] {
|
|
67
|
+
const collator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
|
|
68
|
+
return params.entries
|
|
69
|
+
.map((entry, order) => ({
|
|
70
|
+
entry,
|
|
71
|
+
order,
|
|
72
|
+
score: scoreInputSurfaceSearchCandidate(
|
|
73
|
+
{
|
|
74
|
+
id: entry.appId,
|
|
75
|
+
label: entry.title || entry.appId,
|
|
76
|
+
description: entry.description,
|
|
77
|
+
aliases: [entry.id, entry.fileName],
|
|
78
|
+
},
|
|
79
|
+
params.query,
|
|
80
|
+
),
|
|
81
|
+
}))
|
|
82
|
+
.filter((entry) => entry.score > 0)
|
|
83
|
+
.sort((left, right) => {
|
|
84
|
+
const leftTier = resolveInputSurfaceMatchTier(left.score);
|
|
85
|
+
const rightTier = resolveInputSurfaceMatchTier(right.score);
|
|
86
|
+
if (rightTier !== leftTier) {
|
|
87
|
+
return rightTier - leftTier;
|
|
88
|
+
}
|
|
89
|
+
if (left.entry.favorite !== right.entry.favorite) {
|
|
90
|
+
return left.entry.favorite ? -1 : 1;
|
|
91
|
+
}
|
|
92
|
+
const rightActivity = getPanelAppActivityTime(right.entry);
|
|
93
|
+
const leftActivity = getPanelAppActivityTime(left.entry);
|
|
94
|
+
if (rightActivity !== leftActivity) {
|
|
95
|
+
return rightActivity - leftActivity;
|
|
96
|
+
}
|
|
97
|
+
if (right.score !== left.score) {
|
|
98
|
+
return right.score - left.score;
|
|
99
|
+
}
|
|
100
|
+
const labelCompare = collator.compare(left.entry.title || left.entry.appId, right.entry.title || right.entry.appId);
|
|
101
|
+
return labelCompare || left.order - right.order;
|
|
102
|
+
})
|
|
103
|
+
.map(({ entry }) => entry);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildPanelAppInputSurfaceItemEntries(params: {
|
|
107
|
+
entries: readonly PanelAppEntryView[];
|
|
108
|
+
keyPrefix?: string;
|
|
109
|
+
query: string;
|
|
110
|
+
texts: PanelAppInputSurfaceItemTexts;
|
|
111
|
+
}): Array<{ entry: PanelAppEntryView; item: ChatInputSurfaceItem }> {
|
|
112
|
+
const keyPrefix = params.keyPrefix ?? 'panel-app';
|
|
113
|
+
return resolvePanelAppInputSurfaceEntries({
|
|
114
|
+
entries: params.entries,
|
|
115
|
+
query: params.query,
|
|
116
|
+
}).map((entry) => ({
|
|
117
|
+
entry,
|
|
118
|
+
item: {
|
|
119
|
+
key: `${keyPrefix}:${entry.appId}`,
|
|
120
|
+
icon: 'panel-app',
|
|
121
|
+
title: entry.title || entry.appId,
|
|
122
|
+
subtitle: params.texts.subtitle,
|
|
123
|
+
description: (entry.description ?? '').trim() || params.texts.noDescriptionLabel,
|
|
124
|
+
detailLines: [
|
|
125
|
+
`${params.texts.appIdLabel}: ${entry.appId}`,
|
|
126
|
+
`${params.texts.fileLabel}: ${entry.fileName}`,
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function buildPanelAppInputSurfaceItems(params: {
|
|
133
|
+
entries: readonly PanelAppEntryView[];
|
|
134
|
+
keyPrefix?: string;
|
|
135
|
+
query: string;
|
|
136
|
+
texts: PanelAppInputSurfaceItemTexts;
|
|
137
|
+
}): ChatInputSurfaceItem[] {
|
|
138
|
+
return buildPanelAppInputSurfaceItemEntries(params).map(({ item }) => item);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildPanelAppReferenceItems(params: {
|
|
142
|
+
entries: readonly PanelAppEntryView[];
|
|
143
|
+
query: string;
|
|
144
|
+
sectionLabel: string;
|
|
145
|
+
texts: PanelAppInputSurfaceItemTexts;
|
|
146
|
+
}): ChatInputSurfaceItem[] {
|
|
147
|
+
return buildPanelAppInputSurfaceItemEntries(params).map(({ entry, item }) => ({
|
|
148
|
+
...item,
|
|
149
|
+
sectionKey: PANEL_APP_SECTION_KEY,
|
|
150
|
+
sectionLabel: params.sectionLabel,
|
|
151
|
+
tokenKind: 'panel_app',
|
|
152
|
+
tokenKey: entry.appId,
|
|
153
|
+
value: entry.appId,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildWorkspaceReferenceItems(params: {
|
|
158
|
+
entries: readonly ServerPathSearchEntryView[];
|
|
159
|
+
projectRoot: string;
|
|
160
|
+
texts: ContextReferenceInputSurfaceTexts;
|
|
161
|
+
}): ChatInputSurfaceItem[] {
|
|
162
|
+
const projectLabel = resolveProjectLabel(params.projectRoot);
|
|
163
|
+
return params.entries.map((entry) => {
|
|
164
|
+
const pathSegments = entry.relativePath.split('/').filter(Boolean);
|
|
165
|
+
return {
|
|
166
|
+
key: `workspace:${entry.kind}:${entry.relativePath}`,
|
|
167
|
+
icon: entry.kind === 'directory' ? 'folder' : 'file',
|
|
168
|
+
title: entry.name,
|
|
169
|
+
subtitle: entry.parentRelativePath || projectLabel,
|
|
170
|
+
description: entry.kind === 'directory'
|
|
171
|
+
? params.texts.directoryDescription
|
|
172
|
+
: params.texts.fileDescription,
|
|
173
|
+
detailLines: [
|
|
174
|
+
`${params.texts.projectRootLabel}: ${params.projectRoot}`,
|
|
175
|
+
entry.relativePath,
|
|
176
|
+
],
|
|
177
|
+
sectionKey: WORKSPACE_SECTION_KEY,
|
|
178
|
+
sectionLabel: params.texts.workspaceSectionLabel,
|
|
179
|
+
tokenKind: entry.kind === 'directory'
|
|
180
|
+
? CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND
|
|
181
|
+
: CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
182
|
+
tokenKey: entry.relativePath,
|
|
183
|
+
pathPreview: {
|
|
184
|
+
rootLabel: projectLabel,
|
|
185
|
+
segments: pathSegments.map((label, index) => ({
|
|
186
|
+
label,
|
|
187
|
+
kind: index === pathSegments.length - 1 ? entry.kind : 'directory',
|
|
188
|
+
})),
|
|
189
|
+
},
|
|
190
|
+
} satisfies ChatInputSurfaceItem;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function buildFilesNavigationItem(texts: ContextReferenceInputSurfaceTexts): ChatInputSurfaceItem {
|
|
195
|
+
return {
|
|
196
|
+
key: FILES_NAVIGATION_ITEM_KEY,
|
|
197
|
+
icon: 'files',
|
|
198
|
+
title: texts.filesLabel,
|
|
199
|
+
subtitle: texts.filesSubtitle,
|
|
200
|
+
description: texts.filesDescription,
|
|
201
|
+
detailLines: [],
|
|
202
|
+
hintLabel: texts.filesHintLabel,
|
|
203
|
+
selectionBehavior: 'navigate',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function buildRootNavigationItem(texts: ContextReferenceInputSurfaceTexts): ChatInputSurfaceItem {
|
|
208
|
+
return {
|
|
209
|
+
key: ROOT_NAVIGATION_ITEM_KEY,
|
|
210
|
+
icon: 'back',
|
|
211
|
+
title: texts.backLabel,
|
|
212
|
+
subtitle: '',
|
|
213
|
+
description: texts.backDescription,
|
|
214
|
+
detailLines: [],
|
|
215
|
+
hintLabel: texts.backHintLabel,
|
|
216
|
+
selectionBehavior: 'navigate',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function createContextReferenceInputSurfacePlugin(params: {
|
|
221
|
+
itemTexts: {
|
|
222
|
+
context: ContextReferenceInputSurfaceTexts;
|
|
223
|
+
panelApp: PanelAppInputSurfaceItemTexts;
|
|
224
|
+
};
|
|
225
|
+
menuTexts: ChatInputSurfaceMenuTexts;
|
|
226
|
+
onNavigate: (mode: ContextReferenceMode) => void;
|
|
227
|
+
}): ChatInputSurfacePlugin<ChatInputProductPluginData> {
|
|
228
|
+
return createInputSurfaceTriggeredPanelPlugin({
|
|
229
|
+
key: 'context-reference',
|
|
230
|
+
trigger: CONTEXT_REFERENCE_TRIGGER_SPEC,
|
|
231
|
+
resolvePanel: ({ data, trigger }) => {
|
|
232
|
+
const workspaceItems = buildWorkspaceReferenceItems({
|
|
233
|
+
entries: data.serverPathEntries,
|
|
234
|
+
projectRoot: data.projectRoot,
|
|
235
|
+
texts: params.itemTexts.context,
|
|
236
|
+
});
|
|
237
|
+
const items = data.referenceMode === 'files'
|
|
238
|
+
? [buildRootNavigationItem(params.itemTexts.context), ...workspaceItems]
|
|
239
|
+
: [
|
|
240
|
+
buildFilesNavigationItem(params.itemTexts.context),
|
|
241
|
+
...(trigger.query ? workspaceItems : []),
|
|
242
|
+
...buildPanelAppReferenceItems({
|
|
243
|
+
entries: data.panelApps,
|
|
244
|
+
query: trigger.query,
|
|
245
|
+
sectionLabel: params.itemTexts.context.panelAppSectionLabel,
|
|
246
|
+
texts: params.itemTexts.panelApp,
|
|
247
|
+
}),
|
|
248
|
+
];
|
|
249
|
+
const relevantLoading = data.referenceMode === 'files'
|
|
250
|
+
? data.isServerPathSearchLoading
|
|
251
|
+
: data.isPanelAppsLoading || (Boolean(trigger.query) && data.isServerPathSearchLoading);
|
|
252
|
+
return {
|
|
253
|
+
isLoading: data.referenceMode === 'files'
|
|
254
|
+
? relevantLoading
|
|
255
|
+
: relevantLoading && items.length === 0,
|
|
256
|
+
items,
|
|
257
|
+
notice: data.serverPathSearchError
|
|
258
|
+
? {
|
|
259
|
+
message: `${params.itemTexts.context.searchFailedLabel}: ${data.serverPathSearchError}`,
|
|
260
|
+
tone: 'error',
|
|
261
|
+
}
|
|
262
|
+
: undefined,
|
|
263
|
+
onSelectItem: (item) => {
|
|
264
|
+
if (item.key === FILES_NAVIGATION_ITEM_KEY) {
|
|
265
|
+
params.onNavigate('files');
|
|
266
|
+
} else if (item.key === ROOT_NAVIGATION_ITEM_KEY) {
|
|
267
|
+
params.onNavigate('root');
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
texts: params.menuTexts,
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { ChatInputBarAdapterTexts } from '@/features/chat/types/chat-input-
|
|
|
11
11
|
import {
|
|
12
12
|
buildPanelAppInputSurfaceItems,
|
|
13
13
|
type PanelAppInputSurfaceItemTexts,
|
|
14
|
-
} from './
|
|
14
|
+
} from './context-reference-plugin.utils';
|
|
15
15
|
import {
|
|
16
16
|
resolveInputSurfaceMatchTier,
|
|
17
17
|
scoreInputSurfaceSearchCandidate,
|
|
@@ -69,6 +69,7 @@ function buildSlashCommandItems(params: {
|
|
|
69
69
|
})
|
|
70
70
|
.map(({ command }) => ({
|
|
71
71
|
key: `command:${command.key}`,
|
|
72
|
+
icon: 'command',
|
|
72
73
|
title: command.title,
|
|
73
74
|
subtitle: params.texts.commandSubtitle,
|
|
74
75
|
description: command.description,
|
|
@@ -103,6 +104,7 @@ function buildSlashSkillItems(params: {
|
|
|
103
104
|
[...data.recentSkillValues],
|
|
104
105
|
).map((item) => ({
|
|
105
106
|
...item,
|
|
107
|
+
icon: 'skill',
|
|
106
108
|
hintLabel: skillHintLabel,
|
|
107
109
|
sectionKey: item.sectionKey
|
|
108
110
|
? `${SLASH_SKILL_SECTION_KEY}:${item.sectionKey}`
|
|
@@ -125,6 +127,7 @@ function buildSlashPanelAppItems(params: {
|
|
|
125
127
|
texts: params.itemTexts,
|
|
126
128
|
}).map((item) => ({
|
|
127
129
|
...item,
|
|
130
|
+
icon: 'panel-app',
|
|
128
131
|
hintLabel: params.panelAppHintLabel,
|
|
129
132
|
sectionKey: SLASH_PANEL_APP_SECTION_KEY,
|
|
130
133
|
sectionLabel: params.panelAppSectionLabel,
|
|
@@ -89,6 +89,32 @@ describe('deriveNcpMessagePartsFromComposer', () => {
|
|
|
89
89
|
]);
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
it('serializes workspace references without turning them into uploaded file parts', () => {
|
|
93
|
+
expect(
|
|
94
|
+
deriveNcpMessagePartsFromComposer(
|
|
95
|
+
[
|
|
96
|
+
createChatComposerTokenNode({
|
|
97
|
+
tokenKind: 'workspace_file',
|
|
98
|
+
tokenKey: 'src/file name.ts',
|
|
99
|
+
label: 'file name.ts',
|
|
100
|
+
}),
|
|
101
|
+
createChatComposerTextNode(' and '),
|
|
102
|
+
createChatComposerTokenNode({
|
|
103
|
+
tokenKind: 'workspace_directory',
|
|
104
|
+
tokenKey: 'docs/设计',
|
|
105
|
+
label: '设计',
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
108
|
+
[],
|
|
109
|
+
),
|
|
110
|
+
).toEqual([
|
|
111
|
+
{
|
|
112
|
+
type: 'text',
|
|
113
|
+
text: '@file:src%2Ffile%20name.ts and @folder:docs%2F%E8%AE%BE%E8%AE%A1',
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
|
|
92
118
|
it('preserves uploaded attachment references when the attachment has a server uri', () => {
|
|
93
119
|
const parts = deriveNcpMessagePartsFromComposer(
|
|
94
120
|
[
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createChatComposerTextNode, createChatComposerTokenNode } from '@nextclaw/agent-chat-ui';
|
|
2
2
|
import {
|
|
3
3
|
buildInlineTokensFromTextProtocol,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
buildInlineTokensFromComposer,
|
|
5
|
+
CHAT_INLINE_TOKENS_METADATA_KEY,
|
|
6
6
|
readInlineTokensFromMetadata,
|
|
7
7
|
resolveInlineTokensForText
|
|
8
8
|
} from '@/features/chat/features/input/utils/chat-inline-token.utils';
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
describe('chat-inline-token utils', () => {
|
|
11
11
|
it('builds ordered inline skill tokens from composer nodes', () => {
|
|
12
12
|
expect(
|
|
13
|
-
|
|
13
|
+
buildInlineTokensFromComposer([
|
|
14
14
|
createChatComposerTextNode('before '),
|
|
15
15
|
createChatComposerTokenNode({
|
|
16
16
|
tokenKind: 'skill',
|
|
@@ -42,7 +42,7 @@ describe('chat-inline-token utils', () => {
|
|
|
42
42
|
it('reads generic inline token metadata safely', () => {
|
|
43
43
|
expect(
|
|
44
44
|
readInlineTokensFromMetadata({
|
|
45
|
-
[
|
|
45
|
+
[CHAT_INLINE_TOKENS_METADATA_KEY]: [
|
|
46
46
|
{
|
|
47
47
|
kind: 'skill',
|
|
48
48
|
key: 'weather',
|
|
@@ -97,4 +97,50 @@ describe('chat-inline-token utils', () => {
|
|
|
97
97
|
}
|
|
98
98
|
]);
|
|
99
99
|
});
|
|
100
|
+
|
|
101
|
+
it('serializes and parses workspace file and directory references', () => {
|
|
102
|
+
expect(
|
|
103
|
+
buildInlineTokensFromComposer([
|
|
104
|
+
createChatComposerTokenNode({
|
|
105
|
+
tokenKind: 'workspace_file',
|
|
106
|
+
tokenKey: 'src/file name.ts',
|
|
107
|
+
label: 'file name.ts',
|
|
108
|
+
}),
|
|
109
|
+
createChatComposerTokenNode({
|
|
110
|
+
tokenKind: 'workspace_directory',
|
|
111
|
+
tokenKey: 'docs/设计',
|
|
112
|
+
label: '设计',
|
|
113
|
+
}),
|
|
114
|
+
]),
|
|
115
|
+
).toEqual([
|
|
116
|
+
{
|
|
117
|
+
kind: 'workspace_file',
|
|
118
|
+
key: 'src/file name.ts',
|
|
119
|
+
label: 'file name.ts',
|
|
120
|
+
rawText: '@file:src%2Ffile%20name.ts',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
kind: 'workspace_directory',
|
|
124
|
+
key: 'docs/设计',
|
|
125
|
+
label: '设计',
|
|
126
|
+
rawText: '@folder:docs%2F%E8%AE%BE%E8%AE%A1',
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
expect(
|
|
130
|
+
buildInlineTokensFromTextProtocol('review @file:src%2Ffile%20name.ts and @folder:docs%2F%E8%AE%BE%E8%AE%A1'),
|
|
131
|
+
).toEqual([
|
|
132
|
+
{
|
|
133
|
+
kind: 'workspace_file',
|
|
134
|
+
key: 'src/file name.ts',
|
|
135
|
+
label: 'file name.ts',
|
|
136
|
+
rawText: '@file:src%2Ffile%20name.ts',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
kind: 'workspace_directory',
|
|
140
|
+
key: 'docs/设计',
|
|
141
|
+
label: '设计',
|
|
142
|
+
rawText: '@folder:docs%2F%E8%AE%BE%E8%AE%A1',
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
100
146
|
});
|
|
@@ -10,9 +10,7 @@ import {
|
|
|
10
10
|
import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
11
11
|
import type { NcpMessagePart } from '@nextclaw/ncp';
|
|
12
12
|
import type { NcpDraftAttachment } from '@nextclaw/ncp-react';
|
|
13
|
-
|
|
14
|
-
const CHAT_SKILL_TOKEN_PREFIX = '$';
|
|
15
|
-
const CHAT_PANEL_APP_TOKEN_PREFIX = '@panel-app:';
|
|
13
|
+
import { serializeChatComposerTokenText } from './chat-composer-token-protocol.utils';
|
|
16
14
|
|
|
17
15
|
function appendTextPart(parts: NcpMessagePart[], text: string): NcpMessagePart[] {
|
|
18
16
|
if (text.length === 0) {
|
|
@@ -110,13 +108,9 @@ export function deriveNcpMessagePartsFromComposer(
|
|
|
110
108
|
continue;
|
|
111
109
|
}
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (node.tokenKind === 'panel_app') {
|
|
119
|
-
parts = appendTextPart(parts, `${CHAT_PANEL_APP_TOKEN_PREFIX}${node.tokenKey}`);
|
|
111
|
+
const tokenText = serializeChatComposerTokenText(node);
|
|
112
|
+
if (tokenText) {
|
|
113
|
+
parts = appendTextPart(parts, tokenText);
|
|
120
114
|
continue;
|
|
121
115
|
}
|
|
122
116
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
|
|
3
|
+
CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
4
|
+
} from '@nextclaw/shared';
|
|
5
|
+
|
|
6
|
+
const CHAT_SKILL_TOKEN_PREFIX = '$';
|
|
7
|
+
const CHAT_PANEL_APP_TOKEN_PREFIX = '@panel-app:';
|
|
8
|
+
const CHAT_WORKSPACE_FILE_TOKEN_PREFIX = '@file:';
|
|
9
|
+
const CHAT_WORKSPACE_DIRECTORY_TOKEN_PREFIX = '@folder:';
|
|
10
|
+
|
|
11
|
+
export function serializeChatComposerTokenText(params: {
|
|
12
|
+
tokenKey: string;
|
|
13
|
+
tokenKind: string;
|
|
14
|
+
}): string | null {
|
|
15
|
+
const { tokenKey, tokenKind } = params;
|
|
16
|
+
if (tokenKind === 'skill') {
|
|
17
|
+
return `${CHAT_SKILL_TOKEN_PREFIX}${tokenKey}`;
|
|
18
|
+
}
|
|
19
|
+
if (tokenKind === 'panel_app') {
|
|
20
|
+
return `${CHAT_PANEL_APP_TOKEN_PREFIX}${tokenKey}`;
|
|
21
|
+
}
|
|
22
|
+
if (tokenKind === CHAT_WORKSPACE_FILE_TOKEN_KIND) {
|
|
23
|
+
return `${CHAT_WORKSPACE_FILE_TOKEN_PREFIX}${encodeURIComponent(tokenKey)}`;
|
|
24
|
+
}
|
|
25
|
+
if (tokenKind === CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND) {
|
|
26
|
+
return `${CHAT_WORKSPACE_DIRECTORY_TOKEN_PREFIX}${encodeURIComponent(tokenKey)}`;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|