feat: ask页面

This commit is contained in:
jinyaqiu 2025-08-08 17:19:01 +08:00
parent 6efc508fa0
commit d7e07b327a
9 changed files with 69 additions and 57 deletions

View File

@ -57,6 +57,7 @@ export default function AskScreen() {
if (translationX > threshold) {
// 从左向右滑动,跳转页面
runOnJS(router.replace)("/memo-list");
runOnJS(setConversationId)("")
}
})
.minPointers(1)
@ -244,11 +245,12 @@ export default function AskScreen() {
useFocusEffect(
useCallback(() => {
Keyboard.dismiss();
if (!sessionId) {
setIsHello(true);
setUserMessages([])
}
}, [sessionId])
}, [sessionId, Keyboard])
);
return (

View File

@ -13,6 +13,7 @@ import UploaderProgress from '@/components/file-upload/upload-progress/uploader-
import SkeletonItem from '@/components/memo/SkeletonItem';
// 类型定义
import { Fonts } from '@/constants/Fonts';
import { useUploadManager } from '@/hooks/useUploadManager';
import { getCachedData, prefetchChatDetail, prefetchChats } from '@/lib/prefetch';
import { fetchApi } from '@/lib/server-api-util';
@ -227,7 +228,7 @@ const MemoList = () => {
return (
<ErrorBoundary>
<View style={[styles.container, { paddingTop: insets.top }]}>
<View style={[styles.container, { paddingTop: insets.top + 8 }]}>
<FlatList
ref={flatListRef}
data={historyList}
@ -273,7 +274,7 @@ const styles = StyleSheet.create({
backgroundColor: '#fff',
},
headerContainer: {
paddingBottom: 16,
paddingBottom: 8,
backgroundColor: '#fff',
},
title: {
@ -281,7 +282,8 @@ const styles = StyleSheet.create({
fontWeight: 'bold',
color: '#4C320C',
textAlign: 'center',
marginBottom: 16,
marginBottom: 8,
fontFamily: Fonts["quicksand"]
},
listContent: {
paddingBottom: Platform.select({
@ -314,13 +316,15 @@ const styles = StyleSheet.create({
},
memoTitle: {
fontSize: 16,
fontWeight: '500',
fontWeight: 'bold',
color: '#4C320C',
marginBottom: 4,
fontFamily: Fonts['sfPro']
},
memoSubtitle: {
fontSize: 14,
color: '#AC7E35',
fontFamily: Fonts['inter']
},
separator: {
height: 1 / PixelRatio.get(),

View File

@ -1,7 +1,7 @@
import { StyleProp, StyleSheet, Text, TextStyle, type TextProps } from 'react-native';
import { Colors } from '@/constants/Colors';
import { FontColor, Fonts } from '@/constants/Fonts';
import { FontColor, Fonts, FontSize, FontWeight } from '@/constants/Fonts';
import { useThemeColor } from '@/hooks/useThemeColor';
export type ThemeColor = keyof typeof Colors.light & keyof typeof Colors.dark;
@ -11,9 +11,9 @@ export type ThemedTextProps = TextProps & {
lightColor?: string;
darkColor?: string;
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link' | 'sfPro' | 'inter';
weight?: 'regular' | 'medium' | 'semiBold' | 'bold';
size?: 'xxs' | 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl';
radius?: 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl';
weight?: FontWeight;
size?: FontSize;
radius?: FontSize
color?: ThemeColor | FontColor | ColorValue;
};

View File

@ -1,9 +1,11 @@
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';
@ -52,6 +54,13 @@ function ChatComponent(
}
}, [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 File

@ -1,4 +1,5 @@
import { ThemedText } from "@/components/ThemedText";
import { Fonts } from "@/constants/Fonts";
import { webSocketManager } from "@/lib/websocket-util";
import { Message } from "@/types/ask";
import { Dispatch, SetStateAction } from "react";
@ -58,15 +59,13 @@ export default function AskHello({ setUserMessages, setConversationId, setIsHell
keyboardShouldPersistTaps="handled"
>
<View className="items-center">
<ThemedText style={{ fontSize: 32, fontWeight: 'bold', textAlign: 'center', lineHeight: 40, }}>
<ThemedText style={{ textAlign: 'center', lineHeight: 40, }} size="title" weight="bold">
{t('ask.hi', { ns: 'ask' })}
{"\n"}
{t('ask.iAmMemo', { ns: 'ask' })}
</ThemedText>
<View>
<Image source={require('@/assets/images/png/icon/ip.png')} style={{ width: width * 0.5, height: height * 0.3 }} />
</View>
<ThemedText className="!text-textPrimary text-center -mt-10" style={{ fontSize: 16 }}>
<Image source={require('@/assets/images/png/icon/ip.png')} style={{ width: width * 0.4, height: height * 0.25 }} />
<ThemedText className="text-center -mt-10" size='base' color="textPrimary" type="sfPro" weight="medium">
{t('ask.ready', { ns: 'ask' })}
{"\n"}
{t('ask.justAsk', { ns: 'ask' })}
@ -112,11 +111,14 @@ const styles = StyleSheet.create({
},
case: {
borderWidth: 1,
borderColor: "#AC7E35",
borderColor: Fonts["textPrimary"],
borderRadius: 10,
paddingHorizontal: 8,
paddingVertical: 2,
width: 'auto',
fontSize: 14,
color: "#4C320C"
fontSize: Fonts["sm"],
color: Fonts["textSecondary"],
fontFamily: Fonts["sfPro"]
}
})

View File

@ -12,6 +12,7 @@ import {
View
} from 'react-native';
import { Fonts } from '@/constants/Fonts';
import { webSocketManager, WsMessage } from '@/lib/websocket-util';
import { Message } from '@/types/ask';
import { useTranslation } from 'react-i18next';
@ -190,37 +191,33 @@ export default function SendMessage(props: Props) {
}
]));
let currentSessionId = conversationId;
console.log("currentSessionIdcurrentSessionId", currentSessionId);
// 如果没有对话ID先创建一个新对话
if (!currentSessionId) {
const newCurrentSessionId = await createNewConversation(text);
if (newCurrentSessionId) {
setConversationId(newCurrentSessionId);
webSocketManager.send({
type: 'Chat',
session_id: newCurrentSessionId,
message: text,
image_material_ids: selectedImages.length > 0 ? selectedImages : undefined,
});
setSelectedImages([]);
} else {
console.error("无法获取 session_id消息发送失败。");
console.error("无法获取 session_id消息发送失败1。");
setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng'));
}
}
// 通过 WebSocket 发送消息
if (currentSessionId) {
webSocketManager.send({
type: 'Chat',
session_id: currentSessionId,
message: text,
image_material_ids: selectedImages.length > 0 ? selectedImages : undefined,
});
setSelectedImages([]);
} else {
console.error("无法获取 session_id消息发送失败。");
// 可以在这里处理错误,例如显示一个提示
setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng'));
try {
webSocketManager.send({
type: 'Chat',
session_id: currentSessionId,
message: text,
image_material_ids: selectedImages.length > 0 ? selectedImages : undefined,
});
setSelectedImages([]);
} catch (error) {
console.error("无法获取 session_id消息发送失败2。", error);
setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng'));
}
}
// 将输入框清空
setInputValue('');
@ -252,16 +249,17 @@ export default function SendMessage(props: Props) {
<ScrollView horizontal={true} style={{ display: isHello ? 'flex' : 'none' }}>
<TouchableOpacity style={[styles.button, { borderColor: '#FFB645' }]} onPress={() => handleQuitly('search')}>
<SunSvg width={18} height={18} />
<ThemedText>{t("ask:ask.search")}</ThemedText>
</TouchableOpacity><TouchableOpacity style={[styles.button, { borderColor: '#E2793F' }]} onPress={() => handleQuitly('video')}>
<ThemedText type="sfPro" size="sm" weight='regular' color='textSecondary'>{t("ask:ask.search")}</ThemedText>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, { borderColor: '#E2793F' }]} onPress={() => handleQuitly('video')}>
<VideoSvg width={18} height={18} />
<ThemedText>{t("ask:ask.video")}</ThemedText>
<ThemedText type="sfPro" size="sm" weight='regular' color='textSecondary'>{t("ask:ask.video")}</ThemedText>
</TouchableOpacity>
</ScrollView>
<TextInput
style={styles.input}
placeholder="Ask MeMo Anything..."
placeholderTextColor="#999"
placeholderTextColor={Fonts["textPrimary"]}
value={inputValue}
onChangeText={(text: string) => {
setInputValue(text);
@ -296,25 +294,23 @@ const styles = StyleSheet.create({
display: 'flex',
flexDirection: 'row',
gap: 5,
// backgroundColor: '#F8F8F8'
},
container: {
justifyContent: 'center',
backgroundColor: '#transparent',
},
input: {
// borderColor: '#d9d9d9',
color: Fonts["textPrimary"],
borderColor: '#AC7E35',
borderWidth: 1,
// borderRadius: 18,
borderRadius: 25,
borderRadius: 28,
paddingHorizontal: 20,
paddingVertical: 13,
paddingVertical: 16,
lineHeight: 20,
fontSize: 16,
width: '100%', // 确保输入框宽度撑满
width: '100%',
paddingRight: 50,
backgroundColor: '#fff', // Required for shadow to show on iOS
backgroundColor: '#fff',
shadowColor: '#000',
shadowOffset: {
width: 0,
@ -322,7 +318,6 @@ const styles = StyleSheet.create({
},
shadowOpacity: 0.15,
shadowRadius: 3.84,
// Shadow for Android
elevation: 5,
},
voiceButton: {

View File

@ -26,7 +26,7 @@ const MessageBubble = ({
}: MessageBubbleProps) => {
return (
<View
className={`${isUser ? '!bg-bgPrimary ml-10 rounded-full' : '!bg-aiBubble rounded-2xl'} border-0 ${!isUser && isMessageContainMedia(item) ? '!rounded-t-3xl !rounded-b-2xl' : '!rounded-3xl'} px-3`}
className={`${isUser ? '!bg-bgPrimary ml-10 rounded-full' : '!bg-aiBubble rounded-2xl'} border-0 ${!isUser && isMessageContainMedia(item) ? '!rounded-3xl' : '!rounded-3xl'} px-3`}
style={{ marginRight: getMessageText(item) == "keepSearchIng" ? 0 : isUser ? 0 : 10 }}
>
<MessageContent

View File

@ -4,11 +4,10 @@ import { TFunction } from "i18next";
import React from 'react';
import {
StyleSheet,
TouchableOpacity,
View
} from 'react-native';
import { ThemedText } from "@/components/ThemedText";
import { Fonts } from "@/constants/Fonts";
import MessageRow from './MessageRow';
interface RenderMessageProps {
@ -24,10 +23,9 @@ interface RenderMessageProps {
t: TFunction;
setCancel: React.Dispatch<React.SetStateAction<boolean>>;
cancel: boolean;
setUserMessages: React.Dispatch<React.SetStateAction<Message[]>>;
}
const MessageItem = ({ setCancel, cancel = true, t, insets, item, sessionId, setModalVisible, modalVisible, setModalDetailsVisible, modalDetailsVisible, setSelectedImages, selectedImages, setUserMessages }: RenderMessageProps) => {
const MessageItem = ({ setCancel, cancel = true, t, insets, item, sessionId, setModalVisible, modalVisible, setModalDetailsVisible, modalDetailsVisible, setSelectedImages, selectedImages }: RenderMessageProps) => {
const isUser = item.role === User;
return (
@ -44,7 +42,7 @@ const MessageItem = ({ setCancel, cancel = true, t, insets, item, sessionId, set
setSelectedImages={setSelectedImages}
setModalDetailsVisible={setModalDetailsVisible}
/>
{item.content instanceof Array && item.content.filter((media: ContentPart) => media.type !== 'text').length > 0 && (
{/* {item.content instanceof Array && item.content.filter((media: ContentPart) => media.type !== 'text').length > 0 && (
<View style={styles.tips}>
<TouchableOpacity style={[styles.tip, { borderRadius: 16 }]} onPress={() => {
@ -55,7 +53,7 @@ const MessageItem = ({ setCancel, cancel = true, t, insets, item, sessionId, set
<ThemedText style={styles.tipText}>Help me find materials for subsequent operations.</ThemedText>
</TouchableOpacity>
</View>
)}
)} */}
</View>
</View>
);
@ -74,7 +72,8 @@ const styles = StyleSheet.create({
},
tipText: {
color: '#4C320C',
fontSize: 14
fontSize: 14,
fontFamily: Fonts['inter']
}
});

View File

@ -22,6 +22,7 @@ export const Fonts = {
'3xl': 30,
'4xl': 36,
'5xl': 48,
"title": 32,
// color
bgPrimary: '#FFB645',
@ -37,6 +38,6 @@ export const Fonts = {
} as const;
export type FontWeight = keyof Omit<typeof Fonts, 'quicksand' | 'sfPro' | 'inter' | 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl'>;
export type FontSize = 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl';
export type FontSize = 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | 'title';
export type FontColor = 'bgPrimary' | 'bgSecondary' | 'textPrimary' | 'textSecondary' | 'textThird' | 'textWhite';