tango-app-api-audio-analytics 1.0.14 → 1.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-audio-analytics",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "audioAnalytics",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -14,6 +14,7 @@
14
14
  "license": "ISC",
15
15
  "dependencies": {
16
16
  "@aws-sdk/client-secrets-manager": "^3.1003.0",
17
+ "@google/generative-ai": "^0.24.1",
17
18
  "aws-sdk": "^2.1693.0",
18
19
  "body-parser": "^2.2.2",
19
20
  "cors": "^2.8.6",
@@ -1,5 +1,6 @@
1
1
  import { insertWithId, updateOpenSearchData, searchOpenSearchData, logger } from 'tango-app-api-middleware';
2
2
  import { randomUUID } from 'crypto';
3
+ import { GoogleGenerativeAI } from '@google/generative-ai';
3
4
 
4
5
  const EXTERNAL_STREAM_API = 'http://172.236.179.51:8000/stream';
5
6
 
@@ -31,7 +32,7 @@ export async function createCohort( req, res ) {
31
32
  updatedAt: new Date().toISOString(),
32
33
  };
33
34
 
34
- const result = await insertWithId( 'tango-audio-config', cohortId, cohortData );
35
+ const result = await insertWithId( 'tango-audio-config-test', cohortId, cohortData );
35
36
  logger.info( { result } );
36
37
 
37
38
  if ( result && result.body && result.body.result === 'created' ) {
@@ -67,7 +68,7 @@ export async function createBulkCohort( req, res ) {
67
68
  updatedAt: new Date().toISOString(),
68
69
  };
69
70
 
70
- const result = await insertWithId( 'tango-audio-config', cohortId, cohortData );
71
+ const result = await insertWithId( 'tango-audio-config-test', cohortId, cohortData );
71
72
  logger.info( { result } );
72
73
 
73
74
  if ( result && result.body && result.body.result === 'created' ) {
@@ -118,7 +119,7 @@ export async function updateCohort( req, res ) {
118
119
  doc_as_upsert: true,
119
120
  };
120
121
 
121
- const result = await updateOpenSearchData( 'tango-audio-config', cohortId, updatePayload );
122
+ const result = await updateOpenSearchData( 'tango-audio-config-test', cohortId, updatePayload );
122
123
  logger.info( { result } );
123
124
 
124
125
  if ( result && result.body && result.body.result === 'updated' ) {
@@ -144,7 +145,7 @@ export async function deleteCohort( req, res ) {
144
145
  },
145
146
  };
146
147
 
147
- const result = await updateOpenSearchData( 'tango-audio-config', cohortId, updatePayload );
148
+ const result = await updateOpenSearchData( 'tango-audio-config-test', cohortId, updatePayload );
148
149
  logger.info( { result } );
149
150
 
150
151
  if ( result && result.body && result.body.result === 'updated' ) {
@@ -169,7 +170,7 @@ export async function getCohort( req, res ) {
169
170
  },
170
171
  };
171
172
 
172
- const result = await searchOpenSearchData( 'tango-audio-config', query );
173
+ const result = await searchOpenSearchData( 'tango-audio-config-test', query );
173
174
  logger.info( { result } );
174
175
 
175
176
  if ( !result || result?.body?.hits?.hits?.length === 0 ) {
@@ -204,7 +205,7 @@ export async function listCohortsByClient( req, res ) {
204
205
  sort: [ { createdAt: { order: 'desc' } } ],
205
206
  };
206
207
 
207
- const result = await searchOpenSearchData( 'tango-audio-config', query );
208
+ const result = await searchOpenSearchData( 'tango-audio-config-test', query );
208
209
  logger.info( { result } );
209
210
 
210
211
  const total = result?.body?.hits?.total?.value || 0;
@@ -234,6 +235,94 @@ export async function listCohortsByClient( req, res ) {
234
235
  }
235
236
  }
236
237
 
238
+
239
+ export async function chatHistoryList( req, res ) {
240
+ try {
241
+ /* eslint-disable camelcase */
242
+ const { userId, limit = 10, offset = 0 } = req.body;
243
+
244
+ logger.info( { message: 'Fetching chat history', userId, limit, offset } );
245
+
246
+ // Build OpenSearch query to filter by user_id
247
+ const query = {
248
+ query: {
249
+ match: {
250
+ user_id: userId,
251
+ },
252
+ },
253
+ _source: [ 'session_id', 'session_title' ], // Only fetch required fields
254
+ size: Number( limit ),
255
+ from: Number( offset ),
256
+ sort: [ { session_date: { order: 'desc' } } ], // Sort by creation date, most recent first
257
+ };
258
+
259
+ // Search in tango-ai-session index
260
+ const result = await searchOpenSearchData( 'tango-ai-sessions', query );
261
+ logger.info( { message: 'Chat history search result', resultHits: result?.body?.hits?.hits?.length } );
262
+
263
+ if ( !result || !result.body || !result.body.hits ) {
264
+ return res.sendError( 'Failed to fetch chat history', 500 );
265
+ }
266
+
267
+ const total = result.body.hits.total?.value || 0;
268
+ const sessions = ( result.body.hits.hits || [] ).map( ( hit ) => ( {
269
+ session_id: hit._source.session_id,
270
+ session_title: hit._source.session_title,
271
+ } ) );
272
+ /* eslint-enable camelcase */
273
+
274
+ return res.sendSuccess( {
275
+ sessions,
276
+ count: total,
277
+ // pagination: {
278
+ // total,
279
+ // limit: Number( limit ),
280
+ // offset: Number( offset ),
281
+ // hasMore: Number( offset ) + Number( limit ) < total,
282
+ // },
283
+ } );
284
+ } catch ( error ) {
285
+ const err = error.message || 'Internal Server Error';
286
+ logger.error( { error, message: req.body, function: 'chatHistoryList' } );
287
+ return res.sendError( err, 500 );
288
+ }
289
+ }
290
+
291
+ export async function getChat( req, res ) {
292
+ try {
293
+ /* eslint-disable camelcase */
294
+ const { sessionId } = req.body;
295
+
296
+ logger.info( { message: 'Fetching chat details', sessionId } );
297
+
298
+ // Build OpenSearch query to find by session_id
299
+ const query = {
300
+ query: {
301
+ match: {
302
+ session_id: sessionId,
303
+ },
304
+ },
305
+ };
306
+
307
+ // Search in tango-ai-sessions index
308
+ const result = await searchOpenSearchData( 'tango-ai-sessions', query );
309
+ logger.info( { message: 'Chat details search result', found: result?.body?.hits?.hits?.length > 0 } );
310
+
311
+ if ( !result || !result.body || !result.body.hits || result.body.hits.hits.length === 0 ) {
312
+ return res.sendError( 'Chat session not found', 404 );
313
+ }
314
+
315
+ const chatSession = result.body.hits.hits[0]._source;
316
+ /* eslint-enable camelcase */
317
+
318
+ return res.sendSuccess( chatSession );
319
+ } catch ( error ) {
320
+ const err = error.message || 'Internal Server Error';
321
+ logger.error( { error, message: req.body, function: 'getChat' } );
322
+ return res.sendError( err, 500 );
323
+ }
324
+ }
325
+
237
326
  export const callExternalStreamAPI = async ( req, res ) => {
238
327
  try {
239
328
  const { prompt, storeId, productModule, country, region, fromDate, toDate, clusters } = req.body;
@@ -305,6 +394,52 @@ export const callExternalStreamAPI = async ( req, res ) => {
305
394
  }
306
395
  };
307
396
 
397
+ export const getGeminiResponse = async ( req, res ) => {
398
+ try {
399
+ const { context, topic, sessionData } = req.body;
400
+
401
+ // Build a prompt to get suggestions from Gemini
402
+ const suggestionPrompt = `Based on the following context, provide helpful suggestion prompts that the user could ask for autocomplete:
403
+ Context: ${context || 'General assistance'}
404
+ ${topic ? `Topic: ${topic}` : ''}
405
+ ${sessionData ? `Session Information: ${JSON.stringify( sessionData )}` : ''}
406
+
407
+ Generate 5-8 concise and relevant suggestion prompts. Format exactly as a JSON array of objects with this structure:
408
+ [
409
+ {"id": 1, "text": "suggestion text here", "category": "category_name"},
410
+ {"id": 2, "text": "suggestion text here", "category": "category_name"}
411
+ ]
412
+ Only return the JSON array, no other text.`;
413
+
414
+ const genAI = new GoogleGenerativeAI( process.env.GOOGLE_API_KEY );
415
+
416
+ const model = genAI.getGenerativeModel( {
417
+ model: 'gemini-2.5-pro',
418
+ } );
419
+
420
+ const result = await model.generateContent( suggestionPrompt );
421
+ const responseText = result.response.text().trim();
422
+ logger.info( { responseText } );
423
+ // Parse the JSON array response
424
+ let suggestionsArray = [];
425
+ suggestionsArray = JSON.parse( responseText );
426
+ logger.info( { suggestionsArray } );
427
+
428
+ if ( !Array.isArray( suggestionsArray ) ) {
429
+ suggestionsArray = [];
430
+ }
431
+
432
+
433
+ logger.info( { message: 'Gemini autocomplete suggestions generated', context, suggestionCount: suggestionsArray.length } );
434
+
435
+ return res.sendSuccess( { suggestions: suggestionsArray } );
436
+ } catch ( error ) {
437
+ logger.error( `Error getting Gemini suggestions: ${error.message}` );
438
+ const err = error.message || 'Internal server error while getting Gemini suggestions';
439
+ return res.sendError( err, 500 );
440
+ }
441
+ };
442
+
308
443
  // ======================= CHAT STREAM API =======================
309
444
 
310
445
  const CHAT_STREAM_API = 'http://13.232.38.210:8000/api/chat/stream';
@@ -380,3 +515,4 @@ export async function chatStream( req, res ) {
380
515
  }
381
516
  }
382
517
 
518
+
@@ -686,3 +686,60 @@ const chatStreamSchema = joi.object( {
686
686
  export const chatStreamValid = {
687
687
  body: chatStreamSchema,
688
688
  };
689
+
690
+ // ======================= CHAT HISTORY LIST API SCHEMA =======================
691
+
692
+ /**
693
+ * Chat History List Request Schema
694
+ * For fetching session history by user_id from tango-ai-session index
695
+ */
696
+ const chatHistoryListSchema = joi.object( {
697
+ userId: joi.string().required().description( 'User ID to fetch session history for' ),
698
+ limit: joi.number().integer().min( 1 ).max( 1000 ).optional().default( 10 ).description( 'Pagination limit' ),
699
+ offset: joi.number().integer().min( 0 ).optional().default( 0 ).description( 'Pagination offset' ),
700
+ } ).strict();
701
+
702
+ export const chatHistoryListValid = {
703
+ body: chatHistoryListSchema,
704
+ };
705
+
706
+ // ======================= GET SINGLE CHAT API SCHEMA =======================
707
+
708
+ /**
709
+ * Get Single Chat Request Schema
710
+ * For fetching a single session by session_id from tango-ai-sessions index
711
+ */
712
+ const getChatSchema = joi.object( {
713
+ sessionId: joi.string().required().description( 'Session ID to fetch' ),
714
+ } ).strict();
715
+
716
+ export const getChatValid = {
717
+ body: getChatSchema,
718
+ };
719
+
720
+ // ======================= GEMINI SUGGESTION PROMPTS API SCHEMA =======================
721
+
722
+ /**
723
+ * Gemini Suggestion Prompts Request Schema
724
+ * For generating autocomplete suggestions based on context using Gemini AI
725
+ *
726
+ * Response format:
727
+ * {
728
+ * "status": "success",
729
+ * "data": {
730
+ * "suggestions": [
731
+ * { "id": 1, "text": "suggestion text", "category": "category_name" },
732
+ * { "id": 2, "text": "suggestion text", "category": "category_name" }
733
+ * ]
734
+ * }
735
+ * }
736
+ */
737
+ const getGeminiResponseSchema = joi.object( {
738
+ context: joi.string().optional().description( 'Context for generating suggestions' ),
739
+ topic: joi.string().optional().description( 'Topic for suggestion generation' ),
740
+ sessionData: joi.object().optional().unknown( true ).description( 'Session data for context' ),
741
+ } ).strict();
742
+
743
+ export const getGeminiResponseValid = {
744
+ body: getGeminiResponseSchema,
745
+ };
@@ -2,8 +2,8 @@
2
2
  import express from 'express';
3
3
  import { validate } from 'tango-app-api-middleware';
4
4
  import { createCohortValid, createBulkCohortValid, updateCohortValid, getCohortValid, listCohortsByClientValid, deleteCohortValid } from '../dtos/audioAnalytics.dtos.js';
5
- import { cohortAnalysisCardValid, conversationsListValid, conversationDetailsValid, chatStreamValid } from '../dtos/audioAnalytics.dtos.js';
6
- import { createCohort, createBulkCohort, updateCohort, deleteCohort, getCohort, listCohortsByClient, chatStream } from '../controllers/audioAnalytics.controller.js';
5
+ import { cohortAnalysisCardValid, conversationsListValid, conversationDetailsValid, chatStreamValid, chatHistoryListValid, getChatValid, getGeminiResponseValid } from '../dtos/audioAnalytics.dtos.js';
6
+ import { createCohort, createBulkCohort, updateCohort, deleteCohort, getCohort, listCohortsByClient, chatStream, chatHistoryList, getChat, getGeminiResponse } from '../controllers/audioAnalytics.controller.js';
7
7
  import { getCohortAnalysisCard } from '../controllers/cohortAnalytics.controller.js';
8
8
  import { getConversationsList, getConversationDetails } from '../controllers/conversationAnalytics.controller.js';
9
9
 
@@ -23,6 +23,8 @@ audioAnalyticsrouter.post( '/update-cohort', validate( updateCohortValid ), upda
23
23
  audioAnalyticsrouter.get( '/get-cohort/:cohortId', validate( getCohortValid ), getCohort );
24
24
  audioAnalyticsrouter.get( '/list-cohorts', validate( listCohortsByClientValid ), listCohortsByClient );
25
25
 
26
+ audioAnalyticsrouter.post( '/chat-history-list', validate( chatHistoryListValid ), chatHistoryList );
27
+ audioAnalyticsrouter.post( '/get-chat', validate( getChatValid ), getChat );
26
28
  // Cohort Analytics Routes
27
29
  audioAnalyticsrouter.post( '/cohort-analysis-card', validate( cohortAnalysisCardValid ), getCohortAnalysisCard );
28
30
 
@@ -33,5 +35,8 @@ audioAnalyticsrouter.post( '/conversations/:conversationId', validate( conversat
33
35
  // Chat Stream API Route
34
36
  audioAnalyticsrouter.post( '/chat/stream', validate( chatStreamValid ), chatStream );
35
37
 
38
+ // Gemini Suggestion Prompts API Route
39
+ audioAnalyticsrouter.post( '/gemini/suggestions', validate( getGeminiResponseValid ), getGeminiResponse );
40
+
36
41
 
37
42
  export default audioAnalyticsrouter;