{{ importStatus }}
+ } @if (importCounts) {Trouvé jusqu'ici : {{ importCounts.arcs }} arc(s) · {{ importCounts.chapters }} chapitre(s) · {{ importCounts.scenes }} scène(s) · {{ importCounts.npcs }} PNJ diff --git a/web/src/app/campaigns/campaign/campaign-import/campaign-import.component.scss b/web/src/app/campaigns/campaign/campaign-import/campaign-import.component.scss index 82fc027..aef67f9 100644 --- a/web/src/app/campaigns/campaign/campaign-import/campaign-import.component.scss +++ b/web/src/app/campaigns/campaign/campaign-import/campaign-import.component.scss @@ -87,6 +87,16 @@ .import-counts { margin: 0.55rem 0 0; color: #9ca3af; font-size: 0.8rem; } +// Message d'attente live (fournisseur saturé → retry, morceau re-découpé…) : +// ambre pour signaler « ça travaille, mais il se passe quelque chose ». +.import-status { + margin: 0.55rem 0 0; + color: #fbbf24; + font-size: 0.8rem; + font-style: italic; + line-height: 1.4; +} + .import-error, .apply-error { margin: 1rem 0 0; diff --git a/web/src/app/campaigns/campaign/campaign-import/campaign-import.component.ts b/web/src/app/campaigns/campaign/campaign-import/campaign-import.component.ts index 3b26920..b791005 100644 --- a/web/src/app/campaigns/campaign/campaign-import/campaign-import.component.ts +++ b/web/src/app/campaigns/campaign/campaign-import/campaign-import.component.ts @@ -74,6 +74,12 @@ export class CampaignImportComponent implements OnInit { importing = false; importPhase = ''; importProgress: { current: number; total: number } | null = null; + /** + * Dernier message de statut du flux (fournisseur saturé → retry, morceau + * re-découpé/ignoré…). Effacé à chaque progression : il explique l'ATTENTE + * en cours, pas l'historique. + */ + importStatus: string | null = null; importCounts: { arcs: number; chapters: number; scenes: number; npcs: number } | null = null; importError: string | null = null; /** Vrai une fois la proposition reçue (on affiche l'arbre éditable). */ @@ -132,12 +138,15 @@ export class CampaignImportComponent implements OnInit { this.applyError = null; this.importPhase = 'Extraction du texte…'; this.importProgress = null; + this.importStatus = null; this.importCounts = null; this.tree = []; this.service.importStructureStream(this.campaignId, file).subscribe({ next: (ev) => { if (ev.type === 'progress') { + // Un morceau vient d'aboutir : le message d'attente est obsolète. + this.importStatus = null; if (ev.total === 0) { this.importPhase = 'Extraction du texte…'; this.importProgress = null; @@ -149,10 +158,13 @@ export class CampaignImportComponent implements OnInit { scenes: ev.sceneCount, npcs: ev.npcCount ?? 0 }; } + } else if (ev.type === 'status') { + this.importStatus = ev.message; } else if (ev.type === 'done') { this.importing = false; this.importPhase = ''; this.importProgress = null; + this.importStatus = null; if ((ev.arcs ?? []).length === 0 && (ev.npcs ?? []).length === 0) { this.importError = "Aucune structure narrative détectée dans ce PDF."; this.reviewing = false; diff --git a/web/src/app/game-systems/game-system-edit/game-system-edit.component.html b/web/src/app/game-systems/game-system-edit/game-system-edit.component.html index 442e943..b03d50d 100644 --- a/web/src/app/game-systems/game-system-edit/game-system-edit.component.html +++ b/web/src/app/game-systems/game-system-edit/game-system-edit.component.html @@ -60,6 +60,9 @@ } + @if (importStatus) { +
{{ importStatus }}
+ } @if (importFound.length) {
Sections trouvées : {{ importFound.join(' · ') }}
diff --git a/web/src/app/game-systems/game-system-edit/game-system-edit.component.scss b/web/src/app/game-systems/game-system-edit/game-system-edit.component.scss
index 5ec3253..5835413 100644
--- a/web/src/app/game-systems/game-system-edit/game-system-edit.component.scss
+++ b/web/src/app/game-systems/game-system-edit/game-system-edit.component.scss
@@ -163,6 +163,16 @@
line-height: 1.4;
}
+// Message d'attente live (fournisseur saturé → retry, morceau re-découpé…) :
+// ambre pour signaler « ça travaille, mais il se passe quelque chose ».
+.import-status {
+ margin: 0.55rem 0 0;
+ color: #fbbf24;
+ font-size: 0.8rem;
+ font-style: italic;
+ line-height: 1.4;
+}
+
.import-note {
margin: -0.4rem 0 1rem;
padding: 0.55rem 0.8rem;
diff --git a/web/src/app/game-systems/game-system-edit/game-system-edit.component.ts b/web/src/app/game-systems/game-system-edit/game-system-edit.component.ts
index 434e0fc..8afecba 100644
--- a/web/src/app/game-systems/game-system-edit/game-system-edit.component.ts
+++ b/web/src/app/game-systems/game-system-edit/game-system-edit.component.ts
@@ -69,6 +69,12 @@ export class GameSystemEditComponent implements OnInit {
importProgress: { current: number; total: number } | null = null;
/** Titres de sections trouvés au fil de l'eau (affichage live). */
importFound: string[] = [];
+ /**
+ * Dernier message de statut du flux (fournisseur saturé → retry, morceau
+ * re-découpé/ignoré…). Effacé à chaque progression : il explique l'ATTENTE
+ * en cours, pas l'historique.
+ */
+ importStatus: string | null = null;
name = '';
description = '';
@@ -139,10 +145,13 @@ export class GameSystemEditComponent implements OnInit {
this.importPhase = 'Extraction du texte…';
this.importProgress = null;
this.importFound = [];
+ this.importStatus = null;
this.service.importRulesStream(file).subscribe({
next: (ev) => {
if (ev.type === 'progress') {
+ // Un morceau vient d'aboutir : le message d'attente est obsolète.
+ this.importStatus = null;
if (ev.total === 0) {
// Phase d'extraction (total encore inconnu).
this.importPhase = 'Extraction du texte…';
@@ -154,6 +163,8 @@ export class GameSystemEditComponent implements OnInit {
if (!this.importFound.includes(t)) this.importFound.push(t);
}
}
+ } else if (ev.type === 'status') {
+ this.importStatus = ev.message;
} else if (ev.type === 'done') {
this.finishImport(ev.sections, ev.pageCount, ev.ocrPageCount);
}
diff --git a/web/src/app/services/campaign-import.model.ts b/web/src/app/services/campaign-import.model.ts
index 84adb95..7b4202d 100644
--- a/web/src/app/services/campaign-import.model.ts
+++ b/web/src/app/services/campaign-import.model.ts
@@ -63,10 +63,13 @@ export interface CampaignImportApplyResult {
/**
* Évènements du flux SSE d'import streamé.
* - progress : avancement (total=0 ⇒ extraction en cours).
+ * - status : message d'attente lisible (fournisseur saturé → retry, morceau
+ * re-découpé, morceau ignoré…) — feedback live pour l'utilisateur.
* - done : arbre proposé (à réviser).
* - error : message d'erreur côté serveur.
*/
export type CampaignImportStreamEvent =
+ | { type: 'status'; message: string }
| {
type: 'progress';
current: number;
diff --git a/web/src/app/services/campaign-import.service.ts b/web/src/app/services/campaign-import.service.ts
index f5cd6b1..d6f8888 100644
--- a/web/src/app/services/campaign-import.service.ts
+++ b/web/src/app/services/campaign-import.service.ts
@@ -74,6 +74,12 @@ export class CampaignImportService {
try { message = (JSON.parse(currentData) as { message?: string }).message ?? message; } catch { /* défaut */ }
terminated = true;
subscriber.error(new Error(message));
+ } else if (name === 'status') {
+ // Message d'attente lisible (fournisseur saturé, morceau re-découpé…).
+ try {
+ const obj = JSON.parse(currentData) as { message?: string };
+ if (obj.message) subscriber.next({ type: 'status', message: obj.message });
+ } catch { /* bloc malformé ignoré */ }
} else if (name === 'progress' || name === 'done') {
try {
const obj = JSON.parse(currentData);
diff --git a/web/src/app/services/game-system.model.ts b/web/src/app/services/game-system.model.ts
index fbb5810..b1ea099 100644
--- a/web/src/app/services/game-system.model.ts
+++ b/web/src/app/services/game-system.model.ts
@@ -32,11 +32,14 @@ export interface RulesImportResponse {
/**
* Évènements du flux SSE d'import streamé.
* - progress : avancement (total=0 ⇒ phase d'extraction en cours).
+ * - status : message d'attente lisible (fournisseur saturé → retry, morceau
+ * re-découpé, morceau ignoré…) — feedback live pour l'utilisateur.
* - done : résultat final (sections proposées).
* - error : message d'erreur côté serveur.
*/
export type RulesImportStreamEvent =
| { type: 'progress'; current: number; total: number; pageCount: number; ocrPageCount: number; newSectionTitles: string[] }
+ | { type: 'status'; message: string }
| { type: 'done'; sections: Record