@nextclaw/ui 0.15.18 → 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 +39 -0
- package/dist/assets/api-tWUa6l9J.js +13 -0
- package/dist/assets/app-presenter-provider-s-FrOinZ.js +106 -0
- package/dist/assets/{appearance-settings-page-BkQaPtQ-.js → appearance-settings-page-dMRBD7ni.js} +1 -1
- package/dist/assets/{book-open-WsEnuW9P.js → book-open-BQ29ZmDE.js} +1 -1
- package/dist/assets/{card-xVrS1b1T.js → card-CtKueklV.js} +1 -1
- package/dist/assets/{channels-list-page-CAWuTHSM.js → channels-list-page-C8y3rqYt.js} +2 -2
- package/dist/assets/chat-page-MXBltS3G.js +1 -0
- package/dist/assets/{config-split-page-xj0UtMow.js → config-split-page-uC6d_r9c.js} +1 -1
- package/dist/assets/{confirm-dialog-e2YCUzt1.js → confirm-dialog-C1pbqHZX.js} +1 -1
- package/dist/assets/desktop-update-config-DssP8LCk.js +1 -0
- package/dist/assets/{dist-Ca-jiFrK.js → dist-rYHnBo8t.js} +1 -1
- package/dist/assets/doc-browser-AXF1zRsM.js +1 -0
- package/dist/assets/doc-browser-D8ng4EUc.js +1 -0
- package/dist/assets/doc-browser-context-CPHNzQ8m.js +1 -0
- package/dist/assets/{eye-B30J471A.js → eye-CiJz3ODY.js} +1 -1
- package/dist/assets/{form-actions-CnSzu3WD.js → form-actions-BnZVzLZf.js} +1 -1
- package/dist/assets/index-CocHWEsI.js +2 -0
- package/dist/assets/index-DN6KM7WD.css +1 -0
- package/dist/assets/loader-circle-AOpK5kkW.js +1 -0
- package/dist/assets/{logo-badge-CISsltAo.js → logo-badge-BXI-Kdhd.js} +1 -1
- package/dist/assets/mcp-marketplace-page-5ogeBHnl.js +1 -0
- package/dist/assets/mcp-marketplace-page-CxNe-Q9Q.js +9 -0
- package/dist/assets/model-config-page-CR_mgEVp.js +1 -0
- package/dist/assets/{navigation-link-qPPUiLt0.js → navigation-link-C1cvB7y4.js} +1 -1
- package/dist/assets/plus-DI37QuHc.js +1 -0
- package/dist/assets/{popover-DHx55dbc.js → popover-ZoDwVwwd.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-1TJxAhx6.js → provider-scoped-model-input-DIL6Ex_d.js} +1 -1
- package/dist/assets/{providers-config-page-BwHtHIPe.js → providers-config-page-D2NXVctW.js} +1 -1
- package/dist/assets/react-39CDNZlM.js +8 -0
- package/dist/assets/{refresh-cw-C7bynrWX.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-BLrTVO3n.js → runtime-config-page-0lb5NyKc.js} +1 -1
- package/dist/assets/{save-CsRsFtHS.js → save-BrckuAXY.js} +1 -1
- package/dist/assets/{search-D0HtJIL-.js → search-BhXQB2TO.js} +1 -1
- package/dist/assets/{search-config-page-n1GElgij.js → search-config-page-DFOQ6SDJ.js} +1 -1
- package/dist/assets/{secrets-config-page-DbUc3Ggi.js → secrets-config-page-BqqeClqE.js} +2 -2
- package/dist/assets/security-config-13AtPIlm.js +1 -0
- package/dist/assets/{select-C1zfp9zM.js → select-CVzGGT38.js} +1 -1
- package/dist/assets/{settings-2-Ba3cLSN2.js → settings-2-BP6L2enG.js} +1 -1
- package/dist/assets/skeleton-OwCNQJ7M.js +1 -0
- package/dist/assets/{tabs-DmKgMKzb.js → tabs-B-QWjKCL.js} +1 -1
- package/dist/assets/{tag-chip-2vwlVUwM.js → tag-chip-C88mxjwx.js} +1 -1
- package/dist/assets/{trash-2-CUpWjNHu.js → trash-2-ck3t33I3.js} +1 -1
- package/dist/assets/use-config-BAr5bH2c.js +1 -0
- package/dist/assets/{use-confirm-dialog-CeUEE4p5.js → use-confirm-dialog-C4e0nG_4.js} +1 -1
- package/dist/assets/{use-viewport-layout-DaKJ-kap.js → use-viewport-layout-k1isXfNm.js} +1 -1
- package/dist/assets/x-C1ijNWcE.js +1 -0
- package/dist/index.html +28 -28
- 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-inline-panel-app-card.test.tsx +35 -0
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +12 -1
- package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +3 -1
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +13 -5
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +10 -5
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +9 -0
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +4 -1
- package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +29 -0
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +1 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime-queue.test.tsx +32 -14
- package/src/features/chat/features/workspace/components/__tests__/workspace-file-content-preview.test.tsx +41 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +4 -0
- package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +15 -2
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +11 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +1 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +11 -8
- package/src/features/chat/managers/__tests__/chat-ui.manager.test.ts +6 -0
- package/src/features/chat/managers/chat-thread.manager.ts +2 -0
- package/src/features/chat/managers/chat-ui.manager.ts +10 -4
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +26 -0
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +4 -0
- package/src/features/chat/stores/chat-thread.store.ts +6 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +32 -0
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +2 -0
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +20 -38
- package/src/shared/components/doc-browser/types/doc-browser.types.ts +5 -0
- package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +63 -0
- package/src/shared/hooks/use-projects.ts +2 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +14 -4
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +14 -4
- package/dist/assets/api-D5UCLVE_.js +0 -13
- package/dist/assets/app-presenter-provider-De7Zwitu.js +0 -106
- package/dist/assets/chat-page-AvOuaxuv.js +0 -1
- package/dist/assets/desktop-update-config-D5NIaimo.js +0 -1
- package/dist/assets/doc-browser-B2tNEKrJ.js +0 -1
- package/dist/assets/doc-browser-CZX6I6CU.js +0 -1
- package/dist/assets/doc-browser-context-CGz0LULR.js +0 -1
- package/dist/assets/index-B5AoUrMf.js +0 -2
- package/dist/assets/index-QveCjxCj.css +0 -1
- package/dist/assets/loader-circle-DjwSRQHW.js +0 -1
- package/dist/assets/mcp-marketplace-page-BZa7p7Qf.js +0 -1
- package/dist/assets/mcp-marketplace-page-CPcPzMUQ.js +0 -9
- package/dist/assets/model-config-page-D6ex3_NM.js +0 -1
- package/dist/assets/plus-C45awBb1.js +0 -1
- package/dist/assets/react-DerwD2EA.js +0 -8
- package/dist/assets/remote-B6TXClIg.js +0 -1
- package/dist/assets/rotate-cw-Bhx0XkQP.js +0 -1
- package/dist/assets/security-config-n2WHhHXV.js +0 -1
- package/dist/assets/skeleton-DL5npsVL.js +0 -1
- package/dist/assets/use-config-BvxDWV-M.js +0 -1
- package/dist/assets/x-DGdYT7rH.js +0 -1
package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx
CHANGED
|
@@ -9,6 +9,14 @@ const usePanelAppsMock = vi.hoisted(() =>
|
|
|
9
9
|
isLoading: false,
|
|
10
10
|
})),
|
|
11
11
|
);
|
|
12
|
+
const useProjectsMock = vi.hoisted(() =>
|
|
13
|
+
vi.fn(() => ({
|
|
14
|
+
data: { projects: [] },
|
|
15
|
+
error: null,
|
|
16
|
+
isFetching: false,
|
|
17
|
+
isLoading: false,
|
|
18
|
+
})),
|
|
19
|
+
);
|
|
12
20
|
const useServerPathSearchMock = vi.hoisted(() =>
|
|
13
21
|
vi.fn(() => ({
|
|
14
22
|
data: { entries: [] },
|
|
@@ -38,6 +46,9 @@ const useServerPathBrowseMock = vi.hoisted(() =>
|
|
|
38
46
|
vi.mock('@/features/panel-apps', () => ({
|
|
39
47
|
usePanelApps: usePanelAppsMock,
|
|
40
48
|
}));
|
|
49
|
+
vi.mock('@/shared/hooks/use-projects', () => ({
|
|
50
|
+
useProjects: useProjectsMock,
|
|
51
|
+
}));
|
|
41
52
|
vi.mock('@/shared/hooks/use-server-path-search', () => ({
|
|
42
53
|
useServerPathSearch: useServerPathSearchMock,
|
|
43
54
|
}));
|
|
@@ -68,6 +79,7 @@ function createHookParams() {
|
|
|
68
79
|
|
|
69
80
|
beforeEach(() => {
|
|
70
81
|
usePanelAppsMock.mockClear();
|
|
82
|
+
useProjectsMock.mockClear();
|
|
71
83
|
useServerPathBrowseMock.mockClear();
|
|
72
84
|
useServerPathSearchMock.mockClear();
|
|
73
85
|
});
|
|
@@ -84,6 +96,7 @@ it('browses nested folders while reserving search for project-wide queries', ()
|
|
|
84
96
|
start: 0,
|
|
85
97
|
}),
|
|
86
98
|
);
|
|
99
|
+
expect(useProjectsMock).toHaveBeenLastCalledWith({ enabled: true });
|
|
87
100
|
const filesItem = result.current.inputSurfaceState.panel?.items.find(
|
|
88
101
|
(item) => item.selectionBehavior === 'navigate',
|
|
89
102
|
);
|
|
@@ -10,11 +10,13 @@ import {
|
|
|
10
10
|
CONTEXT_REFERENCE_TRIGGER_SPEC,
|
|
11
11
|
createContextReferenceInputSurfacePlugin,
|
|
12
12
|
} from '@/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils';
|
|
13
|
+
import type { ContextReferenceLocation } from '@/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types';
|
|
13
14
|
import {
|
|
14
15
|
createSlashCommandInputSurfacePlugin,
|
|
15
16
|
type ChatSlashCommandDescriptor,
|
|
16
17
|
} from '@/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils';
|
|
17
18
|
import type { ChatInputBarAdapterTexts, ChatSkillRecord } from '@/features/chat/types/chat-input-bar.types';
|
|
19
|
+
import { useProjects } from '@/shared/hooks/use-projects';
|
|
18
20
|
import { useServerPathBrowse } from '@/shared/hooks/use-server-path-browse';
|
|
19
21
|
import { useServerPathSearch } from '@/shared/hooks/use-server-path-search';
|
|
20
22
|
import type { ServerPathEntryView, ServerPathSearchEntryView } from '@/shared/lib/api';
|
|
@@ -39,7 +41,7 @@ type UseChatInputSurfaceStateParams = {
|
|
|
39
41
|
function buildChatInputSurfacePlugins(params: {
|
|
40
42
|
commands: readonly ChatSlashCommandDescriptor[];
|
|
41
43
|
language: I18nLanguage;
|
|
42
|
-
onNavigate: (
|
|
44
|
+
onNavigate: (location: ContextReferenceLocation) => void;
|
|
43
45
|
onSelectPanelApp: (appId: string) => void;
|
|
44
46
|
onSelectSkill: (skillRef: string) => void;
|
|
45
47
|
slashTexts: ChatInputSurfaceSlashTexts;
|
|
@@ -105,6 +107,15 @@ function buildChatInputSurfacePlugins(params: {
|
|
|
105
107
|
parentLabel: t('chatContextReferenceParent', language),
|
|
106
108
|
parentDescription: t('chatContextReferenceParentDescription', language),
|
|
107
109
|
parentHintLabel: t('chatContextReferenceParentHint', language),
|
|
110
|
+
projectDescription: t('chatContextReferenceProjectDescription', language),
|
|
111
|
+
projectPathLabel: t('chatContextReferenceProjectPath', language),
|
|
112
|
+
projectSectionLabel: t('chatContextReferenceProjectSection', language),
|
|
113
|
+
projectSubtitle: t('chatContextReferenceProjectType', language),
|
|
114
|
+
projectsDescription: t('chatContextReferenceProjectsDescription', language),
|
|
115
|
+
projectsHintLabel: t('chatContextReferenceProjectsHint', language),
|
|
116
|
+
projectsLabel: t('chatContextReferenceProjectsLabel', language),
|
|
117
|
+
projectsLoadFailedLabel: t('chatContextReferenceProjectsLoadFailed', language),
|
|
118
|
+
projectsSubtitle: t('chatContextReferenceProjectsType', language),
|
|
108
119
|
projectRootLabel: t('chatContextReferenceProjectRoot', language),
|
|
109
120
|
searchFailedLabel: t('chatContextReferenceSearchFailed', language),
|
|
110
121
|
workspaceSectionLabel: t('chatContextReferenceWorkspaceSection', language),
|
|
@@ -150,7 +161,9 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
150
161
|
skillRecords,
|
|
151
162
|
commands,
|
|
152
163
|
} = params;
|
|
153
|
-
const [
|
|
164
|
+
const [referenceLocation, setReferenceLocation] = useState<ContextReferenceLocation>({
|
|
165
|
+
view: 'root',
|
|
166
|
+
});
|
|
154
167
|
const [inputSurfaceTrigger, setInputSurfaceTriggerState] = useState<ChatInputSurfaceTrigger | null>(null);
|
|
155
168
|
const inputSurfaceTriggerSignatureRef = useRef('null');
|
|
156
169
|
const setInputSurfaceTrigger = useCallback((nextTrigger: ChatInputSurfaceTrigger | null): void => {
|
|
@@ -158,23 +171,25 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
158
171
|
if (inputSurfaceTriggerSignatureRef.current === nextSignature) return;
|
|
159
172
|
inputSurfaceTriggerSignatureRef.current = nextSignature;
|
|
160
173
|
if (!nextTrigger) {
|
|
161
|
-
|
|
174
|
+
setReferenceLocation({ view: 'root' });
|
|
162
175
|
}
|
|
163
176
|
setInputSurfaceTriggerState(nextTrigger);
|
|
164
177
|
}, []);
|
|
165
178
|
const isContextReferenceTrigger = inputSurfaceTrigger?.key === CONTEXT_REFERENCE_TRIGGER_SPEC.key;
|
|
179
|
+
const referencePath = referenceLocation.view === 'files' ? referenceLocation.path : '';
|
|
166
180
|
const deferredReferenceQuery = useDeferredValue(
|
|
167
181
|
isContextReferenceTrigger ? inputSurfaceTrigger.query : '',
|
|
168
182
|
);
|
|
169
183
|
const isBrowsingServerPaths = Boolean(
|
|
170
184
|
isContextReferenceTrigger &&
|
|
171
185
|
projectRoot &&
|
|
172
|
-
|
|
186
|
+
referenceLocation.view === 'files' &&
|
|
173
187
|
!deferredReferenceQuery.trim(),
|
|
174
188
|
);
|
|
175
189
|
const shouldSearchServerPaths = Boolean(
|
|
176
190
|
isContextReferenceTrigger &&
|
|
177
191
|
projectRoot &&
|
|
192
|
+
referenceLocation.view !== 'projects' &&
|
|
178
193
|
deferredReferenceQuery.trim(),
|
|
179
194
|
);
|
|
180
195
|
const serverPathBrowse = useServerPathBrowse({
|
|
@@ -193,7 +208,7 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
193
208
|
() => isBrowsingServerPaths
|
|
194
209
|
? buildBrowsedReferenceEntries({
|
|
195
210
|
entries: serverPathBrowse.data?.entries ?? [],
|
|
196
|
-
parentRelativePath: referencePath
|
|
211
|
+
parentRelativePath: referencePath,
|
|
197
212
|
})
|
|
198
213
|
: serverPathSearch.data?.entries ?? [],
|
|
199
214
|
[
|
|
@@ -207,12 +222,13 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
207
222
|
isContextReferenceTrigger ||
|
|
208
223
|
inputSurfaceTrigger?.key === CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC.key;
|
|
209
224
|
const panelApps = usePanelApps({ enabled: shouldLoadPanelApps });
|
|
225
|
+
const projects = useProjects({ enabled: isContextReferenceTrigger });
|
|
210
226
|
|
|
211
227
|
const inputSurfacePlugins = useMemo(
|
|
212
228
|
() => buildChatInputSurfacePlugins({
|
|
213
229
|
commands,
|
|
214
230
|
language,
|
|
215
|
-
onNavigate:
|
|
231
|
+
onNavigate: setReferenceLocation,
|
|
216
232
|
onSelectPanelApp,
|
|
217
233
|
onSelectSkill,
|
|
218
234
|
slashTexts: itemTexts.slashTexts,
|
|
@@ -229,12 +245,15 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
229
245
|
() => resolveChatInputSurfaceState({
|
|
230
246
|
data: {
|
|
231
247
|
isPanelAppsLoading: panelApps.isLoading || panelApps.isFetching,
|
|
248
|
+
isProjectsLoading: projects.isLoading || projects.isFetching,
|
|
232
249
|
isServerPathSearchLoading: activeServerPathQuery.isLoading || activeServerPathQuery.isFetching,
|
|
233
250
|
isSkillsLoading,
|
|
234
251
|
panelApps: panelApps.data?.entries ?? [],
|
|
235
252
|
projectRoot,
|
|
253
|
+
projects: projects.data?.projects ?? [],
|
|
254
|
+
projectsError: projects.error instanceof Error ? projects.error.message : null,
|
|
236
255
|
recentSkillValues,
|
|
237
|
-
|
|
256
|
+
referenceLocation,
|
|
238
257
|
serverPathEntries,
|
|
239
258
|
serverPathSearchError: activeServerPathQuery.error instanceof Error
|
|
240
259
|
? activeServerPathQuery.error.message
|
|
@@ -251,10 +270,14 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
251
270
|
panelApps.isFetching,
|
|
252
271
|
panelApps.isLoading,
|
|
253
272
|
projectRoot,
|
|
273
|
+
projects.data?.projects,
|
|
274
|
+
projects.error,
|
|
275
|
+
projects.isFetching,
|
|
276
|
+
projects.isLoading,
|
|
254
277
|
activeServerPathQuery.error,
|
|
255
278
|
activeServerPathQuery.isFetching,
|
|
256
279
|
activeServerPathQuery.isLoading,
|
|
257
|
-
|
|
280
|
+
referenceLocation,
|
|
258
281
|
serverPathEntries,
|
|
259
282
|
isSkillsLoading,
|
|
260
283
|
recentSkillValues,
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
CONTEXT_REFERENCE_TRIGGER_SPEC,
|
|
5
5
|
createContextReferenceInputSurfacePlugin,
|
|
6
6
|
} from '@/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils';
|
|
7
|
-
import type {
|
|
7
|
+
import type { ContextReferenceLocation } from '@/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types';
|
|
8
|
+
import type { PanelAppEntryView, ProjectView } from '@/shared/lib/api';
|
|
8
9
|
|
|
9
10
|
function createPanelApp(): PanelAppEntryView {
|
|
10
11
|
return {
|
|
@@ -25,6 +26,15 @@ function createPanelApp(): PanelAppEntryView {
|
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
function createProject(): ProjectView {
|
|
30
|
+
return {
|
|
31
|
+
name: 'NextClaw',
|
|
32
|
+
rootPath: '/tmp/nextclaw',
|
|
33
|
+
createdAt: '2026-07-28T00:00:00.000Z',
|
|
34
|
+
updatedAt: '2026-07-28T00:00:00.000Z',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
function createPlugin(onNavigate = vi.fn()) {
|
|
29
39
|
return createContextReferenceInputSurfacePlugin({
|
|
30
40
|
itemTexts: {
|
|
@@ -43,6 +53,15 @@ function createPlugin(onNavigate = vi.fn()) {
|
|
|
43
53
|
parentLabel: 'Up one folder',
|
|
44
54
|
parentDescription: 'Browse parent',
|
|
45
55
|
parentHintLabel: 'Enter to browse parent',
|
|
56
|
+
projectDescription: 'Project context',
|
|
57
|
+
projectPathLabel: 'Project path',
|
|
58
|
+
projectSectionLabel: 'Projects',
|
|
59
|
+
projectSubtitle: 'Project',
|
|
60
|
+
projectsDescription: 'Browse registered projects',
|
|
61
|
+
projectsHintLabel: 'Enter to browse projects',
|
|
62
|
+
projectsLabel: 'Projects',
|
|
63
|
+
projectsLoadFailedLabel: 'Projects failed',
|
|
64
|
+
projectsSubtitle: 'Project Context',
|
|
46
65
|
projectRootLabel: 'Project directory',
|
|
47
66
|
searchFailedLabel: 'Search failed',
|
|
48
67
|
workspaceSectionLabel: 'Files & Folders',
|
|
@@ -65,15 +84,18 @@ function createPlugin(onNavigate = vi.fn()) {
|
|
|
65
84
|
});
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
function createData(
|
|
87
|
+
function createData(referenceLocation: ContextReferenceLocation = { view: 'root' }) {
|
|
69
88
|
return {
|
|
70
89
|
isPanelAppsLoading: false,
|
|
90
|
+
isProjectsLoading: false,
|
|
71
91
|
isServerPathSearchLoading: false,
|
|
72
92
|
isSkillsLoading: false,
|
|
73
93
|
panelApps: [createPanelApp()],
|
|
74
94
|
projectRoot: '/tmp/project',
|
|
95
|
+
projects: [createProject()],
|
|
96
|
+
projectsError: null,
|
|
75
97
|
recentSkillValues: [],
|
|
76
|
-
|
|
98
|
+
referenceLocation,
|
|
77
99
|
serverPathEntries: [
|
|
78
100
|
{
|
|
79
101
|
name: 'server-path.ts',
|
|
@@ -98,7 +120,7 @@ function createData(referencePath: string | null = null) {
|
|
|
98
120
|
}
|
|
99
121
|
|
|
100
122
|
describe('context reference input surface plugin', () => {
|
|
101
|
-
it('shows Files & Folders and panel apps in the root @ menu', () => {
|
|
123
|
+
it('shows Files & Folders, Projects, and panel apps in the root @ menu', () => {
|
|
102
124
|
const state = resolveChatInputSurfaceState({
|
|
103
125
|
plugins: [createPlugin()],
|
|
104
126
|
trigger: {
|
|
@@ -116,6 +138,11 @@ describe('context reference input surface plugin', () => {
|
|
|
116
138
|
icon: 'files',
|
|
117
139
|
selectionBehavior: 'navigate',
|
|
118
140
|
}),
|
|
141
|
+
expect.objectContaining({
|
|
142
|
+
title: 'Projects',
|
|
143
|
+
icon: 'project',
|
|
144
|
+
selectionBehavior: 'navigate',
|
|
145
|
+
}),
|
|
119
146
|
expect.objectContaining({
|
|
120
147
|
key: 'panel-app:task-board',
|
|
121
148
|
tokenKind: 'panel_app',
|
|
@@ -133,7 +160,7 @@ describe('context reference input surface plugin', () => {
|
|
|
133
160
|
start: 0,
|
|
134
161
|
end: 7,
|
|
135
162
|
},
|
|
136
|
-
data: createData(''),
|
|
163
|
+
data: createData({ view: 'files', path: '' }),
|
|
137
164
|
});
|
|
138
165
|
|
|
139
166
|
expect(state.panel?.items.slice(1)).toEqual([
|
|
@@ -173,7 +200,7 @@ describe('context reference input surface plugin', () => {
|
|
|
173
200
|
|
|
174
201
|
state.panel?.onSelectItem?.(item!);
|
|
175
202
|
|
|
176
|
-
expect(onNavigate).toHaveBeenCalledWith('');
|
|
203
|
+
expect(onNavigate).toHaveBeenCalledWith({ view: 'files', path: '' });
|
|
177
204
|
expect(item).not.toHaveProperty('tokenKind');
|
|
178
205
|
});
|
|
179
206
|
|
|
@@ -187,7 +214,7 @@ describe('context reference input surface plugin', () => {
|
|
|
187
214
|
start: 0,
|
|
188
215
|
end: 1,
|
|
189
216
|
},
|
|
190
|
-
data: createData(''),
|
|
217
|
+
data: createData({ view: 'files', path: '' }),
|
|
191
218
|
});
|
|
192
219
|
const directoryItem = rootState.panel?.items.find((item) => item.title === 'docs');
|
|
193
220
|
|
|
@@ -197,7 +224,7 @@ describe('context reference input surface plugin', () => {
|
|
|
197
224
|
});
|
|
198
225
|
expect(directoryItem?.tokenKind).toBeUndefined();
|
|
199
226
|
rootState.panel?.onSelectItem?.(directoryItem!);
|
|
200
|
-
expect(onNavigate).toHaveBeenCalledWith('docs');
|
|
227
|
+
expect(onNavigate).toHaveBeenCalledWith({ view: 'files', path: 'docs' });
|
|
201
228
|
|
|
202
229
|
const nestedState = resolveChatInputSurfaceState({
|
|
203
230
|
plugins: [createPlugin(onNavigate)],
|
|
@@ -207,7 +234,7 @@ describe('context reference input surface plugin', () => {
|
|
|
207
234
|
start: 0,
|
|
208
235
|
end: 1,
|
|
209
236
|
},
|
|
210
|
-
data: createData('docs'),
|
|
237
|
+
data: createData({ view: 'files', path: 'docs' }),
|
|
211
238
|
});
|
|
212
239
|
expect(nestedState.panel?.items).toEqual(expect.arrayContaining([
|
|
213
240
|
expect.objectContaining({
|
|
@@ -222,4 +249,47 @@ describe('context reference input surface plugin', () => {
|
|
|
222
249
|
}),
|
|
223
250
|
]));
|
|
224
251
|
});
|
|
252
|
+
|
|
253
|
+
it('opens the project view and builds semantic project tokens', () => {
|
|
254
|
+
const onNavigate = vi.fn();
|
|
255
|
+
const rootState = resolveChatInputSurfaceState({
|
|
256
|
+
plugins: [createPlugin(onNavigate)],
|
|
257
|
+
trigger: {
|
|
258
|
+
...CONTEXT_REFERENCE_TRIGGER_SPEC,
|
|
259
|
+
query: '',
|
|
260
|
+
start: 0,
|
|
261
|
+
end: 1,
|
|
262
|
+
},
|
|
263
|
+
data: createData(),
|
|
264
|
+
});
|
|
265
|
+
const projectsItem = rootState.panel?.items.find((item) => item.title === 'Projects');
|
|
266
|
+
|
|
267
|
+
rootState.panel?.onSelectItem?.(projectsItem!);
|
|
268
|
+
expect(onNavigate).toHaveBeenCalledWith({ view: 'projects' });
|
|
269
|
+
|
|
270
|
+
const projectsState = resolveChatInputSurfaceState({
|
|
271
|
+
plugins: [createPlugin(onNavigate)],
|
|
272
|
+
trigger: {
|
|
273
|
+
...CONTEXT_REFERENCE_TRIGGER_SPEC,
|
|
274
|
+
query: 'next',
|
|
275
|
+
start: 0,
|
|
276
|
+
end: 5,
|
|
277
|
+
},
|
|
278
|
+
data: createData({ view: 'projects' }),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
expect(projectsState.panel?.items).toEqual([
|
|
282
|
+
expect.objectContaining({
|
|
283
|
+
title: 'Back',
|
|
284
|
+
selectionBehavior: 'navigate',
|
|
285
|
+
}),
|
|
286
|
+
expect.objectContaining({
|
|
287
|
+
title: 'NextClaw',
|
|
288
|
+
icon: 'project',
|
|
289
|
+
tokenKind: 'project',
|
|
290
|
+
tokenKey: '/tmp/nextclaw',
|
|
291
|
+
detailLines: ['Project path: /tmp/nextclaw'],
|
|
292
|
+
}),
|
|
293
|
+
]);
|
|
294
|
+
});
|
|
225
295
|
});
|
|
@@ -86,6 +86,7 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
86
86
|
const state = resolveChatInputSurfaceState({
|
|
87
87
|
data: {
|
|
88
88
|
isPanelAppsLoading: false,
|
|
89
|
+
isProjectsLoading: false,
|
|
89
90
|
isServerPathSearchLoading: false,
|
|
90
91
|
isSkillsLoading: false,
|
|
91
92
|
panelApps: [
|
|
@@ -96,8 +97,10 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
96
97
|
}),
|
|
97
98
|
],
|
|
98
99
|
projectRoot: '/tmp/project',
|
|
100
|
+
projects: [],
|
|
101
|
+
projectsError: null,
|
|
99
102
|
recentSkillValues: [],
|
|
100
|
-
|
|
103
|
+
referenceLocation: { view: 'root' },
|
|
101
104
|
serverPathEntries: [],
|
|
102
105
|
serverPathSearchError: null,
|
|
103
106
|
skillRecords: [
|
|
@@ -153,12 +156,15 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
153
156
|
const state = resolveChatInputSurfaceState({
|
|
154
157
|
data: {
|
|
155
158
|
isPanelAppsLoading: false,
|
|
159
|
+
isProjectsLoading: false,
|
|
156
160
|
isServerPathSearchLoading: false,
|
|
157
161
|
isSkillsLoading: false,
|
|
158
162
|
panelApps: [],
|
|
159
163
|
projectRoot: '/tmp/project',
|
|
164
|
+
projects: [],
|
|
165
|
+
projectsError: null,
|
|
160
166
|
recentSkillValues: [],
|
|
161
|
-
|
|
167
|
+
referenceLocation: { view: 'root' },
|
|
162
168
|
serverPathEntries: [],
|
|
163
169
|
serverPathSearchError: null,
|
|
164
170
|
skillRecords: [
|
|
@@ -202,12 +208,15 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
202
208
|
const state = resolveChatInputSurfaceState({
|
|
203
209
|
data: {
|
|
204
210
|
isPanelAppsLoading: false,
|
|
211
|
+
isProjectsLoading: false,
|
|
205
212
|
isServerPathSearchLoading: false,
|
|
206
213
|
isSkillsLoading: false,
|
|
207
214
|
panelApps: [],
|
|
208
215
|
projectRoot: '/tmp/project',
|
|
216
|
+
projects: [],
|
|
217
|
+
projectsError: null,
|
|
209
218
|
recentSkillValues: [],
|
|
210
|
-
|
|
219
|
+
referenceLocation: { view: 'root' },
|
|
211
220
|
serverPathEntries: [],
|
|
212
221
|
serverPathSearchError: null,
|
|
213
222
|
skillRecords: [],
|
|
@@ -233,6 +242,7 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
233
242
|
const state = resolveChatInputSurfaceState({
|
|
234
243
|
data: {
|
|
235
244
|
isPanelAppsLoading: false,
|
|
245
|
+
isProjectsLoading: false,
|
|
236
246
|
isServerPathSearchLoading: false,
|
|
237
247
|
isSkillsLoading: false,
|
|
238
248
|
panelApps: [
|
|
@@ -243,8 +253,10 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
243
253
|
}),
|
|
244
254
|
],
|
|
245
255
|
projectRoot: '/tmp/project',
|
|
256
|
+
projects: [],
|
|
257
|
+
projectsError: null,
|
|
246
258
|
recentSkillValues: [],
|
|
247
|
-
|
|
259
|
+
referenceLocation: { view: 'root' },
|
|
248
260
|
serverPathEntries: [],
|
|
249
261
|
serverPathSearchError: null,
|
|
250
262
|
skillRecords: [],
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { ChatSkillRecord } from '@/features/chat/types/chat-input-bar.types';
|
|
2
|
-
import type { PanelAppEntryView, ServerPathSearchEntryView } from '@/shared/lib/api';
|
|
2
|
+
import type { PanelAppEntryView, ProjectView, ServerPathSearchEntryView } from '@/shared/lib/api';
|
|
3
|
+
|
|
4
|
+
export type ContextReferenceLocation =
|
|
5
|
+
| { view: 'root' }
|
|
6
|
+
| { view: 'files'; path: string }
|
|
7
|
+
| { view: 'projects' };
|
|
3
8
|
|
|
4
9
|
export type ChatInputProductPluginData = {
|
|
5
10
|
isPanelAppsLoading: boolean;
|
|
11
|
+
isProjectsLoading: boolean;
|
|
6
12
|
isServerPathSearchLoading: boolean;
|
|
7
13
|
isSkillsLoading: boolean;
|
|
8
14
|
panelApps: readonly PanelAppEntryView[];
|
|
9
15
|
projectRoot: string;
|
|
16
|
+
projects: readonly ProjectView[];
|
|
17
|
+
projectsError: string | null;
|
|
10
18
|
recentSkillValues: readonly string[];
|
|
11
|
-
|
|
19
|
+
referenceLocation: ContextReferenceLocation;
|
|
12
20
|
serverPathEntries: readonly ServerPathSearchEntryView[];
|
|
13
21
|
serverPathSearchError: string | null;
|
|
14
22
|
skillRecords: readonly ChatSkillRecord[];
|
package/src/features/chat/features/input/input-surface-plugins/context-reference-items.utils.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import type { ChatInputSurfaceItem } from '@nextclaw/agent-chat-ui';
|
|
2
|
+
import {
|
|
3
|
+
CHAT_PROJECT_TOKEN_KIND,
|
|
4
|
+
CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
|
|
5
|
+
CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
6
|
+
} from '@nextclaw/shared';
|
|
7
|
+
import type { ProjectView, ServerPathSearchEntryView } from '@/shared/lib/api';
|
|
8
|
+
import type { ContextReferenceLocation } from './chat-input-product-plugin-adapters.types';
|
|
9
|
+
import {
|
|
10
|
+
resolveInputSurfaceMatchTier,
|
|
11
|
+
scoreInputSurfaceSearchCandidate,
|
|
12
|
+
} from './input-surface-search.utils';
|
|
13
|
+
|
|
14
|
+
export const FILES_NAVIGATION_ITEM_KEY = 'context-reference:navigate:files';
|
|
15
|
+
export const PROJECTS_NAVIGATION_ITEM_KEY = 'context-reference:navigate:projects';
|
|
16
|
+
export const ROOT_NAVIGATION_ITEM_KEY = 'context-reference:navigate:root';
|
|
17
|
+
|
|
18
|
+
const WORKSPACE_SECTION_KEY = 'workspace';
|
|
19
|
+
const PROJECT_SECTION_KEY = 'projects';
|
|
20
|
+
|
|
21
|
+
export type ContextReferenceInputSurfaceTexts = {
|
|
22
|
+
backLabel: string;
|
|
23
|
+
backDescription: string;
|
|
24
|
+
backHintLabel: string;
|
|
25
|
+
currentDirectoryLabel: string;
|
|
26
|
+
directoryDescription: string;
|
|
27
|
+
fileDescription: string;
|
|
28
|
+
filesDescription: string;
|
|
29
|
+
filesHintLabel: string;
|
|
30
|
+
filesLabel: string;
|
|
31
|
+
filesSubtitle: string;
|
|
32
|
+
panelAppSectionLabel: string;
|
|
33
|
+
parentLabel: string;
|
|
34
|
+
parentDescription: string;
|
|
35
|
+
parentHintLabel: string;
|
|
36
|
+
projectDescription: string;
|
|
37
|
+
projectPathLabel: string;
|
|
38
|
+
projectSectionLabel: string;
|
|
39
|
+
projectSubtitle: string;
|
|
40
|
+
projectsDescription: string;
|
|
41
|
+
projectsHintLabel: string;
|
|
42
|
+
projectsLabel: string;
|
|
43
|
+
projectsLoadFailedLabel: string;
|
|
44
|
+
projectsSubtitle: string;
|
|
45
|
+
projectRootLabel: string;
|
|
46
|
+
searchFailedLabel: string;
|
|
47
|
+
workspaceSectionLabel: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function resolveProjectLabel(projectRoot: string): string {
|
|
51
|
+
return projectRoot.split(/[\\/]+/).filter(Boolean).at(-1) ?? projectRoot;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildPathPreview(params: {
|
|
55
|
+
kind: 'directory' | 'file';
|
|
56
|
+
projectRoot: string;
|
|
57
|
+
relativePath: string;
|
|
58
|
+
}) {
|
|
59
|
+
const { kind, projectRoot, relativePath } = params;
|
|
60
|
+
const pathSegments = relativePath.split('/').filter(Boolean);
|
|
61
|
+
return {
|
|
62
|
+
rootLabel: resolveProjectLabel(projectRoot),
|
|
63
|
+
segments: pathSegments.map((label, index) => ({
|
|
64
|
+
label,
|
|
65
|
+
kind: index === pathSegments.length - 1 ? kind : 'directory' as const,
|
|
66
|
+
})),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function buildProjectReferenceItems(params: {
|
|
71
|
+
projects: readonly ProjectView[];
|
|
72
|
+
query: string;
|
|
73
|
+
texts: ContextReferenceInputSurfaceTexts;
|
|
74
|
+
}): ChatInputSurfaceItem[] {
|
|
75
|
+
const collator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
|
|
76
|
+
return params.projects
|
|
77
|
+
.map((project, order) => ({
|
|
78
|
+
order,
|
|
79
|
+
project,
|
|
80
|
+
score: scoreInputSurfaceSearchCandidate(
|
|
81
|
+
{
|
|
82
|
+
id: project.rootPath,
|
|
83
|
+
label: project.name,
|
|
84
|
+
description: project.rootPath,
|
|
85
|
+
aliases: project.template ? [project.template] : [],
|
|
86
|
+
},
|
|
87
|
+
params.query,
|
|
88
|
+
),
|
|
89
|
+
}))
|
|
90
|
+
.filter(({ score }) => score > 0)
|
|
91
|
+
.sort((left, right) => {
|
|
92
|
+
const leftTier = resolveInputSurfaceMatchTier(left.score);
|
|
93
|
+
const rightTier = resolveInputSurfaceMatchTier(right.score);
|
|
94
|
+
if (rightTier !== leftTier) {
|
|
95
|
+
return rightTier - leftTier;
|
|
96
|
+
}
|
|
97
|
+
if (right.score !== left.score) {
|
|
98
|
+
return right.score - left.score;
|
|
99
|
+
}
|
|
100
|
+
return collator.compare(left.project.name, right.project.name) || left.order - right.order;
|
|
101
|
+
})
|
|
102
|
+
.map(({ project }) => ({
|
|
103
|
+
key: `context-reference:project:${project.rootPath}`,
|
|
104
|
+
icon: 'project',
|
|
105
|
+
title: project.name,
|
|
106
|
+
subtitle: params.texts.projectSubtitle,
|
|
107
|
+
description: params.texts.projectDescription,
|
|
108
|
+
detailLines: [`${params.texts.projectPathLabel}: ${project.rootPath}`],
|
|
109
|
+
sectionKey: PROJECT_SECTION_KEY,
|
|
110
|
+
sectionLabel: params.texts.projectSectionLabel,
|
|
111
|
+
value: project.rootPath,
|
|
112
|
+
tokenKind: CHAT_PROJECT_TOKEN_KIND,
|
|
113
|
+
tokenKey: project.rootPath,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildWorkspaceReferenceItems(params: {
|
|
118
|
+
entries: readonly ServerPathSearchEntryView[];
|
|
119
|
+
navigateDirectories: boolean;
|
|
120
|
+
projectRoot: string;
|
|
121
|
+
texts: ContextReferenceInputSurfaceTexts;
|
|
122
|
+
}): ChatInputSurfaceItem[] {
|
|
123
|
+
const projectLabel = resolveProjectLabel(params.projectRoot);
|
|
124
|
+
return params.entries.map((entry) => {
|
|
125
|
+
const navigates = params.navigateDirectories && entry.kind === 'directory';
|
|
126
|
+
return {
|
|
127
|
+
key: `workspace:${entry.kind}:${entry.relativePath}`,
|
|
128
|
+
icon: entry.kind === 'directory' ? 'folder' : 'file',
|
|
129
|
+
title: entry.name,
|
|
130
|
+
subtitle: entry.parentRelativePath || projectLabel,
|
|
131
|
+
description: entry.kind === 'directory'
|
|
132
|
+
? params.texts.directoryDescription
|
|
133
|
+
: params.texts.fileDescription,
|
|
134
|
+
detailLines: [
|
|
135
|
+
`${params.texts.projectRootLabel}: ${params.projectRoot}`,
|
|
136
|
+
entry.relativePath,
|
|
137
|
+
],
|
|
138
|
+
sectionKey: WORKSPACE_SECTION_KEY,
|
|
139
|
+
sectionLabel: params.texts.workspaceSectionLabel,
|
|
140
|
+
value: entry.relativePath,
|
|
141
|
+
tokenKind: navigates
|
|
142
|
+
? undefined
|
|
143
|
+
: entry.kind === 'directory'
|
|
144
|
+
? CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND
|
|
145
|
+
: CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
146
|
+
tokenKey: navigates ? undefined : entry.relativePath,
|
|
147
|
+
selectionBehavior: navigates ? 'navigate' : undefined,
|
|
148
|
+
pathPreview: buildPathPreview({
|
|
149
|
+
kind: entry.kind,
|
|
150
|
+
projectRoot: params.projectRoot,
|
|
151
|
+
relativePath: entry.relativePath,
|
|
152
|
+
}),
|
|
153
|
+
} satisfies ChatInputSurfaceItem;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function buildCurrentDirectoryReferenceItem(params: {
|
|
158
|
+
projectRoot: string;
|
|
159
|
+
referencePath: string;
|
|
160
|
+
texts: ContextReferenceInputSurfaceTexts;
|
|
161
|
+
}): ChatInputSurfaceItem {
|
|
162
|
+
const { projectRoot, referencePath, texts } = params;
|
|
163
|
+
const title = referencePath.split('/').filter(Boolean).at(-1) ?? referencePath;
|
|
164
|
+
return {
|
|
165
|
+
key: `context-reference:current-directory:${referencePath}`,
|
|
166
|
+
icon: 'folder',
|
|
167
|
+
title: texts.currentDirectoryLabel,
|
|
168
|
+
subtitle: title,
|
|
169
|
+
description: texts.directoryDescription,
|
|
170
|
+
detailLines: [
|
|
171
|
+
`${texts.projectRootLabel}: ${projectRoot}`,
|
|
172
|
+
referencePath,
|
|
173
|
+
],
|
|
174
|
+
sectionKey: WORKSPACE_SECTION_KEY,
|
|
175
|
+
sectionLabel: texts.workspaceSectionLabel,
|
|
176
|
+
value: referencePath,
|
|
177
|
+
tokenKind: CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND,
|
|
178
|
+
tokenKey: referencePath,
|
|
179
|
+
pathPreview: buildPathPreview({
|
|
180
|
+
kind: 'directory',
|
|
181
|
+
projectRoot,
|
|
182
|
+
relativePath: referencePath,
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function buildFilesNavigationItem(
|
|
188
|
+
texts: ContextReferenceInputSurfaceTexts,
|
|
189
|
+
): ChatInputSurfaceItem {
|
|
190
|
+
return {
|
|
191
|
+
key: FILES_NAVIGATION_ITEM_KEY,
|
|
192
|
+
icon: 'files',
|
|
193
|
+
title: texts.filesLabel,
|
|
194
|
+
subtitle: texts.filesSubtitle,
|
|
195
|
+
description: texts.filesDescription,
|
|
196
|
+
detailLines: [],
|
|
197
|
+
hintLabel: texts.filesHintLabel,
|
|
198
|
+
selectionBehavior: 'navigate',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function buildProjectsNavigationItem(
|
|
203
|
+
texts: ContextReferenceInputSurfaceTexts,
|
|
204
|
+
): ChatInputSurfaceItem {
|
|
205
|
+
return {
|
|
206
|
+
key: PROJECTS_NAVIGATION_ITEM_KEY,
|
|
207
|
+
icon: 'project',
|
|
208
|
+
title: texts.projectsLabel,
|
|
209
|
+
subtitle: texts.projectsSubtitle,
|
|
210
|
+
description: texts.projectsDescription,
|
|
211
|
+
detailLines: [],
|
|
212
|
+
hintLabel: texts.projectsHintLabel,
|
|
213
|
+
selectionBehavior: 'navigate',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveParentReferencePath(referencePath: string): string | null {
|
|
218
|
+
const segments = referencePath.split('/').filter(Boolean);
|
|
219
|
+
return segments.length === 0 ? null : segments.slice(0, -1).join('/');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function buildBackNavigationItem(params: {
|
|
223
|
+
location: ContextReferenceLocation;
|
|
224
|
+
texts: ContextReferenceInputSurfaceTexts;
|
|
225
|
+
}): ChatInputSurfaceItem {
|
|
226
|
+
const { location, texts } = params;
|
|
227
|
+
const referencePath = location.view === 'files' ? location.path : '';
|
|
228
|
+
const isRootView = location.view !== 'files' || !referencePath;
|
|
229
|
+
return {
|
|
230
|
+
key: ROOT_NAVIGATION_ITEM_KEY,
|
|
231
|
+
icon: 'back',
|
|
232
|
+
title: isRootView ? texts.backLabel : texts.parentLabel,
|
|
233
|
+
subtitle: '',
|
|
234
|
+
description: isRootView ? texts.backDescription : texts.parentDescription,
|
|
235
|
+
detailLines: [],
|
|
236
|
+
hintLabel: isRootView ? texts.backHintLabel : texts.parentHintLabel,
|
|
237
|
+
value: resolveParentReferencePath(referencePath) ?? '',
|
|
238
|
+
selectionBehavior: 'navigate',
|
|
239
|
+
};
|
|
240
|
+
}
|