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 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
- border:1px solid blue
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: 18px;
63
+ font-size: 14px;
62
64
  display:block;
63
65
  font-weight: bold;
64
66
  }
@@ -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
- +((specs.link && '<a class="non-link" onclick="__cancelClick();" href="'+specs.link+'">')||'')
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
- +((specs.link && '</a>')||'')
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 true;
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
- +((specs.link && '<a class="non-link" onclick="__cancelClick();" href="'+specs.link+'">')||'')
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
- +((specs.link && '</a>')||'')
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 true;
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
- +((specs.link && '<a class="non-link" onclick="__cancelClick();" href="'+specs.link+'">')||'')
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
- +((specs.link && '</a>')||'')
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 true;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-object-editor",
3
- "version": "0.10.430",
3
+ "version": "0.10.432",
4
4
  "description": "JOE the Json Object Editor | Platform Edition",
5
5
  "main": "app.js",
6
6
  "scripts": {
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;
@@ -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',
@@ -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:'auto',
73
+ height:'500px',
74
74
  display:'Instructions',
75
+ default:'code',
75
76
  type:function(item){
76
- if(!item.instructions_format){
77
- return 'wysiwyg';
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
- { value: "gpt-4o", name: "GPT-4o (Current Default)" }
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);
@@ -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
- try{
503
- var pluginpath = internalPluginsDir + p;
504
- fs.accessSync(pluginpath, fs.F_OK);
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
- }catch(e){
507
- var pluginpath = pluginsDir + p;
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", locked:true,values:function(obj,prop){
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
- toolProperties.name = propObj.name;
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
- "type": "function",
42
- "name": "get_weather",
43
- "description": "Get current temperature for provided coordinates in celsius.",
51
+ "type": "function",
52
+ "function": {
53
+ "name": "findBusinessLinks",
54
+ "description": "Find relevant links for a business.",
44
55
  "parameters": {
45
- "type": "object",
46
- "properties": {
47
- "latitude": { "type": "number" },
48
- "longitude": { "type": "number" }
49
- },
50
- "required": ["latitude", "longitude"],
51
- "additionalProperties": false
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" },