@peopl-health/nexus 2.0.5 → 2.0.6
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/lib/config/interactionConfig.js +3 -0
- package/lib/controllers/interactionController.js +33 -0
- package/lib/controllers/messageController.js +30 -0
- package/lib/controllers/patientController.js +23 -0
- package/lib/core/NexusMessaging.js +6 -3
- package/lib/models/index.js +2 -0
- package/lib/models/interactionModel.js +17 -0
- package/lib/models/messageModel.js +31 -1
- package/lib/routes/index.js +27 -1
- package/lib/storage/MongoStorage.js +3 -1
- package/lib/utils/messageParser.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { Interaction } = require('../models/interactionModel');
|
|
2
|
+
const { INTERACTION_QUALITY_VALUES } = require('../config/interactionConfig');
|
|
3
|
+
|
|
4
|
+
const addInteractionController = async (req, res) => {
|
|
5
|
+
try {
|
|
6
|
+
const { messages, whatsapp_id, quality, description } = req.body;
|
|
7
|
+
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
|
8
|
+
return res.status(400).json({ success: false, error: 'Messages array is required and must not be empty' });
|
|
9
|
+
}
|
|
10
|
+
if (!whatsapp_id) return res.status(400).json({ success: false, error: 'WhatsApp ID is required' });
|
|
11
|
+
if (!quality || !INTERACTION_QUALITY_VALUES.includes(quality)) {
|
|
12
|
+
return res.status(400).json({ success: false, error: `Quality must be one of: ${INTERACTION_QUALITY_VALUES.join(', ')}` });
|
|
13
|
+
}
|
|
14
|
+
const interaction = await Interaction.create({ messages, whatsapp_id, quality, description });
|
|
15
|
+
res.status(201).json({ success: true, interaction });
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error('Error adding interaction:', error);
|
|
18
|
+
res.status(500).json({ success: false, error: error.message });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getInteractionsByWhatsappIdController = async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const { whatsapp_id } = req.params;
|
|
25
|
+
const interactions = await Interaction.find({ whatsapp_id }).populate('messages').sort({ createdAt: -1 });
|
|
26
|
+
res.status(200).json({ success: true, whatsappId: whatsapp_id, count: interactions.length, interactions });
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error fetching interactions:', error);
|
|
29
|
+
res.status(500).json({ success: false, error: error.message });
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
module.exports = { addInteractionController, getInteractionsByWhatsappIdController };
|
|
@@ -348,10 +348,40 @@ const getLastInteractionController = async (req, res) => {
|
|
|
348
348
|
}
|
|
349
349
|
};
|
|
350
350
|
|
|
351
|
+
const getMessageQualityController = async (req, res) => {
|
|
352
|
+
try {
|
|
353
|
+
const { id } = req.params;
|
|
354
|
+
const message = await Message.findById(id);
|
|
355
|
+
if (!message) return res.status(404).json({ success: false, error: 'Message not found' });
|
|
356
|
+
res.status(200).json({ success: true, messageId: id, quality: message.quality });
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error('Error fetching message quality:', error);
|
|
359
|
+
res.status(500).json({ success: false, error: error.message });
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const updateMessageQualityController = async (req, res) => {
|
|
364
|
+
try {
|
|
365
|
+
const { id } = req.params;
|
|
366
|
+
const { quality } = req.body;
|
|
367
|
+
if (!['low', 'medium', 'high'].includes(quality)) {
|
|
368
|
+
return res.status(400).json({ success: false, error: 'Invalid quality value. Must be low, medium, or high' });
|
|
369
|
+
}
|
|
370
|
+
const message = await Message.findByIdAndUpdate(id, { quality }, { new: true });
|
|
371
|
+
if (!message) return res.status(404).json({ success: false, error: 'Message not found' });
|
|
372
|
+
res.status(200).json({ success: true, messageId: id, quality: message.quality });
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error('Error updating message quality:', error);
|
|
375
|
+
res.status(500).json({ success: false, error: error.message });
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
351
379
|
module.exports = {
|
|
352
380
|
sendMessageController,
|
|
353
381
|
sendBulkMessageController,
|
|
354
382
|
sendBulkMessageAirtableController,
|
|
355
383
|
getLastInteractionController,
|
|
384
|
+
getMessageQualityController,
|
|
385
|
+
updateMessageQualityController,
|
|
356
386
|
configureMessageController
|
|
357
387
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { getRecordByFilter } = require('../services/airtableService');
|
|
2
|
+
const { Monitoreo_ID } = require('../config/airtableConfig');
|
|
3
|
+
|
|
4
|
+
const getPatientInfoController = async (req, res) => {
|
|
5
|
+
try {
|
|
6
|
+
const { id } = req.params;
|
|
7
|
+
if (!id) return res.status(400).json({ success: false, error: 'WhatsApp ID is required' });
|
|
8
|
+
|
|
9
|
+
const records = await getRecordByFilter(Monitoreo_ID, 'estado_general', `{whatsapp_id}='${id}'`);
|
|
10
|
+
|
|
11
|
+
if (!records || records.length === 0) {
|
|
12
|
+
return res.status(404).json({ success: false, error: 'Patient not found' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const clinicalContext = records[0]['clinical-context-json'];
|
|
16
|
+
res.status(200).json({ success: true, whatsappId: id, clinicalContext });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('Error fetching patient info:', error);
|
|
19
|
+
res.status(500).json({ success: false, error: error.message });
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
module.exports = { getPatientInfoController };
|
|
@@ -434,7 +434,8 @@ class NexusMessaging {
|
|
|
434
434
|
await this.sendMessage({
|
|
435
435
|
code: from,
|
|
436
436
|
body: response,
|
|
437
|
-
processed: true
|
|
437
|
+
processed: true,
|
|
438
|
+
origin: 'assistant'
|
|
438
439
|
});
|
|
439
440
|
}
|
|
440
441
|
} catch (error) {
|
|
@@ -502,7 +503,8 @@ class NexusMessaging {
|
|
|
502
503
|
await this.sendMessage({
|
|
503
504
|
code: from,
|
|
504
505
|
body: response,
|
|
505
|
-
processed: true
|
|
506
|
+
processed: true,
|
|
507
|
+
origin: 'assistant'
|
|
506
508
|
});
|
|
507
509
|
}
|
|
508
510
|
} catch (error) {
|
|
@@ -642,7 +644,8 @@ class NexusMessaging {
|
|
|
642
644
|
await this.sendMessage({
|
|
643
645
|
code: chatId,
|
|
644
646
|
body: botResponse,
|
|
645
|
-
processed: true
|
|
647
|
+
processed: true,
|
|
648
|
+
origin: 'assistant'
|
|
646
649
|
});
|
|
647
650
|
}
|
|
648
651
|
|
package/lib/models/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const { Message, getMessageValues, formatTimestamp } = require('./messageModel');
|
|
2
2
|
const { Thread } = require('./threadModel');
|
|
3
|
+
const { Interaction } = require('./interactionModel');
|
|
3
4
|
|
|
4
5
|
module.exports = {
|
|
5
6
|
Message,
|
|
6
7
|
Thread,
|
|
8
|
+
Interaction,
|
|
7
9
|
getMessageValues,
|
|
8
10
|
formatTimestamp
|
|
9
11
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
const { INTERACTION_QUALITY_VALUES } = require('../config/interactionConfig');
|
|
3
|
+
|
|
4
|
+
const interactionSchema = new mongoose.Schema({
|
|
5
|
+
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message', required: true }],
|
|
6
|
+
whatsapp_id: { type: String, required: true, index: true },
|
|
7
|
+
quality: {
|
|
8
|
+
type: String,
|
|
9
|
+
enum: INTERACTION_QUALITY_VALUES,
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
description: { type: String, default: null }
|
|
13
|
+
}, { timestamps: true });
|
|
14
|
+
|
|
15
|
+
const Interaction = mongoose.model('Interaction', interactionSchema);
|
|
16
|
+
|
|
17
|
+
module.exports = { Interaction, interactionSchema };
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const mongoose = require('mongoose');
|
|
2
2
|
const moment = require('moment-timezone');
|
|
3
|
+
const { getRecordByFilter } = require('../services/airtableService');
|
|
4
|
+
const { Monitoreo_ID } = require('../config/airtableConfig');
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
const messageSchema = new mongoose.Schema({
|
|
8
|
+
raw: { type: Object, required: false },
|
|
6
9
|
nombre_whatsapp: { type: String, required: true },
|
|
7
10
|
numero: { type: String, required: true },
|
|
8
11
|
body: { type: String, required: true },
|
|
@@ -16,6 +19,7 @@ const messageSchema = new mongoose.Schema({
|
|
|
16
19
|
enum: ['button', 'list', 'flow', 'quick_reply'],
|
|
17
20
|
default: null
|
|
18
21
|
},
|
|
22
|
+
interactive_data: { type: Object, default: null },
|
|
19
23
|
group_id: { type: String, default: null },
|
|
20
24
|
reply_id: { type: String, default: null },
|
|
21
25
|
processed: { type: Boolean, default: false },
|
|
@@ -23,6 +27,10 @@ const messageSchema = new mongoose.Schema({
|
|
|
23
27
|
assistant_id: { type: String, default: null },
|
|
24
28
|
content_sid: { type: String, default: null },
|
|
25
29
|
from_me: { type: Boolean, default: false },
|
|
30
|
+
origin: {
|
|
31
|
+
type: String,
|
|
32
|
+
enum: ['whatsapp', 'assistant', 'patient'],
|
|
33
|
+
default: 'whatsapp' },
|
|
26
34
|
media: {
|
|
27
35
|
contentType: { type: String, default: null },
|
|
28
36
|
bucketName: { type: String, default: null },
|
|
@@ -35,6 +43,12 @@ const messageSchema = new mongoose.Schema({
|
|
|
35
43
|
thumbnail: { type: String, default: null },
|
|
36
44
|
metadata: { type: Object, default: null }
|
|
37
45
|
},
|
|
46
|
+
clinical_context: { type: Object, default: null },
|
|
47
|
+
quality: {
|
|
48
|
+
type: String,
|
|
49
|
+
enum: ['low', 'medium', 'high'],
|
|
50
|
+
default: 'medium'
|
|
51
|
+
},
|
|
38
52
|
memoryType: {
|
|
39
53
|
type: String,
|
|
40
54
|
enum: ['active', 'archived'],
|
|
@@ -58,10 +72,23 @@ messageSchema.pre('save', function (next) {
|
|
|
58
72
|
|
|
59
73
|
const Message = mongoose.model('Message', messageSchema);
|
|
60
74
|
|
|
75
|
+
async function getClinicalContext(whatsappId) {
|
|
76
|
+
try {
|
|
77
|
+
const records = await getRecordByFilter(Monitoreo_ID, 'estado_general', `{whatsapp_id}='${whatsappId}'`);
|
|
78
|
+
if (records && records.length > 0 && records[0]['clinical-context-json']) {
|
|
79
|
+
return records[0]['clinical-context-json'];
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('Error fetching clinical context from Airtable:', error);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
61
87
|
|
|
62
88
|
async function insertMessage(values) {
|
|
63
89
|
try {
|
|
64
90
|
const skipNumbers = ['5215592261426@s.whatsapp.net', '5215547411345@s.whatsapp.net', '51985959446@s.whatsapp.net'];
|
|
91
|
+
const clinical_context = await getClinicalContext(values.numero);
|
|
65
92
|
const messageData = {
|
|
66
93
|
nombre_whatsapp: values.nombre_whatsapp,
|
|
67
94
|
numero: values.numero,
|
|
@@ -72,12 +99,15 @@ async function insertMessage(values) {
|
|
|
72
99
|
is_media: values.is_media,
|
|
73
100
|
is_interactive: values.is_interactive || false,
|
|
74
101
|
interaction_type: values.interaction_type || null,
|
|
102
|
+
interactive_data: values.interactive_data || null,
|
|
75
103
|
group_id: values.group_id,
|
|
76
104
|
reply_id: values.reply_id,
|
|
77
105
|
from_me: values.from_me,
|
|
78
106
|
processed: values.processed || skipNumbers.includes(values.numero),
|
|
79
107
|
media: values.media ? values.media : null,
|
|
80
|
-
content_sid: values.content_sid || null
|
|
108
|
+
content_sid: values.content_sid || null,
|
|
109
|
+
clinical_context: clinical_context,
|
|
110
|
+
raw: values.raw || null
|
|
81
111
|
};
|
|
82
112
|
console.log('Inserting message', messageData);
|
|
83
113
|
|
package/lib/routes/index.js
CHANGED
|
@@ -32,7 +32,18 @@ const messageRouteDefinitions = {
|
|
|
32
32
|
'POST /send': 'sendMessageController',
|
|
33
33
|
'POST /send-bulk': 'sendBulkMessageController',
|
|
34
34
|
'POST /send-bulk-airtable': 'sendBulkMessageAirtableController',
|
|
35
|
-
'GET /last': 'getLastInteractionController'
|
|
35
|
+
'GET /last': 'getLastInteractionController',
|
|
36
|
+
'GET /:id/quality': 'getMessageQualityController',
|
|
37
|
+
'PUT /:id/quality': 'updateMessageQualityController'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const interactionRouteDefinitions = {
|
|
41
|
+
'POST /': 'addInteractionController',
|
|
42
|
+
'GET /:whatsapp_id': 'getInteractionsByWhatsappIdController'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const patientRouteDefinitions = {
|
|
46
|
+
'GET /:id': 'getPatientInfoController'
|
|
36
47
|
};
|
|
37
48
|
|
|
38
49
|
const templateRouteDefinitions = {
|
|
@@ -68,8 +79,10 @@ const createRouter = (routeDefinitions, controllers) => {
|
|
|
68
79
|
// Import built-in controllers
|
|
69
80
|
const assistantController = require('../controllers/assistantController');
|
|
70
81
|
const conversationController = require('../controllers/conversationController');
|
|
82
|
+
const interactionController = require('../controllers/interactionController');
|
|
71
83
|
const mediaController = require('../controllers/mediaController');
|
|
72
84
|
const messageController = require('../controllers/messageController');
|
|
85
|
+
const patientController = require('../controllers/patientController');
|
|
73
86
|
const templateController = require('../controllers/templateController');
|
|
74
87
|
const templateFlowController = require('../controllers/templateFlowController');
|
|
75
88
|
const uploadController = require('../controllers/uploadController');
|
|
@@ -96,6 +109,10 @@ const builtInControllers = {
|
|
|
96
109
|
sendTemplateToNewNumberController: conversationController.sendTemplateToNewNumberController,
|
|
97
110
|
markMessagesAsReadController: conversationController.markMessagesAsReadController,
|
|
98
111
|
|
|
112
|
+
// Interaction controllers
|
|
113
|
+
addInteractionController: interactionController.addInteractionController,
|
|
114
|
+
getInteractionsByWhatsappIdController: interactionController.getInteractionsByWhatsappIdController,
|
|
115
|
+
|
|
99
116
|
// Media controllers
|
|
100
117
|
getMediaController: mediaController.getMediaController,
|
|
101
118
|
handleFileUpload: uploadController.handleFileUpload,
|
|
@@ -105,6 +122,11 @@ const builtInControllers = {
|
|
|
105
122
|
sendBulkMessageController: messageController.sendBulkMessageController,
|
|
106
123
|
sendBulkMessageAirtableController: messageController.sendBulkMessageAirtableController,
|
|
107
124
|
getLastInteractionController: messageController.getLastInteractionController,
|
|
125
|
+
getMessageQualityController: messageController.getMessageQualityController,
|
|
126
|
+
updateMessageQualityController: messageController.updateMessageQualityController,
|
|
127
|
+
|
|
128
|
+
// Patient controllers
|
|
129
|
+
getPatientInfoController: patientController.getPatientInfoController,
|
|
108
130
|
|
|
109
131
|
// Template controllers
|
|
110
132
|
createTemplate: templateController.createTemplate,
|
|
@@ -123,8 +145,10 @@ const builtInControllers = {
|
|
|
123
145
|
const setupDefaultRoutes = (app) => {
|
|
124
146
|
app.use('/api/assistant', createRouter(assistantRouteDefinitions, builtInControllers));
|
|
125
147
|
app.use('/api/conversation', createRouter(conversationRouteDefinitions, builtInControllers));
|
|
148
|
+
app.use('/api/interaction', createRouter(interactionRouteDefinitions, builtInControllers));
|
|
126
149
|
app.use('/api/media', createRouter(mediaRouteDefinitions, builtInControllers));
|
|
127
150
|
app.use('/api/message', createRouter(messageRouteDefinitions, builtInControllers));
|
|
151
|
+
app.use('/api/patient', createRouter(patientRouteDefinitions, builtInControllers));
|
|
128
152
|
app.use('/api/template', createRouter(templateRouteDefinitions, builtInControllers));
|
|
129
153
|
};
|
|
130
154
|
|
|
@@ -132,8 +156,10 @@ module.exports = {
|
|
|
132
156
|
// Route definitions for customers to use with their own controllers
|
|
133
157
|
assistantRoutes: assistantRouteDefinitions,
|
|
134
158
|
conversationRoutes: conversationRouteDefinitions,
|
|
159
|
+
interactionRoutes: interactionRouteDefinitions,
|
|
135
160
|
mediaRoutes: mediaRouteDefinitions,
|
|
136
161
|
messageRoutes: messageRouteDefinitions,
|
|
162
|
+
patientRoutes: patientRouteDefinitions,
|
|
137
163
|
templateRoutes: templateRouteDefinitions,
|
|
138
164
|
|
|
139
165
|
// Helper functions
|
|
@@ -203,12 +203,14 @@ class MongoStorage {
|
|
|
203
203
|
is_media: isMedia,
|
|
204
204
|
is_interactive: messageData.isInteractive || false,
|
|
205
205
|
interaction_type: messageData.interactionType || null,
|
|
206
|
+
interactive_data: messageData.interactiveData || null,
|
|
206
207
|
group_id: isGroup ? normalizedNumero : null,
|
|
207
208
|
reply_id: messageData.reply_id || messageData.replyId || null,
|
|
208
209
|
from_me: messageData.fromMe !== undefined ? messageData.fromMe : true,
|
|
209
210
|
media,
|
|
210
211
|
content_sid: messageData.contentSid || null,
|
|
211
|
-
template_variables: messageData.variables ? JSON.stringify(messageData.variables) : null
|
|
212
|
+
template_variables: messageData.variables ? JSON.stringify(messageData.variables) : null,
|
|
213
|
+
raw: messageData.raw || null
|
|
212
214
|
};
|
|
213
215
|
}
|
|
214
216
|
|
|
@@ -33,7 +33,8 @@ class MessageParser {
|
|
|
33
33
|
type: 'interactive',
|
|
34
34
|
interactive: interactive,
|
|
35
35
|
isInteractive: true, // Flag to indicate this is an interactive message
|
|
36
|
-
interactionType: interactive.type // Store the specific interaction type
|
|
36
|
+
interactionType: interactive.type, // Store the specific interaction type
|
|
37
|
+
interactiveData: interactive
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
|