2025-08-08 18:55:18 +08:00

140 lines
5.2 KiB
TypeScript

import { ContentPart, Message } from '@/types/ask';
import { useFocusEffect } from 'expo-router';
import React, { Dispatch, ForwardedRef, forwardRef, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
FlatList,
FlatListProps,
Keyboard,
SafeAreaView,
View
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import MessageItem from '../chat/message-item/message-item';
import SelectModel from "./selectModel";
import SingleContentModel from "./singleContentModel";
// 继承 FlatListProps 来接收所有 FlatList 的属性
interface ChatProps extends Omit<FlatListProps<Message>, 'data' | 'renderItem'> {
userMessages: Message[];
sessionId: string;
setSelectedImages: Dispatch<SetStateAction<string[]>>;
selectedImages: string[];
}
function ChatComponent(
{ userMessages, sessionId, setSelectedImages, selectedImages, ...restProps }: ChatProps,
ref: ForwardedRef<FlatList<Message>>
) {
const insets = useSafeAreaInsets();
const [modalVisible, setModalVisible] = React.useState({ visible: false, data: {} as ContentPart });
const { t } = useTranslation();
const keyExtractor = useCallback((item: Message) => `${item.role}-${item.timestamp}`, []);
// 取消展示右键菜单
const [cancel, setCancel] = useState(true);
const contentContainerStyle = useMemo(() => ({
padding: 16,
flexGrow: 1,
paddingTop: 0,
}), []);
const [modalDetailsVisible, setModalDetailsVisible] = useState<{ visible: boolean, content: any }>({ visible: false, content: [] });
const flatListRef = useRef<FlatList>(null);
const prevMessagesLength = useRef(0);
// 自动滚动到底部
useEffect(() => {
if (userMessages.length > 0 && userMessages.length !== prevMessagesLength.current) {
setTimeout(() => {
flatListRef.current?.scrollToEnd({ animated: true });
}, 100);
prevMessagesLength.current = userMessages.length;
}
}, [userMessages.length]);
useFocusEffect(
useCallback(() => {
Keyboard.dismiss();
}, [Keyboard, sessionId])
);
const renderMessageItem = useCallback(({ item, index }: { item: Message, index: number }) => {
const itemStyle = index === 0 ? { marginTop: 16, marginHorizontal: 16 } : { marginHorizontal: 16 };
return (
<View style={itemStyle}>
<MessageItem
item={item}
insets={insets}
sessionId={sessionId}
modalVisible={modalVisible}
setModalVisible={setModalVisible}
modalDetailsVisible={modalDetailsVisible}
setModalDetailsVisible={setModalDetailsVisible}
selectedImages={selectedImages}
setSelectedImages={setSelectedImages}
t={t}
cancel={cancel}
setCancel={setCancel}
/>
</View>
);
}, [insets, sessionId, modalVisible, modalDetailsVisible, selectedImages, t, cancel]);
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
ref={(node) => {
// 处理转发 ref 和内部 ref
if (ref) {
if (typeof ref === 'function') {
ref(node);
} else {
ref.current = node;
}
}
flatListRef.current = node;
}}
data={userMessages}
keyExtractor={keyExtractor}
renderItem={renderMessageItem}
contentContainerStyle={contentContainerStyle}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={10}
windowSize={11}
onContentSizeChange={() => {
if (userMessages.length > 0) {
flatListRef.current?.scrollToEnd({ animated: true });
}
}}
onLayout={() => {
if (userMessages.length > 0) {
flatListRef.current?.scrollToEnd({ animated: false });
}
}}
{...restProps}
/>
{/* 单个图片弹窗 */}
<SingleContentModel modalVisible={modalVisible} setModalVisible={setModalVisible} />
{/* 全部图片详情弹窗 */}
<SelectModel
modalDetailsVisible={modalDetailsVisible}
setModalDetailsVisible={setModalDetailsVisible}
insets={insets}
setSelectedImages={setSelectedImages}
selectedImages={selectedImages}
t={t}
setCancel={setCancel}
cancel={cancel}
/>
</SafeAreaView>
);
}
export default React.memo(forwardRef(ChatComponent));