@vezlo/assistant-server 2.6.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.
Files changed (47) hide show
  1. package/README.md +5 -1
  2. package/database-schema.sql +6 -0
  3. package/dist/src/bootstrap/initializeServices.d.ts +2 -0
  4. package/dist/src/bootstrap/initializeServices.d.ts.map +1 -1
  5. package/dist/src/bootstrap/initializeServices.js +4 -1
  6. package/dist/src/bootstrap/initializeServices.js.map +1 -1
  7. package/dist/src/controllers/ChatController.d.ts +3 -12
  8. package/dist/src/controllers/ChatController.d.ts.map +1 -1
  9. package/dist/src/controllers/ChatController.js +121 -203
  10. package/dist/src/controllers/ChatController.js.map +1 -1
  11. package/dist/src/controllers/KnowledgeController.d.ts +8 -1
  12. package/dist/src/controllers/KnowledgeController.d.ts.map +1 -1
  13. package/dist/src/controllers/KnowledgeController.js +38 -1
  14. package/dist/src/controllers/KnowledgeController.js.map +1 -1
  15. package/dist/src/migrations/009_add_archived_at_column.d.ts +4 -0
  16. package/dist/src/migrations/009_add_archived_at_column.d.ts.map +1 -0
  17. package/dist/src/migrations/009_add_archived_at_column.js +17 -0
  18. package/dist/src/migrations/009_add_archived_at_column.js.map +1 -0
  19. package/dist/src/schemas/ConversationSchemas.d.ts +10 -0
  20. package/dist/src/schemas/ConversationSchemas.d.ts.map +1 -1
  21. package/dist/src/schemas/ConversationSchemas.js +4 -2
  22. package/dist/src/schemas/ConversationSchemas.js.map +1 -1
  23. package/dist/src/schemas/index.d.ts +10 -0
  24. package/dist/src/schemas/index.d.ts.map +1 -1
  25. package/dist/src/server.js +79 -1
  26. package/dist/src/server.js.map +1 -1
  27. package/dist/src/services/CitationService.d.ts +14 -0
  28. package/dist/src/services/CitationService.d.ts.map +1 -0
  29. package/dist/src/services/CitationService.js +76 -0
  30. package/dist/src/services/CitationService.js.map +1 -0
  31. package/dist/src/services/ResponseGenerationService.d.ts +65 -0
  32. package/dist/src/services/ResponseGenerationService.d.ts.map +1 -0
  33. package/dist/src/services/ResponseGenerationService.js +165 -0
  34. package/dist/src/services/ResponseGenerationService.js.map +1 -0
  35. package/dist/src/services/ResponseStreamingService.d.ts +33 -0
  36. package/dist/src/services/ResponseStreamingService.d.ts.map +1 -0
  37. package/dist/src/services/ResponseStreamingService.js +114 -0
  38. package/dist/src/services/ResponseStreamingService.js.map +1 -0
  39. package/dist/src/storage/ConversationRepository.d.ts.map +1 -1
  40. package/dist/src/storage/ConversationRepository.js +10 -1
  41. package/dist/src/storage/ConversationRepository.js.map +1 -1
  42. package/dist/src/storage/SupabaseStorage.d.ts.map +1 -1
  43. package/dist/src/storage/SupabaseStorage.js +7 -1
  44. package/dist/src/storage/SupabaseStorage.js.map +1 -1
  45. package/dist/src/types/index.d.ts +2 -0
  46. package/dist/src/types/index.d.ts.map +1 -1
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # Vezlo AI Assistant Server
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@vezlo/assistant-server.svg)](https://www.npmjs.com/package/@vezlo/assistant-server) [![license](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](https://opensource.org/licenses/AGPL-3.0)
4
+
3
5
  ๐Ÿš€ **Production-ready Node.js/TypeScript API server** for the Vezlo AI Assistant platform - Complete backend APIs with advanced RAG (chunk-based semantic search + adjacent retrieval), Docker deployment, and database migrations.
4
6
 
7
+ **๐Ÿ“‹ [Changelog](./CHANGELOG.md)** | **๐Ÿ› [Report Issue](https://github.com/vezlo/assistant-server/issues)** | **๐Ÿ’ฌ [Discussions](https://github.com/vezlo/assistant-server/discussions)**
8
+
5
9
  ## ๐Ÿšจ Breaking Change Notice
6
10
 
7
11
  ### v2.3.0 - Enhanced RAG System
@@ -723,4 +727,4 @@ This project is dual-licensed:
723
727
 
724
728
  ---
725
729
 
726
- **Status**: โœ… Production Ready | **Version**: 2.6.0 | **Node.js**: 20+ | **TypeScript**: 5+
730
+ **Status**: โœ… Production Ready | **Version**: 2.8.0 | **Node.js**: 20+ | **TypeScript**: 5+
@@ -58,6 +58,10 @@ INSERT INTO knex_migrations (name, batch, migration_time)
58
58
  SELECT '008_add_conversation_stats_rpc.ts', 1, NOW()
59
59
  WHERE NOT EXISTS (SELECT 1 FROM knex_migrations WHERE name = '008_add_conversation_stats_rpc.ts');
60
60
 
61
+ INSERT INTO knex_migrations (name, batch, migration_time)
62
+ SELECT '009_add_archived_at_column.ts', 1, NOW()
63
+ WHERE NOT EXISTS (SELECT 1 FROM knex_migrations WHERE name = '009_add_archived_at_column.ts');
64
+
61
65
  -- Set migration lock to unlocked (0 = unlocked, 1 = locked)
62
66
  INSERT INTO knex_migrations_lock (index, is_locked)
63
67
  VALUES (1, 0)
@@ -125,6 +129,7 @@ CREATE TABLE IF NOT EXISTS vezlo_conversations (
125
129
  joined_at TIMESTAMPTZ,
126
130
  responded_at TIMESTAMPTZ,
127
131
  closed_at TIMESTAMPTZ,
132
+ archived_at TIMESTAMPTZ,
128
133
  last_message_at TIMESTAMPTZ,
129
134
  created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
130
135
  updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
@@ -284,6 +289,7 @@ CREATE INDEX IF NOT EXISTS idx_vezlo_conversations_updated_at ON vezlo_conversat
284
289
  CREATE INDEX IF NOT EXISTS idx_vezlo_conversations_last_message_at ON vezlo_conversations(last_message_at DESC);
285
290
  CREATE INDEX IF NOT EXISTS idx_vezlo_conversations_joined_at ON vezlo_conversations(joined_at);
286
291
  CREATE INDEX IF NOT EXISTS idx_vezlo_conversations_closed_at ON vezlo_conversations(closed_at);
292
+ CREATE INDEX IF NOT EXISTS idx_vezlo_conversations_archived_at ON vezlo_conversations(archived_at);
287
293
 
288
294
  -- Messages indexes
289
295
  CREATE INDEX IF NOT EXISTS idx_vezlo_messages_uuid ON vezlo_messages(uuid);
@@ -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;KAChC,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,CA8G3F"}
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":"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"}
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;
@@ -24,23 +26,12 @@ export declare class ChatController {
24
26
  getConversationMessages(req: AuthenticatedRequest, res: Response): Promise<void>;
25
27
  joinConversation(req: AuthenticatedRequest, res: Response): Promise<void>;
26
28
  closeConversation(req: AuthenticatedRequest, res: Response): Promise<void>;
29
+ archiveConversation(req: AuthenticatedRequest, res: Response): Promise<void>;
27
30
  sendAgentMessage(req: AuthenticatedRequest, res: Response): Promise<void>;
28
31
  getUserConversations(req: AuthenticatedRequest, res: Response): Promise<void>;
29
32
  deleteConversation(req: Request, res: Response): Promise<void>;
30
33
  submitFeedback(req: Request | AuthenticatedRequest, res: Response): Promise<void>;
31
34
  deleteFeedback(req: Request | AuthenticatedRequest, res: Response): Promise<void>;
32
- private classifyIntent;
33
- /**
34
- * Handle intent classification result
35
- * Returns response content if non-knowledge intent, null if knowledge intent
36
- */
37
- private handleIntentResult;
38
- /**
39
- * Stream text content word by word to simulate streaming
40
- * This ensures consistent SSE format for all responses
41
- */
42
- private streamTextContent;
43
- private getFallbackResponse;
44
35
  private respondWithAssistantMessage;
45
36
  private saveAssistantMessage;
46
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,EAA8B,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE,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;gBAG5C,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;IAY1G,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;IAsPzE,eAAe,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDxE,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,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;IA8D7E,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;YAkCzE,cAAc;IAmB5B;;;OAGG;YACW,kBAAkB;IAwBhC;;;OAGG;YACW,iBAAiB;IA2B/B,OAAO,CAAC,mBAAmB;YAeb,2BAA2B;YA4B3B,oBAAoB;CA4EnC"}
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(uuid);
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 !== uuid).slice(-this.chatHistoryLength);
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 (always stream, consistent format)
250
- res.setHeader('Content-Type', 'text/event-stream');
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 = await this.handleIntentResult(intentResult, userMessage, conversationId, conversation);
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
- if (aiService && aiService.knowledgeBaseService) {
276
- try {
277
- logger_1.default.info(`๐Ÿ” Searching KB: query="${userMessageContent.substring(0, 50)}...", companyId=${companyId}`);
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
- conversationHistory: messages.map(msg => ({
321
- role: msg.role,
322
- content: msg.content
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
- let chunkCount = 0;
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: uuid,
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 (no content - already streamed)
366
- const completionData = JSON.stringify({
367
- type: 'completion',
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
- const errorData = JSON.stringify({
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
- const errorData = JSON.stringify({
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: uuid,
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
- const errorData = JSON.stringify({
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) {
@@ -462,11 +381,13 @@ class ChatController {
462
381
  return;
463
382
  }
464
383
  const toIso = (date) => (date ? date.toISOString() : null);
465
- const status = conversation.closedAt
466
- ? 'closed'
467
- : conversation.joinedAt
468
- ? 'in_progress'
469
- : 'open';
384
+ const status = conversation.archivedAt
385
+ ? 'archived'
386
+ : conversation.closedAt
387
+ ? 'closed'
388
+ : conversation.joinedAt
389
+ ? 'in_progress'
390
+ : 'open';
470
391
  res.json({
471
392
  uuid: conversation.id,
472
393
  title: conversation.title,
@@ -478,6 +399,7 @@ class ChatController {
478
399
  joined_at: toIso(conversation.joinedAt),
479
400
  responded_at: toIso(conversation.respondedAt),
480
401
  closed_at: toIso(conversation.closedAt),
402
+ archived_at: toIso(conversation.archivedAt),
481
403
  last_message_at: toIso(conversation.lastMessageAt),
482
404
  status
483
405
  });
@@ -718,6 +640,69 @@ class ChatController {
718
640
  });
719
641
  }
720
642
  }
643
+ // Archive conversation
644
+ async archiveConversation(req, res) {
645
+ try {
646
+ if (!req.user || !req.profile) {
647
+ res.status(401).json({ error: 'Authentication required' });
648
+ return;
649
+ }
650
+ const { uuid } = req.params;
651
+ const conversation = await this.storage.getConversation(uuid);
652
+ if (!conversation) {
653
+ res.status(404).json({ error: 'Conversation not found' });
654
+ return;
655
+ }
656
+ if (conversation.organizationId !== req.profile.companyId) {
657
+ res.status(404).json({ error: 'Conversation not found' });
658
+ return;
659
+ }
660
+ if (!conversation.closedAt) {
661
+ res.status(400).json({ error: 'Conversation must be closed before archiving' });
662
+ return;
663
+ }
664
+ if (conversation.archivedAt) {
665
+ res.status(400).json({ error: 'Conversation is already archived' });
666
+ return;
667
+ }
668
+ const archivedAt = new Date();
669
+ await this.storage.updateConversation(uuid, {
670
+ archivedAt
671
+ });
672
+ if (this.realtimePublisher) {
673
+ try {
674
+ const { data: company } = await this.supabase
675
+ .from('vezlo_companies')
676
+ .select('uuid')
677
+ .eq('id', conversation.organizationId)
678
+ .single();
679
+ if (company?.uuid) {
680
+ await this.realtimePublisher.publish(`company:${company.uuid}:conversations`, 'conversation:archived', {
681
+ conversation_uuid: uuid,
682
+ conversation_update: {
683
+ archived_at: archivedAt.toISOString(),
684
+ status: 'archived'
685
+ }
686
+ });
687
+ }
688
+ }
689
+ catch (error) {
690
+ logger_1.default.error('[ChatController] Failed to publish archive conversation update:', error);
691
+ }
692
+ }
693
+ res.json({
694
+ success: true,
695
+ archived_at: archivedAt.toISOString()
696
+ });
697
+ }
698
+ catch (error) {
699
+ logger_1.default.error('Archive conversation error:', error);
700
+ res.status(500).json({
701
+ error: 'Failed to archive conversation',
702
+ message: error instanceof Error ? error.message : 'Unknown error'
703
+ });
704
+ }
705
+ }
721
706
  // Send agent message
722
707
  async sendAgentMessage(req, res) {
723
708
  try {
@@ -815,10 +800,13 @@ class ChatController {
815
800
  const offset = (page - 1) * pageSize;
816
801
  const orderParam = req.query.order_by || 'last_message_at';
817
802
  const orderBy = orderParam === 'created_at' ? 'updated_at' : 'last_message_at';
803
+ const statusParam = req.query.status;
804
+ const status = statusParam === 'archived' ? 'archived' : statusParam === 'active' ? 'active' : undefined;
818
805
  const { conversations, total } = await this.storage.getUserConversations(req.user.id, req.profile.companyId, {
819
806
  limit: pageSize,
820
807
  offset,
821
- orderBy: orderBy
808
+ orderBy: orderBy,
809
+ status
822
810
  });
823
811
  const toIso = (date) => (date ? date.toISOString() : null);
824
812
  res.json({
@@ -831,12 +819,15 @@ class ChatController {
831
819
  joined_at: toIso(conversation.joinedAt),
832
820
  responded_at: toIso(conversation.respondedAt),
833
821
  closed_at: toIso(conversation.closedAt),
822
+ archived_at: toIso(conversation.archivedAt),
834
823
  last_message_at: toIso(conversation.lastMessageAt),
835
- status: conversation.closedAt
836
- ? 'closed'
837
- : conversation.joinedAt
838
- ? 'in_progress'
839
- : 'open'
824
+ status: conversation.archivedAt
825
+ ? 'archived'
826
+ : conversation.closedAt
827
+ ? 'closed'
828
+ : conversation.joinedAt
829
+ ? 'in_progress'
830
+ : 'open'
840
831
  })),
841
832
  pagination: {
842
833
  page,
@@ -960,79 +951,6 @@ class ChatController {
960
951
  });
961
952
  }
962
953
  }
963
- async classifyIntent(message, history) {
964
- if (!this.intentService) {
965
- return {
966
- intent: 'knowledge',
967
- needsGuardrail: false,
968
- contactEmail: null
969
- };
970
- }
971
- const resolvedHistory = Array.isArray(history) ? history : [];
972
- logger_1.default.info('๐Ÿงญ Classifying user intent...');
973
- return this.intentService.classify({
974
- message,
975
- conversationHistory: resolvedHistory
976
- });
977
- }
978
- /**
979
- * Handle intent classification result
980
- * Returns response content if non-knowledge intent, null if knowledge intent
981
- */
982
- async handleIntentResult(result, userMessage, conversationId, conversation) {
983
- if (result.needsGuardrail && result.intent !== 'guardrail') {
984
- logger_1.default.info('๐Ÿ›ก๏ธ Guardrail triggered');
985
- 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.`;
986
- }
987
- logger_1.default.info(`๐Ÿงพ Intent result: ${result.intent}${result.needsGuardrail ? ' (guardrail triggered)' : ''}`);
988
- // For non-knowledge intents, return the response content to be streamed
989
- if (result.intent !== 'knowledge') {
990
- const responseContent = result.response || this.getFallbackResponse(result.intent);
991
- return responseContent;
992
- }
993
- // Knowledge intent - proceed to RAG flow (return null to indicate streaming will happen later)
994
- logger_1.default.info('๐Ÿ“š Intent requires knowledge lookup; proceeding with RAG flow.');
995
- return null;
996
- }
997
- /**
998
- * Stream text content word by word to simulate streaming
999
- * This ensures consistent SSE format for all responses
1000
- */
1001
- async streamTextContent(content, res) {
1002
- const words = content.split(' ');
1003
- const chunkSize = 2; // Stream 2 words at a time for smoother experience
1004
- const totalChunks = Math.ceil(words.length / chunkSize);
1005
- for (let i = 0; i < words.length; i += chunkSize) {
1006
- const chunk = words.slice(i, i + chunkSize).join(' ') + (i + chunkSize < words.length ? ' ' : '');
1007
- const chunkIndex = Math.floor(i / chunkSize) + 1;
1008
- const isLastChunk = chunkIndex === totalChunks;
1009
- const chunkData = JSON.stringify({
1010
- type: 'chunk',
1011
- content: chunk,
1012
- done: isLastChunk // Mark last chunk with done: true
1013
- });
1014
- res.write(`data: ${chunkData}\n\n`);
1015
- // Flush the response to ensure chunks are sent immediately
1016
- if (res.flush) {
1017
- res.flush();
1018
- }
1019
- // Delay for smooth streaming effect (30ms for better visibility)
1020
- await new Promise(resolve => setTimeout(resolve, 30));
1021
- }
1022
- }
1023
- getFallbackResponse(intent) {
1024
- // Fallback responses in case LLM doesn't generate one (shouldn't happen, but safety net)
1025
- const fallbacks = {
1026
- greeting: 'Hello! How can I help you today?',
1027
- acknowledgment: "You're welcome! Let me know if you need anything else.",
1028
- personality: `I'm ${process.env.ASSISTANT_NAME || 'AI Assistant'}, your AI assistant for ${process.env.ORGANIZATION_NAME || 'Your Organization'}.`,
1029
- clarification: "I'm not sure I understood. Could you clarify what you need help with?",
1030
- guardrail: "I can help with documentation or implementation guidance, but I can't share credentials or confidential configuration.",
1031
- human_support_request: "I'd be happy to connect you with our support team. Could you please provide your email address?",
1032
- human_support_email: "Thank you! Our support team will reach out to you shortly."
1033
- };
1034
- return fallbacks[intent] || "I'm here to help. What would you like to know?";
1035
- }
1036
954
  async respondWithAssistantMessage(payload, res) {
1037
955
  const assistantMessage = await this.saveAssistantMessage({
1038
956
  conversation: payload.conversation,