@nextclaw/ui 0.12.9 → 0.12.11
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 +102 -0
- package/dist/assets/ChannelsList-SQ7Oxotv.js +8 -0
- package/dist/assets/DocBrowser-BCO2k6XD.js +1 -0
- package/dist/assets/{DocBrowser-6ReNjvzF.js → DocBrowser-rDOjI3ga.js} +1 -1
- package/dist/assets/{DocBrowserContext-B6SpA7Qs.js → DocBrowserContext-BUq3Wo8O.js} +1 -1
- package/dist/assets/{LogoBadge-ByNLYg65.js → LogoBadge-DP8Ye7wJ.js} +1 -1
- package/dist/assets/ModelConfig-C77Ae9ru.js +1 -0
- package/dist/assets/ProviderScopedModelInput-CEnK61uo.js +1 -0
- package/dist/assets/ProvidersList-BCupBayq.js +1 -0
- package/dist/assets/RuntimeConfig-Ad-CAcmy.js +1 -0
- package/dist/assets/SearchConfig-BfCz4wJ4.js +1 -0
- package/dist/assets/SecretsConfig-DjmBIhyy.js +3 -0
- package/dist/assets/{SessionsConfig-ChHQ7M5c.js → SessionsConfig-CvjxU40H.js} +2 -2
- package/dist/assets/{book-open-BdcxxoQu.js → book-open-BE8M56IM.js} +1 -1
- package/dist/assets/chat-page-JKC6ln-y.js +58 -0
- package/dist/assets/chat-session-display-YcRMrAMa.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-DK5HPmIK.js → chunk-JZWAC4HX-erTUn3b8.js} +1 -1
- package/dist/assets/client-CszWMVKi.js +7 -0
- package/dist/assets/config-split-page-BAGSzUR3.js +1 -0
- package/dist/assets/{createLucideIcon-BSeTgkZW.js → createLucideIcon-CCiTGX8L.js} +1 -1
- package/dist/assets/desktop-DfkLlkG2.js +1 -0
- package/dist/assets/desktop-update-config-BXeGlqHD.js +1 -0
- package/dist/assets/dialog-BghZFPch.js +5 -0
- package/dist/assets/{dist-6TrrnPCR.js → dist-Dd9cr-kz.js} +1 -1
- package/dist/assets/dist-ZwoAXs46.js +9 -0
- package/dist/assets/{download-BhDxnyvU.js → download-D7LOizcW.js} +1 -1
- package/dist/assets/es2015-CEAreese.js +41 -0
- package/dist/assets/{external-link-BgErLCNT.js → external-link-qsnCMhw1.js} +1 -1
- package/dist/assets/{hash-Bl7dr_UG.js → hash-0zjWsNl-.js} +1 -1
- package/dist/assets/{i18n-eDHeDY0n.js → i18n-DvzXOGQX.js} +1 -1
- package/dist/assets/index-DvVTC9FF.css +1 -0
- package/dist/assets/index-lr6rQUSd.js +2 -0
- package/dist/assets/key-round-BLe9D8ND.js +1 -0
- package/dist/assets/loader-circle-wj7kARHv.js +1 -0
- package/dist/assets/{logos-x89HbrZ4.js → logos-_v5b2SdG.js} +1 -1
- package/dist/assets/marketplace-page-CAAk1Khc.js +1 -0
- package/dist/assets/marketplace-page-CfCiq90S.js +49 -0
- package/dist/assets/mcp-marketplace-page-D0Pp9Hs-.js +40 -0
- package/dist/assets/play-o6NmwGTi.js +1 -0
- package/dist/assets/plus-I9pBS4Fl.js +1 -0
- package/dist/assets/{refresh-cw-C47QSEwg.js → refresh-cw-MNqgR3LZ.js} +1 -1
- package/dist/assets/remote-C9fXm4V5.js +1 -0
- package/dist/assets/{save-3S6-H3Xw.js → save-D4bObrmH.js} +1 -1
- package/dist/assets/search-DxmL3IWE.js +1 -0
- package/dist/assets/security-config-BUm6FFfl.js +1 -0
- package/dist/assets/select-BILPf7zs.js +1 -0
- package/dist/assets/setting-row-BATDgg4r.js +1 -0
- package/dist/assets/skeleton-COKMAnJy.js +1 -0
- package/dist/assets/{switch-BsLtHOH-.js → switch-CBOzecWS.js} +1 -1
- package/dist/assets/{tabs-custom-D3HYMt6k.js → tabs-custom-Bx3cNhD-.js} +1 -1
- package/dist/assets/tag-chip-zUaDE2-H.js +1 -0
- package/dist/assets/{trash-2-G48scll7.js → trash-2-CQUgYyRn.js} +1 -1
- package/dist/assets/use-infinite-scroll-loader-B5V2Klve.js +1 -0
- package/dist/assets/useConfirmDialog-patAnl1g.js +1 -0
- package/dist/assets/{useMutation-CBWjE2uj.js → useMutation-__AYv-Pz.js} +1 -1
- package/dist/assets/x-BHUGQIUv.js +1 -0
- package/dist/index.html +22 -22
- 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/module-structure.config.json +7 -0
- package/package.json +6 -6
- 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/src/api/chat-session-type.types.ts +7 -0
- package/src/api/config.ts +10 -0
- package/src/api/raw-client.test.ts +1 -1
- package/src/api/{raw-client.ts → raw-client.utils.ts} +2 -0
- package/src/api/runtime-control.types.ts +8 -0
- package/src/api/types.ts +48 -0
- package/src/app/components/app-manager-provider.tsx +20 -0
- package/src/app/managers/app.manager.ts +12 -0
- package/src/app.tsx +223 -59
- 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/chat-conversation-panel.test.tsx +30 -0
- package/src/components/chat/chat-conversation-panel.tsx +83 -13
- package/src/components/chat/chat-input/ncp-chat-input-availability.utils.test.ts +92 -0
- package/src/components/chat/chat-input/ncp-chat-input-availability.utils.ts +45 -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 +87 -0
- package/src/components/chat/chat-session-workspace-file-preview.tsx +14 -43
- package/src/components/chat/chat-session-workspace-panel-nav.tsx +8 -2
- package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
- package/src/components/chat/containers/chat-input-bar.container.tsx +24 -12
- package/src/components/chat/{ChatSidebar.test.tsx → containers/chat-sidebar.test.tsx} +5 -4
- package/src/components/chat/{ChatSidebar.tsx → containers/chat-sidebar.tsx} +24 -72
- package/src/components/chat/hooks/use-chat-sidebar-session-label-editor.ts +49 -0
- package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
- package/src/components/chat/ncp/ncp-app-client-fetch.ts +3 -0
- package/src/components/chat/ncp/ncp-chat-input.manager.ts +13 -5
- package/src/components/chat/ncp/ncp-chat-page.tsx +23 -2
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +1 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +3 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +10 -4
- package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.test.tsx +48 -4
- package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.ts +43 -5
- package/src/components/chat/ncp/tests/ncp-chat-input.manager.test.ts +51 -1
- package/src/components/chat/stores/chat-input.store.ts +2 -1
- package/src/components/chat/stores/chat-thread.store.ts +3 -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/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/desktop-update-config.test.tsx +10 -4
- package/src/components/config/desktop-update-config.tsx +5 -3
- package/src/components/config/provider-enabled-field.tsx +17 -10
- package/src/components/config/runtime-control-card.test.tsx +136 -158
- package/src/components/config/runtime-control-card.tsx +43 -68
- package/src/components/config/runtime-presence-card.test.tsx +10 -14
- package/src/components/config/runtime-presence-card.tsx +97 -81
- package/src/components/layout/AppLayout.tsx +25 -37
- package/src/components/layout/Sidebar.tsx +4 -4
- package/src/components/layout/app-layout.test.tsx +46 -14
- package/src/components/layout/runtime-status-entry.test.tsx +101 -0
- package/src/components/layout/runtime-status-entry.tsx +95 -0
- package/src/components/layout/sidebar.layout.test.tsx +11 -5
- 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/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/features/account/components/account-panel.tsx +255 -0
- package/src/features/account/index.ts +6 -0
- package/src/{account → features/account}/managers/account.manager.ts +6 -5
- package/src/features/remote/components/remote-access-page.test.tsx +104 -0
- package/src/features/remote/components/remote-access-page.tsx +250 -0
- package/src/{hooks/useRemoteAccess.ts → features/remote/hooks/use-remote-access.ts} +1 -1
- package/src/features/remote/index.ts +27 -0
- package/src/{remote → features/remote}/managers/remote-access.manager.ts +3 -4
- package/src/{remote → features/remote/services}/remote-access-feedback.service.test.ts +1 -1
- package/src/features/system-status/hooks/use-system-status.ts +104 -0
- package/src/features/system-status/index.ts +12 -0
- package/src/features/system-status/managers/system-status.manager.bootstrap-polling.test.ts +126 -0
- package/src/features/system-status/managers/system-status.manager.test.ts +142 -0
- package/src/features/system-status/managers/system-status.manager.ts +511 -0
- package/src/features/system-status/stores/system-status.store.ts +32 -0
- package/src/features/system-status/types/system-status.types.ts +73 -0
- package/src/features/system-status/utils/system-status.utils.test.ts +132 -0
- package/src/features/system-status/utils/system-status.utils.ts +202 -0
- package/src/hooks/use-realtime-query-bridge.ts +34 -18
- 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/i18n.chat.ts +8 -0
- package/src/lib/i18n.remote.ts +1 -1
- package/src/lib/i18n.runtime-control.ts +31 -0
- package/src/lib/i18n.ts +5 -8
- 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/platforms/desktop/index.ts +20 -0
- package/src/{desktop → platforms/desktop}/managers/desktop-presence.manager.ts +2 -2
- package/src/{desktop → platforms/desktop}/managers/desktop-update.manager.ts +2 -2
- package/src/{desktop → platforms/desktop}/stores/desktop-presence.store.ts +1 -1
- package/src/{desktop → platforms/desktop}/stores/desktop-update.store.ts +1 -1
- package/src/stores/ui.store.ts +0 -9
- package/src/transport/{app-client.ts → app-client.service.ts} +9 -9
- package/src/transport/app-client.test.ts +9 -5
- package/src/transport/index.ts +1 -1
- package/src/transport/{local.transport.ts → local-transport.service.ts} +14 -12
- package/dist/assets/ChannelsList-Ita2Zm1_.js +0 -8
- package/dist/assets/DocBrowser-BNwbPHf4.js +0 -1
- package/dist/assets/MarketplacePage-CjX2MWww.js +0 -1
- package/dist/assets/MarketplacePage-D0sDlYX4.js +0 -49
- package/dist/assets/McpMarketplacePage-BGKJm1sJ.js +0 -40
- package/dist/assets/ModelConfig-BzZenCH-.js +0 -1
- package/dist/assets/ProviderScopedModelInput-Da7khnBA.js +0 -1
- package/dist/assets/ProvidersList-BbVzRxjY.js +0 -1
- package/dist/assets/RemoteAccessPage-BaDH_X1Q.js +0 -1
- package/dist/assets/RuntimeConfig-F_XKGgLm.js +0 -1
- package/dist/assets/SearchConfig-BGkzXQP-.js +0 -1
- package/dist/assets/SecretsConfig-D281Rotl.js +0 -3
- package/dist/assets/app-query-client-VnFElj4E.js +0 -1
- package/dist/assets/chat-page-Doe0yTtB.js +0 -58
- package/dist/assets/chat-session-display-cw78aiI_.js +0 -1
- package/dist/assets/client-_i4MU2bB.js +0 -7
- package/dist/assets/config-DtIQwrHF.js +0 -1
- package/dist/assets/config-layout-CHs0mAaR.js +0 -1
- package/dist/assets/desktop-update-config-Dpcf4BKG.js +0 -1
- package/dist/assets/dist-ccBFUi-o.js +0 -9
- package/dist/assets/index-CF9xve0E.js +0 -6
- package/dist/assets/index-FgA52VBt.css +0 -1
- package/dist/assets/infiniteQueryBehavior-ZDS92Qpp.js +0 -1
- package/dist/assets/loader-circle-ACM1s51e.js +0 -1
- package/dist/assets/page-layout-vZnghcFy.js +0 -1
- package/dist/assets/play-CFUwCA2E.js +0 -1
- package/dist/assets/plus-rYsv72JG.js +0 -1
- package/dist/assets/popover-Bg1VoTZ6.js +0 -1
- package/dist/assets/refresh-ccw-DT98i__E.js +0 -1
- package/dist/assets/rotate-cw-JtFzpNn6.js +0 -1
- package/dist/assets/search-3kFR_zh9.js +0 -1
- package/dist/assets/security-config-BWaiARNk.js +0 -1
- package/dist/assets/select-DJ2MUjBB.js +0 -41
- package/dist/assets/skeleton-ByQepn0M.js +0 -1
- package/dist/assets/status-dot-vbanNPFU.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-DkNhD-42.js +0 -1
- package/dist/assets/useConfirmDialog-BkvTN-vd.js +0 -1
- package/dist/assets/x-ByDbItbq.js +0 -1
- package/src/account/components/account-panel.tsx +0 -135
- 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/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/src/hooks/use-runtime-control.ts +0 -24
- package/src/presenter/app-presenter-context.tsx +0 -20
- package/src/presenter/app.presenter.ts +0 -12
- package/src/runtime-control/runtime-control.manager.ts +0 -118
- /package/dist/assets/{config-hints-BhTmc9P1.js → config-hints-DSQQbeOA.js} +0 -0
- /package/src/{account → features/account}/stores/account.store.ts +0 -0
- /package/src/{remote → features/remote/services}/remote-access-feedback.service.ts +0 -0
- /package/src/{remote/remote-access.query.ts → features/remote/services/remote-access-query.service.ts} +0 -0
- /package/src/{remote → features/remote}/stores/remote-access.store.ts +0 -0
- /package/src/{desktop → platforms/desktop/types}/desktop-update.types.ts +0 -0
|
@@ -269,6 +269,23 @@ describe("ChatConversationPanel", () => {
|
|
|
269
269
|
expect(screen.queryByText("Engineer")).toBeNull();
|
|
270
270
|
});
|
|
271
271
|
|
|
272
|
+
it("renders a fuller loading skeleton before provider state settles", () => {
|
|
273
|
+
useChatThreadStore.setState({
|
|
274
|
+
snapshot: {
|
|
275
|
+
...useChatThreadStore.getState().snapshot,
|
|
276
|
+
isProviderStateResolved: false,
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
render(<ChatConversationPanel />);
|
|
281
|
+
|
|
282
|
+
expect(screen.getByTestId("chat-conversation-skeleton")).toBeTruthy();
|
|
283
|
+
expect(
|
|
284
|
+
screen.getAllByTestId("chat-conversation-skeleton-bubble"),
|
|
285
|
+
).toHaveLength(4);
|
|
286
|
+
expect(screen.queryByTestId("chat-input-bar")).toBeNull();
|
|
287
|
+
});
|
|
288
|
+
|
|
272
289
|
it("keeps the message area clean while a session history is hydrating", () => {
|
|
273
290
|
useChatThreadStore.setState({
|
|
274
291
|
snapshot: {
|
|
@@ -288,6 +305,16 @@ describe("ChatConversationPanel", () => {
|
|
|
288
305
|
expect(screen.queryByText("No messages yet. Send one to start.")).toBeNull();
|
|
289
306
|
});
|
|
290
307
|
|
|
308
|
+
it("does not render runtime lifecycle copy in the conversation alert strip", () => {
|
|
309
|
+
render(<ChatConversationPanel />);
|
|
310
|
+
|
|
311
|
+
expect(
|
|
312
|
+
screen.queryByText(
|
|
313
|
+
"聊天能力正在初始化。你可以先输入内容,完成后即可发送。",
|
|
314
|
+
),
|
|
315
|
+
).toBeNull();
|
|
316
|
+
});
|
|
317
|
+
|
|
291
318
|
it("does not auto-open the child-session panel until the panel is explicitly opened", () => {
|
|
292
319
|
useChatThreadStore.setState({
|
|
293
320
|
snapshot: {
|
|
@@ -495,5 +522,8 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
495
522
|
expect(screen.getByTestId("workspace-file-preview").textContent).toBe(
|
|
496
523
|
"README.md",
|
|
497
524
|
);
|
|
525
|
+
expect(screen.getByTestId("workspace-tabs-bar").className).toContain(
|
|
526
|
+
"workspace-horizontal-scrollbar",
|
|
527
|
+
);
|
|
498
528
|
});
|
|
499
529
|
});
|
|
@@ -9,35 +9,93 @@ import {
|
|
|
9
9
|
import { ChatWelcome } from "@/components/chat/ChatWelcome";
|
|
10
10
|
import { ChatSessionWorkspacePanel } from "@/components/chat/chat-session-workspace-panel";
|
|
11
11
|
import { AgentAvatar } from "@/components/common/AgentAvatar";
|
|
12
|
+
import { SessionContextIconNode } from "@/components/common/session-context-icon";
|
|
12
13
|
import { usePresenter } from "@/components/chat/presenter/chat-presenter-context";
|
|
13
14
|
import { ChatSessionHeaderActions } from "@/components/chat/session-header/chat-session-header-actions";
|
|
14
15
|
import { ChatSessionProjectBadge } from "@/components/chat/session-header/chat-session-project-badge";
|
|
15
16
|
import { useChatInputStore } from "@/components/chat/stores/chat-input.store";
|
|
16
17
|
import { useChatThreadStore } from "@/components/chat/stores/chat-thread.store";
|
|
18
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
17
19
|
import { resolveAgentRuntimeSessionType } from "@/components/chat/useChatSessionTypeState";
|
|
18
20
|
import { t } from "@/lib/i18n";
|
|
19
21
|
import { cn } from "@/lib/utils";
|
|
20
22
|
|
|
23
|
+
const CHAT_CONVERSATION_SKELETON_BUBBLES = [
|
|
24
|
+
{
|
|
25
|
+
key: "hero",
|
|
26
|
+
alignmentClassName: "justify-start",
|
|
27
|
+
bubbleClassName: "max-w-[78%] h-32 rounded-[30px]",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: "follow-up",
|
|
31
|
+
alignmentClassName: "justify-start",
|
|
32
|
+
bubbleClassName: "max-w-[62%] h-24 rounded-[28px]",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "reply",
|
|
36
|
+
alignmentClassName: "justify-end",
|
|
37
|
+
bubbleClassName: "max-w-[70%] h-24 rounded-[28px]",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
key: "detail",
|
|
41
|
+
alignmentClassName: "justify-start",
|
|
42
|
+
bubbleClassName: "max-w-[88%] h-36 rounded-[30px]",
|
|
43
|
+
},
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
21
46
|
function ChatConversationSkeleton() {
|
|
22
47
|
return (
|
|
23
|
-
<section
|
|
48
|
+
<section
|
|
49
|
+
data-testid="chat-conversation-skeleton"
|
|
50
|
+
className="flex-1 min-h-0 flex flex-col overflow-hidden bg-gradient-to-b from-gray-50/60 to-white"
|
|
51
|
+
>
|
|
24
52
|
<div className="flex-1 min-h-0 overflow-y-auto custom-scrollbar">
|
|
25
|
-
<div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
|
|
26
|
-
<div className="
|
|
27
|
-
<div className="
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
53
|
+
<div className="mx-auto flex min-h-full w-full max-w-[min(1120px,100%)] flex-col px-6 py-5">
|
|
54
|
+
<div className="flex flex-1 flex-col gap-8">
|
|
55
|
+
<div className="space-y-6">
|
|
56
|
+
<Skeleton className="h-6 w-52 rounded-lg bg-gray-200/90" />
|
|
57
|
+
<div className="space-y-5">
|
|
58
|
+
{CHAT_CONVERSATION_SKELETON_BUBBLES.map((bubble) => (
|
|
59
|
+
<div
|
|
60
|
+
key={bubble.key}
|
|
61
|
+
className={cn("flex w-full", bubble.alignmentClassName)}
|
|
62
|
+
>
|
|
63
|
+
<Skeleton
|
|
64
|
+
data-testid="chat-conversation-skeleton-bubble"
|
|
65
|
+
className={cn(
|
|
66
|
+
"w-full bg-gray-200/80",
|
|
67
|
+
bubble.bubbleClassName,
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="mt-auto grid gap-4 pb-2 sm:grid-cols-[minmax(0,1fr)_minmax(180px,240px)] sm:items-end">
|
|
75
|
+
<div className="space-y-3">
|
|
76
|
+
<Skeleton className="h-4 w-40 rounded-full bg-gray-200/70" />
|
|
77
|
+
<Skeleton className="h-[112px] w-full rounded-[30px] bg-gray-200/70" />
|
|
78
|
+
</div>
|
|
79
|
+
<div className="hidden justify-end sm:flex">
|
|
80
|
+
<Skeleton className="h-10 w-36 rounded-full bg-gray-200/75" />
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
31
83
|
</div>
|
|
32
84
|
</div>
|
|
33
85
|
</div>
|
|
34
86
|
<div className="border-t border-gray-200/80 bg-white p-4">
|
|
35
87
|
<div className="mx-auto w-full max-w-[min(1120px,100%)]">
|
|
36
|
-
<div className="rounded-2xl border border-gray-200 bg-white shadow-card
|
|
37
|
-
<div className="
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
88
|
+
<div className="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-card">
|
|
89
|
+
<div className="px-4 py-2.5">
|
|
90
|
+
<Skeleton className="h-[84px] w-full rounded-[28px] bg-gray-200/80" />
|
|
91
|
+
</div>
|
|
92
|
+
<div className="flex items-center justify-between gap-3 px-3 pb-3">
|
|
93
|
+
<div className="flex items-center gap-2">
|
|
94
|
+
<Skeleton className="h-8 w-20 rounded-full bg-gray-200/75" />
|
|
95
|
+
<Skeleton className="h-8 w-28 rounded-full bg-gray-200/75" />
|
|
96
|
+
<Skeleton className="hidden h-8 w-24 rounded-full bg-gray-200/70 sm:block" />
|
|
97
|
+
</div>
|
|
98
|
+
<Skeleton className="h-8 w-8 rounded-full bg-gray-200/85" />
|
|
41
99
|
</div>
|
|
42
100
|
</div>
|
|
43
101
|
</div>
|
|
@@ -130,7 +188,19 @@ function ChatConversationHeader({
|
|
|
130
188
|
{sessionHeaderTitle}
|
|
131
189
|
</span>
|
|
132
190
|
{snapshot.sessionTypeLabel ? (
|
|
133
|
-
<span className="shrink-0 rounded-full border border-gray-200 bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">
|
|
191
|
+
<span className="inline-flex shrink-0 items-center gap-1.5 rounded-full border border-gray-200 bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">
|
|
192
|
+
{snapshot.sessionTypeIcon?.src ? (
|
|
193
|
+
<span className="inline-flex h-[1.125rem] w-[1.125rem] items-center justify-center">
|
|
194
|
+
<SessionContextIconNode
|
|
195
|
+
icon={{
|
|
196
|
+
kind: "runtime-image",
|
|
197
|
+
src: snapshot.sessionTypeIcon.src,
|
|
198
|
+
alt: snapshot.sessionTypeIcon.alt ?? null,
|
|
199
|
+
name: snapshot.sessionTypeLabel
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
</span>
|
|
203
|
+
) : null}
|
|
134
204
|
{snapshot.sessionTypeLabel}
|
|
135
205
|
</span>
|
|
136
206
|
) : null}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
hasNcpChatModelOptions,
|
|
4
|
+
isNcpChatComposerDisabled,
|
|
5
|
+
isNcpChatModelOptionsEmpty,
|
|
6
|
+
isNcpChatModelOptionsLoading,
|
|
7
|
+
isNcpChatSendDisabled,
|
|
8
|
+
} from '@/components/chat/chat-input/ncp-chat-input-availability.utils';
|
|
9
|
+
import type { ChatInputSnapshot } from '@/components/chat/stores/chat-input.store';
|
|
10
|
+
|
|
11
|
+
function createSnapshot(
|
|
12
|
+
overrides: Partial<ChatInputSnapshot> = {}
|
|
13
|
+
): ChatInputSnapshot {
|
|
14
|
+
return {
|
|
15
|
+
isProviderStateResolved: false,
|
|
16
|
+
composerNodes: [],
|
|
17
|
+
attachments: [],
|
|
18
|
+
draft: '',
|
|
19
|
+
pendingSessionType: 'native',
|
|
20
|
+
pendingProjectRoot: null,
|
|
21
|
+
pendingProjectRootSessionKey: null,
|
|
22
|
+
defaultSessionType: 'native',
|
|
23
|
+
canStopGeneration: false,
|
|
24
|
+
stopDisabledReason: null,
|
|
25
|
+
sendError: null,
|
|
26
|
+
isSending: false,
|
|
27
|
+
modelOptions: [],
|
|
28
|
+
selectedModel: '',
|
|
29
|
+
selectedThinkingLevel: null,
|
|
30
|
+
sessionTypeOptions: [],
|
|
31
|
+
selectedSessionType: 'native',
|
|
32
|
+
stopSupported: false,
|
|
33
|
+
stopReason: undefined,
|
|
34
|
+
canEditSessionType: true,
|
|
35
|
+
sessionTypeUnavailable: false,
|
|
36
|
+
skillRecords: [],
|
|
37
|
+
isSkillsLoading: false,
|
|
38
|
+
selectedSkills: [],
|
|
39
|
+
...overrides,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('ncp-chat-input-availability.utils', () => {
|
|
44
|
+
it('keeps the composer editable during cold start while send remains blocked', () => {
|
|
45
|
+
const snapshot = createSnapshot({
|
|
46
|
+
isProviderStateResolved: false,
|
|
47
|
+
modelOptions: [],
|
|
48
|
+
sessionTypeUnavailable: false,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(hasNcpChatModelOptions(snapshot)).toBe(false);
|
|
52
|
+
expect(isNcpChatModelOptionsLoading(snapshot)).toBe(true);
|
|
53
|
+
expect(isNcpChatComposerDisabled(snapshot)).toBe(false);
|
|
54
|
+
expect(
|
|
55
|
+
isNcpChatSendDisabled({
|
|
56
|
+
snapshot,
|
|
57
|
+
hasSendableDraft: true,
|
|
58
|
+
isRuntimeBlocked: true,
|
|
59
|
+
})
|
|
60
|
+
).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('marks model options as empty only after provider state resolves', () => {
|
|
64
|
+
const loadingSnapshot = createSnapshot({
|
|
65
|
+
isProviderStateResolved: false,
|
|
66
|
+
modelOptions: [],
|
|
67
|
+
});
|
|
68
|
+
const emptySnapshot = createSnapshot({
|
|
69
|
+
isProviderStateResolved: true,
|
|
70
|
+
modelOptions: [],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(isNcpChatModelOptionsEmpty(loadingSnapshot)).toBe(false);
|
|
74
|
+
expect(isNcpChatModelOptionsEmpty(emptySnapshot)).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('disables both editing and sending when the session type is unavailable', () => {
|
|
78
|
+
const snapshot = createSnapshot({
|
|
79
|
+
isProviderStateResolved: true,
|
|
80
|
+
sessionTypeUnavailable: true,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(isNcpChatComposerDisabled(snapshot)).toBe(true);
|
|
84
|
+
expect(
|
|
85
|
+
isNcpChatSendDisabled({
|
|
86
|
+
snapshot,
|
|
87
|
+
hasSendableDraft: true,
|
|
88
|
+
isRuntimeBlocked: false,
|
|
89
|
+
})
|
|
90
|
+
).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ChatInputSnapshot } from '@/components/chat/stores/chat-input.store';
|
|
2
|
+
|
|
3
|
+
type NcpChatInputAvailabilitySnapshot = Pick<
|
|
4
|
+
ChatInputSnapshot,
|
|
5
|
+
'isProviderStateResolved' | 'modelOptions' | 'sessionTypeUnavailable'
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
export function hasNcpChatModelOptions(
|
|
9
|
+
snapshot: NcpChatInputAvailabilitySnapshot
|
|
10
|
+
): boolean {
|
|
11
|
+
return snapshot.modelOptions.length > 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isNcpChatModelOptionsLoading(
|
|
15
|
+
snapshot: NcpChatInputAvailabilitySnapshot
|
|
16
|
+
): boolean {
|
|
17
|
+
return !snapshot.isProviderStateResolved && !hasNcpChatModelOptions(snapshot);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isNcpChatModelOptionsEmpty(
|
|
21
|
+
snapshot: NcpChatInputAvailabilitySnapshot
|
|
22
|
+
): boolean {
|
|
23
|
+
return snapshot.isProviderStateResolved && !hasNcpChatModelOptions(snapshot);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isNcpChatComposerDisabled(
|
|
27
|
+
snapshot: NcpChatInputAvailabilitySnapshot
|
|
28
|
+
): boolean {
|
|
29
|
+
return snapshot.sessionTypeUnavailable;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isNcpChatSendDisabled(params: {
|
|
33
|
+
hasSendableDraft: boolean;
|
|
34
|
+
snapshot: NcpChatInputAvailabilitySnapshot;
|
|
35
|
+
isRuntimeBlocked: boolean;
|
|
36
|
+
}): boolean {
|
|
37
|
+
const { hasSendableDraft, isRuntimeBlocked, snapshot } = params;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
isRuntimeBlocked ||
|
|
41
|
+
!hasSendableDraft ||
|
|
42
|
+
!hasNcpChatModelOptions(snapshot) ||
|
|
43
|
+
snapshot.sessionTypeUnavailable
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { useEffect } from
|
|
2
|
-
import type { Dispatch, MutableRefObject, SetStateAction } from
|
|
3
|
-
import { ChatSidebar } from
|
|
4
|
-
import { ChatConversationPanel } from
|
|
5
|
-
import { AgentsPage } from
|
|
6
|
-
import { CronConfig } from
|
|
7
|
-
import { MarketplacePage } from
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
|
|
3
|
+
import { ChatSidebar } from "@/components/chat/containers/chat-sidebar";
|
|
4
|
+
import { ChatConversationPanel } from "@/components/chat/chat-conversation-panel";
|
|
5
|
+
import { AgentsPage } from "@/components/agents/agents-page";
|
|
6
|
+
import { CronConfig } from "@/components/config/CronConfig";
|
|
7
|
+
import { MarketplacePage } from "@/components/marketplace/marketplace-page";
|
|
8
8
|
|
|
9
|
-
export type MainPanelView =
|
|
9
|
+
export type MainPanelView = "chat" | "cron" | "skills" | "agents";
|
|
10
10
|
|
|
11
11
|
export type ChatPageProps = {
|
|
12
12
|
view: MainPanelView;
|
|
@@ -32,7 +32,7 @@ export function useChatSessionSync(params: UseChatSessionSyncParams): void {
|
|
|
32
32
|
} = params;
|
|
33
33
|
|
|
34
34
|
useEffect(() => {
|
|
35
|
-
if (view !==
|
|
35
|
+
if (view !== "chat") {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
if (routeSessionKey) {
|
|
@@ -45,7 +45,13 @@ export function useChatSessionSync(params: UseChatSessionSyncParams): void {
|
|
|
45
45
|
setSelectedSessionKey(null);
|
|
46
46
|
resetStreamState();
|
|
47
47
|
}
|
|
48
|
-
}, [
|
|
48
|
+
}, [
|
|
49
|
+
resetStreamState,
|
|
50
|
+
routeSessionKey,
|
|
51
|
+
selectedSessionKey,
|
|
52
|
+
setSelectedSessionKey,
|
|
53
|
+
view,
|
|
54
|
+
]);
|
|
49
55
|
|
|
50
56
|
useEffect(() => {
|
|
51
57
|
selectedSessionKeyRef.current = selectedSessionKey;
|
|
@@ -62,17 +68,17 @@ export function ChatPageLayout({ view, confirmDialog }: ChatPageLayoutProps) {
|
|
|
62
68
|
<div className="h-full flex">
|
|
63
69
|
<ChatSidebar />
|
|
64
70
|
|
|
65
|
-
{view ===
|
|
71
|
+
{view === "chat" ? (
|
|
66
72
|
<ChatConversationPanel />
|
|
67
73
|
) : (
|
|
68
74
|
<section className="flex-1 min-h-0 overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
|
|
69
|
-
{view ===
|
|
75
|
+
{view === "cron" ? (
|
|
70
76
|
<div className="h-full overflow-auto custom-scrollbar">
|
|
71
77
|
<div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
|
|
72
78
|
<CronConfig />
|
|
73
79
|
</div>
|
|
74
80
|
</div>
|
|
75
|
-
) : view ===
|
|
81
|
+
) : view === "agents" ? (
|
|
76
82
|
<div className="h-full overflow-auto custom-scrollbar">
|
|
77
83
|
<div className="mx-auto w-full max-w-[min(1180px,100%)] px-6 py-5">
|
|
78
84
|
<AgentsPage />
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { ChatSessionTypeOptionItem } from "@/components/chat/chat-session-type-option-item";
|
|
4
|
+
|
|
5
|
+
describe("ChatSessionTypeOptionItem", () => {
|
|
6
|
+
it("renders a runtime icon image when the session type option provides an app resource URI", () => {
|
|
7
|
+
render(
|
|
8
|
+
<ChatSessionTypeOptionItem
|
|
9
|
+
option={{
|
|
10
|
+
value: "codex",
|
|
11
|
+
label: "Codex",
|
|
12
|
+
icon: {
|
|
13
|
+
kind: "image",
|
|
14
|
+
src: "app://runtime-icons/codex-openai.svg",
|
|
15
|
+
alt: "Codex",
|
|
16
|
+
},
|
|
17
|
+
ready: true,
|
|
18
|
+
}}
|
|
19
|
+
onSelect={vi.fn()}
|
|
20
|
+
/>,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const runtimeIcon = screen.getByRole("img", { name: "Codex logo" });
|
|
24
|
+
expect(runtimeIcon.getAttribute("src")).toBe("/runtime-icons/codex-openai.svg");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("keeps ready options visually compact without repeating helper copy", () => {
|
|
28
|
+
render(
|
|
29
|
+
<ChatSessionTypeOptionItem
|
|
30
|
+
option={{
|
|
31
|
+
value: "claude",
|
|
32
|
+
label: "Claude",
|
|
33
|
+
icon: {
|
|
34
|
+
kind: "image",
|
|
35
|
+
src: "app://runtime-icons/claude.ico",
|
|
36
|
+
alt: "Claude",
|
|
37
|
+
},
|
|
38
|
+
ready: true,
|
|
39
|
+
}}
|
|
40
|
+
onSelect={vi.fn()}
|
|
41
|
+
/>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(screen.getAllByText("Ready")).toHaveLength(1);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ChatInputSnapshot } from "@/components/chat/stores/chat-input.store";
|
|
2
|
+
import { SessionContextIconNode } from "@/components/common/session-context-icon";
|
|
3
|
+
import { t } from "@/lib/i18n";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
type SessionTypeOption = ChatInputSnapshot["sessionTypeOptions"][number];
|
|
7
|
+
|
|
8
|
+
export function ChatSessionTypeOptionItem(props: {
|
|
9
|
+
option: SessionTypeOption;
|
|
10
|
+
onSelect: () => void;
|
|
11
|
+
}) {
|
|
12
|
+
const { option, onSelect } = props;
|
|
13
|
+
const helperText =
|
|
14
|
+
option.ready === false
|
|
15
|
+
? option.reasonMessage?.trim() || t("statusSetup")
|
|
16
|
+
: null;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
onClick={onSelect}
|
|
22
|
+
className="w-full rounded-2xl px-3 py-2.5 text-left transition-colors hover:bg-gray-50"
|
|
23
|
+
>
|
|
24
|
+
<div className="flex items-start gap-3">
|
|
25
|
+
<div className="flex min-w-0 flex-1 items-start gap-2.5">
|
|
26
|
+
{option.icon?.src ? (
|
|
27
|
+
<span className="inline-flex h-5 w-5 shrink-0 items-center justify-center pt-0.5">
|
|
28
|
+
<SessionContextIconNode
|
|
29
|
+
icon={{
|
|
30
|
+
kind: "runtime-image",
|
|
31
|
+
src: option.icon.src,
|
|
32
|
+
alt: option.icon.alt ?? null,
|
|
33
|
+
name: option.label,
|
|
34
|
+
}}
|
|
35
|
+
/>
|
|
36
|
+
</span>
|
|
37
|
+
) : null}
|
|
38
|
+
<div className="min-w-0 flex-1">
|
|
39
|
+
<div className="flex items-center justify-between gap-3">
|
|
40
|
+
<div className="truncate text-[13px] font-semibold text-gray-900">
|
|
41
|
+
{option.label}
|
|
42
|
+
</div>
|
|
43
|
+
<span
|
|
44
|
+
className={cn(
|
|
45
|
+
"inline-flex shrink-0 items-center gap-1.5 text-[11px] font-medium",
|
|
46
|
+
option.ready === false ? "text-amber-700" : "text-emerald-600",
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<span
|
|
50
|
+
className={cn(
|
|
51
|
+
"h-1.5 w-1.5 rounded-full",
|
|
52
|
+
option.ready === false ? "bg-amber-500" : "bg-emerald-500",
|
|
53
|
+
)}
|
|
54
|
+
/>
|
|
55
|
+
{option.ready === false ? t("statusSetup") : t("statusReady")}
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
{helperText ? (
|
|
59
|
+
<div className="mt-1 pr-4 text-[11px] leading-4 text-gray-500">
|
|
60
|
+
{helperText}
|
|
61
|
+
</div>
|
|
62
|
+
) : null}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</button>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -2,6 +2,7 @@ import { render, screen } from "@testing-library/react";
|
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { ChatSessionWorkspaceFilePreview } from "@/components/chat/chat-session-workspace-file-preview";
|
|
4
4
|
import type { ChatWorkspaceFileTab } from "@/components/chat/stores/chat-thread.store";
|
|
5
|
+
import { t } from "@/lib/i18n";
|
|
5
6
|
|
|
6
7
|
const serverPathReadMock = vi.fn();
|
|
7
8
|
|
|
@@ -88,4 +89,90 @@ describe("ChatSessionWorkspaceFilePreview", () => {
|
|
|
88
89
|
"workspace",
|
|
89
90
|
);
|
|
90
91
|
});
|
|
92
|
+
|
|
93
|
+
it("does not repeat the preview badge inside the workspace header", () => {
|
|
94
|
+
serverPathReadMock.mockReturnValue({
|
|
95
|
+
isLoading: false,
|
|
96
|
+
error: null,
|
|
97
|
+
data: {
|
|
98
|
+
kind: "text",
|
|
99
|
+
resolvedPath: "/tmp/example.ts",
|
|
100
|
+
text: "const answer = 42;\n",
|
|
101
|
+
truncated: false,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
render(
|
|
106
|
+
<ChatSessionWorkspaceFilePreview
|
|
107
|
+
file={buildWorkspaceFile({ viewMode: "preview" })}
|
|
108
|
+
sessionProjectRoot="/tmp"
|
|
109
|
+
onFileOpen={vi.fn()}
|
|
110
|
+
/>,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(screen.queryByText(t("chatWorkspacePreview"))).toBeNull();
|
|
114
|
+
expect(screen.getByTitle("/tmp/example.ts")).toBeTruthy();
|
|
115
|
+
expect(screen.getByText("tmp")).toBeTruthy();
|
|
116
|
+
expect(screen.getByText("example.ts")).toBeTruthy();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("renders project-relative breadcrumbs when the file is inside the workspace", () => {
|
|
120
|
+
serverPathReadMock.mockReturnValue({
|
|
121
|
+
isLoading: false,
|
|
122
|
+
error: null,
|
|
123
|
+
data: {
|
|
124
|
+
kind: "text",
|
|
125
|
+
resolvedPath: "/tmp/workspace/src/example.ts",
|
|
126
|
+
text: "const answer = 42;\n",
|
|
127
|
+
truncated: false,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
render(
|
|
132
|
+
<ChatSessionWorkspaceFilePreview
|
|
133
|
+
file={buildWorkspaceFile({ viewMode: "preview" })}
|
|
134
|
+
sessionProjectRoot="/tmp/workspace"
|
|
135
|
+
onFileOpen={vi.fn()}
|
|
136
|
+
/>,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(screen.getByText("workspace")).toBeTruthy();
|
|
140
|
+
expect(screen.getByText("src")).toBeTruthy();
|
|
141
|
+
expect(screen.getByText("example.ts")).toBeTruthy();
|
|
142
|
+
expect(
|
|
143
|
+
screen.getByTestId("workspace-file-breadcrumb-scroll").className,
|
|
144
|
+
).toContain("py-1.5");
|
|
145
|
+
expect(screen.getByTestId("workspace-file-breadcrumbs").className).toContain(
|
|
146
|
+
"workspace-horizontal-scrollbar",
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("keeps line and truncation metadata without the duplicated type badge", () => {
|
|
151
|
+
serverPathReadMock.mockReturnValue({
|
|
152
|
+
isLoading: false,
|
|
153
|
+
error: null,
|
|
154
|
+
data: {
|
|
155
|
+
kind: "text",
|
|
156
|
+
resolvedPath: "/tmp/example.ts",
|
|
157
|
+
text: "const answer = 42;\n",
|
|
158
|
+
truncated: true,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
render(
|
|
163
|
+
<ChatSessionWorkspaceFilePreview
|
|
164
|
+
file={buildWorkspaceFile({
|
|
165
|
+
viewMode: "preview",
|
|
166
|
+
line: 12,
|
|
167
|
+
column: 4,
|
|
168
|
+
})}
|
|
169
|
+
sessionProjectRoot="/tmp"
|
|
170
|
+
onFileOpen={vi.fn()}
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(screen.getByText("L12:4")).toBeTruthy();
|
|
175
|
+
expect(screen.getByText(t("chatWorkspacePreviewTruncated"))).toBeTruthy();
|
|
176
|
+
expect(screen.queryByText(t("chatWorkspacePreview"))).toBeNull();
|
|
177
|
+
});
|
|
91
178
|
});
|