json-object-editor 0.10.425 → 0.10.431
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 +2 -0
- package/capp/capp.css +8 -1
- package/capp/capp.js +12 -5
- package/css/joe-ai.css +174 -0
- package/css/joe-styles.css +25 -1
- package/css/joe.css +25 -7
- package/js/JsonObjectEditor.jquery.craydent.js +28 -9
- package/js/joe-ai copy.js +517 -0
- package/js/joe-ai.js +287 -159
- package/js/joe-full.js +28 -9
- package/js/joe.js +28 -15
- package/package.json +1 -1
- package/pages/template.html +2 -1
- package/readme.md +2 -0
- package/server/app-config.js +2 -2
- package/server/fields/core.js +15 -7
- package/server/init.js +1 -1
- package/server/modules/Apps.js +49 -8
- package/server/modules/Server.js +29 -5
- package/server/modules/Storage.js +3 -0
- package/server/modules/UniversalShorthand.js +1 -2
- package/server/modules/Utils.js +62 -0
- package/server/plugins/chatgpt-assistants.js +359 -0
- package/server/plugins/chatgpt-tools.js +79 -0
- package/server/plugins/plugin-utils.js +46 -0
- package/server/schemas/ai_conversation.js +45 -14
- package/server/schemas/ai_tool.js +94 -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,30 +237,58 @@
|
|
|
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
|
`;
|
|
231
259
|
}
|
|
232
260
|
|
|
233
261
|
renderError(message) {
|
|
234
|
-
this.shadowRoot.innerHTML = `<div style="color:red;">${message}</div
|
|
262
|
+
this.shadowRoot.innerHTML = `<div style="color:red;">${message}</div>
|
|
263
|
+
<div class="close-btn" title="Close Chatbox" >${_joe.SVG.icon.close}</div>`;
|
|
264
|
+
this.shadowRoot.querySelector('.close-btn').addEventListener('click', () => {
|
|
265
|
+
//this.closeChat()
|
|
266
|
+
this.closest('joe-ai-chatbox').closeChat();
|
|
267
|
+
});
|
|
235
268
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
async getResponse(conversation_id,content,role,assistant_id){
|
|
272
|
+
const response = await fetch('/API/plugin/chatgpt-assistants/addMessage', {
|
|
273
|
+
method: 'POST',
|
|
274
|
+
headers: { 'Content-Type': 'application/json' },
|
|
275
|
+
body: JSON.stringify({
|
|
276
|
+
conversation_id: conversation_id,
|
|
277
|
+
content: content,
|
|
278
|
+
role: role||'system',
|
|
279
|
+
assistant_id: assistant_id||Ai.default_ai?Ai.default_ai.value:null
|
|
280
|
+
})
|
|
281
|
+
}).then(res => res.json());
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async sendMessage() {//** */
|
|
285
|
+
|
|
286
|
+
const input = this.UI.textarea;
|
|
239
287
|
const message = input.value.trim();
|
|
240
288
|
if (!message) return;
|
|
241
289
|
|
|
242
290
|
input.disabled = true;
|
|
243
|
-
this.
|
|
291
|
+
this.UI.sendButton.disabled = true;
|
|
244
292
|
|
|
245
293
|
try {
|
|
246
294
|
const response = await fetch('/API/plugin/chatgpt-assistants/addMessage', {
|
|
@@ -256,7 +304,9 @@
|
|
|
256
304
|
if (response.success && response.runObj) {
|
|
257
305
|
this.currentRunId = response.runObj.id; // Store the run ID for polling
|
|
258
306
|
await this.loadConversation(); // reload messages
|
|
307
|
+
this.UI.content.update(); // update messages
|
|
259
308
|
this.startPolling(); // 🌸 start watching for assistant reply!
|
|
309
|
+
input.value = '';
|
|
260
310
|
} else {
|
|
261
311
|
alert('Failed to send message.');
|
|
262
312
|
}
|
|
@@ -265,9 +315,9 @@
|
|
|
265
315
|
alert('Error sending message.');
|
|
266
316
|
}
|
|
267
317
|
|
|
268
|
-
|
|
269
|
-
input.disabled = false;
|
|
270
|
-
this.shadowRoot.getElementById('send-button').disabled = false;
|
|
318
|
+
|
|
319
|
+
// input.disabled = false;
|
|
320
|
+
// this.shadowRoot.getElementById('send-button').disabled = false;
|
|
271
321
|
}
|
|
272
322
|
|
|
273
323
|
startPolling() {
|
|
@@ -286,18 +336,21 @@
|
|
|
286
336
|
const threadMessages = await resThread.json();
|
|
287
337
|
|
|
288
338
|
if (threadMessages?.messages) {
|
|
289
|
-
this.
|
|
290
|
-
this.
|
|
339
|
+
this.UI.content.update(threadMessages.messages);
|
|
340
|
+
//this.messages = threadMessages.messages;
|
|
341
|
+
//this.render();
|
|
291
342
|
}
|
|
292
343
|
|
|
293
344
|
clearInterval(this.pollingInterval);
|
|
294
345
|
this.pollingInterval = null;
|
|
295
346
|
this.hideThinkingMessage();
|
|
347
|
+
this.UI.textarea.disabled = false;
|
|
348
|
+
this.UI.sendButton.disabled = false;
|
|
296
349
|
}
|
|
297
350
|
}, 2000);
|
|
298
351
|
}
|
|
299
352
|
showThinkingMessage() {
|
|
300
|
-
const messagesDiv = this.
|
|
353
|
+
const messagesDiv = this.UI.content;
|
|
301
354
|
if (!messagesDiv) return;
|
|
302
355
|
|
|
303
356
|
// Pull assistant thinking text
|
|
@@ -329,14 +382,69 @@
|
|
|
329
382
|
}
|
|
330
383
|
|
|
331
384
|
customElements.define('joe-ai-chatbox', JoeAIChatbox);
|
|
332
|
-
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class JoeObject extends HTMLElement {
|
|
388
|
+
constructor() {
|
|
389
|
+
super();
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
this.object_id = this.getAttribute('object_id');
|
|
393
|
+
this.object = $J.get(this.object_id);
|
|
394
|
+
|
|
395
|
+
}
|
|
396
|
+
connectedCallback() {
|
|
397
|
+
const id = this.getAttribute('object_id');
|
|
398
|
+
var sTemp = $J.schema('business')?.listView?.title||false;
|
|
399
|
+
this.innerHTML = (sTemp)?JOE.propAsFuncOrValue(sTemp,this.object) :`<jo-title>${this.object.name}</jo-title>
|
|
400
|
+
<jo-subtitle>${this.object.info} - ${this.object._id}</jo-subtitle>`;
|
|
401
|
+
this.addEventListener('click', () => {
|
|
402
|
+
// Handle click event here, e.g., open the object in a new tab or show details
|
|
403
|
+
goJoe(_joe.search(this.object._id)[0],{schema:this.object.itemtype})
|
|
404
|
+
//window.open(`/object/${this.object_id}`, '_blank');
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
}
|
|
409
|
+
customElements.define('joe-object', JoeObject);
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
//**YES**
|
|
413
|
+
Ai.spawnChatHelper = async function(object_id,user_id=_joe.User._id,conversation_id) {
|
|
414
|
+
//if not conversation_id, create a new one
|
|
415
|
+
let convo_id = conversation_id;
|
|
416
|
+
var newChat = false;
|
|
417
|
+
if(!convo_id){
|
|
418
|
+
const response = await fetch('/API/plugin/chatgpt-assistants/createConversation', {
|
|
419
|
+
method: 'POST',
|
|
420
|
+
headers: { 'Content-Type': 'application/json' },
|
|
421
|
+
body: JSON.stringify({
|
|
422
|
+
object_id,
|
|
423
|
+
user_id
|
|
424
|
+
})
|
|
425
|
+
}).then(res => res.json());
|
|
426
|
+
convo_id = response?.conversation?._id;
|
|
427
|
+
newChat = true;
|
|
428
|
+
if(response.error){
|
|
429
|
+
console.error('❌ Failed to create conversation:', response.error);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
await _joe.Ai.spawnContextualChat(convo_id,{object_id,newChat});
|
|
435
|
+
}
|
|
436
|
+
Ai.getDefaultAssistant = function() {
|
|
437
|
+
|
|
438
|
+
Ai.default_ai = Ai.default_ai ||_joe.Data.setting.where({name:'DEFAULT_AI_ASSISTANT'})[0]||false;
|
|
439
|
+
return Ai.default_ai;
|
|
440
|
+
}
|
|
333
441
|
// ========== HELPERS ==========
|
|
334
442
|
Ai.spawnContextualChat = async function(conversationId, options = {}) {
|
|
335
443
|
if (!conversationId) {
|
|
336
444
|
console.warn("Missing conversation ID for chat spawn.");
|
|
337
445
|
return;
|
|
338
446
|
}
|
|
339
|
-
Ai.
|
|
447
|
+
Ai.getDefaultAssistant();
|
|
340
448
|
|
|
341
449
|
// 1. Check if chat already open
|
|
342
450
|
if (Ai._openChats[conversationId]) {
|
|
@@ -346,9 +454,11 @@
|
|
|
346
454
|
}
|
|
347
455
|
|
|
348
456
|
try {
|
|
457
|
+
const flattened = _joe.Object.flatten(options.object_id);
|
|
458
|
+
if (options.newChat) {
|
|
349
459
|
// 2. Prepare context
|
|
350
|
-
|
|
351
|
-
const contextInstructions = _joe.Ai.generateContextInstructions(flattened);
|
|
460
|
+
|
|
461
|
+
const contextInstructions = _joe.Ai.generateContextInstructions(flattened,options.object_id);
|
|
352
462
|
|
|
353
463
|
// 3. Inject context into backend
|
|
354
464
|
const contextResult = await fetch('/API/plugin/chatgpt-assistants/addMessage', {
|
|
@@ -356,9 +466,10 @@
|
|
|
356
466
|
headers: { 'Content-Type': 'application/json' },
|
|
357
467
|
body: JSON.stringify({
|
|
358
468
|
conversation_id: conversationId,
|
|
359
|
-
|
|
469
|
+
role: 'JOE',
|
|
360
470
|
content: contextInstructions,
|
|
361
|
-
assistant_id: Ai.default_ai.value
|
|
471
|
+
assistant_id: Ai.default_ai.value,
|
|
472
|
+
object_id: options.object_id
|
|
362
473
|
})
|
|
363
474
|
}).then(res => res.json());
|
|
364
475
|
|
|
@@ -366,38 +477,51 @@
|
|
|
366
477
|
console.error('❌ Failed to inject context:', contextResult?.error);
|
|
367
478
|
return;
|
|
368
479
|
}
|
|
369
|
-
|
|
480
|
+
}
|
|
370
481
|
// 4. Create new chatbox
|
|
371
482
|
const chat = document.createElement('joe-ai-chatbox');
|
|
372
483
|
chat.setAttribute('conversation_id', conversationId);
|
|
373
|
-
|
|
484
|
+
const screenWidth = window.innerWidth;
|
|
485
|
+
if(screenWidth <= 768){
|
|
486
|
+
chat.setAttribute('mobile', 'true');
|
|
487
|
+
chat.style.width = 'auto';
|
|
488
|
+
chat.style.left = '0px';
|
|
489
|
+
}
|
|
490
|
+
else{
|
|
491
|
+
chat.setAttribute('mobile', 'false');
|
|
492
|
+
chat.style.width = options.width || '640px';
|
|
493
|
+
chat.style.left = 'auto';
|
|
494
|
+
}
|
|
374
495
|
// Apply styles
|
|
375
|
-
|
|
376
|
-
chat.style.height = options.height || '420px';
|
|
377
|
-
chat.style.bottom = options.bottom || '
|
|
378
|
-
chat.style.right = options.right || '
|
|
496
|
+
|
|
497
|
+
//chat.style.height = options.height || '420px';
|
|
498
|
+
chat.style.bottom = options.bottom || '50px';
|
|
499
|
+
chat.style.right = options.right || '0px';
|
|
500
|
+
chat.style.top = options.top || '50px';
|
|
379
501
|
chat.style.position = 'fixed';
|
|
380
502
|
chat.style.zIndex = '10000';
|
|
381
503
|
chat.style.background = '#efefef';
|
|
382
504
|
chat.style.border = '1px solid #fff';
|
|
383
505
|
chat.style.borderRadius = '8px';
|
|
384
|
-
chat.style.boxShadow = '0px
|
|
506
|
+
chat.style.boxShadow = '0px 1px 4px rgba(0, 0, 0, 0.3)';
|
|
385
507
|
chat.style.padding = '5px';
|
|
386
|
-
|
|
508
|
+
chat.style.margin = '5px';
|
|
509
|
+
|
|
387
510
|
document.body.appendChild(chat);
|
|
388
511
|
|
|
389
512
|
// 5. Track it
|
|
390
513
|
Ai._openChats[conversationId] = chat;
|
|
391
514
|
|
|
515
|
+
if (options.newChat) {
|
|
392
516
|
// 6. Show soft local UI message
|
|
393
|
-
_joe.Ai.injectSystemMessage(conversationId, `Context injected: ${flattened.name || flattened.title || 'Object'} (${flattened._id})`);
|
|
394
|
-
|
|
517
|
+
//_joe.Ai.injectSystemMessage(conversationId, `Context injected: ${flattened.name || flattened.title || 'Object'} (${flattened._id})`);
|
|
518
|
+
}
|
|
395
519
|
} catch (err) {
|
|
396
520
|
console.error('❌ spawnChat context injection failed:', err);
|
|
397
521
|
}
|
|
398
522
|
};
|
|
399
523
|
|
|
400
|
-
|
|
524
|
+
/* Ai.spawnChat = function(conversationId, options = {}) {
|
|
401
525
|
if (!conversationId) {
|
|
402
526
|
console.warn("Missing conversation ID for chat spawn.");
|
|
403
527
|
return;
|
|
@@ -442,27 +566,31 @@
|
|
|
442
566
|
return chat;
|
|
443
567
|
// 4. Optionally clean up when chatbox is removed (if you wire close buttons later)
|
|
444
568
|
};
|
|
445
|
-
|
|
446
|
-
Ai.generateContextInstructions = function(flattenedObj) {
|
|
569
|
+
*/
|
|
570
|
+
Ai.generateContextInstructions = function(flattenedObj,object_id) {
|
|
447
571
|
if (!flattenedObj) return '';
|
|
448
572
|
|
|
449
|
-
let context =
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
573
|
+
let context = `{{{BEGIN_OBJECT:${object_id}}}}`+
|
|
574
|
+
"Context: You are assisting the user with the following object:\n\n";
|
|
575
|
+
|
|
576
|
+
context += JSON.stringify(flattenedObj, null, 2) + "\n\n";
|
|
577
|
+
// for (const [key, value] of Object.entries(flattenedObj)) {
|
|
578
|
+
// if (typeof value === 'object' && value !== null) {
|
|
579
|
+
// context += `- ${key}: (linked object)\n`;
|
|
580
|
+
// for (const [subkey, subval] of Object.entries(value)) {
|
|
581
|
+
// context += ` • ${subkey}: ${subval}\n`;
|
|
582
|
+
// }
|
|
583
|
+
// } else {
|
|
584
|
+
// context += `- ${key}: ${value}\n`;
|
|
585
|
+
// }
|
|
586
|
+
// }
|
|
461
587
|
|
|
462
|
-
context += `\nAlways refer to this context when answering questions or completing tasks related to this object.\n
|
|
588
|
+
context += `\nAlways refer to this context when answering questions or completing tasks related to this object.\n`+
|
|
589
|
+
`{{{END_OBJECT:${object_id}}}}`;
|
|
463
590
|
|
|
464
591
|
return context;
|
|
465
592
|
};
|
|
593
|
+
|
|
466
594
|
Ai.injectSystemMessage = async function(conversationId, text) {
|
|
467
595
|
if (!conversationId || !text) return;
|
|
468
596
|
|
|
@@ -470,7 +598,7 @@
|
|
|
470
598
|
// Create a system-style message object
|
|
471
599
|
const messageObj = {
|
|
472
600
|
conversation_id: conversationId,
|
|
473
|
-
role: '
|
|
601
|
+
role: 'joe',
|
|
474
602
|
content: text,
|
|
475
603
|
created: new Date().toISOString()
|
|
476
604
|
};
|