json-object-editor 0.10.624 → 0.10.632
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 +15 -0
- package/_www/ai-widget-test.html +367 -0
- package/_www/mcp-export.html +5 -5
- package/_www/mcp-prompt.html +1 -1
- package/_www/mcp-test.html +14 -5
- package/css/joe-styles.css +11 -3
- package/css/joe.css +12 -4
- package/css/joe.min.css +1 -1
- package/docs/joe_agent_custom_gpt_instructions_v_3.md +132 -0
- package/dummy +10 -0
- package/img/svgs/ai_assistant.svg +1 -0
- package/img/svgs/ai_assistant_white.svg +1 -0
- package/js/JsonObjectEditor.jquery.craydent.js +34 -3
- package/js/joe-ai.js +784 -52
- package/js/joe.js +35 -4
- package/js/joe.min.js +1 -1
- package/package.json +1 -1
- package/readme.md +17 -10
- package/server/apps/aihub.js +97 -0
- package/server/fields/core.js +4 -1
- package/server/modules/MCP.js +263 -26
- package/server/modules/Server.js +1 -46
- package/server/plugins/auth.js +34 -30
- package/server/plugins/chatgpt-assistants.js +70 -35
- package/server/plugins/chatgpt.js +560 -44
- package/server/schemas/ai_assistant.js +149 -1
- package/server/schemas/ai_conversation.js +14 -1
- package/server/schemas/ai_widget_conversation.js +133 -14
- package/server/schemas/project.js +27 -3
- package/server/schemas/task.js +1 -0
package/server/modules/Server.js
CHANGED
|
@@ -171,52 +171,7 @@ server.get(JOE.webconfig.joepath+'_www/mcp-schemas.html',auth,function(req,res){
|
|
|
171
171
|
|
|
172
172
|
// AI Widget test page (Responses + assistants) – auth protected
|
|
173
173
|
server.get(['/ai-widget-test.html', JOE.webconfig.joepath + 'ai-widget-test.html'], auth, function(req,res){
|
|
174
|
-
|
|
175
|
-
if (!assistantId && JOE && JOE.Utils && JOE.Utils.Settings) {
|
|
176
|
-
try {
|
|
177
|
-
var settingObj = JOE.Utils.Settings('DEFAULT_AI_ASSISTANT', { object: true });
|
|
178
|
-
assistantId = (settingObj && settingObj.value) || '';
|
|
179
|
-
} catch(e) {
|
|
180
|
-
console.log('[ai-widget-test] error reading DEFAULT_AI_ASSISTANT:', e && e.message);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
var joePath = (JOE && JOE.webconfig && JOE.webconfig.joepath) || '/JsonObjectEditor/';
|
|
184
|
-
var assistantAttr = assistantId ? ' ai_assistant_id="'+assistantId.replace(/"/g,'"')+'"' : '';
|
|
185
|
-
|
|
186
|
-
res.send(`<!doctype html>
|
|
187
|
-
<html>
|
|
188
|
-
<head>
|
|
189
|
-
<meta charset="utf-8">
|
|
190
|
-
<title>JOE AI Widget Test</title>
|
|
191
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
192
|
-
<style>
|
|
193
|
-
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;background:#f3f4f6;}
|
|
194
|
-
h1{margin-top:0;}
|
|
195
|
-
.small{font-size:13px;color:#6b7280;margin-bottom:16px;}
|
|
196
|
-
.container{max-width:480px;margin:0 auto;background:#fff;border-radius:12px;box-shadow:0 4px 14px rgba(0,0,0,0.06);padding:16px;}
|
|
197
|
-
.meta{font-size:12px;color:#6b7280;margin-bottom:8px;}
|
|
198
|
-
code{background:#e5e7eb;border-radius:4px;padding:2px 4px;font-size:12px;}
|
|
199
|
-
</style>
|
|
200
|
-
</head>
|
|
201
|
-
<body>
|
|
202
|
-
<div id="mcp-nav"></div>
|
|
203
|
-
<script src="${joePath}_www/mcp-nav.js"></script>
|
|
204
|
-
|
|
205
|
-
<div class="container">
|
|
206
|
-
<h1>AI Widget Test</h1>
|
|
207
|
-
<div class="small">
|
|
208
|
-
This page mounts <code><joe-ai-widget></code> and sends messages through
|
|
209
|
-
<code>/API/plugin/chatgpt/widget*</code> using the OpenAI Responses API.
|
|
210
|
-
${assistantId ? `Using <code>DEFAULT_AI_ASSISTANT</code> (ai_assistant_id=${assistantId}).` : 'No DEFAULT_AI_ASSISTANT is set; widget will use model defaults.'}
|
|
211
|
-
You can override the assistant via <code>?assistant_id=<ai_assistant _id></code>.
|
|
212
|
-
</div>
|
|
213
|
-
|
|
214
|
-
<joe-ai-widget id="widget" title="JOE AI Assistant"${assistantAttr}></joe-ai-widget>
|
|
215
|
-
</div>
|
|
216
|
-
|
|
217
|
-
<script src="${joePath}js/joe-ai.js"></script>
|
|
218
|
-
</body>
|
|
219
|
-
</html>`);
|
|
174
|
+
res.sendFile(path.join(JOE.joedir,'_www','ai-widget-test.html'));
|
|
220
175
|
});
|
|
221
176
|
|
|
222
177
|
server.use(JOE.webconfig.joepath,express.static(JOE.joedir));
|
package/server/plugins/auth.js
CHANGED
|
@@ -22,50 +22,46 @@ function Auth(){
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
this.login = async function(data,req,res){
|
|
25
|
-
|
|
26
|
-
//
|
|
25
|
+
// Use got to exchange the Google authorization_code for tokens.
|
|
26
|
+
// We POST form-encoded params to the current Google OAuth token endpoint.
|
|
27
|
+
const gotMod = await import('got');
|
|
28
|
+
const got = gotMod.default || gotMod;
|
|
29
|
+
|
|
27
30
|
var originalUrl = data.state||'';
|
|
28
31
|
console.log(originalUrl);
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
const tokenUrl = 'https://oauth2.googleapis.com/token';
|
|
34
|
+
const options = {
|
|
31
35
|
method: 'POST',
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
// Google expects application/x-www-form-urlencoded body, not querystring.
|
|
37
|
+
form: {
|
|
34
38
|
grant_type: 'authorization_code',
|
|
35
39
|
code: data.code,
|
|
36
40
|
redirect_uri: `${JOE.webconfig.authorization.host}/API/plugin/auth/login`,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'client_secret':JOE.webconfig.authorization.client_secret
|
|
41
|
+
client_id: JOE.webconfig.authorization.client_id,
|
|
42
|
+
client_secret: JOE.webconfig.authorization.client_secret
|
|
40
43
|
},
|
|
41
|
-
headers:
|
|
42
|
-
{
|
|
44
|
+
headers: {
|
|
43
45
|
'cache-control': 'no-cache',
|
|
44
|
-
|
|
45
|
-
Accept: 'application/json',
|
|
46
|
-
|
|
46
|
+
Accept: 'application/json'
|
|
47
47
|
},
|
|
48
48
|
responseType: 'json',
|
|
49
49
|
https: {
|
|
50
50
|
rejectUnauthorized: false
|
|
51
51
|
}
|
|
52
|
-
// rejectUnauthorized:false,
|
|
53
|
-
// json: true
|
|
54
52
|
};
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
got.default(options)
|
|
58
|
-
.catch(error => {
|
|
59
|
-
res.send(error);
|
|
60
|
-
})
|
|
54
|
+
got(tokenUrl, options)
|
|
61
55
|
.then(response => {
|
|
62
|
-
const body = response.body;
|
|
56
|
+
const body = response.body || {};
|
|
63
57
|
// if (error){
|
|
64
58
|
// res.send(error);
|
|
65
59
|
// return;
|
|
66
60
|
// }
|
|
67
61
|
if (body.error){
|
|
68
|
-
|
|
62
|
+
// Bubble up Google's error payload so it's easier to diagnose
|
|
63
|
+
console.error('[auth.login] Google token error payload:', body);
|
|
64
|
+
res.status(400).send(body);
|
|
69
65
|
return;
|
|
70
66
|
}
|
|
71
67
|
//res.send(body);
|
|
@@ -91,17 +87,25 @@ function Auth(){
|
|
|
91
87
|
res.redirect(finalUrl);
|
|
92
88
|
return;
|
|
93
89
|
}
|
|
94
|
-
|
|
90
|
+
//redirect to home or gotoUrl
|
|
95
91
|
res.redirect(originalUrl || `/JOE/${User.apps[0]}`);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
})
|
|
93
|
+
.catch(error => {
|
|
94
|
+
// Log structured info so we can see status + body from Google
|
|
95
|
+
const status = error.response && error.response.statusCode;
|
|
96
|
+
const body = error.response && error.response.body;
|
|
97
|
+
console.error('[auth.login] HTTPError from Google token endpoint:', {
|
|
98
|
+
status,
|
|
99
|
+
body,
|
|
100
|
+
message: error.message
|
|
101
|
+
});
|
|
102
|
+
res.status(status || 500).send(body || {
|
|
103
|
+
error: 'oauth_token_error',
|
|
104
|
+
message: error.message
|
|
105
|
+
});
|
|
100
106
|
});
|
|
101
107
|
|
|
102
|
-
|
|
103
|
-
//return(data);
|
|
104
|
-
return({use_callback:true})
|
|
108
|
+
return({use_callback:true});
|
|
105
109
|
}
|
|
106
110
|
this.html = function(data,req,res){
|
|
107
111
|
|
|
@@ -205,18 +205,13 @@ function ChatGPTAssistants() {
|
|
|
205
205
|
resave = true;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// Ensure we have a thread_id; create one if missing
|
|
208
209
|
if (!convo.thread_id) {
|
|
209
210
|
const thread = await openai.beta.threads.create();
|
|
210
211
|
convo.thread_id = thread.id;
|
|
211
212
|
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
213
|
}
|
|
219
|
-
|
|
214
|
+
|
|
220
215
|
if(resave){
|
|
221
216
|
await new Promise((resolve, reject) => {
|
|
222
217
|
JOE.Storage.save(convo, 'ai_conversation', function(err, saved) {
|
|
@@ -224,49 +219,89 @@ function ChatGPTAssistants() {
|
|
|
224
219
|
resolve(saved);
|
|
225
220
|
});
|
|
226
221
|
});
|
|
222
|
+
}
|
|
227
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Helper to start a run on the current thread for the correct assistant.
|
|
226
|
+
*/
|
|
227
|
+
async function startRunForConversation(convo, data){
|
|
228
|
+
let runObj = null;
|
|
229
|
+
// Prefer assistants array if present
|
|
230
|
+
if (convo.assistants && convo.assistants.length > 0) {
|
|
231
|
+
const assistant_id = data.assistant_id || convo.assistants?.[0]?.openai_id || convo.assistants?.[0];
|
|
232
|
+
const assistant = JOE.Data.ai_assistant.find(a => a._id === assistant_id) || $J.get(assistant_id);
|
|
233
|
+
if (assistant && assistant.assistant_id) {
|
|
234
|
+
runObj = await openai.beta.threads.runs.create(convo.thread_id, {
|
|
235
|
+
assistant_id: assistant.assistant_id
|
|
236
|
+
});
|
|
237
|
+
coloredLog(`Assistant run started: ${runObj.id}`);
|
|
238
|
+
}
|
|
239
|
+
} else if (convo.assistant) {
|
|
240
|
+
const assistant = $J.get(convo.assistant);
|
|
241
|
+
if (assistant && assistant.assistant_id) {
|
|
242
|
+
runObj = await openai.beta.threads.runs.create(convo.thread_id, {
|
|
243
|
+
assistant_id: assistant.assistant_id
|
|
244
|
+
});
|
|
245
|
+
coloredLog(`Assistant run started: ${runObj.id}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return runObj;
|
|
228
249
|
}
|
|
229
250
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
251
|
+
let runObj = null;
|
|
252
|
+
try {
|
|
253
|
+
// Normal path: append user message to existing thread
|
|
254
|
+
await openai.beta.threads.messages.create(convo.thread_id, {
|
|
255
|
+
role: "user",
|
|
256
|
+
content: content
|
|
257
|
+
});
|
|
258
|
+
runObj = await startRunForConversation(convo, data);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
// If a previous run is still active on this thread, OpenAI returns 400:
|
|
261
|
+
// "Can't add messages to <thread> while a run <run_x> is active."
|
|
262
|
+
const msg = err && err.message ? String(err.message) : '';
|
|
263
|
+
if (err && err.status === 400 && msg.indexOf("Can't add messages to") !== -1 && msg.indexOf("while a run") !== -1) {
|
|
264
|
+
coloredLog("⚠️ Active run detected for conversation " + convo._id + " on thread " + convo.thread_id);
|
|
265
|
+
return({
|
|
266
|
+
error: "active_run",
|
|
267
|
+
message: "This conversation has an active assistant run on its thread. Please wait for it to complete or start a new conversation.",
|
|
268
|
+
thread_id: convo.thread_id
|
|
244
269
|
});
|
|
245
|
-
|
|
246
|
-
console.log(`Assistant run started: ${runObj.id}`);
|
|
247
|
-
// You could optionally poll for completion here
|
|
248
270
|
}
|
|
249
|
-
|
|
250
|
-
|
|
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}`);
|
|
271
|
+
// Unknown error – rethrow to outer catch
|
|
272
|
+
throw err;
|
|
256
273
|
}
|
|
257
|
-
|
|
274
|
+
|
|
275
|
+
return({
|
|
276
|
+
success: true,
|
|
277
|
+
runObj,
|
|
278
|
+
run_id: runObj && runObj.id,
|
|
279
|
+
thread_id: convo.thread_id
|
|
280
|
+
});
|
|
258
281
|
} catch (err) {
|
|
259
282
|
console.error('❌ addMessage error:', err);
|
|
260
|
-
|
|
283
|
+
const msg = err && err.message ? String(err.message) : 'Unknown error';
|
|
284
|
+
return({ error: "Failed to send message.", message: msg });
|
|
261
285
|
}
|
|
262
286
|
};
|
|
263
287
|
this.getRunStatus = async function(data, req, res) {
|
|
264
288
|
try {
|
|
289
|
+
// Debug: log raw data coming into getRunStatus to diagnose bad IDs
|
|
290
|
+
try {
|
|
291
|
+
coloredLog("🔍 getRunStatus called with data:", JSON.stringify(data));
|
|
292
|
+
} catch(_e) {
|
|
293
|
+
console.log("[chatgpt-assistants] getRunStatus data (fallback log):", data);
|
|
294
|
+
}
|
|
295
|
+
|
|
265
296
|
const run_id = data.run_id;
|
|
266
297
|
const thread_id = data.thread_id;
|
|
267
298
|
var errors = [];
|
|
268
|
-
if (!run_id
|
|
269
|
-
|
|
299
|
+
if (!run_id || run_id === 'undefined' || run_id === 'null') {
|
|
300
|
+
errors.push("Missing run ID.");
|
|
301
|
+
}
|
|
302
|
+
if (!thread_id || thread_id === 'undefined' || thread_id === 'null') {
|
|
303
|
+
errors.push("Missing thread ID.");
|
|
304
|
+
}
|
|
270
305
|
|
|
271
306
|
if (errors.length) {
|
|
272
307
|
return { error: errors.join(" ") };
|