json-object-editor 0.10.642 → 0.10.653

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.
@@ -432,7 +432,40 @@ function shrinkUnderstandObjectMessagesForTokens(messages) {
432
432
  model: 'gpt-4o',
433
433
  });
434
434
  coloredLog(chatCompletion);
435
- return {payload,chatCompletion,content:chatCompletion.choices[0].message.content};
435
+ const text = chatCompletion.choices && chatCompletion.choices[0] && chatCompletion.choices[0].message && chatCompletion.choices[0].message.content || '';
436
+ // Optionally persist as ai_response with parsed JSON when applicable
437
+ const parsed = (function(){
438
+ try {
439
+ const jt = extractJsonText(text);
440
+ return jt ? JSON.parse(jt) : null;
441
+ } catch(_e){ return null; }
442
+ })();
443
+ try {
444
+ var creator_type = null;
445
+ var creator_id = null;
446
+ try{
447
+ var u = req && req.User;
448
+ if (u && u._id){
449
+ creator_type = 'user';
450
+ creator_id = u._id;
451
+ }
452
+ }catch(_e){}
453
+ const aiResponse = {
454
+ itemtype: 'ai_response',
455
+ name: 'Test Prompt → ChatGPT',
456
+ response_type: 'testPrompt',
457
+ response: text,
458
+ response_json: parsed,
459
+ response_id: chatCompletion.id || '',
460
+ user_prompt: payload && payload.data && payload.data.prompt || 'Tell me a story about JOE: the json object editor in under 256 chars.',
461
+ model_used: 'gpt-4o',
462
+ created: (new Date()).toISOString(),
463
+ creator_type: creator_type,
464
+ creator_id: creator_id
465
+ };
466
+ JOE.Storage.save(aiResponse, 'ai_response', function(){}, { history: false, user: (req && req.User) || { name:'system' } });
467
+ } catch(_e){ /* best-effort only */ }
468
+ return {payload,chatCompletion,content:text};
436
469
  } catch (error) {
437
470
  if (error.status === 429) {
438
471
  return { errors: 'You exceeded your current quota, please check your plan and billing details.' };
@@ -498,7 +531,7 @@ function shrinkUnderstandObjectMessagesForTokens(messages) {
498
531
  response: chatContent,
499
532
  payload,
500
533
  prompt_method:req.params.method
501
- });
534
+ }, req && req.User);
502
535
  coloredLog("response saved -"+responseName);
503
536
  return {payload,
504
537
  businessOBJ,
@@ -549,8 +582,16 @@ function shrinkUnderstandObjectMessagesForTokens(messages) {
549
582
  return match ? match[1] : null;
550
583
  }
551
584
 
552
- async function saveAIResponse(data) {
585
+ async function saveAIResponse(data, user) {
553
586
  try {
587
+ var creator_type = null;
588
+ var creator_id = null;
589
+ try{
590
+ if (user && user._id){
591
+ creator_type = 'user';
592
+ creator_id = user._id;
593
+ }
594
+ }catch(_e){}
554
595
  const aiResponse = {
555
596
  name: data.name,
556
597
  itemtype: 'ai_response',
@@ -560,7 +601,9 @@ function shrinkUnderstandObjectMessagesForTokens(messages) {
560
601
  payload: data.payload,
561
602
  prompt_method:data.prompt_method,
562
603
  created: (new Date).toISOString(),
563
- _id:cuid()
604
+ _id:cuid(),
605
+ creator_type: creator_type,
606
+ creator_id: creator_id
564
607
  // Add any other fields you want to save
565
608
  };
566
609
  await new Promise((resolve, reject) => {
@@ -880,7 +923,9 @@ this.executeJOEAiPrompt = async function(data, req, res) {
880
923
  params,
881
924
  referenced_object_ids: referencedObjectIds,
882
925
  response_id:response.id,
883
- usage: response.usage || {}
926
+ usage: response.usage || {},
927
+ user: req && req.User,
928
+ ai_assistant_id: data.ai_assistant_id
884
929
  });
885
930
 
886
931
  return { success: true, ai_response_id: saved._id,response:response.output_text || "",usage:response.usage };
@@ -903,13 +948,34 @@ this.executeJOEAiPrompt = async function(data, req, res) {
903
948
  max_tokens: prompt.max_tokens ?? 1200
904
949
  };
905
950
  }
906
- async function saveAiResponseRefactor({ prompt, ai_response_content, user_prompt, params, referenced_object_ids,response_id,usage}) {
951
+ async function saveAiResponseRefactor({ prompt, ai_response_content, user_prompt, params, referenced_object_ids,response_id,usage,user,ai_assistant_id}) {
907
952
  var response_keys = [];
908
953
  try {
909
954
  response_keys = Object.keys(JSON.parse(ai_response_content));
910
955
  }catch (e) {
911
956
  console.error('❌ Error parsing AI response content for keys:', e);
912
957
  }
958
+ // Best-effort parse into JSON for downstream agents (Thought pipeline, etc.)
959
+ let parsedResponse = null;
960
+ try {
961
+ const jt = extractJsonText(ai_response_content);
962
+ if (jt) {
963
+ parsedResponse = JSON.parse(jt);
964
+ }
965
+ } catch(_e) {
966
+ parsedResponse = null;
967
+ }
968
+ var creator_type = null;
969
+ var creator_id = null;
970
+ try{
971
+ if (ai_assistant_id){
972
+ creator_type = 'ai_assistant';
973
+ creator_id = ai_assistant_id;
974
+ } else if (user && user._id){
975
+ creator_type = 'user';
976
+ creator_id = user._id;
977
+ }
978
+ }catch(_e){}
913
979
  const aiResponse = {
914
980
  name: `${prompt.name}`,
915
981
  itemtype: 'ai_response',
@@ -917,6 +983,7 @@ this.executeJOEAiPrompt = async function(data, req, res) {
917
983
  prompt_name: prompt.name,
918
984
  prompt_method:prompt.prompt_method,
919
985
  response: ai_response_content,
986
+ response_json: parsedResponse,
920
987
  response_keys: response_keys,
921
988
  response_id:response_id||'',
922
989
  user_prompt: user_prompt,
@@ -926,7 +993,9 @@ this.executeJOEAiPrompt = async function(data, req, res) {
926
993
  model_used: prompt.ai_model || "gpt-4o",
927
994
  referenced_objects: referenced_object_ids, // new flexible array of referenced object ids
928
995
  created: (new Date).toISOString(),
929
- _id: cuid()
996
+ _id: cuid(),
997
+ creator_type: creator_type,
998
+ creator_id: creator_id
930
999
  };
931
1000
 
932
1001
  await new Promise((resolve, reject) => {
@@ -0,0 +1,90 @@
1
+ var schema = {
2
+ title: "AI Pipeline | ${name}",
3
+ display: "AI Pipeline",
4
+ info: "Configurable AI context pipelines composed of ordered steps, used to compile context for agents like the Thought Engine.",
5
+ summary: {
6
+ description: "Declarative definition of AI context pipelines: an ordered list of steps (schema summaries, thoughts, objects, etc.) that feed agents.",
7
+ purpose: "Use ai_pipeline to configure what context is compiled for a given agent run (e.g., which schemas, thoughts, and objects are included) without changing code.",
8
+ labelField: "name",
9
+ defaultSort: { field: "created", dir: "desc" },
10
+ searchableFields: ["name", "pipeline_id", "info", "_id"],
11
+ allowedSorts: ["created", "joeUpdated", "name"],
12
+ relationships: {
13
+ outbound: [
14
+ // Future: agents that reference this pipeline_id
15
+ ],
16
+ inbound: { graphRef: "server/relationships.graph.json" }
17
+ },
18
+ joeManagedFields: ["created", "joeUpdated"],
19
+ fields: [
20
+ { name: "_id", type: "string", required: true },
21
+ { name: "itemtype", type: "string", required: true, const: "ai_pipeline" },
22
+ { name: "name", type: "string", required: true },
23
+ { name: "pipeline_id", type: "string", required: true },
24
+ { name: "info", type: "string" },
25
+ // Steps are minimal, but expressive enough to mirror current PIPELINES config
26
+ { name: "steps", type: "objectList" },
27
+ { name: "joeUpdated", type: "string", format: "date-time" },
28
+ { name: "created", type: "string", format: "date-time" }
29
+ ]
30
+ },
31
+ listView: {
32
+ title: function (p) {
33
+ return `
34
+ <joe-subtext>${_joe.Utils.prettyPrintDTS(p.created)}</joe-subtext>
35
+ <joe-title>${p.name}</joe-title>
36
+ <joe-subtitle>${p.pipeline_id || ""}</joe-subtitle>
37
+ `;
38
+ },
39
+ listWindowTitle: "AI Pipelines"
40
+ },
41
+ sorter: ["!created", "name"],
42
+ fields: [
43
+ "name",
44
+ { name: "pipeline_id", width: "50%", comment: "Logical id used by agents and ThoughtPipeline (e.g., thought_default, protocol_planning)." },
45
+ "info",
46
+
47
+ { section_start: "steps", display: "Pipeline Steps", collapsed: false },
48
+ {
49
+ name: "steps",
50
+ type: "objectList",
51
+ display: "Steps",
52
+ comment: "Ordered steps that define how context is compiled.",
53
+ properties: [
54
+ { name: "id", width: "20%", comment: "Stable step id (e.g., schema_summaries, accepted_thoughts)." },
55
+ {
56
+ name: "step_type",
57
+ type: "select",
58
+ values: ["schema_summaries", "thoughts", "objects", "scope_object", "text", "tools"],
59
+ width: "15%",
60
+ comment: "Determines how this step is executed."
61
+ },
62
+ {
63
+ name: "render_mode",
64
+ type: "select",
65
+ values: ["json", "compact_json", "bullets", "text"],
66
+ width: "15%",
67
+ comment: "How this step is rendered into the agent prompt."
68
+ },
69
+ { name: "required", type: "boolean", width: "10%", comment: "If true, pipeline fails when this step cannot be resolved." },
70
+ {
71
+ name: "selector",
72
+ type: "code",
73
+ language: "json",
74
+ width: "40%",
75
+ comment: "Free-form selector JSON (e.g., { \"names\": [\"thought\",\"ai_prompt\" ] } or { \"query\": {\"itemtype\":\"thought\"}, \"sortBy\":\"joeUpdated\" } )."
76
+ }
77
+ ]
78
+ },
79
+ { section_end: "steps" },
80
+
81
+ { section_start: "system", collapsed: true },
82
+ "_id",
83
+ "created",
84
+ "itemtype",
85
+ { section_end: "system" }
86
+ ],
87
+ idprop: "_id"
88
+ };
89
+
90
+ module.exports = schema;
@@ -1,5 +1,6 @@
1
1
  var schema = {
2
2
  title : 'Ai Response | ${name}',
3
+ display:'AI Response',
3
4
  info:"An ai response from openai etc",
4
5
  summary:{
5
6
  description:'Stored AI response objects generated from ai_prompt and the chatgpt plugin.',
@@ -23,14 +24,18 @@ var schema = {
23
24
  { name:'name', type:'string', required:true },
24
25
  { name:'ai_prompt', type:'string', isReference:true, targetSchema:'ai_prompt' },
25
26
  { name:'referenced_objects', type:'string', isArray:true },
27
+ { name:'generated_thoughts', type:'string', isArray:true, isReference:true, targetSchema:'thought' },
26
28
  { name:'user_prompt', type:'string' },
27
29
  { name:'prompt_method', type:'string' },
28
30
  { name:'model_used', type:'string' },
29
31
  { name:'response_type', type:'string' },
30
32
  { name:'response', type:'string' },
33
+ { name:'response_json', type:'object' },
31
34
  { name:'response_keys', type:'string', isArray:true },
32
35
  { name:'response_id', type:'string' },
33
36
  { name:'usage', type:'object' },
37
+ // { name:'creator_type', type:'string', enumValues:['user','ai_assistant'] },
38
+ // { name:'creator_id', type:'string' },
34
39
  { name:'tags', type:'string', isArray:true, isReference:true, targetSchema:'tag' },
35
40
  { name:'joeUpdated', type:'string', format:'date-time' },
36
41
  { name:'created', type:'string', format:'date-time' }
@@ -60,18 +65,129 @@ var schema = {
60
65
  },
61
66
  listWindowTitle: 'Ai Responses'
62
67
  },
63
- subsets: function(){
64
- return _joe.Filter.Options.tags({group:true,collapsed:false}).concat(_joe.getDataset('ai_prompt').map(prompt => {
65
- var color = prompt.status && $J.get(prompt.status,'status').color;
66
- return {
67
- name: prompt.name,
68
- id: prompt._id,
69
- filter: { ai_prompt: prompt._id },
70
- stripecolor: color || null // optional
71
- };
72
- }))
73
- },
68
+ subsets: function(a,b,c){
69
+ // Base subsets: tag-based and by ai_prompt
70
+ var subsets = _joe.Filter.Options.tags({group:true,collapsed:false}).concat(
71
+ _joe.getDataset('ai_prompt').map(function(prompt){
72
+ var color = prompt.status && $J.get(prompt.status,'status').color;
73
+ return {
74
+ name: prompt.name,
75
+ id: prompt._id,
76
+ filter: { ai_prompt: prompt._id },
77
+ stripecolor: color || null // optional
78
+ };
79
+ })
80
+ );
74
81
 
82
+ // Additional subset: "Generated Thoughts" – detect from raw JSON
83
+ // We look inside response_json for proposed_thoughts / thoughts arrays.
84
+ try{
85
+ var ds = _joe.getDataset('ai_response') || [];
86
+ var ids = [];
87
+ ds.map(function(r){
88
+ var j = r && r.response_json;
89
+ if (!j) { return; }
90
+ if (Array.isArray(j.proposed_thoughts) && j.proposed_thoughts.length) {
91
+ ids.push(r._id);
92
+ return;
93
+ }
94
+ if (Array.isArray(j.thoughts) && j.thoughts.length) {
95
+ ids.push(r._id);
96
+ }
97
+ });
98
+ if (ids.length) {
99
+ subsets.push({
100
+ name: 'Generated Thoughts',
101
+ id: 'generated_thoughts',
102
+ filter: { _id: { $in: ids } },
103
+ stripecolor: _joe.Colors.ai
104
+ });
105
+ }
106
+ }catch(_e){ /* best-effort only */ }
107
+ // subsets.push(
108
+ // {
109
+ // name: 'True',
110
+ // id: 'isTrue',
111
+ // filter: function(air,index, arra){
112
+ // return air.response_json && air.response_json.proposed_thoughts && air.response_json.proposed_thoughts.length > 0;
113
+ // },
114
+ // stripecolor: 'gold'
115
+ // }
116
+ // );
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
+ filters:function(){
126
+ var filters = [];
127
+ // Tag filters
128
+ filters = filters.concat(
129
+ _joe.Filter.Options.tags({group:'tags',untagged:true,collapsed:true})
130
+ );
131
+ // creator_type simple value grouping
132
+ // try{
133
+ // filters = filters.concat(
134
+ // _joe.Filter.Options.getDatasetPropertyValues('ai_response','creator_type',{ group: 'creator_type', collapsed: true })
135
+ // );
136
+ // }catch(_e){}
137
+ // // creator_id filters with human-readable names
138
+ // try{
139
+ // var dataset = _joe.getDataset('ai_response') || [];
140
+ // var byCreator = {};
141
+ // dataset.map(function(r){
142
+ // if(!r.creator_id){ return; }
143
+ // var key = r.creator_type+':'+r.creator_id;
144
+ // if(!byCreator[key]){
145
+ // var name = r.creator_id;
146
+ // if(r.creator_type === 'user' && _joe.Data.user){
147
+ // var u = _joe.Data.user.find(function(x){ return x && x._id === r.creator_id; });
148
+ // if(u){ name = u.fullname || u.name || u.email || u._id; }
149
+ // }else if(r.creator_type === 'ai_assistant' && _joe.Data.ai_assistant){
150
+ // var a = _joe.Data.ai_assistant.find(function(x){ return x && x._id === r.creator_id; });
151
+ // if(a){ name = a.name || a._id; }
152
+ // }
153
+ // byCreator[key] = {
154
+ // name: name,
155
+ // filter: { creator_type: r.creator_type, creator_id: r.creator_id }
156
+ // };
157
+ // }
158
+ // });
159
+ // var keys = Object.keys(byCreator);
160
+ // if(keys.length){
161
+ // filters.push({ group_start:'creator', collapsed:true });
162
+ // keys.sort(function(a,b){
163
+ // var na = byCreator[a].name.toLowerCase();
164
+ // var nb = byCreator[b].name.toLowerCase();
165
+ // if(na>nb) return 1;
166
+ // if(na<nb) return -1;
167
+ // return 0;
168
+ // }).map(function(k){
169
+ // filters.push({
170
+ // name: byCreator[k].name,
171
+ // filter: byCreator[k].filter
172
+ // });
173
+ // });
174
+ // filters.push({ group_end:'creator' });
175
+ // }
176
+ // }catch(_e){}
177
+ return filters;
178
+ },
179
+ itemExpander:function(air){
180
+ if(air.response_json && air.response_json.proposed_thoughts){
181
+ //list proposed thoughts in html with header "Proposed Thoughts"
182
+ var html = `<joe-card><joe-title>Proposed Thoughts</joe-title>`;
183
+ air.response_json.proposed_thoughts.map(thought=>{
184
+ html += `<joe-subtitle>${thought.statement}</joe-subtitle><br/>`;
185
+ });
186
+ html += `</joe-card>`;
187
+ return html;
188
+ }
189
+ return _joe.schemas.thought.methods.listThoughts(air);
190
+ },
75
191
  sorter:['!created','name'],
76
192
  checkChanges:false,
77
193
  fields:[
@@ -81,6 +197,9 @@ var schema = {
81
197
  {name:'referenced_objects',type:'objectReference',width:'50%',values:function(air,orList){
82
198
  return $J.search({_id:{$in:air.referenced_objects}});
83
199
  },display:'Referenced Objects',locked:true},
200
+ {name:'generated_thoughts',type:'objectReference',width:'50%',values:function(air,orList){
201
+ return $J.search({source_ai_response:air._id,itemtype:'thought'});
202
+ },display:'Generated Thoughts',locked:true},
84
203
  {name:'user_prompt', display:'Sent User Prompt',comment:"The input sent by the user after all processing",locked:true,type:'rendering'},
85
204
 
86
205
  {name:'prompt_method',locked:true,width:'50%'},
@@ -94,17 +213,47 @@ var schema = {
94
213
 
95
214
  //{name:'params', type:'rendering', locked:true},
96
215
  {section_start:'response',collapsed:false},
97
- {name:'response_type',type:'text',display:'Response Type',
98
- placeholder:'(a code for the responsetype)',locked:true,width:'50%',condition:function(v){
216
+ {
217
+ name:'response_type',
218
+ type:'text',
219
+ display:'Response Type',
220
+ placeholder:'(a code for the responsetype)',
221
+ locked:true,
222
+ width:'50%',
223
+ condition:function(v){
99
224
  return v.length > 0;
100
- }},
101
-
102
- {name:'response', type:'rendering', locked:true,height:'500px'},
225
+ }
226
+ },
227
+ {name:'response', type:'rendering', locked:true, height:'500px'},
228
+ {
229
+ name:'response_json',
230
+ type:'content',
231
+ display:'Parsed Response (JSON)',
232
+ locked:true,
233
+ run:function(air){
234
+ if (!air || !air.response_json) { return ''; }
235
+ try{
236
+ var json = JSON.stringify(air.response_json, null, 2);
237
+ // basic HTML escape for safety
238
+ json = String(json)
239
+ .replace(/&/g,'&amp;')
240
+ .replace(/</g,'&lt;')
241
+ .replace(/>/g,'&gt;');
242
+ return '<pre style="white-space:pre-wrap; max-height:260px; overflow:auto;">'+json+'</pre>';
243
+ }catch(e){
244
+ return '<pre>'+String(air.response_json)+'</pre>';
245
+ }
246
+ }
247
+ },
103
248
  {name:'response_keys', type:'text', locked:true,display:'Response Keys'},
104
249
  {name:'response_id', type:'text', display:'openAI response ID',locked:true},
105
250
  {section_end:'response'},
106
251
  {sidebar_start:'right', collapsed:false},
252
+ // {section_start:'creator'},
253
+ // 'creator_type','creator_id',
254
+ // {section_end:'creator'},
107
255
  {section_start:'workflow'},
256
+ 'status',
108
257
  'tags',
109
258
  {section_end:'workflow'},
110
259
  {section_start:'tokens'},
@@ -156,7 +156,8 @@ var task = function(){return{
156
156
  _joe.Sections.unFocusAll();
157
157
  _joe.Sections.setTabbedMode(false);
158
158
  }
159
- }
159
+ },
160
+ // (Deprecated) task-specific Thought trigger kept for backward compatibility if needed
160
161
  },
161
162
  onPanelShow:function(state){
162
163
 
@@ -180,6 +181,8 @@ var task = function(){return{
180
181
  {section_start:'JAI',display:'JOE Ai'},
181
182
  "objectChat",
182
183
  "listConversations",
184
+ 'proposeThought',
185
+ 'ai_responses',
183
186
  {section_end:'JAI'},
184
187
  {sidebar_end:'left'},
185
188
  {section_start:'overview'},