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.
@@ -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
- var assistantId = (req.query.assistant_id || req.query.assistant || '').trim();
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>&lt;joe-ai-widget&gt;</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=&lt;ai_assistant _id&gt;</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));
@@ -22,50 +22,46 @@ function Auth(){
22
22
  };
23
23
 
24
24
  this.login = async function(data,req,res){
25
- const got = await import('got');
26
- //make post request
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
- var options = {
33
+ const tokenUrl = 'https://oauth2.googleapis.com/token';
34
+ const options = {
31
35
  method: 'POST',
32
- url: 'https://www.googleapis.com/oauth2/v4/token',
33
- searchParams: {
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
- 'Content-Type': 'application/x-www-form-urlencoded',
38
- 'client_id':JOE.webconfig.authorization.client_id,
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
- Authorization: JOE.webconfig.authorization.header,
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
- // request(options, function (error, response, body) {
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
- res.send(body.error);
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
- //redirect to home or gotoUrl
90
+ //redirect to home or gotoUrl
95
91
  res.redirect(originalUrl || `/JOE/${User.apps[0]}`);
96
- //res.send(body);
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
- 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
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
- }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}`);
271
+ // Unknown error – rethrow to outer catch
272
+ throw err;
256
273
  }
257
- return({ success: true,runObj });
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
- return({ error: "Failed to send message.", message: err.message });
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) errors.push("Missing run ID.");
269
- if(!thread_id) errors.push("Missing thread ID.");
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(" ") };