json-object-editor 0.10.443 → 0.10.501

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/_www/mcp-test.html +136 -0
  3. package/css/jif/Read Me.txt +5 -5
  4. package/css/jif/demo-files/demo.css +153 -153
  5. package/css/jif/demo-files/demo.js +30 -30
  6. package/css/jif/demo.html +285 -285
  7. package/css/jif/fonts/joeiconfont.svg +24 -24
  8. package/css/jif/ie7/ie7.css +45 -45
  9. package/css/jif/ie7/ie7.js +46 -46
  10. package/css/jif/selection.json +573 -573
  11. package/css/joe.css +12 -12
  12. package/css/jquery-ui-1.10.4.custom.min.css +5 -5
  13. package/css/jquery-ui.min.css +6 -6
  14. package/css/jquery.timepicker.css +72 -72
  15. package/es5-build/web-components/account-info.js +136 -136
  16. package/es5-build/web-components/capp-components.js +160 -160
  17. package/es5-build/web-components/capp-panel.js +85 -85
  18. package/es5-build/web-components/capp-view.js +73 -73
  19. package/es5-build/web-components/joe-autocomplete.js +149 -149
  20. package/es5-build/web-components/joe-button.js +132 -132
  21. package/es5-build/web-components/joe-card.js +92 -92
  22. package/es5-build/web-components/joe-component.js +74 -74
  23. package/es5-build/web-components/joe-field.js +70 -70
  24. package/es5-build/web-components/joe-list-item.js +176 -176
  25. package/es5-build/web-components/joe-user-cube.js +100 -100
  26. package/es5-build/web-components/report-components.js +133 -133
  27. package/grunt/build +86 -86
  28. package/grunt/package-lock.json +9105 -9105
  29. package/grunt/package.json +47 -47
  30. package/grunt/src +86 -86
  31. package/js/joe.js +10 -10
  32. package/js/leaflet.js +8 -8
  33. package/js/libs/adapter-latest.js +4400 -4400
  34. package/js/libs/craydent-1.9.2.js +11741 -11741
  35. package/js/libs/craydent-upload-2.0.0.js +394 -394
  36. package/js/libs/hammer.min.208.js +6 -6
  37. package/js/libs/jquery-3.5.1.min.js +2 -2
  38. package/js/libs/moment.min.js +6 -6
  39. package/js/native-shim.js +46 -46
  40. package/js/plugins/c3/c3.min.js +5 -5
  41. package/js/plugins/c3/d3.v3.min.js +4 -4
  42. package/js/plugins/threejs/Detector.js +78 -78
  43. package/js/plugins/threejs/LICENSE +21 -21
  44. package/js/plugins/threejs/MTLLoader.js +417 -417
  45. package/js/plugins/threejs/OBJLoader.js +564 -564
  46. package/js/plugins/threejs/OrbitControls.js +1037 -1037
  47. package/js/plugins/threejs/README.md +9 -9
  48. package/js/plugins/threejs/assets/female-croupier-2013-03-26.mtl +3 -3
  49. package/js/plugins/threejs/index.html +178 -178
  50. package/js/plugins/threejs/three.js +41507 -41507
  51. package/package.json +2 -2
  52. package/readme.md +113 -0
  53. package/server/init.js +7 -0
  54. package/server/modules/MCP.js +310 -0
  55. package/server/modules/Server.js +23 -2
  56. package/server/plugins/chatgpt-assistants.js +359 -359
  57. package/server/plugins/chatgpt-tools.js +79 -79
@@ -1,359 +1,359 @@
1
-
2
- const { run } = require("googleapis/build/src/apis/run");
3
- const OpenAI = require("openai");
4
-
5
- function ChatGPTAssistants() {
6
- const self = this;
7
-
8
- function coloredLog(message) {
9
- console.log(JOE.Utils.color('[chatgpt-assistants]', 'plugin', false), message);
10
- }
11
-
12
-
13
-
14
- function getAPIKey() {
15
- const setting = JOE.Utils.Settings('OPENAI_API_KEY');
16
- if (!setting) throw new Error("Missing OPENAI_API_KEY setting");
17
- return setting;
18
- }
19
-
20
- function newClient() {
21
- return new OpenAI({ apiKey: getAPIKey() });
22
- }
23
-
24
- function validateThreadId(thread_id) {
25
- const validPattern = /^[a-zA-Z0-9_-]+$/;
26
- if (!validPattern.test(thread_id)) {
27
- console.error("❌ Invalid characters in thread_id!");
28
- return false;
29
- }
30
- return true;
31
- }
32
- function stripHtml(html) {
33
- if (!html) return "";
34
- return html.replace(/<[^>]*>?/gm, '').trim();
35
- }
36
- this.getRandomTheme = async function(data, req, res) {
37
- try {
38
- const themes = ["hope", "peace", "perseverance", "joy", "strength", "love", "faith", "healing"];
39
- const randomTheme = themes[Math.floor(Math.random() * themes.length)];
40
-
41
- coloredLog("🎯 Selected Random Theme: " + randomTheme);
42
-
43
- return res.jsonp({
44
- theme: randomTheme
45
- });
46
-
47
- } catch (err) {
48
- coloredLog("❌ Error in getRandomTheme: " + err.message);
49
- return res.jsonp({ error: err.message });
50
- }
51
- };
52
-
53
- this.testAssistant = async function(data, req, res){
54
- const openai = newClient();
55
- const assistant = await openai.beta.assistants.retrieve("asst_HFzrtyAVyzDDwn1PLOIwU26W");
56
- thread = await openai.beta.threads.create();
57
-
58
- // 2. Add a user message
59
- await openai.beta.threads.messages.create({
60
- thread_id: thread.id,
61
- role: 'user',
62
- content: `Please analyze this business: welltyme.com`
63
- });
64
- coloredLog('⚡ Starting Assistant thread');
65
- console.log(assistant);
66
- return res.jsonp({ assistant });
67
- }
68
- this.syncAssistantToOpenAI = async function(data, req, res) {
69
- const openai = newClient();
70
-
71
- try {
72
- coloredLog("🔄 Starting syncAssistantToOpenAI");
73
-
74
- const assistant = JOE.Data.ai_assistant.find(a => a._id === data._id);
75
- if (!assistant) {
76
- return ({ error: "AI Assistant not found in Joe." });
77
- }
78
-
79
- // Check if updating or creating
80
- let existing = null;
81
- if (assistant.assistant_id) {
82
- try {
83
- existing = await openai.beta.assistants.retrieve(assistant.assistant_id);
84
- } catch (e) {
85
- console.warn('Assistant ID exists but not found at OpenAI, will create new.');
86
- existing = null;
87
- }
88
- }
89
- const payload = {
90
- name: assistant.name,
91
- instructions: stripHtml(assistant.instructions || ""),
92
- model: assistant.ai_model || "gpt-4o",
93
- tools: [],
94
- //file_search: { enabled: !!assistant.file_search_enabled },
95
- //code_interpreter: { enabled: !!assistant.code_interpreter_enabled }
96
- };
97
- // 1. Add user-defined function tools
98
- const parsedTools = Array.isArray(assistant.tools) ? assistant.tools : JSON.parse(assistant.tools || "[]");
99
- payload.tools.push(...parsedTools);
100
-
101
- // 2. Add built-in tools if toggled ON
102
- if (assistant.file_search_enabled) {
103
- payload.tools.push({ type: "file_search" });
104
- }
105
-
106
- if (assistant.code_interpreter_enabled) {
107
- payload.tools.push({ type: "code_interpreter" });
108
- }
109
- // if (existing && existing.file_ids?.length) {
110
- // payload.file_ids = existing.file_ids;
111
- // }
112
-
113
- // Only set file_ids if handling files manually
114
- if (assistant.file_ids && assistant.file_ids.length > 0) {
115
- payload.file_ids = assistant.file_ids;
116
- }
117
- coloredLog("📦 Payload for OpenAI sync:");
118
- coloredLog(JSON.stringify(payload, null, 2));
119
-
120
- let apiResponse;
121
-
122
- if (!assistant.assistant_id) {
123
- coloredLog("🆕 No assistant_id found. Creating new Assistant...");
124
- apiResponse = await openai.beta.assistants.create(payload);
125
- assistant.assistant_id = apiResponse.id;
126
- } else {
127
- coloredLog("♻️ assistant_id found. Updating existing Assistant...");
128
- apiResponse = await openai.beta.assistants.update(assistant.assistant_id, payload);
129
- }
130
-
131
- assistant.last_synced = new Date().toISOString();
132
-
133
- // 💾 Set status to "assistant_synced" if found
134
- const syncStatus = JOE.Data.status.find(s => s.code === "assistant_synced");
135
- if (syncStatus) {
136
- assistant.status = syncStatus._id;
137
- }
138
-
139
- await new Promise((resolve, reject) => {
140
- JOE.Storage.save(assistant, 'ai_assistant', function(err, saved) {
141
- if (err) {
142
- return reject(err);
143
- }
144
- resolve(saved);
145
- });
146
- });
147
-
148
- coloredLog("💾 Assistant sync saved back to Joe successfully.");
149
-
150
- return ({
151
- success: true,
152
- assistant_id: assistant.assistant_id,
153
- message: assistant.assistant_id ? "Assistant synced successfully." : "Assistant created successfully."
154
- });
155
-
156
- } catch (err) {
157
- coloredLog("❌ Error in syncAssistantToOpenAI: " + err.message);
158
- return ({ error: err.message });
159
- }
160
- };
161
- this.getThreadMessages = async function(data, req, res) {
162
- coloredLog("🔄 Starting getThreadMessages");
163
- try {
164
- const thread_id = data.thread_id;
165
- if (!thread_id) {
166
- return ({ error: "Missing thread ID." });
167
- }
168
- if(data.polling){
169
- coloredLog('Polling for thread messages: ' + thread_id);
170
- }
171
- const openai = newClient();
172
- const messages = await openai.beta.threads.messages.list(thread_id);
173
-
174
- //return res.jsonp({ success: true, messages: messages.data || [] });
175
- var succResp = { success: true, messages: messages.data || [] }
176
- return(succResp);
177
- } catch (err) {
178
- console.error('❌ getThreadMessages error:', err);
179
-
180
- return({ error: "Failed to load thread messages." });
181
- }
182
- };
183
- this.addMessage = async function(data, req, res) {
184
- try {
185
- const { conversation_id, content,object_id} = data;
186
- if (!conversation_id || !content) {
187
- return({ error: "Missing conversation ID or content." });
188
- }
189
-
190
- const openai = newClient();
191
-
192
- const convo = await new Promise((resolve, reject) => {
193
- JOE.Storage.load('ai_conversation', { _id: conversation_id }, (err, results) => {
194
- if (err || !results || !results.length) {
195
- return reject("Conversation not found.");
196
- }
197
- resolve(results[0]);
198
- });
199
- });
200
- var resave = false;
201
- //add objectID
202
- if(object_id && !convo.context_objects.includes(object_id)){
203
- if(!convo.context_objects) convo.context_objects = [];
204
- convo.context_objects.push(object_id);
205
- resave = true;
206
- }
207
-
208
- if (!convo.thread_id) {
209
- const thread = await openai.beta.threads.create();
210
- convo.thread_id = thread.id;
211
- resave = true;
212
- // await new Promise((resolve, reject) => {
213
- // JOE.Storage.save(convo, 'ai_conversation', function(err, saved) {
214
- // if (err) return reject(err);
215
- // resolve(saved);
216
- // });
217
- // });
218
- }
219
-
220
- if(resave){
221
- await new Promise((resolve, reject) => {
222
- JOE.Storage.save(convo, 'ai_conversation', function(err, saved) {
223
- if (err) return reject(err);
224
- resolve(saved);
225
- });
226
- });
227
-
228
- }
229
-
230
- await openai.beta.threads.messages.create(convo.thread_id, {
231
- role: "user",
232
- content: content
233
- });
234
- var runObj = null;
235
- // NOW ➔ trigger assistant reply if assistant is selected
236
- if (convo.assistants && convo.assistants.length > 0) {
237
- // const assistant_id = convo.assistants[0]; // Assuming you store OpenAI assistant ID here
238
- //get assistant object by id
239
- const assistant_id = data.assistant_id || convo.assistants?.[0]?.openai_id;
240
- const assistant = JOE.Data.ai_assistant.find(a => a._id === assistant_id);
241
- if (assistant.assistant_id) {
242
- runObj = await openai.beta.threads.runs.create(convo.thread_id, {
243
- assistant_id: assistant.assistant_id
244
- });
245
-
246
- console.log(`Assistant run started: ${runObj.id}`);
247
- // You could optionally poll for completion here
248
- }
249
- }else if(convo.assistant){
250
- const assistant = $J.get(convo.assistant);
251
- runObj = await openai.beta.threads.runs.create(convo.thread_id, {
252
- assistant_id: assistant.assistant_id // Assuming you store OpenAI assistant ID here
253
- });
254
-
255
- coloredLog(`Assistant run started: ${runObj.id}`);
256
- }
257
- return({ success: true,runObj });
258
- } catch (err) {
259
- console.error('❌ addMessage error:', err);
260
- return({ error: "Failed to send message.", message: err.message });
261
- }
262
- };
263
- this.getRunStatus = async function(data, req, res) {
264
- try {
265
- const run_id = data.run_id;
266
- const thread_id = data.thread_id;
267
- var errors = [];
268
- if (!run_id) errors.push("Missing run ID.");
269
- if(!thread_id) errors.push("Missing thread ID.");
270
-
271
- if (errors.length) {
272
- return { error: errors.join(" ") };
273
- }
274
-
275
- const openai = newClient();
276
- const run = await openai.beta.threads.runs.retrieve(thread_id, run_id);
277
- coloredLog("🔄 Run status retrieved: " + run.status +'assistant_id: ' + run.assistant_id);
278
- return {
279
- id: run.id,
280
- status: run.status,
281
- assistant_id: run.assistant_id,
282
- completed_at: run.completed_at,
283
- usage: run.usage || {}
284
- };
285
-
286
- } catch (err) {
287
- console.error("❌ getRunStatus error:", err);
288
- return { error: "Failed to check run status." };
289
- }
290
- };
291
-
292
- function getDefaultAssistant() {
293
- var asst_id = JOE.Utils.Settings('DEFAULT_AI_ASSISTANT');
294
- const defaultAssistant = $J.get(asst_id);
295
- if (!defaultAssistant) {
296
- throw new Error("Default Assistant not found.");
297
- }
298
- return defaultAssistant;
299
- }
300
- this.createConversation = async function(data, req, res) {
301
- //this function creates a new conversation
302
- //it should use the default assistant and add the context of the passed object id after being flattend to the conversation.
303
- //save user_is to the conversation object
304
-
305
- try {
306
- const { object_id, user_id } = data;
307
- const openai = newClient();
308
- const user = $J.get(user_id);
309
- const contextObject = $J.get(object_id);
310
- var newConvo = null;
311
- var assistant = getDefaultAssistant();
312
- var assistants = assistant? [assistant._id]:[];
313
- const convo ={
314
- _id:cuid(),
315
- created: new Date().toISOString(),
316
- _joeUpdated: new Date().toISOString(),
317
- itemtype: 'ai_conversation',
318
- name:`${user.name}'s conversation about ${contextObject.name} [${contextObject.itemtype}]`,
319
- user:user_id,
320
- members:[],
321
- assistant:assistant._id,
322
- context_objects:[object_id]
323
- }
324
-
325
- if (!convo.thread_id) {
326
- const thread = await openai.beta.threads.create();
327
- convo.thread_id = thread.id;
328
-
329
- newConvo = await new Promise((resolve, reject) => {
330
- JOE.Storage.save(convo, 'ai_conversation', function(err, saved) {
331
- if (err) return reject(err);
332
- resolve(saved);
333
- });
334
- });
335
- }
336
-
337
- return({ success: true,conversation:newConvo });
338
- } catch (err) {
339
- console.error('❌ conversation creation error:', err);
340
- return({ error: "Failed to create conversation.",message: err.message });
341
- }
342
- };
343
-
344
- //self.profileBusinessFromURL = profileBusinessFromURL;
345
- this.async = {
346
- syncAssistantToOpenAI : this.syncAssistantToOpenAI ,
347
- getRunStatus: this.getRunStatus,
348
- getThreadMessages: this.getThreadMessages,
349
- addMessage: this.addMessage,
350
- getRandomTheme: this.getRandomTheme,
351
- testAssistant: this.testAssistant,
352
- createConversation: this.createConversation
353
-
354
- };
355
- return self;
356
-
357
- }
358
-
359
- module.exports = new ChatGPTAssistants();
1
+
2
+ const { run } = require("googleapis/build/src/apis/run");
3
+ const OpenAI = require("openai");
4
+
5
+ function ChatGPTAssistants() {
6
+ const self = this;
7
+
8
+ function coloredLog(message) {
9
+ console.log(JOE.Utils.color('[chatgpt-assistants]', 'plugin', false), message);
10
+ }
11
+
12
+
13
+
14
+ function getAPIKey() {
15
+ const setting = JOE.Utils.Settings('OPENAI_API_KEY');
16
+ if (!setting) throw new Error("Missing OPENAI_API_KEY setting");
17
+ return setting;
18
+ }
19
+
20
+ function newClient() {
21
+ return new OpenAI({ apiKey: getAPIKey() });
22
+ }
23
+
24
+ function validateThreadId(thread_id) {
25
+ const validPattern = /^[a-zA-Z0-9_-]+$/;
26
+ if (!validPattern.test(thread_id)) {
27
+ console.error("❌ Invalid characters in thread_id!");
28
+ return false;
29
+ }
30
+ return true;
31
+ }
32
+ function stripHtml(html) {
33
+ if (!html) return "";
34
+ return html.replace(/<[^>]*>?/gm, '').trim();
35
+ }
36
+ this.getRandomTheme = async function(data, req, res) {
37
+ try {
38
+ const themes = ["hope", "peace", "perseverance", "joy", "strength", "love", "faith", "healing"];
39
+ const randomTheme = themes[Math.floor(Math.random() * themes.length)];
40
+
41
+ coloredLog("🎯 Selected Random Theme: " + randomTheme);
42
+
43
+ return res.jsonp({
44
+ theme: randomTheme
45
+ });
46
+
47
+ } catch (err) {
48
+ coloredLog("❌ Error in getRandomTheme: " + err.message);
49
+ return res.jsonp({ error: err.message });
50
+ }
51
+ };
52
+
53
+ this.testAssistant = async function(data, req, res){
54
+ const openai = newClient();
55
+ const assistant = await openai.beta.assistants.retrieve("asst_HFzrtyAVyzDDwn1PLOIwU26W");
56
+ thread = await openai.beta.threads.create();
57
+
58
+ // 2. Add a user message
59
+ await openai.beta.threads.messages.create({
60
+ thread_id: thread.id,
61
+ role: 'user',
62
+ content: `Please analyze this business: welltyme.com`
63
+ });
64
+ coloredLog('⚡ Starting Assistant thread');
65
+ console.log(assistant);
66
+ return res.jsonp({ assistant });
67
+ }
68
+ this.syncAssistantToOpenAI = async function(data, req, res) {
69
+ const openai = newClient();
70
+
71
+ try {
72
+ coloredLog("🔄 Starting syncAssistantToOpenAI");
73
+
74
+ const assistant = JOE.Data.ai_assistant.find(a => a._id === data._id);
75
+ if (!assistant) {
76
+ return ({ error: "AI Assistant not found in Joe." });
77
+ }
78
+
79
+ // Check if updating or creating
80
+ let existing = null;
81
+ if (assistant.assistant_id) {
82
+ try {
83
+ existing = await openai.beta.assistants.retrieve(assistant.assistant_id);
84
+ } catch (e) {
85
+ console.warn('Assistant ID exists but not found at OpenAI, will create new.');
86
+ existing = null;
87
+ }
88
+ }
89
+ const payload = {
90
+ name: assistant.name,
91
+ instructions: stripHtml(assistant.instructions || ""),
92
+ model: assistant.ai_model || "gpt-4o",
93
+ tools: [],
94
+ //file_search: { enabled: !!assistant.file_search_enabled },
95
+ //code_interpreter: { enabled: !!assistant.code_interpreter_enabled }
96
+ };
97
+ // 1. Add user-defined function tools
98
+ const parsedTools = Array.isArray(assistant.tools) ? assistant.tools : JSON.parse(assistant.tools || "[]");
99
+ payload.tools.push(...parsedTools);
100
+
101
+ // 2. Add built-in tools if toggled ON
102
+ if (assistant.file_search_enabled) {
103
+ payload.tools.push({ type: "file_search" });
104
+ }
105
+
106
+ if (assistant.code_interpreter_enabled) {
107
+ payload.tools.push({ type: "code_interpreter" });
108
+ }
109
+ // if (existing && existing.file_ids?.length) {
110
+ // payload.file_ids = existing.file_ids;
111
+ // }
112
+
113
+ // Only set file_ids if handling files manually
114
+ if (assistant.file_ids && assistant.file_ids.length > 0) {
115
+ payload.file_ids = assistant.file_ids;
116
+ }
117
+ coloredLog("📦 Payload for OpenAI sync:");
118
+ coloredLog(JSON.stringify(payload, null, 2));
119
+
120
+ let apiResponse;
121
+
122
+ if (!assistant.assistant_id) {
123
+ coloredLog("🆕 No assistant_id found. Creating new Assistant...");
124
+ apiResponse = await openai.beta.assistants.create(payload);
125
+ assistant.assistant_id = apiResponse.id;
126
+ } else {
127
+ coloredLog("♻️ assistant_id found. Updating existing Assistant...");
128
+ apiResponse = await openai.beta.assistants.update(assistant.assistant_id, payload);
129
+ }
130
+
131
+ assistant.last_synced = new Date().toISOString();
132
+
133
+ // 💾 Set status to "assistant_synced" if found
134
+ const syncStatus = JOE.Data.status.find(s => s.code === "assistant_synced");
135
+ if (syncStatus) {
136
+ assistant.status = syncStatus._id;
137
+ }
138
+
139
+ await new Promise((resolve, reject) => {
140
+ JOE.Storage.save(assistant, 'ai_assistant', function(err, saved) {
141
+ if (err) {
142
+ return reject(err);
143
+ }
144
+ resolve(saved);
145
+ });
146
+ });
147
+
148
+ coloredLog("💾 Assistant sync saved back to Joe successfully.");
149
+
150
+ return ({
151
+ success: true,
152
+ assistant_id: assistant.assistant_id,
153
+ message: assistant.assistant_id ? "Assistant synced successfully." : "Assistant created successfully."
154
+ });
155
+
156
+ } catch (err) {
157
+ coloredLog("❌ Error in syncAssistantToOpenAI: " + err.message);
158
+ return ({ error: err.message });
159
+ }
160
+ };
161
+ this.getThreadMessages = async function(data, req, res) {
162
+ coloredLog("🔄 Starting getThreadMessages");
163
+ try {
164
+ const thread_id = data.thread_id;
165
+ if (!thread_id) {
166
+ return ({ error: "Missing thread ID." });
167
+ }
168
+ if(data.polling){
169
+ coloredLog('Polling for thread messages: ' + thread_id);
170
+ }
171
+ const openai = newClient();
172
+ const messages = await openai.beta.threads.messages.list(thread_id);
173
+
174
+ //return res.jsonp({ success: true, messages: messages.data || [] });
175
+ var succResp = { success: true, messages: messages.data || [] }
176
+ return(succResp);
177
+ } catch (err) {
178
+ console.error('❌ getThreadMessages error:', err);
179
+
180
+ return({ error: "Failed to load thread messages." });
181
+ }
182
+ };
183
+ this.addMessage = async function(data, req, res) {
184
+ try {
185
+ const { conversation_id, content,object_id} = data;
186
+ if (!conversation_id || !content) {
187
+ return({ error: "Missing conversation ID or content." });
188
+ }
189
+
190
+ const openai = newClient();
191
+
192
+ const convo = await new Promise((resolve, reject) => {
193
+ JOE.Storage.load('ai_conversation', { _id: conversation_id }, (err, results) => {
194
+ if (err || !results || !results.length) {
195
+ return reject("Conversation not found.");
196
+ }
197
+ resolve(results[0]);
198
+ });
199
+ });
200
+ var resave = false;
201
+ //add objectID
202
+ if(object_id && !convo.context_objects.includes(object_id)){
203
+ if(!convo.context_objects) convo.context_objects = [];
204
+ convo.context_objects.push(object_id);
205
+ resave = true;
206
+ }
207
+
208
+ if (!convo.thread_id) {
209
+ const thread = await openai.beta.threads.create();
210
+ convo.thread_id = thread.id;
211
+ resave = true;
212
+ // await new Promise((resolve, reject) => {
213
+ // JOE.Storage.save(convo, 'ai_conversation', function(err, saved) {
214
+ // if (err) return reject(err);
215
+ // resolve(saved);
216
+ // });
217
+ // });
218
+ }
219
+
220
+ if(resave){
221
+ await new Promise((resolve, reject) => {
222
+ JOE.Storage.save(convo, 'ai_conversation', function(err, saved) {
223
+ if (err) return reject(err);
224
+ resolve(saved);
225
+ });
226
+ });
227
+
228
+ }
229
+
230
+ await openai.beta.threads.messages.create(convo.thread_id, {
231
+ role: "user",
232
+ content: content
233
+ });
234
+ var runObj = null;
235
+ // NOW ➔ trigger assistant reply if assistant is selected
236
+ if (convo.assistants && convo.assistants.length > 0) {
237
+ // const assistant_id = convo.assistants[0]; // Assuming you store OpenAI assistant ID here
238
+ //get assistant object by id
239
+ const assistant_id = data.assistant_id || convo.assistants?.[0]?.openai_id;
240
+ const assistant = JOE.Data.ai_assistant.find(a => a._id === assistant_id);
241
+ if (assistant.assistant_id) {
242
+ runObj = await openai.beta.threads.runs.create(convo.thread_id, {
243
+ assistant_id: assistant.assistant_id
244
+ });
245
+
246
+ console.log(`Assistant run started: ${runObj.id}`);
247
+ // You could optionally poll for completion here
248
+ }
249
+ }else if(convo.assistant){
250
+ const assistant = $J.get(convo.assistant);
251
+ runObj = await openai.beta.threads.runs.create(convo.thread_id, {
252
+ assistant_id: assistant.assistant_id // Assuming you store OpenAI assistant ID here
253
+ });
254
+
255
+ coloredLog(`Assistant run started: ${runObj.id}`);
256
+ }
257
+ return({ success: true,runObj });
258
+ } catch (err) {
259
+ console.error('❌ addMessage error:', err);
260
+ return({ error: "Failed to send message.", message: err.message });
261
+ }
262
+ };
263
+ this.getRunStatus = async function(data, req, res) {
264
+ try {
265
+ const run_id = data.run_id;
266
+ const thread_id = data.thread_id;
267
+ var errors = [];
268
+ if (!run_id) errors.push("Missing run ID.");
269
+ if(!thread_id) errors.push("Missing thread ID.");
270
+
271
+ if (errors.length) {
272
+ return { error: errors.join(" ") };
273
+ }
274
+
275
+ const openai = newClient();
276
+ const run = await openai.beta.threads.runs.retrieve(thread_id, run_id);
277
+ coloredLog("🔄 Run status retrieved: " + run.status +'assistant_id: ' + run.assistant_id);
278
+ return {
279
+ id: run.id,
280
+ status: run.status,
281
+ assistant_id: run.assistant_id,
282
+ completed_at: run.completed_at,
283
+ usage: run.usage || {}
284
+ };
285
+
286
+ } catch (err) {
287
+ console.error("❌ getRunStatus error:", err);
288
+ return { error: "Failed to check run status." };
289
+ }
290
+ };
291
+
292
+ function getDefaultAssistant() {
293
+ var asst_id = JOE.Utils.Settings('DEFAULT_AI_ASSISTANT');
294
+ const defaultAssistant = $J.get(asst_id);
295
+ if (!defaultAssistant) {
296
+ throw new Error("Default Assistant not found.");
297
+ }
298
+ return defaultAssistant;
299
+ }
300
+ this.createConversation = async function(data, req, res) {
301
+ //this function creates a new conversation
302
+ //it should use the default assistant and add the context of the passed object id after being flattend to the conversation.
303
+ //save user_is to the conversation object
304
+
305
+ try {
306
+ const { object_id, user_id } = data;
307
+ const openai = newClient();
308
+ const user = $J.get(user_id);
309
+ const contextObject = $J.get(object_id);
310
+ var newConvo = null;
311
+ var assistant = getDefaultAssistant();
312
+ var assistants = assistant? [assistant._id]:[];
313
+ const convo ={
314
+ _id:cuid(),
315
+ created: new Date().toISOString(),
316
+ _joeUpdated: new Date().toISOString(),
317
+ itemtype: 'ai_conversation',
318
+ name:`${user.name}'s conversation about ${contextObject.name} [${contextObject.itemtype}]`,
319
+ user:user_id,
320
+ members:[],
321
+ assistant:assistant._id,
322
+ context_objects:[object_id]
323
+ }
324
+
325
+ if (!convo.thread_id) {
326
+ const thread = await openai.beta.threads.create();
327
+ convo.thread_id = thread.id;
328
+
329
+ newConvo = await new Promise((resolve, reject) => {
330
+ JOE.Storage.save(convo, 'ai_conversation', function(err, saved) {
331
+ if (err) return reject(err);
332
+ resolve(saved);
333
+ });
334
+ });
335
+ }
336
+
337
+ return({ success: true,conversation:newConvo });
338
+ } catch (err) {
339
+ console.error('❌ conversation creation error:', err);
340
+ return({ error: "Failed to create conversation.",message: err.message });
341
+ }
342
+ };
343
+
344
+ //self.profileBusinessFromURL = profileBusinessFromURL;
345
+ this.async = {
346
+ syncAssistantToOpenAI : this.syncAssistantToOpenAI ,
347
+ getRunStatus: this.getRunStatus,
348
+ getThreadMessages: this.getThreadMessages,
349
+ addMessage: this.addMessage,
350
+ getRandomTheme: this.getRandomTheme,
351
+ testAssistant: this.testAssistant,
352
+ createConversation: this.createConversation
353
+
354
+ };
355
+ return self;
356
+
357
+ }
358
+
359
+ module.exports = new ChatGPTAssistants();