@vezlo/assistant-server 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/bootstrap/initializeServices.d.ts +2 -0
- package/dist/src/bootstrap/initializeServices.d.ts.map +1 -1
- package/dist/src/bootstrap/initializeServices.js +4 -1
- package/dist/src/bootstrap/initializeServices.js.map +1 -1
- package/dist/src/controllers/ChatController.d.ts +2 -12
- package/dist/src/controllers/ChatController.d.ts.map +1 -1
- package/dist/src/controllers/ChatController.js +38 -192
- package/dist/src/controllers/ChatController.js.map +1 -1
- package/dist/src/controllers/KnowledgeController.d.ts +8 -1
- package/dist/src/controllers/KnowledgeController.d.ts.map +1 -1
- package/dist/src/controllers/KnowledgeController.js +38 -1
- package/dist/src/controllers/KnowledgeController.js.map +1 -1
- package/dist/src/server.js +44 -1
- package/dist/src/server.js.map +1 -1
- package/dist/src/services/CitationService.d.ts +14 -0
- package/dist/src/services/CitationService.d.ts.map +1 -0
- package/dist/src/services/CitationService.js +76 -0
- package/dist/src/services/CitationService.js.map +1 -0
- package/dist/src/services/ResponseGenerationService.d.ts +65 -0
- package/dist/src/services/ResponseGenerationService.d.ts.map +1 -0
- package/dist/src/services/ResponseGenerationService.js +165 -0
- package/dist/src/services/ResponseGenerationService.js.map +1 -0
- package/dist/src/services/ResponseStreamingService.d.ts +33 -0
- package/dist/src/services/ResponseStreamingService.d.ts.map +1 -0
- package/dist/src/services/ResponseStreamingService.js +114 -0
- package/dist/src/services/ResponseStreamingService.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
2
|
import { UnifiedStorage } from '../storage/UnifiedStorage';
|
|
3
3
|
import { KnowledgeBaseService } from '../services/KnowledgeBaseService';
|
|
4
|
+
import { CitationService } from '../services/CitationService';
|
|
4
5
|
import { AIService } from '../services/AIService';
|
|
5
6
|
import { ChatManager } from '../services/ChatManager';
|
|
6
7
|
import { ChatController } from '../controllers/ChatController';
|
|
@@ -25,6 +26,7 @@ export interface InitializedCoreServices {
|
|
|
25
26
|
chatManager: ChatManager;
|
|
26
27
|
apiKeyService: ApiKeyService;
|
|
27
28
|
companyService: CompanyService;
|
|
29
|
+
citationService: CitationService;
|
|
28
30
|
};
|
|
29
31
|
controllers: {
|
|
30
32
|
chatController: ChatController;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initializeServices.d.ts","sourceRoot":"","sources":["../../../src/bootstrap/initializeServices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAIrE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE;QACR,OAAO,EAAE,cAAc,CAAC;QACxB,aAAa,EAAE,oBAAoB,CAAC;QACpC,SAAS,EAAE,SAAS,CAAC;QACrB,WAAW,EAAE,WAAW,CAAC;QACzB,aAAa,EAAE,aAAa,CAAC;QAC7B,cAAc,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"initializeServices.d.ts","sourceRoot":"","sources":["../../../src/bootstrap/initializeServices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAIrE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE;QACR,OAAO,EAAE,cAAc,CAAC;QACxB,aAAa,EAAE,oBAAoB,CAAC;QACpC,SAAS,EAAE,SAAS,CAAC;QACrB,WAAW,EAAE,WAAW,CAAC;QACzB,aAAa,EAAE,aAAa,CAAC;QAC7B,cAAc,EAAE,cAAc,CAAC;QAC/B,eAAe,EAAE,eAAe,CAAC;KAClC,CAAC;IACF,WAAW,EAAE;QACX,cAAc,EAAE,cAAc,CAAC;QAC/B,mBAAmB,EAAE,mBAAmB,CAAC;QACzC,cAAc,EAAE,cAAc,CAAC;QAC/B,gBAAgB,EAAE,gBAAgB,CAAC;QACnC,iBAAiB,EAAE,iBAAiB,CAAC;KACtC,CAAC;IACF,MAAM,EAAE;QACN,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AAKD,wBAAgB,oBAAoB,IAAI,MAAM,CAa7C;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,kBAAkB,GAAG,uBAAuB,CAiH3F"}
|
|
@@ -8,6 +8,7 @@ exports.initializeCoreServices = initializeCoreServices;
|
|
|
8
8
|
const logger_1 = __importDefault(require("../config/logger"));
|
|
9
9
|
const UnifiedStorage_1 = require("../storage/UnifiedStorage");
|
|
10
10
|
const KnowledgeBaseService_1 = require("../services/KnowledgeBaseService");
|
|
11
|
+
const CitationService_1 = require("../services/CitationService");
|
|
11
12
|
const AIService_1 = require("../services/AIService");
|
|
12
13
|
const ChatManager_1 = require("../services/ChatManager");
|
|
13
14
|
const ChatController_1 = require("../controllers/ChatController");
|
|
@@ -44,6 +45,7 @@ function initializeCoreServices(options) {
|
|
|
44
45
|
supabase,
|
|
45
46
|
tableName: knowledgeTableName
|
|
46
47
|
});
|
|
48
|
+
const citationService = new CitationService_1.CitationService(supabase, tablePrefix);
|
|
47
49
|
// Initialize V2 service for adjacent chunk retrieval
|
|
48
50
|
// Read AI configuration from environment
|
|
49
51
|
// Use AI_MODEL for all OpenAI calls (intent classification + response generation)
|
|
@@ -94,7 +96,7 @@ function initializeCoreServices(options) {
|
|
|
94
96
|
intentService,
|
|
95
97
|
realtimePublisher
|
|
96
98
|
});
|
|
97
|
-
const knowledgeController = new KnowledgeController_1.KnowledgeController(knowledgeBase, aiService);
|
|
99
|
+
const knowledgeController = new KnowledgeController_1.KnowledgeController(knowledgeBase, aiService, citationService);
|
|
98
100
|
const authController = new AuthController_1.AuthController(supabase);
|
|
99
101
|
const apiKeyService = new ApiKeyService_1.ApiKeyService(supabase);
|
|
100
102
|
const apiKeyController = new ApiKeyController_1.ApiKeyController(apiKeyService);
|
|
@@ -104,6 +106,7 @@ function initializeCoreServices(options) {
|
|
|
104
106
|
services: {
|
|
105
107
|
storage,
|
|
106
108
|
knowledgeBase,
|
|
109
|
+
citationService,
|
|
107
110
|
aiService,
|
|
108
111
|
chatManager,
|
|
109
112
|
apiKeyService,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initializeServices.js","sourceRoot":"","sources":["../../../src/bootstrap/initializeServices.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"initializeServices.js","sourceRoot":"","sources":["../../../src/bootstrap/initializeServices.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,8 @@ export declare class ChatController {
|
|
|
12
12
|
private chatHistoryLength;
|
|
13
13
|
private intentService?;
|
|
14
14
|
private realtimePublisher?;
|
|
15
|
+
private responseGenerationService;
|
|
16
|
+
private responseStreamingService;
|
|
15
17
|
constructor(chatManager: ChatManager, storage: UnifiedStorage, supabase: SupabaseClient, options?: {
|
|
16
18
|
historyLength?: number;
|
|
17
19
|
intentService?: IntentService;
|
|
@@ -30,18 +32,6 @@ export declare class ChatController {
|
|
|
30
32
|
deleteConversation(req: Request, res: Response): Promise<void>;
|
|
31
33
|
submitFeedback(req: Request | AuthenticatedRequest, res: Response): Promise<void>;
|
|
32
34
|
deleteFeedback(req: Request | AuthenticatedRequest, res: Response): Promise<void>;
|
|
33
|
-
private classifyIntent;
|
|
34
|
-
/**
|
|
35
|
-
* Handle intent classification result
|
|
36
|
-
* Returns response content if non-knowledge intent, null if knowledge intent
|
|
37
|
-
*/
|
|
38
|
-
private handleIntentResult;
|
|
39
|
-
/**
|
|
40
|
-
* Stream text content word by word to simulate streaming
|
|
41
|
-
* This ensures consistent SSE format for all responses
|
|
42
|
-
*/
|
|
43
|
-
private streamTextContent;
|
|
44
|
-
private getFallbackResponse;
|
|
45
35
|
private respondWithAssistantMessage;
|
|
46
36
|
private saveAssistantMessage;
|
|
47
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatController.d.ts","sourceRoot":"","sources":["../../../src/controllers/ChatController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"ChatController.d.ts","sourceRoot":"","sources":["../../../src/controllers/ChatController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAIlE,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,iBAAiB,CAAC,CAAoB;IAC9C,OAAO,CAAC,yBAAyB,CAA4B;IAC7D,OAAO,CAAC,wBAAwB,CAA2B;gBAGzD,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,aAAa,CAAC;QAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;KAAO;IAqB1G,kBAAkB,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA4I3E,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAyF7D,gBAAgB,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAgLzE,eAAe,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAuDxE,uBAAuB,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA4DhF,gBAAgB,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAuGzE,iBAAiB,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAoG1E,mBAAmB,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA6E5E,gBAAgB,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAmGzE,oBAAoB,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAoE7E,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB9D,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAgEjF,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;YAmCzE,2BAA2B;YA4B3B,oBAAoB;CA4EnC"}
|
|
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ChatController = void 0;
|
|
7
7
|
const logger_1 = __importDefault(require("../config/logger"));
|
|
8
|
+
const ResponseGenerationService_1 = require("../services/ResponseGenerationService");
|
|
9
|
+
const ResponseStreamingService_1 = require("../services/ResponseStreamingService");
|
|
8
10
|
class ChatController {
|
|
9
11
|
constructor(chatManager, storage, supabase, options = {}) {
|
|
10
12
|
this.chatManager = chatManager;
|
|
@@ -14,6 +16,10 @@ class ChatController {
|
|
|
14
16
|
this.chatHistoryLength = typeof historyLength === 'number' && historyLength > 0 ? historyLength : 2;
|
|
15
17
|
this.intentService = options.intentService;
|
|
16
18
|
this.realtimePublisher = options.realtimePublisher;
|
|
19
|
+
// Initialize services
|
|
20
|
+
const aiService = chatManager.aiService;
|
|
21
|
+
this.responseGenerationService = new ResponseGenerationService_1.ResponseGenerationService(this.intentService, aiService, this.chatHistoryLength);
|
|
22
|
+
this.responseStreamingService = new ResponseStreamingService_1.ResponseStreamingService();
|
|
17
23
|
}
|
|
18
24
|
// Create a new conversation
|
|
19
25
|
async createConversation(req, res) {
|
|
@@ -222,10 +228,16 @@ class ChatController {
|
|
|
222
228
|
}
|
|
223
229
|
// Generate AI response for a user message
|
|
224
230
|
async generateResponse(req, res) {
|
|
231
|
+
const { uuid } = req.params;
|
|
232
|
+
if (!uuid) {
|
|
233
|
+
res.status(400).json({ error: 'Message UUID is required' });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// After validation, uuid is guaranteed to be a string - use type assertion
|
|
237
|
+
const messageUuid = uuid;
|
|
225
238
|
try {
|
|
226
|
-
const { uuid } = req.params;
|
|
227
239
|
// Get the user message by ID using the repository
|
|
228
|
-
const userMessage = await this.storage.getMessageById(
|
|
240
|
+
const userMessage = await this.storage.getMessageById(messageUuid);
|
|
229
241
|
if (!userMessage) {
|
|
230
242
|
res.status(404).json({ error: 'Message not found' });
|
|
231
243
|
return;
|
|
@@ -235,7 +247,7 @@ class ChatController {
|
|
|
235
247
|
// Get conversation context (recent messages)
|
|
236
248
|
// Exclude the current user message to avoid duplication (it's added separately as the query)
|
|
237
249
|
const allMessages = await this.chatManager.getRecentMessages(conversationId, this.chatHistoryLength + 1);
|
|
238
|
-
const messages = allMessages.filter(msg => msg.id !==
|
|
250
|
+
const messages = allMessages.filter(msg => msg.id !== messageUuid).slice(-this.chatHistoryLength);
|
|
239
251
|
logger_1.default.info(`📜 Retrieved ${messages.length} message(s) from conversation history (limit: ${this.chatHistoryLength})`);
|
|
240
252
|
const conversation = await this.storage.getConversation(conversationId);
|
|
241
253
|
// Check if conversation has been joined by an agent
|
|
@@ -246,140 +258,57 @@ class ChatController {
|
|
|
246
258
|
});
|
|
247
259
|
return;
|
|
248
260
|
}
|
|
249
|
-
// Set up Server-Sent Events (SSE) headers for streaming
|
|
250
|
-
|
|
251
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
252
|
-
res.setHeader('Connection', 'keep-alive');
|
|
253
|
-
res.setHeader('X-Accel-Buffering', 'no'); // Disable nginx buffering
|
|
261
|
+
// Set up Server-Sent Events (SSE) headers for streaming
|
|
262
|
+
this.responseStreamingService.setupSSEHeaders(res);
|
|
254
263
|
// Run intent classification to decide handling strategy
|
|
255
|
-
const intentResult = await this.classifyIntent(userMessageContent, messages);
|
|
256
|
-
const intentResponse =
|
|
264
|
+
const intentResult = await this.responseGenerationService.classifyIntent(userMessageContent, messages);
|
|
265
|
+
const intentResponse = this.responseGenerationService.handleIntentResult(intentResult, userMessageContent);
|
|
257
266
|
let accumulatedContent = '';
|
|
258
267
|
let assistantMessageId;
|
|
268
|
+
let sources = [];
|
|
259
269
|
try {
|
|
260
270
|
// If intent returned a response (non-knowledge intent), stream it
|
|
261
271
|
if (intentResponse) {
|
|
262
272
|
logger_1.default.info(`📤 Streaming intent response for: ${intentResult.intent}`);
|
|
263
|
-
await this.streamTextContent(intentResponse, res);
|
|
273
|
+
await this.responseStreamingService.streamTextContent(intentResponse, res);
|
|
264
274
|
accumulatedContent = intentResponse;
|
|
265
275
|
}
|
|
266
276
|
else {
|
|
267
277
|
// Knowledge intent - proceed with RAG flow and stream AI response
|
|
268
278
|
logger_1.default.info('📚 Streaming knowledge-based response');
|
|
269
|
-
// Get knowledge base search results if available
|
|
270
|
-
const aiService = this.chatManager.aiService;
|
|
271
|
-
let knowledgeResults = null;
|
|
272
279
|
// Get conversation to extract company_id for knowledge base search
|
|
273
280
|
const companyIdRaw = req.profile?.companyId || conversation?.organizationId;
|
|
274
281
|
const companyId = companyIdRaw ? (typeof companyIdRaw === 'string' ? parseInt(companyIdRaw, 10) : companyIdRaw) : undefined;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const searchResults = await aiService.knowledgeBaseService.search(userMessageContent, {
|
|
279
|
-
limit: 5,
|
|
280
|
-
company_id: companyId
|
|
281
|
-
});
|
|
282
|
-
logger_1.default.info(`📊 Found knowledge base results: ${searchResults.length}`);
|
|
283
|
-
if (searchResults.length > 0) {
|
|
284
|
-
knowledgeResults = '\n\nRelevant information from knowledge base:\n';
|
|
285
|
-
searchResults.forEach((result) => {
|
|
286
|
-
const title = result.title || 'Untitled';
|
|
287
|
-
const content = result.content || '';
|
|
288
|
-
if (content.trim()) {
|
|
289
|
-
knowledgeResults += `- ${title}: ${content}\n`;
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
// Verify we actually have meaningful content (not just the header)
|
|
293
|
-
const headerLength = '\n\nRelevant information from knowledge base:\n'.length;
|
|
294
|
-
if (knowledgeResults.length > headerLength + 10) {
|
|
295
|
-
logger_1.default.info(`✅ Knowledge context prepared (${knowledgeResults.length} chars, ${searchResults.length} results)`);
|
|
296
|
-
// Log first 200 chars for debugging
|
|
297
|
-
logger_1.default.info(`📝 Knowledge preview: ${knowledgeResults.substring(0, 200)}...`);
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
logger_1.default.warn(`⚠️ Knowledge results found but content is empty or too short (${knowledgeResults.length} chars), treating as no results`);
|
|
301
|
-
knowledgeResults = '';
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
knowledgeResults = '';
|
|
306
|
-
logger_1.default.info('⚠️ No knowledge base results found; will return appropriate fallback response');
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
catch (error) {
|
|
310
|
-
console.error('❌ Failed to search knowledge base:', error);
|
|
311
|
-
logger_1.default.error('Failed to search knowledge base:', error);
|
|
312
|
-
knowledgeResults = null;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
logger_1.default.warn('⚠️ AI service or knowledge base service not available');
|
|
317
|
-
}
|
|
282
|
+
// Search knowledge base and extract sources
|
|
283
|
+
const { knowledgeResults, sources: extractedSources } = await this.responseGenerationService.searchKnowledgeBase(userMessageContent, companyId);
|
|
284
|
+
sources = extractedSources;
|
|
318
285
|
// Build context for AI
|
|
319
|
-
const chatContext =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
knowledgeResults: knowledgeResults ?? undefined
|
|
325
|
-
};
|
|
286
|
+
const chatContext = this.responseGenerationService.buildChatContext(messages, knowledgeResults);
|
|
287
|
+
const aiService = this.responseGenerationService.getAIService();
|
|
288
|
+
if (!aiService) {
|
|
289
|
+
throw new Error('AI service not available');
|
|
290
|
+
}
|
|
326
291
|
// Stream response from OpenAI
|
|
327
|
-
logger_1.default.info('🔄 Starting OpenAI stream...');
|
|
328
292
|
const stream = aiService.generateResponseStream(userMessageContent, chatContext);
|
|
329
|
-
|
|
330
|
-
for await (const { chunk, done, fullContent } of stream) {
|
|
331
|
-
chunkCount++;
|
|
332
|
-
// Always send the chunk (even if empty with done flag)
|
|
333
|
-
const chunkData = JSON.stringify({
|
|
334
|
-
type: 'chunk',
|
|
335
|
-
content: chunk,
|
|
336
|
-
done: done || false // Include done flag
|
|
337
|
-
});
|
|
338
|
-
res.write(`data: ${chunkData}\n\n`);
|
|
339
|
-
if (res.flush)
|
|
340
|
-
res.flush();
|
|
341
|
-
// Update accumulated content
|
|
342
|
-
if (chunk) {
|
|
343
|
-
accumulatedContent += chunk;
|
|
344
|
-
}
|
|
345
|
-
// Log first and last chunks
|
|
346
|
-
if (chunkCount === 1) {
|
|
347
|
-
logger_1.default.info(`📤 First chunk sent: "${chunk.substring(0, 30)}..."`);
|
|
348
|
-
}
|
|
349
|
-
if (done && fullContent) {
|
|
350
|
-
accumulatedContent = fullContent;
|
|
351
|
-
logger_1.default.info(`🏁 Stream complete: ${chunkCount} chunks sent, ${fullContent.length} total chars`);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
293
|
+
accumulatedContent = await this.responseStreamingService.streamAIResponse(stream, res, sources, knowledgeResults);
|
|
354
294
|
}
|
|
355
295
|
// Save the message after streaming completes
|
|
356
296
|
try {
|
|
357
297
|
const assistantMessage = await this.saveAssistantMessage({
|
|
358
298
|
conversation,
|
|
359
299
|
conversationId,
|
|
360
|
-
parentMessageId:
|
|
300
|
+
parentMessageId: messageUuid,
|
|
361
301
|
content: accumulatedContent,
|
|
362
302
|
toolResults: []
|
|
363
303
|
});
|
|
364
304
|
assistantMessageId = assistantMessage.id;
|
|
365
|
-
// Send completion event with final message metadata
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
uuid: assistantMessage.id,
|
|
369
|
-
parent_message_uuid: uuid,
|
|
370
|
-
status: 'completed',
|
|
371
|
-
created_at: assistantMessage.createdAt.toISOString()
|
|
372
|
-
});
|
|
373
|
-
res.write(`data: ${completionData}\n\n`);
|
|
305
|
+
// Send completion event with final message metadata
|
|
306
|
+
// Use non-null assertion since both are guaranteed to exist at this point
|
|
307
|
+
this.responseStreamingService.sendCompletionEvent(res, assistantMessage.id, messageUuid, assistantMessage.createdAt);
|
|
374
308
|
}
|
|
375
309
|
catch (saveError) {
|
|
376
310
|
logger_1.default.error('Failed to save assistant message:', saveError);
|
|
377
|
-
|
|
378
|
-
type: 'error',
|
|
379
|
-
error: 'Failed to save message',
|
|
380
|
-
message: saveError instanceof Error ? saveError.message : 'Unknown error'
|
|
381
|
-
});
|
|
382
|
-
res.write(`data: ${errorData}\n\n`);
|
|
311
|
+
this.responseStreamingService.sendErrorEvent(res, 'Failed to save message', saveError instanceof Error ? saveError.message : 'Unknown error');
|
|
383
312
|
}
|
|
384
313
|
// Close the stream
|
|
385
314
|
res.end();
|
|
@@ -388,12 +317,7 @@ class ChatController {
|
|
|
388
317
|
logger_1.default.error('Streaming error:', streamError);
|
|
389
318
|
// Try to send error to client if connection is still open
|
|
390
319
|
try {
|
|
391
|
-
|
|
392
|
-
type: 'error',
|
|
393
|
-
error: 'Failed to generate response',
|
|
394
|
-
message: streamError instanceof Error ? streamError.message : 'Unknown error'
|
|
395
|
-
});
|
|
396
|
-
res.write(`data: ${errorData}\n\n`);
|
|
320
|
+
this.responseStreamingService.sendErrorEvent(res, 'Failed to generate response', streamError instanceof Error ? streamError.message : 'Unknown error');
|
|
397
321
|
res.end();
|
|
398
322
|
}
|
|
399
323
|
catch (writeError) {
|
|
@@ -406,7 +330,7 @@ class ChatController {
|
|
|
406
330
|
await this.saveAssistantMessage({
|
|
407
331
|
conversation,
|
|
408
332
|
conversationId,
|
|
409
|
-
parentMessageId:
|
|
333
|
+
parentMessageId: messageUuid,
|
|
410
334
|
content: accumulatedContent,
|
|
411
335
|
toolResults: []
|
|
412
336
|
});
|
|
@@ -429,12 +353,7 @@ class ChatController {
|
|
|
429
353
|
else {
|
|
430
354
|
// Headers already sent, try to send SSE error
|
|
431
355
|
try {
|
|
432
|
-
|
|
433
|
-
type: 'error',
|
|
434
|
-
error: 'Failed to generate response',
|
|
435
|
-
message: error instanceof Error ? error.message : 'Unknown error'
|
|
436
|
-
});
|
|
437
|
-
res.write(`data: ${errorData}\n\n`);
|
|
356
|
+
this.responseStreamingService.sendErrorEvent(res, 'Failed to generate response', error instanceof Error ? error.message : 'Unknown error');
|
|
438
357
|
res.end();
|
|
439
358
|
}
|
|
440
359
|
catch (writeError) {
|
|
@@ -1032,79 +951,6 @@ class ChatController {
|
|
|
1032
951
|
});
|
|
1033
952
|
}
|
|
1034
953
|
}
|
|
1035
|
-
async classifyIntent(message, history) {
|
|
1036
|
-
if (!this.intentService) {
|
|
1037
|
-
return {
|
|
1038
|
-
intent: 'knowledge',
|
|
1039
|
-
needsGuardrail: false,
|
|
1040
|
-
contactEmail: null
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
const resolvedHistory = Array.isArray(history) ? history : [];
|
|
1044
|
-
logger_1.default.info('🧭 Classifying user intent...');
|
|
1045
|
-
return this.intentService.classify({
|
|
1046
|
-
message,
|
|
1047
|
-
conversationHistory: resolvedHistory
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Handle intent classification result
|
|
1052
|
-
* Returns response content if non-knowledge intent, null if knowledge intent
|
|
1053
|
-
*/
|
|
1054
|
-
async handleIntentResult(result, userMessage, conversationId, conversation) {
|
|
1055
|
-
if (result.needsGuardrail && result.intent !== 'guardrail') {
|
|
1056
|
-
logger_1.default.info('🛡️ Guardrail triggered');
|
|
1057
|
-
return `I can help with documentation or implementation guidance, but I can't share credentials or confidential configuration. Please contact your system administrator or support for access.`;
|
|
1058
|
-
}
|
|
1059
|
-
logger_1.default.info(`🧾 Intent result: ${result.intent}${result.needsGuardrail ? ' (guardrail triggered)' : ''}`);
|
|
1060
|
-
// For non-knowledge intents, return the response content to be streamed
|
|
1061
|
-
if (result.intent !== 'knowledge') {
|
|
1062
|
-
const responseContent = result.response || this.getFallbackResponse(result.intent);
|
|
1063
|
-
return responseContent;
|
|
1064
|
-
}
|
|
1065
|
-
// Knowledge intent - proceed to RAG flow (return null to indicate streaming will happen later)
|
|
1066
|
-
logger_1.default.info('📚 Intent requires knowledge lookup; proceeding with RAG flow.');
|
|
1067
|
-
return null;
|
|
1068
|
-
}
|
|
1069
|
-
/**
|
|
1070
|
-
* Stream text content word by word to simulate streaming
|
|
1071
|
-
* This ensures consistent SSE format for all responses
|
|
1072
|
-
*/
|
|
1073
|
-
async streamTextContent(content, res) {
|
|
1074
|
-
const words = content.split(' ');
|
|
1075
|
-
const chunkSize = 2; // Stream 2 words at a time for smoother experience
|
|
1076
|
-
const totalChunks = Math.ceil(words.length / chunkSize);
|
|
1077
|
-
for (let i = 0; i < words.length; i += chunkSize) {
|
|
1078
|
-
const chunk = words.slice(i, i + chunkSize).join(' ') + (i + chunkSize < words.length ? ' ' : '');
|
|
1079
|
-
const chunkIndex = Math.floor(i / chunkSize) + 1;
|
|
1080
|
-
const isLastChunk = chunkIndex === totalChunks;
|
|
1081
|
-
const chunkData = JSON.stringify({
|
|
1082
|
-
type: 'chunk',
|
|
1083
|
-
content: chunk,
|
|
1084
|
-
done: isLastChunk // Mark last chunk with done: true
|
|
1085
|
-
});
|
|
1086
|
-
res.write(`data: ${chunkData}\n\n`);
|
|
1087
|
-
// Flush the response to ensure chunks are sent immediately
|
|
1088
|
-
if (res.flush) {
|
|
1089
|
-
res.flush();
|
|
1090
|
-
}
|
|
1091
|
-
// Delay for smooth streaming effect (30ms for better visibility)
|
|
1092
|
-
await new Promise(resolve => setTimeout(resolve, 30));
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
getFallbackResponse(intent) {
|
|
1096
|
-
// Fallback responses in case LLM doesn't generate one (shouldn't happen, but safety net)
|
|
1097
|
-
const fallbacks = {
|
|
1098
|
-
greeting: 'Hello! How can I help you today?',
|
|
1099
|
-
acknowledgment: "You're welcome! Let me know if you need anything else.",
|
|
1100
|
-
personality: `I'm ${process.env.ASSISTANT_NAME || 'AI Assistant'}, your AI assistant for ${process.env.ORGANIZATION_NAME || 'Your Organization'}.`,
|
|
1101
|
-
clarification: "I'm not sure I understood. Could you clarify what you need help with?",
|
|
1102
|
-
guardrail: "I can help with documentation or implementation guidance, but I can't share credentials or confidential configuration.",
|
|
1103
|
-
human_support_request: "I'd be happy to connect you with our support team. Could you please provide your email address?",
|
|
1104
|
-
human_support_email: "Thank you! Our support team will reach out to you shortly."
|
|
1105
|
-
};
|
|
1106
|
-
return fallbacks[intent] || "I'm here to help. What would you like to know?";
|
|
1107
|
-
}
|
|
1108
954
|
async respondWithAssistantMessage(payload, res) {
|
|
1109
955
|
const assistantMessage = await this.saveAssistantMessage({
|
|
1110
956
|
conversation: payload.conversation,
|