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.
- package/CHANGELOG.md +48 -0
- package/_www/mcp-test.html +18 -0
- package/css/joe-ai.css +1 -1
- package/css/joe-styles.css +52 -2
- package/css/joe.css +54 -3
- package/css/joe.min.css +1 -1
- package/docs/joe_agent_custom_gpt_instructions_v_3.md +57 -3
- package/js/JsonObjectEditor.jquery.craydent.js +281 -10
- package/js/joe-ai.js +237 -6
- package/js/joe.js +282 -11
- package/js/joe.min.js +1 -1
- package/package.json +1 -1
- package/readme.md +56 -3
- package/server/apps/aihub.js +1 -0
- package/server/fields/core.js +65 -8
- package/server/modules/MCP.js +1237 -914
- package/server/modules/ThoughtPipeline.js +23 -3
- package/server/plugins/awsConnect.js +31 -1
- package/server/plugins/chatgpt.js +235 -10
- package/server/schemas/ai_pipeline.js +90 -0
- package/server/schemas/ai_prompt.js +2 -0
- package/server/schemas/ai_response.js +171 -17
- package/server/schemas/status.js +12 -2
- package/server/schemas/task.js +9 -3
- package/server/schemas/thought.js +57 -5
- package/server/webconfig.js +1 -1
|
@@ -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
|
-
|
|
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 it
|
|
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 = '
|
|
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
|
-
|
|
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
|
-
|
|
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',
|