From c406312e0ae6c1a49611c0d67ea573f5ce955c7e Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Mon, 4 Aug 2025 11:49:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E7=BD=AE=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/_layout.tsx | 4 +- app/(tabs)/reset-password.tsx | 155 +++++++++++++++++++++++++--------- components/ask/send.tsx | 18 ++-- 3 files changed, 130 insertions(+), 47 deletions(-) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 514e602..5e4c871 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,4 +1,5 @@ import { HapticTab } from '@/components/HapticTab'; +import AskNavbar from '@/components/layout/ask'; import { TabBarIcon } from '@/components/navigation/TabBarIcon'; import { requestNotificationPermission } from '@/components/owner/utils'; import TabBarBackground from '@/components/ui/TabBarBackground'; @@ -7,14 +8,13 @@ import { useColorScheme } from '@/hooks/useColorScheme'; import { prefetchChats } from '@/lib/prefetch'; import { fetchApi } from '@/lib/server-api-util'; import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util'; +import { TransitionPresets } from '@react-navigation/bottom-tabs'; import * as Notifications from 'expo-notifications'; import { Tabs } from 'expo-router'; import * as SecureStore from 'expo-secure-store'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Platform } from 'react-native'; -import { TransitionPresets } from '@react-navigation/bottom-tabs'; -import AskNavbar from '@/components/layout/ask'; interface PollingData { title: string; diff --git a/app/(tabs)/reset-password.tsx b/app/(tabs)/reset-password.tsx index 3995603..598ad10 100644 --- a/app/(tabs)/reset-password.tsx +++ b/app/(tabs)/reset-password.tsx @@ -7,19 +7,19 @@ import { Ionicons } from '@expo/vector-icons'; import { useLocalSearchParams, useRouter } from 'expo-router'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { ActivityIndicator, KeyboardAvoidingView, Platform, ScrollView, TextInput, TouchableOpacity, View } from 'react-native'; +import { ActivityIndicator, KeyboardAvoidingView, Platform, ScrollView, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native'; -const resetPassword = () => { +const ResetPassword = () => { const { t } = useTranslation(); const router = useRouter(); const { session_id: resetPasswordSessionId, token } = useLocalSearchParams<{ session_id: string; token: string }>(); - // 使用 auth context 登录 const { login } = useAuth(); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [loading, setLoading] = useState(false); const [showPassword, setShowPassword] = useState(false); + const [showSecondPassword, setShowSecondPassword] = useState(false); const [error, setError] = useState(''); const validatePassword = (pwd: string) => { @@ -38,8 +38,8 @@ const resetPassword = () => { return; } - if (!validatePassword(password)) { - setError(t('auth.signup.passwordAuth', { ns: 'login' })); + if (password?.length < 6) { + setError(t('auth.signup.pwdLengthError', { ns: 'login' })); return; } @@ -64,6 +64,7 @@ const resetPassword = () => { if (login) { login(response, response.access_token || ''); } + router.push('/ask'); } catch (error) { console.error('Reset password error:', error); setError(t('auth.resetPwd.error', { ns: 'login' }) || 'Failed to reset password'); @@ -75,80 +76,75 @@ const resetPassword = () => { return ( - - - + + + {t('auth.resetPwd.title', { ns: 'login' })} {error ? ( - + {error} ) : null} - - + + { + setPassword(value) + }} secureTextEntry={!showPassword} - autoCapitalize="none" - autoCorrect={false} /> setShowPassword(!showPassword)} - className="p-2" + style={styles.eyeIcon} > - - - + { + setConfirmPassword(value) + }} + secureTextEntry={!showSecondPassword} /> setShowPassword(!showPassword)} - className="p-2" + onPress={() => setShowSecondPassword(!showSecondPassword)} + style={styles.eyeIcon} > - {loading ? ( ) : ( - + {t('auth.resetPwd.resetButton', { ns: 'login' })} )} @@ -157,6 +153,87 @@ const resetPassword = () => { ); -} +}; -export default resetPassword +const styles = StyleSheet.create({ + passwordInputContainer: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: 12, + backgroundColor: '#FFF8DE', + overflow: 'hidden', + }, + container: { + flex: 1, + backgroundColor: '#fff', + }, + scrollContainer: { + flexGrow: 1, + justifyContent: 'center', + padding: 20, + }, + formContainer: { + width: '100%', + maxWidth: 400, + alignSelf: 'center', + padding: 20, + borderRadius: 12, + backgroundColor: '#fff', + }, + title: { + fontSize: 24, + fontWeight: 'bold', + marginBottom: 24, + textAlign: 'center', + color: '#1f2937', + }, + errorText: { + color: '#ef4444', + marginBottom: 16, + textAlign: 'center', + }, + inputContainer: { + marginBottom: 24, + gap: 16 + }, + inputWrapper: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1, + borderColor: '#e5e7eb', + borderRadius: 8, + paddingHorizontal: 12, + }, + confirmInput: { + marginTop: 16, + }, + input: { + borderRadius: 12, + paddingHorizontal: 16, + paddingVertical: 12, + fontSize: 16, + textAlignVertical: 'center', + backgroundColor: '#FFF8DE' + }, + eyeIcon: { + padding: 8, + }, + submitButton: { + width: '100%', + paddingVertical: 12, + borderRadius: 8, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#E2793F', + }, + submitButtonDisabled: { + backgroundColor: '#f59e0b', + }, + submitButtonText: { + color: '#fff', + fontSize: 16, + fontWeight: '600', + }, +}); + +export default ResetPassword; diff --git a/components/ask/send.tsx b/components/ask/send.tsx index b70c028..08effe9 100644 --- a/components/ask/send.tsx +++ b/components/ask/send.tsx @@ -12,12 +12,11 @@ import { View } from 'react-native'; +import { webSocketManager, WsMessage } from '@/lib/websocket-util'; import { Message } from '@/types/ask'; import { useTranslation } from 'react-i18next'; import { ThemedText } from '../ThemedText'; import { createNewConversation } from './utils'; -import { WsMessage } from '@/lib/websocket-util'; -import { webSocketManager } from '@/lib/websocket-util'; interface Props { setIsHello: Dispatch>, @@ -38,12 +37,12 @@ export default function SendMessage(props: Props) { const [inputValue, setInputValue] = useState(''); // 添加一个ref来跟踪键盘状态 - const isKeyboardVisible = useRef(false); + const isKeyboardVisible = useRef(false); const chunkQueue = useRef([]); const renderInterval = useRef | null>(null); useEffect(() => { - const handleChatStream = (message: WsMessage) => { + const handleChatStream = (message: WsMessage) => { if (message.type !== 'ChatStream' || !message.chunk) return; chunkQueue.current.push(message.chunk); @@ -76,7 +75,7 @@ export default function SendMessage(props: Props) { } }; - const handleChatStreamEnd = (message: WsMessage) => { + const handleChatStreamEnd = (message: WsMessage) => { if (message.type !== 'ChatStreamEnd') return; // Stop the timer and process any remaining chunks @@ -129,7 +128,7 @@ export default function SendMessage(props: Props) { webSocketManager.subscribe('ChatStreamEnd', typedHandleChatStreamEnd); webSocketManager.subscribe('ChatResponse', typedHandleChatResponse); - return () => { + return () => { webSocketManager.unsubscribe('ChatStream', typedHandleChatStream); webSocketManager.unsubscribe('ChatStreamEnd', typedHandleChatStreamEnd); webSocketManager.unsubscribe('ChatResponse', typedHandleChatResponse); @@ -194,6 +193,13 @@ export default function SendMessage(props: Props) { if (!currentSessionId) { currentSessionId = await createNewConversation(text); setConversationId(currentSessionId); + webSocketManager.send({ + type: 'Chat', + session_id: currentSessionId, + message: text, + image_material_ids: selectedImages.length > 0 ? selectedImages : undefined, + }); + setSelectedImages([]); } // 通过 WebSocket 发送消息