json-object-editor 0.10.654 → 0.10.660

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.
@@ -1,375 +1,415 @@
1
- var schema = {
2
- title : 'Ai Response | ${name}',
3
- display:'AI Response',
4
- info:"An ai response from openai etc",
5
- summary:{
6
- description:'Stored AI response objects generated from ai_prompt and the chatgpt plugin.',
7
- purpose:'Use ai_response to persist responses, token usage, and referenced objects from OpenAI calls for auditing, reuse, and comparison back to JOE objects.',
8
- labelField:'name',
9
- defaultSort:{ field:'created', dir:'desc' },
10
- searchableFields:['name','prompt_method','model_used','response_type','response_id','tags','_id'],
11
- allowedSorts:['created','joeUpdated','name','model_used'],
12
- relationships:{
13
- outbound:[
14
- { field:'ai_prompt', targetSchema:'ai_prompt', cardinality:'one' },
15
- { field:'referenced_objects', targetSchema:'<schemaName>', cardinality:'many' },
16
- { field:'tags', targetSchema:'tag', cardinality:'many' }
17
- ],
18
- inbound:{ graphRef:'server/relationships.graph.json' }
19
- },
20
- joeManagedFields:['created','joeUpdated'],
21
- fields:[
22
- { name:'_id', type:'string', required:true },
23
- { name:'itemtype', type:'string', required:true, const:'ai_response' },
24
- { name:'name', type:'string', required:true },
25
- { name:'ai_prompt', type:'string', isReference:true, targetSchema:'ai_prompt' },
26
- { name:'referenced_objects', type:'string', isArray:true },
27
- { name:'generated_thoughts', type:'string', isArray:true, isReference:true, targetSchema:'thought' },
28
- { name:'user_prompt', type:'string' },
29
- { name:'prompt_method', type:'string' },
30
- { name:'model_used', type:'string' },
31
- { name:'response_type', type:'string' },
32
- { name:'response', type:'string' },
33
- { name:'response_json', type:'object' },
34
- { name:'response_keys', type:'string', isArray:true },
35
- { name:'response_id', type:'string' },
36
- { name:'usage', type:'object' },
37
- { name:'used_openai_file_ids', type:'string', isArray:true },
38
- // { name:'creator_type', type:'string', enumValues:['user','ai_assistant'] },
39
- // { name:'creator_id', type:'string' },
40
- { name:'tags', type:'string', isArray:true, isReference:true, targetSchema:'tag' },
41
- { name:'joeUpdated', type:'string', format:'date-time' },
42
- { name:'created', type:'string', format:'date-time' }
43
- ]
44
- },
45
- menuicon:`
46
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="-150 -150 1324 1324">
47
- <path d="M314.57 597.81h376.06v28.2H314.57zm0 75.21h376.06v28.2H314.57zm0 75.21h376.06v28.2H314.57zm239.99-498.47 151.31 31.3c12.23 2.53 12.23 20 0 22.53l-151.31 31.3-31.3 151.31c-2.53 12.23-20 12.23-22.53 0l-31.3-151.31-151.31-31.3c-12.23-2.53-12.23-20 0-22.53l151.31-31.3 31.3-151.31c2.53-12.23 20-12.23 22.53 0l31.3 151.31ZM388.55 102.88l65.55 13.56c5.3 1.1 5.3 8.66 0 9.76l-65.55 13.56-13.56 65.55c-1.1 5.3-8.66 5.3-9.76 0l-13.56-65.55-65.55-13.56c-5.3-1.1-5.3-8.66 0-9.76l65.55-13.56 13.56-65.55c1.1-5.3 8.66-5.3 9.76 0l13.56 65.55Zm20.9 365.17 63.07 13.04c5.1 1.05 5.1 8.33 0 9.39l-63.07 13.04-13.04 63.07c-1.05 5.1-8.33 5.1-9.39 0l-13.04-63.07-63.07-13.04c-5.1-1.05-5.1-8.33 0-9.39l63.07-13.04 13.04-63.07c1.05-5.1 8.33-5.1 9.39 0l13.04 63.07Z"/>
48
- <path d="M689 155.94v37.61h67.44v676.9H267.56v-676.9H319v-37.61h-89.04v752.12h564.08V155.94H689z"/>
49
- </svg>`,
50
- listView:{
51
- title: function(air){
52
- var html = `<joe-subtext class="fright">${_joe.Utils.prettyPrintDTS(air.created)}</joe-subtext>
53
- <joe-title>${air.name}</joe-title>
54
- <joe-subtitle>${(air.response_keys?.length && `returns <b>${air.response_keys}</b>`) ||''}</joe-subtitle>
55
- ${air.prompt_method?`<joe-subtext class="fright">method ${air.prompt_method}</joe-subtext>`:''}
56
- <div>${(air.referenced_objects||[]).map(function(ref){
57
- var obj = $J.get(ref);
58
- if(obj){
59
- return `<joe-subtext>${obj.itemtype}:<b>${obj.name}</b> - ${obj._id}</joe-subtext>`;
60
- }else{
61
- return `<joe-subtext>${ref}</joe-subtext>`;
62
- }
63
- }).join('')}</div>`;
64
- air.tags?.length && (html+=_joe.schemas.tag.methods.renderTags(air.tags,'fright'));
65
- return html;
66
- },
67
- listWindowTitle: 'Ai Responses'
68
- },
69
- subsets: function(a,b,c){
70
- var subsets = [];
71
- // Base subsets: tag-based and by ai_prompt
72
- var subsets = subsets.concat(
73
- _joe.getDataset('ai_prompt').map(function(prompt){
74
- var color = prompt.status && $J.get(prompt.status,'status').color;
75
- return {
76
- name: prompt.name,
77
- id: prompt._id,
78
- filter: { ai_prompt: prompt._id },
79
- stripecolor: color || null // optional
80
- };
81
- })
82
- );
83
-
84
- // Additional subset: "Generated Thoughts" – detect from raw JSON
85
- // We look inside response_json for proposed_thoughts / thoughts arrays.
86
- try{
87
- var ds = _joe.getDataset('ai_response') || [];
88
- var ids = [];
89
- ds.map(function(r){
90
- var j = r && r.response_json;
91
- if (!j) { return; }
92
- if (Array.isArray(j.proposed_thoughts) && j.proposed_thoughts.length) {
93
- ids.push(r._id);
94
- return;
95
- }
96
- if (Array.isArray(j.thoughts) && j.thoughts.length) {
97
- ids.push(r._id);
98
- }
99
- });
100
- if (ids.length) {
101
- subsets.push({
102
- name: 'Generated Thoughts',
103
- id: 'generated_thoughts',
104
- filter: { _id: { $in: ids } },
105
- stripecolor: _joe.Colors.ai
106
- });
107
- }
108
- }catch(_e){ }
109
- //add status subsets
110
- subsets = subsets.concat(_joe.Filter.Options.status({group:'status'}));
111
- return subsets;
112
- },
113
- stripeColor:function(air){
114
- if(air.response_json && air.response_json.proposed_thoughts){
115
- return {color:_joe.Colors.ai,title:'Proposed '+air.response_json.proposed_thoughts.length+' Thoughts'};
116
- }
117
- return null;
118
- },
119
- bgColor:function(air){//status color
120
- if(air.status){
121
- var status = _joe.getDataItem(air.status,'status');
122
- if(status && status.color){
123
- return {color:status.color,title:status.name};
124
- }
125
- }
126
- return null;
127
- },
128
- filters:function(){
129
- var filters = [];
130
- // Tag filters
131
- filters = filters.concat(
132
- _joe.Filter.Options.tags({group:'tags',untagged:true,collapsed:true})
133
- );
134
- // creator_type simple value grouping
135
- // try{
136
- // filters = filters.concat(
137
- // _joe.Filter.Options.getDatasetPropertyValues('ai_response','creator_type',{ group: 'creator_type', collapsed: true })
138
- // );
139
- // }catch(_e){}
140
- // // creator_id filters with human-readable names
141
- // try{
142
- // var dataset = _joe.getDataset('ai_response') || [];
143
- // var byCreator = {};
144
- // dataset.map(function(r){
145
- // if(!r.creator_id){ return; }
146
- // var key = r.creator_type+':'+r.creator_id;
147
- // if(!byCreator[key]){
148
- // var name = r.creator_id;
149
- // if(r.creator_type === 'user' && _joe.Data.user){
150
- // var u = _joe.Data.user.find(function(x){ return x && x._id === r.creator_id; });
151
- // if(u){ name = u.fullname || u.name || u.email || u._id; }
152
- // }else if(r.creator_type === 'ai_assistant' && _joe.Data.ai_assistant){
153
- // var a = _joe.Data.ai_assistant.find(function(x){ return x && x._id === r.creator_id; });
154
- // if(a){ name = a.name || a._id; }
155
- // }
156
- // byCreator[key] = {
157
- // name: name,
158
- // filter: { creator_type: r.creator_type, creator_id: r.creator_id }
159
- // };
160
- // }
161
- // });
162
- // var keys = Object.keys(byCreator);
163
- // if(keys.length){
164
- // filters.push({ group_start:'creator', collapsed:true });
165
- // keys.sort(function(a,b){
166
- // var na = byCreator[a].name.toLowerCase();
167
- // var nb = byCreator[b].name.toLowerCase();
168
- // if(na>nb) return 1;
169
- // if(na<nb) return -1;
170
- // return 0;
171
- // }).map(function(k){
172
- // filters.push({
173
- // name: byCreator[k].name,
174
- // filter: byCreator[k].filter
175
- // });
176
- // });
177
- // filters.push({ group_end:'creator' });
178
- // }
179
- // }catch(_e){}
180
- return filters;
181
- },
182
- itemExpander:function(air){
183
- if(air.response_json && air.response_json.proposed_thoughts){
184
- //list proposed thoughts in html with header "Proposed Thoughts"
185
- var html = `<joe-card><joe-title>Proposed Thoughts</joe-title>`;
186
- air.response_json.proposed_thoughts.map(thought=>{
187
- html += `<joe-subtitle>${thought.statement}</joe-subtitle><br/>`;
188
- });
189
- html += `</joe-card>`;
190
- return html;
191
- }
192
- return _joe.schemas.thought.methods.listThoughts(air);
193
- },
194
- sorter:['!created','name'],
195
- checkChanges:false,
196
- fields:[
197
- 'name',
198
- //'info',
199
- {name:'ai_prompt',type:'objectReference', values:'ai_prompt',display:'Ai Prompt',width:'50%',locked:true},
200
- {name:'referenced_objects',type:'objectReference',width:'50%',values:function(air,orList){
201
- return $J.search({_id:{$in:air.referenced_objects}});
202
- },display:'Referenced Objects',locked:true},
203
- {name:'generated_thoughts',type:'objectReference',width:'50%',values:function(air,orList){
204
- return $J.search({source_ai_response:air._id,itemtype:'thought'});
205
- },display:'Generated Thoughts',locked:true},
206
- {name:'user_prompt', display:'Sent User Prompt',comment:"The input sent by the user after all processing",locked:true,type:'rendering'},
207
-
208
- {name:'prompt_method',locked:true,width:'50%'},
209
- {name:'model_used',locked:true,width:'50%'},
210
-
211
- // {name:'ai_prompt',type:'select', values:'ai_prompt',display:'Ai Prompt',width:'50%',template:function(resp,prompt){
212
- // return prompt.name;
213
- // }},
214
- //{name:'business',type:'objectReference', values:'business',display:'Business',locked:true},
215
-
216
-
217
- //{name:'params', type:'rendering', locked:true},
218
- {section_start:'response',collapsed:false},
219
- {
220
- name:'response_type',
221
- type:'text',
222
- display:'Response Type',
223
- placeholder:'(a code for the responsetype)',
224
- locked:true,
225
- width:'50%',
226
- condition:function(v){
227
- return v.length > 0;
228
- }
229
- },
230
- {name:'response', type:'rendering', locked:true, height:'500px'},
231
- {
232
- name:'response_json',
233
- type:'content',
234
- display:'Parsed Response (JSON)',
235
- locked:true,
236
- run:function(air){
237
- if (!air || !air.response_json) { return ''; }
238
- try{
239
- var json = JSON.stringify(air.response_json, null, 2);
240
- // basic HTML escape for safety
241
- json = String(json)
242
- .replace(/&/g,'&amp;')
243
- .replace(/</g,'&lt;')
244
- .replace(/>/g,'&gt;');
245
- return '<pre style="white-space:pre-wrap; max-height:260px; overflow:auto;">'+json+'</pre>';
246
- }catch(e){
247
- return '<pre>'+String(air.response_json)+'</pre>';
248
- }
249
- }
250
- },
251
- {name:'response_keys', type:'text', locked:true,display:'Response Keys'},
252
- {name:'response_id', type:'text', display:'openAI response ID',locked:true},
253
- {name:'used_openai_file_ids', type:'content', display:'OpenAI File IDs (Used)', locked:true, run:function(air){
254
- var ids = air && air.used_openai_file_ids;
255
- if(!ids || !ids.length){ return '<joe-subtext>None</joe-subtext>'; }
256
- return ids.map(function(id){ return '<joe-subtext>'+id+'</joe-subtext>'; }).join('');
257
- }},
258
-
259
- {section_end:'response'},
260
- {sidebar_start:'right', collapsed:false},
261
- // {section_start:'creator'},
262
- // 'creator_type','creator_id',
263
- // {section_end:'creator'},
264
- {section_start:'workflow'},
265
- 'status',
266
- 'tags',
267
- {section_end:'workflow'},
268
- {section_start:'tokens'},
269
- {name:'usage',type:'content',display:'Tokens Used',run:function(air){
270
- if(!air.usage || !air.usage.total_tokens){return '';}
271
- var html=`<joe-text>input <b>${air.usage.input_tokens}</b></joe-text>
272
- <joe-text>output <b>${air.usage.output_tokens}</b></joe-text>
273
- <joe-text>total <b>${air.usage.total_tokens}</b></joe-text>`
274
- return html;
275
- }},
276
- {section_end:'tokens'},
277
- {sidebar_end:'right'},
278
- //{name:'payload', type:'code', locked:true,height:'500px'},
279
- //{name:'raw_response', type:'code', language:'json',locked:true},
280
- {section_start:'system',collapsed:true},
281
- '_id','created','itemtype',
282
- {section_end:'system'},
283
-
284
- ],
285
- methods:{
286
- compareResponseToObject:function(response_id,object_id,do_alert){
287
- let response =$J.get(response_id);
288
- var object;
289
- let object_keys;
290
- if(_joe.current.object._id == object_id){
291
- object = _jco(true);
292
- object_keys = _joe.current.fields.map(f=>f.name)
293
- }
294
- else{
295
- object = $J.get(object_id);
296
- object_keys = Object.keys(object);
297
- }
298
- if(!response || !object){return false;}
299
- //evaluate the response to see if it is valid json
300
- try{
301
- let parsed = JSON.parse(response.response);
302
- //find the keys in the response that are in the object
303
- let keys = Object.keys(parsed);
304
-
305
- let match = keys.filter(k=>object_keys.includes(k));
306
- //if there are no keys that match, return false
307
- if(!match.length){
308
- do_alert && alert('Response does not match any object keys: '+keys.join(','));
309
- return false;}
310
- //return an array of matching keys
311
- do_alert && ( doit = confirm('Replace object values with responses for: '+match.join(',')));
312
- if(doit){
313
- _joe.schemas.ai_response.methods.updateObjectFromResponse(response_id,object_id,match);
314
- }
315
- return match;
316
- }catch(e){
317
- //if the response is not valid json, return false
318
- return false;
319
- }
320
- },
321
- updateObjectFromResponse:function(response_id,object_id,fields){
322
- let response =$J.get(response_id);
323
- let parsed = JSON.parse(response.response);
324
- var object;
325
- if(_joe.current.object._id == object_id){
326
- object = _jco(true);
327
- }
328
- else{
329
- object = $J.get(object_id);
330
- }
331
- //handle tags
332
- if(response.tags && response.tags.length){
333
- let newTags = (object.tags||[]).concat(response.tags);
334
- //tags array to a set, then back to array
335
- newTags = [...new Set(newTags)];
336
- _joe.Fields.set('tags',newTags);
337
-
338
- }
339
- fields.map(function(field){
340
- let newVal = object[field];
341
- //update the object with the response
342
- if(object[field].isArray()){
343
- //append the response to the array
344
- newVal = object[field].concat(parsed[field]);
345
- }else if(typeof object[field] == 'string'){
346
- //merge the response with the object
347
- newVal = parsed[field]
348
- }
349
- _joe.Fields.set(field,newVal);
350
- });
351
- },
352
- listResponses:function(obj){
353
- let html ='';
354
- let responses = _joe.getDataset('ai_response').filter(r=>{
355
- return r.referenced_objects && r.referenced_objects.includes(obj._id);
356
- })
357
- $c.sortBy(responses,'!created');
358
-
359
- responses.map(resp=>{
360
- let temp = `<joe-subtitle>${resp.name}</joe-subtitle>
361
- <joe-subtext>${_joe.Utils.prettyPrintDTS(resp.created)}</joe-subtext>
362
- ${resp.tags && _joe.schemas.tag.methods.renderTags(resp.tags) || ''}`;
363
-
364
- resp.response_keys && (temp += `<joe-button-wrapper class="bottom"><joe-button class="joe-button joe-blue-button" onclick="_joe.Utils.stopPropagation(event);_joe.schemas.ai_response.methods.compareResponseToObject('${resp._id}','${obj._id}',true);">< merge ${resp.response_keys.length} fields</joe-button><joe-button-wrapper>`);
365
- html+=_joe.renderFieldListItem(resp,temp,'ai_response',{icon:'ai_assistant',link:function(item){
366
- return location.href.replace(location.hash,`#/${item.itemtype}/${item._id}`);
367
- }});
368
- })
369
- return html;
370
- }
371
- },
372
- idprop : "_id"
373
- };
374
-
1
+ var schema = {
2
+ title : 'Ai Response | ${name}',
3
+ display:'AI Response',
4
+ info:"An ai response from openai etc",
5
+ summary:{
6
+ description:'Stored AI response objects generated from ai_prompt and the chatgpt plugin.',
7
+ purpose:'Use ai_response to persist responses, token usage, and referenced objects from OpenAI calls for auditing, reuse, and comparison back to JOE objects.',
8
+ labelField:'name',
9
+ defaultSort:{ field:'created', dir:'desc' },
10
+ searchableFields:['name','prompt_method','model_used','response_type','response_id','tags','_id'],
11
+ allowedSorts:['created','joeUpdated','name','model_used'],
12
+ relationships:{
13
+ outbound:[
14
+ { field:'ai_prompt', targetSchema:'ai_prompt', cardinality:'one' },
15
+ { field:'referenced_objects', targetSchema:'<schemaName>', cardinality:'many' },
16
+ { field:'tags', targetSchema:'tag', cardinality:'many' }
17
+ ],
18
+ inbound:{ graphRef:'server/relationships.graph.json' }
19
+ },
20
+ joeManagedFields:['created','joeUpdated'],
21
+ fields:[
22
+ { name:'_id', type:'string', required:true },
23
+ { name:'itemtype', type:'string', required:true, const:'ai_response' },
24
+ { name:'name', type:'string', required:true },
25
+ { name:'ai_prompt', type:'string', isReference:true, targetSchema:'ai_prompt' },
26
+ { name:'referenced_objects', type:'string', isArray:true },
27
+ { name:'generated_thoughts', type:'string', isArray:true, isReference:true, targetSchema:'thought' },
28
+ { name:'user_prompt', type:'string' },
29
+ { name:'prompt_method', type:'string' },
30
+ { name:'model_used', type:'string' },
31
+ { name:'response_type', type:'string' },
32
+ { name:'response', type:'string' },
33
+ { name:'response_json', type:'object' },
34
+ { name:'response_keys', type:'string', isArray:true },
35
+ { name:'response_id', type:'string' },
36
+ { name:'usage', type:'object' },
37
+ { name:'used_openai_file_ids', type:'string', isArray:true },
38
+ // MCP configuration and audit fields
39
+ { name:'mcp_enabled', type:'boolean' },
40
+ { name:'mcp_toolset', type:'string' },
41
+ { name:'mcp_selected_tools', type:'string', isArray:true },
42
+ { name:'mcp_instructions_mode', type:'string' },
43
+ { name:'mcp_tools_used', type:'object', isArray:true },
44
+ // { name:'creator_type', type:'string', enumValues:['user','ai_assistant'] },
45
+ // { name:'creator_id', type:'string' },
46
+ { name:'tags', type:'string', isArray:true, isReference:true, targetSchema:'tag' },
47
+ { name:'joeUpdated', type:'string', format:'date-time' },
48
+ { name:'created', type:'string', format:'date-time' }
49
+ ]
50
+ },
51
+ menuicon:`
52
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-150 -150 1324 1324">
53
+ <path d="M314.57 597.81h376.06v28.2H314.57zm0 75.21h376.06v28.2H314.57zm0 75.21h376.06v28.2H314.57zm239.99-498.47 151.31 31.3c12.23 2.53 12.23 20 0 22.53l-151.31 31.3-31.3 151.31c-2.53 12.23-20 12.23-22.53 0l-31.3-151.31-151.31-31.3c-12.23-2.53-12.23-20 0-22.53l151.31-31.3 31.3-151.31c2.53-12.23 20-12.23 22.53 0l31.3 151.31ZM388.55 102.88l65.55 13.56c5.3 1.1 5.3 8.66 0 9.76l-65.55 13.56-13.56 65.55c-1.1 5.3-8.66 5.3-9.76 0l-13.56-65.55-65.55-13.56c-5.3-1.1-5.3-8.66 0-9.76l65.55-13.56 13.56-65.55c1.1-5.3 8.66-5.3 9.76 0l13.56 65.55Zm20.9 365.17 63.07 13.04c5.1 1.05 5.1 8.33 0 9.39l-63.07 13.04-13.04 63.07c-1.05 5.1-8.33 5.1-9.39 0l-13.04-63.07-63.07-13.04c-5.1-1.05-5.1-8.33 0-9.39l63.07-13.04 13.04-63.07c1.05-5.1 8.33-5.1 9.39 0l13.04 63.07Z"/>
54
+ <path d="M689 155.94v37.61h67.44v676.9H267.56v-676.9H319v-37.61h-89.04v752.12h564.08V155.94H689z"/>
55
+ </svg>`,
56
+ listView:{
57
+ title: function(air){
58
+ var html = `<joe-subtext class="fright">${_joe.Utils.prettyPrintDTS(air.created)}</joe-subtext>
59
+ <joe-title>${air.name}</joe-title>
60
+ <joe-subtitle>${(air.response_keys?.length && `returns <b>${air.response_keys}</b>`) ||''}</joe-subtitle>
61
+ ${air.prompt_method?`<joe-subtext class="fright">method ${air.prompt_method}</joe-subtext>`:''}
62
+ <div>${(air.referenced_objects||[]).map(function(ref){
63
+ var obj = $J.get(ref);
64
+ if(obj){
65
+ return `<joe-subtext>${obj.itemtype}:<b>${obj.name}</b> - ${obj._id}</joe-subtext>`;
66
+ }else{
67
+ return `<joe-subtext>${ref}</joe-subtext>`;
68
+ }
69
+ }).join('')}</div>`;
70
+ air.tags?.length && (html+=_joe.schemas.tag.methods.renderTags(air.tags,'fright'));
71
+ return html;
72
+ },
73
+ listWindowTitle: 'Ai Responses'
74
+ },
75
+ subsets: function(a,b,c){
76
+ var subsets = [];
77
+ // Base subsets: tag-based and by ai_prompt
78
+ var subsets = subsets.concat(
79
+ _joe.getDataset('ai_prompt').map(function(prompt){
80
+ var color = prompt.status && $J.get(prompt.status,'status').color;
81
+ return {
82
+ name: prompt.name,
83
+ id: prompt._id,
84
+ filter: { ai_prompt: prompt._id },
85
+ stripecolor: color || null // optional
86
+ };
87
+ })
88
+ );
89
+
90
+ // Additional subset: "Generated Thoughts" – detect from raw JSON
91
+ // We look inside response_json for proposed_thoughts / thoughts arrays.
92
+ try{
93
+ var ds = _joe.getDataset('ai_response') || [];
94
+ var ids = [];
95
+ ds.map(function(r){
96
+ var j = r && r.response_json;
97
+ if (!j) { return; }
98
+ if (Array.isArray(j.proposed_thoughts) && j.proposed_thoughts.length) {
99
+ ids.push(r._id);
100
+ return;
101
+ }
102
+ if (Array.isArray(j.thoughts) && j.thoughts.length) {
103
+ ids.push(r._id);
104
+ }
105
+ });
106
+ if (ids.length) {
107
+ subsets.push({
108
+ name: 'Generated Thoughts',
109
+ id: 'generated_thoughts',
110
+ filter: { _id: { $in: ids } },
111
+ stripecolor: _joe.Colors.ai
112
+ });
113
+ }
114
+ }catch(_e){ }
115
+ //add status subsets
116
+ subsets = subsets.concat(_joe.Filter.Options.status({group:'status'}));
117
+ return subsets;
118
+ },
119
+ stripeColor:function(air){
120
+ if(air.response_json && air.response_json.proposed_thoughts){
121
+ return {color:_joe.Colors.ai,title:'Proposed '+air.response_json.proposed_thoughts.length+' Thoughts'};
122
+ }
123
+ return null;
124
+ },
125
+ bgColor:function(air){//status color
126
+ if(air.status){
127
+ var status = _joe.getDataItem(air.status,'status');
128
+ if(status && status.color){
129
+ return {color:status.color,title:status.name};
130
+ }
131
+ }
132
+ return null;
133
+ },
134
+ filters:function(){
135
+ var filters = [];
136
+ // Tag filters
137
+ filters = filters.concat(
138
+ _joe.Filter.Options.tags({group:'tags',untagged:true,collapsed:true})
139
+ );
140
+ // creator_type simple value grouping
141
+ // try{
142
+ // filters = filters.concat(
143
+ // _joe.Filter.Options.getDatasetPropertyValues('ai_response','creator_type',{ group: 'creator_type', collapsed: true })
144
+ // );
145
+ // }catch(_e){}
146
+ // // creator_id filters with human-readable names
147
+ // try{
148
+ // var dataset = _joe.getDataset('ai_response') || [];
149
+ // var byCreator = {};
150
+ // dataset.map(function(r){
151
+ // if(!r.creator_id){ return; }
152
+ // var key = r.creator_type+':'+r.creator_id;
153
+ // if(!byCreator[key]){
154
+ // var name = r.creator_id;
155
+ // if(r.creator_type === 'user' && _joe.Data.user){
156
+ // var u = _joe.Data.user.find(function(x){ return x && x._id === r.creator_id; });
157
+ // if(u){ name = u.fullname || u.name || u.email || u._id; }
158
+ // }else if(r.creator_type === 'ai_assistant' && _joe.Data.ai_assistant){
159
+ // var a = _joe.Data.ai_assistant.find(function(x){ return x && x._id === r.creator_id; });
160
+ // if(a){ name = a.name || a._id; }
161
+ // }
162
+ // byCreator[key] = {
163
+ // name: name,
164
+ // filter: { creator_type: r.creator_type, creator_id: r.creator_id }
165
+ // };
166
+ // }
167
+ // });
168
+ // var keys = Object.keys(byCreator);
169
+ // if(keys.length){
170
+ // filters.push({ group_start:'creator', collapsed:true });
171
+ // keys.sort(function(a,b){
172
+ // var na = byCreator[a].name.toLowerCase();
173
+ // var nb = byCreator[b].name.toLowerCase();
174
+ // if(na>nb) return 1;
175
+ // if(na<nb) return -1;
176
+ // return 0;
177
+ // }).map(function(k){
178
+ // filters.push({
179
+ // name: byCreator[k].name,
180
+ // filter: byCreator[k].filter
181
+ // });
182
+ // });
183
+ // filters.push({ group_end:'creator' });
184
+ // }
185
+ // }catch(_e){}
186
+ return filters;
187
+ },
188
+ itemExpander:function(air){
189
+ if(air.response_json && air.response_json.proposed_thoughts){
190
+ //list proposed thoughts in html with header "Proposed Thoughts"
191
+ var html = `<joe-card><joe-title>Proposed Thoughts</joe-title>`;
192
+ air.response_json.proposed_thoughts.map(thought=>{
193
+ html += `<joe-subtitle>${thought.statement}</joe-subtitle><br/>`;
194
+ });
195
+ html += `</joe-card>`;
196
+ return html;
197
+ }
198
+ return _joe.schemas.thought.methods.listThoughts(air);
199
+ },
200
+ sorter:['!created','name'],
201
+ checkChanges:false,
202
+ fields:[
203
+ 'name',
204
+ //'info',
205
+ {name:'ai_prompt',type:'objectReference', values:'ai_prompt',display:'Ai Prompt',width:'50%',locked:true},
206
+ {name:'referenced_objects',type:'objectReference',width:'50%',values:function(air,orList){
207
+ return $J.search({_id:{$in:air.referenced_objects}});
208
+ },display:'Referenced Objects',locked:true},
209
+ {name:'generated_thoughts',type:'objectReference',width:'50%',values:function(air,orList){
210
+ return $J.search({source_ai_response:air._id,itemtype:'thought'});
211
+ },display:'Generated Thoughts',locked:true},
212
+ {name:'user_prompt', display:'Sent User Prompt',comment:"The input sent by the user after all processing",locked:true,type:'rendering'},
213
+
214
+ {name:'prompt_method',locked:true,width:'50%'},
215
+ {name:'model_used',locked:true,width:'50%'},
216
+
217
+ // {name:'ai_prompt',type:'select', values:'ai_prompt',display:'Ai Prompt',width:'50%',template:function(resp,prompt){
218
+ // return prompt.name;
219
+ // }},
220
+ //{name:'business',type:'objectReference', values:'business',display:'Business',locked:true},
221
+
222
+
223
+ //{name:'params', type:'rendering', locked:true},
224
+ {section_start:'response',collapsed:false},
225
+ {
226
+ name:'response_type',
227
+ type:'text',
228
+ display:'Response Type',
229
+ placeholder:'(a code for the responsetype)',
230
+ locked:true,
231
+ width:'50%',
232
+ condition:function(v){
233
+ return v.length > 0;
234
+ }
235
+ },
236
+ {name:'response', type:'rendering', locked:true, height:'500px'},
237
+ {
238
+ name:'response_json',
239
+ type:'content',
240
+ display:'Parsed Response (JSON)',
241
+ locked:true,
242
+ run:function(air){
243
+ if (!air || !air.response_json) { return ''; }
244
+ try{
245
+ var json = JSON.stringify(air.response_json, null, 2);
246
+ // basic HTML escape for safety
247
+ json = String(json)
248
+ .replace(/&/g,'&amp;')
249
+ .replace(/</g,'&lt;')
250
+ .replace(/>/g,'&gt;');
251
+ return '<pre style="white-space:pre-wrap; max-height:260px; overflow:auto;">'+json+'</pre>';
252
+ }catch(e){
253
+ return '<pre>'+String(air.response_json)+'</pre>';
254
+ }
255
+ }
256
+ },
257
+ {name:'response_keys', type:'text', locked:true,display:'Response Keys'},
258
+ {name:'response_id', type:'text', display:'openAI response ID',locked:true},
259
+ // {name:'used_openai_file_ids', type:'content', display:'OpenAI File IDs (Used)', locked:true, run:function(air){
260
+ // var ids = air && air.used_openai_file_ids;
261
+ // if(!ids || !ids.length){ return '<joe-subtext>None</joe-subtext>'; }
262
+ // return ids.map(function(id){ return '<joe-subtext>'+id+'</joe-subtext>'; }).join('');
263
+ // }},
264
+ {name:'used_openai_file_ids', type:'text', display:'OpenAI File IDs (Used)', locked:true},
265
+
266
+ {section_end:'response'},
267
+ {sidebar_start:'right', collapsed:false},
268
+ // {section_start:'creator'},
269
+ // 'creator_type','creator_id',
270
+ // {section_end:'creator'},
271
+ {section_start:'workflow'},
272
+ 'status',
273
+ 'tags',
274
+ {section_end:'workflow'},
275
+ {section_start:'tokens'},
276
+ {name:'usage',type:'content',display:'Tokens Used',run:function(air){
277
+ if(!air.usage || !air.usage.total_tokens){return '';}
278
+ var html=`<joe-text>input <b>${air.usage.input_tokens}</b></joe-text>
279
+ <joe-text>output <b>${air.usage.output_tokens}</b></joe-text>
280
+ <joe-text>total <b>${air.usage.total_tokens}</b></joe-text>`
281
+ return html;
282
+ }},
283
+ {section_end:'tokens'},
284
+ {section_start:'mcp',collapsed:true},
285
+ {name:'mcp_enabled',type:'boolean',display:'MCP Enabled',locked:true},
286
+ {name:'mcp_toolset',type:'text',display:'MCP Toolset',locked:true},
287
+ {name:'mcp_instructions_mode',type:'text',display:'MCP Instructions Mode',locked:true},
288
+ {
289
+ name:'mcp_selected_tools',
290
+ type:'content',
291
+ display:'MCP Tools (configured)',
292
+ run:function(air){
293
+ var arr = air && air.mcp_selected_tools;
294
+ if(!arr || !arr.length){ return '<joe-subtext>None</joe-subtext>'; }
295
+ return arr.map(function(n){ return '<joe-tag>'+n+'</joe-tag>'; }).join(' ');
296
+ }
297
+ },
298
+ {
299
+ name:'mcp_tools_used',
300
+ type:'content',
301
+ display:'MCP Tools Used',
302
+ run:function(air){
303
+ var calls = air && air.mcp_tools_used;
304
+ if(!calls || !calls.length){ return '<joe-subtext>None</joe-subtext>'; }
305
+ return calls.map(function(c){
306
+ try{
307
+ var name = c && c.name ? c.name : '(unknown)';
308
+ var args = c && c.arguments ? JSON.stringify(c.arguments) : '';
309
+ return '<div><strong>'+name+'</strong>' + (args ? (': <code>'+String(args).replace(/</g,"&lt;").replace(/>/g,"&gt;")+'</code>') : '') + '</div>';
310
+ }catch(e){
311
+ return '<div>'+JSON.stringify(c)+'</div>';
312
+ }
313
+ }).join('');
314
+ }
315
+ },
316
+ {section_end:'mcp'},
317
+ {sidebar_end:'right'},
318
+ //{name:'payload', type:'code', locked:true,height:'500px'},
319
+ //{name:'raw_response', type:'code', language:'json',locked:true},
320
+ {section_start:'system',collapsed:true},
321
+ '_id','created','itemtype',
322
+ {section_end:'system'},
323
+
324
+ ],
325
+ methods:{
326
+ compareResponseToObject:function(response_id,object_id,do_alert){
327
+ let response =$J.get(response_id);
328
+ var object;
329
+ let object_keys;
330
+ if(_joe.current.object._id == object_id){
331
+ object = _jco(true);
332
+ object_keys = _joe.current.fields.map(f=>f.name)
333
+ }
334
+ else{
335
+ object = $J.get(object_id);
336
+ object_keys = Object.keys(object);
337
+ }
338
+ if(!response || !object){return false;}
339
+ //evaluate the response to see if it is valid json
340
+ try{
341
+ let parsed = JSON.parse(response.response);
342
+ //find the keys in the response that are in the object
343
+ let keys = Object.keys(parsed);
344
+
345
+ let match = keys.filter(k=>object_keys.includes(k));
346
+ //if there are no keys that match, return false
347
+ if(!match.length){
348
+ do_alert && alert('Response does not match any object keys: '+keys.join(','));
349
+ return false;}
350
+ //return an array of matching keys
351
+ do_alert && ( doit = confirm('Replace object values with responses for: '+match.join(',')));
352
+ if(doit){
353
+ _joe.schemas.ai_response.methods.updateObjectFromResponse(response_id,object_id,match);
354
+ }
355
+ return match;
356
+ }catch(e){
357
+ //if the response is not valid json, return false
358
+ return false;
359
+ }
360
+ },
361
+ updateObjectFromResponse:function(response_id,object_id,fields){
362
+ let response =$J.get(response_id);
363
+ let parsed = JSON.parse(response.response);
364
+ var object;
365
+ if(_joe.current.object._id == object_id){
366
+ object = _jco(true);
367
+ }
368
+ else{
369
+ object = $J.get(object_id);
370
+ }
371
+ //handle tags
372
+ if(response.tags && response.tags.length){
373
+ let newTags = (object.tags||[]).concat(response.tags);
374
+ //tags array to a set, then back to array
375
+ newTags = [...new Set(newTags)];
376
+ _joe.Fields.set('tags',newTags);
377
+
378
+ }
379
+ fields.map(function(field){
380
+ let newVal = object[field];
381
+ //update the object with the response
382
+ if(object[field].isArray()){
383
+ //append the response to the array
384
+ newVal = object[field].concat(parsed[field]);
385
+ }else if(typeof object[field] == 'string'){
386
+ //merge the response with the object
387
+ newVal = parsed[field]
388
+ }
389
+ _joe.Fields.set(field,newVal);
390
+ });
391
+ },
392
+ listResponses:function(obj){
393
+ let html ='';
394
+ let responses = _joe.getDataset('ai_response').filter(r=>{
395
+ return r.referenced_objects && r.referenced_objects.includes(obj._id);
396
+ })
397
+ $c.sortBy(responses,'!created');
398
+
399
+ responses.map(resp=>{
400
+ let temp = `<joe-subtitle>${resp.name}</joe-subtitle>
401
+ <joe-subtext>${_joe.Utils.prettyPrintDTS(resp.created)}</joe-subtext>
402
+ ${resp.tags && _joe.schemas.tag.methods.renderTags(resp.tags) || ''}`;
403
+
404
+ resp.response_keys && (temp += `<joe-button-wrapper class="bottom"><joe-button class="joe-button joe-blue-button" onclick="_joe.Utils.stopPropagation(event);_joe.schemas.ai_response.methods.compareResponseToObject('${resp._id}','${obj._id}',true);">< merge ${resp.response_keys.length} fields</joe-button><joe-button-wrapper>`);
405
+ html+=_joe.renderFieldListItem(resp,temp,'ai_response',{icon:'ai_assistant',link:function(item){
406
+ return location.href.replace(location.hash,`#/${item.itemtype}/${item._id}`);
407
+ }});
408
+ })
409
+ return html;
410
+ }
411
+ },
412
+ idprop : "_id"
413
+ };
414
+
375
415
  module.exports = schema;