From aa03a6798ae1937e6a7e180324b45d87df3000c6 Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Tue, 5 Aug 2025 13:52:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ts=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/ask.tsx | 7 +- app/(tabs)/rights.tsx | 2 +- components/ask/voice.tsx | 221 ------------------- components/file-upload/files-uploader.tsx | 11 +- components/file-upload/uploadQueueManager.ts | 6 +- components/firework.tsx | 4 +- components/login/login.tsx | 3 +- components/lottie/lottie.tsx | 4 +- components/owner/rights/payType.tsx | 5 - components/owner/setting.tsx | 2 +- components/owner/utils.ts | 4 +- components/utils/objectFlat.ts | 26 ++- types/works.ts | 109 --------- 13 files changed, 38 insertions(+), 366 deletions(-) delete mode 100644 components/ask/voice.tsx delete mode 100644 types/works.ts diff --git a/app/(tabs)/ask.tsx b/app/(tabs)/ask.tsx index 2a15ace..10c3a95 100644 --- a/app/(tabs)/ask.tsx +++ b/app/(tabs)/ask.tsx @@ -48,11 +48,6 @@ export default function AskScreen() { } }, []); - const updateState = (value: string) => { - console.log('Received in JS:', value); - // 更新 React 状态或路由 - router.replace(value); - }; // 右滑 const gesture = Gesture.Pan() .onEnd((event) => { @@ -61,7 +56,7 @@ export default function AskScreen() { if (translationX > threshold) { // 从左向右滑动,跳转页面 - runOnJS(router.replace)("memo-list"); + runOnJS(router.replace)("/memo-list"); } }) .minPointers(1) diff --git a/app/(tabs)/rights.tsx b/app/(tabs)/rights.tsx index e9e97c2..ed2c1d7 100644 --- a/app/(tabs)/rights.tsx +++ b/app/(tabs)/rights.tsx @@ -143,7 +143,7 @@ export default function Rights() { } setConfirmLoading(true); const history = await fetchPurchaseHistory() - const historyIds = history?.filter((item: any) => isOrderExpired(item?.expirationDateIos))?.map((i) => { return i?.id }) + const historyIds = history?.filter((item: any) => isOrderExpired(item?.expirationDateIos))?.map((i: any) => { return i?.id }) if (historyIds?.includes(payType)) { setConfirmLoading(false); setTimeout(() => { diff --git a/components/ask/voice.tsx b/components/ask/voice.tsx deleted file mode 100644 index c272dc9..0000000 --- a/components/ask/voice.tsx +++ /dev/null @@ -1,221 +0,0 @@ -'use client'; -import React, { Dispatch, SetStateAction, useCallback, useState } from 'react'; -import { - StyleSheet, - TextInput, - View -} from 'react-native'; - -import { fetchApi } from '@/lib/server-api-util'; -import { Message } from '@/types/ask'; -import { RecordingPresets, useAudioRecorder } from 'expo-audio'; -interface Props { - setIsHello: (isHello: boolean) => void, - conversationId: string | null, - setUserMessages: Dispatch>; - setConversationId: (conversationId: string) => void, -} -export default function AudioRecordPlay(props: Props) { - const { setIsHello, conversationId, setUserMessages, setConversationId } = props; - const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY); - const [isRecording, setIsRecording] = useState(false); - const [isVoiceStart, setIsVoiceStart] = useState(false); - const [elapsedTime, setElapsedTime] = useState(0); - - // 用户询问 - const [inputValue, setInputValue] = useState(''); - const [timerInterval, setTimerInterval] = useState(0); - - const formatTime = (ms: number): string => { - const totalSeconds = ms / 1000; - const minutes = Math.floor(totalSeconds / 60); - const seconds = Math.floor(totalSeconds % 60); - const milliseconds = Math.floor(ms % 1000); - - return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(milliseconds).padStart(3, '0')}`; - }; - // 开始录音 - const record = async () => { - await audioRecorder.prepareToRecordAsync(); - const startTime = Date.now(); - - // 每 10ms 更新一次时间 - const interval = setInterval(() => { - const elapsed = Date.now() - startTime; - setElapsedTime(elapsed); - }, 10); - - setTimerInterval(interval); - setIsVoiceStart(true) - audioRecorder.record(); - setIsRecording(true); - }; - - const stopRecording = async () => { - // The recording will be available on `audioRecorder.uri`. - - - if (timerInterval) clearInterval(timerInterval); - setTimerInterval(0); - await audioRecorder.stop(); - setIsRecording(false); - }; - - // useEffect(() => { - // (async () => { - // const status = await AudioModule.requestRecordingPermissionsAsync(); - // if (!status.granted) { - // Alert.alert('Permission to access microphone was denied'); - // } - // })(); - // }, []); - // 获取对话信息 - const createNewConversation = useCallback(async (user_text: string) => { - const data = await fetchApi("/chat/new", { - method: "POST", - }); - setConversationId(data); - await getConversation({ session_id: data, user_text }); - }, []); - - const getConversation = useCallback(async ({ session_id, user_text }: { session_id: string, user_text: string }) => { - if (!session_id) return; - const response = await fetchApi(`/chat`, { - method: "POST", - body: JSON.stringify({ - session_id, - user_text - }) - }); - setUserMessages((prev: Message[]) => [...prev, response]); - }, []); - - // 使用 useCallback 缓存 handleSubmit - const handleSubmit = () => { - const text = inputValue; - if (text) { - setUserMessages(pre => ([...pre, { - content: { - text: text - }, - role: 'User', - timestamp: new Date().toISOString() - } - ])); - if (!conversationId) { - createNewConversation(text); - setIsHello(false); - } else { - getConversation({ - session_id: conversationId, - user_text: text - }); - } - setInputValue(''); - } - } - - return ( - - - {/* console.log('Left icon pressed')} - className={`absolute left-2 top-1/2 -translate-y-1/2 p-2 bg-white rounded-full ${isVoiceStart ? "opacity-100" : "opacity-0"}`} // 使用绝对定位将按钮放在输入框内右侧 - > - - */} - { - setInputValue(text); - }} - onSubmitEditing={handleSubmit} - editable={!isVoiceStart} - // 调起的键盘类型 - returnKeyType="send" - /> - {/* - {isVoiceStart ? : } - */} - - - ); -} - -const styles = StyleSheet.create({ - container: { - justifyContent: 'center', - backgroundColor: '#fff', - }, - title: { - fontSize: 20, - fontWeight: 'bold', - marginBottom: 20, - textAlign: 'center', - }, - recordButton: { - padding: 15, - borderRadius: 8, - alignItems: 'center', - marginBottom: 20, - }, - startButton: { - backgroundColor: '#ff6b6b', - }, - stopButton: { - backgroundColor: '#4CAF50', - }, - buttonText: { - color: 'white', - fontSize: 16, - }, - listTitle: { - fontWeight: 'bold', - marginBottom: 10, - }, - emptyText: { - fontStyle: 'italic', - color: '#888', - marginBottom: 10, - }, - recordingItem: { - padding: 10, - borderBottomWidth: 1, - borderBottomColor: '#eee', - }, - uriText: { - fontSize: 12, - color: '#777', - }, - leftIcon: { - padding: 10, - paddingLeft: 15, - }, - input: { - borderColor: '#FF9500', - borderWidth: 1, - borderRadius: 25, - paddingHorizontal: 20, - paddingVertical: 12, - fontSize: 16, - width: '100%', // 确保输入框宽度撑满 - paddingRight: 50 - }, - voiceButton: { - width: 40, - height: 40, - borderRadius: 20, - backgroundColor: '#FF9500', - justifyContent: 'center', - alignItems: 'center', - marginRight: 8, // 添加一点右边距 - }, -}); \ No newline at end of file diff --git a/components/file-upload/files-uploader.tsx b/components/file-upload/files-uploader.tsx index 15794cd..7fb2375 100644 --- a/components/file-upload/files-uploader.tsx +++ b/components/file-upload/files-uploader.tsx @@ -1,14 +1,14 @@ +import { requestLocationPermission, requestMediaLibraryPermission } from '@/components/owner/utils'; import { addMaterial, confirmUpload, getUploadUrl } from '@/lib/background-uploader/api'; import { ConfirmUpload, ExifData, FileUploadItem, ImagesuploaderProps, UploadResult, UploadTask, defaultExifData } from '@/lib/background-uploader/types'; import { uploadFileWithProgress } from '@/lib/background-uploader/uploader'; import { compressImage } from '@/lib/image-process/imageCompress'; +import { PermissionService } from '@/lib/PermissionService'; import { createVideoThumbnailFile } from '@/lib/video-process/videoThumbnail'; import * as ImagePicker from 'expo-image-picker'; -import { requestLocationPermission, requestMediaLibraryPermission } from '@/components/owner/utils'; -import { PermissionService } from '@/lib/PermissionService'; import * as MediaLibrary from 'expo-media-library'; import React, { useEffect, useState } from 'react'; -import { Alert, Button, Platform, TouchableOpacity, View } from 'react-native'; +import { Button, Platform, TouchableOpacity, View } from 'react-native'; import UploadPreview from './preview'; export const ImagesUploader: React.FC = ({ @@ -48,6 +48,7 @@ export const ImagesUploader: React.FC = ({ id: fileId, uri: asset.uri, previewUrl: asset.uri, // 使用 asset.uri 作为初始预览 + preview: asset.uri, // 使用 asset.uri 作为初始预览 name: asset.fileName || 'file', progress: 0, status: 'uploading', @@ -221,7 +222,7 @@ export const ImagesUploader: React.FC = ({ const batchResults = await Promise.allSettled( batch.map(asset => processSingleAsset(asset)) ); - + // 收集成功的结果 for (const result of batchResults) { if (result.status === 'fulfilled' && result.value) { @@ -235,7 +236,7 @@ export const ImagesUploader: React.FC = ({ for (let i = 0; i < assets.length; i += CONCURRENCY_LIMIT) { batches.push(assets.slice(i, i + CONCURRENCY_LIMIT)); } - + // 并行处理所有批次,但限制并发数量 for (let i = 0; i < batches.length; i += CONCURRENCY_LIMIT) { const batchGroup = batches.slice(i, i + CONCURRENCY_LIMIT); diff --git a/components/file-upload/uploadQueueManager.ts b/components/file-upload/uploadQueueManager.ts index 38e722d..467b944 100644 --- a/components/file-upload/uploadQueueManager.ts +++ b/components/file-upload/uploadQueueManager.ts @@ -1,3 +1,5 @@ +import { fetchApi } from '@/lib/server-api-util'; +import { ConfirmUpload, UploadUrlResponse } from '@/types/upload'; import * as SecureStore from 'expo-secure-store'; const QUEUE_KEY = 'uploadQueue'; @@ -34,14 +36,14 @@ export const uploadMediaFile = async (asset: any) => { : `video/${filename.split('.').pop()}`; const formData = new FormData(); - formData.append('file', { uri, name: filename, type } as any); + formData.append('file', { uri, name: filename, type } as unknown as File); await getUploadUrl({ ...formData, name: filename, type, size: asset.fileSize - }, {}).then((res) => { + } as unknown as File, {}).then((res) => { confirmUpload(res.file_id).then((confirmRes) => { addMaterial(res.file_id, confirmRes.file_id) }).catch((error) => { diff --git a/components/firework.tsx b/components/firework.tsx index 8b4e1fb..6319b9d 100644 --- a/components/firework.tsx +++ b/components/firework.tsx @@ -47,7 +47,7 @@ export const Fireworks: React.FC = ({ const [particles, setParticles] = useState([]); const [isPlaying, setIsPlaying] = useState(autoPlay); const particleId = useRef(0); - const timerRef = useRef(null); + const timerRef = useRef | null>(null); // 生成随机位置 const getRandomPosition = () => { @@ -112,7 +112,7 @@ export const Fireworks: React.FC = ({ ]), // 旋转效果 Animated.timing(rotation, { - toValue: rotation._value + 360, + toValue: (rotation as Animated.Value & { _value: number })._value + 360, duration: 2000, easing: Easing.linear, useNativeDriver: true, diff --git a/components/login/login.tsx b/components/login/login.tsx index 84017c4..2e528be 100644 --- a/components/login/login.tsx +++ b/components/login/login.tsx @@ -52,7 +52,8 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi router.replace('/user-message'); } } catch (error) { - setError(error.message || t('auth.login.loginError', { ns: 'login' })); + const errorMessage = error instanceof Error ? error.message : t('auth.login.loginError', { ns: 'login' }); + setError(errorMessage); } finally { setIsLoading(false); } diff --git a/components/lottie/lottie.tsx b/components/lottie/lottie.tsx index 3bb2eaa..ae45c7b 100644 --- a/components/lottie/lottie.tsx +++ b/components/lottie/lottie.tsx @@ -1,6 +1,8 @@ // welcome.tsx (Web 版本) // 在 Web 端不显示任何内容 +import { StyleProp, ViewStyle } from "react-native"; + // 占位符 移动端实际引入文件是 welcome.native.tsx 文件 -export default function WebLottie(props: { source: string }) { +export default function WebLottie(props: { source: string, style?: StyleProp, loop?: boolean }) { return null; } \ No newline at end of file diff --git a/components/owner/rights/payType.tsx b/components/owner/rights/payType.tsx index 3aa909f..3ac7567 100644 --- a/components/owner/rights/payType.tsx +++ b/components/owner/rights/payType.tsx @@ -142,11 +142,6 @@ const styles = StyleSheet.create({ alignItems: 'center', marginBottom: 20, }, - modalTitle: { - fontSize: 20, - fontWeight: 'bold', - color: '#4C320C', - }, closeButton: { fontSize: 28, color: '#4C320C', diff --git a/components/owner/setting.tsx b/components/owner/setting.tsx index 9f83dfb..88f5f5e 100644 --- a/components/owner/setting.tsx +++ b/components/owner/setting.tsx @@ -387,7 +387,7 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible: - + ); diff --git a/components/owner/utils.ts b/components/owner/utils.ts index 8794e2c..1df1a89 100644 --- a/components/owner/utils.ts +++ b/components/owner/utils.ts @@ -25,7 +25,7 @@ export const reverseGeocode = async (latitude: number, longitude: number): Promi try { const addressResults = await fetchApi(`/area/gecoding?latitude=${latitude}&longitude=${longitude}`); if (Object.keys(addressResults).length === 0) { - return null; + return undefined; } console.log('地址1:', addressResults); @@ -34,7 +34,7 @@ export const reverseGeocode = async (latitude: number, longitude: number): Promi } else { SecureStore.setItemAsync('location', JSON.stringify(addressResults)); } - return addressResults + return addressResults as unknown as Address; } catch (error) { console.log('逆地理编码失败:', error); return undefined; diff --git a/components/utils/objectFlat.ts b/components/utils/objectFlat.ts index 288925f..07f7e5f 100644 --- a/components/utils/objectFlat.ts +++ b/components/utils/objectFlat.ts @@ -4,29 +4,31 @@ interface RawData { location?: { latitude?: string | number; }; + [key: string]: any; // Allow any additional properties } -export function transformData(data: RawData): RawData { +export function transformData(data: RawData): Omit { const result = { ...data }; if (result.exif) { const newExif: Record = {}; for (const key in result.exif) { - const value = result.exif[key]; + const value: unknown = result.exif[key]; // 普通对象:{Exif}, {TIFF}, {XMP} 等 - if (typeof value === 'object' && !Array.isArray(value)) { + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + const obj = value as Record; if (key === '{GPS}') { // 处理 GPS 字段:所有子字段加前缀 "GPS" - for (const subKey in value) { + for (const subKey in obj) { const newKey = 'GPS' + subKey; // 所有字段都加前缀 - newExif[newKey] = value[subKey]; + newExif[newKey] = obj[subKey]; } } else { // 其它嵌套对象直接展开字段 - for (const subKey in value) { - newExif[subKey] = value[subKey]; + for (const subKey in obj) { + newExif[subKey] = obj[subKey]; } } } else { @@ -35,8 +37,12 @@ export function transformData(data: RawData): RawData { } } - result.exif = newExif; + // 合并展开的 exif 数据并移除 exif 属性 + const { exif, ...rest } = result; + return { ...rest, ...newExif }; } - // 最后将result的exif信息平铺 - return { ...result, ...result.exif, exif: undefined }; + + // 如果没有 exif 数据,直接返回原数据(排除 exif 属性) + const { exif, ...rest } = result; + return rest; } \ No newline at end of file diff --git a/types/works.ts b/types/works.ts deleted file mode 100644 index 1f0c293..0000000 --- a/types/works.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { MaterialItemWithMetadata } from "@/components/waterfall"; - -// 模板列表 list -export interface NodeTemplates { - id: string; - node_template_id: string - name: string; - description: string; - model: string; - prompt: string; - created_at: string; - updated_at: string; - current_version_id: number; - version: number; - is_current_version: boolean -} - -//模板详情 -export interface NodeTemplateDetail extends NodeTemplates { - material_infos: MaterialItemWithMetadata[] - example_material_ids: string[] - available_model_names: string[] -} - -// 更新模板 -export interface UpdateNodeTemplate { - example_material_ids: string[]; -} - -export interface Shot { - camera_direction: string; - camera_movement: string; - clip_id: number; - composition: string; - music_description: string; - perspective: string; - scene_name: string; - scene_sequence: number; - shot_description: string; - shot_duration: string; - shot_name: string; - shot_sequence: number; - shot_sizes: string; - sound_effect: string; - transition_in: string; - voice_over: string; -} -// 图片的debug -interface CaptionResult { - video_shots?: Shot[]; - caption_result: { - common: { - background: string; - }; - daily_life: { - activity: string; - atmosphere: string; - extra_elements: string; - people_presence: string; - scene: string; - }; - person: { - person_count: string; - person_details: Array<{ - action: string; - age: string; - expression: string; - gender: string; - mood: string; - }>; - person_identity: string; - scene: string; - }; - }; -} -export interface PhotosDebug { - id: string, - state: string, - name: string, - context: { - input: Object, - outputs: CaptionResult, - metadata: Object, - } - -} - -// prompt需要传参 -export interface PromptParams { - name: string, - value_type: string, - required: boolean, - description: string -} - -// input output -export interface InputOutput { - is_async: boolean, - name: string, - context: { - metadata: {}, - outputs: { raw_output?: JSON, metadata?: {} }, - inputs: { - material_id: string, - prompt: string - model_name: string - } - } -}