@nextclaw/ui 0.15.24 → 0.15.25
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 +21 -0
- package/dist/assets/{api-BxPNDp30.js → api-FK7tikI1.js} +3 -3
- package/dist/assets/appearance-settings-page-BYbvl2wj.js +1 -0
- package/dist/assets/{channels-list-page-DRaNWAxC.js → channels-list-page-CocdcIko.js} +2 -2
- package/dist/assets/chat-page-C5E-LLVd.js +1 -0
- package/dist/assets/{config-split-page-BVWVDbvG.js → config-split-page-CA_a0AJ5.js} +1 -1
- package/dist/assets/{confirm-dialog-mA2iVzxh.js → confirm-dialog-CxZfenjK.js} +1 -1
- package/dist/assets/{desktop-update-config-DyQb1LXr.js → desktop-update-config-BEKC8Ly9.js} +1 -1
- package/dist/assets/{dist-B4Pf5X3Q.js → dist-BH7W5yCi.js} +1 -1
- package/dist/assets/{dist-DHEWkGWX.js → dist-CaYdVVeO.js} +1 -1
- package/dist/assets/doc-browser-Cl2ldE3f.js +1 -0
- package/dist/assets/doc-browser-DXCSd63E.js +1 -0
- package/dist/assets/{doc-browser-context-CuGI6PQc.js → doc-browser-context-FDPs3gJf.js} +1 -1
- package/dist/assets/{form-actions-BOfIyG3O.js → form-actions-0c0aw_bl.js} +1 -1
- package/dist/assets/i18n-provider-DkITJNz8.js +1 -0
- package/dist/assets/index-CQ2LcI2p.css +1 -0
- package/dist/assets/index-CrdfvBVg.js +107 -0
- package/dist/assets/loader-circle-DHtUxCIA.js +1 -0
- package/dist/assets/{mcp-marketplace-page-ofDqbOIF.js → mcp-marketplace-page-Bc_jI5iH.js} +2 -2
- package/dist/assets/mcp-marketplace-page-CzCieD9r.js +1 -0
- package/dist/assets/{model-config-page-D73Tu3jW.js → model-config-page-C4jw-pcj.js} +1 -1
- package/dist/assets/{navigation-link-CAFOcXNg.js → navigation-link-CVy0_kVl.js} +1 -1
- package/dist/assets/plus-CG1RwZQq.js +1 -0
- package/dist/assets/{provider-scoped-model-input-BaJJJYMX.js → provider-scoped-model-input-CmH4BFvc.js} +1 -1
- package/dist/assets/{providers-config-page-BL6IRVBm.js → providers-config-page-CJQjwzaw.js} +1 -1
- package/dist/assets/{react-BC_orpAj.js → react-DjFBVA0n.js} +1 -1
- package/dist/assets/{refresh-cw-CWDp_JnN.js → refresh-cw-j2bl79Ea.js} +1 -1
- package/dist/assets/remote-BIlpD3yL.js +1 -0
- package/dist/assets/rotate-cw-bKG8zH8M.js +1 -0
- package/dist/assets/runtime-config-page-B_x2Glz5.js +1 -0
- package/dist/assets/{save-Cnr71JON.js → save-BKNrtK81.js} +1 -1
- package/dist/assets/{search-CCX11X2m.js → search-CkOm65W_.js} +1 -1
- package/dist/assets/{search-config-page-BB6d5gT7.js → search-config-page-BaIijtmO.js} +1 -1
- package/dist/assets/{secrets-config-page-DYsDeT9K.js → secrets-config-page-CJjtdFAl.js} +2 -2
- package/dist/assets/security-config-DB8apj09.js +1 -0
- package/dist/assets/{select-DvwBu99K.js → select-Ea7ffKCE.js} +1 -1
- package/dist/assets/{settings-2-Dmv6zMne.js → settings-2-CtyN3m_V.js} +1 -1
- package/dist/assets/skeleton-Bt6zxcAB.js +1 -0
- package/dist/assets/{tag-chip-gMPvx7n_.js → tag-chip-C9l4xsKp.js} +1 -1
- package/dist/assets/x-CowtCu9Z.js +1 -0
- package/dist/index.html +20 -20
- package/package.json +8 -8
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +30 -1
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +1 -1
- package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +40 -3
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.selection.test.tsx +66 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +13 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +573 -90
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +389 -288
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +54 -35
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +79 -148
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +149 -94
- package/src/features/chat/features/workspace/components/project-files/__tests__/workspace-directory-entry-menu.test.tsx +50 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-directory-entry-menu.tsx +209 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +411 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +112 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-project-files-toolbar.tsx +64 -0
- package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +88 -0
- package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +335 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-path-mutation.utils.ts +58 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +23 -22
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -4
- package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +86 -60
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +103 -99
- package/src/features/chat/managers/chat-composer-intent.manager.ts +37 -33
- package/src/features/chat/managers/chat-thread.manager.ts +132 -128
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +20 -40
- package/src/features/chat/stores/chat-thread.store.ts +47 -74
- package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +35 -38
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +25 -19
- package/src/shared/components/ui/context-menu/__tests__/context-menu.test.tsx +60 -34
- package/src/shared/components/ui/context-menu/context-menu.tsx +95 -68
- package/src/shared/lib/api/managers/client.manager.ts +31 -17
- package/src/shared/lib/api/server-path/server-path.types.ts +66 -0
- package/src/shared/lib/api/types.ts +145 -116
- package/src/shared/lib/api/utils/server-path.utils.ts +35 -11
- package/src/shared/lib/i18n/locales/en-US/chat.json +42 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +42 -0
- package/dist/assets/appearance-settings-page-DpVJjvkB.js +0 -1
- package/dist/assets/chat-page-BuFDmG5_.js +0 -1
- package/dist/assets/doc-browser-CtO_HtRy.js +0 -1
- package/dist/assets/doc-browser-eMUagyBV.js +0 -1
- package/dist/assets/index-BjBdWHV1.js +0 -107
- package/dist/assets/index-DmpmjAGQ.css +0 -1
- package/dist/assets/loader-circle-Df8wF2Pr.js +0 -1
- package/dist/assets/mcp-marketplace-page-DI49IKxp.js +0 -1
- package/dist/assets/plus-DVU5RLCS.js +0 -1
- package/dist/assets/remote-T24O58lH.js +0 -1
- package/dist/assets/rotate-cw-BqhwSV5p.js +0 -1
- package/dist/assets/runtime-config-page-CXUx017t.js +0 -1
- package/dist/assets/security-config-CVPgqL7G.js +0 -1
- package/dist/assets/skeleton-C5tWata3.js +0 -1
- package/dist/assets/use-confirm-dialog-D_3-QpZU.js +0 -1
- package/dist/assets/x-BEWSqeux.js +0 -1
package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
import type { ReactNode } from
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ChatFileOpenActionViewModel } from '@nextclaw/agent-chat-ui';
|
|
2
3
|
import {
|
|
3
4
|
AlarmClock,
|
|
4
5
|
ChevronRight,
|
|
5
6
|
FolderTree,
|
|
6
7
|
GitBranch,
|
|
7
8
|
MessageSquarePlus,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
9
|
+
PanelLeftClose,
|
|
10
|
+
PanelLeftOpen,
|
|
11
|
+
} from 'lucide-react';
|
|
12
|
+
import type { CronJobView } from '@/shared/lib/api';
|
|
13
|
+
import { usePresenter } from '@/features/chat/components/providers/chat-presenter.provider';
|
|
14
|
+
import { SessionConversationArea } from '@/features/chat/features/conversation/components/session-conversation-area';
|
|
15
|
+
import { ChatSessionWorkspaceFilePreview } from '@/features/chat/features/workspace/components/chat-session-workspace-file-preview';
|
|
16
|
+
import { ChatSessionWorkspaceDirectoryBrowser } from '@/features/chat/features/workspace/components/chat-session-workspace-directory-browser';
|
|
17
|
+
import { SessionCronJobContent } from '@/features/chat/features/workspace/components/session-cron-job-content';
|
|
18
|
+
import type { ResolvedChildSessionTab } from '@/features/chat/features/ncp/hooks/use-ncp-child-session-tabs-view';
|
|
19
|
+
import type { WorkspaceSelection } from '@/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils';
|
|
20
|
+
import { useServerPathBrowse } from '@/shared/hooks/use-server-path-browse';
|
|
21
|
+
import {
|
|
22
|
+
CHAT_WORKSPACE_EXPLORER_MAX_WIDTH,
|
|
23
|
+
CHAT_WORKSPACE_EXPLORER_MIN_WIDTH,
|
|
24
|
+
} from '@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils';
|
|
25
|
+
import { useWorkspaceExplorerLayout } from '@/features/chat/features/workspace/hooks/use-workspace-explorer-layout';
|
|
26
|
+
import { t } from '@/shared/lib/i18n';
|
|
27
|
+
import { cn } from '@/shared/lib/utils';
|
|
28
|
+
import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
|
|
20
29
|
|
|
21
30
|
type ChatSessionWorkspacePanelContentProps = {
|
|
22
31
|
activeSelection: WorkspaceSelection;
|
|
@@ -53,15 +62,13 @@ function WorkspaceOverviewEntry({
|
|
|
53
62
|
<span className="min-w-0 flex-1">
|
|
54
63
|
<span className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
|
55
64
|
<span className="truncate">{title}</span>
|
|
56
|
-
{typeof count ===
|
|
65
|
+
{typeof count === 'number' ? (
|
|
57
66
|
<span className="rounded-full bg-gray-100 px-1.5 py-0.5 text-[10px] font-semibold tabular-nums text-gray-500">
|
|
58
67
|
{count}
|
|
59
68
|
</span>
|
|
60
69
|
) : null}
|
|
61
70
|
</span>
|
|
62
|
-
<span className="mt-0.5 block text-xs leading-5 text-gray-500">
|
|
63
|
-
{description}
|
|
64
|
-
</span>
|
|
71
|
+
<span className="mt-0.5 block text-xs leading-5 text-gray-500">{description}</span>
|
|
65
72
|
</span>
|
|
66
73
|
<ChevronRight className="h-4 w-4 shrink-0 text-gray-300 transition-transform group-hover:translate-x-0.5 group-hover:text-gray-500" />
|
|
67
74
|
</button>
|
|
@@ -82,18 +89,14 @@ function WorkspaceOverview({
|
|
|
82
89
|
return (
|
|
83
90
|
<div className="h-full overflow-auto bg-gray-50/45 px-4 py-5 custom-scrollbar">
|
|
84
91
|
<div className="mx-auto max-w-xl">
|
|
85
|
-
<h2 className="text-base font-semibold text-gray-900">
|
|
86
|
-
|
|
87
|
-
</h2>
|
|
88
|
-
<p className="mt-1 text-xs leading-5 text-gray-500">
|
|
89
|
-
{t("chatWorkspaceOverviewDescription")}
|
|
90
|
-
</p>
|
|
92
|
+
<h2 className="text-base font-semibold text-gray-900">{t('chatWorkspaceOverview')}</h2>
|
|
93
|
+
<p className="mt-1 text-xs leading-5 text-gray-500">{t('chatWorkspaceOverviewDescription')}</p>
|
|
91
94
|
<div className="mt-4 space-y-2">
|
|
92
95
|
<WorkspaceOverviewEntry
|
|
93
96
|
count={childSessionTabs.length}
|
|
94
|
-
description={t(
|
|
97
|
+
description={t('chatWorkspaceChildSessionsDescription')}
|
|
95
98
|
icon={<GitBranch className="h-4 w-4" />}
|
|
96
|
-
title={t(
|
|
99
|
+
title={t('chatWorkspaceChildSessions')}
|
|
97
100
|
onClick={() => {
|
|
98
101
|
if (sessionKey) {
|
|
99
102
|
presenter.chatThreadManager.openChildSessions(sessionKey);
|
|
@@ -102,9 +105,9 @@ function WorkspaceOverview({
|
|
|
102
105
|
/>
|
|
103
106
|
<WorkspaceOverviewEntry
|
|
104
107
|
count={sessionCronJobs.length}
|
|
105
|
-
description={t(
|
|
108
|
+
description={t('chatWorkspaceSessionCronJobsDescription')}
|
|
106
109
|
icon={<AlarmClock className="h-4 w-4" />}
|
|
107
|
-
title={t(
|
|
110
|
+
title={t('chatWorkspaceSessionCronJobs')}
|
|
108
111
|
onClick={() => {
|
|
109
112
|
if (sessionKey) {
|
|
110
113
|
presenter.chatThreadManager.openSessionCronPanel(sessionKey);
|
|
@@ -112,9 +115,9 @@ function WorkspaceOverview({
|
|
|
112
115
|
}}
|
|
113
116
|
/>
|
|
114
117
|
<WorkspaceOverviewEntry
|
|
115
|
-
description={t(
|
|
118
|
+
description={t('chatWorkspaceProjectFilesDescription')}
|
|
116
119
|
icon={<FolderTree className="h-4 w-4" />}
|
|
117
|
-
title={t(
|
|
120
|
+
title={t('chatWorkspaceProjectFiles')}
|
|
118
121
|
onClick={() => {
|
|
119
122
|
if (sessionKey) {
|
|
120
123
|
presenter.chatThreadManager.openProjectFiles(sessionKey);
|
|
@@ -127,28 +130,20 @@ function WorkspaceOverview({
|
|
|
127
130
|
);
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
function WorkspaceChildSessions({
|
|
131
|
-
childSessionTabs,
|
|
132
|
-
}: {
|
|
133
|
-
childSessionTabs: readonly ResolvedChildSessionTab[];
|
|
134
|
-
}) {
|
|
133
|
+
function WorkspaceChildSessions({ childSessionTabs }: { childSessionTabs: readonly ResolvedChildSessionTab[] }) {
|
|
135
134
|
const presenter = usePresenter();
|
|
136
135
|
|
|
137
136
|
return (
|
|
138
137
|
<div className="h-full overflow-auto bg-gray-50/45 px-4 py-5 custom-scrollbar">
|
|
139
138
|
<div className="mx-auto max-w-xl">
|
|
140
139
|
<div className="flex items-center gap-2">
|
|
141
|
-
<h2 className="text-base font-semibold text-gray-900">
|
|
142
|
-
{t("chatWorkspaceChildSessions")}
|
|
143
|
-
</h2>
|
|
140
|
+
<h2 className="text-base font-semibold text-gray-900">{t('chatWorkspaceChildSessions')}</h2>
|
|
144
141
|
<span className="rounded-full bg-gray-100 px-1.5 py-0.5 text-[10px] font-semibold tabular-nums text-gray-500">
|
|
145
142
|
{childSessionTabs.length}
|
|
146
143
|
</span>
|
|
147
144
|
</div>
|
|
148
145
|
{childSessionTabs.length === 0 ? (
|
|
149
|
-
<div className="py-10 text-center text-sm text-gray-500">
|
|
150
|
-
{t("chatWorkspaceChildSessionsEmpty")}
|
|
151
|
-
</div>
|
|
146
|
+
<div className="py-10 text-center text-sm text-gray-500">{t('chatWorkspaceChildSessionsEmpty')}</div>
|
|
152
147
|
) : (
|
|
153
148
|
<div className="mt-4 space-y-2">
|
|
154
149
|
{childSessionTabs.map((tab) => (
|
|
@@ -156,24 +151,16 @@ function WorkspaceChildSessions({
|
|
|
156
151
|
key={tab.sessionKey}
|
|
157
152
|
type="button"
|
|
158
153
|
className="group flex w-full items-center gap-3 rounded-lg border border-gray-200/80 bg-white px-3 py-3 text-left transition-colors hover:border-gray-300 hover:bg-gray-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/25"
|
|
159
|
-
onClick={() =>
|
|
160
|
-
presenter.chatThreadManager.selectChildSessionDetail(
|
|
161
|
-
tab.sessionKey,
|
|
162
|
-
)
|
|
163
|
-
}
|
|
154
|
+
onClick={() => presenter.chatThreadManager.selectChildSessionDetail(tab.sessionKey)}
|
|
164
155
|
>
|
|
165
156
|
<span className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gray-100 text-gray-600">
|
|
166
157
|
<GitBranch className="h-4 w-4" />
|
|
167
158
|
</span>
|
|
168
159
|
<span className="min-w-0 flex-1">
|
|
169
|
-
<span className="block truncate text-sm font-medium text-gray-900">
|
|
170
|
-
{tab.title}
|
|
171
|
-
</span>
|
|
160
|
+
<span className="block truncate text-sm font-medium text-gray-900">{tab.title}</span>
|
|
172
161
|
{tab.projectName || tab.sessionTypeLabel ? (
|
|
173
162
|
<span className="mt-0.5 block truncate text-xs text-gray-500">
|
|
174
|
-
{[tab.sessionTypeLabel, tab.projectName]
|
|
175
|
-
.filter(Boolean)
|
|
176
|
-
.join(" · ")}
|
|
163
|
+
{[tab.sessionTypeLabel, tab.projectName].filter(Boolean).join(' · ')}
|
|
177
164
|
</span>
|
|
178
165
|
) : null}
|
|
179
166
|
</span>
|
|
@@ -188,10 +175,12 @@ function WorkspaceChildSessions({
|
|
|
188
175
|
}
|
|
189
176
|
|
|
190
177
|
function WorkspaceProjectFiles({
|
|
178
|
+
onFileOpen,
|
|
191
179
|
projectRoot,
|
|
192
180
|
sessionKey,
|
|
193
181
|
workingDir,
|
|
194
182
|
}: {
|
|
183
|
+
onFileOpen?: (action: ChatFileOpenActionViewModel) => void;
|
|
195
184
|
projectRoot: string | null;
|
|
196
185
|
sessionKey: string | null;
|
|
197
186
|
workingDir: string | null;
|
|
@@ -207,7 +196,7 @@ function WorkspaceProjectFiles({
|
|
|
207
196
|
if (!rootPath) {
|
|
208
197
|
return (
|
|
209
198
|
<div className="flex h-full items-center justify-center px-6 text-center text-sm text-gray-500">
|
|
210
|
-
{t(
|
|
199
|
+
{t('chatWorkspaceProjectFilesUnavailable')}
|
|
211
200
|
</div>
|
|
212
201
|
);
|
|
213
202
|
}
|
|
@@ -216,13 +205,13 @@ function WorkspaceProjectFiles({
|
|
|
216
205
|
<ChatSessionWorkspaceDirectoryBrowser
|
|
217
206
|
key={rootPath}
|
|
218
207
|
browseQuery={browseQuery}
|
|
219
|
-
onFileOpen={presenter.chatThreadManager.openFilePreview}
|
|
208
|
+
onFileOpen={onFileOpen ?? presenter.chatThreadManager.openFilePreview}
|
|
220
209
|
sessionKey={sessionKey}
|
|
221
|
-
renderStatus={({ text, tone =
|
|
210
|
+
renderStatus={({ text, tone = 'muted' }) => (
|
|
222
211
|
<div
|
|
223
212
|
className={cn(
|
|
224
|
-
|
|
225
|
-
tone ===
|
|
213
|
+
'flex h-full items-center justify-center px-6 text-center text-sm',
|
|
214
|
+
tone === 'error' ? 'text-rose-600' : 'text-gray-500',
|
|
226
215
|
)}
|
|
227
216
|
>
|
|
228
217
|
{text}
|
|
@@ -242,11 +231,9 @@ function ChildSessionMetaChip({ value }: { value: string }) {
|
|
|
242
231
|
}
|
|
243
232
|
|
|
244
233
|
function ChildSessionMetaStrip({ tab }: { tab: ResolvedChildSessionTab }) {
|
|
245
|
-
const metaItems = [
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
tab.projectName,
|
|
249
|
-
].filter((value): value is string => Boolean(value?.trim()));
|
|
234
|
+
const metaItems = [tab.sessionTypeLabel, tab.preferredModel, tab.projectName].filter((value): value is string =>
|
|
235
|
+
Boolean(value?.trim()),
|
|
236
|
+
);
|
|
250
237
|
|
|
251
238
|
if (metaItems.length === 0 && !tab.projectRoot) {
|
|
252
239
|
return null;
|
|
@@ -274,11 +261,9 @@ function WorkspaceSideChatDraftHeader() {
|
|
|
274
261
|
<div className="border-b border-gray-200/70 px-4 py-3">
|
|
275
262
|
<div className="flex min-w-0 items-center gap-2 text-sm font-semibold text-gray-900">
|
|
276
263
|
<MessageSquarePlus className="h-4 w-4 shrink-0 text-primary" />
|
|
277
|
-
<span className="truncate">{t(
|
|
264
|
+
<span className="truncate">{t('chatWorkspaceSideChatDraftTitle')}</span>
|
|
278
265
|
</div>
|
|
279
|
-
<p className="mt-1 text-xs leading-5 text-gray-500">
|
|
280
|
-
{t("chatWorkspaceSideChatDraftSubtitle")}
|
|
281
|
-
</p>
|
|
266
|
+
<p className="mt-1 text-xs leading-5 text-gray-500">{t('chatWorkspaceSideChatDraftSubtitle')}</p>
|
|
282
267
|
</div>
|
|
283
268
|
);
|
|
284
269
|
}
|
|
@@ -291,10 +276,13 @@ function WorkspaceSelectedContent({
|
|
|
291
276
|
sessionCronJobs,
|
|
292
277
|
sessionProjectRoot,
|
|
293
278
|
sessionWorkingDir,
|
|
294
|
-
|
|
279
|
+
explorerControl,
|
|
280
|
+
}: ChatSessionWorkspacePanelContentProps & {
|
|
281
|
+
explorerControl?: { open: boolean; onToggle: () => void };
|
|
282
|
+
}) {
|
|
295
283
|
const presenter = usePresenter();
|
|
296
284
|
|
|
297
|
-
if (activeSelection.kind ===
|
|
285
|
+
if (activeSelection.kind === 'overview') {
|
|
298
286
|
return (
|
|
299
287
|
<WorkspaceOverview
|
|
300
288
|
childSessionTabs={childSessionTabs}
|
|
@@ -304,18 +292,18 @@ function WorkspaceSelectedContent({
|
|
|
304
292
|
);
|
|
305
293
|
}
|
|
306
294
|
|
|
307
|
-
if (activeSelection.kind ===
|
|
295
|
+
if (activeSelection.kind === 'child-sessions') {
|
|
308
296
|
return <WorkspaceChildSessions childSessionTabs={childSessionTabs} />;
|
|
309
297
|
}
|
|
310
298
|
|
|
311
|
-
if (activeSelection.kind ===
|
|
299
|
+
if (activeSelection.kind === 'side-chat-draft') {
|
|
312
300
|
return (
|
|
313
301
|
<>
|
|
314
302
|
<WorkspaceSideChatDraftHeader />
|
|
315
303
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
316
304
|
<SessionConversationArea
|
|
317
305
|
materializationContext={{
|
|
318
|
-
kind:
|
|
306
|
+
kind: 'child',
|
|
319
307
|
parentSessionKey: activeSelection.draft.parentSessionKey,
|
|
320
308
|
inheritContext: true,
|
|
321
309
|
}}
|
|
@@ -333,7 +321,7 @@ function WorkspaceSelectedContent({
|
|
|
333
321
|
);
|
|
334
322
|
}
|
|
335
323
|
|
|
336
|
-
if (activeSelection.kind ===
|
|
324
|
+
if (activeSelection.kind === 'child-session') {
|
|
337
325
|
return (
|
|
338
326
|
<>
|
|
339
327
|
<ChildSessionMetaStrip tab={activeSelection.tab} />
|
|
@@ -344,9 +332,21 @@ function WorkspaceSelectedContent({
|
|
|
344
332
|
);
|
|
345
333
|
}
|
|
346
334
|
|
|
347
|
-
if (activeSelection.kind ===
|
|
335
|
+
if (activeSelection.kind === 'file') {
|
|
348
336
|
return (
|
|
349
337
|
<ChatSessionWorkspaceFilePreview
|
|
338
|
+
breadcrumbLeading={
|
|
339
|
+
<IconActionButton
|
|
340
|
+
data-testid="workspace-explorer-toggle"
|
|
341
|
+
icon={
|
|
342
|
+
explorerControl?.open ? <PanelLeftClose className="h-4 w-4" /> : <PanelLeftOpen className="h-4 w-4" />
|
|
343
|
+
}
|
|
344
|
+
label={t(explorerControl?.open ? 'chatWorkspaceHideProjectFiles' : 'chatWorkspaceShowProjectFiles')}
|
|
345
|
+
size="sm"
|
|
346
|
+
tooltipSide="bottom"
|
|
347
|
+
onClick={explorerControl?.onToggle}
|
|
348
|
+
/>
|
|
349
|
+
}
|
|
350
350
|
file={activeSelection.file}
|
|
351
351
|
refreshVersion={filePreviewRefreshVersion}
|
|
352
352
|
sessionProjectRoot={sessionProjectRoot}
|
|
@@ -365,41 +365,96 @@ function WorkspaceSelectedContent({
|
|
|
365
365
|
return <SessionCronJobContent jobs={sessionCronJobs} />;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
export function ChatSessionWorkspacePanelContent(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const
|
|
368
|
+
export function ChatSessionWorkspacePanelContent({
|
|
369
|
+
activeSelection,
|
|
370
|
+
sessionKey,
|
|
371
|
+
sessionProjectRoot,
|
|
372
|
+
sessionWorkingDir,
|
|
373
|
+
...selectedContentProps
|
|
374
|
+
}: ChatSessionWorkspacePanelContentProps) {
|
|
375
|
+
const projectFilesActive = activeSelection.kind === 'project-files';
|
|
376
|
+
const fileActive = activeSelection.kind === 'file';
|
|
377
|
+
const presenter = usePresenter();
|
|
378
|
+
const {
|
|
379
|
+
compact,
|
|
380
|
+
containerRef,
|
|
381
|
+
explorerOpen,
|
|
382
|
+
explorerOverlay,
|
|
383
|
+
explorerWidth,
|
|
384
|
+
onResizeStart,
|
|
385
|
+
setExplorerOpen,
|
|
386
|
+
showExplorer,
|
|
387
|
+
} = useWorkspaceExplorerLayout({ fileActive, projectFilesActive, sessionProjectRoot, sessionWorkingDir });
|
|
378
388
|
|
|
379
389
|
return (
|
|
380
|
-
|
|
390
|
+
<div
|
|
391
|
+
ref={containerRef}
|
|
392
|
+
data-testid="workspace-shared-explorer-layout"
|
|
393
|
+
className="relative flex h-full min-h-0 flex-1 overflow-hidden"
|
|
394
|
+
>
|
|
395
|
+
{explorerOverlay ? (
|
|
396
|
+
<button
|
|
397
|
+
data-testid="workspace-explorer-scrim"
|
|
398
|
+
type="button"
|
|
399
|
+
aria-label={t('chatWorkspaceHideProjectFiles')}
|
|
400
|
+
className="absolute inset-0 z-10 bg-black/15"
|
|
401
|
+
onClick={() => setExplorerOpen(false)}
|
|
402
|
+
/>
|
|
403
|
+
) : null}
|
|
381
404
|
<div
|
|
405
|
+
data-testid="workspace-shared-explorer"
|
|
406
|
+
data-mode={projectFilesActive ? 'full' : explorerOverlay ? 'overlay' : 'side'}
|
|
382
407
|
className={cn(
|
|
383
|
-
|
|
384
|
-
|
|
408
|
+
'relative min-h-0 shrink-0 flex-col border-r border-gray-200/80 bg-white',
|
|
409
|
+
showExplorer ? 'flex' : 'hidden',
|
|
410
|
+
projectFilesActive ? 'flex-1 border-r-0' : null,
|
|
411
|
+
explorerOverlay ? 'absolute inset-y-0 left-0 z-20 w-[min(320px,86%)] shadow-xl' : null,
|
|
385
412
|
)}
|
|
386
|
-
|
|
413
|
+
style={!projectFilesActive && !explorerOverlay ? { width: explorerWidth } : undefined}
|
|
414
|
+
aria-hidden={!showExplorer}
|
|
387
415
|
>
|
|
388
416
|
<WorkspaceProjectFiles
|
|
417
|
+
onFileOpen={(action) => {
|
|
418
|
+
if (compact) setExplorerOpen(false);
|
|
419
|
+
presenter.chatThreadManager.openFilePreview(action);
|
|
420
|
+
}}
|
|
389
421
|
projectRoot={sessionProjectRoot}
|
|
390
422
|
sessionKey={sessionKey}
|
|
391
423
|
workingDir={sessionWorkingDir}
|
|
392
424
|
/>
|
|
425
|
+
{fileActive && !compact ? (
|
|
426
|
+
<div
|
|
427
|
+
data-testid="workspace-explorer-resize-handle"
|
|
428
|
+
aria-label={t('chatWorkspaceResizeProjectFiles')}
|
|
429
|
+
aria-orientation="vertical"
|
|
430
|
+
aria-valuemax={CHAT_WORKSPACE_EXPLORER_MAX_WIDTH}
|
|
431
|
+
aria-valuemin={CHAT_WORKSPACE_EXPLORER_MIN_WIDTH}
|
|
432
|
+
aria-valuenow={explorerWidth}
|
|
433
|
+
role="separator"
|
|
434
|
+
className="absolute inset-y-0 right-[-4px] z-20 w-2 cursor-ew-resize hover:bg-primary/10"
|
|
435
|
+
onPointerDown={onResizeStart}
|
|
436
|
+
/>
|
|
437
|
+
) : null}
|
|
393
438
|
</div>
|
|
394
439
|
{projectFilesActive ? null : (
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
440
|
+
<div className="min-w-0 min-h-0 flex-1">
|
|
441
|
+
<WorkspaceSelectedContent
|
|
442
|
+
{...selectedContentProps}
|
|
443
|
+
activeSelection={activeSelection}
|
|
444
|
+
explorerControl={
|
|
445
|
+
fileActive
|
|
446
|
+
? {
|
|
447
|
+
open: explorerOpen,
|
|
448
|
+
onToggle: () => setExplorerOpen((value) => !value),
|
|
449
|
+
}
|
|
450
|
+
: undefined
|
|
451
|
+
}
|
|
452
|
+
sessionKey={sessionKey}
|
|
453
|
+
sessionProjectRoot={sessionProjectRoot}
|
|
454
|
+
sessionWorkingDir={sessionWorkingDir}
|
|
455
|
+
/>
|
|
456
|
+
</div>
|
|
402
457
|
)}
|
|
403
|
-
|
|
458
|
+
</div>
|
|
404
459
|
);
|
|
405
460
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { buildWorkspaceEntryContextMenuGroups } from "@/features/chat/features/workspace/components/project-files/workspace-directory-entry-menu";
|
|
3
|
+
import type { ServerPathEntryView } from "@/shared/lib/api";
|
|
4
|
+
|
|
5
|
+
function buildMenu(entry: ServerPathEntryView) {
|
|
6
|
+
return buildWorkspaceEntryContextMenuGroups({
|
|
7
|
+
busy: false,
|
|
8
|
+
downloadUrl: entry.kind === "file" ? "/download" : null,
|
|
9
|
+
entry,
|
|
10
|
+
onAddToChat: vi.fn(),
|
|
11
|
+
onCreateFile: vi.fn(),
|
|
12
|
+
onCreateFolder: vi.fn(),
|
|
13
|
+
onDelete: vi.fn(),
|
|
14
|
+
onOpen: vi.fn(),
|
|
15
|
+
onRenameRequest: vi.fn(),
|
|
16
|
+
onRevealPath: vi.fn(),
|
|
17
|
+
onUpload: vi.fn(),
|
|
18
|
+
ownCreateTarget: { label: entry.name, path: entry.path, refresh: vi.fn() },
|
|
19
|
+
parentRefresh: vi.fn(),
|
|
20
|
+
relativePath: entry.name,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function visibleGroupKeys(entry: ServerPathEntryView) {
|
|
25
|
+
return buildMenu(entry)
|
|
26
|
+
.filter((group) => group.items.length > 0)
|
|
27
|
+
.map((group) => group.key);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("workspace directory entry context menus", () => {
|
|
31
|
+
it("keeps Open exclusive to files", () => {
|
|
32
|
+
expect(
|
|
33
|
+
visibleGroupKeys({
|
|
34
|
+
name: "src",
|
|
35
|
+
path: "/workspace/src",
|
|
36
|
+
kind: "directory",
|
|
37
|
+
hidden: false,
|
|
38
|
+
}),
|
|
39
|
+
).toEqual(["nextclaw", "manage", "path", "rename", "danger"]);
|
|
40
|
+
|
|
41
|
+
expect(
|
|
42
|
+
visibleGroupKeys({
|
|
43
|
+
name: "app.ts",
|
|
44
|
+
path: "/workspace/app.ts",
|
|
45
|
+
kind: "file",
|
|
46
|
+
hidden: false,
|
|
47
|
+
}),
|
|
48
|
+
).toEqual(["open", "nextclaw", "path", "rename", "danger"]);
|
|
49
|
+
});
|
|
50
|
+
});
|