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.
- package/CHANGELOG.md +47 -1
- package/_www/mcp-test.html +287 -276
- package/css/joe-ai.css +1 -1
- package/css/joe-styles.css +56 -1
- package/css/joe.css +57 -2
- package/css/joe.min.css +1 -1
- package/js/JsonObjectEditor.jquery.craydent.js +152 -17
- package/js/joe-ai.js +2075 -1825
- package/js/joe.js +153 -18
- package/js/joe.min.js +1 -1
- package/package.json +1 -1
- package/readme.md +74 -4
- package/server/fields/core.js +54 -6
- package/server/modules/MCP.js +1364 -1237
- package/server/modules/Sites.js +79 -0
- package/server/modules/Storage.js +28 -1
- package/server/modules/ThoughtPipeline.js +6 -0
- package/server/plugins/awsConnect.js +31 -1
- package/server/plugins/chatgpt.js +1732 -1339
- package/server/schemas/ai_prompt.js +389 -322
- package/server/schemas/ai_response.js +414 -365
- package/server/schemas/status.js +12 -2
- package/server/schemas/task.js +9 -3
package/server/modules/Sites.js
CHANGED
|
@@ -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
|
-
|
|
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
|
})
|