@nextclaw/ui 0.12.30 → 0.12.32
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 +130 -0
- package/dist/assets/{api-CKKNxbsy.js → api-rASx2-N4.js} +1 -1
- package/dist/assets/{app-manager-provider-BM1hziPv.js → app-manager-provider-CUkcbdWk.js} +1 -1
- package/dist/assets/{channels-list-page-B6JASU3q.js → channels-list-page-CE_9jne_.js} +1 -1
- package/dist/assets/{chat-page-C_lQm75P.js → chat-page-BMJLtk3O.js} +1 -1
- package/dist/assets/cpu-DKc3bQic.js +3 -0
- package/dist/assets/desktop-DJpQzxsz.js +3 -0
- package/dist/assets/{desktop-update-config-jWt63bXG.js → desktop-update-config-DAD9JN9Q.js} +1 -1
- package/dist/assets/{dialog-DWZeMf9V.js → dialog-CR80q3Rm.js} +1 -1
- package/dist/assets/{dist-DN-YM_0Y.js → dist-NBjcwlsw.js} +1 -1
- package/dist/assets/doc-browser-B19cYGxa.js +1 -0
- package/dist/assets/{doc-browser-CAhfnm0D.js → doc-browser-CDBYvb8A.js} +1 -1
- package/dist/assets/doc-browser-IRh6LCzu.js +1 -0
- package/dist/assets/{doc-browser-context-FukQHvyo.js → doc-browser-context-BPeeiNJ_.js} +1 -1
- package/dist/assets/{es2015-ZJ-nWL4L.js → es2015-CVAuMqlM.js} +1 -1
- package/dist/assets/index-BEn7o-oH.js +100 -0
- package/dist/assets/index-DV57tBmg.css +1 -0
- package/dist/assets/{marketplace-page-BdiLk2eA.js → marketplace-page-B3Q_uKOZ.js} +2 -2
- package/dist/assets/marketplace-page-CCq1bFb1.js +1 -0
- package/dist/assets/{mcp-marketplace-page-DTCFgcDl.js → mcp-marketplace-page-D9maE_Sf.js} +1 -1
- package/dist/assets/mcp-marketplace-page-O5AQzBYl.js +1 -0
- package/dist/assets/{model-config-QChwQThC.js → model-config-BsEQYJvz.js} +1 -1
- package/dist/assets/{notice-card-BM2_YNoc.js → notice-card-CF4AJPM3.js} +1 -1
- package/dist/assets/{popover-2H9DS7Cv.js → popover-DeAliE_4.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-DsZJ44ZV.js → provider-scoped-model-input-B8LJ6RCt.js} +1 -1
- package/dist/assets/{providers-list-lpGJJ21M.js → providers-list-5mA-2zno.js} +1 -1
- package/dist/assets/remote-DiPWGRZc.js +1 -0
- package/dist/assets/runtime-config-page-BSoKMvCh.js +1 -0
- package/dist/assets/{search-config-CJOrq3h0.js → search-config-DxWm2GrK.js} +1 -1
- package/dist/assets/{secrets-config-C17j6mjS.js → secrets-config-qutTAlFx.js} +1 -1
- package/dist/assets/{select-C2c10F3e.js → select-HI5kiwX_.js} +1 -1
- package/dist/assets/{setting-row-CB_ZFbIZ.js → setting-row-B4QnghjV.js} +1 -1
- package/dist/assets/{tag-chip-CrQfVN3A.js → tag-chip-BPgMfp9l.js} +1 -1
- package/dist/assets/{use-config-jAYudJxy.js → use-config-BFqzVF8Y.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-zzviKbU8.js → use-infinite-scroll-loader-CyNa4Vj6.js} +1 -1
- package/dist/index.html +18 -18
- package/package.json +9 -9
- package/src/app/components/layout/app-layout.test.tsx +58 -4
- package/src/app/components/layout/app-layout.tsx +2 -2
- package/src/app/components/theme-provider.tsx +0 -1
- package/src/app/hooks/use-viewport-layout.ts +0 -1
- package/src/features/chat/components/chat-session-workspace-panel.tsx +54 -51
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +2 -1
- package/src/features/chat/managers/chat-session-list.manager.test.ts +6 -4
- package/src/features/chat/managers/chat-session-list.manager.ts +0 -6
- package/src/features/chat/utils/chat-message-invalid-tool-arguments.utils.test.ts +79 -0
- package/src/features/chat/utils/chat-message-part.utils.ts +34 -19
- package/src/main.tsx +5 -3
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +16 -4
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +56 -13
- package/src/platforms/desktop/types/desktop-update.types.ts +2 -2
- package/src/shared/components/doc-browser/doc-browser-context.tsx +0 -27
- package/src/shared/components/doc-browser/doc-browser.test.tsx +11 -9
- package/src/shared/components/doc-browser/doc-browser.tsx +37 -63
- package/src/shared/components/resizable-right-panel/resizable-right-panel.test.tsx +38 -0
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +46 -0
- package/dist/assets/cpu-DPPwMzoC.js +0 -3
- package/dist/assets/desktop-CmIbyIrg.js +0 -3
- package/dist/assets/doc-browser-p9DDNPWB.js +0 -1
- package/dist/assets/doc-browser-rZIQIjuw.js +0 -1
- package/dist/assets/index-CTfzteME.js +0 -100
- package/dist/assets/index-DKC9hvsP.css +0 -1
- package/dist/assets/marketplace-page-C3RVIckE.js +0 -1
- package/dist/assets/mcp-marketplace-page-BVCDibC7.js +0 -1
- package/dist/assets/remote-Dm6hKGew.js +0 -1
- package/dist/assets/runtime-config-page-CDEAJFfY.js +0 -1
package/dist/index.html
CHANGED
|
@@ -78,35 +78,35 @@
|
|
|
78
78
|
})();
|
|
79
79
|
</script>
|
|
80
80
|
<title>NextClaw</title>
|
|
81
|
-
<script type="module" crossorigin src="/assets/index-
|
|
81
|
+
<script type="module" crossorigin src="/assets/index-BEn7o-oH.js"></script>
|
|
82
82
|
<link rel="modulepreload" crossorigin href="/assets/i18n-D1144VAA.js">
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-DzY6wN61.js">
|
|
84
|
-
<link rel="modulepreload" crossorigin href="/assets/cpu-
|
|
85
|
-
<link rel="modulepreload" crossorigin href="/assets/api-
|
|
86
|
-
<link rel="modulepreload" crossorigin href="/assets/es2015-
|
|
87
|
-
<link rel="modulepreload" crossorigin href="/assets/select-
|
|
88
|
-
<link rel="modulepreload" crossorigin href="/assets/dist-
|
|
84
|
+
<link rel="modulepreload" crossorigin href="/assets/cpu-DKc3bQic.js">
|
|
85
|
+
<link rel="modulepreload" crossorigin href="/assets/api-rASx2-N4.js">
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/es2015-CVAuMqlM.js">
|
|
87
|
+
<link rel="modulepreload" crossorigin href="/assets/select-HI5kiwX_.js">
|
|
88
|
+
<link rel="modulepreload" crossorigin href="/assets/dist-NBjcwlsw.js">
|
|
89
89
|
<link rel="modulepreload" crossorigin href="/assets/x-BeyYA_h6.js">
|
|
90
|
-
<link rel="modulepreload" crossorigin href="/assets/dialog-
|
|
91
|
-
<link rel="modulepreload" crossorigin href="/assets/popover-
|
|
90
|
+
<link rel="modulepreload" crossorigin href="/assets/dialog-CR80q3Rm.js">
|
|
91
|
+
<link rel="modulepreload" crossorigin href="/assets/popover-DeAliE_4.js">
|
|
92
92
|
<link rel="modulepreload" crossorigin href="/assets/search-CRtQwr-h.js">
|
|
93
93
|
<link rel="modulepreload" crossorigin href="/assets/book-open-BcnAiKde.js">
|
|
94
|
+
<link rel="modulepreload" crossorigin href="/assets/external-link-CpEvG65F.js">
|
|
95
|
+
<link rel="modulepreload" crossorigin href="/assets/plus-B-YHtTNC.js">
|
|
96
|
+
<link rel="modulepreload" crossorigin href="/assets/doc-browser-context-BPeeiNJ_.js">
|
|
97
|
+
<link rel="modulepreload" crossorigin href="/assets/doc-browser-B19cYGxa.js">
|
|
94
98
|
<link rel="modulepreload" crossorigin href="/assets/download-CMM8po31.js">
|
|
95
99
|
<link rel="modulepreload" crossorigin href="/assets/key-round-DUq47t0P.js">
|
|
96
|
-
<link rel="modulepreload" crossorigin href="/assets/notice-card-
|
|
100
|
+
<link rel="modulepreload" crossorigin href="/assets/notice-card-CF4AJPM3.js">
|
|
97
101
|
<link rel="modulepreload" crossorigin href="/assets/status-dot-l3kPFdq_.js">
|
|
98
|
-
<link rel="modulepreload" crossorigin href="/assets/app-manager-provider-
|
|
102
|
+
<link rel="modulepreload" crossorigin href="/assets/app-manager-provider-CUkcbdWk.js">
|
|
99
103
|
<link rel="modulepreload" crossorigin href="/assets/settings-MWL2SMyk.js">
|
|
100
104
|
<link rel="modulepreload" crossorigin href="/assets/refresh-cw-CrbD8EkT.js">
|
|
101
105
|
<link rel="modulepreload" crossorigin href="/assets/sparkles-BmgOD4nY.js">
|
|
102
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
103
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
104
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
105
|
-
<link rel="
|
|
106
|
-
<link rel="modulepreload" crossorigin href="/assets/doc-browser-CAhfnm0D.js">
|
|
107
|
-
<link rel="modulepreload" crossorigin href="/assets/use-config-jAYudJxy.js">
|
|
108
|
-
<link rel="modulepreload" crossorigin href="/assets/desktop-CmIbyIrg.js">
|
|
109
|
-
<link rel="stylesheet" crossorigin href="/assets/index-DKC9hvsP.css">
|
|
106
|
+
<link rel="modulepreload" crossorigin href="/assets/doc-browser-CDBYvb8A.js">
|
|
107
|
+
<link rel="modulepreload" crossorigin href="/assets/use-config-BFqzVF8Y.js">
|
|
108
|
+
<link rel="modulepreload" crossorigin href="/assets/desktop-DJpQzxsz.js">
|
|
109
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DV57tBmg.css">
|
|
110
110
|
</head>
|
|
111
111
|
|
|
112
112
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ui",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.32",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
"tailwind-merge": "^2.5.4",
|
|
29
29
|
"zod": "^3.23.8",
|
|
30
30
|
"zustand": "^5.0.2",
|
|
31
|
-
"@nextclaw/agent-chat": "0.1.
|
|
32
|
-
"@nextclaw/
|
|
33
|
-
"@nextclaw/
|
|
34
|
-
"@nextclaw/ncp": "0.5.
|
|
35
|
-
"@nextclaw/ncp-http-agent-client": "0.3.
|
|
36
|
-
"@nextclaw/
|
|
37
|
-
"@nextclaw/
|
|
38
|
-
"@nextclaw/shared": "0.1.
|
|
31
|
+
"@nextclaw/agent-chat": "0.1.20",
|
|
32
|
+
"@nextclaw/agent-chat-ui": "0.3.22",
|
|
33
|
+
"@nextclaw/client-sdk": "0.1.11",
|
|
34
|
+
"@nextclaw/ncp": "0.5.15",
|
|
35
|
+
"@nextclaw/ncp-http-agent-client": "0.3.27",
|
|
36
|
+
"@nextclaw/ncp-react": "0.4.35",
|
|
37
|
+
"@nextclaw/server": "0.12.23",
|
|
38
|
+
"@nextclaw/shared": "0.1.9"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@testing-library/react": "^16.3.0",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
2
4
|
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
|
3
|
-
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
6
|
import { AppLayout } from "@/app/components/layout/app-layout";
|
|
5
7
|
import { I18nProvider } from "@/app/components/i18n-provider";
|
|
6
8
|
|
|
@@ -22,7 +24,22 @@ vi.mock("@/app/components/layout/sidebar", () => ({
|
|
|
22
24
|
),
|
|
23
25
|
}));
|
|
24
26
|
|
|
27
|
+
vi.mock("@/platforms/mobile", () => ({
|
|
28
|
+
MobileAppShell: ({ children }: { children: ReactNode }) => (
|
|
29
|
+
<div data-testid="mobile-app-shell">{children}</div>
|
|
30
|
+
),
|
|
31
|
+
}));
|
|
32
|
+
|
|
25
33
|
describe("AppLayout", () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
useViewportLayoutMock.mockReturnValue({
|
|
36
|
+
mode: "desktop",
|
|
37
|
+
isMobile: false,
|
|
38
|
+
isDesktop: true,
|
|
39
|
+
});
|
|
40
|
+
window.nextclawDesktop = undefined;
|
|
41
|
+
});
|
|
42
|
+
|
|
26
43
|
it("treats /agents as a main workspace route instead of the settings shell", () => {
|
|
27
44
|
const { container } = render(
|
|
28
45
|
<I18nProvider>
|
|
@@ -97,13 +114,50 @@ describe("AppLayout", () => {
|
|
|
97
114
|
);
|
|
98
115
|
|
|
99
116
|
expect(screen.getByTestId("chat-content")).toBeTruthy();
|
|
117
|
+
expect(screen.getByTestId("mobile-app-shell")).toBeTruthy();
|
|
100
118
|
expect(screen.queryByTestId("settings-sidebar-header")).toBeNull();
|
|
101
119
|
expect(container.querySelector("aside")).toBeNull();
|
|
120
|
+
});
|
|
102
121
|
|
|
122
|
+
it("keeps the desktop shell on desktop hosts even when the viewport is narrow", () => {
|
|
123
|
+
window.nextclawDesktop = {
|
|
124
|
+
platform: "win32",
|
|
125
|
+
} as typeof window.nextclawDesktop;
|
|
103
126
|
useViewportLayoutMock.mockReturnValue({
|
|
104
|
-
mode: "
|
|
105
|
-
isMobile:
|
|
106
|
-
isDesktop:
|
|
127
|
+
mode: "mobile",
|
|
128
|
+
isMobile: true,
|
|
129
|
+
isDesktop: false,
|
|
107
130
|
});
|
|
131
|
+
|
|
132
|
+
const queryClient = new QueryClient({
|
|
133
|
+
defaultOptions: {
|
|
134
|
+
queries: {
|
|
135
|
+
retry: false,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
render(
|
|
141
|
+
<QueryClientProvider client={queryClient}>
|
|
142
|
+
<I18nProvider>
|
|
143
|
+
<MemoryRouter initialEntries={["/chat"]}>
|
|
144
|
+
<Routes>
|
|
145
|
+
<Route
|
|
146
|
+
path="*"
|
|
147
|
+
element={
|
|
148
|
+
<AppLayout>
|
|
149
|
+
<div data-testid="chat-content">Chat Content</div>
|
|
150
|
+
</AppLayout>
|
|
151
|
+
}
|
|
152
|
+
/>
|
|
153
|
+
</Routes>
|
|
154
|
+
</MemoryRouter>
|
|
155
|
+
</I18nProvider>
|
|
156
|
+
</QueryClientProvider>,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
expect(screen.getByTestId("chat-content")).toBeTruthy();
|
|
160
|
+
expect(screen.getByTestId("desktop-window-chrome")).toBeTruthy();
|
|
161
|
+
expect(screen.getByTestId("desktop-window-chrome-main-drag-region")).toBeTruthy();
|
|
108
162
|
});
|
|
109
163
|
});
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { useDocLinkInterceptor } from "@/shared/components/doc-browser/use-doc-link-interceptor";
|
|
8
8
|
import { useI18n } from "@/app/components/i18n-provider";
|
|
9
9
|
import { useViewportLayout } from "@/app/hooks/use-viewport-layout";
|
|
10
|
-
import { DesktopAppShell } from "@/platforms/desktop";
|
|
10
|
+
import { DesktopAppShell, getDesktopHostPlatform } from "@/platforms/desktop";
|
|
11
11
|
import { MobileAppShell } from "@/platforms/mobile";
|
|
12
12
|
import { resolveUiDocumentTitle } from "@/shared/lib/ui-document-title";
|
|
13
13
|
|
|
@@ -26,7 +26,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
26
26
|
document.title = resolveUiDocumentTitle(pathname);
|
|
27
27
|
}, [pathname, language]);
|
|
28
28
|
|
|
29
|
-
if (isMobile) {
|
|
29
|
+
if (isMobile && getDesktopHostPlatform() === null) {
|
|
30
30
|
return (
|
|
31
31
|
<MobileAppShell pathname={pathname} isDocBrowserOpen={isOpen}>
|
|
32
32
|
{children}
|
|
@@ -30,6 +30,7 @@ import { usePresenter } from "@/features/chat/components/providers/chat-presente
|
|
|
30
30
|
import { ChatSessionWorkspaceFilePreview } from "./chat-session-workspace-file-preview";
|
|
31
31
|
import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
|
|
32
32
|
import { SessionCronJobContent } from "@/features/chat/components/workspace/session-cron-job-content";
|
|
33
|
+
import { ResizableRightPanel } from "@/shared/components/resizable-right-panel/resizable-right-panel";
|
|
33
34
|
import { t } from "@/shared/lib/i18n";
|
|
34
35
|
import { cn } from "@/shared/lib/utils";
|
|
35
36
|
|
|
@@ -314,63 +315,65 @@ export function ChatSessionWorkspacePanel({
|
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
return (
|
|
317
|
-
<
|
|
318
|
+
<ResizableRightPanel
|
|
318
319
|
className={cn(
|
|
319
320
|
displayMode === "overlay"
|
|
320
|
-
? "
|
|
321
|
-
: "hidden
|
|
321
|
+
? "bg-white"
|
|
322
|
+
: "hidden border-gray-200/70 bg-white/95 backdrop-blur-sm md:flex",
|
|
322
323
|
)}
|
|
324
|
+
defaultWidth={480}
|
|
325
|
+
minWidth={360}
|
|
326
|
+
maxWidth={860}
|
|
327
|
+
overlay={displayMode === "overlay"}
|
|
323
328
|
>
|
|
324
|
-
<div className="flex
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
</div>
|
|
329
|
+
<div className="flex items-center justify-between gap-3 border-b border-gray-200/70 px-4 py-2.5">
|
|
330
|
+
<button
|
|
331
|
+
type="button"
|
|
332
|
+
onClick={presenter.chatThreadManager.goToParentSession}
|
|
333
|
+
className={cn(
|
|
334
|
+
"inline-flex items-center gap-1 text-xs font-medium text-gray-600 transition-colors hover:text-gray-900",
|
|
335
|
+
!hasParentSession && "pointer-events-none opacity-0",
|
|
336
|
+
)}
|
|
337
|
+
>
|
|
338
|
+
<ArrowLeft className="h-3.5 w-3.5" />
|
|
339
|
+
<span>{t("chatBackToParent")}</span>
|
|
340
|
+
</button>
|
|
341
|
+
<button
|
|
342
|
+
type="button"
|
|
343
|
+
onClick={presenter.chatThreadManager.closeWorkspacePanel}
|
|
344
|
+
className="rounded-full border border-gray-200/80 p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
|
345
|
+
aria-label={t("chatWorkspaceClosePanel")}
|
|
346
|
+
>
|
|
347
|
+
<X className="h-4 w-4" />
|
|
348
|
+
</button>
|
|
349
|
+
</div>
|
|
346
350
|
|
|
347
|
-
|
|
351
|
+
<WorkspaceTabsBar tabs={workspaceTabs} />
|
|
348
352
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
</div>
|
|
353
|
+
<div className="flex min-h-0 flex-1 flex-col bg-white">
|
|
354
|
+
{activeSelection.kind === "child-session" ? (
|
|
355
|
+
<>
|
|
356
|
+
<WorkspaceActiveChildHeader tab={activeSelection.tab} />
|
|
357
|
+
<div className="flex-1 min-h-0">
|
|
358
|
+
<ChildSessionContent
|
|
359
|
+
sessionKey={activeSelection.tab.sessionKey}
|
|
360
|
+
onToolAction={
|
|
361
|
+
presenter.chatThreadManager.openSessionFromToolAction
|
|
362
|
+
}
|
|
363
|
+
onFileOpen={presenter.chatThreadManager.openFilePreview}
|
|
364
|
+
/>
|
|
365
|
+
</div>
|
|
366
|
+
</>
|
|
367
|
+
) : activeSelection.kind === "file" ? (
|
|
368
|
+
<ChatSessionWorkspaceFilePreview
|
|
369
|
+
file={activeSelection.file}
|
|
370
|
+
sessionProjectRoot={sessionProjectRoot}
|
|
371
|
+
onFileOpen={presenter.chatThreadManager.openFilePreview}
|
|
372
|
+
/>
|
|
373
|
+
) : (
|
|
374
|
+
<SessionCronJobContent jobs={sessionCronJobs} />
|
|
375
|
+
)}
|
|
373
376
|
</div>
|
|
374
|
-
</
|
|
377
|
+
</ResizableRightPanel>
|
|
375
378
|
);
|
|
376
379
|
}
|
|
@@ -2,7 +2,7 @@ import { render, screen } from "@testing-library/react";
|
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { ChatConversationPanel } from "@/features/chat/components/conversation/chat-conversation-panel";
|
|
5
|
-
import { ChatSessionWorkspacePanel } from "@/features/chat";
|
|
5
|
+
import { ChatSessionWorkspacePanel } from "@/features/chat/components/chat-session-workspace-panel";
|
|
6
6
|
import type { ResolvedChildSessionTab } from "@/features/chat/hooks/use-ncp-child-session-tabs-view";
|
|
7
7
|
import type { CronJobView } from "@/shared/lib/api";
|
|
8
8
|
import { useChatInputStore } from "@/features/chat/stores/chat-input.store";
|
|
@@ -556,6 +556,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
556
556
|
);
|
|
557
557
|
|
|
558
558
|
expect(screen.queryByText("Child sessions")).toBeNull();
|
|
559
|
+
expect(screen.getByTestId("resizable-right-panel-handle")).toBeTruthy();
|
|
559
560
|
expect(screen.getAllByText("北京天气")).toHaveLength(2);
|
|
560
561
|
expect(screen.getByText("Codex")).toBeTruthy();
|
|
561
562
|
expect(screen.getByText("openai/gpt-5.3-codex")).toBeTruthy();
|
|
@@ -42,8 +42,9 @@ describe('ChatSessionListManager', () => {
|
|
|
42
42
|
snapshot: {
|
|
43
43
|
...useChatThreadStore.getState().snapshot,
|
|
44
44
|
workspacePanelParentKey: 'session-1',
|
|
45
|
+
activeWorkspacePanelKind: 'child-session',
|
|
45
46
|
activeChildSessionKey: 'child-session-1',
|
|
46
|
-
activeWorkspaceFileKey:
|
|
47
|
+
activeWorkspaceFileKey: null,
|
|
47
48
|
},
|
|
48
49
|
});
|
|
49
50
|
});
|
|
@@ -155,7 +156,7 @@ describe('ChatSessionListManager', () => {
|
|
|
155
156
|
expect(useChatInputStore.getState().snapshot.pendingProjectRootSessionKey).toBeNull();
|
|
156
157
|
});
|
|
157
158
|
|
|
158
|
-
it('delegates existing-session selection to routing
|
|
159
|
+
it('delegates existing-session selection to routing while preserving workspace panel state', () => {
|
|
159
160
|
const uiManager = {
|
|
160
161
|
goToChatRoot: vi.fn(),
|
|
161
162
|
goToSession: vi.fn(),
|
|
@@ -170,8 +171,9 @@ describe('ChatSessionListManager', () => {
|
|
|
170
171
|
|
|
171
172
|
expect(uiManager.goToSession).toHaveBeenCalledWith('session-2');
|
|
172
173
|
expect(useChatSessionListStore.getState().snapshot.selectedSessionKey).toBe('session-1');
|
|
173
|
-
expect(useChatThreadStore.getState().snapshot.workspacePanelParentKey).
|
|
174
|
-
expect(useChatThreadStore.getState().snapshot.
|
|
174
|
+
expect(useChatThreadStore.getState().snapshot.workspacePanelParentKey).toBe('session-1');
|
|
175
|
+
expect(useChatThreadStore.getState().snapshot.activeWorkspacePanelKind).toBe('child-session');
|
|
176
|
+
expect(useChatThreadStore.getState().snapshot.activeChildSessionKey).toBe('child-session-1');
|
|
175
177
|
expect(useChatThreadStore.getState().snapshot.activeWorkspaceFileKey).toBeNull();
|
|
176
178
|
});
|
|
177
179
|
|
|
@@ -154,12 +154,6 @@ export class ChatSessionListManager {
|
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
selectSession = (sessionKey: string) => {
|
|
157
|
-
useChatThreadStore.getState().setSnapshot({
|
|
158
|
-
workspacePanelParentKey: null,
|
|
159
|
-
activeWorkspacePanelKind: null,
|
|
160
|
-
activeChildSessionKey: null,
|
|
161
|
-
activeWorkspaceFileKey: null,
|
|
162
|
-
});
|
|
163
157
|
this.uiManager.goToSession(sessionKey);
|
|
164
158
|
};
|
|
165
159
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ToolInvocationStatus, type UiMessage } from "@nextclaw/agent-chat";
|
|
2
|
+
import { adaptChatMessages } from "./chat-message.utils";
|
|
3
|
+
import type { ChatMessageSource } from "./chat-message.utils";
|
|
4
|
+
|
|
5
|
+
const defaultTexts = {
|
|
6
|
+
roleLabels: {
|
|
7
|
+
user: "You",
|
|
8
|
+
assistant: "Assistant",
|
|
9
|
+
tool: "Tool",
|
|
10
|
+
system: "System",
|
|
11
|
+
fallback: "Message",
|
|
12
|
+
},
|
|
13
|
+
reasoningLabel: "Reasoning",
|
|
14
|
+
toolCallLabel: "Tool Call",
|
|
15
|
+
toolResultLabel: "Tool Result",
|
|
16
|
+
toolInputLabel: "Input",
|
|
17
|
+
toolNoOutputLabel: "No output",
|
|
18
|
+
toolOutputLabel: "Output",
|
|
19
|
+
toolStatusPreparingLabel: "Preparing",
|
|
20
|
+
toolStatusRunningLabel: "Running",
|
|
21
|
+
toolStatusCompletedLabel: "Completed",
|
|
22
|
+
toolStatusFailedLabel: "Failed",
|
|
23
|
+
toolStatusCancelledLabel: "Cancelled",
|
|
24
|
+
imageAttachmentLabel: "Image attachment",
|
|
25
|
+
fileAttachmentLabel: "File attachment",
|
|
26
|
+
unknownPartLabel: "Unknown Part",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function adapt(uiMessages: UiMessage[]) {
|
|
30
|
+
return adaptChatMessages({
|
|
31
|
+
uiMessages: uiMessages as unknown as ChatMessageSource[],
|
|
32
|
+
formatTimestamp: (value) => `formatted:${value}`,
|
|
33
|
+
texts: defaultTexts,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
it("renders invalid tool arguments as a failed argument card instead of a normal raw-args card", () => {
|
|
38
|
+
const rawArgumentsText = '{"task":"spawn","request">\n<||DSML||parameter: "final_reply"}';
|
|
39
|
+
const adapted = adapt([
|
|
40
|
+
{
|
|
41
|
+
id: "assistant-invalid-tool-args",
|
|
42
|
+
role: "assistant",
|
|
43
|
+
parts: [
|
|
44
|
+
{
|
|
45
|
+
type: "tool-invocation",
|
|
46
|
+
toolInvocation: {
|
|
47
|
+
status: ToolInvocationStatus.RESULT,
|
|
48
|
+
toolCallId: "call-invalid-args",
|
|
49
|
+
toolName: "sessions_spawn",
|
|
50
|
+
args: rawArgumentsText,
|
|
51
|
+
result: {
|
|
52
|
+
ok: false,
|
|
53
|
+
error: {
|
|
54
|
+
code: "invalid_tool_arguments",
|
|
55
|
+
message: "Tool arguments are invalid.",
|
|
56
|
+
toolCallId: "call-invalid-args",
|
|
57
|
+
toolName: "sessions_spawn",
|
|
58
|
+
rawArgumentsText,
|
|
59
|
+
issues: ["Invalid JSON"],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
69
|
+
type: "tool-card",
|
|
70
|
+
card: {
|
|
71
|
+
toolName: "sessions_spawn",
|
|
72
|
+
summary: "Invalid arguments: Invalid JSON",
|
|
73
|
+
input: rawArgumentsText,
|
|
74
|
+
output: "Invalid JSON",
|
|
75
|
+
statusTone: "error",
|
|
76
|
+
statusLabel: "Failed",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -21,7 +21,6 @@ import { buildFileOperationCardData } from "./file-operation/card.utils";
|
|
|
21
21
|
import { buildSessionRequestToolCard } from "./chat-message-session-request-tool-card.utils";
|
|
22
22
|
import type {
|
|
23
23
|
ChatMessagePartViewModel,
|
|
24
|
-
ChatToolPartViewModel,
|
|
25
24
|
} from "@nextclaw/agent-chat-ui";
|
|
26
25
|
import type {
|
|
27
26
|
ChatMessageAdapterTexts,
|
|
@@ -39,6 +38,17 @@ type ChatMessagePartAdapterParams = {
|
|
|
39
38
|
texts: ChatMessageAdapterTexts;
|
|
40
39
|
};
|
|
41
40
|
|
|
41
|
+
function readInvalidToolArgumentsError(value: unknown): Record<string, unknown> | null {
|
|
42
|
+
const error = typeof value === "object" && value !== null && !Array.isArray(value)
|
|
43
|
+
? (value as { error?: unknown }).error
|
|
44
|
+
: null;
|
|
45
|
+
return typeof error === "object" &&
|
|
46
|
+
error !== null &&
|
|
47
|
+
!Array.isArray(error) &&
|
|
48
|
+
(error as { code?: unknown }).code === "invalid_tool_arguments"
|
|
49
|
+
? error as Record<string, unknown>
|
|
50
|
+
: null;
|
|
51
|
+
}
|
|
42
52
|
|
|
43
53
|
function buildReasoningPart(
|
|
44
54
|
part: Extract<ChatMessagePartSource, { type: "reasoning" }>,
|
|
@@ -113,6 +123,10 @@ function buildToolInvocationPart(
|
|
|
113
123
|
result: invocation.result,
|
|
114
124
|
texts,
|
|
115
125
|
});
|
|
126
|
+
const invalidArgsError = readInvalidToolArgumentsError(invocation.result);
|
|
127
|
+
const invalidArgsIssue = Array.isArray(invalidArgsError?.issues)
|
|
128
|
+
? invalidArgsError.issues.find((issue): issue is string => typeof issue === "string" && issue.trim().length > 0)
|
|
129
|
+
: undefined;
|
|
116
130
|
const fileOperationCardData = buildFileOperationCardData({
|
|
117
131
|
toolName: invocation.toolName,
|
|
118
132
|
status: invocation.status,
|
|
@@ -121,14 +135,14 @@ function buildToolInvocationPart(
|
|
|
121
135
|
parsedArgs: invocation.parsedArgs,
|
|
122
136
|
result: invocation.result,
|
|
123
137
|
});
|
|
124
|
-
const detail =
|
|
125
|
-
|
|
126
|
-
summarizeToolArgs(invocation.parsedArgs ?? invocation.args);
|
|
127
|
-
const input = fileOperationCardData
|
|
128
|
-
? undefined
|
|
129
|
-
: buildToolInvocationInput(invocation.args, invocation.parsedArgs);
|
|
138
|
+
const detail = invalidArgsIssue
|
|
139
|
+
? `Invalid arguments: ${invalidArgsIssue}`
|
|
140
|
+
: fileOperationCardData?.summary ?? summarizeToolArgs(invocation.parsedArgs ?? invocation.args);
|
|
141
|
+
const input = fileOperationCardData ? undefined : buildToolInvocationInput(invocation.args, invocation.parsedArgs);
|
|
130
142
|
const rawResult =
|
|
131
|
-
|
|
143
|
+
invalidArgsIssue
|
|
144
|
+
? invalidArgsIssue
|
|
145
|
+
: typeof invocation.error === "string" && invocation.error.trim()
|
|
132
146
|
? invocation.error.trim()
|
|
133
147
|
: invocation.result != null
|
|
134
148
|
? stringifyUnknown(invocation.result).trim()
|
|
@@ -149,8 +163,8 @@ function buildToolInvocationPart(
|
|
|
149
163
|
outputData: invocation.result,
|
|
150
164
|
callId: invocation.toolCallId || undefined,
|
|
151
165
|
hasResult: statusView.hasResult,
|
|
152
|
-
statusTone: statusView.statusTone,
|
|
153
|
-
statusLabel: statusView.statusLabel,
|
|
166
|
+
statusTone: invalidArgsError ? "error" : statusView.statusTone,
|
|
167
|
+
statusLabel: invalidArgsError ? texts.toolStatusFailedLabel : statusView.statusLabel,
|
|
154
168
|
...(fileOperationCardData?.fileOperation
|
|
155
169
|
? { fileOperation: fileOperationCardData.fileOperation }
|
|
156
170
|
: {}),
|
|
@@ -217,17 +231,18 @@ function isToolInvocationPart(
|
|
|
217
231
|
export function adaptChatMessagePart(
|
|
218
232
|
params: ChatMessagePartAdapterParams,
|
|
219
233
|
): ChatMessagePartViewModel | null {
|
|
220
|
-
|
|
221
|
-
|
|
234
|
+
const { inlineTokens, part, texts } = params;
|
|
235
|
+
if (isTextPart(part)) {
|
|
236
|
+
return buildTextPart(part, inlineTokens);
|
|
222
237
|
}
|
|
223
|
-
if (isReasoningPart(
|
|
224
|
-
return buildReasoningPart(
|
|
238
|
+
if (isReasoningPart(part)) {
|
|
239
|
+
return buildReasoningPart(part, texts);
|
|
225
240
|
}
|
|
226
|
-
if (isFilePart(
|
|
227
|
-
return buildFilePart(
|
|
241
|
+
if (isFilePart(part)) {
|
|
242
|
+
return buildFilePart(part, texts);
|
|
228
243
|
}
|
|
229
|
-
if (isToolInvocationPart(
|
|
230
|
-
return buildToolInvocationPart(
|
|
244
|
+
if (isToolInvocationPart(part)) {
|
|
245
|
+
return buildToolInvocationPart(part, texts);
|
|
231
246
|
}
|
|
232
|
-
return buildUnknownPart(
|
|
247
|
+
return buildUnknownPart(part, texts);
|
|
233
248
|
}
|
package/src/main.tsx
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import { BrowserRouter } from 'react-router-dom';
|
|
3
|
+
import { BrowserRouter, MemoryRouter } from 'react-router-dom';
|
|
4
4
|
import App from './app';
|
|
5
5
|
import { I18nProvider } from '@/components/providers/I18nProvider';
|
|
6
6
|
import { ThemeProvider } from '@/components/providers/ThemeProvider';
|
|
7
7
|
import './index.css';
|
|
8
8
|
|
|
9
|
+
const AppRouter = window.nextclawDesktop?.platform === 'win32' ? MemoryRouter : BrowserRouter;
|
|
10
|
+
|
|
9
11
|
createRoot(document.getElementById('root')!).render(
|
|
10
12
|
<StrictMode>
|
|
11
13
|
<ThemeProvider>
|
|
12
14
|
<I18nProvider>
|
|
13
|
-
<
|
|
15
|
+
<AppRouter>
|
|
14
16
|
<App />
|
|
15
|
-
</
|
|
17
|
+
</AppRouter>
|
|
16
18
|
</I18nProvider>
|
|
17
19
|
</ThemeProvider>
|
|
18
20
|
</StrictMode>
|
|
@@ -48,14 +48,26 @@ describe("DesktopAppShell", () => {
|
|
|
48
48
|
|
|
49
49
|
const chrome = screen.getByTestId("desktop-window-chrome");
|
|
50
50
|
const sidebarChrome = screen.getByTestId("desktop-window-chrome-sidebar");
|
|
51
|
-
const
|
|
51
|
+
const mainDragRegion = screen.getByTestId("desktop-window-chrome-main-drag-region");
|
|
52
|
+
const resizeStrip = screen.getByTestId("desktop-window-chrome-resize-strip");
|
|
53
|
+
const controls = screen.getByTestId("desktop-window-controls");
|
|
52
54
|
|
|
53
55
|
expect(chrome).toBeTruthy();
|
|
54
56
|
expect(chrome.parentElement?.style.getPropertyValue("--desktop-titlebar-height")).toBe("40px");
|
|
57
|
+
expect(chrome.className).toContain("bg-secondary");
|
|
58
|
+
expect(chrome.className).toContain("border-b");
|
|
59
|
+
expect(chrome.className).toContain("desktop-window-drag");
|
|
55
60
|
expect(sidebarChrome.className).toContain("w-[var(--desktop-sidebar-width)]");
|
|
56
|
-
expect(sidebarChrome.className).
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
61
|
+
expect(sidebarChrome.className).toContain("desktop-window-no-drag");
|
|
62
|
+
expect(mainDragRegion.className).toContain("flex-1");
|
|
63
|
+
expect(mainDragRegion.className).toContain("desktop-window-drag");
|
|
64
|
+
expect(resizeStrip.className).toContain("desktop-window-no-drag");
|
|
65
|
+
expect(resizeStrip.className).toContain("top-0");
|
|
66
|
+
expect(resizeStrip.className).toContain("h-1");
|
|
67
|
+
expect(controls.className).toContain("desktop-window-no-drag");
|
|
68
|
+
expect(screen.getByLabelText("Minimize").className).toContain("desktop-window-no-drag");
|
|
69
|
+
expect(screen.getByLabelText("Maximize").className).toContain("desktop-window-no-drag");
|
|
70
|
+
expect(screen.getByLabelText("Close").className).toContain("desktop-window-no-drag");
|
|
59
71
|
expect(screen.getByTestId("app-content")).toBeTruthy();
|
|
60
72
|
});
|
|
61
73
|
|