Changement sur le Readme
Ajout d'une partie spécifique pour des PNJ dans la partie campagne
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
import com.loremind.domain.campaigncontext.ports.NpcRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service d'application pour les fiches de PNJ (campagne).
|
||||
*/
|
||||
@Service
|
||||
public class NpcService {
|
||||
|
||||
private final NpcRepository npcRepository;
|
||||
|
||||
public NpcService(NpcRepository npcRepository) {
|
||||
this.npcRepository = npcRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter Object pour la création / mise à jour d'un Npc.
|
||||
* `order` est fourni par le controller ; si absent, le service le calcule.
|
||||
*/
|
||||
public record NpcData(String name, String markdownContent, String campaignId, Integer order) {}
|
||||
|
||||
public Npc createNpc(NpcData data) {
|
||||
int order = data.order() != null
|
||||
? data.order()
|
||||
: nextOrderFor(data.campaignId());
|
||||
Npc npc = Npc.builder()
|
||||
.name(data.name())
|
||||
.markdownContent(data.markdownContent())
|
||||
.campaignId(data.campaignId())
|
||||
.order(order)
|
||||
.build();
|
||||
return npcRepository.save(npc);
|
||||
}
|
||||
|
||||
public Optional<Npc> getNpcById(String id) {
|
||||
return npcRepository.findById(id);
|
||||
}
|
||||
|
||||
public List<Npc> getNpcsByCampaignId(String campaignId) {
|
||||
return npcRepository.findByCampaignId(campaignId);
|
||||
}
|
||||
|
||||
public Npc updateNpc(String id, NpcData data) {
|
||||
Npc existing = npcRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Npc non trouvé avec l'ID: " + id));
|
||||
existing.setName(data.name());
|
||||
existing.setMarkdownContent(data.markdownContent());
|
||||
if (data.order() != null) {
|
||||
existing.setOrder(data.order());
|
||||
}
|
||||
return npcRepository.save(existing);
|
||||
}
|
||||
|
||||
public void deleteNpc(String id) {
|
||||
npcRepository.deleteById(id);
|
||||
}
|
||||
|
||||
/** Renvoie la prochaine position libre — append en fin de liste. */
|
||||
private int nextOrderFor(String campaignId) {
|
||||
return npcRepository.findByCampaignId(campaignId).stream()
|
||||
.mapToInt(Npc::getOrder)
|
||||
.max()
|
||||
.orElse(-1) + 1;
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,20 @@ import com.loremind.domain.campaigncontext.Arc;
|
||||
import com.loremind.domain.campaigncontext.Campaign;
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import com.loremind.domain.campaigncontext.Character;
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.domain.campaigncontext.ports.ArcRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.CampaignRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.ChapterRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.CharacterRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.NpcRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.SceneRepository;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.ArcSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.BranchHint;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.ChapterSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.CharacterSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.NpcSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.SceneSummary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -42,21 +45,24 @@ public class CampaignStructuralContextBuilder {
|
||||
private final ChapterRepository chapterRepository;
|
||||
private final SceneRepository sceneRepository;
|
||||
private final CharacterRepository characterRepository;
|
||||
private final NpcRepository npcRepository;
|
||||
|
||||
public CampaignStructuralContextBuilder(
|
||||
CampaignRepository campaignRepository,
|
||||
ArcRepository arcRepository,
|
||||
ChapterRepository chapterRepository,
|
||||
SceneRepository sceneRepository,
|
||||
CharacterRepository characterRepository) {
|
||||
CharacterRepository characterRepository,
|
||||
NpcRepository npcRepository) {
|
||||
this.campaignRepository = campaignRepository;
|
||||
this.arcRepository = arcRepository;
|
||||
this.chapterRepository = chapterRepository;
|
||||
this.sceneRepository = sceneRepository;
|
||||
this.characterRepository = characterRepository;
|
||||
this.npcRepository = npcRepository;
|
||||
}
|
||||
|
||||
/** Longueur max du snippet de PJ injecté dans le contexte (coût tokens maîtrisé). */
|
||||
/** Longueur max du snippet de PJ/PNJ injecté dans le contexte (coût tokens maîtrisé). */
|
||||
private static final int CHARACTER_SNIPPET_MAX_LEN = 160;
|
||||
|
||||
/**
|
||||
@@ -79,11 +85,17 @@ public class CampaignStructuralContextBuilder {
|
||||
.map(this::toCharacterSummary)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<NpcSummary> npcs = npcRepository.findByCampaignId(campaignId).stream()
|
||||
.sorted(Comparator.comparingInt(Npc::getOrder))
|
||||
.map(this::toNpcSummary)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new CampaignStructuralContext(
|
||||
campaign.getName(),
|
||||
campaign.getDescription(),
|
||||
arcs,
|
||||
characters);
|
||||
characters,
|
||||
npcs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,6 +107,11 @@ public class CampaignStructuralContextBuilder {
|
||||
return new CharacterSummary(c.getName(), extractSnippet(c.getMarkdownContent()));
|
||||
}
|
||||
|
||||
/** Symétrique à {@link #toCharacterSummary} pour les PNJ. */
|
||||
private NpcSummary toNpcSummary(Npc n) {
|
||||
return new NpcSummary(n.getName(), extractSnippet(n.getMarkdownContent()));
|
||||
}
|
||||
|
||||
private static String extractSnippet(String markdown) {
|
||||
if (markdown == null || markdown.isBlank()) return "";
|
||||
String firstLine = markdown.lines()
|
||||
|
||||
@@ -3,10 +3,12 @@ package com.loremind.application.generationcontext;
|
||||
import com.loremind.domain.campaigncontext.Arc;
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import com.loremind.domain.campaigncontext.Character;
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.domain.campaigncontext.ports.ArcRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.ChapterRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.CharacterRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.NpcRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.SceneRepository;
|
||||
import com.loremind.domain.generationcontext.NarrativeEntityContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -29,22 +31,25 @@ public class NarrativeEntityContextBuilder {
|
||||
private final ChapterRepository chapterRepository;
|
||||
private final SceneRepository sceneRepository;
|
||||
private final CharacterRepository characterRepository;
|
||||
private final NpcRepository npcRepository;
|
||||
|
||||
public NarrativeEntityContextBuilder(
|
||||
ArcRepository arcRepository,
|
||||
ChapterRepository chapterRepository,
|
||||
SceneRepository sceneRepository,
|
||||
CharacterRepository characterRepository) {
|
||||
CharacterRepository characterRepository,
|
||||
NpcRepository npcRepository) {
|
||||
this.arcRepository = arcRepository;
|
||||
this.chapterRepository = chapterRepository;
|
||||
this.sceneRepository = sceneRepository;
|
||||
this.characterRepository = characterRepository;
|
||||
this.npcRepository = npcRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge l'entité narrative ciblée et la projette vers un VO du GenerationContext.
|
||||
*
|
||||
* @param entityType "arc", "chapter", "scene" ou "character" (insensible à la casse)
|
||||
* @param entityType "arc", "chapter", "scene", "character" ou "npc" (insensible à la casse)
|
||||
* @param entityId l'ID de l'entité
|
||||
* @throws IllegalArgumentException si le type est inconnu ou l'entité introuvable
|
||||
*/
|
||||
@@ -55,6 +60,7 @@ public class NarrativeEntityContextBuilder {
|
||||
case "chapter" -> fromChapter(loadChapter(entityId));
|
||||
case "scene" -> fromScene(loadScene(entityId));
|
||||
case "character" -> fromCharacter(loadCharacter(entityId));
|
||||
case "npc" -> fromNpc(loadNpc(entityId));
|
||||
default -> throw new IllegalArgumentException("Type d'entité narrative inconnu: " + entityType);
|
||||
};
|
||||
}
|
||||
@@ -81,6 +87,11 @@ public class NarrativeEntityContextBuilder {
|
||||
.orElseThrow(() -> new IllegalArgumentException("Personnage non trouvé: " + id));
|
||||
}
|
||||
|
||||
private Npc loadNpc(String id) {
|
||||
return npcRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("PNJ non trouvé: " + id));
|
||||
}
|
||||
|
||||
// --- Mapping entité → VO ------------------------------------------------
|
||||
|
||||
private NarrativeEntityContext fromArc(Arc a) {
|
||||
@@ -123,6 +134,12 @@ public class NarrativeEntityContextBuilder {
|
||||
return new NarrativeEntityContext("character", c.getName(), fields);
|
||||
}
|
||||
|
||||
private NarrativeEntityContext fromNpc(Npc n) {
|
||||
Map<String, String> fields = new LinkedHashMap<>();
|
||||
putField(fields, "fiche complète (markdown)", n.getMarkdownContent());
|
||||
return new NarrativeEntityContext("npc", n.getName(), fields);
|
||||
}
|
||||
|
||||
/** Null/blank devient chaîne vide — uniforme côté prompt, pas de NPE côté LLM. */
|
||||
private static void putField(Map<String, String> target, String key, String value) {
|
||||
target.put(key, value == null ? "" : value);
|
||||
|
||||
@@ -12,9 +12,10 @@ import java.time.LocalDateTime;
|
||||
* backstory, équipement). Évolution prévue vers un système templaté par
|
||||
* GameSystem (la fiche Nimble n'a pas les mêmes champs qu'une fiche D&D).
|
||||
* <p>
|
||||
* Scope strict PJ : les PNJ restent dans le Lore (pages templatées) ou
|
||||
* dans les scènes elles-mêmes. Si le besoin de PNJ spécifiques à une
|
||||
* campagne remonte, on étendra l'entité (ex: type enum PJ/PNJ).
|
||||
* Scope strict PJ : les PNJ sont gérés par l'entité {@link Npc} dédiée
|
||||
* (entité distincte plutôt qu'enum PJ/PNJ — invariants métier divergents).
|
||||
* Évolution prévue : système de templating partagé PJ/PNJ piloté par
|
||||
* GameSystem pour adapter les blocs aux différents systèmes de JDR.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.loremind.domain.campaigncontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Fiche de personnage non-joueur (PNJ) d'une campagne.
|
||||
* <p>
|
||||
* MVP : entité dédiée, distincte de {@link Character} (PJ). Choix DDD assumé —
|
||||
* un PNJ a vocation à porter à terme des invariants métier propres (faction,
|
||||
* statut vivant/mort/disparu, visibilité côté joueurs, relations inter-PNJ)
|
||||
* qui n'ont aucun sens sur un PJ. Mutualiser via un enum aurait pollué l'entité
|
||||
* PJ avec des champs inutiles ({@code if (type == NPC)} partout = anti-pattern).
|
||||
* <p>
|
||||
* Contenu markdown libre comme les PJ. Évolution prévue : templating partagé
|
||||
* PJ/PNJ piloté par GameSystem.
|
||||
* <p>
|
||||
* Scope campagne : les PNJ "univers" (worldboss, figures du Lore) restent
|
||||
* gérés via le système Page/Template du LoreContext.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class Npc {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
|
||||
/** Contenu libre markdown — description, motivation, stats, notes MJ. Nullable à la création. */
|
||||
private String markdownContent;
|
||||
|
||||
/** Référence vers la Campaign parente (cross-aggregate via ID, jamais d'objet). */
|
||||
private String campaignId;
|
||||
|
||||
/** Ordre d'affichage dans la liste des PNJ de la campagne. */
|
||||
private int order;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.loremind.domain.campaigncontext.ports;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des fiches de PNJ (campagne).
|
||||
*/
|
||||
public interface NpcRepository {
|
||||
|
||||
Npc save(Npc npc);
|
||||
|
||||
Optional<Npc> findById(String id);
|
||||
|
||||
List<Npc> findByCampaignId(String campaignId);
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
}
|
||||
@@ -22,12 +22,14 @@ import java.util.List;
|
||||
* Record Java : pur domaine, aucune dépendance technique.
|
||||
*
|
||||
* @param characters Personnages joueurs (PJ) de la campagne. Vide si aucun.
|
||||
* @param npcs Personnages non-joueurs (PNJ) de la campagne. Vide si aucun.
|
||||
*/
|
||||
public record CampaignStructuralContext(
|
||||
String campaignName,
|
||||
String campaignDescription,
|
||||
List<ArcSummary> arcs,
|
||||
List<CharacterSummary> characters) {
|
||||
List<CharacterSummary> characters,
|
||||
List<NpcSummary> npcs) {
|
||||
|
||||
/**
|
||||
* Résumé d'un PJ : nom + snippet court du markdown.
|
||||
@@ -39,6 +41,14 @@ public record CampaignStructuralContext(
|
||||
public record CharacterSummary(String name, String snippet) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Résumé d'un PNJ : symétrique à {@link CharacterSummary}.
|
||||
* Snippet court extrait du markdown — la fiche complète est réservée
|
||||
* à un usage focus (à venir, entity_type="npc").
|
||||
*/
|
||||
public record NpcSummary(String name, String snippet) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Résumé d'un arc : nom + description courte + ses chapitres.
|
||||
*
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.loremind.domain.generationcontext.CampaignStructuralContext.ArcSummar
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.BranchHint;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.ChapterSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.CharacterSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.NpcSummary;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext.SceneSummary;
|
||||
import com.loremind.domain.generationcontext.ChatMessage;
|
||||
import com.loremind.domain.generationcontext.ChatRequest;
|
||||
@@ -132,6 +133,12 @@ public class BrainChatPayloadBuilder {
|
||||
.map(this::characterSummaryToMap)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
// Liste des PNJ : symétrique aux PJ, omise si vide pour alléger le payload.
|
||||
if (ctx.npcs() != null && !ctx.npcs().isEmpty()) {
|
||||
map.put("npcs", ctx.npcs().stream()
|
||||
.map(this::npcSummaryToMap)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -144,6 +151,15 @@ public class BrainChatPayloadBuilder {
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> npcSummaryToMap(NpcSummary n) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("name", n.name());
|
||||
if (n.snippet() != null && !n.snippet().isBlank()) {
|
||||
map.put("snippet", n.snippet());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper générique pour sérialiser les entités structurelles (Arc/Chapter/Scene)
|
||||
* avec name, description et illustration_count conditionnel.
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.loremind.infrastructure.persistence.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Entité JPA pour les fiches de PNJ d'une campagne.
|
||||
* Pas de FK physique vers campaigns (weak reference cross-agrégat intra-contexte).
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "npcs")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class NpcJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "markdown_content", columnDefinition = "TEXT")
|
||||
private String markdownContent;
|
||||
|
||||
@Column(name = "campaign_id", nullable = false)
|
||||
private Long campaignId;
|
||||
|
||||
@Column(name = "\"order\"", nullable = false)
|
||||
private int order;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.NpcJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface NpcJpaRepository extends JpaRepository<NpcJpaEntity, Long> {
|
||||
|
||||
List<NpcJpaEntity> findByCampaignIdOrderByOrderAsc(Long campaignId);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
import com.loremind.domain.campaigncontext.ports.NpcRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.NpcJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.NpcJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Repository
|
||||
public class PostgresNpcRepository implements NpcRepository {
|
||||
|
||||
private final NpcJpaRepository jpaRepository;
|
||||
|
||||
public PostgresNpcRepository(NpcJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Npc save(Npc npc) {
|
||||
NpcJpaEntity entity = toJpaEntity(npc);
|
||||
NpcJpaEntity saved = jpaRepository.save(entity);
|
||||
return toDomainEntity(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Npc> findById(String id) {
|
||||
return jpaRepository.findById(Long.parseLong(id)).map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Npc> findByCampaignId(String campaignId) {
|
||||
return jpaRepository.findByCampaignIdOrderByOrderAsc(Long.parseLong(campaignId)).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
jpaRepository.deleteById(Long.parseLong(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(String id) {
|
||||
return jpaRepository.existsById(Long.parseLong(id));
|
||||
}
|
||||
|
||||
private Npc toDomainEntity(NpcJpaEntity e) {
|
||||
return Npc.builder()
|
||||
.id(e.getId().toString())
|
||||
.name(e.getName())
|
||||
.markdownContent(e.getMarkdownContent())
|
||||
.campaignId(e.getCampaignId().toString())
|
||||
.order(e.getOrder())
|
||||
.createdAt(e.getCreatedAt())
|
||||
.updatedAt(e.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
private NpcJpaEntity toJpaEntity(Npc n) {
|
||||
Long id = n.getId() != null ? Long.parseLong(n.getId()) : null;
|
||||
return NpcJpaEntity.builder()
|
||||
.id(id)
|
||||
.name(n.getName())
|
||||
.markdownContent(n.getMarkdownContent())
|
||||
.campaignId(Long.parseLong(n.getCampaignId()))
|
||||
.order(n.getOrder())
|
||||
.createdAt(n.getCreatedAt())
|
||||
.updatedAt(n.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.campaigncontext.NpcService;
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.NpcDTO;
|
||||
import com.loremind.infrastructure.web.mapper.NpcMapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/npcs")
|
||||
public class NpcController {
|
||||
|
||||
private final NpcService npcService;
|
||||
private final NpcMapper npcMapper;
|
||||
|
||||
public NpcController(NpcService npcService, NpcMapper npcMapper) {
|
||||
this.npcService = npcService;
|
||||
this.npcMapper = npcMapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<NpcDTO> createNpc(@RequestBody NpcDTO dto) {
|
||||
Npc created = npcService.createNpc(
|
||||
new NpcService.NpcData(dto.getName(), dto.getMarkdownContent(), dto.getCampaignId(), null)
|
||||
);
|
||||
return ResponseEntity.ok(npcMapper.toDTO(created));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<NpcDTO> getNpcById(@PathVariable String id) {
|
||||
return npcService.getNpcById(id)
|
||||
.map(n -> ResponseEntity.ok(npcMapper.toDTO(n)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping("/campaign/{campaignId}")
|
||||
public ResponseEntity<List<NpcDTO>> getNpcsByCampaign(@PathVariable String campaignId) {
|
||||
List<NpcDTO> dtos = npcService.getNpcsByCampaignId(campaignId).stream()
|
||||
.map(npcMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<NpcDTO> updateNpc(@PathVariable String id, @RequestBody NpcDTO dto) {
|
||||
Npc updated = npcService.updateNpc(
|
||||
id,
|
||||
new NpcService.NpcData(dto.getName(), dto.getMarkdownContent(), dto.getCampaignId(), dto.getOrder())
|
||||
);
|
||||
return ResponseEntity.ok(npcMapper.toDTO(updated));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteNpc(@PathVariable String id) {
|
||||
npcService.deleteNpc(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.loremind.infrastructure.web.dto.campaigncontext;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* DTO pour les fiches de PNJ d'une campagne.
|
||||
*/
|
||||
@Data
|
||||
public class NpcDTO {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String markdownContent;
|
||||
private String campaignId;
|
||||
private int order;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.NpcDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class NpcMapper {
|
||||
|
||||
public NpcDTO toDTO(Npc n) {
|
||||
if (n == null) return null;
|
||||
NpcDTO dto = new NpcDTO();
|
||||
dto.setId(n.getId());
|
||||
dto.setName(n.getName());
|
||||
dto.setMarkdownContent(n.getMarkdownContent());
|
||||
dto.setCampaignId(n.getCampaignId());
|
||||
dto.setOrder(n.getOrder());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public Npc toDomain(NpcDTO dto) {
|
||||
if (dto == null) return null;
|
||||
return Npc.builder()
|
||||
.id(dto.getId())
|
||||
.name(dto.getName())
|
||||
.markdownContent(dto.getMarkdownContent())
|
||||
.campaignId(dto.getCampaignId())
|
||||
.order(dto.getOrder())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
import com.loremind.domain.campaigncontext.ports.NpcRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Test unitaire pour NpcService.
|
||||
* Couvre la création (avec auto-calcul de l'order), la lecture, la mise à jour
|
||||
* (incl. cas non trouvé), la suppression, et le calcul d'order.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class NpcServiceTest {
|
||||
|
||||
@Mock
|
||||
private NpcRepository npcRepository;
|
||||
|
||||
@InjectMocks
|
||||
private NpcService npcService;
|
||||
|
||||
private Npc testNpc;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testNpc = Npc.builder()
|
||||
.id("npc-1")
|
||||
.name("Borin le forgeron")
|
||||
.markdownContent("# Borin\nForgeron nain")
|
||||
.campaignId("camp-1")
|
||||
.order(1)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateNpc_WithExplicitOrder() {
|
||||
when(npcRepository.save(any(Npc.class))).thenReturn(testNpc);
|
||||
|
||||
Npc result = npcService.createNpc(
|
||||
new NpcService.NpcData("Borin le forgeron", "# Borin", "camp-1", 5));
|
||||
|
||||
assertNotNull(result);
|
||||
ArgumentCaptor<Npc> captor = ArgumentCaptor.forClass(Npc.class);
|
||||
verify(npcRepository).save(captor.capture());
|
||||
assertEquals(5, captor.getValue().getOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateNpc_AutoComputesNextOrder_WhenNullProvided() {
|
||||
// Existant : 2 PNJ avec orders 0 et 3 → next = 4
|
||||
Npc a = Npc.builder().id("a").campaignId("camp-1").order(0).build();
|
||||
Npc b = Npc.builder().id("b").campaignId("camp-1").order(3).build();
|
||||
when(npcRepository.findByCampaignId("camp-1")).thenReturn(List.of(a, b));
|
||||
when(npcRepository.save(any(Npc.class))).thenReturn(testNpc);
|
||||
|
||||
npcService.createNpc(new NpcService.NpcData("Nouveau", null, "camp-1", null));
|
||||
|
||||
ArgumentCaptor<Npc> captor = ArgumentCaptor.forClass(Npc.class);
|
||||
verify(npcRepository).save(captor.capture());
|
||||
assertEquals(4, captor.getValue().getOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateNpc_FirstNpcGetsOrderZero() {
|
||||
when(npcRepository.findByCampaignId("camp-1")).thenReturn(List.of());
|
||||
when(npcRepository.save(any(Npc.class))).thenReturn(testNpc);
|
||||
|
||||
npcService.createNpc(new NpcService.NpcData("Premier", null, "camp-1", null));
|
||||
|
||||
ArgumentCaptor<Npc> captor = ArgumentCaptor.forClass(Npc.class);
|
||||
verify(npcRepository).save(captor.capture());
|
||||
assertEquals(0, captor.getValue().getOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetNpcById_Found() {
|
||||
when(npcRepository.findById("npc-1")).thenReturn(Optional.of(testNpc));
|
||||
|
||||
Optional<Npc> result = npcService.getNpcById("npc-1");
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("Borin le forgeron", result.get().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetNpcById_NotFound() {
|
||||
when(npcRepository.findById("missing")).thenReturn(Optional.empty());
|
||||
|
||||
Optional<Npc> result = npcService.getNpcById("missing");
|
||||
|
||||
assertFalse(result.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetNpcsByCampaignId_DelegatesToRepository() {
|
||||
Npc a = Npc.builder().id("a").campaignId("camp-1").order(1).build();
|
||||
Npc b = Npc.builder().id("b").campaignId("camp-1").order(2).build();
|
||||
when(npcRepository.findByCampaignId("camp-1")).thenReturn(List.of(a, b));
|
||||
|
||||
List<Npc> result = npcService.getNpcsByCampaignId("camp-1");
|
||||
|
||||
assertEquals(2, result.size());
|
||||
verify(npcRepository).findByCampaignId("camp-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateNpc_Success() {
|
||||
when(npcRepository.findById("npc-1")).thenReturn(Optional.of(testNpc));
|
||||
when(npcRepository.save(any(Npc.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
Npc result = npcService.updateNpc("npc-1",
|
||||
new NpcService.NpcData("Borin renommé", "# v2", "camp-1", 7));
|
||||
|
||||
assertEquals("Borin renommé", result.getName());
|
||||
assertEquals("# v2", result.getMarkdownContent());
|
||||
assertEquals(7, result.getOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateNpc_OrderNullPreservesExistingOrder() {
|
||||
when(npcRepository.findById("npc-1")).thenReturn(Optional.of(testNpc));
|
||||
when(npcRepository.save(any(Npc.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
Npc result = npcService.updateNpc("npc-1",
|
||||
new NpcService.NpcData("Borin", "# txt", "camp-1", null));
|
||||
|
||||
// testNpc avait order=1 → préservé
|
||||
assertEquals(1, result.getOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateNpc_NotFoundThrows() {
|
||||
when(npcRepository.findById("missing")).thenReturn(Optional.empty());
|
||||
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
() -> npcService.updateNpc("missing",
|
||||
new NpcService.NpcData("x", null, "camp-1", null)));
|
||||
assertTrue(ex.getMessage().contains("missing"));
|
||||
verify(npcRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteNpc_DelegatesToRepository() {
|
||||
npcService.deleteNpc("npc-1");
|
||||
verify(npcRepository).deleteById("npc-1");
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,15 @@ package com.loremind.application.generationcontext;
|
||||
import com.loremind.domain.campaigncontext.Arc;
|
||||
import com.loremind.domain.campaigncontext.Campaign;
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import com.loremind.domain.campaigncontext.Character;
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.domain.campaigncontext.SceneBranch;
|
||||
import com.loremind.domain.campaigncontext.ports.ArcRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.CampaignRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.ChapterRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.CharacterRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.NpcRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.SceneRepository;
|
||||
import com.loremind.domain.generationcontext.CampaignStructuralContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -43,6 +46,8 @@ public class CampaignStructuralContextBuilderTest {
|
||||
private SceneRepository sceneRepository;
|
||||
@Mock
|
||||
private CharacterRepository characterRepository;
|
||||
@Mock
|
||||
private NpcRepository npcRepository;
|
||||
|
||||
@InjectMocks
|
||||
private CampaignStructuralContextBuilder builder;
|
||||
@@ -144,6 +149,66 @@ public class CampaignStructuralContextBuilderTest {
|
||||
assertEquals("(scène inconnue)", scene1Summary.branches().get(1).targetSceneName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_ProjectsCharactersAndNpcsWithSnippets() {
|
||||
Character pj1 = Character.builder().id("c-1").campaignId("camp-1").order(1)
|
||||
.name("Aragorn")
|
||||
.markdownContent("# Aragorn\n\nRôdeur du Nord, héritier d'Isildur.")
|
||||
.build();
|
||||
Character pj2 = Character.builder().id("c-2").campaignId("camp-1").order(2)
|
||||
.name("Legolas")
|
||||
.markdownContent(null) // pas de snippet → string vide
|
||||
.build();
|
||||
Npc npc1 = Npc.builder().id("n-1").campaignId("camp-1").order(2)
|
||||
.name("Borin le forgeron")
|
||||
.markdownContent("# Borin\n\nNain barbu au regard perçant, ancien clan Feuillefer.")
|
||||
.build();
|
||||
Npc npc2 = Npc.builder().id("n-2").campaignId("camp-1").order(1)
|
||||
.name("Dame Elara")
|
||||
.markdownContent("")
|
||||
.build();
|
||||
|
||||
when(campaignRepository.findById("camp-1")).thenReturn(Optional.of(campaign));
|
||||
when(arcRepository.findByCampaignId("camp-1")).thenReturn(List.of());
|
||||
when(characterRepository.findByCampaignId("camp-1")).thenReturn(List.of(pj2, pj1));
|
||||
when(npcRepository.findByCampaignId("camp-1")).thenReturn(List.of(npc1, npc2));
|
||||
|
||||
CampaignStructuralContext ctx = builder.build("camp-1");
|
||||
|
||||
// PJ triés par order croissant
|
||||
assertEquals(2, ctx.characters().size());
|
||||
assertEquals("Aragorn", ctx.characters().get(0).name());
|
||||
assertEquals("Rôdeur du Nord, héritier d'Isildur.", ctx.characters().get(0).snippet());
|
||||
assertEquals("Legolas", ctx.characters().get(1).name());
|
||||
assertEquals("", ctx.characters().get(1).snippet());
|
||||
|
||||
// PNJ triés par order croissant : Elara (1) avant Borin (2)
|
||||
assertEquals(2, ctx.npcs().size());
|
||||
assertEquals("Dame Elara", ctx.npcs().get(0).name());
|
||||
assertEquals("", ctx.npcs().get(0).snippet());
|
||||
assertEquals("Borin le forgeron", ctx.npcs().get(1).name());
|
||||
assertEquals("Nain barbu au regard perçant, ancien clan Feuillefer.",
|
||||
ctx.npcs().get(1).snippet());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_TruncatesLongSnippet() {
|
||||
// Snippet > 160 chars : doit être tronqué à 159 + "…"
|
||||
String longLine = "x".repeat(200);
|
||||
Npc longNpc = Npc.builder().id("n-1").campaignId("camp-1").order(1)
|
||||
.name("Verbeux").markdownContent(longLine).build();
|
||||
|
||||
when(campaignRepository.findById("camp-1")).thenReturn(Optional.of(campaign));
|
||||
when(arcRepository.findByCampaignId("camp-1")).thenReturn(List.of());
|
||||
when(npcRepository.findByCampaignId("camp-1")).thenReturn(List.of(longNpc));
|
||||
|
||||
CampaignStructuralContext ctx = builder.build("camp-1");
|
||||
|
||||
String snippet = ctx.npcs().get(0).snippet();
|
||||
assertEquals(160, snippet.length());
|
||||
assertTrue(snippet.endsWith("…"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_CountsIllustrationsNullSafe() {
|
||||
Arc arc = Arc.builder().id("arc-1").name("Arc").description("").order(1)
|
||||
|
||||
@@ -2,9 +2,13 @@ package com.loremind.application.generationcontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Arc;
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import com.loremind.domain.campaigncontext.Character;
|
||||
import com.loremind.domain.campaigncontext.Npc;
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.domain.campaigncontext.ports.ArcRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.ChapterRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.CharacterRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.NpcRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.SceneRepository;
|
||||
import com.loremind.domain.generationcontext.NarrativeEntityContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -30,6 +34,8 @@ public class NarrativeEntityContextBuilderTest {
|
||||
@Mock private ArcRepository arcRepository;
|
||||
@Mock private ChapterRepository chapterRepository;
|
||||
@Mock private SceneRepository sceneRepository;
|
||||
@Mock private CharacterRepository characterRepository;
|
||||
@Mock private NpcRepository npcRepository;
|
||||
|
||||
@InjectMocks private NarrativeEntityContextBuilder builder;
|
||||
|
||||
@@ -107,11 +113,59 @@ public class NarrativeEntityContextBuilderTest {
|
||||
assertEquals("arc", ctx.entityType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_Character_MarkdownProjected() {
|
||||
Character c = Character.builder()
|
||||
.id("c-1").name("Aragorn").markdownContent("# Aragorn\nRôdeur")
|
||||
.build();
|
||||
when(characterRepository.findById("c-1")).thenReturn(Optional.of(c));
|
||||
|
||||
NarrativeEntityContext ctx = builder.build("character", "c-1");
|
||||
|
||||
assertEquals("character", ctx.entityType());
|
||||
assertEquals("Aragorn", ctx.title());
|
||||
assertEquals("# Aragorn\nRôdeur", ctx.fields().get("fiche complète (markdown)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_Npc_MarkdownProjected() {
|
||||
Npc n = Npc.builder()
|
||||
.id("n-1").name("Borin le forgeron")
|
||||
.markdownContent("# Borin\n**Faction :** Clan Feuillefer")
|
||||
.build();
|
||||
when(npcRepository.findById("n-1")).thenReturn(Optional.of(n));
|
||||
|
||||
NarrativeEntityContext ctx = builder.build("npc", "n-1");
|
||||
|
||||
assertEquals("npc", ctx.entityType());
|
||||
assertEquals("Borin le forgeron", ctx.title());
|
||||
assertEquals("# Borin\n**Faction :** Clan Feuillefer",
|
||||
ctx.fields().get("fiche complète (markdown)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_Npc_NormalizesCase() {
|
||||
Npc n = Npc.builder().id("n-1").name("Elara").markdownContent("desc").build();
|
||||
when(npcRepository.findById("n-1")).thenReturn(Optional.of(n));
|
||||
|
||||
NarrativeEntityContext ctx = builder.build(" NPC ", "n-1");
|
||||
assertEquals("npc", ctx.entityType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_NpcNotFoundThrows() {
|
||||
when(npcRepository.findById("missing")).thenReturn(Optional.empty());
|
||||
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
() -> builder.build("npc", "missing"));
|
||||
assertTrue(ex.getMessage().contains("missing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuild_UnknownTypeThrows() {
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
() -> builder.build("npc", "id"));
|
||||
assertTrue(ex.getMessage().contains("npc"));
|
||||
() -> builder.build("alien", "id"));
|
||||
assertTrue(ex.getMessage().contains("alien"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -55,7 +55,7 @@ public class StreamChatForCampaignUseCaseTest {
|
||||
@SuppressWarnings("unchecked")
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
campaignCtx = new CampaignStructuralContext("X", "d", List.of(), List.of());
|
||||
campaignCtx = new CampaignStructuralContext("X", "d", List.of(), List.of(), List.of());
|
||||
messages = List.of();
|
||||
onUsage = mock(Consumer.class);
|
||||
onToken = mock(Consumer.class);
|
||||
|
||||
@@ -43,6 +43,7 @@ class CampaignStructuralContextTest {
|
||||
"Les Ombres",
|
||||
"Une campagne dark fantasy",
|
||||
List.of(arc),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
assertEquals("Les Ombres", ctx.campaignName());
|
||||
|
||||
@@ -56,7 +56,7 @@ class ChatRequestTest {
|
||||
ChatRequest request = ChatRequest.builder()
|
||||
.messages(sampleMessages)
|
||||
.campaignContext(new CampaignStructuralContext(
|
||||
"Les Ombres", "...", List.of(), List.of()))
|
||||
"Les Ombres", "...", List.of(), List.of(), List.of()))
|
||||
.narrativeEntity(new NarrativeEntityContext(
|
||||
"scene", "L'auberge", Map.of("location", "Taverne")))
|
||||
.build();
|
||||
|
||||
@@ -167,7 +167,7 @@ class BrainChatPayloadBuilderTest {
|
||||
ChapterSummary chapter = new ChapterSummary("L'arrivee", "...", 0, List.of(scene));
|
||||
ArcSummary arc = new ArcSummary("Acte I", "Mise en place", 1, List.of(chapter));
|
||||
CampaignStructuralContext camp = new CampaignStructuralContext(
|
||||
"Les Ombres", "dark fantasy", List.of(arc), List.of());
|
||||
"Les Ombres", "dark fantasy", List.of(arc), List.of(), List.of());
|
||||
ChatRequest req = ChatRequest.builder().messages(sampleMessages).campaignContext(camp).build();
|
||||
|
||||
Map<String, Object> payload = builder.build(req);
|
||||
@@ -200,7 +200,7 @@ class BrainChatPayloadBuilderTest {
|
||||
void build_arcSummary_omitsIllustrationCount_whenZero() {
|
||||
ArcSummary arc = new ArcSummary("A", "", 0, List.of());
|
||||
CampaignStructuralContext camp = new CampaignStructuralContext(
|
||||
"X", "", List.of(arc), List.of());
|
||||
"X", "", List.of(arc), List.of(), List.of());
|
||||
ChatRequest req = ChatRequest.builder().messages(sampleMessages).campaignContext(camp).build();
|
||||
|
||||
Map<String, Object> payload = builder.build(req);
|
||||
@@ -217,7 +217,7 @@ class BrainChatPayloadBuilderTest {
|
||||
ChapterSummary chapter = new ChapterSummary("Ch", "", 0, List.of(scene));
|
||||
ArcSummary arc = new ArcSummary("A", "", 0, List.of(chapter));
|
||||
CampaignStructuralContext camp = new CampaignStructuralContext(
|
||||
"X", "", List.of(arc), List.of());
|
||||
"X", "", List.of(arc), List.of(), List.of());
|
||||
ChatRequest req = ChatRequest.builder().messages(sampleMessages).campaignContext(camp).build();
|
||||
|
||||
Map<String, Object> payload = builder.build(req);
|
||||
@@ -236,7 +236,7 @@ class BrainChatPayloadBuilderTest {
|
||||
ChapterSummary chapter = new ChapterSummary("Ch", "", 0, List.of(scene));
|
||||
ArcSummary arc = new ArcSummary("A", "", 0, List.of(chapter));
|
||||
CampaignStructuralContext camp = new CampaignStructuralContext(
|
||||
"X", "", List.of(arc), List.of());
|
||||
"X", "", List.of(arc), List.of(), List.of());
|
||||
ChatRequest req = ChatRequest.builder().messages(sampleMessages).campaignContext(camp).build();
|
||||
|
||||
Map<String, Object> payload = builder.build(req);
|
||||
@@ -269,7 +269,7 @@ class BrainChatPayloadBuilderTest {
|
||||
@Test
|
||||
void build_campaignScenario_includesBothContextsAndEntity() {
|
||||
CampaignStructuralContext camp = new CampaignStructuralContext(
|
||||
"X", "", List.of(), List.of());
|
||||
"X", "", List.of(), List.of(), List.of());
|
||||
NarrativeEntityContext entity = new NarrativeEntityContext("arc", "T", Map.of());
|
||||
ChatRequest req = ChatRequest.builder()
|
||||
.messages(sampleMessages)
|
||||
|
||||
Reference in New Issue
Block a user