Import campagne : detection des PNJ et creatures notables (bout en bout)
Brain : la phase MAP recense les PNJ NOMMES et creatures uniques (pas les monstres generiques) avec une courte fiche fidele au livre ; dedoublonnage inter-morceaux (description la plus complete gardee) ; payload done + npc_count. Core : CampaignImportProposal.npcs + NpcProposal ; a l apply, creation comme Npc de la campagne (description -> values[Description], meme convention que les cartes d action des ateliers) avec anti-doublon par nom. Web : section de revue PNJ a cases a cocher (coches par defaut, grises si deja presents), compteurs de progression et recapitulatif mis a jour. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import com.loremind.domain.campaigncontext.CampaignImportProgress;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.ArcProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.ChapterProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.NpcProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.RoomProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.SceneProposal;
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
@@ -37,22 +38,26 @@ public class CampaignImportService {
|
||||
private final ArcService arcService;
|
||||
private final ChapterService chapterService;
|
||||
private final SceneService sceneService;
|
||||
private final NpcService npcService;
|
||||
|
||||
public CampaignImportService(
|
||||
CampaignPdfImporter campaignPdfImporter,
|
||||
CampaignService campaignService,
|
||||
ArcService arcService,
|
||||
ChapterService chapterService,
|
||||
SceneService sceneService) {
|
||||
SceneService sceneService,
|
||||
NpcService npcService) {
|
||||
this.campaignPdfImporter = campaignPdfImporter;
|
||||
this.campaignService = campaignService;
|
||||
this.arcService = arcService;
|
||||
this.chapterService = chapterService;
|
||||
this.sceneService = sceneService;
|
||||
this.npcService = npcService;
|
||||
}
|
||||
|
||||
/** Résumé de ce qui a été créé par {@link #applyStructure}. */
|
||||
public record ApplyResult(int arcsCreated, int chaptersCreated, int scenesCreated) {}
|
||||
public record ApplyResult(
|
||||
int arcsCreated, int chaptersCreated, int scenesCreated, int npcsCreated) {}
|
||||
|
||||
/** Génère la proposition d'arbre (streamée). Ne persiste rien. */
|
||||
public void importStructureStreaming(
|
||||
@@ -130,7 +135,36 @@ public class CampaignImportService {
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ApplyResult(arcsCreated, chaptersCreated, scenesCreated);
|
||||
|
||||
int npcsCreated = createNpcs(campaignId, proposal.npcs());
|
||||
return new ApplyResult(arcsCreated, chaptersCreated, scenesCreated, npcsCreated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée les PNJ proposés (description → values["Description"], même convention
|
||||
* que les cartes d'action des ateliers). Les PNJ portant un nom déjà présent
|
||||
* dans la campagne sont ignorés (ré-import sans doublon).
|
||||
*/
|
||||
private int createNpcs(String campaignId, List<NpcProposal> proposals) {
|
||||
if (proposals == null || proposals.isEmpty()) return 0;
|
||||
java.util.Set<String> existingNames = new java.util.HashSet<>();
|
||||
for (var npc : npcService.getNpcsByCampaignId(campaignId)) {
|
||||
existingNames.add(npc.getName().trim().toLowerCase());
|
||||
}
|
||||
int created = 0;
|
||||
for (NpcProposal p : proposals) {
|
||||
if (isBlank(p.name()) || !existingNames.add(p.name().trim().toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
npcService.createNpc(new NpcService.NpcData(
|
||||
p.name().trim(), null, null,
|
||||
isBlank(p.description())
|
||||
? java.util.Map.of()
|
||||
: java.util.Map.of("Description", p.description().trim()),
|
||||
null, null, campaignId, null, null));
|
||||
created++;
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
/** Compte les nœuds déjà présents (existingId non vide) d'une liste. */
|
||||
|
||||
@@ -14,5 +14,6 @@ public record CampaignImportProgress(
|
||||
int ocrPageCount,
|
||||
int arcCount,
|
||||
int chapterCount,
|
||||
int sceneCount) {
|
||||
int sceneCount,
|
||||
int npcCount) {
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.List;
|
||||
* PROPOSITION non persistée : l'UI laisse l'utilisateur réviser/éditer l'arbre
|
||||
* avant la création effective des arcs/chapitres/scènes. Records purs (domaine).
|
||||
*/
|
||||
public record CampaignImportProposal(List<ArcProposal> arcs) {
|
||||
public record CampaignImportProposal(List<ArcProposal> arcs, List<NpcProposal> npcs) {
|
||||
|
||||
/**
|
||||
* {@code existingId} (nullable) : si présent, le nœud existe DÉJÀ dans la
|
||||
@@ -37,4 +37,14 @@ public record CampaignImportProposal(List<ArcProposal> arcs) {
|
||||
|
||||
public record RoomProposal(String name, String description, String enemies, String loot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* PNJ/creature notable detecte dans le PDF (PNJ nommes, boss). Propose a la
|
||||
* revue (coche par defaut) ; cree comme Npc de la campagne a l''apply avec
|
||||
* sa description dans values["Description"] (meme convention que les cartes
|
||||
* d''action des ateliers).
|
||||
*/
|
||||
public record NpcProposal(String name, String description) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.loremind.domain.campaigncontext.CampaignImportProgress;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.ArcProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.ChapterProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.NpcProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.RoomProposal;
|
||||
import com.loremind.domain.campaigncontext.CampaignImportProposal.SceneProposal;
|
||||
import com.loremind.domain.campaigncontext.ports.CampaignImportException;
|
||||
@@ -119,7 +120,7 @@ public class BrainCampaignImportClient implements CampaignPdfImporter {
|
||||
return;
|
||||
}
|
||||
if ("extracting".equals(event)) {
|
||||
onProgress.accept(new CampaignImportProgress(0, 0, 0, 0, 0, 0, 0));
|
||||
onProgress.accept(new CampaignImportProgress(0, 0, 0, 0, 0, 0, 0, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,7 +131,7 @@ public class BrainCampaignImportClient implements CampaignPdfImporter {
|
||||
pageCount[0] = node.path("page_count").asInt();
|
||||
ocrPageCount[0] = node.path("ocr_page_count").asInt();
|
||||
onProgress.accept(new CampaignImportProgress(
|
||||
0, node.path("total").asInt(), pageCount[0], ocrPageCount[0], 0, 0, 0));
|
||||
0, node.path("total").asInt(), pageCount[0], ocrPageCount[0], 0, 0, 0, 0));
|
||||
} else if ("progress".equals(event)) {
|
||||
onProgress.accept(new CampaignImportProgress(
|
||||
node.path("current").asInt(),
|
||||
@@ -139,10 +140,12 @@ public class BrainCampaignImportClient implements CampaignPdfImporter {
|
||||
ocrPageCount[0],
|
||||
node.path("arc_count").asInt(),
|
||||
node.path("chapter_count").asInt(),
|
||||
node.path("scene_count").asInt()));
|
||||
node.path("scene_count").asInt(),
|
||||
node.path("npc_count").asInt()));
|
||||
} else if ("done".equals(event)) {
|
||||
terminated[0] = true;
|
||||
onDone.accept(new CampaignImportProposal(toArcs(node.path("arcs"))));
|
||||
onDone.accept(new CampaignImportProposal(
|
||||
toArcs(node.path("arcs")), toNpcs(node.path("npcs"))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +205,16 @@ public class BrainCampaignImportClient implements CampaignPdfImporter {
|
||||
return rooms;
|
||||
}
|
||||
|
||||
private List<NpcProposal> toNpcs(JsonNode npcsNode) {
|
||||
List<NpcProposal> npcs = new ArrayList<>();
|
||||
if (npcsNode != null && npcsNode.isArray()) {
|
||||
for (JsonNode n : npcsNode) {
|
||||
npcs.add(new NpcProposal(text(n, "name"), text(n, "description")));
|
||||
}
|
||||
}
|
||||
return npcs;
|
||||
}
|
||||
|
||||
// --- Helpers -------------------------------------------------------------
|
||||
|
||||
private ByteArrayResource filePart(byte[] pdfBytes, String filename) {
|
||||
|
||||
Reference in New Issue
Block a user