@mongoosejs/studio 0.0.73 → 0.0.75

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.
@@ -614,6 +614,14 @@ video {
614
614
  right: 0px;
615
615
  }
616
616
 
617
+ .right-1 {
618
+ right: 0.25rem;
619
+ }
620
+
621
+ .top-1 {
622
+ top: 0.25rem;
623
+ }
624
+
617
625
  .top-\[90\%\] {
618
626
  top: 90%;
619
627
  }
@@ -648,6 +656,11 @@ video {
648
656
  margin-bottom: -0.5rem;
649
657
  }
650
658
 
659
+ .mx-4 {
660
+ margin-left: 1rem;
661
+ margin-right: 1rem;
662
+ }
663
+
651
664
  .mx-auto {
652
665
  margin-left: auto;
653
666
  margin-right: auto;
@@ -694,6 +707,10 @@ video {
694
707
  margin-left: 0.75rem;
695
708
  }
696
709
 
710
+ .ml-auto {
711
+ margin-left: auto;
712
+ }
713
+
697
714
  .mr-1 {
698
715
  margin-right: 0.25rem;
699
716
  }
@@ -785,10 +802,18 @@ video {
785
802
  height: 2rem;
786
803
  }
787
804
 
805
+ .\!h-\[90vh\] {
806
+ height: 90vh !important;
807
+ }
808
+
788
809
  .h-16 {
789
810
  height: 4rem;
790
811
  }
791
812
 
813
+ .h-3 {
814
+ height: 0.75rem;
815
+ }
816
+
792
817
  .h-4 {
793
818
  height: 1rem;
794
819
  }
@@ -825,14 +850,30 @@ video {
825
850
  height: 100%;
826
851
  }
827
852
 
853
+ .h-px {
854
+ height: 1px;
855
+ }
856
+
857
+ .max-h-\[30vh\] {
858
+ max-height: 30vh;
859
+ }
860
+
828
861
  .max-h-\[50vh\] {
829
862
  max-height: 50vh;
830
863
  }
831
864
 
865
+ .\!w-\[90vw\] {
866
+ width: 90vw !important;
867
+ }
868
+
832
869
  .w-16 {
833
870
  width: 4rem;
834
871
  }
835
872
 
873
+ .w-3 {
874
+ width: 0.75rem;
875
+ }
876
+
836
877
  .w-4 {
837
878
  width: 1rem;
838
879
  }
@@ -873,6 +914,14 @@ video {
873
914
  max-width: 64rem;
874
915
  }
875
916
 
917
+ .max-w-\[calc\(100\%-6\.5rem\)\] {
918
+ max-width: calc(100% - 6.5rem);
919
+ }
920
+
921
+ .max-w-xs {
922
+ max-width: 20rem;
923
+ }
924
+
876
925
  .flex-1 {
877
926
  flex: 1 1 0%;
878
927
  }
@@ -909,6 +958,10 @@ video {
909
958
  transform-origin: top right;
910
959
  }
911
960
 
961
+ .transform {
962
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
963
+ }
964
+
912
965
  .cursor-not-allowed {
913
966
  cursor: not-allowed;
914
967
  }
@@ -943,10 +996,18 @@ video {
943
996
  flex-direction: column;
944
997
  }
945
998
 
999
+ .items-start {
1000
+ align-items: flex-start;
1001
+ }
1002
+
946
1003
  .items-center {
947
1004
  align-items: center;
948
1005
  }
949
1006
 
1007
+ .justify-end {
1008
+ justify-content: flex-end;
1009
+ }
1010
+
950
1011
  .justify-center {
951
1012
  justify-content: center;
952
1013
  }
@@ -1003,6 +1064,12 @@ video {
1003
1064
  margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
1004
1065
  }
1005
1066
 
1067
+ .space-x-3 > :not([hidden]) ~ :not([hidden]) {
1068
+ --tw-space-x-reverse: 0;
1069
+ margin-right: calc(0.75rem * var(--tw-space-x-reverse));
1070
+ margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
1071
+ }
1072
+
1006
1073
  .space-y-1 > :not([hidden]) ~ :not([hidden]) {
1007
1074
  --tw-space-y-reverse: 0;
1008
1075
  margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
@@ -1052,10 +1119,18 @@ video {
1052
1119
  overflow: auto;
1053
1120
  }
1054
1121
 
1122
+ .overflow-hidden {
1123
+ overflow: hidden;
1124
+ }
1125
+
1055
1126
  .overflow-x-auto {
1056
1127
  overflow-x: auto;
1057
1128
  }
1058
1129
 
1130
+ .overflow-y-auto {
1131
+ overflow-y: auto;
1132
+ }
1133
+
1059
1134
  .truncate {
1060
1135
  overflow: hidden;
1061
1136
  text-overflow: ellipsis;
@@ -1066,6 +1141,10 @@ video {
1066
1141
  white-space: nowrap;
1067
1142
  }
1068
1143
 
1144
+ .whitespace-pre-wrap {
1145
+ white-space: pre-wrap;
1146
+ }
1147
+
1069
1148
  .rounded {
1070
1149
  border-radius: 0.25rem;
1071
1150
  }
@@ -1120,6 +1199,14 @@ video {
1120
1199
  border-right-width: 1px;
1121
1200
  }
1122
1201
 
1202
+ .border-t {
1203
+ border-top-width: 1px;
1204
+ }
1205
+
1206
+ .border-none {
1207
+ border-style: none;
1208
+ }
1209
+
1123
1210
  .border-gray-100 {
1124
1211
  --tw-border-opacity: 1;
1125
1212
  border-color: rgb(243 244 246 / var(--tw-border-opacity));
@@ -1149,6 +1236,11 @@ video {
1149
1236
  background-color: rgb(59 130 246 / var(--tw-bg-opacity));
1150
1237
  }
1151
1238
 
1239
+ .bg-blue-600 {
1240
+ --tw-bg-opacity: 1;
1241
+ background-color: rgb(37 99 235 / var(--tw-bg-opacity));
1242
+ }
1243
+
1152
1244
  .bg-forest-green-600 {
1153
1245
  --tw-bg-opacity: 1;
1154
1246
  background-color: rgb(0 202 44 / var(--tw-bg-opacity));
@@ -1164,6 +1256,11 @@ video {
1164
1256
  background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1165
1257
  }
1166
1258
 
1259
+ .bg-gray-300 {
1260
+ --tw-bg-opacity: 1;
1261
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1262
+ }
1263
+
1167
1264
  .bg-gray-400 {
1168
1265
  --tw-bg-opacity: 1;
1169
1266
  background-color: rgb(156 163 175 / var(--tw-bg-opacity));
@@ -1189,11 +1286,21 @@ video {
1189
1286
  background-color: rgb(31 41 55 / var(--tw-bg-opacity));
1190
1287
  }
1191
1288
 
1289
+ .bg-green-300 {
1290
+ --tw-bg-opacity: 1;
1291
+ background-color: rgb(134 239 172 / var(--tw-bg-opacity));
1292
+ }
1293
+
1192
1294
  .bg-green-50 {
1193
1295
  --tw-bg-opacity: 1;
1194
1296
  background-color: rgb(240 253 244 / var(--tw-bg-opacity));
1195
1297
  }
1196
1298
 
1299
+ .bg-green-500 {
1300
+ --tw-bg-opacity: 1;
1301
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
1302
+ }
1303
+
1197
1304
  .bg-green-600 {
1198
1305
  --tw-bg-opacity: 1;
1199
1306
  background-color: rgb(22 163 74 / var(--tw-bg-opacity));
@@ -1281,6 +1388,10 @@ video {
1281
1388
  padding: 0.5rem;
1282
1389
  }
1283
1390
 
1391
+ .p-3 {
1392
+ padding: 0.75rem;
1393
+ }
1394
+
1284
1395
  .p-4 {
1285
1396
  padding: 1rem;
1286
1397
  }
@@ -1398,6 +1509,10 @@ video {
1398
1509
  padding-top: 0.25rem;
1399
1510
  }
1400
1511
 
1512
+ .pt-\[1px\] {
1513
+ padding-top: 1px;
1514
+ }
1515
+
1401
1516
  .text-left {
1402
1517
  text-align: left;
1403
1518
  }
@@ -1644,6 +1759,12 @@ video {
1644
1759
  transition-duration: 150ms;
1645
1760
  }
1646
1761
 
1762
+ .transition-colors {
1763
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
1764
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1765
+ transition-duration: 150ms;
1766
+ }
1767
+
1647
1768
  .placeholder\:text-gray-400::-moz-placeholder {
1648
1769
  --tw-text-opacity: 1;
1649
1770
  color: rgb(156 163 175 / var(--tw-text-opacity));
@@ -1679,11 +1800,21 @@ video {
1679
1800
  background-color: rgb(37 99 235 / var(--tw-bg-opacity));
1680
1801
  }
1681
1802
 
1803
+ .hover\:bg-blue-700:hover {
1804
+ --tw-bg-opacity: 1;
1805
+ background-color: rgb(29 78 216 / var(--tw-bg-opacity));
1806
+ }
1807
+
1682
1808
  .hover\:bg-forest-green-500:hover {
1683
1809
  --tw-bg-opacity: 1;
1684
1810
  background-color: rgb(0 242 58 / var(--tw-bg-opacity));
1685
1811
  }
1686
1812
 
1813
+ .hover\:bg-gray-200:hover {
1814
+ --tw-bg-opacity: 1;
1815
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1816
+ }
1817
+
1687
1818
  .hover\:bg-gray-300:hover {
1688
1819
  --tw-bg-opacity: 1;
1689
1820
  background-color: rgb(209 213 219 / var(--tw-bg-opacity));
@@ -1704,11 +1835,21 @@ video {
1704
1835
  background-color: rgb(75 85 99 / var(--tw-bg-opacity));
1705
1836
  }
1706
1837
 
1838
+ .hover\:bg-green-300:hover {
1839
+ --tw-bg-opacity: 1;
1840
+ background-color: rgb(134 239 172 / var(--tw-bg-opacity));
1841
+ }
1842
+
1707
1843
  .hover\:bg-green-500:hover {
1708
1844
  --tw-bg-opacity: 1;
1709
1845
  background-color: rgb(34 197 94 / var(--tw-bg-opacity));
1710
1846
  }
1711
1847
 
1848
+ .hover\:bg-green-600:hover {
1849
+ --tw-bg-opacity: 1;
1850
+ background-color: rgb(22 163 74 / var(--tw-bg-opacity));
1851
+ }
1852
+
1712
1853
  .hover\:bg-red-600:hover {
1713
1854
  --tw-bg-opacity: 1;
1714
1855
  background-color: rgb(220 38 38 / var(--tw-bg-opacity));
@@ -1926,6 +2067,11 @@ video {
1926
2067
  background-color: rgb(107 114 128 / var(--tw-bg-opacity));
1927
2068
  }
1928
2069
 
2070
+ .disabled\:bg-gray-600:disabled {
2071
+ --tw-bg-opacity: 1;
2072
+ background-color: rgb(75 85 99 / var(--tw-bg-opacity));
2073
+ }
2074
+
1929
2075
  .disabled\:text-gray-300:disabled {
1930
2076
  --tw-text-opacity: 1;
1931
2077
  color: rgb(209 213 219 / var(--tw-text-opacity));
@@ -46,6 +46,25 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
46
46
  return client.post('', { action: 'Dashboard.updateDashboard', ...params}).then(res => res.data);
47
47
  }
48
48
  }
49
+ exports.ChatThread = {
50
+ createChatMessage(params) {
51
+ return client.post('', { action: 'ChatThread.createChatMessage', ...params }).then(res => res.data);
52
+ },
53
+ createChatThread(params) {
54
+ return client.post('', { action: 'ChatThread.createChatThread', ...params }).then(res => res.data);
55
+ },
56
+ getChatThread(params) {
57
+ return client.post('', { action: 'ChatThread.getChatThread', ...params }).then(res => res.data);
58
+ },
59
+ listChatThreads(params) {
60
+ return client.post('', { action: 'ChatThread.listChatThreads', ...params }).then(res => res.data);
61
+ }
62
+ }
63
+ exports.ChatMessage = {
64
+ executeScript(params) {
65
+ return client.post('', { action: 'ChatMessage.executeScript', ...params }).then(res => res.data);
66
+ }
67
+ }
49
68
  exports.Model = {
50
69
  createChart(params) {
51
70
  return client.post('', { action: 'Model.createChart', ...params}).then(res => res.data);
@@ -117,6 +136,25 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
117
136
  return client.post('/Dashboard/updateDashboard', params).then(res => res.data);
118
137
  }
119
138
  }
139
+ exports.ChatThread = {
140
+ createChatMessage: function createChatMessage(params) {
141
+ return client.post('/ChatThread/createChatMessage', params).then(res => res.data);
142
+ },
143
+ createChatThread: function createChatThread(params) {
144
+ return client.post('/ChatThread/createChatThread', params).then(res => res.data);
145
+ },
146
+ getChatThread: function getChatThread(params) {
147
+ return client.post('/ChatThread/getChatThread', params).then(res => res.data);
148
+ },
149
+ listChatThreads: function listChatThreads(params) {
150
+ return client.post('/ChatThread/listChatThreads', params).then(res => res.data);
151
+ }
152
+ };
153
+ exports.ChatMessage = {
154
+ executeScript: function executeScript(params) {
155
+ return client.post('/ChatMessage/executeScript', params).then(res => res.data);
156
+ }
157
+ };
120
158
  exports.Model = {
121
159
  createChart: function (params) {
122
160
  return client.post('/Model/createChart', params).then(res => res.data);
@@ -170,7 +208,4 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
170
208
  return client.post('/Model/updateDocument', params).then(res => res.data);
171
209
  }
172
210
  };
173
- exports.Script = {
174
-
175
- };
176
211
  }
@@ -1,3 +1,13 @@
1
1
  <button v-bind="attrsToBind" :disabled="isDisabled" @click="handleClick">
2
- <slot></slot>
3
- </button>
2
+ <div v-if="status === 'in_progress'" style="text-align: center">
3
+ <svg style="height: 1em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
4
+ <g>
5
+ <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity="0.3" />
6
+ <path d="M12 2a10 10 0 0 1 10 10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
7
+ <animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite" />
8
+ </path>
9
+ </g>
10
+ </svg>
11
+ </div>
12
+ <slot v-if="status == 'success' || status == 'init'"></slot>
13
+ </button>
@@ -0,0 +1,16 @@
1
+ <div class="relative flex items-start space-x-3" :class="{'justify-end': message.role === 'user'}">
2
+ <div
3
+ class="min-w-0 max-w-[calc(100%-6.5rem)]"
4
+ :class="{'text-right': message.role === 'user'}">
5
+
6
+ <div class="text-sm text-gray-900 p-3 rounded-md inline-block" :class="styleForMessage">
7
+ <div v-for="part in contentSplitByScripts">
8
+ <div v-if="part.type === 'text'" v-html="marked(part.content)">
9
+ </div>
10
+ <div v-else-if="part.type === 'code'">
11
+ <chat-message-script :message="message" :script="part.content" :language="part.language"></chat-message-script>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </div>
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ const api = require('../../api');
4
+ const marked = require('marked').marked;
5
+ const template = require('./chat-message.html');
6
+
7
+ module.exports = app => app.component('chat-message', {
8
+ template: template,
9
+ props: ['message'],
10
+ computed: {
11
+ styleForMessage() {
12
+ return this.message.role === 'user' ? 'bg-gray-100' : '';
13
+ },
14
+ contentSplitByScripts() {
15
+ const content = this.message.content;
16
+ const parts = [];
17
+ let currentIndex = 0;
18
+ let codeBlockMatch;
19
+
20
+ // Regular expression to match markdown code blocks
21
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)\n```/g;
22
+
23
+ while ((codeBlockMatch = codeBlockRegex.exec(content)) !== null) {
24
+ // Add text before the code block
25
+ if (codeBlockMatch.index > currentIndex) {
26
+ parts.push({
27
+ type: 'text',
28
+ content: content.substring(currentIndex, codeBlockMatch.index)
29
+ });
30
+ }
31
+
32
+ // Add the code block
33
+ parts.push({
34
+ type: 'code',
35
+ language: codeBlockMatch[1] || '',
36
+ content: codeBlockMatch[2]
37
+ });
38
+
39
+ currentIndex = codeBlockMatch.index + codeBlockMatch[0].length;
40
+ }
41
+
42
+ // Add any remaining text after the last code block
43
+ if (currentIndex < content.length) {
44
+ parts.push({
45
+ type: 'text',
46
+ content: content.substring(currentIndex)
47
+ });
48
+ }
49
+
50
+ return parts;
51
+ }
52
+ },
53
+ methods: {
54
+ marked(text) {
55
+ return marked(text);
56
+ },
57
+ async executeScript(message, script) {
58
+ const { chatMessage } = await api.ChatMessage.executeScript({
59
+ chatMessageId: message._id,
60
+ script
61
+ });
62
+ message.executionResult = chatMessage.executionResult;
63
+ console.log(message);
64
+ },
65
+ }
66
+ });
@@ -0,0 +1,52 @@
1
+ <div class="relative border rounded bg-gray-100 text-black text-sm overflow-hidden">
2
+ <div class="flex border-b pt-[1px] text-xs font-medium bg-gray-200">
3
+ <button
4
+ class="px-3 py-1 border-r border-gray-300 hover:bg-green-300"
5
+ :class="{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}"
6
+ @click="activeTab = 'code'">
7
+ Code
8
+ </button>
9
+ <button
10
+ class="px-3 py-1 hover:bg-green-300"
11
+ :class="{'bg-green-300': activeTab === 'output'}"
12
+ @click="activeTab = 'output'">
13
+ Output
14
+ </button>
15
+ <div class="ml-auto mr-1 flex">
16
+ <button
17
+ v-if="activeTab === 'output'"
18
+ class="px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center"
19
+ @click="openDetailModal">
20
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
21
+ <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" />
22
+ </svg>
23
+ </button>
24
+ <async-button
25
+ class="px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400"
26
+ @click="executeScript(message, script)">
27
+ Execute
28
+ </async-button>
29
+ </div>
30
+ </div>
31
+
32
+ <pre class="p-3 whitespace-pre-wrap max-h-[30vh] overflow-y-auto" v-show="activeTab === 'code'"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
33
+
34
+ <pre class="p-3 whitespace-pre-wrap max-h-[30vh] overflow-y-auto bg-white border-t" v-show="activeTab === 'output'">
35
+ <dashboard-chart v-if="message.executionResult?.output?.$chart" :value="message.executionResult?.output" />
36
+ <div v-else>
37
+ {{ message.executionResult?.output ? JSON.stringify(message.executionResult.output, null, 2) : 'No output' }}
38
+ </div>
39
+ </pre>
40
+
41
+ <modal ref="outputModal" v-if="showDetailModal" containerClass="!h-[90vh] !w-[90vw]">
42
+ <template #body>
43
+ <div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="showDetailModal = false;">&times;</div>
44
+ <div class="h-full overflow-auto">
45
+ <dashboard-chart v-if="message.executionResult?.output?.$chart" :value="message.executionResult?.output" />
46
+ <pre v-else class="whitespace-pre-wrap">
47
+ {{ message.executionResult?.output ? JSON.stringify(message.executionResult.output, null, 2) : 'No output' }}
48
+ </pre>
49
+ </div>
50
+ </template>
51
+ </modal>
52
+ </div>
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const api = require('../../api');
4
+ const marked = require('marked').marked;
5
+ const template = require('./chat-message-script.html');
6
+
7
+ module.exports = app => app.component('chat-message-script', {
8
+ template: template,
9
+ props: ['message', 'script', 'language'],
10
+ data: () => ({ activeTab: 'code', showDetailModal: false }),
11
+ computed: {
12
+ styleForMessage() {
13
+ return this.message.role === 'user' ? 'bg-gray-100' : '';
14
+ }
15
+ },
16
+ methods: {
17
+ async executeScript(message, script) {
18
+ const { chatMessage } = await api.ChatMessage.executeScript({
19
+ chatMessageId: message._id,
20
+ script
21
+ });
22
+ message.executionResult = chatMessage.executionResult;
23
+ this.activeTab = 'output';
24
+ },
25
+ openDetailModal() {
26
+ this.showDetailModal = true;
27
+ }
28
+ },
29
+ mounted() {
30
+ Prism.highlightElement(this.$refs.code);
31
+ if (this.message.executionResult?.output) {
32
+ this.activeTab = 'output';
33
+ }
34
+ }
35
+ });
@@ -0,0 +1,69 @@
1
+ <div class="flex" style="height: calc(100vh - 55px)">
2
+ <!-- Sidebar: Chat Threads -->
3
+ <aside class="w-64 bg-gray-100 border-r overflow-y-auto h-full">
4
+ <div class="p-4 font-bold text-lg border-b">Chat Threads</div>
5
+ <div class="p-4">
6
+ <button
7
+ @click="createNewThread"
8
+ class="w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
9
+ >
10
+ Create New Thread
11
+ </button>
12
+ </div>
13
+ <div v-if="status === 'loaded' && chatThreads.length === 0" class="p-4 text-sm text-gray-700">
14
+ No threads yet
15
+ </div>
16
+ <ul vif="status === 'loaded'">
17
+ <li
18
+ v-for="thread in chatThreads"
19
+ :key="thread._id"
20
+ @click="selectThread(thread._id)"
21
+ class="p-4 hover:bg-gray-200 cursor-pointer"
22
+ :class="{ 'bg-gray-300': thread._id === chatThreadId }"
23
+ >
24
+ {{ thread.title || 'Untitled Thread' }}
25
+ </li>
26
+ </ul>
27
+ </aside>
28
+
29
+ <!-- Main Chat Area -->
30
+ <main class="flex-1 flex flex-col">
31
+ <div class="flex-1 overflow-y-auto p-6 space-y-4" ref="messagesContainer">
32
+ <ul role="list" class="space-y-4">
33
+ <div v-if="true">
34
+ <div class="flex items-center justify-center py-3 mb-4">
35
+ <div class="bg-gray-300 h-px flex-grow max-w-xs"></div>
36
+ <p class="mx-4 text-sm font-medium text-gray-500">This is the beginning of the message thread</p>
37
+ <div class="bg-gray-300 h-px flex-grow max-w-xs"></div>
38
+ </div>
39
+ </div>
40
+ <li v-for="message in chatMessages" :key="message._id">
41
+ <chat-message :message="message"></chat-message>
42
+ </li>
43
+ </ul>
44
+ </div>
45
+
46
+
47
+ <!-- Input Area -->
48
+ <div class="border-t p-4">
49
+ <form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2">
50
+ <input
51
+ v-model="newMessage"
52
+ placeholder="Ask something..."
53
+ class="flex-1 border rounded px-4 py-2"
54
+ />
55
+ <button class="bg-blue-600 text-white px-4 py-2 rounded disabled:bg-gray-600" :disabled="sendingMessage">
56
+ <svg v-if="sendingMessage" style="height: 1em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
57
+ <g>
58
+ <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity="0.3" />
59
+ <path d="M12 2a10 10 0 0 1 10 10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
60
+ <animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite" />
61
+ </path>
62
+ </g>
63
+ </svg>
64
+ <span v-else>Send</span>
65
+ </button>
66
+ </form>
67
+ </div>
68
+ </main>
69
+ </div>