json-object-editor 0.10.657 → 0.10.662
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 +15 -1
- package/css/joe-styles.css +1 -1
- package/css/joe.css +2 -2
- package/css/joe.min.css +1 -1
- package/docs/JOE_AI_Overview.md +73 -17
- package/docs/React_Form_Integration_Example.md +299 -0
- package/docs/React_Form_Integration_Strategy.md +397 -0
- package/dummy +9 -1
- package/form-qs.json +1007 -0
- package/js/joe-ai.js +257 -31
- package/js/joe-react-form.js +608 -0
- package/js/joe.js +1 -1
- package/package.json +1 -1
- package/react-form-spa-ex.js +570 -0
- package/readme.md +13 -1
- package/server/fields/core.js +52 -2
- package/server/modules/MCP.js +4 -0
- package/server/modules/Sites.js +28 -2
- package/server/plugins/chatgpt.js +304 -24
- package/server/plugins/formBuilder.js +98 -0
- package/server/schemas/ai_assistant.js +58 -45
- package/server/schemas/ai_prompt.js +4 -58
- package/server/schemas/ai_widget_conversation.js +31 -0
- package/server/schemas/include.js +8 -3
- package/server/schemas/page.js +6 -0
package/js/joe-ai.js
CHANGED
|
@@ -27,6 +27,28 @@
|
|
|
27
27
|
.replace(/\n/g, '<br>');
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// Load MCP manifest once per page and expose tool names via Ai.mcpToolNames
|
|
31
|
+
Ai.mcpToolNames = [];
|
|
32
|
+
(function initMcpToolNames(){
|
|
33
|
+
try{
|
|
34
|
+
if (typeof fetch !== 'function') { return; }
|
|
35
|
+
fetch('/.well-known/mcp/manifest.json')
|
|
36
|
+
.then(function(r){
|
|
37
|
+
if (!r.ok){ throw new Error('HTTP '+r.status); }
|
|
38
|
+
return r.json();
|
|
39
|
+
})
|
|
40
|
+
.then(function(man){
|
|
41
|
+
try{
|
|
42
|
+
var tools = (man && man.tools) || [];
|
|
43
|
+
Ai.mcpToolNames = tools.map(function(t){ return t && t.name; }).filter(Boolean).sort();
|
|
44
|
+
}catch(_e){}
|
|
45
|
+
})
|
|
46
|
+
.catch(function(e){
|
|
47
|
+
console.warn('[joe-ai] MCP manifest load failed', e);
|
|
48
|
+
});
|
|
49
|
+
}catch(_e){}
|
|
50
|
+
})();
|
|
51
|
+
|
|
30
52
|
class JoeAIChatbox extends HTMLElement {
|
|
31
53
|
constructor() {
|
|
32
54
|
super();
|
|
@@ -773,7 +795,9 @@
|
|
|
773
795
|
const payload = {
|
|
774
796
|
model: this.model || undefined,
|
|
775
797
|
ai_assistant_id: this.getAttribute('ai_assistant_id') || undefined,
|
|
776
|
-
source: this.getAttribute('source') || 'widget'
|
|
798
|
+
source: this.getAttribute('source') || 'widget',
|
|
799
|
+
scope_itemtype: this.getAttribute('scope_itemtype') || undefined,
|
|
800
|
+
scope_id: this.getAttribute('scope_id') || undefined
|
|
777
801
|
};
|
|
778
802
|
// Resolve the effective user for this widget and pass id/name/color
|
|
779
803
|
// explicitly to the server. This works for:
|
|
@@ -871,7 +895,11 @@
|
|
|
871
895
|
conversation_id: this.conversation_id,
|
|
872
896
|
content: text,
|
|
873
897
|
role: 'user',
|
|
898
|
+
// Legacy OpenAI Assistants id (if present) plus the current JOE
|
|
899
|
+
// ai_assistant id. The backend prefers the JOE id (ai_assistant_id)
|
|
900
|
+
// and falls back to assistant_id only when needed.
|
|
874
901
|
assistant_id: this.assistant_id || undefined,
|
|
902
|
+
ai_assistant_id: this.getAttribute('ai_assistant_id') || undefined,
|
|
875
903
|
model: this.model || undefined
|
|
876
904
|
};
|
|
877
905
|
try {
|
|
@@ -1362,29 +1390,11 @@
|
|
|
1362
1390
|
|
|
1363
1391
|
|
|
1364
1392
|
//**YES**
|
|
1393
|
+
// Legacy assistants-based object chat helper (kept for reference); the new
|
|
1394
|
+
// object chat path uses the widget + ai_widget_conversation stack instead.
|
|
1365
1395
|
Ai.spawnChatHelper = async function(object_id,user_id=_joe.User._id,conversation_id) {
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
var newChat = false;
|
|
1369
|
-
if(!convo_id){
|
|
1370
|
-
const response = await fetch('/API/plugin/chatgpt-assistants/createConversation', {
|
|
1371
|
-
method: 'POST',
|
|
1372
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1373
|
-
body: JSON.stringify({
|
|
1374
|
-
object_id,
|
|
1375
|
-
user_id
|
|
1376
|
-
})
|
|
1377
|
-
}).then(res => res.json());
|
|
1378
|
-
convo_id = response?.conversation?._id;
|
|
1379
|
-
newChat = true;
|
|
1380
|
-
if(response.error){
|
|
1381
|
-
console.error('❌ Failed to create conversation:', response.error);
|
|
1382
|
-
alert('Failed to create conversation: '+response.message);
|
|
1383
|
-
return;
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
await _joe.Ai.spawnContextualChat(convo_id,{object_id,newChat});
|
|
1396
|
+
console.warn('spawnChatHelper (assistants chat) is legacy; use object chat widget instead.');
|
|
1397
|
+
return;
|
|
1388
1398
|
}
|
|
1389
1399
|
Ai.getDefaultAssistant = function() {
|
|
1390
1400
|
|
|
@@ -1573,7 +1583,208 @@
|
|
|
1573
1583
|
} catch (err) {
|
|
1574
1584
|
console.error("❌ injectSystemMessage failed:", err);
|
|
1575
1585
|
}
|
|
1576
|
-
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
// Optional helpers for making object chat shells draggable / resizable.
|
|
1589
|
+
// Kept behind flags so they can be toggled or iterated on without
|
|
1590
|
+
// affecting core behavior.
|
|
1591
|
+
Ai.objectChatDraggable = false;
|
|
1592
|
+
Ai.objectChatResizable = false;
|
|
1593
|
+
Ai.enableObjectChatDrag = function(shell){
|
|
1594
|
+
try{
|
|
1595
|
+
var header = shell.querySelector('.joe-object-chat-header');
|
|
1596
|
+
if (!header) { return; }
|
|
1597
|
+
let dragging = false;
|
|
1598
|
+
let dragOffsetX = 0, dragOffsetY = 0;
|
|
1599
|
+
header.addEventListener('pointerdown', function(e){
|
|
1600
|
+
dragging = true;
|
|
1601
|
+
shell.setPointerCapture(e.pointerId);
|
|
1602
|
+
dragOffsetX = e.clientX - shell.offsetLeft;
|
|
1603
|
+
dragOffsetY = e.clientY - shell.offsetTop;
|
|
1604
|
+
});
|
|
1605
|
+
header.addEventListener('pointermove', function(e){
|
|
1606
|
+
if (!dragging) return;
|
|
1607
|
+
const x = e.clientX - dragOffsetX;
|
|
1608
|
+
const y = e.clientY - dragOffsetY;
|
|
1609
|
+
shell.style.left = x + 'px';
|
|
1610
|
+
shell.style.top = y + 'px';
|
|
1611
|
+
shell.style.right = 'auto';
|
|
1612
|
+
shell.style.bottom = 'auto';
|
|
1613
|
+
});
|
|
1614
|
+
header.addEventListener('pointerup', function(e){
|
|
1615
|
+
if (!dragging) return;
|
|
1616
|
+
dragging = false;
|
|
1617
|
+
try{ shell.releasePointerCapture(e.pointerId); }catch(_e){}
|
|
1618
|
+
});
|
|
1619
|
+
}catch(e){
|
|
1620
|
+
console.warn('enableObjectChatDrag error', e);
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
Ai.enableObjectChatResize = function(shell){
|
|
1624
|
+
try{
|
|
1625
|
+
shell.style.resize = 'both';
|
|
1626
|
+
shell.style.overflow = 'hidden';
|
|
1627
|
+
}catch(e){
|
|
1628
|
+
console.warn('enableObjectChatResize error', e);
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
// Simple object-scoped widget chat launcher. For a given object id +
|
|
1633
|
+
// itemtype we maintain (per-page) a single floating panel containing:
|
|
1634
|
+
// - <joe-ai-assistant-picker>
|
|
1635
|
+
// - <joe-ai-widget>
|
|
1636
|
+
// This reuses the AIHub chat stack, but adds scope_itemtype/scope_id so
|
|
1637
|
+
// the backend can attach object files and context.
|
|
1638
|
+
Ai._objectWidgets = Ai._objectWidgets || {};
|
|
1639
|
+
Ai.openObjectChatLauncher = function(object_id, itemtype, name){
|
|
1640
|
+
try{
|
|
1641
|
+
if (!object_id) { return; }
|
|
1642
|
+
var key = (itemtype || 'item') + ':' + object_id;
|
|
1643
|
+
var existing = Ai._objectWidgets[key];
|
|
1644
|
+
if (existing && document.body.contains(existing)) {
|
|
1645
|
+
existing.scrollIntoView({ behavior:'smooth', block:'end' });
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
var isMobile = (window.innerWidth || document.documentElement.clientWidth || 0) <= 768;
|
|
1649
|
+
var shell = document.createElement('div');
|
|
1650
|
+
shell.className = 'joe-object-chat-shell';
|
|
1651
|
+
|
|
1652
|
+
var safeKey = key.replace(/[^a-zA-Z0-9_\-]/g,'_');
|
|
1653
|
+
var widgetId = 'object_chat_'+safeKey;
|
|
1654
|
+
var title = name || (itemtype+': '+object_id);
|
|
1655
|
+
|
|
1656
|
+
// Header with assistant picker + close button
|
|
1657
|
+
var header = document.createElement('div');
|
|
1658
|
+
header.className = 'joe-object-chat-header';
|
|
1659
|
+
header.style.display = 'flex';
|
|
1660
|
+
header.style.alignItems = 'center';
|
|
1661
|
+
header.style.justifyContent = 'space-between';
|
|
1662
|
+
header.style.padding = '4px 8px';
|
|
1663
|
+
header.style.background = '#111827';
|
|
1664
|
+
header.style.color = '#f9fafb';
|
|
1665
|
+
header.style.cursor = isMobile ? 'default' : 'move';
|
|
1666
|
+
|
|
1667
|
+
var titleSpan = document.createElement('span');
|
|
1668
|
+
titleSpan.textContent = title;
|
|
1669
|
+
titleSpan.style.fontSize = '12px';
|
|
1670
|
+
titleSpan.style.marginRight = '8px';
|
|
1671
|
+
|
|
1672
|
+
var picker = document.createElement('joe-ai-assistant-picker');
|
|
1673
|
+
picker.setAttribute('for_widget', widgetId);
|
|
1674
|
+
picker.setAttribute('source', 'object_chat');
|
|
1675
|
+
picker.style.flex = '1 1 auto';
|
|
1676
|
+
picker.style.marginRight = '8px';
|
|
1677
|
+
|
|
1678
|
+
var closeBtn = document.createElement('button');
|
|
1679
|
+
closeBtn.type = 'button';
|
|
1680
|
+
closeBtn.textContent = '×';
|
|
1681
|
+
closeBtn.title = 'Close chat';
|
|
1682
|
+
closeBtn.style.border = 'none';
|
|
1683
|
+
closeBtn.style.background = 'transparent';
|
|
1684
|
+
closeBtn.style.color = '#f9fafb';
|
|
1685
|
+
closeBtn.style.cursor = 'pointer';
|
|
1686
|
+
closeBtn.style.fontSize = '16px';
|
|
1687
|
+
closeBtn.style.lineHeight = '16px';
|
|
1688
|
+
closeBtn.style.padding = '0 4px';
|
|
1689
|
+
|
|
1690
|
+
header.appendChild(titleSpan);
|
|
1691
|
+
header.appendChild(picker);
|
|
1692
|
+
header.appendChild(closeBtn);
|
|
1693
|
+
|
|
1694
|
+
// Widget body
|
|
1695
|
+
var w = document.createElement('joe-ai-widget');
|
|
1696
|
+
w.setAttribute('id', widgetId);
|
|
1697
|
+
w.setAttribute('title', title);
|
|
1698
|
+
w.setAttribute('source','object_chat');
|
|
1699
|
+
w.setAttribute('scope_itemtype', itemtype || '');
|
|
1700
|
+
w.setAttribute('scope_id', object_id);
|
|
1701
|
+
// Default assistant for this widget; picker can override per-conversation.
|
|
1702
|
+
try{
|
|
1703
|
+
// Ai.getDefaultAssistant returns the DEFAULT_AI_ASSISTANT setting
|
|
1704
|
+
// record, whose .value is the ai_assistant _id we want to use here.
|
|
1705
|
+
var defSetting = Ai.getDefaultAssistant && Ai.getDefaultAssistant();
|
|
1706
|
+
if (defSetting && defSetting.value){
|
|
1707
|
+
w.setAttribute('ai_assistant_id', defSetting.value);
|
|
1708
|
+
}
|
|
1709
|
+
}catch(_e){}
|
|
1710
|
+
// Pass current user id through so widgetStart/widgetMessage can
|
|
1711
|
+
// attribute the conversation correctly.
|
|
1712
|
+
try{
|
|
1713
|
+
if (_joe && _joe.User && _joe.User._id){
|
|
1714
|
+
w.setAttribute('user_id', _joe.User._id);
|
|
1715
|
+
// Also set the property so resolveUser sees it even if the
|
|
1716
|
+
// attribute change happens after the custom element constructor.
|
|
1717
|
+
w.user_id = _joe.User._id;
|
|
1718
|
+
}
|
|
1719
|
+
}catch(_e){}
|
|
1720
|
+
w.style.display = 'block';
|
|
1721
|
+
w.style.width = '100%';
|
|
1722
|
+
w.style.height = '100%';
|
|
1723
|
+
|
|
1724
|
+
shell.appendChild(header);
|
|
1725
|
+
shell.appendChild(w);
|
|
1726
|
+
|
|
1727
|
+
// Positioning and sizing
|
|
1728
|
+
shell.style.position = 'fixed';
|
|
1729
|
+
shell.style.zIndex = '10000';
|
|
1730
|
+
shell.style.boxShadow = '0 8px 24px rgba(15,23,42,0.4)';
|
|
1731
|
+
shell.style.background = '#f3f4f6';
|
|
1732
|
+
shell.style.border = '1px solid #e5e7eb';
|
|
1733
|
+
shell.style.borderRadius = '10px 0 0 10px';
|
|
1734
|
+
shell.style.overflow = 'hidden';
|
|
1735
|
+
shell.style.display = 'flex';
|
|
1736
|
+
shell.style.flexDirection = 'column';
|
|
1737
|
+
|
|
1738
|
+
function applyDesktopLayout(){
|
|
1739
|
+
shell.style.width = '420px';
|
|
1740
|
+
shell.style.height = '60vh';
|
|
1741
|
+
shell.style.right = '0px';
|
|
1742
|
+
shell.style.bottom = '50px';
|
|
1743
|
+
shell.style.left = 'auto';
|
|
1744
|
+
shell.style.top = 'auto';
|
|
1745
|
+
shell.style.resize = 'both';
|
|
1746
|
+
}
|
|
1747
|
+
function applyMobileLayout(){
|
|
1748
|
+
shell.style.width = '100vw';
|
|
1749
|
+
shell.style.height = '50vh';
|
|
1750
|
+
shell.style.left = '0px';
|
|
1751
|
+
shell.style.right = '0px';
|
|
1752
|
+
shell.style.bottom = '0px';
|
|
1753
|
+
shell.style.top = 'auto';
|
|
1754
|
+
shell.style.resize = 'none';
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
if (isMobile){
|
|
1758
|
+
applyMobileLayout();
|
|
1759
|
+
}else{
|
|
1760
|
+
applyDesktopLayout();
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
document.body.appendChild(shell);
|
|
1764
|
+
Ai._objectWidgets[key] = shell;
|
|
1765
|
+
|
|
1766
|
+
// Close behavior
|
|
1767
|
+
closeBtn.addEventListener('click', function(){
|
|
1768
|
+
try{
|
|
1769
|
+
if (Ai._objectWidgets[key] === shell){
|
|
1770
|
+
delete Ai._objectWidgets[key];
|
|
1771
|
+
}
|
|
1772
|
+
}catch(_e){}
|
|
1773
|
+
shell.remove();
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
if (!isMobile){
|
|
1777
|
+
if (Ai.objectChatDraggable) {
|
|
1778
|
+
Ai.enableObjectChatDrag(shell);
|
|
1779
|
+
}
|
|
1780
|
+
if (Ai.objectChatResizable) {
|
|
1781
|
+
Ai.enableObjectChatResize(shell);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
}catch(e){
|
|
1785
|
+
console.error('openObjectChatLauncher error', e);
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1577
1788
|
|
|
1578
1789
|
// ---------- Autofill (Responses) ----------
|
|
1579
1790
|
// Usage from schema: add `ai:{ prompt:'...' }` to a field. Core will render a
|
|
@@ -1879,13 +2090,23 @@
|
|
|
1879
2090
|
var schema = (_joe && _joe.current && _joe.current.schema) || {};
|
|
1880
2091
|
// Prefer the live, constructed field defs; fall back to schema.fields
|
|
1881
2092
|
var liveFieldDefs = (_joe && _joe.current && Array.isArray(_joe.current.fields) && _joe.current.fields) || [];
|
|
1882
|
-
var schemaFields =
|
|
2093
|
+
var schemaFields = [];
|
|
2094
|
+
if (schema && schema.fields) {
|
|
2095
|
+
if (Array.isArray(schema.fields)) {
|
|
2096
|
+
schemaFields = schema.fields;
|
|
2097
|
+
} else if (typeof schema.fields === 'function') {
|
|
2098
|
+
try{
|
|
2099
|
+
var tmp = schema.fields();
|
|
2100
|
+
if (Array.isArray(tmp)) { schemaFields = tmp; }
|
|
2101
|
+
}catch(_e){}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
1883
2104
|
var fieldNames = [];
|
|
1884
2105
|
// collect from live constructed defs
|
|
1885
2106
|
liveFieldDefs.forEach(function(fd){
|
|
1886
2107
|
if(fd && fd.name && fd.type === 'uploader'){ fieldNames.push(fd.name); }
|
|
1887
2108
|
});
|
|
1888
|
-
// also collect from raw schema (string or object), dedupe
|
|
2109
|
+
// also collect from raw schema (string or object or spec), dedupe
|
|
1889
2110
|
schemaFields.forEach(function(f){
|
|
1890
2111
|
var fname = (typeof f === 'string') ? f : (f && (f.name || f.field || f.prop));
|
|
1891
2112
|
if(!fname){ return; }
|
|
@@ -2065,11 +2286,16 @@
|
|
|
2065
2286
|
};
|
|
2066
2287
|
|
|
2067
2288
|
|
|
2068
|
-
// Attach AI to _joe
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2289
|
+
// Attach AI to _joe. On some pages joe-ai.js may load before _joe is
|
|
2290
|
+
// constructed; in that case, defer attachment until window load.
|
|
2291
|
+
function attachAItoJoe(){
|
|
2292
|
+
if (!window._joe) { return false; }
|
|
2293
|
+
if (!_joe.Ai) { _joe.Ai = Ai; }
|
|
2294
|
+
return true;
|
|
2295
|
+
}
|
|
2296
|
+
if (!attachAItoJoe()) {
|
|
2297
|
+
console.warn('joeAI.js loaded before _joe was ready; deferring Ai attach.');
|
|
2298
|
+
window.addEventListener('load', attachAItoJoe);
|
|
2073
2299
|
}
|
|
2074
2300
|
|
|
2075
2301
|
})();
|