@mongoosejs/studio 0.3.10 → 0.4.0

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.
@@ -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
  }
@@ -3577,6 +3593,10 @@ video {
3577
3593
  background-color: rgb(75 85 99 / var(--tw-bg-opacity));
3578
3594
  }
3579
3595
 
3596
+ .disabled\:bg-page:disabled {
3597
+ background-color: var(--color-page);
3598
+ }
3599
+
3580
3600
  .disabled\:text-gray-300:disabled {
3581
3601
  --tw-text-opacity: 1;
3582
3602
  color: rgb(209 213 219 / var(--tw-text-opacity));
@@ -3753,6 +3773,10 @@ video {
3753
3773
  margin-right: -2rem;
3754
3774
  }
3755
3775
 
3776
+ .lg\:flex {
3777
+ display: flex;
3778
+ }
3779
+
3756
3780
  .lg\:hidden {
3757
3781
  display: none;
3758
3782
  }
@@ -3769,10 +3793,6 @@ video {
3769
3793
  width: 16rem;
3770
3794
  }
3771
3795
 
3772
- .lg\:max-w-\[calc\(100vw-15rem\)\] {
3773
- max-width: calc(100vw - 15rem);
3774
- }
3775
-
3776
3796
  .lg\:max-w-\[calc\(100vw-20rem\)\] {
3777
3797
  max-width: calc(100vw - 20rem);
3778
3798
  }
@@ -3809,4 +3829,14 @@ video {
3809
3829
 
3810
3830
  :is(:where(.dark) .dark\:bg-shark-800\/80) {
3811
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));
3812
3842
  }
@@ -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-[calc(100vw-3rem)] lg:max-w-[calc(100vw-15rem)]"
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,
@@ -33,9 +33,9 @@
33
33
  </button>
34
34
  <button
35
35
  v-if="activeTab === 'code' && !isEditing"
36
- class="px-2 py-1 mr-1 text-xs bg-page0 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center"
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-3 w-3" viewBox="0 0 20 20" fill="currentColor">
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-4 h-4">
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-4 h-4" 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-4 h-4" 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>
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
- <!-- Main Chat Area -->
92
- <main class="flex-1 flex flex-col bg-slate-50">
93
- <div class="flex-1 overflow-y-auto p-6 space-y-4" ref="messagesContainer">
94
- <div v-if="chatMessages?.length === 0">
95
- <div class="flex items-center w-full h-full justify-center py-3 mb-4">
96
- <p class="mx-4 font-bold text-content">
97
- Ask Mongoose Studio to analyze your data or generate a script
98
- </p>
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
- <!-- Input Area -->
110
- <div class="border-t p-4">
111
- <form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2 items-end justify-end">
112
- <textarea
113
- v-model="newMessage"
114
- :placeholder="sendingMessage ? 'Sending...' : 'Ask about your data, generate a query, or build a chart…'"
115
- class="flex-1 border rounded px-4 py-2 resize-none overflow-y-auto shadow-sm"
116
- :disabled="sendingMessage"
117
- rows="2"
118
- ref="messageInput"
119
- @input="adjustTextareaHeight"
120
- @keydown.enter.exact.prevent="handleEnter"
121
- ></textarea>
122
- <button class="bg-primary hover:bg-primary-hover text-white px-4 h-[42px] rounded disabled:bg-gray-600" :disabled="sendingMessage">
123
- <svg v-if="sendingMessage" style="height: 1em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
124
- <g>
125
- <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity="0.3" />
126
- <path d="M12 2a10 10 0 0 1 10 10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
127
- <animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite" />
128
- </path>
129
- </g>
130
- </svg>
131
- <span v-else>Send</span>
132
- </button>
133
- </form>
134
- </div>
135
- </main>
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.$nextTick(() => {
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.$nextTick(() => {
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.$nextTick(() => {
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
  }
@@ -21,7 +21,7 @@
21
21
  </div>
22
22
  <div class="flex space-x-2">
23
23
  <async-button @click="updateCode" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500">Submit</async-button>
24
- <button @click="closeEditor" class="px-4 py-2 bg-page0 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500">Cancel</button>
24
+ <button @click="closeEditor" class="px-4 py-2 bg-gray-700 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500">Cancel</button>
25
25
  </div>
26
26
  </div>
27
27
  </div>
@@ -117,7 +117,7 @@
117
117
  @click="stagingSelect"
118
118
  type="button"
119
119
  :class="{
120
- 'bg-page0 ring-inset ring-2 ring-gray-300 hover:bg-gray-200 text-content-secondary': selectMultiple,
120
+ 'bg-gray-700 ring-inset ring-2 ring-gray-300 hover:bg-gray-200 text-content-secondary': selectMultiple,
121
121
  'bg-primary hover:bg-primary-hover text-primary-text': !selectMultiple
122
122
  }"
123
123
  class="rounded px-2 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
@@ -17,7 +17,7 @@
17
17
  <async-button
18
18
  type="button"
19
19
  @click="loginWithGithub"
20
- class="rounded bg-primary px-2 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:bg-page0">
20
+ class="rounded bg-primary px-2 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:bg-page">
21
21
  <svg viewBox="0 0 98 98" class="inline mr-1" height="1.5em" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>
22
22
  Login With GitHub
23
23
  </async-button>
@@ -26,7 +26,7 @@
26
26
  <async-button
27
27
  type="button"
28
28
  @click="loginWithGoogle"
29
- class="rounded bg-primary px-2 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:bg-page0">
29
+ class="rounded bg-primary px-2 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:bg-page">
30
30
  <svg class="inline" xmlns="http://www.w3.org/2000/svg" height="1.5em" viewBox="0 0 512 512"><path fill="#fff" d="M386 400c45-42 65-112 53-179H260v74h102c-4 24-18 44-38 57z"/><path fill="#fff" d="M90 341a192 192 0 0 0 296 59l-62-48c-53 35-141 22-171-60z"/><path fill="#fff" d="M153 292c-8-25-8-48 0-73l-63-49c-23 46-30 111 0 171z"/><path fill="#fff" d="M153 219c22-69 116-109 179-50l55-54c-78-75-230-72-297 55z"/></svg>
31
31
  Login With Google
32
32
  </async-button>
@@ -93,7 +93,7 @@
93
93
  @click="showNewInvitationModal = true"
94
94
  :disabled="status === 'loading'"
95
95
  :tier="workspace?.subscriptionTier"
96
- class="block rounded-md bg-primary px-3 py-2 text-center text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover disabled:bg-page0 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary">
96
+ class="block rounded-md bg-primary px-3 py-2 text-center text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover disabled:bg-page disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary">
97
97
  New Invitation
98
98
  <svg class="inline w-4 h-4 ml-1" v-if="workspace && !workspace.subscriptionTier" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
99
99
  <path fill-rule="evenodd" d="M12 1.5a5.25 5.25 0 00-5.25 5.25v3a3 3 0 00-3 3v6.75a3 3 0 003 3h10.5a3 3 0 003-3v-6.75a3 3 0 00-3-3v-3c0-2.9-2.35-5.25-5.25-5.25zm3.75 8.25v-3a3.75 3.75 0 10-7.5 0v3h7.5z" clip-rule="evenodd" />