json-object-editor 0.10.653 → 0.10.657

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.
@@ -555,6 +555,85 @@ var routeWithoutSlash = route.startsWith('/') ? route.slice(1) : route;
555
555
  logit(`sent html ${new Date().getTime() - routeStartTimer}`);
556
556
  return;
557
557
  }
558
+
559
+ /*HANDLE PAGES WITHOUT LAYOUT - render content directly*/
560
+ if(!layout && page.content_type && (page.content_type == 'code' || page.content_type == 'wysiwyg')){
561
+ var html = page.content || '';
562
+
563
+ // Debug: log content length and first 200 chars
564
+ console.log(modulename+' [NO LAYOUT] content length:', html.length);
565
+ console.log(modulename+' [NO LAYOUT] content preview:', html.substring(0, 200));
566
+
567
+ // Process includes
568
+ includes = Array.from(new Set(includes));
569
+ var INC = '',incobj;
570
+ includes.map(function(i){
571
+ incobj = JOE.Data.include.where({_id:i})[0]||{filetype:'notfound'};
572
+ var url='/_include/'+incobj._id;
573
+ switch(incobj.filetype){
574
+ case 'css':
575
+ INC += '<link rel="Stylesheet" type="text/css" href="'+url+'"/>';
576
+ break;
577
+ case 'js':
578
+ INC += '<script src="'+url+'" type="application/javascript"></script>';
579
+ break;
580
+ }
581
+ });
582
+
583
+ // Check if this is a complete HTML document
584
+ var isCompleteHTML = html.indexOf('<!doctype') != -1 || html.indexOf('<html') != -1;
585
+
586
+ // Only process template variables if:
587
+ // 1. Content is NOT a complete HTML document, OR
588
+ // 2. Content explicitly uses JOE template syntax (${this.PAGE.}, ${this.SITE.}, etc.)
589
+ var hasJoeTemplateSyntax = html.indexOf('${this.') != -1 ||
590
+ html.indexOf('${PAGE.') != -1 ||
591
+ html.indexOf('${SITE.') != -1 ||
592
+ html.indexOf('${DATA.') != -1 ||
593
+ html.indexOf('${INCLUDES}') != -1;
594
+
595
+ if(!isCompleteHTML || hasJoeTemplateSyntax){
596
+ // Process template variables if content uses them
597
+ var content = {
598
+ DYNAMIC:DYNAMIC,
599
+ INCLUDES:INC,
600
+ PAGE:page,
601
+ SITE:site,
602
+ JOEPATH:JOE.webconfig.joepath,
603
+ DATA:datasets,
604
+ WEBCONFIG:JOE.webconfig,
605
+ SECTION:section_content||{},
606
+ REQUEST:req,
607
+ ORIGIN:origin
608
+ };
609
+ if(hasJoeTemplateSyntax || html.indexOf('${') != -1){
610
+ console.log(modulename+' [NO LAYOUT] processing template variables');
611
+ html = fillTemplate(html, content);
612
+ }
613
+ } else {
614
+ console.log(modulename+' [NO LAYOUT] skipping template processing for complete HTML document');
615
+ }
616
+
617
+ // If content doesn't already have HTML structure, wrap it
618
+ if(!isCompleteHTML){
619
+ html = '<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">' + INC + '</head><body>' + html + '</body></html>';
620
+ } else {
621
+ // Insert includes into existing HTML
622
+ if(html.indexOf('</head>') != -1){
623
+ html = html.replace('</head>', INC + '</head>');
624
+ } else if(html.indexOf('<head>') != -1){
625
+ html = html.replace('<head>', '<head>' + INC);
626
+ } else {
627
+ // No head tag, prepend to html
628
+ html = html.replace(/<html[^>]*>/, function(match){ return match + '<head>' + INC + '</head>'; });
629
+ }
630
+ }
631
+
632
+ console.log(modulename+' [NO LAYOUT] final HTML length:', html.length);
633
+ res.send(html);
634
+ logit(`sent html (no layout) ${new Date().getTime() - routeStartTimer}`);
635
+ return;
636
+ }
558
637
  }
559
638
 
560
639
  res.send(payload);
@@ -129,6 +129,11 @@ function Storage(specs){
129
129
  }
130
130
  var event_specs = {timestamp:ts};
131
131
 
132
+ // Build a history payload for auditing. We sanitize a copy of
133
+ // the document before diffing so that the craydent Object.equals
134
+ // helper never sees raw `null`/`undefined` values (its internal
135
+ // comparison calls `.toString()` on both sides, which will throw
136
+ // on `null`/`undefined` when they are equal).
132
137
  var history_payload = {
133
138
  itemid:data._id,
134
139
  collection:collection,
@@ -138,7 +143,29 @@ function Storage(specs){
138
143
  };
139
144
  //var cached = JOE.Cache.findByID(data.itemtype,data._id);
140
145
  if(cached){
141
- history_payload.changes = $c.changes(cached,data);
146
+ // Use sanitized clones for history diffing to avoid
147
+ // craydent-object's equals() calling `.toString()` on
148
+ // `null`/`undefined` and throwing. This does not affect
149
+ // what gets saved to Mongo, only what is recorded in
150
+ // the history "changes" payload.
151
+ var _sanitizeForHistory = function(input){
152
+ if (input === null || input === undefined){ return ''; }
153
+ if (Array.isArray(input)){
154
+ return input.map(function(v){ return _sanitizeForHistory(v); });
155
+ }
156
+ if (typeof input === 'object'){
157
+ var out = {};
158
+ for (var k in input){
159
+ if (!input.hasOwnProperty(k)){ continue; }
160
+ out[k] = _sanitizeForHistory(input[k]);
161
+ }
162
+ return out;
163
+ }
164
+ return input;
165
+ };
166
+ var cachedSafe = _sanitizeForHistory(cached);
167
+ var dataSafe = _sanitizeForHistory(data);
168
+ history_payload.changes = $c.changes(cachedSafe,dataSafe);
142
169
  //sanitize
143
170
  for(var hvar in history_payload.changes){
144
171
  if(hvar.indexOf('$') != -1){
@@ -460,6 +460,12 @@ ThoughtPipeline.runAgent = async function runAgent(agentId, userInput, scopeId,
460
460
  usage: response.usage || {},
461
461
  prompt_method: 'ThoughtPipeline.runAgent'
462
462
  };
463
+ // Persist used OpenAI file ids when provided (audit convenience)
464
+ try{
465
+ if (ctx && Array.isArray(ctx.openai_file_ids) && ctx.openai_file_ids.length){
466
+ aiResponseObj.used_openai_file_ids = ctx.openai_file_ids.slice(0,10);
467
+ }
468
+ }catch(_e){}
463
469
 
464
470
  var savedResponse = await new Promise(function (resolve, reject) {
465
471
  try {
@@ -3,6 +3,7 @@ function AWSConnect(){
3
3
  this.default = function(data,req,res){
4
4
  // AWS SDK v3 (modular)
5
5
  const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
6
+ const chatgpt = require('./chatgpt.js');
6
7
  var settings_config = tryEval(JOE.Cache.settings.AWS_S3CONFIG)||{};
7
8
  var config = $c.merge(settings_config);
8
9
 
@@ -67,12 +68,41 @@ var response = {
67
68
  }
68
69
 
69
70
  s3.send(new PutObjectCommand(s3Params))
70
- .then(function(data){
71
+ .then(async function(data){
71
72
  // Construct canonical URL from region + bucket
72
73
  var region = config.region;
73
74
  var url = 'https://'+Bucket+'.s3.'+region+'.amazonaws.com/'+Key;
74
75
  response.data = data;
75
76
  response.url = url;
77
+ response.etag = data && (data.ETag || data.ETAG || data.eTag);
78
+
79
+ // If OpenAI key is configured, also upload to OpenAI Files (purpose: assistants)
80
+ try{
81
+ var hasOpenAIKey = !!JOE.Utils.Settings && !!JOE.Utils.Settings('OPENAI_API_KEY');
82
+ if(hasOpenAIKey){
83
+ // Prefer original buffer when provided via base64
84
+ if(data && typeof data === 'object'){ /* noop to keep linter happy */}
85
+ if(typeof s3Params.Body !== 'string' && s3Params.Body){
86
+ var filenameOnly = Key.split('/').pop();
87
+ var result = await chatgpt.filesUploadFromBufferHelper({
88
+ buffer: s3Params.Body,
89
+ filename: filenameOnly,
90
+ contentType: s3Params.ContentType,
91
+ purpose: 'assistants'
92
+ });
93
+ if(result && result.id){
94
+ response.openai_file_id = result.id;
95
+ response.openai_purpose = result.purpose || 'assistants';
96
+ }
97
+ }else{
98
+ // Fallback: if we didn't have a buffer (unlikely with current flow),
99
+ // skip immediate upload; client can use retry endpoint.
100
+ }
101
+ }
102
+ }catch(e){
103
+ // Non-fatal: S3 upload already succeeded
104
+ response.openai_error = (e && e.message) || String(e);
105
+ }
76
106
  res.status(200).send(response);
77
107
  console.log("Successfully uploaded data to "+Key);
78
108
  })