@nextclaw/ui 0.11.1 → 0.11.3
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 +16 -0
- package/dist/assets/{ChannelsList-CVPqrxns.js → ChannelsList-CKl1Zg8f.js} +4 -4
- package/dist/assets/{ChatPage-BO1VUrAY.js → ChatPage-BJgO27mk.js} +25 -25
- package/dist/assets/{DocBrowser-FBwg8iji.js → DocBrowser-DYRBs4-z.js} +1 -1
- package/dist/assets/{LogoBadge-BCmJfRT8.js → LogoBadge-33Qlv3Hg.js} +1 -1
- package/dist/assets/{MarketplacePage-DWxXUOCx.js → MarketplacePage-B8BZVtjV.js} +2 -2
- package/dist/assets/{McpMarketplacePage-Bth9X_hu.js → McpMarketplacePage-BRuE5fJJ.js} +1 -1
- package/dist/assets/{ModelConfig-PkSp_ioc.js → ModelConfig-BiFblwO-.js} +1 -1
- package/dist/assets/{ProvidersList-DVDge8wa.js → ProvidersList-9goRgHE4.js} +1 -1
- package/dist/assets/{RemoteAccessPage-BVkzfEaL.js → RemoteAccessPage-5vCxZPS6.js} +1 -1
- package/dist/assets/{RuntimeConfig-ByJs3khh.js → RuntimeConfig-BmDFHBdW.js} +1 -1
- package/dist/assets/{SearchConfig-KZUAqYJN.js → SearchConfig-CJx5CKwG.js} +1 -1
- package/dist/assets/{SecretsConfig-qwB_Y_Ka.js → SecretsConfig-B91efXoK.js} +2 -2
- package/dist/assets/{SessionsConfig-CGCl4UTr.js → SessionsConfig-CbFPVmx3.js} +2 -2
- package/dist/assets/index-BtAuUyww.css +1 -0
- package/dist/assets/index-COJomMe9.js +8 -0
- package/dist/assets/{label-7JEFhkur.js → label-BnSDpjhL.js} +1 -1
- package/dist/assets/{ncp-session-adapter-BOqhkrc-.js → ncp-session-adapter-w8ZHprab.js} +1 -1
- package/dist/assets/{page-layout-B7q511TE.js → page-layout-B1RIu5-r.js} +1 -1
- package/dist/assets/{popover-CywJGmPr.js → popover-ChzbCIfO.js} +1 -1
- package/dist/assets/{security-config-zi2UxN5r.js → security-config-eYa6Ovfa.js} +1 -1
- package/dist/assets/skeleton-D4Eyop0R.js +1 -0
- package/dist/assets/{status-dot-BilwNdTT.js → status-dot-CrCw5tkJ.js} +1 -1
- package/dist/assets/{switch-BLp2Pno1.js → switch-C3vVTpfU.js} +1 -1
- package/dist/assets/{tabs-custom-CgIdQMGC.js → tabs-custom-Ilrgt6n1.js} +1 -1
- package/dist/assets/{useConfirmDialog-BitswAkv.js → useConfirmDialog-BeaFLDO8.js} +1 -1
- package/dist/assets/{vendor-D_JxmsLV.js → vendor-waGu-koL.js} +84 -69
- package/dist/index.html +3 -3
- package/package.json +4 -4
- package/src/components/chat/adapters/chat-message.adapter.test.ts +71 -4
- package/src/components/chat/adapters/chat-message.adapter.ts +195 -78
- package/src/components/chat/containers/chat-message-list.container.tsx +7 -0
- package/src/lib/i18n.chat.ts +7 -0
- package/dist/assets/index-CrilScMo.css +0 -1
- package/dist/assets/index-D41ntvb7.js +0 -8
- package/dist/assets/skeleton-qUJZQ03S.js +0 -1
package/dist/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-COJomMe9.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-waGu-koL.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BtAuUyww.css">
|
|
12
12
|
</head>
|
|
13
13
|
|
|
14
14
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ui",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,10 +28,10 @@
|
|
|
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.3",
|
|
32
|
-
"@nextclaw/ncp-http-agent-client": "0.3.4",
|
|
33
|
-
"@nextclaw/agent-chat-ui": "0.2.5",
|
|
34
31
|
"@nextclaw/ncp": "0.4.0",
|
|
32
|
+
"@nextclaw/ncp-http-agent-client": "0.3.4",
|
|
33
|
+
"@nextclaw/agent-chat-ui": "0.2.7",
|
|
34
|
+
"@nextclaw/agent-chat": "0.1.3",
|
|
35
35
|
"@nextclaw/ncp-react": "0.4.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
@@ -17,8 +17,15 @@ const defaultTexts = {
|
|
|
17
17
|
reasoningLabel: "Reasoning",
|
|
18
18
|
toolCallLabel: "Tool Call",
|
|
19
19
|
toolResultLabel: "Tool Result",
|
|
20
|
+
toolInputLabel: "Input Summary",
|
|
21
|
+
toolCallIdLabel: "Call ID",
|
|
20
22
|
toolNoOutputLabel: "No output",
|
|
21
23
|
toolOutputLabel: "View Output",
|
|
24
|
+
toolStatusPreparingLabel: "Preparing",
|
|
25
|
+
toolStatusRunningLabel: "Running",
|
|
26
|
+
toolStatusCompletedLabel: "Completed",
|
|
27
|
+
toolStatusFailedLabel: "Failed",
|
|
28
|
+
toolStatusCancelledLabel: "Cancelled",
|
|
22
29
|
imageAttachmentLabel: "Image attachment",
|
|
23
30
|
fileAttachmentLabel: "File attachment",
|
|
24
31
|
unknownPartLabel: "Unknown Part",
|
|
@@ -66,9 +73,7 @@ it("maps markdown, reasoning, and tool parts into UI view models", () => {
|
|
|
66
73
|
|
|
67
74
|
expect(adapted).toHaveLength(1);
|
|
68
75
|
expect(adapted[0]?.roleLabel).toBe("Assistant");
|
|
69
|
-
expect(adapted[0]?.timestampLabel).toBe(
|
|
70
|
-
"formatted:2026-03-17T10:00:00.000Z",
|
|
71
|
-
);
|
|
76
|
+
expect(adapted[0]?.timestampLabel).toBe("formatted:2026-03-17T10:00:00.000Z");
|
|
72
77
|
expect(adapted[0]?.parts.map((part) => part.type)).toEqual([
|
|
73
78
|
"markdown",
|
|
74
79
|
"reasoning",
|
|
@@ -82,12 +87,67 @@ it("maps markdown, reasoning, and tool parts into UI view models", () => {
|
|
|
82
87
|
expect(adapted[0]?.parts[2]).toMatchObject({
|
|
83
88
|
type: "tool-card",
|
|
84
89
|
card: {
|
|
90
|
+
callId: "call-1",
|
|
91
|
+
callIdLabel: "Call ID",
|
|
92
|
+
inputLabel: "Input Summary",
|
|
93
|
+
statusLabel: "Completed",
|
|
94
|
+
statusTone: "success",
|
|
85
95
|
titleLabel: "Tool Result",
|
|
86
96
|
outputLabel: "View Output",
|
|
87
97
|
},
|
|
88
98
|
});
|
|
89
99
|
});
|
|
90
100
|
|
|
101
|
+
it("maps tool lifecycle statuses into visible card state feedback", () => {
|
|
102
|
+
const adapted = adapt([
|
|
103
|
+
{
|
|
104
|
+
id: "assistant-tool-statuses",
|
|
105
|
+
role: "assistant",
|
|
106
|
+
parts: [
|
|
107
|
+
{
|
|
108
|
+
type: "tool-invocation",
|
|
109
|
+
toolInvocation: {
|
|
110
|
+
status: ToolInvocationStatus.PARTIAL_CALL,
|
|
111
|
+
toolCallId: "call-prep",
|
|
112
|
+
toolName: "web_search",
|
|
113
|
+
args: '{"q":"latest"}',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: "tool-invocation",
|
|
118
|
+
toolInvocation: {
|
|
119
|
+
status: ToolInvocationStatus.ERROR,
|
|
120
|
+
toolCallId: "call-error",
|
|
121
|
+
toolName: "exec_command",
|
|
122
|
+
args: '{"cmd":"exit 1"}',
|
|
123
|
+
error: "Command failed",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
] as unknown as ChatMessageSource[]);
|
|
129
|
+
|
|
130
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
131
|
+
type: "tool-card",
|
|
132
|
+
card: {
|
|
133
|
+
statusTone: "running",
|
|
134
|
+
statusLabel: "Preparing",
|
|
135
|
+
titleLabel: "Tool Call",
|
|
136
|
+
callId: "call-prep",
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
expect(adapted[0]?.parts[1]).toMatchObject({
|
|
140
|
+
type: "tool-card",
|
|
141
|
+
card: {
|
|
142
|
+
statusTone: "error",
|
|
143
|
+
statusLabel: "Failed",
|
|
144
|
+
titleLabel: "Tool Result",
|
|
145
|
+
output: "Command failed",
|
|
146
|
+
callId: "call-error",
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
91
151
|
it("maps non-standard roles back to the generic message role", () => {
|
|
92
152
|
const adapted = adapt([
|
|
93
153
|
{
|
|
@@ -149,6 +209,7 @@ it("maps file parts into previewable attachment view models", () => {
|
|
|
149
209
|
type: "file",
|
|
150
210
|
mimeType: "image/png",
|
|
151
211
|
data: "ZmFrZS1pbWFnZQ==",
|
|
212
|
+
sizeBytes: 4096,
|
|
152
213
|
},
|
|
153
214
|
],
|
|
154
215
|
},
|
|
@@ -160,6 +221,7 @@ it("maps file parts into previewable attachment view models", () => {
|
|
|
160
221
|
label: "Image attachment",
|
|
161
222
|
mimeType: "image/png",
|
|
162
223
|
dataUrl: "data:image/png;base64,ZmFrZS1pbWFnZQ==",
|
|
224
|
+
sizeBytes: 4096,
|
|
163
225
|
isImage: true,
|
|
164
226
|
},
|
|
165
227
|
});
|
|
@@ -176,6 +238,7 @@ it("keeps named non-image files as downloadable attachments", () => {
|
|
|
176
238
|
name: "spec.pdf",
|
|
177
239
|
mimeType: "application/pdf",
|
|
178
240
|
data: "cGRm",
|
|
241
|
+
sizeBytes: 2048,
|
|
179
242
|
},
|
|
180
243
|
],
|
|
181
244
|
},
|
|
@@ -187,6 +250,7 @@ it("keeps named non-image files as downloadable attachments", () => {
|
|
|
187
250
|
label: "spec.pdf",
|
|
188
251
|
mimeType: "application/pdf",
|
|
189
252
|
dataUrl: "data:application/pdf;base64,cGRm",
|
|
253
|
+
sizeBytes: 2048,
|
|
190
254
|
isImage: false,
|
|
191
255
|
},
|
|
192
256
|
});
|
|
@@ -212,6 +276,7 @@ it("renders asset tool results as previewable files", () => {
|
|
|
212
276
|
name: "output.png",
|
|
213
277
|
mimeType: "image/png",
|
|
214
278
|
url: "/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F27%2Fasset_1",
|
|
279
|
+
sizeBytes: 5120,
|
|
215
280
|
},
|
|
216
281
|
},
|
|
217
282
|
},
|
|
@@ -225,7 +290,9 @@ it("renders asset tool results as previewable files", () => {
|
|
|
225
290
|
file: {
|
|
226
291
|
label: "output.png",
|
|
227
292
|
mimeType: "image/png",
|
|
228
|
-
dataUrl:
|
|
293
|
+
dataUrl:
|
|
294
|
+
"/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F27%2Fasset_1",
|
|
295
|
+
sizeBytes: 5120,
|
|
229
296
|
isImage: true,
|
|
230
297
|
},
|
|
231
298
|
});
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
2
|
stringifyUnknown,
|
|
3
3
|
summarizeToolArgs,
|
|
4
|
-
type ToolCard
|
|
5
|
-
} from
|
|
4
|
+
type ToolCard,
|
|
5
|
+
} from "@/lib/chat-message";
|
|
6
6
|
import type {
|
|
7
7
|
ChatMessageRole,
|
|
8
8
|
ChatMessageViewModel,
|
|
9
|
-
ChatToolPartViewModel
|
|
10
|
-
} from
|
|
9
|
+
ChatToolPartViewModel,
|
|
10
|
+
} from "@nextclaw/agent-chat-ui";
|
|
11
11
|
|
|
12
12
|
export type ChatMessagePartSource =
|
|
13
13
|
| {
|
|
14
|
-
type:
|
|
14
|
+
type: "text";
|
|
15
15
|
text: string;
|
|
16
16
|
}
|
|
17
17
|
| {
|
|
18
|
-
type:
|
|
18
|
+
type: "file";
|
|
19
19
|
mimeType: string;
|
|
20
20
|
data: string;
|
|
21
21
|
url?: string;
|
|
22
22
|
name?: string;
|
|
23
|
+
sizeBytes?: number;
|
|
23
24
|
}
|
|
24
25
|
| {
|
|
25
|
-
type:
|
|
26
|
+
type: "reasoning";
|
|
26
27
|
reasoning: string;
|
|
27
28
|
}
|
|
28
29
|
| {
|
|
29
|
-
type:
|
|
30
|
+
type: "tool-invocation";
|
|
30
31
|
toolInvocation: {
|
|
31
32
|
status?: string;
|
|
32
33
|
toolName: string;
|
|
@@ -34,6 +35,7 @@ export type ChatMessagePartSource =
|
|
|
34
35
|
parsedArgs?: unknown;
|
|
35
36
|
result?: unknown;
|
|
36
37
|
error?: string;
|
|
38
|
+
cancelled?: boolean;
|
|
37
39
|
toolCallId?: string;
|
|
38
40
|
};
|
|
39
41
|
}
|
|
@@ -63,8 +65,15 @@ export type ChatMessageAdapterTexts = {
|
|
|
63
65
|
reasoningLabel: string;
|
|
64
66
|
toolCallLabel: string;
|
|
65
67
|
toolResultLabel: string;
|
|
68
|
+
toolInputLabel: string;
|
|
69
|
+
toolCallIdLabel: string;
|
|
66
70
|
toolNoOutputLabel: string;
|
|
67
71
|
toolOutputLabel: string;
|
|
72
|
+
toolStatusPreparingLabel: string;
|
|
73
|
+
toolStatusRunningLabel: string;
|
|
74
|
+
toolStatusCompletedLabel: string;
|
|
75
|
+
toolStatusFailedLabel: string;
|
|
76
|
+
toolStatusCancelledLabel: string;
|
|
68
77
|
imageAttachmentLabel: string;
|
|
69
78
|
fileAttachmentLabel: string;
|
|
70
79
|
unknownPartLabel: string;
|
|
@@ -73,91 +82,114 @@ export type ChatMessageAdapterTexts = {
|
|
|
73
82
|
const INVISIBLE_ONLY_TEXT_PATTERN = /\u200B|\u200C|\u200D|\u2060|\uFEFF/g;
|
|
74
83
|
|
|
75
84
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
76
|
-
return typeof value ===
|
|
85
|
+
return typeof value === "object" && value !== null;
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
function readOptionalString(value: unknown): string | null {
|
|
80
|
-
if (typeof value !==
|
|
89
|
+
if (typeof value !== "string") {
|
|
81
90
|
return null;
|
|
82
91
|
}
|
|
83
92
|
const trimmed = value.trim();
|
|
84
93
|
return trimmed.length > 0 ? trimmed : null;
|
|
85
94
|
}
|
|
86
95
|
|
|
96
|
+
function readOptionalNumber(value: unknown): number | null {
|
|
97
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
if (typeof value !== "string") {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const trimmed = value.trim();
|
|
104
|
+
if (!trimmed) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const parsed = Number(trimmed);
|
|
108
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
87
111
|
function extractAssetFileView(
|
|
88
112
|
value: unknown,
|
|
89
|
-
texts: ChatMessageAdapterTexts
|
|
90
|
-
):
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| null {
|
|
113
|
+
texts: ChatMessageAdapterTexts,
|
|
114
|
+
): {
|
|
115
|
+
type: "file";
|
|
116
|
+
file: {
|
|
117
|
+
label: string;
|
|
118
|
+
mimeType: string;
|
|
119
|
+
dataUrl: string;
|
|
120
|
+
sizeBytes?: number;
|
|
121
|
+
isImage: boolean;
|
|
122
|
+
};
|
|
123
|
+
} | null {
|
|
101
124
|
if (!isRecord(value)) {
|
|
102
125
|
return null;
|
|
103
126
|
}
|
|
104
127
|
const assetCandidate = isRecord(value.asset)
|
|
105
128
|
? value.asset
|
|
106
|
-
: Array.isArray(value.assets) &&
|
|
129
|
+
: Array.isArray(value.assets) &&
|
|
130
|
+
value.assets.length > 0 &&
|
|
131
|
+
isRecord(value.assets[0])
|
|
107
132
|
? value.assets[0]
|
|
108
133
|
: null;
|
|
109
134
|
if (!assetCandidate) {
|
|
110
135
|
return null;
|
|
111
136
|
}
|
|
112
137
|
const url = readOptionalString(assetCandidate.url);
|
|
113
|
-
const mimeType =
|
|
138
|
+
const mimeType =
|
|
139
|
+
readOptionalString(assetCandidate.mimeType) ?? "application/octet-stream";
|
|
140
|
+
const sizeBytes = readOptionalNumber(assetCandidate.sizeBytes);
|
|
114
141
|
if (!url) {
|
|
115
142
|
return null;
|
|
116
143
|
}
|
|
117
144
|
const label =
|
|
118
145
|
readOptionalString(assetCandidate.name) ??
|
|
119
|
-
(mimeType.startsWith(
|
|
146
|
+
(mimeType.startsWith("image/")
|
|
147
|
+
? texts.imageAttachmentLabel
|
|
148
|
+
: texts.fileAttachmentLabel);
|
|
120
149
|
return {
|
|
121
|
-
type:
|
|
150
|
+
type: "file",
|
|
122
151
|
file: {
|
|
123
152
|
label,
|
|
124
153
|
mimeType,
|
|
125
154
|
dataUrl: url,
|
|
126
|
-
|
|
127
|
-
|
|
155
|
+
...(sizeBytes != null ? { sizeBytes } : {}),
|
|
156
|
+
isImage: mimeType.startsWith("image/"),
|
|
157
|
+
},
|
|
128
158
|
};
|
|
129
159
|
}
|
|
130
160
|
|
|
131
|
-
function isTextPart(
|
|
132
|
-
|
|
161
|
+
function isTextPart(
|
|
162
|
+
part: ChatMessagePartSource,
|
|
163
|
+
): part is Extract<ChatMessagePartSource, { type: "text" }> {
|
|
164
|
+
return part.type === "text" && typeof part.text === "string";
|
|
133
165
|
}
|
|
134
166
|
|
|
135
167
|
function isReasoningPart(
|
|
136
|
-
part: ChatMessagePartSource
|
|
137
|
-
): part is Extract<ChatMessagePartSource, { type:
|
|
138
|
-
return part.type ===
|
|
168
|
+
part: ChatMessagePartSource,
|
|
169
|
+
): part is Extract<ChatMessagePartSource, { type: "reasoning" }> {
|
|
170
|
+
return part.type === "reasoning" && typeof part.reasoning === "string";
|
|
139
171
|
}
|
|
140
172
|
|
|
141
173
|
function isFilePart(
|
|
142
|
-
part: ChatMessagePartSource
|
|
143
|
-
): part is Extract<ChatMessagePartSource, { type:
|
|
174
|
+
part: ChatMessagePartSource,
|
|
175
|
+
): part is Extract<ChatMessagePartSource, { type: "file" }> {
|
|
144
176
|
return (
|
|
145
|
-
part.type ===
|
|
146
|
-
typeof part.mimeType ===
|
|
147
|
-
typeof part.data ===
|
|
177
|
+
part.type === "file" &&
|
|
178
|
+
typeof part.mimeType === "string" &&
|
|
179
|
+
typeof part.data === "string"
|
|
148
180
|
);
|
|
149
181
|
}
|
|
150
182
|
|
|
151
183
|
function isToolInvocationPart(
|
|
152
|
-
part: ChatMessagePartSource
|
|
153
|
-
): part is Extract<ChatMessagePartSource, { type:
|
|
154
|
-
if (part.type !==
|
|
184
|
+
part: ChatMessagePartSource,
|
|
185
|
+
): part is Extract<ChatMessagePartSource, { type: "tool-invocation" }> {
|
|
186
|
+
if (part.type !== "tool-invocation") {
|
|
155
187
|
return false;
|
|
156
188
|
}
|
|
157
189
|
if (!isRecord(part.toolInvocation)) {
|
|
158
190
|
return false;
|
|
159
191
|
}
|
|
160
|
-
return typeof part.toolInvocation.toolName ===
|
|
192
|
+
return typeof part.toolInvocation.toolName === "string";
|
|
161
193
|
}
|
|
162
194
|
|
|
163
195
|
function resolveMessageTimestamp(message: ChatMessageSource): string {
|
|
@@ -170,33 +202,38 @@ function resolveMessageTimestamp(message: ChatMessageSource): string {
|
|
|
170
202
|
|
|
171
203
|
function resolveRoleLabel(
|
|
172
204
|
role: string,
|
|
173
|
-
texts: ChatMessageAdapterTexts[
|
|
205
|
+
texts: ChatMessageAdapterTexts["roleLabels"],
|
|
174
206
|
): string {
|
|
175
|
-
if (role ===
|
|
207
|
+
if (role === "user") {
|
|
176
208
|
return texts.user;
|
|
177
209
|
}
|
|
178
|
-
if (role ===
|
|
210
|
+
if (role === "assistant") {
|
|
179
211
|
return texts.assistant;
|
|
180
212
|
}
|
|
181
|
-
if (role ===
|
|
213
|
+
if (role === "tool") {
|
|
182
214
|
return texts.tool;
|
|
183
215
|
}
|
|
184
|
-
if (role ===
|
|
216
|
+
if (role === "system") {
|
|
185
217
|
return texts.system;
|
|
186
218
|
}
|
|
187
219
|
return texts.fallback;
|
|
188
220
|
}
|
|
189
221
|
|
|
190
222
|
function resolveUiRole(role: string): ChatMessageRole {
|
|
191
|
-
if (
|
|
223
|
+
if (
|
|
224
|
+
role === "user" ||
|
|
225
|
+
role === "assistant" ||
|
|
226
|
+
role === "tool" ||
|
|
227
|
+
role === "system"
|
|
228
|
+
) {
|
|
192
229
|
return role;
|
|
193
230
|
}
|
|
194
|
-
return
|
|
231
|
+
return "message";
|
|
195
232
|
}
|
|
196
233
|
|
|
197
234
|
function buildToolCard(
|
|
198
|
-
toolCard:
|
|
199
|
-
texts: ChatMessageAdapterTexts
|
|
235
|
+
toolCard: ToolCardViewSource,
|
|
236
|
+
texts: ChatMessageAdapterTexts,
|
|
200
237
|
): ChatToolPartViewModel {
|
|
201
238
|
return {
|
|
202
239
|
kind: toolCard.kind,
|
|
@@ -204,9 +241,75 @@ function buildToolCard(
|
|
|
204
241
|
summary: toolCard.detail,
|
|
205
242
|
output: toolCard.text,
|
|
206
243
|
hasResult: Boolean(toolCard.hasResult),
|
|
207
|
-
|
|
244
|
+
statusTone: toolCard.statusTone,
|
|
245
|
+
statusLabel: toolCard.statusLabel,
|
|
246
|
+
titleLabel:
|
|
247
|
+
toolCard.kind === "call" ? texts.toolCallLabel : texts.toolResultLabel,
|
|
248
|
+
inputLabel: texts.toolInputLabel,
|
|
208
249
|
outputLabel: texts.toolOutputLabel,
|
|
209
|
-
emptyLabel: texts.toolNoOutputLabel
|
|
250
|
+
emptyLabel: texts.toolNoOutputLabel,
|
|
251
|
+
callIdLabel: texts.toolCallIdLabel,
|
|
252
|
+
callId: toolCard.callId,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
type ToolCardViewSource = ToolCard & {
|
|
257
|
+
statusTone: ChatToolPartViewModel["statusTone"];
|
|
258
|
+
statusLabel: string;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
function resolveToolCardStatus(params: {
|
|
262
|
+
status?: string;
|
|
263
|
+
error?: string;
|
|
264
|
+
cancelled?: boolean;
|
|
265
|
+
result?: unknown;
|
|
266
|
+
texts: ChatMessageAdapterTexts;
|
|
267
|
+
}): Pick<
|
|
268
|
+
ChatToolPartViewModel,
|
|
269
|
+
"kind" | "hasResult" | "statusTone" | "statusLabel"
|
|
270
|
+
> {
|
|
271
|
+
const rawStatus =
|
|
272
|
+
typeof params.status === "string" ? params.status.trim().toLowerCase() : "";
|
|
273
|
+
const hasError =
|
|
274
|
+
typeof params.error === "string" && params.error.trim().length > 0;
|
|
275
|
+
const isCancelled = params.cancelled === true || rawStatus === "cancelled";
|
|
276
|
+
if (isCancelled) {
|
|
277
|
+
return {
|
|
278
|
+
kind: "result",
|
|
279
|
+
hasResult: true,
|
|
280
|
+
statusTone: "cancelled",
|
|
281
|
+
statusLabel: params.texts.toolStatusCancelledLabel,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (hasError || rawStatus === "error") {
|
|
285
|
+
return {
|
|
286
|
+
kind: "result",
|
|
287
|
+
hasResult: true,
|
|
288
|
+
statusTone: "error",
|
|
289
|
+
statusLabel: params.texts.toolStatusFailedLabel,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (rawStatus === "result" || params.result != null) {
|
|
293
|
+
return {
|
|
294
|
+
kind: "result",
|
|
295
|
+
hasResult: true,
|
|
296
|
+
statusTone: "success",
|
|
297
|
+
statusLabel: params.texts.toolStatusCompletedLabel,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (rawStatus === "partial-call") {
|
|
301
|
+
return {
|
|
302
|
+
kind: "call",
|
|
303
|
+
hasResult: false,
|
|
304
|
+
statusTone: "running",
|
|
305
|
+
statusLabel: params.texts.toolStatusPreparingLabel,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
kind: "call",
|
|
310
|
+
hasResult: false,
|
|
311
|
+
statusTone: "running",
|
|
312
|
+
statusLabel: params.texts.toolStatusRunningLabel,
|
|
210
313
|
};
|
|
211
314
|
}
|
|
212
315
|
|
|
@@ -238,8 +341,8 @@ export function adaptChatMessages(params: {
|
|
|
238
341
|
return null;
|
|
239
342
|
}
|
|
240
343
|
return {
|
|
241
|
-
type:
|
|
242
|
-
text
|
|
344
|
+
type: "markdown" as const,
|
|
345
|
+
text,
|
|
243
346
|
};
|
|
244
347
|
}
|
|
245
348
|
if (isReasoningPart(part)) {
|
|
@@ -248,66 +351,80 @@ export function adaptChatMessages(params: {
|
|
|
248
351
|
return null;
|
|
249
352
|
}
|
|
250
353
|
return {
|
|
251
|
-
type:
|
|
354
|
+
type: "reasoning" as const,
|
|
252
355
|
text,
|
|
253
|
-
label: params.texts.reasoningLabel
|
|
356
|
+
label: params.texts.reasoningLabel,
|
|
254
357
|
};
|
|
255
358
|
}
|
|
256
359
|
if (isFilePart(part)) {
|
|
257
|
-
const isImage = part.mimeType.startsWith(
|
|
360
|
+
const isImage = part.mimeType.startsWith("image/");
|
|
361
|
+
const sizeBytes = readOptionalNumber(part.sizeBytes);
|
|
258
362
|
return {
|
|
259
|
-
type:
|
|
363
|
+
type: "file" as const,
|
|
260
364
|
file: {
|
|
261
365
|
label:
|
|
262
|
-
typeof part.name ===
|
|
366
|
+
typeof part.name === "string" && part.name.trim()
|
|
263
367
|
? part.name.trim()
|
|
264
368
|
: isImage
|
|
265
369
|
? params.texts.imageAttachmentLabel
|
|
266
370
|
: params.texts.fileAttachmentLabel,
|
|
267
371
|
mimeType: part.mimeType,
|
|
268
372
|
dataUrl:
|
|
269
|
-
typeof part.url ===
|
|
373
|
+
typeof part.url === "string" && part.url.trim().length > 0
|
|
270
374
|
? part.url.trim()
|
|
271
375
|
: `data:${part.mimeType};base64,${part.data}`,
|
|
272
|
-
|
|
273
|
-
|
|
376
|
+
...(sizeBytes != null ? { sizeBytes } : {}),
|
|
377
|
+
isImage,
|
|
378
|
+
},
|
|
274
379
|
};
|
|
275
380
|
}
|
|
276
381
|
if (isToolInvocationPart(part)) {
|
|
277
382
|
const invocation = part.toolInvocation;
|
|
278
|
-
const assetFileView = extractAssetFileView(
|
|
383
|
+
const assetFileView = extractAssetFileView(
|
|
384
|
+
invocation.result,
|
|
385
|
+
params.texts,
|
|
386
|
+
);
|
|
279
387
|
if (assetFileView) {
|
|
280
388
|
return assetFileView;
|
|
281
389
|
}
|
|
282
|
-
const
|
|
390
|
+
const statusView = resolveToolCardStatus({
|
|
391
|
+
status: invocation.status,
|
|
392
|
+
error: invocation.error,
|
|
393
|
+
cancelled: invocation.cancelled,
|
|
394
|
+
result: invocation.result,
|
|
395
|
+
texts: params.texts,
|
|
396
|
+
});
|
|
397
|
+
const detail = summarizeToolArgs(
|
|
398
|
+
invocation.parsedArgs ?? invocation.args,
|
|
399
|
+
);
|
|
283
400
|
const rawResult =
|
|
284
|
-
typeof invocation.error ===
|
|
401
|
+
typeof invocation.error === "string" && invocation.error.trim()
|
|
285
402
|
? invocation.error.trim()
|
|
286
403
|
: invocation.result != null
|
|
287
404
|
? stringifyUnknown(invocation.result).trim()
|
|
288
|
-
:
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
const card: ToolCard = {
|
|
292
|
-
kind: hasResult ? 'result' : 'call',
|
|
405
|
+
: "";
|
|
406
|
+
const card: ToolCardViewSource = {
|
|
407
|
+
kind: statusView.kind,
|
|
293
408
|
name: invocation.toolName,
|
|
294
409
|
detail,
|
|
295
410
|
text: rawResult || undefined,
|
|
296
411
|
callId: invocation.toolCallId || undefined,
|
|
297
|
-
hasResult
|
|
412
|
+
hasResult: statusView.hasResult,
|
|
413
|
+
statusTone: statusView.statusTone,
|
|
414
|
+
statusLabel: statusView.statusLabel,
|
|
298
415
|
};
|
|
299
416
|
return {
|
|
300
|
-
type:
|
|
301
|
-
card: buildToolCard(card, params.texts)
|
|
417
|
+
type: "tool-card" as const,
|
|
418
|
+
card: buildToolCard(card, params.texts),
|
|
302
419
|
};
|
|
303
420
|
}
|
|
304
421
|
return {
|
|
305
|
-
type:
|
|
422
|
+
type: "unknown" as const,
|
|
306
423
|
label: params.texts.unknownPartLabel,
|
|
307
|
-
rawType: typeof part.type ===
|
|
308
|
-
text: stringifyUnknown(part)
|
|
424
|
+
rawType: typeof part.type === "string" ? part.type : "unknown",
|
|
425
|
+
text: stringifyUnknown(part),
|
|
309
426
|
};
|
|
310
427
|
})
|
|
311
|
-
.filter((part) => part !== null)
|
|
428
|
+
.filter((part) => part !== null),
|
|
312
429
|
}));
|
|
313
430
|
}
|
|
@@ -46,8 +46,15 @@ export function ChatMessageListContainer(props: ChatMessageListContainerProps) {
|
|
|
46
46
|
reasoningLabel: t("chatReasoning"),
|
|
47
47
|
toolCallLabel: t("chatToolCall"),
|
|
48
48
|
toolResultLabel: t("chatToolResult"),
|
|
49
|
+
toolInputLabel: t("chatToolInput"),
|
|
50
|
+
toolCallIdLabel: t("chatToolCallId"),
|
|
49
51
|
toolNoOutputLabel: t("chatToolNoOutput"),
|
|
50
52
|
toolOutputLabel: t("chatToolOutput"),
|
|
53
|
+
toolStatusPreparingLabel: t("chatToolStatusPreparing"),
|
|
54
|
+
toolStatusRunningLabel: t("chatToolStatusRunning"),
|
|
55
|
+
toolStatusCompletedLabel: t("chatToolStatusCompleted"),
|
|
56
|
+
toolStatusFailedLabel: t("chatToolStatusFailed"),
|
|
57
|
+
toolStatusCancelledLabel: t("chatToolStatusCancelled"),
|
|
51
58
|
imageAttachmentLabel: t("chatImageAttachment"),
|
|
52
59
|
fileAttachmentLabel: t("chatFileAttachment"),
|
|
53
60
|
unknownPartLabel: t("chatUnknownPart"),
|
package/src/lib/i18n.chat.ts
CHANGED
|
@@ -70,10 +70,17 @@ export const CHAT_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
70
70
|
chatRoleMessage: { zh: '消息', en: 'Message' },
|
|
71
71
|
chatToolCall: { zh: '工具调用', en: 'Tool Call' },
|
|
72
72
|
chatToolResult: { zh: '工具结果', en: 'Tool Result' },
|
|
73
|
+
chatToolInput: { zh: '输入摘要', en: 'Input Summary' },
|
|
74
|
+
chatToolCallId: { zh: '调用 ID', en: 'Call ID' },
|
|
73
75
|
chatToolWorkflow: { zh: '工具工作流', en: 'Tool Workflow' },
|
|
74
76
|
chatToolWorkflowDetails: { zh: '展开查看参数和结果', en: 'Expand to view params and results' },
|
|
75
77
|
chatToolOutput: { zh: '查看输出', en: 'View Output' },
|
|
76
78
|
chatToolNoOutput: { zh: '无输出(执行完成)', en: 'No output (completed)' },
|
|
79
|
+
chatToolStatusPreparing: { zh: '准备中', en: 'Preparing' },
|
|
80
|
+
chatToolStatusRunning: { zh: '执行中', en: 'Running' },
|
|
81
|
+
chatToolStatusCompleted: { zh: '已完成', en: 'Completed' },
|
|
82
|
+
chatToolStatusFailed: { zh: '失败', en: 'Failed' },
|
|
83
|
+
chatToolStatusCancelled: { zh: '已取消', en: 'Cancelled' },
|
|
77
84
|
chatReasoning: { zh: '推理过程', en: 'Reasoning' },
|
|
78
85
|
chatImageAttachment: { zh: '图片附件', en: 'Image attachment' },
|
|
79
86
|
chatFileAttachment: { zh: '文件附件', en: 'File attachment' },
|