@nextclaw/ui 0.15.19 → 0.15.20
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 +18 -0
- package/dist/assets/{api-O5yEgzBt.js → api-tWUa6l9J.js} +3 -3
- package/dist/assets/app-presenter-provider-s-FrOinZ.js +106 -0
- package/dist/assets/appearance-settings-page-dMRBD7ni.js +1 -0
- package/dist/assets/{book-open-HtUSPwoS.js → book-open-BQ29ZmDE.js} +1 -1
- package/dist/assets/card-CtKueklV.js +1 -0
- package/dist/assets/{channels-list-page-Bspr1Jp4.js → channels-list-page-C8y3rqYt.js} +2 -2
- package/dist/assets/{chat-page-BKQ89f4s.js → chat-page-MXBltS3G.js} +1 -1
- package/dist/assets/config-split-page-uC6d_r9c.js +1 -0
- package/dist/assets/confirm-dialog-C1pbqHZX.js +5 -0
- package/dist/assets/desktop-update-config-DssP8LCk.js +1 -0
- package/dist/assets/{dist-CZx9Q6jc.js → dist-rYHnBo8t.js} +1 -1
- package/dist/assets/doc-browser-AXF1zRsM.js +1 -0
- package/dist/assets/{doc-browser-B4l0Xu7u.js → doc-browser-D8ng4EUc.js} +1 -1
- package/dist/assets/{doc-browser-context-Dy7EhLI8.js → doc-browser-context-CPHNzQ8m.js} +1 -1
- package/dist/assets/{eye-DU7p1-7X.js → eye-CiJz3ODY.js} +1 -1
- package/dist/assets/form-actions-BnZVzLZf.js +1 -0
- package/dist/assets/{index-6pHX0DZH.js → index-CocHWEsI.js} +2 -2
- package/dist/assets/loader-circle-AOpK5kkW.js +1 -0
- package/dist/assets/logo-badge-BXI-Kdhd.js +1 -0
- package/dist/assets/mcp-marketplace-page-5ogeBHnl.js +1 -0
- package/dist/assets/{mcp-marketplace-page-Dg03SZX1.js → mcp-marketplace-page-CxNe-Q9Q.js} +3 -3
- package/dist/assets/{model-config-page-DOBqLZoQ.js → model-config-page-CR_mgEVp.js} +1 -1
- package/dist/assets/{navigation-link-Ct6xtnVM.js → navigation-link-C1cvB7y4.js} +1 -1
- package/dist/assets/plus-DI37QuHc.js +1 -0
- package/dist/assets/{popover-C2oIpGBF.js → popover-ZoDwVwwd.js} +1 -1
- package/dist/assets/provider-scoped-model-input-DIL6Ex_d.js +1 -0
- package/dist/assets/{providers-config-page-BAVPJKpx.js → providers-config-page-D2NXVctW.js} +1 -1
- package/dist/assets/react-39CDNZlM.js +8 -0
- package/dist/assets/{refresh-cw-BZ8giusN.js → refresh-cw-DKewcdAl.js} +1 -1
- package/dist/assets/remote-DYOOO1YG.js +1 -0
- package/dist/assets/rotate-cw-PmqA8LQT.js +1 -0
- package/dist/assets/{runtime-config-page-D0244uxJ.js → runtime-config-page-0lb5NyKc.js} +1 -1
- package/dist/assets/{save-CSzFGRse.js → save-BrckuAXY.js} +1 -1
- package/dist/assets/{search-BmSWsiG2.js → search-BhXQB2TO.js} +1 -1
- package/dist/assets/{search-config-page-CrL1pUzy.js → search-config-page-DFOQ6SDJ.js} +1 -1
- package/dist/assets/{secrets-config-page-BlgsD0-L.js → secrets-config-page-BqqeClqE.js} +1 -1
- package/dist/assets/security-config-13AtPIlm.js +1 -0
- package/dist/assets/{select-CklWb6Rs.js → select-CVzGGT38.js} +1 -1
- package/dist/assets/{settings-2-BSZf8327.js → settings-2-BP6L2enG.js} +1 -1
- package/dist/assets/skeleton-OwCNQJ7M.js +1 -0
- package/dist/assets/{tabs-Cah5klko.js → tabs-B-QWjKCL.js} +1 -1
- package/dist/assets/{tag-chip-DcrirDgN.js → tag-chip-C88mxjwx.js} +1 -1
- package/dist/assets/{trash-2-BLQOULWD.js → trash-2-ck3t33I3.js} +1 -1
- package/dist/assets/{use-config-CuFIsUVG.js → use-config-BAr5bH2c.js} +1 -1
- package/dist/assets/use-confirm-dialog-C4e0nG_4.js +1 -0
- package/dist/assets/{use-viewport-layout-DU29bUJS.js → use-viewport-layout-k1isXfNm.js} +1 -1
- package/dist/assets/x-C1ijNWcE.js +1 -0
- package/dist/index.html +27 -27
- package/package.json +9 -9
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-run-queue.test.tsx +28 -0
- package/src/features/chat/features/conversation/hooks/use-session-run-queue.ts +4 -5
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +13 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +31 -8
- package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +79 -9
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +16 -4
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -2
- package/src/features/chat/features/input/input-surface-plugins/context-reference-items.utils.ts +240 -0
- package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +62 -173
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +29 -0
- package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +5 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -5
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +12 -1
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +9 -5
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +26 -0
- package/src/shared/hooks/use-projects.ts +2 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +13 -4
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +13 -4
- package/dist/assets/app-presenter-provider-DXTYtH78.js +0 -106
- package/dist/assets/appearance-settings-page-BuGxSvlJ.js +0 -1
- package/dist/assets/card-GkBc-UMw.js +0 -1
- package/dist/assets/config-split-page-CvLXSFSC.js +0 -1
- package/dist/assets/confirm-dialog-BVH7haN0.js +0 -5
- package/dist/assets/desktop-update-config-C9ZUI8UW.js +0 -1
- package/dist/assets/doc-browser-DsVF1DrS.js +0 -1
- package/dist/assets/form-actions-aM9_jSJC.js +0 -1
- package/dist/assets/loader-circle-Dd6jw700.js +0 -1
- package/dist/assets/logo-badge-2-iV1pkr.js +0 -1
- package/dist/assets/mcp-marketplace-page-C-d0RMfg.js +0 -1
- package/dist/assets/plus-CsgeRId8.js +0 -1
- package/dist/assets/provider-scoped-model-input-DDqCdnTU.js +0 -1
- package/dist/assets/react-CeMr9V52.js +0 -8
- package/dist/assets/remote-C-r0SHcz.js +0 -1
- package/dist/assets/rotate-cw-DVdRfMSO.js +0 -1
- package/dist/assets/security-config-cDrdpMJW.js +0 -1
- package/dist/assets/skeleton-DG9u1BmA.js +0 -1
- package/dist/assets/use-confirm-dialog-CcySmkWp.js +0 -1
- package/dist/assets/x-BVoeXny1.js +0 -1
package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts
CHANGED
|
@@ -5,20 +5,28 @@ import {
|
|
|
5
5
|
type ChatInputSurfacePlugin,
|
|
6
6
|
type ChatInputSurfaceTriggerSpec,
|
|
7
7
|
} from '@nextclaw/agent-chat-ui';
|
|
8
|
+
import type { PanelAppEntryView } from '@/shared/lib/api';
|
|
9
|
+
import type {
|
|
10
|
+
ChatInputProductPluginData,
|
|
11
|
+
ContextReferenceLocation,
|
|
12
|
+
} from './chat-input-product-plugin-adapters.types';
|
|
8
13
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
buildBackNavigationItem,
|
|
15
|
+
buildCurrentDirectoryReferenceItem,
|
|
16
|
+
buildFilesNavigationItem,
|
|
17
|
+
buildProjectReferenceItems,
|
|
18
|
+
buildProjectsNavigationItem,
|
|
19
|
+
buildWorkspaceReferenceItems,
|
|
20
|
+
FILES_NAVIGATION_ITEM_KEY,
|
|
21
|
+
PROJECTS_NAVIGATION_ITEM_KEY,
|
|
22
|
+
ROOT_NAVIGATION_ITEM_KEY,
|
|
23
|
+
type ContextReferenceInputSurfaceTexts,
|
|
24
|
+
} from './context-reference-items.utils';
|
|
14
25
|
import {
|
|
15
26
|
scoreInputSurfaceSearchCandidate,
|
|
16
27
|
resolveInputSurfaceMatchTier,
|
|
17
28
|
} from './input-surface-search.utils';
|
|
18
29
|
|
|
19
|
-
const FILES_NAVIGATION_ITEM_KEY = 'context-reference:navigate:files';
|
|
20
|
-
const ROOT_NAVIGATION_ITEM_KEY = 'context-reference:navigate:root';
|
|
21
|
-
const WORKSPACE_SECTION_KEY = 'workspace';
|
|
22
30
|
const PANEL_APP_SECTION_KEY = 'panel-apps';
|
|
23
31
|
|
|
24
32
|
export const CONTEXT_REFERENCE_TRIGGER_SPEC: ChatInputSurfaceTriggerSpec = {
|
|
@@ -33,34 +41,10 @@ export type PanelAppInputSurfaceItemTexts = {
|
|
|
33
41
|
subtitle: string;
|
|
34
42
|
};
|
|
35
43
|
|
|
36
|
-
export type ContextReferenceInputSurfaceTexts = {
|
|
37
|
-
backLabel: string;
|
|
38
|
-
backDescription: string;
|
|
39
|
-
backHintLabel: string;
|
|
40
|
-
currentDirectoryLabel: string;
|
|
41
|
-
directoryDescription: string;
|
|
42
|
-
fileDescription: string;
|
|
43
|
-
filesDescription: string;
|
|
44
|
-
filesHintLabel: string;
|
|
45
|
-
filesLabel: string;
|
|
46
|
-
filesSubtitle: string;
|
|
47
|
-
panelAppSectionLabel: string;
|
|
48
|
-
parentLabel: string;
|
|
49
|
-
parentDescription: string;
|
|
50
|
-
parentHintLabel: string;
|
|
51
|
-
projectRootLabel: string;
|
|
52
|
-
searchFailedLabel: string;
|
|
53
|
-
workspaceSectionLabel: string;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
44
|
function getPanelAppActivityTime(entry: PanelAppEntryView): number {
|
|
57
45
|
return Date.parse(entry.lastOpenedAt ?? entry.updatedAt) || 0;
|
|
58
46
|
}
|
|
59
47
|
|
|
60
|
-
function resolveProjectLabel(projectRoot: string): string {
|
|
61
|
-
return projectRoot.split(/[\\/]+/).filter(Boolean).at(-1) ?? projectRoot;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
48
|
function resolvePanelAppInputSurfaceEntries(params: {
|
|
65
49
|
entries: readonly PanelAppEntryView[];
|
|
66
50
|
query: string;
|
|
@@ -155,171 +139,64 @@ function buildPanelAppReferenceItems(params: {
|
|
|
155
139
|
}));
|
|
156
140
|
}
|
|
157
141
|
|
|
158
|
-
function buildPathPreview(params: {
|
|
159
|
-
kind: 'directory' | 'file';
|
|
160
|
-
projectRoot: string;
|
|
161
|
-
relativePath: string;
|
|
162
|
-
}) {
|
|
163
|
-
const { kind, projectRoot, relativePath } = params;
|
|
164
|
-
const pathSegments = relativePath.split('/').filter(Boolean);
|
|
165
|
-
return {
|
|
166
|
-
rootLabel: resolveProjectLabel(projectRoot),
|
|
167
|
-
segments: pathSegments.map((label, index) => ({
|
|
168
|
-
label,
|
|
169
|
-
kind: index === pathSegments.length - 1 ? kind : 'directory' as const,
|
|
170
|
-
})),
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function buildWorkspaceReferenceItems(params: {
|
|
175
|
-
entries: readonly ServerPathSearchEntryView[];
|
|
176
|
-
navigateDirectories: boolean;
|
|
177
|
-
projectRoot: string;
|
|
178
|
-
texts: ContextReferenceInputSurfaceTexts;
|
|
179
|
-
}): ChatInputSurfaceItem[] {
|
|
180
|
-
const projectLabel = resolveProjectLabel(params.projectRoot);
|
|
181
|
-
return params.entries.map((entry) => {
|
|
182
|
-
const navigates = params.navigateDirectories && entry.kind === 'directory';
|
|
183
|
-
return {
|
|
184
|
-
key: `workspace:${entry.kind}:${entry.relativePath}`,
|
|
185
|
-
icon: entry.kind === 'directory' ? 'folder' : 'file',
|
|
186
|
-
title: entry.name,
|
|
187
|
-
subtitle: entry.parentRelativePath || projectLabel,
|
|
188
|
-
description: entry.kind === 'directory'
|
|
189
|
-
? params.texts.directoryDescription
|
|
190
|
-
: params.texts.fileDescription,
|
|
191
|
-
detailLines: [
|
|
192
|
-
`${params.texts.projectRootLabel}: ${params.projectRoot}`,
|
|
193
|
-
entry.relativePath,
|
|
194
|
-
],
|
|
195
|
-
sectionKey: WORKSPACE_SECTION_KEY,
|
|
196
|
-
sectionLabel: params.texts.workspaceSectionLabel,
|
|
197
|
-
value: entry.relativePath,
|
|
198
|
-
tokenKind: navigates
|
|
199
|
-
? undefined
|
|
200
|
-
: entry.kind === 'directory'
|
|
201
|
-
? CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND
|
|
202
|
-
: CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
203
|
-
tokenKey: navigates ? undefined : entry.relativePath,
|
|
204
|
-
selectionBehavior: navigates ? 'navigate' : undefined,
|
|
205
|
-
pathPreview: buildPathPreview({
|
|
206
|
-
kind: entry.kind,
|
|
207
|
-
projectRoot: params.projectRoot,
|
|
208
|
-
relativePath: entry.relativePath,
|
|
209
|
-
}),
|
|
210
|
-
} satisfies ChatInputSurfaceItem;
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function resolveParentReferencePath(referencePath: string): string | null {
|
|
215
|
-
const segments = referencePath.split('/').filter(Boolean);
|
|
216
|
-
if (segments.length === 0) {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
return segments.slice(0, -1).join('/');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function buildCurrentDirectoryReferenceItem(params: {
|
|
223
|
-
projectRoot: string;
|
|
224
|
-
referencePath: string;
|
|
225
|
-
texts: ContextReferenceInputSurfaceTexts;
|
|
226
|
-
}): ChatInputSurfaceItem {
|
|
227
|
-
const { projectRoot, referencePath, texts } = params;
|
|
228
|
-
const title = referencePath.split('/').filter(Boolean).at(-1) ?? referencePath;
|
|
229
|
-
return {
|
|
230
|
-
key: `context-reference:current-directory:${referencePath}`,
|
|
231
|
-
icon: 'folder',
|
|
232
|
-
title: texts.currentDirectoryLabel,
|
|
233
|
-
subtitle: title,
|
|
234
|
-
description: texts.directoryDescription,
|
|
235
|
-
detailLines: [
|
|
236
|
-
`${texts.projectRootLabel}: ${projectRoot}`,
|
|
237
|
-
referencePath,
|
|
238
|
-
],
|
|
239
|
-
sectionKey: WORKSPACE_SECTION_KEY,
|
|
240
|
-
sectionLabel: texts.workspaceSectionLabel,
|
|
241
|
-
value: referencePath,
|
|
242
|
-
tokenKind: CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
|
|
243
|
-
tokenKey: referencePath,
|
|
244
|
-
pathPreview: buildPathPreview({
|
|
245
|
-
kind: 'directory',
|
|
246
|
-
projectRoot,
|
|
247
|
-
relativePath: referencePath,
|
|
248
|
-
}),
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function buildFilesNavigationItem(texts: ContextReferenceInputSurfaceTexts): ChatInputSurfaceItem {
|
|
253
|
-
return {
|
|
254
|
-
key: FILES_NAVIGATION_ITEM_KEY,
|
|
255
|
-
icon: 'files',
|
|
256
|
-
title: texts.filesLabel,
|
|
257
|
-
subtitle: texts.filesSubtitle,
|
|
258
|
-
description: texts.filesDescription,
|
|
259
|
-
detailLines: [],
|
|
260
|
-
hintLabel: texts.filesHintLabel,
|
|
261
|
-
selectionBehavior: 'navigate',
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function buildBackNavigationItem(params: {
|
|
266
|
-
referencePath: string;
|
|
267
|
-
texts: ContextReferenceInputSurfaceTexts;
|
|
268
|
-
}): ChatInputSurfaceItem {
|
|
269
|
-
const { referencePath, texts } = params;
|
|
270
|
-
const isProjectRoot = !referencePath;
|
|
271
|
-
return {
|
|
272
|
-
key: ROOT_NAVIGATION_ITEM_KEY,
|
|
273
|
-
icon: 'back',
|
|
274
|
-
title: isProjectRoot ? texts.backLabel : texts.parentLabel,
|
|
275
|
-
subtitle: '',
|
|
276
|
-
description: isProjectRoot ? texts.backDescription : texts.parentDescription,
|
|
277
|
-
detailLines: [],
|
|
278
|
-
hintLabel: isProjectRoot ? texts.backHintLabel : texts.parentHintLabel,
|
|
279
|
-
value: resolveParentReferencePath(referencePath) ?? '',
|
|
280
|
-
selectionBehavior: 'navigate',
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
142
|
export function createContextReferenceInputSurfacePlugin(params: {
|
|
285
143
|
itemTexts: {
|
|
286
144
|
context: ContextReferenceInputSurfaceTexts;
|
|
287
145
|
panelApp: PanelAppInputSurfaceItemTexts;
|
|
288
146
|
};
|
|
289
147
|
menuTexts: ChatInputSurfaceMenuTexts;
|
|
290
|
-
onNavigate: (
|
|
148
|
+
onNavigate: (location: ContextReferenceLocation) => void;
|
|
291
149
|
}): ChatInputSurfacePlugin<ChatInputProductPluginData> {
|
|
292
150
|
return createInputSurfaceTriggeredPanelPlugin({
|
|
293
151
|
key: 'context-reference',
|
|
294
152
|
trigger: CONTEXT_REFERENCE_TRIGGER_SPEC,
|
|
295
153
|
resolvePanel: ({ data, trigger }) => {
|
|
296
|
-
const isFilesMode = data.
|
|
154
|
+
const isFilesMode = data.referenceLocation.view === 'files';
|
|
155
|
+
const isProjectsMode = data.referenceLocation.view === 'projects';
|
|
297
156
|
const isBrowsing = isFilesMode && !trigger.query.trim();
|
|
157
|
+
const referencePath = data.referenceLocation.view === 'files'
|
|
158
|
+
? data.referenceLocation.path
|
|
159
|
+
: '';
|
|
298
160
|
const workspaceItems = buildWorkspaceReferenceItems({
|
|
299
161
|
entries: data.serverPathEntries,
|
|
300
162
|
navigateDirectories: isBrowsing,
|
|
301
163
|
projectRoot: data.projectRoot,
|
|
302
164
|
texts: params.itemTexts.context,
|
|
303
165
|
});
|
|
304
|
-
const
|
|
166
|
+
const projectItems = buildProjectReferenceItems({
|
|
167
|
+
projects: data.projects,
|
|
168
|
+
query: trigger.query,
|
|
169
|
+
texts: params.itemTexts.context,
|
|
170
|
+
});
|
|
171
|
+
const currentDirectoryItem = isBrowsing && referencePath
|
|
305
172
|
? buildCurrentDirectoryReferenceItem({
|
|
306
173
|
projectRoot: data.projectRoot,
|
|
307
|
-
referencePath
|
|
174
|
+
referencePath,
|
|
308
175
|
texts: params.itemTexts.context,
|
|
309
176
|
})
|
|
310
177
|
: null;
|
|
311
178
|
const items = isFilesMode
|
|
312
179
|
? [
|
|
313
180
|
buildBackNavigationItem({
|
|
314
|
-
|
|
181
|
+
location: data.referenceLocation,
|
|
315
182
|
texts: params.itemTexts.context,
|
|
316
183
|
}),
|
|
317
184
|
...(currentDirectoryItem ? [currentDirectoryItem] : []),
|
|
318
185
|
...workspaceItems,
|
|
319
186
|
]
|
|
320
|
-
:
|
|
187
|
+
: isProjectsMode
|
|
188
|
+
? [
|
|
189
|
+
buildBackNavigationItem({
|
|
190
|
+
location: data.referenceLocation,
|
|
191
|
+
texts: params.itemTexts.context,
|
|
192
|
+
}),
|
|
193
|
+
...projectItems,
|
|
194
|
+
]
|
|
195
|
+
: [
|
|
321
196
|
buildFilesNavigationItem(params.itemTexts.context),
|
|
197
|
+
buildProjectsNavigationItem(params.itemTexts.context),
|
|
322
198
|
...(trigger.query ? workspaceItems : []),
|
|
199
|
+
...(trigger.query ? projectItems : []),
|
|
323
200
|
...buildPanelAppReferenceItems({
|
|
324
201
|
entries: data.panelApps,
|
|
325
202
|
query: trigger.query,
|
|
@@ -329,25 +206,37 @@ export function createContextReferenceInputSurfacePlugin(params: {
|
|
|
329
206
|
];
|
|
330
207
|
const relevantLoading = isFilesMode
|
|
331
208
|
? data.isServerPathSearchLoading
|
|
332
|
-
:
|
|
209
|
+
: isProjectsMode
|
|
210
|
+
? data.isProjectsLoading
|
|
211
|
+
: data.isPanelAppsLoading ||
|
|
212
|
+
(Boolean(trigger.query) && (data.isProjectsLoading || data.isServerPathSearchLoading));
|
|
213
|
+
const errorMessage = isProjectsMode && data.projectsError
|
|
214
|
+
? `${params.itemTexts.context.projectsLoadFailedLabel}: ${data.projectsError}`
|
|
215
|
+
: data.serverPathSearchError
|
|
216
|
+
? `${params.itemTexts.context.searchFailedLabel}: ${data.serverPathSearchError}`
|
|
217
|
+
: null;
|
|
333
218
|
return {
|
|
334
|
-
isLoading:
|
|
335
|
-
? relevantLoading
|
|
336
|
-
: relevantLoading && items.length === 0,
|
|
219
|
+
isLoading: isProjectsMode ? relevantLoading : relevantLoading && items.length === 0,
|
|
337
220
|
items,
|
|
338
|
-
notice:
|
|
221
|
+
notice: errorMessage
|
|
339
222
|
? {
|
|
340
|
-
message:
|
|
223
|
+
message: errorMessage,
|
|
341
224
|
tone: 'error',
|
|
342
225
|
}
|
|
343
226
|
: undefined,
|
|
344
227
|
onSelectItem: (item) => {
|
|
345
228
|
if (item.key === FILES_NAVIGATION_ITEM_KEY) {
|
|
346
|
-
params.onNavigate('');
|
|
229
|
+
params.onNavigate({ view: 'files', path: '' });
|
|
230
|
+
} else if (item.key === PROJECTS_NAVIGATION_ITEM_KEY) {
|
|
231
|
+
params.onNavigate({ view: 'projects' });
|
|
347
232
|
} else if (item.key === ROOT_NAVIGATION_ITEM_KEY) {
|
|
348
|
-
params.onNavigate(
|
|
233
|
+
params.onNavigate(
|
|
234
|
+
isFilesMode && referencePath
|
|
235
|
+
? { view: 'files', path: item.value ?? '' }
|
|
236
|
+
: { view: 'root' },
|
|
237
|
+
);
|
|
349
238
|
} else if (item.selectionBehavior === 'navigate' && item.value) {
|
|
350
|
-
params.onNavigate(item.value);
|
|
239
|
+
params.onNavigate({ view: 'files', path: item.value });
|
|
351
240
|
}
|
|
352
241
|
},
|
|
353
242
|
texts: params.menuTexts,
|
|
@@ -226,6 +226,35 @@ describe('chat inline token workspace references', () => {
|
|
|
226
226
|
]);
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
+
it('serializes project references with their registered path and display name', () => {
|
|
230
|
+
expect(
|
|
231
|
+
buildInlineTokensFromComposer([
|
|
232
|
+
createChatComposerTokenNode({
|
|
233
|
+
tokenKind: 'project',
|
|
234
|
+
tokenKey: '/tmp/NextClaw Project',
|
|
235
|
+
label: 'NextClaw',
|
|
236
|
+
}),
|
|
237
|
+
]),
|
|
238
|
+
).toEqual([
|
|
239
|
+
{
|
|
240
|
+
kind: 'project',
|
|
241
|
+
key: '/tmp/NextClaw Project',
|
|
242
|
+
label: 'NextClaw',
|
|
243
|
+
rawText: '@project:%2Ftmp%2FNextClaw%20Project',
|
|
244
|
+
},
|
|
245
|
+
]);
|
|
246
|
+
expect(
|
|
247
|
+
buildInlineTokensFromTextProtocol('review @project:%2Ftmp%2FNextClaw%20Project'),
|
|
248
|
+
).toEqual([
|
|
249
|
+
{
|
|
250
|
+
kind: 'project',
|
|
251
|
+
key: '/tmp/NextClaw Project',
|
|
252
|
+
label: 'NextClaw Project',
|
|
253
|
+
rawText: '@project:%2Ftmp%2FNextClaw%20Project',
|
|
254
|
+
},
|
|
255
|
+
]);
|
|
256
|
+
});
|
|
257
|
+
|
|
229
258
|
it('resolves workspace token paths inside POSIX and Windows project roots', () => {
|
|
230
259
|
expect(resolveWorkspaceReferencePath({
|
|
231
260
|
projectRoot: '/tmp/project/',
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CHAT_PROJECT_TOKEN_KIND,
|
|
2
3
|
CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
|
|
3
4
|
CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
4
5
|
} from '@nextclaw/shared';
|
|
5
6
|
|
|
6
7
|
const CHAT_SKILL_TOKEN_PREFIX = '$';
|
|
7
8
|
const CHAT_PANEL_APP_TOKEN_PREFIX = '@panel-app:';
|
|
9
|
+
const CHAT_PROJECT_TOKEN_PREFIX = '@project:';
|
|
8
10
|
const CHAT_WORKSPACE_FILE_TOKEN_PREFIX = '@file:';
|
|
9
11
|
const CHAT_WORKSPACE_DIRECTORY_TOKEN_PREFIX = '@folder:';
|
|
10
12
|
|
|
@@ -20,6 +22,9 @@ export function serializeChatComposerTokenText(params: {
|
|
|
20
22
|
if (tokenKind === 'panel_app') {
|
|
21
23
|
return `${CHAT_PANEL_APP_TOKEN_PREFIX}${tokenKey}`;
|
|
22
24
|
}
|
|
25
|
+
if (tokenKind === CHAT_PROJECT_TOKEN_KIND) {
|
|
26
|
+
return `${CHAT_PROJECT_TOKEN_PREFIX}${encodeURIComponent(tokenKey)}`;
|
|
27
|
+
}
|
|
23
28
|
if (tokenKind === CHAT_WORKSPACE_FILE_TOKEN_KIND) {
|
|
24
29
|
return `${CHAT_WORKSPACE_FILE_TOKEN_PREFIX}${encodeURIComponent(tokenKey)}`;
|
|
25
30
|
}
|
|
@@ -2,6 +2,7 @@ import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
|
2
2
|
import {
|
|
3
3
|
CHAT_INLINE_TOKENS_METADATA_KEY,
|
|
4
4
|
CHAT_INLINE_TOKENS_SCHEMA_VERSION,
|
|
5
|
+
CHAT_PROJECT_TOKEN_KIND,
|
|
5
6
|
CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
|
|
6
7
|
CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
7
8
|
type ChatInlineTokenMetadata,
|
|
@@ -13,6 +14,7 @@ import { serializeChatComposerTokenText } from './chat-composer-token-protocol.u
|
|
|
13
14
|
export { CHAT_INLINE_TOKENS_METADATA_KEY };
|
|
14
15
|
const CHAT_PANEL_APP_TOKEN_PREFIX = '@panel-app:';
|
|
15
16
|
const CHAT_PANEL_APP_TOKEN_PATTERN = /@panel-app:([A-Za-z0-9_-]+)/g;
|
|
17
|
+
const CHAT_PROJECT_TOKEN_PATTERN = /@project:([^\s]+)/g;
|
|
16
18
|
const CHAT_WORKSPACE_FILE_TOKEN_PATTERN = /@file:([^\s]+)/g;
|
|
17
19
|
const CHAT_WORKSPACE_DIRECTORY_TOKEN_PATTERN = /@folder:([^\s]+)/g;
|
|
18
20
|
|
|
@@ -96,6 +98,7 @@ export function buildInlineTokensFromComposer(
|
|
|
96
98
|
}
|
|
97
99
|
if (
|
|
98
100
|
node.tokenKind !== 'skill' &&
|
|
101
|
+
node.tokenKind !== CHAT_PROJECT_TOKEN_KIND &&
|
|
99
102
|
node.tokenKind !== CHAT_WORKSPACE_FILE_TOKEN_KIND &&
|
|
100
103
|
node.tokenKind !== CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND
|
|
101
104
|
) {
|
|
@@ -121,6 +124,15 @@ export function buildInlineTokensFromComposer(
|
|
|
121
124
|
});
|
|
122
125
|
continue;
|
|
123
126
|
}
|
|
127
|
+
if (node.tokenKind === CHAT_PROJECT_TOKEN_KIND) {
|
|
128
|
+
tokens.push({
|
|
129
|
+
kind: CHAT_PROJECT_TOKEN_KIND,
|
|
130
|
+
key: node.tokenKey,
|
|
131
|
+
label: node.label,
|
|
132
|
+
rawText,
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
124
136
|
const workspaceKind = node.tokenKind === CHAT_WORKSPACE_FILE_TOKEN_KIND
|
|
125
137
|
? CHAT_WORKSPACE_FILE_TOKEN_KIND
|
|
126
138
|
: CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND;
|
|
@@ -134,8 +146,11 @@ export function buildInlineTokensFromComposer(
|
|
|
134
146
|
return dedupeInlineTokens(tokens);
|
|
135
147
|
}
|
|
136
148
|
|
|
137
|
-
function
|
|
138
|
-
kind:
|
|
149
|
+
function appendEncodedKeyTokens(params: {
|
|
150
|
+
kind:
|
|
151
|
+
| typeof CHAT_PROJECT_TOKEN_KIND
|
|
152
|
+
| typeof CHAT_WORKSPACE_FILE_TOKEN_KIND
|
|
153
|
+
| typeof CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND;
|
|
139
154
|
pattern: RegExp;
|
|
140
155
|
text: string;
|
|
141
156
|
tokens: ChatInlineTokenSource[];
|
|
@@ -155,7 +170,7 @@ function appendWorkspaceTokens(params: {
|
|
|
155
170
|
tokens.push({
|
|
156
171
|
kind,
|
|
157
172
|
key,
|
|
158
|
-
label: key.split(
|
|
173
|
+
label: key.split(/[\\/]+/).filter(Boolean).at(-1) ?? key,
|
|
159
174
|
rawText: match[0],
|
|
160
175
|
});
|
|
161
176
|
}
|
|
@@ -175,13 +190,19 @@ export function buildInlineTokensFromTextProtocol(text: string): ChatInlineToken
|
|
|
175
190
|
rawText: `${CHAT_PANEL_APP_TOKEN_PREFIX}${key}`
|
|
176
191
|
});
|
|
177
192
|
}
|
|
178
|
-
|
|
193
|
+
appendEncodedKeyTokens({
|
|
194
|
+
kind: CHAT_PROJECT_TOKEN_KIND,
|
|
195
|
+
pattern: CHAT_PROJECT_TOKEN_PATTERN,
|
|
196
|
+
text,
|
|
197
|
+
tokens,
|
|
198
|
+
});
|
|
199
|
+
appendEncodedKeyTokens({
|
|
179
200
|
kind: CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
180
201
|
pattern: CHAT_WORKSPACE_FILE_TOKEN_PATTERN,
|
|
181
202
|
text,
|
|
182
203
|
tokens,
|
|
183
204
|
});
|
|
184
|
-
|
|
205
|
+
appendEncodedKeyTokens({
|
|
185
206
|
kind: CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
|
|
186
207
|
pattern: CHAT_WORKSPACE_DIRECTORY_TOKEN_PATTERN,
|
|
187
208
|
text,
|
package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx
CHANGED
|
@@ -453,7 +453,7 @@ it("wires markdown file link actions to the workspace file preview manager", ()
|
|
|
453
453
|
expect(captures.openFilePreview).toHaveBeenCalledWith(action);
|
|
454
454
|
});
|
|
455
455
|
|
|
456
|
-
it("opens workspace
|
|
456
|
+
it("opens workspace and project reference tokens from the message", () => {
|
|
457
457
|
captures.selectedSession = {
|
|
458
458
|
projectRoot: "/tmp/project",
|
|
459
459
|
workingDir: "/tmp/project/packages/ui",
|
|
@@ -474,6 +474,12 @@ it("opens workspace file and directory tokens from the message", () => {
|
|
|
474
474
|
label: "server",
|
|
475
475
|
rawText: "@folder:packages%2Fserver",
|
|
476
476
|
});
|
|
477
|
+
onInlineTokenClick?.({
|
|
478
|
+
kind: "project",
|
|
479
|
+
key: "/tmp/nextclaw",
|
|
480
|
+
label: "NextClaw",
|
|
481
|
+
rawText: "@project:%2Ftmp%2Fnextclaw",
|
|
482
|
+
});
|
|
477
483
|
|
|
478
484
|
expect(captures.openFilePreview).toHaveBeenNthCalledWith(1, {
|
|
479
485
|
path: "/tmp/project/docs/guide.md",
|
|
@@ -485,6 +491,11 @@ it("opens workspace file and directory tokens from the message", () => {
|
|
|
485
491
|
label: "server",
|
|
486
492
|
viewMode: "preview",
|
|
487
493
|
});
|
|
494
|
+
expect(captures.openFilePreview).toHaveBeenNthCalledWith(3, {
|
|
495
|
+
path: "/tmp/nextclaw",
|
|
496
|
+
label: "NextClaw",
|
|
497
|
+
viewMode: "preview",
|
|
498
|
+
});
|
|
488
499
|
});
|
|
489
500
|
|
|
490
501
|
it("opens panel app tokens through the existing content owner", () => {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type RefObject,
|
|
7
7
|
} from "react";
|
|
8
8
|
import type { NcpMessage } from "@nextclaw/ncp";
|
|
9
|
+
import { CHAT_PROJECT_TOKEN_KIND } from "@nextclaw/shared";
|
|
9
10
|
import { toast } from "sonner";
|
|
10
11
|
import {
|
|
11
12
|
type ChatInlineDisplayViewModel,
|
|
@@ -346,13 +347,16 @@ export function ChatMessageListContainer({
|
|
|
346
347
|
}
|
|
347
348
|
if (
|
|
348
349
|
"key" in token &&
|
|
349
|
-
(token.kind ===
|
|
350
|
+
(token.kind === CHAT_PROJECT_TOKEN_KIND ||
|
|
351
|
+
token.kind === "workspace_file" ||
|
|
350
352
|
token.kind === "workspace_directory")
|
|
351
353
|
) {
|
|
352
|
-
const path =
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
const path = token.kind === CHAT_PROJECT_TOKEN_KIND
|
|
355
|
+
? token.key
|
|
356
|
+
: resolveWorkspaceReferencePath({
|
|
357
|
+
projectRoot: selectedSession?.projectRoot,
|
|
358
|
+
relativePath: token.key,
|
|
359
|
+
});
|
|
356
360
|
if (path) {
|
|
357
361
|
presenter.chatThreadManager.openFilePreview({
|
|
358
362
|
path,
|
|
@@ -272,6 +272,32 @@ describe('buildChatRunMetadata', () => {
|
|
|
272
272
|
});
|
|
273
273
|
});
|
|
274
274
|
|
|
275
|
+
it('adds structured project references to run metadata', () => {
|
|
276
|
+
expect(
|
|
277
|
+
buildChatRunMetadata({
|
|
278
|
+
composerNodes: [
|
|
279
|
+
createChatComposerTokenNode({
|
|
280
|
+
tokenKind: 'project',
|
|
281
|
+
tokenKey: '/tmp/nextclaw',
|
|
282
|
+
label: 'NextClaw',
|
|
283
|
+
}),
|
|
284
|
+
],
|
|
285
|
+
}),
|
|
286
|
+
).toMatchObject({
|
|
287
|
+
ui_inline_tokens: {
|
|
288
|
+
schemaVersion: 2,
|
|
289
|
+
items: [
|
|
290
|
+
{
|
|
291
|
+
kind: 'project',
|
|
292
|
+
key: '/tmp/nextclaw',
|
|
293
|
+
label: 'NextClaw',
|
|
294
|
+
rawText: '@project:%2Ftmp%2Fnextclaw',
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
275
301
|
it('serializes child session materialization metadata for side chat drafts', () => {
|
|
276
302
|
expect(
|
|
277
303
|
buildChatRunMetadata({
|
|
@@ -5,10 +5,11 @@ import { t } from "@/shared/lib/i18n";
|
|
|
5
5
|
|
|
6
6
|
const PROJECTS_QUERY_KEY = ["projects"] as const;
|
|
7
7
|
|
|
8
|
-
export function useProjects() {
|
|
8
|
+
export function useProjects(options: { enabled?: boolean } = {}) {
|
|
9
9
|
return useQuery({
|
|
10
10
|
queryKey: PROJECTS_QUERY_KEY,
|
|
11
11
|
queryFn: fetchProjects,
|
|
12
|
+
enabled: options.enabled ?? true,
|
|
12
13
|
staleTime: 30_000,
|
|
13
14
|
});
|
|
14
15
|
}
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"chatProcessSummaryCancelled": "cancelled",
|
|
201
201
|
"chatRuntimeInitializing": "Chat is still initializing. You can keep typing and send once startup finishes.",
|
|
202
202
|
"chatRuntimeInitializationFailed": "Chat startup failed. Please retry in a moment or inspect the service logs.",
|
|
203
|
-
"chatInputPlaceholder": "Type a message, / for commands, apps or skills, @ for files, folders or panel apps, Enter to send, Shift + Enter for newline",
|
|
203
|
+
"chatInputPlaceholder": "Type a message, / for commands, apps or skills, @ for files, folders, projects or panel apps, Enter to send, Shift + Enter for newline",
|
|
204
204
|
"chatInputPlaceholderCompact": "Message NextClaw...",
|
|
205
205
|
"chatInputHint": "Multi-turn context is preserved in the current session.",
|
|
206
206
|
"chatSlashSection": "Slash menu",
|
|
@@ -239,15 +239,15 @@
|
|
|
239
239
|
"chatPanelAppReferenceNoDescription": "No panel app description",
|
|
240
240
|
"chatContextReferenceLoading": "Searching references...",
|
|
241
241
|
"chatContextReferenceSection": "References",
|
|
242
|
-
"chatContextReferenceNoResult": "No matching files, folders, or panel apps",
|
|
243
|
-
"chatContextReferenceHint": "Type @ to reference a file, folder, or panel app",
|
|
242
|
+
"chatContextReferenceNoResult": "No matching files, folders, projects, or panel apps",
|
|
243
|
+
"chatContextReferenceHint": "Type @ to reference a file, folder, project, or panel app",
|
|
244
244
|
"chatContextReferenceItemHint": "Press Enter to add this reference.",
|
|
245
245
|
"chatContextReferenceFilesLabel": "Files & Folders",
|
|
246
246
|
"chatContextReferenceFilesType": "Project Context",
|
|
247
247
|
"chatContextReferenceFilesDescription": "Search and reference files or folders in the current project.",
|
|
248
248
|
"chatContextReferenceFilesHint": "Press Enter to browse files and folders.",
|
|
249
249
|
"chatContextReferenceBack": "Back to all references",
|
|
250
|
-
"chatContextReferenceBackDescription": "Return to the unified file, folder, and panel app reference menu.",
|
|
250
|
+
"chatContextReferenceBackDescription": "Return to the unified file, folder, project, and panel app reference menu.",
|
|
251
251
|
"chatContextReferenceBackHint": "Press Enter to go back.",
|
|
252
252
|
"chatContextReferenceParent": "Up one folder",
|
|
253
253
|
"chatContextReferenceParentDescription": "Browse the parent folder without closing the reference menu.",
|
|
@@ -255,6 +255,15 @@
|
|
|
255
255
|
"chatContextReferenceCurrentDirectory": "Reference current folder",
|
|
256
256
|
"chatContextReferenceFileDescription": "Use this file as explicit context for the current message.",
|
|
257
257
|
"chatContextReferenceDirectoryDescription": "Use this folder as a context scope for the current message.",
|
|
258
|
+
"chatContextReferenceProjectsLabel": "Projects",
|
|
259
|
+
"chatContextReferenceProjectsType": "Project Context",
|
|
260
|
+
"chatContextReferenceProjectsDescription": "Choose a registered project as context for the current message.",
|
|
261
|
+
"chatContextReferenceProjectsHint": "Press Enter to browse projects.",
|
|
262
|
+
"chatContextReferenceProjectType": "Project",
|
|
263
|
+
"chatContextReferenceProjectDescription": "Use this project's name, path, and directory outline as explicit context for the current message.",
|
|
264
|
+
"chatContextReferenceProjectPath": "Project path",
|
|
265
|
+
"chatContextReferenceProjectSection": "Projects",
|
|
266
|
+
"chatContextReferenceProjectsLoadFailed": "Failed to load projects",
|
|
258
267
|
"chatContextReferenceProjectRoot": "Project directory",
|
|
259
268
|
"chatContextReferenceWorkspaceSection": "Files & Folders",
|
|
260
269
|
"chatContextReferenceSearchFailed": "File search failed",
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"chatProcessSummaryCancelled": "已取消",
|
|
201
201
|
"chatRuntimeInitializing": "聊天能力正在初始化。你可以先输入内容,完成后即可发送。",
|
|
202
202
|
"chatRuntimeInitializationFailed": "聊天能力启动失败,请稍后重试或检查服务日志。",
|
|
203
|
-
"chatInputPlaceholder": "输入消息,/ 选命令、应用或技能,@
|
|
203
|
+
"chatInputPlaceholder": "输入消息,/ 选命令、应用或技能,@ 引用文件、目录、项目或面板应用,Enter 发送,Shift + Enter 换行",
|
|
204
204
|
"chatInputPlaceholderCompact": "发消息...",
|
|
205
205
|
"chatInputHint": "支持多轮上下文,默认走当前会话。",
|
|
206
206
|
"chatSlashSection": "斜杠菜单",
|
|
@@ -239,15 +239,15 @@
|
|
|
239
239
|
"chatPanelAppReferenceNoDescription": "暂无面板应用描述",
|
|
240
240
|
"chatContextReferenceLoading": "搜索引用中…",
|
|
241
241
|
"chatContextReferenceSection": "引用",
|
|
242
|
-
"chatContextReferenceNoResult": "
|
|
243
|
-
"chatContextReferenceHint": "输入 @
|
|
242
|
+
"chatContextReferenceNoResult": "没有匹配的文件、目录、项目或面板应用",
|
|
243
|
+
"chatContextReferenceHint": "输入 @ 引用文件、目录、项目或面板应用",
|
|
244
244
|
"chatContextReferenceItemHint": "回车添加这个引用。",
|
|
245
245
|
"chatContextReferenceFilesLabel": "文件与文件夹",
|
|
246
246
|
"chatContextReferenceFilesType": "项目上下文",
|
|
247
247
|
"chatContextReferenceFilesDescription": "搜索并引用当前项目中的文件或目录。",
|
|
248
248
|
"chatContextReferenceFilesHint": "回车进入文件与文件夹。",
|
|
249
249
|
"chatContextReferenceBack": "返回全部引用",
|
|
250
|
-
"chatContextReferenceBackDescription": "
|
|
250
|
+
"chatContextReferenceBackDescription": "返回文件、目录、项目和面板应用的统一引用入口。",
|
|
251
251
|
"chatContextReferenceBackHint": "回车返回。",
|
|
252
252
|
"chatContextReferenceParent": "返回上一级",
|
|
253
253
|
"chatContextReferenceParentDescription": "在不关闭引用菜单的情况下浏览上一级文件夹。",
|
|
@@ -255,6 +255,15 @@
|
|
|
255
255
|
"chatContextReferenceCurrentDirectory": "引用当前文件夹",
|
|
256
256
|
"chatContextReferenceFileDescription": "把这个文件作为本次消息的显式上下文。",
|
|
257
257
|
"chatContextReferenceDirectoryDescription": "把这个目录作为本次消息的上下文范围。",
|
|
258
|
+
"chatContextReferenceProjectsLabel": "项目",
|
|
259
|
+
"chatContextReferenceProjectsType": "项目上下文",
|
|
260
|
+
"chatContextReferenceProjectsDescription": "从已登记项目中选择一个,作为本次消息的上下文。",
|
|
261
|
+
"chatContextReferenceProjectsHint": "回车进入项目列表。",
|
|
262
|
+
"chatContextReferenceProjectType": "项目",
|
|
263
|
+
"chatContextReferenceProjectDescription": "把这个项目的名称、路径和目录概览作为本次消息的显式上下文。",
|
|
264
|
+
"chatContextReferenceProjectPath": "项目路径",
|
|
265
|
+
"chatContextReferenceProjectSection": "项目",
|
|
266
|
+
"chatContextReferenceProjectsLoadFailed": "项目加载失败",
|
|
258
267
|
"chatContextReferenceProjectRoot": "项目目录",
|
|
259
268
|
"chatContextReferenceWorkspaceSection": "文件与文件夹",
|
|
260
269
|
"chatContextReferenceSearchFailed": "文件搜索失败",
|