Files
LocalMind/components/chat/MessageList.tsx
T
evandro cb6eda0562 refactor(chat): split ChatScreen and reuse regenerateResponse after editing a message
The edit path duplicated the streaming flow with inline require() calls; it now reuses regenerateResponse, which also fixes:
- edited conversations were never saved to History
- the answer generated after an edit could not be stopped (no abort handle kept)
Stopping a stream now saves the partial conversation, like finishing or failing does.
Adds characterization tests for ChatScreen and useLMStudio.
2026-10-10 04:16:29 -03:00

61 lines
2.2 KiB
TypeScript

import { MessageBubble } from '@/components/chat/MessageBubble';
import { TypingIndicator } from '@/components/chat/TypingIndicator';
import { ChatMessage } from '@/store/useAppStore';
import { findLastAssistantId, shouldShowTypingIndicator } from '@/utils/chatSelectors';
import React, { useEffect, useRef } from 'react';
import { FlatList, StyleSheet } from 'react-native';
import Animated, { FadeInDown } from 'react-native-reanimated';
interface MessageListProps {
messages: ChatMessage[];
isStreaming: boolean;
onEdit: (id: string) => void;
onDelete: (id: string) => void;
onRegenerate: () => void;
}
export function MessageList({ messages, isStreaming, onEdit, onDelete, onRegenerate }: MessageListProps) {
const flatListRef = useRef<FlatList>(null);
const lastAssistantId = findLastAssistantId(messages);
const lastContentLength = messages[messages.length - 1]?.content?.length;
useEffect(() => {
if (messages.length > 0 && flatListRef.current) {
setTimeout(() => {
flatListRef.current?.scrollToEnd({ animated: !isStreaming });
}, 100);
}
}, [messages.length, lastContentLength, isStreaming]);
return (
<>
<FlatList
ref={flatListRef}
data={messages}
keyExtractor={(item) => item.id}
renderItem={({ item, index }) => (
<Animated.View entering={FadeInDown.duration(300).delay(index > messages.length - 4 ? 100 : 0)}>
<MessageBubble
message={item}
onEdit={() => onEdit(item.id)}
onRegenerate={onRegenerate}
onDelete={() => onDelete(item.id)}
isStreaming={isStreaming}
isLastAssistant={item.id === lastAssistantId}
/>
</Animated.View>
)}
contentContainerStyle={styles.listContent}
/>
{shouldShowTypingIndicator(messages, isStreaming) && <TypingIndicator />}
</>
);
}
const styles = StyleSheet.create({
listContent: {
paddingTop: 16,
paddingBottom: 24,
},
});