@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
@@ -1192,6 +1192,10 @@ video {
1192
1192
  width: 90vw !important;
1193
1193
  }
1194
1194
 
1195
+ .\!w-\[min\(94vw\2c 28rem\)\] {
1196
+ width: min(94vw,28rem) !important;
1197
+ }
1198
+
1195
1199
  .w-0 {
1196
1200
  width: 0px;
1197
1201
  }
@@ -1583,6 +1587,16 @@ video {
1583
1587
  gap: 2rem;
1584
1588
  }
1585
1589
 
1590
+ .gap-x-1 {
1591
+ -moz-column-gap: 0.25rem;
1592
+ column-gap: 0.25rem;
1593
+ }
1594
+
1595
+ .gap-x-1\.5 {
1596
+ -moz-column-gap: 0.375rem;
1597
+ column-gap: 0.375rem;
1598
+ }
1599
+
1586
1600
  .gap-x-4 {
1587
1601
  -moz-column-gap: 1rem;
1588
1602
  column-gap: 1rem;
@@ -1666,6 +1680,10 @@ video {
1666
1680
  overflow: auto;
1667
1681
  }
1668
1682
 
1683
+ .\!overflow-hidden {
1684
+ overflow: hidden !important;
1685
+ }
1686
+
1669
1687
  .overflow-hidden {
1670
1688
  overflow: hidden;
1671
1689
  }
@@ -1708,6 +1726,10 @@ video {
1708
1726
  word-break: break-all;
1709
1727
  }
1710
1728
 
1729
+ .\!rounded-lg {
1730
+ border-radius: 0.5rem !important;
1731
+ }
1732
+
1711
1733
  .rounded {
1712
1734
  border-radius: 0.25rem;
1713
1735
  }
@@ -1762,6 +1784,10 @@ video {
1762
1784
  border-bottom-right-radius: 0px;
1763
1785
  }
1764
1786
 
1787
+ .\!border {
1788
+ border-width: 1px !important;
1789
+ }
1790
+
1765
1791
  .border {
1766
1792
  border-width: 1px;
1767
1793
  }
@@ -1826,6 +1852,10 @@ video {
1826
1852
  border-style: none;
1827
1853
  }
1828
1854
 
1855
+ .\!border-edge {
1856
+ border-color: var(--color-edge) !important;
1857
+ }
1858
+
1829
1859
  .border-black\/10 {
1830
1860
  border-color: rgb(0 0 0 / 0.1);
1831
1861
  }
@@ -1867,6 +1897,11 @@ video {
1867
1897
  border-color: rgb(254 202 202 / var(--tw-border-opacity));
1868
1898
  }
1869
1899
 
1900
+ .border-red-300 {
1901
+ --tw-border-opacity: 1;
1902
+ border-color: rgb(252 165 165 / var(--tw-border-opacity));
1903
+ }
1904
+
1870
1905
  .border-red-400 {
1871
1906
  --tw-border-opacity: 1;
1872
1907
  border-color: rgb(248 113 113 / var(--tw-border-opacity));
@@ -1906,6 +1941,10 @@ video {
1906
1941
  border-right-color: rgb(17 24 39 / var(--tw-border-opacity));
1907
1942
  }
1908
1943
 
1944
+ .\!bg-surface {
1945
+ background-color: var(--color-surface) !important;
1946
+ }
1947
+
1909
1948
  .bg-amber-100 {
1910
1949
  --tw-bg-opacity: 1;
1911
1950
  background-color: rgb(254 243 199 / var(--tw-bg-opacity));
@@ -2190,6 +2229,10 @@ video {
2190
2229
  --tw-gradient-to: #dc2626 var(--tw-gradient-to-position);
2191
2230
  }
2192
2231
 
2232
+ .\!p-0 {
2233
+ padding: 0px !important;
2234
+ }
2235
+
2193
2236
  .p-0 {
2194
2237
  padding: 0px;
2195
2238
  }
@@ -2381,6 +2424,14 @@ video {
2381
2424
  padding-left: 2rem;
2382
2425
  }
2383
2426
 
2427
+ .pr-12 {
2428
+ padding-right: 3rem;
2429
+ }
2430
+
2431
+ .pr-2 {
2432
+ padding-right: 0.5rem;
2433
+ }
2434
+
2384
2435
  .pr-3 {
2385
2436
  padding-right: 0.75rem;
2386
2437
  }
@@ -2446,6 +2497,10 @@ video {
2446
2497
  font-size: 10px;
2447
2498
  }
2448
2499
 
2500
+ .text-\[11px\] {
2501
+ font-size: 11px;
2502
+ }
2503
+
2449
2504
  .text-base {
2450
2505
  font-size: 1rem;
2451
2506
  line-height: 1.5rem;
@@ -2791,6 +2846,12 @@ video {
2791
2846
  opacity: 0.75;
2792
2847
  }
2793
2848
 
2849
+ .\!shadow-xl {
2850
+ --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1) !important;
2851
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color) !important;
2852
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) !important;
2853
+ }
2854
+
2794
2855
  .shadow {
2795
2856
  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
2796
2857
  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -3227,6 +3288,11 @@ video {
3227
3288
  background-color: rgb(254 226 226 / var(--tw-bg-opacity));
3228
3289
  }
3229
3290
 
3291
+ .hover\:bg-red-50:hover {
3292
+ --tw-bg-opacity: 1;
3293
+ background-color: rgb(254 242 242 / var(--tw-bg-opacity));
3294
+ }
3295
+
3230
3296
  .hover\:bg-red-500:hover {
3231
3297
  --tw-bg-opacity: 1;
3232
3298
  background-color: rgb(239 68 68 / var(--tw-bg-opacity));
@@ -3487,6 +3553,11 @@ video {
3487
3553
  --tw-ring-color: var(--color-primary-subtle);
3488
3554
  }
3489
3555
 
3556
+ .focus\:ring-red-200:focus {
3557
+ --tw-ring-opacity: 1;
3558
+ --tw-ring-color: rgb(254 202 202 / var(--tw-ring-opacity));
3559
+ }
3560
+
3490
3561
  .focus\:ring-sky-600:focus {
3491
3562
  --tw-ring-opacity: 1;
3492
3563
  --tw-ring-color: rgb(2 132 199 / var(--tw-ring-opacity));
@@ -3822,6 +3893,11 @@ video {
3822
3893
  }
3823
3894
  }
3824
3895
 
3896
+ :is(:where(.dark) .dark\:\!bg-shark-900) {
3897
+ --tw-bg-opacity: 1 !important;
3898
+ background-color: rgb(21 25 30 / var(--tw-bg-opacity)) !important;
3899
+ }
3900
+
3825
3901
  :is(:where(.dark) .dark\:bg-shark-800) {
3826
3902
  --tw-bg-opacity: 1;
3827
3903
  background-color: rgb(33 37 41 / var(--tw-bg-opacity));
@@ -3835,6 +3911,11 @@ video {
3835
3911
  background-color: rgb(33 37 41 / 0.8);
3836
3912
  }
3837
3913
 
3914
+ :is(:where(.dark) .dark\:bg-shark-900) {
3915
+ --tw-bg-opacity: 1;
3916
+ background-color: rgb(21 25 30 / var(--tw-bg-opacity));
3917
+ }
3918
+
3838
3919
  :is(:where(.dark) .dark\:text-gray-400) {
3839
3920
  --tw-text-opacity: 1;
3840
3921
  color: rgb(156 163 175 / var(--tw-text-opacity));
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ const RECENT_PAGES_STORAGE_KEY = 'studio:recent-pages-history';
4
+ const RECENT_PAGES_CHANGED_EVENT = 'studio-recent-pages-changed';
5
+ const RECENT_PAGES_LOCK_NAME = `studio-lock:${RECENT_PAGES_STORAGE_KEY}`;
6
+ const MAX_RECENT_PAGES = 50;
7
+
8
+ /** Collapses hash-style prefixes (e.g. `/#/`) to a normal path; keeps query string. */
9
+ function normalizeTrackedPath(fullPath) {
10
+ if (!fullPath || typeof fullPath !== 'string') {
11
+ return '/';
12
+ }
13
+ const qIndex = fullPath.indexOf('?');
14
+ const rawMain = qIndex === -1 ? fullPath : fullPath.slice(0, qIndex);
15
+ const qs = qIndex === -1 ? '' : fullPath.slice(qIndex);
16
+
17
+ let pathOnly = rawMain.trim().replace(/#/g, '');
18
+ pathOnly = pathOnly.replace(/^\/+/u, '/');
19
+ if (pathOnly !== '/' && /\/$/u.test(pathOnly)) {
20
+ pathOnly = pathOnly.replace(/\/+$/u, '');
21
+ }
22
+ if (pathOnly === '' || pathOnly === '/') {
23
+ pathOnly = '/';
24
+ } else if (!pathOnly.startsWith('/')) {
25
+ pathOnly = `/${pathOnly}`;
26
+ }
27
+
28
+ const out = qs ? pathOnly + qs : pathOnly;
29
+ return out;
30
+ }
31
+
32
+ function safeReadRecentPages() {
33
+ if (typeof window === 'undefined' || !window.localStorage) {
34
+ return [];
35
+ }
36
+ try {
37
+ const raw = window.localStorage.getItem(RECENT_PAGES_STORAGE_KEY);
38
+ if (!raw) {
39
+ return [];
40
+ }
41
+ const parsed = JSON.parse(raw);
42
+ if (!Array.isArray(parsed)) {
43
+ return [];
44
+ }
45
+ return parsed
46
+ .map(entry => {
47
+ if (!entry || typeof entry.path !== 'string') {
48
+ return null;
49
+ }
50
+ return { ...entry, path: normalizeTrackedPath(entry.path) };
51
+ })
52
+ .filter(
53
+ entry =>
54
+ entry &&
55
+ typeof entry.label === 'string' &&
56
+ typeof entry.visitedAt === 'number' &&
57
+ entry.path !== ''
58
+ )
59
+ .slice(0, MAX_RECENT_PAGES);
60
+ } catch {
61
+ return [];
62
+ }
63
+ }
64
+
65
+ function notifyRecentPagesChangedFromThisTab() {
66
+ if (typeof window === 'undefined') {
67
+ return;
68
+ }
69
+ try {
70
+ window.dispatchEvent(new CustomEvent(RECENT_PAGES_CHANGED_EVENT));
71
+ } catch {
72
+ /* ignore */
73
+ }
74
+ }
75
+
76
+ function saveRecentPages(entries) {
77
+ if (typeof window === 'undefined' || !window.localStorage) {
78
+ return;
79
+ }
80
+ window.localStorage.setItem(
81
+ RECENT_PAGES_STORAGE_KEY,
82
+ JSON.stringify(entries.slice(0, MAX_RECENT_PAGES))
83
+ );
84
+ notifyRecentPagesChangedFromThisTab();
85
+ }
86
+
87
+ function withRecentPagesStorageLock(syncFn) {
88
+ if (typeof window === 'undefined' || !window.localStorage) {
89
+ syncFn();
90
+ return Promise.resolve();
91
+ }
92
+
93
+ try {
94
+ const locksApi =
95
+ typeof navigator !== 'undefined' && navigator.locks !== undefined ? navigator.locks : null;
96
+
97
+ if (locksApi !== null && typeof locksApi.request === 'function') {
98
+ return locksApi.request(RECENT_PAGES_LOCK_NAME, syncFn);
99
+ }
100
+ } catch {
101
+ /* fall through */
102
+ }
103
+
104
+ syncFn();
105
+ return Promise.resolve();
106
+ }
107
+
108
+ module.exports = {
109
+ RECENT_PAGES_STORAGE_KEY,
110
+ RECENT_PAGES_CHANGED_EVENT,
111
+ MAX_RECENT_PAGES,
112
+ normalizeTrackedPath,
113
+ safeReadRecentPages,
114
+ saveRecentPages,
115
+ withRecentPagesStorageLock
116
+ };
@@ -9,6 +9,7 @@ const client = axios.create({
9
9
  console.log('API baseURL:', window.MONGOOSE_STUDIO_CONFIG.baseURL);
10
10
 
11
11
  window.apiClient = client;
12
+ exports.fetch = fetch;
12
13
  client.interceptors.request.use(req => {
13
14
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
14
15
  if (accessToken) {
@@ -24,10 +25,92 @@ client.interceptors.response.use(
24
25
  if (typeof err?.response?.data === 'string') {
25
26
  throw new Error(`Error in ${err.config?.method} ${err.config?.url}: ${err.response.data}`);
26
27
  }
28
+ if (typeof err?.response?.data?.message === 'string') {
29
+ throw new Error(`Error in ${err.config?.method} ${err.config?.url}: ${err.response.data.message}`);
30
+ }
27
31
  throw err;
28
32
  }
29
33
  );
30
34
 
35
+ async function* streamSSE(url, options = {}) {
36
+ const fetch = exports.fetch;
37
+ const response = await fetch(url, {
38
+ method: 'GET',
39
+ ...options,
40
+ headers: {
41
+ ...(options.headers || {}),
42
+ Accept: 'text/event-stream'
43
+ }
44
+ });
45
+
46
+ if (!response.ok) {
47
+ throw new Error(`HTTP error! Status: ${response.status}`);
48
+ }
49
+
50
+ const reader = response.body.getReader();
51
+ const decoder = new TextDecoder('utf-8');
52
+ let buffer = '';
53
+
54
+ while (true) {
55
+ const { done, value } = await reader.read();
56
+ if (done) {
57
+ break;
58
+ }
59
+ buffer += decoder.decode(value, { stream: true });
60
+
61
+ let eventEnd;
62
+ while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
63
+ const eventStr = buffer.slice(0, eventEnd);
64
+ buffer = buffer.slice(eventEnd + 2);
65
+
66
+ const event = parseSSEEvent(eventStr);
67
+ if (event == null) {
68
+ continue;
69
+ }
70
+ if (event.type === 'error') {
71
+ const message = getSSEErrorMessage(event.data);
72
+ throw new Error(message);
73
+ }
74
+ yield event.data;
75
+ }
76
+ }
77
+ }
78
+
79
+ function parseSSEEvent(eventStr) {
80
+ const lines = eventStr.split('\n');
81
+ let type = 'message';
82
+ let data = '';
83
+ for (const line of lines) {
84
+ if (line.startsWith('event:')) {
85
+ type = line.slice(6).trim() || 'message';
86
+ } else if (line.startsWith('data:')) {
87
+ data += line.slice(5).trim();
88
+ }
89
+ }
90
+ if (!data) {
91
+ return null;
92
+ }
93
+
94
+ try {
95
+ return { type, data: JSON.parse(data) };
96
+ } catch (err) {
97
+ return { type, data };
98
+ }
99
+ }
100
+
101
+ function getSSEErrorMessage(data) {
102
+ if (typeof data === 'string') {
103
+ return data || 'Streaming request failed.';
104
+ }
105
+ if (typeof data?.message === 'string' && data.message.length > 0) {
106
+ return data.message;
107
+ }
108
+ if (typeof data?.error === 'string' && data.error.length > 0) {
109
+ return data.error;
110
+ }
111
+ return 'Streaming request failed.';
112
+ }
113
+
31
114
  if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
32
115
  exports.getCapabilities = function getCapabilities() {
33
116
  return client.post('', { action: 'getCapabilities' }).then(res => res.data);
@@ -103,6 +186,7 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
103
186
  exportQueryResults(params) {
104
187
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
105
188
 
189
+ const fetch = exports.fetch;
106
190
  return fetch(window.MONGOOSE_STUDIO_CONFIG.baseURL + '?' + new URLSearchParams({ ...params, action: 'Model.exportQueryResults' }).toString(), {
107
191
  method: 'GET',
108
192
  headers: {
@@ -166,6 +250,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
166
250
  dropIndex: function dropIndex(params) {
167
251
  return client.post('', { action: 'Model.dropIndex', ...params }).then(res => res.data);
168
252
  },
253
+ dropCollection: function dropCollection(params) {
254
+ return client.post('', { action: 'Model.dropCollection', ...params }).then(res => res.data);
255
+ },
169
256
  listModels: function listModels() {
170
257
  return client.post('', { action: 'Model.listModels' }).then(res => res.data);
171
258
  },
@@ -254,50 +341,11 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
254
341
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
255
342
  const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/ChatThread/streamChatMessage?' + new URLSearchParams(params).toString();
256
343
 
257
- const response = await fetch(url, {
258
- method: 'GET',
344
+ yield* streamSSE(url, {
259
345
  headers: {
260
- Authorization: `${accessToken}`,
261
- Accept: 'text/event-stream'
346
+ Authorization: `${accessToken}`
262
347
  }
263
348
  });
264
-
265
- if (!response.ok) {
266
- throw new Error(`HTTP error! Status: ${response.status}`);
267
- }
268
-
269
- const reader = response.body.getReader();
270
- const decoder = new TextDecoder('utf-8');
271
- let buffer = '';
272
-
273
- while (true) {
274
- const { done, value } = await reader.read();
275
- if (done) break;
276
- buffer += decoder.decode(value, { stream: true });
277
-
278
- let eventEnd;
279
- while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
280
- const eventStr = buffer.slice(0, eventEnd);
281
- buffer = buffer.slice(eventEnd + 2);
282
-
283
- // Parse SSE event
284
- const lines = eventStr.split('\n');
285
- let data = '';
286
- for (const line of lines) {
287
- if (line.startsWith('data:')) {
288
- data += line.slice(5).trim();
289
- }
290
- }
291
- if (data) {
292
- try {
293
- const res = JSON.parse(data);
294
- yield res;
295
- } catch (err) {
296
- yield data;
297
- }
298
- }
299
- }
300
- }
301
349
  }
302
350
  };
303
351
  exports.ChatMessage = {
@@ -330,6 +378,7 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
330
378
  exportQueryResults(params) {
331
379
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
332
380
 
381
+ const fetch = exports.fetch;
333
382
  return fetch(window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/exportQueryResults?' + new URLSearchParams(params).toString(), {
334
383
  method: 'GET',
335
384
  headers: {
@@ -367,50 +416,11 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
367
416
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
368
417
  const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/getDocumentsStream?' + new URLSearchParams(params).toString();
369
418
 
370
- const response = await fetch(url, {
371
- method: 'GET',
419
+ yield* streamSSE(url, {
372
420
  headers: {
373
- Authorization: `${accessToken}`,
374
- Accept: 'text/event-stream'
421
+ Authorization: `${accessToken}`
375
422
  }
376
423
  });
377
-
378
- if (!response.ok) {
379
- throw new Error(`HTTP error! Status: ${response.status}`);
380
- }
381
-
382
- const reader = response.body.getReader();
383
- const decoder = new TextDecoder('utf-8');
384
- let buffer = '';
385
-
386
- while (true) {
387
- const { done, value } = await reader.read();
388
- if (done) break;
389
- buffer += decoder.decode(value, { stream: true });
390
-
391
- let eventEnd;
392
- while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
393
- const eventStr = buffer.slice(0, eventEnd);
394
- buffer = buffer.slice(eventEnd + 2);
395
-
396
- // Parse SSE event
397
- const lines = eventStr.split('\n');
398
- let data = '';
399
- for (const line of lines) {
400
- if (line.startsWith('data:')) {
401
- data += line.slice(5).trim();
402
- }
403
- }
404
- if (data) {
405
- try {
406
- yield JSON.parse(data);
407
- } catch (err) {
408
- // If not JSON, yield as string
409
- yield data;
410
- }
411
- }
412
- }
413
- }
414
424
  },
415
425
  getEstimatedDocumentCounts: function getEstimatedDocumentCounts() {
416
426
  return client.post('/Model/getEstimatedDocumentCounts', {}).then(res => res.data);
@@ -419,51 +429,12 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
419
429
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
420
430
  const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/streamDocumentChanges?' + new URLSearchParams(params).toString();
421
431
 
422
- const response = await fetch(url, {
423
- method: 'GET',
432
+ yield* streamSSE(url, {
424
433
  headers: {
425
- Authorization: `${accessToken}`,
426
- Accept: 'text/event-stream'
434
+ Authorization: `${accessToken}`
427
435
  },
428
436
  signal: options.signal
429
437
  });
430
-
431
- if (!response.ok) {
432
- throw new Error(`HTTP error! Status: ${response.status}`);
433
- }
434
-
435
- const reader = response.body.getReader();
436
- const decoder = new TextDecoder('utf-8');
437
- let buffer = '';
438
-
439
- while (true) {
440
- const { done, value } = await reader.read();
441
- if (done) break;
442
- buffer += decoder.decode(value, { stream: true });
443
-
444
- let eventEnd;
445
- while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
446
- const eventStr = buffer.slice(0, eventEnd);
447
- buffer = buffer.slice(eventEnd + 2);
448
-
449
- // Parse SSE event
450
- const lines = eventStr.split('\n');
451
- let data = '';
452
- for (const line of lines) {
453
- if (line.startsWith('data:')) {
454
- data += line.slice(5).trim();
455
- }
456
- }
457
- if (data) {
458
- try {
459
- yield JSON.parse(data);
460
- } catch (err) {
461
- // If not JSON, yield as string
462
- yield data;
463
- }
464
- }
465
- }
466
- }
467
438
  },
468
439
  getCollectionInfo: function getCollectionInfo(params) {
469
440
  return client.post('/Model/getCollectionInfo', params).then(res => res.data);
@@ -484,50 +455,11 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
484
455
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
485
456
  const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/streamChatMessage?' + new URLSearchParams(params).toString();
486
457
 
487
- const response = await fetch(url, {
488
- method: 'GET',
458
+ yield* streamSSE(url, {
489
459
  headers: {
490
- Authorization: `${accessToken}`,
491
- Accept: 'text/event-stream'
460
+ Authorization: `${accessToken}`
492
461
  }
493
462
  });
494
-
495
- if (!response.ok) {
496
- throw new Error(`HTTP error! Status: ${response.status}`);
497
- }
498
-
499
- const reader = response.body.getReader();
500
- const decoder = new TextDecoder('utf-8');
501
- let buffer = '';
502
-
503
- while (true) {
504
- const { done, value } = await reader.read();
505
- if (done) break;
506
- buffer += decoder.decode(value, { stream: true });
507
-
508
- let eventEnd;
509
- while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
510
- const eventStr = buffer.slice(0, eventEnd);
511
- buffer = buffer.slice(eventEnd + 2);
512
-
513
- // Parse SSE event
514
- const lines = eventStr.split('\n');
515
- let data = '';
516
- for (const line of lines) {
517
- if (line.startsWith('data:')) {
518
- data += line.slice(5).trim();
519
- }
520
- }
521
- if (data) {
522
- try {
523
- yield JSON.parse(data);
524
- } catch (err) {
525
- // If not JSON, yield as string
526
- yield data;
527
- }
528
- }
529
- }
530
- }
531
463
  },
532
464
  updateDocuments: function updateDocument(params) {
533
465
  return client.post('/Model/updateDocuments', params).then(res => res.data);
@@ -49,7 +49,7 @@
49
49
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5" />
50
50
  </svg>
51
51
  </button>
52
- <div v-if="!isEditing" class="relative mr-1" ref="runDropdown">
52
+ <div class="relative mr-1" ref="runDropdown">
53
53
  <div class="flex items-center">
54
54
  <button
55
55
  class="px-2 py-1 text-xs bg-primary hover:bg-primary-hover text-primary-text border-none rounded-l-md cursor-pointer transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed"
@@ -146,11 +146,6 @@
146
146
  @click.stop="cancelEditing">
147
147
  Cancel
148
148
  </button>
149
- <async-button
150
- class="px-2 py-1 text-xs bg-blue-600 text-white border-none rounded cursor-pointer hover:bg-blue-700 transition-colors disabled:bg-gray-400"
151
- @click="executeScript(false)">
152
- Execute
153
- </async-button>
154
149
  </div>
155
150
  </div>
156
151
  <pre v-else class="whitespace-pre-wrap !my-0 bg-muted"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
@@ -12,7 +12,7 @@
12
12
  </button>
13
13
  <!-- Sidebar: Chat Threads -->
14
14
  <aside
15
- class="bg-page border-r overflow-hidden h-full transition-all duration-300 ease-in-out z-20 w-64 fixed lg:relative shrink-0 flex flex-col"
15
+ class="bg-page border-r overflow-hidden transition-all duration-300 ease-in-out z-20 w-64 fixed top-[55px] bottom-0 lg:top-auto lg:bottom-auto lg:relative lg:h-full shrink-0 flex flex-col"
16
16
  :class="hideSidebar === false ? 'translate-x-0' : hideSidebar === true ? '-translate-x-full lg:translate-x-0 lg:!w-0 lg:!border-r-0' : '-translate-x-full lg:translate-x-0'"
17
17
  style="z-index: 1000">
18
18
  <!-- Search + New Thread -->
@@ -26,9 +26,29 @@ module.exports = app => app.component('confirm-changes', {
26
26
  },
27
27
  startSave() {
28
28
  this.$emit('save');
29
+ },
30
+ onEnter(event) {
31
+ if (event.key !== 'Enter') {
32
+ return;
33
+ }
34
+
35
+ const modalMasks = Array.from(document.querySelectorAll('.modal-mask'));
36
+ const currentMask = this.$el?.closest('.modal-mask');
37
+ const isTopMostModal = modalMasks.length > 0 && modalMasks[modalMasks.length - 1] === currentMask;
38
+
39
+ if (!isTopMostModal) {
40
+ return;
41
+ }
42
+
43
+ event.preventDefault();
44
+ this.startSave();
29
45
  }
30
46
  },
31
47
  mounted() {
48
+ window.addEventListener('keydown', this.onEnter);
32
49
  Prism.highlightElement(this.$refs.code);
50
+ },
51
+ beforeUnmount() {
52
+ window.removeEventListener('keydown', this.onEnter);
33
53
  }
34
54
  });