json-object-editor 0.10.625 → 0.10.633
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 +6 -0
- package/_www/ai-widget-test.html +367 -0
- package/_www/mcp-test.html +10 -1
- package/css/joe-styles.css +11 -3
- package/css/joe.css +12 -4
- package/css/joe.min.css +1 -1
- package/docs/joe_agent_custom_gpt_instructions_v_3.md +9 -0
- package/dummy +10 -0
- package/img/svgs/ai_assistant.svg +1 -0
- package/img/svgs/ai_assistant_white.svg +1 -0
- package/js/JsonObjectEditor.jquery.craydent.js +34 -3
- package/js/joe-ai.js +784 -52
- package/js/joe.js +52 -21
- package/js/joe.min.js +1 -1
- package/package.json +1 -1
- package/readme.md +8 -1
- package/server/apps/aihub.js +97 -0
- package/server/fields/core.js +4 -1
- package/server/modules/MCP.js +233 -2
- package/server/modules/Server.js +1 -46
- package/server/plugins/auth.js +34 -30
- package/server/plugins/chatgpt-assistants.js +70 -35
- package/server/plugins/chatgpt.js +560 -44
- package/server/schemas/ai_assistant.js +149 -1
- package/server/schemas/ai_conversation.js +14 -1
- package/server/schemas/ai_widget_conversation.js +133 -14
- package/server/schemas/project.js +27 -3
- package/server/schemas/task.js +6 -3
|
@@ -8,7 +8,7 @@ var schema = {
|
|
|
8
8
|
// Curated summary for agents and tools
|
|
9
9
|
summary:{
|
|
10
10
|
description:'Configuration record for an AI assistant connected to OpenAI.',
|
|
11
|
-
purpose:'Use to
|
|
11
|
+
purpose:'Use ai_assistant to define the model, instructions, tools (MCP-backed), color, and OpenAI assistant_id for chat experiences in JOE (in-app chatbox and embeddable widget). One ai_assistant typically maps to a single OpenAI assistant and can be marked as the default via the DEFAULT_AI_ASSISTANT setting.',
|
|
12
12
|
labelField:'name',
|
|
13
13
|
defaultSort:{ field:'joeUpdated', dir:'desc' },
|
|
14
14
|
searchableFields:['name','info','assistant_id','ai_model','tags','datasets'],
|
|
@@ -33,6 +33,7 @@ var schema = {
|
|
|
33
33
|
{ name:'file_search_enabled', type:'boolean' },
|
|
34
34
|
{ name:'code_interpreter_enabled', type:'boolean' },
|
|
35
35
|
{ name:'assistant_thinking_text', type:'string' },
|
|
36
|
+
{ name:'assistant_color', type:'string' },
|
|
36
37
|
{ name:'instructions', type:'string' },
|
|
37
38
|
{ name:'tools', type:'string' },
|
|
38
39
|
{ name:'files', type:'string', isArray:true, isReference:true, targetSchema:'file' },
|
|
@@ -129,6 +130,117 @@ var schema = {
|
|
|
129
130
|
$button.prop('disabled', false);
|
|
130
131
|
$button.html(originalHTML);
|
|
131
132
|
});
|
|
133
|
+
},
|
|
134
|
+
loadMCPTolsIntoAssistant: async function(currentObject, event){
|
|
135
|
+
try{
|
|
136
|
+
var $button = $(event.target).closest('joe-button');
|
|
137
|
+
var originalHTML = $button.html();
|
|
138
|
+
$button.prop('disabled', true);
|
|
139
|
+
$button.html('Loading MCP tools...');
|
|
140
|
+
|
|
141
|
+
var base = location.origin.replace(/\/$/,'');
|
|
142
|
+
var manifestUrl = base + '/.well-known/mcp/manifest.json';
|
|
143
|
+
const manifest = await fetch(manifestUrl).then(r=>{
|
|
144
|
+
if(!r.ok){ throw new Error('HTTP '+r.status); }
|
|
145
|
+
return r.json();
|
|
146
|
+
});
|
|
147
|
+
var tools = (manifest.tools||[]).map(function(t){
|
|
148
|
+
return {
|
|
149
|
+
type:'function',
|
|
150
|
+
function:{
|
|
151
|
+
name: t.name,
|
|
152
|
+
description: t.description || '',
|
|
153
|
+
parameters: t.params || { type:'object' }
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
var json = JSON.stringify(tools, null, 2);
|
|
158
|
+
|
|
159
|
+
// Update current object and visible tools field via core helper
|
|
160
|
+
currentObject.tools = json;
|
|
161
|
+
if (window._joe && _joe.Fields && typeof _joe.Fields.set === 'function') {
|
|
162
|
+
_joe.Fields.set('tools', json);
|
|
163
|
+
} else {
|
|
164
|
+
// Fallback to direct DOM update if helper is unavailable
|
|
165
|
+
var $field = $('.joe-object-field[data-name="tools"]').find('.joe-field').eq(0);
|
|
166
|
+
if($field && $field.length){
|
|
167
|
+
$field.val(json);
|
|
168
|
+
$field.trigger('input').trigger('change');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
$button.prop('disabled', false);
|
|
173
|
+
$button.html(originalHTML);
|
|
174
|
+
(_joe.toast && _joe.toast('Loaded '+tools.length+' MCP tools into assistant.')) || alert('Loaded '+tools.length+' MCP tools.');
|
|
175
|
+
}catch(e){
|
|
176
|
+
console.error('loadMCPTolsIntoAssistant error', e);
|
|
177
|
+
alert('Failed to load MCP tools: '+(e.message||e));
|
|
178
|
+
try{
|
|
179
|
+
var $button = $(event.target).closest('joe-button');
|
|
180
|
+
$button.prop('disabled', false);
|
|
181
|
+
}catch(_e){}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
setAsDefaultAssistant: async function(currentObject, event){
|
|
185
|
+
try{
|
|
186
|
+
if(!currentObject || !currentObject._id){
|
|
187
|
+
alert('No assistant loaded'); return;
|
|
188
|
+
}
|
|
189
|
+
var $button = $(event.target).closest('joe-button');
|
|
190
|
+
var originalHTML = $button.html();
|
|
191
|
+
$button.prop('disabled', true);
|
|
192
|
+
$button.html('Setting default...');
|
|
193
|
+
|
|
194
|
+
// Find existing DEFAULT_AI_ASSISTANT setting (if any)
|
|
195
|
+
var settings = (_joe && _joe.Data && _joe.Data.setting) || [];
|
|
196
|
+
var existing = settings.where({name:'DEFAULT_AI_ASSISTANT'})[0] || null;
|
|
197
|
+
|
|
198
|
+
// Build querystring for /API/save
|
|
199
|
+
var params = new URLSearchParams();
|
|
200
|
+
params.set('itemtype','setting');
|
|
201
|
+
params.set('name','DEFAULT_AI_ASSISTANT');
|
|
202
|
+
params.set('setting_type', (existing && existing.setting_type) || 'text');
|
|
203
|
+
params.set('info', (existing && existing.info) || 'Default ai_assistant used for chats and widgets.');
|
|
204
|
+
params.set('value', currentObject._id);
|
|
205
|
+
|
|
206
|
+
var url;
|
|
207
|
+
if(existing && existing._id){
|
|
208
|
+
url = '/API/save/' + encodeURIComponent(existing._id) + '?' + params.toString();
|
|
209
|
+
}else{
|
|
210
|
+
url = '/API/save/?' + params.toString();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const resp = await fetch(url, {
|
|
214
|
+
method: 'GET',
|
|
215
|
+
credentials: 'include'
|
|
216
|
+
}).then(r => r.json());
|
|
217
|
+
|
|
218
|
+
if(!resp || resp.status !== 'success'){
|
|
219
|
+
const msg = (resp && resp.error) || 'Save failed';
|
|
220
|
+
throw new Error(msg);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
var saved = resp.results || resp.object || null;
|
|
224
|
+
if(saved){
|
|
225
|
+
if(existing){
|
|
226
|
+
Object.assign(existing, saved);
|
|
227
|
+
}else{
|
|
228
|
+
_joe.Data.setting = _joe.Data.setting || [];
|
|
229
|
+
_joe.Data.setting.push(saved);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
$button.prop('disabled', false);
|
|
234
|
+
$button.html(originalHTML);
|
|
235
|
+
(_joe.toast && _joe.toast('Default assistant set.')) || alert('Default assistant set.');
|
|
236
|
+
}catch(e){
|
|
237
|
+
console.error('setAsDefaultAssistant error', e);
|
|
238
|
+
alert('Failed to set default assistant: '+(e.message||e));
|
|
239
|
+
try{
|
|
240
|
+
var $button = $(event.target).closest('joe-button');
|
|
241
|
+
$button.prop('disabled', false);
|
|
242
|
+
}catch(_e){}
|
|
243
|
+
}
|
|
132
244
|
}
|
|
133
245
|
},
|
|
134
246
|
fields: function() {
|
|
@@ -171,6 +283,7 @@ var schema = {
|
|
|
171
283
|
info: "What the user should see while the Assistant is thinking.",
|
|
172
284
|
placeholder: "e.g. 'Assistant is preparing a response...'"
|
|
173
285
|
},
|
|
286
|
+
{ name: "assistant_color", type: "color", display: "Assistant Color", default: "teal" },
|
|
174
287
|
{ name: "instructions", type: "wysiwyg" }, // Full TinyMCE editor
|
|
175
288
|
{ section_end: "instructions" },
|
|
176
289
|
|
|
@@ -188,6 +301,15 @@ var schema = {
|
|
|
188
301
|
|
|
189
302
|
height: "300px"
|
|
190
303
|
},
|
|
304
|
+
{
|
|
305
|
+
name: "load_mcp_tools",
|
|
306
|
+
type: "button",
|
|
307
|
+
display: "Load MCP Tools",
|
|
308
|
+
method: "loadMCPTolsIntoAssistant",
|
|
309
|
+
color: "blue",
|
|
310
|
+
title: "Fetch MCP tools from this JOE instance and populate the tools JSON array."
|
|
311
|
+
},
|
|
312
|
+
|
|
191
313
|
{ section_end: "tools" },
|
|
192
314
|
|
|
193
315
|
{ section_start: "files" },
|
|
@@ -234,6 +356,32 @@ var schema = {
|
|
|
234
356
|
color: "green",
|
|
235
357
|
title: "Sync this Assistant with OpenAI"
|
|
236
358
|
},
|
|
359
|
+
{
|
|
360
|
+
name: "set_default_assistant",
|
|
361
|
+
type: "button",
|
|
362
|
+
display: "Set as Default Assistant",
|
|
363
|
+
method: "setAsDefaultAssistant",
|
|
364
|
+
color: "orange",
|
|
365
|
+
hidden:function(ai_assistant){
|
|
366
|
+
try{
|
|
367
|
+
var settings = (_joe && _joe.Data && _joe.Data.setting) || [];
|
|
368
|
+
var existing = settings.where({name:'DEFAULT_AI_ASSISTANT'})[0] || null;
|
|
369
|
+
return !!(existing && existing.value === ai_assistant._id);
|
|
370
|
+
}catch(e){
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
title: "Mark this assistant as the DEFAULT_AI_ASSISTANT setting.",
|
|
375
|
+
locked:function(ai_assistant){
|
|
376
|
+
try{
|
|
377
|
+
var settings = (_joe && _joe.Data && _joe.Data.setting) || [];
|
|
378
|
+
var existing = settings.where({name:'DEFAULT_AI_ASSISTANT'})[0] || null;
|
|
379
|
+
return !!(existing && existing.value === ai_assistant._id);
|
|
380
|
+
}catch(e){
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
},
|
|
237
385
|
// {
|
|
238
386
|
// name: "test_button",
|
|
239
387
|
// type: "button",
|
|
@@ -39,7 +39,20 @@ var schema = {
|
|
|
39
39
|
methods:{
|
|
40
40
|
chatSpawner:async function(object_id){
|
|
41
41
|
await _joe.Ai.spawnContextualChat(object_id);
|
|
42
|
-
}
|
|
42
|
+
},
|
|
43
|
+
listConversations:function(bus,oneline){
|
|
44
|
+
let html ='';
|
|
45
|
+
let conversations = _joe.Data.ai_conversation.filter(co=>{
|
|
46
|
+
if(co?.context_objects?.includes(bus._id)) return true;
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
$c.sortBy(conversations,'!created');
|
|
50
|
+
let temp = oneline?null:'<joe-subtext>${RUN[_joe.Utils.prettyPrintDTS;${created}]}</joe-subtext><joe-subtitle>${name}</joe-subtitle>';
|
|
51
|
+
conversations.map(tct=>{
|
|
52
|
+
html+=_joe.renderFieldListItem(tct,temp,'ai_conversation');
|
|
53
|
+
})
|
|
54
|
+
return html;
|
|
55
|
+
},
|
|
43
56
|
},
|
|
44
57
|
listView:{
|
|
45
58
|
title: function(chat){
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
var schema = {
|
|
2
2
|
title: "AI Widget Conversation | ${name||_id}",
|
|
3
|
-
display: "AI
|
|
3
|
+
display: "AI Convo (new)",
|
|
4
4
|
info: "Lightweight conversation log for external AI widgets.",
|
|
5
5
|
summary:{
|
|
6
6
|
description:'Lightweight, persistent conversation record for embeddable AI widgets.',
|
|
7
|
-
purpose:'Use ai_widget_conversation to
|
|
8
|
-
wip:
|
|
7
|
+
purpose:'Use ai_widget_conversation to track widget chats, including model choice, linked ai_assistant, effective system instructions, and a compact messages array. External sites and test pages create/update these records via the chatgpt widgetStart/widgetMessage/widgetHistory endpoints.',
|
|
8
|
+
wip:false,
|
|
9
9
|
labelField:'name',
|
|
10
10
|
defaultSort:{ field:'last_message_at', dir:'desc' },
|
|
11
|
-
searchableFields:['name','info','model','assistant_id','source','_id'],
|
|
11
|
+
searchableFields:['name','info','model','assistant_id','source','user_name','_id'],
|
|
12
12
|
allowedSorts:['last_message_at','created','joeUpdated','name'],
|
|
13
13
|
relationships:{
|
|
14
14
|
outbound:[
|
|
15
15
|
{ field:'assistant', targetSchema:'ai_assistant', cardinality:'one' },
|
|
16
|
+
{ field:'user', targetSchema:'user', cardinality:'one' },
|
|
16
17
|
{ field:'tags', targetSchema:'tag', cardinality:'many' }
|
|
17
18
|
],
|
|
18
19
|
inbound:{ graphRef:'server/relationships.graph.json' }
|
|
@@ -30,34 +31,120 @@ var schema = {
|
|
|
30
31
|
{ name:'messages', type:'objectList' },
|
|
31
32
|
{ name:'last_message_at', type:'string', format:'date-time' },
|
|
32
33
|
{ name:'source', type:'string' },
|
|
34
|
+
{ name:'user', type:'string', isReference:true, targetSchema:'user' },
|
|
35
|
+
{ name:'user_name', type:'string' },
|
|
36
|
+
{ name:'user_color', type:'string' },
|
|
37
|
+
{ name:'assistant_color', type:'string' },
|
|
33
38
|
{ name:'tags', type:'string', isArray:true, isReference:true, targetSchema:'tag' },
|
|
34
39
|
{ name:'joeUpdated', type:'string', format:'date-time' },
|
|
35
40
|
{ name:'created', type:'string', format:'date-time' }
|
|
36
41
|
]
|
|
37
42
|
},
|
|
38
43
|
listView: {
|
|
39
|
-
title:
|
|
44
|
+
title: function (convo) {
|
|
45
|
+
try{
|
|
46
|
+
var ts = convo.last_message_at || convo.joeUpdated || convo.created || '';
|
|
47
|
+
var prettyTs = (_joe && _joe.Utils && typeof _joe.Utils.prettyPrintDTS === 'function')
|
|
48
|
+
? _joe.Utils.prettyPrintDTS(ts)
|
|
49
|
+
: ts;
|
|
50
|
+
var title = convo.name || prettyTs || convo._id;
|
|
51
|
+
|
|
52
|
+
function textColorForBg(hex){
|
|
53
|
+
if (!hex || typeof hex !== 'string' || !/^#?[0-9a-fA-F]{6}$/.test(hex)) return '#000';
|
|
54
|
+
var h = (hex[0] === '#') ? hex.slice(1) : hex;
|
|
55
|
+
var n = parseInt(h, 16);
|
|
56
|
+
var r = (n >> 16) & 0xff;
|
|
57
|
+
var g = (n >> 8) & 0xff;
|
|
58
|
+
var b = n & 0xff;
|
|
59
|
+
var luminance = r * 0.299 + g * 0.587 + b * 0.114;
|
|
60
|
+
return luminance > 186 ? '#000' : '#fff';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function chip(label,bg){
|
|
64
|
+
if(!label){ return ''; }
|
|
65
|
+
var fg = textColorForBg(bg);
|
|
66
|
+
return '<span style="display:inline-block;padding:1px 6px;border-radius:999px;font-size:10px;margin-right:4px;background:'+bg+';color:'+fg+';">'
|
|
67
|
+
+ label + '</span>';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// User chip
|
|
71
|
+
var userName = '';
|
|
72
|
+
var userColor = convo.user_color || '';
|
|
73
|
+
if (convo.user){
|
|
74
|
+
var u = $J.get(convo.user,'user') || $J.get(convo.user);
|
|
75
|
+
if (u){
|
|
76
|
+
userName = u.fullname || u.name || convo.user_name || '';
|
|
77
|
+
userColor = u.color || userColor;
|
|
78
|
+
}
|
|
79
|
+
} else if (convo.user_name){
|
|
80
|
+
userName = convo.user_name;
|
|
81
|
+
}
|
|
82
|
+
var userChip = userName ? chip(userName, userColor || '#4b5563') : '';
|
|
83
|
+
|
|
84
|
+
// Assistant chip
|
|
85
|
+
var asstName = '';
|
|
86
|
+
var asstColor = convo.assistant_color || '';
|
|
87
|
+
if (convo.assistant && _joe && _joe.Data && _joe.Data.ai_assistant){
|
|
88
|
+
var a = $J.get(convo.assistant,'ai_assistant') ||
|
|
89
|
+
_joe.Data.ai_assistant.where({_id:convo.assistant})[0];
|
|
90
|
+
if (a){
|
|
91
|
+
asstName = a.name || a.title || a.assistant_id || a._id;
|
|
92
|
+
asstColor = a.assistant_color || a.color || asstColor;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!asstName && convo.assistant_id && _joe && _joe.Data && _joe.Data.ai_assistant){
|
|
96
|
+
var a2 = _joe.Data.ai_assistant.where({assistant_id:convo.assistant_id})[0];
|
|
97
|
+
if (a2){
|
|
98
|
+
asstName = a2.name || a2.title || a2.assistant_id || a2._id;
|
|
99
|
+
asstColor = a2.assistant_color || a2.color || asstColor;
|
|
100
|
+
}else{
|
|
101
|
+
asstName = convo.assistant_id;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
var asstChip = asstName ? chip(asstName, asstColor || '#2563eb') : '';
|
|
105
|
+
|
|
106
|
+
return ''
|
|
107
|
+
+ '<joe-title>'+title+'</joe-title>'
|
|
108
|
+
+ '<joe-subtitle>'+userChip+asstChip+'</joe-subtitle>'
|
|
109
|
+
+ (prettyTs ? '<joe-subtext>'+prettyTs+'</joe-subtext>' : '');
|
|
110
|
+
}catch(e){
|
|
111
|
+
return '<joe-title>'+(convo.name||convo._id)+'</joe-title>';
|
|
112
|
+
}
|
|
113
|
+
},
|
|
40
114
|
listWindowTitle: "AI Widget Conversations"
|
|
41
115
|
},
|
|
42
116
|
fields: function () {
|
|
43
117
|
return [
|
|
44
118
|
"name",
|
|
45
119
|
"info",
|
|
46
|
-
|
|
47
|
-
{ section_start: "conversation", display: "Conversation", collapsed: false },
|
|
120
|
+
{section_start:"participants"},
|
|
48
121
|
{
|
|
49
|
-
name: "
|
|
122
|
+
name: "user",
|
|
50
123
|
type: "select",
|
|
51
|
-
values: "
|
|
52
|
-
display: "
|
|
53
|
-
comment: "
|
|
124
|
+
values: "user",
|
|
125
|
+
display: "User (JOE)",
|
|
126
|
+
comment: "User of this conversation.",
|
|
127
|
+
width: "50%",
|
|
128
|
+
blank:true
|
|
54
129
|
},
|
|
55
130
|
{
|
|
56
131
|
name: "assistant",
|
|
57
|
-
type: "
|
|
132
|
+
type: "select",
|
|
58
133
|
values: "ai_assistant",
|
|
59
134
|
display: "Assistant (JOE)",
|
|
60
|
-
comment: "Optional link to an ai_assistant config used by this widget."
|
|
135
|
+
comment: "Optional link to an ai_assistant config used by this widget.",
|
|
136
|
+
width: "50%",
|
|
137
|
+
blank:true
|
|
138
|
+
},
|
|
139
|
+
{section_end:"participants"},
|
|
140
|
+
{ section_start: "conversation", display: "Conversation", collapsed: false },
|
|
141
|
+
{
|
|
142
|
+
name: "model",
|
|
143
|
+
type: "select",
|
|
144
|
+
values: "ai_model",
|
|
145
|
+
display: "Model",
|
|
146
|
+
comment: "Default OpenAI model used for this conversation.",
|
|
147
|
+
width: "50%"
|
|
61
148
|
},
|
|
62
149
|
{
|
|
63
150
|
name: "assistant_id",
|
|
@@ -98,7 +185,39 @@ var schema = {
|
|
|
98
185
|
"joeUpdated",
|
|
99
186
|
"itemtype",
|
|
100
187
|
{ section_end: "system" }
|
|
101
|
-
|
|
188
|
+
];
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// Subsets: grouped quick-filters in the list submenu
|
|
192
|
+
subsets:function(){
|
|
193
|
+
var sets = [];
|
|
194
|
+
// Group by source (e.g., aihub_card, widget, etc.)
|
|
195
|
+
sets = sets.concat(
|
|
196
|
+
_joe.Filter.Options.getDatasetPropertyValues('ai_widget_conversation','source',{
|
|
197
|
+
group:'source'
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
return sets;
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// Filters: sidebar filters
|
|
204
|
+
filters:function(){
|
|
205
|
+
var filters = [];
|
|
206
|
+
|
|
207
|
+
// Distinct users referenced by ai_widget_conversation.user
|
|
208
|
+
filters = filters.concat(
|
|
209
|
+
_joe.Filter.Options.datasetProperty('user','user',{
|
|
210
|
+
group:'user',
|
|
211
|
+
collapsed:true
|
|
212
|
+
}),
|
|
213
|
+
// Distinct assistants (JOE ai_assistant) referenced by this conversation
|
|
214
|
+
_joe.Filter.Options.datasetProperty('ai_assistant','assistant',{
|
|
215
|
+
group:'assistant',
|
|
216
|
+
collapsed:true
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return filters;
|
|
102
221
|
},
|
|
103
222
|
|
|
104
223
|
idprop: "_id",
|
|
@@ -45,14 +45,15 @@ var project = function(){return{
|
|
|
45
45
|
purpose:'Projects group tasks, people, and timelines. A project typically has one status and group, many members (users), and many tags. Tasks reference their owning project. Use projects for planning, tracking, and reporting across related tasks.',
|
|
46
46
|
labelField:'name',
|
|
47
47
|
defaultSort:{ field:'joeUpdated', dir:'desc' },
|
|
48
|
-
searchableFields:['name','info','description','_id'],
|
|
48
|
+
searchableFields:['name','info','description','problem_statement','_id'],
|
|
49
49
|
allowedSorts:['priority','status','name','joeUpdated','created','start_dt','end_dt'],
|
|
50
50
|
relationships:{
|
|
51
51
|
outbound:[
|
|
52
52
|
{ field:'status', targetSchema:'status', cardinality:'one' },
|
|
53
53
|
{ field:'group', targetSchema:'group', cardinality:'one' },
|
|
54
54
|
{ field:'members', targetSchema:'user', cardinality:'many' },
|
|
55
|
-
{ field:'tags', targetSchema:'tag', cardinality:'many' }
|
|
55
|
+
{ field:'tags', targetSchema:'tag', cardinality:'many' },
|
|
56
|
+
{ field:'aligned_goals', targetSchema:'goal', cardinality:'many' }
|
|
56
57
|
],
|
|
57
58
|
inbound:{ graphRef:'server/relationships.graph.json' }
|
|
58
59
|
},
|
|
@@ -63,12 +64,24 @@ var project = function(){return{
|
|
|
63
64
|
{ name:'name', type:'string', required:true },
|
|
64
65
|
{ name:'info', type:'string' },
|
|
65
66
|
{ name:'description', type:'string' },
|
|
67
|
+
{ name:'problem_statement', type:'string' },
|
|
66
68
|
{ name:'status', type:'string', isReference:true, targetSchema:'status' },
|
|
67
69
|
{ name:'group', type:'string', isReference:true, targetSchema:'group' },
|
|
68
70
|
{ name:'members', type:'string', isArray:true, isReference:true, targetSchema:'user' },
|
|
69
71
|
{ name:'tags', type:'string', isArray:true, isReference:true, targetSchema:'tag' },
|
|
72
|
+
{ name:'aligned_goals', type:'string', isArray:true, isReference:true, targetSchema:'goal' },
|
|
73
|
+
{ name:'goals', type:'objectList' },
|
|
70
74
|
{ name:'start_dt', type:'string', format:'date-time' },
|
|
71
75
|
{ name:'end_dt', type:'string', format:'date-time' },
|
|
76
|
+
{ name:'phases', type:'objectList' },
|
|
77
|
+
{ name:'subtasks', type:'objectList' },
|
|
78
|
+
{ name:'considerations_list', type:'objectList' },
|
|
79
|
+
{ name:'deliverables', type:'objectList' },
|
|
80
|
+
{ name:'key_features', type:'objectList' },
|
|
81
|
+
{ name:'marketing_tactics', type:'objectList' },
|
|
82
|
+
{ name:'links', type:'objectList' },
|
|
83
|
+
{ name:'pricing', type:'objectList' },
|
|
84
|
+
{ name:'files', type:'string', isArray:true },
|
|
72
85
|
{ name:'priority', type:'number', enumValues:[1,2,3,1000] },
|
|
73
86
|
{ name:'complete', type:'boolean' },
|
|
74
87
|
{ name:'joeUpdated', type:'string', format:'date-time', required:true },
|
|
@@ -77,7 +90,18 @@ var project = function(){return{
|
|
|
77
90
|
},
|
|
78
91
|
fields:function(){
|
|
79
92
|
var fields=[
|
|
80
|
-
|
|
93
|
+
{sidebar_start:'left'},
|
|
94
|
+
{section_start:'ai'},
|
|
95
|
+
'objectChat',
|
|
96
|
+
'listConversations',
|
|
97
|
+
{name:'ai_summary',display:'AI Summary',type:'rendering',comment:'Summarize the project in a few sentences.',
|
|
98
|
+
'ai':{
|
|
99
|
+
label:'summarize with ai',
|
|
100
|
+
prompt:'Summarize the project in a few sentences.'
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{section_end:'ai'},
|
|
104
|
+
{sidebar_end:'left'},
|
|
81
105
|
{section_start:'overview'},
|
|
82
106
|
'name',
|
|
83
107
|
{extend:'info',specs:{comment:'What\'s the problem solved or reason for this project?'}},
|
package/server/schemas/task.js
CHANGED
|
@@ -80,7 +80,7 @@ var task = function(){return{
|
|
|
80
80
|
{ name:'name', type:'string', required:true },
|
|
81
81
|
{ name:'info', type:'string' },
|
|
82
82
|
{ name:'description', type:'string' },
|
|
83
|
-
{ name:'task_type', type:'string', enumValues:['chore','epic'] },
|
|
83
|
+
{ name:'task_type', type:'string', enumValues:['task','chore','epic'] },
|
|
84
84
|
{ name:'status', type:'string', isReference:true, targetSchema:'status', required:true },
|
|
85
85
|
{ name:'project', type:'string', isReference:true, targetSchema:'project' },
|
|
86
86
|
{ name:'parent_task', type:'string', isReference:true, targetSchema:'task' },
|
|
@@ -96,7 +96,7 @@ var task = function(){return{
|
|
|
96
96
|
{ name:'created', type:'string', format:'date-time', required:true }
|
|
97
97
|
]
|
|
98
98
|
},
|
|
99
|
-
task_types:['chore','epic'],
|
|
99
|
+
task_types:['task','chore','epic'],
|
|
100
100
|
methods:{
|
|
101
101
|
addUser:function(userid,goto){
|
|
102
102
|
var current = _jco(true);
|
|
@@ -179,12 +179,15 @@ var task = function(){return{
|
|
|
179
179
|
{sidebar_start:'left'},
|
|
180
180
|
{section_start:'JAI',display:'JOE Ai'},
|
|
181
181
|
"objectChat",
|
|
182
|
+
"listConversations",
|
|
182
183
|
{section_end:'JAI'},
|
|
183
184
|
{sidebar_end:'left'},
|
|
184
185
|
{section_start:'overview'},
|
|
185
186
|
'name',
|
|
186
187
|
'info',
|
|
187
|
-
'description',
|
|
188
|
+
{extend:'description',specs:{
|
|
189
|
+
ai:{prompt:'Summarize the task in a few sentences. Take into account the project the task is associated with if there is one.'}}
|
|
190
|
+
},
|
|
188
191
|
{section_end:'overview'},
|
|
189
192
|
|
|
190
193
|
{section_start:'subtasks'},
|