@nextclaw/ui 0.15.18 → 0.15.19
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 +21 -0
- package/dist/assets/api-O5yEgzBt.js +13 -0
- package/dist/assets/app-presenter-provider-DXTYtH78.js +106 -0
- package/dist/assets/appearance-settings-page-BuGxSvlJ.js +1 -0
- package/dist/assets/{book-open-WsEnuW9P.js → book-open-HtUSPwoS.js} +1 -1
- package/dist/assets/card-GkBc-UMw.js +1 -0
- package/dist/assets/{channels-list-page-CAWuTHSM.js → channels-list-page-Bspr1Jp4.js} +2 -2
- package/dist/assets/chat-page-BKQ89f4s.js +1 -0
- package/dist/assets/config-split-page-CvLXSFSC.js +1 -0
- package/dist/assets/confirm-dialog-BVH7haN0.js +5 -0
- package/dist/assets/desktop-update-config-C9ZUI8UW.js +1 -0
- package/dist/assets/{dist-Ca-jiFrK.js → dist-CZx9Q6jc.js} +1 -1
- package/dist/assets/doc-browser-B4l0Xu7u.js +1 -0
- package/dist/assets/doc-browser-DsVF1DrS.js +1 -0
- package/dist/assets/doc-browser-context-Dy7EhLI8.js +1 -0
- package/dist/assets/{eye-B30J471A.js → eye-DU7p1-7X.js} +1 -1
- package/dist/assets/form-actions-aM9_jSJC.js +1 -0
- package/dist/assets/index-6pHX0DZH.js +2 -0
- package/dist/assets/index-DN6KM7WD.css +1 -0
- package/dist/assets/loader-circle-Dd6jw700.js +1 -0
- package/dist/assets/logo-badge-2-iV1pkr.js +1 -0
- package/dist/assets/mcp-marketplace-page-C-d0RMfg.js +1 -0
- package/dist/assets/mcp-marketplace-page-Dg03SZX1.js +9 -0
- package/dist/assets/model-config-page-DOBqLZoQ.js +1 -0
- package/dist/assets/{navigation-link-qPPUiLt0.js → navigation-link-Ct6xtnVM.js} +1 -1
- package/dist/assets/plus-CsgeRId8.js +1 -0
- package/dist/assets/{popover-DHx55dbc.js → popover-C2oIpGBF.js} +1 -1
- package/dist/assets/provider-scoped-model-input-DDqCdnTU.js +1 -0
- package/dist/assets/{providers-config-page-BwHtHIPe.js → providers-config-page-BAVPJKpx.js} +1 -1
- package/dist/assets/react-CeMr9V52.js +8 -0
- package/dist/assets/{refresh-cw-C7bynrWX.js → refresh-cw-BZ8giusN.js} +1 -1
- package/dist/assets/remote-C-r0SHcz.js +1 -0
- package/dist/assets/rotate-cw-DVdRfMSO.js +1 -0
- package/dist/assets/{runtime-config-page-BLrTVO3n.js → runtime-config-page-D0244uxJ.js} +1 -1
- package/dist/assets/{save-CsRsFtHS.js → save-CSzFGRse.js} +1 -1
- package/dist/assets/{search-D0HtJIL-.js → search-BmSWsiG2.js} +1 -1
- package/dist/assets/{search-config-page-n1GElgij.js → search-config-page-CrL1pUzy.js} +1 -1
- package/dist/assets/{secrets-config-page-DbUc3Ggi.js → secrets-config-page-BlgsD0-L.js} +2 -2
- package/dist/assets/security-config-cDrdpMJW.js +1 -0
- package/dist/assets/{select-C1zfp9zM.js → select-CklWb6Rs.js} +1 -1
- package/dist/assets/{settings-2-Ba3cLSN2.js → settings-2-BSZf8327.js} +1 -1
- package/dist/assets/skeleton-DG9u1BmA.js +1 -0
- package/dist/assets/{tabs-DmKgMKzb.js → tabs-Cah5klko.js} +1 -1
- package/dist/assets/{tag-chip-2vwlVUwM.js → tag-chip-DcrirDgN.js} +1 -1
- package/dist/assets/{trash-2-CUpWjNHu.js → trash-2-BLQOULWD.js} +1 -1
- package/dist/assets/use-config-CuFIsUVG.js +1 -0
- package/dist/assets/use-confirm-dialog-CcySmkWp.js +1 -0
- package/dist/assets/{use-viewport-layout-DaKJ-kap.js → use-viewport-layout-DU29bUJS.js} +1 -1
- package/dist/assets/x-BVoeXny1.js +1 -0
- package/dist/index.html +28 -28
- package/package.json +9 -9
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +35 -0
- package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +3 -1
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +13 -5
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +1 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +9 -0
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +4 -1
- package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +29 -0
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +1 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime-queue.test.tsx +32 -14
- package/src/features/chat/features/workspace/components/__tests__/workspace-file-content-preview.test.tsx +41 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +4 -0
- package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +15 -2
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +11 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +1 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +11 -8
- package/src/features/chat/managers/__tests__/chat-ui.manager.test.ts +6 -0
- package/src/features/chat/managers/chat-thread.manager.ts +2 -0
- package/src/features/chat/managers/chat-ui.manager.ts +10 -4
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +4 -0
- package/src/features/chat/stores/chat-thread.store.ts +6 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +32 -0
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +2 -0
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +20 -38
- package/src/shared/components/doc-browser/types/doc-browser.types.ts +5 -0
- package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +63 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +1 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +1 -0
- package/dist/assets/api-D5UCLVE_.js +0 -13
- package/dist/assets/app-presenter-provider-De7Zwitu.js +0 -106
- package/dist/assets/appearance-settings-page-BkQaPtQ-.js +0 -1
- package/dist/assets/card-xVrS1b1T.js +0 -1
- package/dist/assets/chat-page-AvOuaxuv.js +0 -1
- package/dist/assets/config-split-page-xj0UtMow.js +0 -1
- package/dist/assets/confirm-dialog-e2YCUzt1.js +0 -5
- package/dist/assets/desktop-update-config-D5NIaimo.js +0 -1
- package/dist/assets/doc-browser-B2tNEKrJ.js +0 -1
- package/dist/assets/doc-browser-CZX6I6CU.js +0 -1
- package/dist/assets/doc-browser-context-CGz0LULR.js +0 -1
- package/dist/assets/form-actions-CnSzu3WD.js +0 -1
- package/dist/assets/index-B5AoUrMf.js +0 -2
- package/dist/assets/index-QveCjxCj.css +0 -1
- package/dist/assets/loader-circle-DjwSRQHW.js +0 -1
- package/dist/assets/logo-badge-CISsltAo.js +0 -1
- package/dist/assets/mcp-marketplace-page-BZa7p7Qf.js +0 -1
- package/dist/assets/mcp-marketplace-page-CPcPzMUQ.js +0 -9
- package/dist/assets/model-config-page-D6ex3_NM.js +0 -1
- package/dist/assets/plus-C45awBb1.js +0 -1
- package/dist/assets/provider-scoped-model-input-1TJxAhx6.js +0 -1
- package/dist/assets/react-DerwD2EA.js +0 -8
- package/dist/assets/remote-B6TXClIg.js +0 -1
- package/dist/assets/rotate-cw-Bhx0XkQP.js +0 -1
- package/dist/assets/security-config-n2WHhHXV.js +0 -1
- package/dist/assets/skeleton-DL5npsVL.js +0 -1
- package/dist/assets/use-config-BvxDWV-M.js +0 -1
- package/dist/assets/use-confirm-dialog-CeUEE4p5.js +0 -1
- package/dist/assets/x-DGdYT7rH.js +0 -1
|
@@ -31,7 +31,7 @@ export function ChatInlineFilePreview({
|
|
|
31
31
|
if (display.target.type !== "file") {
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
|
-
const { path, line, column, viewer } = display.target.payload;
|
|
34
|
+
const { path, line, column, params, viewer } = display.target.payload;
|
|
35
35
|
const file = createWorkspaceFileTab(
|
|
36
36
|
{
|
|
37
37
|
path,
|
|
@@ -40,6 +40,7 @@ export function ChatInlineFilePreview({
|
|
|
40
40
|
previewViewer: viewer,
|
|
41
41
|
line,
|
|
42
42
|
column,
|
|
43
|
+
params,
|
|
43
44
|
},
|
|
44
45
|
parentSessionKey,
|
|
45
46
|
);
|
|
@@ -58,6 +59,7 @@ export function ChatInlineFilePreview({
|
|
|
58
59
|
previewViewer,
|
|
59
60
|
line: file.line ?? undefined,
|
|
60
61
|
column: file.column ?? undefined,
|
|
62
|
+
params: file.params ?? undefined,
|
|
61
63
|
});
|
|
62
64
|
};
|
|
63
65
|
|
|
@@ -19,10 +19,11 @@ import { ChatInlineContentSurface } from "@/features/chat/features/message/compo
|
|
|
19
19
|
import { useDocBrowser } from "@/shared/components/doc-browser";
|
|
20
20
|
import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
|
|
21
21
|
import { t } from "@/shared/lib/i18n";
|
|
22
|
+
import { createUiContentParamsWindowName } from "@nextclaw/shared";
|
|
22
23
|
|
|
23
24
|
type ChatInlinePanelAppDescriptor = Pick<
|
|
24
25
|
ChatPanelAppCardViewModel,
|
|
25
|
-
"appId" | "path" | "title"
|
|
26
|
+
"appId" | "params" | "path" | "title"
|
|
26
27
|
>;
|
|
27
28
|
|
|
28
29
|
export function ChatInlinePanelAppCard({
|
|
@@ -60,21 +61,27 @@ export function ChatInlinePanelAppCard({
|
|
|
60
61
|
() =>
|
|
61
62
|
createInlinePanelAppTab({
|
|
62
63
|
appId: entry?.id ?? panelApp.appId,
|
|
64
|
+
params: panelApp.params,
|
|
63
65
|
path: panelApp.path,
|
|
64
66
|
title,
|
|
65
67
|
url,
|
|
66
|
-
|
|
67
|
-
[entry?.id, panelApp.appId, panelApp.path, title, url],
|
|
68
|
+
}),
|
|
69
|
+
[entry?.id, panelApp.appId, panelApp.params, panelApp.path, title, url],
|
|
68
70
|
);
|
|
69
|
-
const
|
|
71
|
+
const contentParamsWindowName = createUiContentParamsWindowName(panelApp.params);
|
|
72
|
+
const iframeInstanceId = `${tab.id}:${tab.navVersion}:${iframeId}:${contentParamsWindowName ?? ""}`;
|
|
70
73
|
const openExpanded = () => {
|
|
71
74
|
if (target) {
|
|
72
|
-
docBrowser.openTarget(
|
|
75
|
+
docBrowser.openTarget({
|
|
76
|
+
...target,
|
|
77
|
+
contentParams: panelApp.params,
|
|
78
|
+
});
|
|
73
79
|
return;
|
|
74
80
|
}
|
|
75
81
|
docBrowser.open(url, {
|
|
76
82
|
dedupeKey: tab.dedupeKey,
|
|
77
83
|
kind: tab.kind,
|
|
84
|
+
contentParams: panelApp.params,
|
|
78
85
|
title,
|
|
79
86
|
});
|
|
80
87
|
};
|
|
@@ -130,6 +137,7 @@ export function ChatInlinePanelAppCard({
|
|
|
130
137
|
ref={iframeRef}
|
|
131
138
|
key={iframeInstanceId}
|
|
132
139
|
src={cardUrl}
|
|
140
|
+
name={contentParamsWindowName}
|
|
133
141
|
title={title}
|
|
134
142
|
sandbox={PANEL_APP_IFRAME_SANDBOX}
|
|
135
143
|
tabIndex={0}
|
|
@@ -145,6 +145,7 @@ function renderChatInlineDisplay(display: ChatInlineDisplayViewModel) {
|
|
|
145
145
|
<ChatInlinePanelAppCard
|
|
146
146
|
panelApp={{
|
|
147
147
|
appId: display.target.payload.appId,
|
|
148
|
+
params: display.target.payload.params,
|
|
148
149
|
path: display.target.payload.path,
|
|
149
150
|
title: display.title,
|
|
150
151
|
}}
|
|
@@ -150,6 +150,9 @@ describe("buildShowContentToolCard", () => {
|
|
|
150
150
|
payload: {
|
|
151
151
|
appId: "reader",
|
|
152
152
|
path: "/tmp/reader.panel",
|
|
153
|
+
params: {
|
|
154
|
+
file: { path: "/tmp/photo.png" },
|
|
155
|
+
},
|
|
153
156
|
},
|
|
154
157
|
},
|
|
155
158
|
title: "Reader",
|
|
@@ -166,6 +169,9 @@ describe("buildShowContentToolCard", () => {
|
|
|
166
169
|
panelApp: {
|
|
167
170
|
appId: "reader",
|
|
168
171
|
path: "/tmp/reader.panel",
|
|
172
|
+
params: {
|
|
173
|
+
file: { path: "/tmp/photo.png" },
|
|
174
|
+
},
|
|
169
175
|
title: "Reader",
|
|
170
176
|
action: {
|
|
171
177
|
kind: "show-content",
|
|
@@ -176,6 +182,9 @@ describe("buildShowContentToolCard", () => {
|
|
|
176
182
|
payload: {
|
|
177
183
|
appId: "reader",
|
|
178
184
|
path: "/tmp/reader.panel",
|
|
185
|
+
params: {
|
|
186
|
+
file: { path: "/tmp/photo.png" },
|
|
187
|
+
},
|
|
179
188
|
},
|
|
180
189
|
},
|
|
181
190
|
title: "Reader",
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "@/features/right-panel-resources";
|
|
6
6
|
import { PANEL_APP_INLINE_HOST_CONTRACT } from "@nextclaw/shared";
|
|
7
7
|
import type { DocBrowserTab } from "@/shared/components/doc-browser/doc-browser-context";
|
|
8
|
+
import type { ChatContentParams } from "@nextclaw/agent-chat-ui";
|
|
8
9
|
|
|
9
10
|
export function createFallbackPanelAppContentPath(appId: string, sourcePath?: string): string {
|
|
10
11
|
return createPanelAppContentPath(appId, sourcePath);
|
|
@@ -37,11 +38,13 @@ export function readInlinePanelAppContentHeight(value: unknown): number | null {
|
|
|
37
38
|
export function createInlinePanelAppTab(params: {
|
|
38
39
|
appId: string;
|
|
39
40
|
path?: string;
|
|
41
|
+
params?: ChatContentParams;
|
|
40
42
|
title: string;
|
|
41
43
|
url: string;
|
|
42
44
|
}): DocBrowserTab {
|
|
43
|
-
const { appId, path, title, url } = params;
|
|
45
|
+
const { appId, params: contentParams, path, title, url } = params;
|
|
44
46
|
return {
|
|
47
|
+
contentParams,
|
|
45
48
|
currentUrl: url,
|
|
46
49
|
dedupeKey: `panel-app:${path ?? appId}`,
|
|
47
50
|
history: [url],
|
package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts
CHANGED
|
@@ -7,6 +7,10 @@ import type {
|
|
|
7
7
|
import type {
|
|
8
8
|
ToolCardViewSource,
|
|
9
9
|
} from "./chat-message-tool-card.utils";
|
|
10
|
+
import {
|
|
11
|
+
readUiContentParams,
|
|
12
|
+
type UiContentParams,
|
|
13
|
+
} from "@nextclaw/shared";
|
|
10
14
|
|
|
11
15
|
const SHOW_CONTENT_TOOL_NAMES = ["show_content", "show_file", "show_url", "show_panel_app"];
|
|
12
16
|
|
|
@@ -56,6 +60,14 @@ function readFileViewer(value: unknown): NonNullable<
|
|
|
56
60
|
: undefined;
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
function readContentParams(value: unknown): UiContentParams | undefined | null {
|
|
64
|
+
try {
|
|
65
|
+
return readUiContentParams(value);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
function readShowContentRequest(value: unknown): ChatUiShowContentRequest | null {
|
|
60
72
|
if (!isRecord(value) || !isRecord(value.target)) {
|
|
61
73
|
return null;
|
|
@@ -78,6 +90,16 @@ function readShowContentRequest(value: unknown): ChatUiShowContentRequest | null
|
|
|
78
90
|
if (!path) {
|
|
79
91
|
return null;
|
|
80
92
|
}
|
|
93
|
+
const params = readContentParams(payload.params);
|
|
94
|
+
if (
|
|
95
|
+
params === null ||
|
|
96
|
+
(params && (
|
|
97
|
+
readFileViewer(payload.viewer) === "source" ||
|
|
98
|
+
!/\.html?$/i.test(path)
|
|
99
|
+
))
|
|
100
|
+
) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
81
103
|
return {
|
|
82
104
|
target: {
|
|
83
105
|
type: "file",
|
|
@@ -86,6 +108,7 @@ function readShowContentRequest(value: unknown): ChatUiShowContentRequest | null
|
|
|
86
108
|
line: readOptionalPositiveInteger(payload.line),
|
|
87
109
|
column: readOptionalPositiveInteger(payload.column),
|
|
88
110
|
viewer: readFileViewer(payload.viewer),
|
|
111
|
+
params: params ?? undefined,
|
|
89
112
|
},
|
|
90
113
|
},
|
|
91
114
|
title,
|
|
@@ -116,12 +139,17 @@ function readShowContentRequest(value: unknown): ChatUiShowContentRequest | null
|
|
|
116
139
|
if (!appId) {
|
|
117
140
|
return null;
|
|
118
141
|
}
|
|
142
|
+
const params = readContentParams(payload.params);
|
|
143
|
+
if (params === null) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
119
146
|
return {
|
|
120
147
|
target: {
|
|
121
148
|
type: "panel_app",
|
|
122
149
|
payload: {
|
|
123
150
|
appId,
|
|
124
151
|
path: readOptionalString(payload.path),
|
|
152
|
+
params: params ?? undefined,
|
|
125
153
|
},
|
|
126
154
|
},
|
|
127
155
|
title,
|
|
@@ -180,6 +208,7 @@ export function buildShowContentToolCard(params: {
|
|
|
180
208
|
? {
|
|
181
209
|
appId: request.target.payload.appId,
|
|
182
210
|
path: request.target.payload.path,
|
|
211
|
+
params: request.target.payload.params,
|
|
183
212
|
title: request.title,
|
|
184
213
|
action: showContentAction,
|
|
185
214
|
}
|
|
@@ -150,6 +150,7 @@ export function buildChatMessageTexts(
|
|
|
150
150
|
copyMessageLabel: t("chatMessageCopy"),
|
|
151
151
|
copiedMessageLabel: t("chatMessageCopied"),
|
|
152
152
|
mermaidDiagramLabel: t("chatMermaidDiagram"),
|
|
153
|
+
mermaidExpandLabel: t("chatMermaidExpand"),
|
|
153
154
|
mermaidLoadingLabel: t("chatMermaidLoading"),
|
|
154
155
|
mermaidRenderErrorLabel: t("chatMermaidRenderError"),
|
|
155
156
|
typingLabel: t("chatTyping"),
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import { act, renderHook } from '@testing-library/react';
|
|
1
|
+
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
NcpEventType,
|
|
5
|
+
type NcpAgentClientEndpoint,
|
|
6
|
+
type NcpAgentSendEnvelope,
|
|
7
|
+
type NcpEndpointSubscriber,
|
|
8
|
+
type NcpMessage,
|
|
9
|
+
} from '@nextclaw/ncp';
|
|
4
10
|
import { useNcpAgentRuntime } from '@nextclaw/ncp-react';
|
|
5
11
|
import { DefaultNcpAgentConversationStateManager } from '@nextclaw/ncp-toolkit';
|
|
6
12
|
|
|
7
13
|
describe('useNcpAgentRuntime backend queue submission', () => {
|
|
8
|
-
it('
|
|
14
|
+
it('keeps a queued message out of the conversation until the backend starts it', async () => {
|
|
9
15
|
const manager = new DefaultNcpAgentConversationStateManager();
|
|
10
16
|
manager.hydrate({ sessionId: 'session-1', messages: [] });
|
|
11
17
|
await manager.dispatch({
|
|
@@ -16,18 +22,22 @@ describe('useNcpAgentRuntime backend queue submission', () => {
|
|
|
16
22
|
messageId: 'assistant-1',
|
|
17
23
|
},
|
|
18
24
|
});
|
|
19
|
-
const send = vi.fn(async () => ({
|
|
25
|
+
const send = vi.fn(async (envelope: NcpAgentSendEnvelope) => ({
|
|
20
26
|
assistantMessageId: null,
|
|
21
27
|
runId: null,
|
|
22
28
|
sessionId: 'session-1',
|
|
23
|
-
userMessageId:
|
|
29
|
+
userMessageId: envelope.message.id,
|
|
24
30
|
}));
|
|
31
|
+
let subscriber: NcpEndpointSubscriber | null = null;
|
|
25
32
|
const client = {
|
|
26
33
|
abort: vi.fn(async () => undefined),
|
|
27
34
|
send,
|
|
28
35
|
stop: vi.fn(async () => undefined),
|
|
29
36
|
stream: vi.fn(async () => undefined),
|
|
30
|
-
subscribe: vi.fn((
|
|
37
|
+
subscribe: vi.fn((listener: NcpEndpointSubscriber) => {
|
|
38
|
+
subscriber = listener;
|
|
39
|
+
return () => undefined;
|
|
40
|
+
}),
|
|
31
41
|
} as unknown as NcpAgentClientEndpoint;
|
|
32
42
|
const { result } = renderHook(() => useNcpAgentRuntime({
|
|
33
43
|
client,
|
|
@@ -46,13 +56,21 @@ describe('useNcpAgentRuntime backend queue submission', () => {
|
|
|
46
56
|
parts: [{ type: 'text', text: 'queue this' }],
|
|
47
57
|
}),
|
|
48
58
|
}));
|
|
49
|
-
expect(result.current.visibleMessages).toEqual([
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
expect(result.current.visibleMessages).toEqual([]);
|
|
60
|
+
|
|
61
|
+
const queuedMessage = send.mock.calls[0][0].message as NcpMessage;
|
|
62
|
+
await act(async () => {
|
|
63
|
+
subscriber?.({
|
|
64
|
+
type: NcpEventType.MessageSent,
|
|
65
|
+
payload: {
|
|
66
|
+
sessionId: 'session-1',
|
|
67
|
+
message: queuedMessage,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
expect(result.current.visibleMessages).toEqual([queuedMessage]);
|
|
74
|
+
});
|
|
57
75
|
});
|
|
58
76
|
});
|
|
@@ -87,3 +87,44 @@ it("reports same-origin HTML height changes without replacing the iframe", () =>
|
|
|
87
87
|
unmount();
|
|
88
88
|
expect(disconnect).toHaveBeenCalledTimes(1);
|
|
89
89
|
});
|
|
90
|
+
|
|
91
|
+
it("passes params through window.name without exposing values in the content URL", () => {
|
|
92
|
+
const { rerender } = render(
|
|
93
|
+
<WorkspaceFileContentPreview
|
|
94
|
+
contentUrl="/api/server-paths/content?path=%2Ftmp%2Fchart.html"
|
|
95
|
+
contentParams={{
|
|
96
|
+
chart: {
|
|
97
|
+
series: [3, 5, 8],
|
|
98
|
+
},
|
|
99
|
+
}}
|
|
100
|
+
kind="html"
|
|
101
|
+
label="chart.html"
|
|
102
|
+
/>,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const frame = screen.getByTestId(
|
|
106
|
+
"workspace-html-preview",
|
|
107
|
+
) as HTMLIFrameElement;
|
|
108
|
+
expect(frame.name).toContain('"series":[3,5,8]');
|
|
109
|
+
expect(frame.src).toContain("nextclawContentParams=1");
|
|
110
|
+
expect(frame.src).not.toContain("series");
|
|
111
|
+
|
|
112
|
+
rerender(
|
|
113
|
+
<WorkspaceFileContentPreview
|
|
114
|
+
contentUrl="/api/server-paths/content?path=%2Ftmp%2Fchart.html"
|
|
115
|
+
contentParams={{
|
|
116
|
+
chart: {
|
|
117
|
+
series: [13, 21, 34],
|
|
118
|
+
},
|
|
119
|
+
}}
|
|
120
|
+
kind="html"
|
|
121
|
+
label="chart.html"
|
|
122
|
+
/>,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const nextFrame = screen.getByTestId(
|
|
126
|
+
"workspace-html-preview",
|
|
127
|
+
) as HTMLIFrameElement;
|
|
128
|
+
expect(nextFrame).not.toBe(frame);
|
|
129
|
+
expect(nextFrame.name).toContain('"series":[13,21,34]');
|
|
130
|
+
});
|
package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx
CHANGED
|
@@ -185,6 +185,7 @@ function WorkspacePreviewBody({
|
|
|
185
185
|
contentUrl,
|
|
186
186
|
contentUrlKind,
|
|
187
187
|
contentLabel,
|
|
188
|
+
contentParams,
|
|
188
189
|
directoryQuery,
|
|
189
190
|
fileBasePath,
|
|
190
191
|
onFileOpen,
|
|
@@ -200,6 +201,7 @@ function WorkspacePreviewBody({
|
|
|
200
201
|
contentUrl: string | null;
|
|
201
202
|
contentUrlKind: WorkspaceFileContentKind | null;
|
|
202
203
|
contentLabel: string;
|
|
204
|
+
contentParams: ChatWorkspaceFileTab["params"];
|
|
203
205
|
directoryQuery: ReturnType<typeof useServerPathBrowse> | null | undefined;
|
|
204
206
|
fileBasePath: string | null;
|
|
205
207
|
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
@@ -216,6 +218,7 @@ function WorkspacePreviewBody({
|
|
|
216
218
|
return (
|
|
217
219
|
<WorkspaceFileContentPreview
|
|
218
220
|
contentUrl={contentUrl}
|
|
221
|
+
contentParams={contentParams ?? undefined}
|
|
219
222
|
kind={contentUrlKind}
|
|
220
223
|
label={contentLabel}
|
|
221
224
|
onHtmlContentHeightChange={onHtmlContentHeightChange}
|
|
@@ -424,6 +427,7 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
424
427
|
contentUrl={contentUrl}
|
|
425
428
|
contentUrlKind={contentUrlKind}
|
|
426
429
|
contentLabel={file.label?.trim() || resolvedPath}
|
|
430
|
+
contentParams={file.params}
|
|
427
431
|
directoryQuery={directoryQuery}
|
|
428
432
|
fileBasePath={sessionWorkingDir}
|
|
429
433
|
onFileOpen={onFileOpen}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
appendUiContentParamsBootstrapQuery,
|
|
4
|
+
createUiContentParamsWindowName,
|
|
5
|
+
readInlineContentHeight,
|
|
6
|
+
type UiContentParams,
|
|
7
|
+
} from "@nextclaw/shared";
|
|
3
8
|
import { WorkspaceDocxPreview } from "./file-content-preview/workspace-docx-preview";
|
|
4
9
|
import { WorkspacePresentationPreview } from "./file-content-preview/workspace-presentation-preview";
|
|
5
10
|
import { WorkspaceSpreadsheetPreview } from "./file-content-preview/workspace-spreadsheet-preview";
|
|
@@ -119,15 +124,18 @@ function WorkspaceUnsupportedContent({
|
|
|
119
124
|
|
|
120
125
|
function WorkspaceHtmlPreview({
|
|
121
126
|
contentUrl,
|
|
127
|
+
contentParams,
|
|
122
128
|
label,
|
|
123
129
|
onContentHeightChange,
|
|
124
130
|
}: {
|
|
125
131
|
contentUrl: string;
|
|
132
|
+
contentParams?: UiContentParams;
|
|
126
133
|
label: string;
|
|
127
134
|
onContentHeightChange?: (height: number) => void;
|
|
128
135
|
}) {
|
|
129
136
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
130
137
|
const observerCleanupRef = useRef<(() => void) | null>(null);
|
|
138
|
+
const contentParamsWindowName = createUiContentParamsWindowName(contentParams);
|
|
131
139
|
|
|
132
140
|
useEffect(
|
|
133
141
|
() => () => {
|
|
@@ -177,10 +185,12 @@ function WorkspaceHtmlPreview({
|
|
|
177
185
|
return (
|
|
178
186
|
<iframe
|
|
179
187
|
ref={iframeRef}
|
|
188
|
+
key={contentParamsWindowName ?? "no-content-params"}
|
|
180
189
|
allowFullScreen
|
|
181
190
|
className="h-full w-full border-0 bg-white"
|
|
182
191
|
data-testid="workspace-html-preview"
|
|
183
|
-
|
|
192
|
+
name={contentParamsWindowName}
|
|
193
|
+
src={appendUiContentParamsBootstrapQuery(contentUrl, contentParams)}
|
|
184
194
|
title={label}
|
|
185
195
|
onLoad={observeContentHeight}
|
|
186
196
|
/>
|
|
@@ -189,11 +199,13 @@ function WorkspaceHtmlPreview({
|
|
|
189
199
|
|
|
190
200
|
export function WorkspaceFileContentPreview({
|
|
191
201
|
contentUrl,
|
|
202
|
+
contentParams,
|
|
192
203
|
kind,
|
|
193
204
|
label,
|
|
194
205
|
onHtmlContentHeightChange,
|
|
195
206
|
}: {
|
|
196
207
|
contentUrl: string;
|
|
208
|
+
contentParams?: UiContentParams;
|
|
197
209
|
kind: WorkspaceFileContentKind;
|
|
198
210
|
label: string;
|
|
199
211
|
onHtmlContentHeightChange?: (height: number) => void;
|
|
@@ -243,6 +255,7 @@ export function WorkspaceFileContentPreview({
|
|
|
243
255
|
return (
|
|
244
256
|
<WorkspaceHtmlPreview
|
|
245
257
|
contentUrl={contentUrl}
|
|
258
|
+
contentParams={contentParams}
|
|
246
259
|
label={label}
|
|
247
260
|
onContentHeightChange={onHtmlContentHeightChange}
|
|
248
261
|
/>
|
package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChatFilePreviewViewer } from '@nextclaw/agent-chat-ui';
|
|
2
2
|
import type { ChatWorkspaceFileTab } from '@/features/chat/stores/chat-thread.store';
|
|
3
|
+
import { readUiContentParams } from '@nextclaw/shared';
|
|
3
4
|
|
|
4
5
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
5
6
|
return !!value && typeof value === 'object';
|
|
@@ -27,6 +28,14 @@ function normalizeFilePreviewViewer(value: unknown): ChatFilePreviewViewer | nul
|
|
|
27
28
|
: null;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
function normalizeContentParams(value: unknown): ChatWorkspaceFileTab['params'] {
|
|
32
|
+
try {
|
|
33
|
+
return readUiContentParams(value) ?? null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
export function normalizePersistedWorkspaceFileTab(
|
|
31
40
|
value: unknown,
|
|
32
41
|
): ChatWorkspaceFileTab | null {
|
|
@@ -52,6 +61,7 @@ export function normalizePersistedWorkspaceFileTab(
|
|
|
52
61
|
previewViewer: normalizeFilePreviewViewer(value.previewViewer),
|
|
53
62
|
line: normalizeOptionalNumber(value.line),
|
|
54
63
|
column: normalizeOptionalNumber(value.column),
|
|
64
|
+
params: normalizeContentParams(value.params),
|
|
55
65
|
rawText: normalizeOptionalText(value.rawText),
|
|
56
66
|
contentUrl: normalizeOptionalString(value.contentUrl),
|
|
57
67
|
mimeType: normalizeOptionalString(value.mimeType),
|
|
@@ -75,6 +85,7 @@ export function toPersistedWorkspaceFileTab(
|
|
|
75
85
|
previewViewer: tab.previewViewer,
|
|
76
86
|
line: tab.line,
|
|
77
87
|
column: tab.column,
|
|
88
|
+
params: tab.params,
|
|
78
89
|
rawText: tab.rawText,
|
|
79
90
|
contentUrl: tab.contentUrl,
|
|
80
91
|
mimeType: tab.mimeType,
|
|
@@ -29,6 +29,7 @@ export function createWorkspaceFileTab(
|
|
|
29
29
|
previewViewer,
|
|
30
30
|
line: action.line ?? null,
|
|
31
31
|
column: action.column ?? null,
|
|
32
|
+
params: action.params ?? null,
|
|
32
33
|
rawText: action.rawText ?? null,
|
|
33
34
|
contentUrl: action.contentUrl?.trim() || null,
|
|
34
35
|
mimeType: action.mimeType?.trim() || null,
|
|
@@ -301,13 +301,15 @@ describe('ChatThreadManager', () => {
|
|
|
301
301
|
);
|
|
302
302
|
|
|
303
303
|
manager.openFilePreview({
|
|
304
|
-
path: '
|
|
305
|
-
label: '
|
|
304
|
+
path: 'chart.html',
|
|
305
|
+
label: 'Chart',
|
|
306
306
|
viewMode: 'preview',
|
|
307
|
+
previewViewer: 'rendered',
|
|
308
|
+
params: { chart: { series: [3, 5, 8] } },
|
|
307
309
|
});
|
|
308
310
|
manager.openFilePreview({
|
|
309
|
-
path: '
|
|
310
|
-
label: '
|
|
311
|
+
path: 'chart.html',
|
|
312
|
+
label: 'Chart',
|
|
311
313
|
viewMode: 'diff',
|
|
312
314
|
beforeText: 'old\n',
|
|
313
315
|
afterText: 'new\n',
|
|
@@ -316,13 +318,14 @@ describe('ChatThreadManager', () => {
|
|
|
316
318
|
expect(useChatThreadStore.getState().snapshot.workspaceFileTabs).toEqual(
|
|
317
319
|
expect.arrayContaining([
|
|
318
320
|
expect.objectContaining({
|
|
319
|
-
key: 'parent-session-1::preview:rendered::
|
|
320
|
-
path: '
|
|
321
|
+
key: 'parent-session-1::preview:rendered::chart.html',
|
|
322
|
+
path: 'chart.html',
|
|
321
323
|
viewMode: 'preview',
|
|
324
|
+
params: { chart: { series: [3, 5, 8] } },
|
|
322
325
|
}),
|
|
323
326
|
expect.objectContaining({
|
|
324
|
-
key: 'parent-session-1::diff::
|
|
325
|
-
path: '
|
|
327
|
+
key: 'parent-session-1::diff::chart.html',
|
|
328
|
+
path: 'chart.html',
|
|
326
329
|
viewMode: 'diff',
|
|
327
330
|
}),
|
|
328
331
|
]),
|
|
@@ -88,6 +88,9 @@ describe('ChatUiManager content display', () => {
|
|
|
88
88
|
type: 'panel_app',
|
|
89
89
|
payload: {
|
|
90
90
|
appId: 'reader',
|
|
91
|
+
params: {
|
|
92
|
+
file: { path: '/tmp/photo.png' },
|
|
93
|
+
},
|
|
91
94
|
},
|
|
92
95
|
},
|
|
93
96
|
title: 'Reader',
|
|
@@ -95,6 +98,9 @@ describe('ChatUiManager content display', () => {
|
|
|
95
98
|
|
|
96
99
|
expect(docBrowserManager.openTarget).toHaveBeenCalledWith({
|
|
97
100
|
dedupeKey: 'panel-app:encoded-reader',
|
|
101
|
+
contentParams: {
|
|
102
|
+
file: { path: '/tmp/photo.png' },
|
|
103
|
+
},
|
|
98
104
|
historyPolicy: 'managed',
|
|
99
105
|
kind: 'panel-app',
|
|
100
106
|
resourceUri: 'nextclaw://panel-app/encoded-reader',
|
|
@@ -347,6 +347,7 @@ export class ChatThreadManager {
|
|
|
347
347
|
previewViewer: nextViewer,
|
|
348
348
|
line: sourceTab.line ?? undefined,
|
|
349
349
|
column: sourceTab.column ?? undefined,
|
|
350
|
+
params: sourceTab.params ?? undefined,
|
|
350
351
|
rawText: sourceTab.rawText ?? undefined,
|
|
351
352
|
contentUrl: sourceTab.contentUrl ?? undefined,
|
|
352
353
|
mimeType: sourceTab.mimeType ?? undefined,
|
|
@@ -419,6 +420,7 @@ export class ChatThreadManager {
|
|
|
419
420
|
previewViewer: request.target.payload.viewer,
|
|
420
421
|
line: request.target.payload.line,
|
|
421
422
|
column: request.target.payload.column,
|
|
423
|
+
params: request.target.payload.params,
|
|
422
424
|
});
|
|
423
425
|
return;
|
|
424
426
|
}
|
|
@@ -96,21 +96,27 @@ export class ChatUiManager {
|
|
|
96
96
|
});
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
|
-
const { appId, path } = target.payload;
|
|
99
|
+
const { appId, params: contentParams, path } = target.payload;
|
|
100
100
|
if (path) {
|
|
101
101
|
this.docBrowserManager.open(createPanelAppResourceUri(appId, path), {
|
|
102
|
+
contentParams,
|
|
102
103
|
title,
|
|
103
104
|
});
|
|
104
105
|
return;
|
|
105
106
|
}
|
|
106
107
|
const entry = await this.resolvePanelAppEntry(appId);
|
|
107
108
|
if (entry) {
|
|
108
|
-
this.docBrowserManager.openTarget(
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
this.docBrowserManager.openTarget(
|
|
110
|
+
{
|
|
111
|
+
...createPanelAppRightPanelResourceTarget(entry),
|
|
112
|
+
contentParams,
|
|
113
|
+
},
|
|
114
|
+
{ title },
|
|
115
|
+
);
|
|
111
116
|
return;
|
|
112
117
|
}
|
|
113
118
|
this.docBrowserManager.open(createPanelAppResourceUri(appId), {
|
|
119
|
+
contentParams,
|
|
114
120
|
title,
|
|
115
121
|
});
|
|
116
122
|
};
|
|
@@ -60,6 +60,7 @@ describe('chat thread workspace panel persistence', () => {
|
|
|
60
60
|
label: 'README.md',
|
|
61
61
|
viewMode: 'preview',
|
|
62
62
|
previewViewer: 'rendered',
|
|
63
|
+
params: { chart: { series: [3, 5, 8] } },
|
|
63
64
|
rawText: '# Hello',
|
|
64
65
|
fullLines: [],
|
|
65
66
|
},
|
|
@@ -98,6 +99,9 @@ describe('chat thread workspace panel persistence', () => {
|
|
|
98
99
|
expect(persisted.state.snapshot.isSending).toBeUndefined();
|
|
99
100
|
expect(persisted.state.snapshot.workspaceFileTabs[0].rawText).toBe('# Hello');
|
|
100
101
|
expect(persisted.state.snapshot.workspaceFileTabs[0].previewViewer).toBe('rendered');
|
|
102
|
+
expect(persisted.state.snapshot.workspaceFileTabs[0].params).toEqual({
|
|
103
|
+
chart: { series: [3, 5, 8] },
|
|
104
|
+
});
|
|
101
105
|
expect(persisted.state.snapshot.workspaceFileTabs[0].fullLines).toBeUndefined();
|
|
102
106
|
});
|
|
103
107
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
|
3
3
|
import type { NcpMessage } from '@nextclaw/ncp';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ChatContentParams,
|
|
6
|
+
ChatFileOperationLineViewModel,
|
|
7
|
+
ChatFilePreviewViewer,
|
|
8
|
+
} from '@nextclaw/agent-chat-ui';
|
|
5
9
|
import type {
|
|
6
10
|
SessionContextWindowView,
|
|
7
11
|
SessionTypeIconView
|
|
@@ -38,6 +42,7 @@ export type ChatWorkspaceFileTab = {
|
|
|
38
42
|
previewViewer?: ChatFilePreviewViewer | null;
|
|
39
43
|
line?: number | null;
|
|
40
44
|
column?: number | null;
|
|
45
|
+
params?: ChatContentParams | null;
|
|
41
46
|
rawText?: string | null;
|
|
42
47
|
/** Attachment/binary content URL for workspace-native media preview. */
|
|
43
48
|
contentUrl?: string | null;
|