Skip to content

Commit 32e6347

Browse files
authored
🐛 fix: slove sse output loading problem (#120)
1 parent 9555b01 commit 32e6347

File tree

2 files changed

+118
-17
lines changed

2 files changed

+118
-17
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ant-design/pro-chat",
3-
"version": "1.10.0",
3+
"version": "1.10.1",
44
"description": "a solution for ai chat",
55
"keywords": [
66
"ai",

src/ProChat/store/action.ts

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { isFunctionMessage } from '@/ProChat/utils/message';
88
import { setNamespace } from '@/ProChat/utils/storeDebug';
99
import { nanoid } from '@/ProChat/utils/uuid';
1010
import { ChatMessage } from '@/types/message';
11-
import { startTransition } from 'react';
1211

1312
import { initialModelConfig } from '@/ProChat/store/initialState';
1413
import { ChatStreamPayload } from '@/ProChat/types/chat';
@@ -110,6 +109,16 @@ export interface ChatAction {
110109
*/
111110
updateMessageContent: (id: string, content: string) => Promise<void>;
112111

112+
/**
113+
* 创建一条平滑输出的内容
114+
*/
115+
createSmoothMessage: (id: string) => {
116+
startAnimation: (speed?: number) => Promise<void>;
117+
stopAnimation: () => void;
118+
outputQueue: string[];
119+
isAnimationActive: boolean;
120+
};
121+
113122
/**
114123
* 获取当前 loading 生成的消息 id
115124
* @returns 消息 id | undefined
@@ -152,8 +161,14 @@ export const chatAction: StateCreator<ChatStore, [['zustand/devtools', never]],
152161
onChatsChange?.(nextChats);
153162
},
154163
generateMessage: async (messages, assistantId) => {
155-
const { dispatchMessage, toggleChatLoading, config, defaultModelFetcher } = get();
156-
164+
const {
165+
dispatchMessage,
166+
toggleChatLoading,
167+
config,
168+
defaultModelFetcher,
169+
createSmoothMessage,
170+
updateMessageContent,
171+
} = get();
157172
const abortController = toggleChatLoading(
158173
true,
159174
assistantId,
@@ -203,23 +218,33 @@ export const chatAction: StateCreator<ChatStore, [['zustand/devtools', never]],
203218
let output = '';
204219
let isFunctionCall = false;
205220

221+
const { startAnimation, stopAnimation, outputQueue, isAnimationActive } =
222+
createSmoothMessage(assistantId);
223+
206224
await fetchSSE(fetcher, {
207225
onErrorHandle: (error) => {
208226
dispatchMessage({ id: assistantId, key: 'error', type: 'updateMessage', value: error });
209227
},
228+
onAbort: async () => {
229+
stopAnimation();
230+
},
231+
onFinish: async (content) => {
232+
stopAnimation();
233+
if (outputQueue.length > 0 && !isFunctionCall) {
234+
await startAnimation(15);
235+
}
236+
await updateMessageContent(assistantId, content);
237+
},
210238
onMessageHandle: (text) => {
211239
output += text;
212-
if (!abortController.signal.aborted) {
213-
startTransition(() => {
214-
dispatchMessage({
215-
id: assistantId,
216-
key: 'content',
217-
type: 'updateMessage',
218-
value: output,
219-
});
220-
});
221-
} else {
240+
241+
if (!isAnimationActive && !isFunctionCall) startAnimation();
242+
243+
if (abortController?.signal.aborted) {
244+
// aborted 后停止当前输出
222245
return;
246+
} else {
247+
outputQueue.push(text);
223248
}
224249

225250
// TODO: need a function call judge callback
@@ -230,9 +255,20 @@ export const chatAction: StateCreator<ChatStore, [['zustand/devtools', never]],
230255
},
231256
});
232257

233-
startTransition(() => {
234-
toggleChatLoading(false, undefined, t('generateMessage(end)') as string);
235-
});
258+
let timeoutId; // 用于存储轮询队列的计时器id
259+
const checkAndToggleChatLoading = () => {
260+
clearTimeout(timeoutId); // 清除任何现有的计时器
261+
// 等待队列内容输出完毕
262+
if (outputQueue === undefined || outputQueue.length === 0) {
263+
// 当队列为空时
264+
toggleChatLoading(false, undefined, t('generateMessage(end)') as string);
265+
} else {
266+
// 如果队列不为空,则设置一个延迟或者使用某种形式的轮询来再次检查队列
267+
timeoutId = setTimeout(checkAndToggleChatLoading, 30); // CHECK_INTERVAL 是毫秒数,代表检查间隔时间
268+
}
269+
};
270+
271+
checkAndToggleChatLoading();
236272

237273
return { isFunctionCall };
238274
},
@@ -380,6 +416,71 @@ export const chatAction: StateCreator<ChatStore, [['zustand/devtools', never]],
380416
return nanoid();
381417
},
382418

419+
createSmoothMessage: (id) => {
420+
const { dispatchMessage } = get();
421+
422+
let buffer = '';
423+
// why use queue: https://shareg.pt/GLBrjpK
424+
let outputQueue: string[] = [];
425+
426+
// eslint-disable-next-line no-undef
427+
let animationTimeoutId: NodeJS.Timeout | null = null;
428+
let isAnimationActive = false;
429+
430+
// when you need to stop the animation, call this function
431+
const stopAnimation = () => {
432+
isAnimationActive = false;
433+
if (animationTimeoutId !== null) {
434+
clearTimeout(animationTimeoutId);
435+
animationTimeoutId = null;
436+
}
437+
};
438+
439+
// define startAnimation function to display the text in buffer smooth
440+
// when you need to start the animation, call this function
441+
const startAnimation = (speed = 2) =>
442+
new Promise<void>((resolve) => {
443+
if (isAnimationActive) {
444+
resolve();
445+
return;
446+
}
447+
448+
isAnimationActive = true;
449+
450+
const updateText = () => {
451+
// 如果动画已经不再激活,则停止更新文本
452+
if (!isAnimationActive) {
453+
clearTimeout(animationTimeoutId!);
454+
animationTimeoutId = null;
455+
resolve();
456+
}
457+
458+
// 如果还有文本没有显示
459+
// 检查队列中是否有字符待显示
460+
if (outputQueue.length > 0) {
461+
// 从队列中获取前两个字符(如果存在)
462+
const charsToAdd = outputQueue.splice(0, speed).join('');
463+
buffer += charsToAdd;
464+
465+
// 更新消息内容,这里可能需要结合实际情况调整
466+
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: buffer });
467+
468+
// 设置下一个字符的延迟
469+
animationTimeoutId = setTimeout(updateText, 16); // 16 毫秒的延迟模拟打字机效果
470+
} else {
471+
// 当所有字符都显示完毕时,清除动画状态
472+
isAnimationActive = false;
473+
animationTimeoutId = null;
474+
resolve();
475+
}
476+
};
477+
478+
updateText();
479+
});
480+
481+
return { startAnimation, stopAnimation, outputQueue, isAnimationActive };
482+
},
483+
383484
getChatLoadingId: () => {
384485
const { chatLoadingId } = get();
385486
return chatLoadingId;

0 commit comments

Comments
 (0)