Ajout d'un mode "jeu" (possibilité de lancer des sessions dans une campagne). Cela permet de faire de prendre des notes en live au cours d'une partie et d'avoir plusieurs outils sous la main pour aider le mj :
All checks were successful
Build & Push Images / build (brain) (push) Successful in 1m20s
Build & Push Images / build (core) (push) Successful in 1m50s
Build & Push Images / build-switcher (push) Successful in 18s
Build & Push Images / build (web) (push) Successful in 1m47s

- Possibilité de parler à une IA pour règle de jeu ou élément de lore / campagne au cours d'une partie comme aide mémoire
- Onglet dédié aux personnages de la campagne
- Onglet dédié aux scènes
- Onglet avec dès pour ceux qui souhaitent ;

Possibilité de rajouté une note en tant qu'évènement, jet de dès ou encore action du joueur par exemple. D'autres ajouts seront fait dans le futur (notamment des tables aléatoires pour PNJ en live).
This commit is contained in:
2026-05-20 14:59:26 +02:00
parent 87865338a0
commit 694f687fec
53 changed files with 3614 additions and 17 deletions

View File

@@ -47,6 +47,7 @@ export type NarrativeEntityType = 'arc' | 'chapter' | 'scene' | 'character' | 'n
export class AiChatService {
private readonly loreEndpoint = '/api/ai/chat/stream';
private readonly campaignEndpoint = '/api/ai/chat/stream-campaign';
private readonly sessionEndpoint = '/api/ai/chat/stream-session';
/**
* Streame la réponse de l'IA pour un historique de messages donné (chat ancré Lore).
@@ -89,7 +90,16 @@ export class AiChatService {
return this.streamSse(this.campaignEndpoint, body);
}
/** Plumbing SSE mutualisé entre les 2 endpoints (Lore et Campaign). */
/**
* Streame la réponse de l'IA pour un chat pendant une Session de jeu.
* Le backend reconstitue automatiquement le contexte complet (lore +
* campagne + système de JDR + journal de session).
*/
streamChatForSession(sessionId: string, messages: ChatMessage[]): Observable<ChatStreamEvent> {
return this.streamSse(this.sessionEndpoint, { sessionId, messages });
}
/** Plumbing SSE mutualisé entre les endpoints (Lore / Campaign / Session). */
private streamSse(endpoint: string, body: Record<string, unknown>): Observable<ChatStreamEvent> {
return new Observable<ChatStreamEvent>((subscriber) => {
const controller = new AbortController();

View File

@@ -0,0 +1,35 @@
/** Type d'une entrée de journal de session — miroir de l'enum Java EntryType. */
export type EntryType = 'NOTE' | 'EVENT' | 'DICE_ROLL' | 'PLAYER_ACTION';
/** Entrée du journal d'une Session (note, évènement, jet, action joueur). */
export interface SessionEntry {
id: string;
sessionId: string;
type: EntryType;
content: string;
occurredAt: string;
createdAt: string;
updatedAt: string;
}
/** Payload de création/édition d'une entrée. */
export interface SessionEntryInput {
type: EntryType;
content: string;
/** Optionnel : si absent, le backend utilisera "maintenant". */
occurredAt?: string;
}
/** Métadonnées d'affichage par type — utilisées par la timeline. */
export interface EntryTypeMeta {
label: string;
icon: 'StickyNote' | 'Sparkles' | 'Dices' | 'UserCheck';
color: string;
}
export const ENTRY_TYPE_META: Record<EntryType, EntryTypeMeta> = {
NOTE: { label: 'Note', icon: 'StickyNote', color: '#9ca3af' },
EVENT: { label: 'Évènement', icon: 'Sparkles', color: '#f59e0b' },
DICE_ROLL: { label: 'Jet de dés', icon: 'Dices', color: '#6c63ff' },
PLAYER_ACTION: { label: 'Action joueur', icon: 'UserCheck', color: '#10b981' },
};

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { SessionEntry, SessionEntryInput } from './session-entry.model';
/**
* Service HTTP pour le journal d'une Session.
* Endpoints imbriqués : /api/sessions/{sessionId}/entries.
*/
@Injectable({
providedIn: 'root'
})
export class SessionEntryService {
private base(sessionId: string): string {
return `/api/sessions/${sessionId}/entries`;
}
constructor(private http: HttpClient) {}
getEntries(sessionId: string): Observable<SessionEntry[]> {
return this.http.get<SessionEntry[]>(this.base(sessionId));
}
createEntry(sessionId: string, input: SessionEntryInput): Observable<SessionEntry> {
return this.http.post<SessionEntry>(this.base(sessionId), input);
}
updateEntry(sessionId: string, entryId: string, input: SessionEntryInput): Observable<SessionEntry> {
return this.http.put<SessionEntry>(`${this.base(sessionId)}/${entryId}`, input);
}
deleteEntry(sessionId: string, entryId: string): Observable<void> {
return this.http.delete<void>(`${this.base(sessionId)}/${entryId}`);
}
}

View File

@@ -0,0 +1,15 @@
/**
* Modèle Session côté Frontend.
* Miroir du SessionDTO Java exposé par /api/sessions.
*/
export interface Session {
id: string;
name: string;
campaignId: string;
startedAt: string;
/** Null/undefined = session en cours. */
endedAt: string | null;
createdAt: string;
updatedAt: string;
active: boolean;
}

View File

@@ -0,0 +1,51 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Session } from './session.model';
/**
* Service HTTP pour le Play Context (gestion des Sessions de jeu).
* Port de sortie vers le Backend Java (Architecture Hexagonale).
*/
@Injectable({
providedIn: 'root'
})
export class SessionService {
private apiUrl = '/api/sessions';
constructor(private http: HttpClient) {}
/** Lance une nouvelle session sur la campagne donnée. */
startSession(campaignId: string): Observable<Session> {
return this.http.post<Session>(this.apiUrl, { campaignId });
}
/** Récupère la session active (204 No Content si aucune). */
getActiveSession(): Observable<Session | null> {
return this.http.get<Session | null>(`${this.apiUrl}/active`, { observe: 'body' });
}
getSessions(campaignId?: string): Observable<Session[]> {
let params = new HttpParams();
if (campaignId) {
params = params.set('campaignId', campaignId);
}
return this.http.get<Session[]>(this.apiUrl, { params });
}
getSessionById(id: string): Observable<Session> {
return this.http.get<Session>(`${this.apiUrl}/${id}`);
}
endSession(id: string): Observable<Session> {
return this.http.post<Session>(`${this.apiUrl}/${id}/end`, {});
}
renameSession(id: string, name: string): Observable<Session> {
return this.http.patch<Session>(`${this.apiUrl}/${id}`, { name });
}
deleteSession(id: string): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}