@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
@@ -0,0 +1,437 @@
1
+ import type {
2
+ ChatFileOperationBlockViewModel,
3
+ } from "@nextclaw/agent-chat-ui";
4
+ import {
5
+ buildFullReplaceBlock,
6
+ buildRawPreviewBlock,
7
+ parsePatchBlocks,
8
+ type ParsedBlock,
9
+ } from "@/components/chat/adapters/chat-message.file-operation-diff";
10
+ import { readPartialJsonStringField } from "@/components/chat/adapters/chat-message.partial-json";
11
+
12
+ type ToolInvocationSource = {
13
+ toolName: string;
14
+ status?: string;
15
+ toolCallId?: string;
16
+ args?: unknown;
17
+ parsedArgs?: unknown;
18
+ result?: unknown;
19
+ };
20
+
21
+ type FileOperationCardData = {
22
+ summary?: string;
23
+ fileOperation?: {
24
+ blocks: ChatFileOperationBlockViewModel[];
25
+ };
26
+ };
27
+
28
+ const FILE_TOOL_NAMES = new Set([
29
+ "file_change",
30
+ "read_file",
31
+ "write_file",
32
+ "edit_file",
33
+ "apply_patch",
34
+ ]);
35
+
36
+ function isRecord(value: unknown): value is Record<string, unknown> {
37
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38
+ }
39
+
40
+ function readNonEmptyString(value: unknown): string | null {
41
+ if (typeof value !== "string") {
42
+ return null;
43
+ }
44
+ const trimmed = value.trim();
45
+ return trimmed.length > 0 ? trimmed : null;
46
+ }
47
+
48
+ function normalizePath(value: unknown): string | null {
49
+ if (typeof value === "string" && value.trim()) {
50
+ return value.trim();
51
+ }
52
+ return null;
53
+ }
54
+
55
+ function readRecordPayload(value: unknown): Record<string, unknown> | null {
56
+ if (isRecord(value)) {
57
+ return value;
58
+ }
59
+ if (typeof value !== "string") {
60
+ return null;
61
+ }
62
+ const trimmed = value.trim();
63
+ if (!trimmed.startsWith("{")) {
64
+ return null;
65
+ }
66
+ try {
67
+ const parsed = JSON.parse(trimmed) as unknown;
68
+ return isRecord(parsed) ? parsed : null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ function readPartialRecordPayload(value: unknown): Record<string, unknown> | null {
75
+ if (isRecord(value)) {
76
+ return value;
77
+ }
78
+ if (typeof value !== "string") {
79
+ return null;
80
+ }
81
+ const trimmed = value.trim();
82
+ if (!trimmed.startsWith("{")) {
83
+ return null;
84
+ }
85
+ const path =
86
+ readPartialJsonStringField(trimmed, [
87
+ "path",
88
+ "filePath",
89
+ "file_path",
90
+ "targetPath",
91
+ "target_path",
92
+ "filename",
93
+ "name",
94
+ ])?.value ?? null;
95
+ const content =
96
+ readPartialJsonStringField(
97
+ trimmed,
98
+ ["content", "text", "afterText", "after_text"],
99
+ )?.value ?? null;
100
+ const oldText =
101
+ readPartialJsonStringField(
102
+ trimmed,
103
+ ["oldText", "beforeText", "before_text"],
104
+ )?.value ?? null;
105
+ const newText =
106
+ readPartialJsonStringField(
107
+ trimmed,
108
+ ["newText", "afterText", "after_text"],
109
+ )?.value ?? null;
110
+ const patch =
111
+ readPartialJsonStringField(
112
+ trimmed,
113
+ ["patch", "diff", "unifiedDiff", "unified_diff"],
114
+ )?.value ?? null;
115
+
116
+ const partialRecord: Record<string, unknown> = {};
117
+ if (path) {
118
+ partialRecord.path = path;
119
+ }
120
+ if (content) {
121
+ partialRecord.content = content;
122
+ }
123
+ if (oldText) {
124
+ partialRecord.oldText = oldText;
125
+ }
126
+ if (newText) {
127
+ partialRecord.newText = newText;
128
+ }
129
+ if (patch) {
130
+ partialRecord.patch = patch;
131
+ }
132
+
133
+ return Object.keys(partialRecord).length > 0 ? partialRecord : null;
134
+ }
135
+
136
+ function readPath(record: Record<string, unknown>): string | null {
137
+ return (
138
+ normalizePath(record.path) ??
139
+ normalizePath(record.filePath) ??
140
+ normalizePath(record.file_path) ??
141
+ normalizePath(record.targetPath) ??
142
+ normalizePath(record.target_path) ??
143
+ normalizePath(record.filename) ??
144
+ normalizePath(record.name)
145
+ );
146
+ }
147
+
148
+ function readOperation(record: Record<string, unknown>): string | null {
149
+ return (
150
+ readNonEmptyString(record.operation) ??
151
+ readNonEmptyString(record.op) ??
152
+ readNonEmptyString(record.action) ??
153
+ readNonEmptyString(record.kind) ??
154
+ readNonEmptyString(record.type) ??
155
+ readNonEmptyString(record.status)
156
+ );
157
+ }
158
+
159
+ function readPatchText(record: Record<string, unknown>): string | null {
160
+ return (
161
+ readNonEmptyString(record.patch) ??
162
+ readNonEmptyString(record.diff) ??
163
+ readNonEmptyString(record.unifiedDiff) ??
164
+ readNonEmptyString(record.unified_diff)
165
+ );
166
+ }
167
+
168
+ function readBeforeText(record: Record<string, unknown>): string | null {
169
+ return (
170
+ readNonEmptyString(record.beforeText) ??
171
+ readNonEmptyString(record.before_text) ??
172
+ readNonEmptyString(record.oldText) ??
173
+ readNonEmptyString(record.old_text) ??
174
+ readNonEmptyString(record.oldContent) ??
175
+ readNonEmptyString(record.old_content) ??
176
+ readNonEmptyString(record.before) ??
177
+ readNonEmptyString(record.previous)
178
+ );
179
+ }
180
+
181
+ function readAfterText(record: Record<string, unknown>): string | null {
182
+ return (
183
+ readNonEmptyString(record.afterText) ??
184
+ readNonEmptyString(record.after_text) ??
185
+ readNonEmptyString(record.newText) ??
186
+ readNonEmptyString(record.new_text) ??
187
+ readNonEmptyString(record.newContent) ??
188
+ readNonEmptyString(record.new_content) ??
189
+ readNonEmptyString(record.content) ??
190
+ readNonEmptyString(record.text) ??
191
+ readNonEmptyString(record.after) ??
192
+ readNonEmptyString(record.updated)
193
+ );
194
+ }
195
+
196
+ function finalizeParsedBlocks(blocks: ParsedBlock[]): FileOperationCardData | null {
197
+ const normalizedBlocks = blocks
198
+ .map((block, index) => ({
199
+ key: `${block.path}-${index + 1}`,
200
+ path: block.path,
201
+ display: block.display,
202
+ ...(block.caption ? { caption: block.caption } : {}),
203
+ lines: block.lines,
204
+ ...(block.rawText ? { rawText: block.rawText } : {}),
205
+ ...(block.truncated ? { truncated: true } : {}),
206
+ }))
207
+ .filter((block) => block.lines.length > 0 || Boolean(block.rawText));
208
+
209
+ if (normalizedBlocks.length === 0) {
210
+ return null;
211
+ }
212
+
213
+ const paths = normalizedBlocks.map((block) => block.path);
214
+ const summary =
215
+ paths.length === 1
216
+ ? paths[0]
217
+ : `${paths.length} files · ${paths.slice(0, 2).join(" · ")}${paths.length > 2 ? " …" : ""}`;
218
+
219
+ return {
220
+ summary,
221
+ fileOperation: {
222
+ blocks: normalizedBlocks,
223
+ },
224
+ };
225
+ }
226
+
227
+ function buildBlockFromChangeRecord(record: Record<string, unknown>, fallbackPath: string): ParsedBlock | null {
228
+ const path = readPath(record) ?? fallbackPath;
229
+ const operation = readOperation(record);
230
+ const patchText = readPatchText(record);
231
+ if (patchText) {
232
+ const parsedBlocks = parsePatchBlocks(patchText);
233
+ if (parsedBlocks.length > 0) {
234
+ return parsedBlocks[0] ?? null;
235
+ }
236
+ }
237
+
238
+ const beforeText = readBeforeText(record);
239
+ const afterText = readAfterText(record);
240
+ if (beforeText != null || afterText != null) {
241
+ return buildFullReplaceBlock({
242
+ path,
243
+ beforeText,
244
+ afterText,
245
+ operation,
246
+ });
247
+ }
248
+
249
+ const previewText = readNonEmptyString(record.preview);
250
+ if (previewText) {
251
+ return buildRawPreviewBlock({
252
+ path,
253
+ text: previewText,
254
+ operation,
255
+ });
256
+ }
257
+
258
+ return null;
259
+ }
260
+
261
+ function buildBlocksFromChanges(changes: unknown): ParsedBlock[] {
262
+ if (!Array.isArray(changes)) {
263
+ return [];
264
+ }
265
+
266
+ const blocks: ParsedBlock[] = [];
267
+ changes.forEach((entry, index) => {
268
+ if (typeof entry === "string") {
269
+ const parsedBlocks = parsePatchBlocks(entry);
270
+ if (parsedBlocks.length > 0) {
271
+ blocks.push(...parsedBlocks);
272
+ }
273
+ return;
274
+ }
275
+ if (!isRecord(entry)) {
276
+ return;
277
+ }
278
+ const path = readPath(entry) ?? `file-${index + 1}`;
279
+ const block = buildBlockFromChangeRecord(entry, path);
280
+ if (block) {
281
+ blocks.push(block);
282
+ return;
283
+ }
284
+ const nestedChanges = Array.isArray(entry.changes) ? buildBlocksFromChanges(entry.changes) : [];
285
+ if (nestedChanges.length > 0) {
286
+ blocks.push(...nestedChanges);
287
+ }
288
+ });
289
+ return blocks;
290
+ }
291
+
292
+ function buildFileChangeCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
293
+ const sourceRecord =
294
+ readRecordPayload(invocation.result) ??
295
+ readRecordPayload(invocation.parsedArgs) ??
296
+ readRecordPayload(invocation.args);
297
+ if (!sourceRecord) {
298
+ return null;
299
+ }
300
+ const blocks = buildBlocksFromChanges(sourceRecord.changes);
301
+ return finalizeParsedBlocks(blocks);
302
+ }
303
+
304
+ function buildReadFileCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
305
+ const argsRecord =
306
+ readRecordPayload(invocation.parsedArgs) ??
307
+ readRecordPayload(invocation.args) ??
308
+ readPartialRecordPayload(invocation.args);
309
+ const path = argsRecord && readPath(argsRecord);
310
+ const content = readNonEmptyString(invocation.result);
311
+ if (!path || !content) {
312
+ return null;
313
+ }
314
+ return finalizeParsedBlocks(
315
+ [
316
+ buildRawPreviewBlock({
317
+ path,
318
+ text: content,
319
+ operation: "read",
320
+ }),
321
+ ].filter((block): block is ParsedBlock => Boolean(block)),
322
+ );
323
+ }
324
+
325
+ function buildWriteFileCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
326
+ const isStreamingPartialCall = invocation.status === "partial-call";
327
+ if (isStreamingPartialCall && typeof invocation.args === "string") {
328
+ const pathField = readPartialJsonStringField(invocation.args, [
329
+ "path",
330
+ "filePath",
331
+ "file_path",
332
+ "targetPath",
333
+ "target_path",
334
+ "filename",
335
+ "name",
336
+ ]);
337
+ const contentField = readPartialJsonStringField(
338
+ invocation.args,
339
+ ["content", "text", "afterText", "after_text"],
340
+ );
341
+ if (pathField?.value && contentField?.value) {
342
+ const previewBlock = buildRawPreviewBlock({
343
+ path: pathField.value,
344
+ text: contentField.value,
345
+ operation: "write",
346
+ });
347
+ if (previewBlock) {
348
+ return finalizeParsedBlocks([previewBlock]);
349
+ }
350
+ }
351
+ }
352
+
353
+ const argsRecord =
354
+ readRecordPayload(invocation.parsedArgs) ??
355
+ readRecordPayload(invocation.args) ??
356
+ readPartialRecordPayload(invocation.args);
357
+ if (!argsRecord) {
358
+ return null;
359
+ }
360
+ const path = readPath(argsRecord);
361
+ const content = readNonEmptyString(argsRecord.content);
362
+ if (!path || !content) {
363
+ return null;
364
+ }
365
+ return finalizeParsedBlocks(
366
+ [
367
+ buildRawPreviewBlock({
368
+ path,
369
+ text: content,
370
+ operation: "write",
371
+ }),
372
+ ].filter((block): block is ParsedBlock => Boolean(block)),
373
+ );
374
+ }
375
+
376
+ function buildEditFileCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
377
+ const argsRecord =
378
+ readRecordPayload(invocation.parsedArgs) ??
379
+ readRecordPayload(invocation.args) ??
380
+ readPartialRecordPayload(invocation.args);
381
+ if (!argsRecord) {
382
+ return null;
383
+ }
384
+ const path = readPath(argsRecord);
385
+ const beforeText = readNonEmptyString(argsRecord.oldText) ?? readNonEmptyString(argsRecord.beforeText);
386
+ const afterText = readNonEmptyString(argsRecord.newText) ?? readNonEmptyString(argsRecord.afterText);
387
+ if (!path || (beforeText == null && afterText == null)) {
388
+ return null;
389
+ }
390
+ return finalizeParsedBlocks(
391
+ [
392
+ buildFullReplaceBlock({
393
+ path,
394
+ beforeText,
395
+ afterText,
396
+ operation: "edit",
397
+ }),
398
+ ].filter((block): block is ParsedBlock => Boolean(block)),
399
+ );
400
+ }
401
+
402
+ function buildApplyPatchCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
403
+ const parsedArgsRecord = readRecordPayload(invocation.parsedArgs);
404
+ const argsRecord = readRecordPayload(invocation.args) ?? readPartialRecordPayload(invocation.args);
405
+ const patchText =
406
+ readNonEmptyString(invocation.args) ??
407
+ (parsedArgsRecord ? readNonEmptyString(parsedArgsRecord.patch) : null) ??
408
+ (argsRecord ? readNonEmptyString(argsRecord.patch) : null);
409
+ if (!patchText) {
410
+ return null;
411
+ }
412
+ return finalizeParsedBlocks(parsePatchBlocks(patchText));
413
+ }
414
+
415
+ export function buildFileOperationCardData(
416
+ invocation: ToolInvocationSource,
417
+ ): FileOperationCardData | null {
418
+ if (!FILE_TOOL_NAMES.has(invocation.toolName)) {
419
+ return null;
420
+ }
421
+ if (invocation.toolName === "file_change") {
422
+ return buildFileChangeCardData(invocation);
423
+ }
424
+ if (invocation.toolName === "read_file") {
425
+ return buildReadFileCardData(invocation);
426
+ }
427
+ if (invocation.toolName === "write_file") {
428
+ return buildWriteFileCardData(invocation);
429
+ }
430
+ if (invocation.toolName === "edit_file") {
431
+ return buildEditFileCardData(invocation);
432
+ }
433
+ if (invocation.toolName === "apply_patch") {
434
+ return buildApplyPatchCardData(invocation);
435
+ }
436
+ return null;
437
+ }