@nextclaw/ui 0.12.8 → 0.12.10
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 +96 -0
- package/dist/assets/ChannelsList-M9FTK1Ak.js +8 -0
- package/dist/assets/DocBrowser-CH7-GxlL.js +1 -0
- package/dist/assets/{DocBrowser-BMxf9CIK.js → DocBrowser-DMfr0Oow.js} +1 -1
- package/dist/assets/{DocBrowserContext-Ce28gRXt.js → DocBrowserContext-BXydqby-.js} +1 -1
- package/dist/assets/{LogoBadge-o92MOA2L.js → LogoBadge-hO7tY7hE.js} +1 -1
- package/dist/assets/ModelConfig-CNIgLf0e.js +1 -0
- package/dist/assets/{ProviderScopedModelInput-CmTIzgI7.js → ProviderScopedModelInput-B3HWP4oz.js} +1 -1
- package/dist/assets/ProvidersList-CHjMnRhX.js +1 -0
- package/dist/assets/RuntimeConfig-psp8nMSG.js +1 -0
- package/dist/assets/SearchConfig-CSoKip1f.js +1 -0
- package/dist/assets/{SecretsConfig-Ba1RPJaG.js → SecretsConfig-MEt6MjuD.js} +2 -2
- package/dist/assets/SessionsConfig-DifCiXwR.js +2 -0
- package/dist/assets/{app-query-client-DniXoIN5.js → app-query-client-9jNewezV.js} +1 -1
- package/dist/assets/{book-open-DocgeQtR.js → book-open-DzdUViDm.js} +1 -1
- package/dist/assets/chat-page-CLp0UV0Y.js +58 -0
- package/dist/assets/chat-session-display-DsYHx0RZ.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-BvKvh1R8.js → chunk-JZWAC4HX-C5dEc8hV.js} +1 -1
- package/dist/assets/{client-CVqPF5ie.js → client-C-8fH7-c.js} +1 -1
- package/dist/assets/{config-Bop2oB18.js → config-CBScxsdV.js} +1 -1
- package/dist/assets/config-split-page-BUout_Ak.js +1 -0
- package/dist/assets/{createLucideIcon-DVv8taGY.js → createLucideIcon-dy5ie7Ox.js} +1 -1
- package/dist/assets/desktop-update-config-2BS6BMkW.js +1 -0
- package/dist/assets/{dist-DmAlInRu.js → dist-BruyLa92.js} +1 -1
- package/dist/assets/{dist-Da5Gm_pO.js → dist-Cy7_j6hA.js} +1 -1
- package/dist/assets/download-BD0ETkB-.js +1 -0
- package/dist/assets/{external-link-DFjw3x1B.js → external-link-kZSAO8nT.js} +1 -1
- package/dist/assets/{hash-DJtaCejM.js → hash-BHJC2Ovu.js} +1 -1
- package/dist/assets/i18n-CpTZLchQ.js +1 -0
- package/dist/assets/index-mW8W2FUu.css +1 -0
- package/dist/assets/index-zDZfXoI4.js +6 -0
- package/dist/assets/{infiniteQueryBehavior-DHSEQ3OH.js → infiniteQueryBehavior-CyER9hv0.js} +1 -1
- package/dist/assets/loader-circle-Bc2gCU33.js +1 -0
- package/dist/assets/{logos-DEFUIR12.js → logos-B7gRObP8.js} +1 -1
- package/dist/assets/marketplace-page-3qVMnF3d.js +1 -0
- package/dist/assets/marketplace-page-BhFIeQzI.js +49 -0
- package/dist/assets/mcp-marketplace-page-DYfteJ1D.js +40 -0
- package/dist/assets/{page-layout-Da3i3r6G.js → page-layout-0UcO9H9Z.js} +1 -1
- package/dist/assets/play-CKDjSQFL.js +1 -0
- package/dist/assets/plus-CG0QrVY_.js +1 -0
- package/dist/assets/{refresh-ccw-D6HkNtfz.js → refresh-ccw-COVhNHtN.js} +1 -1
- package/dist/assets/{refresh-cw-DRcvRrnc.js → refresh-cw-Bcv40SXy.js} +1 -1
- package/dist/assets/remote-access-page-CWHG-sug.js +1 -0
- package/dist/assets/{rotate-cw-BmDKfXtH.js → rotate-cw-oHMKJMC8.js} +1 -1
- package/dist/assets/{save-DHGmi2e9.js → save-EqJPOF0G.js} +1 -1
- package/dist/assets/search-BCAlB8nz.js +1 -0
- package/dist/assets/security-config-Slh0Mayz.js +1 -0
- package/dist/assets/select-CVz0t7MF.js +41 -0
- package/dist/assets/setting-row-CbVHAuQt.js +1 -0
- package/dist/assets/skeleton-D5rdKvzy.js +1 -0
- package/dist/assets/{status-dot-DurKKSwA.js → status-dot-DpPtVzQT.js} +1 -1
- package/dist/assets/{switch-0rmPBRKI.js → switch-CM29eCAR.js} +1 -1
- package/dist/assets/{tabs-custom-5JLVL6v8.js → tabs-custom-YcZUWn3o.js} +1 -1
- package/dist/assets/tag-chip-DMXdnLcj.js +1 -0
- package/dist/assets/{trash-2-C6caKPoz.js → trash-2-mJT6oWa2.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-dwnaa_qi.js → use-infinite-scroll-loader-DJ1L81Dz.js} +1 -1
- package/dist/assets/{useConfirmDialog-mMeWD_yo.js → useConfirmDialog-BsVuqu1x.js} +1 -1
- package/dist/assets/{useMutation-BmxxvCNf.js → useMutation-CNcz2fgt.js} +1 -1
- package/dist/assets/x-Czwxm82I.js +1 -0
- package/dist/index.html +95 -21
- package/dist/manifest.webmanifest +30 -0
- package/dist/offline.html +102 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/runtime-icons/claude.ico +0 -0
- package/dist/runtime-icons/codex-openai.svg +6 -0
- package/dist/runtime-icons/hermes-agent.png +0 -0
- package/dist/sw.js +80 -0
- package/index.html +73 -1
- package/package.json +5 -5
- package/public/manifest.webmanifest +30 -0
- package/public/offline.html +102 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/runtime-icons/claude.ico +0 -0
- package/public/runtime-icons/codex-openai.svg +6 -0
- package/public/runtime-icons/hermes-agent.png +0 -0
- package/public/sw.js +80 -0
- package/src/account/components/account-panel.tsx +217 -97
- package/src/account/managers/account.manager.ts +3 -2
- package/src/api/chat-session-type.types.ts +7 -0
- package/src/api/runtime-control.types.ts +8 -0
- package/src/api/server-path.ts +27 -4
- package/src/api/types.ts +25 -10
- package/src/app.tsx +227 -54
- package/src/components/agents/agent-dialogs.tsx +499 -0
- package/src/components/agents/agents-page.test.tsx +238 -0
- package/src/components/agents/agents-page.tsx +435 -0
- package/src/components/chat/ChatSidebar.test.tsx +43 -1
- package/src/components/chat/ChatSidebar.tsx +35 -35
- package/src/components/chat/adapters/chat-message.summary-truncation.test.ts +66 -0
- package/src/components/chat/adapters/file-operation/card.ts +9 -0
- package/src/components/chat/adapters/file-operation/diff.ts +14 -0
- package/src/components/chat/{ChatConversationPanel.test.tsx → chat-conversation-panel.test.tsx} +127 -206
- package/src/components/chat/chat-conversation-panel.tsx +482 -0
- package/src/components/chat/chat-page-shell.tsx +19 -13
- package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
- package/src/components/chat/chat-session-type-option-item.tsx +68 -0
- package/src/components/chat/chat-session-workspace-file-preview.test.tsx +178 -0
- package/src/components/chat/chat-session-workspace-file-preview.tsx +278 -0
- package/src/components/chat/chat-session-workspace-panel-nav.tsx +203 -0
- package/src/components/chat/chat-session-workspace-panel.tsx +318 -0
- package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
- package/src/components/chat/chat-sidebar-session-item.tsx +32 -2
- package/src/components/chat/containers/chat-message-list.container.test.tsx +49 -0
- package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
- package/src/components/chat/managers/chat-session-list.manager.test.ts +12 -0
- package/src/components/chat/managers/chat-session-list.manager.ts +7 -0
- package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
- package/src/components/chat/ncp/ncp-chat-page.tsx +9 -7
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +179 -41
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +36 -1
- package/src/components/chat/ncp/ncp-session-adapter.ts +20 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +62 -13
- package/src/components/chat/ncp/tests/ncp-chat-thread.manager.test.ts +189 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +13 -2
- package/src/components/chat/session-header/chat-session-header-actions.test.tsx +26 -0
- package/src/components/chat/session-header/chat-session-header-actions.tsx +19 -1
- package/src/components/chat/stores/chat-input.store.ts +2 -1
- package/src/components/chat/stores/chat-thread.store.ts +27 -1
- package/src/components/chat/useChatSessionTypeState.ts +10 -1
- package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
- package/src/components/common/BrandHeader.tsx +3 -1
- package/src/components/common/session-context-icon.tsx +15 -2
- package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
- package/src/components/config/ChannelForm.test.tsx +89 -3
- package/src/components/config/ChannelForm.tsx +157 -188
- package/src/components/config/ChannelsList.test.tsx +163 -119
- package/src/components/config/ChannelsList.tsx +90 -101
- package/src/components/config/ProviderForm.tsx +108 -146
- package/src/components/config/ProvidersList.tsx +100 -123
- package/src/components/config/RuntimeConfig.tsx +141 -2
- package/src/components/config/SearchConfig.tsx +423 -393
- package/src/components/config/channel-form-fields-section.tsx +70 -37
- package/src/components/config/config-split-page.tsx +109 -0
- package/src/components/config/provider-enabled-field.tsx +17 -10
- package/src/components/config/runtime-control-card.test.tsx +56 -0
- package/src/components/config/runtime-control-card.tsx +25 -0
- package/src/components/config/runtime-presence-card.tsx +93 -79
- package/src/components/layout/AppLayout.tsx +25 -37
- package/src/components/layout/app-layout.test.tsx +46 -14
- package/src/components/layout/runtime-status-entry.test.tsx +157 -0
- package/src/components/layout/runtime-status-entry.tsx +143 -0
- package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
- package/src/components/marketplace/marketplace-list-card.tsx +288 -0
- package/src/components/marketplace/marketplace-page-data.ts +129 -0
- package/src/components/marketplace/marketplace-page.test.tsx +339 -0
- package/src/components/marketplace/marketplace-page.tsx +596 -0
- package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
- package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
- package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
- package/src/components/providers/ThemeProvider.tsx +5 -0
- package/src/components/remote/remote-access-page.test.tsx +105 -0
- package/src/components/remote/remote-access-page.tsx +248 -0
- package/src/components/ui/notice-card.tsx +129 -0
- package/src/components/ui/setting-row.tsx +51 -0
- package/src/components/ui/tag-chip.tsx +39 -0
- package/src/components/ui/textarea.tsx +19 -0
- package/src/hooks/server-path/use-server-path-read.ts +20 -0
- package/src/hooks/useConfig.ts +2 -1
- package/src/index.css +24 -0
- package/src/lib/app-resource-uri.test.ts +20 -0
- package/src/lib/app-resource-uri.ts +29 -0
- package/src/lib/chat-message.ts +14 -3
- package/src/lib/i18n.chat.ts +12 -1
- package/src/lib/i18n.pwa.ts +62 -0
- package/src/lib/i18n.remote.ts +1 -1
- package/src/lib/i18n.runtime-control.ts +31 -0
- package/src/lib/i18n.ts +7 -10
- package/src/lib/session-context.utils.test.ts +71 -0
- package/src/lib/session-context.utils.ts +28 -3
- package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
- package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
- package/src/pwa/components/pwa-install-entry.test.tsx +110 -0
- package/src/pwa/components/pwa-install-entry.tsx +205 -0
- package/src/pwa/managers/pwa-install.manager.test.ts +160 -0
- package/src/pwa/managers/pwa-install.manager.ts +232 -0
- package/src/pwa/managers/pwa-runtime.manager.ts +196 -0
- package/src/pwa/managers/pwa-shell-theme.manager.test.ts +30 -0
- package/src/pwa/managers/pwa-shell-theme.manager.ts +46 -0
- package/src/pwa/pwa-install-banner.storage.ts +55 -0
- package/src/pwa/pwa.types.ts +22 -0
- package/src/pwa/register-pwa.ts +14 -0
- package/src/pwa/stores/pwa.store.ts +17 -0
- package/src/vite-env.d.ts +9 -0
- package/dist/assets/ChannelsList-KIQIxluX.js +0 -8
- package/dist/assets/DocBrowser-CyDgAtO9.js +0 -1
- package/dist/assets/MarketplacePage-BySqkYDh.js +0 -49
- package/dist/assets/MarketplacePage-C0olZaek.js +0 -1
- package/dist/assets/McpMarketplacePage-DqKaiXO9.js +0 -40
- package/dist/assets/ModelConfig-IrmzoslW.js +0 -1
- package/dist/assets/ProvidersList-8_Kalfwl.js +0 -1
- package/dist/assets/RemoteAccessPage-CyQlSjPf.js +0 -1
- package/dist/assets/RuntimeConfig-Bk0uYBhf.js +0 -1
- package/dist/assets/SearchConfig-DNBR-UbE.js +0 -1
- package/dist/assets/SessionsConfig-Doqp5ghH.js +0 -2
- package/dist/assets/chat-page-Bph8M5zo.js +0 -58
- package/dist/assets/chat-session-display-CoN3Wmn-.js +0 -1
- package/dist/assets/config-layout-DmlGaay2.js +0 -1
- package/dist/assets/desktop-update-config-1KBrqLBC.js +0 -1
- package/dist/assets/i18n-CwHZ-9vt.js +0 -1
- package/dist/assets/index-DafCdM4F.css +0 -1
- package/dist/assets/index-DdksE6U3.js +0 -6
- package/dist/assets/loader-circle-PsSP0H9n.js +0 -1
- package/dist/assets/play-DBQbBxTA.js +0 -1
- package/dist/assets/plus-DUOVbsyQ.js +0 -1
- package/dist/assets/popover-C_mWOFzI.js +0 -1
- package/dist/assets/search-MChQRYR1.js +0 -1
- package/dist/assets/security-config-CbXfPZzr.js +0 -1
- package/dist/assets/select-Caud8QvU.js +0 -41
- package/dist/assets/skeleton-B-4vRq_Z.js +0 -1
- package/dist/assets/x-DuMhMATD.js +0 -1
- package/src/components/agents/AgentDialogs.tsx +0 -400
- package/src/components/agents/AgentsPage.test.tsx +0 -217
- package/src/components/agents/AgentsPage.tsx +0 -352
- package/src/components/chat/ChatConversationPanel.tsx +0 -256
- package/src/components/chat/chat-child-session-panel.tsx +0 -270
- package/src/components/config/config-layout.ts +0 -10
- package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
- package/src/components/marketplace/MarketplacePage.tsx +0 -827
- package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
- package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
- package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
- package/src/components/remote/RemoteAccessPage.tsx +0 -144
- /package/dist/assets/{config-hints-BZoDjXye.js → config-hints-BhTmc9P1.js} +0 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { ArrowLeft, FolderGit2, Loader2, X } from "lucide-react";
|
|
3
|
+
import type {
|
|
4
|
+
ChatFileOpenActionViewModel,
|
|
5
|
+
ChatToolActionViewModel,
|
|
6
|
+
} from "@nextclaw/agent-chat-ui";
|
|
7
|
+
import { useStickyBottomScroll } from "@nextclaw/agent-chat-ui";
|
|
8
|
+
import { ChatMessageListContainer } from "@/components/chat/containers/chat-message-list.container";
|
|
9
|
+
import {
|
|
10
|
+
useNcpChildSessionTabsView,
|
|
11
|
+
type ResolvedChildSessionTab,
|
|
12
|
+
} from "@/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view";
|
|
13
|
+
import { useNcpSessionConversation } from "@/components/chat/ncp/session-conversation/use-ncp-session-conversation";
|
|
14
|
+
import {
|
|
15
|
+
shouldShowUnreadSessionIndicator,
|
|
16
|
+
useChatSessionListStore,
|
|
17
|
+
} from "@/components/chat/stores/chat-session-list.store";
|
|
18
|
+
import type {
|
|
19
|
+
ChatChildSessionTab,
|
|
20
|
+
ChatWorkspaceFileTab,
|
|
21
|
+
} from "@/components/chat/stores/chat-thread.store";
|
|
22
|
+
import {
|
|
23
|
+
readWorkspaceFileTitle,
|
|
24
|
+
resolveWorkspaceSelection,
|
|
25
|
+
type WorkspaceTabViewModel,
|
|
26
|
+
WorkspaceTabsBar,
|
|
27
|
+
} from "@/components/chat/chat-session-workspace-panel-nav";
|
|
28
|
+
import { usePresenter } from "@/components/chat/presenter/chat-presenter-context";
|
|
29
|
+
import { ChatSessionWorkspaceFilePreview } from "@/components/chat/chat-session-workspace-file-preview";
|
|
30
|
+
import { AgentIdentityAvatar } from "@/components/common/agent-identity";
|
|
31
|
+
import { t } from "@/lib/i18n";
|
|
32
|
+
import { cn } from "@/lib/utils";
|
|
33
|
+
|
|
34
|
+
type ChatSessionWorkspacePanelProps = {
|
|
35
|
+
childSessionTabs: readonly ChatChildSessionTab[];
|
|
36
|
+
activeChildSessionKey: string | null;
|
|
37
|
+
workspaceFileTabs: readonly ChatWorkspaceFileTab[];
|
|
38
|
+
activeWorkspaceFileKey: string | null;
|
|
39
|
+
sessionProjectRoot: string | null;
|
|
40
|
+
onSelectSession: (sessionKey: string) => void;
|
|
41
|
+
onSelectFile: (fileKey: string) => void;
|
|
42
|
+
onCloseFile: (fileKey: string) => void;
|
|
43
|
+
onClose: () => void;
|
|
44
|
+
onBackToParent: () => void;
|
|
45
|
+
onToolAction?: (action: ChatToolActionViewModel) => void;
|
|
46
|
+
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function ChildSessionContent({
|
|
50
|
+
sessionKey,
|
|
51
|
+
onToolAction,
|
|
52
|
+
onFileOpen,
|
|
53
|
+
}: {
|
|
54
|
+
sessionKey: string;
|
|
55
|
+
onToolAction?: (action: ChatToolActionViewModel) => void;
|
|
56
|
+
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
57
|
+
}) {
|
|
58
|
+
const agent = useNcpSessionConversation(sessionKey);
|
|
59
|
+
const messages = agent.visibleMessages;
|
|
60
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
61
|
+
const { onScroll } = useStickyBottomScroll({
|
|
62
|
+
scrollRef,
|
|
63
|
+
resetKey: sessionKey,
|
|
64
|
+
isLoading: agent.isHydrating,
|
|
65
|
+
hasContent: messages.length > 0,
|
|
66
|
+
contentVersion: messages[messages.length - 1] ?? null,
|
|
67
|
+
stickyThresholdPx: 20,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
ref={scrollRef}
|
|
73
|
+
onScroll={onScroll}
|
|
74
|
+
className="h-full overflow-y-auto custom-scrollbar"
|
|
75
|
+
>
|
|
76
|
+
{agent.isHydrating ? (
|
|
77
|
+
<div className="flex h-full items-center justify-center text-sm text-gray-500">
|
|
78
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
79
|
+
{t("chatChildSessionLoading")}
|
|
80
|
+
</div>
|
|
81
|
+
) : agent.hydrateError ? (
|
|
82
|
+
<div className="px-4 py-5 text-sm text-rose-600">
|
|
83
|
+
{agent.hydrateError.message}
|
|
84
|
+
</div>
|
|
85
|
+
) : messages.length === 0 && !agent.isRunning ? (
|
|
86
|
+
<div className="px-4 py-5 text-sm text-gray-500">
|
|
87
|
+
{t("chatChildSessionEmpty")}
|
|
88
|
+
</div>
|
|
89
|
+
) : (
|
|
90
|
+
<div className="px-4 py-5">
|
|
91
|
+
<ChatMessageListContainer
|
|
92
|
+
messages={messages}
|
|
93
|
+
isSending={agent.isRunning}
|
|
94
|
+
onToolAction={onToolAction}
|
|
95
|
+
onFileOpen={onFileOpen}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function ChildSessionMetaChip({ value }: { value: string }) {
|
|
104
|
+
return (
|
|
105
|
+
<span className="inline-flex max-w-full items-center rounded border border-gray-200 bg-gray-50 px-2 py-0.5 text-[10px] font-medium text-gray-600">
|
|
106
|
+
<span className="truncate">{value}</span>
|
|
107
|
+
</span>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function ChildSessionMetaStrip({ tab }: { tab: ResolvedChildSessionTab }) {
|
|
112
|
+
const metaItems = [
|
|
113
|
+
tab.sessionTypeLabel,
|
|
114
|
+
tab.preferredModel,
|
|
115
|
+
tab.projectName,
|
|
116
|
+
].filter((value): value is string => Boolean(value?.trim()));
|
|
117
|
+
|
|
118
|
+
if (metaItems.length === 0 && !tab.projectRoot) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div className="mt-3 space-y-2">
|
|
124
|
+
{metaItems.length > 0 ? (
|
|
125
|
+
<div className="flex flex-wrap gap-1.5">
|
|
126
|
+
{metaItems.map((item) => (
|
|
127
|
+
<ChildSessionMetaChip key={item} value={item} />
|
|
128
|
+
))}
|
|
129
|
+
</div>
|
|
130
|
+
) : null}
|
|
131
|
+
{tab.projectRoot ? (
|
|
132
|
+
<div
|
|
133
|
+
title={tab.projectRoot}
|
|
134
|
+
className="truncate rounded border border-gray-200 bg-gray-50 px-2 py-1.5 font-mono text-[11px] text-gray-500"
|
|
135
|
+
>
|
|
136
|
+
{tab.projectRoot}
|
|
137
|
+
</div>
|
|
138
|
+
) : null}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function WorkspaceActiveChildHeader({
|
|
144
|
+
tab,
|
|
145
|
+
}: {
|
|
146
|
+
tab: ResolvedChildSessionTab;
|
|
147
|
+
}) {
|
|
148
|
+
return (
|
|
149
|
+
<div className="border-b border-gray-200/70 px-4 py-3">
|
|
150
|
+
<div className="flex min-w-0 items-center gap-2 text-sm font-semibold text-gray-900">
|
|
151
|
+
{tab.agentId ? (
|
|
152
|
+
<AgentIdentityAvatar
|
|
153
|
+
agentId={tab.agentId}
|
|
154
|
+
className="h-4 w-4 shrink-0"
|
|
155
|
+
/>
|
|
156
|
+
) : (
|
|
157
|
+
<FolderGit2 className="h-4 w-4 shrink-0 text-gray-400" />
|
|
158
|
+
)}
|
|
159
|
+
<span className="truncate">{tab.title}</span>
|
|
160
|
+
</div>
|
|
161
|
+
<ChildSessionMetaStrip tab={tab} />
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function ChatSessionWorkspacePanel({
|
|
167
|
+
childSessionTabs,
|
|
168
|
+
activeChildSessionKey,
|
|
169
|
+
workspaceFileTabs,
|
|
170
|
+
activeWorkspaceFileKey,
|
|
171
|
+
sessionProjectRoot,
|
|
172
|
+
onSelectSession,
|
|
173
|
+
onSelectFile,
|
|
174
|
+
onCloseFile,
|
|
175
|
+
onClose,
|
|
176
|
+
onBackToParent,
|
|
177
|
+
onToolAction,
|
|
178
|
+
onFileOpen,
|
|
179
|
+
}: ChatSessionWorkspacePanelProps) {
|
|
180
|
+
const presenter = usePresenter();
|
|
181
|
+
const resolvedChildTabs = useNcpChildSessionTabsView(childSessionTabs);
|
|
182
|
+
const optimisticReadAtBySessionKey = useChatSessionListStore(
|
|
183
|
+
(state) => state.optimisticReadAtBySessionKey,
|
|
184
|
+
);
|
|
185
|
+
const activeSelection = resolveWorkspaceSelection({
|
|
186
|
+
activeChildSessionKey,
|
|
187
|
+
activeWorkspaceFileKey,
|
|
188
|
+
childSessionTabs: resolvedChildTabs,
|
|
189
|
+
workspaceFileTabs,
|
|
190
|
+
});
|
|
191
|
+
const hasParentSession = resolvedChildTabs.some((tab) =>
|
|
192
|
+
Boolean(tab.parentSessionKey),
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (activeSelection?.kind !== "child-session") {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const activeTabReadAt = activeSelection.tab.lastMessageAt?.trim() ?? null;
|
|
200
|
+
if (!activeTabReadAt) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
presenter.chatSessionListManager.markSessionRead(
|
|
204
|
+
activeSelection.tab.sessionKey,
|
|
205
|
+
activeTabReadAt,
|
|
206
|
+
activeSelection.tab.readAt ?? null,
|
|
207
|
+
);
|
|
208
|
+
}, [activeSelection, presenter]);
|
|
209
|
+
|
|
210
|
+
const workspaceTabs = useMemo<WorkspaceTabViewModel[]>(() => {
|
|
211
|
+
const childTabs = resolvedChildTabs.map((tab) => {
|
|
212
|
+
const optimisticReadAt = optimisticReadAtBySessionKey[tab.sessionKey];
|
|
213
|
+
const effectiveReadAt =
|
|
214
|
+
optimisticReadAt && tab.readAt
|
|
215
|
+
? optimisticReadAt.localeCompare(tab.readAt) > 0
|
|
216
|
+
? optimisticReadAt
|
|
217
|
+
: tab.readAt
|
|
218
|
+
: optimisticReadAt ?? tab.readAt;
|
|
219
|
+
return {
|
|
220
|
+
key: `child:${tab.sessionKey}`,
|
|
221
|
+
kind: "child-session" as const,
|
|
222
|
+
title: tab.title,
|
|
223
|
+
tooltip: tab.title,
|
|
224
|
+
agentId: tab.agentId,
|
|
225
|
+
active:
|
|
226
|
+
activeSelection?.kind === "child-session" &&
|
|
227
|
+
activeSelection.tab.sessionKey === tab.sessionKey,
|
|
228
|
+
showUnreadDot: shouldShowUnreadSessionIndicator({
|
|
229
|
+
active:
|
|
230
|
+
activeSelection?.kind === "child-session" &&
|
|
231
|
+
activeSelection.tab.sessionKey === tab.sessionKey,
|
|
232
|
+
lastMessageAt: tab.lastMessageAt,
|
|
233
|
+
readAt: effectiveReadAt,
|
|
234
|
+
runStatus: tab.runStatus,
|
|
235
|
+
}),
|
|
236
|
+
onSelect: () => onSelectSession(tab.sessionKey),
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const fileTabs = workspaceFileTabs.map((file) => ({
|
|
241
|
+
key: `file:${file.key}`,
|
|
242
|
+
kind: "file" as const,
|
|
243
|
+
title: readWorkspaceFileTitle(file),
|
|
244
|
+
tooltip: file.path,
|
|
245
|
+
viewMode: file.viewMode,
|
|
246
|
+
active:
|
|
247
|
+
activeSelection?.kind === "file" &&
|
|
248
|
+
activeSelection.file.key === file.key,
|
|
249
|
+
onSelect: () => onSelectFile(file.key),
|
|
250
|
+
onClose: () => onCloseFile(file.key),
|
|
251
|
+
}));
|
|
252
|
+
|
|
253
|
+
return [...childTabs, ...fileTabs];
|
|
254
|
+
}, [
|
|
255
|
+
activeSelection,
|
|
256
|
+
onCloseFile,
|
|
257
|
+
onSelectFile,
|
|
258
|
+
onSelectSession,
|
|
259
|
+
optimisticReadAtBySessionKey,
|
|
260
|
+
resolvedChildTabs,
|
|
261
|
+
workspaceFileTabs,
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
if (!activeSelection) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<aside className="hidden shrink-0 border-l border-gray-200/70 bg-white/95 backdrop-blur-sm md:flex md:w-[26rem] lg:w-[30rem] xl:w-[34rem]">
|
|
270
|
+
<div className="flex h-full min-h-0 w-full flex-col">
|
|
271
|
+
<div className="flex items-center justify-between gap-3 border-b border-gray-200/70 px-4 py-2.5">
|
|
272
|
+
<button
|
|
273
|
+
type="button"
|
|
274
|
+
onClick={onBackToParent}
|
|
275
|
+
className={cn(
|
|
276
|
+
"inline-flex items-center gap-1 text-xs font-medium text-gray-600 transition-colors hover:text-gray-900",
|
|
277
|
+
!hasParentSession && "pointer-events-none opacity-0",
|
|
278
|
+
)}
|
|
279
|
+
>
|
|
280
|
+
<ArrowLeft className="h-3.5 w-3.5" />
|
|
281
|
+
<span>{t("chatBackToParent")}</span>
|
|
282
|
+
</button>
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
onClick={onClose}
|
|
286
|
+
className="rounded-full border border-gray-200/80 p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
|
287
|
+
aria-label={t("chatWorkspaceClosePanel")}
|
|
288
|
+
>
|
|
289
|
+
<X className="h-4 w-4" />
|
|
290
|
+
</button>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
<WorkspaceTabsBar tabs={workspaceTabs} />
|
|
294
|
+
|
|
295
|
+
<div className="flex min-h-0 flex-1 flex-col bg-white">
|
|
296
|
+
{activeSelection.kind === "child-session" ? (
|
|
297
|
+
<>
|
|
298
|
+
<WorkspaceActiveChildHeader tab={activeSelection.tab} />
|
|
299
|
+
<div className="flex-1 min-h-0">
|
|
300
|
+
<ChildSessionContent
|
|
301
|
+
sessionKey={activeSelection.tab.sessionKey}
|
|
302
|
+
onToolAction={onToolAction}
|
|
303
|
+
onFileOpen={onFileOpen}
|
|
304
|
+
/>
|
|
305
|
+
</div>
|
|
306
|
+
</>
|
|
307
|
+
) : (
|
|
308
|
+
<ChatSessionWorkspaceFilePreview
|
|
309
|
+
file={activeSelection.file}
|
|
310
|
+
sessionProjectRoot={sessionProjectRoot}
|
|
311
|
+
onFileOpen={onFileOpen}
|
|
312
|
+
/>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</aside>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useMemo, useState, type ReactNode } from 'react';
|
|
2
2
|
import { Plus } from 'lucide-react';
|
|
3
3
|
import { Button } from '@/components/ui/button';
|
|
4
|
+
import { ChatSessionTypeOptionItem } from '@/components/chat/chat-session-type-option-item';
|
|
4
5
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
5
6
|
import type { ChatInputSnapshot } from '@/components/chat/stores/chat-input.store';
|
|
6
7
|
import type { NcpSessionListItemView } from '@/components/chat/ncp/use-ncp-session-list-view';
|
|
7
8
|
import { t } from '@/lib/i18n';
|
|
8
|
-
import { cn } from '@/lib/utils';
|
|
9
9
|
|
|
10
10
|
export type ChatSidebarProjectGroup = {
|
|
11
11
|
projectRoot: string;
|
|
@@ -34,16 +34,6 @@ function resolveProjectGroupDefaultSessionType(
|
|
|
34
34
|
return sessionTypeOptions[0]?.value ?? defaultSessionType;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function resolveSessionTypeStatusText(option: {
|
|
38
|
-
ready?: boolean;
|
|
39
|
-
reasonMessage?: string | null;
|
|
40
|
-
}): string {
|
|
41
|
-
if (option.ready === false) {
|
|
42
|
-
return option.reasonMessage?.trim() || t('statusSetup');
|
|
43
|
-
}
|
|
44
|
-
return t('statusReady');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
48
38
|
const { groups, defaultSessionType, sessionTypeOptions, renderSessionItem, onCreateSession } = props;
|
|
49
39
|
const [openProjectRoot, setOpenProjectRoot] = useState<string | null>(null);
|
|
@@ -91,38 +81,23 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
91
81
|
<Plus className="h-3.5 w-3.5" />
|
|
92
82
|
</Button>
|
|
93
83
|
</PopoverTrigger>
|
|
94
|
-
<PopoverContent
|
|
95
|
-
|
|
84
|
+
<PopoverContent
|
|
85
|
+
align="end"
|
|
86
|
+
className="w-56 rounded-2xl border border-gray-200/80 bg-white p-1.5 shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
|
|
87
|
+
>
|
|
88
|
+
<div className="px-3 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-gray-400">
|
|
96
89
|
{t('chatSessionTypeLabel')}
|
|
97
90
|
</div>
|
|
98
|
-
<div className="
|
|
91
|
+
<div className="space-y-1">
|
|
99
92
|
{sessionTypeOptions.map((option) => (
|
|
100
|
-
<
|
|
93
|
+
<ChatSessionTypeOptionItem
|
|
101
94
|
key={`${group.projectRoot}:${option.value}`}
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
option={option}
|
|
96
|
+
onSelect={() => {
|
|
104
97
|
onCreateSession(option.value, group.projectRoot);
|
|
105
98
|
setOpenProjectRoot(null);
|
|
106
99
|
}}
|
|
107
|
-
|
|
108
|
-
>
|
|
109
|
-
<div className="flex items-center justify-between gap-3">
|
|
110
|
-
<div className="text-[13px] font-medium text-gray-900">{option.label}</div>
|
|
111
|
-
<span
|
|
112
|
-
className={cn(
|
|
113
|
-
'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold',
|
|
114
|
-
option.ready === false
|
|
115
|
-
? 'bg-amber-100 text-amber-800'
|
|
116
|
-
: 'bg-emerald-100 text-emerald-700'
|
|
117
|
-
)}
|
|
118
|
-
>
|
|
119
|
-
{option.ready === false ? t('statusSetup') : t('statusReady')}
|
|
120
|
-
</span>
|
|
121
|
-
</div>
|
|
122
|
-
<div className="mt-0.5 text-[11px] text-gray-500">
|
|
123
|
-
{resolveSessionTypeStatusText(option)}
|
|
124
|
-
</div>
|
|
125
|
-
</button>
|
|
100
|
+
/>
|
|
126
101
|
))}
|
|
127
102
|
</div>
|
|
128
103
|
</PopoverContent>
|
|
@@ -8,7 +8,7 @@ import { type SessionContextView } from '@/lib/session-context.utils';
|
|
|
8
8
|
import type { SessionRunStatus } from '@/lib/session-run-status';
|
|
9
9
|
import { cn } from '@/lib/utils';
|
|
10
10
|
import { formatDateTime, t } from '@/lib/i18n';
|
|
11
|
-
import { Check, Pencil, X } from 'lucide-react';
|
|
11
|
+
import { Check, GitBranch, Pencil, X } from 'lucide-react';
|
|
12
12
|
|
|
13
13
|
type ChatSidebarSessionItemProps = {
|
|
14
14
|
session: SessionEntryView;
|
|
@@ -20,10 +20,12 @@ type ChatSidebarSessionItemProps = {
|
|
|
20
20
|
agentId?: string | null;
|
|
21
21
|
agentLabel?: string | null;
|
|
22
22
|
agentAvatarUrl?: string | null;
|
|
23
|
+
childSessionCount?: number;
|
|
23
24
|
isEditing: boolean;
|
|
24
25
|
draftLabel: string;
|
|
25
26
|
isSaving: boolean;
|
|
26
27
|
onSelect: () => void;
|
|
28
|
+
onOpenChildSessions?: () => void;
|
|
27
29
|
onStartEditing: () => void;
|
|
28
30
|
onDraftLabelChange: (value: string) => void;
|
|
29
31
|
onSave: () => void | Promise<void>;
|
|
@@ -108,7 +110,9 @@ function ChatSidebarSessionDisplayView({
|
|
|
108
110
|
agentId,
|
|
109
111
|
agentLabel,
|
|
110
112
|
agentAvatarUrl,
|
|
113
|
+
childSessionCount = 0,
|
|
111
114
|
onSelect,
|
|
115
|
+
onOpenChildSessions,
|
|
112
116
|
onStartEditing
|
|
113
117
|
}: ChatSidebarSessionDisplayViewProps) {
|
|
114
118
|
const iconTone = active ? 'text-gray-700' : 'text-gray-500';
|
|
@@ -162,9 +166,31 @@ function ChatSidebarSessionDisplayView({
|
|
|
162
166
|
</span>
|
|
163
167
|
</div>
|
|
164
168
|
<div className="mt-0.5 text-[11px] text-gray-400 truncate">
|
|
165
|
-
|
|
169
|
+
<span>
|
|
170
|
+
{agentLabel?.trim() ? `${agentLabel} · ` : ''}{session.messageCount} · {formatDateTime(session.updatedAt)}
|
|
171
|
+
</span>
|
|
166
172
|
</div>
|
|
167
173
|
</button>
|
|
174
|
+
{childSessionCount > 0 && onOpenChildSessions ? (
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
onClick={(event) => {
|
|
178
|
+
event.stopPropagation();
|
|
179
|
+
onOpenChildSessions();
|
|
180
|
+
}}
|
|
181
|
+
className={cn(
|
|
182
|
+
'absolute right-7 top-0 inline-flex h-7 items-center gap-1 rounded-lg px-1.5 text-[10px] font-medium text-gray-400 transition-all hover:bg-white hover:text-gray-900',
|
|
183
|
+
active
|
|
184
|
+
? 'opacity-100'
|
|
185
|
+
: 'opacity-0 group-hover/session:opacity-100 group-focus-within/session:opacity-100'
|
|
186
|
+
)}
|
|
187
|
+
aria-label={t('chatSessionOpenChildSessions')}
|
|
188
|
+
title={t('chatSessionOpenChildSessions')}
|
|
189
|
+
>
|
|
190
|
+
<GitBranch className="h-3.5 w-3.5" />
|
|
191
|
+
<span>{childSessionCount}</span>
|
|
192
|
+
</button>
|
|
193
|
+
) : null}
|
|
168
194
|
<button
|
|
169
195
|
type="button"
|
|
170
196
|
onClick={(event) => {
|
|
@@ -195,10 +221,12 @@ export function ChatSidebarSessionItem({
|
|
|
195
221
|
agentId,
|
|
196
222
|
agentLabel,
|
|
197
223
|
agentAvatarUrl,
|
|
224
|
+
childSessionCount,
|
|
198
225
|
isEditing,
|
|
199
226
|
draftLabel,
|
|
200
227
|
isSaving,
|
|
201
228
|
onSelect,
|
|
229
|
+
onOpenChildSessions,
|
|
202
230
|
onStartEditing,
|
|
203
231
|
onDraftLabelChange,
|
|
204
232
|
onSave,
|
|
@@ -234,6 +262,8 @@ export function ChatSidebarSessionItem({
|
|
|
234
262
|
agentLabel={agentLabel}
|
|
235
263
|
agentAvatarUrl={agentAvatarUrl}
|
|
236
264
|
onSelect={onSelect}
|
|
265
|
+
childSessionCount={childSessionCount}
|
|
266
|
+
onOpenChildSessions={onOpenChildSessions}
|
|
237
267
|
onStartEditing={onStartEditing}
|
|
238
268
|
/>
|
|
239
269
|
)}
|
|
@@ -147,6 +147,55 @@ it("adapts persisted inline token metadata into rich message parts", () => {
|
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
it("keeps Hermes tool invocation parts as tool cards instead of flattening them into plain text", () => {
|
|
151
|
+
const message = {
|
|
152
|
+
id: "assistant-hermes-tool-1",
|
|
153
|
+
sessionId: "session-1",
|
|
154
|
+
role: "assistant",
|
|
155
|
+
status: "final",
|
|
156
|
+
timestamp: "2026-04-16T00:00:00.000Z",
|
|
157
|
+
parts: [
|
|
158
|
+
{
|
|
159
|
+
type: "reasoning",
|
|
160
|
+
text: "The user wants Python files.",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: "\n",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
type: "tool-invocation",
|
|
168
|
+
toolCallId: "hermes-inline-tool-1",
|
|
169
|
+
toolName: "search_files",
|
|
170
|
+
state: "call",
|
|
171
|
+
args: "{\"pattern\":\"*.py\"}",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: "\nFound them.",
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
} satisfies NcpMessage;
|
|
179
|
+
|
|
180
|
+
render(<ChatMessageListContainer messages={[message]} isSending={false} />);
|
|
181
|
+
|
|
182
|
+
const renderedMessages =
|
|
183
|
+
captures.renders[captures.renders.length - 1]?.messages ?? [];
|
|
184
|
+
expect(renderedMessages[0]).toMatchObject({
|
|
185
|
+
parts: expect.arrayContaining([
|
|
186
|
+
expect.objectContaining({
|
|
187
|
+
type: "tool-card",
|
|
188
|
+
card: expect.objectContaining({
|
|
189
|
+
toolName: "search_files",
|
|
190
|
+
titleLabel: "chatToolCall",
|
|
191
|
+
statusTone: "running",
|
|
192
|
+
statusLabel: "chatToolStatusRunning",
|
|
193
|
+
}),
|
|
194
|
+
}),
|
|
195
|
+
]),
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
150
199
|
it("passes localized attachment card texts to the shared chat UI", () => {
|
|
151
200
|
captures.language = "zh";
|
|
152
201
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import type { NcpMessage } from "@nextclaw/ncp";
|
|
3
3
|
import {
|
|
4
|
+
type ChatFileOpenActionViewModel,
|
|
4
5
|
type ChatToolActionViewModel,
|
|
5
6
|
type ChatMessageViewModel,
|
|
6
7
|
ChatMessageList,
|
|
@@ -21,6 +22,7 @@ type ChatMessageListContainerProps = {
|
|
|
21
22
|
isSending: boolean;
|
|
22
23
|
className?: string;
|
|
23
24
|
onToolAction?: (action: ChatToolActionViewModel) => void;
|
|
25
|
+
onFileOpen?: (action: ChatFileOpenActionViewModel) => void;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
const messageViewModelCache = new WeakMap<
|
|
@@ -87,6 +89,7 @@ export function ChatMessageListContainer({
|
|
|
87
89
|
isSending,
|
|
88
90
|
className,
|
|
89
91
|
onToolAction,
|
|
92
|
+
onFileOpen,
|
|
90
93
|
}: ChatMessageListContainerProps) {
|
|
91
94
|
const { language } = useI18n();
|
|
92
95
|
const texts = useMemo<ChatMessageAdapterTexts>(
|
|
@@ -144,6 +147,7 @@ export function ChatMessageListContainer({
|
|
|
144
147
|
className={className}
|
|
145
148
|
texts={messageTexts}
|
|
146
149
|
onToolAction={onToolAction}
|
|
150
|
+
onFileOpen={onFileOpen}
|
|
147
151
|
renderToolAgent={(agentId) => (
|
|
148
152
|
<AgentIdentityAvatar
|
|
149
153
|
agentId={agentId}
|
|
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
import { ChatSessionListManager } from '@/components/chat/managers/chat-session-list.manager';
|
|
3
3
|
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
4
4
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
5
|
+
import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
|
|
5
6
|
|
|
6
7
|
const mocks = vi.hoisted(() => ({
|
|
7
8
|
updateNcpSession: vi.fn(),
|
|
@@ -33,6 +34,14 @@ describe('ChatSessionListManager', () => {
|
|
|
33
34
|
listMode: 'time-first'
|
|
34
35
|
}
|
|
35
36
|
});
|
|
37
|
+
useChatThreadStore.setState({
|
|
38
|
+
snapshot: {
|
|
39
|
+
...useChatThreadStore.getState().snapshot,
|
|
40
|
+
workspacePanelParentKey: 'session-1',
|
|
41
|
+
activeChildSessionKey: 'child-session-1',
|
|
42
|
+
activeWorkspaceFileKey: 'session-1::/tmp/demo.md',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
36
45
|
});
|
|
37
46
|
|
|
38
47
|
it('applies the requested session type when creating a session', () => {
|
|
@@ -133,6 +142,9 @@ describe('ChatSessionListManager', () => {
|
|
|
133
142
|
|
|
134
143
|
expect(uiManager.goToSession).toHaveBeenCalledWith('session-2');
|
|
135
144
|
expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBe('session-1');
|
|
145
|
+
expect(useChatThreadStore.getState().snapshot.workspacePanelParentKey).toBeNull();
|
|
146
|
+
expect(useChatThreadStore.getState().snapshot.activeChildSessionKey).toBeNull();
|
|
147
|
+
expect(useChatThreadStore.getState().snapshot.activeWorkspaceFileKey).toBeNull();
|
|
136
148
|
});
|
|
137
149
|
|
|
138
150
|
it('updates the sidebar list mode without touching other session list state', () => {
|
|
@@ -25,8 +25,10 @@ export class ChatSessionListManager {
|
|
|
25
25
|
isAwaitingAssistantOutput: false,
|
|
26
26
|
parentSessionKey: null,
|
|
27
27
|
parentSessionLabel: null,
|
|
28
|
+
workspacePanelParentKey: null,
|
|
28
29
|
childSessionTabs: [],
|
|
29
30
|
activeChildSessionKey: null,
|
|
31
|
+
activeWorkspaceFileKey: null,
|
|
30
32
|
});
|
|
31
33
|
};
|
|
32
34
|
|
|
@@ -157,6 +159,11 @@ export class ChatSessionListManager {
|
|
|
157
159
|
};
|
|
158
160
|
|
|
159
161
|
selectSession = (sessionKey: string) => {
|
|
162
|
+
useChatThreadStore.getState().setSnapshot({
|
|
163
|
+
workspacePanelParentKey: null,
|
|
164
|
+
activeChildSessionKey: null,
|
|
165
|
+
activeWorkspaceFileKey: null,
|
|
166
|
+
});
|
|
160
167
|
this.uiManager.goToSession(sessionKey);
|
|
161
168
|
};
|
|
162
169
|
|