@nextclaw/ui 0.5.26 → 0.5.29
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 +22 -0
- package/dist/assets/{ChannelsList-D0Wk08Ki.js → ChannelsList-DEr4kE7H.js} +1 -1
- package/dist/assets/ChatPage-DI2euxZy.js +32 -0
- package/dist/assets/{CronConfig-D-3Y8kWb.js → CronConfig-DAlt-x5i.js} +1 -1
- package/dist/assets/{DocBrowser-BSPKhqrK.js → DocBrowser-TrMsdXgx.js} +1 -1
- package/dist/assets/{MarketplacePage-Dkm2FTtN.js → MarketplacePage-Dwm527F7.js} +1 -1
- package/dist/assets/{ModelConfig-2cpAmvGq.js → ModelConfig-srggzgfA.js} +1 -1
- package/dist/assets/{ProvidersList-Dot21pAy.js → ProvidersList-8kFCDiqC.js} +1 -1
- package/dist/assets/{RuntimeConfig-BNw_Ms_Y.js → RuntimeConfig-CLbdKAlo.js} +1 -1
- package/dist/assets/{SecretsConfig-z8M3PDJP.js → SecretsConfig-DXCdR0Be.js} +1 -1
- package/dist/assets/{SessionsConfig-XVHZ-FG5.js → SessionsConfig-iKpz3Sts.js} +1 -1
- package/dist/assets/{action-link-CpPJJN-z.js → action-link-w4jS8X9q.js} +1 -1
- package/dist/assets/{card-DsZ2Am92.js → card-CVj65Dvi.js} +1 -1
- package/dist/assets/chat-message-D0s61C4e.js +5 -0
- package/dist/assets/{dialog-BysNu5hM.js → dialog-lK79rlAw.js} +1 -1
- package/dist/assets/{index-Bny21Br0.js → index-BXgULtdk.js} +2 -2
- package/dist/assets/{label-q6RASlER.js → label-l-fECYi3.js} +1 -1
- package/dist/assets/{page-layout-WiVrFc8t.js → page-layout-BghxFaNt.js} +1 -1
- package/dist/assets/{switch-DM_YYUgB.js → switch-B4yFzIbc.js} +1 -1
- package/dist/assets/{tabs-custom-mlgm-IGH.js → tabs-custom-B4q02QSV.js} +1 -1
- package/dist/assets/useConfig-C9k3TmQk.js +6 -0
- package/dist/assets/{useConfirmDialog-DamaA60g.js → useConfirmDialog-C20D5SYn.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/api/config.ts +14 -1
- package/src/api/types.ts +14 -0
- package/src/components/chat/ChatPage.tsx +95 -35
- package/src/components/chat/ChatThread.tsx +58 -32
- package/src/hooks/useConfig.ts +2 -1
- package/src/lib/chat-message.ts +169 -153
- package/dist/assets/ChatPage-Deg2lBH4.js +0 -32
- package/dist/assets/chat-message-Jxa8JFA_.js +0 -9
- package/dist/assets/useConfig-BOn-kp8G.js +0 -6
package/src/lib/chat-message.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SessionMessageView } from '@/api/types';
|
|
1
|
+
import type { SessionEventView, SessionMessageView } from '@/api/types';
|
|
2
2
|
|
|
3
3
|
export type ChatRole = 'user' | 'assistant' | 'tool' | 'system' | 'other';
|
|
4
4
|
|
|
@@ -11,14 +11,28 @@ export type ToolCard = {
|
|
|
11
11
|
hasResult?: boolean;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export type
|
|
14
|
+
export type ChatTimelineMessageItem = {
|
|
15
|
+
kind: 'message';
|
|
15
16
|
key: string;
|
|
16
17
|
role: ChatRole;
|
|
17
|
-
messages: SessionMessageView[];
|
|
18
18
|
timestamp: string;
|
|
19
|
+
message: SessionMessageView;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
export type ChatTimelineAssistantFlowItem = {
|
|
23
|
+
kind: 'assistant_flow';
|
|
24
|
+
key: string;
|
|
25
|
+
role: 'assistant';
|
|
26
|
+
timestamp: string;
|
|
27
|
+
primaryText: string;
|
|
28
|
+
primaryReasoning: string;
|
|
29
|
+
followupText: string;
|
|
30
|
+
followupReasoning: string;
|
|
31
|
+
toolCards: ToolCard[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ChatTimelineItem = ChatTimelineMessageItem | ChatTimelineAssistantFlowItem;
|
|
35
|
+
|
|
22
36
|
const TOOL_DETAIL_FIELDS = ['cmd', 'command', 'query', 'q', 'path', 'url', 'to', 'channel', 'agentId', 'sessionKey'];
|
|
23
37
|
|
|
24
38
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -104,30 +118,6 @@ function hasToolCalls(message: SessionMessageView): boolean {
|
|
|
104
118
|
return Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
105
119
|
}
|
|
106
120
|
|
|
107
|
-
function mergeMessageContent(base: unknown, addition: unknown): unknown {
|
|
108
|
-
const left = extractMessageText(base).trim();
|
|
109
|
-
const right = extractMessageText(addition).trim();
|
|
110
|
-
if (!left) {
|
|
111
|
-
return right;
|
|
112
|
-
}
|
|
113
|
-
if (!right) {
|
|
114
|
-
return left;
|
|
115
|
-
}
|
|
116
|
-
return `${left}\n\n${right}`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function mergeReasoningContent(base: unknown, addition: unknown): string | undefined {
|
|
120
|
-
const left = typeof base === 'string' ? base.trim() : '';
|
|
121
|
-
const right = typeof addition === 'string' ? addition.trim() : '';
|
|
122
|
-
if (!left) {
|
|
123
|
-
return right || undefined;
|
|
124
|
-
}
|
|
125
|
-
if (!right) {
|
|
126
|
-
return left;
|
|
127
|
-
}
|
|
128
|
-
return `${left}\n\n${right}`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
121
|
export function normalizeChatRole(message: Pick<SessionMessageView, 'role' | 'name' | 'tool_call_id' | 'tool_calls'>): ChatRole {
|
|
132
122
|
const role = message.role.toLowerCase().trim();
|
|
133
123
|
if (role === 'user') {
|
|
@@ -177,7 +167,7 @@ export function extractMessageText(content: unknown): string {
|
|
|
177
167
|
return stringifyUnknown(content);
|
|
178
168
|
}
|
|
179
169
|
|
|
180
|
-
|
|
170
|
+
function buildToolCallCards(message: SessionMessageView): ToolCard[] {
|
|
181
171
|
const cards: ToolCard[] = [];
|
|
182
172
|
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
183
173
|
for (const call of toolCalls) {
|
|
@@ -187,176 +177,202 @@ export function extractToolCards(message: SessionMessageView): ToolCard[] {
|
|
|
187
177
|
const fn = isRecord(call.function) ? call.function : null;
|
|
188
178
|
const name = toToolName(fn?.name ?? call.name);
|
|
189
179
|
const args = fn?.arguments ?? call.arguments;
|
|
190
|
-
const resultText = typeof call.result_text === 'string' ? call.result_text.trim() : '';
|
|
191
|
-
const hasResult = call.has_result === true || typeof call.result_text === 'string';
|
|
192
180
|
cards.push({
|
|
193
181
|
kind: 'call',
|
|
194
182
|
name,
|
|
195
183
|
detail: summarizeToolArgs(args),
|
|
196
|
-
callId: typeof call.id === 'string' ? call.id : undefined,
|
|
197
|
-
|
|
198
|
-
hasResult
|
|
184
|
+
callId: typeof call.id === 'string' && call.id.trim() ? call.id : undefined,
|
|
185
|
+
hasResult: false
|
|
199
186
|
});
|
|
200
187
|
}
|
|
188
|
+
return cards;
|
|
189
|
+
}
|
|
201
190
|
|
|
191
|
+
export function extractToolCards(message: SessionMessageView): ToolCard[] {
|
|
192
|
+
const cards = buildToolCallCards(message);
|
|
202
193
|
const role = normalizeChatRole(message);
|
|
203
194
|
if (role === 'tool' || typeof message.tool_call_id === 'string') {
|
|
204
|
-
const text = extractMessageText(message.content).trim();
|
|
205
195
|
cards.push({
|
|
206
196
|
kind: 'result',
|
|
207
197
|
name: toToolName(message.name ?? cards[0]?.name),
|
|
208
|
-
text,
|
|
198
|
+
text: extractMessageText(message.content).trim(),
|
|
209
199
|
callId: typeof message.tool_call_id === 'string' ? message.tool_call_id : undefined,
|
|
210
200
|
hasResult: true
|
|
211
201
|
});
|
|
212
202
|
}
|
|
213
|
-
|
|
214
203
|
return cards;
|
|
215
204
|
}
|
|
216
205
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
206
|
+
function normalizeEvent(event: SessionEventView, index: number): SessionEventView & { _idx: number; _seq: number } {
|
|
207
|
+
const seq = Number.isFinite(event.seq) && event.seq > 0 ? Math.trunc(event.seq) : index + 1;
|
|
208
|
+
const timestamp =
|
|
209
|
+
typeof event.timestamp === 'string' && event.timestamp
|
|
210
|
+
? event.timestamp
|
|
211
|
+
: event.message?.timestamp ?? new Date().toISOString();
|
|
223
212
|
return {
|
|
224
|
-
...
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
213
|
+
...event,
|
|
214
|
+
timestamp,
|
|
215
|
+
_idx: index,
|
|
216
|
+
_seq: seq
|
|
228
217
|
};
|
|
229
218
|
}
|
|
230
219
|
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
if (typeof message.tool_call_id !== 'string' || !message.tool_call_id.trim()) {
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const callId = message.tool_call_id.trim();
|
|
244
|
-
const text = extractMessageText(message.content).trim();
|
|
245
|
-
const existing = resultByCallId.get(callId) ?? { texts: [] };
|
|
246
|
-
if (typeof message.name === 'string' && message.name.trim()) {
|
|
247
|
-
existing.name = message.name.trim();
|
|
248
|
-
}
|
|
249
|
-
existing.texts.push(text);
|
|
250
|
-
resultByCallId.set(callId, existing);
|
|
220
|
+
function inferEventTypeFromMessage(message: SessionMessageView): string {
|
|
221
|
+
const role = normalizeChatRole(message);
|
|
222
|
+
if (role === 'assistant' && hasToolCalls(message)) {
|
|
223
|
+
return 'assistant.tool_call';
|
|
224
|
+
}
|
|
225
|
+
if (role === 'tool') {
|
|
226
|
+
return 'tool.result';
|
|
251
227
|
}
|
|
228
|
+
return `message.${role}`;
|
|
229
|
+
}
|
|
252
230
|
|
|
253
|
-
|
|
231
|
+
export function buildFallbackEventsFromMessages(messages: SessionMessageView[]): SessionEventView[] {
|
|
232
|
+
return messages.map((message, index) => ({
|
|
233
|
+
seq: index + 1,
|
|
234
|
+
type: inferEventTypeFromMessage(message),
|
|
235
|
+
timestamp: message.timestamp,
|
|
236
|
+
message
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
254
239
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
240
|
+
function appendText(base: string, next: string): string {
|
|
241
|
+
if (!next) {
|
|
242
|
+
return base;
|
|
243
|
+
}
|
|
244
|
+
if (!base) {
|
|
245
|
+
return next;
|
|
246
|
+
}
|
|
247
|
+
return `${base}\n\n${next}`;
|
|
248
|
+
}
|
|
259
249
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
250
|
+
export function buildChatTimeline(events: SessionEventView[]): ChatTimelineItem[] {
|
|
251
|
+
const normalized = events
|
|
252
|
+
.map((event, index) => normalizeEvent(event, index))
|
|
253
|
+
.sort((left, right) => {
|
|
254
|
+
if (left._seq !== right._seq) {
|
|
255
|
+
return left._seq - right._seq;
|
|
264
256
|
}
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
257
|
+
const leftTs = Date.parse(left.timestamp);
|
|
258
|
+
const rightTs = Date.parse(right.timestamp);
|
|
259
|
+
if (Number.isFinite(leftTs) && Number.isFinite(rightTs) && leftTs !== rightTs) {
|
|
260
|
+
return leftTs - rightTs;
|
|
268
261
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
...call,
|
|
272
|
-
result_text: result.texts.filter(Boolean).join('\n\n'),
|
|
273
|
-
has_result: true,
|
|
274
|
-
result_name: result.name
|
|
275
|
-
};
|
|
276
|
-
}) as Array<Record<string, unknown>>;
|
|
277
|
-
}
|
|
262
|
+
return left._idx - right._idx;
|
|
263
|
+
});
|
|
278
264
|
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
265
|
+
const timeline: ChatTimelineItem[] = [];
|
|
266
|
+
let activeFlow:
|
|
267
|
+
| {
|
|
268
|
+
item: ChatTimelineAssistantFlowItem;
|
|
269
|
+
cardByCallId: Map<string, ToolCard>;
|
|
270
|
+
pendingCallIds: Set<string>;
|
|
271
|
+
awaitingFollowup: boolean;
|
|
272
|
+
}
|
|
273
|
+
| null = null;
|
|
274
|
+
|
|
275
|
+
const closeActiveFlow = () => {
|
|
276
|
+
activeFlow = null;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
for (const event of normalized) {
|
|
280
|
+
const message = event.message;
|
|
281
|
+
if (!message) {
|
|
294
282
|
continue;
|
|
295
283
|
}
|
|
296
284
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
285
|
+
const role = normalizeChatRole(message);
|
|
286
|
+
const timestamp =
|
|
287
|
+
typeof message.timestamp === 'string' && message.timestamp
|
|
288
|
+
? message.timestamp
|
|
289
|
+
: event.timestamp;
|
|
290
|
+
|
|
291
|
+
if (role === 'assistant' && hasToolCalls(message)) {
|
|
292
|
+
closeActiveFlow();
|
|
293
|
+
const toolCards = buildToolCallCards(message);
|
|
294
|
+
const item: ChatTimelineAssistantFlowItem = {
|
|
295
|
+
kind: 'assistant_flow',
|
|
296
|
+
key: `flow-${event._seq}-${event._idx}`,
|
|
297
|
+
role: 'assistant',
|
|
298
|
+
timestamp,
|
|
299
|
+
primaryText: extractMessageText(message.content).trim(),
|
|
300
|
+
primaryReasoning:
|
|
301
|
+
typeof message.reasoning_content === 'string' ? message.reasoning_content.trim() : '',
|
|
302
|
+
followupText: '',
|
|
303
|
+
followupReasoning: '',
|
|
304
|
+
toolCards
|
|
305
|
+
};
|
|
307
306
|
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
307
|
+
const cardByCallId = new Map<string, ToolCard>();
|
|
308
|
+
const pendingCallIds = new Set<string>();
|
|
309
|
+
for (const card of toolCards) {
|
|
310
|
+
if (typeof card.callId === 'string' && card.callId.trim()) {
|
|
311
|
+
cardByCallId.set(card.callId, card);
|
|
312
|
+
pendingCallIds.add(card.callId);
|
|
313
|
+
}
|
|
312
314
|
}
|
|
313
315
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
timeline.push(item);
|
|
317
|
+
activeFlow = {
|
|
318
|
+
item,
|
|
319
|
+
cardByCallId,
|
|
320
|
+
pendingCallIds,
|
|
321
|
+
awaitingFollowup: pendingCallIds.size === 0
|
|
319
322
|
};
|
|
320
|
-
|
|
323
|
+
continue;
|
|
321
324
|
}
|
|
322
325
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
if (role === 'tool') {
|
|
327
|
+
const callId =
|
|
328
|
+
typeof message.tool_call_id === 'string' && message.tool_call_id.trim()
|
|
329
|
+
? message.tool_call_id.trim()
|
|
330
|
+
: undefined;
|
|
331
|
+
if (activeFlow && callId && activeFlow.cardByCallId.has(callId)) {
|
|
332
|
+
const card = activeFlow.cardByCallId.get(callId)!;
|
|
333
|
+
const resultText = extractMessageText(message.content).trim();
|
|
334
|
+
card.text = appendText(card.text ?? '', resultText);
|
|
335
|
+
card.hasResult = true;
|
|
336
|
+
if (typeof message.name === 'string' && message.name.trim()) {
|
|
337
|
+
card.name = message.name.trim();
|
|
338
|
+
}
|
|
339
|
+
activeFlow.pendingCallIds.delete(callId);
|
|
340
|
+
activeFlow.awaitingFollowup = activeFlow.pendingCallIds.size === 0;
|
|
341
|
+
activeFlow.item.timestamp = timestamp;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
329
344
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
345
|
+
timeline.push({
|
|
346
|
+
kind: 'message',
|
|
347
|
+
key: `message-${event._seq}-${event._idx}`,
|
|
348
|
+
role,
|
|
349
|
+
timestamp,
|
|
350
|
+
message
|
|
351
|
+
});
|
|
352
|
+
closeActiveFlow();
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
333
355
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
previous.role === role &&
|
|
343
|
-
Math.abs(ts - lastTs) <= MERGE_WINDOW_MS;
|
|
344
|
-
|
|
345
|
-
if (canMerge) {
|
|
346
|
-
previous.messages.push(message);
|
|
347
|
-
previous.timestamp = message.timestamp;
|
|
348
|
-
lastTs = ts;
|
|
356
|
+
if (role === 'assistant' && activeFlow && activeFlow.awaitingFollowup && !hasToolCalls(message)) {
|
|
357
|
+
const text = extractMessageText(message.content).trim();
|
|
358
|
+
const reasoning =
|
|
359
|
+
typeof message.reasoning_content === 'string' ? message.reasoning_content.trim() : '';
|
|
360
|
+
activeFlow.item.followupText = appendText(activeFlow.item.followupText, text);
|
|
361
|
+
activeFlow.item.followupReasoning = appendText(activeFlow.item.followupReasoning, reasoning);
|
|
362
|
+
activeFlow.item.timestamp = timestamp;
|
|
363
|
+
closeActiveFlow();
|
|
349
364
|
continue;
|
|
350
365
|
}
|
|
351
366
|
|
|
352
|
-
|
|
353
|
-
|
|
367
|
+
timeline.push({
|
|
368
|
+
kind: 'message',
|
|
369
|
+
key: `message-${event._seq}-${event._idx}`,
|
|
354
370
|
role,
|
|
355
|
-
|
|
356
|
-
|
|
371
|
+
timestamp,
|
|
372
|
+
message
|
|
357
373
|
});
|
|
358
|
-
|
|
374
|
+
closeActiveFlow();
|
|
359
375
|
}
|
|
360
376
|
|
|
361
|
-
return
|
|
377
|
+
return timeline;
|
|
362
378
|
}
|