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

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.16",
3
+ "version": "1.0.18",
4
4
  "description": "audioAnalytics",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -418,18 +418,31 @@ Only return the JSON array, no other text.`;
418
418
  } );
419
419
 
420
420
  const result = await model.generateContent( suggestionPrompt );
421
- const responseText = result.response.text().trim();
422
- logger.info( { responseText } );
421
+ let responseText = result.response.text().trim();
422
+ logger.info( { message: 'Raw Gemini response', responseText } );
423
+
424
+ // Remove markdown code fences (```json ... ```)
425
+ responseText = responseText.replace( /^```(?:json)?\s*/i, '' ).replace( /\s*```$/, '' ).trim();
426
+ logger.info( { message: 'Cleaned response', responseText } );
427
+
423
428
  // Parse the JSON array response
424
429
  let suggestionsArray = [];
425
- suggestionsArray = JSON.parse( responseText );
426
- logger.info( { suggestionsArray } );
430
+ try {
431
+ suggestionsArray = JSON.parse( responseText );
427
432
 
428
- if ( !Array.isArray( suggestionsArray ) ) {
429
- suggestionsArray = [];
433
+ if ( !Array.isArray( suggestionsArray ) ) {
434
+ suggestionsArray = [];
435
+ }
436
+ } catch ( parseError ) {
437
+ logger.warn( { message: 'Failed to parse Gemini response as JSON', error: parseError.message, responseText } );
438
+ // Fallback: try to extract suggestions from text
439
+ suggestionsArray = responseText.split( '\n' ).filter( ( line ) => line.trim() ).map( ( text, index ) => ( {
440
+ id: index + 1,
441
+ text: text.replace( /^\d+\.\s*/, '' ).trim(),
442
+ category: 'general',
443
+ } ) );
430
444
  }
431
445
 
432
-
433
446
  logger.info( { message: 'Gemini autocomplete suggestions generated', context, suggestionCount: suggestionsArray.length } );
434
447
 
435
448
  return res.sendSuccess( { suggestions: suggestionsArray } );
@@ -442,7 +455,7 @@ Only return the JSON array, no other text.`;
442
455
 
443
456
  // ======================= CHAT STREAM API =======================
444
457
 
445
- const CHAT_STREAM_API = 'http://13.232.38.210:8000/api/chat/stream';
458
+ const CHAT_STREAM_API = 'http://65.2.124.154:8000/api/chat/stream';
446
459
 
447
460
  /**
448
461
  * Chat Stream API - Streams response from external AI chat API
@@ -515,4 +528,86 @@ export async function chatStream( req, res ) {
515
528
  }
516
529
  }
517
530
 
531
+ /**
532
+ * AI Stream Response - Calls external chat stream API and returns all chunks as JSON array
533
+ * @param {Object} req - Express request object
534
+ * @param {Object} res - Express response object
535
+ * Returns: [{ text: "..." }, { text: "..." }, ...]
536
+ */
537
+ export async function aiStreamResponse( req, res ) {
538
+ try {
539
+ /* eslint-disable camelcase */
540
+ const { user_id, session_id, session_date, session_timezone, message, config } = req.body;
541
+
542
+ logger.info( { message: 'Received ai-stream-response request', user_id, session_id } );
543
+
544
+ // Map camelCase to snake_case for external API
545
+ const payload = {
546
+ user_id: user_id,
547
+ session_id: session_id,
548
+ session_date: session_date,
549
+ session_timezone: session_timezone,
550
+ message,
551
+ config: {
552
+ selected_stores: config.selected_stores,
553
+ date_range: config.date_range,
554
+ selected_products: config.selected_products,
555
+ },
556
+ };
557
+ /* eslint-enable camelcase */
558
+
559
+ const response = await fetch( CHAT_STREAM_API, {
560
+ method: 'POST',
561
+ headers: {
562
+ 'Content-Type': 'application/json',
563
+ 'Accept': 'text/event-stream',
564
+ 'Referer': '',
565
+ },
566
+ body: JSON.stringify( payload ),
567
+ } );
568
+
569
+ if ( !response.ok ) {
570
+ logger.error( response, `External Chat Stream API error: ${response.status} ${response.statusText}` );
571
+ return res.sendError( `External API error: ${response.statusText}`, response.status );
572
+ }
573
+
574
+ const chunks = [];
575
+ const decoder = new TextDecoder();
576
+
577
+ for await ( const chunk of response.body ) {
578
+ const text = decoder.decode( chunk, { stream: true } );
579
+
580
+ // Parse SSE lines: "data: {...}" or plain text
581
+ const lines = text.split( '\n' );
582
+ for ( const line of lines ) {
583
+ const trimmed = line.trim();
584
+ if ( !trimmed ) continue;
585
+
586
+ if ( trimmed.startsWith( 'data:' ) ) {
587
+ const content = trimmed.slice( 5 ).trim();
588
+ if ( content && content !== '[DONE]' ) {
589
+ try {
590
+ const parsed = JSON.parse( content );
591
+ // Extract text from common SSE formats
592
+ const textValue = parsed.text ?? parsed.content ?? parsed.delta ?? parsed.message ?? JSON.stringify( parsed );
593
+ if ( textValue ) chunks.push( { text: textValue } );
594
+ } catch {
595
+ if ( content ) chunks.push( { text: content } );
596
+ }
597
+ }
598
+ } else {
599
+ chunks.push( { text: trimmed } );
600
+ }
601
+ }
602
+ }
603
+
604
+ logger.info( { message: 'ai-stream-response completed', chunkCount: chunks.length } );
605
+
606
+ return res.sendSuccess( chunks );
607
+ } catch ( error ) {
608
+ logger.error( { error: error, function: 'aiStreamResponse' } );
609
+ return res.sendError( error.message || 'Internal Server Error', 500 );
610
+ }
611
+ }
612
+
518
613
 
@@ -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, 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';
5
+ import { cohortAnalysisCardValid, conversationsListValid, conversationDetailsValid, chatHistoryListValid, getChatValid, getGeminiResponseValid } from '../dtos/audioAnalytics.dtos.js';
6
+ import { createCohort, createBulkCohort, updateCohort, deleteCohort, getCohort, listCohortsByClient, chatHistoryList, getChat, getGeminiResponse, aiStreamResponse } 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
 
@@ -32,11 +32,11 @@ audioAnalyticsrouter.post( '/cohort-analysis-card', validate( cohortAnalysisCard
32
32
  audioAnalyticsrouter.post( '/conversations/list', validate( conversationsListValid ), getConversationsList );
33
33
  audioAnalyticsrouter.post( '/conversations/:conversationId', validate( conversationDetailsValid ), getConversationDetails );
34
34
 
35
- // Chat Stream API Route
36
- audioAnalyticsrouter.post( '/chat/stream', validate( chatStreamValid ), chatStream );
37
35
 
38
36
  // Gemini Suggestion Prompts API Route
39
37
  audioAnalyticsrouter.post( '/gemini/suggestions', validate( getGeminiResponseValid ), getGeminiResponse );
40
38
 
39
+ // AI streaming response route
40
+ audioAnalyticsrouter.post( '/ai-stream-response', aiStreamResponse );
41
41
 
42
42
  export default audioAnalyticsrouter;