/** * Lego 03 / Lego 06 — RAG Chat via NestJS Socket Proxy * * Sends chat messages to NestJS over the existing /vision Socket.io connection. * NestJS forwards the message server-to-server to n8n (no CORS constraint), * then emits chat:result back here. * * This service does NOT open a second socket — it shares the /vision namespace * socket with VisionSocketService by injecting VisionSocketService and calling * its exposed socket methods. Instead, we open our own /vision socket here * specifically for chat so lifecycle is independently managed. * * Events: * emit → chat:send { message: string } * on ← chat:result * on ← chat:error { message: string } */ import { Injectable, signal, OnDestroy } from '@angular/core'; import { io, Socket } from 'socket.io-client'; import { environment } from '../../environments/environment'; export interface ChatResult { output?: string; answer?: string; response?: string; text?: string; [key: string]: any; } @Injectable({ providedIn: 'root' }) export class ChatSocketService implements OnDestroy { readonly sending = signal(false); readonly lastError = signal(null); private socket: Socket; constructor() { this.socket = io(`${environment.nestWsUrl}/vision`, { transports: ['websocket'], reconnection: true, reconnectionDelay: 1000, secure: true, rejectUnauthorized: false, }); } /** * Send a chat message to NestJS. NestJS proxies it to n8n server-to-server. * Returns a Promise that resolves with the n8n response or rejects on error. */ send(message: string): Promise { return new Promise((resolve, reject) => { this.sending.set(true); this.lastError.set(null); // One-shot listeners — removed immediately after first fire const onResult = (data: ChatResult) => { cleanup(); this.sending.set(false); resolve(data); }; const onError = (err: { message: string }) => { cleanup(); this.sending.set(false); const msg = err?.message ?? 'n8n proxy error'; this.lastError.set(msg); reject(new Error(msg)); }; const cleanup = () => { this.socket.off('chat:result', onResult); this.socket.off('chat:error', onError); }; this.socket.once('chat:result', onResult); this.socket.once('chat:error', onError); this.socket.emit('chat:send', { message }); }); } clearBackendSession(): void { this.socket.emit('chat:clear'); } ngOnDestroy(): void { this.socket.disconnect(); } }