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.
@@ -0,0 +1,729 @@
1
+ const OpenAI = require("openai");
2
+ const { google } = require('googleapis');
3
+ const path = require('path');
4
+ // const { name } = require("json-object-editor/server/webconfig");
5
+
6
+ function ChatGPT() {
7
+ // const fetch = (await import('node-fetch')).default;
8
+ //const openai = new OpenAI();
9
+ // Load the service account key JSON file
10
+ const serviceAccountKeyFile = path.join(__dirname, '../local-joe-239900-e9e3b447c70e.json');
11
+ const google_auth = new google.auth.GoogleAuth({
12
+ keyFile: serviceAccountKeyFile,
13
+ scopes: ['https://www.googleapis.com/auth/documents.readonly'],
14
+ });
15
+
16
+ var self = this;
17
+ this.async ={};
18
+ function coloredLog(message){
19
+ console.log(JOE.Utils.color('[chatgpt]', 'plugin', false), message);
20
+ }
21
+ //xx -setup and send a test prompt to chatgpt
22
+ //xx get the api key from joe settings
23
+
24
+ //get a prompt from id
25
+ //send the prompt to chatgpt
26
+
27
+ //++get the cotnent of a file
28
+ //++send the content of a file to chatgpt
29
+
30
+ //++ structure data
31
+ //++ save the response to an ai_repsonse
32
+ //create an ai_response
33
+ //store the content
34
+ //attach to the request
35
+ //store ids sent with the request
36
+ this.default = function(data, req, res) {
37
+ try {
38
+ var payload = {
39
+ params: req.params,
40
+ data: data
41
+ };
42
+ } catch (e) {
43
+ return { errors: 'plugin error: ' + e, failedat: 'plugin' };
44
+ }
45
+ return payload;
46
+ };
47
+ function getAPIKey() {
48
+ const setting = JOE.Utils.Settings('OPENAI_API_KEY');
49
+ if (!setting) throw new Error("Missing OPENAI_API_KEY setting");
50
+ return setting;
51
+ }
52
+ function getSchemaDef(name) {
53
+ if (!name) return { full: null, summary: null };
54
+ const full = JOE.Schemas && JOE.Schemas.schema && JOE.Schemas.schema[name];
55
+ const summary = JOE.Schemas && JOE.Schemas.summary && JOE.Schemas.summary[name];
56
+ return { full, summary };
57
+ }
58
+
59
+ // function newClient(){
60
+ // var key = getAPIKey();
61
+ // var c = new OpenAI({
62
+ // apiKey: key, // This is the default and can be omitted
63
+ // });
64
+ // if(!c || !c.apiKey){
65
+ // return { errors: 'No API key provided' };
66
+ // }
67
+ // return c;
68
+ // }
69
+ function newClient() {
70
+ return new OpenAI({ apiKey: getAPIKey() });
71
+ }
72
+ this.testPrompt= async function(data, req, res) {
73
+ try {
74
+ var payload = {
75
+ params: req.params,
76
+ data: data
77
+ };
78
+ } catch (e) {
79
+ return { errors: 'plugin error: ' + e, failedat: 'plugin' };
80
+ }
81
+ const client = newClient();
82
+ if(client.errors){
83
+ return { errors: client.errors };
84
+ }
85
+ try {
86
+ const chatCompletion = await client.chat.completions.create({
87
+ messages: [{ role: 'user', content: 'Tell me a story about JOE: the json object editor in under 256 chars.' }],
88
+ model: 'gpt-4o',
89
+ });
90
+ coloredLog(chatCompletion);
91
+ return {payload,chatCompletion,content:chatCompletion.choices[0].message.content};
92
+ } catch (error) {
93
+ if (error.status === 429) {
94
+ return { errors: 'You exceeded your current quota, please check your plan and billing details.' };
95
+ } else {
96
+ return { errors: 'plugin error: ' + error.message, failedat: 'plugin' };
97
+ }
98
+ }
99
+ }
100
+
101
+ this.sendInitialConsultTranscript= async function(data, req, res) {
102
+ coloredLog("sendInitialConsultTranscript");
103
+ //get the prompt object from the prompt id
104
+ //get the business object from the refrenced object id
105
+ //see if there is a initial_transcript_url property on that object
106
+ //if there is, get the content of the file
107
+ //send the content to chatgpt, with the template property of the prompt object
108
+ //get the response
109
+ try {
110
+ var payload = {
111
+ params: req.params,
112
+ data: data
113
+ };
114
+ } catch (e) {
115
+ return { errors: 'plugin error: ' + e, failedat: 'plugin' };
116
+ }
117
+ var businessOBJ = JOE.Data.business.find(b=>b._id == data.business);
118
+ var promptOBJ = JOE.Data.ai_prompt.find(p=>p._id == data.ai_prompt);
119
+
120
+
121
+ // See if there is an initial_transcript_url property on that object
122
+ const transcriptUrl = businessOBJ.initial_transcript_url;
123
+ if (!transcriptUrl) {
124
+ return res.jsonp({ error: 'No initial transcript URL found' });
125
+ }
126
+
127
+ //Get the content of the file from Google Docs
128
+ const transcriptContent = await getGoogleDocContent(transcriptUrl);
129
+ if (!transcriptContent || transcriptContent.error) {
130
+ return res.jsonp({ error: (transcriptContent.error && transcriptContent.error.message)||'Failed to fetch transcript content' });
131
+ }
132
+ const tokenCount = countTokens(`${promptOBJ.template}\n\n${transcriptContent}`);
133
+ payload.tokenCount = tokenCount;
134
+ coloredLog("token count: "+tokenCount);
135
+ //return res.jsonp({tokens:tokenCount,content:transcriptContent});
136
+ // Send the content to ChatGPT, with the template property of the prompt object
137
+ const client = new OpenAI({
138
+ apiKey: getAPIKey(), // This is the default and can be omitted
139
+ });
140
+
141
+ const chatResponse = await client.chat.completions.create({
142
+ messages: [{ role: 'user', content: `${promptOBJ.template}\n\n${transcriptContent}` }],
143
+ model: 'gpt-4o',
144
+ });
145
+
146
+ // Get the response
147
+ const chatContent = chatResponse.choices[0].message.content;
148
+ const responseName = `${businessOBJ.name} - ${promptOBJ.name}`;
149
+ // Save the response
150
+ await saveAIResponse({
151
+ name:responseName,
152
+ business: data.business,
153
+ ai_prompt: data.ai_prompt,
154
+ response: chatContent,
155
+ payload,
156
+ prompt_method:req.params.method
157
+ });
158
+ coloredLog("response saved -"+responseName);
159
+ return {payload,
160
+ businessOBJ,
161
+ promptOBJ,
162
+ chatContent,
163
+ responseName
164
+ };
165
+
166
+ }
167
+
168
+ async function getGoogleDocContent(docUrl) {
169
+ try {
170
+ const auth = new google.auth.GoogleAuth({
171
+ scopes: ['https://www.googleapis.com/auth/documents.readonly']
172
+ });
173
+ //get google docs apikey from settings
174
+ const GOOGLE_API_KEY = JOE.Utils.Settings('GOOGLE_DOCS_API_KEY');
175
+ const docs = google.docs({ version: 'v1', auth:google_auth });
176
+ const docId = extractDocIdFromUrl(docUrl);
177
+ const doc = await docs.documents.get({ documentId: docId });
178
+
179
+ let content = doc.data.body.content.map(element => {
180
+ if (element.paragraph && element.paragraph.elements) {
181
+ return element.paragraph.elements.map(
182
+ e => e.textRun ? e.textRun.content.replace(/Euron Nicholson/g, '[EN]').replace(/\d{2}:\d{2}:\d{2}\.\d{3} --> \d{2}:\d{2}:\d{2}\.\d{3}/g, '-ts-')
183
+ : ''
184
+ ).join('');
185
+ }
186
+ return '';
187
+ }).join('\n');
188
+
189
+ // Remove timestamps and line numbers
190
+ //content = content.replace(/^\d+\n\d{2}:\d{2}:\d{2}\.\d{3} --> \d{2}:\d{2}:\d{2}\.\d{3}\n/gm, '');
191
+
192
+ return content;
193
+ } catch (error) {
194
+ console.error('Error fetching Google Doc content:', error);
195
+ return {error};
196
+ }
197
+ }
198
+ function countTokens(text, model = 'gpt-4o') {
199
+ const enc = encoding_for_model(model);
200
+ const tokens = enc.encode(text);
201
+ return tokens.length;
202
+ }
203
+ function extractDocIdFromUrl(url) {
204
+ const match = url.match(/\/d\/([a-zA-Z0-9-_]+)/);
205
+ return match ? match[1] : null;
206
+ }
207
+
208
+ async function saveAIResponse(data) {
209
+ try {
210
+ const aiResponse = {
211
+ name: data.name,
212
+ itemtype: 'ai_response',
213
+ business: data.business,
214
+ ai_prompt: data.ai_prompt,
215
+ response: data.response,
216
+ payload: data.payload,
217
+ prompt_method:data.prompt_method,
218
+ created: (new Date).toISOString(),
219
+ _id:cuid()
220
+ // Add any other fields you want to save
221
+ };
222
+ await new Promise((resolve, reject) => {
223
+ JOE.Storage.save(aiResponse, 'ai_response', function(err, result) {
224
+ if (err) {
225
+ coloredLog('Error saving AI response: ' + err);
226
+ reject(err);
227
+ } else {
228
+ coloredLog('AI response saved successfully');
229
+ resolve(result);
230
+ }
231
+ });
232
+ });
233
+ } catch (error) {
234
+ coloredLog('Error in saveAIResponse: ' + error);
235
+ }
236
+ }
237
+
238
+ // Autofill feature (Responses API; supports assistant_id or model)
239
+ this.autofill = async function (data, req, res) {
240
+ const startedAt = Date.now();
241
+ try {
242
+ const body = data || {};
243
+ const objectId = body.object_id || body._id;
244
+ const object = body.object || $J.get(objectId);
245
+ const schemaName = body.schema || (object && object.itemtype) || body.itemtype;
246
+ const { full: schemaFull, summary: schemaSummary } = getSchemaDef(schemaName);
247
+ const rawFields = body.fields || body.field;
248
+ const fields = Array.isArray(rawFields) ? rawFields : (rawFields ? [rawFields] : []);
249
+ const userPrompt = body.prompt || '';
250
+ const assistantId = body.assistant_id || null;
251
+
252
+ if (!object) {
253
+ return { success: false, error: 'Object not found', code: 'OBJECT_NOT_FOUND' };
254
+ }
255
+ if (!schemaName) {
256
+ return { success: false, error: 'Schema name not determined', code: 'SCHEMA_REQUIRED' };
257
+ }
258
+ if (!fields.length) {
259
+ return { success: false, error: 'No fields specified', code: 'FIELDS_REQUIRED' };
260
+ }
261
+
262
+ const flattened = JOE.Utils.flattenObject(object._id);
263
+ const systemText = [
264
+ 'You are JOE (Json Object Editor) assistant.',
265
+ 'Task: Populate only the requested fields according to the provided schema context and JOE conventions.',
266
+ '- Respect field types (text, number, arrays, enums, references).',
267
+ '- Do NOT invent IDs for reference fields; only return human text for text-like fields.',
268
+ '- If a field is an enum, choose the closest valid enum. If unsure, omit it from patch.',
269
+ '- If a field is an array, return an array of values.',
270
+ '- Never modify unrelated fields.',
271
+ '- Output MUST be strict JSON with a top-level key "patch" containing only populated fields.',
272
+ '- If you lack sufficient information, return an empty patch.'
273
+ ].join('\\n');
274
+
275
+ const schemaForContext = schemaSummary || schemaFull || {};
276
+ const userInput = JSON.stringify({
277
+ action: 'autofill_fields',
278
+ target_schema: schemaName,
279
+ requested_fields: fields,
280
+ user_prompt: userPrompt,
281
+ object_context: flattened,
282
+ schema_context: schemaForContext
283
+ }, null, ' ');
284
+
285
+ const openai = newClient();
286
+ const model = body.model || 'gpt-4o-mini';
287
+
288
+ const requestBase = {
289
+ response_format: {
290
+ type: 'json_schema',
291
+ json_schema: {
292
+ name: 'joe_autofill_patch',
293
+ strict: true,
294
+ schema: {
295
+ type: 'object',
296
+ additionalProperties: false,
297
+ required: ['patch'],
298
+ properties: {
299
+ patch: { type: 'object', additionalProperties: true }
300
+ }
301
+ }
302
+ }
303
+ },
304
+ temperature: 0.2,
305
+ instructions: systemText,
306
+ input: userInput
307
+ };
308
+
309
+ let response;
310
+ if (assistantId) {
311
+ response = await openai.responses.create({ assistant_id: assistantId, ...requestBase });
312
+ } else {
313
+ response = await openai.responses.create({ model, ...requestBase });
314
+ }
315
+
316
+ let textOut = '';
317
+ try { textOut = response.output_text || ''; } catch (_e) {}
318
+ if (!textOut && response && Array.isArray(response.output)) {
319
+ for (let i = 0; i < response.output.length; i++) {
320
+ const item = response.output[i];
321
+ if (item && item.type === 'message' && item.content && Array.isArray(item.content)) {
322
+ const textPart = item.content.find(function (c) { return c.type === 'output_text' || c.type === 'text'; });
323
+ if (textPart && (textPart.text || textPart.output_text)) {
324
+ textOut = textPart.text || textPart.output_text;
325
+ break;
326
+ }
327
+ }
328
+ }
329
+ }
330
+
331
+ let patch = {};
332
+ try {
333
+ const parsed = JSON.parse(textOut || '{}');
334
+ patch = parsed.patch || {};
335
+ } catch (_e) {}
336
+
337
+ const filteredPatch = {};
338
+ fields.forEach(function (f) {
339
+ if (Object.prototype.hasOwnProperty.call(patch, f)) {
340
+ filteredPatch[f] = patch[f];
341
+ }
342
+ });
343
+
344
+ // Optional save
345
+ let savedItem = null;
346
+ if (body.save_history || body.save_itemtype) {
347
+ const targetItemtype = body.save_itemtype || 'ai_response';
348
+ if (JOE.Schemas && JOE.Schemas.schema && JOE.Schemas.schema[targetItemtype]) {
349
+ const saveObj = {
350
+ itemtype: targetItemtype,
351
+ name: `[${schemaName}] autofill → ${fields.join(', ')}`,
352
+ object_id: object._id,
353
+ target_schema: schemaName,
354
+ fields,
355
+ prompt: userPrompt,
356
+ patch: filteredPatch,
357
+ model,
358
+ raw: { response }
359
+ };
360
+ await new Promise(function (resolve) {
361
+ JOE.Storage.save(saveObj, targetItemtype, function (_err, saved) {
362
+ savedItem = saved || null;
363
+ resolve();
364
+ });
365
+ });
366
+ }
367
+ }
368
+
369
+ return {
370
+ success: true,
371
+ patch: filteredPatch,
372
+ model,
373
+ usage: response && response.usage,
374
+ saved: !!savedItem,
375
+ saved_item: savedItem,
376
+ elapsed_ms: Date.now() - startedAt
377
+ };
378
+ } catch (e) {
379
+ return { success: false, error: e && e.message || 'Unknown error' };
380
+ }
381
+ };
382
+
383
+ this.getResponse = function(data, req, res) {
384
+ try {
385
+ var prompt = data.prompt;
386
+ if (!prompt) {
387
+ return { error: 'No prompt provided' };
388
+ }
389
+
390
+ // Simulate a response from ChatGPT
391
+ var response = `ChatGPT response to: ${prompt}`;
392
+ res.jsonp({ response: response });
393
+ return { use_callback: true };
394
+ } catch (e) {
395
+ return { errors: 'plugin error: ' + e, failedat: 'plugin' };
396
+ }
397
+ };
398
+
399
+ this.html = function(data, req, res) {
400
+ return JSON.stringify(self.default(data, req), '', '\t\r\n <br/>');
401
+ };
402
+ /* NEW AI RESPONSE API*/
403
+
404
+ this.executeJOEAiPrompt = async function(data, req, res) {
405
+ const referencedObjectIds = []; // Track all objects touched during helper function
406
+ try {
407
+ const promptId = data.ai_prompt;
408
+ const params = data;
409
+
410
+ if (!promptId) {
411
+ return { error: "Missing prompt_id." };
412
+ }
413
+
414
+ const prompt = await $J.get(promptId); // Use $J.get for consistency
415
+ if (!prompt) {
416
+ return { error: "Prompt not found." };
417
+ }
418
+
419
+ let instructions = prompt.instructions || "";
420
+ let finalInstructions=instructions;
421
+ let finalInput='';
422
+ // Pre-load all content_objects if content_items exist
423
+ const contentObjects = {};
424
+
425
+ if (prompt.content_items && Array.isArray(prompt.content_items)) {
426
+ for (const content of prompt.content_items) {
427
+ if (params[content.reference]) {
428
+ const obj = $J.get(params[content.reference]);
429
+ if (obj) {
430
+ contentObjects[content.itemtype] = obj;
431
+
432
+ // Pre-track referenced object
433
+ if (obj._id && !referencedObjectIds.includes(obj._id)) {
434
+ referencedObjectIds.push(obj._id);
435
+ }
436
+ }
437
+ }
438
+ }
439
+ }
440
+
441
+ // Execute any helper functions if present
442
+ if (prompt.functions) {
443
+ const modFunc = JOE.Utils.requireFromString(prompt.functions, prompt._id);
444
+ const helperResult = await modFunc({
445
+ instructions,
446
+ params,
447
+ ai_prompt: prompt,
448
+ content_objects: contentObjects,
449
+ trackObject: (obj) => {
450
+ if (obj?._id && !referencedObjectIds.includes(obj._id)) {
451
+ referencedObjectIds.push(obj._id);
452
+ }
453
+ }
454
+ });
455
+
456
+ if (typeof helperResult === 'object' && helperResult.error) {
457
+ return { error: helperResult.error };
458
+ }
459
+
460
+ // Assume the result is { instructions, input }
461
+ finalInstructions = helperResult.instructions || instructions;
462
+ finalInput = helperResult.input;
463
+ }
464
+
465
+ const openai = newClient(); // however your OpenAI client is created
466
+
467
+ const payload = {
468
+ model: prompt.ai_model || "gpt-4o",
469
+ instructions: finalInstructions||instructions, // string only
470
+ input:finalInput||'',
471
+ tools: prompt.tools || [{ "type": "web_search" }],
472
+ tool_choice: prompt.tool_choice || "auto",
473
+ temperature: prompt.temperature ? parseFloat(prompt.temperature) : 0.7,
474
+ //return_token_usage: true
475
+ //max_tokens: prompt.max_tokens ?? 1200
476
+ };
477
+
478
+ const response = await openai.responses.create(payload);
479
+
480
+
481
+ // const payload = createResponsePayload(prompt, params, instructions, data.user_prompt);
482
+
483
+ // const response = await openai.chat.completions.create(payload);
484
+
485
+ const saved = await saveAiResponseRefactor({
486
+ prompt,
487
+ ai_response_content: response.output_text || "",
488
+ user_prompt: payload.input,
489
+ params,
490
+ referenced_object_ids: referencedObjectIds,
491
+ response_id:response.id,
492
+ usage: response.usage || {}
493
+ });
494
+
495
+ return { success: true, ai_response_id: saved._id,response:response.output_text || "",usage:response.usage };
496
+ } catch (e) {
497
+ console.error('❌ executeJOEAiPrompt error:', e);
498
+ return { error: "Failed to execute AI prompt.",message: e.message };
499
+ }
500
+ };
501
+
502
+ function createResponsePayload(prompt, params, instructions, user_prompt) {
503
+ return {
504
+ model: prompt.model || "gpt-4o",
505
+ messages: [
506
+ { role: "system", content: instructions },
507
+ { role: "user", content: user_prompt || "" }
508
+ ],
509
+ tools: prompt.tools || undefined,
510
+ tool_choice: prompt.tool_choice || "auto",
511
+ temperature: prompt.temperature ?? 0.7,
512
+ max_tokens: prompt.max_tokens ?? 1200
513
+ };
514
+ }
515
+ async function saveAiResponseRefactor({ prompt, ai_response_content, user_prompt, params, referenced_object_ids,response_id,usage}) {
516
+ var response_keys = [];
517
+ try {
518
+ response_keys = Object.keys(JSON.parse(ai_response_content));
519
+ }catch (e) {
520
+ console.error('❌ Error parsing AI response content for keys:', e);
521
+ }
522
+ const aiResponse = {
523
+ name: `${prompt.name}`,
524
+ itemtype: 'ai_response',
525
+ ai_prompt: prompt._id,
526
+ prompt_name: prompt.name,
527
+ prompt_method:prompt.prompt_method,
528
+ response: ai_response_content,
529
+ response_keys: response_keys,
530
+ response_id:response_id||'',
531
+ user_prompt: user_prompt,
532
+ params_used: params,
533
+ usage: usage || {},
534
+ tags: prompt.tags || [],
535
+ model_used: prompt.ai_model || "gpt-4o",
536
+ referenced_objects: referenced_object_ids, // new flexible array of referenced object ids
537
+ created: (new Date).toISOString(),
538
+ _id: cuid()
539
+ };
540
+
541
+ await new Promise((resolve, reject) => {
542
+ JOE.Storage.save(aiResponse, 'ai_response', function(err, result) {
543
+ if (err) {
544
+ console.error('❌ Error saving AI response:', err);
545
+ reject(err);
546
+ } else {
547
+ console.log('✅ AI response saved successfully');
548
+ resolve(result);
549
+ }
550
+ });
551
+ });
552
+
553
+ return aiResponse;
554
+ }
555
+
556
+ // ---------- Widget chat endpoints (Responses API + optional assistants) ----------
557
+ function normalizeMessages(messages) {
558
+ if (!Array.isArray(messages)) { return []; }
559
+ return messages.map(function (m) {
560
+ return {
561
+ role: m.role || 'assistant',
562
+ content: m.content || '',
563
+ created_at: m.created_at || m.created || new Date().toISOString()
564
+ };
565
+ });
566
+ }
567
+
568
+ this.widgetStart = async function (data, req, res) {
569
+ try {
570
+ var body = data || {};
571
+ var model = body.model || "gpt-4.1-mini";
572
+ var assistant = body.ai_assistant_id ? $J.get(body.ai_assistant_id) : null;
573
+ var system = body.system || (assistant && assistant.instructions) || "";
574
+
575
+ var convo = {
576
+ _id: (typeof cuid === 'function') ? cuid() : undefined,
577
+ itemtype: "ai_widget_conversation",
578
+ model: assistant && assistant.ai_model || model,
579
+ assistant: assistant && assistant._id,
580
+ assistant_id: assistant && assistant.assistant_id,
581
+ system: system,
582
+ messages: [],
583
+ source: body.source || "widget",
584
+ created: new Date().toISOString(),
585
+ joeUpdated: new Date().toISOString()
586
+ };
587
+
588
+ const saved = await new Promise(function (resolve, reject) {
589
+ JOE.Storage.save(convo, "ai_widget_conversation", function (err, result) {
590
+ if (err) return reject(err);
591
+ resolve(result);
592
+ });
593
+ });
594
+
595
+ return {
596
+ success: true,
597
+ conversation_id: saved._id,
598
+ model: saved.model,
599
+ assistant_id: saved.assistant_id || null
600
+ };
601
+ } catch (e) {
602
+ console.error("[chatgpt] widgetStart error:", e);
603
+ return { success: false, error: e && e.message || "Unknown error" };
604
+ }
605
+ };
606
+
607
+ this.widgetHistory = async function (data, req, res) {
608
+ try {
609
+ var conversation_id = data.conversation_id || data._id;
610
+ if (!conversation_id) {
611
+ return { success: false, error: "Missing conversation_id" };
612
+ }
613
+ const convo = await new Promise(function (resolve, reject) {
614
+ JOE.Storage.load("ai_widget_conversation", { _id: conversation_id }, function (err, results) {
615
+ if (err) return reject(err);
616
+ resolve(results && results[0]);
617
+ });
618
+ });
619
+ if (!convo) {
620
+ return { success: false, error: "Conversation not found" };
621
+ }
622
+
623
+ convo.messages = normalizeMessages(convo.messages);
624
+ return {
625
+ success: true,
626
+ conversation_id: convo._id,
627
+ model: convo.model,
628
+ assistant_id: convo.assistant_id || null,
629
+ messages: convo.messages
630
+ };
631
+ } catch (e) {
632
+ console.error("[chatgpt] widgetHistory error:", e);
633
+ return { success: false, error: e && e.message || "Unknown error" };
634
+ }
635
+ };
636
+
637
+ this.widgetMessage = async function (data, req, res) {
638
+ try {
639
+ var body = data || {};
640
+ var conversation_id = body.conversation_id || body._id;
641
+ var content = body.content;
642
+ var role = body.role || "user";
643
+
644
+ if (!conversation_id || !content) {
645
+ return { success: false, error: "Missing conversation_id or content" };
646
+ }
647
+
648
+ const convo = await new Promise(function (resolve, reject) {
649
+ JOE.Storage.load("ai_widget_conversation", { _id: conversation_id }, function (err, results) {
650
+ if (err) return reject(err);
651
+ resolve(results && results[0]);
652
+ });
653
+ });
654
+ if (!convo) {
655
+ return { success: false, error: "Conversation not found" };
656
+ }
657
+
658
+ convo.messages = normalizeMessages(convo.messages);
659
+ const nowIso = new Date().toISOString();
660
+
661
+ // Append user message
662
+ const userMsg = { role: role, content: content, created_at: nowIso };
663
+ convo.messages.push(userMsg);
664
+
665
+ const openai = newClient();
666
+ const assistantId = body.assistant_id || convo.assistant_id || null;
667
+ const model = convo.model || body.model || "gpt-4.1-mini";
668
+
669
+ const systemText = convo.system || "";
670
+ const messagesForModel = convo.messages.map(function (m) {
671
+ return { role: m.role, content: m.content };
672
+ });
673
+
674
+ let response;
675
+ if (assistantId) {
676
+ response = await openai.responses.create({
677
+ assistant_id: assistantId,
678
+ input: messagesForModel
679
+ });
680
+ } else {
681
+ response = await openai.responses.create({
682
+ model: model,
683
+ instructions: systemText,
684
+ input: messagesForModel
685
+ });
686
+ }
687
+
688
+ const assistantText = response.output_text || "";
689
+ const assistantMsg = {
690
+ role: "assistant",
691
+ content: assistantText,
692
+ created_at: new Date().toISOString()
693
+ };
694
+ convo.messages.push(assistantMsg);
695
+ convo.last_message_at = assistantMsg.created_at;
696
+ convo.joeUpdated = assistantMsg.created_at;
697
+
698
+ await new Promise(function (resolve, reject) {
699
+ JOE.Storage.save(convo, "ai_widget_conversation", function (err, saved) {
700
+ if (err) return reject(err);
701
+ resolve(saved);
702
+ });
703
+ });
704
+
705
+ return {
706
+ success: true,
707
+ conversation_id: convo._id,
708
+ model: model,
709
+ assistant_id: assistantId,
710
+ messages: convo.messages,
711
+ last_message: assistantMsg,
712
+ usage: response.usage || {}
713
+ };
714
+ } catch (e) {
715
+ console.error("[chatgpt] widgetMessage error:", e);
716
+ return { success: false, error: e && e.message || "Unknown error" };
717
+ }
718
+ };
719
+
720
+ this.async={
721
+ executeJOEAiPrompt:this.executeJOEAiPrompt,
722
+ testPrompt:this.testPrompt,
723
+ sendInitialConsultTranscript:this.sendInitialConsultTranscript,
724
+ }
725
+ this.protected = [,'testPrompt'];
726
+ return self;
727
+ }
728
+
729
+ module.exports = new ChatGPT();