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
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
(function(){
|
|
2
|
+
// Define the joeAI namespace
|
|
3
|
+
const Ai = {};
|
|
4
|
+
const self = this;
|
|
5
|
+
Ai._openChats = {}; // Conversation ID -> element
|
|
6
|
+
Ai.default_ai = null; // Default AI assistant ID
|
|
7
|
+
// ========== COMPONENTS ==========
|
|
8
|
+
|
|
9
|
+
class JoeAIChatbox extends HTMLElement {
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
this.attachShadow({ mode: 'open' });
|
|
15
|
+
this.messages = [];
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
connectedCallback() {
|
|
20
|
+
this.conversation_id = this.getAttribute('conversation_id');
|
|
21
|
+
|
|
22
|
+
this.selected_assistant_id = Ai.default_ai?Ai.default_ai.value:null;
|
|
23
|
+
this.ui = {};
|
|
24
|
+
if (!this.conversation_id) {
|
|
25
|
+
this.renderError("Missing conversation_id");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this.loadConversation();
|
|
29
|
+
this.getAllAssistants();
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
async getAllAssistants() {
|
|
33
|
+
const res = await fetch('/API/item/ai_assistant');
|
|
34
|
+
const result = await res.json();
|
|
35
|
+
if (result.error) {
|
|
36
|
+
console.error("Error fetching assistants:", result.error);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.allAssistants = {};
|
|
40
|
+
|
|
41
|
+
if (Array.isArray(result.item)) {
|
|
42
|
+
result.item.map(a => {
|
|
43
|
+
if (a._id) {
|
|
44
|
+
this.allAssistants[a._id] = a;
|
|
45
|
+
}
|
|
46
|
+
if (a.openai_id) {
|
|
47
|
+
this.allAssistants[a.openai_id] = a; // Optional dual-key if you prefer
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
async loadConversation() {
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(`/API/object/ai_conversation/_id/${this.conversation_id}`);
|
|
56
|
+
const convo = await res.json();
|
|
57
|
+
|
|
58
|
+
if (!convo || convo.error) {
|
|
59
|
+
this.renderError("Conversation not found.");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.conversation = convo;
|
|
63
|
+
this.messages = [];
|
|
64
|
+
|
|
65
|
+
if (convo.thread_id) {
|
|
66
|
+
const resThread = await fetch(`/API/plugin/chatgpt-assistants/getThreadMessages?thread_id=${convo.thread_id}`);
|
|
67
|
+
const threadMessages = await resThread.json();
|
|
68
|
+
this.messages = threadMessages?.messages || [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.render();
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error("Chatbox load error:", err);
|
|
74
|
+
this.renderError("Error loading conversation.");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
getAssistantInfo(id){
|
|
78
|
+
const assistant = this.allAssistants[id];
|
|
79
|
+
if (assistant) {
|
|
80
|
+
return assistant;
|
|
81
|
+
} else {
|
|
82
|
+
console.warn("Assistant not found:", id);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
render() {
|
|
87
|
+
const convoName = this.conversation.name || "Untitled Conversation";
|
|
88
|
+
const convoInfo = this.conversation.info || "";
|
|
89
|
+
|
|
90
|
+
const chatMessages = this.messages.map(msg => this.renderMessage(msg)).reverse().join('');
|
|
91
|
+
|
|
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
|
+
|
|
111
|
+
// Inject external CSS
|
|
112
|
+
const styleLink = document.createElement('link');
|
|
113
|
+
styleLink.setAttribute('rel', 'stylesheet');
|
|
114
|
+
styleLink.setAttribute('href', '/JsonObjectEditor/css/joe-ai.css'); // Adjust path as needed
|
|
115
|
+
this.shadowRoot.appendChild(styleLink);
|
|
116
|
+
|
|
117
|
+
// Build inner HTML in a wrapper
|
|
118
|
+
const wrapper = document.createElement('div');
|
|
119
|
+
wrapper.className = 'chatbox';
|
|
120
|
+
wrapper.innerHTML = `
|
|
121
|
+
<div class="close-btn" title="Close Chatbox" >${_joe.SVG.icon.close}</div>
|
|
122
|
+
<div class="header">
|
|
123
|
+
<h2>${convoName}</h2>
|
|
124
|
+
<p>${convoInfo}</p>
|
|
125
|
+
${assistantSelect}
|
|
126
|
+
</div>
|
|
127
|
+
<div class="messages">${chatMessages}</div>
|
|
128
|
+
<div class="inputRow">
|
|
129
|
+
<textarea id="chat-input" type="text" placeholder="Type a message..."></textarea>
|
|
130
|
+
<button id="send-button">Send</button>
|
|
131
|
+
</div>
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
this.shadowRoot.appendChild(wrapper);
|
|
135
|
+
|
|
136
|
+
const messagesDiv = this.shadowRoot.querySelector('.messages');
|
|
137
|
+
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
138
|
+
this.shadowRoot.getElementById('send-button').addEventListener('click', () => this.sendMessage());
|
|
139
|
+
|
|
140
|
+
// Wire up the close button
|
|
141
|
+
this.shadowRoot.querySelector('.close-btn').addEventListener('click', () => {
|
|
142
|
+
//this.closeChat()
|
|
143
|
+
this.closest('joe-ai-chatbox').closeChat();
|
|
144
|
+
});
|
|
145
|
+
this.ui.assistant_select = this.shadowRoot.querySelector('#assistant-select');
|
|
146
|
+
this.ui.assistant_select?.addEventListener('change', (e) => {
|
|
147
|
+
this.selected_assistant_id = e.target.value;
|
|
148
|
+
});
|
|
149
|
+
this.selected_assistant_id = this.ui.assistant_select?.value || null;
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
renderMessage(msg) {
|
|
154
|
+
const role = msg.role || 'user';
|
|
155
|
+
const classes = `message ${role}`;
|
|
156
|
+
|
|
157
|
+
let contentText = '';
|
|
158
|
+
|
|
159
|
+
if (Array.isArray(msg.content)) {
|
|
160
|
+
// OpenAI style: array of parts
|
|
161
|
+
contentText = msg.content.map(part => {
|
|
162
|
+
if (part.type === 'text' && part.text && part.text.value) {
|
|
163
|
+
return part.text.value;
|
|
164
|
+
}
|
|
165
|
+
return '';
|
|
166
|
+
}).join('\n');
|
|
167
|
+
} else if (typeof msg.content === 'string') {
|
|
168
|
+
contentText = msg.content;
|
|
169
|
+
} else {
|
|
170
|
+
contentText = '[Unsupported message format]';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Build timestamp
|
|
174
|
+
const createdAt = msg.created_at ? new Date(msg.created_at * 1000) : null; // OpenAI sends timestamps in seconds
|
|
175
|
+
const timestamp = createdAt ? createdAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '';
|
|
176
|
+
|
|
177
|
+
return `
|
|
178
|
+
<div class="${classes}">
|
|
179
|
+
<div class="meta">
|
|
180
|
+
<participant-name>${role.toUpperCase()}</participant-name>
|
|
181
|
+
${timestamp ? `<span class="timestamp">${timestamp}</span>` : ''}</div>
|
|
182
|
+
<div class="content">${contentText}</div>
|
|
183
|
+
</div>
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
renderError(message) {
|
|
188
|
+
this.shadowRoot.innerHTML = `<div style="color:red;">${message}</div>`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
async getResponse(conversation_id,content,role,assistant_id){
|
|
193
|
+
const response = await fetch('/API/plugin/chatgpt-assistants/addMessage', {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: { 'Content-Type': 'application/json' },
|
|
196
|
+
body: JSON.stringify({
|
|
197
|
+
conversation_id: conversation_id,
|
|
198
|
+
content: content,
|
|
199
|
+
role: role||'system',
|
|
200
|
+
assistant_id: assistant_id||Ai.default_ai?Ai.default_ai.value:null
|
|
201
|
+
})
|
|
202
|
+
}).then(res => res.json());
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async sendMessage() {//** */
|
|
206
|
+
const input = this.shadowRoot.getElementById('chat-input');
|
|
207
|
+
const message = input.value.trim();
|
|
208
|
+
if (!message) return;
|
|
209
|
+
|
|
210
|
+
input.disabled = true;
|
|
211
|
+
this.shadowRoot.getElementById('send-button').disabled = true;
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const response = await fetch('/API/plugin/chatgpt-assistants/addMessage', {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: { 'Content-Type': 'application/json' },
|
|
217
|
+
body: JSON.stringify({
|
|
218
|
+
conversation_id: this.conversation_id,
|
|
219
|
+
content: message,
|
|
220
|
+
assistant_id: this.selected_assistant_id||Ai.default_ai?Ai.default_ai.value:null
|
|
221
|
+
})
|
|
222
|
+
}).then(res => res.json());
|
|
223
|
+
|
|
224
|
+
if (response.success && response.runObj) {
|
|
225
|
+
this.currentRunId = response.runObj.id; // Store the run ID for polling
|
|
226
|
+
await this.loadConversation(); // reload messages
|
|
227
|
+
this.startPolling(); // 🌸 start watching for assistant reply!
|
|
228
|
+
input.value = '';
|
|
229
|
+
} else {
|
|
230
|
+
alert('Failed to send message.');
|
|
231
|
+
}
|
|
232
|
+
} catch (err) {
|
|
233
|
+
console.error('Send message error:', err);
|
|
234
|
+
alert('Error sending message.');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
input.disabled = false;
|
|
239
|
+
this.shadowRoot.getElementById('send-button').disabled = false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
startPolling() {
|
|
243
|
+
if (this.pollingInterval) return; // Already polling
|
|
244
|
+
|
|
245
|
+
// Insert thinking message
|
|
246
|
+
this.showThinkingMessage();
|
|
247
|
+
|
|
248
|
+
this.pollingInterval = setInterval(async () => {
|
|
249
|
+
const runRes = await fetch(`/API/plugin/chatgpt-assistants/getRunStatus?thread_id=${this.conversation.thread_id}&run_id=${this.currentRunId}`);
|
|
250
|
+
const run = await runRes.json();
|
|
251
|
+
|
|
252
|
+
if (run.status === 'completed') {
|
|
253
|
+
const resThread = await fetch(`/API/plugin/chatgpt-assistants/getThreadMessages?thread_id=${this.conversation.thread_id}`);
|
|
254
|
+
//const activeAssistant = this.conversation.assistants.find(a => a.openai_id === run.assistant_id);
|
|
255
|
+
const threadMessages = await resThread.json();
|
|
256
|
+
|
|
257
|
+
if (threadMessages?.messages) {
|
|
258
|
+
this.messages = threadMessages.messages;
|
|
259
|
+
//this.render();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
clearInterval(this.pollingInterval);
|
|
263
|
+
this.pollingInterval = null;
|
|
264
|
+
this.hideThinkingMessage();
|
|
265
|
+
}
|
|
266
|
+
}, 2000);
|
|
267
|
+
}
|
|
268
|
+
showThinkingMessage() {
|
|
269
|
+
const messagesDiv = this.shadowRoot.querySelector('.messages');
|
|
270
|
+
if (!messagesDiv) return;
|
|
271
|
+
|
|
272
|
+
// Pull assistant thinking text
|
|
273
|
+
const assistant = this.getAssistantInfo(this.selected_assistant_id);
|
|
274
|
+
const thinkingText = assistant?.assistant_thinking_text || 'Assistant is thinking...';
|
|
275
|
+
|
|
276
|
+
const div = document.createElement('div');
|
|
277
|
+
div.className = 'thinking-message';
|
|
278
|
+
div.textContent = thinkingText;
|
|
279
|
+
div.id = 'thinking-message';
|
|
280
|
+
messagesDiv.appendChild(div);
|
|
281
|
+
|
|
282
|
+
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
hideThinkingMessage() {
|
|
286
|
+
const existing = this.shadowRoot.querySelector('#thinking-message');
|
|
287
|
+
if (existing) existing.remove();
|
|
288
|
+
}
|
|
289
|
+
closeChat() {
|
|
290
|
+
// Remove the element
|
|
291
|
+
this.remove();
|
|
292
|
+
|
|
293
|
+
// Clean up from open chat registry if possible
|
|
294
|
+
if (_joe && _joe.Ai && _joe.Ai._openChats) {
|
|
295
|
+
delete _joe.Ai._openChats[this.conversation_id];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
customElements.define('joe-ai-chatbox', JoeAIChatbox);
|
|
301
|
+
|
|
302
|
+
//**YES**
|
|
303
|
+
Ai.spawnChatHelper = async function(object_id,user_id=_joe.User._id,conversation_id) {
|
|
304
|
+
//if not conversation_id, create a new one
|
|
305
|
+
let convo_id = conversation_id;
|
|
306
|
+
var newChat = false;
|
|
307
|
+
if(!convo_id){
|
|
308
|
+
const response = await fetch('/API/plugin/chatgpt-assistants/createConversation', {
|
|
309
|
+
method: 'POST',
|
|
310
|
+
headers: { 'Content-Type': 'application/json' },
|
|
311
|
+
body: JSON.stringify({
|
|
312
|
+
object_id,
|
|
313
|
+
user_id
|
|
314
|
+
})
|
|
315
|
+
}).then(res => res.json());
|
|
316
|
+
convo_id = response?.conversation?._id;
|
|
317
|
+
newChat = true;
|
|
318
|
+
if(response.error){
|
|
319
|
+
console.error('❌ Failed to create conversation:', response.error);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await _joe.Ai.spawnContextualChat(convo_id,{object_id,newChat});
|
|
325
|
+
}
|
|
326
|
+
// ========== HELPERS ==========
|
|
327
|
+
Ai.spawnContextualChat = async function(conversationId, options = {}) {
|
|
328
|
+
if (!conversationId) {
|
|
329
|
+
console.warn("Missing conversation ID for chat spawn.");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
Ai.default_ai = _joe.Data.setting.where({name:'DEFAULT_AI_ASSISTANT'})[0]||false;
|
|
333
|
+
|
|
334
|
+
// 1. Check if chat already open
|
|
335
|
+
if (Ai._openChats[conversationId]) {
|
|
336
|
+
console.log("Chatbox already open for", conversationId);
|
|
337
|
+
Ai._openChats[conversationId].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const flattened = _joe.Object.flatten(options.object_id);
|
|
343
|
+
if (options.newChat) {
|
|
344
|
+
// 2. Prepare context
|
|
345
|
+
|
|
346
|
+
const contextInstructions = _joe.Ai.generateContextInstructions(flattened,options.object_id);
|
|
347
|
+
|
|
348
|
+
// 3. Inject context into backend
|
|
349
|
+
const contextResult = await fetch('/API/plugin/chatgpt-assistants/addMessage', {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
headers: { 'Content-Type': 'application/json' },
|
|
352
|
+
body: JSON.stringify({
|
|
353
|
+
conversation_id: conversationId,
|
|
354
|
+
role: 'system',
|
|
355
|
+
content: contextInstructions,
|
|
356
|
+
assistant_id: Ai.default_ai.value
|
|
357
|
+
})
|
|
358
|
+
}).then(res => res.json());
|
|
359
|
+
|
|
360
|
+
if (!contextResult || contextResult.error) {
|
|
361
|
+
console.error('❌ Failed to inject context:', contextResult?.error);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// 4. Create new chatbox
|
|
366
|
+
const chat = document.createElement('joe-ai-chatbox');
|
|
367
|
+
chat.setAttribute('conversation_id', conversationId);
|
|
368
|
+
const screenWidth = window.innerWidth;
|
|
369
|
+
if(screenWidth <= 768){
|
|
370
|
+
chat.setAttribute('mobile', 'true');
|
|
371
|
+
chat.style.width = 'auto';
|
|
372
|
+
chat.style.left = '0px';
|
|
373
|
+
}
|
|
374
|
+
else{
|
|
375
|
+
chat.setAttribute('mobile', 'false');
|
|
376
|
+
chat.style.width = options.width || '640px';
|
|
377
|
+
chat.style.left = 'auto';
|
|
378
|
+
}
|
|
379
|
+
// Apply styles
|
|
380
|
+
|
|
381
|
+
//chat.style.height = options.height || '420px';
|
|
382
|
+
chat.style.bottom = options.bottom || '50px';
|
|
383
|
+
chat.style.right = options.right || '0px';
|
|
384
|
+
chat.style.top = options.top || '100px';
|
|
385
|
+
chat.style.position = 'fixed';
|
|
386
|
+
chat.style.zIndex = '10000';
|
|
387
|
+
chat.style.background = '#efefef';
|
|
388
|
+
chat.style.border = '1px solid #fff';
|
|
389
|
+
chat.style.borderRadius = '8px';
|
|
390
|
+
chat.style.boxShadow = '0px 1px 4px rgba(0, 0, 0, 0.3)';
|
|
391
|
+
chat.style.padding = '5px';
|
|
392
|
+
chat.style.margin = '5px';
|
|
393
|
+
|
|
394
|
+
document.body.appendChild(chat);
|
|
395
|
+
|
|
396
|
+
// 5. Track it
|
|
397
|
+
Ai._openChats[conversationId] = chat;
|
|
398
|
+
|
|
399
|
+
if (options.newChat) {
|
|
400
|
+
// 6. Show soft local UI message
|
|
401
|
+
_joe.Ai.injectSystemMessage(conversationId, `Context injected: ${flattened.name || flattened.title || 'Object'} (${flattened._id})`);
|
|
402
|
+
}
|
|
403
|
+
} catch (err) {
|
|
404
|
+
console.error('❌ spawnChat context injection failed:', err);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
/* Ai.spawnChat = function(conversationId, options = {}) {
|
|
409
|
+
if (!conversationId) {
|
|
410
|
+
console.warn("Missing conversation ID for chat spawn.");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 1. Check if chatbox already open
|
|
415
|
+
if (Ai._openChats[conversationId]) {
|
|
416
|
+
console.log("Chatbox already open for", conversationId);
|
|
417
|
+
Ai._openChats[conversationId].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// 2. Create new chatbox
|
|
422
|
+
const chat = document.createElement('joe-ai-chatbox');
|
|
423
|
+
chat.setAttribute('conversation_id', conversationId);
|
|
424
|
+
|
|
425
|
+
const flattened = _joe.Object.flatten();
|
|
426
|
+
const contextInstructions = _joe.Ai.generateContextInstructions(flattened);
|
|
427
|
+
|
|
428
|
+
// Actually inject into AI backend (for assistant awareness) if you have that later
|
|
429
|
+
// For now: silently show a soft system bubble
|
|
430
|
+
_joe.Ai.injectSystemMessage(conversationId, `Context injected: ${flattened.name || flattened.title || 'Object'} (${flattened._id})`);
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
// Apply styles
|
|
434
|
+
chat.style.width = options.width || '400px';
|
|
435
|
+
chat.style.height = options.height || '420px';
|
|
436
|
+
chat.style.bottom = options.bottom || '20px';
|
|
437
|
+
chat.style.right = options.right || '20px';
|
|
438
|
+
chat.style.position = 'fixed';
|
|
439
|
+
chat.style.zIndex = '10000';
|
|
440
|
+
chat.style.background = '#efefef';
|
|
441
|
+
chat.style.border = '1px solid #fff';
|
|
442
|
+
chat.style.borderRadius = '8px';
|
|
443
|
+
chat.style.boxShadow = '0px 2px 10px rgba(0,0,0,0.1)';
|
|
444
|
+
chat.style.padding = '5px';
|
|
445
|
+
|
|
446
|
+
document.body.appendChild(chat);
|
|
447
|
+
|
|
448
|
+
// 3. Track it
|
|
449
|
+
Ai._openChats[conversationId] = chat;
|
|
450
|
+
return chat;
|
|
451
|
+
// 4. Optionally clean up when chatbox is removed (if you wire close buttons later)
|
|
452
|
+
};
|
|
453
|
+
*/
|
|
454
|
+
Ai.generateContextInstructions = function(flattenedObj,object_id) {
|
|
455
|
+
if (!flattenedObj) return '';
|
|
456
|
+
|
|
457
|
+
let context = `{{{BEGIN_OBJECT:${object_id}}}}`+
|
|
458
|
+
"Context: You are assisting the user with the following object:\n\n";
|
|
459
|
+
|
|
460
|
+
context += JSON.stringify(flattenedObj, null, 2) + "\n\n";
|
|
461
|
+
// for (const [key, value] of Object.entries(flattenedObj)) {
|
|
462
|
+
// if (typeof value === 'object' && value !== null) {
|
|
463
|
+
// context += `- ${key}: (linked object)\n`;
|
|
464
|
+
// for (const [subkey, subval] of Object.entries(value)) {
|
|
465
|
+
// context += ` • ${subkey}: ${subval}\n`;
|
|
466
|
+
// }
|
|
467
|
+
// } else {
|
|
468
|
+
// context += `- ${key}: ${value}\n`;
|
|
469
|
+
// }
|
|
470
|
+
// }
|
|
471
|
+
|
|
472
|
+
context += `\nAlways refer to this context when answering questions or completing tasks related to this object.\n`+
|
|
473
|
+
`{{{END_OBJECT:${object_id}}}}`;
|
|
474
|
+
|
|
475
|
+
return context;
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
Ai.injectSystemMessage = async function(conversationId, text) {
|
|
479
|
+
if (!conversationId || !text) return;
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
// Create a system-style message object
|
|
483
|
+
const messageObj = {
|
|
484
|
+
conversation_id: conversationId,
|
|
485
|
+
role: 'joe',
|
|
486
|
+
content: text,
|
|
487
|
+
created: new Date().toISOString()
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// You could either push this directly into chatbox if loaded, or update server messages if you have backend ready
|
|
491
|
+
const chatbox = document.querySelector(`joe-ai-chatbox[conversation_id="${conversationId}"]`);
|
|
492
|
+
if (chatbox) {
|
|
493
|
+
if (!chatbox.messages) {
|
|
494
|
+
chatbox.messages = [];
|
|
495
|
+
}
|
|
496
|
+
chatbox.messages.push(messageObj);
|
|
497
|
+
|
|
498
|
+
// Optionally trigger a soft re-render of chatbox if needed
|
|
499
|
+
if (typeof chatbox.renderMessages === 'function') {
|
|
500
|
+
chatbox.renderMessages();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
} catch (err) {
|
|
504
|
+
console.error("❌ injectSystemMessage failed:", err);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
// Attach AI to _joe
|
|
510
|
+
if (window._joe) {
|
|
511
|
+
_joe.Ai = Ai;
|
|
512
|
+
} else {
|
|
513
|
+
console.warn('joeAI.js loaded before _joe was ready.');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
})();
|
|
517
|
+
|