2025-07-31 14:11:49 +08:00

255 lines
12 KiB
TypeScript

import CancelSvg from '@/assets/icons/svg/cancel.svg';
import ChatSvg from "@/assets/icons/svg/chat.svg";
import DownloadSvg from '@/assets/icons/svg/download.svg';
import MoreSvg from "@/assets/icons/svg/more.svg";
import { Message, Video } from "@/types/ask";
import { MaterialItem } from "@/types/personal-info";
import { TFunction } from "i18next";
import React from 'react';
import {
Image,
Pressable,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
import ContextMenu from "../gusture/contextMenu";
import { ThemedText } from "../ThemedText";
import SelectModel from "./selectModel";
import SingleContentModel from "./singleContentModel";
import TypewriterText from "./typewriterText";
import { mergeArrays, saveMediaToGallery } from "./utils";
interface RenderMessageProps {
insets: { top: number };
item: Message;
sessionId: string;
setModalVisible: React.Dispatch<React.SetStateAction<{ visible: boolean, data: Video | MaterialItem }>>;
modalVisible: { visible: boolean, data: Video | MaterialItem };
setModalDetailsVisible: React.Dispatch<React.SetStateAction<{ visible: boolean, content: any }>>;
modalDetailsVisible: { visible: boolean, content: any };
setSelectedImages: React.Dispatch<React.SetStateAction<string[]>>;
selectedImages: string[];
t: TFunction;
setCancel: React.Dispatch<React.SetStateAction<boolean>>;
cancel: boolean;
}
const MessageItem = ({ setCancel, cancel = true, t, insets, item, sessionId, setModalVisible, modalVisible, setModalDetailsVisible, modalDetailsVisible, setSelectedImages, selectedImages }: RenderMessageProps) => {
const isUser = item.role === 'User';
return (
<View className={`flex-row items-start gap-2 w-full ${isUser ? 'justify-end' : 'justify-start'}`}>
{!isUser && <ChatSvg width={36} height={36} />}
<View className="max-w-[90%] mb-[1rem] flex flex-col gap-2">
<View
style={[
styles.messageBubble,
isUser ? styles.userBubble : styles.aiBubble
]}
className={`${isUser ? '!bg-bgPrimary ml-10 rounded-full' : '!bg-aiBubble mr-10 rounded-2xl'} border-0 ${!isUser && (item.content.video_material_infos && item.content.video_material_infos.length > 0 || item.content.image_material_infos && item.content.image_material_infos.length > 0) ? '!rounded-t-3xl !rounded-b-2xl' : '!rounded-3xl'}`}
>
<View className={`${isUser ? 'bg-bgPrimary' : 'bg-aiBubble'}`}>
<Text style={isUser ? styles.userText : styles.aiText}>
{!isUser
?
sessionId ? item.content.text : <TypewriterText text={item.content.text} speed={100} loop={item.content.text == "正在寻找,请稍等..."} />
: item.content.text
}
</Text>
{(mergeArrays(item.content.image_material_infos || [], item.content.video_material_infos || [])?.length || 0 > 0) && (
<View className="relative">
<View style={[styles.imageGridContainer, { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' }]}>
{mergeArrays(item.content.image_material_infos || [], item.content.video_material_infos || [])?.slice(0, 3)?.map((image) => (
<Pressable
key={image?.id || image?.video?.id}
onPress={() => {
setModalVisible({ visible: true, data: image });
}}
style={{
width: '32%',
aspectRatio: 1,
marginBottom: 8,
}}
>
<ContextMenu
items={[
{
svg: <DownloadSvg width={20} height={20} />,
label: "保存",
onPress: () => {
const imageUrl = image?.preview_file_info?.url || image.video?.preview_file_info?.url;
if (imageUrl) {
saveMediaToGallery(imageUrl, t);
}
},
textStyle: { color: '#4C320C' }
},
{
svg: <CancelSvg width={20} height={20} color='red' />,
label: "取消",
onPress: () => console.log('取消'),
textStyle: { color: 'red' }
}
]}
menuStyle={{
backgroundColor: 'white',
borderRadius: 8,
padding: 8,
minWidth: 150,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
}}
>
<Image
source={{ uri: image?.preview_file_info?.url || image.video?.preview_file_info?.url }}
style={{
width: '100%',
height: '100%',
borderRadius: 12,
}}
resizeMode="cover"
loadingIndicatorSource={require('@/assets/images/png/placeholder.png')}
/>
</ContextMenu>
</Pressable>
))}
</View>
{
((item.content.video_material_infos?.length || 0) + (item.content.image_material_infos?.length || 0)) > 3
&& <TouchableOpacity className="absolute top-1/2 -translate-y-1/2 -right-4 translate-x-1/2 bg-bgPrimary flex flex-row items-center gap-2 p-1 pl-2 rounded-full" onPress={() => {
setSelectedImages([])
setModalDetailsVisible({ visible: true, content: item.content });
}}>
<ThemedText className="!text-white font-semibold">{((item.content.video_material_infos?.length || 0) + (item.content.image_material_infos?.length || 0))}</ThemedText>
<View className="bg-white rounded-full p-2">
<MoreSvg />
</View>
</TouchableOpacity>
}
</View>
)}
</View>
</View>
{/* {item.askAgain && item.askAgain.length > 0 && (
<View className={`mr-10`}>
{item.askAgain.map((suggestion, index, array) => (
<TouchableOpacity
key={suggestion.id}
className={`bg-yellow-50 rounded-xl px-4 py-2 border border-yellow-200 border-0 mb-2 ${index === array.length - 1 ? 'mb-0 rounded-b-3xl rounded-t-2xl' : 'rounded-2xl'}`}
>
<Text className="text-gray-700">{suggestion.text}</Text>
</TouchableOpacity>
))}
</View>
)} */}
{/* 单个图片弹窗 */}
<SingleContentModel modalVisible={modalVisible} setModalVisible={setModalVisible} />
{/* 全部图片详情弹窗 */}
<SelectModel
modalDetailsVisible={modalDetailsVisible}
setModalDetailsVisible={setModalDetailsVisible}
insets={insets}
setSelectedImages={setSelectedImages}
selectedImages={selectedImages}
t={t}
setCancel={setCancel}
cancel={cancel}
/>
</View>
</View>
);
};
export default MessageItem;
const styles = StyleSheet.create({
imageGridContainer: {
flexDirection: 'row',
flexWrap: 'nowrap',
width: '100%',
marginTop: 8,
},
video: {
width: '100%',
height: '100%',
},
imageContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
width: '100%',
maxHeight: '60%',
},
fullWidthImage: {
width: '100%',
height: "54%",
marginBottom: 8,
},
gridImage: {
aspectRatio: 1,
marginBottom: 8,
},
background: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)', // 添加半透明黑色背景
},
centeredView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
modalView: {
borderRadius: 20,
alignItems: 'center',
height: '100%',
width: "100%",
justifyContent: 'center',
alignSelf: 'center',
},
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
userAvatar: {
width: 30,
height: 30,
borderRadius: 15,
},
messageList: {
padding: 16,
},
messageBubble: {
paddingHorizontal: 16,
paddingVertical: 12,
fontWeight: "600"
},
userBubble: {
alignSelf: 'flex-end',
backgroundColor: '#FFB645',
marginLeft: '20%',
},
aiBubble: {
alignSelf: 'flex-start',
backgroundColor: '#fff',
marginRight: '20%',
borderWidth: 1,
borderColor: '#e5e5ea',
},
userText: {
color: '#4C320C',
fontSize: 16,
},
aiText: {
color: '#000',
fontSize: 16,
},
});