@mongoosejs/studio 0.3.11 → 0.4.1
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/README.md +9 -0
- package/backend/actions/ChatThread/createChatMessage.js +15 -3
- package/backend/actions/ChatThread/createChatThread.js +8 -1
- package/backend/actions/ChatThread/index.js +1 -0
- package/backend/actions/ChatThread/streamChatMessage.js +49 -14
- package/backend/actions/ChatThread/toggleAgentMode.js +37 -0
- package/backend/actions/Model/getDocuments.js +7 -4
- package/backend/actions/Model/getDocumentsStream.js +7 -4
- package/backend/actions/Model/streamChatMessage.js +15 -4
- package/backend/authorize.js +1 -0
- package/backend/chatAgent/agentSystemPrompt.js +45 -0
- package/backend/chatAgent/agentToolMetadata.js +20 -0
- package/backend/chatAgent/getAgentTools.js +140 -0
- package/backend/db/chatMessageSchema.js +5 -0
- package/backend/db/chatThreadSchema.js +4 -0
- package/backend/helpers/isBindIPConnection.js +8 -0
- package/backend/helpers/isLocalhostConnection.js +8 -0
- package/backend/helpers/normalizeBindIPOption.js +16 -0
- package/backend/helpers/parseProjectionParam.js +180 -0
- package/backend/integrations/callLLM.js +4 -2
- package/backend/integrations/streamLLM.js +12 -4
- package/eslint.config.js +2 -1
- package/express.js +16 -0
- package/frontend/public/app.js +913 -110
- package/frontend/public/tw.css +34 -8
- package/frontend/src/api.js +6 -0
- package/frontend/src/chat/chat-message/chat-message.html +12 -1
- package/frontend/src/chat/chat-message/chat-message.js +8 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +1 -1
- package/frontend/src/chat/chat.html +91 -44
- package/frontend/src/chat/chat.js +119 -19
- package/frontend/src/create-document/create-document.js +3 -0
- package/frontend/src/detail-objectid/detail-objectid.html +1 -0
- package/frontend/src/detail-objectid/detail-objectid.js +67 -0
- package/frontend/src/document-details/document-property/document-property.html +8 -3
- package/frontend/src/document-details/document-property/document-property.js +7 -0
- package/frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.html +12 -0
- package/frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.js +19 -0
- package/frontend/src/models/document-search/document-search.js +6 -0
- package/frontend/src/models/models.html +6 -8
- package/frontend/src/models/models.js +257 -85
- package/frontend/src/models/projection-search/projection-search.html +27 -0
- package/frontend/src/models/projection-search/projection-search.js +226 -0
- package/index.d.ts +1 -1
- package/package.json +3 -2
package/frontend/public/tw.css
CHANGED
|
@@ -1072,6 +1072,10 @@ video {
|
|
|
1072
1072
|
height: 0.75rem;
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
|
+
.h-3\.5 {
|
|
1076
|
+
height: 0.875rem;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1075
1079
|
.h-4 {
|
|
1076
1080
|
height: 1rem;
|
|
1077
1081
|
}
|
|
@@ -1212,6 +1216,10 @@ video {
|
|
|
1212
1216
|
width: 0.75rem;
|
|
1213
1217
|
}
|
|
1214
1218
|
|
|
1219
|
+
.w-3\.5 {
|
|
1220
|
+
width: 0.875rem;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1215
1223
|
.w-36 {
|
|
1216
1224
|
width: 9rem;
|
|
1217
1225
|
}
|
|
@@ -1256,6 +1264,10 @@ video {
|
|
|
1256
1264
|
width: 2rem;
|
|
1257
1265
|
}
|
|
1258
1266
|
|
|
1267
|
+
.w-80 {
|
|
1268
|
+
width: 20rem;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1259
1271
|
.w-\[240px\] {
|
|
1260
1272
|
width: 240px;
|
|
1261
1273
|
}
|
|
@@ -1320,14 +1332,14 @@ video {
|
|
|
1320
1332
|
max-width: 280px;
|
|
1321
1333
|
}
|
|
1322
1334
|
|
|
1323
|
-
.max-w-\[calc\(100vw-3rem\)\] {
|
|
1324
|
-
max-width: calc(100vw - 3rem);
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
1335
|
.max-w-\[calc\(100vw-4rem\)\] {
|
|
1328
1336
|
max-width: calc(100vw - 4rem);
|
|
1329
1337
|
}
|
|
1330
1338
|
|
|
1339
|
+
.max-w-full {
|
|
1340
|
+
max-width: 100%;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1331
1343
|
.max-w-lg {
|
|
1332
1344
|
max-width: 32rem;
|
|
1333
1345
|
}
|
|
@@ -1774,6 +1786,10 @@ video {
|
|
|
1774
1786
|
border-bottom-width: 4px;
|
|
1775
1787
|
}
|
|
1776
1788
|
|
|
1789
|
+
.border-l {
|
|
1790
|
+
border-left-width: 1px;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1777
1793
|
.border-l-0 {
|
|
1778
1794
|
border-left-width: 0px;
|
|
1779
1795
|
}
|
|
@@ -3757,6 +3773,10 @@ video {
|
|
|
3757
3773
|
margin-right: -2rem;
|
|
3758
3774
|
}
|
|
3759
3775
|
|
|
3776
|
+
.lg\:flex {
|
|
3777
|
+
display: flex;
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3760
3780
|
.lg\:hidden {
|
|
3761
3781
|
display: none;
|
|
3762
3782
|
}
|
|
@@ -3773,10 +3793,6 @@ video {
|
|
|
3773
3793
|
width: 16rem;
|
|
3774
3794
|
}
|
|
3775
3795
|
|
|
3776
|
-
.lg\:max-w-\[calc\(100vw-15rem\)\] {
|
|
3777
|
-
max-width: calc(100vw - 15rem);
|
|
3778
|
-
}
|
|
3779
|
-
|
|
3780
3796
|
.lg\:max-w-\[calc\(100vw-20rem\)\] {
|
|
3781
3797
|
max-width: calc(100vw - 20rem);
|
|
3782
3798
|
}
|
|
@@ -3813,4 +3829,14 @@ video {
|
|
|
3813
3829
|
|
|
3814
3830
|
:is(:where(.dark) .dark\:bg-shark-800\/80) {
|
|
3815
3831
|
background-color: rgb(33 37 41 / 0.8);
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
:is(:where(.dark) .dark\:text-gray-400) {
|
|
3835
|
+
--tw-text-opacity: 1;
|
|
3836
|
+
color: rgb(156 163 175 / var(--tw-text-opacity));
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
:is(:where(.dark) .dark\:text-gray-500) {
|
|
3840
|
+
--tw-text-opacity: 1;
|
|
3841
|
+
color: rgb(107 114 128 / var(--tw-text-opacity));
|
|
3816
3842
|
}
|
package/frontend/src/api.js
CHANGED
|
@@ -62,6 +62,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
62
62
|
shareChatThread(params) {
|
|
63
63
|
return client.post('', { action: 'ChatThread.shareChatThread', ...params }).then(res => res.data);
|
|
64
64
|
},
|
|
65
|
+
toggleAgentMode(params) {
|
|
66
|
+
return client.post('', { action: 'ChatThread.toggleAgentMode', ...params }).then(res => res.data);
|
|
67
|
+
},
|
|
65
68
|
streamChatMessage: async function* streamChatMessage(params) {
|
|
66
69
|
// Don't stream on Next.js or Netlify for now.
|
|
67
70
|
const data = await client.post('', { action: 'ChatThread.createChatMessage', ...params }).then(res => res.data);
|
|
@@ -238,6 +241,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
238
241
|
shareChatThread: function shareChatThread(params) {
|
|
239
242
|
return client.post('/ChatThread/shareChatThread', params).then(res => res.data);
|
|
240
243
|
},
|
|
244
|
+
toggleAgentMode: function toggleAgentMode(params) {
|
|
245
|
+
return client.post('/ChatThread/toggleAgentMode', params).then(res => res.data);
|
|
246
|
+
},
|
|
241
247
|
streamChatMessage: async function* streamChatMessage(params) {
|
|
242
248
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
243
249
|
const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/ChatThread/streamChatMessage?' + new URLSearchParams(params).toString();
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
<div class="relative flex items-start" :class="{'justify-end': message.role === 'user'}">
|
|
2
2
|
<div
|
|
3
|
-
class="min-w-0 max-w-
|
|
3
|
+
class="min-w-0 max-w-full"
|
|
4
4
|
:class="{'text-right': message.role === 'user'}">
|
|
5
5
|
|
|
6
|
+
<div v-if="message.toolCalls && message.toolCalls.length" class="mb-2 text-xs text-gray-500 dark:text-gray-400">
|
|
7
|
+
<div
|
|
8
|
+
v-for="(tc, i) in message.toolCalls"
|
|
9
|
+
:key="i"
|
|
10
|
+
class="flex items-center gap-1.5 py-0.5 font-mono">
|
|
11
|
+
<svg :class="['w-3.5 h-3.5 shrink-0', tc.status === 'running' ? 'animate-spin text-gray-400 dark:text-gray-500' : 'text-gray-500 dark:text-gray-400']" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75a4.5 4.5 0 0 1-4.884 4.484c-1.076-.091-2.264.071-2.95.904l-7.152 8.684a2.548 2.548 0 1 1-3.586-3.586l8.684-7.152c.833-.686.995-1.874.904-2.95a4.5 4.5 0 0 1 6.336-4.486l-3.276 3.276a3.004 3.004 0 0 0 2.25 2.25l3.276-3.276c.256.565.398 1.192.398 1.852Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M4.867 19.125h.008v.008h-.008v-.008Z" /></svg>
|
|
12
|
+
<span>{{ tc.toolName }}</span>
|
|
13
|
+
<span v-if="tc.toolName !== 'typeCheck'" class="text-gray-400 dark:text-gray-500">{{ formatToolInput(tc.input) }}</span>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
6
17
|
<div class="text-sm text-content rounded-md inline-block relative" :class="styleForMessage">
|
|
7
18
|
<div v-for="part in contentSplitByScripts">
|
|
8
19
|
<div v-if="part.type === 'text'" v-html="marked(part.content)">
|
|
@@ -54,6 +54,14 @@ module.exports = app => app.component('chat-message', {
|
|
|
54
54
|
marked(text) {
|
|
55
55
|
return marked(text);
|
|
56
56
|
},
|
|
57
|
+
formatToolInput(input) {
|
|
58
|
+
if (!input) return '';
|
|
59
|
+
const parts = [];
|
|
60
|
+
if (input.modelName) parts.push(input.modelName);
|
|
61
|
+
if (input.filter && Object.keys(input.filter).length) parts.push(JSON.stringify(input.filter));
|
|
62
|
+
if (input.limit && input.limit !== 10) parts.push('limit=' + input.limit);
|
|
63
|
+
return parts.length ? '(' + parts.join(', ') + ')' : '';
|
|
64
|
+
},
|
|
57
65
|
async executeScript(message, script) {
|
|
58
66
|
const { chatMessage } = await api.ChatMessage.executeScript({
|
|
59
67
|
chatMessageId: message._id,
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
v-if="activeTab === 'code' && !isEditing"
|
|
36
36
|
class="px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center"
|
|
37
37
|
@click.stop="startEditing">
|
|
38
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-
|
|
38
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4" viewBox="0 0 20 20" fill="currentColor">
|
|
39
39
|
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z" />
|
|
40
40
|
</svg>
|
|
41
41
|
</button>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
class="rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted"
|
|
70
70
|
title="Hide sidebar"
|
|
71
71
|
>
|
|
72
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-
|
|
72
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
|
73
73
|
<path stroke-linecap="round" stroke-linejoin="round" d="m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5" />
|
|
74
74
|
</svg>
|
|
75
75
|
</button>
|
|
@@ -82,57 +82,104 @@
|
|
|
82
82
|
aria-label="Share thread with workspace"
|
|
83
83
|
:title="'Share thread with workspace' + (!hasWorkspace ? ' (requires a pro workspace)' : !chatThreadId ? ': Open a thread first!' : '')"
|
|
84
84
|
>
|
|
85
|
-
<svg v-if="hasWorkspace" xmlns="http://www.w3.org/2000/svg" class="w-
|
|
86
|
-
<svg v-else xmlns="http://www.w3.org/2000/svg" class="w-
|
|
85
|
+
<svg v-if="hasWorkspace" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z"/></svg>
|
|
86
|
+
<svg v-else xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z"/></svg>
|
|
87
|
+
</button>
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
@click="toggleAgentMode"
|
|
91
|
+
class="rounded p-1.5 transition-colors"
|
|
92
|
+
:class="isAgentModeEnabled ? 'text-primary bg-primary/10' : 'text-gray-400 hover:text-gray-600 hover:bg-muted'"
|
|
93
|
+
:aria-label="isAgentModeEnabled ? 'Turn agent mode off' : 'Turn agent mode on'"
|
|
94
|
+
:aria-pressed="isAgentModeEnabled"
|
|
95
|
+
:title="isAgentModeEnabled ? 'Agent mode on' : 'Agent mode off'"
|
|
96
|
+
>
|
|
97
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="currentColor" viewBox="0 0 200 200"><path d="m187 170.9-38.72-13.73-0.55-0.25-11.24-12.18v-9.98c6.3-6.82 6.43-11.03 8.3-23.22 7.34-1.41 10.2-7.7 12.28-18.9 0.97-5.02 2.72-9.88 0.14-13.92-1.38-2.35-3.49-3.08-5.62-3.38 1.65-11.57 5.47-27.07-2.68-42.38-5.47-9.94-14-14.18-21.8-14.92-3.5-9.15-12.32-13.06-27.11-13.06h-1.06c-27.2 0-48.53 17.96-52.18 39.74-2.05 11.38-0.41 20.41 0.74 30.03-2.23 0.19-4.38 1.02-5.84 3.42-2.21 3.83-1.27 8.84 0.06 14.19 2 8.28 2.26 16.18 12.9 18.96 1.47 6.52 0.58 14.07 8.3 22.84v10.77l-12.54 12.83-38.38 12.85v24.18h57.31c-3.11-14.37-6.53-23.75-5.45-42.72l0.44-0.34c5.71 9.04 16.02 15.97 29.22 25.91l-12.68 17.15h38.07l-13.16-17.2c11.44-9.29 22.31-16.55 29.2-25.38 4.68 9.53-1.48 26.7-4.72 42.58h56.77v-23.89zm-49.56-49.09c-1.43 5.43-2.53 6.16-9.69 14.68l-11.68 13.11c-3.48 3.27-4.8 3.27-11.11 3.27h-10.39c-6.25 0-7.45-0.78-11.01-4.46-6.78-7.7-11.76-13.37-15.09-17.32-5.39-6.33-5.85-7.2-7.16-18.89-0.73-4.86-3.02-14.25-2.94-14.91 3.94 3.43 13.59 3.35 16.89 3.35 12.76 0 15.79-2.74 19.92-17.12 0.68-2.98 1.71-3.53 4-3.53 2.04 0 3.04 0.98 3.86 3.71 3.9 13.69 5.2 16.52 16.15 16.76h2.99c8.1 0 15.01-1.52 18.58-2.93-0.5 5.06-2.28 16.7-3.32 24.28zm4.72-39.02c-1.97 10.98-2.94 12.3-17.69 12.91-9.05 0.38-11.35-0.71-14.17-8.69-3.69-11.25-2.17-13 5.55-13.83 6.36-0.83 11.74-0.79 15.92-0.79 10.01 0 12.11 0.27 10.39 10.4zm-84.38-32c0-11.99 9.26-21.63 15.54-23.52 4.46-1.14 7.99 3.61 15.25 5.74 5.06 1.54 10.71 1.32 11.84 1.32 11.49 0 16.71-2.62 21.55-6.91 1.66-1.28 3.22-1.57 4.86-0.67 10.89 6.36 15.34 16.28 14.74 26.07-0.36 5.5-2.18 9.03-1.38 12.23l1.14 3.62c-3.83-0.82-6.2-0.92-10.69-0.92-9.44 0-17.61 1.24-22.14 2.5-3.66 1.24-5.2 1.44-8.35 1.44-2.98 0-4.92-0.2-7.56-1.44-4.97-1.54-14.49-2.5-21.47-2.5-3.69 0-7 0.01-10.9 0.62l-1.84 0.3 0.42-3.07c0.82-3.01-1.01-7.56-1.01-14.81z"/><path d="m116.6 125.4-12.18-2.58c-2.29-0.68-3.7 0.76-5.4 0.76-1.66 0-2.79-1.35-5.4-0.66l-10.88 2.36c-1.35 0.12-1.55 1.44-0.79 3.32 0.38 1.02 1.26 0.7 3.07 0.36 3.25-0.46 4.65-0.84 8.11-1.04 1.85-0.17 2.59 0.41 5.06 0.41h1.14c1.72 0 2.62-0.55 4.15-0.31 3.81 0.31 5.64 0.72 11.64 1.22 2.08 0.38 2.4-1.15 2.21-2.52-0.07-0.65-0.24-1.1-0.73-1.32z"/><path d="m94.08 133.5c-1.96 0-2.49 1.23-2.4 2.39 0.06 1.38 0.93 2.27 2.57 2.27h10.06c2.13 0 2.7-1.25 2.7-2.56 0-1.13-0.65-2.1-2.53-2.1h-10.4z"/><path d="m91.31 106.6c-2.13 0-2.51 3.32-1.24 4.55 1.11 1.08 7.39 3.76 9.34 3.76s8.38-3.24 9.4-3.87c1.9-1.2 0.85-4.82-1.7-4.44-1.84 0.3-6.01 3.39-7.96 3.01-1.53-0.32-6.08-3.01-7.84-3.01z"/><path d="m85.62 73.71c-5.3-1.1-10.53-1.15-14.51-1.15-10.96 0-14.08-0.44-14.33 5.26-0.47 11.15 1.82 15.96 6.69 17.19 4.7 1.26 6.84 1.08 11.71 1.08 8.88 0 10.9-1.18 14.23-10.67 2.38-6.81 1.87-10.59-3.79-11.71z"/><path d="m114.5 73.51c-7.72 0.83-7.35 2.71-6.39 9.73 1.78 7.47 3.64 11.94 10.97 12.49 3.07 0.14 4.97-0.05 8-0.32 10.72-0.96 12.6-2.48 14.45-12.22 1.88-10.29-0.54-10.77-9.79-10.77-4.18 0-10.45 0.26-17.24 1.09z"/></svg>
|
|
87
98
|
</button>
|
|
88
99
|
</div>
|
|
89
100
|
</aside>
|
|
90
101
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
<div
|
|
95
|
-
<div
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
<div class="flex-1 min-w-0 flex">
|
|
103
|
+
<!-- Main Chat Area -->
|
|
104
|
+
<main class="flex-1 min-w-0 flex flex-col bg-slate-50">
|
|
105
|
+
<div class="flex-1 overflow-y-auto p-6 space-y-4" ref="messagesContainer">
|
|
106
|
+
<div v-if="chatMessages?.length === 0">
|
|
107
|
+
<div class="flex items-center w-full h-full justify-center py-3 mb-4">
|
|
108
|
+
<p class="mx-4 font-bold text-content">
|
|
109
|
+
Ask Mongoose Studio to analyze your data or generate a script
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
99
112
|
</div>
|
|
113
|
+
<ul role="list" class="space-y-4" v-else>
|
|
114
|
+
<li v-for="message in chatMessages" :key="message._id">
|
|
115
|
+
<chat-message :message="message" :target-dashboard-id="currentThread?.dashboardId"></chat-message>
|
|
116
|
+
</li>
|
|
117
|
+
</ul>
|
|
100
118
|
</div>
|
|
101
|
-
<ul role="list" class="space-y-4" v-else>
|
|
102
|
-
<li v-for="message in chatMessages" :key="message._id">
|
|
103
|
-
<chat-message :message="message" :target-dashboard-id="currentThread?.dashboardId"></chat-message>
|
|
104
|
-
</li>
|
|
105
|
-
</ul>
|
|
106
|
-
</div>
|
|
107
119
|
|
|
108
120
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
121
|
+
<!-- Input Area -->
|
|
122
|
+
<div class="border-t p-4">
|
|
123
|
+
<form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2 items-end justify-end">
|
|
124
|
+
<textarea
|
|
125
|
+
v-model="newMessage"
|
|
126
|
+
:placeholder="sendingMessage ? 'Sending...' : 'Ask about your data, generate a query, or build a chart…'"
|
|
127
|
+
class="flex-1 border rounded px-4 py-2 resize-none overflow-y-auto shadow-sm"
|
|
128
|
+
:disabled="sendingMessage"
|
|
129
|
+
rows="2"
|
|
130
|
+
ref="messageInput"
|
|
131
|
+
@input="adjustTextareaHeight"
|
|
132
|
+
@keydown.enter.exact.prevent="handleEnter"
|
|
133
|
+
></textarea>
|
|
134
|
+
<button class="bg-primary hover:bg-primary-hover text-white px-4 h-[42px] rounded disabled:bg-gray-600" :disabled="sendingMessage">
|
|
135
|
+
<svg v-if="sendingMessage" style="height: 1em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
136
|
+
<g>
|
|
137
|
+
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity="0.3" />
|
|
138
|
+
<path d="M12 2a10 10 0 0 1 10 10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
139
|
+
<animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite" />
|
|
140
|
+
</path>
|
|
141
|
+
</g>
|
|
142
|
+
</svg>
|
|
143
|
+
<span v-else>Send</span>
|
|
144
|
+
</button>
|
|
145
|
+
</form>
|
|
146
|
+
</div>
|
|
147
|
+
</main>
|
|
148
|
+
|
|
149
|
+
<aside
|
|
150
|
+
v-if="isAgentModeEnabled"
|
|
151
|
+
class="hidden lg:flex shrink-0 border-l border-edge bg-page transition-all duration-300 ease-in-out overflow-hidden"
|
|
152
|
+
:class="showAgentSidebar ? 'w-80' : 'w-0 border-l-0'"
|
|
153
|
+
>
|
|
154
|
+
<div v-if="showAgentSidebar" class="flex h-full w-80 flex-col">
|
|
155
|
+
<div class="flex items-center justify-between border-b border-edge px-4 py-3">
|
|
156
|
+
<div>
|
|
157
|
+
<h2 class="text-sm font-semibold text-content">Agent Mode</h2>
|
|
158
|
+
<p class="mt-1 text-xs text-content-tertiary">Agent mode lets Mongoose Studio inspect your data and validate scripts before it responds.</p>
|
|
159
|
+
</div>
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
@click="closeAgentSidebar"
|
|
163
|
+
class="rounded p-1.5 text-gray-400 hover:bg-muted hover:text-gray-600"
|
|
164
|
+
title="Hide agent sidebar"
|
|
165
|
+
>
|
|
166
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
|
167
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
168
|
+
</svg>
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="flex-1 overflow-y-auto px-4 py-4">
|
|
172
|
+
<div class="mb-3 text-xs font-semibold uppercase tracking-wider text-gray-400">Available Tools</div>
|
|
173
|
+
<ul role="list" class="space-y-3">
|
|
174
|
+
<li v-for="tool in agentTools" :key="tool.name" class="rounded-md border border-edge bg-surface px-3 py-3">
|
|
175
|
+
<div class="text-sm font-medium text-content">{{ tool.name }}</div>
|
|
176
|
+
<div class="mt-1 text-sm text-content-secondary">{{ tool.description }}</div>
|
|
177
|
+
</li>
|
|
178
|
+
</ul>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</aside>
|
|
182
|
+
</div>
|
|
136
183
|
|
|
137
184
|
<pro-upgrade-modal
|
|
138
185
|
:show="showProUpgradeModal"
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
|
+
const agentToolMetadata = require('../../../backend/chatAgent/agentToolMetadata');
|
|
4
5
|
const getCurrentDateTimeContext = require('../getCurrentDateTimeContext');
|
|
5
6
|
const template = require('./chat.html');
|
|
6
7
|
|
|
8
|
+
const AGENT_MODE_STORAGE_KEY = '_mongooseStudioAgentMode';
|
|
9
|
+
|
|
7
10
|
module.exports = {
|
|
8
11
|
template: template,
|
|
9
12
|
props: ['threadId'],
|
|
@@ -12,12 +15,15 @@ module.exports = {
|
|
|
12
15
|
sendingMessage: false,
|
|
13
16
|
newMessage: '',
|
|
14
17
|
chatThreadId: null,
|
|
18
|
+
draftAgentMode: false,
|
|
15
19
|
chatThreads: [],
|
|
16
20
|
chatMessages: [],
|
|
17
21
|
hideSidebar: null,
|
|
22
|
+
showAgentSidebar: false,
|
|
18
23
|
sharingThread: false,
|
|
19
24
|
threadSearch: '',
|
|
20
|
-
showProUpgradeModal: false
|
|
25
|
+
showProUpgradeModal: false,
|
|
26
|
+
agentTools: agentToolMetadata
|
|
21
27
|
}),
|
|
22
28
|
methods: {
|
|
23
29
|
async sendMessage() {
|
|
@@ -26,7 +32,9 @@ module.exports = {
|
|
|
26
32
|
const content = this.newMessage;
|
|
27
33
|
this.newMessage = '';
|
|
28
34
|
if (!this.chatThreadId) {
|
|
29
|
-
const { chatThread } = await api.ChatThread.createChatThread(
|
|
35
|
+
const { chatThread } = await api.ChatThread.createChatThread({
|
|
36
|
+
agentMode: this.draftAgentMode
|
|
37
|
+
});
|
|
30
38
|
this.chatThreads.unshift(chatThread);
|
|
31
39
|
this.chatThreadId = chatThread._id;
|
|
32
40
|
this.chatMessages = [];
|
|
@@ -40,11 +48,7 @@ module.exports = {
|
|
|
40
48
|
role: 'user'
|
|
41
49
|
});
|
|
42
50
|
|
|
43
|
-
this
|
|
44
|
-
if (this.$refs.messagesContainer) {
|
|
45
|
-
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
46
|
-
}
|
|
47
|
-
});
|
|
51
|
+
this.scrollToBottom();
|
|
48
52
|
|
|
49
53
|
const params = {
|
|
50
54
|
chatThreadId: this.chatThreadId,
|
|
@@ -53,6 +57,7 @@ module.exports = {
|
|
|
53
57
|
};
|
|
54
58
|
let userChatMessage = null;
|
|
55
59
|
let assistantChatMessage = null;
|
|
60
|
+
const toolCalls = [];
|
|
56
61
|
for await (const event of api.ChatThread.streamChatMessage(params)) {
|
|
57
62
|
if (event.chatMessage) {
|
|
58
63
|
if (!userChatMessage) {
|
|
@@ -61,6 +66,7 @@ module.exports = {
|
|
|
61
66
|
} else {
|
|
62
67
|
const assistantChatMessageIndex = this.chatMessages.indexOf(assistantChatMessage);
|
|
63
68
|
assistantChatMessage = event.chatMessage;
|
|
69
|
+
assistantChatMessage.toolCalls = toolCalls;
|
|
64
70
|
if (assistantChatMessageIndex !== -1) {
|
|
65
71
|
this.chatMessages.splice(assistantChatMessageIndex, 1, assistantChatMessage);
|
|
66
72
|
} else {
|
|
@@ -73,27 +79,44 @@ module.exports = {
|
|
|
73
79
|
thread.title = event.chatThread.title;
|
|
74
80
|
}
|
|
75
81
|
}
|
|
82
|
+
} else if (event.toolCall) {
|
|
83
|
+
toolCalls.push({ toolName: event.toolCall.toolName, input: event.toolCall.input, status: 'running' });
|
|
84
|
+
if (!assistantChatMessage) {
|
|
85
|
+
assistantChatMessage = {
|
|
86
|
+
_id: Math.random().toString(36).substr(2, 9),
|
|
87
|
+
content: '',
|
|
88
|
+
role: 'assistant',
|
|
89
|
+
toolCalls: [...toolCalls]
|
|
90
|
+
};
|
|
91
|
+
this.chatMessages.push(assistantChatMessage);
|
|
92
|
+
assistantChatMessage = this.chatMessages[this.chatMessages.length - 1];
|
|
93
|
+
} else {
|
|
94
|
+
assistantChatMessage.toolCalls = [...toolCalls];
|
|
95
|
+
}
|
|
96
|
+
this.scrollToBottom();
|
|
97
|
+
} else if (event.toolResult) {
|
|
98
|
+
const tc = toolCalls.find(t => t.toolName === event.toolResult.toolName && t.status === 'running');
|
|
99
|
+
if (tc) {
|
|
100
|
+
tc.status = 'done';
|
|
101
|
+
}
|
|
102
|
+
if (assistantChatMessage) {
|
|
103
|
+
assistantChatMessage.toolCalls = [...toolCalls];
|
|
104
|
+
}
|
|
105
|
+
this.scrollToBottom();
|
|
76
106
|
} else if (event.textPart) {
|
|
77
107
|
if (!assistantChatMessage) {
|
|
78
108
|
assistantChatMessage = {
|
|
79
109
|
_id: Math.random().toString(36).substr(2, 9),
|
|
80
110
|
content: event.textPart,
|
|
81
|
-
role: 'assistant'
|
|
111
|
+
role: 'assistant',
|
|
112
|
+
toolCalls: [...toolCalls]
|
|
82
113
|
};
|
|
83
114
|
this.chatMessages.push(assistantChatMessage);
|
|
84
115
|
assistantChatMessage = this.chatMessages[this.chatMessages.length - 1];
|
|
85
|
-
this
|
|
86
|
-
if (this.$refs.messagesContainer) {
|
|
87
|
-
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
116
|
+
this.scrollToBottom();
|
|
90
117
|
} else {
|
|
91
118
|
assistantChatMessage.content += event.textPart;
|
|
92
|
-
this
|
|
93
|
-
if (this.$refs.messagesContainer) {
|
|
94
|
-
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
95
|
-
}
|
|
96
|
-
});
|
|
119
|
+
this.scrollToBottom();
|
|
97
120
|
}
|
|
98
121
|
} else if (event.message) {
|
|
99
122
|
throw new Error(event.message);
|
|
@@ -112,10 +135,38 @@ module.exports = {
|
|
|
112
135
|
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
113
136
|
}
|
|
114
137
|
});
|
|
138
|
+
} catch (err) {
|
|
139
|
+
this.$toast.error(err?.message || 'Failed to send message.');
|
|
115
140
|
} finally {
|
|
116
141
|
this.sendingMessage = false;
|
|
117
142
|
}
|
|
118
143
|
},
|
|
144
|
+
async toggleAgentMode() {
|
|
145
|
+
const wasEnabled = this.isAgentModeEnabled;
|
|
146
|
+
const newValue = !this.isAgentModeEnabled;
|
|
147
|
+
this.draftAgentMode = newValue;
|
|
148
|
+
this.persistAgentModePreference(newValue);
|
|
149
|
+
if (!this.chatThreadId) {
|
|
150
|
+
this.maybeOpenAgentSidebar({ wasEnabled, isEnabled: newValue });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const { chatThread } = await api.ChatThread.toggleAgentMode({
|
|
154
|
+
chatThreadId: this.chatThreadId,
|
|
155
|
+
agentMode: newValue
|
|
156
|
+
});
|
|
157
|
+
const idx = this.chatThreads.findIndex(t => t._id === chatThread._id);
|
|
158
|
+
if (idx !== -1) {
|
|
159
|
+
this.chatThreads.splice(idx, 1, chatThread);
|
|
160
|
+
}
|
|
161
|
+
this.maybeOpenAgentSidebar({ wasEnabled, isEnabled: newValue });
|
|
162
|
+
},
|
|
163
|
+
scrollToBottom() {
|
|
164
|
+
this.$nextTick(() => {
|
|
165
|
+
if (this.$refs.messagesContainer) {
|
|
166
|
+
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
},
|
|
119
170
|
handleEnter(ev) {
|
|
120
171
|
if (!ev.shiftKey) {
|
|
121
172
|
this.sendMessage();
|
|
@@ -135,7 +186,9 @@ module.exports = {
|
|
|
135
186
|
return message.role === 'user' ? 'bg-muted' : '';
|
|
136
187
|
},
|
|
137
188
|
async createNewThread() {
|
|
138
|
-
const { chatThread } = await api.ChatThread.createChatThread(
|
|
189
|
+
const { chatThread } = await api.ChatThread.createChatThread({
|
|
190
|
+
agentMode: this.draftAgentMode
|
|
191
|
+
});
|
|
139
192
|
this.$toast.success('Chat thread created!');
|
|
140
193
|
this.$router.push('/chat/' + chatThread._id);
|
|
141
194
|
},
|
|
@@ -177,12 +230,57 @@ module.exports = {
|
|
|
177
230
|
} finally {
|
|
178
231
|
this.sharingThread = false;
|
|
179
232
|
}
|
|
233
|
+
},
|
|
234
|
+
isDesktopViewport() {
|
|
235
|
+
return typeof window !== 'undefined' &&
|
|
236
|
+
typeof window.matchMedia === 'function' &&
|
|
237
|
+
window.matchMedia('(min-width: 1024px)').matches;
|
|
238
|
+
},
|
|
239
|
+
getAgentModePreference() {
|
|
240
|
+
if (typeof window === 'undefined') {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
return window.localStorage?.getItem(AGENT_MODE_STORAGE_KEY) === 'true';
|
|
244
|
+
},
|
|
245
|
+
persistAgentModePreference(agentMode) {
|
|
246
|
+
if (typeof window !== 'undefined') {
|
|
247
|
+
window.localStorage?.setItem(AGENT_MODE_STORAGE_KEY, String(agentMode));
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
async syncCurrentThreadAgentMode() {
|
|
251
|
+
if (!this.chatThreadId || !this.currentThread || !this.draftAgentMode || this.currentThread.agentMode) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const { chatThread } = await api.ChatThread.toggleAgentMode({
|
|
255
|
+
chatThreadId: this.chatThreadId,
|
|
256
|
+
agentMode: true
|
|
257
|
+
});
|
|
258
|
+
const idx = this.chatThreads.findIndex(t => t._id === chatThread._id);
|
|
259
|
+
if (idx !== -1) {
|
|
260
|
+
this.chatThreads.splice(idx, 1, chatThread);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
maybeOpenAgentSidebar({ wasEnabled, isEnabled }) {
|
|
264
|
+
if (!isEnabled) {
|
|
265
|
+
this.showAgentSidebar = false;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (wasEnabled || !this.isDesktopViewport()) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
this.showAgentSidebar = true;
|
|
272
|
+
},
|
|
273
|
+
closeAgentSidebar() {
|
|
274
|
+
this.showAgentSidebar = false;
|
|
180
275
|
}
|
|
181
276
|
},
|
|
182
277
|
computed: {
|
|
183
278
|
currentThread() {
|
|
184
279
|
return this.chatThreads.find(t => t._id === this.chatThreadId);
|
|
185
280
|
},
|
|
281
|
+
isAgentModeEnabled() {
|
|
282
|
+
return this.currentThread?.agentMode ?? this.draftAgentMode;
|
|
283
|
+
},
|
|
186
284
|
hasWorkspace() {
|
|
187
285
|
return !!window.MONGOOSE_STUDIO_CONFIG.workspace?._id;
|
|
188
286
|
},
|
|
@@ -200,10 +298,12 @@ module.exports = {
|
|
|
200
298
|
async mounted() {
|
|
201
299
|
window.pageState = this;
|
|
202
300
|
|
|
301
|
+
this.draftAgentMode = this.getAgentModePreference();
|
|
203
302
|
this.chatThreadId = this.threadId;
|
|
204
303
|
const { chatThreads } = await api.ChatThread.listChatThreads();
|
|
205
304
|
this.chatThreads = chatThreads;
|
|
206
305
|
if (this.chatThreadId) {
|
|
306
|
+
await this.syncCurrentThreadAgentMode();
|
|
207
307
|
const { chatMessages } = await api.ChatThread.getChatThread({ chatThreadId: this.chatThreadId });
|
|
208
308
|
this.chatMessages = chatMessages;
|
|
209
309
|
}
|
|
@@ -53,6 +53,9 @@ module.exports = app => app.component('create-document', {
|
|
|
53
53
|
documentData: this.aiOriginalDocument,
|
|
54
54
|
currentDateTime: getCurrentDateTimeContext()
|
|
55
55
|
})) {
|
|
56
|
+
if (event?.message) {
|
|
57
|
+
throw new Error(event.message);
|
|
58
|
+
}
|
|
56
59
|
if (event?.textPart) {
|
|
57
60
|
this.aiSuggestion += event.textPart;
|
|
58
61
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<pre class="w-full whitespace-pre-wrap break-words font-mono text-sm text-content-secondary m-0">{{displayValue}}</pre>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const template = require('./detail-objectid.html');
|
|
4
|
+
const ObjectId = require('mongoose/lib/types/objectid');
|
|
5
|
+
|
|
6
|
+
module.exports = app => app.component('detail-objectid', {
|
|
7
|
+
template,
|
|
8
|
+
props: ['value', 'viewMode'],
|
|
9
|
+
emits: ['updated'],
|
|
10
|
+
watch: {
|
|
11
|
+
displayValue: {
|
|
12
|
+
immediate: true,
|
|
13
|
+
handler(val) {
|
|
14
|
+
this.$emit('updated', val);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
computed: {
|
|
19
|
+
format() {
|
|
20
|
+
return this.viewMode || 'hex';
|
|
21
|
+
},
|
|
22
|
+
hexValue() {
|
|
23
|
+
if (this.value == null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (typeof this.value === 'string') {
|
|
27
|
+
return this.value;
|
|
28
|
+
}
|
|
29
|
+
if (typeof this.value === 'object') {
|
|
30
|
+
if (typeof this.value.toHexString === 'function') {
|
|
31
|
+
return this.value.toHexString();
|
|
32
|
+
}
|
|
33
|
+
if (typeof this.value.$oid === 'string') {
|
|
34
|
+
return this.value.$oid;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return String(this.value);
|
|
38
|
+
},
|
|
39
|
+
objectIdDate() {
|
|
40
|
+
const hex = this.hexValue;
|
|
41
|
+
return new ObjectId(hex).getTimestamp();
|
|
42
|
+
},
|
|
43
|
+
displayValue() {
|
|
44
|
+
if (this.value == null) {
|
|
45
|
+
return String(this.value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const hex = this.hexValue;
|
|
49
|
+
if (this.format === 'object_call') {
|
|
50
|
+
return `ObjectId('${hex}')`;
|
|
51
|
+
}
|
|
52
|
+
if (this.format === 'unix_seconds') {
|
|
53
|
+
if (!this.objectIdDate) {
|
|
54
|
+
return 'Invalid ObjectId';
|
|
55
|
+
}
|
|
56
|
+
return String(Math.floor(this.objectIdDate.getTime() / 1000));
|
|
57
|
+
}
|
|
58
|
+
if (this.format === 'date') {
|
|
59
|
+
if (!this.objectIdDate) {
|
|
60
|
+
return 'Invalid ObjectId';
|
|
61
|
+
}
|
|
62
|
+
return this.objectIdDate.toISOString();
|
|
63
|
+
}
|
|
64
|
+
return hex;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|