IA Assistant version initiale

This commit is contained in:
ocroguennec committed 2026-09-05 10:54:50 +02:00
1 parent 3e8508d48b
commit 662003fdd3
29 files changed
+4253 -10

No files matched your search

+57
View File
@@ -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); }