This commit is contained in:
seb
2026-09-19 06:37:42 +02:00
parent 02e9a555d6
commit abe078ff05
14 changed files with 1197 additions and 560 deletions

View File

@@ -105,6 +105,11 @@ export class ApiClient {
update: (id, body) => this.#json('PATCH', `/api/delivery/${enc(id)}`, { body }),
/** DELETE /api/delivery/{id} → 204 */
delete: (id) => this.#empty('DELETE', `/api/delivery/${enc(id)}`),
/** GET /api/delivery/{id}/file/{fileId} → Blob (full binary content) */
getFile: (id, fileId, { download = false } = {}) =>
this.#blob('GET', `/api/delivery/${enc(id)}/file/${enc(fileId)}`, {
query: download ? { download: 'true' } : undefined,
}),
/**
* POST /api/delivery/parse — multipart/form-data, field "file"
* Accepts PDF or text files (max 50 MB), detection via mimetype/filename
@@ -153,6 +158,9 @@ export class ApiClient {
}
}
},
parsePdfSse(file) {
return this.parseSse(file);
},
};

View File

@@ -682,6 +682,75 @@ export const delivery = {
return http(`/api/delivery/${id}`, { method: 'DELETE' });
},
/**
* GET /api/delivery/:id/file/:fileId → Blob
* Full binary content of attached file (PDF, text, etc.)
*/
async getFile(id, fileId, { download = false } = {}) {
const query = download ? '?download=true' : '';
const res = await fetch(`/api/delivery/${encodeURIComponent(id)}/file/${encodeURIComponent(fileId)}${query}`, {
method: 'GET',
credentials: 'include',
});
if (!res.ok) {
let message = res.statusText;
try {
const data = await res.json();
message = data.error || data.message || message;
} catch { /* ignore */ }
const err = new Error(message || `Request failed (${res.status})`);
err.status = res.status;
throw err;
}
return res.blob();
},
/**
* POST /api/delivery/parse — multipart/form-data, field "file"
* Accepts PDF, TXT or CSV document (max 50 MB)
* Streams SSE events: status, llm_call, tool_call, usage, progress, result, error
*/
async *parseSse(file) {
const formData = new FormData();
formData.append('file', file, file.name);
const res = await fetch('/api/delivery/parse', {
method: 'POST',
credentials: 'include',
body: formData,
});
if (!res.ok) {
let message = res.statusText;
try {
const data = await res.json();
message = data.error || data.message || message;
} catch { /* ignore */ }
const err = new Error(message || `Request failed (${res.status})`);
err.status = res.status;
throw err;
}
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = '';
let currentEvent = 'message';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
const lines = buf.split('\n');
buf = lines.pop();
for (const line of lines) {
if (line.startsWith('event:')) {
currentEvent = line.slice(6).trim();
} else if (line.startsWith('data:')) {
const raw = line.slice(5).trim();
let data;
try { data = JSON.parse(raw); } catch { data = raw; }
yield { event: currentEvent, data };
currentEvent = 'message';
}
}
}
},
};
/**

View File

@@ -1,7 +1,16 @@
import { createContext, createElement, useContext, useRef } from 'react';
import { StorageManager } from './StorageManager';
export const StorageManagerContext = createContext(null);
let defaultStorageManager = null;
export function getStorageManager() {
if (!defaultStorageManager) {
defaultStorageManager = new StorageManager();
}
return defaultStorageManager;
}
export const StorageManagerContext = createContext(getStorageManager());
/**
* Provider — mount once near the top of the tree.
@@ -9,7 +18,7 @@ export const StorageManagerContext = createContext(null);
export function StorageManagerProvider({ children }) {
const mgrRef = useRef(null);
if (!mgrRef.current) {
mgrRef.current = new StorageManager();
mgrRef.current = getStorageManager();
}
return createElement(StorageManagerContext.Provider, { value: mgrRef.current }, children);
@@ -19,9 +28,6 @@ export function StorageManagerProvider({ children }) {
* Hook for function components that need the store manager.
*/
export function useStorageManager() {
const ctx = useContext(StorageManagerContext);
if (!ctx) {
throw new Error('useStorageManager must be used within a <StorageManagerProvider>');
}
const ctx = useContext(StorageManagerContext) || getStorageManager();
return ctx;
}