A React Native app for the ultimate thinking partner.
1/**
2 * File Upload Utility
3 *
4 * Handles document file picking and upload for file attachments.
5 * Supports web, iOS, and Android platforms.
6 *
7 * This utility is used by MessageInputEnhanced to handle document uploads.
8 */
9
10import { Platform, Alert } from 'react-native';
11import * as DocumentPicker from 'expo-document-picker';
12
13export interface FilePickerResult {
14 name: string;
15 size: number;
16 type: string;
17 file: File; // Web File object
18 uri?: string; // Mobile URI
19}
20
21/**
22 * Opens a file picker for document selection
23 *
24 * @returns Promise<FilePickerResult | null> - Selected file info or null if cancelled
25 */
26export async function pickFile(): Promise<FilePickerResult | null> {
27 // Web platform - use HTML file input
28 if (Platform.OS === 'web') {
29 return pickFileWeb();
30 }
31
32 // Mobile platforms (iOS/Android) - use expo-document-picker
33 return pickFileMobile();
34}
35
36/**
37 * Web file picker implementation
38 */
39async function pickFileWeb(): Promise<FilePickerResult | null> {
40 return new Promise((resolve) => {
41 try {
42 const input = document.createElement('input');
43 input.type = 'file';
44 input.accept = '.pdf,.txt,.md,.json,.csv,.doc,.docx';
45
46 input.onchange = async (e: any) => {
47 const file = e.target?.files?.[0];
48 if (!file) {
49 resolve(null);
50 return;
51 }
52
53 console.log('Selected file:', file.name, 'size:', file.size, 'type:', file.type);
54
55 // Check file size (10MB limit)
56 const MAX_SIZE = 10 * 1024 * 1024;
57 if (file.size > MAX_SIZE) {
58 const sizeMB = (file.size / 1024 / 1024).toFixed(2);
59 Alert.alert(
60 'File Too Large',
61 `This file is ${sizeMB}MB. Maximum allowed is 10MB.`
62 );
63 resolve(null);
64 return;
65 }
66
67 resolve({
68 name: file.name,
69 size: file.size,
70 type: file.type,
71 file,
72 });
73 };
74
75 input.oncancel = () => {
76 resolve(null);
77 };
78
79 input.click();
80 } catch (error) {
81 console.error('Error creating file picker:', error);
82 Alert.alert('Error', 'Failed to open file picker');
83 resolve(null);
84 }
85 });
86}
87
88/**
89 * Mobile file picker implementation (iOS/Android)
90 */
91async function pickFileMobile(): Promise<FilePickerResult | null> {
92 try {
93 const result = await DocumentPicker.getDocumentAsync({
94 type: ['application/pdf', 'text/plain', 'text/markdown', 'application/json',
95 'text/csv', 'application/msword',
96 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
97 copyToCacheDirectory: true,
98 });
99
100 if (result.canceled) {
101 return null;
102 }
103
104 const asset = result.assets[0];
105 if (!asset) {
106 return null;
107 }
108
109 console.log('Selected file:', asset.name, 'size:', asset.size, 'type:', asset.mimeType);
110
111 // Check file size (10MB limit)
112 const MAX_SIZE = 10 * 1024 * 1024;
113 if (asset.size && asset.size > MAX_SIZE) {
114 const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
115 Alert.alert(
116 'File Too Large',
117 `This file is ${sizeMB}MB. Maximum allowed is 10MB.`
118 );
119 return null;
120 }
121
122 // For mobile, we need to convert the URI to a File object for upload
123 // We'll fetch the file content and create a blob
124 const response = await fetch(asset.uri);
125 const blob = await response.blob();
126 const file = new File([blob], asset.name, { type: asset.mimeType || 'application/octet-stream' });
127
128 return {
129 name: asset.name,
130 size: asset.size || blob.size,
131 type: asset.mimeType || 'application/octet-stream',
132 file,
133 uri: asset.uri,
134 };
135 } catch (error) {
136 console.error('Error picking file:', error);
137 Alert.alert('Error', 'Failed to pick file');
138 return null;
139 }
140}