@nextclaw/ui 0.15.1 → 0.15.3
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 +51 -0
- package/dist/assets/{api-b1R9VdeH.js → api-iNBpppzj.js} +3 -3
- package/dist/assets/{appearance-settings-page-D2O9pch9.js → appearance-settings-page-DMffQFR3.js} +1 -1
- package/dist/assets/{book-open-B77lQ2Tg.js → book-open-C3mJGwiy.js} +1 -1
- package/dist/assets/channels-list-page-CCtp0BRS.js +8 -0
- package/dist/assets/chat-page-H5a0dWvO.js +1 -0
- package/dist/assets/config-split-page-CrOJgB-Y.js +1 -0
- package/dist/assets/{confirm-dialog-C-NXSioX.js → confirm-dialog-Chv50USy.js} +2 -2
- package/dist/assets/{desktop-update-config-usHpXugP.js → desktop-update-config-KzTuXRf_.js} +1 -1
- package/dist/assets/{dist-jdGmbb6w.js → dist-BunzmC4B.js} +1 -1
- package/dist/assets/{dist-YtoqU-AT.js → dist-ChmGwvtK.js} +1 -1
- package/dist/assets/doc-browser-DNRL4Zvd.js +1 -0
- package/dist/assets/doc-browser-Dg0pmkOg.js +1 -0
- package/dist/assets/doc-browser-context-BHY-0FSD.js +1 -0
- package/dist/assets/{es2015-bNpL2LKy.js → es2015-DQZ5JWwK.js} +1 -1
- package/dist/assets/{external-link-BM6fk8q-.js → external-link-CU0kzzIP.js} +1 -1
- package/dist/assets/form-actions-BvCp_0lS.js +1 -0
- package/dist/assets/{i18n-provider-CntWolRA.js → i18n-provider-Cu8XyZb_.js} +1 -1
- package/dist/assets/index-CzfBdvK1.css +1 -0
- package/dist/assets/index-DGqHne1Q.js +104 -0
- package/dist/assets/loader-circle-CDSFuzmk.js +1 -0
- package/dist/assets/mcp-marketplace-page-BeEQK0DL.js +9 -0
- package/dist/assets/mcp-marketplace-page-D6idOTLp.js +1 -0
- package/dist/assets/model-config-page-bNNo-XyG.js +1 -0
- package/dist/assets/plus-D4DsdLN-.js +1 -0
- package/dist/assets/{provider-scoped-model-input-Cm_a4GHf.js → provider-scoped-model-input-IlgQCsoT.js} +1 -1
- package/dist/assets/providers-config-page-wLo6CAJw.js +1 -0
- package/dist/assets/{react-BN8uzPnp.js → react-BPIsQIlO.js} +1 -1
- package/dist/assets/{refresh-cw-BpVcFPmJ.js → refresh-cw-CILd1vAP.js} +1 -1
- package/dist/assets/remote-uKEYp7AR.js +1 -0
- package/dist/assets/rotate-cw-wPFPg1kH.js +1 -0
- package/dist/assets/runtime-config-page-DDn1sP4G.js +1 -0
- package/dist/assets/{save-BpFW5fpk.js → save-ByJqcPTP.js} +1 -1
- package/dist/assets/{search-D0uO7enf.js → search-DVIV6pfD.js} +1 -1
- package/dist/assets/search-config-page-B-Cf4iiB.js +1 -0
- package/dist/assets/secrets-config-page-DPKsY8Iw.js +3 -0
- package/dist/assets/security-config-e9SSBlKr.js +1 -0
- package/dist/assets/{select-Bh-CrA4p.js → select-BdgczSNw.js} +1 -1
- package/dist/assets/{setting-row-BJfCHRcb.js → setting-row-BkTVUTKx.js} +1 -1
- package/dist/assets/{settings-2-Blzi1vr6.js → settings-2-CH5shGg_.js} +1 -1
- package/dist/assets/{side-dock-ZdaSrQVB.js → side-dock-BsBvcJ9i.js} +2 -2
- package/dist/assets/{skeleton-BLlEzebZ.js → skeleton-DeX1FKw7.js} +1 -1
- package/dist/assets/switch-Cv8Ekb3x.js +1 -0
- package/dist/assets/{tag-chip-CzGihiQx.js → tag-chip-RB9uKcoC.js} +1 -1
- package/dist/assets/{tooltip-CdcrBjrC.js → tooltip-oQlEN-l6.js} +1 -1
- package/dist/assets/{use-config-CVPgsBmU.js → use-config-l3W7y8Qz.js} +1 -1
- package/dist/assets/{use-confirm-dialog-CSeBk9SF.js → use-confirm-dialog-BAxaVOJI.js} +1 -1
- package/dist/assets/x-BBMffEN0.js +1 -0
- package/dist/index.html +38 -30
- package/index.html +12 -4
- package/package.json +9 -9
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +29 -1
- package/src/app/components/layout/sidebar-items.tsx +46 -7
- package/src/app/components/layout/sidebar-rail.styles.ts +2 -2
- package/src/app/components/layout/sidebar.tsx +84 -62
- package/src/app/configs/app-navigation.config.ts +49 -0
- package/src/app/styles/design-system.css +99 -4
- package/src/features/channels/components/config/channel-form.tsx +14 -11
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +40 -0
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +3 -1
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
- package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +48 -0
- package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +55 -0
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +0 -3
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +3 -4
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +30 -31
- package/src/features/chat/components/layout/chat-sidebar.tsx +21 -7
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -1
- package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +1 -1
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +22 -1
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +62 -1
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +10 -5
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +47 -1
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +11 -1
- package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +23 -0
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -11
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +5 -0
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +138 -0
- package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +116 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +108 -46
- package/src/features/chat/features/session/components/chat-sidebar-session-entry.tsx +6 -0
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +48 -24
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +0 -3
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +0 -4
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +2 -2
- package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +25 -3
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +9 -4
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +0 -5
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +226 -56
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +144 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +99 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +2 -2
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +4 -2
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +232 -21
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +3 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +44 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +45 -3
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +4 -0
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +33 -2
- package/src/features/chat/managers/chat-session-list.manager.ts +32 -0
- package/src/features/chat/managers/chat-thread.manager.ts +2 -0
- package/src/features/chat/stores/chat-session-list.store.ts +28 -5
- package/src/features/chat/stores/chat-thread.store.ts +3 -0
- package/src/features/cron/components/cron-config.tsx +63 -80
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +44 -6
- package/src/features/marketplace/components/marketplace-detail-doc.tsx +8 -4
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +5 -12
- package/src/features/marketplace/hooks/use-marketplace-detail-doc-entry.ts +84 -0
- package/src/features/panel-apps/components/panel-app-list-item.tsx +6 -6
- package/src/features/panel-apps/components/panel-apps-list.tsx +2 -2
- package/src/features/panel-apps/index.ts +1 -0
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +2 -10
- package/src/features/panel-apps/utils/panel-app-iframe.utils.ts +6 -0
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -3
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +3 -1
- package/src/features/right-panel-resources/components/right-panel-resource-home-page.tsx +1 -1
- package/src/features/service-apps/components/service-apps-panel.tsx +145 -35
- package/src/features/settings/components/config/provider-auth-section.tsx +1 -1
- package/src/features/settings/components/config/provider-form.tsx +11 -8
- package/src/features/settings/components/config/provider-models-section.tsx +7 -7
- package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
- package/src/features/settings/components/config/secrets-config-form.tsx +5 -4
- package/src/features/settings/pages/model-config-page.tsx +26 -25
- package/src/features/settings/pages/providers-config-page.tsx +10 -13
- package/src/features/settings/pages/search-config-page.tsx +8 -5
- package/src/features/side-dock/components/side-dock.tsx +3 -3
- package/src/features/system-status/components/config/runtime-config-editor.tsx +5 -4
- package/src/index.css +257 -13
- package/src/shared/components/actions/action-link.tsx +1 -1
- package/src/shared/components/config-split-page.tsx +6 -6
- package/src/shared/components/doc-browser/__tests__/doc-browser-url.utils.test.ts +73 -0
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +48 -0
- package/src/shared/components/doc-browser/doc-browser-context.tsx +4 -0
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +6 -2
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +2 -1
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +10 -10
- package/src/shared/components/doc-browser/doc-browser.tsx +6 -4
- package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +62 -4
- package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +18 -0
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +28 -3
- package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +36 -0
- package/src/shared/components/ui/actions/form-actions.tsx +25 -0
- package/src/shared/components/ui/actions/icon-action-button.tsx +25 -2
- package/src/shared/components/ui/button.tsx +1 -1
- package/src/shared/components/ui/card.tsx +12 -4
- package/src/shared/components/ui/dialog.tsx +1 -1
- package/src/shared/components/ui/input.tsx +1 -1
- package/src/shared/components/ui/select.tsx +1 -1
- package/src/shared/components/ui/switch.tsx +1 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +2 -1
- package/src/shared/components/ui/tabs.tsx +1 -1
- package/src/shared/components/ui/textarea.tsx +1 -1
- package/src/shared/hooks/use-server-path-browse.ts +26 -4
- package/src/shared/hooks/use-server-path-read.ts +11 -1
- package/src/shared/lib/api/utils/server-path.utils.ts +1 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +99 -0
- package/src/shared/lib/i18n/locales/en-US/core.json +3 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +5 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +99 -0
- package/src/shared/lib/i18n/locales/zh-CN/core.json +3 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +5 -0
- package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +9 -5
- package/src/shared/lib/theme/index.test.ts +21 -0
- package/src/shared/lib/theme/index.ts +6 -4
- package/src/vite-env.d.ts +2 -0
- package/dist/assets/channels-list-page-DcsIQc6A.js +0 -8
- package/dist/assets/chat-page-CZ7WxPir.js +0 -1
- package/dist/assets/config-split-page-BWQ6_Raq.js +0 -1
- package/dist/assets/doc-browser-B_Tf9n70.js +0 -1
- package/dist/assets/doc-browser-BdZPFOVa.js +0 -1
- package/dist/assets/doc-browser-context-DwokemUn.js +0 -1
- package/dist/assets/index-BIcEw90u.js +0 -104
- package/dist/assets/index-BiNSTKTU.css +0 -1
- package/dist/assets/loader-circle-MBgNSVIH.js +0 -1
- package/dist/assets/mcp-marketplace-page-CLmUstLN.js +0 -9
- package/dist/assets/mcp-marketplace-page-rZkmWPMy.js +0 -1
- package/dist/assets/model-config-page-Di0ySJD2.js +0 -1
- package/dist/assets/plus-vDqW5Kxp.js +0 -1
- package/dist/assets/providers-config-page-BAf2W0MV.js +0 -1
- package/dist/assets/remote-CLQ0IBM8.js +0 -1
- package/dist/assets/rotate-cw-CvNBv7_w.js +0 -1
- package/dist/assets/runtime-config-page-CflvvoDc.js +0 -1
- package/dist/assets/search-config-page-D0klFUXd.js +0 -1
- package/dist/assets/secrets-config-page-dVZ631Wp.js +0 -3
- package/dist/assets/security-config-C5gnlkuj.js +0 -1
- package/dist/assets/switch-CFLJFNSd.js +0 -1
- package/dist/assets/x-BF2KQ--V.js +0 -1
- /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-C_iP1zl_.js} +0 -0
package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx
CHANGED
|
@@ -4,12 +4,20 @@ import type {
|
|
|
4
4
|
ChatFileOperationBlockViewModel,
|
|
5
5
|
ChatFilePreviewViewer,
|
|
6
6
|
} from "@nextclaw/agent-chat-ui";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
ChatMessageMarkdown,
|
|
9
|
+
FileOperationCodeSurface,
|
|
10
|
+
} from "@nextclaw/agent-chat-ui";
|
|
8
11
|
import type { ChatWorkspaceFileTab } from "@/features/chat/stores/chat-thread.store";
|
|
12
|
+
import { ChatSessionWorkspaceDirectoryBrowser } from "./chat-session-workspace-directory-browser";
|
|
9
13
|
import { ChatSessionWorkspaceFileBreadcrumbs } from "./chat-session-workspace-file-breadcrumbs";
|
|
14
|
+
import { useServerPathBrowse } from "@/shared/hooks/use-server-path-browse";
|
|
10
15
|
import { useServerPathRead } from "@/shared/hooks/use-server-path-read";
|
|
11
16
|
import { buildServerPathContentUrl } from "@/shared/lib/api";
|
|
12
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
buildLineDiff,
|
|
19
|
+
buildPreviewLines,
|
|
20
|
+
} from "@/features/chat/features/message/utils/file-operation/line-builder.utils";
|
|
13
21
|
import { t } from "@/shared/lib/i18n";
|
|
14
22
|
import { buildWorkspaceFileBreadcrumb } from "@/shared/lib/session-project";
|
|
15
23
|
import { cn } from "@/shared/lib/utils";
|
|
@@ -31,6 +39,39 @@ function resolveFilePreviewViewer(params: {
|
|
|
31
39
|
return params.viewer === "rendered" && /\.html?$/i.test(params.path) ? "rendered" : "source";
|
|
32
40
|
}
|
|
33
41
|
|
|
42
|
+
type ContentUrlPreviewKind = "image" | "audio" | "video" | "pdf" | "html" | "other";
|
|
43
|
+
|
|
44
|
+
function resolveContentUrlPreviewKind(params: {
|
|
45
|
+
path: string;
|
|
46
|
+
mimeType?: string | null;
|
|
47
|
+
}): ContentUrlPreviewKind {
|
|
48
|
+
const mime = params.mimeType?.trim().toLowerCase() ?? "";
|
|
49
|
+
const path = params.path.trim().toLowerCase();
|
|
50
|
+
if (mime.startsWith("image/") || /\.(avif|bmp|gif|heic|heif|ico|jpe?g|png|svg|tiff?|webp)$/i.test(path)) {
|
|
51
|
+
return "image";
|
|
52
|
+
}
|
|
53
|
+
if (mime.startsWith("audio/") || /\.(aac|flac|m4a|mp3|ogg|opus|wav|weba)$/i.test(path)) {
|
|
54
|
+
return "audio";
|
|
55
|
+
}
|
|
56
|
+
if (mime.startsWith("video/") || /\.(avi|m4v|mkv|mov|mp4|webm|wmv)$/i.test(path)) {
|
|
57
|
+
return "video";
|
|
58
|
+
}
|
|
59
|
+
if (mime.includes("pdf") || path.endsWith(".pdf")) {
|
|
60
|
+
return "pdf";
|
|
61
|
+
}
|
|
62
|
+
if (mime.includes("html") || /\.html?$/i.test(path)) {
|
|
63
|
+
return "html";
|
|
64
|
+
}
|
|
65
|
+
return "other";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function appendPreviewRefreshVersion(url: string, refreshVersion: number): string {
|
|
69
|
+
if (refreshVersion <= 0) {
|
|
70
|
+
return url;
|
|
71
|
+
}
|
|
72
|
+
return `${url}${url.includes("?") ? "&" : "?"}refresh=${refreshVersion}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
34
75
|
function buildPreviewBlock(params: {
|
|
35
76
|
path: string;
|
|
36
77
|
text: string;
|
|
@@ -160,7 +201,109 @@ function WorkspaceHtmlRenderedPreview({
|
|
|
160
201
|
);
|
|
161
202
|
}
|
|
162
203
|
|
|
204
|
+
function WorkspaceContentUrlPreview({
|
|
205
|
+
contentUrl,
|
|
206
|
+
kind,
|
|
207
|
+
label,
|
|
208
|
+
}: {
|
|
209
|
+
contentUrl: string;
|
|
210
|
+
kind: ContentUrlPreviewKind;
|
|
211
|
+
label: string;
|
|
212
|
+
}) {
|
|
213
|
+
if (kind === "image") {
|
|
214
|
+
return (
|
|
215
|
+
<div className="flex h-full items-center justify-center overflow-auto custom-scrollbar bg-white p-4">
|
|
216
|
+
<img
|
|
217
|
+
src={contentUrl}
|
|
218
|
+
alt={label}
|
|
219
|
+
className="max-h-full max-w-full object-contain"
|
|
220
|
+
data-testid="workspace-content-image"
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (kind === "audio") {
|
|
226
|
+
return (
|
|
227
|
+
<div className="flex h-full items-center justify-center bg-white px-6">
|
|
228
|
+
<audio
|
|
229
|
+
controls
|
|
230
|
+
preload="metadata"
|
|
231
|
+
aria-label={label}
|
|
232
|
+
className="w-full max-w-xl"
|
|
233
|
+
data-testid="workspace-content-audio"
|
|
234
|
+
src={contentUrl}
|
|
235
|
+
/>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (kind === "video") {
|
|
240
|
+
return (
|
|
241
|
+
<div className="flex h-full items-center justify-center bg-black">
|
|
242
|
+
<video
|
|
243
|
+
controls
|
|
244
|
+
playsInline
|
|
245
|
+
preload="metadata"
|
|
246
|
+
aria-label={label}
|
|
247
|
+
className="max-h-full max-w-full"
|
|
248
|
+
data-testid="workspace-content-video"
|
|
249
|
+
src={contentUrl}
|
|
250
|
+
/>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
if (kind === "pdf" || kind === "html") {
|
|
255
|
+
return (
|
|
256
|
+
<iframe
|
|
257
|
+
allowFullScreen
|
|
258
|
+
className="h-full w-full border-0 bg-white"
|
|
259
|
+
data-testid={kind === "pdf" ? "workspace-content-pdf" : "workspace-html-preview"}
|
|
260
|
+
src={contentUrl}
|
|
261
|
+
title={label}
|
|
262
|
+
/>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
return (
|
|
266
|
+
<div
|
|
267
|
+
className="flex h-full flex-col items-center justify-center gap-4 px-6 text-center"
|
|
268
|
+
data-testid="workspace-content-unsupported"
|
|
269
|
+
>
|
|
270
|
+
<div className="max-w-sm space-y-1.5">
|
|
271
|
+
<p className="truncate text-sm font-medium text-foreground" title={label}>
|
|
272
|
+
{label}
|
|
273
|
+
</p>
|
|
274
|
+
<p className="text-sm text-muted-foreground">
|
|
275
|
+
{t("chatWorkspacePreviewUnsupported")}
|
|
276
|
+
</p>
|
|
277
|
+
<p className="text-xs leading-5 text-muted-foreground/80">
|
|
278
|
+
{t("chatWorkspacePreviewUnsupportedHint")}
|
|
279
|
+
</p>
|
|
280
|
+
</div>
|
|
281
|
+
<div className="flex flex-wrap items-center justify-center gap-2">
|
|
282
|
+
<a
|
|
283
|
+
href={contentUrl}
|
|
284
|
+
download={label}
|
|
285
|
+
className="inline-flex h-8 items-center rounded-lg border border-border bg-card px-3 text-xs font-medium text-foreground transition-colors hover:bg-muted"
|
|
286
|
+
>
|
|
287
|
+
{t("chatWorkspacePreviewDownload")}
|
|
288
|
+
</a>
|
|
289
|
+
<a
|
|
290
|
+
href={contentUrl}
|
|
291
|
+
target="_blank"
|
|
292
|
+
rel="noreferrer"
|
|
293
|
+
className="inline-flex h-8 items-center rounded-lg bg-primary px-3 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90"
|
|
294
|
+
>
|
|
295
|
+
{t("chatWorkspacePreviewOpenExternally")}
|
|
296
|
+
</a>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
163
302
|
function WorkspacePreviewBody({
|
|
303
|
+
contentUrl,
|
|
304
|
+
contentUrlKind,
|
|
305
|
+
contentLabel,
|
|
306
|
+
directoryQuery,
|
|
164
307
|
onFileOpen,
|
|
165
308
|
previewBlock,
|
|
166
309
|
previewKind,
|
|
@@ -169,25 +312,57 @@ function WorkspacePreviewBody({
|
|
|
169
312
|
previewUrl,
|
|
170
313
|
previewViewer,
|
|
171
314
|
}: {
|
|
315
|
+
contentUrl: string | null;
|
|
316
|
+
contentUrlKind: ContentUrlPreviewKind | null;
|
|
317
|
+
contentLabel: string;
|
|
318
|
+
directoryQuery: ReturnType<typeof useServerPathBrowse> | null | undefined;
|
|
172
319
|
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
173
320
|
previewBlock: ChatFileOperationBlockViewModel | null;
|
|
174
321
|
previewKind: "text" | "markdown" | "binary";
|
|
175
|
-
previewQuery: ReturnType<typeof useServerPathRead
|
|
322
|
+
previewQuery: ReturnType<typeof useServerPathRead> | null | undefined;
|
|
176
323
|
previewText: string | null;
|
|
177
324
|
previewUrl: string | null;
|
|
178
325
|
previewViewer: "source" | "rendered";
|
|
179
326
|
}) {
|
|
180
|
-
if (
|
|
181
|
-
return
|
|
327
|
+
if (contentUrl && contentUrlKind) {
|
|
328
|
+
return (
|
|
329
|
+
<WorkspaceContentUrlPreview
|
|
330
|
+
contentUrl={contentUrl}
|
|
331
|
+
kind={contentUrlKind}
|
|
332
|
+
label={contentLabel}
|
|
333
|
+
/>
|
|
334
|
+
);
|
|
182
335
|
}
|
|
183
336
|
|
|
184
|
-
if (
|
|
337
|
+
if (directoryQuery?.data) {
|
|
338
|
+
return (
|
|
339
|
+
<ChatSessionWorkspaceDirectoryBrowser
|
|
340
|
+
browseQuery={directoryQuery}
|
|
341
|
+
onFileOpen={onFileOpen}
|
|
342
|
+
renderStatus={(params) => <WorkspaceFilePreviewStatus {...params} />}
|
|
343
|
+
/>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (directoryQuery?.isLoading && previewQuery?.error && !previewBlock) {
|
|
348
|
+
return (
|
|
349
|
+
<WorkspaceFilePreviewStatus text={t("chatWorkspaceLoadingDirectory")} />
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if ((directoryQuery?.isLoading || previewQuery?.isLoading) && !previewBlock) {
|
|
354
|
+
return (
|
|
355
|
+
<WorkspaceFilePreviewStatus text={t("chatWorkspaceLoadingPreview")} />
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (previewQuery?.data?.kind === "binary") {
|
|
185
360
|
return (
|
|
186
361
|
<WorkspaceFilePreviewStatus text={t("chatWorkspacePreviewUnsupported")} />
|
|
187
362
|
);
|
|
188
363
|
}
|
|
189
364
|
|
|
190
|
-
if (previewQuery
|
|
365
|
+
if (previewQuery?.error && !previewBlock) {
|
|
191
366
|
return (
|
|
192
367
|
<WorkspaceFilePreviewStatus
|
|
193
368
|
tone="error"
|
|
@@ -229,6 +404,7 @@ function WorkspacePreviewBody({
|
|
|
229
404
|
|
|
230
405
|
type ChatSessionWorkspaceFilePreviewProps = {
|
|
231
406
|
file: ChatWorkspaceFileTab;
|
|
407
|
+
refreshVersion?: number;
|
|
232
408
|
sessionProjectRoot: string | null;
|
|
233
409
|
sessionWorkingDir: string | null;
|
|
234
410
|
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
@@ -236,65 +412,96 @@ type ChatSessionWorkspaceFilePreviewProps = {
|
|
|
236
412
|
|
|
237
413
|
export function ChatSessionWorkspaceFilePreview({
|
|
238
414
|
file,
|
|
415
|
+
refreshVersion = 0,
|
|
239
416
|
sessionProjectRoot,
|
|
240
417
|
sessionWorkingDir,
|
|
241
418
|
onFileOpen,
|
|
242
419
|
}: ChatSessionWorkspaceFilePreviewProps) {
|
|
243
420
|
const isPreviewMode = file.viewMode === "preview";
|
|
421
|
+
const contentUrl = file.contentUrl?.trim() || null;
|
|
422
|
+
const usesServerPath = isPreviewMode && !contentUrl;
|
|
244
423
|
const previewQuery = useServerPathRead({
|
|
245
424
|
path: file.path,
|
|
246
425
|
basePath: sessionWorkingDir,
|
|
247
|
-
enabled:
|
|
426
|
+
enabled: usesServerPath,
|
|
427
|
+
});
|
|
428
|
+
const directoryQuery = useServerPathBrowse({
|
|
429
|
+
path: file.path,
|
|
430
|
+
basePath: sessionWorkingDir,
|
|
431
|
+
includeFiles: true,
|
|
432
|
+
enabled: usesServerPath,
|
|
248
433
|
});
|
|
249
434
|
const diffBlock = useMemo(
|
|
250
435
|
() => (file.viewMode === "diff" ? buildDiffBlock(file) : null),
|
|
251
436
|
[file],
|
|
252
437
|
);
|
|
253
438
|
const previewText =
|
|
254
|
-
isPreviewMode ? previewQuery
|
|
439
|
+
isPreviewMode ? previewQuery?.data?.text ?? file.rawText ?? null : null;
|
|
255
440
|
const previewKind = inferPreviewKind({
|
|
256
|
-
path: previewQuery
|
|
257
|
-
serverKind: previewQuery
|
|
441
|
+
path: previewQuery?.data?.resolvedPath ?? file.path,
|
|
442
|
+
serverKind: previewQuery?.data?.kind,
|
|
258
443
|
});
|
|
259
|
-
const
|
|
444
|
+
const contentUrlKind = contentUrl
|
|
445
|
+
? resolveContentUrlPreviewKind({
|
|
446
|
+
path: file.path,
|
|
447
|
+
mimeType: file.mimeType,
|
|
448
|
+
})
|
|
449
|
+
: null;
|
|
450
|
+
const resolvedPath =
|
|
451
|
+
directoryQuery?.data?.currentPath ??
|
|
452
|
+
previewQuery?.data?.resolvedPath ??
|
|
453
|
+
file.path;
|
|
454
|
+
const previewPathKind = directoryQuery?.data ? "directory" : "file";
|
|
260
455
|
const previewViewer = resolveFilePreviewViewer({
|
|
261
456
|
path: resolvedPath,
|
|
262
457
|
viewer: file.previewViewer,
|
|
263
458
|
});
|
|
264
459
|
const previewUrl =
|
|
265
|
-
previewViewer === "rendered" && previewQuery
|
|
266
|
-
?
|
|
460
|
+
previewViewer === "rendered" && previewQuery?.data?.resolvedPath
|
|
461
|
+
? appendPreviewRefreshVersion(
|
|
462
|
+
buildServerPathContentUrl(previewQuery.data.resolvedPath),
|
|
463
|
+
refreshVersion,
|
|
464
|
+
)
|
|
267
465
|
: null;
|
|
268
466
|
const previewBlock = useMemo(() => {
|
|
269
|
-
if (!isPreviewMode || !previewText) {
|
|
467
|
+
if (!isPreviewMode || !previewText || contentUrl) {
|
|
270
468
|
return null;
|
|
271
469
|
}
|
|
272
470
|
return buildPreviewBlock({
|
|
273
|
-
path: previewQuery
|
|
471
|
+
path: previewQuery?.data?.resolvedPath ?? file.path,
|
|
274
472
|
text: previewText,
|
|
275
|
-
languageHint: previewQuery
|
|
473
|
+
languageHint: previewQuery?.data?.languageHint ?? null,
|
|
276
474
|
line: file.line,
|
|
277
475
|
});
|
|
278
476
|
}, [
|
|
477
|
+
contentUrl,
|
|
279
478
|
file.line,
|
|
280
479
|
file.path,
|
|
281
480
|
isPreviewMode,
|
|
282
|
-
previewQuery
|
|
283
|
-
previewQuery
|
|
481
|
+
previewQuery?.data?.languageHint,
|
|
482
|
+
previewQuery?.data?.resolvedPath,
|
|
284
483
|
previewText,
|
|
285
484
|
]);
|
|
286
|
-
const isTruncated = Boolean(previewQuery
|
|
485
|
+
const isTruncated = Boolean(previewQuery?.data?.truncated);
|
|
287
486
|
const breadcrumbBasePath = sessionProjectRoot ?? sessionWorkingDir;
|
|
288
487
|
const breadcrumb = useMemo(
|
|
289
488
|
() =>
|
|
290
489
|
buildWorkspaceFileBreadcrumb({
|
|
291
490
|
path: resolvedPath,
|
|
491
|
+
kind: previewPathKind,
|
|
292
492
|
sessionProjectRoot: breadcrumbBasePath,
|
|
293
493
|
line: file.line,
|
|
294
494
|
column: file.column,
|
|
295
495
|
truncated: isTruncated,
|
|
296
496
|
}),
|
|
297
|
-
[
|
|
497
|
+
[
|
|
498
|
+
breadcrumbBasePath,
|
|
499
|
+
file.column,
|
|
500
|
+
file.line,
|
|
501
|
+
isTruncated,
|
|
502
|
+
previewPathKind,
|
|
503
|
+
resolvedPath,
|
|
504
|
+
],
|
|
298
505
|
);
|
|
299
506
|
|
|
300
507
|
return (
|
|
@@ -309,6 +516,10 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
309
516
|
<WorkspaceDiffBody diffBlock={diffBlock} />
|
|
310
517
|
) : (
|
|
311
518
|
<WorkspacePreviewBody
|
|
519
|
+
contentUrl={contentUrl}
|
|
520
|
+
contentUrlKind={contentUrlKind}
|
|
521
|
+
contentLabel={file.label?.trim() || resolvedPath}
|
|
522
|
+
directoryQuery={directoryQuery}
|
|
312
523
|
onFileOpen={onFileOpen}
|
|
313
524
|
previewBlock={previewBlock}
|
|
314
525
|
previewKind={previewKind}
|
package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { t } from "@/shared/lib/i18n";
|
|
|
10
10
|
|
|
11
11
|
type ChatSessionWorkspacePanelContentProps = {
|
|
12
12
|
activeSelection: WorkspaceSelection;
|
|
13
|
+
filePreviewRefreshVersion: number;
|
|
13
14
|
sessionCronJobs: readonly CronJobView[];
|
|
14
15
|
sessionProjectRoot: string | null;
|
|
15
16
|
sessionWorkingDir: string | null;
|
|
@@ -67,6 +68,7 @@ function WorkspaceSideChatDraftHeader() {
|
|
|
67
68
|
|
|
68
69
|
export function ChatSessionWorkspacePanelContent({
|
|
69
70
|
activeSelection,
|
|
71
|
+
filePreviewRefreshVersion,
|
|
70
72
|
sessionCronJobs,
|
|
71
73
|
sessionProjectRoot,
|
|
72
74
|
sessionWorkingDir,
|
|
@@ -113,6 +115,7 @@ export function ChatSessionWorkspacePanelContent({
|
|
|
113
115
|
return (
|
|
114
116
|
<ChatSessionWorkspaceFilePreview
|
|
115
117
|
file={activeSelection.file}
|
|
118
|
+
refreshVersion={filePreviewRefreshVersion}
|
|
116
119
|
sessionProjectRoot={sessionProjectRoot}
|
|
117
120
|
sessionWorkingDir={sessionWorkingDir}
|
|
118
121
|
onFileOpen={presenter.chatThreadManager.openFilePreview}
|
package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AlarmClock,
|
|
3
|
+
ArrowLeft,
|
|
4
|
+
ArrowRight,
|
|
5
|
+
FileCode2,
|
|
6
|
+
Maximize2,
|
|
7
|
+
MessageSquarePlus,
|
|
8
|
+
MessageSquareText,
|
|
9
|
+
Minimize2,
|
|
10
|
+
RefreshCw,
|
|
11
|
+
X,
|
|
12
|
+
} from "lucide-react";
|
|
2
13
|
import type { WorkspaceTabViewModel } from "@/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils";
|
|
3
14
|
import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
|
|
4
15
|
import {
|
|
@@ -56,22 +67,54 @@ function buildCompactWorkspaceTabs(
|
|
|
56
67
|
export function WorkspaceTabsBar({
|
|
57
68
|
canGoBack,
|
|
58
69
|
canGoForward,
|
|
70
|
+
isMaximized = false,
|
|
59
71
|
onClose,
|
|
60
72
|
onGoBack,
|
|
61
73
|
onGoForward,
|
|
74
|
+
onRefreshFile,
|
|
75
|
+
onToggleMaximize,
|
|
62
76
|
tabs,
|
|
63
77
|
}: {
|
|
64
78
|
canGoBack: boolean;
|
|
65
79
|
canGoForward: boolean;
|
|
80
|
+
isMaximized?: boolean;
|
|
66
81
|
onClose: () => void;
|
|
67
82
|
onGoBack: () => void;
|
|
68
83
|
onGoForward: () => void;
|
|
84
|
+
onRefreshFile?: () => void;
|
|
85
|
+
onToggleMaximize?: () => void;
|
|
69
86
|
tabs: readonly WorkspaceTabViewModel[];
|
|
70
87
|
}) {
|
|
71
88
|
const compactTabs = buildCompactWorkspaceTabs(tabs);
|
|
72
89
|
const actions: CompactTabStripAction[] = [
|
|
73
90
|
{ key: "back", icon: <ArrowLeft className="h-4 w-4" />, label: t("chatWorkspaceBack"), disabled: !canGoBack, onClick: onGoBack },
|
|
74
91
|
{ key: "forward", icon: <ArrowRight className="h-4 w-4" />, label: t("chatWorkspaceForward"), disabled: !canGoForward, onClick: onGoForward },
|
|
92
|
+
...(onRefreshFile
|
|
93
|
+
? [
|
|
94
|
+
{
|
|
95
|
+
key: "refresh-file",
|
|
96
|
+
icon: <RefreshCw className="h-4 w-4" />,
|
|
97
|
+
label: t("chatWorkspaceRefreshFile"),
|
|
98
|
+
onClick: onRefreshFile,
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
: []),
|
|
102
|
+
...(onToggleMaximize
|
|
103
|
+
? [
|
|
104
|
+
{
|
|
105
|
+
key: "maximize",
|
|
106
|
+
icon: isMaximized ? (
|
|
107
|
+
<Minimize2 className="h-4 w-4" />
|
|
108
|
+
) : (
|
|
109
|
+
<Maximize2 className="h-4 w-4" />
|
|
110
|
+
),
|
|
111
|
+
label: isMaximized
|
|
112
|
+
? t("chatWorkspaceRestorePanel")
|
|
113
|
+
: t("chatWorkspaceMaximizePanel"),
|
|
114
|
+
onClick: onToggleMaximize,
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
: []),
|
|
75
118
|
{ key: "close", icon: <X className="h-4 w-4" />, label: t("chatWorkspaceClosePanel"), onClick: onClose },
|
|
76
119
|
];
|
|
77
120
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { useEffect, useMemo } from "react";
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
2
3
|
import type { CronJobView } from "@/shared/lib/api";
|
|
3
4
|
import { useNcpChildSessionTabsView } from "@/features/chat/features/ncp/hooks/use-ncp-child-session-tabs-view";
|
|
4
5
|
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
@@ -22,6 +23,8 @@ import {
|
|
|
22
23
|
canGoBackInNavigationHistory,
|
|
23
24
|
canGoForwardInNavigationHistory,
|
|
24
25
|
} from "@/shared/lib/navigation-history";
|
|
26
|
+
import { buildServerPathBrowseQueryKey } from "@/shared/hooks/use-server-path-browse";
|
|
27
|
+
import { buildServerPathReadQueryKey } from "@/shared/hooks/use-server-path-read";
|
|
25
28
|
|
|
26
29
|
type ChatSessionWorkspacePanelProps = {
|
|
27
30
|
sessionKey: string | null;
|
|
@@ -55,6 +58,9 @@ export function ChatSessionWorkspacePanel({
|
|
|
55
58
|
displayMode = "docked",
|
|
56
59
|
}: ChatSessionWorkspacePanelProps) {
|
|
57
60
|
const presenter = usePresenter();
|
|
61
|
+
const queryClient = useQueryClient();
|
|
62
|
+
const [isMaximized, setIsMaximized] = useState(false);
|
|
63
|
+
const [filePreviewRefreshVersion, setFilePreviewRefreshVersion] = useState(0);
|
|
58
64
|
const resolvedChildTabs = useNcpChildSessionTabsView(childSessionTabs);
|
|
59
65
|
const optimisticReadAtBySessionKey = useChatSessionListStore(
|
|
60
66
|
(state) => state.optimisticReadAtBySessionKey,
|
|
@@ -110,30 +116,66 @@ export function ChatSessionWorkspacePanel({
|
|
|
110
116
|
return null;
|
|
111
117
|
}
|
|
112
118
|
|
|
119
|
+
const isContainerMaximized = displayMode === "docked" && isMaximized;
|
|
120
|
+
const isOverlayPanel = displayMode === "overlay" || isContainerMaximized;
|
|
121
|
+
const activeFile =
|
|
122
|
+
activeSelection.kind === "file" && activeSelection.file.viewMode === "preview"
|
|
123
|
+
? activeSelection.file
|
|
124
|
+
: null;
|
|
125
|
+
const refreshActiveFile = activeFile
|
|
126
|
+
? () => {
|
|
127
|
+
void queryClient.invalidateQueries({
|
|
128
|
+
queryKey: buildServerPathReadQueryKey({
|
|
129
|
+
path: activeFile.path,
|
|
130
|
+
basePath: sessionWorkingDir,
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
void queryClient.invalidateQueries({
|
|
134
|
+
queryKey: buildServerPathBrowseQueryKey({
|
|
135
|
+
path: activeFile.path,
|
|
136
|
+
basePath: sessionWorkingDir,
|
|
137
|
+
includeFiles: true,
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
setFilePreviewRefreshVersion((value) => value + 1);
|
|
141
|
+
}
|
|
142
|
+
: undefined;
|
|
143
|
+
|
|
113
144
|
return (
|
|
114
145
|
<ResizableRightPanel
|
|
146
|
+
data-testid="chat-session-workspace-panel"
|
|
115
147
|
className={cn(
|
|
116
|
-
|
|
148
|
+
isOverlayPanel
|
|
117
149
|
? "bg-white"
|
|
118
150
|
: "hidden border-gray-200/70 bg-white/95 backdrop-blur-sm md:flex",
|
|
151
|
+
isContainerMaximized ? "shadow-xl" : null,
|
|
119
152
|
)}
|
|
120
153
|
defaultWidth={480}
|
|
121
154
|
minWidth={360}
|
|
122
155
|
maxWidth={860}
|
|
123
|
-
overlay={
|
|
156
|
+
overlay={isOverlayPanel}
|
|
157
|
+
overlayScope={isContainerMaximized ? "container" : "viewport"}
|
|
124
158
|
>
|
|
125
159
|
<WorkspaceTabsBar
|
|
126
160
|
tabs={workspaceTabs}
|
|
127
161
|
canGoBack={canGoBackInNavigationHistory(workspaceHistory)}
|
|
128
162
|
canGoForward={canGoForwardInNavigationHistory(workspaceHistory)}
|
|
163
|
+
isMaximized={isContainerMaximized}
|
|
129
164
|
onGoBack={presenter.chatThreadManager.goBackWorkspacePanel}
|
|
130
165
|
onGoForward={presenter.chatThreadManager.goForwardWorkspacePanel}
|
|
166
|
+
onRefreshFile={refreshActiveFile}
|
|
167
|
+
onToggleMaximize={
|
|
168
|
+
displayMode === "docked"
|
|
169
|
+
? () => setIsMaximized((value) => !value)
|
|
170
|
+
: undefined
|
|
171
|
+
}
|
|
131
172
|
onClose={presenter.chatThreadManager.closeWorkspacePanel}
|
|
132
173
|
/>
|
|
133
174
|
|
|
134
175
|
<div className="flex min-h-0 flex-1 flex-col bg-white">
|
|
135
176
|
<ChatSessionWorkspacePanelContent
|
|
136
177
|
activeSelection={activeSelection}
|
|
178
|
+
filePreviewRefreshVersion={filePreviewRefreshVersion}
|
|
137
179
|
sessionCronJobs={sessionCronJobs}
|
|
138
180
|
sessionProjectRoot={sessionProjectRoot}
|
|
139
181
|
sessionWorkingDir={sessionWorkingDir}
|
package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts
CHANGED
|
@@ -53,6 +53,8 @@ export function normalizePersistedWorkspaceFileTab(
|
|
|
53
53
|
line: normalizeOptionalNumber(value.line),
|
|
54
54
|
column: normalizeOptionalNumber(value.column),
|
|
55
55
|
rawText: normalizeOptionalText(value.rawText),
|
|
56
|
+
contentUrl: normalizeOptionalString(value.contentUrl),
|
|
57
|
+
mimeType: normalizeOptionalString(value.mimeType),
|
|
56
58
|
beforeText: normalizeOptionalText(value.beforeText),
|
|
57
59
|
afterText: normalizeOptionalText(value.afterText),
|
|
58
60
|
patchText: normalizeOptionalText(value.patchText),
|
|
@@ -74,6 +76,8 @@ export function toPersistedWorkspaceFileTab(
|
|
|
74
76
|
line: tab.line,
|
|
75
77
|
column: tab.column,
|
|
76
78
|
rawText: tab.rawText,
|
|
79
|
+
contentUrl: tab.contentUrl,
|
|
80
|
+
mimeType: tab.mimeType,
|
|
77
81
|
beforeText: tab.beforeText,
|
|
78
82
|
afterText: tab.afterText,
|
|
79
83
|
patchText: tab.patchText,
|
|
@@ -42,7 +42,10 @@ function resetChatSessionListManagerState() {
|
|
|
42
42
|
snapshot: {
|
|
43
43
|
...useChatSessionListStore.getState().snapshot,
|
|
44
44
|
selectedSessionKey: 'session-1',
|
|
45
|
-
listMode: 'time-first'
|
|
45
|
+
listMode: 'time-first',
|
|
46
|
+
pinnedSessionKeys: [],
|
|
47
|
+
pinnedProjectRoots: [],
|
|
48
|
+
collapsedProjectRoots: [],
|
|
46
49
|
}
|
|
47
50
|
});
|
|
48
51
|
useChatThreadStore.setState({
|
|
@@ -191,6 +194,31 @@ describe('ChatSessionListManager list preference and read state', () => {
|
|
|
191
194
|
});
|
|
192
195
|
});
|
|
193
196
|
|
|
197
|
+
it('persists session and project list preferences through the list owner', () => {
|
|
198
|
+
const manager = new ChatSessionListManager(
|
|
199
|
+
{} as ConstructorParameters<typeof ChatSessionListManager>[0]
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
manager.toggleSessionPinned('session-2');
|
|
203
|
+
manager.toggleProjectPinned('/tmp/project-alpha');
|
|
204
|
+
manager.toggleProjectCollapsed('/tmp/project-alpha');
|
|
205
|
+
|
|
206
|
+
expect(useChatSessionListStore.getState().snapshot).toMatchObject({
|
|
207
|
+
pinnedSessionKeys: ['session-2'],
|
|
208
|
+
pinnedProjectRoots: ['/tmp/project-alpha'],
|
|
209
|
+
collapsedProjectRoots: ['/tmp/project-alpha'],
|
|
210
|
+
});
|
|
211
|
+
expect(persistStorage.get(chatSessionListModeStorageKey)).toMatchObject({
|
|
212
|
+
state: {
|
|
213
|
+
snapshot: {
|
|
214
|
+
pinnedSessionKeys: ['session-2'],
|
|
215
|
+
pinnedProjectRoots: ['/tmp/project-alpha'],
|
|
216
|
+
collapsedProjectRoots: ['/tmp/project-alpha'],
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
194
222
|
it('marks a session as read through the session list owner boundary', () => {
|
|
195
223
|
const manager = new ChatSessionListManager(
|
|
196
224
|
{} as ConstructorParameters<typeof ChatSessionListManager>[0]
|
|
@@ -266,7 +294,10 @@ describe('ChatSessionListStore persistence', () => {
|
|
|
266
294
|
useChatSessionListStore.setState({
|
|
267
295
|
snapshot: {
|
|
268
296
|
...useChatSessionListStore.getState().snapshot,
|
|
269
|
-
listMode: 'time-first'
|
|
297
|
+
listMode: 'time-first',
|
|
298
|
+
pinnedSessionKeys: [],
|
|
299
|
+
pinnedProjectRoots: [],
|
|
300
|
+
collapsedProjectRoots: [],
|
|
270
301
|
}
|
|
271
302
|
});
|
|
272
303
|
});
|
|
@@ -13,6 +13,17 @@ type WorkspaceChildReadState = {
|
|
|
13
13
|
readAt?: string | null;
|
|
14
14
|
runStatus?: string | null;
|
|
15
15
|
};
|
|
16
|
+
|
|
17
|
+
function toggleListValue(values: string[], value: string): string[] {
|
|
18
|
+
const normalizedValue = value.trim();
|
|
19
|
+
if (!normalizedValue) {
|
|
20
|
+
return values;
|
|
21
|
+
}
|
|
22
|
+
return values.includes(normalizedValue)
|
|
23
|
+
? values.filter((item) => item !== normalizedValue)
|
|
24
|
+
: [normalizedValue, ...values];
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
export class ChatSessionListManager {
|
|
17
28
|
constructor(private uiManager: ChatUiManager) {}
|
|
18
29
|
|
|
@@ -110,6 +121,27 @@ export class ChatSessionListManager {
|
|
|
110
121
|
useChatSessionListStore.getState().setSnapshot({ listMode: value });
|
|
111
122
|
};
|
|
112
123
|
|
|
124
|
+
toggleSessionPinned = (sessionKey: string) => {
|
|
125
|
+
const { pinnedSessionKeys } = useChatSessionListStore.getState().snapshot;
|
|
126
|
+
useChatSessionListStore.getState().setSnapshot({
|
|
127
|
+
pinnedSessionKeys: toggleListValue(pinnedSessionKeys, sessionKey),
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
toggleProjectPinned = (projectRoot: string) => {
|
|
132
|
+
const { pinnedProjectRoots } = useChatSessionListStore.getState().snapshot;
|
|
133
|
+
useChatSessionListStore.getState().setSnapshot({
|
|
134
|
+
pinnedProjectRoots: toggleListValue(pinnedProjectRoots, projectRoot),
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
toggleProjectCollapsed = (projectRoot: string) => {
|
|
139
|
+
const { collapsedProjectRoots } = useChatSessionListStore.getState().snapshot;
|
|
140
|
+
useChatSessionListStore.getState().setSnapshot({
|
|
141
|
+
collapsedProjectRoots: toggleListValue(collapsedProjectRoots, projectRoot),
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
|
|
113
145
|
markSessionRead = (
|
|
114
146
|
sessionKey: string | null | undefined,
|
|
115
147
|
readAt: string | null | undefined,
|
|
@@ -128,6 +128,8 @@ export class ChatThreadManager {
|
|
|
128
128
|
line: action.line ?? null,
|
|
129
129
|
column: action.column ?? null,
|
|
130
130
|
rawText: action.rawText ?? null,
|
|
131
|
+
contentUrl: action.contentUrl?.trim() || null,
|
|
132
|
+
mimeType: action.mimeType?.trim() || null,
|
|
131
133
|
beforeText: action.beforeText ?? null,
|
|
132
134
|
afterText: action.afterText ?? null,
|
|
133
135
|
patchText: action.patchText ?? null,
|