Amélioration du feedback pendant les imports sur les PDF
All checks were successful
All checks were successful
passage en 0.12.4-beta
This commit is contained in:
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "loremind-web",
|
||||
"version": "0.12.3-beta",
|
||||
"version": "0.12.4-beta",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "loremind-web",
|
||||
"version": "0.12.3-beta",
|
||||
"version": "0.12.4-beta",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^21.2.16",
|
||||
"@angular/common": "^21.2.16",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "loremind-web",
|
||||
"version": "0.12.3-beta",
|
||||
"version": "0.12.4-beta",
|
||||
"description": "LoreMind Frontend - Angular",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (importStatus) {
|
||||
<p class="import-status" role="status">{{ importStatus }}</p>
|
||||
}
|
||||
@if (importCounts) {
|
||||
<p class="import-counts">
|
||||
Trouvé jusqu'ici : {{ importCounts.arcs }} arc(s) · {{ importCounts.chapters }} chapitre(s) · {{ importCounts.scenes }} scène(s) · {{ importCounts.npcs }} PNJ
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -60,6 +60,9 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (importStatus) {
|
||||
<p class="import-status" role="status">{{ importStatus }}</p>
|
||||
}
|
||||
@if (importFound.length) {
|
||||
<p class="import-found">
|
||||
Sections trouvées : {{ importFound.join(' · ') }}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<string, string>; pageCount: number; ocrPageCount: number }
|
||||
| { type: 'error'; message: string };
|
||||
|
||||
|
||||
@@ -105,6 +105,12 @@ export class GameSystemService {
|
||||
try { message = (JSON.parse(currentData) as { message?: string }).message ?? message; } catch { /* garde le 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);
|
||||
|
||||
Reference in New Issue
Block a user