@peopl-health/nexus 1.1.8 → 1.3.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.
- package/MIGRATION_GUIDE.md +71 -379
- package/README.md +80 -559
- package/lib/adapters/BaileysProvider.js +25 -1
- package/lib/adapters/TwilioProvider.js +107 -0
- package/lib/adapters/registry.js +29 -0
- package/lib/config/configLoader.js +38 -0
- package/lib/controllers/assistantController.js +3 -3
- package/lib/controllers/messageController.js +31 -1
- package/lib/core/NexusMessaging.js +151 -35
- package/lib/helpers/assistantHelper.js +6 -6
- package/lib/helpers/baileysHelper.js +3 -3
- package/lib/helpers/twilioHelper.js +3 -3
- package/lib/index.d.ts +1 -1
- package/lib/index.js +84 -9
- package/lib/interactive/index.js +86 -0
- package/lib/interactive/registry.js +31 -0
- package/lib/interactive/twilioMapper.js +60 -0
- package/lib/models/messageModel.js +8 -0
- package/lib/routes/index.js +3 -1
- package/lib/services/airtableService.js +4 -0
- package/lib/services/assistantService.js +16 -2
- package/lib/storage/NoopStorage.js +19 -0
- package/lib/storage/registry.js +31 -0
- package/lib/templates/predefinedTemplates.js +1 -3
- package/lib/utils/defaultLLMProvider.js +1 -1
- package/lib/utils/index.js +3 -5
- package/package.json +2 -1
|
@@ -14,7 +14,10 @@ class BaileysProvider extends MessageProvider {
|
|
|
14
14
|
|
|
15
15
|
async initialize() {
|
|
16
16
|
try {
|
|
17
|
-
const
|
|
17
|
+
const baileys = require('baileys');
|
|
18
|
+
// Support both CJS and ESM shapes
|
|
19
|
+
const makeWASocket = baileys.default || baileys.makeWASocket || baileys;
|
|
20
|
+
const useMultiFileAuthState = baileys.useMultiFileAuthState || baileys.useMultiFileAuthState;
|
|
18
21
|
const { useMongoDBAuthState } = require('../config/mongoAuthConfig');
|
|
19
22
|
const pino = require('pino');
|
|
20
23
|
|
|
@@ -175,6 +178,27 @@ class BaileysProvider extends MessageProvider {
|
|
|
175
178
|
}
|
|
176
179
|
this.isConnected = false;
|
|
177
180
|
}
|
|
181
|
+
|
|
182
|
+
// Content/Template operations are not supported for Baileys
|
|
183
|
+
async listTemplates() {
|
|
184
|
+
throw new Error('Template operations are only supported with Twilio provider');
|
|
185
|
+
}
|
|
186
|
+
async getTemplate() {
|
|
187
|
+
throw new Error('Template operations are only supported with Twilio provider');
|
|
188
|
+
}
|
|
189
|
+
async checkApprovalStatus() {
|
|
190
|
+
throw new Error('Template operations are only supported with Twilio provider');
|
|
191
|
+
}
|
|
192
|
+
async submitForApproval() {
|
|
193
|
+
throw new Error('Template operations are only supported with Twilio provider');
|
|
194
|
+
}
|
|
195
|
+
async deleteTemplate() {
|
|
196
|
+
throw new Error('Template operations are only supported with Twilio provider');
|
|
197
|
+
}
|
|
198
|
+
async createTemplate() {
|
|
199
|
+
throw new Error('Template operations are only supported with Twilio provider');
|
|
200
|
+
}
|
|
201
|
+
|
|
178
202
|
}
|
|
179
203
|
|
|
180
204
|
module.exports = { BaileysProvider };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { MessageProvider } = require('../core/MessageProvider');
|
|
2
|
+
const axios = require('axios');
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Twilio WhatsApp messaging provider
|
|
@@ -132,6 +133,112 @@ class TwilioProvider extends MessageProvider {
|
|
|
132
133
|
throw new Error(`Failed to list templates: ${error.message}`);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Fetch a specific template/content by SID
|
|
139
|
+
*/
|
|
140
|
+
async getTemplate(sid) {
|
|
141
|
+
if (!this.isConnected || !this.twilioClient) {
|
|
142
|
+
throw new Error('Twilio provider not initialized');
|
|
143
|
+
}
|
|
144
|
+
if (!sid) throw new Error('Content SID is required');
|
|
145
|
+
try {
|
|
146
|
+
const content = await this.twilioClient.content.v1.contents(sid).fetch();
|
|
147
|
+
return content;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw new Error(`Failed to get template: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Attempt to check approval status.
|
|
155
|
+
* Twilio SDK may not expose approval requests directly; return content and null approval by default.
|
|
156
|
+
*/
|
|
157
|
+
async checkApprovalStatus(sid) {
|
|
158
|
+
if (!sid) throw new Error('Content SID is required');
|
|
159
|
+
const content = await this.getTemplate(sid);
|
|
160
|
+
try {
|
|
161
|
+
const links = (content && content.links) || {};
|
|
162
|
+
let approvalsUrl = links.approvals || links.approvalRequests || links.approval_requests;
|
|
163
|
+
if (!approvalsUrl) {
|
|
164
|
+
approvalsUrl = 'https://content.twilio.com/v1/Content/' + sid + '/ApprovalRequests';
|
|
165
|
+
}
|
|
166
|
+
const resp = await axios.get(approvalsUrl, { auth: { username: this.accountSid, password: this.authToken } });
|
|
167
|
+
const data = resp && resp.data ? resp.data : null;
|
|
168
|
+
let approvals = [];
|
|
169
|
+
if (data) {
|
|
170
|
+
approvals = data.approval_requests || data.approvalRequests || data.results || data.data || (Array.isArray(data) ? data : []);
|
|
171
|
+
if (!Array.isArray(approvals) && data.approvalRequest) approvals = [data.approvalRequest];
|
|
172
|
+
}
|
|
173
|
+
const approvalRequest = Array.isArray(approvals) && approvals.length > 0 ? approvals[0] : null;
|
|
174
|
+
return { content, approvalRequest };
|
|
175
|
+
} catch (error) {
|
|
176
|
+
// If approval endpoint unavailable, return content only
|
|
177
|
+
return { content, approvalRequest: null, warning: error.message };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Submit template for approval (best-effort placeholder)
|
|
183
|
+
*/
|
|
184
|
+
async submitForApproval(contentSid, name, category) {
|
|
185
|
+
if (!contentSid) throw new Error('Content SID is required');
|
|
186
|
+
const content = await this.getTemplate(contentSid);
|
|
187
|
+
try {
|
|
188
|
+
const links = (content && content.links) || {};
|
|
189
|
+
let approvalsUrl = links.approvals || links.approvalRequests || links.approval_requests;
|
|
190
|
+
if (!approvalsUrl) {
|
|
191
|
+
approvalsUrl = 'https://content.twilio.com/v1/Content/' + contentSid + '/ApprovalRequests';
|
|
192
|
+
}
|
|
193
|
+
const payload = {
|
|
194
|
+
name,
|
|
195
|
+
category,
|
|
196
|
+
friendly_name: name,
|
|
197
|
+
categories: category ? [category] : undefined,
|
|
198
|
+
channel: 'whatsapp'
|
|
199
|
+
};
|
|
200
|
+
const resp = await axios.post(approvalsUrl, payload, { auth: { username: this.accountSid, password: this.authToken } });
|
|
201
|
+
return { success: true, contentSid, approvalRequest: resp.data || null };
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw new Error(`Failed to submit for approval: ${error.message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Delete a template/content by SID
|
|
209
|
+
*/
|
|
210
|
+
async deleteTemplate(sid) {
|
|
211
|
+
if (!this.isConnected || !this.twilioClient) {
|
|
212
|
+
throw new Error('Twilio provider not initialized');
|
|
213
|
+
}
|
|
214
|
+
if (!sid) throw new Error('Content SID is required');
|
|
215
|
+
try {
|
|
216
|
+
await this.twilioClient.content.v1.contents(sid).remove();
|
|
217
|
+
return { success: true };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw new Error(`Failed to delete template: ${error.message}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create a template/content using Twilio Content API
|
|
225
|
+
* @param {Object} templateData - Must follow Twilio Content API schema
|
|
226
|
+
*/
|
|
227
|
+
async createTemplate(templateData) {
|
|
228
|
+
if (!this.isConnected || !this.twilioClient) {
|
|
229
|
+
throw new Error('Twilio provider not initialized');
|
|
230
|
+
}
|
|
231
|
+
if (!templateData || typeof templateData !== 'object') {
|
|
232
|
+
throw new Error('templateData must be an object');
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const created = await this.twilioClient.content.v1.contents.create(templateData);
|
|
236
|
+
return created;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
throw new Error(`Failed to create template: ${error.message}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
135
242
|
}
|
|
136
243
|
|
|
137
244
|
module.exports = { TwilioProvider };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { TwilioProvider } = require('./TwilioProvider');
|
|
2
|
+
const { BaileysProvider } = require('./BaileysProvider');
|
|
3
|
+
|
|
4
|
+
const _providers = new Map();
|
|
5
|
+
|
|
6
|
+
function registerProvider(name, ProviderClass) {
|
|
7
|
+
if (!name || !ProviderClass) throw new Error('registerProvider requires name and ProviderClass');
|
|
8
|
+
_providers.set(String(name).toLowerCase(), ProviderClass);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getProvider(name) {
|
|
12
|
+
return _providers.get(String(name || '').toLowerCase());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createProvider(name, config) {
|
|
16
|
+
const ProviderClass = getProvider(name);
|
|
17
|
+
if (!ProviderClass) throw new Error(`Unsupported provider: ${name}`);
|
|
18
|
+
return new ProviderClass(config || {});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Register built-ins
|
|
22
|
+
registerProvider('twilio', TwilioProvider);
|
|
23
|
+
registerProvider('baileys', BaileysProvider);
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
registerProvider,
|
|
27
|
+
getProvider,
|
|
28
|
+
createProvider
|
|
29
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const defaults = {
|
|
4
|
+
provider: { name: 'twilio', config: {} },
|
|
5
|
+
storage: null,
|
|
6
|
+
features: { airtable: true, s3: true },
|
|
7
|
+
assistants: { registry: {}, select: {}, getAssistantById: null },
|
|
8
|
+
handlers: {},
|
|
9
|
+
interactive: {}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function merge(a, b) {
|
|
13
|
+
if (!b) return { ...a };
|
|
14
|
+
const out = { ...a };
|
|
15
|
+
for (const k of Object.keys(b)) {
|
|
16
|
+
if (b[k] && typeof b[k] === 'object' && !Array.isArray(b[k])) {
|
|
17
|
+
out[k] = merge(a[k] || {}, b[k]);
|
|
18
|
+
} else {
|
|
19
|
+
out[k] = b[k];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function loadNexusConfig(userConfig) {
|
|
26
|
+
if (userConfig && typeof userConfig === 'object') {
|
|
27
|
+
return merge(defaults, userConfig);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const localPath = path.resolve(process.cwd(), 'nexus.config.js');
|
|
31
|
+
const fileConfig = require(localPath);
|
|
32
|
+
return merge(defaults, fileConfig || {});
|
|
33
|
+
} catch {
|
|
34
|
+
return { ...defaults };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { loadNexusConfig, defaults };
|
|
@@ -34,7 +34,7 @@ const addInsAssistantController = async (req, res) => {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
const addMsgAssistantController = async (req, res) => {
|
|
37
|
-
const { code, messages, reply } = req.body;
|
|
37
|
+
const { code, messages, reply = false } = req.body;
|
|
38
38
|
|
|
39
39
|
try {
|
|
40
40
|
const ans = await addMsgAssistant(code, messages, reply);
|
|
@@ -47,7 +47,7 @@ const addMsgAssistantController = async (req, res) => {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
const createAssistantController = async (req, res) => {
|
|
50
|
-
const { assistant_id, codes, messages=[], force=false } = req.body;
|
|
50
|
+
const { assistant_id, codes, instrucciones=[], messages=[], force=false } = req.body;
|
|
51
51
|
if (!Array.isArray(codes) || codes.length === 0) {
|
|
52
52
|
return res.status(400).send({ error: 'codes must be a non-empty array' });
|
|
53
53
|
}
|
|
@@ -62,7 +62,7 @@ const createAssistantController = async (req, res) => {
|
|
|
62
62
|
if (!force) continue;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
await createAssistant(code, assistant_id, messages, thread);
|
|
65
|
+
await createAssistant(code, assistant_id, [...instrucciones, ...messages], thread);
|
|
66
66
|
console.log('messages', messages);
|
|
67
67
|
for (const message of messages) {
|
|
68
68
|
console.log('message', message);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { Message } = require('../models/messageModel.js');
|
|
2
|
+
|
|
1
3
|
// Import from Nexus core
|
|
2
4
|
const { sendMessage } = require('../core/NexusMessaging');
|
|
3
5
|
|
|
@@ -223,8 +225,36 @@ const sendBulkMessageAirtableController = async (req, res) => {
|
|
|
223
225
|
}
|
|
224
226
|
};
|
|
225
227
|
|
|
228
|
+
const getLastInteractionController = async (req, res) => {
|
|
229
|
+
const { code } = req.body;
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const lastMessage = await Message.findOne({
|
|
233
|
+
$or: [
|
|
234
|
+
{ numero: code },
|
|
235
|
+
{ group_id: code }
|
|
236
|
+
]
|
|
237
|
+
}).sort({ createdAt: -1 }).exec();
|
|
238
|
+
|
|
239
|
+
if (!lastMessage) {
|
|
240
|
+
return res.status(404).send({ message: 'No messages found for the provided code.' });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const createdAt = new Date(lastMessage.createdAt);
|
|
244
|
+
const now = new Date();
|
|
245
|
+
const timeDiffMs = now - createdAt;
|
|
246
|
+
const minutes = Math.floor(timeDiffMs / (1000 * 60));
|
|
247
|
+
|
|
248
|
+
return res.status(200).send({ message: 'Last interaction retrieved successfully.', lastMessage, minutes });
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(error);
|
|
251
|
+
return res.status(500).send({ message: 'Failed to retrieve the last interaction.', error });
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
226
255
|
module.exports = {
|
|
227
256
|
sendMessageController,
|
|
228
257
|
sendBulkMessageController,
|
|
229
|
-
sendBulkMessageAirtableController
|
|
258
|
+
sendBulkMessageAirtableController,
|
|
259
|
+
getLastInteractionController
|
|
230
260
|
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
const { TwilioProvider } = require('../adapters/TwilioProvider');
|
|
2
|
-
const { BaileysProvider } = require('../adapters/BaileysProvider');
|
|
3
|
-
const mongoose = require('mongoose');
|
|
4
1
|
const { airtable, getBase } = require('../config/airtableConfig');
|
|
2
|
+
const { convertTwilioToInternalFormat } = require('../helpers/twilioHelper');
|
|
3
|
+
const { replyAssistant } = require('../services/assistantService');
|
|
4
|
+
const { createProvider } = require('../adapters/registry');
|
|
5
|
+
|
|
6
|
+
const mongoose = require('mongoose');
|
|
5
7
|
const OpenAI = require('openai');
|
|
8
|
+
const EventEmitter = require('events');
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Core messaging class that manages providers and message handling
|
|
@@ -23,6 +26,8 @@ class NexusMessaging {
|
|
|
23
26
|
onKeyword: null,
|
|
24
27
|
onFlow: null
|
|
25
28
|
};
|
|
29
|
+
this.events = new EventEmitter();
|
|
30
|
+
this.middleware = { any: [], message: [], interactive: [], media: [], command: [], keyword: [], flow: [] };
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/**
|
|
@@ -79,6 +84,54 @@ class NexusMessaging {
|
|
|
79
84
|
return this.llmProvider;
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Get the underlying messaging provider instance
|
|
89
|
+
*/
|
|
90
|
+
getProvider() {
|
|
91
|
+
return this.provider;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Access the internal event bus (EventEmitter)
|
|
96
|
+
*/
|
|
97
|
+
getEventBus() {
|
|
98
|
+
return this.events;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Register middleware. Usage: use(type, fn) or use(fn) for global.
|
|
103
|
+
* Middleware signature: async (messageData, nexus, next) => {}
|
|
104
|
+
*/
|
|
105
|
+
use(typeOrFn, maybeFn) {
|
|
106
|
+
if (typeof typeOrFn === 'function') {
|
|
107
|
+
this.middleware.any.push(typeOrFn);
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
const type = String(typeOrFn || '').toLowerCase();
|
|
111
|
+
const fn = maybeFn;
|
|
112
|
+
if (!this.middleware[type]) this.middleware[type] = [];
|
|
113
|
+
this.middleware[type].push(fn);
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Internal helper to run middleware pipeline and the final handler
|
|
119
|
+
*/
|
|
120
|
+
async _runPipeline(type, messageData, finalHandler) {
|
|
121
|
+
const chain = [...(this.middleware.any || []), ...(this.middleware[type] || []), async (ctx) => { return finalHandler(ctx); }];
|
|
122
|
+
let idx = -1;
|
|
123
|
+
const runner = async (i) => {
|
|
124
|
+
if (i <= idx) throw new Error('next() called multiple times');
|
|
125
|
+
idx = i;
|
|
126
|
+
const fn = chain[i];
|
|
127
|
+
if (!fn) return;
|
|
128
|
+
return await fn(messageData, this, () => runner(i+1));
|
|
129
|
+
};
|
|
130
|
+
return await runner(0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
82
135
|
/**
|
|
83
136
|
* Initialize Nexus with all services
|
|
84
137
|
* @param {Object} options - Configuration options
|
|
@@ -122,17 +175,7 @@ class NexusMessaging {
|
|
|
122
175
|
* @param {Object} providerConfig - Provider-specific configuration
|
|
123
176
|
*/
|
|
124
177
|
async initializeProvider(providerType, providerConfig) {
|
|
125
|
-
|
|
126
|
-
case 'twilio':
|
|
127
|
-
this.provider = new TwilioProvider(providerConfig);
|
|
128
|
-
break;
|
|
129
|
-
case 'baileys':
|
|
130
|
-
this.provider = new BaileysProvider(providerConfig);
|
|
131
|
-
break;
|
|
132
|
-
default:
|
|
133
|
-
throw new Error(`Unsupported provider: ${providerType}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
178
|
+
this.provider = createProvider(providerType, providerConfig);
|
|
136
179
|
await this.provider.initialize();
|
|
137
180
|
}
|
|
138
181
|
|
|
@@ -209,12 +252,18 @@ class NexusMessaging {
|
|
|
209
252
|
throw new Error('No provider initialized');
|
|
210
253
|
}
|
|
211
254
|
|
|
212
|
-
|
|
255
|
+
// Backward compatibility: accept `code` as destination
|
|
256
|
+
const normalized = { ...messageData };
|
|
257
|
+
if (!normalized.to && normalized.code) {
|
|
258
|
+
normalized.to = normalized.code;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const result = await this.provider.sendMessage(normalized);
|
|
213
262
|
|
|
214
263
|
// Store message if storage is configured
|
|
215
264
|
if (this.messageStorage) {
|
|
216
265
|
await this.messageStorage.saveMessage({
|
|
217
|
-
...
|
|
266
|
+
...normalized,
|
|
218
267
|
messageId: result.messageId,
|
|
219
268
|
provider: result.provider,
|
|
220
269
|
timestamp: new Date(),
|
|
@@ -268,44 +317,111 @@ class NexusMessaging {
|
|
|
268
317
|
}
|
|
269
318
|
|
|
270
319
|
async handleMessage(messageData) {
|
|
271
|
-
|
|
272
|
-
|
|
320
|
+
this.events.emit && this.events.emit('message:received', messageData);
|
|
321
|
+
const final = async (ctx) => {
|
|
322
|
+
if (this.handlers.onMessage) {
|
|
323
|
+
return await this.handlers.onMessage(ctx, this);
|
|
324
|
+
} else {
|
|
325
|
+
return await this.handleMessageWithAssistant(ctx);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const result = await this._runPipeline('message', messageData, final);
|
|
329
|
+
this.events.emit && this.events.emit('message:handled', messageData);
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async handleMessageWithAssistant(messageData) {
|
|
334
|
+
try {
|
|
335
|
+
// Convert Twilio format to internal format if needed
|
|
336
|
+
const internalMessage = this.provider.constructor.name === 'TwilioProvider'
|
|
337
|
+
? convertTwilioToInternalFormat(messageData)
|
|
338
|
+
: messageData;
|
|
339
|
+
|
|
340
|
+
// Extract standardized data
|
|
341
|
+
const extractedData = {
|
|
342
|
+
from: internalMessage.key?.remoteJid || '',
|
|
343
|
+
message: internalMessage.message?.conversation || '',
|
|
344
|
+
messageId: internalMessage.key?.id || '',
|
|
345
|
+
fromMe: internalMessage.key?.fromMe || false
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const response = await replyAssistant(
|
|
349
|
+
extractedData.from,
|
|
350
|
+
extractedData.message
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
if (response) {
|
|
354
|
+
await this.sendMessage({
|
|
355
|
+
to: extractedData.from,
|
|
356
|
+
message: response
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error('Error in handleMessageWithAssistant:', error);
|
|
273
361
|
}
|
|
274
362
|
}
|
|
275
363
|
|
|
276
364
|
async handleInteractive(messageData) {
|
|
277
|
-
// Store interactive message
|
|
278
365
|
if (this.messageStorage) {
|
|
279
366
|
await this.messageStorage.saveInteractive(messageData);
|
|
280
367
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
368
|
+
this.events.emit && this.events.emit('interactive:received', messageData);
|
|
369
|
+
const final = async (ctx) => {
|
|
370
|
+
if (this.handlers.onInteractive) {
|
|
371
|
+
return await this.handlers.onInteractive(ctx, this);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const result = await this._runPipeline('interactive', messageData, final);
|
|
375
|
+
this.events.emit && this.events.emit('interactive:handled', messageData);
|
|
376
|
+
return result;
|
|
285
377
|
}
|
|
286
378
|
|
|
287
379
|
async handleMedia(messageData) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
380
|
+
this.events.emit && this.events.emit('media:received', messageData);
|
|
381
|
+
const final = async (ctx) => {
|
|
382
|
+
if (this.handlers.onMedia) {
|
|
383
|
+
return await this.handlers.onMedia(ctx, this);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const result = await this._runPipeline('media', messageData, final);
|
|
387
|
+
this.events.emit && this.events.emit('media:handled', messageData);
|
|
388
|
+
return result;
|
|
291
389
|
}
|
|
292
390
|
|
|
293
391
|
async handleCommand(messageData) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
392
|
+
this.events.emit && this.events.emit('command:received', messageData);
|
|
393
|
+
const final = async (ctx) => {
|
|
394
|
+
if (this.handlers.onCommand) {
|
|
395
|
+
return await this.handlers.onCommand(ctx, this);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
const result = await this._runPipeline('command', messageData, final);
|
|
399
|
+
this.events.emit && this.events.emit('command:handled', messageData);
|
|
400
|
+
return result;
|
|
297
401
|
}
|
|
298
402
|
|
|
299
403
|
async handleKeyword(messageData) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
404
|
+
this.events.emit && this.events.emit('keyword:received', messageData);
|
|
405
|
+
const final = async (ctx) => {
|
|
406
|
+
if (this.handlers.onKeyword) {
|
|
407
|
+
return await this.handlers.onKeyword(ctx, this);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const result = await this._runPipeline('keyword', messageData, final);
|
|
411
|
+
this.events.emit && this.events.emit('keyword:handled', messageData);
|
|
412
|
+
return result;
|
|
303
413
|
}
|
|
304
414
|
|
|
305
415
|
async handleFlow(messageData) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
416
|
+
this.events.emit && this.events.emit('flow:received', messageData);
|
|
417
|
+
const final = async (ctx) => {
|
|
418
|
+
if (this.handlers.onFlow) {
|
|
419
|
+
return await this.handlers.onFlow(ctx, this);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
const result = await this._runPipeline('flow', messageData, final);
|
|
423
|
+
this.events.emit && this.events.emit('flow:handled', messageData);
|
|
424
|
+
return result;
|
|
309
425
|
}
|
|
310
426
|
|
|
311
427
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { downloadFileFromS3, generatePresignedUrl } = require('../config/awsConfig.js');
|
|
2
2
|
const { openaiClient } = require('../config/llmConfig.js');
|
|
3
3
|
|
|
4
|
-
const {
|
|
4
|
+
const { Message } = require('../models/messageModel.js');
|
|
5
5
|
|
|
6
6
|
const { convertPdfToImages } = require('./filesHelper.js');
|
|
7
7
|
const { analyzeImage } = require('../helpers/llmsHelper.js');
|
|
@@ -78,7 +78,7 @@ async function getLastMessages(code) {
|
|
|
78
78
|
query.is_group = false;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
const lastMessages = await
|
|
81
|
+
const lastMessages = await Message.find(query).sort({ timestamp: -1 });
|
|
82
82
|
console.log('[getLastMessages] lastMessages', lastMessages.map(msg => msg.body).join('\n\n'));
|
|
83
83
|
|
|
84
84
|
if (lastMessages.length === 0) return [];
|
|
@@ -86,7 +86,7 @@ async function getLastMessages(code) {
|
|
|
86
86
|
let patientReply = [];
|
|
87
87
|
for (const message of lastMessages) {
|
|
88
88
|
patientReply.push(message);
|
|
89
|
-
await
|
|
89
|
+
await Message.updateOne(
|
|
90
90
|
{ message_id: message.message_id, timestamp: message.timestamp },
|
|
91
91
|
{ $set: { processed: true } }
|
|
92
92
|
);
|
|
@@ -101,7 +101,7 @@ async function getLastMessages(code) {
|
|
|
101
101
|
|
|
102
102
|
async function getLastNMessages(code, n) {
|
|
103
103
|
try {
|
|
104
|
-
const lastMessages = await
|
|
104
|
+
const lastMessages = await Message.find({ numero: code })
|
|
105
105
|
.sort({ timestamp: -1 })
|
|
106
106
|
.limit(n);
|
|
107
107
|
|
|
@@ -149,7 +149,7 @@ function formatMessage(reply) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
async function downloadMediaAndCreateFile(code, reply) {
|
|
152
|
-
const resultMedia = await
|
|
152
|
+
const resultMedia = await Message.findOne({
|
|
153
153
|
message_id: reply.message_id,
|
|
154
154
|
timestamp: reply.timestamp,
|
|
155
155
|
media: { $ne: null }
|
|
@@ -259,7 +259,7 @@ async function processMessage(code, reply, thread) {
|
|
|
259
259
|
|
|
260
260
|
console.log('Formatted message:', formattedMessage);
|
|
261
261
|
|
|
262
|
-
await
|
|
262
|
+
await Message.updateOne(
|
|
263
263
|
{ message_id: reply.message_id, timestamp: reply.timestamp },
|
|
264
264
|
{ $set: { assistant_id: thread.assistant_id, thread_id: thread.thread_id } }
|
|
265
265
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { Message, insertMessage, getMessageValues } = require('../models/messageModel.js');
|
|
2
2
|
const { uploadMediaToS3 } = require('./mediaHelper.js');
|
|
3
3
|
const { downloadMediaMessage } = require('baileys');
|
|
4
4
|
|
|
@@ -103,7 +103,7 @@ function extractContentTypeAndReply(message, messageType) {
|
|
|
103
103
|
async function isRecentMessage(chatId) {
|
|
104
104
|
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
|
105
105
|
|
|
106
|
-
const recentMessage = await
|
|
106
|
+
const recentMessage = await Message.find({
|
|
107
107
|
$or: [{ group_id: chatId }, { numero: chatId }],
|
|
108
108
|
timestamp: { $gte: fiveMinutesAgo.toISOString() }
|
|
109
109
|
}).sort({ timestamp: -1 }).limit(1);
|
|
@@ -112,7 +112,7 @@ async function isRecentMessage(chatId) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
async function getLastMessages(chatId, n) {
|
|
115
|
-
const messages = await
|
|
115
|
+
const messages = await Message.find({ group_id: chatId })
|
|
116
116
|
.sort({ timestamp: -1 })
|
|
117
117
|
.limit(n)
|
|
118
118
|
.select('timestamp numero nombre_whatsapp body');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { Message } = require('../models/messageModel');
|
|
2
2
|
|
|
3
3
|
const axios = require('axios');
|
|
4
4
|
const { v4: uuidv4 } = require('uuid');
|
|
@@ -68,7 +68,7 @@ function extractTitle(message, mediaType) {
|
|
|
68
68
|
async function isRecentMessage(chatId) {
|
|
69
69
|
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
|
70
70
|
|
|
71
|
-
const recentMessage = await
|
|
71
|
+
const recentMessage = await Message.find({
|
|
72
72
|
$or: [{ group_id: chatId }, { numero: chatId }],
|
|
73
73
|
timestamp: { $gte: fiveMinutesAgo.toISOString() }
|
|
74
74
|
}).sort({ timestamp: -1 }).limit(1);
|
|
@@ -78,7 +78,7 @@ async function isRecentMessage(chatId) {
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
async function getLastMessages(chatId, n) {
|
|
81
|
-
const messages = await
|
|
81
|
+
const messages = await Message.find({ numero: chatId })
|
|
82
82
|
.sort({ timestamp: -1 })
|
|
83
83
|
.limit(n)
|
|
84
84
|
.select('timestamp numero nombre_whatsapp body');
|
package/lib/index.d.ts
CHANGED