json-object-editor 0.10.521 → 0.10.622

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-object-editor",
3
- "version": "0.10.521",
3
+ "version": "0.10.622",
4
4
  "description": "JOE the Json Object Editor | Platform Edition",
5
5
  "main": "app.js",
6
6
  "scripts": {
@@ -30,7 +30,8 @@
30
30
  },
31
31
  "homepage": "http://craydent.com/JsonObjectEditor/docs.html",
32
32
  "dependencies": {
33
- "aws-sdk": "^2.358.0",
33
+ "@aws-sdk/client-s3": "^3.927.0",
34
+ "@aws-sdk/client-ses": "^3.927.0",
34
35
  "basic-auth": "^1.0.3",
35
36
  "body-parser": "^1.18.3",
36
37
  "compression": "^1.7.3",
@@ -43,9 +44,8 @@
43
44
  "mailgun": "^0.5.0",
44
45
  "mongojs": "^2.3.0",
45
46
  "mysql": "^2.16.0",
46
- "nodemailer": "^2.7.2",
47
- "nodemailer-ses-transport": "^1.4.0",
48
- "openai": "^5.23.2",
47
+ "nodemailer": "^6.10.1",
48
+ "openai": "^6.9.0",
49
49
  "opener": "^1.4.3",
50
50
  "pem": "^1.13.2",
51
51
  "plaid": "^13.1.0",
package/readme.md CHANGED
@@ -155,6 +155,19 @@ JOE is software that allows you to manage data models via JSON objects. There ar
155
155
  - If you see a payload like `{ originalURL: "/...", site: "no site found" }`, the request hit the Sites catch-all. Ensure MCP routes are initialized before Sites (handled by default in `server/init.js` via `MCP.init()`), and use the correct URL: `/.well-known/mcp/manifest.json` or `/mcp`.
156
156
  - To update contact email on /privacy and /terms, set Setting `PRIVACY_CONTACT`.
157
157
 
158
+ ## File uploads (S3)
159
+ - Uploader field options:
160
+ - `allowmultiple: true|false` — allow selecting multiple files.
161
+ - `url_field: 'image_url'` — on success, sets this property to the remote URL and rerenders that field.
162
+ - `ACL: 'public-read'` — optional per-field ACL. When omitted, server currently defaults to `public-read` (temporary during migration).
163
+ - Flow:
164
+ - Client posts `{ Key, base64, contentType, ACL? }` to `/API/plugin/awsConnect`.
165
+ - Server uploads with AWS SDK v3 and returns `{ url, Key, bucket, etag }` (HTTP 200).
166
+ - Client uses `response.url`; if `url_field` is set, it assigns and rerenders that field.
167
+ - Errors:
168
+ - If bucket or region config is missing, server returns 400 with a clear message.
169
+ - If the bucket has ACLs disabled, server returns 400: “Bucket has ACLs disabled… remove ACL or switch to presigned/proxy access.”
170
+
158
171
  ## SERVER/PLATFORM mode
159
172
  check port 2099
160
173
  /JOE/
@@ -310,6 +323,7 @@ Properties for all Fields
310
323
  - `'section_label`:use instead of section_start for display name
311
324
  - `section_end`: name/id of section(str)
312
325
  - template: html template for fillTemplate(template,current_object);
326
+ - Note: for fields, `condition` removes the field from the DOM while `hidden` only toggles visibility; sections use `condition` (and sidebar sections use `hidden`) differently than fields.
313
327
 
314
328
  ##page sidebar
315
329
  {sidebar_start: 'SectionName',
@@ -518,9 +532,132 @@ Use these helpers to quickly generate subset/filter options and add per-item str
518
532
 
519
533
  ### e | exporting an object in pretty format json (or minified)
520
534
  JOE.exportJSON = function(object,objvarname,minify)
521
-
535
+
522
536
  ##Useful Functions
523
537
  _joe.reload(hideMessage,specs)
524
538
  - use specs.overwreite object to extend reloaded object.
539
+
540
+ _joe.constructObjectFromFields()
541
+
542
+ ## AI / Chat Integrations (OpenAI Responses + Assistants)
543
+
544
+ JOE ships a few plugins and schemas to help you connect OpenAI assistants and the new Responses API to your data model.
545
+
546
+ - **`chatgpt` plugin**
547
+ - Central entry point for OpenAI calls (uses the `openai` Node SDK and the Responses API).
548
+ - Looks up the OpenAI API key from the `setting` schema:
549
+ - `OPENAI_API_KEY`: your secret key (can be a service‑account key, e.g. `sk-svcacct-...`).
550
+ - Key methods:
551
+ - **`autofill`** (`POST /API/plugin/chatgpt/autofill`):
552
+ - Given `{ object_id, schema, fields, prompt?, assistant_id?, model? }`, calls `openai.responses.create(...)` and returns a JSON patch for the requested fields only.
553
+ - Used by `_joe.Ai.populateField(...)` for schema‑driven “AI fill” buttons.
554
+ - **`executeJOEAiPrompt`**:
555
+ - Drives the `ai_prompt` → `ai_response` workflow using Responses API + optional helper functions.
556
+ - **Widget‑focused methods**:
557
+ - `widgetStart`, `widgetMessage`, `widgetHistory` for the embeddable AI widget (see below).
558
+
559
+ - **`chatgpt-assistants` plugin (legacy Assistants API)**
560
+ - Manages the older `beta.assistants` / `beta.threads` API for JOE’s rich in‑app chat (`<joe-ai-chatbox>`).
561
+ - Still used by the `ai_conversation` flow; new integrations should prefer the Responses‑based `chatgpt` plugin.
562
+
563
+ - **`chatgpt-tools` plugin**
564
+ - Exposes JOE “AI tools” configured via the `ai_tool` schema.
565
+ - Useful when you want OpenAI tools that call back into JOE (e.g. search, profile, etc.).
566
+
567
+ ### AI Assistant schemas
568
+
569
+ - **`ai_assistant` schema**
570
+ - Represents a JOE‑side “assistant config” that can be synced to an OpenAI assistant.
571
+ - Key fields:
572
+ - `ai_model`: default model (e.g. `gpt-4.1-mini`, `gpt-4o`).
573
+ - `instructions`: system prompt for the assistant.
574
+ - `assistant_id`: OpenAI assistant ID (`asst_...`) set by the sync button.
575
+ - `file_search_enabled`, `code_interpreter_enabled`: toggle built‑in tools.
576
+ - `tools`: JSON definition array for custom function tools.
577
+ - `assistant_thinking_text`: what the UI shows while the assistant is “thinking”.
578
+ - UI highlights the **default assistant**:
579
+ - The assistant whose `_id` matches the `DEFAULT_AI_ASSISTANT` setting is given a golden stripe in list view.
580
+ - Default assistant resolution:
581
+ - The client helper `Ai.getDefaultAssistant()` in `joe-ai.js` looks up `_joe.Data.setting.where({ name: 'DEFAULT_AI_ASSISTANT' })[0]` and caches it.
582
+
583
+ - **`ai_widget_conversation` schema**
584
+ - Lightweight conversation log used by the new embeddable widget (`<joe-ai-widget>`).
585
+ - Fields:
586
+ - `model`: model name used for the conversation (or inherited from `ai_assistant.ai_model`).
587
+ - `assistant`: optional reference to the JOE `ai_assistant` used.
588
+ - `assistant_id`: OpenAI assistant ID (`asst_...`) used by the Responses API.
589
+ - `system`: effective system prompt.
590
+ - `messages`: JSON array of `{ role, content, created_at }` for the widget chat.
591
+ - `last_message_at`, `source`, `tags`, standard system fields.
592
+ - This schema is what the widget endpoints in `chatgpt` read/write.
593
+
594
+ ### Embeddable AI Widget (`<joe-ai-widget>`)
595
+
596
+ JOE includes a small web component, defined in `js/joe-ai.js`, that can be dropped into any web page and talks to your JOE instance via the `chatgpt` plugin.
597
+
598
+ - **Custom element**: `<joe-ai-widget>`
599
+ - Attributes:
600
+ - `endpoint` (optional): Base URL to the JOE instance. Defaults to same origin.
601
+ Example: `endpoint="https://my-joe.example.com"`.
602
+ - `ai_assistant_id` (optional): `_id` of an `ai_assistant` document in JOE. If present, the server will load this assistant and use its `ai_model`, `instructions`, and `assistant_id` when starting the conversation.
603
+ - `assistant_id` (optional): Direct OpenAI assistant ID (`asst_...`). If provided, the widget uses Responses API with `assistant_id` instead of a bare `model`.
604
+ - `model` (optional): Model name to use when no assistant is supplied (e.g. `gpt-4.1-mini`).
605
+ - `title` (optional): Header title text shown in the widget panel.
606
+ - `placeholder` (optional): Input placeholder text.
607
+ - `source` (optional): Arbitrary string stored on the conversation (`ai_widget_conversation.source`).
608
+ - `conversation_id` (optional): Existing `ai_widget_conversation._id` to resume a saved chat.
609
+ - Behavior:
610
+ - On first attach:
611
+ - If `conversation_id` is set → calls `GET /API/plugin/chatgpt/widgetHistory?conversation_id=...` and renders messages.
612
+ - Otherwise → calls `POST /API/plugin/chatgpt/widgetStart` to create a new `ai_widget_conversation` and stores the returned `conversation_id` (and any `assistant_id`/`model`) as attributes.
613
+ - On send:
614
+ - Optimistically appends a `{ role:'user', content }` message in the UI.
615
+ - Calls `POST /API/plugin/chatgpt/widgetMessage` with `{ conversation_id, content, role:'user', assistant_id?, model? }`.
616
+ - Renders the updated `messages` and assistant reply from the response.
617
+
618
+ - **Server endpoints (`chatgpt` plugin)**:
619
+ - `POST /API/plugin/chatgpt/widgetStart` → `chatgpt.widgetStart(data)`
620
+ - Input: `{ model?, ai_assistant_id?, source? }`.
621
+ - Behavior:
622
+ - If `ai_assistant_id` is provided, loads that `ai_assistant` and seeds `model`, `assistant_id`, `system` from it.
623
+ - Creates `ai_widget_conversation` with empty `messages` and timestamps.
624
+ - Returns `{ success, conversation_id, model, assistant_id }`.
625
+ - `GET /API/plugin/chatgpt/widgetHistory?conversation_id=...` → `chatgpt.widgetHistory(data)`
626
+ - Input: `{ conversation_id }`.
627
+ - Returns `{ success, conversation_id, model, assistant_id, messages }`.
628
+ - `POST /API/plugin/chatgpt/widgetMessage` → `chatgpt.widgetMessage(data)`
629
+ - Input: `{ conversation_id, content, role='user', assistant_id?, model? }`.
630
+ - Behavior:
631
+ - Loads `ai_widget_conversation`, appends a user message.
632
+ - Calls `openai.responses.create`:
633
+ - With `assistant_id` if available (preferred), or
634
+ - With `model` and `instructions` (from `system`) otherwise.
635
+ - Appends an assistant message, updates `last_message_at`, saves the conversation.
636
+ - Returns `{ success, conversation_id, model, assistant_id, messages, last_message, usage }`.
637
+
638
+ ### AI Widget Test Page
639
+
640
+ To help you develop and debug the widget + plugin in your instance, JOE exposes an auth‑protected test page, similar to the MCP test pages.
641
+
642
+ - **Routes** (both require standard JOE `auth`):
643
+ - `/ai-widget-test.html`
644
+ - `${JOE.webconfig.joepath}ai-widget-test.html` (e.g. `/JsonObjectEditor/ai-widget-test.html`).
645
+
646
+ - **Behavior** (`server/modules/Server.js`):
647
+ - The route handler:
648
+ - Reads `assistant_id` or `assistant` from the query string.
649
+ - If not present, tries to resolve the default assistant via:
650
+ - `JOE.Utils.Settings('DEFAULT_AI_ASSISTANT', { object: true })` and uses its `.value` as `ai_assistant_id`.
651
+ - Renders a small HTML page that:
652
+ - Includes the MCP nav (`_www/mcp-nav.js`) so you can jump between MCP and widget tests.
653
+ - Mounts `<joe-ai-widget title="JOE AI Assistant" ai_assistant_id="...">` in a centered card.
654
+ - Loads `js/joe-ai.js`, which defines both `<joe-ai-chatbox>` (for in‑app use) and `<joe-ai-widget>` (for embedding).
655
+
656
+ - **Shared nav link**:
657
+ - The shared dev nav (`_www/mcp-nav.js`) includes an “AI Widget” link:
658
+
659
+ ```html
660
+ <a href="/ai-widget-test.html" target="ai_widget_test_win" rel="noopener">AI Widget</a>
661
+ ```
525
662
 
526
- _joe.constructObjectFromFields()
663
+ - This appears on MCP test/export/prompt pages and on the AI widget test page itself.
@@ -31,12 +31,13 @@ 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','plugin-utils.js','chatgpt.js','chatgpt-assistants.js','chatgpt-tools.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','chatgpt-responses.js'],
35
35
  collections:((default_schemas.concat(['schema','group',
36
36
  'site','page','post','layout','block','include','event',
37
37
  'project','board','task',
38
38
  'transaction','budget','ledger',
39
39
  'location','device','notification',
40
+ /*'ai_assistant','ai_conversation','ai_response','ai_tool','ai_prompt',*/
40
41
  'visitor','question','form','submission','session','touchpoint','member']))).sort(),
41
42
  dashboard:[
42
43
  JOE.Apps.Cards.appHome({cssclass:'w3 h4'}),
@@ -83,22 +83,7 @@ var fields = {
83
83
  return item.instructions_format;
84
84
  }
85
85
  },
86
- ai_model:{
87
- type: "select",
88
- display: "Ai Model",
89
- values: [
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.`,
100
- default: "gpt-4o",
101
- },
86
+
102
87
  template:{
103
88
  height:'600px',
104
89
  type:function(item){
@@ -300,6 +285,7 @@ var fields = {
300
285
  autocomplete_template:'<joe-title>${name} (.${filetype})</joe-title><joe-subtext>${info}</joe-subtext>'
301
286
 
302
287
  },
288
+ /* file_upload:{type:'uploader',allowmultiple:true, height:'300px',comment:'drag files here to upload', onConfirm:_joe.SERVER.Plugins.awsFileUpload,use_legacy:true},*/
303
289
  datasets:{type:'group',cols:2,
304
290
  comment:'which itemtypes(schemas) does this pertain to',values:function(){
305
291
  if(typeof(__collectionNames) != undefined){
@@ -529,7 +515,39 @@ var fields = {
529
515
  html += _joe.renderFieldListItem(item,temp,item.itemtype);
530
516
  });
531
517
  return html;
532
- }}
518
+ }},
519
+ //AI Fields
520
+ ai_model:{
521
+ type: "select",
522
+ display: "Ai Model",
523
+ values: [
524
+ { value:"gpt-5.1", name: "GPT-5.1 (Strong, 128k)" },
525
+ { value:"gpt-5", name: "GPT-5 (Strong, 128K)" },
526
+ { value:"gpt-5-mini", name: "GPT-5-mini (Cheap, 1M)" },
527
+ { value:"gpt-5-nano", name: "GPT-5-nano (Fastest, light tasks)" },
528
+ { value: "gpt-4o", name: "GPT-4o (Fast, 128k)" },
529
+ { value: "gpt-4.1", name: "GPT-4.1 (Strong, 1M)" },
530
+ { value: "gpt-4.1-mini", name: "4.1-mini (Cheap, 1M)" },
531
+ { value: "gpt-4.1-nano", name: "4.1-nano (Fastest, light tasks)" }
532
+ ],
533
+ tooltip:`Ai Model Guide -
534
+ 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.
535
+ 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.
536
+ 4.1-mini is significantly cheaper than full 4.1, with great balance for most structured AI workflows.
537
+ 4.1-nano is best for lightweight classification or routing logic where speed and cost matter more than depth.`,
538
+ default: "gpt-4o",
539
+ },
540
+ objectChat:{
541
+ type:'button',
542
+ display:'Start Chat',
543
+ icon:'ai_assistant',
544
+ onclick:function(object){
545
+ if (!object || !object._id) return '';
546
+ return `_joe.Ai.spawnChatHelper('${object._id}');`;
547
+ },
548
+
549
+ }
550
+
533
551
  };
534
552
 
535
553
  module.exports = fields;
@@ -169,6 +169,56 @@ server.get(JOE.webconfig.joepath+'_www/mcp-schemas.html',auth,function(req,res){
169
169
  res.sendFile(path.join(JOE.joedir,'_www','mcp-schemas.html'));
170
170
  });
171
171
 
172
+ // AI Widget test page (Responses + assistants) – auth protected
173
+ server.get(['/ai-widget-test.html', JOE.webconfig.joepath + 'ai-widget-test.html'], auth, function(req,res){
174
+ var assistantId = (req.query.assistant_id || req.query.assistant || '').trim();
175
+ if (!assistantId && JOE && JOE.Utils && JOE.Utils.Settings) {
176
+ try {
177
+ var settingObj = JOE.Utils.Settings('DEFAULT_AI_ASSISTANT', { object: true });
178
+ assistantId = (settingObj && settingObj.value) || '';
179
+ } catch(e) {
180
+ console.log('[ai-widget-test] error reading DEFAULT_AI_ASSISTANT:', e && e.message);
181
+ }
182
+ }
183
+ var joePath = (JOE && JOE.webconfig && JOE.webconfig.joepath) || '/JsonObjectEditor/';
184
+ var assistantAttr = assistantId ? ' ai_assistant_id="'+assistantId.replace(/"/g,'&quot;')+'"' : '';
185
+
186
+ res.send(`<!doctype html>
187
+ <html>
188
+ <head>
189
+ <meta charset="utf-8">
190
+ <title>JOE AI Widget Test</title>
191
+ <meta name="viewport" content="width=device-width, initial-scale=1">
192
+ <style>
193
+ body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;background:#f3f4f6;}
194
+ h1{margin-top:0;}
195
+ .small{font-size:13px;color:#6b7280;margin-bottom:16px;}
196
+ .container{max-width:480px;margin:0 auto;background:#fff;border-radius:12px;box-shadow:0 4px 14px rgba(0,0,0,0.06);padding:16px;}
197
+ .meta{font-size:12px;color:#6b7280;margin-bottom:8px;}
198
+ code{background:#e5e7eb;border-radius:4px;padding:2px 4px;font-size:12px;}
199
+ </style>
200
+ </head>
201
+ <body>
202
+ <div id="mcp-nav"></div>
203
+ <script src="${joePath}_www/mcp-nav.js"></script>
204
+
205
+ <div class="container">
206
+ <h1>AI Widget Test</h1>
207
+ <div class="small">
208
+ This page mounts <code>&lt;joe-ai-widget&gt;</code> and sends messages through
209
+ <code>/API/plugin/chatgpt/widget*</code> using the OpenAI Responses API.
210
+ ${assistantId ? `Using <code>DEFAULT_AI_ASSISTANT</code> (ai_assistant_id=${assistantId}).` : 'No DEFAULT_AI_ASSISTANT is set; widget will use model defaults.'}
211
+ You can override the assistant via <code>?assistant_id=&lt;ai_assistant _id&gt;</code>.
212
+ </div>
213
+
214
+ <joe-ai-widget id="widget" title="JOE AI Assistant"${assistantAttr}></joe-ai-widget>
215
+ </div>
216
+
217
+ <script src="${joePath}js/joe-ai.js"></script>
218
+ </body>
219
+ </html>`);
220
+ });
221
+
172
222
  server.use(JOE.webconfig.joepath,express.static(JOE.joedir));
173
223
 
174
224
  //USER
@@ -101,7 +101,13 @@ function Storage(specs){
101
101
  try{
102
102
  if(!data.joeUpdated){ data.joeUpdated = new Date().toISOString(); }
103
103
  if(!data.created){
104
- var cachedBefore = (data && data._id && JOE && JOE.Cache && JOE.Cache.findByID) ? JOE.Cache.findByID(collection,data._id) : null;
104
+ var cachedBefore = null;
105
+ // Only use Cache.findByID when _id is a string; ObjectId instances can cause internal split() errors.
106
+ if (data && data._id && typeof data._id === 'string' && JOE && JOE.Cache && JOE.Cache.findByID){
107
+ try {
108
+ cachedBefore = JOE.Cache.findByID(collection, data._id);
109
+ } catch(__e){ cachedBefore = null; }
110
+ }
105
111
  if(!cachedBefore){ data.created = new Date().toISOString(); }
106
112
  }
107
113
  }catch(_e){}
@@ -74,7 +74,8 @@ var utils = function(){
74
74
  module:"\x1b[33m",
75
75
  white:"\x1b[37m",
76
76
  black:'\x1b[30m',
77
- gray:'\x1b[90m'
77
+ gray:'\x1b[90m',
78
+ teal:'\x1b[36m'
78
79
  };
79
80
  var messagestring = '';
80
81
  styles.map(function(i){
@@ -1,8 +1,8 @@
1
1
  function AWSConnect(){
2
2
  var self = this;
3
3
  this.default = function(data,req,res){
4
- // Load the AWS SDK for Node.js
5
- var AWS = require('aws-sdk');
4
+ // AWS SDK v3 (modular)
5
+ const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
6
6
  var settings_config = tryEval(JOE.Cache.settings.AWS_S3CONFIG)||{};
7
7
  var config = $c.merge(settings_config);
8
8
 
@@ -10,10 +10,18 @@ var filename = data.filename || req.params.filename||cuid();
10
10
  var directory = data.directory|| req.params.directory || 'uploads';
11
11
  var Key = data.Key || directory+'/'+filename;
12
12
 
13
- // Set your region for future requests.
14
- AWS.config.update(config);
13
+ // Create S3 client with provided config (region/credentials)
14
+ const s3 = new S3Client({
15
+ region: config.region,
16
+ credentials: (config.accessKeyId && config.secretAccessKey) ? {
17
+ accessKeyId: config.accessKeyId,
18
+ secretAccessKey: config.secretAccessKey,
19
+ sessionToken: config.sessionToken
20
+ } : undefined
21
+ });
15
22
 
16
- var ACL = data.ACL || 'public-read';
23
+ // Default to public-read unless explicitly overridden; allows legacy behavior to keep working
24
+ var ACL = (data.hasOwnProperty('ACL') ? data.ACL : 'public-read');
17
25
  var Bucket = JOE.Cache.settings.AWS_BUCKETNAME;
18
26
  var file = data.file || 'body';
19
27
  // Create a bucket using bound parameters and put something in it.
@@ -25,14 +33,14 @@ var s3Params = {
25
33
 
26
34
  if(data.base64){
27
35
  //var buf = new Buffer(data.base64.replace(/^data:image\/\w+;base64,/, ""),'base64');
28
- var buf = new Buffer(data.base64.replace(/^data:[a-zA-Z0-9\_\-]*\/[a-zA-Z0-9\_\-]*;base64,/, ""),'base64');
36
+ var buf = Buffer.from(data.base64.replace(/^data:[a-zA-Z0-9\_\-]*\/[a-zA-Z0-9\_\-]*;base64,/, ""),'base64');
29
37
  s3Params.Body = buf;
30
38
  s3Params.ContentEncoding = 'base64';
31
- s3Params.ContentType = data.extension || 'image/jpeg';
39
+ // Prefer explicit contentType; fall back to extension or a safe default
40
+ s3Params.ContentType = data.contentType || data.extension || 'application/octet-stream';
32
41
 
33
42
  }
34
43
 
35
- var s3bucket = new AWS.S3(s3Params);
36
44
  var payload = {
37
45
  params:s3Params,
38
46
  config:config,
@@ -45,19 +53,42 @@ var response = {
45
53
  Key:Key,
46
54
  bucket:Bucket
47
55
  }
48
- s3Params.ACL = ACL || 'public-read';
49
- s3bucket.putObject(s3Params, function(err, data) {
50
- response.data = data;
51
- response.error = err;
52
- res.send(response);
56
+ // Early validation
57
+ if(!Bucket){
58
+ return res.status(400).send({ error:'AWS bucket is not configured (settings.AWS_BUCKETNAME).' });
59
+ }
60
+ if(!config || !config.region){
61
+ return res.status(400).send({ error:'AWS S3 region is missing. Set settings.AWS_S3CONFIG.region.' });
62
+ }
63
+
64
+ // Only include ACL if requested (string and not empty)
65
+ if(ACL){
66
+ s3Params.ACL = ACL;
67
+ }
53
68
 
54
- if (err) {
55
- console.log("Error uploading data: ", err);
56
- } else {
57
- console.log("Successfully uploaded data to "+Key);
58
- }
59
- return;
60
- });
69
+ s3.send(new PutObjectCommand(s3Params))
70
+ .then(function(data){
71
+ // Construct canonical URL from region + bucket
72
+ var region = config.region;
73
+ var url = 'https://'+Bucket+'.s3.'+region+'.amazonaws.com/'+Key;
74
+ response.data = data;
75
+ response.url = url;
76
+ res.status(200).send(response);
77
+ console.log("Successfully uploaded data to "+Key);
78
+ })
79
+ .catch(function(err){
80
+ var code = (err && (err.Code || err.name)) || 'UploadError';
81
+ var message = (err && (err.message || String(err))) || 'Upload failed';
82
+ // Clear, actionable error when ACLs are disabled on bucket
83
+ if(code === 'AccessControlListNotSupported'){
84
+ return res.status(400).send({
85
+ error: 'Bucket has ACLs disabled (Object Ownership enforced). Remove ACL or switch to presigned/proxy access.',
86
+ code: code
87
+ });
88
+ }
89
+ console.log("Error uploading data: ", err);
90
+ res.status(500).send({ error: message, code: code });
91
+ });
61
92
  return {use_callback:true};
62
93
 
63
94
  },
@@ -1,6 +1,7 @@
1
-
2
- const { run } = require("googleapis/build/src/apis/run");
1
+ const savedArrayDelete = Array.prototype.delete;
2
+ delete Array.prototype.delete;
3
3
  const OpenAI = require("openai");
4
+ if (savedArrayDelete) Array.prototype.delete = savedArrayDelete;
4
5
 
5
6
  function ChatGPTAssistants() {
6
7
  const self = this;
@@ -67,7 +68,6 @@ function ChatGPTAssistants() {
67
68
  }
68
69
  this.syncAssistantToOpenAI = async function(data, req, res) {
69
70
  const openai = newClient();
70
-
71
71
  try {
72
72
  coloredLog("🔄 Starting syncAssistantToOpenAI");
73
73
 
@@ -0,0 +1,45 @@
1
+ const OpenAI = require("openai");
2
+
3
+ function ChatGPTResponses() {
4
+ const self = this;
5
+
6
+ function log(prefix, message, data) {
7
+ const p = JOE.Utils.color('[chatgpt-responses]', 'plugin', false);
8
+ console.log(p, prefix, message || '', (data !== undefined ? data : ''));
9
+ }
10
+
11
+ this.default = function (data, req, res) {
12
+ try {
13
+ return {
14
+ success: true,
15
+ message: 'chatgpt-responses proxy plugin ready',
16
+ params: req.params,
17
+ query: req.query,
18
+ body: req.body
19
+ };
20
+ } catch (e) {
21
+ return { errors: 'plugin error: ' + e, failedat: 'plugin' };
22
+ }
23
+ };
24
+
25
+ // Thin proxy to chatgpt.autofill to avoid duplication
26
+ this.autofill = async function (data, req, res) {
27
+ try {
28
+ const base = JOE.Apps && JOE.Apps.plugins && JOE.Apps.plugins['chatgpt'];
29
+ if (!base || !base.autofill) {
30
+ return { success: false, error: 'chatgpt.autofill not available' };
31
+ }
32
+ const result = await base.autofill(data, req, res);
33
+ return result;
34
+ } catch (e) {
35
+ log('error', e && e.message);
36
+ return { success: false, error: e && e.message || 'Unknown error' };
37
+ }
38
+ };
39
+
40
+ return self;
41
+ }
42
+
43
+ module.exports = new ChatGPTResponses();
44
+
45
+