|
|
@@ -1,15 +1,13 @@
|
|
|
/**
|
|
|
* Lego 03 / Lego 06 / Lego 14 — n8n RAG Chatbot Tab (Intelligence)
|
|
|
*
|
|
|
- * Sends user prompts to n8n via HTTP POST to the RAG webhook.
|
|
|
- * Displays conversation history with streamed or batch responses.
|
|
|
+ * Chat messages are sent to NestJS via Socket.io (chat:send).
|
|
|
+ * NestJS proxies them server-to-server to n8n — no CORS constraint.
|
|
|
+ * The n8n webhook URL lives in NestJS .env (N8N_WEBHOOK_URL).
|
|
|
*
|
|
|
- * Lego 06: Webhook URL is NOT hardcoded. It is read from localStorage
|
|
|
- * so the user can point it at any running n8n instance. A settings
|
|
|
- * panel inside this tab writes the URL back to localStorage on save.
|
|
|
- *
|
|
|
- * The n8n webhook internally triggers: LanceDB search → Ollama.
|
|
|
- * This component is the Angular entry point to that chain.
|
|
|
+ * Lego 06: The optional webhook URL in localStorage is kept only for the
|
|
|
+ * SurveillanceService pulse-check ("Test Connection"). It is no longer
|
|
|
+ * required for sending messages — the proxy handles routing.
|
|
|
*/
|
|
|
|
|
|
import {
|
|
|
@@ -21,18 +19,11 @@ import {
|
|
|
} from '@angular/core';
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
-import { HttpClient } from '@angular/common/http';
|
|
|
import { SurveillanceService } from '../../services/surveillance.service';
|
|
|
-import { environment } from '../../../environments/environment';
|
|
|
+import { ChatSocketService } from '../../services/chat-socket.service';
|
|
|
|
|
|
const WEBHOOK_STORAGE_KEY = 'n8n_webhook_url';
|
|
|
|
|
|
-/** True when the user has never saved a webhook URL to localStorage */
|
|
|
-function hasStoredWebhook(): boolean {
|
|
|
- const v = localStorage.getItem(WEBHOOK_STORAGE_KEY);
|
|
|
- return !!v && v.trim().length > 0;
|
|
|
-}
|
|
|
-
|
|
|
export interface ChatMessage {
|
|
|
role: 'user' | 'bot' | 'error';
|
|
|
text: string;
|
|
|
@@ -61,12 +52,8 @@ export class ChatbotComponent implements AfterViewChecked {
|
|
|
inputText = '';
|
|
|
loading = signal<boolean>(false);
|
|
|
|
|
|
- // Lego 06 — Webhook URL from localStorage only; no env fallback (forces setup)
|
|
|
- webhookUrl = signal<string>(
|
|
|
- localStorage.getItem(WEBHOOK_STORAGE_KEY) ?? ''
|
|
|
- );
|
|
|
- /** True until the user saves a URL — shows the Setup Required gate screen */
|
|
|
- setupRequired = signal<boolean>(!hasStoredWebhook());
|
|
|
+ // Lego 06 — Optional URL stored locally for pulse-check only (not used for sending)
|
|
|
+ webhookUrl = signal<string>(localStorage.getItem(WEBHOOK_STORAGE_KEY) ?? '');
|
|
|
webhookInputDraft = '';
|
|
|
showWebhookConfig = signal<boolean>(false);
|
|
|
|
|
|
@@ -76,7 +63,7 @@ export class ChatbotComponent implements AfterViewChecked {
|
|
|
testingConnection = signal<boolean>(false);
|
|
|
|
|
|
constructor(
|
|
|
- private http: HttpClient,
|
|
|
+ public chatSocket: ChatSocketService,
|
|
|
public surveillance: SurveillanceService,
|
|
|
) {
|
|
|
this.webhookInputDraft = this.webhookUrl();
|
|
|
@@ -98,48 +85,39 @@ export class ChatbotComponent implements AfterViewChecked {
|
|
|
this.testingConnection.set(false);
|
|
|
}
|
|
|
|
|
|
- sendMessage(): void {
|
|
|
+ async sendMessage(): Promise<void> {
|
|
|
const text = this.inputText.trim();
|
|
|
- if (!text || this.loading() || !this.webhookUrl()) return;
|
|
|
- if (this.surveillance.n8nStatus() === 'offline') return;
|
|
|
+ if (!text || this.loading() || this.chatSocket.sending()) return;
|
|
|
|
|
|
- // Push user message
|
|
|
+ // Push user message immediately
|
|
|
this.pushMessage({ role: 'user', text, timestamp: new Date() });
|
|
|
this.inputText = '';
|
|
|
this.loading.set(true);
|
|
|
|
|
|
const start = Date.now();
|
|
|
|
|
|
- this.http
|
|
|
- .post<any>(this.webhookUrl(), { query: text, prompt: text })
|
|
|
- .subscribe({
|
|
|
- next: (response) => {
|
|
|
- const durationMs = Date.now() - start;
|
|
|
- // n8n may return { output }, { answer }, { response }, or plain string
|
|
|
- const botText =
|
|
|
- response?.output ??
|
|
|
- response?.answer ??
|
|
|
- response?.response ??
|
|
|
- response?.text ??
|
|
|
- (typeof response === 'string' ? response : JSON.stringify(response));
|
|
|
-
|
|
|
- this.pushMessage({
|
|
|
- role: 'bot',
|
|
|
- text: botText,
|
|
|
- timestamp: new Date(),
|
|
|
- durationMs,
|
|
|
- });
|
|
|
- this.loading.set(false);
|
|
|
- },
|
|
|
- error: (err) => {
|
|
|
- this.pushMessage({
|
|
|
- role: 'error',
|
|
|
- text: `n8n unreachable: ${err.message ?? 'Unknown error'}. Is the workflow active on port 5678?`,
|
|
|
- timestamp: new Date(),
|
|
|
- });
|
|
|
- this.loading.set(false);
|
|
|
- },
|
|
|
+ try {
|
|
|
+ const response = await this.chatSocket.send(text);
|
|
|
+ const durationMs = Date.now() - start;
|
|
|
+
|
|
|
+ // n8n may return { output }, { answer }, { response }, { text }, or a raw string
|
|
|
+ const botText =
|
|
|
+ response?.output ??
|
|
|
+ response?.answer ??
|
|
|
+ response?.response ??
|
|
|
+ response?.text ??
|
|
|
+ (typeof response === 'string' ? response : JSON.stringify(response));
|
|
|
+
|
|
|
+ this.pushMessage({ role: 'bot', text: botText, timestamp: new Date(), durationMs });
|
|
|
+ } catch (err: any) {
|
|
|
+ this.pushMessage({
|
|
|
+ role: 'error',
|
|
|
+ text: `Proxy error: ${err.message ?? 'Unknown error'}. Check NestJS → n8n connection.`,
|
|
|
+ timestamp: new Date(),
|
|
|
});
|
|
|
+ } finally {
|
|
|
+ this.loading.set(false);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
onEnter(event: KeyboardEvent): void {
|
|
|
@@ -170,9 +148,8 @@ export class ChatbotComponent implements AfterViewChecked {
|
|
|
if (!url) return;
|
|
|
localStorage.setItem(WEBHOOK_STORAGE_KEY, url);
|
|
|
this.webhookUrl.set(url);
|
|
|
- this.setupRequired.set(false);
|
|
|
this.showWebhookConfig.set(false);
|
|
|
- // Immediately pulse-check the newly saved URL
|
|
|
+ // Immediately pulse-check the saved URL
|
|
|
this.surveillance.checkN8nStatus(url);
|
|
|
}
|
|
|
|