Intégration du graphe et du multi-branche pour la partie campagne

This commit is contained in:
2026-04-21 05:05:11 +02:00
parent 1a5b6f8d79
commit 67818f0d3d
31 changed files with 1933 additions and 13 deletions

View File

@@ -179,7 +179,9 @@ class ChatUseCase:
return (
"--- CAMPAGNE COURANTE ---\n"
f"Nom : {ctx.campaign_name}{desc}{lore_note}\n\n"
f"Structure narrative :\n{arcs_block}"
"Structure narrative (les flèches → indiquent des transitions de scène "
"déclenchées par un choix des joueurs) :\n"
f"{arcs_block}"
)
@staticmethod
@@ -212,6 +214,11 @@ class ChatUseCase:
block.append(f" - {scene.name} (scène){sc_hint}")
if scene.description:
block.append(f" Description : {scene.description}")
for br in scene.branches:
cond = f" (si : {br.condition})" if br.condition else ""
block.append(
f'"{br.label}" vers {br.target_scene_name}{cond}'
)
return block
@staticmethod

View File

@@ -4,7 +4,7 @@ On utilise @dataclass (pas Pydantic) pour garder le domaine exempt de toute
dépendance framework. Pydantic apparaît uniquement aux frontières : DTOs HTTP
dans `main.py`, Settings dans `core/config.py`.
"""
from dataclasses import dataclass
from dataclasses import dataclass, field
@dataclass(frozen=True)
@@ -109,15 +109,30 @@ class PageContext:
values: dict[str, str]
@dataclass(frozen=True)
class SceneBranchHint:
"""Indice d'une branche narrative vers une autre scène du même chapitre.
Le Core Java résout déjà `targetSceneId` en nom humain avant l'envoi :
l'IA ne voit donc jamais d'UUID, seulement des noms qu'elle peut citer.
"""
label: str
target_scene_name: str
condition: str | None = None
@dataclass(frozen=True)
class SceneSummary:
"""Résumé d'une scène : nom + description courte + nb illustrations."""
"""Résumé d'une scène : nom + description courte + illustrations + branches."""
name: str
description: str | None
# Depuis l'etape 6 : permet a l'IA de savoir qu'une scene a des illustrations
# attachees. 0 par defaut pour retrocompat si le Core n'envoie rien.
illustration_count: int = 0
# Connexions narratives sortantes (livre dont vous etes le heros).
branches: list[SceneBranchHint] = field(default_factory=list)
@dataclass(frozen=True)

View File

@@ -24,6 +24,7 @@ from app.domain.models import (
PageContext,
PageGenerationContext,
PageSummary,
SceneBranchHint,
SceneSummary,
)
from app.domain.ports import LLMProvider, LLMProviderError
@@ -105,6 +106,14 @@ class PageContextDTO(BaseModel):
values: dict[str, str] = Field(default_factory=dict)
class SceneBranchHintDTO(BaseModel):
"""Indice d'une branche narrative (le Core a deja resolu le nom cible)."""
label: str
target_scene_name: str
condition: str | None = None
class SceneSummaryDTO(BaseModel):
"""Résumé d'une scène : nom + description courte (synopsis)."""
@@ -113,6 +122,8 @@ class SceneSummaryDTO(BaseModel):
# Optionnel : le Core Java ne serialise illustration_count QUE si > 0
# (payload plus leger). Defaut 0 = pas d'illustrations ou champ absent.
illustration_count: int = 0
# Branches narratives sortantes, omises cote Core si vides.
branches: list[SceneBranchHintDTO] = Field(default_factory=list)
class ChapterSummaryDTO(BaseModel):
@@ -357,6 +368,14 @@ def _to_campaign_context(dto: CampaignContextDTO | None) -> CampaignStructuralCo
name=sc.name,
description=sc.description,
illustration_count=sc.illustration_count,
branches=[
SceneBranchHint(
label=br.label,
target_scene_name=br.target_scene_name,
condition=br.condition,
)
for br in sc.branches
],
)
for sc in ch.scenes
],