@nextclaw/ui 0.11.19 → 0.11.21

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/assets/{ChannelsList-DAx7wv0_.js → ChannelsList-ByHWHkQS.js} +1 -1
  3. package/dist/assets/ChatPage-FdT3pDnw.js +42 -0
  4. package/dist/assets/{DocBrowser-DKkE3Y4I.js → DocBrowser-3y_NHZ71.js} +1 -1
  5. package/dist/assets/DocBrowser-CMdPdbZj.js +1 -0
  6. package/dist/assets/{DocBrowserContext-BcZRBsCg.js → DocBrowserContext-CVJuwCcw.js} +1 -1
  7. package/dist/assets/{LogoBadge-BIPDLEwK.js → LogoBadge-D8fyilO-.js} +1 -1
  8. package/dist/assets/MarketplacePage-9oKmxN2n.js +1 -0
  9. package/dist/assets/{MarketplacePage-Dlp5BgCh.js → MarketplacePage-CmhsZXr1.js} +1 -1
  10. package/dist/assets/{McpMarketplacePage-CwKtAil8.js → McpMarketplacePage-C7PkCYbp.js} +1 -1
  11. package/dist/assets/{ModelConfig-Dg6F3Ldb.js → ModelConfig-DmCY6jWM.js} +1 -1
  12. package/dist/assets/{ProvidersList-f7bQdRxA.js → ProvidersList-ClT-34aX.js} +1 -1
  13. package/dist/assets/RemoteAccessPage-B6hUZl1O.js +1 -0
  14. package/dist/assets/{RuntimeConfig-M4OKjmgU.js → RuntimeConfig-C5aqliGk.js} +1 -1
  15. package/dist/assets/{SearchConfig-v46R5a2U.js → SearchConfig-Dm7r2yfp.js} +1 -1
  16. package/dist/assets/{SecretsConfig-CXvUpbB_.js → SecretsConfig-BBP_mbQh.js} +1 -1
  17. package/dist/assets/{SessionsConfig-7vUHMtOh.js → SessionsConfig-6wNJloZN.js} +1 -1
  18. package/dist/assets/{book-open-DzSduAaw.js → book-open-B26jGBjY.js} +1 -1
  19. package/dist/assets/{chat-session-display-CGfXhJoT.js → chat-session-display-Bjmn4aIZ.js} +1 -1
  20. package/dist/assets/{chunk-JZWAC4HX-C1vpvW4r.js → chunk-JZWAC4HX-B-4B29RN.js} +1 -1
  21. package/dist/assets/{config-Df97LeLR.js → config-BaC29Qf-.js} +1 -1
  22. package/dist/assets/{createLucideIcon-CcR5wVoU.js → createLucideIcon-DiFAvXmK.js} +1 -1
  23. package/dist/assets/{dist-BMlnBah3.js → dist-kW_O3kyZ.js} +1 -1
  24. package/dist/assets/{dist-Dii9v3X9.js → dist-pCfWPG1A.js} +1 -1
  25. package/dist/assets/{external-link-CnSDrvJE.js → external-link-D5-p-Gmm.js} +1 -1
  26. package/dist/assets/{hash-CAnX6PNt.js → hash-BlwrSV0q.js} +1 -1
  27. package/dist/assets/i18n-CSytxMFI.js +1 -0
  28. package/dist/assets/{index-BahpXJg8.css → index-CUy6doWo.css} +1 -1
  29. package/dist/assets/{index-B0DzQqwv.js → index-DvKS3L9j.js} +3 -3
  30. package/dist/assets/{label-CtIFj7_6.js → label-RyXfZqkP.js} +1 -1
  31. package/dist/assets/loader-circle-B2J777gj.js +1 -0
  32. package/dist/assets/{logos-3KFNiOej.js → logos-Bpl8QTgI.js} +1 -1
  33. package/dist/assets/{page-layout-BMwpn87D.js → page-layout--S0YBU0W.js} +1 -1
  34. package/dist/assets/plus-CM9XJ0Tf.js +1 -0
  35. package/dist/assets/{popover-BIzq25oH.js → popover-BEjfbEwy.js} +1 -1
  36. package/dist/assets/{react-ji6GGP_j.js → react-BuSP2-8B.js} +1 -1
  37. package/dist/assets/{save-CMgYkJ-y.js → save-DPPPpD_c.js} +1 -1
  38. package/dist/assets/search-Ctaw34Kp.js +1 -0
  39. package/dist/assets/{security-config-Xi5DYW7j.js → security-config-6t78Ph-I.js} +1 -1
  40. package/dist/assets/{select-Cz82gl01.js → select-CT50pzod.js} +1 -1
  41. package/dist/assets/skeleton-Bycyb0zU.js +1 -0
  42. package/dist/assets/{status-dot-C7q1HvLH.js → status-dot-BbBqRHfh.js} +1 -1
  43. package/dist/assets/{switch-DYswvkYj.js → switch-D3l6AcCk.js} +1 -1
  44. package/dist/assets/{tabs-custom-DKYQxrx1.js → tabs-custom-TZQ5WPWP.js} +1 -1
  45. package/dist/assets/{trash-2-DfXI7-ap.js → trash-2-B2_AGVE3.js} +1 -1
  46. package/dist/assets/{useConfirmDialog-CXDAxtRL.js → useConfirmDialog-BDpdjfIO.js} +1 -1
  47. package/dist/assets/{useMutation-s2sn2yzh.js → useMutation-BzCrO8j-.js} +1 -1
  48. package/dist/assets/x-CHOBE-63.js +1 -0
  49. package/dist/index.html +18 -18
  50. package/package.json +6 -6
  51. package/src/components/chat/adapters/chat-message-part.adapter.ts +74 -3
  52. package/src/components/chat/adapters/chat-message.adapter.test.ts +321 -3
  53. package/src/components/chat/adapters/chat-message.file-operation-card.ts +437 -0
  54. package/src/components/chat/adapters/chat-message.file-operation-diff.ts +408 -0
  55. package/src/components/chat/adapters/chat-message.partial-json.ts +89 -0
  56. package/src/components/chat/containers/chat-input-bar.container.tsx +8 -8
  57. package/src/components/chat/containers/chat-message-list.container.tsx +1 -0
  58. package/src/components/chat/ncp/ncp-session-adapter.test.ts +173 -0
  59. package/src/components/chat/useNcpAgentRuntime.test.tsx +90 -0
  60. package/src/lib/i18n.chat.ts +2 -1
  61. package/src/remote/remote-access-feedback.service.test.ts +18 -0
  62. package/src/remote/remote-access-feedback.service.ts +10 -1
  63. package/dist/assets/ChatPage-l2PYwCeB.js +0 -38
  64. package/dist/assets/DocBrowser-CIHLqoIm.js +0 -1
  65. package/dist/assets/MarketplacePage-TVeyVOuO.js +0 -1
  66. package/dist/assets/RemoteAccessPage-w_dY7P4T.js +0 -1
  67. package/dist/assets/i18n-CXBpwAwA.js +0 -1
  68. package/dist/assets/loader-circle-qgU4zQDw.js +0 -1
  69. package/dist/assets/plus-C9cYVbL-.js +0 -1
  70. package/dist/assets/search-sl1OeJFl.js +0 -1
  71. package/dist/assets/skeleton-rgIt7a5q.js +0 -1
  72. package/dist/assets/x-MIimOGs6.js +0 -1
@@ -3,6 +3,7 @@ import {
3
3
  adaptNcpSessionSummary,
4
4
  readNcpSessionPreferredThinking
5
5
  } from '@/components/chat/ncp/ncp-session-adapter';
6
+ import { adaptChatMessage } from '@/components/chat/adapters/chat-message.adapter';
6
7
  import type { NcpSessionSummaryView } from '@/api/types';
7
8
 
8
9
  function createSummary(partial: Partial<NcpSessionSummaryView> = {}): NcpSessionSummaryView {
@@ -79,6 +80,178 @@ describe('adaptNcpMessageToUiMessage', () => {
79
80
  }
80
81
  ]);
81
82
  });
83
+
84
+ it('keeps streamed native file tool args renderable as a preview before the tool result arrives', () => {
85
+ const uiMessage = adaptNcpMessageToUiMessage({
86
+ id: 'ncp-message-tool-1',
87
+ sessionId: 'ncp-session-1',
88
+ role: 'assistant',
89
+ status: 'streaming',
90
+ timestamp: '2026-04-01T00:00:00.000Z',
91
+ parts: [
92
+ {
93
+ type: 'tool-invocation',
94
+ toolCallId: 'tool-edit-1',
95
+ toolName: 'edit_file',
96
+ state: 'partial-call',
97
+ args: JSON.stringify({
98
+ path: 'src/app.ts',
99
+ oldText: 'const count = 1;',
100
+ newText: 'const count = 2;',
101
+ }),
102
+ },
103
+ ],
104
+ });
105
+
106
+ const adapted = adaptChatMessage(
107
+ {
108
+ id: uiMessage.id,
109
+ role: uiMessage.role,
110
+ meta: {
111
+ timestamp: uiMessage.meta?.timestamp,
112
+ status: uiMessage.meta?.status,
113
+ },
114
+ parts: uiMessage.parts as never,
115
+ },
116
+ {
117
+ formatTimestamp: (value) => value ?? '',
118
+ texts: {
119
+ roleLabels: {
120
+ user: 'User',
121
+ assistant: 'Assistant',
122
+ tool: 'Tool',
123
+ system: 'System',
124
+ fallback: 'Message',
125
+ },
126
+ reasoningLabel: 'Reasoning',
127
+ toolCallLabel: 'Tool Call',
128
+ toolResultLabel: 'Tool Result',
129
+ toolInputLabel: 'Input',
130
+ toolNoOutputLabel: 'No output',
131
+ toolOutputLabel: 'Output',
132
+ toolStatusPreparingLabel: 'Preparing',
133
+ toolStatusRunningLabel: 'Running',
134
+ toolStatusCompletedLabel: 'Completed',
135
+ toolStatusFailedLabel: 'Failed',
136
+ toolStatusCancelledLabel: 'Cancelled',
137
+ imageAttachmentLabel: 'Image',
138
+ fileAttachmentLabel: 'File',
139
+ unknownPartLabel: 'Unknown',
140
+ },
141
+ },
142
+ );
143
+
144
+ expect(adapted.parts[0]).toMatchObject({
145
+ type: 'tool-card',
146
+ card: {
147
+ toolName: 'edit_file',
148
+ summary: 'src/app.ts',
149
+ statusTone: 'running',
150
+ fileOperation: {
151
+ blocks: [
152
+ {
153
+ path: 'src/app.ts',
154
+ lines: [
155
+ {
156
+ kind: 'remove',
157
+ text: 'const count = 1;',
158
+ },
159
+ {
160
+ kind: 'add',
161
+ text: 'const count = 2;',
162
+ },
163
+ ],
164
+ },
165
+ ],
166
+ },
167
+ },
168
+ });
169
+ });
170
+
171
+ it('downgrades large streamed write_file payloads into a lightweight preview block', () => {
172
+ const largeContent = Array.from({ length: 300 }, (_, index) => `line ${index + 1}`).join('\n');
173
+ const uiMessage = adaptNcpMessageToUiMessage({
174
+ id: 'ncp-message-tool-write-1',
175
+ sessionId: 'ncp-session-1',
176
+ role: 'assistant',
177
+ status: 'streaming',
178
+ timestamp: '2026-04-01T00:00:00.000Z',
179
+ parts: [
180
+ {
181
+ type: 'tool-invocation',
182
+ toolCallId: 'tool-write-1',
183
+ toolName: 'write_file',
184
+ state: 'partial-call',
185
+ args: JSON.stringify({
186
+ path: 'src/game.html',
187
+ content: largeContent,
188
+ }),
189
+ },
190
+ ],
191
+ });
192
+
193
+ const adapted = adaptChatMessage(
194
+ {
195
+ id: uiMessage.id,
196
+ role: uiMessage.role,
197
+ meta: {
198
+ timestamp: uiMessage.meta?.timestamp,
199
+ status: uiMessage.meta?.status,
200
+ },
201
+ parts: uiMessage.parts as never,
202
+ },
203
+ {
204
+ formatTimestamp: (value) => value ?? '',
205
+ texts: {
206
+ roleLabels: {
207
+ user: 'User',
208
+ assistant: 'Assistant',
209
+ tool: 'Tool',
210
+ system: 'System',
211
+ fallback: 'Message',
212
+ },
213
+ reasoningLabel: 'Reasoning',
214
+ toolCallLabel: 'Tool Call',
215
+ toolResultLabel: 'Tool Result',
216
+ toolInputLabel: 'Input',
217
+ toolNoOutputLabel: 'No output',
218
+ toolOutputLabel: 'Output',
219
+ toolStatusPreparingLabel: 'Preparing',
220
+ toolStatusRunningLabel: 'Running',
221
+ toolStatusCompletedLabel: 'Completed',
222
+ toolStatusFailedLabel: 'Failed',
223
+ toolStatusCancelledLabel: 'Cancelled',
224
+ imageAttachmentLabel: 'Image',
225
+ fileAttachmentLabel: 'File',
226
+ unknownPartLabel: 'Unknown',
227
+ },
228
+ },
229
+ );
230
+
231
+ expect(adapted.parts[0]).toMatchObject({
232
+ type: 'tool-card',
233
+ card: {
234
+ toolName: 'write_file',
235
+ summary: 'src/game.html',
236
+ statusTone: 'running',
237
+ fileOperation: {
238
+ blocks: expect.arrayContaining([
239
+ expect.objectContaining({
240
+ path: 'src/game.html',
241
+ display: 'preview',
242
+ lines: expect.arrayContaining([
243
+ expect.objectContaining({
244
+ kind: 'add',
245
+ text: 'line 1',
246
+ newLineNumber: 1,
247
+ }),
248
+ ]),
249
+ }),
250
+ ]),
251
+ },
252
+ },
253
+ });
254
+ });
82
255
  });
83
256
 
84
257
  describe('readNcpSessionPreferredThinking', () => {
@@ -0,0 +1,90 @@
1
+ import { act, renderHook } from "@testing-library/react";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { NcpEventType, type NcpEndpointEvent } from "@nextclaw/ncp";
4
+ import { useNcpAgentRuntime } from "../../../../ncp-packages/nextclaw-ncp-react/src/hooks/use-ncp-agent-runtime.ts";
5
+
6
+ function createEvent(type: NcpEventType, delta: string): NcpEndpointEvent {
7
+ return {
8
+ type,
9
+ payload: {
10
+ sessionId: "session-1",
11
+ messageId: "assistant-1",
12
+ toolCallId: "tool-1",
13
+ delta,
14
+ },
15
+ } as NcpEndpointEvent;
16
+ }
17
+
18
+ describe("useNcpAgentRuntime", () => {
19
+ beforeEach(() => {
20
+ vi.useFakeTimers();
21
+ });
22
+
23
+ afterEach(() => {
24
+ vi.useRealTimers();
25
+ });
26
+
27
+ it("batches streamed endpoint events before dispatching them to the manager", async () => {
28
+ let subscriber: ((event: NcpEndpointEvent) => void) | null = null;
29
+ const snapshot = {
30
+ messages: [],
31
+ streamingMessage: null,
32
+ error: null,
33
+ activeRun: null,
34
+ };
35
+ const client = {
36
+ subscribe: vi.fn((callback: (event: NcpEndpointEvent) => void) => {
37
+ subscriber = callback;
38
+ return () => {
39
+ subscriber = null;
40
+ };
41
+ }),
42
+ stop: vi.fn().mockResolvedValue(undefined),
43
+ send: vi.fn().mockResolvedValue(undefined),
44
+ abort: vi.fn().mockResolvedValue(undefined),
45
+ stream: vi.fn().mockResolvedValue(undefined),
46
+ };
47
+ const manager = {
48
+ getSnapshot: vi.fn(() => snapshot),
49
+ subscribe: vi.fn(() => () => {}),
50
+ dispatch: vi.fn().mockResolvedValue(undefined),
51
+ dispatchBatch: vi.fn().mockResolvedValue(undefined),
52
+ };
53
+
54
+ renderHook(() =>
55
+ useNcpAgentRuntime({
56
+ sessionId: "session-1",
57
+ client: client as never,
58
+ manager: manager as never,
59
+ }),
60
+ );
61
+
62
+ expect(subscriber).not.toBeNull();
63
+
64
+ act(() => {
65
+ subscriber?.(createEvent(NcpEventType.MessageToolCallArgsDelta, '{"path":"src/app.ts",'));
66
+ subscriber?.(
67
+ createEvent(
68
+ NcpEventType.MessageToolCallArgsDelta,
69
+ '"content":"console.log(1);"}',
70
+ ),
71
+ );
72
+ });
73
+
74
+ expect(manager.dispatchBatch).not.toHaveBeenCalled();
75
+
76
+ await act(async () => {
77
+ vi.advanceTimersByTime(16);
78
+ await Promise.resolve();
79
+ });
80
+
81
+ expect(manager.dispatchBatch).toHaveBeenCalledTimes(1);
82
+ expect(manager.dispatchBatch).toHaveBeenCalledWith([
83
+ createEvent(NcpEventType.MessageToolCallArgsDelta, '{"path":"src/app.ts",'),
84
+ createEvent(
85
+ NcpEventType.MessageToolCallArgsDelta,
86
+ '"content":"console.log(1);"}',
87
+ ),
88
+ ]);
89
+ });
90
+ });
@@ -74,9 +74,10 @@ export const CHAT_LABELS: Record<string, { zh: string; en: string }> = {
74
74
  chatRoleMessage: { zh: '消息', en: 'Message' },
75
75
  chatToolCall: { zh: '工具调用', en: 'Tool Call' },
76
76
  chatToolResult: { zh: '工具结果', en: 'Tool Result' },
77
+ chatToolInput: { zh: '输入', en: 'Input' },
77
78
  chatToolWorkflow: { zh: '工具工作流', en: 'Tool Workflow' },
78
79
  chatToolWorkflowDetails: { zh: '展开查看参数和结果', en: 'Expand to view params and results' },
79
- chatToolOutput: { zh: '查看输出', en: 'View Output' },
80
+ chatToolOutput: { zh: '输出', en: 'Output' },
80
81
  chatToolNoOutput: { zh: '无输出(执行完成)', en: 'No output (completed)' },
81
82
  chatToolStatusPreparing: { zh: '准备中', en: 'Preparing' },
82
83
  chatToolStatusRunning: { zh: '执行中', en: 'Running' },
@@ -70,6 +70,24 @@ describe('remote-access-feedback.service', () => {
70
70
 
71
71
  expect(feedback.hero.title).toBe('远程访问暂时没有连上');
72
72
  expect(feedback.primaryAction?.kind).toBe('repair');
73
+ expect(feedback.issueHint?.body).toBe(
74
+ '远程访问暂时不可用。你可以先重新连接;如果问题持续,再重新登录或稍后再试。 (Remote relay closed unexpectedly.)'
75
+ );
76
+ });
77
+
78
+ it('keeps the generic hint unchanged when there is no runtime error detail', () => {
79
+ const status = createRemoteAccessView({
80
+ runtime: {
81
+ enabled: true,
82
+ mode: 'service',
83
+ state: 'disconnected',
84
+ lastError: null,
85
+ updatedAt: '2026-03-23T00:00:00.000Z'
86
+ }
87
+ });
88
+
89
+ const feedback = buildRemoteAccessFeedbackView(status);
90
+
73
91
  expect(feedback.issueHint?.body).toBe('远程访问暂时不可用。你可以先重新连接;如果问题持续,再重新登录或稍后再试。');
74
92
  });
75
93
  });
@@ -42,6 +42,15 @@ function readRuntimeError(status: RemoteAccessView | undefined): string {
42
42
  return status?.runtime?.lastError?.trim() || '';
43
43
  }
44
44
 
45
+ function buildRuntimeIssueHintBody(status: RemoteAccessView | undefined): string {
46
+ const genericHint = t('remoteStatusIssueDetailGeneric');
47
+ const error = readRuntimeError(status);
48
+ if (!error) {
49
+ return genericHint;
50
+ }
51
+ return `${genericHint} (${error})`;
52
+ }
53
+
45
54
  export function requiresRemoteReauthorization(status: RemoteAccessView | undefined): boolean {
46
55
  if (!status?.settings.enabled) {
47
56
  return false;
@@ -187,7 +196,7 @@ export function buildRemoteAccessFeedbackView(status: RemoteAccessView | undefin
187
196
  },
188
197
  issueHint: {
189
198
  title: t('remoteStatusRecoveryTitle'),
190
- body: t('remoteStatusIssueDetailGeneric')
199
+ body: buildRuntimeIssueHintBody(status)
191
200
  },
192
201
  shouldShowIssueHint: Boolean(status.settings.enabled && status.account.loggedIn),
193
202
  requiresReauthorization: false