Amélioration de l'IA pour la partie atelier PDF
Mise en place d'un outil permettant de faire des tableau d'objets pour des boutiques par exemple
This commit is contained in:
@@ -180,7 +180,8 @@
|
||||
[sessionId]="session.id"
|
||||
[canAddToJournal]="session.active"
|
||||
(rolled)="onDiceRolled($event)"
|
||||
(aiReplyToJournal)="onAiReplyToJournal($event)">
|
||||
(aiReplyToJournal)="onAiReplyToJournal($event)"
|
||||
(noteToJournal)="onItemNoteToJournal($event)">
|
||||
</app-session-reference-panel>
|
||||
</aside>
|
||||
|
||||
|
||||
@@ -265,6 +265,22 @@ export class SessionDetailComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Réception d'un objet de catalogue à consigner dans le journal.
|
||||
* Le panneau fournit déjà une chaîne formatée (🛒 …) : on la sauvegarde
|
||||
* telle quelle comme entrée NOTE.
|
||||
*/
|
||||
onItemNoteToJournal(content: string): void {
|
||||
if (!this.session || !this.session.active) return;
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed) return;
|
||||
const input: SessionEntryInput = { type: 'NOTE', content: trimmed };
|
||||
this.entryService.createEntry(this.session.id, input).subscribe({
|
||||
next: created => this.entries = [created, ...this.entries],
|
||||
error: () => console.error('Erreur lors de l\'ajout de l\'objet au journal')
|
||||
});
|
||||
}
|
||||
|
||||
deleteEntry(entry: SessionEntry): void {
|
||||
if (!this.session) return;
|
||||
const session = this.session;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<div class="sic-panel">
|
||||
<p class="loading-hint" *ngIf="loading">Chargement…</p>
|
||||
|
||||
<!-- Liste des catalogues -->
|
||||
<div *ngIf="!loading && !selected" class="sic-list">
|
||||
<p class="empty-hint" *ngIf="catalogs.length === 0">
|
||||
Aucun catalogue d'objets dans cette campagne.
|
||||
</p>
|
||||
<button *ngFor="let c of catalogs" type="button" class="sic-item" (click)="select(c)">
|
||||
<lucide-icon [img]="Package" [size]="13"></lucide-icon>
|
||||
<span class="sic-item-name">{{ c.name }}</span>
|
||||
<span class="sic-item-count">{{ c.items.length }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Catalogue sélectionné -->
|
||||
<div *ngIf="selected" class="sic-detail">
|
||||
<button type="button" class="sic-back" (click)="backToList()">
|
||||
<lucide-icon [img]="ChevronLeft" [size]="13"></lucide-icon> Catalogues
|
||||
</button>
|
||||
<h4>{{ selected.name }}</h4>
|
||||
|
||||
<div class="sic-group" *ngFor="let g of groups">
|
||||
<div class="sic-group-title" *ngIf="g.category !== '—'">{{ g.category }}</div>
|
||||
<div class="sic-obj" *ngFor="let it of g.items">
|
||||
<div class="sic-obj-main">
|
||||
<span class="sic-obj-name">{{ it.name }}</span>
|
||||
<span class="sic-obj-price" *ngIf="it.price">{{ it.price }}</span>
|
||||
</div>
|
||||
<div class="sic-obj-desc" *ngIf="it.description">{{ it.description }}</div>
|
||||
<button type="button" class="sic-note" (click)="note(it)" [disabled]="!canAddToJournal"
|
||||
title="Consigner au journal (ex. achat)">
|
||||
<lucide-icon [img]="BookmarkPlus" [size]="12"></lucide-icon> Journal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,48 @@
|
||||
.sic-panel { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.25rem; }
|
||||
|
||||
.loading-hint, .empty-hint {
|
||||
color: var(--color-text-muted, #9aa0aa); font-size: 0.85rem; font-style: italic; padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.sic-list { display: flex; flex-direction: column; gap: 0.3rem; }
|
||||
.sic-item {
|
||||
display: flex; align-items: center; gap: 0.45rem; width: 100%;
|
||||
padding: 0.45rem 0.6rem; border-radius: 7px;
|
||||
border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03);
|
||||
color: inherit; cursor: pointer; text-align: left;
|
||||
&:hover { background: rgba(255,255,255,0.08); }
|
||||
.sic-item-name { flex: 1; }
|
||||
.sic-item-count {
|
||||
font-size: 0.72rem; color: var(--color-text-muted, #9aa0aa);
|
||||
background: rgba(255,255,255,0.06); padding: 0.05rem 0.35rem; border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.sic-detail { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.sic-back {
|
||||
display: inline-flex; align-items: center; gap: 0.25rem; align-self: flex-start;
|
||||
padding: 0.2rem 0.4rem; border: none; background: none;
|
||||
color: var(--color-text-muted, #9aa0aa); cursor: pointer; font-size: 0.8rem;
|
||||
&:hover { color: inherit; }
|
||||
}
|
||||
h4 { margin: 0; font-size: 0.95rem; }
|
||||
|
||||
.sic-group-title {
|
||||
font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;
|
||||
color: #c4a8ff; margin: 0.5rem 0 0.2rem;
|
||||
}
|
||||
|
||||
.sic-obj {
|
||||
padding: 0.4rem 0.5rem; border-radius: 7px; background: rgba(255,255,255,0.03); margin-bottom: 0.3rem;
|
||||
.sic-obj-main { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
|
||||
.sic-obj-name { font-weight: 500; }
|
||||
.sic-obj-price { color: #e0c074; font-size: 0.82rem; white-space: nowrap; }
|
||||
.sic-obj-desc { font-size: 0.82rem; color: var(--color-text-muted, #cfd3da); margin-top: 0.15rem; }
|
||||
.sic-note {
|
||||
display: inline-flex; align-items: center; gap: 0.25rem; margin-top: 0.35rem;
|
||||
padding: 0.2rem 0.45rem; border-radius: 5px; font-size: 0.74rem;
|
||||
border: 1px solid rgba(255,255,255,0.14); background: rgba(255,255,255,0.05); color: inherit; cursor: pointer;
|
||||
&:hover:not(:disabled) { background: rgba(255,255,255,0.1); }
|
||||
&:disabled { opacity: 0.5; cursor: default; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { LucideAngularModule, Package, BookmarkPlus, ChevronLeft } from 'lucide-angular';
|
||||
import { catchError, of } from 'rxjs';
|
||||
import { ItemCatalogService } from '../../services/item-catalog.service';
|
||||
import { ItemCatalog, CatalogItem } from '../../services/item-catalog.model';
|
||||
|
||||
interface ItemGroup { category: string; items: CatalogItem[]; }
|
||||
|
||||
/**
|
||||
* Panneau « Objets » du mode jeu : consulter les catalogues (boutiques) de la
|
||||
* campagne et consigner un objet au journal (ex. « le joueur achète X »).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-session-item-catalogs-panel',
|
||||
standalone: true,
|
||||
imports: [CommonModule, LucideAngularModule],
|
||||
templateUrl: './session-item-catalogs-panel.component.html',
|
||||
styleUrls: ['./session-item-catalogs-panel.component.scss']
|
||||
})
|
||||
export class SessionItemCatalogsPanelComponent implements OnInit {
|
||||
readonly Package = Package;
|
||||
readonly BookmarkPlus = BookmarkPlus;
|
||||
readonly ChevronLeft = ChevronLeft;
|
||||
|
||||
@Input() campaignId!: string;
|
||||
@Input() canAddToJournal = true;
|
||||
/** Émis pour consigner un objet au journal (entrée NOTE). */
|
||||
@Output() noteToJournal = new EventEmitter<string>();
|
||||
|
||||
catalogs: ItemCatalog[] = [];
|
||||
loading = false;
|
||||
selected: ItemCatalog | null = null;
|
||||
|
||||
constructor(private service: ItemCatalogService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.campaignId) return;
|
||||
this.loading = true;
|
||||
this.service.getByCampaign(this.campaignId).pipe(catchError(() => of([] as ItemCatalog[])))
|
||||
.subscribe(list => { this.catalogs = list; this.loading = false; });
|
||||
}
|
||||
|
||||
select(c: ItemCatalog): void { this.selected = c; }
|
||||
backToList(): void { this.selected = null; }
|
||||
|
||||
get groups(): ItemGroup[] {
|
||||
const map = new Map<string, CatalogItem[]>();
|
||||
for (const it of this.selected?.items ?? []) {
|
||||
const cat = (it.category ?? '').trim() || '—';
|
||||
if (!map.has(cat)) map.set(cat, []);
|
||||
map.get(cat)!.push(it);
|
||||
}
|
||||
return [...map.entries()]
|
||||
.sort(([a], [b]) => (a === '—' ? 1 : b === '—' ? -1 : a.localeCompare(b, 'fr')))
|
||||
.map(([category, items]) => ({ category, items }));
|
||||
}
|
||||
|
||||
note(it: CatalogItem): void {
|
||||
if (!this.canAddToJournal || !this.selected) return;
|
||||
const price = it.price ? ` (${it.price})` : '';
|
||||
this.noteToJournal.emit(`🛒 ${this.selected.name} — ${it.name}${price}`);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,13 @@
|
||||
<lucide-icon [img]="Table2" [size]="14"></lucide-icon>
|
||||
Tables
|
||||
</button>
|
||||
<button type="button"
|
||||
class="ref-tab"
|
||||
[class.ref-tab--active]="activeTab === 'objects'"
|
||||
(click)="selectTab('objects')">
|
||||
<lucide-icon [img]="Package" [size]="14"></lucide-icon>
|
||||
Objets
|
||||
</button>
|
||||
<button type="button"
|
||||
class="ref-tab"
|
||||
[class.ref-tab--active]="activeTab === 'characters'"
|
||||
@@ -64,6 +71,14 @@
|
||||
(aiReplyToJournal)="onAiSaveToJournal($event)">
|
||||
</app-session-random-tables-panel>
|
||||
|
||||
<!-- ====== Catalogues d'objets ====== -->
|
||||
<app-session-item-catalogs-panel
|
||||
*ngIf="activeTab === 'objects'"
|
||||
[campaignId]="campaignId"
|
||||
[canAddToJournal]="canAddToJournal"
|
||||
(noteToJournal)="onItemNote($event)">
|
||||
</app-session-item-catalogs-panel>
|
||||
|
||||
<!-- ====== Personnages (PJ + PNJ) ====== -->
|
||||
<div *ngIf="activeTab === 'characters'" class="ref-list">
|
||||
<p class="loading-hint" *ngIf="loadingChars">Chargement…</p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { LucideAngularModule, User, Drama, Swords, Dices, ExternalLink, Sparkles, Table2 } from 'lucide-angular';
|
||||
import { LucideAngularModule, User, Drama, Swords, Dices, ExternalLink, Sparkles, Table2, Package } from 'lucide-angular';
|
||||
import { catchError, of } from 'rxjs';
|
||||
import { CampaignService } from '../../services/campaign.service';
|
||||
import { CharacterService } from '../../services/character.service';
|
||||
@@ -14,8 +14,9 @@ import {
|
||||
} from '../session-dice-panel/session-dice-panel.component';
|
||||
import { SessionAiChatPanelComponent } from '../session-ai-chat-panel/session-ai-chat-panel.component';
|
||||
import { SessionRandomTablesPanelComponent } from '../session-random-tables-panel/session-random-tables-panel.component';
|
||||
import { SessionItemCatalogsPanelComponent } from '../session-item-catalogs-panel/session-item-catalogs-panel.component';
|
||||
|
||||
type TabId = 'dice' | 'tables' | 'characters' | 'scenes' | 'ai';
|
||||
type TabId = 'dice' | 'tables' | 'objects' | 'characters' | 'scenes' | 'ai';
|
||||
|
||||
/**
|
||||
* Panneau latéral du mode jeu : référence rapide en lecture seule.
|
||||
@@ -30,7 +31,7 @@ type TabId = 'dice' | 'tables' | 'characters' | 'scenes' | 'ai';
|
||||
@Component({
|
||||
selector: 'app-session-reference-panel',
|
||||
standalone: true,
|
||||
imports: [CommonModule, LucideAngularModule, SessionDicePanelComponent, SessionAiChatPanelComponent, SessionRandomTablesPanelComponent],
|
||||
imports: [CommonModule, LucideAngularModule, SessionDicePanelComponent, SessionAiChatPanelComponent, SessionRandomTablesPanelComponent, SessionItemCatalogsPanelComponent],
|
||||
templateUrl: './session-reference-panel.component.html',
|
||||
styleUrls: ['./session-reference-panel.component.scss']
|
||||
})
|
||||
@@ -42,6 +43,7 @@ export class SessionReferencePanelComponent implements OnChanges {
|
||||
readonly ExternalLink = ExternalLink;
|
||||
readonly Sparkles = Sparkles;
|
||||
readonly Table2 = Table2;
|
||||
readonly Package = Package;
|
||||
|
||||
@Input() campaignId!: string;
|
||||
/** Partie active — nécessaire pour charger les PJ (refonte Playthrough). */
|
||||
@@ -51,6 +53,8 @@ export class SessionReferencePanelComponent implements OnChanges {
|
||||
@Output() rolled = new EventEmitter<DiceRollResult>();
|
||||
/** Émis quand l'IA répond et que le MJ veut sauvegarder la réponse comme entrée. */
|
||||
@Output() aiReplyToJournal = new EventEmitter<string>();
|
||||
/** Émis pour consigner un objet de catalogue au journal (entrée NOTE). */
|
||||
@Output() noteToJournal = new EventEmitter<string>();
|
||||
|
||||
activeTab: TabId = 'dice';
|
||||
|
||||
@@ -143,4 +147,8 @@ export class SessionReferencePanelComponent implements OnChanges {
|
||||
onAiSaveToJournal(content: string): void {
|
||||
this.aiReplyToJournal.emit(content);
|
||||
}
|
||||
|
||||
onItemNote(content: string): void {
|
||||
this.noteToJournal.emit(content);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user