@amaster.ai/components-templates 1.6.0 → 1.8.0
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/components/ai-assistant/package.json +3 -3
- package/components/ai-assistant/template/components/chat-input.tsx +17 -4
- package/components/ai-assistant/template/components/chat-messages.tsx +1 -1
- package/components/ai-assistant/template/components/voice-input.tsx +9 -1
- package/components/ai-assistant/template/hooks/useConversation.ts +0 -23
- package/components/ai-assistant-taro/package.json +5 -5
- package/components/ai-assistant-taro/template/components/ChatAssistantMessage.tsx +24 -2
- package/components/ai-assistant-taro/template/components/ChatInput.tsx +45 -16
- package/components/ai-assistant-taro/template/components/markdown.tsx +343 -137
- package/components/ai-assistant-taro/template/hooks/useConversation.ts +542 -424
- package/components/ai-assistant-taro/template/index.tsx +2 -2
- package/components/ai-assistant-taro/template/types.ts +16 -0
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
Message1,
|
|
2
3
|
SendStreamingMessageResponse,
|
|
3
4
|
SendStreamingMessageSuccessResponse,
|
|
5
|
+
TextPart,
|
|
4
6
|
} from "@a2a-js/sdk";
|
|
7
|
+
import { produce } from "immer";
|
|
5
8
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
-
import { client } from "
|
|
9
|
+
import { client } from "@/lib/client";
|
|
7
10
|
import { useAiAssistantI18n } from "../i18n";
|
|
8
11
|
import type {
|
|
9
12
|
Conversation,
|
|
@@ -13,8 +16,32 @@ import type {
|
|
|
13
16
|
TextMessage,
|
|
14
17
|
ThoughtMessage,
|
|
15
18
|
ToolMessage,
|
|
19
|
+
UIRenderMessage,
|
|
16
20
|
} from "../types";
|
|
17
21
|
|
|
22
|
+
interface HistoryState {
|
|
23
|
+
next: string | null;
|
|
24
|
+
hasMore: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UpdateMessageInput {
|
|
28
|
+
taskId: string;
|
|
29
|
+
messageId: string;
|
|
30
|
+
content?: string;
|
|
31
|
+
partial?: boolean;
|
|
32
|
+
status?: string;
|
|
33
|
+
response?: any;
|
|
34
|
+
metadata?: Record<string, any>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type MessageHandler = (
|
|
38
|
+
conv: Conversation,
|
|
39
|
+
part: any,
|
|
40
|
+
id: string,
|
|
41
|
+
role: Role,
|
|
42
|
+
status?: any,
|
|
43
|
+
) => Conversation;
|
|
44
|
+
|
|
18
45
|
class SimpleAbortController {
|
|
19
46
|
aborted = false;
|
|
20
47
|
private listeners: (() => void)[] = [];
|
|
@@ -43,26 +70,6 @@ function createAbortController(): AbortController | SimpleAbortController {
|
|
|
43
70
|
return new SimpleAbortController();
|
|
44
71
|
}
|
|
45
72
|
|
|
46
|
-
export interface UpdateMessageInput {
|
|
47
|
-
taskId: string;
|
|
48
|
-
messageId: string;
|
|
49
|
-
content?: string;
|
|
50
|
-
partial?: boolean;
|
|
51
|
-
status?: string;
|
|
52
|
-
response?: any;
|
|
53
|
-
metadata?: Record<string, any>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ================= 消息处理器 =================
|
|
57
|
-
|
|
58
|
-
type MessageHandler = (
|
|
59
|
-
conv: Conversation,
|
|
60
|
-
part: any,
|
|
61
|
-
id: string,
|
|
62
|
-
role: Role,
|
|
63
|
-
status?: any,
|
|
64
|
-
) => Conversation;
|
|
65
|
-
|
|
66
73
|
const handlers: Record<string, MessageHandler> = {
|
|
67
74
|
"text-content": (conv, part, id, role, status) => {
|
|
68
75
|
if (!id) return conv;
|
|
@@ -96,7 +103,6 @@ const handlers: Record<string, MessageHandler> = {
|
|
|
96
103
|
};
|
|
97
104
|
},
|
|
98
105
|
|
|
99
|
-
// thought 类型(逐步追加 description)
|
|
100
106
|
thought: (conv, part, id, role, status) => {
|
|
101
107
|
if (!id) return conv;
|
|
102
108
|
const newChunk = part?.data?.description || "";
|
|
@@ -129,14 +135,13 @@ const handlers: Record<string, MessageHandler> = {
|
|
|
129
135
|
};
|
|
130
136
|
},
|
|
131
137
|
|
|
132
|
-
// 工具调用(状态变更 + 最终结果)
|
|
133
138
|
tool: (conv, part, _id, role, status) => {
|
|
134
139
|
const partData = part?.data || {};
|
|
135
140
|
const tool = partData?.tool;
|
|
136
141
|
const name = tool?.displayName || tool?.name || "";
|
|
137
142
|
const request = partData?.request;
|
|
138
143
|
const id = request?.callId;
|
|
139
|
-
if (!id || !name) return conv;
|
|
144
|
+
if (!id || !name) return conv;
|
|
140
145
|
|
|
141
146
|
const toolStatus = partData?.status;
|
|
142
147
|
const existing = conv.messages.find((m) => m.messageId === id);
|
|
@@ -174,7 +179,7 @@ const handlers: Record<string, MessageHandler> = {
|
|
|
174
179
|
};
|
|
175
180
|
},
|
|
176
181
|
|
|
177
|
-
error: (conv, part, id, role) => {
|
|
182
|
+
error: (conv, part, id, role, _status) => {
|
|
178
183
|
const errorMsg = part?.text || part?.data?.error || "发生错误";
|
|
179
184
|
return {
|
|
180
185
|
...conv,
|
|
@@ -192,60 +197,92 @@ const handlers: Record<string, MessageHandler> = {
|
|
|
192
197
|
lastUpdated: new Date().toISOString(),
|
|
193
198
|
};
|
|
194
199
|
},
|
|
195
|
-
};
|
|
196
200
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
201
|
+
"ui-render": (conv, part, id, role, status) => {
|
|
202
|
+
if (!id) return conv;
|
|
203
|
+
const existing = conv.messages.find((m) => m.messageId === id);
|
|
204
|
+
const partData = part?.data || {};
|
|
205
|
+
const spec = partData.spec || { root: "", elements: {} };
|
|
200
206
|
|
|
201
|
-
|
|
207
|
+
if (existing && existing.kind === "ui-render") {
|
|
208
|
+
return {
|
|
209
|
+
...conv,
|
|
210
|
+
messages: conv.messages.map((m) =>
|
|
211
|
+
m.messageId === id ? { ...(m as UIRenderMessage), spec } : m,
|
|
212
|
+
),
|
|
213
|
+
lastUpdated: new Date().toISOString(),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
202
216
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
217
|
+
return {
|
|
218
|
+
...conv,
|
|
219
|
+
messages: [
|
|
220
|
+
...conv.messages,
|
|
221
|
+
{
|
|
222
|
+
messageId: id,
|
|
223
|
+
role,
|
|
224
|
+
kind: "ui-render",
|
|
225
|
+
spec,
|
|
226
|
+
timestamp: status?.timestamp || new Date().toISOString(),
|
|
227
|
+
} as UIRenderMessage,
|
|
228
|
+
],
|
|
229
|
+
lastUpdated: new Date().toISOString(),
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
};
|
|
207
233
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
234
|
+
const getKind = (part: any, metadata?: any) => {
|
|
235
|
+
let kind = "unknown";
|
|
236
|
+
if (part?.data?.tool) {
|
|
237
|
+
kind = "tool";
|
|
238
|
+
} else if (part?.data?.type === "ui") {
|
|
239
|
+
kind = "ui-render";
|
|
240
|
+
} else if (part?.kind === "text") {
|
|
241
|
+
kind = "text-content";
|
|
242
|
+
} else if (
|
|
243
|
+
part?.data?.type === "though" ||
|
|
244
|
+
metadata?.coderAgent?.kind === "thought"
|
|
245
|
+
) {
|
|
246
|
+
kind = "thought";
|
|
247
|
+
} else if (metadata?.error) {
|
|
248
|
+
kind = "error";
|
|
249
|
+
}
|
|
250
|
+
return kind;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const generateId = () =>
|
|
254
|
+
`msg-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
214
255
|
|
|
215
|
-
|
|
256
|
+
type ConversationsState = {
|
|
257
|
+
byId: Record<string, Conversation>;
|
|
258
|
+
order: string[];
|
|
259
|
+
};
|
|
216
260
|
|
|
217
|
-
export function
|
|
218
|
-
const [
|
|
219
|
-
|
|
220
|
-
|
|
261
|
+
export function useConversation() {
|
|
262
|
+
const [state, setState] = useState<ConversationsState>({
|
|
263
|
+
byId: {},
|
|
264
|
+
order: [],
|
|
265
|
+
});
|
|
221
266
|
const [isLoading, setIsLoading] = useState(false);
|
|
222
267
|
const [isLoadingHistory, setIsLoadingHistory] = useState(false);
|
|
223
268
|
const [historyState, setHistoryState] = useState<HistoryState>({
|
|
224
269
|
next: null,
|
|
225
270
|
hasMore: false,
|
|
226
271
|
});
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const abortControllerRef = useRef<
|
|
230
|
-
AbortController | SimpleAbortController | null
|
|
231
|
-
>(null);
|
|
272
|
+
const loadingRef = useRef(false);
|
|
273
|
+
const abortControllerRef = useRef<any>(null);
|
|
232
274
|
const forceStopRef = useRef(false);
|
|
233
|
-
const lastMessageTypeRef = useRef<string>("");
|
|
275
|
+
const lastMessageTypeRef = useRef<string>("unknown");
|
|
234
276
|
const lastMessageIdRef = useRef<string>("");
|
|
277
|
+
const lastMessageRoleRef = useRef<Role | string>("");
|
|
235
278
|
const currentStreamingTaskIdRef = useRef<string | null>(null);
|
|
236
|
-
const
|
|
279
|
+
const [starting, setStarting] = useState(true);
|
|
280
|
+
const {t} = useAiAssistantI18n();
|
|
237
281
|
|
|
238
282
|
useEffect(() => {
|
|
239
283
|
loadingRef.current = isLoading;
|
|
240
284
|
}, [isLoading]);
|
|
241
285
|
|
|
242
|
-
const handleEnd = useCallback(() => {
|
|
243
|
-
setIsLoading(false);
|
|
244
|
-
lastMessageTypeRef.current = "";
|
|
245
|
-
lastMessageIdRef.current = "";
|
|
246
|
-
currentStreamingTaskIdRef.current = null;
|
|
247
|
-
}, []);
|
|
248
|
-
|
|
249
286
|
const abort = useCallback(() => {
|
|
250
287
|
if (abortControllerRef.current) {
|
|
251
288
|
abortControllerRef.current.abort();
|
|
@@ -255,167 +292,218 @@ export function useConversationProcessor() {
|
|
|
255
292
|
setIsLoading(false);
|
|
256
293
|
}, []);
|
|
257
294
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
295
|
+
const handleEnd = useCallback(() => {
|
|
296
|
+
abort();
|
|
297
|
+
lastMessageTypeRef.current = "";
|
|
298
|
+
lastMessageIdRef.current = "";
|
|
299
|
+
lastMessageRoleRef.current = "";
|
|
300
|
+
currentStreamingTaskIdRef.current = null;
|
|
301
|
+
}, [abort]);
|
|
302
|
+
|
|
303
|
+
// 填充数据到 conversation 中
|
|
304
|
+
const fillData = ({
|
|
305
|
+
kind,
|
|
306
|
+
taskId,
|
|
307
|
+
part,
|
|
308
|
+
status,
|
|
309
|
+
handler,
|
|
310
|
+
isHistory,
|
|
311
|
+
historyId,
|
|
312
|
+
messageId,
|
|
313
|
+
}: {
|
|
314
|
+
kind: string;
|
|
315
|
+
taskId: string;
|
|
316
|
+
part: any;
|
|
317
|
+
status: any;
|
|
318
|
+
handler: MessageHandler;
|
|
319
|
+
isHistory?: boolean;
|
|
320
|
+
historyId?: string;
|
|
321
|
+
messageId?: string;
|
|
322
|
+
}) => {
|
|
323
|
+
setState(
|
|
324
|
+
produce((draft) => {
|
|
325
|
+
let id = messageId || lastMessageIdRef.current;
|
|
326
|
+
const role = status?.message?.role === "agent" ? "assistant" : "user";
|
|
327
|
+
|
|
328
|
+
if (
|
|
329
|
+
!messageId &&
|
|
330
|
+
(kind === "ui-render" ||
|
|
331
|
+
(kind !== "unknown" && kind !== lastMessageTypeRef.current))
|
|
332
|
+
) {
|
|
333
|
+
lastMessageTypeRef.current = kind;
|
|
334
|
+
id = generateId();
|
|
335
|
+
lastMessageIdRef.current = id;
|
|
336
|
+
}
|
|
282
337
|
|
|
283
|
-
|
|
284
|
-
setConversationsMap((prev) => {
|
|
285
|
-
const prevConv = prev.get(taskId) ?? {
|
|
338
|
+
const prevConv = draft.byId[taskId] ?? {
|
|
286
339
|
taskId,
|
|
287
|
-
status: "completed"
|
|
340
|
+
status: isHistory ? "completed" : "submitted",
|
|
288
341
|
messages: [],
|
|
342
|
+
role,
|
|
289
343
|
historyId,
|
|
290
344
|
lastUpdated: new Date().toISOString(),
|
|
291
345
|
};
|
|
292
346
|
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
const nextConv = handler(prevConv, firstPart, messageId, role, {
|
|
296
|
-
message: data,
|
|
297
|
-
});
|
|
347
|
+
const nextConv = handler(prevConv, part, id, role, status);
|
|
298
348
|
|
|
299
|
-
|
|
300
|
-
nextMap.set(taskId, {
|
|
349
|
+
draft.byId[taskId] = {
|
|
301
350
|
...nextConv,
|
|
351
|
+
status: status?.state || prevConv.status,
|
|
302
352
|
lastUpdated: new Date().toISOString(),
|
|
303
|
-
}
|
|
304
|
-
return nextMap;
|
|
305
|
-
});
|
|
306
|
-
},
|
|
307
|
-
[],
|
|
308
|
-
);
|
|
353
|
+
};
|
|
309
354
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
355
|
+
const index = draft.order.indexOf(taskId);
|
|
356
|
+
if (index !== -1) {
|
|
357
|
+
draft.order.splice(index, 1);
|
|
358
|
+
}
|
|
313
359
|
|
|
314
|
-
|
|
360
|
+
if (!isHistory) {
|
|
361
|
+
draft.order.push(taskId);
|
|
362
|
+
}
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
};
|
|
315
366
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
367
|
+
// 处理实时数据
|
|
368
|
+
const processLiveData = useCallback(
|
|
369
|
+
(data: SendStreamingMessageResponse) => {
|
|
370
|
+
if (!(data as any)?.result) return;
|
|
371
|
+
const result = (data as SendStreamingMessageSuccessResponse).result;
|
|
320
372
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const parts = status?.message?.parts || [];
|
|
324
|
-
const part = parts?.[0];
|
|
325
|
-
|
|
326
|
-
let kind = "unknown";
|
|
327
|
-
if (part?.data?.tool) {
|
|
328
|
-
kind = "tool";
|
|
329
|
-
} else if (part?.data?.type === "ui") {
|
|
330
|
-
kind = "ui-render";
|
|
331
|
-
} else if (part?.kind === "text") {
|
|
332
|
-
kind = "text-content";
|
|
333
|
-
} else if (
|
|
334
|
-
part?.data?.type === "though" ||
|
|
335
|
-
metadata?.coderAgent?.kind === "thought"
|
|
336
|
-
) {
|
|
337
|
-
kind = "thought";
|
|
338
|
-
} else if (metadata?.error) {
|
|
339
|
-
kind = "error";
|
|
340
|
-
}
|
|
373
|
+
const { taskId, status, metadata, final } = result as any;
|
|
341
374
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
375
|
+
if (final) {
|
|
376
|
+
handleEnd();
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
347
379
|
|
|
348
|
-
|
|
349
|
-
lastMessageTypeRef.current = kind;
|
|
350
|
-
lastMessageIdRef.current = generateId();
|
|
351
|
-
}
|
|
380
|
+
if (!taskId) return;
|
|
352
381
|
|
|
353
|
-
|
|
382
|
+
currentStreamingTaskIdRef.current = taskId;
|
|
354
383
|
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
384
|
+
const parts = status?.message?.parts || [];
|
|
385
|
+
const firstPart = parts?.[0];
|
|
386
|
+
const kind = getKind(firstPart, metadata);
|
|
387
|
+
|
|
388
|
+
const handler = handlers[kind];
|
|
389
|
+
if (!handler) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
removeLoadingPlaceholder();
|
|
362
393
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
(status?.message?.role === "agent" ? "assistant" : "") || "assistant",
|
|
394
|
+
fillData({
|
|
395
|
+
kind,
|
|
396
|
+
taskId,
|
|
397
|
+
part: firstPart,
|
|
368
398
|
status,
|
|
369
|
-
|
|
399
|
+
handler,
|
|
400
|
+
});
|
|
401
|
+
},
|
|
402
|
+
[handleEnd],
|
|
403
|
+
);
|
|
370
404
|
|
|
371
|
-
|
|
405
|
+
const processHistoryData = useCallback(
|
|
406
|
+
(data: Message1, taskId: string) => {
|
|
407
|
+
const { messageId, historyId } = (data || {}) as any;
|
|
408
|
+
if (!messageId) return;
|
|
372
409
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
410
|
+
const parts = data.parts || [];
|
|
411
|
+
const firstPart = parts?.[0];
|
|
412
|
+
|
|
413
|
+
const kind = getKind(firstPart);
|
|
414
|
+
|
|
415
|
+
if (kind === "text-content") {
|
|
416
|
+
let text = (firstPart as TextPart).text || "";
|
|
417
|
+
text = text.trim();
|
|
418
|
+
if (text.startsWith("<ui>")) {
|
|
419
|
+
const json = text.slice(4).trim();
|
|
420
|
+
try {
|
|
421
|
+
const partData = JSON.parse(json);
|
|
422
|
+
if (partData.root && partData.elements) {
|
|
423
|
+
fillData({
|
|
424
|
+
kind: 'ui-render',
|
|
425
|
+
taskId,
|
|
426
|
+
part: { kind: 'data', data: { type: "ui", spec: partData } },
|
|
427
|
+
handler: handlers['ui-render'],
|
|
428
|
+
isHistory: true,
|
|
429
|
+
historyId,
|
|
430
|
+
messageId,
|
|
431
|
+
status: { message: data },
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
} catch (error) {
|
|
435
|
+
// 解析失败,有可能是半截
|
|
436
|
+
}
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const handler = handlers[kind];
|
|
442
|
+
if (!handler) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
fillData({
|
|
446
|
+
kind,
|
|
447
|
+
taskId,
|
|
448
|
+
part: firstPart,
|
|
449
|
+
handler,
|
|
450
|
+
isHistory: true,
|
|
451
|
+
historyId,
|
|
452
|
+
messageId,
|
|
453
|
+
status: { message: data },
|
|
377
454
|
});
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
455
|
+
},
|
|
456
|
+
[handleEnd],
|
|
457
|
+
);
|
|
381
458
|
|
|
382
459
|
const conversations = useMemo(() => {
|
|
383
|
-
return
|
|
384
|
-
|
|
460
|
+
return state.order
|
|
461
|
+
.map((id) => state.byId[id])
|
|
462
|
+
.filter(Boolean) as Conversation[];
|
|
463
|
+
}, [state]);
|
|
385
464
|
|
|
386
465
|
const getConversation = useCallback(
|
|
387
466
|
(taskId: string) => {
|
|
388
|
-
return
|
|
467
|
+
return state.byId[taskId];
|
|
389
468
|
},
|
|
390
|
-
[
|
|
469
|
+
[state],
|
|
391
470
|
);
|
|
392
471
|
|
|
393
472
|
const clearConversation = useCallback((taskId: string) => {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
473
|
+
setState(
|
|
474
|
+
produce((draft) => {
|
|
475
|
+
if (!draft.byId[taskId]) return;
|
|
476
|
+
delete draft.byId[taskId];
|
|
477
|
+
draft.order = draft.order.filter((id) => id !== taskId);
|
|
478
|
+
}),
|
|
479
|
+
);
|
|
400
480
|
}, []);
|
|
401
481
|
|
|
402
482
|
const removeMessage = useCallback((taskId: string, messageId: string) => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
483
|
+
setState(
|
|
484
|
+
produce((draft) => {
|
|
485
|
+
const conv = draft.byId[taskId];
|
|
486
|
+
if (!conv) return;
|
|
406
487
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
};
|
|
488
|
+
const nextMessages = conv.messages.filter(
|
|
489
|
+
(m) => m.messageId !== messageId,
|
|
490
|
+
);
|
|
411
491
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
492
|
+
draft.byId[taskId] = {
|
|
493
|
+
...conv,
|
|
494
|
+
messages: nextMessages,
|
|
495
|
+
lastUpdated: new Date().toISOString(),
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const index = draft.order.indexOf(taskId);
|
|
499
|
+
if (index !== -1) {
|
|
500
|
+
draft.order.splice(index, 1);
|
|
501
|
+
}
|
|
502
|
+
draft.order.push(taskId);
|
|
503
|
+
}),
|
|
504
|
+
);
|
|
416
505
|
}, []);
|
|
417
506
|
|
|
418
|
-
// 新增:更新已有消息
|
|
419
507
|
const updateMessage = useCallback((input: UpdateMessageInput) => {
|
|
420
508
|
const {
|
|
421
509
|
taskId,
|
|
@@ -427,72 +515,75 @@ export function useConversationProcessor() {
|
|
|
427
515
|
metadata = {},
|
|
428
516
|
} = input;
|
|
429
517
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
518
|
+
setState(
|
|
519
|
+
produce((draft) => {
|
|
520
|
+
const conv = draft.byId[taskId];
|
|
521
|
+
if (!conv) return;
|
|
433
522
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
);
|
|
437
|
-
if (msgIndex === -1) {
|
|
438
|
-
console.warn(
|
|
439
|
-
`Message ${messageId} not found in conversation ${taskId}`,
|
|
523
|
+
const msgIndex = conv.messages.findIndex(
|
|
524
|
+
(m) => m.messageId === messageId,
|
|
440
525
|
);
|
|
441
|
-
|
|
442
|
-
|
|
526
|
+
if (msgIndex === -1) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
443
529
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
: content;
|
|
530
|
+
const oldMsg = conv.messages[msgIndex];
|
|
531
|
+
const updatedMsg: MessagesItem = { ...oldMsg, ...metadata };
|
|
532
|
+
|
|
533
|
+
if (content !== undefined) {
|
|
534
|
+
if ("content" in updatedMsg) {
|
|
535
|
+
updatedMsg.content = partial
|
|
536
|
+
? (updatedMsg as TextMessage).content + content
|
|
537
|
+
: content;
|
|
538
|
+
} else if ("thought" in updatedMsg) {
|
|
539
|
+
updatedMsg.thought = partial
|
|
540
|
+
? (updatedMsg as ThoughtMessage).thought + content
|
|
541
|
+
: content;
|
|
542
|
+
} else if (oldMsg.kind === "text-content") {
|
|
543
|
+
(updatedMsg as any).content = partial
|
|
544
|
+
? ((oldMsg as any).content || "") + content
|
|
545
|
+
: content;
|
|
546
|
+
}
|
|
462
547
|
}
|
|
463
|
-
}
|
|
464
548
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
549
|
+
if ("toolStatus" in updatedMsg || status) {
|
|
550
|
+
(updatedMsg as any).toolStatus =
|
|
551
|
+
status || (updatedMsg as any).toolStatus;
|
|
552
|
+
}
|
|
553
|
+
if (response !== undefined) {
|
|
554
|
+
(updatedMsg as any).response = response;
|
|
555
|
+
}
|
|
473
556
|
|
|
474
|
-
|
|
475
|
-
|
|
557
|
+
const nextMessages = [...conv.messages];
|
|
558
|
+
nextMessages[msgIndex] = updatedMsg;
|
|
476
559
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
560
|
+
draft.byId[taskId] = {
|
|
561
|
+
...conv,
|
|
562
|
+
messages: nextMessages,
|
|
563
|
+
lastUpdated: new Date().toISOString(),
|
|
564
|
+
};
|
|
482
565
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
566
|
+
const index = draft.order.indexOf(taskId);
|
|
567
|
+
if (index !== -1) {
|
|
568
|
+
draft.order.splice(index, 1);
|
|
569
|
+
}
|
|
570
|
+
draft.order.push(taskId);
|
|
571
|
+
}),
|
|
572
|
+
);
|
|
487
573
|
}, []);
|
|
488
574
|
|
|
489
575
|
const addConversation = useCallback((conversation: Conversation) => {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
576
|
+
setState(
|
|
577
|
+
produce((draft) => {
|
|
578
|
+
const id = conversation.taskId;
|
|
579
|
+
draft.byId[id] = conversation;
|
|
580
|
+
const index = draft.order.indexOf(id);
|
|
581
|
+
if (index !== -1) {
|
|
582
|
+
draft.order.splice(index, 1);
|
|
583
|
+
}
|
|
584
|
+
draft.order.push(id);
|
|
585
|
+
}),
|
|
586
|
+
);
|
|
496
587
|
}, []);
|
|
497
588
|
|
|
498
589
|
const addLoadingPlaceholder = useCallback(() => {
|
|
@@ -510,260 +601,288 @@ export function useConversationProcessor() {
|
|
|
510
601
|
],
|
|
511
602
|
lastUpdated: new Date().toISOString(),
|
|
512
603
|
});
|
|
513
|
-
}, [
|
|
604
|
+
}, [addConversation]);
|
|
605
|
+
|
|
606
|
+
const removeLoadingPlaceholder = useCallback(() => {
|
|
607
|
+
clearConversation("loading-placeholder");
|
|
608
|
+
}, [clearConversation]);
|
|
514
609
|
|
|
515
610
|
useEffect(() => {
|
|
516
611
|
if (!isLoading) {
|
|
517
612
|
removeLoadingPlaceholder();
|
|
518
613
|
}
|
|
519
|
-
}, [isLoading]);
|
|
614
|
+
}, [isLoading, removeLoadingPlaceholder]);
|
|
520
615
|
|
|
521
|
-
const
|
|
522
|
-
|
|
616
|
+
const onSendError = useCallback(() => {
|
|
617
|
+
addConversation({
|
|
618
|
+
taskId: generateId(),
|
|
619
|
+
status: "error",
|
|
620
|
+
messages: [
|
|
621
|
+
{
|
|
622
|
+
messageId: `error-${Date.now()}`,
|
|
623
|
+
role: "assistant",
|
|
624
|
+
kind: "error",
|
|
625
|
+
content: t.errorMessage,
|
|
626
|
+
timestamp: new Date().toISOString(),
|
|
627
|
+
} as ErrorMessage,
|
|
628
|
+
],
|
|
629
|
+
lastUpdated: new Date().toISOString(),
|
|
630
|
+
});
|
|
631
|
+
setIsLoading(false);
|
|
632
|
+
}, [addConversation]);
|
|
633
|
+
|
|
634
|
+
const loadHistory = useCallback(async (limit = 20, next?: string) => {
|
|
635
|
+
setIsLoadingHistory(true);
|
|
636
|
+
let scrollToId = "";
|
|
637
|
+
try {
|
|
638
|
+
const copilot = client.copilot as any;
|
|
639
|
+
const result = await copilot.getHistory?.(limit, next);
|
|
640
|
+
const messages = result?.messages || [];
|
|
641
|
+
const length = messages.length;
|
|
642
|
+
scrollToId = length > 0 ? messages?.[length - 1]?.messageId : "";
|
|
643
|
+
|
|
644
|
+
let taskId = "";
|
|
645
|
+
let lastRole = "";
|
|
646
|
+
const taskIds: string[] = [];
|
|
647
|
+
messages.forEach((msg: any) => {
|
|
648
|
+
const role = msg.role === "agent" ? "assistant" : "user";
|
|
649
|
+
if (role !== lastRole) {
|
|
650
|
+
taskId = `history-conv-${generateId()}`;
|
|
651
|
+
taskIds.push(taskId);
|
|
652
|
+
lastRole = role;
|
|
653
|
+
}
|
|
654
|
+
processHistoryData(msg, taskId);
|
|
655
|
+
});
|
|
656
|
+
setState(
|
|
657
|
+
produce((draft) => {
|
|
658
|
+
draft.order = [...taskIds, ...draft.order];
|
|
659
|
+
}),
|
|
660
|
+
);
|
|
661
|
+
setHistoryState({
|
|
662
|
+
next: result?.next || null,
|
|
663
|
+
hasMore: result?.hasMore || false,
|
|
664
|
+
});
|
|
665
|
+
} catch (error) {
|
|
666
|
+
console.error("[Chat] Failed to load history:", error);
|
|
667
|
+
} finally {
|
|
668
|
+
setIsLoadingHistory(false);
|
|
669
|
+
scrollToId &&
|
|
670
|
+
next &&
|
|
671
|
+
setTimeout(() => {
|
|
672
|
+
document
|
|
673
|
+
.querySelector(`[data-conversation-message-id="${scrollToId}"]`)
|
|
674
|
+
?.scrollIntoView({ behavior: "instant", block: "center" });
|
|
675
|
+
}, 20);
|
|
676
|
+
}
|
|
523
677
|
}, []);
|
|
524
678
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
679
|
+
const loadMoreHistory = useCallback(async () => {
|
|
680
|
+
if (!historyState.hasMore || isLoadingHistory || !historyState.next) return;
|
|
681
|
+
await loadHistory(20, historyState.next);
|
|
682
|
+
}, [historyState.hasMore, historyState.next, isLoadingHistory]);
|
|
529
683
|
|
|
530
|
-
|
|
684
|
+
const checkActiveTask = useCallback(async () => {
|
|
685
|
+
try {
|
|
686
|
+
const result = await client.copilot.getChatStatus();
|
|
687
|
+
const taskId = result.taskId;
|
|
688
|
+
if (taskId && result.working) {
|
|
689
|
+
currentStreamingTaskIdRef.current = taskId;
|
|
531
690
|
|
|
532
|
-
|
|
533
|
-
|
|
691
|
+
const stream = await client.copilot.chat([], { taskId });
|
|
692
|
+
for await (const chunk of stream) {
|
|
693
|
+
if (forceStopRef.current) break;
|
|
694
|
+
processLiveData(chunk);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
} catch (error) {
|
|
698
|
+
console.error("[Chat] Failed to check active task:", error);
|
|
699
|
+
}
|
|
700
|
+
}, [processLiveData]);
|
|
534
701
|
|
|
702
|
+
const cancelChat = useCallback(
|
|
703
|
+
async (taskId?: string) => {
|
|
704
|
+
abort();
|
|
535
705
|
addConversation({
|
|
536
|
-
taskId,
|
|
537
|
-
status: "
|
|
706
|
+
taskId: generateId(),
|
|
707
|
+
status: "error",
|
|
538
708
|
messages: [
|
|
539
709
|
{
|
|
540
|
-
messageId:
|
|
541
|
-
role: "
|
|
710
|
+
messageId: `cancel-${Date.now()}`,
|
|
711
|
+
role: "assistant",
|
|
542
712
|
kind: "text-content",
|
|
543
|
-
content:
|
|
713
|
+
content: t.cancelled,
|
|
544
714
|
timestamp: new Date().toISOString(),
|
|
545
|
-
},
|
|
715
|
+
} as TextMessage,
|
|
546
716
|
],
|
|
547
717
|
lastUpdated: new Date().toISOString(),
|
|
548
718
|
});
|
|
719
|
+
const targetTaskId = taskId || currentStreamingTaskIdRef.current;
|
|
720
|
+
if (!targetTaskId) return;
|
|
549
721
|
|
|
550
|
-
|
|
722
|
+
try {
|
|
723
|
+
await client.copilot.cancelChat(targetTaskId);
|
|
724
|
+
currentStreamingTaskIdRef.current = null;
|
|
725
|
+
} catch (error) {
|
|
726
|
+
console.error("[Chat] Failed to cancel chat:", error);
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
[abort],
|
|
730
|
+
);
|
|
551
731
|
|
|
552
|
-
|
|
732
|
+
const resetConversation = useCallback(
|
|
733
|
+
(greeting?: string) => {
|
|
734
|
+
abort();
|
|
735
|
+
setState({ byId: {}, order: [] });
|
|
736
|
+
currentStreamingTaskIdRef.current = null;
|
|
737
|
+
|
|
738
|
+
if (greeting) {
|
|
739
|
+
const taskId = `conv-${Date.now()}`;
|
|
740
|
+
setState({
|
|
741
|
+
byId: {
|
|
742
|
+
[taskId]: {
|
|
743
|
+
taskId,
|
|
744
|
+
status: "submitted",
|
|
745
|
+
messages: [
|
|
746
|
+
{
|
|
747
|
+
messageId: `greeting-${Date.now()}`,
|
|
748
|
+
role: "assistant",
|
|
749
|
+
kind: "text-content",
|
|
750
|
+
content: greeting,
|
|
751
|
+
timestamp: new Date().toISOString(),
|
|
752
|
+
} as TextMessage,
|
|
753
|
+
],
|
|
754
|
+
lastUpdated: new Date().toISOString(),
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
order: [taskId],
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
[abort],
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
const startNewConversation = useCallback(async () => {
|
|
765
|
+
addConversation({
|
|
766
|
+
taskId: generateId(),
|
|
767
|
+
status: "submitted",
|
|
768
|
+
messages: [],
|
|
769
|
+
lastUpdated: new Date().toISOString(),
|
|
770
|
+
system: { level: "newConversation" },
|
|
771
|
+
});
|
|
772
|
+
try {
|
|
773
|
+
const copilot = client.copilot as any;
|
|
774
|
+
await copilot.newConversation?.();
|
|
775
|
+
} catch (error) {
|
|
776
|
+
console.error("[Chat] Failed to create new conversation:", error);
|
|
777
|
+
}
|
|
778
|
+
}, []);
|
|
779
|
+
|
|
780
|
+
const subscribeChatEvents = useCallback(
|
|
781
|
+
async ({
|
|
782
|
+
userContent,
|
|
783
|
+
taskId,
|
|
784
|
+
}: {
|
|
785
|
+
userContent?: string;
|
|
786
|
+
taskId?: string;
|
|
787
|
+
}) => {
|
|
788
|
+
let hasResponse = false;
|
|
553
789
|
|
|
554
790
|
if (abortControllerRef.current) {
|
|
555
791
|
abortControllerRef.current.abort();
|
|
556
792
|
}
|
|
557
|
-
|
|
793
|
+
if (typeof AbortController !== "undefined") {
|
|
794
|
+
abortControllerRef.current = createAbortController();
|
|
795
|
+
}
|
|
558
796
|
const controller = abortControllerRef.current;
|
|
559
|
-
|
|
560
797
|
forceStopRef.current = false;
|
|
561
798
|
|
|
799
|
+
// real chat
|
|
562
800
|
try {
|
|
563
|
-
const stream = client.copilot.chat(
|
|
564
|
-
{ role: "user", content: userContent },
|
|
565
|
-
|
|
801
|
+
const stream = client.copilot.chat(
|
|
802
|
+
userContent ? [{ role: "user", content: userContent }] : [],
|
|
803
|
+
taskId ? { taskId } : undefined,
|
|
804
|
+
);
|
|
566
805
|
|
|
567
806
|
for await (const chunk of stream) {
|
|
568
807
|
if (controller?.signal.aborted || forceStopRef.current) break;
|
|
808
|
+
hasResponse = true;
|
|
569
809
|
|
|
570
|
-
|
|
571
|
-
processDataLine(chunk);
|
|
810
|
+
processLiveData(chunk);
|
|
572
811
|
|
|
573
|
-
// 检查是否结束
|
|
574
812
|
if ((chunk as any)?.isFinal || (chunk as any)?.final) {
|
|
575
813
|
break;
|
|
576
814
|
}
|
|
577
815
|
}
|
|
578
816
|
} catch (err: any) {
|
|
579
817
|
if (err.name === "AbortError" || forceStopRef.current) {
|
|
580
|
-
console.
|
|
818
|
+
console.error("[Chat] Request aborted", err);
|
|
581
819
|
} else {
|
|
582
820
|
console.error("[Chat] Stream error:", err);
|
|
583
|
-
|
|
584
|
-
addConversation({
|
|
585
|
-
taskId: id,
|
|
586
|
-
status: "error",
|
|
587
|
-
messages: [
|
|
588
|
-
{
|
|
589
|
-
messageId: `error-${Date.now()}`,
|
|
590
|
-
role: "assistant",
|
|
591
|
-
kind: "error",
|
|
592
|
-
content: t.errorMessage,
|
|
593
|
-
timestamp: new Date().toISOString(),
|
|
594
|
-
} as ErrorMessage,
|
|
595
|
-
],
|
|
596
|
-
lastUpdated: new Date().toISOString(),
|
|
597
|
-
});
|
|
821
|
+
onSendError();
|
|
598
822
|
}
|
|
599
823
|
} finally {
|
|
600
|
-
|
|
601
|
-
if (
|
|
602
|
-
|
|
824
|
+
abort();
|
|
825
|
+
if (!hasResponse) {
|
|
826
|
+
onSendError();
|
|
603
827
|
}
|
|
604
828
|
}
|
|
605
829
|
},
|
|
606
|
-
[
|
|
830
|
+
[],
|
|
607
831
|
);
|
|
608
832
|
|
|
609
|
-
const
|
|
610
|
-
(
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
setConversationsMap(new Map());
|
|
614
|
-
setIsLoading(false);
|
|
615
|
-
setHistoryState({ next: null, hasMore: false });
|
|
616
|
-
|
|
617
|
-
if (welcome) {
|
|
618
|
-
const taskId = `conv-${Date.now()}`;
|
|
619
|
-
setConversationsMap(
|
|
620
|
-
new Map([
|
|
621
|
-
[
|
|
622
|
-
taskId,
|
|
623
|
-
{
|
|
624
|
-
taskId,
|
|
625
|
-
status: "submitted",
|
|
626
|
-
messages: [
|
|
627
|
-
{
|
|
628
|
-
messageId: `greeting-${Date.now()}`,
|
|
629
|
-
role: "assistant",
|
|
630
|
-
kind: "text-content",
|
|
631
|
-
content: welcome,
|
|
632
|
-
timestamp: new Date().toISOString(),
|
|
633
|
-
} as TextMessage,
|
|
634
|
-
],
|
|
635
|
-
lastUpdated: new Date().toISOString(),
|
|
636
|
-
},
|
|
637
|
-
],
|
|
638
|
-
]),
|
|
639
|
-
);
|
|
833
|
+
const sendMessage = useCallback(
|
|
834
|
+
async (userContent: string) => {
|
|
835
|
+
if (loadingRef.current) {
|
|
836
|
+
return;
|
|
640
837
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
);
|
|
838
|
+
if (!userContent.trim()) return;
|
|
839
|
+
const userMsgId = generateId();
|
|
644
840
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
if (role !== lastRole) {
|
|
660
|
-
taskId = `history-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
661
|
-
taskIds.push(taskId);
|
|
662
|
-
lastRole = role;
|
|
663
|
-
}
|
|
664
|
-
processHistoryData(msg, taskId);
|
|
665
|
-
});
|
|
841
|
+
addConversation({
|
|
842
|
+
taskId: generateId(),
|
|
843
|
+
status: "submitted",
|
|
844
|
+
messages: [
|
|
845
|
+
{
|
|
846
|
+
messageId: userMsgId,
|
|
847
|
+
role: "user",
|
|
848
|
+
kind: "text-content",
|
|
849
|
+
content: userContent,
|
|
850
|
+
timestamp: new Date().toISOString(),
|
|
851
|
+
},
|
|
852
|
+
],
|
|
853
|
+
lastUpdated: new Date().toISOString(),
|
|
854
|
+
});
|
|
666
855
|
|
|
667
|
-
|
|
668
|
-
next: result?.next || null,
|
|
669
|
-
hasMore: result?.hasMore || false,
|
|
670
|
-
});
|
|
856
|
+
addLoadingPlaceholder();
|
|
671
857
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
console.error("[Chat] Failed to load history:", error);
|
|
675
|
-
return { taskIds: [], hasMore: false };
|
|
676
|
-
} finally {
|
|
677
|
-
setIsLoadingHistory(false);
|
|
678
|
-
}
|
|
858
|
+
setIsLoading(true);
|
|
859
|
+
subscribeChatEvents({ userContent });
|
|
679
860
|
},
|
|
680
|
-
[
|
|
861
|
+
[
|
|
862
|
+
addConversation,
|
|
863
|
+
addLoadingPlaceholder,
|
|
864
|
+
processLiveData,
|
|
865
|
+
onSendError,
|
|
866
|
+
abort,
|
|
867
|
+
],
|
|
681
868
|
);
|
|
682
869
|
|
|
683
|
-
const loadMoreHistory = useCallback(async () => {
|
|
684
|
-
if (!historyState.hasMore || isLoadingHistory || !historyState.next) return;
|
|
685
|
-
await loadHistory(20, historyState.next || undefined);
|
|
686
|
-
}, [historyState.hasMore, historyState.next, isLoadingHistory, loadHistory]);
|
|
687
|
-
|
|
688
|
-
const checkActiveTask = useCallback(async () => {
|
|
689
|
-
try {
|
|
690
|
-
const result = await client.copilot.getChatStatus();
|
|
691
|
-
const taskId = result.taskId;
|
|
692
|
-
if (taskId && result.working) {
|
|
693
|
-
currentStreamingTaskIdRef.current = taskId;
|
|
694
|
-
setIsLoading(true);
|
|
695
|
-
|
|
696
|
-
const stream = await client.copilot.chat([], { taskId });
|
|
697
|
-
for await (const chunk of stream) {
|
|
698
|
-
if (forceStopRef.current) break;
|
|
699
|
-
processDataLine(chunk);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
} catch (error) {
|
|
703
|
-
console.error("[Chat] Failed to check active task:", error);
|
|
704
|
-
}
|
|
705
|
-
}, [processDataLine]);
|
|
706
|
-
|
|
707
|
-
const cancelChat = useCallback(async () => {
|
|
708
|
-
abort();
|
|
709
|
-
|
|
710
|
-
const targetTaskId = currentStreamingTaskIdRef.current;
|
|
711
|
-
if (!targetTaskId) return;
|
|
712
|
-
|
|
713
|
-
addConversation({
|
|
714
|
-
taskId: generateId(),
|
|
715
|
-
status: "completed",
|
|
716
|
-
messages: [
|
|
717
|
-
{
|
|
718
|
-
messageId: `cancel-${Date.now()}`,
|
|
719
|
-
role: "assistant",
|
|
720
|
-
kind: "text-content",
|
|
721
|
-
content: t.cancelled,
|
|
722
|
-
timestamp: new Date().toISOString(),
|
|
723
|
-
} as TextMessage,
|
|
724
|
-
],
|
|
725
|
-
lastUpdated: new Date().toISOString(),
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
try {
|
|
729
|
-
await client.copilot.cancelChat(targetTaskId);
|
|
730
|
-
currentStreamingTaskIdRef.current = null;
|
|
731
|
-
} catch (error) {
|
|
732
|
-
console.error("[Chat] Failed to cancel chat:", error);
|
|
733
|
-
}
|
|
734
|
-
}, [abort, addConversation, t.cancelled]);
|
|
735
|
-
|
|
736
|
-
const startNewConversation = useCallback(async () => {
|
|
737
|
-
addConversation({
|
|
738
|
-
taskId: generateId(),
|
|
739
|
-
status: "submitted",
|
|
740
|
-
messages: [],
|
|
741
|
-
lastUpdated: new Date().toISOString(),
|
|
742
|
-
system: { level: "newConversation" },
|
|
743
|
-
});
|
|
744
|
-
try {
|
|
745
|
-
const copilot = client.copilot as any;
|
|
746
|
-
await copilot.newConversation?.();
|
|
747
|
-
} catch (error) {
|
|
748
|
-
console.error("[Chat] Failed to create new conversation:", error);
|
|
749
|
-
}
|
|
750
|
-
}, [addConversation]);
|
|
751
|
-
|
|
752
870
|
useEffect(() => {
|
|
753
871
|
if (starting) {
|
|
754
872
|
const init = async () => {
|
|
755
|
-
console.
|
|
873
|
+
console.debug("[Chat] Initializing conversation...");
|
|
756
874
|
try {
|
|
757
875
|
await loadHistory();
|
|
758
876
|
await checkActiveTask();
|
|
877
|
+
setStarting(false);
|
|
759
878
|
} catch (error) {
|
|
760
|
-
console.
|
|
879
|
+
console.error("[Chat] Initialization error:", error);
|
|
880
|
+
setStarting(false);
|
|
761
881
|
}
|
|
762
|
-
setStarting(false);
|
|
763
882
|
};
|
|
764
883
|
init();
|
|
765
884
|
}
|
|
766
|
-
}, [starting]);
|
|
885
|
+
}, [starting, loadHistory, checkActiveTask]);
|
|
767
886
|
|
|
768
887
|
return {
|
|
769
888
|
starting,
|
|
@@ -778,7 +897,6 @@ export function useConversationProcessor() {
|
|
|
778
897
|
startNewConversation,
|
|
779
898
|
loadHistory,
|
|
780
899
|
loadMoreHistory,
|
|
781
|
-
processDataLine,
|
|
782
900
|
getConversation,
|
|
783
901
|
clearConversation,
|
|
784
902
|
removeMessage,
|