Les réponses ne ce baseront que sur les sources que l'on aura cocher au préalable
140 lines
6.1 KiB
HTML
140 lines
6.1 KiB
HTML
@if (detail) {
|
|
<div class="nbd-page">
|
|
<div class="nbd-toolbar">
|
|
<button class="btn-back" (click)="back()">
|
|
<lucide-icon [img]="ArrowLeft" [size]="14"></lucide-icon> Ateliers
|
|
</button>
|
|
<input class="nbd-title" [(ngModel)]="detail.name" (blur)="rename()" (keyup.enter)="rename()">
|
|
</div>
|
|
<div class="nbd-grid">
|
|
<!-- Sources -->
|
|
<aside class="nbd-sources">
|
|
<div class="nbd-sources-head">
|
|
<h3><lucide-icon [img]="FileText" [size]="15"></lucide-icon> Sources</h3>
|
|
<label class="btn-upload" [class.disabled]="uploading">
|
|
<lucide-icon [img]="Upload" [size]="13"></lucide-icon>
|
|
{{ uploading ? 'Indexation…' : 'Ajouter un PDF' }}
|
|
<input type="file" accept="application/pdf" hidden (change)="onFile($event)" [disabled]="uploading">
|
|
</label>
|
|
</div>
|
|
@if (uploadError) {
|
|
<p class="nbd-upload-error">{{ uploadError }}</p>
|
|
}
|
|
@if (readySourceCount > 1) {
|
|
<p class="nbd-sources-hint"
|
|
[class.partial]="selectedSourceIds.size < readySourceCount">
|
|
{{ selectedSourceIds.size }}/{{ readySourceCount }} source(s) utilisée(s) par le chat et l'analyse approfondie
|
|
</p>
|
|
}
|
|
@for (s of sources; track s) {
|
|
<div class="nbd-source" [class.nbd-source--off]="s.status === 'READY' && !isSelected(s)">
|
|
@if (s.status === 'READY') {
|
|
<input
|
|
type="checkbox"
|
|
class="nbd-source-check"
|
|
[checked]="isSelected(s)"
|
|
(change)="toggleSource(s)"
|
|
[title]="isSelected(s)
|
|
? 'Source utilisée par le chat — décocher pour l\'exclure'
|
|
: 'Source ignorée par le chat — cocher pour l\'inclure'" />
|
|
} @else {
|
|
<lucide-icon
|
|
[img]="s.status === 'FAILED' ? AlertCircle : Loader"
|
|
[size]="14"
|
|
[class.fail]="s.status === 'FAILED'" [class.busy]="s.status === 'INDEXING'">
|
|
</lucide-icon>
|
|
}
|
|
<div class="nbd-source-info">
|
|
<div class="nbd-source-name">{{ s.filename }}</div>
|
|
<div class="nbd-source-meta">
|
|
@if (s.status === 'READY') {
|
|
<span>{{ s.pageCount }} p. · {{ s.chunkCount }} extraits</span>
|
|
}
|
|
@if (s.status === 'INDEXING') {
|
|
<span>indexation en cours…</span>
|
|
}
|
|
@if (s.status === 'FAILED') {
|
|
<span>échec de l'indexation</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
<button class="nbd-source-del" (click)="removeSource(s)" title="Supprimer">
|
|
<lucide-icon [img]="Trash2" [size]="13"></lucide-icon>
|
|
</button>
|
|
</div>
|
|
}
|
|
@if (sources.length === 0 && !uploading) {
|
|
<p class="nbd-empty">
|
|
Ajoute un PDF source pour commencer à discuter avec.
|
|
</p>
|
|
}
|
|
</aside>
|
|
<!-- Chat -->
|
|
<section class="nbd-chat">
|
|
<div class="nbd-messages">
|
|
@if (messages.length === 0) {
|
|
<p class="nbd-empty">
|
|
Pose une question sur ta source, ou demande une adaptation pour ta campagne.
|
|
@if (!hasReadySource()) {
|
|
<span><br>(Ajoute d'abord une source indexée pour des réponses ancrées.)</span>
|
|
}
|
|
</p>
|
|
}
|
|
@for (m of messages; track m) {
|
|
<div class="nbd-msg" [class.user]="m.role === 'user'" [class.assistant]="m.role === 'assistant'">
|
|
<div class="nbd-msg-role">
|
|
@if (m.role === 'assistant') {
|
|
<lucide-icon [img]="Sparkles" [size]="12"></lucide-icon>
|
|
}
|
|
{{ m.role === 'user' ? 'Vous' : 'IA' }}
|
|
</div>
|
|
@if (m.role === 'assistant') {
|
|
@if (parsedOf(m); as p) {
|
|
@if (sending && deepProgress && !p.text) {
|
|
<div class="nbd-deep-progress">
|
|
<lucide-icon [img]="Layers" [size]="13"></lucide-icon>
|
|
Analyse approfondie du document… {{ deepProgress.current }}/{{ deepProgress.total }}
|
|
</div>
|
|
}
|
|
<div class="nbd-msg-content">{{ p.text }}@if (sending && !p.text && !p.actions.length && !deepProgress) {
|
|
<span class="cursor">▌</span>
|
|
}</div>
|
|
@for (a of p.actions; track $index) {
|
|
<app-notebook-action-card
|
|
[action]="a"
|
|
[campaignId]="campaignId"
|
|
[arcs]="arcs"
|
|
[chaptersByArc]="chaptersByArc"
|
|
(created)="onActionCreated()">
|
|
</app-notebook-action-card>
|
|
}
|
|
@if (sourcesLabel(m); as label) {
|
|
<div class="nbd-msg-sources" title="Pages de la source utilisées pour ancrer cette réponse">
|
|
📖 {{ label }}
|
|
</div>
|
|
}
|
|
}
|
|
} @else {
|
|
<div class="nbd-msg-content">{{ m.content }}</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
<div class="nbd-input">
|
|
<textarea [(ngModel)]="draft" rows="2"
|
|
placeholder="Demande une adaptation, un résumé, un PNJ inspiré de la source…"
|
|
(keydown.enter)="$event.preventDefault(); send()"></textarea>
|
|
<button class="btn-deep" (click)="send(true)" [disabled]="sending || !draft.trim()"
|
|
title="Analyse approfondie : lit TOUT le document (plus lent, exhaustif). Idéal pour « liste tous les… »">
|
|
<lucide-icon [img]="Layers" [size]="15"></lucide-icon>
|
|
</button>
|
|
<button class="btn-send" (click)="send()" [disabled]="sending || !draft.trim()"
|
|
title="Réponse rapide (recherche ciblée dans le document)">
|
|
<lucide-icon [img]="Send" [size]="15"></lucide-icon>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
}
|