json-object-editor 0.10.440 → 0.10.444
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/CHANGELOG.md +2 -0
- package/build.js +61 -0
- package/css/jif/Read Me.txt +5 -5
- package/css/jif/demo-files/demo.css +153 -153
- package/css/jif/demo-files/demo.js +30 -30
- package/css/jif/demo.html +285 -285
- package/css/jif/fonts/joeiconfont.svg +24 -24
- package/css/jif/ie7/ie7.css +45 -45
- package/css/jif/ie7/ie7.js +46 -46
- package/css/jif/selection.json +573 -573
- package/css/joe.css +15 -9
- package/css/joe.min.css +7 -0
- package/css/jquery-ui-1.10.4.custom.min.css +5 -5
- package/css/jquery-ui.min.css +6 -6
- package/css/jquery.timepicker.css +72 -72
- package/es5-build/web-components/account-info.js +136 -136
- package/es5-build/web-components/capp-components.js +160 -160
- package/es5-build/web-components/capp-panel.js +85 -85
- package/es5-build/web-components/capp-view.js +73 -73
- package/es5-build/web-components/joe-autocomplete.js +149 -149
- package/es5-build/web-components/joe-button.js +132 -132
- package/es5-build/web-components/joe-card.js +92 -92
- package/es5-build/web-components/joe-component.js +74 -74
- package/es5-build/web-components/joe-field.js +70 -70
- package/es5-build/web-components/joe-list-item.js +176 -176
- package/es5-build/web-components/joe-user-cube.js +100 -100
- package/es5-build/web-components/report-components.js +133 -133
- package/grunt/build +86 -86
- package/grunt/package-lock.json +9105 -9105
- package/grunt/package.json +47 -47
- package/grunt/src +86 -86
- package/js/joe.js +12 -8
- package/js/joe.min.js +1 -15
- package/js/leaflet.js +8 -8
- package/js/libs/adapter-latest.js +4400 -4400
- package/js/libs/craydent-1.9.2.js +11741 -11741
- package/js/libs/craydent-upload-2.0.0.js +394 -394
- package/js/libs/hammer.min.208.js +6 -6
- package/js/libs/jquery-3.5.1.min.js +2 -2
- package/js/libs/moment.min.js +6 -6
- package/js/native-shim.js +46 -46
- package/js/plugins/c3/c3.min.js +5 -5
- package/js/plugins/c3/d3.v3.min.js +4 -4
- package/js/plugins/threejs/Detector.js +78 -78
- package/js/plugins/threejs/LICENSE +21 -21
- package/js/plugins/threejs/MTLLoader.js +417 -417
- package/js/plugins/threejs/OBJLoader.js +564 -564
- package/js/plugins/threejs/OrbitControls.js +1037 -1037
- package/js/plugins/threejs/README.md +9 -9
- package/js/plugins/threejs/assets/female-croupier-2013-03-26.mtl +3 -3
- package/js/plugins/threejs/index.html +178 -178
- package/js/plugins/threejs/three.js +41507 -41507
- package/package.json +9 -4
- package/server/fields/core.js +1 -1
- package/server/modules/Server.js +21 -1
- package/server/plugins/chatgpt-assistants.js +359 -359
- package/server/plugins/chatgpt-tools.js +79 -79
- package/server/plugins/engagementTracker.js +78 -0
- package/server/schemas/engagement_event.js +33 -0
- package/server/schemas/page.js +18 -1
|
@@ -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();
|