(e: React.ChangeEvent<HTMLInputElement>)
| 2172 | } |
| 2173 | |
| 2174 | const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => { |
| 2175 | const file = e.target.files?.[0] |
| 2176 | // Always reset input so the same file can be re-selected |
| 2177 | e.target.value = '' |
| 2178 | if (!file) return |
| 2179 | |
| 2180 | if (file.size > MAX_IMPORT_SIZE) { |
| 2181 | setImportStatus('error') |
| 2182 | setImportMessage(`File too large (${(file.size / 1024 / 1024).toFixed(1)} MB). Maximum is 10 MB.`) |
| 2183 | return |
| 2184 | } |
| 2185 | |
| 2186 | const reader = new FileReader() |
| 2187 | reader.onload = (event) => { |
| 2188 | try { |
| 2189 | const imported = JSON.parse(event.target?.result as string) |
| 2190 | |
| 2191 | // Legacy export: just an array of conversations |
| 2192 | if (Array.isArray(imported)) { |
| 2193 | if (!imported.every((c: unknown) => c && typeof c === 'object' && 'id' in (c as Record<string, unknown>) && 'messages' in (c as Record<string, unknown>))) { |
| 2194 | throw new Error('Array does not contain valid conversations.') |
| 2195 | } |
| 2196 | setPendingImport({ conversations: imported }) |
| 2197 | setPendingImportSummary(`${imported.length} conversations (legacy format)`) |
| 2198 | return |
| 2199 | } |
| 2200 | |
| 2201 | if (typeof imported !== 'object' || imported === null) { |
| 2202 | throw new Error('Not a valid G0DM0D3 export file.') |
| 2203 | } |
| 2204 | |
| 2205 | // Validate conversations field if present |
| 2206 | if (imported.conversations !== undefined && !Array.isArray(imported.conversations)) { |
| 2207 | throw new Error('Invalid backup: conversations field is not an array.') |
| 2208 | } |
| 2209 | |
| 2210 | // Must have conversations or _source marker |
| 2211 | if (!imported.conversations && !imported._source) { |
| 2212 | throw new Error('Not a valid G0DM0D3 export file.') |
| 2213 | } |
| 2214 | |
| 2215 | const convCount = imported.conversations?.length ?? 0 |
| 2216 | const hasSettings = imported.theme || imported.defaultModel || imported.apiKey |
| 2217 | const memCount = imported.memories?.length ?? 0 |
| 2218 | const parts = [] |
| 2219 | if (convCount > 0) parts.push(`${convCount} conversations`) |
| 2220 | if (memCount > 0) parts.push(`${memCount} memories`) |
| 2221 | if (hasSettings) parts.push('settings') |
| 2222 | if (imported.apiKey) parts.push('API key') |
| 2223 | |
| 2224 | setPendingImport(imported) |
| 2225 | setPendingImportSummary(parts.join(', ') || 'backup data') |
| 2226 | } catch (err) { |
| 2227 | setImportStatus('error') |
| 2228 | setImportMessage(err instanceof Error ? err.message : 'Failed to parse export file.') |
| 2229 | } |
| 2230 | } |
| 2231 | reader.readAsText(file) |
nothing calls this directly
no outgoing calls
no test coverage detected