Modification du timeout pour permettre d'analyser de plus gros livres
All checks were successful
Build & Push Images / build (brain) (push) Successful in 1m28s
Build & Push Images / build (core) (push) Successful in 1m48s
Build & Push Images / build-switcher (push) Successful in 16s
Build & Push Images / build (web) (push) Successful in 1m41s

This commit is contained in:
2026-06-08 14:49:00 +02:00
parent ba5f9c7a2e
commit b968f12a0c
5 changed files with 23 additions and 15 deletions

View File

@@ -66,7 +66,7 @@ from app.infrastructure.pdf_extractor import PyMuPdfTextExtractor
app = FastAPI( app = FastAPI(
title="LoreMind Brain", title="LoreMind Brain",
description="Backend IA pour la génération de contenu narratif.", description="Backend IA pour la génération de contenu narratif.",
version="0.11.1-beta", version="0.11.2-beta",
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -14,7 +14,7 @@
<groupId>com.loremind</groupId> <groupId>com.loremind</groupId>
<artifactId>loremind-core</artifactId> <artifactId>loremind-core</artifactId>
<version>0.11.1-beta</version> <version>0.11.2-beta</version>
<name>LoreMind Core</name> <name>LoreMind Core</name>
<description>Backend Core - Architecture Hexagonale</description> <description>Backend Core - Architecture Hexagonale</description>

View File

@@ -28,7 +28,10 @@ import java.util.Map;
@RequestMapping("/api/notebooks") @RequestMapping("/api/notebooks")
public class NotebookController { public class NotebookController {
private static final long SSE_TIMEOUT_MS = 10 * 60 * 1000L; // L'analyse approfondie (map-reduce sur tout le doc) peut être longue sur un gros
// livre / un modèle lent → 30 min. Pour aller plus vite : modèle gros-contexte
// (moins de lots). Au-delà, l'expiration est gérée proprement (pas de crash).
private static final long SSE_TIMEOUT_MS = 30 * 60 * 1000L;
private final NotebookService service; private final NotebookService service;
private final NotebookChatStreamer chatStreamer; private final NotebookChatStreamer chatStreamer;
@@ -143,13 +146,18 @@ public class NotebookController {
return emitter; return emitter;
} }
// --- Helpers SSE (mêmes conventions que AiChatController) --- // --- Helpers SSE ---
// IMPORTANT : on attrape AUSSI IllegalStateException. Si le flux a déjà été fermé
// (timeout async, client déconnecté), `emitter.send/complete` la lève — et comme
// ces helpers tournent dans un thread d'exécuteur, une exception non gérée y
// remontait jusqu'au pool ("Exception in thread task-1: ResponseBodyEmitter has
// already completed"). On l'ignore silencieusement : il n'y a plus rien à envoyer.
private void sendToken(SseEmitter emitter, String token) { private void sendToken(SseEmitter emitter, String token) {
try { try {
emitter.send(SseEmitter.event().name("token").data("{\"token\":" + jsonEscape(token) + "}")); emitter.send(SseEmitter.event().name("token").data("{\"token\":" + jsonEscape(token) + "}"));
} catch (IOException e) { } catch (IOException | IllegalStateException e) {
emitter.completeWithError(e); // flux fermé/expiré : on cesse d'écrire
} }
} }
@@ -157,8 +165,8 @@ public class NotebookController {
try { try {
emitter.send(SseEmitter.event().name("progress") emitter.send(SseEmitter.event().name("progress")
.data("{\"current\":" + p.current() + ",\"total\":" + p.total() + "}")); .data("{\"current\":" + p.current() + ",\"total\":" + p.total() + "}"));
} catch (IOException e) { } catch (IOException | IllegalStateException e) {
emitter.completeWithError(e); // flux fermé/expiré : on cesse d'écrire
} }
} }
@@ -166,8 +174,8 @@ public class NotebookController {
try { try {
emitter.send(SseEmitter.event().name("done").data("{}")); emitter.send(SseEmitter.event().name("done").data("{}"));
emitter.complete(); emitter.complete();
} catch (IOException e) { } catch (IOException | IllegalStateException e) {
emitter.completeWithError(e); // flux déjà fermé/expiré : rien à compléter
} }
} }
@@ -176,8 +184,8 @@ public class NotebookController {
String message = error.getMessage() != null ? error.getMessage() : error.getClass().getSimpleName(); String message = error.getMessage() != null ? error.getMessage() : error.getClass().getSimpleName();
emitter.send(SseEmitter.event().name("error").data("{\"message\":" + jsonEscape(message) + "}")); emitter.send(SseEmitter.event().name("error").data("{\"message\":" + jsonEscape(message) + "}"));
emitter.complete(); emitter.complete();
} catch (IOException ioe) { } catch (IOException | IllegalStateException e) {
emitter.completeWithError(ioe); // flux déjà fermé/expiré : rien à envoyer
} }
} }

4
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "loremind-web", "name": "loremind-web",
"version": "0.11.1-beta", "version": "0.11.2-beta",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "loremind-web", "name": "loremind-web",
"version": "0.11.1-beta", "version": "0.11.2-beta",
"dependencies": { "dependencies": {
"@angular/animations": "^17.0.0", "@angular/animations": "^17.0.0",
"@angular/common": "^17.0.0", "@angular/common": "^17.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "loremind-web", "name": "loremind-web",
"version": "0.11.1-beta", "version": "0.11.2-beta",
"description": "LoreMind Frontend - Angular", "description": "LoreMind Frontend - Angular",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",