@mongoosejs/studio 0.2.5 → 0.2.7

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.
@@ -594,6 +594,10 @@ video {
594
594
  pointer-events: none;
595
595
  }
596
596
 
597
+ .visible {
598
+ visibility: visible;
599
+ }
600
+
597
601
  .invisible {
598
602
  visibility: hidden;
599
603
  }
@@ -631,6 +635,10 @@ video {
631
635
  bottom: 0px;
632
636
  }
633
637
 
638
+ .-left-1 {
639
+ left: -0.25rem;
640
+ }
641
+
634
642
  .-left-2 {
635
643
  left: -0.5rem;
636
644
  }
@@ -643,6 +651,10 @@ video {
643
651
  left: 0px;
644
652
  }
645
653
 
654
+ .left-full {
655
+ left: 100%;
656
+ }
657
+
646
658
  .right-0 {
647
659
  right: 0px;
648
660
  }
@@ -934,6 +946,10 @@ video {
934
946
  height: 90vh !important;
935
947
  }
936
948
 
949
+ .h-0 {
950
+ height: 0px;
951
+ }
952
+
937
953
  .h-10 {
938
954
  height: 2.5rem;
939
955
  }
@@ -966,6 +982,10 @@ video {
966
982
  height: 1.5rem;
967
983
  }
968
984
 
985
+ .h-64 {
986
+ height: 16rem;
987
+ }
988
+
969
989
  .h-8 {
970
990
  height: 2rem;
971
991
  }
@@ -1018,6 +1038,10 @@ video {
1018
1038
  min-height: 200px;
1019
1039
  }
1020
1040
 
1041
+ .min-h-\[400px\] {
1042
+ min-height: 400px;
1043
+ }
1044
+
1021
1045
  .\!w-0 {
1022
1046
  width: 0px !important;
1023
1047
  }
@@ -1206,6 +1230,10 @@ video {
1206
1230
  cursor: auto;
1207
1231
  }
1208
1232
 
1233
+ .cursor-help {
1234
+ cursor: help;
1235
+ }
1236
+
1209
1237
  .cursor-not-allowed {
1210
1238
  cursor: not-allowed;
1211
1239
  }
@@ -1497,6 +1525,10 @@ video {
1497
1525
  border-bottom-width: 2px;
1498
1526
  }
1499
1527
 
1528
+ .border-b-4 {
1529
+ border-bottom-width: 4px;
1530
+ }
1531
+
1500
1532
  .border-l-\[3px\] {
1501
1533
  border-left-width: 3px;
1502
1534
  }
@@ -1509,10 +1541,18 @@ video {
1509
1541
  border-right-width: 0px;
1510
1542
  }
1511
1543
 
1544
+ .border-r-4 {
1545
+ border-right-width: 4px;
1546
+ }
1547
+
1512
1548
  .border-t {
1513
1549
  border-top-width: 1px;
1514
1550
  }
1515
1551
 
1552
+ .border-t-4 {
1553
+ border-top-width: 4px;
1554
+ }
1555
+
1516
1556
  .border-none {
1517
1557
  border-style: none;
1518
1558
  }
@@ -1566,6 +1606,11 @@ video {
1566
1606
  border-left-color: rgb(59 130 246 / var(--tw-border-opacity));
1567
1607
  }
1568
1608
 
1609
+ .border-r-gray-900 {
1610
+ --tw-border-opacity: 1;
1611
+ border-right-color: rgb(17 24 39 / var(--tw-border-opacity));
1612
+ }
1613
+
1569
1614
  .\!bg-zinc-50 {
1570
1615
  --tw-bg-opacity: 1 !important;
1571
1616
  background-color: rgb(250 250 250 / var(--tw-bg-opacity)) !important;
@@ -1611,6 +1656,11 @@ video {
1611
1656
  background-color: rgb(37 99 235 / var(--tw-bg-opacity));
1612
1657
  }
1613
1658
 
1659
+ .bg-emerald-600 {
1660
+ --tw-bg-opacity: 1;
1661
+ background-color: rgb(5 150 105 / var(--tw-bg-opacity));
1662
+ }
1663
+
1614
1664
  .bg-forest-green-600 {
1615
1665
  --tw-bg-opacity: 1;
1616
1666
  background-color: rgb(0 202 44 / var(--tw-bg-opacity));
@@ -1772,6 +1822,10 @@ video {
1772
1822
  padding: 0px;
1773
1823
  }
1774
1824
 
1825
+ .p-0\.5 {
1826
+ padding: 0.125rem;
1827
+ }
1828
+
1775
1829
  .p-1 {
1776
1830
  padding: 0.25rem;
1777
1831
  }
@@ -2244,6 +2298,12 @@ video {
2244
2298
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2245
2299
  }
2246
2300
 
2301
+ .shadow-xl {
2302
+ --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
2303
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
2304
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2305
+ }
2306
+
2247
2307
  .outline-none {
2248
2308
  outline: 2px solid transparent;
2249
2309
  outline-offset: 2px;
@@ -2269,6 +2329,12 @@ video {
2269
2329
  outline-color: #d1d5db;
2270
2330
  }
2271
2331
 
2332
+ .ring {
2333
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2334
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2335
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2336
+ }
2337
+
2272
2338
  .ring-1 {
2273
2339
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2274
2340
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
@@ -2446,6 +2512,11 @@ video {
2446
2512
  background-color: rgb(29 78 216 / var(--tw-bg-opacity));
2447
2513
  }
2448
2514
 
2515
+ .hover\:bg-emerald-500:hover {
2516
+ --tw-bg-opacity: 1;
2517
+ background-color: rgb(16 185 129 / var(--tw-bg-opacity));
2518
+ }
2519
+
2449
2520
  .hover\:bg-forest-green-500:hover {
2450
2521
  --tw-bg-opacity: 1;
2451
2522
  background-color: rgb(0 242 58 / var(--tw-bg-opacity));
@@ -2486,6 +2557,11 @@ video {
2486
2557
  background-color: rgb(75 85 99 / var(--tw-bg-opacity));
2487
2558
  }
2488
2559
 
2560
+ .hover\:bg-gray-700:hover {
2561
+ --tw-bg-opacity: 1;
2562
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity));
2563
+ }
2564
+
2489
2565
  .hover\:bg-green-100:hover {
2490
2566
  --tw-bg-opacity: 1;
2491
2567
  background-color: rgb(220 252 231 / var(--tw-bg-opacity));
@@ -2576,6 +2652,11 @@ video {
2576
2652
  color: rgb(30 64 175 / var(--tw-text-opacity));
2577
2653
  }
2578
2654
 
2655
+ .hover\:text-gray-600:hover {
2656
+ --tw-text-opacity: 1;
2657
+ color: rgb(75 85 99 / var(--tw-text-opacity));
2658
+ }
2659
+
2579
2660
  .hover\:text-gray-700:hover {
2580
2661
  --tw-text-opacity: 1;
2581
2662
  color: rgb(55 65 81 / var(--tw-text-opacity));
@@ -2586,6 +2667,11 @@ video {
2586
2667
  color: rgb(31 41 55 / var(--tw-text-opacity));
2587
2668
  }
2588
2669
 
2670
+ .hover\:text-gray-900:hover {
2671
+ --tw-text-opacity: 1;
2672
+ color: rgb(17 24 39 / var(--tw-text-opacity));
2673
+ }
2674
+
2589
2675
  .hover\:text-slate-700:hover {
2590
2676
  --tw-text-opacity: 1;
2591
2677
  color: rgb(51 65 85 / var(--tw-text-opacity));
@@ -2620,6 +2706,11 @@ video {
2620
2706
  border-color: transparent;
2621
2707
  }
2622
2708
 
2709
+ .focus\:border-ultramarine-500:focus {
2710
+ --tw-border-opacity: 1;
2711
+ border-color: rgb(63 83 255 / var(--tw-border-opacity));
2712
+ }
2713
+
2623
2714
  .focus\:opacity-100:focus {
2624
2715
  opacity: 1;
2625
2716
  }
@@ -2871,6 +2962,10 @@ video {
2871
2962
  flex: none;
2872
2963
  }
2873
2964
 
2965
+ .sm\:flex-row {
2966
+ flex-direction: row;
2967
+ }
2968
+
2874
2969
  .sm\:flex-col {
2875
2970
  flex-direction: column;
2876
2971
  }
@@ -147,6 +147,14 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
147
147
  updateDocument: function updateDocument(params) {
148
148
  return client.post('', { action: 'Model.updateDocument', ...params }).then(res => res.data);
149
149
  },
150
+ createChatMessage(params) {
151
+ return client.post('', { action: 'Model.createChatMessage', ...params }).then(res => res.data);
152
+ },
153
+ streamChatMessage: async function* streamChatMessage(params) {
154
+ // Don't stream on Next.js or Netlify for now.
155
+ const data = await client.post('', { action: 'Model.createChatMessage', ...params }).then(res => res.data);
156
+ yield { textPart: data.text };
157
+ },
150
158
  updateDocuments: function updateDocuments(params) {
151
159
  return client.post('', { action: 'Model.updateDocuments', ...params }).then(res => res.data);
152
160
  }
@@ -250,6 +258,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
250
258
  createChart: function(params) {
251
259
  return client.post('/Model/createChart', params).then(res => res.data);
252
260
  },
261
+ createChatMessage: function(params) {
262
+ return client.post('/Model/createChatMessage', params).then(res => res.data);
263
+ },
253
264
  createDocument: function(params) {
254
265
  return client.post('/Model/createDocument', params).then(res => res.data);
255
266
  },
@@ -356,6 +367,55 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
356
367
  updateDocument: function updateDocument(params) {
357
368
  return client.post('/Model/updateDocument', params).then(res => res.data);
358
369
  },
370
+ streamChatMessage: async function* streamChatMessage(params) {
371
+ const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
372
+ const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/streamChatMessage?' + new URLSearchParams(params).toString();
373
+
374
+ const response = await fetch(url, {
375
+ method: 'GET',
376
+ headers: {
377
+ Authorization: `${accessToken}`,
378
+ Accept: 'text/event-stream'
379
+ }
380
+ });
381
+
382
+ if (!response.ok) {
383
+ throw new Error(`HTTP error! Status: ${response.status}`);
384
+ }
385
+
386
+ const reader = response.body.getReader();
387
+ const decoder = new TextDecoder('utf-8');
388
+ let buffer = '';
389
+
390
+ while (true) {
391
+ const { done, value } = await reader.read();
392
+ if (done) break;
393
+ buffer += decoder.decode(value, { stream: true });
394
+
395
+ let eventEnd;
396
+ while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
397
+ const eventStr = buffer.slice(0, eventEnd);
398
+ buffer = buffer.slice(eventEnd + 2);
399
+
400
+ // Parse SSE event
401
+ const lines = eventStr.split('\n');
402
+ let data = '';
403
+ for (const line of lines) {
404
+ if (line.startsWith('data:')) {
405
+ data += line.slice(5).trim();
406
+ }
407
+ }
408
+ if (data) {
409
+ try {
410
+ yield JSON.parse(data);
411
+ } catch (err) {
412
+ // If not JSON, yield as string
413
+ yield data;
414
+ }
415
+ }
416
+ }
417
+ }
418
+ },
359
419
  updateDocuments: function updateDocument(params) {
360
420
  return client.post('/Model/updateDocuments', params).then(res => res.data);
361
421
  }
@@ -30,7 +30,9 @@ module.exports = {
30
30
  this.$toast.success('Chat thread created!');
31
31
  }
32
32
 
33
+ const userChatMessageIndex = this.chatMessages.length;
33
34
  this.chatMessages.push({
35
+ _id: Math.random().toString(36).substr(2, 9),
34
36
  content,
35
37
  role: 'user'
36
38
  });
@@ -48,11 +50,12 @@ module.exports = {
48
50
  if (event.chatMessage) {
49
51
  if (!userChatMessage) {
50
52
  userChatMessage = event.chatMessage;
51
- } else if (!assistantChatMessage) {
53
+ this.chatMessages.splice(userChatMessageIndex, 1, userChatMessage);
54
+ } else {
52
55
  const assistantChatMessageIndex = this.chatMessages.indexOf(assistantChatMessage);
53
56
  assistantChatMessage = event.chatMessage;
54
57
  if (assistantChatMessageIndex !== -1) {
55
- this.chatMessages[assistantChatMessageIndex] = assistantChatMessage;
58
+ this.chatMessages.splice(assistantChatMessageIndex, 1, assistantChatMessage);
56
59
  } else {
57
60
  this.chatMessages.push(assistantChatMessage);
58
61
  }
@@ -66,6 +69,7 @@ module.exports = {
66
69
  } else if (event.textPart) {
67
70
  if (!assistantChatMessage) {
68
71
  assistantChatMessage = {
72
+ _id: Math.random().toString(36).substr(2, 9),
69
73
  content: event.textPart,
70
74
  role: 'assistant'
71
75
  };
@@ -1,6 +1,41 @@
1
1
  <div>
2
+ <div class="mb-4">
3
+ <label class="block text-sm font-bold text-gray-900">AI Mode</label>
4
+ <div class="mt-2 flex flex-col gap-2 sm:flex-row sm:items-center">
5
+ <input
6
+ v-model="aiPrompt"
7
+ type="text"
8
+ placeholder="Describe the document you'd like to create..."
9
+ @keydown.enter.prevent="requestAiSuggestion()"
10
+ class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-ultramarine-500 focus:outline-none focus:ring-1 focus:ring-ultramarine-500"
11
+ />
12
+ <button
13
+ @click="requestAiSuggestion()"
14
+ :disabled="aiStreaming || !aiPrompt.trim()"
15
+ class="inline-flex items-center justify-center rounded-md bg-ultramarine-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 disabled:cursor-not-allowed disabled:opacity-50"
16
+ >
17
+ {{ aiStreaming ? 'Generating...' : 'Generate' }}
18
+ </button>
19
+ </div>
20
+ <p class="mt-2 text-xs text-gray-500">Use AI to draft the document. You can accept or reject the suggestion once it finishes.</p>
21
+ <div v-if="aiSuggestionReady" class="mt-3 flex flex-wrap gap-2">
22
+ <button
23
+ @click="acceptAiSuggestion()"
24
+ class="rounded-md bg-emerald-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-emerald-500"
25
+ >
26
+ Accept suggestion
27
+ </button>
28
+ <button
29
+ @click="rejectAiSuggestion()"
30
+ class="rounded-md bg-gray-100 px-2.5 py-1.5 text-sm font-semibold text-gray-700 shadow-sm hover:bg-gray-200"
31
+ >
32
+ Reject suggestion
33
+ </button>
34
+ </div>
35
+ </div>
2
36
  <div class="mb-2">
3
- <textarea class="border border-gray-200 p-2 h-[300px] w-full" ref="codeEditor"></textarea>
37
+ <label class="block text-sm font-bold text-gray-900">Document to Create</label>
38
+ <textarea class="border border-gray-200 p-2 h-[300px] w-full mt-2" ref="codeEditor"></textarea>
4
39
  </div>
5
40
  <button @click="createDocument()" class="rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">Submit</button>
6
41
  <div v-if="errors.length > 0" class="rounded-md bg-red-50 p-4 mt-1">
@@ -23,10 +23,60 @@ module.exports = app => app.component('create-document', {
23
23
  return {
24
24
  documentData: '',
25
25
  editor: null,
26
- errors: []
26
+ errors: [],
27
+ aiPrompt: '',
28
+ aiSuggestion: '',
29
+ aiOriginalDocument: '',
30
+ aiStreaming: false,
31
+ aiSuggestionReady: false
27
32
  };
28
33
  },
29
34
  methods: {
35
+ async requestAiSuggestion() {
36
+ if (this.aiStreaming) {
37
+ return;
38
+ }
39
+ const prompt = this.aiPrompt.trim();
40
+ if (!prompt) {
41
+ return;
42
+ }
43
+
44
+ this.aiOriginalDocument = this.editor.getValue();
45
+ this.aiSuggestion = '';
46
+ this.aiSuggestionReady = false;
47
+ this.aiStreaming = true;
48
+
49
+ try {
50
+ for await (const event of api.Model.streamChatMessage({
51
+ model: this.currentModel,
52
+ content: prompt,
53
+ documentData: this.aiOriginalDocument
54
+ })) {
55
+ if (event?.textPart) {
56
+ this.aiSuggestion += event.textPart;
57
+ this.editor.setValue(this.aiSuggestion);
58
+ }
59
+ }
60
+ this.aiSuggestionReady = true;
61
+ } catch (err) {
62
+ this.editor.setValue(this.aiOriginalDocument);
63
+ this.$toast.error('Failed to generate a document suggestion.');
64
+ throw err;
65
+ } finally {
66
+ this.aiStreaming = false;
67
+ }
68
+ },
69
+ acceptAiSuggestion() {
70
+ this.aiSuggestionReady = false;
71
+ this.aiSuggestion = '';
72
+ this.aiOriginalDocument = '';
73
+ },
74
+ rejectAiSuggestion() {
75
+ this.editor.setValue(this.aiOriginalDocument);
76
+ this.aiSuggestionReady = false;
77
+ this.aiSuggestion = '';
78
+ this.aiOriginalDocument = '';
79
+ },
30
80
  async createDocument() {
31
81
  const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
32
82
  try {
@@ -1,3 +1,16 @@
1
1
  <div class="w-full">
2
- <pre class="w-full whitespace-pre-wrap break-words font-mono text-sm text-gray-700 m-0">{{displayValue}}</pre>
3
- </div>
2
+ <pre v-if="!isGeoJsonGeometry || !mapVisible" class="w-full whitespace-pre-wrap break-words font-mono text-sm text-gray-700 m-0">{{displayValue}}</pre>
3
+ <div v-show="isGeoJsonGeometry && mapVisible" class="mt-2 border border-gray-200 rounded relative">
4
+ <div ref="map" class="h-64 w-full" style="min-height: 256px; height: 256px;"></div>
5
+ <!-- Undo button below map -->
6
+ <div v-if="isEditable && canUndo" class="mt-2 flex justify-end">
7
+ <button
8
+ @click="undoDelete"
9
+ class="text-xs px-3 py-1.5 bg-gray-600 text-white rounded hover:bg-gray-700 transition-colors"
10
+ title="Undo all changes"
11
+ >
12
+ Undo
13
+ </button>
14
+ </div>
15
+ </div>
16
+ </div>