feat: ask页面样式
This commit is contained in:
parent
c406312e0a
commit
b266b6646d
@ -4,10 +4,11 @@ import AskHello from "@/components/ask/hello";
|
||||
import SendMessage from "@/components/ask/send";
|
||||
import { ThemedText } from "@/components/ThemedText";
|
||||
import { fetchApi } from "@/lib/server-api-util";
|
||||
import { webSocketManager, WsMessage } from "@/lib/websocket-util";
|
||||
import { getWebSocketErrorMessage, webSocketManager, WsMessage } from "@/lib/websocket-util";
|
||||
import { Assistant, Message } from "@/types/ask";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Animated,
|
||||
FlatList,
|
||||
@ -31,6 +32,7 @@ export default function AskScreen() {
|
||||
const [selectedImages, setSelectedImages] = useState<string[]>([]);
|
||||
const fadeAnim = useRef(new Animated.Value(1)).current;
|
||||
const fadeAnimChat = useRef(new Animated.Value(0)).current;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { sessionId, newSession } = useLocalSearchParams<{
|
||||
sessionId: string;
|
||||
@ -132,16 +134,23 @@ export default function AskScreen() {
|
||||
|
||||
const handleError = (message: WsMessage) => {
|
||||
if (message.type === 'Error') {
|
||||
console.error(`WebSocket Error: ${message.code} - ${message.message}`);
|
||||
console.log(`WebSocket Error: ${message.code} - ${message.message}`);
|
||||
// 可以在这里添加错误提示,例如替换最后一条消息为错误信息
|
||||
setUserMessages(prev => {
|
||||
const newMessages = [...prev];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage && typeof lastMessage.content === 'string' && lastMessage.content === 'keepSearchIng') {
|
||||
lastMessage.content = `Error: ${message.message}`;
|
||||
}
|
||||
return newMessages;
|
||||
})
|
||||
// 创建新的数组和新的消息对象
|
||||
return prev.map((msg, index) => {
|
||||
if (index === prev.length - 1 &&
|
||||
typeof msg.content === 'string' &&
|
||||
msg.content === 'keepSearchIng') {
|
||||
// 返回新的消息对象
|
||||
return {
|
||||
...msg,
|
||||
content: getWebSocketErrorMessage(message.code, t)
|
||||
};
|
||||
}
|
||||
return msg;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import ConversationsSvg from '@/assets/icons/svg/conversations.svg';
|
||||
import StoriesSvg from '@/assets/icons/svg/stories.svg';
|
||||
import CarouselComponent from '@/components/owner/carousel';
|
||||
|
||||
import CreateCountComponent from '@/components/owner/createCount';
|
||||
import Ranking from '@/components/owner/ranking';
|
||||
import MemberCard from '@/components/owner/rights/memberCard';
|
||||
import SettingModal from '@/components/owner/setting';
|
||||
import SkeletonOwner from '@/components/owner/SkeletonOwner';
|
||||
@ -126,9 +128,9 @@ export default function OwnerPage() {
|
||||
<MemberCard pro={userInfoDetails?.membership_level} />
|
||||
|
||||
{/* 分类 */}
|
||||
{/* <View style={{ marginHorizontal: -16, marginBottom: -16 }}>
|
||||
<View style={{ marginHorizontal: -16, marginBottom: -16 }}>
|
||||
<CarouselComponent data={userInfoDetails?.material_counter} />
|
||||
</View> */}
|
||||
</View>
|
||||
|
||||
{/* 作品数据 */}
|
||||
<View className='flex flex-row justify-between gap-[1rem]'>
|
||||
@ -137,7 +139,7 @@ export default function OwnerPage() {
|
||||
</View>
|
||||
|
||||
{/* 排行榜 */}
|
||||
{/* <Ranking data={userInfoDetails.title_rankings} /> */}
|
||||
<Ranking data={userInfoDetails.title_rankings} />
|
||||
</View>
|
||||
}
|
||||
// 优化性能:添加 getItemLayout
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ContentPart, Message } from '@/types/ask';
|
||||
import React, { Dispatch, ForwardedRef, forwardRef, SetStateAction, useCallback, useMemo, useState } from 'react';
|
||||
import React, { Dispatch, ForwardedRef, forwardRef, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
FlatList,
|
||||
@ -12,6 +12,8 @@ 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[];
|
||||
@ -37,6 +39,18 @@ function ChatComponent(
|
||||
}), []);
|
||||
|
||||
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]);
|
||||
|
||||
const renderMessageItem = useCallback(({ item, index }: { item: Message, index: number }) => {
|
||||
const itemStyle = index === 0 ? { marginTop: 16, marginHorizontal: 16 } : { marginHorizontal: 16 };
|
||||
@ -63,7 +77,17 @@ function ChatComponent(
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1 }}>
|
||||
<FlatList
|
||||
ref={ref}
|
||||
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}
|
||||
@ -75,7 +99,17 @@ function ChatComponent(
|
||||
updateCellsBatchingPeriod={50}
|
||||
initialNumToRender={10}
|
||||
windowSize={11}
|
||||
{...restProps} // 将所有其他属性传递给 FlatList
|
||||
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} />
|
||||
|
||||
@ -100,7 +100,7 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 8,
|
||||
padding: 16,
|
||||
},
|
||||
dot: {
|
||||
width: 8,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import MessageBubble from './MessageBubble';
|
||||
import { getMessageText } from "@/types/ask";
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import MessageBubble from './MessageBubble';
|
||||
|
||||
interface MessageRowProps {
|
||||
item: any;
|
||||
@ -14,19 +14,19 @@ interface MessageRowProps {
|
||||
setModalDetailsVisible: React.Dispatch<React.SetStateAction<{ visible: boolean, content: any }>>;
|
||||
}
|
||||
|
||||
const MessageRow = ({
|
||||
item,
|
||||
isUser,
|
||||
setModalVisible,
|
||||
setCancel,
|
||||
cancel,
|
||||
t,
|
||||
setSelectedImages,
|
||||
const MessageRow = ({
|
||||
item,
|
||||
isUser,
|
||||
setModalVisible,
|
||||
setCancel,
|
||||
cancel,
|
||||
t,
|
||||
setSelectedImages,
|
||||
setModalDetailsVisible
|
||||
}: MessageRowProps) => {
|
||||
return (
|
||||
<View className="w-full flex-row items-end">
|
||||
<MessageBubble
|
||||
<View className="w-full" style={{ flexDirection: getMessageText(item) == "keepSearchIng" ? 'row' : 'column', alignItems: getMessageText(item) == "keepSearchIng" ? 'flex-end' : 'flex-start', gap: getMessageText(item) == "keepSearchIng" ? 8 : 0 }}>
|
||||
<MessageBubble
|
||||
item={item}
|
||||
isUser={isUser}
|
||||
setModalVisible={setModalVisible}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import ChatSvg from "@/assets/icons/svg/chat.svg";
|
||||
import { ContentPart, getMessageText, isMessageContainMedia, Message, User } from "@/types/ask";
|
||||
import { ContentPart, Message, User } from "@/types/ask";
|
||||
import { TFunction } from "i18next";
|
||||
import React from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
@ -30,8 +29,8 @@ const MessageItem = ({ setCancel, cancel = true, t, insets, item, sessionId, set
|
||||
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 ">
|
||||
<MessageRow
|
||||
<View className="max-w-[85%] mb-[1rem] flex flex-col gap-2">
|
||||
<MessageRow
|
||||
item={item}
|
||||
isUser={isUser}
|
||||
setModalVisible={setModalVisible}
|
||||
|
||||
@ -31,6 +31,11 @@
|
||||
"introduction3": "Want to make your videos more engaging and story-driven? Start with an image search!\n\nFirst, decide on your video's theme—whether it's healing natural landscapes, retro cityscapes, or vibrant life moments. Then search for related images. For example, if your theme is 'Spring Limited,' search for 'cherry blossoms falling,' 'picnic in the grass,' or 'first buds of spring.'\n\nThese images can help you visualize your video's flow and even spark new ideas—like how an old photo of a vintage object might inspire a story about time, or how a series of starry sky images could connect into a narrative about dreams and distant places. String these images together with the right music and captions, and you've got yourself a heartwarming video. Give it a try!",
|
||||
"search": "Search Assets",
|
||||
"video": "Create Video",
|
||||
"think": "Thinking..."
|
||||
"think": "Thinking...",
|
||||
"insufficientPoints": "Insufficient points. Please go to your profile to purchase more points 😊",
|
||||
"invalidToken": "Invalid token",
|
||||
"notFound": "Resource not found",
|
||||
"permissionDenied": "Permission denied",
|
||||
"notConnected": "Not connected. Please check your network connection or restart the app 😊"
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,11 @@
|
||||
"introduction3": "想让你的视频内容更吸睛、更有故事感吗?不妨试试从搜索图片入手吧!\n\n你可以先确定视频的主题——是治愈系的自然风景,还是复古风的城市街景,或是充满活力的生活瞬间?然后根据主题去搜索相关的图片,比如想做'春日限定'主题,就搜'樱花飘落''草地野餐''嫩芽初绽'之类的画面。\n\n这些图片能帮你快速理清视频的画面脉络,甚至能激发新的创意——比如一张老照片里的复古物件,或许能延伸出一段关于时光的故事;一组星空图片,说不定能串联成关于梦想与远方的叙事。把这些图片按你的想法串联起来,配上合适的音乐和文案,一段有温度的视频就诞生啦,试试看吧~",
|
||||
"search": "检索素材",
|
||||
"video": "创作视频",
|
||||
"think": "思考中..."
|
||||
"think": "思考中...",
|
||||
"insufficientPoints": "积分不足,您可以前往个人中心去购买积分😊",
|
||||
"invalidToken": "无效的令牌",
|
||||
"notFound": "未找到资源",
|
||||
"permissionDenied": "权限不足",
|
||||
"notConnected": "未连接,请检查网络连接,或者退出app重试😊"
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,8 @@ export interface PagedResult<T> {
|
||||
// 获取.env文件中的变量
|
||||
|
||||
|
||||
export const API_ENDPOINT = Constants.expoConfig?.extra?.API_ENDPOINT || "https://api.memorywake.com/api";
|
||||
export const API_ENDPOINT = Constants.expoConfig?.extra?.API_ENDPOINT || "http://192.168.31.16:31646/api";
|
||||
|
||||
|
||||
// 更新 access_token 的逻辑 - 用于React组件中
|
||||
export const useAuthToken = async<T>(message: string | null) => {
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import Constants from 'expo-constants';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { TFunction } from 'i18next';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
// 从环境变量或默认值中定义 WebSocket 端点
|
||||
export const WEBSOCKET_ENDPOINT = Constants.expoConfig?.extra?.WEBSOCKET_ENDPOINT || "wss://api.memorywake.com/ws";
|
||||
export const WEBSOCKET_ENDPOINT = Constants.expoConfig?.extra?.WEBSOCKET_ENDPOINT || "ws://192.168.31.16:31646/ws/chat";
|
||||
|
||||
export type WebSocketStatus = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';
|
||||
|
||||
@ -225,3 +226,17 @@ class WebSocketManager {
|
||||
|
||||
// 导出一个单例,确保整个应用共享同一个 WebSocket 连接
|
||||
export const webSocketManager = new WebSocketManager();
|
||||
|
||||
|
||||
// webscoket 错误映射
|
||||
export const getWebSocketErrorMessage = (key: string, t: TFunction) => {
|
||||
const messages = {
|
||||
'INSUFFICIENT_POINTS': t('ask:ask.insufficientPoints'),
|
||||
'INVALID_TOKEN': t('ask:ask.invalidToken'),
|
||||
'NOT_FOUND': t('ask:ask.notFound'),
|
||||
'PERMISSION_DENIED': t('ask:ask.permissionDenied'),
|
||||
'NOT_CONNECTED': t('ask:ask.notConnected'),
|
||||
};
|
||||
|
||||
return messages[key as keyof typeof messages] || t('ask:ask.unknownError');
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user