@nextclaw/ui 0.11.17 → 0.11.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/assets/ChannelsList-DAx7wv0_.js +8 -0
  3. package/dist/assets/{ChatPage-C47h6sfA.js → ChatPage-l2PYwCeB.js} +9 -7
  4. package/dist/assets/DocBrowser-CIHLqoIm.js +1 -0
  5. package/dist/assets/{DocBrowser-C_C7daBv.js → DocBrowser-DKkE3Y4I.js} +1 -1
  6. package/dist/assets/{DocBrowserContext-CJ-YKtWh.js → DocBrowserContext-BcZRBsCg.js} +1 -1
  7. package/dist/assets/{LogoBadge-DRDmIa7o.js → LogoBadge-BIPDLEwK.js} +1 -1
  8. package/dist/assets/{MarketplacePage-DaSRsFUA.js → MarketplacePage-Dlp5BgCh.js} +1 -1
  9. package/dist/assets/MarketplacePage-TVeyVOuO.js +1 -0
  10. package/dist/assets/{McpMarketplacePage-B7HZn8zG.js → McpMarketplacePage-CwKtAil8.js} +1 -1
  11. package/dist/assets/{ModelConfig-MSi8VF9p.js → ModelConfig-Dg6F3Ldb.js} +1 -1
  12. package/dist/assets/{ProvidersList-_NBpSQWn.js → ProvidersList-f7bQdRxA.js} +1 -1
  13. package/dist/assets/{RemoteAccessPage-DSmdSsCJ.js → RemoteAccessPage-w_dY7P4T.js} +1 -1
  14. package/dist/assets/{RuntimeConfig-msA8NZOj.js → RuntimeConfig-M4OKjmgU.js} +1 -1
  15. package/dist/assets/{SearchConfig-BBtxHIN_.js → SearchConfig-v46R5a2U.js} +1 -1
  16. package/dist/assets/{SecretsConfig-BMAqj52o.js → SecretsConfig-CXvUpbB_.js} +1 -1
  17. package/dist/assets/{SessionsConfig-CEJqgz8F.js → SessionsConfig-7vUHMtOh.js} +1 -1
  18. package/dist/assets/{book-open-1agbn9dT.js → book-open-DzSduAaw.js} +1 -1
  19. package/dist/assets/{chat-session-display-DBBUJOYN.js → chat-session-display-CGfXhJoT.js} +1 -1
  20. package/dist/assets/{chunk-JZWAC4HX-BUooP92l.js → chunk-JZWAC4HX-C1vpvW4r.js} +1 -1
  21. package/dist/assets/{config-jOAXZWun.js → config-Df97LeLR.js} +1 -1
  22. package/dist/assets/{createLucideIcon-B8FV3fzy.js → createLucideIcon-CcR5wVoU.js} +1 -1
  23. package/dist/assets/{dist-D3OJg9V0.js → dist-BMlnBah3.js} +1 -1
  24. package/dist/assets/{dist-Cy668qFZ.js → dist-Dii9v3X9.js} +1 -1
  25. package/dist/assets/{external-link-DI4ZmR3r.js → external-link-CnSDrvJE.js} +1 -1
  26. package/dist/assets/{hash-DoXBhX9w.js → hash-CAnX6PNt.js} +1 -1
  27. package/dist/assets/i18n-CXBpwAwA.js +1 -0
  28. package/dist/assets/{index-bAeWRAyo.js → index-B0DzQqwv.js} +3 -3
  29. package/dist/assets/index-BahpXJg8.css +1 -0
  30. package/dist/assets/{label-Cz0q8fx4.js → label-CtIFj7_6.js} +1 -1
  31. package/dist/assets/loader-circle-qgU4zQDw.js +1 -0
  32. package/dist/assets/{logos-DjrINZ7P.js → logos-3KFNiOej.js} +1 -1
  33. package/dist/assets/{page-layout-Hr-Dvq4o.js → page-layout-BMwpn87D.js} +1 -1
  34. package/dist/assets/plus-C9cYVbL-.js +1 -0
  35. package/dist/assets/{popover-_nEUAtWY.js → popover-BIzq25oH.js} +1 -1
  36. package/dist/assets/{react-Bsr_GLhi.js → react-ji6GGP_j.js} +1 -1
  37. package/dist/assets/{save-Caodcm4q.js → save-CMgYkJ-y.js} +1 -1
  38. package/dist/assets/search-sl1OeJFl.js +1 -0
  39. package/dist/assets/{security-config-Zf1RBeS1.js → security-config-Xi5DYW7j.js} +1 -1
  40. package/dist/assets/{select-D60QRHg9.js → select-Cz82gl01.js} +1 -1
  41. package/dist/assets/skeleton-rgIt7a5q.js +1 -0
  42. package/dist/assets/{status-dot-D43lBF1a.js → status-dot-C7q1HvLH.js} +1 -1
  43. package/dist/assets/{switch-CcBS0F3U.js → switch-DYswvkYj.js} +1 -1
  44. package/dist/assets/{tabs-custom-UTbefkqB.js → tabs-custom-DKYQxrx1.js} +1 -1
  45. package/dist/assets/{trash-2-DvPrU1xO.js → trash-2-DfXI7-ap.js} +1 -1
  46. package/dist/assets/{useConfirmDialog-B89bxcd6.js → useConfirmDialog-CXDAxtRL.js} +1 -1
  47. package/dist/assets/{useMutation-BpXHE2OV.js → useMutation-s2sn2yzh.js} +1 -1
  48. package/dist/assets/x-MIimOGs6.js +1 -0
  49. package/dist/index.html +18 -18
  50. package/package.json +6 -6
  51. package/src/components/chat/ChatConversationPanel.tsx +1 -0
  52. package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +103 -1
  53. package/src/components/chat/adapters/chat-input-bar.adapter.ts +80 -2
  54. package/src/components/chat/adapters/chat-message-inline-content.adapter.ts +95 -0
  55. package/src/components/chat/adapters/chat-message-part.adapter.ts +384 -0
  56. package/src/components/chat/adapters/chat-message.adapter.test.ts +49 -2
  57. package/src/components/chat/adapters/chat-message.adapter.ts +18 -366
  58. package/src/components/chat/adapters/chat-message.subagent-tool-card.ts +46 -17
  59. package/src/components/chat/chat-composer-state.test.ts +2 -6
  60. package/src/components/chat/chat-composer-state.ts +27 -6
  61. package/src/components/chat/chat-inline-token.utils.test.ts +87 -0
  62. package/src/components/chat/chat-inline-token.utils.ts +146 -0
  63. package/src/components/chat/chat-input/chat-input-bar.controller.test.tsx +24 -0
  64. package/src/components/chat/chat-input/chat-input-bar.controller.ts +81 -44
  65. package/src/components/chat/chat-recent-skills.manager.ts +8 -0
  66. package/src/components/chat/containers/chat-input-bar.container.tsx +31 -4
  67. package/src/components/chat/containers/chat-message-list.container.test.tsx +45 -0
  68. package/src/components/chat/containers/chat-message-list.container.tsx +11 -5
  69. package/src/components/chat/ncp/NcpChatPage.tsx +10 -1
  70. package/src/components/chat/ncp/ncp-chat-input.manager.ts +18 -4
  71. package/src/components/chat/presenter/chat-presenter-context.tsx +1 -0
  72. package/src/components/config/ChannelForm.tsx +71 -39
  73. package/src/components/config/channel-form-fields.test.ts +28 -0
  74. package/src/components/config/channel-form-fields.ts +95 -30
  75. package/src/components/config/weixin-channel-auth-section.test.tsx +26 -0
  76. package/src/components/config/weixin-channel-auth-section.tsx +6 -2
  77. package/src/lib/i18n.channel-auth.ts +5 -0
  78. package/dist/assets/ChannelsList-askIl_uW.js +0 -8
  79. package/dist/assets/DocBrowser-Cf7uSIoM.js +0 -1
  80. package/dist/assets/MarketplacePage-q12sRrvZ.js +0 -1
  81. package/dist/assets/i18n-Cn8SErDV.js +0 -1
  82. package/dist/assets/index-B2VeWxfm.css +0 -1
  83. package/dist/assets/loader-circle-d_mzMi2S.js +0 -1
  84. package/dist/assets/plus-BnGg0mB-.js +0 -1
  85. package/dist/assets/search-CQCQaN4Z.js +0 -1
  86. package/dist/assets/skeleton-BvV_2nf3.js +0 -1
  87. package/dist/assets/x-C8AWDn7c.js +0 -1
@@ -0,0 +1,384 @@
1
+ import {
2
+ stringifyUnknown,
3
+ summarizeToolArgs,
4
+ type ToolCard,
5
+ } from "@/lib/chat-message";
6
+ import {
7
+ type ChatInlineTokenSource,
8
+ } from "@/components/chat/chat-inline-token.utils";
9
+ import {
10
+ buildRenderableText,
11
+ buildTextPart,
12
+ } from "@/components/chat/adapters/chat-message-inline-content.adapter";
13
+ import { buildSubagentToolCard } from "@/components/chat/adapters/chat-message.subagent-tool-card";
14
+ import type {
15
+ ChatMessagePartViewModel,
16
+ ChatToolPartViewModel,
17
+ } from "@nextclaw/agent-chat-ui";
18
+
19
+ export type ChatMessageAdapterTexts = {
20
+ roleLabels: {
21
+ user: string;
22
+ assistant: string;
23
+ tool: string;
24
+ system: string;
25
+ fallback: string;
26
+ };
27
+ reasoningLabel: string;
28
+ toolCallLabel: string;
29
+ toolResultLabel: string;
30
+ toolNoOutputLabel: string;
31
+ toolOutputLabel: string;
32
+ toolStatusPreparingLabel: string;
33
+ toolStatusRunningLabel: string;
34
+ toolStatusCompletedLabel: string;
35
+ toolStatusFailedLabel: string;
36
+ toolStatusCancelledLabel: string;
37
+ imageAttachmentLabel: string;
38
+ fileAttachmentLabel: string;
39
+ unknownPartLabel: string;
40
+ };
41
+
42
+ export type ChatMessagePartSource =
43
+ | {
44
+ type: "text";
45
+ text: string;
46
+ }
47
+ | {
48
+ type: "file";
49
+ mimeType: string;
50
+ data: string;
51
+ url?: string;
52
+ name?: string;
53
+ sizeBytes?: number;
54
+ }
55
+ | {
56
+ type: "reasoning";
57
+ reasoning: string;
58
+ }
59
+ | {
60
+ type: "tool-invocation";
61
+ toolInvocation: {
62
+ status?: string;
63
+ toolName: string;
64
+ args?: unknown;
65
+ parsedArgs?: unknown;
66
+ result?: unknown;
67
+ error?: string;
68
+ cancelled?: boolean;
69
+ toolCallId?: string;
70
+ };
71
+ }
72
+ | {
73
+ type: string;
74
+ [key: string]: unknown;
75
+ };
76
+
77
+ type ToolCardViewSource = ToolCard & {
78
+ statusTone: ChatToolPartViewModel["statusTone"];
79
+ statusLabel: string;
80
+ };
81
+
82
+ type ChatMessagePartAdapterParams = {
83
+ part: ChatMessagePartSource;
84
+ inlineTokens: readonly ChatInlineTokenSource[];
85
+ texts: ChatMessageAdapterTexts;
86
+ };
87
+
88
+ function isRecord(value: unknown): value is Record<string, unknown> {
89
+ return typeof value === "object" && value !== null;
90
+ }
91
+
92
+ function readOptionalString(value: unknown): string | null {
93
+ if (typeof value !== "string") {
94
+ return null;
95
+ }
96
+ const trimmed = value.trim();
97
+ return trimmed.length > 0 ? trimmed : null;
98
+ }
99
+
100
+ function readOptionalNumber(value: unknown): number | null {
101
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
102
+ return value;
103
+ }
104
+ if (typeof value !== "string") {
105
+ return null;
106
+ }
107
+ const trimmed = value.trim();
108
+ if (!trimmed) {
109
+ return null;
110
+ }
111
+ const parsed = Number(trimmed);
112
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
113
+ }
114
+
115
+ function extractAssetFileView(
116
+ value: unknown,
117
+ texts: ChatMessageAdapterTexts,
118
+ ): Extract<ChatMessagePartViewModel, { type: "file" }> | null {
119
+ if (!isRecord(value)) {
120
+ return null;
121
+ }
122
+ const assetCandidate = isRecord(value.asset)
123
+ ? value.asset
124
+ : Array.isArray(value.assets) &&
125
+ value.assets.length > 0 &&
126
+ isRecord(value.assets[0])
127
+ ? value.assets[0]
128
+ : null;
129
+ if (!assetCandidate) {
130
+ return null;
131
+ }
132
+ const url = readOptionalString(assetCandidate.url);
133
+ const mimeType =
134
+ readOptionalString(assetCandidate.mimeType) ?? "application/octet-stream";
135
+ const sizeBytes = readOptionalNumber(assetCandidate.sizeBytes);
136
+ if (!url) {
137
+ return null;
138
+ }
139
+ const label =
140
+ readOptionalString(assetCandidate.name) ??
141
+ (mimeType.startsWith("image/")
142
+ ? texts.imageAttachmentLabel
143
+ : texts.fileAttachmentLabel);
144
+ return {
145
+ type: "file",
146
+ file: {
147
+ label,
148
+ mimeType,
149
+ dataUrl: url,
150
+ ...(sizeBytes != null ? { sizeBytes } : {}),
151
+ isImage: mimeType.startsWith("image/"),
152
+ },
153
+ };
154
+ }
155
+
156
+ function buildToolCard(
157
+ toolCard: ToolCardViewSource,
158
+ texts: ChatMessageAdapterTexts,
159
+ ): ChatToolPartViewModel {
160
+ return {
161
+ kind: toolCard.kind,
162
+ toolName: toolCard.name,
163
+ summary: toolCard.detail,
164
+ output: toolCard.text,
165
+ hasResult: Boolean(toolCard.hasResult),
166
+ statusTone: toolCard.statusTone,
167
+ statusLabel: toolCard.statusLabel,
168
+ titleLabel:
169
+ toolCard.kind === "call" ? texts.toolCallLabel : texts.toolResultLabel,
170
+ outputLabel: texts.toolOutputLabel,
171
+ emptyLabel: texts.toolNoOutputLabel,
172
+ };
173
+ }
174
+
175
+ function resolveToolCardStatus(params: {
176
+ status?: string;
177
+ error?: string;
178
+ cancelled?: boolean;
179
+ result?: unknown;
180
+ texts: ChatMessageAdapterTexts;
181
+ }): Pick<
182
+ ChatToolPartViewModel,
183
+ "kind" | "hasResult" | "statusTone" | "statusLabel"
184
+ > {
185
+ const rawStatus =
186
+ typeof params.status === "string" ? params.status.trim().toLowerCase() : "";
187
+ const hasError =
188
+ typeof params.error === "string" && params.error.trim().length > 0;
189
+ const isCancelled = params.cancelled === true || rawStatus === "cancelled";
190
+ if (isCancelled) {
191
+ return {
192
+ kind: "result",
193
+ hasResult: true,
194
+ statusTone: "cancelled",
195
+ statusLabel: params.texts.toolStatusCancelledLabel,
196
+ };
197
+ }
198
+ if (hasError || rawStatus === "error") {
199
+ return {
200
+ kind: "result",
201
+ hasResult: true,
202
+ statusTone: "error",
203
+ statusLabel: params.texts.toolStatusFailedLabel,
204
+ };
205
+ }
206
+ if (rawStatus === "result" || params.result != null) {
207
+ return {
208
+ kind: "result",
209
+ hasResult: true,
210
+ statusTone: "success",
211
+ statusLabel: params.texts.toolStatusCompletedLabel,
212
+ };
213
+ }
214
+ if (rawStatus === "partial-call") {
215
+ return {
216
+ kind: "call",
217
+ hasResult: false,
218
+ statusTone: "running",
219
+ statusLabel: params.texts.toolStatusPreparingLabel,
220
+ };
221
+ }
222
+ return {
223
+ kind: "call",
224
+ hasResult: false,
225
+ statusTone: "running",
226
+ statusLabel: params.texts.toolStatusRunningLabel,
227
+ };
228
+ }
229
+
230
+ function buildReasoningPart(
231
+ part: Extract<ChatMessagePartSource, { type: "reasoning" }>,
232
+ texts: ChatMessageAdapterTexts,
233
+ ): Extract<ChatMessagePartViewModel, { type: "reasoning" }> | null {
234
+ const text = buildRenderableText(part.reasoning);
235
+ if (!text) {
236
+ return null;
237
+ }
238
+ return {
239
+ type: "reasoning",
240
+ text,
241
+ label: texts.reasoningLabel,
242
+ };
243
+ }
244
+
245
+ function buildFilePart(
246
+ part: Extract<ChatMessagePartSource, { type: "file" }>,
247
+ texts: ChatMessageAdapterTexts,
248
+ ): Extract<ChatMessagePartViewModel, { type: "file" }> {
249
+ const isImage = part.mimeType.startsWith("image/");
250
+ const sizeBytes = readOptionalNumber(part.sizeBytes);
251
+ return {
252
+ type: "file",
253
+ file: {
254
+ label:
255
+ typeof part.name === "string" && part.name.trim()
256
+ ? part.name.trim()
257
+ : isImage
258
+ ? texts.imageAttachmentLabel
259
+ : texts.fileAttachmentLabel,
260
+ mimeType: part.mimeType,
261
+ dataUrl:
262
+ typeof part.url === "string" && part.url.trim().length > 0
263
+ ? part.url.trim()
264
+ : `data:${part.mimeType};base64,${part.data}`,
265
+ ...(sizeBytes != null ? { sizeBytes } : {}),
266
+ isImage,
267
+ },
268
+ };
269
+ }
270
+
271
+ function buildToolInvocationPart(
272
+ part: Extract<ChatMessagePartSource, { type: "tool-invocation" }>,
273
+ texts: ChatMessageAdapterTexts,
274
+ ): Extract<ChatMessagePartViewModel, { type: "tool-card" | "file" }> {
275
+ const invocation = part.toolInvocation;
276
+ const assetFileView = extractAssetFileView(invocation.result, texts);
277
+ if (assetFileView) {
278
+ return assetFileView;
279
+ }
280
+
281
+ const subagentToolCard = buildSubagentToolCard({
282
+ invocation,
283
+ texts,
284
+ });
285
+ if (subagentToolCard) {
286
+ return {
287
+ type: "tool-card",
288
+ card: buildToolCard(subagentToolCard, texts),
289
+ };
290
+ }
291
+
292
+ const statusView = resolveToolCardStatus({
293
+ status: invocation.status,
294
+ error: invocation.error,
295
+ cancelled: invocation.cancelled,
296
+ result: invocation.result,
297
+ texts,
298
+ });
299
+ const detail = summarizeToolArgs(invocation.parsedArgs ?? invocation.args);
300
+ const rawResult =
301
+ typeof invocation.error === "string" && invocation.error.trim()
302
+ ? invocation.error.trim()
303
+ : invocation.result != null
304
+ ? stringifyUnknown(invocation.result).trim()
305
+ : "";
306
+ const card: ToolCardViewSource = {
307
+ kind: statusView.kind,
308
+ name: invocation.toolName,
309
+ detail,
310
+ text: rawResult || undefined,
311
+ callId: invocation.toolCallId || undefined,
312
+ hasResult: statusView.hasResult,
313
+ statusTone: statusView.statusTone,
314
+ statusLabel: statusView.statusLabel,
315
+ };
316
+ return {
317
+ type: "tool-card",
318
+ card: buildToolCard(card, texts),
319
+ };
320
+ }
321
+
322
+ function buildUnknownPart(
323
+ part: ChatMessagePartSource,
324
+ texts: ChatMessageAdapterTexts,
325
+ ): Extract<ChatMessagePartViewModel, { type: "unknown" }> {
326
+ return {
327
+ type: "unknown",
328
+ label: texts.unknownPartLabel,
329
+ rawType: typeof part.type === "string" ? part.type : "unknown",
330
+ text: stringifyUnknown(part),
331
+ };
332
+ }
333
+
334
+ function isTextPart(
335
+ part: ChatMessagePartSource,
336
+ ): part is Extract<ChatMessagePartSource, { type: "text" }> {
337
+ return part.type === "text" && typeof part.text === "string";
338
+ }
339
+
340
+ function isReasoningPart(
341
+ part: ChatMessagePartSource,
342
+ ): part is Extract<ChatMessagePartSource, { type: "reasoning" }> {
343
+ return part.type === "reasoning" && typeof part.reasoning === "string";
344
+ }
345
+
346
+ function isFilePart(
347
+ part: ChatMessagePartSource,
348
+ ): part is Extract<ChatMessagePartSource, { type: "file" }> {
349
+ return (
350
+ part.type === "file" &&
351
+ typeof part.mimeType === "string" &&
352
+ typeof part.data === "string"
353
+ );
354
+ }
355
+
356
+ function isToolInvocationPart(
357
+ part: ChatMessagePartSource,
358
+ ): part is Extract<ChatMessagePartSource, { type: "tool-invocation" }> {
359
+ if (part.type !== "tool-invocation") {
360
+ return false;
361
+ }
362
+ if (!isRecord(part.toolInvocation)) {
363
+ return false;
364
+ }
365
+ return typeof part.toolInvocation.toolName === "string";
366
+ }
367
+
368
+ export function adaptChatMessagePart(
369
+ params: ChatMessagePartAdapterParams,
370
+ ): ChatMessagePartViewModel | null {
371
+ if (isTextPart(params.part)) {
372
+ return buildTextPart(params.part, params.inlineTokens);
373
+ }
374
+ if (isReasoningPart(params.part)) {
375
+ return buildReasoningPart(params.part, params.texts);
376
+ }
377
+ if (isFilePart(params.part)) {
378
+ return buildFilePart(params.part, params.texts);
379
+ }
380
+ if (isToolInvocationPart(params.part)) {
381
+ return buildToolInvocationPart(params.part, params.texts);
382
+ }
383
+ return buildUnknownPart(params.part, params.texts);
384
+ }
@@ -172,8 +172,18 @@ it("renders spawn tool cards from structured subagent status updates", () => {
172
172
  type: "tool-card",
173
173
  card: {
174
174
  toolName: "spawn",
175
- summary: "label: Verifier · task: Verify 1+1=2",
176
- output: "Verified 1+1=2.",
175
+ summary: "label: Verifier · run: subagent-1 · task: Verify 1+1=2",
176
+ output: [
177
+ "Run ID: subagent-1",
178
+ "",
179
+ "Label: Verifier",
180
+ "",
181
+ "Task:",
182
+ "Verify 1+1=2",
183
+ "",
184
+ "Result:",
185
+ "Verified 1+1=2.",
186
+ ].join("\n"),
177
187
  statusTone: "success",
178
188
  statusLabel: "Completed",
179
189
  titleLabel: "Tool Result",
@@ -260,6 +270,43 @@ it("maps file parts into previewable attachment view models", () => {
260
270
  });
261
271
  });
262
272
 
273
+ it("renders inline skill tokens as structured inline content parts", () => {
274
+ const adapted = adapt([
275
+ {
276
+ id: "user-inline-skill",
277
+ role: "user",
278
+ meta: {
279
+ inlineTokens: [
280
+ {
281
+ kind: "skill",
282
+ key: "weather",
283
+ label: "Weather",
284
+ rawText: "$weather",
285
+ },
286
+ ],
287
+ },
288
+ parts: [{ type: "text", text: "please use $weather now" }],
289
+ },
290
+ ] as unknown as ChatMessageSource[]);
291
+
292
+ expect(adapted[0]?.parts[0]).toEqual({
293
+ type: "inline-content",
294
+ segments: [
295
+ { type: "markdown", text: "please use " },
296
+ {
297
+ type: "token",
298
+ token: {
299
+ kind: "skill",
300
+ key: "weather",
301
+ label: "Weather",
302
+ rawText: "$weather",
303
+ },
304
+ },
305
+ { type: "markdown", text: " now" },
306
+ ],
307
+ });
308
+ });
309
+
263
310
  it("keeps named non-image files as downloadable attachments", () => {
264
311
  const adapted = adapt([
265
312
  {