@peopl-health/nexus 1.0.3 → 1.1.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/README.md +123 -0
- package/examples/basic-usage.js +22 -77
- package/lib/controllers/assistantController.js +168 -0
- package/lib/controllers/conversationController.js +582 -0
- package/lib/controllers/mediaController.js +105 -0
- package/lib/controllers/messageController.js +218 -0
- package/lib/controllers/templateController.js +631 -0
- package/lib/index.js +8 -6
- package/lib/routes/index.js +87 -0
- package/lib/utils/index.js +24 -11
- package/lib/utils/mongoAuthConfig.js +13 -3
- package/lib/utils/twilioHelper.js +0 -2
- package/lib/utils/whatsappHelper.js +0 -8
- package/package.json +14 -9
package/README.md
CHANGED
|
@@ -219,6 +219,129 @@ await nexus.sendScheduledMessage({
|
|
|
219
219
|
});
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
+
## Routes for Consumer Servers
|
|
223
|
+
|
|
224
|
+
The library exports pre-built route definitions that consumer servers can use with minimal code:
|
|
225
|
+
|
|
226
|
+
### Option 1: Setup All Default Routes (Minimal Code)
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
const express = require('express');
|
|
230
|
+
const { setupDefaultRoutes } = require('@peopl-health/nexus');
|
|
231
|
+
|
|
232
|
+
const app = express();
|
|
233
|
+
|
|
234
|
+
// Define your controllers
|
|
235
|
+
const myControllers = {
|
|
236
|
+
// Assistant controllers
|
|
237
|
+
activeAssistantController: (req, res) => { /* your logic */ },
|
|
238
|
+
createAssistantController: (req, res) => { /* your logic */ },
|
|
239
|
+
listAssistantController: (req, res) => { /* your logic */ },
|
|
240
|
+
addInsAssistantController: (req, res) => { /* your logic */ },
|
|
241
|
+
addMsgAssistantController: (req, res) => { /* your logic */ },
|
|
242
|
+
getInfoAssistantController: (req, res) => { /* your logic */ },
|
|
243
|
+
switchAssistantController: (req, res) => { /* your logic */ },
|
|
244
|
+
stopAssistantController: (req, res) => { /* your logic */ },
|
|
245
|
+
|
|
246
|
+
// Conversation controllers
|
|
247
|
+
getConversationController: (req, res) => { /* your logic */ },
|
|
248
|
+
searchConversationsController: (req, res) => { /* your logic */ },
|
|
249
|
+
getConversationsByNameController: (req, res) => { /* your logic */ },
|
|
250
|
+
getConversationMessagesController: (req, res) => { /* your logic */ },
|
|
251
|
+
getNewMessagesController: (req, res) => { /* your logic */ },
|
|
252
|
+
getConversationReplyController: (req, res) => { /* your logic */ },
|
|
253
|
+
sendTemplateToNewNumberController: (req, res) => { /* your logic */ },
|
|
254
|
+
markMessagesAsReadController: (req, res) => { /* your logic */ },
|
|
255
|
+
|
|
256
|
+
// Media controllers
|
|
257
|
+
getMediaController: (req, res) => { /* your logic */ },
|
|
258
|
+
handleFileUpload: (req, res) => { /* your logic */ },
|
|
259
|
+
|
|
260
|
+
// Message controllers
|
|
261
|
+
sendMessageController: (req, res) => { /* your logic */ },
|
|
262
|
+
sendBulkMessageController: (req, res) => { /* your logic */ },
|
|
263
|
+
sendBulkMessageAirtableController: (req, res) => { /* your logic */ },
|
|
264
|
+
|
|
265
|
+
// Template controllers
|
|
266
|
+
createTemplate: (req, res) => { /* your logic */ },
|
|
267
|
+
listTemplates: (req, res) => { /* your logic */ },
|
|
268
|
+
getPredefinedTemplates: (req, res) => { /* your logic */ },
|
|
269
|
+
getTemplate: (req, res) => { /* your logic */ },
|
|
270
|
+
getCompleteTemplate: (req, res) => { /* your logic */ },
|
|
271
|
+
createFlow: (req, res) => { /* your logic */ },
|
|
272
|
+
deleteFlow: (req, res) => { /* your logic */ },
|
|
273
|
+
submitForApproval: (req, res) => { /* your logic */ },
|
|
274
|
+
checkApprovalStatus: (req, res) => { /* your logic */ },
|
|
275
|
+
deleteTemplate: (req, res) => { /* your logic */ }
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Setup all default routes with one line
|
|
279
|
+
setupDefaultRoutes(app, myControllers);
|
|
280
|
+
|
|
281
|
+
// Add your custom routes
|
|
282
|
+
app.get('/api/custom', myCustomController);
|
|
283
|
+
app.post('/api/special', mySpecialController);
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Option 2: Individual Route Control
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
const express = require('express');
|
|
290
|
+
const { routes, createRouter } = require('@peopl-health/nexus');
|
|
291
|
+
|
|
292
|
+
const app = express();
|
|
293
|
+
|
|
294
|
+
// Setup only specific route groups
|
|
295
|
+
app.use('/api/assistant', createRouter(routes.assistantRoutes, myControllers));
|
|
296
|
+
app.use('/api/message', createRouter(routes.messageRoutes, myControllers));
|
|
297
|
+
app.use('/api/template', createRouter(routes.templateRoutes, myControllers));
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Available Default Route Groups
|
|
301
|
+
|
|
302
|
+
The `setupDefaultRoutes()` function sets up these 5 route groups:
|
|
303
|
+
|
|
304
|
+
#### **Assistant Routes** (`/api/assistant`)
|
|
305
|
+
- `POST /active` - Set active assistant
|
|
306
|
+
- `POST /add-instruction` - Add instruction to assistant
|
|
307
|
+
- `POST /add-msg` - Add message to assistant
|
|
308
|
+
- `POST /create` - Create new assistant
|
|
309
|
+
- `POST /get-info` - Get assistant information
|
|
310
|
+
- `GET /list` - List all assistants
|
|
311
|
+
- `POST /switch` - Switch between assistants
|
|
312
|
+
- `POST /stop` - Stop assistant
|
|
313
|
+
|
|
314
|
+
#### **Conversation Routes** (`/api/conversation`)
|
|
315
|
+
- `GET /` - Get conversations
|
|
316
|
+
- `GET /search` - Search conversations
|
|
317
|
+
- `GET /by-name` - Get conversations by name
|
|
318
|
+
- `GET /:phoneNumber` - Get conversation messages
|
|
319
|
+
- `GET /:phoneNumber/new` - Get new messages
|
|
320
|
+
- `POST /reply` - Send conversation reply
|
|
321
|
+
- `POST /send-template` - Send template to new number
|
|
322
|
+
- `POST /:phoneNumber/read` - Mark messages as read
|
|
323
|
+
|
|
324
|
+
#### **Media Routes** (`/api/media`)
|
|
325
|
+
- `GET /:key(*)` - Get media file
|
|
326
|
+
- `POST /upload` - Upload media file
|
|
327
|
+
|
|
328
|
+
#### **Message Routes** (`/api/message`)
|
|
329
|
+
- `POST /send` - Send single message
|
|
330
|
+
- `POST /send-bulk` - Send bulk messages
|
|
331
|
+
- `POST /send-bulk-airtable` - Send bulk messages from Airtable
|
|
332
|
+
|
|
333
|
+
#### **Template Routes** (`/api/template`)
|
|
334
|
+
- `POST /text` - Create text template
|
|
335
|
+
- `GET /` - List templates
|
|
336
|
+
- `GET /predefined` - Get predefined templates
|
|
337
|
+
- `GET /:id` - Get specific template
|
|
338
|
+
- `GET /complete/:sid` - Get complete template
|
|
339
|
+
- `POST /flow` - Create flow template
|
|
340
|
+
- `DELETE /flow/:sid` - Delete flow template
|
|
341
|
+
- `POST /approval` - Submit template for approval
|
|
342
|
+
- `GET /status/:sid` - Check approval status
|
|
343
|
+
- `DELETE /:id` - Delete template
|
|
344
|
+
|
|
222
345
|
## API Reference
|
|
223
346
|
|
|
224
347
|
### Nexus Class
|
package/examples/basic-usage.js
CHANGED
|
@@ -5,97 +5,42 @@ async function main() {
|
|
|
5
5
|
const nexus = new Nexus();
|
|
6
6
|
|
|
7
7
|
try {
|
|
8
|
-
// Initialize with
|
|
8
|
+
// Initialize with minimal configuration
|
|
9
9
|
await nexus.initialize({
|
|
10
10
|
providerConfig: {
|
|
11
11
|
accountSid: process.env.TWILIO_ACCOUNT_SID,
|
|
12
12
|
authToken: process.env.TWILIO_AUTH_TOKEN,
|
|
13
13
|
phoneNumber: process.env.TWILIO_PHONE_NUMBER
|
|
14
|
-
},
|
|
15
|
-
// storage: 'mongo' is default
|
|
16
|
-
storageConfig: {
|
|
17
|
-
uri: process.env.MONGODB_URI,
|
|
18
|
-
dbName: 'nexus_basic'
|
|
19
|
-
},
|
|
20
|
-
// parser: 'MessageParser' is default
|
|
21
|
-
parserConfig: {
|
|
22
|
-
commandPrefixes: ['/', '!'],
|
|
23
|
-
keywords: ['help', 'support', 'info'],
|
|
24
|
-
flowTriggers: ['start survey', 'begin onboarding']
|
|
25
|
-
},
|
|
26
|
-
// llm: 'openai' is default (optional for basic usage)
|
|
27
|
-
llmConfig: {
|
|
28
|
-
apiKey: process.env.OPENAI_API_KEY
|
|
29
14
|
}
|
|
30
15
|
});
|
|
31
16
|
|
|
32
|
-
// Set up
|
|
33
|
-
nexus.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
nexus.onCommand(async (messageData) => {
|
|
44
|
-
const { command, args } = messageData.command;
|
|
45
|
-
|
|
46
|
-
switch (command) {
|
|
47
|
-
case 'help':
|
|
48
|
-
await nexus.sendMessage({
|
|
49
|
-
to: messageData.from,
|
|
50
|
-
message: 'Available commands:\n/help - Show this help\n/status - Check system status\n/echo [text] - Echo your text'
|
|
51
|
-
});
|
|
52
|
-
break;
|
|
53
|
-
case 'status':
|
|
54
|
-
await nexus.sendMessage({
|
|
55
|
-
to: messageData.from,
|
|
56
|
-
message: `System Status: ${nexus.isConnected() ? 'Connected' : 'Disconnected'}`
|
|
57
|
-
});
|
|
58
|
-
break;
|
|
59
|
-
case 'echo':
|
|
60
|
-
const text = args.join(' ') || 'Nothing to echo!';
|
|
61
|
-
await nexus.sendMessage({
|
|
62
|
-
to: messageData.from,
|
|
63
|
-
message: `Echo: ${text}`
|
|
64
|
-
});
|
|
65
|
-
break;
|
|
66
|
-
default:
|
|
67
|
-
await nexus.sendMessage({
|
|
68
|
-
to: messageData.from,
|
|
69
|
-
message: `Unknown command: ${command}. Type /help for available commands.`
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
});
|
|
17
|
+
// Set up handlers with minimal code
|
|
18
|
+
nexus.setHandlers({
|
|
19
|
+
onMessage: async (messageData) => {
|
|
20
|
+
await nexus.sendMessage({
|
|
21
|
+
to: messageData.from,
|
|
22
|
+
message: `Echo: ${messageData.message}`
|
|
23
|
+
});
|
|
24
|
+
},
|
|
73
25
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
case 'info':
|
|
86
|
-
await nexus.sendMessage({
|
|
87
|
-
to: messageData.from,
|
|
88
|
-
message: 'This is a basic Nexus messaging bot example. It demonstrates simple message handling without AI assistants.'
|
|
89
|
-
});
|
|
90
|
-
break;
|
|
26
|
+
onCommand: async (messageData) => {
|
|
27
|
+
const { command } = messageData.command;
|
|
28
|
+
const responses = {
|
|
29
|
+
help: 'Commands: /help, /status',
|
|
30
|
+
status: `Status: ${nexus.isConnected() ? 'Online' : 'Offline'}`
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
await nexus.sendMessage({
|
|
34
|
+
to: messageData.from,
|
|
35
|
+
message: responses[command] || `Unknown command: ${command}`
|
|
36
|
+
});
|
|
91
37
|
}
|
|
92
38
|
});
|
|
93
39
|
|
|
94
|
-
console.log('Nexus
|
|
95
|
-
console.log('Send messages to your configured phone number to test');
|
|
40
|
+
console.log('Nexus started - send messages to test');
|
|
96
41
|
|
|
97
42
|
} catch (error) {
|
|
98
|
-
console.error('Error
|
|
43
|
+
console.error('Error:', error);
|
|
99
44
|
process.exit(1);
|
|
100
45
|
}
|
|
101
46
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Optional config - will be undefined if not available
|
|
2
|
+
let Config_ID;
|
|
3
|
+
try {
|
|
4
|
+
Config_ID = require('../config/airtableConfig')?.Config_ID;
|
|
5
|
+
} catch (e) {
|
|
6
|
+
Config_ID = null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Optional model imports
|
|
10
|
+
let updateThreadActive, updateThreadStop, Thread;
|
|
11
|
+
try {
|
|
12
|
+
const threadModel = require('../models/threadModel');
|
|
13
|
+
updateThreadActive = threadModel.updateThreadActive;
|
|
14
|
+
updateThreadStop = threadModel.updateThreadStop;
|
|
15
|
+
Thread = threadModel.Thread;
|
|
16
|
+
} catch (e) {
|
|
17
|
+
// Models not available
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Optional service imports
|
|
21
|
+
let getRecordByFilter, createAssistant, addMsgAssistant, addInsAssistant, getThreadInfo, switchAssistant, sendMessage;
|
|
22
|
+
try {
|
|
23
|
+
getRecordByFilter = require('../services/airtableService')?.getRecordByFilter;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Service not available
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const assistantService = require('../services/assistantService');
|
|
29
|
+
createAssistant = assistantService.createAssistant;
|
|
30
|
+
addMsgAssistant = assistantService.addMsgAssistant;
|
|
31
|
+
addInsAssistant = assistantService.addInsAssistant;
|
|
32
|
+
getThreadInfo = assistantService.getThreadInfo;
|
|
33
|
+
switchAssistant = assistantService.switchAssistant;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
// Service not available
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
sendMessage = require('../services/whatsappService')?.sendMessage;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
// Service not available
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
const activeAssistantController = async (req, res) => {
|
|
45
|
+
const { code, active } = req.body;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await updateThreadActive(code, active);
|
|
49
|
+
return res.status(200).send({ message: 'Active assistant' });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.log(error);
|
|
52
|
+
return res.status(500).send({ message: 'Failed to active assistant', error });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const addInsAssistantController = async (req, res) => {
|
|
57
|
+
const { code, instruction } = req.body;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const ans = await addInsAssistant(code, instruction);
|
|
61
|
+
if (ans) await sendMessage({code, message: ans, fileType: 'text'});
|
|
62
|
+
return res.status(200).send({ message: 'Add instruction to the assistant' });
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.log(error);
|
|
65
|
+
res.status(500).send({ message: 'Failed to add instruction to assistant', error });
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const addMsgAssistantController = async (req, res) => {
|
|
70
|
+
const { code, messages, reply } = req.body;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const ans = await addMsgAssistant(code, messages, reply);
|
|
74
|
+
if (ans) await sendMessage({code, message: ans, fileType: 'text'});
|
|
75
|
+
return res.status(200).send({ message: 'Add message to the assistant' });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.log(error);
|
|
78
|
+
res.status(500).send({ message: 'Failed to add message assistant', error });
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const createAssistantController = async (req, res) => {
|
|
83
|
+
const { assistant_id, codes, messages=[], force=false } = req.body;
|
|
84
|
+
if (!Array.isArray(codes) || codes.length === 0) {
|
|
85
|
+
return res.status(400).send({ error: 'codes must be a non-empty array' });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
console.log('codes', codes);
|
|
90
|
+
for (const code of codes) {
|
|
91
|
+
const thread = await Thread.findOne({ code: code });
|
|
92
|
+
if (thread !== null) {
|
|
93
|
+
await switchAssistant(code, assistant_id);
|
|
94
|
+
console.log('FORCE', force);
|
|
95
|
+
if (!force) continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await createAssistant(code, assistant_id, messages, thread);
|
|
99
|
+
console.log('messages', messages);
|
|
100
|
+
for (const message of messages) {
|
|
101
|
+
console.log('message', message);
|
|
102
|
+
await sendMessage({code, message, fileType: 'text'});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
res.status(200).send({ message: 'Create the assistant' });
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.log(error);
|
|
108
|
+
res.status(500).send({ message: 'Failed to create assistant', error });
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getInfoAssistantController = async (req, res) => {
|
|
113
|
+
const { code } = req.body;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
let threadInfo = await getThreadInfo(code);
|
|
117
|
+
return res.status(200).send({ message: 'Send assistant info' , threadInfo});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.log(error);
|
|
120
|
+
res.status(500).send({ message: 'Failed to receive assistant info', error });
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const listAssistantController = async (req, res) => {
|
|
125
|
+
try {
|
|
126
|
+
const assistants = await getRecordByFilter(Config_ID, 'assistants', `status="${process.env.NODE_ENV}"`);
|
|
127
|
+
return res.status(200).send({ message: 'List assistants' , assistants});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.log(error);
|
|
130
|
+
res.status(500).send({ message: 'Failed to list assistants', error });
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const switchAssistantController = async (req, res) => {
|
|
135
|
+
const { code, assistant_id } = req.body;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await switchAssistant(code, assistant_id);
|
|
139
|
+
return res.status(200).send({ message: 'Switch assistant' });
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.log(error);
|
|
142
|
+
res.status(500).send({ message: 'Failed to switch assistant', error });
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const stopAssistantController = async (req, res) => {
|
|
147
|
+
const { code, stop } = req.body;
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
await updateThreadStop(code, stop);
|
|
151
|
+
return res.status(200).send({ message: 'Stop assistant' });
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.log(error);
|
|
154
|
+
return res.status(500).send({ message: 'Failed to stop assistant', error });
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
activeAssistantController,
|
|
161
|
+
addInsAssistantController,
|
|
162
|
+
addMsgAssistantController,
|
|
163
|
+
createAssistantController,
|
|
164
|
+
getInfoAssistantController,
|
|
165
|
+
listAssistantController,
|
|
166
|
+
switchAssistantController,
|
|
167
|
+
stopAssistantController
|
|
168
|
+
};
|