feat: ts报错

This commit is contained in:
jinyaqiu 2025-08-05 13:52:49 +08:00
parent a5f6c71d05
commit aa03a6798a
13 changed files with 38 additions and 366 deletions

View File

@ -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)

View File

@ -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(() => {

View File

@ -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<SetStateAction<Message[]>>;
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<NodeJS.Timeout | number>(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<string>("/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<Message>(`/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 (
<View style={styles.container}>
<View className="relative w-full">
{/* <TouchableOpacity
onPress={() => 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"}`} // 使用绝对定位将按钮放在输入框内右侧
>
<VoiceDeleteSvg />
</TouchableOpacity> */}
<TextInput
style={styles.input}
placeholder="Ask MeMo Anything..."
placeholderTextColor="#999"
className={isVoiceStart ? 'bg-bgPrimary border-none pl-12' : ''}
value={isVoiceStart ? `· · · · · · · · · · · · · · ${formatTime(elapsedTime)}` : inputValue}
onChangeText={(text: string) => {
setInputValue(text);
}}
onSubmitEditing={handleSubmit}
editable={!isVoiceStart}
// 调起的键盘类型
returnKeyType="send"
/>
{/* <TouchableOpacity
style={styles.voiceButton}
className={`absolute right-0 top-1/2 -translate-y-1/2 ${isVoiceStart ? 'bg-white px-8' : 'bg-bgPrimary'}`} // 使用绝对定位将按钮放在输入框内右侧
onPress={isVoiceStart ? stopRecording : record}
>
{isVoiceStart ? <VoiceSendSvg /> : <VoiceSvg />}
</TouchableOpacity> */}
</View>
</View>
);
}
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, // 添加一点右边距
},
});

View File

@ -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<ImagesuploaderProps> = ({
@ -48,6 +48,7 @@ export const ImagesUploader: React.FC<ImagesuploaderProps> = ({
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<ImagesuploaderProps> = ({
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<ImagesuploaderProps> = ({
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);

View File

@ -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) => {

View File

@ -47,7 +47,7 @@ export const Fireworks: React.FC<FireworksProps> = ({
const [particles, setParticles] = useState<Particle[]>([]);
const [isPlaying, setIsPlaying] = useState(autoPlay);
const particleId = useRef(0);
const timerRef = useRef<NodeJS.Timeout | null>(null);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// 生成随机位置
const getRandomPosition = () => {
@ -112,7 +112,7 @@ export const Fireworks: React.FC<FireworksProps> = ({
]),
// 旋转效果
Animated.timing(rotation, {
toValue: rotation._value + 360,
toValue: (rotation as Animated.Value & { _value: number })._value + 360,
duration: 2000,
easing: Easing.linear,
useNativeDriver: true,

View File

@ -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);
}

View File

@ -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<ViewStyle>, loop?: boolean }) {
return null;
}

View File

@ -142,11 +142,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
marginBottom: 20,
},
modalTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#4C320C',
},
closeButton: {
fontSize: 28,
color: '#4C320C',

View File

@ -387,7 +387,7 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
<PrivacyModal modalVisible={privacyModalVisible} setModalVisible={setPrivacyModalVisible} type={modalType} />
<LcensesModal modalVisible={lcensesModalVisible} setModalVisible={setLcensesModalVisible} />
<DeleteModal modalVisible={deleteModalVisible} setModalVisible={setDeleteModalVisible} setSettingModalVisible={setModalVisible} />
<DeleteModal modalVisible={deleteModalVisible} setModalVisible={setDeleteModalVisible} />
</Modal>
</>
);

View File

@ -25,7 +25,7 @@ export const reverseGeocode = async (latitude: number, longitude: number): Promi
try {
const addressResults = await fetchApi<Address[]>(`/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;

View File

@ -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<RawData, 'exif'> {
const result = { ...data };
if (result.exif) {
const newExif: Record<string, any> = {};
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<string, any>;
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;
}

View File

@ -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
}
}
}