@nextclaw/ui 0.15.6 → 0.15.8
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 +34 -0
- package/dist/assets/{api-Dyb5rgh0.js → api-kGIZo6kv.js} +1 -1
- package/dist/assets/{app-presenter-provider-7jV0nPiL.js → app-presenter-provider-Ct0DePOC.js} +48 -48
- package/dist/assets/{appearance-settings-page-DFm3BbEb.js → appearance-settings-page-CcA8Kewq.js} +1 -1
- package/dist/assets/{book-open-L6LCuqOr.js → book-open-D4LvR1N9.js} +1 -1
- package/dist/assets/{channels-list-page-DOaPKNJA.js → channels-list-page-CybW_0nu.js} +1 -1
- package/dist/assets/{chat-page-Cw9uDhD_.js → chat-page-Bs8c1adx.js} +1 -1
- package/dist/assets/{config-split-page-CqlqjnCK.js → config-split-page-BvhZDJGS.js} +1 -1
- package/dist/assets/{confirm-dialog-bAp8NU93.js → confirm-dialog-Ucx_SQm4.js} +1 -1
- package/dist/assets/desktop-update-config-Ch9m1GRB.js +1 -0
- package/dist/assets/{dist-CrtbGLlh.js → dist-CCwmPmQ3.js} +1 -1
- package/dist/assets/doc-browser-Cr7330LU2.js +1 -0
- package/dist/assets/{doc-browser-BmnQa30-.js → doc-browser-N9UwkWvd.js} +1 -1
- package/dist/assets/{doc-browser-context-BlpUUFrR.js → doc-browser-context-CIIKT2sE.js} +1 -1
- package/dist/assets/{download-BhZZNXD8.js → download-CSAHAjF9.js} +1 -1
- package/dist/assets/{external-link-B9Uoq5J7.js → external-link-Dno-cJJr.js} +1 -1
- package/dist/assets/{eye-K5knauEM.js → eye-BZkraQUn.js} +1 -1
- package/dist/assets/{folder-BIYZZWJv.js → folder-Bro2sQD3.js} +1 -1
- package/dist/assets/{form-actions-CKprWFeM.js → form-actions-CC31CR28.js} +1 -1
- package/dist/assets/index-BlKbckX4.css +1 -0
- package/dist/assets/index-EO5Gsaff.js +2 -0
- package/dist/assets/loader-circle-DyToFBCM.js +1 -0
- package/dist/assets/{logo-badge-BQ-XFZod.js → logo-badge-B___Witw.js} +1 -1
- package/dist/assets/mcp-marketplace-page-3Q-VOIJ3.js +1 -0
- package/dist/assets/{mcp-marketplace-page-BfA7A3n4.js → mcp-marketplace-page-Ccy-UG8R.js} +1 -1
- package/dist/assets/{model-config-page-BbWbW_qM.js → model-config-page-BsMxhVaO.js} +1 -1
- package/dist/assets/plus-wynE6QCr.js +1 -0
- package/dist/assets/{popover-CX9zFsuc.js → popover-CAy9XSdY.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-B-9_DtIP.js → provider-scoped-model-input-Cqo78Dx_.js} +1 -1
- package/dist/assets/{providers-config-page-xwIjGur2.js → providers-config-page-CZSc-ruy.js} +1 -1
- package/dist/assets/react-UuyIZ_Oi.js +8 -0
- package/dist/assets/{refresh-cw-kXdvbhFf.js → refresh-cw-w9L_vlM-.js} +1 -1
- package/dist/assets/remote-D4rv7kb-.js +1 -0
- package/dist/assets/rotate-cw-C0VFtLju.js +1 -0
- package/dist/assets/{runtime-config-page-DTsi7czJ.js → runtime-config-page-DM63Nkxm.js} +1 -1
- package/dist/assets/{save-C9qpz2_2.js → save-DdL5VkJS.js} +1 -1
- package/dist/assets/{search-_kEi29qo.js → search-DydusQAS.js} +1 -1
- package/dist/assets/{search-config-page-H7ug8f4D.js → search-config-page-CfwWop50.js} +1 -1
- package/dist/assets/{secrets-config-page-C1_6rINu.js → secrets-config-page-BPIZUwLW.js} +1 -1
- package/dist/assets/security-config-CeBjHoFL.js +1 -0
- package/dist/assets/{select-MJKhKAWR.js → select-BEtjsC3L.js} +1 -1
- package/dist/assets/{setting-row-DA3tJuew.js → setting-row-CTA16hhx.js} +1 -1
- package/dist/assets/{settings-2-D_y7zXJz.js → settings-2-D6BrZlD5.js} +1 -1
- package/dist/assets/{skeleton-0uTLUhm9.js → skeleton-DcJKUZCH.js} +1 -1
- package/dist/assets/{tabs-h44XPocd.js → tabs-CrPVCYHg.js} +1 -1
- package/dist/assets/{tag-chip-bRZ04q3C.js → tag-chip-Bvm-V7SR.js} +1 -1
- package/dist/assets/{trash-2-BRpSE-Wt.js → trash-2-5ABWNTB-.js} +1 -1
- package/dist/assets/{use-config-CwDEAl6f.js → use-config-DJaZYyZC.js} +1 -1
- package/dist/assets/{use-confirm-dialog-BbnWw-8c.js → use-confirm-dialog-QUWL1EuB.js} +1 -1
- package/dist/assets/{use-viewport-layout-CqgIMDQL.js → use-viewport-layout-BWYz8zwd.js} +1 -1
- package/dist/assets/x-gM1Q7cRX.js +1 -0
- package/dist/index.html +30 -29
- package/index.html +1 -0
- package/package.json +6 -6
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +13 -2
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +18 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +47 -2
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +63 -16
- package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +54 -4
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +4 -4
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +1 -3
- package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +110 -27
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +17 -1
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +17 -0
- package/src/features/chat/features/message/components/__tests__/chat-inline-file-preview.test.tsx +159 -0
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +92 -26
- package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +90 -21
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +24 -3
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +1 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +55 -0
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +1 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +5 -6
- package/src/features/chat/features/workspace/components/__tests__/workspace-file-content-preview.test.tsx +67 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +9 -7
- package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +95 -4
- package/src/features/marketplace/components/__tests__/marketplace-page-detail.test.tsx +1 -0
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +62 -0
- package/src/features/marketplace/components/curated-shelves/__tests__/marketplace-curated-scene-route.test.tsx +1 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +2 -15
- package/src/features/marketplace/components/marketplace-page.tsx +10 -2
- package/src/features/marketplace/hooks/__tests__/use-marketplace.test.tsx +26 -1
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +11 -0
- package/src/features/marketplace/hooks/use-marketplace-list-model.ts +1 -1
- package/src/features/marketplace/hooks/use-marketplace.ts +28 -1
- package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +21 -0
- package/src/features/system-status/components/desktop-update-config.tsx +8 -5
- package/src/shared/components/common/__tests__/brand-header.test.tsx +39 -17
- package/src/shared/components/common/brand-header.tsx +55 -51
- package/src/shared/lib/i18n/locales/en-US/chat.json +5 -0
- package/src/shared/lib/i18n/locales/en-US/desktop-update.json +5 -1
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +1 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +5 -0
- package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +5 -1
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +1 -0
- package/dist/assets/desktop-update-config-2SjMfCMl.js +0 -1
- package/dist/assets/doc-browser-A5IRyDXQ2.js +0 -1
- package/dist/assets/index-BVn702m4.css +0 -1
- package/dist/assets/index-CFxJ1Fkg.js +0 -2
- package/dist/assets/loader-circle-Bj2RMrCR.js +0 -1
- package/dist/assets/mcp-marketplace-page-CXHYO95j.js +0 -1
- package/dist/assets/plus-TmFdwO3P.js +0 -1
- package/dist/assets/react-DsgudwpT.js +0 -8
- package/dist/assets/remote-B9F2uCk9.js +0 -1
- package/dist/assets/rotate-cw-DEc6aihr.js +0 -1
- package/dist/assets/security-config-Cr2ERKLu.js +0 -1
- package/dist/assets/x-DdEDL6ju.js +0 -1
|
@@ -54,6 +54,24 @@ vi.mock('@/features/chat/features/input/hooks/use-chat-model-favorites', () => (
|
|
|
54
54
|
}),
|
|
55
55
|
}));
|
|
56
56
|
|
|
57
|
+
vi.mock('@/shared/hooks/use-server-path-browse', () => ({
|
|
58
|
+
useServerPathBrowse: () => ({
|
|
59
|
+
data: { entries: [] },
|
|
60
|
+
error: null,
|
|
61
|
+
isFetching: false,
|
|
62
|
+
isLoading: false,
|
|
63
|
+
}),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
vi.mock('@/shared/hooks/use-server-path-search', () => ({
|
|
67
|
+
useServerPathSearch: () => ({
|
|
68
|
+
data: { entries: [] },
|
|
69
|
+
error: null,
|
|
70
|
+
isFetching: false,
|
|
71
|
+
isLoading: false,
|
|
72
|
+
}),
|
|
73
|
+
}));
|
|
74
|
+
|
|
57
75
|
vi.mock('@/shared/lib/api', async (importOriginal) => {
|
|
58
76
|
const actual = await importOriginal();
|
|
59
77
|
return {
|
package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx
CHANGED
|
@@ -17,6 +17,23 @@ const useServerPathSearchMock = vi.hoisted(() =>
|
|
|
17
17
|
isLoading: false,
|
|
18
18
|
})),
|
|
19
19
|
);
|
|
20
|
+
const useServerPathBrowseMock = vi.hoisted(() =>
|
|
21
|
+
vi.fn(() => ({
|
|
22
|
+
data: {
|
|
23
|
+
entries: [
|
|
24
|
+
{
|
|
25
|
+
name: 'src',
|
|
26
|
+
path: '/tmp/project/src',
|
|
27
|
+
kind: 'directory' as const,
|
|
28
|
+
hidden: false,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
error: null,
|
|
33
|
+
isFetching: false,
|
|
34
|
+
isLoading: false,
|
|
35
|
+
})),
|
|
36
|
+
);
|
|
20
37
|
|
|
21
38
|
vi.mock('@/features/panel-apps', () => ({
|
|
22
39
|
usePanelApps: usePanelAppsMock,
|
|
@@ -24,6 +41,9 @@ vi.mock('@/features/panel-apps', () => ({
|
|
|
24
41
|
vi.mock('@/shared/hooks/use-server-path-search', () => ({
|
|
25
42
|
useServerPathSearch: useServerPathSearchMock,
|
|
26
43
|
}));
|
|
44
|
+
vi.mock('@/shared/hooks/use-server-path-browse', () => ({
|
|
45
|
+
useServerPathBrowse: useServerPathBrowseMock,
|
|
46
|
+
}));
|
|
27
47
|
|
|
28
48
|
function createHookParams() {
|
|
29
49
|
return {
|
|
@@ -48,10 +68,11 @@ function createHookParams() {
|
|
|
48
68
|
|
|
49
69
|
beforeEach(() => {
|
|
50
70
|
usePanelAppsMock.mockClear();
|
|
71
|
+
useServerPathBrowseMock.mockClear();
|
|
51
72
|
useServerPathSearchMock.mockClear();
|
|
52
73
|
});
|
|
53
74
|
|
|
54
|
-
it('
|
|
75
|
+
it('browses nested folders while reserving search for project-wide queries', () => {
|
|
55
76
|
const { result } = renderHook(() => useChatInputSurfaceState(createHookParams()));
|
|
56
77
|
|
|
57
78
|
act(() =>
|
|
@@ -68,9 +89,33 @@ it('enables project path search after navigating into files and folders', () =>
|
|
|
68
89
|
);
|
|
69
90
|
act(() => result.current.inputSurfaceState.panel?.onSelectItem?.(filesItem!));
|
|
70
91
|
|
|
92
|
+
expect(useServerPathBrowseMock).toHaveBeenLastCalledWith({
|
|
93
|
+
path: '.',
|
|
94
|
+
basePath: '/tmp/project',
|
|
95
|
+
includeFiles: true,
|
|
96
|
+
enabled: true,
|
|
97
|
+
});
|
|
98
|
+
const directoryItem = result.current.inputSurfaceState.panel?.items.find(
|
|
99
|
+
(item) => item.title === 'src',
|
|
100
|
+
);
|
|
101
|
+
act(() => result.current.inputSurfaceState.panel?.onSelectItem?.(directoryItem!));
|
|
102
|
+
expect(useServerPathBrowseMock).toHaveBeenLastCalledWith({
|
|
103
|
+
path: 'src',
|
|
104
|
+
basePath: '/tmp/project',
|
|
105
|
+
includeFiles: true,
|
|
106
|
+
enabled: true,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
act(() => result.current.setInputSurfaceTrigger({
|
|
110
|
+
end: 7,
|
|
111
|
+
key: 'context-reference',
|
|
112
|
+
marker: '@',
|
|
113
|
+
query: 'needle',
|
|
114
|
+
start: 0,
|
|
115
|
+
}));
|
|
71
116
|
expect(useServerPathSearchMock).toHaveBeenLastCalledWith({
|
|
72
117
|
basePath: '/tmp/project',
|
|
73
|
-
query: '',
|
|
118
|
+
query: 'needle',
|
|
74
119
|
enabled: true,
|
|
75
120
|
});
|
|
76
121
|
});
|
|
@@ -10,13 +10,14 @@ 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 { ContextReferenceMode } from '@/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types';
|
|
14
13
|
import {
|
|
15
14
|
createSlashCommandInputSurfacePlugin,
|
|
16
15
|
type ChatSlashCommandDescriptor,
|
|
17
16
|
} from '@/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils';
|
|
18
17
|
import type { ChatInputBarAdapterTexts, ChatSkillRecord } from '@/features/chat/types/chat-input-bar.types';
|
|
18
|
+
import { useServerPathBrowse } from '@/shared/hooks/use-server-path-browse';
|
|
19
19
|
import { useServerPathSearch } from '@/shared/hooks/use-server-path-search';
|
|
20
|
+
import type { ServerPathEntryView, ServerPathSearchEntryView } from '@/shared/lib/api';
|
|
20
21
|
|
|
21
22
|
type ChatInputSurfaceSlashTexts = Pick<
|
|
22
23
|
ChatInputBarAdapterTexts,
|
|
@@ -38,7 +39,7 @@ type UseChatInputSurfaceStateParams = {
|
|
|
38
39
|
function buildChatInputSurfacePlugins(params: {
|
|
39
40
|
commands: readonly ChatSlashCommandDescriptor[];
|
|
40
41
|
language: I18nLanguage;
|
|
41
|
-
onNavigate: (
|
|
42
|
+
onNavigate: (path: string | null) => void;
|
|
42
43
|
onSelectPanelApp: (appId: string) => void;
|
|
43
44
|
onSelectSkill: (skillRef: string) => void;
|
|
44
45
|
slashTexts: ChatInputSurfaceSlashTexts;
|
|
@@ -93,6 +94,7 @@ function buildChatInputSurfacePlugins(params: {
|
|
|
93
94
|
backLabel: t('chatContextReferenceBack', language),
|
|
94
95
|
backDescription: t('chatContextReferenceBackDescription', language),
|
|
95
96
|
backHintLabel: t('chatContextReferenceBackHint', language),
|
|
97
|
+
currentDirectoryLabel: t('chatContextReferenceCurrentDirectory', language),
|
|
96
98
|
directoryDescription: t('chatContextReferenceDirectoryDescription', language),
|
|
97
99
|
fileDescription: t('chatContextReferenceFileDescription', language),
|
|
98
100
|
filesDescription: t('chatContextReferenceFilesDescription', language),
|
|
@@ -100,6 +102,9 @@ function buildChatInputSurfacePlugins(params: {
|
|
|
100
102
|
filesLabel: t('chatContextReferenceFilesLabel', language),
|
|
101
103
|
filesSubtitle: t('chatContextReferenceFilesType', language),
|
|
102
104
|
panelAppSectionLabel: t('chatPanelAppReferenceSection', language),
|
|
105
|
+
parentLabel: t('chatContextReferenceParent', language),
|
|
106
|
+
parentDescription: t('chatContextReferenceParentDescription', language),
|
|
107
|
+
parentHintLabel: t('chatContextReferenceParentHint', language),
|
|
103
108
|
projectRootLabel: t('chatContextReferenceProjectRoot', language),
|
|
104
109
|
searchFailedLabel: t('chatContextReferenceSearchFailed', language),
|
|
105
110
|
workspaceSectionLabel: t('chatContextReferenceWorkspaceSection', language),
|
|
@@ -118,6 +123,21 @@ function buildChatInputSurfacePlugins(params: {
|
|
|
118
123
|
];
|
|
119
124
|
}
|
|
120
125
|
|
|
126
|
+
function buildBrowsedReferenceEntries(params: {
|
|
127
|
+
entries: readonly ServerPathEntryView[];
|
|
128
|
+
parentRelativePath: string;
|
|
129
|
+
}): ServerPathSearchEntryView[] {
|
|
130
|
+
const parentRelativePath = params.parentRelativePath.split('/').filter(Boolean).join('/');
|
|
131
|
+
return params.entries.map((entry) => ({
|
|
132
|
+
name: entry.name,
|
|
133
|
+
path: entry.path,
|
|
134
|
+
relativePath: [parentRelativePath, entry.name].filter(Boolean).join('/'),
|
|
135
|
+
parentRelativePath,
|
|
136
|
+
kind: entry.kind,
|
|
137
|
+
hidden: entry.hidden,
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
|
|
121
141
|
export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams) {
|
|
122
142
|
const {
|
|
123
143
|
isSkillsLoading,
|
|
@@ -130,7 +150,7 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
130
150
|
skillRecords,
|
|
131
151
|
commands,
|
|
132
152
|
} = params;
|
|
133
|
-
const [
|
|
153
|
+
const [referencePath, setReferencePath] = useState<string | null>(null);
|
|
134
154
|
const [inputSurfaceTrigger, setInputSurfaceTriggerState] = useState<ChatInputSurfaceTrigger | null>(null);
|
|
135
155
|
const inputSurfaceTriggerSignatureRef = useRef('null');
|
|
136
156
|
const setInputSurfaceTrigger = useCallback((nextTrigger: ChatInputSurfaceTrigger | null): void => {
|
|
@@ -138,7 +158,7 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
138
158
|
if (inputSurfaceTriggerSignatureRef.current === nextSignature) return;
|
|
139
159
|
inputSurfaceTriggerSignatureRef.current = nextSignature;
|
|
140
160
|
if (!nextTrigger) {
|
|
141
|
-
|
|
161
|
+
setReferencePath(null);
|
|
142
162
|
}
|
|
143
163
|
setInputSurfaceTriggerState(nextTrigger);
|
|
144
164
|
}, []);
|
|
@@ -146,16 +166,43 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
146
166
|
const deferredReferenceQuery = useDeferredValue(
|
|
147
167
|
isContextReferenceTrigger ? inputSurfaceTrigger.query : '',
|
|
148
168
|
);
|
|
169
|
+
const isBrowsingServerPaths = Boolean(
|
|
170
|
+
isContextReferenceTrigger &&
|
|
171
|
+
projectRoot &&
|
|
172
|
+
referencePath !== null &&
|
|
173
|
+
!deferredReferenceQuery.trim(),
|
|
174
|
+
);
|
|
149
175
|
const shouldSearchServerPaths = Boolean(
|
|
150
176
|
isContextReferenceTrigger &&
|
|
151
177
|
projectRoot &&
|
|
152
|
-
|
|
178
|
+
deferredReferenceQuery.trim(),
|
|
153
179
|
);
|
|
180
|
+
const serverPathBrowse = useServerPathBrowse({
|
|
181
|
+
path: referencePath || '.',
|
|
182
|
+
basePath: projectRoot,
|
|
183
|
+
includeFiles: true,
|
|
184
|
+
enabled: isBrowsingServerPaths,
|
|
185
|
+
});
|
|
154
186
|
const serverPathSearch = useServerPathSearch({
|
|
155
187
|
basePath: projectRoot,
|
|
156
188
|
query: deferredReferenceQuery,
|
|
157
189
|
enabled: shouldSearchServerPaths,
|
|
158
190
|
});
|
|
191
|
+
const activeServerPathQuery = isBrowsingServerPaths ? serverPathBrowse : serverPathSearch;
|
|
192
|
+
const serverPathEntries = useMemo(
|
|
193
|
+
() => isBrowsingServerPaths
|
|
194
|
+
? buildBrowsedReferenceEntries({
|
|
195
|
+
entries: serverPathBrowse.data?.entries ?? [],
|
|
196
|
+
parentRelativePath: referencePath ?? '',
|
|
197
|
+
})
|
|
198
|
+
: serverPathSearch.data?.entries ?? [],
|
|
199
|
+
[
|
|
200
|
+
isBrowsingServerPaths,
|
|
201
|
+
referencePath,
|
|
202
|
+
serverPathBrowse.data?.entries,
|
|
203
|
+
serverPathSearch.data?.entries,
|
|
204
|
+
],
|
|
205
|
+
);
|
|
159
206
|
const shouldLoadPanelApps =
|
|
160
207
|
isContextReferenceTrigger ||
|
|
161
208
|
inputSurfaceTrigger?.key === CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC.key;
|
|
@@ -165,7 +212,7 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
165
212
|
() => buildChatInputSurfacePlugins({
|
|
166
213
|
commands,
|
|
167
214
|
language,
|
|
168
|
-
onNavigate:
|
|
215
|
+
onNavigate: setReferencePath,
|
|
169
216
|
onSelectPanelApp,
|
|
170
217
|
onSelectSkill,
|
|
171
218
|
slashTexts: itemTexts.slashTexts,
|
|
@@ -182,15 +229,15 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
182
229
|
() => resolveChatInputSurfaceState({
|
|
183
230
|
data: {
|
|
184
231
|
isPanelAppsLoading: panelApps.isLoading || panelApps.isFetching,
|
|
185
|
-
isServerPathSearchLoading:
|
|
232
|
+
isServerPathSearchLoading: activeServerPathQuery.isLoading || activeServerPathQuery.isFetching,
|
|
186
233
|
isSkillsLoading,
|
|
187
234
|
panelApps: panelApps.data?.entries ?? [],
|
|
188
235
|
projectRoot,
|
|
189
236
|
recentSkillValues,
|
|
190
|
-
|
|
191
|
-
serverPathEntries
|
|
192
|
-
serverPathSearchError:
|
|
193
|
-
?
|
|
237
|
+
referencePath,
|
|
238
|
+
serverPathEntries,
|
|
239
|
+
serverPathSearchError: activeServerPathQuery.error instanceof Error
|
|
240
|
+
? activeServerPathQuery.error.message
|
|
194
241
|
: null,
|
|
195
242
|
skillRecords
|
|
196
243
|
},
|
|
@@ -204,11 +251,11 @@ export function useChatInputSurfaceState(params: UseChatInputSurfaceStateParams)
|
|
|
204
251
|
panelApps.isFetching,
|
|
205
252
|
panelApps.isLoading,
|
|
206
253
|
projectRoot,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
254
|
+
activeServerPathQuery.error,
|
|
255
|
+
activeServerPathQuery.isFetching,
|
|
256
|
+
activeServerPathQuery.isLoading,
|
|
257
|
+
referencePath,
|
|
258
|
+
serverPathEntries,
|
|
212
259
|
isSkillsLoading,
|
|
213
260
|
recentSkillValues,
|
|
214
261
|
skillRecords
|
|
@@ -32,6 +32,7 @@ function createPlugin(onNavigate = vi.fn()) {
|
|
|
32
32
|
backLabel: 'Back',
|
|
33
33
|
backDescription: 'Back to references',
|
|
34
34
|
backHintLabel: 'Enter to go back',
|
|
35
|
+
currentDirectoryLabel: 'Reference current folder',
|
|
35
36
|
directoryDescription: 'Directory context',
|
|
36
37
|
fileDescription: 'File context',
|
|
37
38
|
filesDescription: 'Browse project files',
|
|
@@ -39,6 +40,9 @@ function createPlugin(onNavigate = vi.fn()) {
|
|
|
39
40
|
filesLabel: 'Files & Folders',
|
|
40
41
|
filesSubtitle: 'Project Context',
|
|
41
42
|
panelAppSectionLabel: 'Panel Apps',
|
|
43
|
+
parentLabel: 'Up one folder',
|
|
44
|
+
parentDescription: 'Browse parent',
|
|
45
|
+
parentHintLabel: 'Enter to browse parent',
|
|
42
46
|
projectRootLabel: 'Project directory',
|
|
43
47
|
searchFailedLabel: 'Search failed',
|
|
44
48
|
workspaceSectionLabel: 'Files & Folders',
|
|
@@ -61,7 +65,7 @@ function createPlugin(onNavigate = vi.fn()) {
|
|
|
61
65
|
});
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
function createData(
|
|
68
|
+
function createData(referencePath: string | null = null) {
|
|
65
69
|
return {
|
|
66
70
|
isPanelAppsLoading: false,
|
|
67
71
|
isServerPathSearchLoading: false,
|
|
@@ -69,7 +73,7 @@ function createData(referenceMode: 'root' | 'files' = 'root') {
|
|
|
69
73
|
panelApps: [createPanelApp()],
|
|
70
74
|
projectRoot: '/tmp/project',
|
|
71
75
|
recentSkillValues: [],
|
|
72
|
-
|
|
76
|
+
referencePath,
|
|
73
77
|
serverPathEntries: [
|
|
74
78
|
{
|
|
75
79
|
name: 'server-path.ts',
|
|
@@ -129,7 +133,7 @@ describe('context reference input surface plugin', () => {
|
|
|
129
133
|
start: 0,
|
|
130
134
|
end: 7,
|
|
131
135
|
},
|
|
132
|
-
data: createData('
|
|
136
|
+
data: createData(''),
|
|
133
137
|
});
|
|
134
138
|
|
|
135
139
|
expect(state.panel?.items.slice(1)).toEqual([
|
|
@@ -169,7 +173,53 @@ describe('context reference input surface plugin', () => {
|
|
|
169
173
|
|
|
170
174
|
state.panel?.onSelectItem?.(item!);
|
|
171
175
|
|
|
172
|
-
expect(onNavigate).toHaveBeenCalledWith('
|
|
176
|
+
expect(onNavigate).toHaveBeenCalledWith('');
|
|
173
177
|
expect(item).not.toHaveProperty('tokenKind');
|
|
174
178
|
});
|
|
179
|
+
|
|
180
|
+
it('opens browsed directories and offers the current folder as a token', () => {
|
|
181
|
+
const onNavigate = vi.fn();
|
|
182
|
+
const rootState = resolveChatInputSurfaceState({
|
|
183
|
+
plugins: [createPlugin(onNavigate)],
|
|
184
|
+
trigger: {
|
|
185
|
+
...CONTEXT_REFERENCE_TRIGGER_SPEC,
|
|
186
|
+
query: '',
|
|
187
|
+
start: 0,
|
|
188
|
+
end: 1,
|
|
189
|
+
},
|
|
190
|
+
data: createData(''),
|
|
191
|
+
});
|
|
192
|
+
const directoryItem = rootState.panel?.items.find((item) => item.title === 'docs');
|
|
193
|
+
|
|
194
|
+
expect(directoryItem).toMatchObject({
|
|
195
|
+
selectionBehavior: 'navigate',
|
|
196
|
+
value: 'docs',
|
|
197
|
+
});
|
|
198
|
+
expect(directoryItem?.tokenKind).toBeUndefined();
|
|
199
|
+
rootState.panel?.onSelectItem?.(directoryItem!);
|
|
200
|
+
expect(onNavigate).toHaveBeenCalledWith('docs');
|
|
201
|
+
|
|
202
|
+
const nestedState = resolveChatInputSurfaceState({
|
|
203
|
+
plugins: [createPlugin(onNavigate)],
|
|
204
|
+
trigger: {
|
|
205
|
+
...CONTEXT_REFERENCE_TRIGGER_SPEC,
|
|
206
|
+
query: '',
|
|
207
|
+
start: 0,
|
|
208
|
+
end: 1,
|
|
209
|
+
},
|
|
210
|
+
data: createData('docs'),
|
|
211
|
+
});
|
|
212
|
+
expect(nestedState.panel?.items).toEqual(expect.arrayContaining([
|
|
213
|
+
expect.objectContaining({
|
|
214
|
+
title: 'Reference current folder',
|
|
215
|
+
subtitle: 'docs',
|
|
216
|
+
tokenKind: 'workspace_directory',
|
|
217
|
+
tokenKey: 'docs',
|
|
218
|
+
}),
|
|
219
|
+
expect.objectContaining({
|
|
220
|
+
title: 'Up one folder',
|
|
221
|
+
selectionBehavior: 'navigate',
|
|
222
|
+
}),
|
|
223
|
+
]));
|
|
224
|
+
});
|
|
175
225
|
});
|
|
@@ -97,7 +97,7 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
97
97
|
],
|
|
98
98
|
projectRoot: '/tmp/project',
|
|
99
99
|
recentSkillValues: [],
|
|
100
|
-
|
|
100
|
+
referencePath: null,
|
|
101
101
|
serverPathEntries: [],
|
|
102
102
|
serverPathSearchError: null,
|
|
103
103
|
skillRecords: [
|
|
@@ -158,7 +158,7 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
158
158
|
panelApps: [],
|
|
159
159
|
projectRoot: '/tmp/project',
|
|
160
160
|
recentSkillValues: [],
|
|
161
|
-
|
|
161
|
+
referencePath: null,
|
|
162
162
|
serverPathEntries: [],
|
|
163
163
|
serverPathSearchError: null,
|
|
164
164
|
skillRecords: [
|
|
@@ -207,7 +207,7 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
207
207
|
panelApps: [],
|
|
208
208
|
projectRoot: '/tmp/project',
|
|
209
209
|
recentSkillValues: [],
|
|
210
|
-
|
|
210
|
+
referencePath: null,
|
|
211
211
|
serverPathEntries: [],
|
|
212
212
|
serverPathSearchError: null,
|
|
213
213
|
skillRecords: [],
|
|
@@ -244,7 +244,7 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
244
244
|
],
|
|
245
245
|
projectRoot: '/tmp/project',
|
|
246
246
|
recentSkillValues: [],
|
|
247
|
-
|
|
247
|
+
referencePath: null,
|
|
248
248
|
serverPathEntries: [],
|
|
249
249
|
serverPathSearchError: null,
|
|
250
250
|
skillRecords: [],
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { ChatSkillRecord } from '@/features/chat/types/chat-input-bar.types';
|
|
2
2
|
import type { PanelAppEntryView, ServerPathSearchEntryView } from '@/shared/lib/api';
|
|
3
3
|
|
|
4
|
-
export type ContextReferenceMode = 'root' | 'files';
|
|
5
|
-
|
|
6
4
|
export type ChatInputProductPluginData = {
|
|
7
5
|
isPanelAppsLoading: boolean;
|
|
8
6
|
isServerPathSearchLoading: boolean;
|
|
@@ -10,7 +8,7 @@ export type ChatInputProductPluginData = {
|
|
|
10
8
|
panelApps: readonly PanelAppEntryView[];
|
|
11
9
|
projectRoot: string;
|
|
12
10
|
recentSkillValues: readonly string[];
|
|
13
|
-
|
|
11
|
+
referencePath: string | null;
|
|
14
12
|
serverPathEntries: readonly ServerPathSearchEntryView[];
|
|
15
13
|
serverPathSearchError: string | null;
|
|
16
14
|
skillRecords: readonly ChatSkillRecord[];
|
package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts
CHANGED
|
@@ -10,10 +10,7 @@ import {
|
|
|
10
10
|
CHAT_WORKSPACE_FILE_TOKEN_KIND,
|
|
11
11
|
} from '@nextclaw/shared';
|
|
12
12
|
import type { PanelAppEntryView, ServerPathSearchEntryView } from '@/shared/lib/api';
|
|
13
|
-
import type {
|
|
14
|
-
ChatInputProductPluginData,
|
|
15
|
-
ContextReferenceMode,
|
|
16
|
-
} from './chat-input-product-plugin-adapters.types';
|
|
13
|
+
import type { ChatInputProductPluginData } from './chat-input-product-plugin-adapters.types';
|
|
17
14
|
import {
|
|
18
15
|
scoreInputSurfaceSearchCandidate,
|
|
19
16
|
resolveInputSurfaceMatchTier,
|
|
@@ -40,6 +37,7 @@ export type ContextReferenceInputSurfaceTexts = {
|
|
|
40
37
|
backLabel: string;
|
|
41
38
|
backDescription: string;
|
|
42
39
|
backHintLabel: string;
|
|
40
|
+
currentDirectoryLabel: string;
|
|
43
41
|
directoryDescription: string;
|
|
44
42
|
fileDescription: string;
|
|
45
43
|
filesDescription: string;
|
|
@@ -47,6 +45,9 @@ export type ContextReferenceInputSurfaceTexts = {
|
|
|
47
45
|
filesLabel: string;
|
|
48
46
|
filesSubtitle: string;
|
|
49
47
|
panelAppSectionLabel: string;
|
|
48
|
+
parentLabel: string;
|
|
49
|
+
parentDescription: string;
|
|
50
|
+
parentHintLabel: string;
|
|
50
51
|
projectRootLabel: string;
|
|
51
52
|
searchFailedLabel: string;
|
|
52
53
|
workspaceSectionLabel: string;
|
|
@@ -154,14 +155,31 @@ function buildPanelAppReferenceItems(params: {
|
|
|
154
155
|
}));
|
|
155
156
|
}
|
|
156
157
|
|
|
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
|
+
|
|
157
174
|
function buildWorkspaceReferenceItems(params: {
|
|
158
175
|
entries: readonly ServerPathSearchEntryView[];
|
|
176
|
+
navigateDirectories: boolean;
|
|
159
177
|
projectRoot: string;
|
|
160
178
|
texts: ContextReferenceInputSurfaceTexts;
|
|
161
179
|
}): ChatInputSurfaceItem[] {
|
|
162
180
|
const projectLabel = resolveProjectLabel(params.projectRoot);
|
|
163
181
|
return params.entries.map((entry) => {
|
|
164
|
-
const
|
|
182
|
+
const navigates = params.navigateDirectories && entry.kind === 'directory';
|
|
165
183
|
return {
|
|
166
184
|
key: `workspace:${entry.kind}:${entry.relativePath}`,
|
|
167
185
|
icon: entry.kind === 'directory' ? 'folder' : 'file',
|
|
@@ -176,21 +194,61 @@ function buildWorkspaceReferenceItems(params: {
|
|
|
176
194
|
],
|
|
177
195
|
sectionKey: WORKSPACE_SECTION_KEY,
|
|
178
196
|
sectionLabel: params.texts.workspaceSectionLabel,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
+
}),
|
|
190
210
|
} satisfies ChatInputSurfaceItem;
|
|
191
211
|
});
|
|
192
212
|
}
|
|
193
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
|
+
|
|
194
252
|
function buildFilesNavigationItem(texts: ContextReferenceInputSurfaceTexts): ChatInputSurfaceItem {
|
|
195
253
|
return {
|
|
196
254
|
key: FILES_NAVIGATION_ITEM_KEY,
|
|
@@ -204,15 +262,21 @@ function buildFilesNavigationItem(texts: ContextReferenceInputSurfaceTexts): Cha
|
|
|
204
262
|
};
|
|
205
263
|
}
|
|
206
264
|
|
|
207
|
-
function
|
|
265
|
+
function buildBackNavigationItem(params: {
|
|
266
|
+
referencePath: string;
|
|
267
|
+
texts: ContextReferenceInputSurfaceTexts;
|
|
268
|
+
}): ChatInputSurfaceItem {
|
|
269
|
+
const { referencePath, texts } = params;
|
|
270
|
+
const isProjectRoot = !referencePath;
|
|
208
271
|
return {
|
|
209
272
|
key: ROOT_NAVIGATION_ITEM_KEY,
|
|
210
273
|
icon: 'back',
|
|
211
|
-
title: texts.backLabel,
|
|
274
|
+
title: isProjectRoot ? texts.backLabel : texts.parentLabel,
|
|
212
275
|
subtitle: '',
|
|
213
|
-
description: texts.backDescription,
|
|
276
|
+
description: isProjectRoot ? texts.backDescription : texts.parentDescription,
|
|
214
277
|
detailLines: [],
|
|
215
|
-
hintLabel: texts.backHintLabel,
|
|
278
|
+
hintLabel: isProjectRoot ? texts.backHintLabel : texts.parentHintLabel,
|
|
279
|
+
value: resolveParentReferencePath(referencePath) ?? '',
|
|
216
280
|
selectionBehavior: 'navigate',
|
|
217
281
|
};
|
|
218
282
|
}
|
|
@@ -223,19 +287,36 @@ export function createContextReferenceInputSurfacePlugin(params: {
|
|
|
223
287
|
panelApp: PanelAppInputSurfaceItemTexts;
|
|
224
288
|
};
|
|
225
289
|
menuTexts: ChatInputSurfaceMenuTexts;
|
|
226
|
-
onNavigate: (
|
|
290
|
+
onNavigate: (path: string | null) => void;
|
|
227
291
|
}): ChatInputSurfacePlugin<ChatInputProductPluginData> {
|
|
228
292
|
return createInputSurfaceTriggeredPanelPlugin({
|
|
229
293
|
key: 'context-reference',
|
|
230
294
|
trigger: CONTEXT_REFERENCE_TRIGGER_SPEC,
|
|
231
295
|
resolvePanel: ({ data, trigger }) => {
|
|
296
|
+
const isFilesMode = data.referencePath !== null;
|
|
297
|
+
const isBrowsing = isFilesMode && !trigger.query.trim();
|
|
232
298
|
const workspaceItems = buildWorkspaceReferenceItems({
|
|
233
299
|
entries: data.serverPathEntries,
|
|
300
|
+
navigateDirectories: isBrowsing,
|
|
234
301
|
projectRoot: data.projectRoot,
|
|
235
302
|
texts: params.itemTexts.context,
|
|
236
303
|
});
|
|
237
|
-
const
|
|
238
|
-
?
|
|
304
|
+
const currentDirectoryItem = isBrowsing && data.referencePath
|
|
305
|
+
? buildCurrentDirectoryReferenceItem({
|
|
306
|
+
projectRoot: data.projectRoot,
|
|
307
|
+
referencePath: data.referencePath,
|
|
308
|
+
texts: params.itemTexts.context,
|
|
309
|
+
})
|
|
310
|
+
: null;
|
|
311
|
+
const items = isFilesMode
|
|
312
|
+
? [
|
|
313
|
+
buildBackNavigationItem({
|
|
314
|
+
referencePath: data.referencePath ?? '',
|
|
315
|
+
texts: params.itemTexts.context,
|
|
316
|
+
}),
|
|
317
|
+
...(currentDirectoryItem ? [currentDirectoryItem] : []),
|
|
318
|
+
...workspaceItems,
|
|
319
|
+
]
|
|
239
320
|
: [
|
|
240
321
|
buildFilesNavigationItem(params.itemTexts.context),
|
|
241
322
|
...(trigger.query ? workspaceItems : []),
|
|
@@ -246,11 +327,11 @@ export function createContextReferenceInputSurfacePlugin(params: {
|
|
|
246
327
|
texts: params.itemTexts.panelApp,
|
|
247
328
|
}),
|
|
248
329
|
];
|
|
249
|
-
const relevantLoading =
|
|
330
|
+
const relevantLoading = isFilesMode
|
|
250
331
|
? data.isServerPathSearchLoading
|
|
251
332
|
: data.isPanelAppsLoading || (Boolean(trigger.query) && data.isServerPathSearchLoading);
|
|
252
333
|
return {
|
|
253
|
-
isLoading:
|
|
334
|
+
isLoading: isFilesMode
|
|
254
335
|
? relevantLoading
|
|
255
336
|
: relevantLoading && items.length === 0,
|
|
256
337
|
items,
|
|
@@ -262,9 +343,11 @@ export function createContextReferenceInputSurfacePlugin(params: {
|
|
|
262
343
|
: undefined,
|
|
263
344
|
onSelectItem: (item) => {
|
|
264
345
|
if (item.key === FILES_NAVIGATION_ITEM_KEY) {
|
|
265
|
-
params.onNavigate('
|
|
346
|
+
params.onNavigate('');
|
|
266
347
|
} else if (item.key === ROOT_NAVIGATION_ITEM_KEY) {
|
|
267
|
-
params.onNavigate('
|
|
348
|
+
params.onNavigate(data.referencePath ? item.value ?? '' : null);
|
|
349
|
+
} else if (item.selectionBehavior === 'navigate' && item.value) {
|
|
350
|
+
params.onNavigate(item.value);
|
|
268
351
|
}
|
|
269
352
|
},
|
|
270
353
|
texts: params.menuTexts,
|