@nextclaw/ui 0.11.0 → 0.11.2
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 -2
- package/dist/assets/{ChannelsList-BqsOYnXz.js → ChannelsList-CKl1Zg8f.js} +3 -3
- package/dist/assets/ChatPage-BJgO27mk.js +37 -0
- package/dist/assets/{DocBrowser-BmL0QXBZ.js → DocBrowser-DYRBs4-z.js} +1 -1
- package/dist/assets/{LogoBadge-C1HiPZPf.js → LogoBadge-33Qlv3Hg.js} +1 -1
- package/dist/assets/MarketplacePage-B8BZVtjV.js +49 -0
- package/dist/assets/{McpMarketplacePage-CLHFnNBd.js → McpMarketplacePage-BRuE5fJJ.js} +2 -2
- package/dist/assets/{ModelConfig-LQSR58tc.js → ModelConfig-BiFblwO-.js} +1 -1
- package/dist/assets/ProvidersList-9goRgHE4.js +1 -0
- package/dist/assets/RemoteAccessPage-5vCxZPS6.js +1 -0
- package/dist/assets/RuntimeConfig-BmDFHBdW.js +1 -0
- package/dist/assets/{SearchConfig-Chzo_JGs.js → SearchConfig-CJx5CKwG.js} +1 -1
- package/dist/assets/{SecretsConfig-CEIbjZYA.js → SecretsConfig-B91efXoK.js} +2 -2
- package/dist/assets/SessionsConfig-CbFPVmx3.js +2 -0
- package/dist/assets/index-BtAuUyww.css +1 -0
- package/dist/assets/index-COJomMe9.js +8 -0
- package/dist/assets/{label-GACO2RzW.js → label-BnSDpjhL.js} +1 -1
- package/dist/assets/ncp-session-adapter-w8ZHprab.js +1 -0
- package/dist/assets/{page-layout-DjXaK3A3.js → page-layout-B1RIu5-r.js} +1 -1
- package/dist/assets/popover-ChzbCIfO.js +1 -0
- package/dist/assets/security-config-eYa6Ovfa.js +1 -0
- package/dist/assets/skeleton-D4Eyop0R.js +1 -0
- package/dist/assets/{status-dot-IWEBezqb.js → status-dot-CrCw5tkJ.js} +1 -1
- package/dist/assets/{switch-DCHAJSrA.js → switch-C3vVTpfU.js} +1 -1
- package/dist/assets/tabs-custom-Ilrgt6n1.js +1 -0
- package/dist/assets/useConfirmDialog-BeaFLDO8.js +1 -0
- package/dist/assets/{vendor-CNhxtHCf.js → vendor-waGu-koL.js} +101 -86
- package/dist/index.html +3 -3
- package/package.json +5 -5
- package/src/App.test.tsx +42 -10
- package/src/App.tsx +5 -40
- package/src/api/api-base.test.ts +37 -0
- package/src/api/api-base.ts +0 -4
- package/src/api/config.ts +2 -270
- package/src/api/types.ts +0 -117
- package/src/components/chat/ChatPage.tsx +1 -11
- package/src/components/chat/ChatSidebar.test.tsx +1 -50
- package/src/components/chat/ChatSidebar.tsx +0 -5
- package/src/components/chat/README.md +2 -0
- 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/chat-attachment-upload-limit.test.ts +41 -0
- package/src/components/chat/chat-session-display.ts +9 -0
- package/src/components/chat/chat-session-label.service.ts +3 -12
- package/src/components/chat/chat-session-preference-sync.test.ts +10 -13
- package/src/components/chat/chat-stream/types.ts +4 -57
- package/src/components/chat/containers/chat-message-list.container.tsx +7 -0
- package/src/components/chat/ncp/NcpChatPage.tsx +3 -3
- package/src/components/chat/useHydratedNcpAgent.test.tsx +77 -0
- package/src/components/config/README.md +2 -0
- package/src/components/config/SessionsConfig.tsx +152 -132
- package/src/hooks/use-auth.test.ts +3 -3
- package/src/hooks/use-auth.ts +16 -4
- package/src/hooks/use-realtime-query-bridge.ts +0 -24
- package/src/hooks/useConfig.ts +10 -137
- package/src/lib/i18n.chat.ts +7 -0
- package/src/lib/session-run-status.ts +1 -63
- package/src/vite-env.d.ts +1 -0
- package/vite.config.ts +4 -4
- package/dist/assets/ChatPage-CJBYKR-Y.js +0 -38
- package/dist/assets/MarketplacePage-BIRP0NRS.js +0 -49
- package/dist/assets/ProvidersList-CwI-mxah.js +0 -1
- package/dist/assets/RemoteAccessPage-Cw5BqZb6.js +0 -1
- package/dist/assets/RuntimeConfig-DbowSRAb.js +0 -1
- package/dist/assets/SessionsConfig-BR8GfGWL.js +0 -2
- package/dist/assets/chat-message-CPG7zxRR.js +0 -3
- package/dist/assets/index-j6A_-1b6.js +0 -8
- package/dist/assets/index-kaPUhd-8.css +0 -1
- package/dist/assets/popover-DTaFiTmU.js +0 -1
- package/dist/assets/security-config-Dk-yoKvK.js +0 -1
- package/dist/assets/skeleton-Dm2xOBSA.js +0 -1
- package/dist/assets/tabs-custom-DKSbDSB9.js +0 -1
- package/dist/assets/useConfirmDialog-ByJ8A8n7.js +0 -1
- package/src/api/config.stream.test.ts +0 -115
- package/src/components/chat/chat-chain.test.ts +0 -22
- package/src/components/chat/chat-chain.ts +0 -23
- package/src/components/chat/chat-page-data.ts +0 -171
- package/src/components/chat/chat-page-runtime.ts +0 -190
- package/src/components/chat/chat-stream/nextbot-parsers.ts +0 -52
- package/src/components/chat/chat-stream/nextbot-runtime-agent.ts +0 -413
- package/src/components/chat/chat-stream/stream-event-adapter.ts +0 -98
- package/src/components/chat/chat-stream/transport.ts +0 -253
- package/src/components/chat/legacy/LegacyChatPage.tsx +0 -223
- package/src/components/chat/managers/chat-input.manager.ts +0 -228
- package/src/components/chat/managers/chat-thread.manager.ts +0 -87
- package/src/components/chat/presenter/chat.presenter.ts +0 -32
- package/src/components/chat/useChatRuntimeController.ts +0 -134
|
@@ -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
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_NCP_ATTACHMENT_MAX_BYTES,
|
|
4
|
+
uploadFilesAsNcpDraftAttachments
|
|
5
|
+
} from '../../../../ncp-packages/nextclaw-ncp-react/src/attachments/ncp-attachments.ts';
|
|
6
|
+
|
|
7
|
+
describe('ncp attachment upload limit', () => {
|
|
8
|
+
it('accepts files larger than the previous 10MB cap', async () => {
|
|
9
|
+
expect(DEFAULT_NCP_ATTACHMENT_MAX_BYTES).toBe(200 * 1024 * 1024);
|
|
10
|
+
|
|
11
|
+
const file = new File([new Uint8Array(12 * 1024 * 1024)], 'large-image.png', {
|
|
12
|
+
type: 'image/png'
|
|
13
|
+
});
|
|
14
|
+
const uploadBatch = vi.fn(async (files: File[]) =>
|
|
15
|
+
files.map((entry) => ({
|
|
16
|
+
id: entry.name,
|
|
17
|
+
name: entry.name,
|
|
18
|
+
mimeType: entry.type,
|
|
19
|
+
sizeBytes: entry.size,
|
|
20
|
+
assetUri: `asset://store/${entry.name}`,
|
|
21
|
+
}))
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const result = await uploadFilesAsNcpDraftAttachments([file], {
|
|
25
|
+
uploadBatch
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(result.rejected).toEqual([]);
|
|
29
|
+
expect(uploadBatch).toHaveBeenCalledOnce();
|
|
30
|
+
expect(uploadBatch).toHaveBeenCalledWith([file]);
|
|
31
|
+
expect(result.attachments).toEqual([
|
|
32
|
+
{
|
|
33
|
+
id: 'large-image.png',
|
|
34
|
+
name: 'large-image.png',
|
|
35
|
+
mimeType: 'image/png',
|
|
36
|
+
sizeBytes: 12 * 1024 * 1024,
|
|
37
|
+
assetUri: 'asset://store/large-image.png',
|
|
38
|
+
}
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SessionEntryView } from '@/api/types';
|
|
2
|
+
|
|
3
|
+
export function sessionDisplayName(session: SessionEntryView): string {
|
|
4
|
+
if (session.label && session.label.trim()) {
|
|
5
|
+
return session.label.trim();
|
|
6
|
+
}
|
|
7
|
+
const chunks = session.key.split(':');
|
|
8
|
+
return chunks[chunks.length - 1] || session.key;
|
|
9
|
+
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useQueryClient } from '@tanstack/react-query';
|
|
2
2
|
import { toast } from 'sonner';
|
|
3
|
-
import { updateSession } from '@/api/config';
|
|
4
3
|
import { updateNcpSession } from '@/api/ncp-session';
|
|
5
|
-
import type { ChatChain } from '@/components/chat/chat-chain';
|
|
6
4
|
import { t } from '@/lib/i18n';
|
|
7
5
|
|
|
8
6
|
type UpdateChatSessionLabelParams = {
|
|
9
|
-
chatChain: ChatChain;
|
|
10
7
|
sessionKey: string;
|
|
11
8
|
label: string | null;
|
|
12
9
|
};
|
|
@@ -16,15 +13,9 @@ export function useChatSessionLabelService() {
|
|
|
16
13
|
|
|
17
14
|
return async (params: UpdateChatSessionLabelParams): Promise<void> => {
|
|
18
15
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
queryClient.invalidateQueries({ queryKey: ['ncp-session-messages', params.sessionKey] });
|
|
23
|
-
} else {
|
|
24
|
-
await updateSession(params.sessionKey, { label: params.label });
|
|
25
|
-
queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
|
26
|
-
queryClient.invalidateQueries({ queryKey: ['session-history', params.sessionKey] });
|
|
27
|
-
}
|
|
16
|
+
await updateNcpSession(params.sessionKey, { label: params.label });
|
|
17
|
+
queryClient.invalidateQueries({ queryKey: ['ncp-sessions'] });
|
|
18
|
+
queryClient.invalidateQueries({ queryKey: ['ncp-session-messages', params.sessionKey] });
|
|
28
19
|
toast.success(t('configSavedApplied'));
|
|
29
20
|
} catch (error) {
|
|
30
21
|
toast.error(t('configSaveFailed') + ': ' + (error instanceof Error ? error.message : String(error)));
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { updateNcpSession } from '@/api/ncp-session';
|
|
3
3
|
import { ChatSessionPreferenceSync } from '@/components/chat/chat-session-preference-sync';
|
|
4
4
|
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
5
5
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
6
6
|
|
|
7
|
-
vi.mock('@/api/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
metadata: {},
|
|
15
|
-
messages: [],
|
|
16
|
-
events: []
|
|
7
|
+
vi.mock('@/api/ncp-session', () => ({
|
|
8
|
+
updateNcpSession: vi.fn(async () => ({
|
|
9
|
+
sessionId: 'session-1',
|
|
10
|
+
messageCount: 0,
|
|
11
|
+
updatedAt: new Date().toISOString(),
|
|
12
|
+
status: 'idle',
|
|
13
|
+
metadata: {}
|
|
17
14
|
}))
|
|
18
15
|
}));
|
|
19
16
|
|
|
@@ -50,10 +47,10 @@ describe('ChatSessionPreferenceSync', () => {
|
|
|
50
47
|
}
|
|
51
48
|
}));
|
|
52
49
|
|
|
53
|
-
const sync = new ChatSessionPreferenceSync(
|
|
50
|
+
const sync = new ChatSessionPreferenceSync(updateNcpSession);
|
|
54
51
|
sync.syncSelectedSessionPreferences();
|
|
55
52
|
await vi.waitFor(() => {
|
|
56
|
-
expect(
|
|
53
|
+
expect(updateNcpSession).toHaveBeenCalledWith('session-1', {
|
|
57
54
|
preferredModel: 'openai/gpt-5',
|
|
58
55
|
preferredThinking: 'high'
|
|
59
56
|
});
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
2
2
|
import type { NcpMessagePart } from '@nextclaw/ncp';
|
|
3
3
|
import type { NcpDraftAttachment } from '@nextclaw/ncp-react';
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
ChatRunView,
|
|
7
|
-
ChatTurnStreamDeltaEvent,
|
|
8
|
-
ChatTurnStreamReadyEvent,
|
|
9
|
-
ChatTurnStreamSessionEvent,
|
|
10
|
-
SessionMessageView,
|
|
11
|
-
ThinkingLevel
|
|
12
|
-
} from '@/api/types';
|
|
4
|
+
import type { ThinkingLevel } from '@/api/types';
|
|
13
5
|
|
|
14
6
|
export type SendMessageParams = {
|
|
15
|
-
runId?: string;
|
|
16
7
|
message: string;
|
|
17
8
|
sessionKey: string;
|
|
18
9
|
agentId: string;
|
|
@@ -28,58 +19,14 @@ export type SendMessageParams = {
|
|
|
28
19
|
composerNodes?: ChatComposerNode[];
|
|
29
20
|
};
|
|
30
21
|
|
|
31
|
-
export type
|
|
32
|
-
localRunId: number;
|
|
22
|
+
export type ResumeRunParams = {
|
|
33
23
|
sessionKey: string;
|
|
34
|
-
agentId?: string;
|
|
35
|
-
backendRunId?: string;
|
|
36
|
-
backendStopSupported: boolean;
|
|
37
|
-
backendStopReason?: string;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export type StreamReadyPayload = {
|
|
41
|
-
sessionKey: string;
|
|
42
|
-
runId?: string;
|
|
43
|
-
stopSupported?: boolean;
|
|
44
|
-
stopReason?: string;
|
|
45
|
-
requestedAt?: string;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type StreamReadyEvent = ChatTurnStreamReadyEvent;
|
|
49
|
-
export type StreamDeltaEvent = ChatTurnStreamDeltaEvent;
|
|
50
|
-
export type StreamSessionEvent = ChatTurnStreamSessionEvent;
|
|
51
|
-
|
|
52
|
-
export type NextbotAgentRunMetadata =
|
|
53
|
-
| {
|
|
54
|
-
driver: 'nextbot-stream';
|
|
55
|
-
mode: 'send';
|
|
56
|
-
payload: SendMessageParams;
|
|
57
|
-
requestedSkills: string[];
|
|
58
|
-
}
|
|
59
|
-
| {
|
|
60
|
-
driver: 'nextbot-stream';
|
|
61
|
-
mode: 'resume';
|
|
62
|
-
runId: string;
|
|
63
|
-
fromEventIndex?: number;
|
|
64
|
-
sessionKey?: string;
|
|
65
|
-
agentId?: string;
|
|
66
|
-
stopSupported?: boolean;
|
|
67
|
-
stopReason?: string;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export type UseChatStreamControllerParams = {
|
|
71
|
-
selectedSessionKeyRef: MutableRefObject<string | null>;
|
|
72
|
-
setSelectedSessionKey: Dispatch<SetStateAction<string | null>>;
|
|
73
|
-
setDraft: Dispatch<SetStateAction<string>>;
|
|
74
|
-
setComposerNodes: Dispatch<SetStateAction<ChatComposerNode[]>>;
|
|
75
|
-
refetchSessions: () => Promise<unknown>;
|
|
76
|
-
refetchHistory: () => Promise<unknown>;
|
|
77
24
|
};
|
|
78
25
|
|
|
79
26
|
export type ChatStreamActions = {
|
|
80
27
|
sendMessage: (payload: SendMessageParams) => Promise<void>;
|
|
81
28
|
stopCurrentRun: () => Promise<void>;
|
|
82
|
-
resumeRun: (run:
|
|
29
|
+
resumeRun: (run: ResumeRunParams) => Promise<void>;
|
|
83
30
|
resetStreamState: () => void;
|
|
84
|
-
applyHistoryMessages: (messages:
|
|
31
|
+
applyHistoryMessages: (messages: unknown[], options?: { isLoading?: boolean }) => void;
|
|
85
32
|
};
|
|
@@ -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"),
|