Interfaccia Chat Angular RAG
Da Componenti Standalone a Dashboard Amministrativi Completi
Il frontend è dove RAG prende vita per gli utenti finali. Un backend tecnicamente perfetto ma lento da usare o confuso da navigare è praticamente inutile. Questo articolo riguarda la costruzione di un'interfaccia chat Angular intuitiva e reattiva che non sacrifica la potenza per la semplicità.
Angular moderno—componenti standalone, niente NgModules, form reattivi, Material Design 3—permette un codice snello e manutenibile. Aggiungi il supporto per l'internazionalizzazione (i18n), la visibilità delle funzionalità basata sui ruoli e le risposte in streaming, e avrai un frontend che sussurra: "Questo sistema è stato costruito da persone che ci tengono."
La Versione di Angular: Moderna, Non Legacy
Il sistema RAG utilizza Angular 20 con:
- Componenti standalone (nessuna dichiarazione NgModule)
- Architettura reattiva (osservabili RxJS, non promesse)
- Angular Material 3 (stile e componenti moderni)
- Transloco (i18n per il supporto multilingue)
- Modalità TypeScript strict (nessun tipo
anynei percorsi critici)
Questo significa:
- Bundle più veloci (tree-shaking funziona meglio)
- Iniezione di dipendenze più chiara (importazioni esplicite)
- Migliori prestazioni (ottimizzazione del rilevamento delle modifiche)
- Test più semplici (mocking minimo richiesto)
Architettura dei Componenti: Il Pattern di Composizione
Invece di un unico componente dashboard monolitico, l'interfaccia chat è costruita da pezzi piccoli, mirati e riutilizzabili.
┌─────────────────────────────────────────────┐
│ DashboardComponent │
│ (orchestratore, gestore dello stato, layout)│
├─────────────┬─────────────────┬─────────────┤
│ │ │ │
▼ ▼ ▼ ▼
ChatHistory ChatInput ChatMessage ChatActions
(messaggi) (textarea) (renderizzati) (bottoni)
│
└─ SourceBadge
DashboardComponent: L'Orchestratore
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [
/* ... */
ChatActionsComponent
],
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
encapsulation: ViewEncapsulation.None // Tematizzazione Material
})
export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked {
private destroy$ = new Subject();
private shouldScrollToBottom = false;
// Stato
messages: ChatMessage[] = [];
isLoading = false;
currentUser: IUser | null = null;
// Configurazione RAG
selectedModelId: number | null = null;
selectedContextId: number | null = null;
topK = 5;
rerank = false;
formattingMode: 'default' | 'structured' = 'default';
// Stato UI
@ViewChild('messagesContainer')
private messagesContainer!: ElementRef;
constructor(
/* ... */
private chatService: ChatService
) {}
ngOnInit() {
// ...
}
async sendMessage(question: string) {
// ...
}
ngAfterViewChecked() {
// ...
}
ngOnDestroy() {
// ...
}
}
ChatHistoryComponent: Visualizzazione dei Messaggi
@Component({
selector: 'app-chat-history',
standalone: true,
imports: [CommonModule, ChatMessageComponent],
template: `
`,
styles: [`
`]
})
export class ChatHistoryComponent {
@Input() messages: ChatMessage[] = [];
}
ChatInputComponent: Inserimento Domande
@Component({
selector: 'app-chat-input',
standalone: true,
imports: [CommonModule, FormsModule, MatButtonModule, MatIconModule],
template: `
`,
styles: [`
`]
})
export class ChatInputComponent {
@Output() submit = new EventEmitter();
@Input() isLoading = false;
question = '';
onSubmit() {
// ...
}
handleEnter(event: KeyboardEvent) {
// ...
}
}
ChatMessageComponent: Rendering con Citazioni
@Component({
selector: 'app-chat-message',
standalone: true,
imports: [CommonModule, MatButtonModule, MatIconModule],
template: `
`,
styles: [`
`]
})
export class ChatMessageComponent {
@Input() message!: ChatMessage;
@Output() sourceClick = new EventEmitter();
get isUser(): boolean {
// ...
}
formatResponse(response: string): string {
// ...
}
onSourceClick(ref: SourceReference) {
// ...
}
}
Gestione dello Stato: Pattern Store Service
Invece di NgRx (che può essere eccessivo per questo caso d'uso), il sistema RAG utilizza un pattern più semplice chiamato Store Service:
@Injectable({ providedIn: 'root' })
export class ChatStateService {
private messagesSubject = new BehaviorSubject([]);
private modelsSubject = new BehaviorSubject([]);
private contextsSubject = new BehaviorSubject([]);
messages$ = this.messagesSubject.asObservable();
models$ = this.modelsSubject.asObservable();
contexts$ = this.contextsSubject.asObservable();
addMessage(message: ChatMessage) {
// ...
}
loadContexts(contexts: IContext[]) {
// ...
}
clearMessages() {
// ...
}
}
Perché non NgRx?
- Troppo boilerplate per uno stato semplice
- Curva di apprendimento per i nuovi membri del team
- Complessità di debug con tracciamento azione/riduttore
- Store Service è 80/20: 80% dei benefici di NgRx, 20% della complessità
Per applicazioni complesse e multi-funzionalità con stato condiviso tra molti componenti, NgRx ha senso. Per un'interfaccia chat concentrata, Store Service è pragmatico.
Aggiornamenti in Tempo Reale & Risposte Streaming
Quando si interroga il backend, le risposte devono sembrare istantanee anche se l'LLM impiega 5+ secondi.
Pattern di Risposta Streaming
async streamQuery(question: string): Promise {
// Inizio con ack immediato
const tempMessage: ChatMessage = {/* ... */};
this.messages.push(tempMessage);
try {
/* ... */
this.shouldScrollToBottom = true;
} catch (error) {/* ... */}
}
Alternativamente, con SSE (Server-Sent Events):
streamQueryWithSSE(question: string) {
const eventSource = new EventSource(
/* ... */
);
let accumulatedResponse = '';
const messageIndex = this.messages.length - 1;
/* ... */
}
Switch di Contesto: Knowledge Base Multi-Dominio
Gli utenti possono passare tra diversi domini di conoscenza senza uscire dall'app.
export interface IContext {/* ... */}
UI per la selezione del contesto:
Quando il contesto cambia:
- Cancella la cronologia chat (o tagga i messaggi per contesto)
- Aggiorna
selectedContextId - Recupera modelli e impostazioni specifiche del contesto
- Resetta i parametri RAG ai default del contesto
selectContext(contextId: number) {/* ... */}
async loadContextSettings(contextId: number) {/* ... */}
Funzionalità Admin: Gestione Documenti & Contesti
Lo stesso frontend serve due audience: utenti normali (chat) e admin (gestione).
Struttura Tab Dashboard Admin
Componente Gestione Documenti
@Component({
/* ... */
`
})
export class DocumentManagementComponent implements OnInit {/* ... */}
Componente Gestione Contesti
@Component({
/* ... */
`
})
export class ContextManagementComponent implements OnInit {/* ... */}
Internazionalizzazione: Transloco per Multilingua
L'interfaccia chat supporta più lingue senza ricostruire l'app.
Setup i18n
File di traduzione (i18n/en.json):
{/* ... */}
Nei componenti:
Switch lingua:
switchLanguage(lang: string) {/* ... */}
Rendering UI Basato sui Ruoli
Non tutte le funzionalità sono per tutti. Gli admin vedono i tab di gestione, gli utenti no.
// Nel componente
get isAdmin(): boolean {
get canManageContexts(): boolean {
Gestione Errori & Feedback Utente
Gli errori devono essere informativi senza essere tecnici.
private handleError(error: any) {/* ... */}
Notifiche snackbar:
this.notificationService.success('Documento caricato con successo');
this.notificationService.error('Errore nel processare il documento');
this.notificationService.info('Documento in fase di indicizzazione...');
Ottimizzazioni Prestazionali
OnPush Change Detection
@Component({
/* ... */
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChatMessageComponent {/* ... */}
Virtual Scrolling (per cronologie chat lunghe)
Tab Lazy-Loaded
Carica i pannelli admin solo quando vengono aperti:
Material Design 3 & Tematizzazione
La UI si adatta al tema di sistema (chiaro/scuro) usando Material 3.
Configurazione tema (styles.scss):
@import '@angular/material/prebuilt-themes/indigo-pink.css';
// Tema custom (material.io theme builder)
.dark-theme {/* ... */}
.light-theme {/* ... */}
Toggle tema:
toggleTheme() {/* ... */}
Prossimi Passi
Il frontend porta il sistema RAG agli utenti. Nel prossimo articolo esploreremo il cambio provider LLM: come basta una configurazione per passare da API cloud a motori locali senza toccare il codice.
---
Key Takeaways:
✅ Componenti standalone = bundle più puliti e snelli
✅ Pattern composizione = pezzi piccoli, testabili, riutilizzabili
✅ Store Service = gestione stato semplice per questa scala
✅ Material Design 3 = UI moderna, accessibile, responsiva
✅ Transloco = i18n senza complessità
✅ Rendering basato sui ruoli = funzionalità diverse per utenti diversi
✅ OnPush + Virtual scrolling = velocissimo anche con cronologie lunghe
Il frontend è uno strato sottile e bello sopra un backend solido. Gli utenti non devono pensare all'architettura—devono solo fare domande e ottenere risposte.
---
GitHub:
- RAD System (open-source): Github source code
- RAG System (codice non disponibile): Github RAG System Overview