| .. | .. |
|---|
| 13 | 13 | |
|---|
| 14 | 14 | export function MessageList({ messages, isTyping, onDeleteMessage }: MessageListProps) { |
|---|
| 15 | 15 | const listRef = useRef<FlatList<Message>>(null); |
|---|
| 16 | + const prevLengthRef = useRef(0); |
|---|
| 17 | + |
|---|
| 18 | + // Track the last message's content so transcript reflections trigger a scroll |
|---|
| 19 | + const lastContent = messages.length > 0 ? messages[messages.length - 1].content : ""; |
|---|
| 16 | 20 | |
|---|
| 17 | 21 | useEffect(() => { |
|---|
| 18 | 22 | if (messages.length > 0) { |
|---|
| 23 | + // If the message count changed by more than 1, it's a session switch — |
|---|
| 24 | + // snap to bottom instantly instead of visibly scrolling down. |
|---|
| 25 | + const delta = Math.abs(messages.length - prevLengthRef.current); |
|---|
| 26 | + const animated = delta === 1; |
|---|
| 19 | 27 | setTimeout(() => { |
|---|
| 20 | | - listRef.current?.scrollToEnd({ animated: true }); |
|---|
| 28 | + listRef.current?.scrollToEnd({ animated }); |
|---|
| 21 | 29 | }, 50); |
|---|
| 22 | 30 | } |
|---|
| 23 | | - }, [messages.length, isTyping]); |
|---|
| 31 | + prevLengthRef.current = messages.length; |
|---|
| 32 | + }, [messages.length, isTyping, lastContent]); |
|---|
| 24 | 33 | |
|---|
| 25 | 34 | // Play from a voice message and auto-chain all consecutive assistant voice messages after it |
|---|
| 26 | 35 | const handlePlayVoice = useCallback(async (messageId: string) => { |
|---|
| .. | .. |
|---|
| 61 | 70 | /> |
|---|
| 62 | 71 | )} |
|---|
| 63 | 72 | contentContainerStyle={{ paddingVertical: 12 }} |
|---|
| 64 | | - onContentSizeChange={() => { |
|---|
| 65 | | - listRef.current?.scrollToEnd({ animated: false }); |
|---|
| 66 | | - }} |
|---|
| 67 | 73 | showsVerticalScrollIndicator={false} |
|---|
| 68 | 74 | ListFooterComponent={ |
|---|
| 69 | 75 | <> |
|---|