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
|
@@ -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
|
-
|
|
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
|
-
|
|
426
|
-
|
|
430
|
+
try {
|
|
431
|
+
suggestionsArray = JSON.parse( responseText );
|
|
427
432
|
|
|
428
|
-
|
|
429
|
-
|
|
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://
|
|
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,
|
|
6
|
-
import { createCohort, createBulkCohort, updateCohort, deleteCohort, getCohort, listCohortsByClient,
|
|
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;
|