@nextclaw/ui 0.14.4 → 0.15.1
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 +73 -0
- package/dist/assets/api-b1R9VdeH.js +13 -0
- package/dist/assets/appearance-settings-page-D2O9pch9.js +1 -0
- package/dist/assets/book-open-B77lQ2Tg.js +1 -0
- package/dist/assets/channels-list-page-DcsIQc6A.js +8 -0
- package/dist/assets/chat-page-CZ7WxPir.js +1 -0
- package/dist/assets/{config-split-page-CRhlZ_pH.js → config-split-page-BWQ6_Raq.js} +1 -1
- package/dist/assets/confirm-dialog-C-NXSioX.js +5 -0
- package/dist/assets/desktop-update-config-usHpXugP.js +1 -0
- package/dist/assets/{dist-CikkhOlq.js → dist-YtoqU-AT.js} +1 -1
- package/dist/assets/{dist-PVA_xL1D.js → dist-jdGmbb6w.js} +1 -1
- package/dist/assets/doc-browser-B_Tf9n70.js +1 -0
- package/dist/assets/doc-browser-BdZPFOVa.js +1 -0
- package/dist/assets/{doc-browser-context-MTw4EvKd.js → doc-browser-context-DwokemUn.js} +1 -1
- package/dist/assets/es2015-bNpL2LKy.js +41 -0
- package/dist/assets/{external-link-DVJIQf_G.js → external-link-BM6fk8q-.js} +1 -1
- package/dist/assets/host-capabilities-9bB_998Z.js +1 -0
- package/dist/assets/i18n-provider-CntWolRA.js +1 -0
- package/dist/assets/index-BIcEw90u.js +104 -0
- package/dist/assets/index-BiNSTKTU.css +1 -0
- package/dist/assets/loader-circle-MBgNSVIH.js +1 -0
- package/dist/assets/mcp-marketplace-page-CLmUstLN.js +9 -0
- package/dist/assets/mcp-marketplace-page-rZkmWPMy.js +1 -0
- package/dist/assets/model-config-page-Di0ySJD2.js +1 -0
- package/dist/assets/plus-vDqW5Kxp.js +1 -0
- package/dist/assets/provider-scoped-model-input-Cm_a4GHf.js +1 -0
- package/dist/assets/providers-config-page-BAf2W0MV.js +1 -0
- package/dist/assets/{react-VNqT5Zp7.js → react-BN8uzPnp.js} +1 -1
- package/dist/assets/refresh-cw-BpVcFPmJ.js +1 -0
- package/dist/assets/remote-CLQ0IBM8.js +1 -0
- package/dist/assets/rotate-cw-CvNBv7_w.js +1 -0
- package/dist/assets/runtime-config-page-CflvvoDc.js +1 -0
- package/dist/assets/{save-BOJ477H-.js → save-BpFW5fpk.js} +1 -1
- package/dist/assets/{search-BEti5beA.js → search-D0uO7enf.js} +1 -1
- package/dist/assets/search-config-page-D0klFUXd.js +1 -0
- package/dist/assets/{secrets-config-page--IzrIOid.js → secrets-config-page-dVZ631Wp.js} +2 -2
- package/dist/assets/security-config-C5gnlkuj.js +1 -0
- package/dist/assets/select-Bh-CrA4p.js +1 -0
- package/dist/assets/setting-row-BJfCHRcb.js +1 -0
- package/dist/assets/settings-2-Blzi1vr6.js +1 -0
- package/dist/assets/side-dock-ZdaSrQVB.js +3 -0
- package/dist/assets/skeleton-BLlEzebZ.js +1 -0
- package/dist/assets/switch-CFLJFNSd.js +1 -0
- package/dist/assets/tag-chip-CzGihiQx.js +1 -0
- package/dist/assets/tooltip-CdcrBjrC.js +1 -0
- package/dist/assets/use-config-CVPgsBmU.js +1 -0
- package/dist/assets/use-confirm-dialog-CSeBk9SF.js +1 -0
- package/dist/assets/x-BF2KQ--V.js +1 -0
- package/dist/index.html +53 -17
- package/index.html +25 -2
- package/package.json +9 -9
- package/src/app/components/layout/__tests__/app-layout.test.tsx +27 -0
- package/src/app/components/layout/__tests__/settings-entry-page.test.tsx +9 -1
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +6 -3
- package/src/app/components/layout/app-layout.tsx +10 -3
- package/src/app/components/layout/sidebar-items.tsx +8 -8
- package/src/app/components/layout/sidebar-rail.styles.ts +4 -2
- package/src/app/components/layout/sidebar.tsx +21 -28
- package/src/app/configs/app-navigation.config.ts +11 -5
- package/src/app/index.tsx +8 -0
- package/src/app/managers/__tests__/viewport-layout.manager.test.ts +59 -0
- package/src/app/managers/viewport-layout.manager.ts +39 -0
- package/src/app/presenters/app.presenter.ts +25 -2
- package/src/app/styles/design-system.css +91 -4
- 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/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +86 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +132 -1
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +47 -17
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +8 -0
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -3
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +7 -0
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +9 -5
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +5 -5
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +66 -2
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +170 -1
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +23 -1
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +24 -7
- package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +96 -0
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +181 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +351 -89
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +29 -4
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +28 -3
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -8
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +50 -14
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +77 -6
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +21 -12
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +167 -1
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +19 -11
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +38 -64
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +59 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +56 -1
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -2
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +80 -0
- package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +12 -3
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +60 -0
- package/src/features/chat/features/message/utils/chat-message.utils.ts +3 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +62 -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/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +3 -1
- package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
- 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/ncp-session-adapter.utils.ts +1 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +304 -115
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +121 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +75 -20
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +64 -5
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +46 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +83 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +126 -1
- package/src/features/chat/managers/__tests__/chat-ui.manager.test.ts +2 -0
- package/src/features/chat/managers/chat-thread.manager.ts +15 -3
- package/src/features/chat/managers/chat-ui.manager.ts +5 -1
- package/src/features/chat/presenters/chat.presenter.ts +2 -1
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +4 -0
- package/src/features/chat/stores/chat-thread.store.ts +6 -64
- package/src/features/chat/types/chat-message.types.ts +4 -0
- package/src/features/marketplace/components/__tests__/marketplace-page-detail.test.tsx +33 -26
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +1 -0
- package/src/features/marketplace/components/curated-shelves/__tests__/marketplace-curated-scene-route.test.tsx +1 -0
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +115 -0
- package/src/features/marketplace/components/marketplace-detail-doc.tsx +441 -0
- package/src/features/marketplace/components/mcp/__tests__/mcp-marketplace-page.test.tsx +3 -3
- package/src/features/marketplace/components/mcp/mcp-marketplace-doc.ts +0 -60
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +64 -29
- package/src/features/marketplace/hooks/use-marketplace-item-detail.ts +42 -31
- package/src/features/marketplace/index.ts +3 -0
- package/src/features/marketplace/stores/marketplace-detail-doc.store.ts +38 -0
- package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +32 -2
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -0
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +8 -3
- package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -0
- package/src/features/settings/pages/appearance-settings-page.tsx +40 -0
- package/src/features/side-dock/components/__tests__/side-dock.test.tsx +44 -2
- package/src/features/side-dock/components/side-dock.tsx +100 -13
- package/src/features/side-dock/configs/side-dock-built-in-items.config.ts +10 -0
- package/src/features/side-dock/managers/__tests__/side-dock.manager.test.ts +32 -2
- package/src/features/side-dock/managers/side-dock.manager.ts +17 -7
- package/src/features/side-dock/stores/side-dock.store.ts +9 -0
- package/src/features/side-dock/types/side-dock.types.ts +10 -3
- package/src/features/side-dock/utils/side-dock-item.utils.ts +15 -2
- package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +97 -5
- 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 +501 -27
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +1 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +30 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +206 -38
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +34 -11
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +1 -1
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +23 -5
- package/src/shared/components/doc-browser/doc-browser.tsx +128 -75
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +9 -1
- package/src/shared/components/doc-browser/utils/doc-browser-floating-panel.utils.ts +83 -0
- package/src/shared/components/ui/__tests__/dialog.test.tsx +27 -0
- package/src/shared/components/ui/actions/icon-action-button.tsx +14 -2
- package/src/shared/components/ui/dialog.tsx +2 -2
- package/src/shared/components/ui/popover.tsx +1 -1
- package/src/shared/components/ui/select.tsx +1 -1
- package/src/shared/components/ui/switch.tsx +4 -3
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +54 -0
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +4 -5
- package/src/shared/components/ui/tooltip.tsx +1 -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 +27 -1
- package/src/shared/lib/i18n/locales/en-US/agents.json +41 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +18 -6
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -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 +11 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.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 +18 -6
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -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 +11 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +5 -0
- package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +14 -0
- package/src/shared/lib/session-project/index.ts +1 -1
- package/src/shared/lib/session-project/{workspace-file-breadcrumb.ts → workspace-file-breadcrumb.utils.ts} +62 -8
- package/src/shared/lib/theme/index.ts +51 -13
- package/dist/assets/api-BP_8IaxB.js +0 -13
- package/dist/assets/channels-list-page-DEh5Jjqx.js +0 -8
- package/dist/assets/chat-page-BRB0N5WW.js +0 -105
- package/dist/assets/confirm-dialog-DsivFJ4r.js +0 -5
- package/dist/assets/desktop-update-config-B0w9p2ep.js +0 -1
- package/dist/assets/doc-browser-BoNJ7-tE.js +0 -1
- package/dist/assets/doc-browser-CdJ_d4ik.js +0 -1
- package/dist/assets/ellipsis-y6vDtq4M.js +0 -1
- package/dist/assets/index-BiE-K8-q.css +0 -1
- package/dist/assets/index-D9BAE_L_.js +0 -105
- package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +0 -40
- package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +0 -1
- package/dist/assets/model-config-page-DotaTUHb.js +0 -1
- package/dist/assets/plus-BbZqquJe.js +0 -1
- package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +0 -1
- package/dist/assets/providers-config-page-DW4tQSLN.js +0 -1
- package/dist/assets/remote-B8TiUfwW.js +0 -1
- package/dist/assets/rotate-cw-DjHDTkCv.js +0 -1
- package/dist/assets/runtime-config-page-S6ErRHR8.js +0 -1
- package/dist/assets/search-config-page-DGj5am03.js +0 -1
- package/dist/assets/select-hoj8dK4K.js +0 -41
- package/dist/assets/tag-chip-T8-h-die.js +0 -1
- package/dist/assets/use-config-CjRV2hwL.js +0 -1
- package/dist/assets/x-D9PXs_xP.js +0 -1
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.ts +0 -40
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +0 -201
- package/src/features/marketplace/components/marketplace-detail-doc.ts +0 -142
- /package/dist/assets/{config-hints-BkYBc7z0.js → config-hints-fGnUjDe9.js} +0 -0
- /package/dist/assets/{middleware-CkFT3R4v.js → middleware-BILWbJE4.js} +0 -0
package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { Fragment } from "react";
|
|
1
|
+
import { Fragment, useState } from "react";
|
|
2
|
+
import type { ChatFileOpenActionViewModel } from "@nextclaw/agent-chat-ui";
|
|
2
3
|
import { ChevronRight, FileCode2, FolderTree } from "lucide-react";
|
|
3
|
-
import
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/components/ui/popover";
|
|
5
|
+
import { WorkspaceBreadcrumbBrowser } from "./chat-session-workspace-file-breadcrumb-browser";
|
|
6
|
+
import type {
|
|
7
|
+
WorkspaceFileBreadcrumbSegmentViewModel,
|
|
8
|
+
WorkspaceFileBreadcrumbViewModel,
|
|
9
|
+
} from "@/shared/lib/session-project";
|
|
4
10
|
import { t } from "@/shared/lib/i18n";
|
|
5
11
|
import { cn } from "@/shared/lib/utils";
|
|
6
12
|
|
|
@@ -19,7 +25,69 @@ function WorkspaceBreadcrumbMetaChip({ tone = "neutral", value }: { tone?: "neut
|
|
|
19
25
|
);
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
function WorkspaceBreadcrumbSegmentButton({
|
|
29
|
+
onFileOpen,
|
|
30
|
+
segment,
|
|
31
|
+
}: {
|
|
32
|
+
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
33
|
+
segment: WorkspaceFileBreadcrumbSegmentViewModel;
|
|
34
|
+
}) {
|
|
35
|
+
const [open, setOpen] = useState(false);
|
|
36
|
+
const [browsePath, setBrowsePath] = useState<string | null>(segment.browsePath);
|
|
37
|
+
const handleOpenChange = (nextOpen: boolean) => {
|
|
38
|
+
if (nextOpen) {
|
|
39
|
+
setBrowsePath(segment.browsePath);
|
|
40
|
+
}
|
|
41
|
+
setOpen(nextOpen);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Popover open={open} onOpenChange={handleOpenChange}>
|
|
46
|
+
<PopoverTrigger asChild>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
aria-current={segment.isCurrent ? "page" : undefined}
|
|
50
|
+
className={cn(
|
|
51
|
+
"inline-flex h-5 items-center gap-1 rounded-sm px-1 text-[11px] leading-none transition-colors hover:bg-gray-200/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40",
|
|
52
|
+
segment.kind === "workspace"
|
|
53
|
+
? "bg-primary/8 text-primary hover:bg-primary/12"
|
|
54
|
+
: segment.isCurrent
|
|
55
|
+
? "bg-gray-200/70 text-gray-900"
|
|
56
|
+
: "text-gray-500",
|
|
57
|
+
)}
|
|
58
|
+
disabled={!segment.browsePath}
|
|
59
|
+
>
|
|
60
|
+
{segment.kind === "workspace" ? (
|
|
61
|
+
<FolderTree className="h-3 w-3 shrink-0" />
|
|
62
|
+
) : segment.isCurrent ? (
|
|
63
|
+
<FileCode2 className="h-3 w-3 shrink-0" />
|
|
64
|
+
) : null}
|
|
65
|
+
<span>{segment.label}</span>
|
|
66
|
+
</button>
|
|
67
|
+
</PopoverTrigger>
|
|
68
|
+
<PopoverContent
|
|
69
|
+
data-testid="workspace-breadcrumb-popover"
|
|
70
|
+
className="w-[22rem] rounded-md p-0"
|
|
71
|
+
align="start"
|
|
72
|
+
>
|
|
73
|
+
<WorkspaceBreadcrumbBrowser
|
|
74
|
+
browsePath={browsePath}
|
|
75
|
+
onBrowsePathChange={setBrowsePath}
|
|
76
|
+
onClose={() => setOpen(false)}
|
|
77
|
+
onFileOpen={onFileOpen}
|
|
78
|
+
/>
|
|
79
|
+
</PopoverContent>
|
|
80
|
+
</Popover>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function ChatSessionWorkspaceFileBreadcrumbs({
|
|
85
|
+
breadcrumb,
|
|
86
|
+
onFileOpen,
|
|
87
|
+
}: {
|
|
88
|
+
breadcrumb: WorkspaceFileBreadcrumbViewModel;
|
|
89
|
+
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
90
|
+
}) {
|
|
23
91
|
return (
|
|
24
92
|
<div
|
|
25
93
|
data-testid="workspace-file-breadcrumbs"
|
|
@@ -33,23 +101,10 @@ export function ChatSessionWorkspaceFileBreadcrumbs({ breadcrumb }: { breadcrumb
|
|
|
33
101
|
<div className="flex min-w-0 flex-1 items-center gap-1 pr-1">
|
|
34
102
|
{breadcrumb.segments.map((segment, index) => (
|
|
35
103
|
<Fragment key={segment.key}>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
? "bg-primary/8 text-primary"
|
|
41
|
-
: segment.isCurrent
|
|
42
|
-
? "bg-gray-200/70 text-gray-900"
|
|
43
|
-
: "text-gray-500",
|
|
44
|
-
)}
|
|
45
|
-
>
|
|
46
|
-
{segment.kind === "workspace" ? (
|
|
47
|
-
<FolderTree className="h-3 w-3 shrink-0" />
|
|
48
|
-
) : segment.isCurrent ? (
|
|
49
|
-
<FileCode2 className="h-3 w-3 shrink-0" />
|
|
50
|
-
) : null}
|
|
51
|
-
<span>{segment.label}</span>
|
|
52
|
-
</span>
|
|
104
|
+
<WorkspaceBreadcrumbSegmentButton
|
|
105
|
+
segment={segment}
|
|
106
|
+
onFileOpen={onFileOpen}
|
|
107
|
+
/>
|
|
53
108
|
{index < breadcrumb.segments.length - 1 ? (
|
|
54
109
|
<ChevronRight className="h-3 w-3 shrink-0 text-gray-300" />
|
|
55
110
|
) : null}
|
package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ChatFileOpenActionViewModel,
|
|
4
|
+
ChatFileOperationBlockViewModel,
|
|
5
|
+
ChatFilePreviewViewer,
|
|
6
|
+
} from "@nextclaw/agent-chat-ui";
|
|
3
7
|
import { ChatMessageMarkdown, FileOperationCodeSurface } from "@nextclaw/agent-chat-ui";
|
|
4
8
|
import type { ChatWorkspaceFileTab } from "@/features/chat/stores/chat-thread.store";
|
|
5
9
|
import { ChatSessionWorkspaceFileBreadcrumbs } from "./chat-session-workspace-file-breadcrumbs";
|
|
6
10
|
import { useServerPathRead } from "@/shared/hooks/use-server-path-read";
|
|
11
|
+
import { buildServerPathContentUrl } from "@/shared/lib/api";
|
|
7
12
|
import { buildLineDiff, buildPreviewLines } from "@/features/chat/features/message/utils/file-operation/line-builder.utils";
|
|
8
13
|
import { t } from "@/shared/lib/i18n";
|
|
9
14
|
import { buildWorkspaceFileBreadcrumb } from "@/shared/lib/session-project";
|
|
@@ -19,12 +24,20 @@ function inferPreviewKind(params: {
|
|
|
19
24
|
return /\.mdx?$/i.test(params.path) ? "markdown" : "text";
|
|
20
25
|
}
|
|
21
26
|
|
|
27
|
+
function resolveFilePreviewViewer(params: {
|
|
28
|
+
path: string;
|
|
29
|
+
viewer?: ChatFilePreviewViewer | null;
|
|
30
|
+
}): "source" | "rendered" {
|
|
31
|
+
return params.viewer === "rendered" && /\.html?$/i.test(params.path) ? "rendered" : "source";
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
function buildPreviewBlock(params: {
|
|
23
35
|
path: string;
|
|
24
36
|
text: string;
|
|
37
|
+
languageHint?: string | null;
|
|
25
38
|
line?: number | null;
|
|
26
39
|
}): ChatFileOperationBlockViewModel {
|
|
27
|
-
const { line, path, text } = params;
|
|
40
|
+
const { languageHint, line, path, text } = params;
|
|
28
41
|
const startLine = line ?? 1;
|
|
29
42
|
return {
|
|
30
43
|
key: `preview:${path}`,
|
|
@@ -37,6 +50,7 @@ function buildPreviewBlock(params: {
|
|
|
37
50
|
newStartLine: startLine,
|
|
38
51
|
}),
|
|
39
52
|
rawText: text,
|
|
53
|
+
languageHint: languageHint ?? null,
|
|
40
54
|
oldStartLine: startLine,
|
|
41
55
|
newStartLine: startLine,
|
|
42
56
|
};
|
|
@@ -130,18 +144,38 @@ function WorkspaceCodeSurface({
|
|
|
130
144
|
return <div className="h-full overflow-auto custom-scrollbar bg-white"><FileOperationCodeSurface block={block} layout="workspace" /></div>;
|
|
131
145
|
}
|
|
132
146
|
|
|
147
|
+
function WorkspaceHtmlRenderedPreview({
|
|
148
|
+
src,
|
|
149
|
+
}: {
|
|
150
|
+
src: string;
|
|
151
|
+
}) {
|
|
152
|
+
return (
|
|
153
|
+
<iframe
|
|
154
|
+
allowFullScreen
|
|
155
|
+
className="h-full w-full border-0 bg-white"
|
|
156
|
+
data-testid="workspace-html-preview"
|
|
157
|
+
src={src}
|
|
158
|
+
title={t("chatWorkspaceHtmlPreviewTitle")}
|
|
159
|
+
/>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
133
163
|
function WorkspacePreviewBody({
|
|
134
164
|
onFileOpen,
|
|
135
165
|
previewBlock,
|
|
136
166
|
previewKind,
|
|
137
167
|
previewQuery,
|
|
138
168
|
previewText,
|
|
169
|
+
previewUrl,
|
|
170
|
+
previewViewer,
|
|
139
171
|
}: {
|
|
140
172
|
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
141
173
|
previewBlock: ChatFileOperationBlockViewModel | null;
|
|
142
174
|
previewKind: "text" | "markdown" | "binary";
|
|
143
175
|
previewQuery: ReturnType<typeof useServerPathRead>;
|
|
144
176
|
previewText: string | null;
|
|
177
|
+
previewUrl: string | null;
|
|
178
|
+
previewViewer: "source" | "rendered";
|
|
145
179
|
}) {
|
|
146
180
|
if (previewQuery.isLoading && !previewBlock) {
|
|
147
181
|
return <WorkspaceFilePreviewStatus text={t("chatWorkspaceLoadingFile")} />;
|
|
@@ -166,6 +200,10 @@ function WorkspacePreviewBody({
|
|
|
166
200
|
);
|
|
167
201
|
}
|
|
168
202
|
|
|
203
|
+
if (previewViewer === "rendered" && previewUrl) {
|
|
204
|
+
return <WorkspaceHtmlRenderedPreview src={previewUrl} />;
|
|
205
|
+
}
|
|
206
|
+
|
|
169
207
|
if (previewKind === "markdown" && previewText) {
|
|
170
208
|
return (
|
|
171
209
|
<div className="h-full overflow-auto custom-scrollbar px-5 py-4">
|
|
@@ -218,6 +256,15 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
218
256
|
path: previewQuery.data?.resolvedPath ?? file.path,
|
|
219
257
|
serverKind: previewQuery.data?.kind,
|
|
220
258
|
});
|
|
259
|
+
const resolvedPath = previewQuery.data?.resolvedPath ?? file.path;
|
|
260
|
+
const previewViewer = resolveFilePreviewViewer({
|
|
261
|
+
path: resolvedPath,
|
|
262
|
+
viewer: file.previewViewer,
|
|
263
|
+
});
|
|
264
|
+
const previewUrl =
|
|
265
|
+
previewViewer === "rendered" && previewQuery.data?.resolvedPath
|
|
266
|
+
? buildServerPathContentUrl(previewQuery.data.resolvedPath)
|
|
267
|
+
: null;
|
|
221
268
|
const previewBlock = useMemo(() => {
|
|
222
269
|
if (!isPreviewMode || !previewText) {
|
|
223
270
|
return null;
|
|
@@ -225,10 +272,17 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
225
272
|
return buildPreviewBlock({
|
|
226
273
|
path: previewQuery.data?.resolvedPath ?? file.path,
|
|
227
274
|
text: previewText,
|
|
275
|
+
languageHint: previewQuery.data?.languageHint ?? null,
|
|
228
276
|
line: file.line,
|
|
229
277
|
});
|
|
230
|
-
}, [
|
|
231
|
-
|
|
278
|
+
}, [
|
|
279
|
+
file.line,
|
|
280
|
+
file.path,
|
|
281
|
+
isPreviewMode,
|
|
282
|
+
previewQuery.data?.languageHint,
|
|
283
|
+
previewQuery.data?.resolvedPath,
|
|
284
|
+
previewText,
|
|
285
|
+
]);
|
|
232
286
|
const isTruncated = Boolean(previewQuery.data?.truncated);
|
|
233
287
|
const breadcrumbBasePath = sessionProjectRoot ?? sessionWorkingDir;
|
|
234
288
|
const breadcrumb = useMemo(
|
|
@@ -245,7 +299,10 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
245
299
|
|
|
246
300
|
return (
|
|
247
301
|
<div className="flex h-full min-h-0 flex-col bg-white">
|
|
248
|
-
<ChatSessionWorkspaceFileBreadcrumbs
|
|
302
|
+
<ChatSessionWorkspaceFileBreadcrumbs
|
|
303
|
+
breadcrumb={breadcrumb}
|
|
304
|
+
onFileOpen={onFileOpen}
|
|
305
|
+
/>
|
|
249
306
|
|
|
250
307
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
251
308
|
{file.viewMode === "diff" ? (
|
|
@@ -257,6 +314,8 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
257
314
|
previewKind={previewKind}
|
|
258
315
|
previewQuery={previewQuery}
|
|
259
316
|
previewText={previewText}
|
|
317
|
+
previewUrl={previewUrl}
|
|
318
|
+
previewViewer={previewViewer}
|
|
260
319
|
/>
|
|
261
320
|
)}
|
|
262
321
|
</div>
|
|
@@ -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
|
});
|
package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ChatFilePreviewViewer } from '@nextclaw/agent-chat-ui';
|
|
2
|
+
import type { ChatWorkspaceFileTab } from '@/features/chat/stores/chat-thread.store';
|
|
3
|
+
|
|
4
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
5
|
+
return !!value && typeof value === 'object';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function normalizeOptionalString(value: unknown): string | null {
|
|
9
|
+
return typeof value === 'string' && value.trim().length > 0
|
|
10
|
+
? value.trim()
|
|
11
|
+
: null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeOptionalNumber(value: unknown): number | null {
|
|
15
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
16
|
+
? value
|
|
17
|
+
: null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeOptionalText(value: unknown): string | null {
|
|
21
|
+
return typeof value === 'string' ? value : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeFilePreviewViewer(value: unknown): ChatFilePreviewViewer | null {
|
|
25
|
+
return value === 'auto' || value === 'source' || value === 'rendered'
|
|
26
|
+
? value
|
|
27
|
+
: null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizePersistedWorkspaceFileTab(
|
|
31
|
+
value: unknown,
|
|
32
|
+
): ChatWorkspaceFileTab | null {
|
|
33
|
+
if (
|
|
34
|
+
!isRecord(value)
|
|
35
|
+
|| typeof value.key !== 'string'
|
|
36
|
+
|| typeof value.path !== 'string'
|
|
37
|
+
|| (value.viewMode !== 'preview' && value.viewMode !== 'diff')
|
|
38
|
+
) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const key = value.key.trim();
|
|
42
|
+
const path = value.path.trim();
|
|
43
|
+
if (!key || !path) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
key,
|
|
48
|
+
parentSessionKey: normalizeOptionalString(value.parentSessionKey),
|
|
49
|
+
path,
|
|
50
|
+
label: normalizeOptionalString(value.label),
|
|
51
|
+
viewMode: value.viewMode,
|
|
52
|
+
previewViewer: normalizeFilePreviewViewer(value.previewViewer),
|
|
53
|
+
line: normalizeOptionalNumber(value.line),
|
|
54
|
+
column: normalizeOptionalNumber(value.column),
|
|
55
|
+
rawText: normalizeOptionalText(value.rawText),
|
|
56
|
+
beforeText: normalizeOptionalText(value.beforeText),
|
|
57
|
+
afterText: normalizeOptionalText(value.afterText),
|
|
58
|
+
patchText: normalizeOptionalText(value.patchText),
|
|
59
|
+
oldStartLine: normalizeOptionalNumber(value.oldStartLine),
|
|
60
|
+
newStartLine: normalizeOptionalNumber(value.newStartLine),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function toPersistedWorkspaceFileTab(
|
|
65
|
+
tab: ChatWorkspaceFileTab,
|
|
66
|
+
): ChatWorkspaceFileTab {
|
|
67
|
+
return {
|
|
68
|
+
key: tab.key,
|
|
69
|
+
parentSessionKey: tab.parentSessionKey,
|
|
70
|
+
path: tab.path,
|
|
71
|
+
label: tab.label,
|
|
72
|
+
viewMode: tab.viewMode,
|
|
73
|
+
previewViewer: tab.previewViewer,
|
|
74
|
+
line: tab.line,
|
|
75
|
+
column: tab.column,
|
|
76
|
+
rawText: tab.rawText,
|
|
77
|
+
beforeText: tab.beforeText,
|
|
78
|
+
afterText: tab.afterText,
|
|
79
|
+
patchText: tab.patchText,
|
|
80
|
+
oldStartLine: tab.oldStartLine,
|
|
81
|
+
newStartLine: tab.newStartLine,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -155,6 +155,40 @@ describe('ChatThreadManager', () => {
|
|
|
155
155
|
expect(uiManager.goToSession).not.toHaveBeenCalled();
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
it('notifies the app layout coordinator after opening the workspace panel', () => {
|
|
159
|
+
const onWorkspacePanelOpened = vi.fn();
|
|
160
|
+
const manager = new ChatThreadManager(
|
|
161
|
+
createUiManager(),
|
|
162
|
+
{} as ConstructorParameters<typeof ChatThreadManager>[1],
|
|
163
|
+
onWorkspacePanelOpened,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
manager.openFilePreview({
|
|
167
|
+
path: 'README.md',
|
|
168
|
+
label: 'README.md',
|
|
169
|
+
viewMode: 'preview',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(onWorkspacePanelOpened).toHaveBeenCalledTimes(1);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('does not notify the app layout coordinator when no workspace panel opens', () => {
|
|
176
|
+
const onWorkspacePanelOpened = vi.fn();
|
|
177
|
+
const manager = new ChatThreadManager(
|
|
178
|
+
createUiManager(),
|
|
179
|
+
{} as ConstructorParameters<typeof ChatThreadManager>[1],
|
|
180
|
+
onWorkspacePanelOpened,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
manager.openFilePreview({
|
|
184
|
+
path: ' ',
|
|
185
|
+
label: 'README.md',
|
|
186
|
+
viewMode: 'preview',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(onWorkspacePanelOpened).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
|
|
158
192
|
it('opens a side chat draft beside the selected parent session', () => {
|
|
159
193
|
const uiManager = createUiManager({
|
|
160
194
|
goToSession: vi.fn(),
|
|
@@ -293,6 +327,94 @@ describe('ChatThreadManager', () => {
|
|
|
293
327
|
]),
|
|
294
328
|
);
|
|
295
329
|
});
|
|
330
|
+
|
|
331
|
+
});
|
|
332
|
+
|
|
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
|
+
|
|
381
|
+
it('keeps source and rendered previews for the same file as separate workspace tabs', () => {
|
|
382
|
+
const manager = new ChatThreadManager(
|
|
383
|
+
createUiManager(),
|
|
384
|
+
{} as ConstructorParameters<typeof ChatThreadManager>[1],
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
manager.openFilePreview({
|
|
388
|
+
path: 'demo.html',
|
|
389
|
+
label: 'demo.html',
|
|
390
|
+
viewMode: 'preview',
|
|
391
|
+
previewViewer: 'source',
|
|
392
|
+
});
|
|
393
|
+
manager.openFilePreview({
|
|
394
|
+
path: 'demo.html',
|
|
395
|
+
label: 'demo.html',
|
|
396
|
+
viewMode: 'preview',
|
|
397
|
+
previewViewer: 'rendered',
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
401
|
+
activeWorkspaceFileKey: 'parent-session-1::preview:rendered::demo.html',
|
|
402
|
+
workspaceFileTabs: [
|
|
403
|
+
{
|
|
404
|
+
key: 'parent-session-1::preview:rendered::demo.html',
|
|
405
|
+
path: 'demo.html',
|
|
406
|
+
viewMode: 'preview',
|
|
407
|
+
previewViewer: 'rendered',
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
key: 'parent-session-1::preview::demo.html',
|
|
411
|
+
path: 'demo.html',
|
|
412
|
+
viewMode: 'preview',
|
|
413
|
+
previewViewer: 'source',
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
});
|
|
417
|
+
});
|
|
296
418
|
});
|
|
297
419
|
|
|
298
420
|
describe('ChatThreadManager workspace navigation', () => {
|
|
@@ -513,6 +635,7 @@ describe('ChatThreadManager showContent', () => {
|
|
|
513
635
|
payload: {
|
|
514
636
|
path: 'docs/example.md',
|
|
515
637
|
line: 5,
|
|
638
|
+
viewer: 'rendered',
|
|
516
639
|
},
|
|
517
640
|
},
|
|
518
641
|
title: 'Example',
|
|
@@ -522,12 +645,14 @@ describe('ChatThreadManager showContent', () => {
|
|
|
522
645
|
|
|
523
646
|
expect(useChatThreadStore.getState().snapshot).toMatchObject({
|
|
524
647
|
activeWorkspacePanelKind: 'file',
|
|
525
|
-
activeWorkspaceFileKey: 'parent-session-1::preview::docs/example.md',
|
|
648
|
+
activeWorkspaceFileKey: 'parent-session-1::preview:rendered::docs/example.md',
|
|
526
649
|
workspaceFileTabs: [
|
|
527
650
|
expect.objectContaining({
|
|
651
|
+
key: 'parent-session-1::preview:rendered::docs/example.md',
|
|
528
652
|
path: 'docs/example.md',
|
|
529
653
|
label: 'Example',
|
|
530
654
|
line: 5,
|
|
655
|
+
previewViewer: 'rendered',
|
|
531
656
|
viewMode: 'preview',
|
|
532
657
|
}),
|
|
533
658
|
],
|
|
@@ -44,6 +44,7 @@ export class ChatThreadManager {
|
|
|
44
44
|
constructor(
|
|
45
45
|
private uiManager: ChatUiManager,
|
|
46
46
|
private sessionListManager: ChatSessionListManager,
|
|
47
|
+
private readonly onWorkspacePanelOpened?: () => void,
|
|
47
48
|
) {}
|
|
48
49
|
|
|
49
50
|
private hasSnapshotChanges = (patch: Partial<ChatThreadSnapshot>): boolean => {
|
|
@@ -110,14 +111,20 @@ export class ChatThreadManager {
|
|
|
110
111
|
return null;
|
|
111
112
|
}
|
|
112
113
|
const normalizedParentSessionKey = parentSessionKey?.trim() || null;
|
|
113
|
-
const
|
|
114
|
-
|
|
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
|
-
key
|
|
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,
|
|
127
|
+
previewViewer,
|
|
121
128
|
line: action.line ?? null,
|
|
122
129
|
column: action.column ?? null,
|
|
123
130
|
rawText: action.rawText ?? null,
|
|
@@ -265,6 +272,7 @@ export class ChatThreadManager {
|
|
|
265
272
|
useChatThreadStore.getState().setSnapshot(patch);
|
|
266
273
|
}
|
|
267
274
|
this.ensureWorkspaceParentRoute(parentSessionKey);
|
|
275
|
+
this.onWorkspacePanelOpened?.();
|
|
268
276
|
};
|
|
269
277
|
|
|
270
278
|
openSideChatDraft = (parentSessionKey?: string | null) => {
|
|
@@ -285,6 +293,7 @@ export class ChatThreadManager {
|
|
|
285
293
|
key: activeSideChatDraft.draftKey,
|
|
286
294
|
});
|
|
287
295
|
this.ensureWorkspaceParentRoute(resolvedParentSessionKey);
|
|
296
|
+
this.onWorkspacePanelOpened?.();
|
|
288
297
|
};
|
|
289
298
|
|
|
290
299
|
materializeSideChatDraft = (params: {
|
|
@@ -322,6 +331,7 @@ export class ChatThreadManager {
|
|
|
322
331
|
key: nextTab.key,
|
|
323
332
|
});
|
|
324
333
|
this.ensureWorkspaceParentRoute(parentSessionKey);
|
|
334
|
+
this.onWorkspacePanelOpened?.();
|
|
325
335
|
};
|
|
326
336
|
|
|
327
337
|
private openSessionFromToolAction = (action: ChatToolActionViewModel) => {
|
|
@@ -384,6 +394,7 @@ export class ChatThreadManager {
|
|
|
384
394
|
path: request.target.payload.path,
|
|
385
395
|
label: request.title,
|
|
386
396
|
viewMode: 'preview',
|
|
397
|
+
previewViewer: request.target.payload.viewer,
|
|
387
398
|
line: request.target.payload.line,
|
|
388
399
|
column: request.target.payload.column,
|
|
389
400
|
});
|
|
@@ -504,6 +515,7 @@ export class ChatThreadManager {
|
|
|
504
515
|
kind: 'cron',
|
|
505
516
|
});
|
|
506
517
|
this.ensureWorkspaceParentRoute(parentSessionKey);
|
|
518
|
+
this.onWorkspacePanelOpened?.();
|
|
507
519
|
};
|
|
508
520
|
|
|
509
521
|
goBackWorkspacePanel = () => {
|
|
@@ -89,7 +89,11 @@ export class ChatUiManager {
|
|
|
89
89
|
showContent = async (params: { target: ChatUiDisplayContentTarget; title?: string }) => {
|
|
90
90
|
const { target, title } = params;
|
|
91
91
|
if (target.type === 'url') {
|
|
92
|
-
this.docBrowserManager.open(target.payload.url, {
|
|
92
|
+
this.docBrowserManager.open(target.payload.url, {
|
|
93
|
+
dedupeKey: `browser:${target.payload.url}`,
|
|
94
|
+
kind: 'content',
|
|
95
|
+
title,
|
|
96
|
+
});
|
|
93
97
|
return;
|
|
94
98
|
}
|
|
95
99
|
const { appId } = target.payload;
|
|
@@ -16,7 +16,8 @@ export class ChatPresenter {
|
|
|
16
16
|
this.chatQueryManager = new ChatQueryManager();
|
|
17
17
|
this.chatThreadManager = new ChatThreadManager(
|
|
18
18
|
this.chatUiManager,
|
|
19
|
-
this.chatSessionListManager
|
|
19
|
+
this.chatSessionListManager,
|
|
20
|
+
appPresenter.notifyRightPanelOpened,
|
|
20
21
|
);
|
|
21
22
|
}
|
|
22
23
|
}
|