json-object-editor 0.10.425 → 0.10.430
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 +1 -0
- package/capp/capp.css +8 -1
- package/capp/capp.js +12 -5
- package/css/joe-ai.css +172 -0
- package/css/joe-styles.css +15 -1
- package/css/joe.css +15 -7
- package/js/JsonObjectEditor.jquery.craydent.js +4 -1
- package/js/joe-ai copy.js +517 -0
- package/js/joe-ai.js +281 -158
- package/js/joe-full.js +4 -1
- package/js/joe.js +4 -7
- package/package.json +1 -1
- package/pages/template.html +2 -1
- package/server/app-config.js +1 -1
- package/server/init.js +1 -1
- package/server/modules/Apps.js +2 -1
- package/server/modules/Server.js +29 -5
- package/server/modules/UniversalShorthand.js +1 -2
- package/server/modules/Utils.js +62 -0
- package/server/schemas/ai_conversation.js +23 -14
- package/server/schemas/ai_tool.js +84 -0
package/js/joe-ai.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const Ai = {};
|
|
4
4
|
const self = this;
|
|
5
5
|
Ai._openChats = {}; // Conversation ID -> element
|
|
6
|
-
Ai.default_ai = null; // Default AI assistant
|
|
6
|
+
Ai.default_ai = null; // Default AI assistant object
|
|
7
7
|
// ========== COMPONENTS ==========
|
|
8
8
|
|
|
9
9
|
class JoeAIChatbox extends HTMLElement {
|
|
@@ -13,10 +13,15 @@
|
|
|
13
13
|
|
|
14
14
|
this.attachShadow({ mode: 'open' });
|
|
15
15
|
this.messages = [];
|
|
16
|
-
|
|
16
|
+
this.UI={header:null,content:null,footer:null};
|
|
17
|
+
this.conversation = null;
|
|
18
|
+
this.conversation_id = null;
|
|
19
|
+
this.currentRunId = null;
|
|
20
|
+
this.thread_id = null;
|
|
21
|
+
this.user_id = null;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
connectedCallback() {
|
|
24
|
+
async connectedCallback() {
|
|
20
25
|
this.conversation_id = this.getAttribute('conversation_id');
|
|
21
26
|
|
|
22
27
|
this.selected_assistant_id = Ai.default_ai?Ai.default_ai.value:null;
|
|
@@ -25,9 +30,102 @@
|
|
|
25
30
|
this.renderError("Missing conversation_id");
|
|
26
31
|
return;
|
|
27
32
|
}
|
|
28
|
-
|
|
33
|
+
|
|
34
|
+
var c = await this.loadConversation();
|
|
29
35
|
this.getAllAssistants();
|
|
36
|
+
this.setupUI();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setupUI() {
|
|
40
|
+
// Set up UI elements and event listeners
|
|
41
|
+
// Inject external CSS
|
|
42
|
+
const styleLink = document.createElement('link');
|
|
43
|
+
styleLink.setAttribute('rel', 'stylesheet');
|
|
44
|
+
styleLink.setAttribute('href', '/JsonObjectEditor/css/joe-ai.css'); // Adjust path as needed
|
|
45
|
+
this.shadowRoot.appendChild(styleLink);
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Ai.default_ai
|
|
49
|
+
/*HEADER*/
|
|
50
|
+
Ai.getDefaultAssistant();
|
|
51
|
+
const assistantOptions = _joe.Data.ai_assistant.map(a => {
|
|
52
|
+
|
|
53
|
+
const label = a.name || a.title || 'Assistant';
|
|
54
|
+
const value = a._id;
|
|
55
|
+
const selected = value === this.selected_assistant_id ? 'selected' : '';
|
|
56
|
+
|
|
57
|
+
return `<option value="${value}" ${selected}>${label}</option>`;
|
|
58
|
+
}).join('');
|
|
59
|
+
|
|
60
|
+
const assistantSelect = `
|
|
61
|
+
<label-select-wrapper>
|
|
62
|
+
<label class="assistant-select-label" title="joe ai assistants">${_joe && _joe.SVG.icon.assistant}</label>
|
|
63
|
+
<select id="assistant-select">
|
|
64
|
+
${assistantOptions}
|
|
65
|
+
</select>
|
|
66
|
+
</label-select-wrapper>
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
/*CONTENT*/
|
|
70
|
+
const chatMessages = this.messages.map(msg => this.renderMessage(msg)).reverse().join('');
|
|
71
|
+
|
|
72
|
+
// Build inner HTML in a wrapper
|
|
73
|
+
const wrapper = document.createElement('chatbox-wrapper');
|
|
74
|
+
wrapper.className = 'chatbox';
|
|
75
|
+
wrapper.innerHTML = `
|
|
76
|
+
<div class="close-btn" title="Close Chatbox" >${_joe.SVG.icon.close}</div>
|
|
77
|
+
<chat-header>
|
|
78
|
+
<chat-title>${this.conversation.name}</chat-title>
|
|
79
|
+
<p>${this.conversation.info||''}</p>
|
|
80
|
+
${assistantSelect}
|
|
81
|
+
</chat-header>
|
|
82
|
+
<chat-content>${chatMessages}</chat-content>
|
|
83
|
+
<chat-footer>
|
|
84
|
+
<textarea id="chat-input" type="text" placeholder="Type a message..."></textarea>
|
|
85
|
+
<button id="send-button">Send</button>
|
|
86
|
+
</chat-footer>
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
this.shadowRoot.appendChild(wrapper);
|
|
90
|
+
|
|
91
|
+
['header','content','header'].map(u=>{
|
|
92
|
+
this.UI[u] = this.shadowRoot.querySelector('chat-'+u);
|
|
93
|
+
})
|
|
94
|
+
this.UI.content.update = this.updateMessages.bind(this);
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
this.UI.content.scrollTop = this.UI.content.scrollHeight;
|
|
97
|
+
}, 100);
|
|
98
|
+
this.UI.content.scrollTop = this.UI.content.scrollHeight;
|
|
99
|
+
|
|
100
|
+
this.UI.textarea = this.shadowRoot.getElementById('chat-input');
|
|
101
|
+
this.UI.textarea.addEventListener('keydown', (e) => {
|
|
102
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
103
|
+
e.preventDefault(); // Prevent newline if needed
|
|
104
|
+
console.log('Enter pressed!');
|
|
105
|
+
this.sendMessage();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
30
108
|
|
|
109
|
+
this.UI.sendButton = this.shadowRoot.getElementById('send-button');
|
|
110
|
+
this.UI.sendButton.addEventListener('click', () => this.sendMessage());
|
|
111
|
+
|
|
112
|
+
// Wire up the close button
|
|
113
|
+
this.shadowRoot.querySelector('.close-btn').addEventListener('click', () => {
|
|
114
|
+
//this.closeChat()
|
|
115
|
+
this.closest('joe-ai-chatbox').closeChat();
|
|
116
|
+
});
|
|
117
|
+
// this.ui.assistant_select = this.shadowRoot.querySelector('#assistant-select');
|
|
118
|
+
// this.ui.assistant_select?.addEventListener('change', (e) => {
|
|
119
|
+
// this.selected_assistant_id = e.target.value;
|
|
120
|
+
// });
|
|
121
|
+
//this.selected_assistant_id = this.ui.assistant_select?.value || null;
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
updateMessages(messages){
|
|
125
|
+
messages && (this.messages = messages);
|
|
126
|
+
const chatMessages = this.messages.map(msg => this.renderMessage(msg)).reverse().join('');
|
|
127
|
+
this.UI.content.innerHTML = chatMessages;
|
|
128
|
+
this.UI.content.scrollTop = this.UI.content.scrollHeight;
|
|
31
129
|
}
|
|
32
130
|
async getAllAssistants() {
|
|
33
131
|
const res = await fetch('/API/item/ai_assistant');
|
|
@@ -51,6 +149,7 @@
|
|
|
51
149
|
|
|
52
150
|
}
|
|
53
151
|
async loadConversation() {
|
|
152
|
+
//load conversation and messages into this
|
|
54
153
|
try {
|
|
55
154
|
const res = await fetch(`/API/object/ai_conversation/_id/${this.conversation_id}`);
|
|
56
155
|
const convo = await res.json();
|
|
@@ -66,9 +165,11 @@
|
|
|
66
165
|
const resThread = await fetch(`/API/plugin/chatgpt-assistants/getThreadMessages?thread_id=${convo.thread_id}`);
|
|
67
166
|
const threadMessages = await resThread.json();
|
|
68
167
|
this.messages = threadMessages?.messages || [];
|
|
168
|
+
this.thread_id = convo.thread_id;
|
|
169
|
+
this.user = $J.get(convo.user);
|
|
69
170
|
}
|
|
70
171
|
|
|
71
|
-
this.
|
|
172
|
+
return {conversation:convo,messages:this.messages};
|
|
72
173
|
} catch (err) {
|
|
73
174
|
console.error("Chatbox load error:", err);
|
|
74
175
|
this.renderError("Error loading conversation.");
|
|
@@ -87,121 +188,40 @@
|
|
|
87
188
|
const convoName = this.conversation.name || "Untitled Conversation";
|
|
88
189
|
const convoInfo = this.conversation.info || "";
|
|
89
190
|
|
|
90
|
-
const chatMessages = this.messages.map(msg => this.renderMessage(msg)).reverse().join('');
|
|
91
191
|
|
|
92
|
-
const assistantOptions = (this.conversation.assistants || []).map(a => {
|
|
93
|
-
const meta = this.getAssistantInfo(a) || {};
|
|
94
|
-
|
|
95
|
-
const label = meta.name || a.name || a.title || 'Assistant';
|
|
96
|
-
const value = meta._id;
|
|
97
|
-
const selected = value === this.selected_assistant_id ? 'selected' : '';
|
|
98
|
-
|
|
99
|
-
return `<option value="${value}" ${selected}>${label}</option>`;
|
|
100
|
-
}).join('');
|
|
101
|
-
|
|
102
|
-
const assistantSelect = `
|
|
103
|
-
<label-select-wrapper>
|
|
104
|
-
<label class="assistant-select-label" title="joe ai assistants">${_joe && _joe.SVG.icon.assistant}</label>
|
|
105
|
-
<select id="assistant-select">
|
|
106
|
-
${assistantOptions}
|
|
107
|
-
</select>
|
|
108
|
-
</label-select-wrapper>
|
|
109
|
-
`;
|
|
110
192
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
.close-btn {
|
|
118
|
-
position: absolute;
|
|
119
|
-
top: 8px;
|
|
120
|
-
right: 8px;
|
|
121
|
-
width: 24px;
|
|
122
|
-
height: 24px;
|
|
123
|
-
cursor: pointer;
|
|
124
|
-
}
|
|
125
|
-
.close-btn svg { width:100%; height:100%; }
|
|
126
|
-
.header { margin-bottom:10px; }
|
|
127
|
-
.header h2 { margin:0; font-size:18px; }
|
|
128
|
-
.header p { margin:4px 0 0 0; font-size:12px; color:#666; }
|
|
129
|
-
.header svg{
|
|
130
|
-
width: 36px;
|
|
131
|
-
height: 36px;
|
|
132
|
-
}
|
|
133
|
-
label-select-wrapper{
|
|
134
|
-
display: flex; align-items: center; gap: 8px;
|
|
135
|
-
}
|
|
136
|
-
.messages { flex:1; overflow:auto; max-height:300px; margin-bottom:10px;
|
|
137
|
-
background: #fff;
|
|
138
|
-
border: 1px solid #ccc;
|
|
139
|
-
}
|
|
140
|
-
.thinking-message {
|
|
141
|
-
font-size: 13px;
|
|
142
|
-
color: #888;
|
|
143
|
-
font-style: italic;
|
|
144
|
-
margin: 10px 0;
|
|
145
|
-
text-align: center;
|
|
146
|
-
}
|
|
147
|
-
.meta {
|
|
148
|
-
font-size: 14px;
|
|
149
|
-
font-weight: bold;
|
|
150
|
-
margin-bottom: 2px;
|
|
151
|
-
display: flex;
|
|
152
|
-
align-items: center;
|
|
153
|
-
gap: 8px;
|
|
154
|
-
color:#99999999;
|
|
155
|
-
}
|
|
156
|
-
.content {
|
|
157
|
-
font-size: 14px;
|
|
158
|
-
white-space: pre-wrap;
|
|
159
|
-
}
|
|
160
|
-
.inputRow { display:flex; gap:8px; }
|
|
161
|
-
input[type="text"] { flex:1; padding:8px; border-radius:4px; border:1px solid #ccc; }
|
|
162
|
-
button { padding:8px 12px; background:#007bff; color:white; border:none; border-radius:4px; cursor:pointer; }
|
|
163
|
-
button:disabled { opacity:0.5; cursor:default; }
|
|
164
|
-
.message { margin-bottom:10px; padding:8px; border-radius:6px; background:#f5f5f5; }
|
|
165
|
-
.message.assistant { background:#eef; }
|
|
166
|
-
.message.system { background:#fee; }
|
|
167
|
-
</style>
|
|
168
|
-
|
|
169
|
-
<div class="chatbox">
|
|
170
|
-
<div class="close-btn" title="Close Chatbox">
|
|
171
|
-
${_joe.SVG.icon.close}
|
|
172
|
-
</div>
|
|
173
|
-
<div class="header">
|
|
174
|
-
<h2>${convoName}</h2>
|
|
175
|
-
<p>${convoInfo}</p>
|
|
176
|
-
${assistantSelect}
|
|
177
|
-
</div>
|
|
178
|
-
<div class="messages">
|
|
179
|
-
${chatMessages}
|
|
180
|
-
</div>
|
|
181
|
-
<div class="inputRow">
|
|
182
|
-
<input id="chat-input" type="text" placeholder="Type a message..." />
|
|
183
|
-
<button id="send-button">Send</button>
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
`;
|
|
187
|
-
const messagesDiv = this.shadowRoot.querySelector('.messages');
|
|
188
|
-
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
189
|
-
this.shadowRoot.getElementById('send-button').addEventListener('click', () => this.sendMessage());
|
|
190
|
-
|
|
191
|
-
// Wire up the close button
|
|
192
|
-
this.shadowRoot.querySelector('.close-btn').addEventListener('click', () => this.closeChat());
|
|
193
|
-
this.ui.assistant_select = this.shadowRoot.querySelector('#assistant-select');
|
|
194
|
-
this.ui.assistant_select?.addEventListener('change', (e) => {
|
|
195
|
-
this.selected_assistant_id = e.target.value;
|
|
196
|
-
});
|
|
197
|
-
this.selected_assistant_id = this.ui.assistant_select?.value || null;
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
198
|
|
|
199
199
|
}
|
|
200
|
-
|
|
200
|
+
processMessageText(text) {
|
|
201
|
+
|
|
202
|
+
// const replaced = text.replace(
|
|
203
|
+
// /\{\{\{BEGIN_OBJECT:(.*?)\}\}\}[\s\S]*?\{\{\{END_OBJECT:\1\}\}\}/g,
|
|
204
|
+
// (match, cuid) => `<joe-object object_id="${cuid}"></joe-object>`
|
|
205
|
+
// );
|
|
206
|
+
let didReplace = false;
|
|
207
|
+
|
|
208
|
+
const replaced = text.replace(
|
|
209
|
+
/\{\{\{BEGIN_OBJECT:(.*?)\}\}\}[\s\S]*?\{\{\{END_OBJECT:\1\}\}\}/g,
|
|
210
|
+
(match, cuid) => {
|
|
211
|
+
didReplace = true;
|
|
212
|
+
return `<joe-object object_id="${cuid}"></joe-object>`;
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
return {text:replaced, replaced:didReplace};
|
|
217
|
+
}
|
|
201
218
|
renderMessage(msg) {
|
|
202
219
|
const role = msg.role || 'user';
|
|
203
220
|
const classes = `message ${role}`;
|
|
204
|
-
|
|
221
|
+
var username = role;
|
|
222
|
+
if(role === 'user'){
|
|
223
|
+
username = this.user.name||'User';
|
|
224
|
+
}
|
|
205
225
|
let contentText = '';
|
|
206
226
|
|
|
207
227
|
if (Array.isArray(msg.content)) {
|
|
@@ -217,14 +237,22 @@
|
|
|
217
237
|
} else {
|
|
218
238
|
contentText = '[Unsupported message format]';
|
|
219
239
|
}
|
|
220
|
-
|
|
240
|
+
|
|
241
|
+
const ctInfo = this.processMessageText(contentText);
|
|
242
|
+
contentText = ctInfo.text;
|
|
243
|
+
if(ctInfo.replaced){
|
|
244
|
+
username = 'platform'
|
|
245
|
+
}
|
|
246
|
+
|
|
221
247
|
// Build timestamp
|
|
222
248
|
const createdAt = msg.created_at ? new Date(msg.created_at * 1000) : null; // OpenAI sends timestamps in seconds
|
|
223
249
|
const timestamp = createdAt ? createdAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '';
|
|
224
250
|
|
|
225
251
|
return `
|
|
226
252
|
<div class="${classes}">
|
|
227
|
-
<div class="meta"
|
|
253
|
+
<div class="meta">
|
|
254
|
+
<participant-name>${username.toUpperCase()}</participant-name>
|
|
255
|
+
${timestamp ? `<span class="timestamp">${timestamp}</span>` : ''}</div>
|
|
228
256
|
<div class="content">${contentText}</div>
|
|
229
257
|
</div>
|
|
230
258
|
`;
|
|
@@ -233,14 +261,29 @@
|
|
|
233
261
|
renderError(message) {
|
|
234
262
|
this.shadowRoot.innerHTML = `<div style="color:red;">${message}</div>`;
|
|
235
263
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
async getResponse(conversation_id,content,role,assistant_id){
|
|
267
|
+
const response = await fetch('/API/plugin/chatgpt-assistants/addMessage', {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
headers: { 'Content-Type': 'application/json' },
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
conversation_id: conversation_id,
|
|
272
|
+
content: content,
|
|
273
|
+
role: role||'system',
|
|
274
|
+
assistant_id: assistant_id||Ai.default_ai?Ai.default_ai.value:null
|
|
275
|
+
})
|
|
276
|
+
}).then(res => res.json());
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async sendMessage() {//** */
|
|
280
|
+
|
|
281
|
+
const input = this.UI.textarea;
|
|
239
282
|
const message = input.value.trim();
|
|
240
283
|
if (!message) return;
|
|
241
284
|
|
|
242
285
|
input.disabled = true;
|
|
243
|
-
this.
|
|
286
|
+
this.UI.sendButton.disabled = true;
|
|
244
287
|
|
|
245
288
|
try {
|
|
246
289
|
const response = await fetch('/API/plugin/chatgpt-assistants/addMessage', {
|
|
@@ -256,7 +299,9 @@
|
|
|
256
299
|
if (response.success && response.runObj) {
|
|
257
300
|
this.currentRunId = response.runObj.id; // Store the run ID for polling
|
|
258
301
|
await this.loadConversation(); // reload messages
|
|
302
|
+
this.UI.content.update(); // update messages
|
|
259
303
|
this.startPolling(); // 🌸 start watching for assistant reply!
|
|
304
|
+
input.value = '';
|
|
260
305
|
} else {
|
|
261
306
|
alert('Failed to send message.');
|
|
262
307
|
}
|
|
@@ -265,9 +310,9 @@
|
|
|
265
310
|
alert('Error sending message.');
|
|
266
311
|
}
|
|
267
312
|
|
|
268
|
-
|
|
269
|
-
input.disabled = false;
|
|
270
|
-
this.shadowRoot.getElementById('send-button').disabled = false;
|
|
313
|
+
|
|
314
|
+
// input.disabled = false;
|
|
315
|
+
// this.shadowRoot.getElementById('send-button').disabled = false;
|
|
271
316
|
}
|
|
272
317
|
|
|
273
318
|
startPolling() {
|
|
@@ -286,18 +331,21 @@
|
|
|
286
331
|
const threadMessages = await resThread.json();
|
|
287
332
|
|
|
288
333
|
if (threadMessages?.messages) {
|
|
289
|
-
this.
|
|
290
|
-
this.
|
|
334
|
+
this.UI.content.update(threadMessages.messages);
|
|
335
|
+
//this.messages = threadMessages.messages;
|
|
336
|
+
//this.render();
|
|
291
337
|
}
|
|
292
338
|
|
|
293
339
|
clearInterval(this.pollingInterval);
|
|
294
340
|
this.pollingInterval = null;
|
|
295
341
|
this.hideThinkingMessage();
|
|
342
|
+
this.UI.textarea.disabled = false;
|
|
343
|
+
this.UI.sendButton.disabled = false;
|
|
296
344
|
}
|
|
297
345
|
}, 2000);
|
|
298
346
|
}
|
|
299
347
|
showThinkingMessage() {
|
|
300
|
-
const messagesDiv = this.
|
|
348
|
+
const messagesDiv = this.UI.content;
|
|
301
349
|
if (!messagesDiv) return;
|
|
302
350
|
|
|
303
351
|
// Pull assistant thinking text
|
|
@@ -329,14 +377,69 @@
|
|
|
329
377
|
}
|
|
330
378
|
|
|
331
379
|
customElements.define('joe-ai-chatbox', JoeAIChatbox);
|
|
332
|
-
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class JoeObject extends HTMLElement {
|
|
383
|
+
constructor() {
|
|
384
|
+
super();
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
this.object_id = this.getAttribute('object_id');
|
|
388
|
+
this.object = $J.get(this.object_id);
|
|
389
|
+
|
|
390
|
+
}
|
|
391
|
+
connectedCallback() {
|
|
392
|
+
const id = this.getAttribute('object_id');
|
|
393
|
+
var sTemp = $J.schema('business')?.listView?.title||false;
|
|
394
|
+
this.innerHTML = (sTemp)?JOE.propAsFuncOrValue(sTemp,this.object) :`<jo-title>${this.object.name}</jo-title>
|
|
395
|
+
<jo-subtitle>${this.object.info} - ${this.object._id}</jo-subtitle>`;
|
|
396
|
+
this.addEventListener('click', () => {
|
|
397
|
+
// Handle click event here, e.g., open the object in a new tab or show details
|
|
398
|
+
goJoe(_joe.search(this.object._id)[0],{schema:this.object.itemtype})
|
|
399
|
+
//window.open(`/object/${this.object_id}`, '_blank');
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
}
|
|
404
|
+
customElements.define('joe-object', JoeObject);
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
//**YES**
|
|
408
|
+
Ai.spawnChatHelper = async function(object_id,user_id=_joe.User._id,conversation_id) {
|
|
409
|
+
//if not conversation_id, create a new one
|
|
410
|
+
let convo_id = conversation_id;
|
|
411
|
+
var newChat = false;
|
|
412
|
+
if(!convo_id){
|
|
413
|
+
const response = await fetch('/API/plugin/chatgpt-assistants/createConversation', {
|
|
414
|
+
method: 'POST',
|
|
415
|
+
headers: { 'Content-Type': 'application/json' },
|
|
416
|
+
body: JSON.stringify({
|
|
417
|
+
object_id,
|
|
418
|
+
user_id
|
|
419
|
+
})
|
|
420
|
+
}).then(res => res.json());
|
|
421
|
+
convo_id = response?.conversation?._id;
|
|
422
|
+
newChat = true;
|
|
423
|
+
if(response.error){
|
|
424
|
+
console.error('❌ Failed to create conversation:', response.error);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
await _joe.Ai.spawnContextualChat(convo_id,{object_id,newChat});
|
|
430
|
+
}
|
|
431
|
+
Ai.getDefaultAssistant = function() {
|
|
432
|
+
|
|
433
|
+
Ai.default_ai = Ai.default_ai ||_joe.Data.setting.where({name:'DEFAULT_AI_ASSISTANT'})[0]||false;
|
|
434
|
+
return Ai.default_ai;
|
|
435
|
+
}
|
|
333
436
|
// ========== HELPERS ==========
|
|
334
437
|
Ai.spawnContextualChat = async function(conversationId, options = {}) {
|
|
335
438
|
if (!conversationId) {
|
|
336
439
|
console.warn("Missing conversation ID for chat spawn.");
|
|
337
440
|
return;
|
|
338
441
|
}
|
|
339
|
-
Ai.
|
|
442
|
+
Ai.getDefaultAssistant();
|
|
340
443
|
|
|
341
444
|
// 1. Check if chat already open
|
|
342
445
|
if (Ai._openChats[conversationId]) {
|
|
@@ -346,9 +449,11 @@
|
|
|
346
449
|
}
|
|
347
450
|
|
|
348
451
|
try {
|
|
452
|
+
const flattened = _joe.Object.flatten(options.object_id);
|
|
453
|
+
if (options.newChat) {
|
|
349
454
|
// 2. Prepare context
|
|
350
|
-
|
|
351
|
-
const contextInstructions = _joe.Ai.generateContextInstructions(flattened);
|
|
455
|
+
|
|
456
|
+
const contextInstructions = _joe.Ai.generateContextInstructions(flattened,options.object_id);
|
|
352
457
|
|
|
353
458
|
// 3. Inject context into backend
|
|
354
459
|
const contextResult = await fetch('/API/plugin/chatgpt-assistants/addMessage', {
|
|
@@ -356,9 +461,10 @@
|
|
|
356
461
|
headers: { 'Content-Type': 'application/json' },
|
|
357
462
|
body: JSON.stringify({
|
|
358
463
|
conversation_id: conversationId,
|
|
359
|
-
|
|
464
|
+
role: 'JOE',
|
|
360
465
|
content: contextInstructions,
|
|
361
|
-
assistant_id: Ai.default_ai.value
|
|
466
|
+
assistant_id: Ai.default_ai.value,
|
|
467
|
+
object_id: options.object_id
|
|
362
468
|
})
|
|
363
469
|
}).then(res => res.json());
|
|
364
470
|
|
|
@@ -366,38 +472,51 @@
|
|
|
366
472
|
console.error('❌ Failed to inject context:', contextResult?.error);
|
|
367
473
|
return;
|
|
368
474
|
}
|
|
369
|
-
|
|
475
|
+
}
|
|
370
476
|
// 4. Create new chatbox
|
|
371
477
|
const chat = document.createElement('joe-ai-chatbox');
|
|
372
478
|
chat.setAttribute('conversation_id', conversationId);
|
|
373
|
-
|
|
479
|
+
const screenWidth = window.innerWidth;
|
|
480
|
+
if(screenWidth <= 768){
|
|
481
|
+
chat.setAttribute('mobile', 'true');
|
|
482
|
+
chat.style.width = 'auto';
|
|
483
|
+
chat.style.left = '0px';
|
|
484
|
+
}
|
|
485
|
+
else{
|
|
486
|
+
chat.setAttribute('mobile', 'false');
|
|
487
|
+
chat.style.width = options.width || '640px';
|
|
488
|
+
chat.style.left = 'auto';
|
|
489
|
+
}
|
|
374
490
|
// Apply styles
|
|
375
|
-
|
|
376
|
-
chat.style.height = options.height || '420px';
|
|
377
|
-
chat.style.bottom = options.bottom || '
|
|
378
|
-
chat.style.right = options.right || '
|
|
491
|
+
|
|
492
|
+
//chat.style.height = options.height || '420px';
|
|
493
|
+
chat.style.bottom = options.bottom || '50px';
|
|
494
|
+
chat.style.right = options.right || '0px';
|
|
495
|
+
chat.style.top = options.top || '50px';
|
|
379
496
|
chat.style.position = 'fixed';
|
|
380
497
|
chat.style.zIndex = '10000';
|
|
381
498
|
chat.style.background = '#efefef';
|
|
382
499
|
chat.style.border = '1px solid #fff';
|
|
383
500
|
chat.style.borderRadius = '8px';
|
|
384
|
-
chat.style.boxShadow = '0px
|
|
501
|
+
chat.style.boxShadow = '0px 1px 4px rgba(0, 0, 0, 0.3)';
|
|
385
502
|
chat.style.padding = '5px';
|
|
386
|
-
|
|
503
|
+
chat.style.margin = '5px';
|
|
504
|
+
|
|
387
505
|
document.body.appendChild(chat);
|
|
388
506
|
|
|
389
507
|
// 5. Track it
|
|
390
508
|
Ai._openChats[conversationId] = chat;
|
|
391
509
|
|
|
510
|
+
if (options.newChat) {
|
|
392
511
|
// 6. Show soft local UI message
|
|
393
|
-
_joe.Ai.injectSystemMessage(conversationId, `Context injected: ${flattened.name || flattened.title || 'Object'} (${flattened._id})`);
|
|
394
|
-
|
|
512
|
+
//_joe.Ai.injectSystemMessage(conversationId, `Context injected: ${flattened.name || flattened.title || 'Object'} (${flattened._id})`);
|
|
513
|
+
}
|
|
395
514
|
} catch (err) {
|
|
396
515
|
console.error('❌ spawnChat context injection failed:', err);
|
|
397
516
|
}
|
|
398
517
|
};
|
|
399
518
|
|
|
400
|
-
|
|
519
|
+
/* Ai.spawnChat = function(conversationId, options = {}) {
|
|
401
520
|
if (!conversationId) {
|
|
402
521
|
console.warn("Missing conversation ID for chat spawn.");
|
|
403
522
|
return;
|
|
@@ -442,27 +561,31 @@
|
|
|
442
561
|
return chat;
|
|
443
562
|
// 4. Optionally clean up when chatbox is removed (if you wire close buttons later)
|
|
444
563
|
};
|
|
445
|
-
|
|
446
|
-
Ai.generateContextInstructions = function(flattenedObj) {
|
|
564
|
+
*/
|
|
565
|
+
Ai.generateContextInstructions = function(flattenedObj,object_id) {
|
|
447
566
|
if (!flattenedObj) return '';
|
|
448
567
|
|
|
449
|
-
let context =
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
568
|
+
let context = `{{{BEGIN_OBJECT:${object_id}}}}`+
|
|
569
|
+
"Context: You are assisting the user with the following object:\n\n";
|
|
570
|
+
|
|
571
|
+
context += JSON.stringify(flattenedObj, null, 2) + "\n\n";
|
|
572
|
+
// for (const [key, value] of Object.entries(flattenedObj)) {
|
|
573
|
+
// if (typeof value === 'object' && value !== null) {
|
|
574
|
+
// context += `- ${key}: (linked object)\n`;
|
|
575
|
+
// for (const [subkey, subval] of Object.entries(value)) {
|
|
576
|
+
// context += ` • ${subkey}: ${subval}\n`;
|
|
577
|
+
// }
|
|
578
|
+
// } else {
|
|
579
|
+
// context += `- ${key}: ${value}\n`;
|
|
580
|
+
// }
|
|
581
|
+
// }
|
|
461
582
|
|
|
462
|
-
context += `\nAlways refer to this context when answering questions or completing tasks related to this object.\n
|
|
583
|
+
context += `\nAlways refer to this context when answering questions or completing tasks related to this object.\n`+
|
|
584
|
+
`{{{END_OBJECT:${object_id}}}}`;
|
|
463
585
|
|
|
464
586
|
return context;
|
|
465
587
|
};
|
|
588
|
+
|
|
466
589
|
Ai.injectSystemMessage = async function(conversationId, text) {
|
|
467
590
|
if (!conversationId || !text) return;
|
|
468
591
|
|
|
@@ -470,7 +593,7 @@
|
|
|
470
593
|
// Create a system-style message object
|
|
471
594
|
const messageObj = {
|
|
472
595
|
conversation_id: conversationId,
|
|
473
|
-
role: '
|
|
596
|
+
role: 'joe',
|
|
474
597
|
content: text,
|
|
475
598
|
created: new Date().toISOString()
|
|
476
599
|
};
|
package/js/joe-full.js
CHANGED
|
@@ -19585,7 +19585,10 @@ Field Rendering Helpers
|
|
|
19585
19585
|
|
|
19586
19586
|
for (const [field, val] of Object.entries(object)) {
|
|
19587
19587
|
if (val === undefined || val === null) continue;
|
|
19588
|
-
if (field == '_id')
|
|
19588
|
+
if (field == '_id'){
|
|
19589
|
+
flattened[field] = val;
|
|
19590
|
+
continue;
|
|
19591
|
+
}
|
|
19589
19592
|
|
|
19590
19593
|
if (typeof val === 'string' && $c.isCuid(val) && recursive && depth > 0) {
|
|
19591
19594
|
if (!visited.has(val)) {
|
package/js/joe.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/* --------------------------------------------------------
|
|
2
|
-
*
|
|
3
|
-
* JOE - v1.5.0
|
|
4
|
-
* Created by: Corey Hadden
|
|
5
|
-
*
|
|
6
|
-
* -------------------------------------------------------- */
|
|
7
1
|
/* --------------------------------------------------------
|
|
8
2
|
*
|
|
9
3
|
* JOE - v1.5.0
|
|
@@ -8292,7 +8286,10 @@ Field Rendering Helpers
|
|
|
8292
8286
|
|
|
8293
8287
|
for (const [field, val] of Object.entries(object)) {
|
|
8294
8288
|
if (val === undefined || val === null) continue;
|
|
8295
|
-
if (field == '_id')
|
|
8289
|
+
if (field == '_id'){
|
|
8290
|
+
flattened[field] = val;
|
|
8291
|
+
continue;
|
|
8292
|
+
}
|
|
8296
8293
|
|
|
8297
8294
|
if (typeof val === 'string' && $c.isCuid(val) && recursive && depth > 0) {
|
|
8298
8295
|
if (!visited.has(val)) {
|
package/package.json
CHANGED
package/pages/template.html
CHANGED
|
@@ -311,8 +311,9 @@
|
|
|
311
311
|
JOE.init();
|
|
312
312
|
//capp.init();
|
|
313
313
|
var capp_apps = '${USERAPPS}'.split(',') || [];
|
|
314
|
+
var caobj = ${USERAPPSOBJ};
|
|
314
315
|
var all_apps = '${APPS}'.split(',') || [];
|
|
315
|
-
capp.Menu.addFromApps(
|
|
316
|
+
capp.Menu.addFromApps(caobj,'<joe-subtext>${this.webconfig.name}</joe-subtext>${APPNAME}');
|
|
316
317
|
//capp.Button.add('Docs',null,'window.open(\'/JsonObjectEditor/docs.html\')','capp-header');
|
|
317
318
|
|
|
318
319
|
|