json-object-editor 0.10.650 → 0.10.654

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.
@@ -13,8 +13,8 @@ Note: Use `hydrate {}` to load schema summaries and global datasets. Prefer summ
13
13
  - Treat “schema” as “itemtype” (JOE object type). Use `itemtype` in tool params.
14
14
  - Reads are autonomous; writes require explicit confirmation.
15
15
  - Prefer cache (`search` default) for exploration; use `source:"storage"` for authoritative reads.
16
- - Keep results scoped (limit 10–25 by default; increase only when needed).
17
16
  - Do not expose secrets/tokens; server may sanitize.
17
+ - Use slim to reduce payloads when requesting long lists of objects.
18
18
 
19
19
  ## Tools
20
20
  - listSchemas {}
@@ -31,6 +31,16 @@ Note: Use `hydrate {}` to load schema summaries and global datasets. Prefer summ
31
31
  - `related` contains non‑global referenced objects (e.g., ingredient, project, user),
32
32
  - `tags` / `statuses` are deduped lookup maps `{ _id, itemtype, name }` for those global types.
33
33
  - When looking up by id, call `understandObject` first instead of issuing many separate `getObject` / `getSchema` / `search` calls.
34
+ - **findObjectsByTag { tags, itemtype?, limit?, offset?, source?, slim?, withCount?, countOnly?, tagThreshold? }**
35
+ - Find objects that have ALL specified tags (AND logic). Tags can be provided as IDs (CUIDs) or names (strings) - names are resolved via fuzzy search.
36
+ - Returns `{ items, tags, count?, error? }` where `tags` contains the resolved tag objects used in the search.
37
+ - Use `countOnly: true` to get just the count and matched tags without fetching items.
38
+ - If a tag cannot be resolved, returns `{ items: [], tags: [...resolved ones...], error: "message" }` instead of throwing.
39
+ - **findObjectsByStatus { status, itemtype?, limit?, offset?, source?, slim?, withCount?, countOnly?, statusThreshold? }**
40
+ - Find objects by status. Status can be provided as ID (CUID) or name (string) - name is resolved via fuzzy search.
41
+ - Returns `{ items, status, count?, error? }` where `status` is the resolved status object used in the search.
42
+ - Use `countOnly: true` to get just the count and matched status without fetching items.
43
+ - If status cannot be resolved, returns `{ items: [], status: null, error: "message" }` instead of throwing.
34
44
  - saveObject { object }
35
45
  - saveObjects { objects, stopOnError?, concurrency? }
36
46
 
@@ -46,15 +56,59 @@ Note: Use `hydrate {}` to load schema summaries and global datasets. Prefer summ
46
56
 
47
57
  ## 🧭 When Finding Objects
48
58
 
49
- Example: “List all the conditions in the system.”
59
+ ### General Search
60
+ Example: "List all the conditions in the system."
50
61
 
51
- - Identify the itemtype (e.g., `condition`, `client`, `recommendation`, `task`). Do not ask for confirmation if its clear.
62
+ - Identify the itemtype (e.g., `condition`, `client`, `recommendation`, `task`). Do not ask for confirmation if it's clear.
52
63
  - Search by itemtype using `search`.
53
64
  - Use `"slim": true` to limit per‑record data.
54
65
  - Return essential fields: `_id`, `itemtype`, `name`, `info`, timestamps (from slim).
55
66
  - Sort by `name` ascending.
56
67
  - Include `"withCount": true` and a reasonable `"limit"` (e.g., 100–250) for bulk lists.
57
68
 
69
+ ### Finding by Tags
70
+ Example: "Show me all tasks tagged with 'urgent' and 'bug'."
71
+
72
+ - Use `findObjectsByTag` when the user explicitly mentions tags or wants to filter by tags.
73
+ - Tags can be provided as names (e.g., `"urgent"`, `"bug"`) or IDs - the function will resolve names via fuzzy search.
74
+ - The function requires ALL tags to be present (AND logic).
75
+ - Check the `tags` array in the response to see which tag objects were matched.
76
+ - If tags cannot be resolved, the response will include an `error` field with details.
77
+
78
+ Example:
79
+ ```json
80
+ {
81
+ "method": "findObjectsByTag",
82
+ "params": {
83
+ "tags": ["urgent", "bug"],
84
+ "itemtype": "task",
85
+ "limit": 50,
86
+ "slim": true
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### Finding by Status
92
+ Example: "List all tasks with status 'In Progress'."
93
+
94
+ - Use `findObjectsByStatus` when the user explicitly mentions a status or wants to filter by status.
95
+ - Status can be provided as a name (e.g., `"In Progress"`) or ID - the function will resolve names via fuzzy search.
96
+ - Check the `status` object in the response to see which status was matched.
97
+ - If status cannot be resolved, the response will include an `error` field with details.
98
+
99
+ Example:
100
+ ```json
101
+ {
102
+ "method": "findObjectsByStatus",
103
+ "params": {
104
+ "status": "In Progress",
105
+ "itemtype": "task",
106
+ "limit": 50,
107
+ "slim": true
108
+ }
109
+ }
110
+ ```
111
+
58
112
  Example JSON‑RPC:
59
113
 
60
114
  ```json
@@ -5104,9 +5104,145 @@ this.renderHTMLContent = function(specs){
5104
5104
  } else if (elem.msRequestFullscreen) { /* IE11 */
5105
5105
  elem.msRequestFullscreen();
5106
5106
  }
5107
- }
5107
+ };
5108
5108
  encapsulateFieldType('code',self.renderCodeField,
5109
5109
  {fullscreen:this.fullScreenCodeEditor});
5110
+
5111
+ /*----------------------------->
5112
+ Q.1 | JSON Field
5113
+ <-----------------------------*/
5114
+ this.renderJsonField = function(prop){
5115
+ /*|{
5116
+ tags:'render,field,json',
5117
+ specs:'value,standard_field_properties',
5118
+ description:'JSON object editor using Ace in JSON mode; always pretty-prints on load/blur/save.'
5119
+ }|*/
5120
+ var height = (prop.height)?'style="height:'+prop.height+';"' : '';
5121
+ var editor_id = cuid();
5122
+ var rawValue = (typeof prop.value == "undefined") ? {} : prop.value;
5123
+ var displayValue;
5124
+ // Ensure we start from an object and pretty JSON string
5125
+ if (typeof rawValue == "string") {
5126
+ try{
5127
+ rawValue = JSON.parse(rawValue);
5128
+ }catch(e){
5129
+ // fallback: leave as raw string, Ace/validation will handle parse errors on blur/save
5130
+ }
5131
+ }
5132
+ if (rawValue === null || typeof rawValue == "undefined") {
5133
+ rawValue = {};
5134
+ }
5135
+ if (typeof rawValue == "object") {
5136
+ try{
5137
+ displayValue = JSON.stringify(rawValue,null,2);
5138
+ }catch(e){
5139
+ displayValue = "{}";
5140
+ }
5141
+ }else{
5142
+ // Non-object (number/boolean/etc.) – stringify as-is but still require valid JSON
5143
+ try{
5144
+ displayValue = JSON.stringify(rawValue,null,2);
5145
+ }catch(e){
5146
+ displayValue = "{}";
5147
+ }
5148
+ }
5149
+ var html =
5150
+ '<div class="clear joe-ace-holder joe-rendering-field joe-field" '
5151
+ +height+' data-ace_id="'+editor_id+'" data-ftype="ace" name="'+prop.name+'">'+
5152
+ '<textarea class="" id="'+editor_id+'" >'
5153
+ +(displayValue || "{}")+
5154
+ '</textarea>'+
5155
+ '</div>'+
5156
+ '<script>'+
5157
+ 'try{\
5158
+ var editor = ace.edit("'+editor_id+'");\
5159
+ editor.setTheme("ace/theme/tomorrow");\
5160
+ editor.getSession().setUseWrapMode(true);\
5161
+ editor.getSession().setMode("ace/mode/json");\
5162
+ editor.setOptions({\
5163
+ enableBasicAutocompletion: true,\
5164
+ enableLiveAutocompletion: false\
5165
+ });\
5166
+ _joe.ace_editors["'+editor_id+'"] = editor;\
5167
+ }catch(e){ console && console.warn && console.warn("ACE json init error",e);}'
5168
+ +' </script>';
5169
+ return html;
5170
+ };
5171
+ // Helper to (re)parse and pretty-print a json field from its Ace editor.
5172
+ function _normalizeJsonField(fieldName,opts){
5173
+ opts = opts || {};
5174
+ var parent = getJoe(self.joe_index);
5175
+ var $holder = parent.overlay.find('.joe-ace-holder[name="'+fieldName+'"]');
5176
+ if(!$holder.length){ return { ok:true }; }
5177
+ var editorId = $holder.data('ace_id');
5178
+ var editor = _joe.ace_editors[editorId];
5179
+ if(!editor){ return { ok:true }; }
5180
+ var text = editor.getValue();
5181
+ if(!text || !text.trim()){
5182
+ // default to empty object
5183
+ text = '{}';
5184
+ }
5185
+ try{
5186
+ var parsed = JSON.parse(text);
5187
+ // must be an object; if not, still allow but keep as-is
5188
+ try{
5189
+ var pretty = JSON.stringify(parsed,null,2);
5190
+ editor.setValue(pretty,-1);
5191
+ }catch(_e){}
5192
+ $holder.removeClass('joe-field-error');
5193
+ return { ok:true, parsed:parsed };
5194
+ }catch(e){
5195
+ // Attempt a light-weight "loose JSON" fix for common cases:
5196
+ // - Unquoted identifier keys: { name:"Corey" } -> { "name":"Corey" }
5197
+ // - Single-quoted strings: { "name":'Corey' } -> { "name":"Corey" }
5198
+ // - Trailing commas: { "a":1, } -> { "a":1 }
5199
+ var fixed = text;
5200
+ try{
5201
+ // Quote bare identifier keys following { or , and before :
5202
+ fixed = fixed.replace(/([{\[],\s*|[{]\s*|,\s*)([A-Za-z_][A-Za-z0-9_]*)\s*:/g,function(m,prefix,key){
5203
+ return prefix+'"'+key+'":';
5204
+ });
5205
+ // Convert single-quoted strings to double-quoted strings
5206
+ fixed = fixed.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g,'"$1"');
5207
+ // Remove trailing commas before } or ]
5208
+ fixed = fixed.replace(/,\s*([}\]])/g,'$1');
5209
+
5210
+ var reparsed = JSON.parse(fixed);
5211
+ try{
5212
+ var prettyFixed = JSON.stringify(reparsed,null,2);
5213
+ editor.setValue(prettyFixed,-1);
5214
+ }catch(_e2){}
5215
+ $holder.removeClass('joe-field-error');
5216
+ return { ok:true, parsed:reparsed };
5217
+ }catch(_fixErr){
5218
+ $holder.addClass('joe-field-error');
5219
+ if(opts.showMessage && parent.showMessage){
5220
+ parent.showMessage("Field '"+fieldName+"' must be valid JSON (object).");
5221
+ }
5222
+ return { ok:false, error:e };
5223
+ }
5224
+ }
5225
+ }
5226
+ encapsulateFieldType('json',self.renderJsonField,{
5227
+ ready:function(){
5228
+ // Attach blur handlers to normalize/validate json fields when panel shows
5229
+ var parent = getJoe(self.joe_index);
5230
+ parent.overlay.find('.joe-ace-holder[name]').each(function(){
5231
+ var name = $(this).attr('name');
5232
+ var field = self.getField(name);
5233
+ if(!field){ return true; }
5234
+ var ftype = (self.propAsFuncOrValue(field.type)||'').toLowerCase();
5235
+ if(ftype != 'json'){ return true; }
5236
+ var editorId = $(this).data('ace_id');
5237
+ var editor = _joe.ace_editors[editorId];
5238
+ if(!editor){ return true; }
5239
+ editor.on('blur',function(){
5240
+ _normalizeJsonField(name,{showMessage:false});
5241
+ });
5242
+ });
5243
+ },
5244
+ normalize:_normalizeJsonField
5245
+ });
5110
5246
  /*----------------------------->
5111
5247
  Q.2 | QR Field
5112
5248
  <-----------------------------*/
@@ -5215,7 +5351,7 @@ this.renderHTMLContent = function(specs){
5215
5351
  '<div class="joe-uploader-preview">'+dz_message+'</div>'+
5216
5352
  '</div>'+
5217
5353
  '<div class="joe-uploader-message">add a file</div>'+
5218
- '<div class="joe-button joe-green-button joe-upload-cofirm-button hidden" onclick="_joe.uploaderConfirm(\''+uploader_id+'\',\''+prop.name+'\');">Upload File</div>'+__clearDiv__+
5354
+ '<div class="joe-button joe-green-button joe-upload-cofirm-button ui-helper-hidden hidden" onclick="_joe.uploaderConfirm(\''+uploader_id+'\',\''+prop.name+'\');">Upload File</div>'+__clearDiv__+
5219
5355
  '</div>'
5220
5356
  ;
5221
5357
  //var idprop = prop.idprop || '_id';
@@ -5242,7 +5378,7 @@ this.renderHTMLContent = function(specs){
5242
5378
  var jup_template,html= '';
5243
5379
  var alink ="<a href='${url}${base64}' class='file-link' target='_blank'></a>";
5244
5380
  var delete_btn = '<joe-uploader-file-delete-btn class="svg-shadow" onclick="_joe.Fields.uploader.remove(\''+cuid+'\',\'${filename}\');">'+self.SVG.icon.close+'</joe-uploader-delete-btn>';
5245
- var label = '<joe-uploader-file-label>${filename}</joe-uploader-file-label>';
5381
+ var label = '';
5246
5382
 
5247
5383
 
5248
5384
  files.map(function(file){
@@ -5267,6 +5403,12 @@ this.renderHTMLContent = function(specs){
5267
5403
 
5268
5404
 
5269
5405
 
5406
+ // Build the label with filename and optional OpenAI file id on a new line
5407
+ label = '<joe-uploader-file-label>'+(file.filename || '')+'</joe-uploader-file-label>';
5408
+ if(file.openai_file_id){
5409
+ label += '<joe-uploader-file-oaid >'+file.openai_file_id+'</joe-uploader-file-oaid>';
5410
+ }
5411
+
5270
5412
  if((file.type && file.type.contains('image')) || (!file.type && (file.url||file.base64).split('.').contains(['jpg','jpeg','png','gif','svg']))){
5271
5413
  jup_template = '<joe-uploader-file class="'+(file.uploaded && 'uploaded'||'')+'" style="background-image:url(${url}${base64})">'+alink+label+filesize+delete_btn+'</joe-uploader-file>';
5272
5414
  }else if (docTypes.includes((file.url || file.base64).split('.').pop())) {
@@ -5278,7 +5420,16 @@ this.renderHTMLContent = function(specs){
5278
5420
  '<joe-uploader-file-extension >.'+(file.type.split('/')[1] || '???')+'</joe-uploader-file-extension>'+
5279
5421
  alink+label+filesize+delete_btn+'</joe-uploader-file>';
5280
5422
  }
5281
- html += fillTemplate(jup_template,file);
5423
+ // Build optional OpenAI retry/upload button
5424
+ var openaiBtn = '';
5425
+ var needRetry = (!file.openai_file_id || file.openai_status == 'error');
5426
+ var hasUrl = !!file.url;
5427
+ if(hasUrl && needRetry){
5428
+ var openaiLabel = (file.openai_status == 'error') ? 'Retry OpenAI' : 'Upload to OpenAI';
5429
+ var safeFilename = (file.filename || '').replace(/'/g,"\\'");
5430
+ openaiBtn = '<joe-uploader-openai-btn class="joe-button" style="margin-left:6px;" onclick="_joe.SERVER.Plugins.openaiRetryFromUrl(\''+cuid+'\',\''+safeFilename+'\');">'+openaiLabel+'</joe-uploader-openai-btn>';
5431
+ }
5432
+ html += fillTemplate(jup_template,file) + openaiBtn;
5282
5433
  })
5283
5434
  return html+'<div class="clear"></div>';
5284
5435
  }
@@ -5295,7 +5446,7 @@ this.renderHTMLContent = function(specs){
5295
5446
  $(joe_uploader.dropzone).find('img,div').replaceWith('<img src="' + base64 + '">')
5296
5447
  //joe_uploader.dropzone.html('<img src="' + base64 + '">');
5297
5448
  joe_uploader.message.html('<b>'+file.name+'</b> selected');
5298
- joe_uploader.confirmBtn.removeClass('hidden');
5449
+ joe_uploader.confirmBtn.removeClass('ui-helper-hidden hidden');
5299
5450
  }else {
5300
5451
  results.innerHTML = 'Nothing to upload.';
5301
5452
  }
@@ -5326,7 +5477,7 @@ this.renderHTMLContent = function(specs){
5326
5477
  }
5327
5478
  joe_uploader.files.push(temp_file);
5328
5479
  $(joe_uploader.preview).html(_renderUploaderFilePreviews(joe_uploader.files,joe_uploader.cuid));
5329
- joe_uploader.confirmBtn.removeClass('hidden');
5480
+ joe_uploader.confirmBtn.removeClass('ui-helper-hidden hidden');
5330
5481
  }else {
5331
5482
  results.innerHTML = 'Nothing to upload.';
5332
5483
  }
@@ -5394,6 +5545,20 @@ this.renderHTMLContent = function(specs){
5394
5545
  var fileobj = joe_uploader.files.where({filename:filename})[0];
5395
5546
  fileobj.uploaded = new Date();
5396
5547
  fileobj.url = url;
5548
+ // Apply captured OpenAI info if present
5549
+ var key = self.current.object._id + '/' + filename;
5550
+ var oi = _joe._lastOpenAI && _joe._lastOpenAI[key];
5551
+ fileobj.openai_purpose = 'assistants';
5552
+ if(oi){
5553
+ fileobj.openai_file_id = oi.openai_file_id || null;
5554
+ fileobj.openai_status = (oi.openai_file_id && 'ok') || (oi.openai_error && 'error') || 'not_uploaded';
5555
+ fileobj.openai_error = oi.openai_error || null;
5556
+ if(oi.openai_error){
5557
+ joe_uploader.message.append('<div>OpenAI error: '+oi.openai_error+'</div>');
5558
+ }
5559
+ }else{
5560
+ fileobj.openai_status = fileobj.openai_status || 'not_uploaded';
5561
+ }
5397
5562
  delete fileobj.base64;
5398
5563
  }
5399
5564
 
@@ -5464,6 +5629,14 @@ this.renderHTMLContent = function(specs){
5464
5629
  self.uploaders[id].dropzone = $(this).find('.joe-uploader-dropzone');
5465
5630
  self.uploaders[id].confirmBtn = $(this).find('.joe-upload-cofirm-button');
5466
5631
 
5632
+ // If there are pending files (not uploaded or missing url), show the confirm button
5633
+ var pending = (self.uploaders[id].files || []).some(function(file){
5634
+ return $c.isObject(file) && (!file.uploaded || !file.url);
5635
+ });
5636
+ if(pending){
5637
+ self.uploaders[id].confirmBtn.removeClass('ui-helper-hidden hidden');
5638
+ }
5639
+
5467
5640
  });
5468
5641
  };
5469
5642
  encapsulateFieldType('uploader',self.renderUploaderField,
@@ -7557,6 +7730,11 @@ Field Rendering Helpers
7557
7730
  //ready objectlist
7558
7731
  self.Fields.objectlist.ready();
7559
7732
 
7733
+ //ready json (Ace-based) fields
7734
+ if(self.Fields.json && self.Fields.json.ready){
7735
+ self.Fields.json.ready();
7736
+ }
7737
+
7560
7738
 
7561
7739
  //sidebars
7562
7740
  if(self.current.sidebars){
@@ -7682,10 +7860,26 @@ Field Rendering Helpers
7682
7860
  var f,test_info;
7683
7861
  for(var c in changes){
7684
7862
  f = self.getField(c);
7685
-
7686
-
7687
7863
  if(c[0] != '$' && c != 'joeUpdated' && f && f.type !='content' && !parseBoolean(f.passthrough)){
7688
- if(JSON.stringify(_joe.current.object[c]) == JSON.stringify(cc_construct[c])){
7864
+ // Special handling for json fields: compare parsed objects so
7865
+ // whitespace/formatting-only changes are ignored.
7866
+ var ftype = (self.propAsFuncOrValue(f.type)||'').toLowerCase();
7867
+ if(ftype == 'json'){
7868
+ var orig = _joe.current.object[c];
7869
+ var rawNew = cc_construct[c];
7870
+ var sameJson = false;
7871
+ try{
7872
+ var parsedOrig = (typeof orig == "string") ? JSON.parse(orig) : orig;
7873
+ var parsedNew = (typeof rawNew == "string") ? JSON.parse(rawNew) : rawNew;
7874
+ sameJson = (JSON.stringify(parsedOrig) == JSON.stringify(parsedNew));
7875
+ }catch(_e){
7876
+ // parse failure means it is definitely a change that needs attention
7877
+ sameJson = false;
7878
+ }
7879
+ if(sameJson){
7880
+ continue;
7881
+ }
7882
+ }else if(JSON.stringify(_joe.current.object[c]) == JSON.stringify(cc_construct[c])){
7689
7883
  continue;
7690
7884
  }
7691
7885
  if(['qrcode'].indexOf(f.type) != -1){
@@ -7988,6 +8182,27 @@ Field Rendering Helpers
7988
8182
  }
7989
8183
  var callback = callback || self.current.callback || (self.current.schema && self.current.schema.callback) || defaultCallback; //logit;
7990
8184
  var newObj = self.constructObjectFromFields(self.joe_index);
8185
+ // Normalize json fields: parse to objects and block save on invalid JSON.
8186
+ if(_joe && _joe.current && _joe.current.fields){
8187
+ var jsonField, ftype, normResult;
8188
+ for(var i=0, tot=_joe.current.fields.length; i<tot; i++){
8189
+ jsonField = _joe.current.fields[i];
8190
+ ftype = (self.propAsFuncOrValue(jsonField.type)||'').toLowerCase();
8191
+ if(ftype != 'json'){ continue; }
8192
+ // Ensure editor content is normalized and valid
8193
+ normResult = self.Fields.json && self.Fields.json.normalize
8194
+ ? self.Fields.json.normalize(jsonField.name,{showMessage:true})
8195
+ : { ok:true };
8196
+ if(!normResult.ok){
8197
+ // invalid JSON – do not proceed with save
8198
+ return false;
8199
+ }
8200
+ // Use parsed object if available; default to {} when missing
8201
+ var parsed = (normResult.parsed || (newObj[jsonField.name] && typeof newObj[jsonField.name] == "object"
8202
+ ? newObj[jsonField.name] : {}));
8203
+ newObj[jsonField.name] = parsed;
8204
+ }
8205
+ }
7991
8206
  newObj.joeUpdated = new Date();
7992
8207
  overwrites = overwrites || {};
7993
8208
  var obj = $.extend(newObj,overwrites);
@@ -9758,6 +9973,13 @@ logit(intent)
9758
9973
  url = prefix+response.Key;
9759
9974
  }
9760
9975
  var filename = response.Key.replace(self.current.object._id+'/','');
9976
+ // Stash OpenAI info for use during finalization
9977
+ _joe._lastOpenAI = _joe._lastOpenAI || {};
9978
+ _joe._lastOpenAI[response.Key] = {
9979
+ openai_file_id: response.openai_file_id,
9980
+ openai_purpose: response.openai_purpose,
9981
+ openai_error: response.openai_error
9982
+ };
9761
9983
  if(field.url_field){
9762
9984
  var nprop = {};
9763
9985
  _joe.current.object[field.url_field] = url;
@@ -9782,6 +10004,53 @@ logit(intent)
9782
10004
  file: { base64:data.base64, extension:data.extension, type:data.contentType, filename: (_joe.current.object._id+data.extension), name: (_joe.current.object._id+data.extension) },
9783
10005
  field: data.field
9784
10006
  }, callback);
10007
+ },
10008
+ // Retry uploading a specific file to OpenAI using its URL.
10009
+ // Invoked by the uploader row action.
10010
+ openaiRetryFromUrl:function(uploader_id, filename){
10011
+ try{
10012
+ var joe_uploader = self.uploaders[uploader_id];
10013
+ if(!joe_uploader){ return alert('Uploader not found'); }
10014
+ var file = joe_uploader.files.where({filename:filename})[0];
10015
+ if(!file){ return alert('File not found'); }
10016
+ if(!file.url){ return alert('No URL to upload to OpenAI'); }
10017
+ var btn = $(joe_uploader.preview).find("joe-uploader-openai-btn:contains('"+filename+"')");
10018
+ joe_uploader.message.append('<div>Uploading to OpenAI...</div>');
10019
+ $.ajax({
10020
+ url: (location.port && '//'+__jsc.hostname+':'+__jsc.port+'/API/plugin/chatgpt/filesRetryFromUrl/') || '//'+__jsc.hostname+'/API/plugin/chatgpt/filesRetryFromUrl/',
10021
+ type:'POST',
10022
+ data:{
10023
+ url:file.url,
10024
+ filename:file.filename,
10025
+ contentType:file.type
10026
+ },
10027
+ success:function(resp){
10028
+ if(resp && resp.success && resp.openai_file_id){
10029
+ file.openai_file_id = resp.openai_file_id;
10030
+ file.openai_purpose = resp.openai_purpose || 'assistants';
10031
+ file.openai_status = 'ok';
10032
+ file.openai_error = null;
10033
+ joe_uploader.message.append('<div>OpenAI file attached</div>');
10034
+ }else{
10035
+ file.openai_status = 'error';
10036
+ file.openai_error = (resp && resp.error) || 'Upload failed';
10037
+ joe_uploader.message.append('<div>OpenAI error: '+(file.openai_error)+'</div>');
10038
+ }
10039
+ // Refresh preview to reflect new status/button
10040
+ $(joe_uploader.preview).html(_renderUploaderFilePreviews(joe_uploader.files,joe_uploader.cuid));
10041
+ // Save object so file metadata is persisted
10042
+ self.updateObject(null, null, true);
10043
+ },
10044
+ error:function(xhr,status,err){
10045
+ var message = (xhr && (xhr.responseJSON && (xhr.responseJSON.error || xhr.responseJSON.code) || xhr.responseText)) || err || status || 'Retry failed';
10046
+ file.openai_status = 'error';
10047
+ file.openai_error = message;
10048
+ joe_uploader.message.append('<div>OpenAI error: '+message+'</div>');
10049
+ }
10050
+ });
10051
+ }catch(e){
10052
+ alert(e && e.message || e);
10053
+ }
9785
10054
  }
9786
10055
  },
9787
10056
  ready:{
@@ -10679,7 +10948,9 @@ logit(intent)
10679
10948
  /*-------------------------------------------------------------------->
10680
10949
  //Colors
10681
10950
  <--------------------------------------------------------------------*/
10682
- this.Colors={}
10951
+ this.Colors={
10952
+ "ai":"#0D9488"
10953
+ }
10683
10954
  this.Colors.priority = {
10684
10955
  1:'#cc4500',
10685
10956
  2:'#FFee33',