chatccc 0.2.47 → 0.2.49
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/package.json +1 -1
- package/src/__tests__/cards.test.ts +23 -8
- package/src/__tests__/feishu-platform.test.ts +56 -54
- package/src/__tests__/session.test.ts +2 -0
- package/src/cards.ts +3 -1
- package/src/feishu-api.ts +12 -0
- package/src/feishu-platform.ts +138 -133
- package/src/index.ts +35 -0
- package/src/session-chat-binding.ts +102 -86
- package/src/session.ts +109 -90
- package/src/sim-platform.ts +152 -147
- package/src/sim-store.ts +316 -312
|
@@ -1,87 +1,103 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// session ↔ chats 双向映射
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// sessionChatsMap: sessionId → Set<chatId>
|
|
5
|
-
// 由 session.ts 在初始化时调用 rebuildSessionChatsFromRegistry 重建
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
const sessionChatsMap = new Map<string, Set<string>>();
|
|
9
|
-
|
|
10
|
-
/** 从 registry 数据重建映射(由 session.ts 调用,避免循环依赖) */
|
|
11
|
-
export function rebuildSessionChatsFromRegistry(registry: Record<string, { chatId: string; sessionId: string }>): void {
|
|
12
|
-
sessionChatsMap.clear();
|
|
13
|
-
for (const record of Object.values(registry)) {
|
|
14
|
-
if (!record.sessionId || !record.chatId) continue;
|
|
15
|
-
bindChatToSession(record.sessionId, record.chatId);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function bindChatToSession(sessionId: string, chatId: string): void {
|
|
20
|
-
let chats = sessionChatsMap.get(sessionId);
|
|
21
|
-
if (!chats) {
|
|
22
|
-
chats = new Set();
|
|
23
|
-
sessionChatsMap.set(sessionId, chats);
|
|
24
|
-
}
|
|
25
|
-
chats.add(chatId);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function unbindChatFromSession(sessionId: string, chatId: string): void {
|
|
29
|
-
const chats = sessionChatsMap.get(sessionId);
|
|
30
|
-
if (chats) {
|
|
31
|
-
chats.delete(chatId);
|
|
32
|
-
if (chats.size === 0) sessionChatsMap.delete(sessionId);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function getChatsForSession(sessionId: string): string[] {
|
|
37
|
-
const chats = sessionChatsMap.get(sessionId);
|
|
38
|
-
return chats ? Array.from(chats) : [];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** 检查 session 是否还有任何群绑定 */
|
|
42
|
-
export function hasChatsForSession(sessionId: string): boolean {
|
|
43
|
-
return (sessionChatsMap.get(sessionId)?.size ?? 0) > 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** 检查 sessionId 是否正被其他 chatId 使用(有活跃 prompt) */
|
|
47
|
-
export function isSessionRunning(sessionId: string): boolean {
|
|
48
|
-
return activePrompts.has(sessionId);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// activePrompts: sessionId → 活跃 prompt 控制
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
export interface ActivePrompt {
|
|
56
|
-
controller: AbortController;
|
|
57
|
-
stopped: boolean;
|
|
58
|
-
startTime: number;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const activePrompts = new Map<string, ActivePrompt>();
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// session ↔ chats 双向映射
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// sessionChatsMap: sessionId → Set<chatId>
|
|
5
|
+
// 由 session.ts 在初始化时调用 rebuildSessionChatsFromRegistry 重建
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
const sessionChatsMap = new Map<string, Set<string>>();
|
|
9
|
+
|
|
10
|
+
/** 从 registry 数据重建映射(由 session.ts 调用,避免循环依赖) */
|
|
11
|
+
export function rebuildSessionChatsFromRegistry(registry: Record<string, { chatId: string; sessionId: string }>): void {
|
|
12
|
+
sessionChatsMap.clear();
|
|
13
|
+
for (const record of Object.values(registry)) {
|
|
14
|
+
if (!record.sessionId || !record.chatId) continue;
|
|
15
|
+
bindChatToSession(record.sessionId, record.chatId);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function bindChatToSession(sessionId: string, chatId: string): void {
|
|
20
|
+
let chats = sessionChatsMap.get(sessionId);
|
|
21
|
+
if (!chats) {
|
|
22
|
+
chats = new Set();
|
|
23
|
+
sessionChatsMap.set(sessionId, chats);
|
|
24
|
+
}
|
|
25
|
+
chats.add(chatId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function unbindChatFromSession(sessionId: string, chatId: string): void {
|
|
29
|
+
const chats = sessionChatsMap.get(sessionId);
|
|
30
|
+
if (chats) {
|
|
31
|
+
chats.delete(chatId);
|
|
32
|
+
if (chats.size === 0) sessionChatsMap.delete(sessionId);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getChatsForSession(sessionId: string): string[] {
|
|
37
|
+
const chats = sessionChatsMap.get(sessionId);
|
|
38
|
+
return chats ? Array.from(chats) : [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 检查 session 是否还有任何群绑定 */
|
|
42
|
+
export function hasChatsForSession(sessionId: string): boolean {
|
|
43
|
+
return (sessionChatsMap.get(sessionId)?.size ?? 0) > 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 检查 sessionId 是否正被其他 chatId 使用(有活跃 prompt) */
|
|
47
|
+
export function isSessionRunning(sessionId: string): boolean {
|
|
48
|
+
return activePrompts.has(sessionId);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// activePrompts: sessionId → 活跃 prompt 控制
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
export interface ActivePrompt {
|
|
56
|
+
controller: AbortController;
|
|
57
|
+
stopped: boolean;
|
|
58
|
+
startTime: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const activePrompts = new Map<string, ActivePrompt>();
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// lastActiveChat: sessionId → 用户最后发送消息的 chatId
|
|
65
|
+
// 用于确保 display loop 只推送到用户最近活跃的群
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
const lastActiveChatMap = new Map<string, { chatId: string; timestamp: number }>();
|
|
69
|
+
|
|
70
|
+
export function recordLastActiveChat(sessionId: string, chatId: string): void {
|
|
71
|
+
lastActiveChatMap.set(sessionId, { chatId, timestamp: Date.now() });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getLastActiveChat(sessionId: string): string | undefined {
|
|
75
|
+
return lastActiveChatMap.get(sessionId)?.chatId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// displayCards: chatId → 展示卡片状态(display loop 用)
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
export interface DisplayCardState {
|
|
83
|
+
cardId: string;
|
|
84
|
+
sequence: number;
|
|
85
|
+
cardBusy: boolean;
|
|
86
|
+
cardCreatedAt: number;
|
|
87
|
+
lastSentContent: string;
|
|
88
|
+
streamErrorNotified: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const displayCards = new Map<string, DisplayCardState>();
|
|
92
|
+
|
|
93
|
+
/** displayLoops: sessionId → 展示循环的 stop 函数 */
|
|
94
|
+
export const displayLoops = new Map<string, () => void>();
|
|
95
|
+
|
|
96
|
+
export function resetBindingState(): void {
|
|
97
|
+
sessionChatsMap.clear();
|
|
98
|
+
lastActiveChatMap.clear();
|
|
99
|
+
activePrompts.clear();
|
|
100
|
+
displayCards.clear();
|
|
101
|
+
for (const stop of displayLoops.values()) stop();
|
|
102
|
+
displayLoops.clear();
|
|
87
103
|
}
|
package/src/session.ts
CHANGED
|
@@ -43,6 +43,8 @@ import {
|
|
|
43
43
|
displayCards,
|
|
44
44
|
displayLoops,
|
|
45
45
|
rebuildSessionChatsFromRegistry,
|
|
46
|
+
recordLastActiveChat,
|
|
47
|
+
getLastActiveChat,
|
|
46
48
|
} from "./session-chat-binding.ts";
|
|
47
49
|
|
|
48
50
|
// ---------------------------------------------------------------------------
|
|
@@ -245,6 +247,12 @@ export async function recordSessionRegistry(update: SessionRegistryUpdate): Prom
|
|
|
245
247
|
await saveSessionRegistry(data);
|
|
246
248
|
}
|
|
247
249
|
|
|
250
|
+
export async function removeSessionRegistryRecord(chatId: string): Promise<void> {
|
|
251
|
+
const data = await loadSessionRegistry();
|
|
252
|
+
delete data[chatId];
|
|
253
|
+
await saveSessionRegistry(data);
|
|
254
|
+
}
|
|
255
|
+
|
|
248
256
|
export function _setSessionRegistryFileForTest(filePath: string): void {
|
|
249
257
|
sessionRegistryFile = filePath;
|
|
250
258
|
}
|
|
@@ -423,6 +431,9 @@ export async function runAgentSession(
|
|
|
423
431
|
): Promise<void> {
|
|
424
432
|
const tid = traceId ?? "";
|
|
425
433
|
|
|
434
|
+
// 记录用户最后发送消息的群(display loop 只推送到该群)
|
|
435
|
+
recordLastActiveChat(sessionId, _chatId);
|
|
436
|
+
|
|
426
437
|
// 并发检查:同一 session 只能有一个活跃 prompt
|
|
427
438
|
if (activePrompts.has(sessionId)) {
|
|
428
439
|
if (tid) logTrace(tid, "BLOCKED", { outcome: "session_busy", sessionId });
|
|
@@ -513,9 +524,10 @@ export async function runAgentSession(
|
|
|
513
524
|
// 启动 display loop
|
|
514
525
|
ensureDisplayLoop(sessionId);
|
|
515
526
|
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
527
|
+
// 设置最后活跃群头像为 busy
|
|
528
|
+
const activeCid = getLastActiveChat(sessionId) ?? getChatsForSession(sessionId)[0];
|
|
529
|
+
if (activeCid) {
|
|
530
|
+
setChatAvatar(token, activeCid, tool, "busy").catch(() => {});
|
|
519
531
|
}
|
|
520
532
|
|
|
521
533
|
const state: AccumulatorState = {
|
|
@@ -605,6 +617,8 @@ export async function runAgentSession(
|
|
|
605
617
|
running: false,
|
|
606
618
|
});
|
|
607
619
|
}
|
|
620
|
+
const active1 = getLastActiveChat(sessionId) ?? getChatsForSession(sessionId)[0];
|
|
621
|
+
if (active1) setChatAvatar(token, active1, tool, "idle").catch(() => {});
|
|
608
622
|
console.log(`[${ts()}] Session ${sessionId} stopped (content chunks: ${state.chunkCount})`);
|
|
609
623
|
if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "stopped", chunks: state.chunkCount });
|
|
610
624
|
} else {
|
|
@@ -619,8 +633,9 @@ export async function runAgentSession(
|
|
|
619
633
|
startTime: finfo?.startTime ?? now,
|
|
620
634
|
running: false,
|
|
621
635
|
});
|
|
622
|
-
setChatAvatar(token, cid, tool, "idle").catch(() => {});
|
|
623
636
|
}
|
|
637
|
+
const active2 = getLastActiveChat(sessionId) ?? getChatsForSession(sessionId)[0];
|
|
638
|
+
if (active2) setChatAvatar(token, active2, tool, "idle").catch(() => {});
|
|
624
639
|
console.log(`[${ts()}] Session ${sessionId} stream complete (content chunks: ${state.chunkCount})`);
|
|
625
640
|
if (tid) logTrace(tid, "SESSION_END", { sessionId, chunks: state.chunkCount, finalTextLen: finalReply.length });
|
|
626
641
|
}
|
|
@@ -628,7 +643,7 @@ export async function runAgentSession(
|
|
|
628
643
|
}
|
|
629
644
|
|
|
630
645
|
// ---------------------------------------------------------------------------
|
|
631
|
-
// ensureDisplayLoop — 每个 session 一个 display
|
|
646
|
+
// ensureDisplayLoop — 每个 session 一个 display 循环,读文件更新最后活跃群的卡片
|
|
632
647
|
// ---------------------------------------------------------------------------
|
|
633
648
|
|
|
634
649
|
const CARD_ROTATE_MS = 9 * 60 * 1000;
|
|
@@ -643,112 +658,116 @@ export function ensureDisplayLoop(sessionId: string): void {
|
|
|
643
658
|
const state = await readStreamState(sessionId);
|
|
644
659
|
if (!state) return;
|
|
645
660
|
|
|
646
|
-
const
|
|
647
|
-
if (
|
|
648
|
-
//
|
|
661
|
+
const chatId = getLastActiveChat(sessionId);
|
|
662
|
+
if (!chatId) {
|
|
663
|
+
// 无活跃群,若 session 已结束则停止 loop
|
|
649
664
|
if (state.status !== "running") {
|
|
650
665
|
clearInterval(interval);
|
|
651
666
|
displayLoops.delete(sessionId);
|
|
667
|
+
// 兜底:lastActiveChatMap 可能因进程重启丢失,从 registry 映射恢复头像
|
|
668
|
+
const fallbackChat = getChatsForSession(sessionId)[0];
|
|
669
|
+
if (fallbackChat) {
|
|
670
|
+
const t = await import("./feishu-platform.ts").then(m => m.getTenantAccessToken());
|
|
671
|
+
setChatAvatar(t, fallbackChat, state.tool, "idle").catch(() => {});
|
|
672
|
+
}
|
|
652
673
|
}
|
|
653
674
|
return;
|
|
654
675
|
}
|
|
655
676
|
|
|
656
677
|
const isTerminal = state.status !== "running";
|
|
657
678
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
679
|
+
try {
|
|
680
|
+
// getTenantAccessToken 有缓存,多次调用开销低
|
|
681
|
+
const tokenModule = await import("./feishu-platform.ts");
|
|
682
|
+
const token = await tokenModule.getTenantAccessToken();
|
|
683
|
+
const display = displayCards.get(chatId);
|
|
684
|
+
|
|
685
|
+
if (isTerminal) {
|
|
686
|
+
// 发送最终结果
|
|
687
|
+
if (display) {
|
|
688
|
+
while (display.cardBusy) await new Promise(r => setTimeout(r, 20));
|
|
689
|
+
const nextSeq = display.sequence + 1;
|
|
690
|
+
const headerTitle = state.status === "stopped" ? "已停止" : "完成";
|
|
691
|
+
const headerTemplate = state.status === "stopped" ? "red" : undefined;
|
|
692
|
+
const cardContent = state.accumulatedContent || " ";
|
|
693
|
+
const doneCard = buildProgressCard(cardContent, { showStop: false, headerTitle, headerTemplate });
|
|
694
|
+
await updateCardKitCard(token, display.cardId, doneCard, nextSeq).catch(() => {});
|
|
695
|
+
displayCards.delete(chatId);
|
|
696
|
+
}
|
|
697
|
+
if (state.finalReply) {
|
|
698
|
+
await sendTextReply(token, chatId, state.finalReply).catch(() => {});
|
|
699
|
+
} else if (!display && state.accumulatedContent.trim()) {
|
|
700
|
+
const short = truncateContent(state.accumulatedContent, 30, 4000);
|
|
701
|
+
await sendTextReply(token, chatId, `[生成过程]\n${short}`).catch(() => {});
|
|
702
|
+
}
|
|
703
|
+
setChatAvatar(token, chatId, state.tool, "idle").catch(() => {});
|
|
704
|
+
} else {
|
|
705
|
+
// running: 创建或更新卡片
|
|
706
|
+
if (!display) {
|
|
707
|
+
const cardId = await createCardKitCard(token, buildProgressCard("", { showStop: true, headerTitle: "生成中..." })).catch(() => null);
|
|
708
|
+
if (cardId) {
|
|
709
|
+
await sendCardKitMessage(token, chatId, cardId).catch(() => null);
|
|
710
|
+
displayCards.set(chatId, {
|
|
711
|
+
cardId,
|
|
712
|
+
sequence: 1,
|
|
713
|
+
cardBusy: false,
|
|
714
|
+
cardCreatedAt: Date.now(),
|
|
715
|
+
lastSentContent: "",
|
|
716
|
+
streamErrorNotified: false,
|
|
717
|
+
});
|
|
682
718
|
}
|
|
683
|
-
setChatAvatar(token, chatId, state.tool, "idle").catch(() => {});
|
|
684
719
|
} else {
|
|
685
|
-
|
|
686
|
-
if (!display) {
|
|
687
|
-
const cardId = await createCardKitCard(token, buildProgressCard("", { showStop: true, headerTitle: "生成中..." })).catch(() => null);
|
|
688
|
-
if (cardId) {
|
|
689
|
-
await sendCardKitMessage(token, chatId, cardId).catch(() => null);
|
|
690
|
-
displayCards.set(chatId, {
|
|
691
|
-
cardId,
|
|
692
|
-
sequence: 1,
|
|
693
|
-
cardBusy: false,
|
|
694
|
-
cardCreatedAt: Date.now(),
|
|
695
|
-
lastSentContent: "",
|
|
696
|
-
streamErrorNotified: false,
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
} else {
|
|
700
|
-
if (display.cardBusy) continue;
|
|
701
|
-
|
|
702
|
-
// 卡片轮转
|
|
703
|
-
if (Date.now() - display.cardCreatedAt > CARD_ROTATE_MS) {
|
|
704
|
-
display.cardBusy = true;
|
|
705
|
-
try {
|
|
706
|
-
const oldSeqBase = display.sequence;
|
|
707
|
-
const oldDisplayContent = truncateContent(state.accumulatedContent + state.finalReply) || "处理中...";
|
|
708
|
-
const oldCard = buildProgressCard(oldDisplayContent, { showStop: false, headerTitle: "生成中...(上轮)" });
|
|
709
|
-
await updateCardKitCard(token, display.cardId, oldCard, oldSeqBase + 1).catch(() => {});
|
|
710
|
-
const newCardId = await createCardKitCard(token, buildProgressCard("", { showStop: true, headerTitle: "生成中..." }));
|
|
711
|
-
if (newCardId) {
|
|
712
|
-
await sendCardKitMessage(token, chatId, newCardId);
|
|
713
|
-
display.cardId = newCardId;
|
|
714
|
-
display.sequence = 1;
|
|
715
|
-
display.cardCreatedAt = Date.now();
|
|
716
|
-
display.lastSentContent = "";
|
|
717
|
-
display.streamErrorNotified = false;
|
|
718
|
-
}
|
|
719
|
-
} catch (err) {
|
|
720
|
-
console.error(`[${ts()}] [CARDIKT] rotation FAIL for ${chatId}: ${(err as Error).message}`);
|
|
721
|
-
} finally {
|
|
722
|
-
display.cardBusy = false;
|
|
723
|
-
}
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
720
|
+
if (display.cardBusy) return;
|
|
726
721
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (content === display.lastSentContent) continue;
|
|
730
|
-
|
|
731
|
-
display.lastSentContent = content;
|
|
722
|
+
// 卡片轮转
|
|
723
|
+
if (Date.now() - display.cardCreatedAt > CARD_ROTATE_MS) {
|
|
732
724
|
display.cardBusy = true;
|
|
733
|
-
const mySeq = display.sequence + 1;
|
|
734
725
|
try {
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (
|
|
741
|
-
|
|
742
|
-
|
|
726
|
+
const oldSeqBase = display.sequence;
|
|
727
|
+
const oldDisplayContent = truncateContent(state.accumulatedContent + state.finalReply) || "处理中...";
|
|
728
|
+
const oldCard = buildProgressCard(oldDisplayContent, { showStop: false, headerTitle: "生成中...(上轮)" });
|
|
729
|
+
await updateCardKitCard(token, display.cardId, oldCard, oldSeqBase + 1).catch(() => {});
|
|
730
|
+
const newCardId = await createCardKitCard(token, buildProgressCard("", { showStop: true, headerTitle: "生成中..." }));
|
|
731
|
+
if (newCardId) {
|
|
732
|
+
await sendCardKitMessage(token, chatId, newCardId);
|
|
733
|
+
display.cardId = newCardId;
|
|
734
|
+
display.sequence = 1;
|
|
735
|
+
display.cardCreatedAt = Date.now();
|
|
736
|
+
display.lastSentContent = "";
|
|
737
|
+
display.streamErrorNotified = false;
|
|
743
738
|
}
|
|
739
|
+
} catch (err) {
|
|
740
|
+
console.error(`[${ts()}] [CARDIKT] rotation FAIL for ${chatId}: ${(err as Error).message}`);
|
|
744
741
|
} finally {
|
|
745
742
|
display.cardBusy = false;
|
|
746
743
|
}
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
dotCount = (dotCount % 9) + 1;
|
|
748
|
+
const content = truncateContent(state.accumulatedContent + state.finalReply + "\n" + "。".repeat(dotCount));
|
|
749
|
+
if (content === display.lastSentContent) return;
|
|
750
|
+
|
|
751
|
+
display.lastSentContent = content;
|
|
752
|
+
display.cardBusy = true;
|
|
753
|
+
const mySeq = display.sequence + 1;
|
|
754
|
+
try {
|
|
755
|
+
const card = buildProgressCard(content, { showStop: true, headerTitle: "生成中..." });
|
|
756
|
+
await updateCardKitCard(token, display.cardId, card, mySeq);
|
|
757
|
+
display.sequence = mySeq;
|
|
758
|
+
} catch (err) {
|
|
759
|
+
console.error(`[${ts()}] CardKit update error: chatId=${chatId} ${(err as Error).message}`);
|
|
760
|
+
if (!display.streamErrorNotified) {
|
|
761
|
+
display.streamErrorNotified = true;
|
|
762
|
+
sendTextReply(token, chatId, "卡片更新失败,结果将以文本形式发送。").catch(() => {});
|
|
763
|
+
}
|
|
764
|
+
} finally {
|
|
765
|
+
display.cardBusy = false;
|
|
747
766
|
}
|
|
748
767
|
}
|
|
749
|
-
} catch (err) {
|
|
750
|
-
console.error(`[${ts()}] Display loop error for ${chatId}: ${(err as Error).message}`);
|
|
751
768
|
}
|
|
769
|
+
} catch (err) {
|
|
770
|
+
console.error(`[${ts()}] Display loop error for ${chatId}: ${(err as Error).message}`);
|
|
752
771
|
}
|
|
753
772
|
|
|
754
773
|
if (isTerminal) {
|