@mongoosejs/studio 0.5.1 → 0.5.3

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.
Files changed (42) hide show
  1. package/backend/actions/ChatThread/streamChatMessage.js +12 -12
  2. package/backend/actions/Dashboard/getDashboard.js +29 -2
  3. package/backend/actions/Model/dropCollection.js +29 -0
  4. package/backend/actions/Model/index.js +1 -0
  5. package/backend/authorize.js +1 -0
  6. package/backend/chatAgent/agentSystemPrompt.js +22 -10
  7. package/backend/chatAgent/getAgentTools.js +19 -8
  8. package/backend/chatAgent/runChatAgent.js +17 -0
  9. package/backend/db/dashboardSchema.js +0 -32
  10. package/backend/index.js +4 -1
  11. package/backend/integrations/callLLM.js +5 -20
  12. package/backend/integrations/streamLLM.js +5 -24
  13. package/backend/sandbox/createSandbox.js +6 -0
  14. package/backend/vercel.js +3 -0
  15. package/debugging.js +11 -0
  16. package/eval/agent/cases.js +168 -0
  17. package/eval/agent/fixtures/carsharing.js +147 -0
  18. package/eval/agent/fixtures/ecommerce.js +152 -0
  19. package/eval/agent/run.js +323 -0
  20. package/frontend/index.js +2 -1
  21. package/frontend/public/app.js +581 -203
  22. package/frontend/public/tw.css +81 -0
  23. package/frontend/src/_util/recent-pages-history.js +116 -0
  24. package/frontend/src/api.js +96 -164
  25. package/frontend/src/chat/chat-message-script/chat-message-script.html +1 -6
  26. package/frontend/src/chat/chat.html +1 -1
  27. package/frontend/src/document/confirm-changes/confirm-changes.js +20 -0
  28. package/frontend/src/document/document.html +2 -2
  29. package/frontend/src/index.js +148 -1
  30. package/frontend/src/json-node/json-node.html +41 -39
  31. package/frontend/src/json-node/json-node.js +38 -0
  32. package/frontend/src/list-json/list-json.html +3 -2
  33. package/frontend/src/list-json/list-json.js +5 -0
  34. package/frontend/src/modal/modal.css +26 -1
  35. package/frontend/src/modal/modal.html +2 -2
  36. package/frontend/src/modal/modal.js +5 -1
  37. package/frontend/src/models/models.html +45 -5
  38. package/frontend/src/models/models.js +26 -0
  39. package/frontend/src/navbar/navbar.html +92 -5
  40. package/frontend/src/navbar/navbar.js +69 -1
  41. package/index.js +2 -1
  42. package/package.json +3 -2
@@ -1,3 +1,4 @@
1
+ <div>
1
2
  <div class="navbar w-full bg-page border-b border-edge !h-[55px] hidden md:grid grid-cols-[1fr_auto_1fr] items-stretch px-4">
2
3
  <!-- Left: Logo + Brand -->
3
4
  <div class="flex items-center gap-2 self-center">
@@ -31,10 +32,18 @@
31
32
  <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
32
33
  </svg>
33
34
  </span>
34
- <a v-if="hasAccess(roles, 'chat')"
35
+ <a v-if="hasAccess(roles, 'chat') && hasLLMAPIKey"
35
36
  href="#/chat"
36
37
  class="inline-flex items-center px-1 border-b-2 text-sm font-medium"
37
38
  :class="chatView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'">Chat</a>
39
+ <span v-else-if="hasAccess(roles, 'chat') && !hasLLMAPIKey"
40
+ class="inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed"
41
+ :title="chatDisabledReason">
42
+ Chat
43
+ <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
44
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
45
+ </svg>
46
+ </span>
38
47
  <span v-else class="inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed">
39
48
  Chat
40
49
  <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
@@ -59,6 +68,19 @@
59
68
  </nav>
60
69
  <!-- Right: Env Badge + User -->
61
70
  <div class="flex items-center justify-end gap-3 self-center">
71
+ <button
72
+ type="button"
73
+ class="inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary"
74
+ title="Recent pages"
75
+ aria-label="Recent pages"
76
+ aria-haspopup="dialog"
77
+ :aria-expanded="showRecentPagesModal ? 'true' : 'false'"
78
+ @click="openRecentPagesModal"
79
+ >
80
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5" aria-hidden="true">
81
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
82
+ </svg>
83
+ </button>
62
84
  <div
63
85
  v-if="!!state.nodeEnv"
64
86
  title="NODE_ENV"
@@ -141,9 +163,22 @@
141
163
  <span class="text-base font-semibold text-content">Mongoose Studio</span>
142
164
  </router-link>
143
165
  </div>
144
- <div class="flex items-center">
166
+ <div class="flex items-center gap-1 pr-2">
167
+ <button
168
+ type="button"
169
+ class="rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary md:hidden"
170
+ title="Recent pages"
171
+ aria-label="Recent pages"
172
+ aria-haspopup="dialog"
173
+ :aria-expanded="showRecentPagesModal ? 'true' : 'false'"
174
+ @click="openRecentPagesModal"
175
+ >
176
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-6 w-6" aria-hidden="true">
177
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
178
+ </svg>
179
+ </button>
145
180
  <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->
146
- <button type="button" id="open-mobile-menu" class="-ml-2 rounded-md p-2 pr-4 text-gray-400">
181
+ <button type="button" id="open-mobile-menu" class="-ml-2 rounded-md p-2 text-gray-400">
147
182
  <span class="sr-only">Open menu</span>
148
183
  <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
149
184
  <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
@@ -154,7 +189,7 @@
154
189
  <!-- Mobile menu mask -->
155
190
  <div id="mobile-menu-mask" class="fixed inset-0 bg-black bg-opacity-40 z-40 hidden"></div>
156
191
  <!-- Mobile menu drawer -->
157
- <div id="mobile-menu" style="z-index: 10000" class="fixed inset-0 bg-page shadow-lg transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col">
192
+ <div id="mobile-menu" style="z-index: 100000" class="fixed inset-0 bg-page shadow-lg transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col">
158
193
  <div class="flex items-center justify-between px-4 !h-[55px] border-b border-edge">
159
194
  <router-link :to="{ name: defaultRoute }">
160
195
  <svg xmlns="http://www.w3.org/2000/svg" class="h-[32px] text-primary" viewBox="0 0 250 250" fill="currentColor" aria-label="Mongoose Studio Logo">
@@ -201,10 +236,18 @@
201
236
  <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
202
237
  </svg>
203
238
  </span>
204
- <a v-if="hasAccess(roles, 'chat')"
239
+ <a v-if="hasAccess(roles, 'chat') && hasLLMAPIKey"
205
240
  href="#/chat"
206
241
  class="block px-3 py-2 rounded-md text-base font-medium"
207
242
  :class="chatView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'">Chat</a>
243
+ <span v-else-if="hasAccess(roles, 'chat') && !hasLLMAPIKey"
244
+ class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed"
245
+ :title="chatDisabledReason">
246
+ Chat
247
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
248
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
249
+ </svg>
250
+ </span>
208
251
  <span v-else class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed">
209
252
  Chat
210
253
  <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
@@ -250,3 +293,47 @@
250
293
  </nav>
251
294
  </div>
252
295
  </div>
296
+
297
+ <modal
298
+ v-if="showRecentPagesModal"
299
+ flush-container
300
+ containerClass="!max-w-md !w-[min(94vw,28rem)] !overflow-hidden !rounded-lg !border !border-edge !bg-surface !shadow-xl dark:!bg-shark-900 text-content"
301
+ bodyClass="modal-recent-pages-body !p-0"
302
+ >
303
+ <template v-slot:body>
304
+ <div class="modal-exit" @click="closeRecentPagesModal" role="button" aria-label="Close recent pages">&times;</div>
305
+ <div
306
+ class="shrink-0 flex items-center gap-3 border-b border-edge bg-surface px-4 py-4 pr-12 dark:bg-shark-900"
307
+ >
308
+ <h2 class="m-0 text-lg font-semibold text-content">Recent pages</h2>
309
+ <button
310
+ type="button"
311
+ class="rounded px-2 py-1 text-sm font-medium text-content-secondary hover:bg-muted disabled:opacity-50"
312
+ @click="clearRecentPagesHistory"
313
+ :disabled="recentPagesList.length === 0"
314
+ >
315
+ Clear
316
+ </button>
317
+ </div>
318
+ <div class="modal-recent-pages-scroll">
319
+ <div
320
+ v-if="recentPagesList.length === 0"
321
+ class="m-4 rounded border border-dashed border-edge px-3 py-6 text-center text-sm text-content-tertiary"
322
+ >
323
+ No recent pages yet.
324
+ </div>
325
+ <button
326
+ v-for="entry in recentPagesList"
327
+ :key="entry.path + '-' + entry.visitedAt"
328
+ type="button"
329
+ class="w-full px-4 py-2.5 text-left hover:bg-muted"
330
+ :class="entry.path === $route.fullPath ? 'bg-primary-subtle' : ''"
331
+ @click="goToRecentPage(entry)"
332
+ >
333
+ <div class="truncate text-sm font-medium text-content">{{ entry.label }}</div>
334
+ <div class="truncate font-mono text-xs text-content-tertiary">{{ entry.path }}</div>
335
+ </button>
336
+ </div>
337
+ </template>
338
+ </modal>
339
+ </div>
@@ -4,6 +4,13 @@ const api = require('../api');
4
4
  const mothership = require('../mothership');
5
5
  const template = require('./navbar.html');
6
6
  const { routes, hasAccess } = require('../routes');
7
+ const {
8
+ RECENT_PAGES_STORAGE_KEY,
9
+ RECENT_PAGES_CHANGED_EVENT,
10
+ safeReadRecentPages,
11
+ saveRecentPages,
12
+ withRecentPagesStorageLock
13
+ } = require('../_util/recent-pages-history');
7
14
 
8
15
  const appendCSS = require('../appendCSS');
9
16
 
@@ -15,7 +22,9 @@ module.exports = app => app.component('navbar', {
15
22
  inject: ['state'],
16
23
  data: () => ({
17
24
  showFlyout: false,
18
- darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark'
25
+ darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark',
26
+ showRecentPagesModal: false,
27
+ localRecentPages: []
19
28
  }),
20
29
  mounted: function() {
21
30
  window.navbar = this;
@@ -37,6 +46,32 @@ module.exports = app => app.component('navbar', {
37
46
  mobileMenu.classList.add('translate-x-full');
38
47
  });
39
48
  }
49
+
50
+ this.localRecentPages = safeReadRecentPages();
51
+
52
+ this._syncRecentPagesFromOtherTab = ev => {
53
+ if (ev.storageArea !== window.localStorage || ev.key !== RECENT_PAGES_STORAGE_KEY) {
54
+ return;
55
+ }
56
+ this.refreshRecentPagesFromStorage();
57
+ };
58
+ this._syncRecentPagesFromBroadcast = () => this.refreshRecentPagesFromStorage();
59
+
60
+ window.addEventListener('storage', this._syncRecentPagesFromOtherTab);
61
+ window.addEventListener(RECENT_PAGES_CHANGED_EVENT, this._syncRecentPagesFromBroadcast);
62
+ },
63
+ beforeUnmount() {
64
+ if (this._syncRecentPagesFromOtherTab) {
65
+ window.removeEventListener('storage', this._syncRecentPagesFromOtherTab);
66
+ }
67
+ if (this._syncRecentPagesFromBroadcast) {
68
+ window.removeEventListener(RECENT_PAGES_CHANGED_EVENT, this._syncRecentPagesFromBroadcast);
69
+ }
70
+ },
71
+ watch: {
72
+ '$route.fullPath'() {
73
+ this.refreshRecentPagesFromStorage();
74
+ }
40
75
  },
41
76
  computed: {
42
77
  dashboardView() {
@@ -68,6 +103,15 @@ module.exports = app => app.component('navbar', {
68
103
  },
69
104
  hasTaskVisualizer() {
70
105
  return !!window.MONGOOSE_STUDIO_CONFIG.enableTaskVisualizer;
106
+ },
107
+ recentPagesList() {
108
+ return this.localRecentPages;
109
+ },
110
+ hasLLMAPIKey() {
111
+ return !!window.MONGOOSE_STUDIO_CONFIG.hasLLMAPIKey;
112
+ },
113
+ chatDisabledReason() {
114
+ return 'Chat requires an Anthropic, Gemini, or OpenAI API key.';
71
115
  }
72
116
  },
73
117
  methods: {
@@ -97,6 +141,30 @@ module.exports = app => app.component('navbar', {
97
141
  const meta = document.querySelector('meta[name="theme-color"]');
98
142
  if (meta) meta.setAttribute('content', this.darkMode ? '#0f0f0f' : '#ffffff');
99
143
  document.documentElement.dispatchEvent(new CustomEvent('studio-theme-changed', { detail: { dark: this.darkMode } }));
144
+ },
145
+ refreshRecentPagesFromStorage() {
146
+ this.localRecentPages = safeReadRecentPages();
147
+ },
148
+ openRecentPagesModal() {
149
+ this.showRecentPagesModal = true;
150
+ this.refreshRecentPagesFromStorage();
151
+ },
152
+ closeRecentPagesModal() {
153
+ this.showRecentPagesModal = false;
154
+ },
155
+ clearRecentPagesHistory() {
156
+ this.localRecentPages = [];
157
+ void withRecentPagesStorageLock(() => saveRecentPages([]));
158
+ },
159
+ goToRecentPage(entry) {
160
+ if (!entry || !entry.path) {
161
+ return;
162
+ }
163
+ this.showRecentPagesModal = false;
164
+ if (entry.path === this.$route.fullPath) {
165
+ return;
166
+ }
167
+ this.$router.push(entry.path);
100
168
  }
101
169
  },
102
170
  directives: {
package/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  'use strict';
2
-
2
+
3
3
  exports.express = require('./express');
4
+ exports.enableDebugging = require('./debugging').enableDebugging;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.",
5
5
  "homepage": "https://mongoosestudio.app/",
6
6
  "repository": {
@@ -53,6 +53,7 @@
53
53
  "tailwind": "tailwindcss -o ./frontend/public/tw.css",
54
54
  "tailwind:watch": "tailwindcss -o ./frontend/public/tw.css --watch",
55
55
  "test": "mocha test/*.test.js",
56
- "test:frontend": "mocha test/frontend/*.test.js"
56
+ "test:frontend": "mocha test/frontend/*.test.js",
57
+ "eval:agent": "node eval/agent/run.js"
57
58
  }
58
59
  }