(e: React.ChangeEvent<HTMLInputElement>)
| 2252 | } |
| 2253 | |
| 2254 | const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => { |
| 2255 | const file = e.target.files?.[0] |
| 2256 | // Always reset input so the same file can be re-selected |
| 2257 | e.target.value = '' |
| 2258 | if (!file) return |
| 2259 | |
| 2260 | if (file.size > MAX_IMPORT_SIZE) { |
| 2261 | setImportStatus('error') |
| 2262 | setImportMessage(`File too large (${(file.size / 1024 / 1024).toFixed(1)} MB). Maximum is 10 MB.`) |
| 2263 | return |
| 2264 | } |
| 2265 | |
| 2266 | const reader = new FileReader() |
| 2267 | reader.onload = (event) => { |
| 2268 | try { |
| 2269 | const imported = JSON.parse(event.target?.result as string) |
| 2270 | |
| 2271 | // Legacy export: just an array of conversations |
| 2272 | if (Array.isArray(imported)) { |
| 2273 | if (!imported.every((c: unknown) => c && typeof c === 'object' && 'id' in (c as Record<string, unknown>) && 'messages' in (c as Record<string, unknown>))) { |
| 2274 | throw new Error('Array does not contain valid conversations.') |
| 2275 | } |
| 2276 | setPendingImport({ conversations: imported }) |
| 2277 | setPendingImportSummary(`${imported.length} conversations (legacy format)`) |
| 2278 | return |
| 2279 | } |
| 2280 | |
| 2281 | if (typeof imported !== 'object' || imported === null) { |
| 2282 | throw new Error('Not a valid G0DM0D3 export file.') |
| 2283 | } |
| 2284 | |
| 2285 | // Validate conversations field if present |
| 2286 | if (imported.conversations !== undefined && !Array.isArray(imported.conversations)) { |
| 2287 | throw new Error('Invalid backup: conversations field is not an array.') |
| 2288 | } |
| 2289 | |
| 2290 | // Must have conversations or _source marker |
| 2291 | if (!imported.conversations && !imported._source) { |
| 2292 | throw new Error('Not a valid G0DM0D3 export file.') |
| 2293 | } |
| 2294 | |
| 2295 | const convCount = imported.conversations?.length ?? 0 |
| 2296 | const hasSettings = imported.theme || imported.defaultModel || imported.apiKey |
| 2297 | const memCount = imported.memories?.length ?? 0 |
| 2298 | const parts = [] |
| 2299 | if (convCount > 0) parts.push(`${convCount} conversations`) |
| 2300 | if (memCount > 0) parts.push(`${memCount} memories`) |
| 2301 | if (hasSettings) parts.push('settings') |
| 2302 | if (imported.apiKey) parts.push('API key') |
| 2303 | |
| 2304 | setPendingImport(imported) |
| 2305 | setPendingImportSummary(parts.join(', ') || 'backup data') |
| 2306 | } catch (err) { |
| 2307 | setImportStatus('error') |
| 2308 | setImportMessage(err instanceof Error ? err.message : 'Failed to parse export file.') |
| 2309 | } |
| 2310 | } |
| 2311 | reader.readAsText(file) |
nothing calls this directly
no outgoing calls
no test coverage detected