@@ -8,6 +8,7 @@ import { isFunctionMessage } from '@/ProChat/utils/message';
88import { setNamespace } from '@/ProChat/utils/storeDebug' ;
99import { nanoid } from '@/ProChat/utils/uuid' ;
1010import { ChatMessage } from '@/types/message' ;
11+ import { startTransition } from 'react' ;
1112
1213import { initialModelConfig } from '@/ProChat/store/initialState' ;
1314import { ChatStreamPayload } from '@/ProChat/types/chat' ;
@@ -109,16 +110,6 @@ export interface ChatAction {
109110 */
110111 updateMessageContent : ( id : string , content : string ) => Promise < void > ;
111112
112- /**
113- * 创建一条平滑输出的内容
114- */
115- createSmoothMessage : ( id : string ) => {
116- startAnimation : ( speed ?: number ) => Promise < void > ;
117- stopAnimation : ( ) => void ;
118- outputQueue : string [ ] ;
119- isAnimationActive : boolean ;
120- } ;
121-
122113 /**
123114 * 获取当前 loading 生成的消息 id
124115 * @returns 消息 id | undefined
@@ -161,14 +152,7 @@ export const chatAction: StateCreator<ChatStore, [['zustand/devtools', never]],
161152 onChatsChange ?.( nextChats ) ;
162153 } ,
163154 generateMessage : async ( messages , assistantId ) => {
164- const {
165- dispatchMessage,
166- toggleChatLoading,
167- config,
168- defaultModelFetcher,
169- createSmoothMessage,
170- updateMessageContent,
171- } = get ( ) ;
155+ const { dispatchMessage, toggleChatLoading, config, defaultModelFetcher } = get ( ) ;
172156
173157 const abortController = toggleChatLoading (
174158 true ,
@@ -219,35 +203,23 @@ export const chatAction: StateCreator<ChatStore, [['zustand/devtools', never]],
219203 let output = '' ;
220204 let isFunctionCall = false ;
221205
222- const { startAnimation, stopAnimation, outputQueue, isAnimationActive } =
223- createSmoothMessage ( assistantId ) ;
224-
225206 await fetchSSE ( fetcher , {
226207 onErrorHandle : ( error ) => {
227208 dispatchMessage ( { id : assistantId , key : 'error' , type : 'updateMessage' , value : error } ) ;
228209 } ,
229- onAbort : async ( ) => {
230- stopAnimation ( ) ;
231- } ,
232- onFinish : async ( content ) => {
233- stopAnimation ( ) ;
234-
235- if ( outputQueue . length > 0 && ! isFunctionCall ) {
236- await startAnimation ( 15 ) ;
237- }
238-
239- await updateMessageContent ( assistantId , content ) ;
240- } ,
241210 onMessageHandle : ( text ) => {
242211 output += text ;
243-
244- if ( ! isAnimationActive && ! isFunctionCall ) startAnimation ( ) ;
245-
246- if ( abortController ?. signal . aborted ) {
247- // aborted 后停止当前输出
248- return ;
212+ if ( ! abortController . signal . aborted ) {
213+ startTransition ( ( ) => {
214+ dispatchMessage ( {
215+ id : assistantId ,
216+ key : 'content' ,
217+ type : 'updateMessage' ,
218+ value : output ,
219+ } ) ;
220+ } ) ;
249221 } else {
250- outputQueue . push ( ... text . split ( '' ) ) ;
222+ return ;
251223 }
252224
253225 // TODO: need a function call judge callback
@@ -258,76 +230,13 @@ export const chatAction: StateCreator<ChatStore, [['zustand/devtools', never]],
258230 } ,
259231 } ) ;
260232
261- toggleChatLoading ( false , undefined , t ( 'generateMessage(end)' ) as string ) ;
233+ startTransition ( ( ) => {
234+ toggleChatLoading ( false , undefined , t ( 'generateMessage(end)' ) as string ) ;
235+ } ) ;
262236
263237 return { isFunctionCall } ;
264238 } ,
265239
266- createSmoothMessage : ( id ) => {
267- const { dispatchMessage } = get ( ) ;
268-
269- let buffer = '' ;
270- // why use queue: https://shareg.pt/GLBrjpK
271- let outputQueue : string [ ] = [ ] ;
272-
273- // eslint-disable-next-line no-undef
274- let animationTimeoutId : NodeJS . Timeout | null = null ;
275- let isAnimationActive = false ;
276-
277- // when you need to stop the animation, call this function
278- const stopAnimation = ( ) => {
279- isAnimationActive = false ;
280- if ( animationTimeoutId !== null ) {
281- clearTimeout ( animationTimeoutId ) ;
282- animationTimeoutId = null ;
283- }
284- } ;
285-
286- // define startAnimation function to display the text in buffer smooth
287- // when you need to start the animation, call this function
288- const startAnimation = ( speed = 2 ) =>
289- new Promise < void > ( ( resolve ) => {
290- if ( isAnimationActive ) {
291- resolve ( ) ;
292- return ;
293- }
294-
295- isAnimationActive = true ;
296-
297- const updateText = ( ) => {
298- // 如果动画已经不再激活,则停止更新文本
299- if ( ! isAnimationActive ) {
300- clearTimeout ( animationTimeoutId ! ) ;
301- animationTimeoutId = null ;
302- resolve ( ) ;
303- }
304-
305- // 如果还有文本没有显示
306- // 检查队列中是否有字符待显示
307- if ( outputQueue . length > 0 ) {
308- // 从队列中获取前两个字符(如果存在)
309- const charsToAdd = outputQueue . splice ( 0 , speed ) . join ( '' ) ;
310- buffer += charsToAdd ;
311-
312- // 更新消息内容,这里可能需要结合实际情况调整
313- dispatchMessage ( { id, key : 'content' , type : 'updateMessage' , value : buffer } ) ;
314-
315- // 设置下一个字符的延迟
316- animationTimeoutId = setTimeout ( updateText , 16 ) ; // 16 毫秒的延迟模拟打字机效果
317- } else {
318- // 当所有字符都显示完毕时,清除动画状态
319- isAnimationActive = false ;
320- animationTimeoutId = null ;
321- resolve ( ) ;
322- }
323- } ;
324-
325- updateText ( ) ;
326- } ) ;
327-
328- return { startAnimation, stopAnimation, outputQueue, isAnimationActive } ;
329- } ,
330-
331240 realFetchAIResponse : async ( messages , userMessageId ) => {
332241 const { dispatchMessage, generateMessage, config, getMessageId } = get ( ) ;
333242
@@ -412,7 +321,20 @@ export const chatAction: StateCreator<ChatStore, [['zustand/devtools', never]],
412321 } ,
413322
414323 stopGenerateMessage : ( ) => {
415- const { abortController, toggleChatLoading } = get ( ) ;
324+ const { abortController, toggleChatLoading, chatLoadingId, chats, dispatchMessage } = get ( ) ;
325+ // 如果当前 最后一条为 chatLoadingId 停止前需要清空
326+ if ( chats && chats . length > 0 ) {
327+ const lastChat = chats [ chats . length - 1 ] ;
328+ if ( lastChat . content === LOADING_FLAT ) {
329+ dispatchMessage ( {
330+ id : chatLoadingId ,
331+ key : 'content' ,
332+ type : 'updateMessage' ,
333+ value : '' ,
334+ } ) ;
335+ }
336+ }
337+
416338 if ( ! abortController ) return ;
417339
418340 abortController . abort ( ) ;
0 commit comments