// components/DocumentUploader.tsx
import React, { useState } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ActivityIndicator,
Alert,
} from 'react-native';
import { pickAndUploadDocument } from '../services/uploadService';
type UploadState = 'idle' | 'selecting' | 'uploading' | 'processing' | 'completed' | 'failed';
export function DocumentUploader() {
const [state, setState] = useState<UploadState>('idle');
const [progress, setProgress] = useState(0);
const [statusText, setStatusText] = useState('');
const [documentId, setDocumentId] = useState<number | null>(null);
const handleUpload = async () => {
try {
setState('selecting');
setProgress(0);
setStatusText('选择文件...');
const result = await pickAndUploadDocument({
onProgress: (p) => {
setState('uploading');
setProgress(p);
setStatusText(`上传中 ${Math.round(p * 100)}%`);
},
onStatusChange: (status) => {
setDocumentId(status.document_id);
switch (status.status) {
case 'pending':
setState('processing');
setStatusText('等待处理...');
break;
case 'processing':
setState('processing');
setStatusText('正在索引文档...');
break;
case 'completed':
setState('completed');
setStatusText(`索引完成!共 ${status.chunk_count} 个分块`);
break;
case 'failed':
setState('failed');
setStatusText(`索引失败: ${status.error_message}`);
break;
}
},
});
if (result.status === 'completed') {
Alert.alert('成功', '文档上传并索引完成,现在可以进行 RAG 查询了');
} else {
Alert.alert('失败', result.error_message || '索引失败');
}
} catch (error: any) {
setState('failed');
setStatusText(error.message);
Alert.alert('错误', error.message);
}
};
const renderContent = () => {
switch (state) {
case 'idle':
return (
<TouchableOpacity style={styles.button} onPress={handleUpload}>
<Text style={styles.buttonText}>选择文件上传</Text>
</TouchableOpacity>
);
case 'selecting':
return (
<View style={styles.statusContainer}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.statusText}>选择文件...</Text>
</View>
);
case 'uploading':
return (
<View style={styles.statusContainer}>
<View style={styles.progressBar}>
<View style={[styles.progressFill, { width: `${progress * 100}%` }]} />
</View>
<Text style={styles.statusText}>{statusText}</Text>
</View>
);
case 'processing':
return (
<View style={styles.statusContainer}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.statusText}>{statusText}</Text>
</View>
);
case 'completed':
return (
<View style={styles.statusContainer}>
<Text style={styles.successText}>✓ {statusText}</Text>
<TouchableOpacity style={styles.button} onPress={handleUpload}>
<Text style={styles.buttonText}>上传另一个文件</Text>
</TouchableOpacity>
</View>
);
case 'failed':
return (
<View style={styles.statusContainer}>
<Text style={styles.errorText}>✗ {statusText}</Text>
<TouchableOpacity style={styles.button} onPress={handleUpload}>
<Text style={styles.buttonText}>重试</Text>
</TouchableOpacity>
</View>
);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>文档上传</Text>
<Text style={styles.subtitle}>
支持格式: PDF, Word, PPT, 图片, TXT, Markdown
</Text>
<Text style={styles.subtitle}>最大文件大小: 200MB</Text>
{renderContent()}
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
alignItems: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 8,
},
subtitle: {
fontSize: 14,
color: '#666',
marginBottom: 4,
},
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
marginTop: 20,
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
statusContainer: {
alignItems: 'center',
marginTop: 20,
width: '100%',
},
statusText: {
marginTop: 12,
fontSize: 14,
color: '#333',
},
successText: {
fontSize: 16,
color: '#34C759',
fontWeight: '600',
marginBottom: 12,
},
errorText: {
fontSize: 16,
color: '#FF3B30',
fontWeight: '600',
marginBottom: 12,
},
progressBar: {
width: '100%',
height: 8,
backgroundColor: '#E5E5EA',
borderRadius: 4,
overflow: 'hidden',
},
progressFill: {
height: '100%',
backgroundColor: '#007AFF',
},
});