import type { ImportStatementResponse } from '@family-budget/shared'; import { api } from './client'; export async function importStatement( file: File, ): Promise { const formData = new FormData(); formData.append('file', file); return api.postFormData( '/api/import/statement', formData, ); } export interface SseProgressEvent { stage: 'pdf' | 'llm' | 'import'; progress: number; message: string; } export interface SseDoneEvent { stage: 'done'; progress: 100; result: ImportStatementResponse; } export interface SseErrorEvent { stage: 'error'; message: string; } export type SseEvent = SseProgressEvent | SseDoneEvent | SseErrorEvent; export async function importStatementStream( file: File, onEvent: (event: SseEvent) => void, ): Promise { const formData = new FormData(); formData.append('file', file); const res = await fetch('/api/import/statement', { method: 'POST', body: formData, credentials: 'include', }); if (!res.ok) { let msg = 'Ошибка импорта'; try { const body = await res.json(); if (body.message) msg = body.message; } catch { /* use default */ } onEvent({ stage: 'error', message: msg }); return; } const reader = res.body?.getReader(); if (!reader) { onEvent({ stage: 'error', message: 'Streaming не поддерживается' }); return; } const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() ?? ''; for (const line of lines) { const trimmed = line.trim(); if (!trimmed.startsWith('data: ')) continue; try { const parsed = JSON.parse(trimmed.slice(6)) as SseEvent; onEvent(parsed); } catch { /* skip malformed lines */ } } } }