Mise en place de l'anglais comme deuxième langue pour l'application
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
[class.is-resizing]="isResizing"
|
||||
[class.is-wide]="isWide"
|
||||
[style.width.px]="effectiveWidth"
|
||||
aria-label="Assistant IA">
|
||||
[attr.aria-label]="'aiChatDrawer.title' | translate">
|
||||
|
||||
<!-- Poignee de redimensionnement (desactivee en mode wide) -->
|
||||
@if (!isWide) {
|
||||
@@ -13,23 +13,23 @@
|
||||
(mousedown)="onResizeStart($event)"
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Redimensionner le panneau"
|
||||
title="Glisser pour redimensionner"></div>
|
||||
[attr.aria-label]="'aiChatDrawer.resizeAria' | translate"
|
||||
[title]="'aiChatDrawer.resizeTitle' | translate"></div>
|
||||
}
|
||||
|
||||
|
||||
<!-- Sidebar conversations (mode persistent uniquement) -->
|
||||
@if (persistent && sidebarOpen) {
|
||||
<section class="conv-sidebar" aria-label="Conversations">
|
||||
<section class="conv-sidebar" [attr.aria-label]="'aiChatDrawer.conversations' | translate">
|
||||
<div class="conv-sidebar-header">
|
||||
<span class="conv-sidebar-title">Conversations</span>
|
||||
<button type="button" class="conv-new-btn" (click)="startNewConversation()" [disabled]="isStreaming" title="Nouvelle conversation">
|
||||
<span class="conv-sidebar-title">{{ 'aiChatDrawer.conversations' | translate }}</span>
|
||||
<button type="button" class="conv-new-btn" (click)="startNewConversation()" [disabled]="isStreaming" [title]="'aiChatDrawer.newConversation' | translate">
|
||||
<lucide-icon [img]="MessageSquarePlus" [size]="16"></lucide-icon>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="conv-list">
|
||||
@if (conversations.length === 0) {
|
||||
<li class="conv-empty">Aucune conversation</li>
|
||||
<li class="conv-empty">{{ 'aiChatDrawer.noConversation' | translate }}</li>
|
||||
}
|
||||
@for (c of conversations; track c) {
|
||||
<li
|
||||
@@ -42,7 +42,7 @@
|
||||
class="conv-item-del"
|
||||
(click)="deleteConversation(c, $event)"
|
||||
[disabled]="isStreaming"
|
||||
title="Supprimer">
|
||||
[title]="'common.delete' | translate">
|
||||
<lucide-icon [img]="Trash2" [size]="12"></lucide-icon>
|
||||
</button>
|
||||
</li>
|
||||
@@ -59,8 +59,8 @@
|
||||
type="button"
|
||||
class="sidebar-toggle"
|
||||
(click)="toggleSidebar()"
|
||||
[attr.aria-label]="sidebarOpen ? 'Masquer la liste' : 'Afficher la liste'"
|
||||
[title]="sidebarOpen ? 'Masquer la liste' : 'Afficher la liste'">
|
||||
[attr.aria-label]="(sidebarOpen ? 'aiChatDrawer.hideList' : 'aiChatDrawer.showList') | translate"
|
||||
[title]="(sidebarOpen ? 'aiChatDrawer.hideList' : 'aiChatDrawer.showList') | translate">
|
||||
<lucide-icon [img]="sidebarOpen ? PanelLeftClose : PanelLeftOpen" [size]="16"></lucide-icon>
|
||||
</button>
|
||||
}
|
||||
@@ -68,8 +68,8 @@
|
||||
<div class="header-title-wrap">
|
||||
@if (persistent && currentConversationId) {
|
||||
@if (!editingTitle) {
|
||||
<h2 class="header-title" [title]="currentTitle">{{ currentTitle || 'Nouvelle conversation' }}</h2>
|
||||
<button type="button" class="rename-btn" (click)="startRenameTitle()" title="Renommer" aria-label="Renommer">
|
||||
<h2 class="header-title" [title]="currentTitle">{{ currentTitle || ('aiChatDrawer.newConversation' | translate) }}</h2>
|
||||
<button type="button" class="rename-btn" (click)="startRenameTitle()" [title]="'common.rename' | translate" [attr.aria-label]="'common.rename' | translate">
|
||||
<lucide-icon [img]="Pencil" [size]="12"></lucide-icon>
|
||||
</button>
|
||||
} @else {
|
||||
@@ -83,7 +83,7 @@
|
||||
autofocus />
|
||||
}
|
||||
} @else {
|
||||
<h2 class="header-title">Assistant IA</h2>
|
||||
<h2 class="header-title">{{ 'aiChatDrawer.title' | translate }}</h2>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -91,19 +91,19 @@
|
||||
type="button"
|
||||
class="wide-toggle-btn"
|
||||
(click)="toggleWide()"
|
||||
[attr.aria-label]="isWide ? 'Reduire le panneau' : 'Agrandir le panneau'"
|
||||
[title]="isWide ? 'Reduire' : 'Agrandir'">
|
||||
[attr.aria-label]="(isWide ? 'aiChatDrawer.shrinkAria' : 'aiChatDrawer.expandAria') | translate"
|
||||
[title]="(isWide ? 'aiChatDrawer.shrink' : 'aiChatDrawer.expand') | translate">
|
||||
<lucide-icon [img]="isWide ? Minimize2 : Maximize2" [size]="16"></lucide-icon>
|
||||
</button>
|
||||
|
||||
<button type="button" class="close-btn" (click)="onClose()" aria-label="Fermer">
|
||||
<button type="button" class="close-btn" (click)="onClose()" [attr.aria-label]="'common.close' | translate">
|
||||
<lucide-icon [img]="X" [size]="18"></lucide-icon>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
@if (usage) {
|
||||
<div class="context-gauge" [attr.data-level]="usageLevel"
|
||||
[attr.title]="'System: ' + usage.system + ' · Historique: ' + usage.history + ' · Courant: ' + usage.current + (usageHasMax ? ' / ' + usage.max : '') + ' tokens'">
|
||||
[attr.title]="(usageHasMax ? 'aiChatDrawer.gaugeTitleMax' : 'aiChatDrawer.gaugeTitle') | translate:{ system: usage.system, history: usage.history, current: usage.current, max: usage.max }">
|
||||
<!-- Barre seulement si on connaît le plafond (Ollama). Cloud : compteur seul. -->
|
||||
@if (usageHasMax) {
|
||||
<div class="gauge-bar">
|
||||
@@ -111,9 +111,11 @@
|
||||
</div>
|
||||
}
|
||||
<div class="gauge-label">
|
||||
<span class="gauge-text">Contexte : {{ usageTotal }}@if (usageHasMax) {
|
||||
/ {{ usage.max }}
|
||||
} tokens</span>
|
||||
<span class="gauge-text">@if (usageHasMax) {
|
||||
{{ 'aiChatDrawer.gaugeLabelMax' | translate:{ total: usageTotal, max: usage.max } }}
|
||||
} @else {
|
||||
{{ 'aiChatDrawer.gaugeLabel' | translate:{ total: usageTotal } }}
|
||||
}</span>
|
||||
@if (usageHasMax) {
|
||||
<span class="gauge-percent">{{ usagePercent }}%</span>
|
||||
}
|
||||
@@ -172,7 +174,7 @@
|
||||
|
||||
@if (quickSuggestions.length) {
|
||||
<div class="quick-suggestions">
|
||||
<p class="quick-label">Suggestions rapides :</p>
|
||||
<p class="quick-label">{{ 'aiChatDrawer.quickSuggestions' | translate }}</p>
|
||||
<div class="quick-list">
|
||||
@for (s of quickSuggestions; track s) {
|
||||
<button
|
||||
@@ -193,10 +195,10 @@
|
||||
type="text"
|
||||
[(ngModel)]="input"
|
||||
name="chatInput"
|
||||
placeholder="Posez une question..."
|
||||
[placeholder]="'aiChatDrawer.inputPlaceholder' | translate"
|
||||
[disabled]="isStreaming"
|
||||
autocomplete="off" />
|
||||
<button type="submit" class="send-btn" [disabled]="!input.trim() || isStreaming" aria-label="Envoyer">
|
||||
<button type="submit" class="send-btn" [disabled]="!input.trim() || isStreaming" [attr.aria-label]="'aiChatDrawer.send' | translate">
|
||||
<lucide-icon [img]="Send" [size]="16"></lucide-icon>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Component, ElementRef, EventEmitter, HostListener, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { LucideAngularModule, Lightbulb, Maximize2, MessageSquarePlus, Minimize2, PanelLeftClose, PanelLeftOpen, Pencil, Send, Sparkles, Trash2, Wand2, X } from 'lucide-angular';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { AiChatService, ChatMessage, ChatUsage, NarrativeEntityType } from '../../services/ai-chat.service';
|
||||
import { Conversation, ConversationContext } from '../../services/conversation.model';
|
||||
@@ -31,7 +32,7 @@ export interface ChatPrimaryAction {
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-ai-chat-drawer',
|
||||
imports: [FormsModule, LucideAngularModule, MarkdownPipe],
|
||||
imports: [FormsModule, LucideAngularModule, MarkdownPipe, TranslatePipe],
|
||||
templateUrl: './ai-chat-drawer.component.html',
|
||||
styleUrls: ['./ai-chat-drawer.component.scss']
|
||||
})
|
||||
@@ -80,7 +81,7 @@ export class AiChatDrawerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() entityType: NarrativeEntityType | null = null;
|
||||
@Input() entityId: string | null = null;
|
||||
@Input() isOpen = false;
|
||||
@Input() welcomeMessage = 'Bonjour ! Je peux vous aider a developper cette page. Que souhaitez-vous creer ?';
|
||||
@Input() welcomeMessage = '';
|
||||
@Input() quickSuggestions: string[] = [];
|
||||
@Input() primaryAction: ChatPrimaryAction | null = null;
|
||||
@Input() systemPromptAddon: string | null = null;
|
||||
@@ -120,7 +121,10 @@ export class AiChatDrawerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
private readonly chatService: AiChatService,
|
||||
private readonly conversationService: ConversationService,
|
||||
private readonly confirmDialog: ConfirmDialogService,
|
||||
) {}
|
||||
private readonly translate: TranslateService,
|
||||
) {
|
||||
this.welcomeMessage = this.translate.instant('aiChatDrawer.welcome');
|
||||
}
|
||||
|
||||
// --- Jauge de contexte --------------------------------------------------
|
||||
|
||||
@@ -310,7 +314,7 @@ export class AiChatDrawerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.usage = null;
|
||||
this.scrollToBottom();
|
||||
},
|
||||
error: () => (this.errorMessage = 'Impossible de charger la conversation.'),
|
||||
error: () => (this.errorMessage = this.translate.instant('aiChatDrawer.loadConversationError')),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -318,9 +322,9 @@ export class AiChatDrawerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
event.stopPropagation();
|
||||
if (this.isStreaming) return;
|
||||
this.confirmDialog.confirm({
|
||||
title: 'Supprimer la conversation',
|
||||
message: `Supprimer la conversation "${conv.title}" ?`,
|
||||
confirmLabel: 'Supprimer',
|
||||
title: this.translate.instant('aiChatDrawer.deleteConversationTitle'),
|
||||
message: this.translate.instant('aiChatDrawer.deleteConversationMessage', { title: conv.title }),
|
||||
confirmLabel: this.translate.instant('common.delete'),
|
||||
variant: 'danger'
|
||||
}).then(ok => {
|
||||
if (!ok) return;
|
||||
@@ -410,7 +414,7 @@ export class AiChatDrawerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (this.currentConversationId) return Promise.resolve(this.currentConversationId);
|
||||
const ctx = this.buildContext();
|
||||
if (!ctx.loreId && !ctx.campaignId) {
|
||||
this.errorMessage = 'Contexte manquant pour creer une conversation.';
|
||||
this.errorMessage = this.translate.instant('aiChatDrawer.missingContextError');
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
@@ -422,7 +426,7 @@ export class AiChatDrawerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
resolve(conv.id);
|
||||
},
|
||||
error: () => {
|
||||
this.errorMessage = 'Impossible de creer la conversation.';
|
||||
this.errorMessage = this.translate.instant('aiChatDrawer.createConversationError');
|
||||
resolve(null);
|
||||
},
|
||||
});
|
||||
@@ -460,7 +464,7 @@ export class AiChatDrawerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
},
|
||||
error: (err) => {
|
||||
this.isStreaming = false;
|
||||
this.errorMessage = err?.message ?? 'Erreur inconnue.';
|
||||
this.errorMessage = err?.message ?? this.translate.instant('aiChatDrawer.unknownError');
|
||||
this.currentAssistantText = '';
|
||||
},
|
||||
complete: () => {
|
||||
|
||||
Reference in New Issue
Block a user