Skip to content

Commit 9e25b3a

Browse files
fix(ai): stop typewriter animation on ai_indicator.stop (#2918)
### 🎯 Goal Add `AI_STATE_STOP` and adjust typewriter animation so that it stops when `ai_indicator.stop` event arrives. This change should be probably ported to the AI SDK too.
1 parent b66822c commit 9e25b3a

3 files changed

Lines changed: 29 additions & 5 deletions

File tree

src/components/AIStateIndicator/hooks/useAIState.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const AIStates = {
66
ExternalSources: 'AI_STATE_EXTERNAL_SOURCES',
77
Generating: 'AI_STATE_GENERATING',
88
Idle: 'AI_STATE_IDLE',
9+
Stop: 'AI_STATE_STOP',
910
Thinking: 'AI_STATE_THINKING',
1011
};
1112

@@ -37,9 +38,17 @@ export const useAIState = (channel?: Channel): { aiState: AIState } => {
3738
}
3839
});
3940

41+
const indicatorStoppedListener = channel.on('ai_indicator.stop', (event) => {
42+
const { cid } = event;
43+
if (channel.cid === cid) {
44+
setAiState(AIStates.Stop);
45+
}
46+
});
47+
4048
return () => {
4149
indicatorChangedListener.unsubscribe();
4250
indicatorClearedListener.unsubscribe();
51+
indicatorStoppedListener.unsubscribe();
4352
};
4453
}, [channel]);
4554

src/components/Message/StreamedMessageText.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import React from 'react';
1+
import React, { useEffect } from 'react';
22

33
import type { MessageTextProps } from './MessageText';
44
import { MessageText } from './MessageText';
55

6-
import { useMessageContext } from '../../context';
6+
import { useChannelStateContext, useMessageContext } from '../../context';
77
import { useMessageTextStreaming } from './hooks';
88

99
export type StreamedMessageTextProps = Pick<
@@ -22,14 +22,21 @@ export const StreamedMessageText = (props: StreamedMessageTextProps) => {
2222
streamingLetterIntervalMs,
2323
} = props;
2424
const { message: messageFromContext } = useMessageContext('StreamedMessageText');
25+
const { channel } = useChannelStateContext();
2526
const message = messageFromProps || messageFromContext;
2627
const { text = '' } = message;
27-
const { streamedMessageText } = useMessageTextStreaming({
28+
const { skipAnimation, streamedMessageText } = useMessageTextStreaming({
2829
renderingLetterCount,
2930
streamingLetterIntervalMs,
3031
text,
3132
});
3233

34+
useEffect(() => {
35+
channel?.on('ai_indicator.stop', () => {
36+
skipAnimation();
37+
});
38+
}, [channel, skipAnimation]);
39+
3340
return (
3441
<MessageText
3542
message={{ ...message, text: streamedMessageText }}

src/components/Message/hooks/useMessageTextStreaming.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useRef, useState } from 'react';
22

3+
import { useStableCallback } from '../../../utils/useStableCallback';
34
import type { StreamedMessageTextProps } from '../StreamedMessageText';
45

56
export type UseMessageTextStreamingProps = Pick<
@@ -22,15 +23,17 @@ export const useMessageTextStreaming = ({
2223
renderingLetterCount = DEFAULT_RENDERING_LETTER_COUNT,
2324
streamingLetterIntervalMs = DEFAULT_LETTER_INTERVAL,
2425
text,
25-
}: UseMessageTextStreamingProps): { streamedMessageText: string } => {
26+
}: UseMessageTextStreamingProps) => {
2627
const [streamedMessageText, setStreamedMessageText] = useState<string>(text);
2728
const textCursor = useRef<number>(text.length);
2829

2930
useEffect(() => {
3031
const textLength = text.length;
32+
3133
const interval = setInterval(() => {
3234
if (!text || textCursor.current >= textLength) {
3335
clearInterval(interval);
36+
return;
3437
}
3538
const newCursorValue = textCursor.current + renderingLetterCount;
3639
const newText = text.substring(0, newCursorValue);
@@ -43,5 +46,10 @@ export const useMessageTextStreaming = ({
4346
};
4447
}, [streamingLetterIntervalMs, renderingLetterCount, text]);
4548

46-
return { streamedMessageText };
49+
const skipAnimation = useStableCallback(() => {
50+
textCursor.current = text.length;
51+
setStreamedMessageText(text);
52+
});
53+
54+
return { skipAnimation, streamedMessageText } as const;
4755
};

0 commit comments

Comments
 (0)