json-object-editor 0.10.430 → 0.10.432
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/css/joe-ai.css +5 -3
- package/css/joe-styles.css +10 -0
- package/css/joe.css +10 -0
- package/js/JsonObjectEditor.jquery.craydent.js +25 -9
- package/js/joe-ai.js +6 -1
- package/js/joe-full.js +25 -9
- package/js/joe.js +25 -9
- package/package.json +1 -1
- package/readme.md +2 -0
- package/server/app-config.js +1 -1
- package/server/fields/core.js +24 -9
- package/server/modules/Apps.js +47 -7
- package/server/modules/Storage.js +3 -0
- package/server/plugins/chatgpt-assistants.js +359 -0
- package/server/plugins/chatgpt-tools.js +79 -0
- package/server/plugins/plugin-utils.js +46 -0
- package/server/schemas/ai_conversation.js +23 -1
- package/server/schemas/ai_tool.js +23 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
## CHANGELOG
|
|
2
2
|
|
|
3
3
|
### 0.10.400
|
|
4
|
+
432 - minimal updates
|
|
5
|
+
431 - reloadable, plugin-utils, ai chat beta
|
|
4
6
|
430 - JOE Ai chat functional for alpha experiences.
|
|
5
7
|
424 - fixed formbuilder to explicitly call out jQuery
|
|
6
8
|
423 - added contextual items to chat and flattened them.
|
package/css/joe-ai.css
CHANGED
|
@@ -11,17 +11,18 @@ chatbox-wrapper {
|
|
|
11
11
|
display: block;
|
|
12
12
|
}
|
|
13
13
|
chat-footer,
|
|
14
|
-
chat-header
|
|
14
|
+
chat-header,
|
|
15
15
|
chat-content{
|
|
16
16
|
position: absolute;
|
|
17
17
|
left: 0;
|
|
18
18
|
right: 0;
|
|
19
19
|
display: block;
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
chat-header {
|
|
24
24
|
top: 0;
|
|
25
|
+
margin:5px;
|
|
25
26
|
}
|
|
26
27
|
chat-content {
|
|
27
28
|
display: block;;
|
|
@@ -50,6 +51,7 @@ chatbox-wrapper {
|
|
|
50
51
|
width: 24px;
|
|
51
52
|
height: 24px;
|
|
52
53
|
cursor: pointer;
|
|
54
|
+
z-index:5px;
|
|
53
55
|
}
|
|
54
56
|
.close-btn svg {
|
|
55
57
|
width: 100%;
|
|
@@ -58,7 +60,7 @@ chatbox-wrapper {
|
|
|
58
60
|
|
|
59
61
|
chat-title{
|
|
60
62
|
margin: 0;
|
|
61
|
-
font-size:
|
|
63
|
+
font-size: 14px;
|
|
62
64
|
display:block;
|
|
63
65
|
font-weight: bold;
|
|
64
66
|
}
|
package/css/joe-styles.css
CHANGED
|
@@ -2156,6 +2156,12 @@ joe-field{
|
|
|
2156
2156
|
.hide-label > .joe-field-label {
|
|
2157
2157
|
display: none;
|
|
2158
2158
|
}
|
|
2159
|
+
.joe-field-label .joe-reload-icon {
|
|
2160
|
+
font-size: 16px;
|
|
2161
|
+
margin: 0 10px;
|
|
2162
|
+
cursor: pointer;
|
|
2163
|
+
line-height: 14px;
|
|
2164
|
+
}
|
|
2159
2165
|
/*----------------------------- End joe-field-label -----------------------------*/
|
|
2160
2166
|
|
|
2161
2167
|
/*----------------------------- Begin joe-field-comment -----------------------------*/
|
|
@@ -3255,6 +3261,10 @@ html.no-touch .joe-group-item.disabled:hover{
|
|
|
3255
3261
|
padding-left: 10px;
|
|
3256
3262
|
cursor:pointer;
|
|
3257
3263
|
}
|
|
3264
|
+
.joe-content-sidebar .joe-group-item {
|
|
3265
|
+
width: 100%;
|
|
3266
|
+
float: none;
|
|
3267
|
+
}
|
|
3258
3268
|
/*-------------------------
|
|
3259
3269
|
Object List
|
|
3260
3270
|
-------------------------*/
|
package/css/joe.css
CHANGED
|
@@ -2642,6 +2642,12 @@ joe-field{
|
|
|
2642
2642
|
.hide-label > .joe-field-label {
|
|
2643
2643
|
display: none;
|
|
2644
2644
|
}
|
|
2645
|
+
.joe-field-label .joe-reload-icon {
|
|
2646
|
+
font-size: 16px;
|
|
2647
|
+
margin: 0 10px;
|
|
2648
|
+
cursor: pointer;
|
|
2649
|
+
line-height: 14px;
|
|
2650
|
+
}
|
|
2645
2651
|
/*----------------------------- End joe-field-label -----------------------------*/
|
|
2646
2652
|
|
|
2647
2653
|
/*----------------------------- Begin joe-field-comment -----------------------------*/
|
|
@@ -3741,6 +3747,10 @@ html.no-touch .joe-group-item.disabled:hover{
|
|
|
3741
3747
|
padding-left: 10px;
|
|
3742
3748
|
cursor:pointer;
|
|
3743
3749
|
}
|
|
3750
|
+
.joe-content-sidebar .joe-group-item {
|
|
3751
|
+
width: 100%;
|
|
3752
|
+
float: none;
|
|
3753
|
+
}
|
|
3744
3754
|
/*-------------------------
|
|
3745
3755
|
Object List
|
|
3746
3756
|
-------------------------*/
|
|
@@ -1064,6 +1064,8 @@ Column Count
|
|
|
1064
1064
|
this.Header = {}
|
|
1065
1065
|
this.toggleHelpMenu = function(show,target){
|
|
1066
1066
|
|
|
1067
|
+
}
|
|
1068
|
+
this.listUnsavedChanges = function(){
|
|
1067
1069
|
}
|
|
1068
1070
|
this.Header.Render = this.renderEditorHeader = function(specs){
|
|
1069
1071
|
var BM = new Benchmarker();
|
|
@@ -2990,7 +2992,7 @@ this.renderHTMLContent = function(specs){
|
|
|
2990
2992
|
show = ' no-section ';
|
|
2991
2993
|
hidden=true;
|
|
2992
2994
|
}
|
|
2993
|
-
var secname = fillTemplate((prop.display||prop.section_label||prop.section_start),self.current.object);
|
|
2995
|
+
var secname = fillTemplate(self.propAsFuncOrValue((prop.display||prop.section_label||prop.section_start),self.current.object),self.current.object);
|
|
2994
2996
|
var secID = prop.section_start;
|
|
2995
2997
|
if(!secname || !secID){
|
|
2996
2998
|
return '';
|
|
@@ -3226,6 +3228,7 @@ this.renderHTMLContent = function(specs){
|
|
|
3226
3228
|
+fillTemplate(fieldlabel,self.current.object)
|
|
3227
3229
|
+(required && '*' ||'')
|
|
3228
3230
|
+self.renderFieldTooltip(prop)
|
|
3231
|
+
+self.renderFieldReloadIcon(prop)
|
|
3229
3232
|
+'</label>';
|
|
3230
3233
|
|
|
3231
3234
|
//render comment
|
|
@@ -3318,7 +3321,15 @@ this.renderHTMLContent = function(specs){
|
|
|
3318
3321
|
|
|
3319
3322
|
return tooltip_html;
|
|
3320
3323
|
};
|
|
3324
|
+
this.renderFieldReloadIcon = function(prop){
|
|
3325
|
+
var reloadable = self.propAsFuncOrValue(prop.reloadable);
|
|
3326
|
+
if(!reloadable){return '';}
|
|
3327
|
+
//var comment = ($.type(prop.comment) == "function")?prop.comment():prop.comment;
|
|
3328
|
+
var comment_html = ` <joe-icon class="joe-reload-icon fright" title="Reload Field" onclick="_joe.Fields.rerender('${prop.name}')">⟳</joe-icon>`;
|
|
3321
3329
|
|
|
3330
|
+
return comment_html;
|
|
3331
|
+
};
|
|
3332
|
+
|
|
3322
3333
|
this.selectAndRenderFieldType = function(prop){
|
|
3323
3334
|
//var joeFieldBenchmarker = new Benchmarker();
|
|
3324
3335
|
|
|
@@ -4551,7 +4562,8 @@ this.renderHTMLContent = function(specs){
|
|
|
4551
4562
|
stripecolor:false,
|
|
4552
4563
|
bgcolor:false,
|
|
4553
4564
|
checkbox:false,
|
|
4554
|
-
value:''
|
|
4565
|
+
value:'',
|
|
4566
|
+
link:false
|
|
4555
4567
|
},specs);
|
|
4556
4568
|
var schemaprop = specs.schemaprop && self.propAsFuncOrValue(specs.schemaprop,item);
|
|
4557
4569
|
var schema = ((schemaprop && item[schemaprop])||schema||item.itemtype||item.type);
|
|
@@ -4562,7 +4574,7 @@ this.renderHTMLContent = function(specs){
|
|
|
4562
4574
|
|
|
4563
4575
|
var idprop = specs.idprop || (schemaobj && schemaobj.idprop) ||'_id';
|
|
4564
4576
|
var hasMenu = specs.itemMenu && specs.itemMenu.length;
|
|
4565
|
-
var action = specs.action || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
|
|
4577
|
+
var action = self.propAsFuncOrValue(specs.action,item) || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
|
|
4566
4578
|
var nonclickable = self.propAsFuncOrValue(specs.nonclickable,item);
|
|
4567
4579
|
var clickablelistitem = (!specs.gotoButton && !nonclickable/* && !hasMenu*/);
|
|
4568
4580
|
//var clickablelistitem = !!clickable && (!specs.gotoButton && !hasMenu);
|
|
@@ -4577,6 +4589,7 @@ this.renderHTMLContent = function(specs){
|
|
|
4577
4589
|
'data-value="' +encodeURI(JSON.stringify(specs.value))+ '" data-isObject=true'
|
|
4578
4590
|
:'data-value="' + specs.value + '"';
|
|
4579
4591
|
|
|
4592
|
+
var link = specs.link && self.propAsFuncOrValue(specs.link,item);
|
|
4580
4593
|
var html = fillTemplate(`<joe-list-item schema="${schema}"
|
|
4581
4594
|
itemId="${item[idprop]}"
|
|
4582
4595
|
idprop="${idprop}"
|
|
@@ -4599,13 +4612,13 @@ this.renderHTMLContent = function(specs){
|
|
|
4599
4612
|
|
|
4600
4613
|
+ self.Render.itemCheckbox(item,schemaobj)
|
|
4601
4614
|
|
|
4602
|
-
+((
|
|
4615
|
+
+((link && '<a class="non-link" onclick="__cancelClick();" href="'+link+'">')||'')
|
|
4603
4616
|
|
|
4604
4617
|
+ '<div class="joe-field-item-content '+(nonclickable && 'nonclickable' || '')+'" ' + click + ' >'
|
|
4605
4618
|
+ self.propAsFuncOrValue(contentTemplate+__clearDiv__, item)
|
|
4606
4619
|
+ __clearDiv__+'</div>'
|
|
4607
4620
|
|
|
4608
|
-
+((
|
|
4621
|
+
+((link && '</a>')||'')
|
|
4609
4622
|
+self.Render.stripeColor(item,specs.stripecolor)
|
|
4610
4623
|
+ self._renderExpanderButton(expanderContent, item)
|
|
4611
4624
|
+ (specs.deleteButton && deleteButton || '')
|
|
@@ -7380,6 +7393,7 @@ Field Rendering Helpers
|
|
|
7380
7393
|
var sortable_index;
|
|
7381
7394
|
this.Autosave = {
|
|
7382
7395
|
possibleChanges:true,
|
|
7396
|
+
unsavedChanges:false,
|
|
7383
7397
|
activate:function(){
|
|
7384
7398
|
if(!self.specs.autosave){
|
|
7385
7399
|
return;
|
|
@@ -7409,7 +7423,8 @@ Field Rendering Helpers
|
|
|
7409
7423
|
var showUnsaved = (hasChanged && self.getMode() == "details");
|
|
7410
7424
|
document.title =( showUnsaved && '*' ||'')
|
|
7411
7425
|
+document.title.replace('*','');
|
|
7412
|
-
self.overlay.toggleClass('unsaved-changes',showUnsaved)
|
|
7426
|
+
self.overlay.toggleClass('unsaved-changes',showUnsaved);
|
|
7427
|
+
self.Autosave.unsavedChanges = hasChanged;
|
|
7413
7428
|
},intercnt)
|
|
7414
7429
|
},
|
|
7415
7430
|
deactivate:function(){
|
|
@@ -7641,7 +7656,7 @@ Field Rendering Helpers
|
|
|
7641
7656
|
tocheck.map(field=>{
|
|
7642
7657
|
|
|
7643
7658
|
})
|
|
7644
|
-
return (tocheck.length > 0);
|
|
7659
|
+
return (tocheck.length > 0)?tocheck:false;
|
|
7645
7660
|
}
|
|
7646
7661
|
if(self.overlay.hasClass('active') && !listMode && !self.current.changesConfirmed && tocheck.length){
|
|
7647
7662
|
|
|
@@ -7649,7 +7664,7 @@ Field Rendering Helpers
|
|
|
7649
7664
|
self.current.changesConfirmed = confirmed;
|
|
7650
7665
|
return confirmed;
|
|
7651
7666
|
}
|
|
7652
|
-
return
|
|
7667
|
+
return tocheck;
|
|
7653
7668
|
}
|
|
7654
7669
|
/*-------------------------------------------------------------------->
|
|
7655
7670
|
J | MESSAGING
|
|
@@ -9860,7 +9875,7 @@ logit(intent)
|
|
|
9860
9875
|
var users = _joe.Data[(user_dataset || 'user')].where({_id:{$in:ids}});
|
|
9861
9876
|
var html = '';
|
|
9862
9877
|
users.map(function(user){
|
|
9863
|
-
html += self.SERVER.User.Render.cube(user);
|
|
9878
|
+
html += self.SERVER.User.Render.cube(user,cssclass);
|
|
9864
9879
|
})
|
|
9865
9880
|
return html;
|
|
9866
9881
|
},
|
|
@@ -10381,6 +10396,7 @@ logit(intent)
|
|
|
10381
10396
|
var e = e || window.event;
|
|
10382
10397
|
if (e.stopPropagation) e.stopPropagation();
|
|
10383
10398
|
if (e.preventDefault) e.preventDefault();
|
|
10399
|
+
return false;
|
|
10384
10400
|
},
|
|
10385
10401
|
getPossibleValues:function(propertyName,schema,specs){
|
|
10386
10402
|
//TODO:
|
package/js/joe-ai.js
CHANGED
|
@@ -259,7 +259,12 @@
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
renderError(message) {
|
|
262
|
-
this.shadowRoot.innerHTML = `<div style="color:red;">${message}</div
|
|
262
|
+
this.shadowRoot.innerHTML = `<div style="color:red;">${message}</div>
|
|
263
|
+
<div class="close-btn" title="Close Chatbox" >${_joe.SVG.icon.close}</div>`;
|
|
264
|
+
this.shadowRoot.querySelector('.close-btn').addEventListener('click', () => {
|
|
265
|
+
//this.closeChat()
|
|
266
|
+
this.closest('joe-ai-chatbox').closeChat();
|
|
267
|
+
});
|
|
263
268
|
}
|
|
264
269
|
|
|
265
270
|
|
package/js/joe-full.js
CHANGED
|
@@ -12369,6 +12369,8 @@ Column Count
|
|
|
12369
12369
|
this.Header = {}
|
|
12370
12370
|
this.toggleHelpMenu = function(show,target){
|
|
12371
12371
|
|
|
12372
|
+
}
|
|
12373
|
+
this.listUnsavedChanges = function(){
|
|
12372
12374
|
}
|
|
12373
12375
|
this.Header.Render = this.renderEditorHeader = function(specs){
|
|
12374
12376
|
var BM = new Benchmarker();
|
|
@@ -14295,7 +14297,7 @@ this.renderHTMLContent = function(specs){
|
|
|
14295
14297
|
show = ' no-section ';
|
|
14296
14298
|
hidden=true;
|
|
14297
14299
|
}
|
|
14298
|
-
var secname = fillTemplate((prop.display||prop.section_label||prop.section_start),self.current.object);
|
|
14300
|
+
var secname = fillTemplate(self.propAsFuncOrValue((prop.display||prop.section_label||prop.section_start),self.current.object),self.current.object);
|
|
14299
14301
|
var secID = prop.section_start;
|
|
14300
14302
|
if(!secname || !secID){
|
|
14301
14303
|
return '';
|
|
@@ -14531,6 +14533,7 @@ this.renderHTMLContent = function(specs){
|
|
|
14531
14533
|
+fillTemplate(fieldlabel,self.current.object)
|
|
14532
14534
|
+(required && '*' ||'')
|
|
14533
14535
|
+self.renderFieldTooltip(prop)
|
|
14536
|
+
+self.renderFieldReloadIcon(prop)
|
|
14534
14537
|
+'</label>';
|
|
14535
14538
|
|
|
14536
14539
|
//render comment
|
|
@@ -14623,7 +14626,15 @@ this.renderHTMLContent = function(specs){
|
|
|
14623
14626
|
|
|
14624
14627
|
return tooltip_html;
|
|
14625
14628
|
};
|
|
14629
|
+
this.renderFieldReloadIcon = function(prop){
|
|
14630
|
+
var reloadable = self.propAsFuncOrValue(prop.reloadable);
|
|
14631
|
+
if(!reloadable){return '';}
|
|
14632
|
+
//var comment = ($.type(prop.comment) == "function")?prop.comment():prop.comment;
|
|
14633
|
+
var comment_html = ` <joe-icon class="joe-reload-icon fright" title="Reload Field" onclick="_joe.Fields.rerender('${prop.name}')">⟳</joe-icon>`;
|
|
14626
14634
|
|
|
14635
|
+
return comment_html;
|
|
14636
|
+
};
|
|
14637
|
+
|
|
14627
14638
|
this.selectAndRenderFieldType = function(prop){
|
|
14628
14639
|
//var joeFieldBenchmarker = new Benchmarker();
|
|
14629
14640
|
|
|
@@ -15856,7 +15867,8 @@ this.renderHTMLContent = function(specs){
|
|
|
15856
15867
|
stripecolor:false,
|
|
15857
15868
|
bgcolor:false,
|
|
15858
15869
|
checkbox:false,
|
|
15859
|
-
value:''
|
|
15870
|
+
value:'',
|
|
15871
|
+
link:false
|
|
15860
15872
|
},specs);
|
|
15861
15873
|
var schemaprop = specs.schemaprop && self.propAsFuncOrValue(specs.schemaprop,item);
|
|
15862
15874
|
var schema = ((schemaprop && item[schemaprop])||schema||item.itemtype||item.type);
|
|
@@ -15867,7 +15879,7 @@ this.renderHTMLContent = function(specs){
|
|
|
15867
15879
|
|
|
15868
15880
|
var idprop = specs.idprop || (schemaobj && schemaobj.idprop) ||'_id';
|
|
15869
15881
|
var hasMenu = specs.itemMenu && specs.itemMenu.length;
|
|
15870
|
-
var action = specs.action || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
|
|
15882
|
+
var action = self.propAsFuncOrValue(specs.action,item) || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
|
|
15871
15883
|
var nonclickable = self.propAsFuncOrValue(specs.nonclickable,item);
|
|
15872
15884
|
var clickablelistitem = (!specs.gotoButton && !nonclickable/* && !hasMenu*/);
|
|
15873
15885
|
//var clickablelistitem = !!clickable && (!specs.gotoButton && !hasMenu);
|
|
@@ -15882,6 +15894,7 @@ this.renderHTMLContent = function(specs){
|
|
|
15882
15894
|
'data-value="' +encodeURI(JSON.stringify(specs.value))+ '" data-isObject=true'
|
|
15883
15895
|
:'data-value="' + specs.value + '"';
|
|
15884
15896
|
|
|
15897
|
+
var link = specs.link && self.propAsFuncOrValue(specs.link,item);
|
|
15885
15898
|
var html = fillTemplate(`<joe-list-item schema="${schema}"
|
|
15886
15899
|
itemId="${item[idprop]}"
|
|
15887
15900
|
idprop="${idprop}"
|
|
@@ -15904,13 +15917,13 @@ this.renderHTMLContent = function(specs){
|
|
|
15904
15917
|
|
|
15905
15918
|
+ self.Render.itemCheckbox(item,schemaobj)
|
|
15906
15919
|
|
|
15907
|
-
+((
|
|
15920
|
+
+((link && '<a class="non-link" onclick="__cancelClick();" href="'+link+'">')||'')
|
|
15908
15921
|
|
|
15909
15922
|
+ '<div class="joe-field-item-content '+(nonclickable && 'nonclickable' || '')+'" ' + click + ' >'
|
|
15910
15923
|
+ self.propAsFuncOrValue(contentTemplate+__clearDiv__, item)
|
|
15911
15924
|
+ __clearDiv__+'</div>'
|
|
15912
15925
|
|
|
15913
|
-
+((
|
|
15926
|
+
+((link && '</a>')||'')
|
|
15914
15927
|
+self.Render.stripeColor(item,specs.stripecolor)
|
|
15915
15928
|
+ self._renderExpanderButton(expanderContent, item)
|
|
15916
15929
|
+ (specs.deleteButton && deleteButton || '')
|
|
@@ -18685,6 +18698,7 @@ Field Rendering Helpers
|
|
|
18685
18698
|
var sortable_index;
|
|
18686
18699
|
this.Autosave = {
|
|
18687
18700
|
possibleChanges:true,
|
|
18701
|
+
unsavedChanges:false,
|
|
18688
18702
|
activate:function(){
|
|
18689
18703
|
if(!self.specs.autosave){
|
|
18690
18704
|
return;
|
|
@@ -18714,7 +18728,8 @@ Field Rendering Helpers
|
|
|
18714
18728
|
var showUnsaved = (hasChanged && self.getMode() == "details");
|
|
18715
18729
|
document.title =( showUnsaved && '*' ||'')
|
|
18716
18730
|
+document.title.replace('*','');
|
|
18717
|
-
self.overlay.toggleClass('unsaved-changes',showUnsaved)
|
|
18731
|
+
self.overlay.toggleClass('unsaved-changes',showUnsaved);
|
|
18732
|
+
self.Autosave.unsavedChanges = hasChanged;
|
|
18718
18733
|
},intercnt)
|
|
18719
18734
|
},
|
|
18720
18735
|
deactivate:function(){
|
|
@@ -18946,7 +18961,7 @@ Field Rendering Helpers
|
|
|
18946
18961
|
tocheck.map(field=>{
|
|
18947
18962
|
|
|
18948
18963
|
})
|
|
18949
|
-
return (tocheck.length > 0);
|
|
18964
|
+
return (tocheck.length > 0)?tocheck:false;
|
|
18950
18965
|
}
|
|
18951
18966
|
if(self.overlay.hasClass('active') && !listMode && !self.current.changesConfirmed && tocheck.length){
|
|
18952
18967
|
|
|
@@ -18954,7 +18969,7 @@ Field Rendering Helpers
|
|
|
18954
18969
|
self.current.changesConfirmed = confirmed;
|
|
18955
18970
|
return confirmed;
|
|
18956
18971
|
}
|
|
18957
|
-
return
|
|
18972
|
+
return tocheck;
|
|
18958
18973
|
}
|
|
18959
18974
|
/*-------------------------------------------------------------------->
|
|
18960
18975
|
J | MESSAGING
|
|
@@ -21165,7 +21180,7 @@ logit(intent)
|
|
|
21165
21180
|
var users = _joe.Data[(user_dataset || 'user')].where({_id:{$in:ids}});
|
|
21166
21181
|
var html = '';
|
|
21167
21182
|
users.map(function(user){
|
|
21168
|
-
html += self.SERVER.User.Render.cube(user);
|
|
21183
|
+
html += self.SERVER.User.Render.cube(user,cssclass);
|
|
21169
21184
|
})
|
|
21170
21185
|
return html;
|
|
21171
21186
|
},
|
|
@@ -21686,6 +21701,7 @@ logit(intent)
|
|
|
21686
21701
|
var e = e || window.event;
|
|
21687
21702
|
if (e.stopPropagation) e.stopPropagation();
|
|
21688
21703
|
if (e.preventDefault) e.preventDefault();
|
|
21704
|
+
return false;
|
|
21689
21705
|
},
|
|
21690
21706
|
getPossibleValues:function(propertyName,schema,specs){
|
|
21691
21707
|
//TODO:
|
package/js/joe.js
CHANGED
|
@@ -1070,6 +1070,8 @@ Column Count
|
|
|
1070
1070
|
this.Header = {}
|
|
1071
1071
|
this.toggleHelpMenu = function(show,target){
|
|
1072
1072
|
|
|
1073
|
+
}
|
|
1074
|
+
this.listUnsavedChanges = function(){
|
|
1073
1075
|
}
|
|
1074
1076
|
this.Header.Render = this.renderEditorHeader = function(specs){
|
|
1075
1077
|
var BM = new Benchmarker();
|
|
@@ -2996,7 +2998,7 @@ this.renderHTMLContent = function(specs){
|
|
|
2996
2998
|
show = ' no-section ';
|
|
2997
2999
|
hidden=true;
|
|
2998
3000
|
}
|
|
2999
|
-
var secname = fillTemplate((prop.display||prop.section_label||prop.section_start),self.current.object);
|
|
3001
|
+
var secname = fillTemplate(self.propAsFuncOrValue((prop.display||prop.section_label||prop.section_start),self.current.object),self.current.object);
|
|
3000
3002
|
var secID = prop.section_start;
|
|
3001
3003
|
if(!secname || !secID){
|
|
3002
3004
|
return '';
|
|
@@ -3232,6 +3234,7 @@ this.renderHTMLContent = function(specs){
|
|
|
3232
3234
|
+fillTemplate(fieldlabel,self.current.object)
|
|
3233
3235
|
+(required && '*' ||'')
|
|
3234
3236
|
+self.renderFieldTooltip(prop)
|
|
3237
|
+
+self.renderFieldReloadIcon(prop)
|
|
3235
3238
|
+'</label>';
|
|
3236
3239
|
|
|
3237
3240
|
//render comment
|
|
@@ -3324,7 +3327,15 @@ this.renderHTMLContent = function(specs){
|
|
|
3324
3327
|
|
|
3325
3328
|
return tooltip_html;
|
|
3326
3329
|
};
|
|
3330
|
+
this.renderFieldReloadIcon = function(prop){
|
|
3331
|
+
var reloadable = self.propAsFuncOrValue(prop.reloadable);
|
|
3332
|
+
if(!reloadable){return '';}
|
|
3333
|
+
//var comment = ($.type(prop.comment) == "function")?prop.comment():prop.comment;
|
|
3334
|
+
var comment_html = ` <joe-icon class="joe-reload-icon fright" title="Reload Field" onclick="_joe.Fields.rerender('${prop.name}')">⟳</joe-icon>`;
|
|
3327
3335
|
|
|
3336
|
+
return comment_html;
|
|
3337
|
+
};
|
|
3338
|
+
|
|
3328
3339
|
this.selectAndRenderFieldType = function(prop){
|
|
3329
3340
|
//var joeFieldBenchmarker = new Benchmarker();
|
|
3330
3341
|
|
|
@@ -4557,7 +4568,8 @@ this.renderHTMLContent = function(specs){
|
|
|
4557
4568
|
stripecolor:false,
|
|
4558
4569
|
bgcolor:false,
|
|
4559
4570
|
checkbox:false,
|
|
4560
|
-
value:''
|
|
4571
|
+
value:'',
|
|
4572
|
+
link:false
|
|
4561
4573
|
},specs);
|
|
4562
4574
|
var schemaprop = specs.schemaprop && self.propAsFuncOrValue(specs.schemaprop,item);
|
|
4563
4575
|
var schema = ((schemaprop && item[schemaprop])||schema||item.itemtype||item.type);
|
|
@@ -4568,7 +4580,7 @@ this.renderHTMLContent = function(specs){
|
|
|
4568
4580
|
|
|
4569
4581
|
var idprop = specs.idprop || (schemaobj && schemaobj.idprop) ||'_id';
|
|
4570
4582
|
var hasMenu = specs.itemMenu && specs.itemMenu.length;
|
|
4571
|
-
var action = specs.action || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
|
|
4583
|
+
var action = self.propAsFuncOrValue(specs.action,item) || ' onclick="goJoe(_joe.search(\'${'+idprop+'}\')[0],{schema:\''+schema+'\'})" ';
|
|
4572
4584
|
var nonclickable = self.propAsFuncOrValue(specs.nonclickable,item);
|
|
4573
4585
|
var clickablelistitem = (!specs.gotoButton && !nonclickable/* && !hasMenu*/);
|
|
4574
4586
|
//var clickablelistitem = !!clickable && (!specs.gotoButton && !hasMenu);
|
|
@@ -4583,6 +4595,7 @@ this.renderHTMLContent = function(specs){
|
|
|
4583
4595
|
'data-value="' +encodeURI(JSON.stringify(specs.value))+ '" data-isObject=true'
|
|
4584
4596
|
:'data-value="' + specs.value + '"';
|
|
4585
4597
|
|
|
4598
|
+
var link = specs.link && self.propAsFuncOrValue(specs.link,item);
|
|
4586
4599
|
var html = fillTemplate(`<joe-list-item schema="${schema}"
|
|
4587
4600
|
itemId="${item[idprop]}"
|
|
4588
4601
|
idprop="${idprop}"
|
|
@@ -4605,13 +4618,13 @@ this.renderHTMLContent = function(specs){
|
|
|
4605
4618
|
|
|
4606
4619
|
+ self.Render.itemCheckbox(item,schemaobj)
|
|
4607
4620
|
|
|
4608
|
-
+((
|
|
4621
|
+
+((link && '<a class="non-link" onclick="__cancelClick();" href="'+link+'">')||'')
|
|
4609
4622
|
|
|
4610
4623
|
+ '<div class="joe-field-item-content '+(nonclickable && 'nonclickable' || '')+'" ' + click + ' >'
|
|
4611
4624
|
+ self.propAsFuncOrValue(contentTemplate+__clearDiv__, item)
|
|
4612
4625
|
+ __clearDiv__+'</div>'
|
|
4613
4626
|
|
|
4614
|
-
+((
|
|
4627
|
+
+((link && '</a>')||'')
|
|
4615
4628
|
+self.Render.stripeColor(item,specs.stripecolor)
|
|
4616
4629
|
+ self._renderExpanderButton(expanderContent, item)
|
|
4617
4630
|
+ (specs.deleteButton && deleteButton || '')
|
|
@@ -7386,6 +7399,7 @@ Field Rendering Helpers
|
|
|
7386
7399
|
var sortable_index;
|
|
7387
7400
|
this.Autosave = {
|
|
7388
7401
|
possibleChanges:true,
|
|
7402
|
+
unsavedChanges:false,
|
|
7389
7403
|
activate:function(){
|
|
7390
7404
|
if(!self.specs.autosave){
|
|
7391
7405
|
return;
|
|
@@ -7415,7 +7429,8 @@ Field Rendering Helpers
|
|
|
7415
7429
|
var showUnsaved = (hasChanged && self.getMode() == "details");
|
|
7416
7430
|
document.title =( showUnsaved && '*' ||'')
|
|
7417
7431
|
+document.title.replace('*','');
|
|
7418
|
-
self.overlay.toggleClass('unsaved-changes',showUnsaved)
|
|
7432
|
+
self.overlay.toggleClass('unsaved-changes',showUnsaved);
|
|
7433
|
+
self.Autosave.unsavedChanges = hasChanged;
|
|
7419
7434
|
},intercnt)
|
|
7420
7435
|
},
|
|
7421
7436
|
deactivate:function(){
|
|
@@ -7647,7 +7662,7 @@ Field Rendering Helpers
|
|
|
7647
7662
|
tocheck.map(field=>{
|
|
7648
7663
|
|
|
7649
7664
|
})
|
|
7650
|
-
return (tocheck.length > 0);
|
|
7665
|
+
return (tocheck.length > 0)?tocheck:false;
|
|
7651
7666
|
}
|
|
7652
7667
|
if(self.overlay.hasClass('active') && !listMode && !self.current.changesConfirmed && tocheck.length){
|
|
7653
7668
|
|
|
@@ -7655,7 +7670,7 @@ Field Rendering Helpers
|
|
|
7655
7670
|
self.current.changesConfirmed = confirmed;
|
|
7656
7671
|
return confirmed;
|
|
7657
7672
|
}
|
|
7658
|
-
return
|
|
7673
|
+
return tocheck;
|
|
7659
7674
|
}
|
|
7660
7675
|
/*-------------------------------------------------------------------->
|
|
7661
7676
|
J | MESSAGING
|
|
@@ -9866,7 +9881,7 @@ logit(intent)
|
|
|
9866
9881
|
var users = _joe.Data[(user_dataset || 'user')].where({_id:{$in:ids}});
|
|
9867
9882
|
var html = '';
|
|
9868
9883
|
users.map(function(user){
|
|
9869
|
-
html += self.SERVER.User.Render.cube(user);
|
|
9884
|
+
html += self.SERVER.User.Render.cube(user,cssclass);
|
|
9870
9885
|
})
|
|
9871
9886
|
return html;
|
|
9872
9887
|
},
|
|
@@ -10387,6 +10402,7 @@ logit(intent)
|
|
|
10387
10402
|
var e = e || window.event;
|
|
10388
10403
|
if (e.stopPropagation) e.stopPropagation();
|
|
10389
10404
|
if (e.preventDefault) e.preventDefault();
|
|
10405
|
+
return false;
|
|
10390
10406
|
},
|
|
10391
10407
|
getPossibleValues:function(propertyName,schema,specs){
|
|
10392
10408
|
//TODO:
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -198,11 +198,13 @@ a list of schema objects that can configure the editor fields, these can be give
|
|
|
198
198
|
alert(obj.name);
|
|
199
199
|
},*/
|
|
200
200
|
onblur:logit,
|
|
201
|
+
|
|
201
202
|
hideNumbers:boolean *toggle list numbers*
|
|
202
203
|
multipleCallback:function to be called after a multi-edit. passed list of edited items.
|
|
203
204
|
onUpdate: callback for after update. passed single edited items.
|
|
204
205
|
onMultipleUpdate:callback for after multi update.passed list of edited items.
|
|
205
206
|
filters: array of objects
|
|
207
|
+
checkChanges:(bool) whether or not to check for changes via interval and on leave
|
|
206
208
|
}
|
|
207
209
|
##Table View
|
|
208
210
|
- add tableView object to a schema;
|
package/server/app-config.js
CHANGED
|
@@ -31,7 +31,7 @@ var apps = function(){
|
|
|
31
31
|
title:'JOE Platform',
|
|
32
32
|
info:'This is the master Data Model Management System (DMMS) admin page. It has access to all of your schemas and datasets.',
|
|
33
33
|
description:'JOE is the Json Object Editor. The Platform app has access to all your schemas and datasets.',
|
|
34
|
-
plugins:['auth.js','formBuilder.js','reportbuilder.js','callbackTester.js','memberRegistry.js','awsConnect.js','notifier.js','calendar.js','inventory.js','money.js'],
|
|
34
|
+
plugins:['auth.js','formBuilder.js','reportbuilder.js','callbackTester.js','memberRegistry.js','awsConnect.js','notifier.js','calendar.js','inventory.js','money.js','plugin-utils.js','chatgpt.js','chatgpt-assistants.js','chatgpt-tools.js'],
|
|
35
35
|
collections:((default_schemas.concat(['schema','group',
|
|
36
36
|
'site','page','post','layout','block','include','event',
|
|
37
37
|
'project','board','task',
|
package/server/fields/core.js
CHANGED
|
@@ -68,15 +68,16 @@ var fields = {
|
|
|
68
68
|
'coords':{type:'geo'},
|
|
69
69
|
'state':{type:'select',values:usStates,idprop:'code',template:'${code}',width:'25%',minwidth:'100px',blank:true},
|
|
70
70
|
'name':{type:'text',onblur:'_joe.TITLE.set()'},
|
|
71
|
-
instructions_format:{type:'select',values:['wysiwyg','code'], rerender:'instructions',display:'Instructions Format'},
|
|
71
|
+
instructions_format:{type:'select',values:['wysiwyg','code','text',], rerender:'instructions',display:'Instructions Format'},
|
|
72
72
|
instructions:{
|
|
73
|
-
height:'
|
|
73
|
+
height:'500px',
|
|
74
74
|
display:'Instructions',
|
|
75
|
+
default:'code',
|
|
75
76
|
type:function(item){
|
|
76
|
-
if(!item.instructions_format){
|
|
77
|
-
return '
|
|
77
|
+
if(!item.instructions_format || item.instructions_format == 'text'){
|
|
78
|
+
return 'rendering';
|
|
78
79
|
}
|
|
79
|
-
if(["code"].indexOf(item.instructions_format) != -1){
|
|
80
|
+
else if(["code"].indexOf(item.instructions_format) != -1){
|
|
80
81
|
return 'code';
|
|
81
82
|
}
|
|
82
83
|
return item.instructions_format;
|
|
@@ -86,8 +87,16 @@ var fields = {
|
|
|
86
87
|
type: "select",
|
|
87
88
|
display: "Ai Model",
|
|
88
89
|
values: [
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
{ value: "gpt-4o", name: "GPT-4o (Fast, 128k)" },
|
|
91
|
+
{ value: "gpt-4.1", name: "GPT-4.1 (Strong, 1M)" },
|
|
92
|
+
{ value: "gpt-4.1-mini", name: "4.1-mini (Cheap, 1M)" },
|
|
93
|
+
{ value: "gpt-4.1-nano", name: "4.1-nano (Fastest, light tasks)" }
|
|
94
|
+
],
|
|
95
|
+
tooltip:`Ai Model Guide -
|
|
96
|
+
GPT-4o is the default for fast, responsive tasks and supports up to 128k tokens. It’s ideal for short completions, summaries, and dynamic UI tools.
|
|
97
|
+
GPT-4.1 and 4.1-mini support a massive 1 million token context, making them perfect for large inputs like full business profiles, long strategy texts, and multi-object analysis.
|
|
98
|
+
4.1-mini is significantly cheaper than full 4.1, with great balance for most structured AI workflows.
|
|
99
|
+
4.1-nano is best for lightweight classification or routing logic where speed and cost matter more than depth.`,
|
|
91
100
|
default: "gpt-4o",
|
|
92
101
|
},
|
|
93
102
|
template:{
|
|
@@ -174,8 +183,14 @@ var fields = {
|
|
|
174
183
|
return status.color||'';
|
|
175
184
|
}
|
|
176
185
|
},
|
|
177
|
-
tags:{type:'group',icon:'tag',
|
|
178
|
-
|
|
186
|
+
tags:{type:'group',icon:'tag',hidden:function(obj){
|
|
187
|
+
let tags = _joe.Data.tag.filter(function(tag){
|
|
188
|
+
return !(tag.datasets.indexOf(item.itemtype) == -1);
|
|
189
|
+
});
|
|
190
|
+
if(!tags.length){
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
179
194
|
values:function(item){
|
|
180
195
|
var tags = _joe.Data.tag.filter(function(tag){
|
|
181
196
|
return !(tag.datasets.indexOf(item.itemtype) == -1);
|
package/server/modules/Apps.js
CHANGED
|
@@ -496,16 +496,53 @@ function Apps(){
|
|
|
496
496
|
}
|
|
497
497
|
this.loadPlugin = function(p){
|
|
498
498
|
//logit(moduleName+'loading plugin '+p);
|
|
499
|
+
let _joeSrc='';
|
|
499
500
|
console.log(JOE.Utils.color('[plugin]', 'plugin'), 'loaded: '+p);
|
|
500
501
|
var plugin;
|
|
501
502
|
if(p.indexOf('.js') == -1){p+= '.js';}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
503
|
+
|
|
504
|
+
const candidateDirs = [
|
|
505
|
+
pluginsDir,
|
|
506
|
+
internalPluginsDir
|
|
507
|
+
];
|
|
508
|
+
let pluginpath = null;
|
|
509
|
+
let override = false;
|
|
510
|
+
//for (let dir of candidateDirs) {
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
let attempt = internalPluginsDir + p;
|
|
515
|
+
fs.accessSync(attempt, fs.F_OK);
|
|
516
|
+
pluginpath = attempt;
|
|
505
517
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
518
|
+
|
|
519
|
+
} catch (e) {
|
|
520
|
+
// continue
|
|
521
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
let attempt = pluginsDir + p;
|
|
524
|
+
fs.accessSync(attempt, fs.F_OK);
|
|
525
|
+
if(pluginpath){
|
|
526
|
+
override = true;
|
|
527
|
+
}
|
|
528
|
+
pluginpath = attempt;
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
} catch (e) {
|
|
532
|
+
|
|
533
|
+
// continue
|
|
534
|
+
}
|
|
535
|
+
// }
|
|
536
|
+
// try{
|
|
537
|
+
// //first try the pluginsDir, then the internalPluginsDir
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
// var pluginpath = internalPluginsDir + p;
|
|
541
|
+
// fs.accessSync(pluginpath, fs.F_OK);
|
|
542
|
+
|
|
543
|
+
// }catch(e){
|
|
544
|
+
// var pluginpath = pluginsDir + p;
|
|
545
|
+
// }
|
|
509
546
|
try{
|
|
510
547
|
try{
|
|
511
548
|
delete require.cache[require.resolve(pluginpath)];
|
|
@@ -513,12 +550,15 @@ function Apps(){
|
|
|
513
550
|
console.log(moduleName+'[error] '+p+' not deleted');
|
|
514
551
|
}
|
|
515
552
|
plugin = require(pluginpath);
|
|
516
|
-
|
|
553
|
+
_joeSrc = pluginpath;
|
|
517
554
|
}catch(e){
|
|
518
555
|
plugin = {error:'plugin '+p+' not found'};
|
|
519
556
|
console.log(e,plugin);
|
|
520
557
|
}
|
|
558
|
+
plugin._pathname = _joeSrc;
|
|
559
|
+
plugin._override = override;
|
|
521
560
|
self.plugins[p.replace('.js','')] = plugin;
|
|
561
|
+
|
|
522
562
|
return plugin;
|
|
523
563
|
}
|
|
524
564
|
|
|
@@ -102,6 +102,9 @@ function Storage(specs){
|
|
|
102
102
|
|
|
103
103
|
var save_callback = function(err,data){
|
|
104
104
|
callback(err,data);
|
|
105
|
+
if (!specs || specs.push !== false) {
|
|
106
|
+
JOE.io.emit('item_updated', { results: [data] });
|
|
107
|
+
}
|
|
105
108
|
if(!err){
|
|
106
109
|
var ts = new Date().toISOString();
|
|
107
110
|
var cached = JOE.Cache.findByID(data.itemtype,data._id);
|
|
@@ -0,0 +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();
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const OpenAI = require("openai");
|
|
2
|
+
|
|
3
|
+
function ChatGPTTools() {
|
|
4
|
+
const self = this;
|
|
5
|
+
|
|
6
|
+
function coloredLog(message) {
|
|
7
|
+
console.log(JOE.Utils.color('[chatgpt-tools]', 'plugin', false), message);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
function getAPIKey() {
|
|
13
|
+
const setting = JOE.Utils.Settings('OPENAI_API_KEY');
|
|
14
|
+
if (!setting) throw new Error("Missing OPENAI_API_KEY setting");
|
|
15
|
+
return setting;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//search JOE data for ai_tool objects
|
|
19
|
+
function getTools(data){
|
|
20
|
+
var query = Object.assign({itemtype:'ai_tool'}, data||{});
|
|
21
|
+
var tools = JOE.Cache.search(query);
|
|
22
|
+
var functionalTools = {};
|
|
23
|
+
for(var i=0; i<tools.length; i++){
|
|
24
|
+
var tool = tools[i];
|
|
25
|
+
functionalTools[tool._id] = Object.assign(tool,{
|
|
26
|
+
tool_properties:JSON.parse(tool.tool_properties),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return functionalTools;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function newClient() {
|
|
33
|
+
return new OpenAI({ apiKey: getAPIKey() });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
this.default = function(data, req, res) {
|
|
38
|
+
try {
|
|
39
|
+
var payload = {
|
|
40
|
+
params: req.params,
|
|
41
|
+
data: data,
|
|
42
|
+
tools:getTools(data),
|
|
43
|
+
success:true
|
|
44
|
+
};
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return { errors: 'plugin error: ' + e, failedat: 'plugin' };
|
|
47
|
+
}
|
|
48
|
+
return payload;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
this.flattened = function(data, req, res){
|
|
52
|
+
try {
|
|
53
|
+
var flattened = JOE.Utils.flattenObject(data._id);
|
|
54
|
+
var payload = {
|
|
55
|
+
params: req.params,
|
|
56
|
+
data: data,
|
|
57
|
+
object:flattened,
|
|
58
|
+
success:true
|
|
59
|
+
};
|
|
60
|
+
} catch (e) {
|
|
61
|
+
return { errors: 'plugin error: ' + e, failedat: 'plugin' };
|
|
62
|
+
}
|
|
63
|
+
return payload;
|
|
64
|
+
}
|
|
65
|
+
//1. start or continue a conversation
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
//self.profileBusinessFromURL = profileBusinessFromURL;
|
|
71
|
+
this.async = {
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
};
|
|
75
|
+
return self;
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = new ChatGPTTools();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function PluginUtils(){
|
|
2
|
+
var self = this;
|
|
3
|
+
|
|
4
|
+
function getPluginList(){
|
|
5
|
+
const plugins = {}
|
|
6
|
+
for(var plug in JOE.Apps.plugins){
|
|
7
|
+
let plugin = JOE.Apps.plugins[plug];
|
|
8
|
+
plugins[plug]={
|
|
9
|
+
name:plug,
|
|
10
|
+
protected:plugin.protected,
|
|
11
|
+
async:Object.keys(plugin.async||{}),
|
|
12
|
+
_pathname:plugin._pathname,
|
|
13
|
+
_override:plugin._override,
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
return plugins;
|
|
17
|
+
}
|
|
18
|
+
this.default = function(data,req,res){
|
|
19
|
+
// list the plugins that are available
|
|
20
|
+
const plugins = getPluginList();
|
|
21
|
+
try{
|
|
22
|
+
var payload = {
|
|
23
|
+
params:req.params,
|
|
24
|
+
data:data,
|
|
25
|
+
plugins:plugins
|
|
26
|
+
}
|
|
27
|
+
}catch(e){
|
|
28
|
+
return {errors:'plugin error: '+e,failedat:'plugin'};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
return payload;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
this.html = function(data,req,res){
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
return JSON.stringify(self.default(data,req),'','\t\r\n <br/>');
|
|
42
|
+
}
|
|
43
|
+
this.protected = [];
|
|
44
|
+
return self;
|
|
45
|
+
}
|
|
46
|
+
module.exports = new PluginUtils();
|
|
@@ -7,6 +7,25 @@ var schema = {
|
|
|
7
7
|
await _joe.Ai.spawnContextualChat(object_id);
|
|
8
8
|
}
|
|
9
9
|
},
|
|
10
|
+
listView:{
|
|
11
|
+
title: function(chat){
|
|
12
|
+
return `
|
|
13
|
+
|
|
14
|
+
<joe-subtext >${_joe.Utils.prettyPrintDTS(chat.created)}</joe-subtext>
|
|
15
|
+
<joe-title>${chat.name}</joe-title>
|
|
16
|
+
<joe-title>${_joe.SERVER.User.Render.cubes(chat.user,'fleft')}</joe-title>
|
|
17
|
+
<div>${(chat.context_objects||[]).map(function(ref){
|
|
18
|
+
var obj = $J.get(ref);
|
|
19
|
+
if(obj){
|
|
20
|
+
return `<joe-subtext>${obj.itemtype}:<b>${obj.name}</b> - ${obj._id}</joe-subtext>`;
|
|
21
|
+
}else{
|
|
22
|
+
return `<joe-subtext>${ref}</joe-subtext>`;
|
|
23
|
+
}
|
|
24
|
+
}).join('')}</div>
|
|
25
|
+
`;
|
|
26
|
+
},
|
|
27
|
+
listWindowTitle: 'Ai Conversations'
|
|
28
|
+
},
|
|
10
29
|
fields: function() {
|
|
11
30
|
return [
|
|
12
31
|
|
|
@@ -66,7 +85,10 @@ var schema = {
|
|
|
66
85
|
},
|
|
67
86
|
|
|
68
87
|
},
|
|
69
|
-
{name:'context_objects',type:"objectReference",
|
|
88
|
+
{name:'context_objects',type:"objectReference",
|
|
89
|
+
display:'Context Objects',
|
|
90
|
+
comment:'Objects included in this conversation for Ai context.',
|
|
91
|
+
locked:true,values:function(obj,prop){
|
|
70
92
|
return JOE.getData();
|
|
71
93
|
}},
|
|
72
94
|
{ sidebar_end: "right" },
|
|
@@ -18,7 +18,15 @@ var schema = {
|
|
|
18
18
|
// set the tool_properties.name to the passed current objects tool_id
|
|
19
19
|
let rawProps = _jco(true).tool_properties ||"{}";
|
|
20
20
|
var toolProperties = eval('('+rawProps+')')||{};
|
|
21
|
-
|
|
21
|
+
toolProperties.type = 'function';
|
|
22
|
+
toolProperties.function = Object.assign({
|
|
23
|
+
description:'',
|
|
24
|
+
parameters:{
|
|
25
|
+
type:'object',
|
|
26
|
+
properties:{}
|
|
27
|
+
}
|
|
28
|
+
},toolProperties.function,{name:propObj.name});
|
|
29
|
+
//toolProperties.function.name = propObj.name;
|
|
22
30
|
_joe.Fields.set('tool_properties',JSON.stringify(toolProperties,'\n','\t'));
|
|
23
31
|
|
|
24
32
|
}
|
|
@@ -35,22 +43,24 @@ var schema = {
|
|
|
35
43
|
type: "code",
|
|
36
44
|
display: "Tool Properties (OpenAI format)",
|
|
37
45
|
height: "300px",
|
|
46
|
+
language: "json",
|
|
38
47
|
comment:
|
|
39
48
|
`<div>Define the tool funcitonal properties as used in OpenAI tool calling.</div>
|
|
49
|
+
<a href="https://platform.openai.com/docs/guides/gpt/function-calling?api-mode=chat" target="_blank">OpenAI Function Calling</a><br/>
|
|
40
50
|
<pre>{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
51
|
+
"type": "function",
|
|
52
|
+
"function": {
|
|
53
|
+
"name": "findBusinessLinks",
|
|
54
|
+
"description": "Find relevant links for a business.",
|
|
44
55
|
"parameters": {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"strict": true
|
|
56
|
+
"type": "object",
|
|
57
|
+
"properties": {
|
|
58
|
+
"business_name": { "type": "string" },
|
|
59
|
+
"website": { "type": "string", "format": "uri" }
|
|
60
|
+
},
|
|
61
|
+
"required": ["business_name", "website"]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
}</pre>`
|
|
55
65
|
},
|
|
56
66
|
{ section_end: "schema" },
|