@nextclaw/ui 0.15.0 → 0.15.2
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 +50 -0
- package/dist/assets/{api-DLtsiBKm.js → api-B7Qigvtm.js} +2 -2
- package/dist/assets/{appearance-settings-page-CtkeYXux.js → appearance-settings-page-CuE0Iiqj.js} +1 -1
- package/dist/assets/{book-open-BQwWBDKb.js → book-open-Ba2f7YiG.js} +1 -1
- package/dist/assets/channels-list-page-CTsM6TcP.js +8 -0
- package/dist/assets/chat-page-C_5dcuE3.js +1 -0
- package/dist/assets/config-split-page-Fbb4PtTf.js +1 -0
- package/dist/assets/{confirm-dialog-iIJFFp2T.js → confirm-dialog-D2EG3nqE.js} +1 -1
- package/dist/assets/desktop-update-config-BGR3HUi1.js +1 -0
- package/dist/assets/{dist-D2eZC3OR.js → dist-7JTjycxE.js} +1 -1
- package/dist/assets/{dist-CBxWOazz.js → dist-wXLhk4hl.js} +1 -1
- package/dist/assets/doc-browser-bHyXnxlj.js +1 -0
- package/dist/assets/doc-browser-context-CYAGm5d5.js +1 -0
- package/dist/assets/doc-browser-gTrQ3jCF.js +1 -0
- package/dist/assets/{es2015-DhQo_oS6.js → es2015-j1SRCIX5.js} +1 -1
- package/dist/assets/{external-link-CM5dnUI6.js → external-link-DxuPaDoK.js} +1 -1
- package/dist/assets/form-actions-BztL7P_T.js +1 -0
- package/dist/assets/{i18n-provider-di1waTGX.js → i18n-provider-B4oMxOFx.js} +1 -1
- package/dist/assets/index-CvrvblgX.css +1 -0
- package/dist/assets/index-Obd4JYU2.js +104 -0
- package/dist/assets/loader-circle-CdzgK4eH.js +1 -0
- package/dist/assets/{mcp-marketplace-page-C_MVQUbT.js → mcp-marketplace-page-Bt7CuOoq.js} +2 -2
- package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +1 -0
- package/dist/assets/model-config-page-DeKk9gvE.js +1 -0
- package/dist/assets/plus-Bx7wQbhG.js +1 -0
- package/dist/assets/{provider-scoped-model-input-g1WkGiJ8.js → provider-scoped-model-input-DsH2LF1q.js} +1 -1
- package/dist/assets/providers-config-page-BFnGp68F.js +1 -0
- package/dist/assets/{react-DjpzoZXB.js → react-UirzUuST.js} +1 -1
- package/dist/assets/{refresh-cw-DaTv3zxE.js → refresh-cw-eT8BF-8B.js} +1 -1
- package/dist/assets/remote-CDkHZtA8.js +1 -0
- package/dist/assets/rotate-cw-1JqxHTuw.js +1 -0
- package/dist/assets/runtime-config-page-BelAqiNG.js +1 -0
- package/dist/assets/{save-BZof1uie.js → save-Cb2g0BOi.js} +1 -1
- package/dist/assets/{search-MxAarS_g.js → search-BcHjwdsb.js} +1 -1
- package/dist/assets/search-config-page-zPm6--Ig.js +1 -0
- package/dist/assets/secrets-config-page-Cxyp-KuQ.js +3 -0
- package/dist/assets/security-config-B_gCq9fn.js +1 -0
- package/dist/assets/{select-D7GcNP0z.js → select-CdSEOwVM.js} +1 -1
- package/dist/assets/{setting-row-CuozlnJd.js → setting-row-CTAtGexr.js} +1 -1
- package/dist/assets/settings-2-2DWauOjY.js +1 -0
- package/dist/assets/{side-dock-BcLau5S_.js → side-dock-DPvakJ0a.js} +2 -2
- package/dist/assets/{skeleton-shfmXuYp.js → skeleton-P5oM14E1.js} +1 -1
- package/dist/assets/{switch-C1BWMU5c.js → switch-BU_UEULb.js} +1 -1
- package/dist/assets/{tag-chip-DCUrQUCP.js → tag-chip-D-WsKoT1.js} +1 -1
- package/dist/assets/{tooltip-DfAHHA76.js → tooltip-CjkOaHW7.js} +1 -1
- package/dist/assets/{use-config-DAdbT5QJ.js → use-config--hx5oxQn.js} +1 -1
- package/dist/assets/{use-confirm-dialog-eWcJ6V-s.js → use-confirm-dialog-CD9a_SK1.js} +1 -1
- package/dist/assets/x-DT5x45JU.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 +43 -4
- package/src/app/components/layout/sidebar.tsx +83 -61
- package/src/app/configs/app-navigation.config.ts +49 -0
- package/src/app/styles/design-system.css +82 -0
- package/src/features/agents/components/__tests__/agents-page.test.tsx +135 -2
- package/src/features/agents/components/agent-advanced-config-fields.tsx +70 -0
- package/src/features/agents/components/agent-details-dialog.tsx +398 -0
- package/src/features/agents/components/agent-dialogs.tsx +20 -47
- package/src/features/agents/components/agents-page.tsx +76 -10
- package/src/features/agents/types/agent-form.types.ts +21 -0
- package/src/features/agents/utils/agent-form.utils.ts +39 -0
- package/src/features/channels/components/config/channel-form.tsx +14 -11
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +172 -1
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +11 -1
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -3
- 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 +29 -30
- package/src/features/chat/components/layout/chat-sidebar.tsx +21 -7
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +18 -3
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -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 +1 -1
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +10 -5
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -11
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +23 -1
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-session-list-view.test.tsx +74 -0
- package/src/features/chat/features/ncp/hooks/use-ncp-session-list-view.ts +3 -2
- 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 +294 -0
- package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +15 -1
- package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +24 -4
- package/src/features/chat/features/session/utils/chat-session-display.utils.ts +3 -0
- package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +25 -3
- package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +1 -0
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +9 -4
- 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 +193 -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-breadcrumbs.tsx +3 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +67 -14
- 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/__tests__/chat-workspace-panel-view-model.utils.test.ts +46 -0
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +33 -2
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +47 -0
- package/src/features/chat/managers/chat-session-list.manager.ts +32 -0
- package/src/features/chat/managers/chat-thread.manager.ts +8 -2
- package/src/features/chat/stores/chat-session-list.store.ts +28 -5
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +5 -12
- package/src/features/panel-apps/components/panel-app-list-item.tsx +6 -6
- 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/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/system-status/components/__tests__/desktop-update-config.test.tsx +97 -5
- package/src/features/system-status/components/config/runtime-config-editor.tsx +5 -4
- package/src/features/system-status/components/desktop-update-config.tsx +91 -2
- package/src/features/system-status/utils/update-release-notes.utils.ts +94 -0
- package/src/index.css +18 -0
- 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 +5 -1
- 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/card.tsx +12 -4
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +2 -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/ncp-session.types.ts +1 -1
- package/src/shared/lib/api/types.ts +12 -3
- package/src/shared/lib/api/utils/server-path.utils.ts +1 -0
- package/src/shared/lib/i18n/locales/en-US/agents.json +41 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +19 -0
- package/src/shared/lib/i18n/locales/en-US/core.json +3 -0
- package/src/shared/lib/i18n/locales/en-US/desktop-update.json +8 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +5 -0
- package/src/shared/lib/i18n/locales/zh-CN/agents.json +41 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +19 -0
- package/src/shared/lib/i18n/locales/zh-CN/core.json +3 -0
- package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +8 -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-Cv8o9Hxg.js +0 -8
- package/dist/assets/chat-page-BtA7H1Ea.js +0 -1
- package/dist/assets/config-split-page-EidRBpP2.js +0 -1
- package/dist/assets/desktop-update-config-C6n_TB1r.js +0 -1
- package/dist/assets/doc-browser-B76XOXmW.js +0 -1
- package/dist/assets/doc-browser-DBnj9UMB.js +0 -1
- package/dist/assets/doc-browser-context-Sv-044Cl.js +0 -1
- package/dist/assets/ellipsis-2uzhypw5.js +0 -1
- package/dist/assets/index-CBRP-7-a.css +0 -1
- package/dist/assets/index-Z1fYpbNd.js +0 -104
- package/dist/assets/loader-circle-BjmN5DeJ.js +0 -1
- package/dist/assets/mcp-marketplace-page-CoZIiD6G.js +0 -1
- package/dist/assets/model-config-page-Cdr9P6TQ.js +0 -1
- package/dist/assets/plus-3ztRZD_l.js +0 -1
- package/dist/assets/providers-config-page-CWlO7J4A.js +0 -1
- package/dist/assets/remote-CKWZfVVB.js +0 -1
- package/dist/assets/rotate-cw-CHcaRUDz.js +0 -1
- package/dist/assets/runtime-config-page-CfTqh1nv.js +0 -1
- package/dist/assets/search-config-page-Dm8daYny.js +0 -1
- package/dist/assets/secrets-config-page-D8AbcR05.js +0 -3
- package/dist/assets/security-config-Ds0LsJmo.js +0 -1
- package/dist/assets/x-DqL1mYLC.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-breadcrumbs.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment, useState } from "react";
|
|
2
2
|
import type { ChatFileOpenActionViewModel } from "@nextclaw/agent-chat-ui";
|
|
3
|
-
import { ChevronRight, FileCode2, FolderTree } from "lucide-react";
|
|
3
|
+
import { ChevronRight, FileCode2, Folder, FolderTree } from "lucide-react";
|
|
4
4
|
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/components/ui/popover";
|
|
5
5
|
import { WorkspaceBreadcrumbBrowser } from "./chat-session-workspace-file-breadcrumb-browser";
|
|
6
6
|
import type {
|
|
@@ -59,6 +59,8 @@ function WorkspaceBreadcrumbSegmentButton({
|
|
|
59
59
|
>
|
|
60
60
|
{segment.kind === "workspace" ? (
|
|
61
61
|
<FolderTree className="h-3 w-3 shrink-0" />
|
|
62
|
+
) : segment.kind === "directory" && segment.isCurrent ? (
|
|
63
|
+
<Folder className="h-3 w-3 shrink-0" />
|
|
62
64
|
) : segment.isCurrent ? (
|
|
63
65
|
<FileCode2 className="h-3 w-3 shrink-0" />
|
|
64
66
|
) : null}
|
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";
|
|
@@ -24,17 +32,18 @@ function inferPreviewKind(params: {
|
|
|
24
32
|
return /\.mdx?$/i.test(params.path) ? "markdown" : "text";
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
function isHtmlFilePath(path: string): boolean {
|
|
28
|
-
return /\.html?$/i.test(path);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
35
|
function resolveFilePreviewViewer(params: {
|
|
32
36
|
path: string;
|
|
33
37
|
viewer?: ChatFilePreviewViewer | null;
|
|
34
38
|
}): "source" | "rendered" {
|
|
35
|
-
return params.viewer === "rendered" &&
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
return params.viewer === "rendered" && /\.html?$/i.test(params.path) ? "rendered" : "source";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function appendPreviewRefreshVersion(url: string, refreshVersion: number): string {
|
|
43
|
+
if (refreshVersion <= 0) {
|
|
44
|
+
return url;
|
|
45
|
+
}
|
|
46
|
+
return `${url}${url.includes("?") ? "&" : "?"}refresh=${refreshVersion}`;
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
function buildPreviewBlock(params: {
|
|
@@ -167,6 +176,7 @@ function WorkspaceHtmlRenderedPreview({
|
|
|
167
176
|
}
|
|
168
177
|
|
|
169
178
|
function WorkspacePreviewBody({
|
|
179
|
+
directoryQuery,
|
|
170
180
|
onFileOpen,
|
|
171
181
|
previewBlock,
|
|
172
182
|
previewKind,
|
|
@@ -175,6 +185,7 @@ function WorkspacePreviewBody({
|
|
|
175
185
|
previewUrl,
|
|
176
186
|
previewViewer,
|
|
177
187
|
}: {
|
|
188
|
+
directoryQuery: ReturnType<typeof useServerPathBrowse>;
|
|
178
189
|
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
179
190
|
previewBlock: ChatFileOperationBlockViewModel | null;
|
|
180
191
|
previewKind: "text" | "markdown" | "binary";
|
|
@@ -183,8 +194,26 @@ function WorkspacePreviewBody({
|
|
|
183
194
|
previewUrl: string | null;
|
|
184
195
|
previewViewer: "source" | "rendered";
|
|
185
196
|
}) {
|
|
186
|
-
if (
|
|
187
|
-
return
|
|
197
|
+
if (directoryQuery.data) {
|
|
198
|
+
return (
|
|
199
|
+
<ChatSessionWorkspaceDirectoryBrowser
|
|
200
|
+
browseQuery={directoryQuery}
|
|
201
|
+
onFileOpen={onFileOpen}
|
|
202
|
+
renderStatus={(params) => <WorkspaceFilePreviewStatus {...params} />}
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (directoryQuery.isLoading && previewQuery.error && !previewBlock) {
|
|
208
|
+
return (
|
|
209
|
+
<WorkspaceFilePreviewStatus text={t("chatWorkspaceLoadingDirectory")} />
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if ((directoryQuery.isLoading || previewQuery.isLoading) && !previewBlock) {
|
|
214
|
+
return (
|
|
215
|
+
<WorkspaceFilePreviewStatus text={t("chatWorkspaceLoadingPreview")} />
|
|
216
|
+
);
|
|
188
217
|
}
|
|
189
218
|
|
|
190
219
|
if (previewQuery.data?.kind === "binary") {
|
|
@@ -235,6 +264,7 @@ function WorkspacePreviewBody({
|
|
|
235
264
|
|
|
236
265
|
type ChatSessionWorkspaceFilePreviewProps = {
|
|
237
266
|
file: ChatWorkspaceFileTab;
|
|
267
|
+
refreshVersion?: number;
|
|
238
268
|
sessionProjectRoot: string | null;
|
|
239
269
|
sessionWorkingDir: string | null;
|
|
240
270
|
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
@@ -242,6 +272,7 @@ type ChatSessionWorkspaceFilePreviewProps = {
|
|
|
242
272
|
|
|
243
273
|
export function ChatSessionWorkspaceFilePreview({
|
|
244
274
|
file,
|
|
275
|
+
refreshVersion = 0,
|
|
245
276
|
sessionProjectRoot,
|
|
246
277
|
sessionWorkingDir,
|
|
247
278
|
onFileOpen,
|
|
@@ -252,6 +283,12 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
252
283
|
basePath: sessionWorkingDir,
|
|
253
284
|
enabled: isPreviewMode,
|
|
254
285
|
});
|
|
286
|
+
const directoryQuery = useServerPathBrowse({
|
|
287
|
+
path: file.path,
|
|
288
|
+
basePath: sessionWorkingDir,
|
|
289
|
+
includeFiles: true,
|
|
290
|
+
enabled: isPreviewMode,
|
|
291
|
+
});
|
|
255
292
|
const diffBlock = useMemo(
|
|
256
293
|
() => (file.viewMode === "diff" ? buildDiffBlock(file) : null),
|
|
257
294
|
[file],
|
|
@@ -262,14 +299,21 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
262
299
|
path: previewQuery.data?.resolvedPath ?? file.path,
|
|
263
300
|
serverKind: previewQuery.data?.kind,
|
|
264
301
|
});
|
|
265
|
-
const resolvedPath =
|
|
302
|
+
const resolvedPath =
|
|
303
|
+
directoryQuery.data?.currentPath ??
|
|
304
|
+
previewQuery.data?.resolvedPath ??
|
|
305
|
+
file.path;
|
|
306
|
+
const previewPathKind = directoryQuery.data ? "directory" : "file";
|
|
266
307
|
const previewViewer = resolveFilePreviewViewer({
|
|
267
308
|
path: resolvedPath,
|
|
268
309
|
viewer: file.previewViewer,
|
|
269
310
|
});
|
|
270
311
|
const previewUrl =
|
|
271
312
|
previewViewer === "rendered" && previewQuery.data?.resolvedPath
|
|
272
|
-
?
|
|
313
|
+
? appendPreviewRefreshVersion(
|
|
314
|
+
buildServerPathContentUrl(previewQuery.data.resolvedPath),
|
|
315
|
+
refreshVersion,
|
|
316
|
+
)
|
|
273
317
|
: null;
|
|
274
318
|
const previewBlock = useMemo(() => {
|
|
275
319
|
if (!isPreviewMode || !previewText) {
|
|
@@ -295,12 +339,20 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
295
339
|
() =>
|
|
296
340
|
buildWorkspaceFileBreadcrumb({
|
|
297
341
|
path: resolvedPath,
|
|
342
|
+
kind: previewPathKind,
|
|
298
343
|
sessionProjectRoot: breadcrumbBasePath,
|
|
299
344
|
line: file.line,
|
|
300
345
|
column: file.column,
|
|
301
346
|
truncated: isTruncated,
|
|
302
347
|
}),
|
|
303
|
-
[
|
|
348
|
+
[
|
|
349
|
+
breadcrumbBasePath,
|
|
350
|
+
file.column,
|
|
351
|
+
file.line,
|
|
352
|
+
isTruncated,
|
|
353
|
+
previewPathKind,
|
|
354
|
+
resolvedPath,
|
|
355
|
+
],
|
|
304
356
|
);
|
|
305
357
|
|
|
306
358
|
return (
|
|
@@ -315,6 +367,7 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
315
367
|
<WorkspaceDiffBody diffBlock={diffBlock} />
|
|
316
368
|
) : (
|
|
317
369
|
<WorkspacePreviewBody
|
|
370
|
+
directoryQuery={directoryQuery}
|
|
318
371
|
onFileOpen={onFileOpen}
|
|
319
372
|
previewBlock={previewBlock}
|
|
320
373
|
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}
|
|
@@ -158,4 +158,50 @@ describe("buildWorkspaceTabsViewModel", () => {
|
|
|
158
158
|
active: true,
|
|
159
159
|
});
|
|
160
160
|
});
|
|
161
|
+
|
|
162
|
+
it("keeps distinct source and rendered file tabs without collapsing by path", () => {
|
|
163
|
+
const renderedTab = {
|
|
164
|
+
key: "parent::preview:rendered::demo.html",
|
|
165
|
+
parentSessionKey: "parent-1",
|
|
166
|
+
path: "demo.html",
|
|
167
|
+
label: "demo.html",
|
|
168
|
+
viewMode: "preview" as const,
|
|
169
|
+
previewViewer: "rendered" as const,
|
|
170
|
+
};
|
|
171
|
+
const sourceTab = {
|
|
172
|
+
key: "parent::preview::demo.html",
|
|
173
|
+
parentSessionKey: "parent-1",
|
|
174
|
+
path: "demo.html",
|
|
175
|
+
label: "demo.html",
|
|
176
|
+
viewMode: "preview" as const,
|
|
177
|
+
previewViewer: "source" as const,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const tabs = buildWorkspaceTabsViewModel({
|
|
181
|
+
resolvedChildTabs: [],
|
|
182
|
+
activeSideChatDraft: null,
|
|
183
|
+
workspaceFileTabs: [sourceTab, renderedTab],
|
|
184
|
+
sessionCronJobCount: 0,
|
|
185
|
+
activeSelection: {
|
|
186
|
+
kind: "file",
|
|
187
|
+
file: sourceTab,
|
|
188
|
+
},
|
|
189
|
+
optimisticReadAtBySessionKey: {},
|
|
190
|
+
onSelectSession: vi.fn(),
|
|
191
|
+
onSelectFile: vi.fn(),
|
|
192
|
+
onCloseFile: vi.fn(),
|
|
193
|
+
onSelectCronJobs: vi.fn(),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(tabs).toEqual([
|
|
197
|
+
expect.objectContaining({
|
|
198
|
+
key: "file:parent::preview::demo.html",
|
|
199
|
+
active: true,
|
|
200
|
+
}),
|
|
201
|
+
expect.objectContaining({
|
|
202
|
+
key: "file:parent::preview:rendered::demo.html",
|
|
203
|
+
active: false,
|
|
204
|
+
}),
|
|
205
|
+
]);
|
|
206
|
+
});
|
|
161
207
|
});
|
|
@@ -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
|
});
|
|
@@ -331,6 +331,53 @@ describe('ChatThreadManager', () => {
|
|
|
331
331
|
});
|
|
332
332
|
|
|
333
333
|
describe('ChatThreadManager workspace file tabs', () => {
|
|
334
|
+
it('opens HTML previews in source mode when the viewer is omitted or auto', () => {
|
|
335
|
+
const manager = new ChatThreadManager(
|
|
336
|
+
createUiManager(),
|
|
337
|
+
{} as ConstructorParameters<typeof ChatThreadManager>[1],
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
manager.openFilePreview({
|
|
341
|
+
path: 'demo.html',
|
|
342
|
+
label: 'demo.html',
|
|
343
|
+
viewMode: 'preview',
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
347
|
+
activeWorkspaceFileKey: 'parent-session-1::preview::demo.html',
|
|
348
|
+
workspaceFileTabs: [
|
|
349
|
+
{
|
|
350
|
+
key: 'parent-session-1::preview::demo.html',
|
|
351
|
+
path: 'demo.html',
|
|
352
|
+
viewMode: 'preview',
|
|
353
|
+
previewViewer: 'source',
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
manager.openFilePreview({
|
|
359
|
+
path: 'auto.html',
|
|
360
|
+
label: 'auto.html',
|
|
361
|
+
viewMode: 'preview',
|
|
362
|
+
previewViewer: 'auto',
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
366
|
+
activeWorkspaceFileKey: 'parent-session-1::preview::auto.html',
|
|
367
|
+
workspaceFileTabs: [
|
|
368
|
+
{
|
|
369
|
+
key: 'parent-session-1::preview::auto.html',
|
|
370
|
+
path: 'auto.html',
|
|
371
|
+
viewMode: 'preview',
|
|
372
|
+
previewViewer: 'source',
|
|
373
|
+
},
|
|
374
|
+
expect.objectContaining({
|
|
375
|
+
key: 'parent-session-1::preview::demo.html',
|
|
376
|
+
}),
|
|
377
|
+
],
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
334
381
|
it('keeps source and rendered previews for the same file as separate workspace tabs', () => {
|
|
335
382
|
const manager = new ChatThreadManager(
|
|
336
383
|
createUiManager(),
|
|
@@ -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,
|
|
@@ -111,14 +111,20 @@ export class ChatThreadManager {
|
|
|
111
111
|
return null;
|
|
112
112
|
}
|
|
113
113
|
const normalizedParentSessionKey = parentSessionKey?.trim() || null;
|
|
114
|
-
const
|
|
114
|
+
const previewViewer =
|
|
115
|
+
action.viewMode === 'preview' &&
|
|
116
|
+
action.previewViewer !== 'rendered' &&
|
|
117
|
+
(action.previewViewer === 'auto' || action.previewViewer === 'source' || /\.html?$/i.test(normalizedPath))
|
|
118
|
+
? 'source'
|
|
119
|
+
: action.previewViewer ?? null;
|
|
120
|
+
const tabViewIdentity = action.viewMode === 'preview' && previewViewer === 'rendered' ? 'preview:rendered' : action.viewMode;
|
|
115
121
|
return {
|
|
116
122
|
key: `${normalizedParentSessionKey ?? 'draft'}::${tabViewIdentity}::${normalizedPath}`,
|
|
117
123
|
parentSessionKey: normalizedParentSessionKey,
|
|
118
124
|
path: normalizedPath,
|
|
119
125
|
label: action.label?.trim() || null,
|
|
120
126
|
viewMode: action.viewMode,
|
|
121
|
-
previewViewer
|
|
127
|
+
previewViewer,
|
|
122
128
|
line: action.line ?? null,
|
|
123
129
|
column: action.column ?? null,
|
|
124
130
|
rawText: action.rawText ?? null,
|