IA Assistant version initiale
This commit is contained in:
1 parent
3e8508d48b
commit
662003fdd3
29 files changed
+4253
-10
No files matched your search
@@ -64,6 +64,63 @@ export const api = {
|
||||
fetch(BASE + path, { method: 'POST', body: formData, headers: authHeaders() }).then(handle),
|
||||
postForm: (path, formData) =>
|
||||
fetch(BASE + path, { method: 'POST', body: formData, headers: authHeaders() }).then(handle),
|
||||
/** Flux SSE (Server-Sent Events) en POST — utilisé par l'assistant IA
|
||||
* (POST /api/ai/chat). EventSource ne permet pas d'en-têtes personnalisés
|
||||
* (Authorization, X-Investisseur-Id) : on lit donc le flux manuellement
|
||||
* via fetch + ReadableStream, en parsant les trames "event:"/"data:"
|
||||
* séparées par une ligne vide, au format écrit par le backend. Générateur
|
||||
* asynchrone : `for await (const {event, data} of api.stream(...))`.
|
||||
*/
|
||||
stream: async function* (path, body) {
|
||||
const res = await fetch(BASE + path, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok || !res.body) {
|
||||
let msg = res.statusText || 'Requête échouée';
|
||||
try {
|
||||
const text = await res.text();
|
||||
const parsed = text ? JSON.parse(text) : null;
|
||||
if (parsed && parsed.error) msg = parsed.error;
|
||||
} catch { /* corps non-JSON, on garde statusText */ }
|
||||
const err = new Error(msg);
|
||||
err.status = res.status;
|
||||
throw err;
|
||||
}
|
||||
|
||||
const reader = res.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buf = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buf += decoder.decode(value, { stream: true });
|
||||
|
||||
let sepIdx;
|
||||
while ((sepIdx = buf.indexOf('\n\n')) !== -1) {
|
||||
const frame = buf.slice(0, sepIdx);
|
||||
buf = buf.slice(sepIdx + 2);
|
||||
|
||||
let event = 'message';
|
||||
const dataLines = [];
|
||||
for (const line of frame.split('\n')) {
|
||||
if (line.startsWith('event:')) event = line.slice(6).trim();
|
||||
else if (line.startsWith('data:')) dataLines.push(line.slice(5).trim());
|
||||
}
|
||||
if (dataLines.length === 0) continue;
|
||||
|
||||
let data;
|
||||
try { data = JSON.parse(dataLines.join('\n')); }
|
||||
catch { data = dataLines.join('\n'); }
|
||||
|
||||
yield { event, data };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
blob: (path) =>
|
||||
fetch(BASE + path, { headers: authHeaders(), cache: 'no-store' }).then(async res => {
|
||||
if (!res.ok) { const t = await res.text(); throw new Error(t || res.statusText); }
|
||||
|
||||
Reference in new issue
Block a user