json-object-editor 0.10.657 → 0.10.660
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 +5 -1
- package/docs/React_Form_Integration_Strategy.md +398 -0
- package/form-qs.json +1007 -0
- package/js/joe-ai.js +231 -31
- package/package.json +1 -1
- package/react-form-spa-ex.js +570 -0
- package/readme.md +5 -1
- package/server/fields/core.js +4 -1
- package/server/plugins/chatgpt.js +135 -3
- package/server/schemas/ai_assistant.js +15 -1
- package/server/schemas/ai_widget_conversation.js +31 -0
package/js/joe-ai.js
CHANGED
|
@@ -773,7 +773,9 @@
|
|
|
773
773
|
const payload = {
|
|
774
774
|
model: this.model || undefined,
|
|
775
775
|
ai_assistant_id: this.getAttribute('ai_assistant_id') || undefined,
|
|
776
|
-
source: this.getAttribute('source') || 'widget'
|
|
776
|
+
source: this.getAttribute('source') || 'widget',
|
|
777
|
+
scope_itemtype: this.getAttribute('scope_itemtype') || undefined,
|
|
778
|
+
scope_id: this.getAttribute('scope_id') || undefined
|
|
777
779
|
};
|
|
778
780
|
// Resolve the effective user for this widget and pass id/name/color
|
|
779
781
|
// explicitly to the server. This works for:
|
|
@@ -1362,29 +1364,11 @@
|
|
|
1362
1364
|
|
|
1363
1365
|
|
|
1364
1366
|
//**YES**
|
|
1367
|
+
// Legacy assistants-based object chat helper (kept for reference); the new
|
|
1368
|
+
// object chat path uses the widget + ai_widget_conversation stack instead.
|
|
1365
1369
|
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});
|
|
1370
|
+
console.warn('spawnChatHelper (assistants chat) is legacy; use object chat widget instead.');
|
|
1371
|
+
return;
|
|
1388
1372
|
}
|
|
1389
1373
|
Ai.getDefaultAssistant = function() {
|
|
1390
1374
|
|
|
@@ -1573,7 +1557,208 @@
|
|
|
1573
1557
|
} catch (err) {
|
|
1574
1558
|
console.error("❌ injectSystemMessage failed:", err);
|
|
1575
1559
|
}
|
|
1576
|
-
|
|
1560
|
+
};
|
|
1561
|
+
|
|
1562
|
+
// Optional helpers for making object chat shells draggable / resizable.
|
|
1563
|
+
// Kept behind flags so they can be toggled or iterated on without
|
|
1564
|
+
// affecting core behavior.
|
|
1565
|
+
Ai.objectChatDraggable = false;
|
|
1566
|
+
Ai.objectChatResizable = false;
|
|
1567
|
+
Ai.enableObjectChatDrag = function(shell){
|
|
1568
|
+
try{
|
|
1569
|
+
var header = shell.querySelector('.joe-object-chat-header');
|
|
1570
|
+
if (!header) { return; }
|
|
1571
|
+
let dragging = false;
|
|
1572
|
+
let dragOffsetX = 0, dragOffsetY = 0;
|
|
1573
|
+
header.addEventListener('pointerdown', function(e){
|
|
1574
|
+
dragging = true;
|
|
1575
|
+
shell.setPointerCapture(e.pointerId);
|
|
1576
|
+
dragOffsetX = e.clientX - shell.offsetLeft;
|
|
1577
|
+
dragOffsetY = e.clientY - shell.offsetTop;
|
|
1578
|
+
});
|
|
1579
|
+
header.addEventListener('pointermove', function(e){
|
|
1580
|
+
if (!dragging) return;
|
|
1581
|
+
const x = e.clientX - dragOffsetX;
|
|
1582
|
+
const y = e.clientY - dragOffsetY;
|
|
1583
|
+
shell.style.left = x + 'px';
|
|
1584
|
+
shell.style.top = y + 'px';
|
|
1585
|
+
shell.style.right = 'auto';
|
|
1586
|
+
shell.style.bottom = 'auto';
|
|
1587
|
+
});
|
|
1588
|
+
header.addEventListener('pointerup', function(e){
|
|
1589
|
+
if (!dragging) return;
|
|
1590
|
+
dragging = false;
|
|
1591
|
+
try{ shell.releasePointerCapture(e.pointerId); }catch(_e){}
|
|
1592
|
+
});
|
|
1593
|
+
}catch(e){
|
|
1594
|
+
console.warn('enableObjectChatDrag error', e);
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
Ai.enableObjectChatResize = function(shell){
|
|
1598
|
+
try{
|
|
1599
|
+
shell.style.resize = 'both';
|
|
1600
|
+
shell.style.overflow = 'hidden';
|
|
1601
|
+
}catch(e){
|
|
1602
|
+
console.warn('enableObjectChatResize error', e);
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
// Simple object-scoped widget chat launcher. For a given object id +
|
|
1607
|
+
// itemtype we maintain (per-page) a single floating panel containing:
|
|
1608
|
+
// - <joe-ai-assistant-picker>
|
|
1609
|
+
// - <joe-ai-widget>
|
|
1610
|
+
// This reuses the AIHub chat stack, but adds scope_itemtype/scope_id so
|
|
1611
|
+
// the backend can attach object files and context.
|
|
1612
|
+
Ai._objectWidgets = Ai._objectWidgets || {};
|
|
1613
|
+
Ai.openObjectChatLauncher = function(object_id, itemtype, name){
|
|
1614
|
+
try{
|
|
1615
|
+
if (!object_id) { return; }
|
|
1616
|
+
var key = (itemtype || 'item') + ':' + object_id;
|
|
1617
|
+
var existing = Ai._objectWidgets[key];
|
|
1618
|
+
if (existing && document.body.contains(existing)) {
|
|
1619
|
+
existing.scrollIntoView({ behavior:'smooth', block:'end' });
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
var isMobile = (window.innerWidth || document.documentElement.clientWidth || 0) <= 768;
|
|
1623
|
+
var shell = document.createElement('div');
|
|
1624
|
+
shell.className = 'joe-object-chat-shell';
|
|
1625
|
+
|
|
1626
|
+
var safeKey = key.replace(/[^a-zA-Z0-9_\-]/g,'_');
|
|
1627
|
+
var widgetId = 'object_chat_'+safeKey;
|
|
1628
|
+
var title = name || (itemtype+': '+object_id);
|
|
1629
|
+
|
|
1630
|
+
// Header with assistant picker + close button
|
|
1631
|
+
var header = document.createElement('div');
|
|
1632
|
+
header.className = 'joe-object-chat-header';
|
|
1633
|
+
header.style.display = 'flex';
|
|
1634
|
+
header.style.alignItems = 'center';
|
|
1635
|
+
header.style.justifyContent = 'space-between';
|
|
1636
|
+
header.style.padding = '4px 8px';
|
|
1637
|
+
header.style.background = '#111827';
|
|
1638
|
+
header.style.color = '#f9fafb';
|
|
1639
|
+
header.style.cursor = isMobile ? 'default' : 'move';
|
|
1640
|
+
|
|
1641
|
+
var titleSpan = document.createElement('span');
|
|
1642
|
+
titleSpan.textContent = title;
|
|
1643
|
+
titleSpan.style.fontSize = '12px';
|
|
1644
|
+
titleSpan.style.marginRight = '8px';
|
|
1645
|
+
|
|
1646
|
+
var picker = document.createElement('joe-ai-assistant-picker');
|
|
1647
|
+
picker.setAttribute('for_widget', widgetId);
|
|
1648
|
+
picker.setAttribute('source', 'object_chat');
|
|
1649
|
+
picker.style.flex = '1 1 auto';
|
|
1650
|
+
picker.style.marginRight = '8px';
|
|
1651
|
+
|
|
1652
|
+
var closeBtn = document.createElement('button');
|
|
1653
|
+
closeBtn.type = 'button';
|
|
1654
|
+
closeBtn.textContent = '×';
|
|
1655
|
+
closeBtn.title = 'Close chat';
|
|
1656
|
+
closeBtn.style.border = 'none';
|
|
1657
|
+
closeBtn.style.background = 'transparent';
|
|
1658
|
+
closeBtn.style.color = '#f9fafb';
|
|
1659
|
+
closeBtn.style.cursor = 'pointer';
|
|
1660
|
+
closeBtn.style.fontSize = '16px';
|
|
1661
|
+
closeBtn.style.lineHeight = '16px';
|
|
1662
|
+
closeBtn.style.padding = '0 4px';
|
|
1663
|
+
|
|
1664
|
+
header.appendChild(titleSpan);
|
|
1665
|
+
header.appendChild(picker);
|
|
1666
|
+
header.appendChild(closeBtn);
|
|
1667
|
+
|
|
1668
|
+
// Widget body
|
|
1669
|
+
var w = document.createElement('joe-ai-widget');
|
|
1670
|
+
w.setAttribute('id', widgetId);
|
|
1671
|
+
w.setAttribute('title', title);
|
|
1672
|
+
w.setAttribute('source','object_chat');
|
|
1673
|
+
w.setAttribute('scope_itemtype', itemtype || '');
|
|
1674
|
+
w.setAttribute('scope_id', object_id);
|
|
1675
|
+
// Default assistant for this widget; picker can override per-conversation.
|
|
1676
|
+
try{
|
|
1677
|
+
// Ai.getDefaultAssistant returns the DEFAULT_AI_ASSISTANT setting
|
|
1678
|
+
// record, whose .value is the ai_assistant _id we want to use here.
|
|
1679
|
+
var defSetting = Ai.getDefaultAssistant && Ai.getDefaultAssistant();
|
|
1680
|
+
if (defSetting && defSetting.value){
|
|
1681
|
+
w.setAttribute('ai_assistant_id', defSetting.value);
|
|
1682
|
+
}
|
|
1683
|
+
}catch(_e){}
|
|
1684
|
+
// Pass current user id through so widgetStart/widgetMessage can
|
|
1685
|
+
// attribute the conversation correctly.
|
|
1686
|
+
try{
|
|
1687
|
+
if (_joe && _joe.User && _joe.User._id){
|
|
1688
|
+
w.setAttribute('user_id', _joe.User._id);
|
|
1689
|
+
// Also set the property so resolveUser sees it even if the
|
|
1690
|
+
// attribute change happens after the custom element constructor.
|
|
1691
|
+
w.user_id = _joe.User._id;
|
|
1692
|
+
}
|
|
1693
|
+
}catch(_e){}
|
|
1694
|
+
w.style.display = 'block';
|
|
1695
|
+
w.style.width = '100%';
|
|
1696
|
+
w.style.height = '100%';
|
|
1697
|
+
|
|
1698
|
+
shell.appendChild(header);
|
|
1699
|
+
shell.appendChild(w);
|
|
1700
|
+
|
|
1701
|
+
// Positioning and sizing
|
|
1702
|
+
shell.style.position = 'fixed';
|
|
1703
|
+
shell.style.zIndex = '10000';
|
|
1704
|
+
shell.style.boxShadow = '0 8px 24px rgba(15,23,42,0.4)';
|
|
1705
|
+
shell.style.background = '#f3f4f6';
|
|
1706
|
+
shell.style.border = '1px solid #e5e7eb';
|
|
1707
|
+
shell.style.borderRadius = '10px 0 0 10px';
|
|
1708
|
+
shell.style.overflow = 'hidden';
|
|
1709
|
+
shell.style.display = 'flex';
|
|
1710
|
+
shell.style.flexDirection = 'column';
|
|
1711
|
+
|
|
1712
|
+
function applyDesktopLayout(){
|
|
1713
|
+
shell.style.width = '420px';
|
|
1714
|
+
shell.style.height = '60vh';
|
|
1715
|
+
shell.style.right = '0px';
|
|
1716
|
+
shell.style.bottom = '50px';
|
|
1717
|
+
shell.style.left = 'auto';
|
|
1718
|
+
shell.style.top = 'auto';
|
|
1719
|
+
shell.style.resize = 'both';
|
|
1720
|
+
}
|
|
1721
|
+
function applyMobileLayout(){
|
|
1722
|
+
shell.style.width = '100vw';
|
|
1723
|
+
shell.style.height = '50vh';
|
|
1724
|
+
shell.style.left = '0px';
|
|
1725
|
+
shell.style.right = '0px';
|
|
1726
|
+
shell.style.bottom = '0px';
|
|
1727
|
+
shell.style.top = 'auto';
|
|
1728
|
+
shell.style.resize = 'none';
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
if (isMobile){
|
|
1732
|
+
applyMobileLayout();
|
|
1733
|
+
}else{
|
|
1734
|
+
applyDesktopLayout();
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
document.body.appendChild(shell);
|
|
1738
|
+
Ai._objectWidgets[key] = shell;
|
|
1739
|
+
|
|
1740
|
+
// Close behavior
|
|
1741
|
+
closeBtn.addEventListener('click', function(){
|
|
1742
|
+
try{
|
|
1743
|
+
if (Ai._objectWidgets[key] === shell){
|
|
1744
|
+
delete Ai._objectWidgets[key];
|
|
1745
|
+
}
|
|
1746
|
+
}catch(_e){}
|
|
1747
|
+
shell.remove();
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
if (!isMobile){
|
|
1751
|
+
if (Ai.objectChatDraggable) {
|
|
1752
|
+
Ai.enableObjectChatDrag(shell);
|
|
1753
|
+
}
|
|
1754
|
+
if (Ai.objectChatResizable) {
|
|
1755
|
+
Ai.enableObjectChatResize(shell);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}catch(e){
|
|
1759
|
+
console.error('openObjectChatLauncher error', e);
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1577
1762
|
|
|
1578
1763
|
// ---------- Autofill (Responses) ----------
|
|
1579
1764
|
// Usage from schema: add `ai:{ prompt:'...' }` to a field. Core will render a
|
|
@@ -1879,13 +2064,23 @@
|
|
|
1879
2064
|
var schema = (_joe && _joe.current && _joe.current.schema) || {};
|
|
1880
2065
|
// Prefer the live, constructed field defs; fall back to schema.fields
|
|
1881
2066
|
var liveFieldDefs = (_joe && _joe.current && Array.isArray(_joe.current.fields) && _joe.current.fields) || [];
|
|
1882
|
-
var schemaFields =
|
|
2067
|
+
var schemaFields = [];
|
|
2068
|
+
if (schema && schema.fields) {
|
|
2069
|
+
if (Array.isArray(schema.fields)) {
|
|
2070
|
+
schemaFields = schema.fields;
|
|
2071
|
+
} else if (typeof schema.fields === 'function') {
|
|
2072
|
+
try{
|
|
2073
|
+
var tmp = schema.fields();
|
|
2074
|
+
if (Array.isArray(tmp)) { schemaFields = tmp; }
|
|
2075
|
+
}catch(_e){}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
1883
2078
|
var fieldNames = [];
|
|
1884
2079
|
// collect from live constructed defs
|
|
1885
2080
|
liveFieldDefs.forEach(function(fd){
|
|
1886
2081
|
if(fd && fd.name && fd.type === 'uploader'){ fieldNames.push(fd.name); }
|
|
1887
2082
|
});
|
|
1888
|
-
// also collect from raw schema (string or object), dedupe
|
|
2083
|
+
// also collect from raw schema (string or object or spec), dedupe
|
|
1889
2084
|
schemaFields.forEach(function(f){
|
|
1890
2085
|
var fname = (typeof f === 'string') ? f : (f && (f.name || f.field || f.prop));
|
|
1891
2086
|
if(!fname){ return; }
|
|
@@ -2065,11 +2260,16 @@
|
|
|
2065
2260
|
};
|
|
2066
2261
|
|
|
2067
2262
|
|
|
2068
|
-
// Attach AI to _joe
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2263
|
+
// Attach AI to _joe. On some pages joe-ai.js may load before _joe is
|
|
2264
|
+
// constructed; in that case, defer attachment until window load.
|
|
2265
|
+
function attachAItoJoe(){
|
|
2266
|
+
if (!window._joe) { return false; }
|
|
2267
|
+
if (!_joe.Ai) { _joe.Ai = Ai; }
|
|
2268
|
+
return true;
|
|
2269
|
+
}
|
|
2270
|
+
if (!attachAItoJoe()) {
|
|
2271
|
+
console.warn('joeAI.js loaded before _joe was ready; deferring Ai attach.');
|
|
2272
|
+
window.addEventListener('load', attachAItoJoe);
|
|
2073
2273
|
}
|
|
2074
2274
|
|
|
2075
2275
|
})();
|