@mongoosejs/studio 0.0.114 → 0.0.116

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.
@@ -22,6 +22,7 @@ const ExecuteScriptParams = new Archetype({
22
22
 
23
23
  module.exports = ({ db, studioConnection }) => async function executeScript(params) {
24
24
  const { initiatedById, chatMessageId, script, roles } = new ExecuteScriptParams(params);
25
+ const ChatThread = studioConnection.model('__Studio_ChatThread');
25
26
  const ChatMessage = studioConnection.model('__Studio_ChatMessage');
26
27
 
27
28
  await authorize('ChatMessage.executeScript', roles);
@@ -30,8 +31,9 @@ module.exports = ({ db, studioConnection }) => async function executeScript(para
30
31
  if (!chatMessage) {
31
32
  throw new Error('Chat message not found');
32
33
  }
34
+ const chatThread = await ChatThread.findById(chatMessage.chatThreadId).orFail();
33
35
 
34
- if (initiatedById && chatMessage.userId?.toString() !== initiatedById.toString()) {
36
+ if (initiatedById && chatThread.userId?.toString() !== initiatedById.toString()) {
35
37
  throw new Error('Unauthorized');
36
38
  }
37
39
 
@@ -336,6 +336,7 @@ const vanillatoasts = __webpack_require__(/*! vanillatoasts */ "./node_modules/v
336
336
  module.exports = app => app.component('chat-message-script', {
337
337
  template,
338
338
  props: ['message', 'script', 'language'],
339
+ emits: ['copyMessage'],
339
340
  data() {
340
341
  return {
341
342
  activeTab: 'code',
@@ -411,9 +412,13 @@ module.exports = app => app.component('chat-message-script', {
411
412
  this.$router.push('/dashboard/' + dashboard._id);
412
413
  },
413
414
  async copyOutput() {
414
- await navigator.clipboard.writeText(this.message.executionResult.output);
415
+ let output = this.message.executionResult.output;
416
+ if (output != null && typeof output === 'object') {
417
+ output = JSON.stringify(output, null, 2);
418
+ }
419
+ await navigator.clipboard.writeText(output);
415
420
  vanillatoasts.create({
416
- title: 'Text copied!',
421
+ title: 'Code output copied!',
417
422
  type: 'success',
418
423
  timeout: 3000,
419
424
  icon: 'images/success.png',
@@ -457,6 +462,7 @@ module.exports = app => app.component('chat-message-script', {
457
462
 
458
463
  const api = __webpack_require__(/*! ../../api */ "./frontend/src/api.js");
459
464
  const marked = (__webpack_require__(/*! marked */ "./node_modules/marked/lib/marked.cjs").marked);
465
+ const vanillatoasts = __webpack_require__(/*! vanillatoasts */ "./node_modules/vanillatoasts/vanillatoasts.js");
460
466
  const template = __webpack_require__(/*! ./chat-message.html */ "./frontend/src/chat/chat-message/chat-message.html");
461
467
 
462
468
  module.exports = app => app.component('chat-message', {
@@ -464,7 +470,7 @@ module.exports = app => app.component('chat-message', {
464
470
  props: ['message'],
465
471
  computed: {
466
472
  styleForMessage() {
467
- return this.message.role === 'user' ? 'bg-gray-100' : '';
473
+ return this.message.role === 'user' ? 'p-3 bg-gray-100' : 'py-3 pr-3';
468
474
  },
469
475
  contentSplitByScripts() {
470
476
  const content = this.message.content;
@@ -516,6 +522,37 @@ module.exports = app => app.component('chat-message', {
516
522
  });
517
523
  message.executionResult = chatMessage.executionResult;
518
524
  console.log(message);
525
+ },
526
+ async copyMessage() {
527
+ const parts = this.contentSplitByScripts;
528
+ let output = '';
529
+ for (const part of parts) {
530
+ if (part.type === 'text') {
531
+ output += part.content + '\n';
532
+ } else if (part.type === 'code') {
533
+ let result = this.message.executionResult?.output;
534
+ if (result != null && typeof result === 'object') {
535
+ result = JSON.stringify(result, null, 2);
536
+ }
537
+ if (result) {
538
+ let executionOutput = this.message.executionResult?.output;
539
+ if (executionOutput != null && typeof executionOutput === 'object') {
540
+ executionOutput = JSON.stringify(executionOutput, null, 2);
541
+ }
542
+ if (executionOutput) {
543
+ output += '```\n' + executionOutput + '\n```\n';
544
+ }
545
+ }
546
+ }
547
+ }
548
+ await navigator.clipboard.writeText(output.trim());
549
+ vanillatoasts.create({
550
+ title: 'Message output copied!',
551
+ type: 'success',
552
+ timeout: 3000,
553
+ icon: 'images/success.png',
554
+ positionClass: 'bottomRight'
555
+ });
519
556
  }
520
557
  }
521
558
  });
@@ -553,6 +590,8 @@ module.exports = app => app.component('chat', {
553
590
  async sendMessage() {
554
591
  this.sendingMessage = true;
555
592
  try {
593
+ const content = this.newMessage;
594
+ this.newMessage = '';
556
595
  if (!this.chatThreadId) {
557
596
  const { chatThread } = await api.ChatThread.createChatThread();
558
597
  this.chatThreads.unshift(chatThread);
@@ -561,7 +600,7 @@ module.exports = app => app.component('chat', {
561
600
  }
562
601
 
563
602
  this.chatMessages.push({
564
- content: this.newMessage,
603
+ content,
565
604
  role: 'user'
566
605
  });
567
606
 
@@ -573,7 +612,7 @@ module.exports = app => app.component('chat', {
573
612
 
574
613
  const { chatMessages, chatThread } = await api.ChatThread.createChatMessage({
575
614
  chatThreadId: this.chatThreadId,
576
- content: this.newMessage
615
+ content
577
616
  });
578
617
  this.chatMessages.push(chatMessages[1]);
579
618
  for (const thread of this.chatThreads) {
@@ -904,9 +943,11 @@ const template = __webpack_require__(/*! ./dashboard-chart.html */ "./frontend/s
904
943
 
905
944
  module.exports = app => app.component('dashboard-chart', {
906
945
  template: template,
907
- props: ['value', 'responsive'],
946
+ props: ['value', 'fullscreen'],
947
+ emits: ['fullscreen'],
908
948
  data: () => ({
909
- chart: null
949
+ chart: null,
950
+ showDetailModal: false
910
951
  }),
911
952
  mounted() {
912
953
  const ctx = this.$refs.chart.getContext('2d');
@@ -1074,7 +1115,8 @@ const template = __webpack_require__(/*! ./dashboard-result.html */ "./frontend/
1074
1115
 
1075
1116
  module.exports = app => app.component('dashboard-result', {
1076
1117
  template: template,
1077
- props: ['result', 'finishedEvaluatingAt'],
1118
+ props: ['result', 'finishedEvaluatingAt', 'fullscreen'],
1119
+ emits: ['fullscreen'],
1078
1120
  mounted: async function() {
1079
1121
  },
1080
1122
  methods: {
@@ -1180,9 +1222,6 @@ module.exports = app => app.component('dashboard', {
1180
1222
  } finally {
1181
1223
  this.status = 'loaded';
1182
1224
  }
1183
- },
1184
- openDetailModal() {
1185
- this.showDetailModal = true;
1186
1225
  }
1187
1226
  },
1188
1227
  computed: {
@@ -2671,13 +2710,13 @@ module.exports = app => app.component('models', {
2671
2710
  this.query.search = this.searchText;
2672
2711
  const query = this.query;
2673
2712
  const newUrl = this.$router.resolve({ query }).href;
2674
- window.history.pushState(null, '', newUrl);
2713
+ this.$router.push({ query });
2675
2714
  } else {
2676
2715
  this.filter = {};
2677
2716
  delete this.query.search;
2678
2717
  const query = this.query;
2679
2718
  const newUrl = this.$router.resolve({ query }).href;
2680
- window.history.pushState(null, '', newUrl);
2719
+ this.$router.push({ query });
2681
2720
  }
2682
2721
  this.documents = [];
2683
2722
  this.status = 'loading';
@@ -4169,7 +4208,7 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
4169
4208
  /***/ ((module) => {
4170
4209
 
4171
4210
  "use strict";
4172
- module.exports = "<div class=\"relative border rounded bg-gray-100 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b pt-[1px] text-xs font-medium bg-gray-200\">\n <button\n class=\"px-3 py-1 border-r border-gray-300 hover:bg-green-300\"\n :class=\"{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-3 py-1 hover:bg-green-300\"\n :class=\"{'bg-green-300': activeTab === 'output'}\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click=\"copyOutput\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n 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\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <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\" />\n </svg>\n </button>\n <async-button\n 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\"\n @click=\"executeScript(message, script)\">\n Execute\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <pre class=\"p-3 whitespace-pre-wrap max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <dashboard-map v-else-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">&times;</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Code</label>\n <div class=\"border border-gray-200\">\n <textarea class=\"p-2 h-[300px] w-full\" ref=\"dashboardCodeEditor\"></textarea>\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createErrors.length > 0\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
4211
+ module.exports = "<div class=\"relative border rounded bg-gray-100 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b pt-[1px] text-xs font-medium bg-gray-200\">\n <button\n class=\"px-3 py-1 border-r border-gray-300 hover:bg-green-300\"\n :class=\"{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-3 py-1 hover:bg-green-300\"\n :class=\"{'bg-green-300': activeTab === 'output'}\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click=\"copyOutput\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n 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\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <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\" />\n </svg>\n </button>\n <async-button\n 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\"\n @click=\"executeScript(message, script)\">\n Execute\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"$emit('copyMessage'); showDropdown = false\">\n Copy Full Message\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <pre class=\"p-3 whitespace-pre-wrap max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <dashboard-map v-else-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">&times;</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Code</label>\n <div class=\"border border-gray-200\">\n <textarea class=\"p-2 h-[300px] w-full\" ref=\"dashboardCodeEditor\"></textarea>\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createErrors.length > 0\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
4173
4212
 
4174
4213
  /***/ }),
4175
4214
 
@@ -4180,7 +4219,7 @@ module.exports = "<div class=\"relative border rounded bg-gray-100 text-black te
4180
4219
  /***/ ((module) => {
4181
4220
 
4182
4221
  "use strict";
4183
- module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-end': message.role === 'user'}\">\n <div\n class=\"min-w-0 max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)]\"\n :class=\"{'text-right': message.role === 'user'}\">\n\n <div class=\"text-sm text-gray-900 md:p-3 rounded-md inline-block\" :class=\"styleForMessage\">\n <div v-for=\"part in contentSplitByScripts\">\n <div v-if=\"part.type === 'text'\" v-html=\"marked(part.content)\">\n </div>\n <div v-else-if=\"part.type === 'code'\">\n <chat-message-script :message=\"message\" :script=\"part.content\" :language=\"part.language\"></chat-message-script>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
4222
+ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-end': message.role === 'user'}\">\n <div\n class=\"min-w-0 max-w-[calc(100vw-3rem)] lg:max-w-[calc(100vw-15rem)]\"\n :class=\"{'text-right': message.role === 'user'}\">\n\n <div class=\"text-sm text-gray-900 rounded-md inline-block relative\" :class=\"styleForMessage\">\n <div v-for=\"part in contentSplitByScripts\">\n <div v-if=\"part.type === 'text'\" v-html=\"marked(part.content)\">\n </div>\n <div v-else-if=\"part.type === 'code'\">\n <chat-message-script :message=\"message\" :script=\"part.content\" :language=\"part.language\" @copyMessage=\"copyMessage\"></chat-message-script>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
4184
4223
 
4185
4224
  /***/ }),
4186
4225
 
@@ -4191,7 +4230,7 @@ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-en
4191
4230
  /***/ ((module) => {
4192
4231
 
4193
4232
  "use strict";
4194
- module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <button\n class=\"fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white\"\n :class=\"hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'\"\n @click=\"toggleShareThread\"\n :disabled=\"!hasWorkspace || !chatThreadId || sharingThread\"\n aria-label=\"Share thread with workspace\"\n title=\"Share thread with workspace\"\n >\n <svg v-if=\"hasWorkspace\" xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" 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>\n <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>\n </button>\n <!-- Sidebar: Chat Threads -->\n <aside class=\"bg-gray-50 border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Chat Threads</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-64\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"p-4 hover:bg-gray-200 cursor-pointer w-64\"\n :class=\"{ 'bg-gray-300': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <ul role=\"list\" class=\"space-y-4\">\n <div v-if=\"true\">\n <div class=\"flex items-center justify-center py-3 mb-4\">\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n <p class=\"mx-4 text-sm font-medium text-gray-500\">This is the beginning of the message thread</p>\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n </div>\n </div>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n placeholder=\"Ask something...\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto\"\n rows=\"1\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
4233
+ module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <button\n class=\"fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white\"\n :class=\"hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'\"\n @click=\"toggleShareThread\"\n :disabled=\"!hasWorkspace || !chatThreadId || sharingThread\"\n aria-label=\"Share thread with workspace\"\n title=\"Share thread with workspace\"\n >\n <svg v-if=\"hasWorkspace\" xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" 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>\n <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>\n </button>\n <!-- Sidebar: Chat Threads -->\n <aside class=\"bg-gray-50 border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Chat Threads</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-64\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"p-4 hover:bg-gray-200 cursor-pointer w-64\"\n :class=\"{ 'bg-gray-300': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <ul role=\"list\" class=\"space-y-4\">\n <div v-if=\"true\">\n <div class=\"flex items-center justify-center py-3 mb-4\">\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n <p class=\"mx-4 text-sm font-medium text-gray-500\">This is the beginning of the message thread</p>\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n </div>\n </div>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n :placeholder=\"sendingMessage ? 'Sending...' : 'Ask something...'\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto\"\n :disabled=\"sendingMessage\"\n rows=\"1\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
4195
4234
 
4196
4235
  /***/ }),
4197
4236
 
@@ -4257,7 +4296,7 @@ module.exports = "<div>\n <div class=\"mb-2\">\n <textarea class=\"border bo
4257
4296
  /***/ ((module) => {
4258
4297
 
4259
4298
  "use strict";
4260
- module.exports = "<div :class=\"responsive ? 'h-full' : ''\">\n <div v-if=\"header\" class=\"border-b border-gray-100 px-2 pb-2 flex items-center\">\n <div class=\"text-xl font-bold\">{{header}}</div>\n <button\n class=\"ml-auto px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors\"\n @click=\"exportPNG\"\n title=\"Export PNG\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em;\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n </button>\n </div>\n <div v-else class=\"border-b border-gray-100 px-2 pb-2 text-right\">\n <button\n class=\"mt-1 px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors\"\n @click=\"exportPNG\"\n title=\"Export PNG\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em;\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n </button>\n </div>\n <div class=\"text-xl\" :class=\"responsive ? 'h-full' : ''\">\n <canvas ref=\"chart\"></canvas>\n </div>\n</div>\n";
4299
+ module.exports = "<div :class=\"responsive ? 'h-full' : ''\">\n <div v-if=\"header && !fullscreen\" class=\"border-b border-gray-100 px-2 pb-2 flex items-center gap-1\">\n <div class=\"text-xl font-bold\">{{header}}</div>\n <button\n class=\"ml-auto px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors\"\n @click=\"exportPNG\"\n title=\"Export PNG\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em;\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n </button>\n <button\n class=\"px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors flex items-center\"\n @click=\"$emit('fullscreen')\"\n aria-label=\"Expand dashboard result\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <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\" />\n </svg>\n </button>\n </div>\n <div v-else-if=\"!fullscreen\" class=\"pt-1 border-b border-gray-100 px-2 pb-2 text-right flex items-center justify-end gap-1 w-full\">\n <button\n class=\"px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors\"\n @click=\"exportPNG\"\n title=\"Export PNG\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em;\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n </button>\n <button\n class=\"px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors flex items-center\"\n @click=\"$emit('fullscreen')\"\n aria-label=\"Expand dashboard result\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.5em\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <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\" />\n </svg>\n </button>\n </div>\n <div :class=\"responsive ? 'relative h-full min-h-0' : ''\">\n <canvas ref=\"chart\" class=\"block w-full h-full\"></canvas>\n </div>\n</div>\n";
4261
4300
 
4262
4301
  /***/ }),
4263
4302
 
@@ -4301,7 +4340,7 @@ module.exports = "<div class=\"py-2\">\n <div v-if=\"header\" class=\"border-b
4301
4340
  /***/ ((module) => {
4302
4341
 
4303
4342
  "use strict";
4304
- module.exports = "<div>\n <div v-if=\"Array.isArray(result)\">\n <div v-for=\"el in result\" :key=\"el._id || el.finishedEvaluatingAt\">\n <component\n class=\"bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl\"\n :is=\"getComponentForValue(el)\"\n :value=\"el\">\n </component>\n </div>\n </div>\n <div v-else>\n <component\n class=\"bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl\"\n :is=\"getComponentForValue(result)\"\n :value=\"result\">\n </component>\n </div>\n <div class=\"text-right text-sm text-gray-700 mt-1\" v-if=\"finishedEvaluatingAt\">\n Last Evaluated: {{ format.isoToLongDateTime(finishedEvaluatingAt) }}\n </div>\n</div>\n";
4343
+ module.exports = "<div>\n <div v-if=\"Array.isArray(result)\">\n <div v-for=\"el in result\" :key=\"el._id || el.finishedEvaluatingAt\">\n <component\n class=\"bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl\"\n :is=\"getComponentForValue(el)\"\n :value=\"el\">\n </component>\n </div>\n </div>\n <div v-else>\n <component\n class=\"bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl\"\n :is=\"getComponentForValue(result)\"\n :value=\"result\"\n :fullscreen=\"fullscreen\"\n @fullscreen=\"$emit('fullscreen')\">\n </component>\n </div>\n <div class=\"text-right text-sm text-gray-700 mt-1\" v-if=\"finishedEvaluatingAt && !fullscreen\">\n Last Evaluated: {{ format.isoToLongDateTime(finishedEvaluatingAt) }}\n </div>\n</div>\n";
4305
4344
 
4306
4345
  /***/ }),
4307
4346
 
@@ -4323,7 +4362,7 @@ module.exports = "<div class=\"py-2\">\n <div v-if=\"header\" class=\"border-b
4323
4362
  /***/ ((module) => {
4324
4363
 
4325
4364
  "use strict";
4326
- module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"status === 'loading'\" class=\"max-w-5xl mx-auto text-center\">\n <img src=\"images/loader.gif\" class=\"inline mt-10\">\n </div>\n <div v-if=\"dashboard && status !== 'loading'\" class=\"max-w-5xl mx-auto\">\n <div class=\"flex items-center w-full\" v-if=\"!showEditor\">\n <h2 class=\"mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink\">{{title}}</h2>\n <div class=\"flex gap-2\">\n <button\n @click=\"showEditor = true\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\">\n <img src=\"images/edit.svg\" class=\"inline h-[1.25em] mr-1\" /> Edit\n </button>\n\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center rounded-md bg-ultramarine-600 px-4 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n <svg class=\"inline h-[1.25em] mr-1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"m670-140 160-100-160-100v200ZM240-600h480v-80H240v80ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40ZM120-80v-680q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v267q-19-9-39-15t-41-9v-243H200v562h243q5 31 15.5 59T486-86l-6 6-60-60-60 60-60-60-60 60-60-60-60 60Zm120-200h203q3-21 9-41t15-39H240v80Zm0-160h284q38-37 88.5-58.5T720-520H240v80Zm-40 242v-562 562Z\"/></svg>\n Evaluate\n </async-button>\n </div>\n </div>\n <div v-if=\"!showEditor\" class=\"mt-4 mb-4\">\n <div v-if=\"dashboardResults.length === 0\">\n <div class=\"flex flex-col items-center justify-center py-8\">\n <p class=\"text-gray-700 text-base mb-4\">This dashboard hasn't been evaluated yet.</p>\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"rounded-md bg-ultramarine-600 px-4 py-2 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n Evaluate Dashboard\n </async-button>\n </div>\n </div>\n <div v-else>\n <div class=\"relative\">\n <button\n class=\"absolute top-2 right-2 px-2 py-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\"\n aria-label=\"Expand dashboard result\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <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\" />\n </svg>\n </button>\n <dashboard-result\n :key=\"dashboardResult.finishedEvaluatingAt\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\">\n </dashboard-result>\n </div>\n </div>\n </div>\n <div v-if=\"showEditor\" class=\"mt-4\">\n <edit-dashboard\n :dashboardId=\"dashboard._id\"\n :code=\"code\"\n :currentDescription=\"description\"\n :currentTitle=\"title\"\n @close=\"showEditor=false;\"\n @update=\"updateCode\"></edit-dashboard>\n </div>\n <div v-if=\"errorMessage\" class=\"rounded-md bg-red-50 p-4 mt-4\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">{{errorMessage}}</h3>\n </div>\n </div>\n </div>\n\n </div>\n <div v-if=\"!dashboard && status !== 'loading'\">\n No dashboard with the given id could be found.\n </div>\n</div>\n\n<modal\n v-if=\"showDetailModal\"\n containerClass=\"!h-[90vh] !w-[90vw]\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Dashboard Details\"\n>\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\" role=\"button\" aria-label=\"Close modal\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-result\n v-if=\"dashboardResult\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\">\n </dashboard-result>\n </div>\n </template>\n</modal>\n";
4365
+ module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"status === 'loading'\" class=\"max-w-5xl mx-auto text-center\">\n <img src=\"images/loader.gif\" class=\"inline mt-10\">\n </div>\n <div v-if=\"dashboard && status !== 'loading'\" class=\"max-w-5xl mx-auto\">\n <div class=\"flex items-center w-full\" v-if=\"!showEditor\">\n <h2 class=\"mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink\">{{title}}</h2>\n <div class=\"flex gap-2\">\n <button\n @click=\"showEditor = true\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\">\n <img src=\"images/edit.svg\" class=\"inline h-[1.25em] mr-1\" /> Edit\n </button>\n\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center rounded-md bg-ultramarine-600 px-4 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n <svg class=\"inline h-[1.25em] mr-1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"m670-140 160-100-160-100v200ZM240-600h480v-80H240v80ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40ZM120-80v-680q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v267q-19-9-39-15t-41-9v-243H200v562h243q5 31 15.5 59T486-86l-6 6-60-60-60 60-60-60-60 60-60-60-60 60Zm120-200h203q3-21 9-41t15-39H240v80Zm0-160h284q38-37 88.5-58.5T720-520H240v80Zm-40 242v-562 562Z\"/></svg>\n Evaluate\n </async-button>\n </div>\n </div>\n <div v-if=\"!showEditor\" class=\"mt-4 mb-4\">\n <div v-if=\"dashboardResults.length === 0\">\n <div class=\"flex flex-col items-center justify-center py-8\">\n <p class=\"text-gray-700 text-base mb-4\">This dashboard hasn't been evaluated yet.</p>\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"rounded-md bg-ultramarine-600 px-4 py-2 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-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n Evaluate Dashboard\n </async-button>\n </div>\n </div>\n <div v-else>\n <div class=\"relative\">\n <dashboard-result\n :key=\"dashboardResult.finishedEvaluatingAt\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\"\n @fullscreen=\"showDetailModal = true\"\n class=\"h-[40vh]\"\n >\n </dashboard-result>\n </div>\n </div>\n </div>\n <div v-if=\"showEditor\" class=\"mt-4\">\n <edit-dashboard\n :dashboardId=\"dashboard._id\"\n :code=\"code\"\n :currentDescription=\"description\"\n :currentTitle=\"title\"\n @close=\"showEditor=false;\"\n @update=\"updateCode\"></edit-dashboard>\n </div>\n <div v-if=\"errorMessage\" class=\"rounded-md bg-red-50 p-4 mt-4\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">{{errorMessage}}</h3>\n </div>\n </div>\n </div>\n\n </div>\n <div v-if=\"!dashboard && status !== 'loading'\">\n No dashboard with the given id could be found.\n </div>\n</div>\n\n<modal\n v-if=\"showDetailModal\"\n containerClass=\"!h-[90vh] !w-[90vw]\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Dashboard Details\"\n>\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\" role=\"button\" aria-label=\"Close modal\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-result\n v-if=\"dashboardResult\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\"\n :fullscreen=\"true\"\n :responsive=\"true\">\n </dashboard-result>\n </div>\n </template>\n</modal>\n";
4327
4366
 
4328
4367
  /***/ }),
4329
4368
 
@@ -14837,7 +14876,7 @@ var bson = /*#__PURE__*/Object.freeze({
14837
14876
  /***/ ((module) => {
14838
14877
 
14839
14878
  "use strict";
14840
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.114","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"0.0.26","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
14879
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.116","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"0.0.26","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
14841
14880
 
14842
14881
  /***/ })
14843
14882
 
@@ -642,6 +642,10 @@ video {
642
642
  top: 90%;
643
643
  }
644
644
 
645
+ .bottom-0 {
646
+ bottom: 0px;
647
+ }
648
+
645
649
  .isolate {
646
650
  isolation: isolate;
647
651
  }
@@ -882,6 +886,10 @@ video {
882
886
  height: 32px;
883
887
  }
884
888
 
889
+ .h-\[40vh\] {
890
+ height: 40vh;
891
+ }
892
+
885
893
  .h-\[42px\] {
886
894
  height: 42px;
887
895
  }
@@ -898,6 +906,10 @@ video {
898
906
  max-height: 50vh;
899
907
  }
900
908
 
909
+ .min-h-0 {
910
+ min-height: 0px;
911
+ }
912
+
901
913
  .\!w-0 {
902
914
  width: 0px !important;
903
915
  }
@@ -978,6 +990,10 @@ video {
978
990
  max-width: 20rem;
979
991
  }
980
992
 
993
+ .max-w-\[calc\(100vw-3rem\)\] {
994
+ max-width: calc(100vw - 3rem);
995
+ }
996
+
981
997
  .flex-1 {
982
998
  flex: 1 1 0%;
983
999
  }
@@ -2069,6 +2085,11 @@ video {
2069
2085
  color: rgb(10 87 87 / var(--tw-text-opacity));
2070
2086
  }
2071
2087
 
2088
+ .hover\:text-gray-600:hover {
2089
+ --tw-text-opacity: 1;
2090
+ color: rgb(75 85 99 / var(--tw-text-opacity));
2091
+ }
2092
+
2072
2093
  .focus\:z-10:focus {
2073
2094
  z-index: 10;
2074
2095
  }
@@ -2374,6 +2395,10 @@ video {
2374
2395
  max-width: calc(100vw - 20rem);
2375
2396
  }
2376
2397
 
2398
+ .lg\:max-w-\[calc\(100vw-15rem\)\] {
2399
+ max-width: calc(100vw - 15rem);
2400
+ }
2401
+
2377
2402
  .lg\:px-8 {
2378
2403
  padding-left: 2rem;
2379
2404
  padding-right: 2rem;
@@ -1,14 +1,14 @@
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-4rem)] lg:max-w-[calc(100vw-20rem)]"
3
+ class="min-w-0 max-w-[calc(100vw-3rem)] lg:max-w-[calc(100vw-15rem)]"
4
4
  :class="{'text-right': message.role === 'user'}">
5
5
 
6
- <div class="text-sm text-gray-900 md:p-3 rounded-md inline-block" :class="styleForMessage">
6
+ <div class="text-sm text-gray-900 rounded-md inline-block relative" :class="styleForMessage">
7
7
  <div v-for="part in contentSplitByScripts">
8
8
  <div v-if="part.type === 'text'" v-html="marked(part.content)">
9
9
  </div>
10
10
  <div v-else-if="part.type === 'code'">
11
- <chat-message-script :message="message" :script="part.content" :language="part.language"></chat-message-script>
11
+ <chat-message-script :message="message" :script="part.content" :language="part.language" @copyMessage="copyMessage"></chat-message-script>
12
12
  </div>
13
13
  </div>
14
14
  </div>
@@ -2,6 +2,7 @@
2
2
 
3
3
  const api = require('../../api');
4
4
  const marked = require('marked').marked;
5
+ const vanillatoasts = require('vanillatoasts');
5
6
  const template = require('./chat-message.html');
6
7
 
7
8
  module.exports = app => app.component('chat-message', {
@@ -9,7 +10,7 @@ module.exports = app => app.component('chat-message', {
9
10
  props: ['message'],
10
11
  computed: {
11
12
  styleForMessage() {
12
- return this.message.role === 'user' ? 'bg-gray-100' : '';
13
+ return this.message.role === 'user' ? 'p-3 bg-gray-100' : 'py-3 pr-3';
13
14
  },
14
15
  contentSplitByScripts() {
15
16
  const content = this.message.content;
@@ -61,6 +62,37 @@ module.exports = app => app.component('chat-message', {
61
62
  });
62
63
  message.executionResult = chatMessage.executionResult;
63
64
  console.log(message);
65
+ },
66
+ async copyMessage() {
67
+ const parts = this.contentSplitByScripts;
68
+ let output = '';
69
+ for (const part of parts) {
70
+ if (part.type === 'text') {
71
+ output += part.content + '\n';
72
+ } else if (part.type === 'code') {
73
+ let result = this.message.executionResult?.output;
74
+ if (result != null && typeof result === 'object') {
75
+ result = JSON.stringify(result, null, 2);
76
+ }
77
+ if (result) {
78
+ let executionOutput = this.message.executionResult?.output;
79
+ if (executionOutput != null && typeof executionOutput === 'object') {
80
+ executionOutput = JSON.stringify(executionOutput, null, 2);
81
+ }
82
+ if (executionOutput) {
83
+ output += '```\n' + executionOutput + '\n```\n';
84
+ }
85
+ }
86
+ }
87
+ }
88
+ await navigator.clipboard.writeText(output.trim());
89
+ vanillatoasts.create({
90
+ title: 'Message output copied!',
91
+ type: 'success',
92
+ timeout: 3000,
93
+ icon: 'images/success.png',
94
+ positionClass: 'bottomRight'
95
+ });
64
96
  }
65
97
  }
66
98
  });
@@ -50,6 +50,11 @@
50
50
  @click="openCreateDashboardModal(); showDropdown = false">
51
51
  Create Dashboard
52
52
  </button>
53
+ <button
54
+ class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
55
+ @click="$emit('copyMessage'); showDropdown = false">
56
+ Copy Full Message
57
+ </button>
53
58
  </div>
54
59
  </div>
55
60
  </div>
@@ -7,6 +7,7 @@ const vanillatoasts = require('vanillatoasts');
7
7
  module.exports = app => app.component('chat-message-script', {
8
8
  template,
9
9
  props: ['message', 'script', 'language'],
10
+ emits: ['copyMessage'],
10
11
  data() {
11
12
  return {
12
13
  activeTab: 'code',
@@ -82,9 +83,13 @@ module.exports = app => app.component('chat-message-script', {
82
83
  this.$router.push('/dashboard/' + dashboard._id);
83
84
  },
84
85
  async copyOutput() {
85
- await navigator.clipboard.writeText(this.message.executionResult.output);
86
+ let output = this.message.executionResult.output;
87
+ if (output != null && typeof output === 'object') {
88
+ output = JSON.stringify(output, null, 2);
89
+ }
90
+ await navigator.clipboard.writeText(output);
86
91
  vanillatoasts.create({
87
- title: 'Text copied!',
92
+ title: 'Code output copied!',
88
93
  type: 'success',
89
94
  timeout: 3000,
90
95
  icon: 'images/success.png',
@@ -72,8 +72,9 @@
72
72
  <form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2 items-end justify-end">
73
73
  <textarea
74
74
  v-model="newMessage"
75
- placeholder="Ask something..."
75
+ :placeholder="sendingMessage ? 'Sending...' : 'Ask something...'"
76
76
  class="flex-1 border rounded px-4 py-2 resize-none overflow-y-auto"
77
+ :disabled="sendingMessage"
77
78
  rows="1"
78
79
  ref="messageInput"
79
80
  @input="adjustTextareaHeight"
@@ -21,6 +21,8 @@ module.exports = app => app.component('chat', {
21
21
  async sendMessage() {
22
22
  this.sendingMessage = true;
23
23
  try {
24
+ const content = this.newMessage;
25
+ this.newMessage = '';
24
26
  if (!this.chatThreadId) {
25
27
  const { chatThread } = await api.ChatThread.createChatThread();
26
28
  this.chatThreads.unshift(chatThread);
@@ -29,7 +31,7 @@ module.exports = app => app.component('chat', {
29
31
  }
30
32
 
31
33
  this.chatMessages.push({
32
- content: this.newMessage,
34
+ content,
33
35
  role: 'user'
34
36
  });
35
37
 
@@ -41,7 +43,7 @@ module.exports = app => app.component('chat', {
41
43
 
42
44
  const { chatMessages, chatThread } = await api.ChatThread.createChatMessage({
43
45
  chatThreadId: this.chatThreadId,
44
- content: this.newMessage
46
+ content
45
47
  });
46
48
  this.chatMessages.push(chatMessages[1]);
47
49
  for (const thread of this.chatThreads) {
@@ -41,18 +41,13 @@
41
41
  </div>
42
42
  <div v-else>
43
43
  <div class="relative">
44
- <button
45
- class="absolute top-2 right-2 px-2 py-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center"
46
- @click="openDetailModal"
47
- aria-label="Expand dashboard result">
48
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
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
- </svg>
51
- </button>
52
44
  <dashboard-result
53
45
  :key="dashboardResult.finishedEvaluatingAt"
54
46
  :result="dashboardResult.result"
55
- :finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt">
47
+ :finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt"
48
+ @fullscreen="showDetailModal = true"
49
+ class="h-[40vh]"
50
+ >
56
51
  </dashboard-result>
57
52
  </div>
58
53
  </div>
@@ -98,7 +93,9 @@
98
93
  <dashboard-result
99
94
  v-if="dashboardResult"
100
95
  :result="dashboardResult.result"
101
- :finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt">
96
+ :finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt"
97
+ :fullscreen="true"
98
+ :responsive="true">
102
99
  </dashboard-result>
103
100
  </div>
104
101
  </template>
@@ -48,9 +48,6 @@ module.exports = app => app.component('dashboard', {
48
48
  } finally {
49
49
  this.status = 'loaded';
50
50
  }
51
- },
52
- openDetailModal() {
53
- this.showDetailModal = true;
54
51
  }
55
52
  },
56
53
  computed: {
@@ -1,5 +1,5 @@
1
1
  <div :class="responsive ? 'h-full' : ''">
2
- <div v-if="header" class="border-b border-gray-100 px-2 pb-2 flex items-center">
2
+ <div v-if="header && !fullscreen" class="border-b border-gray-100 px-2 pb-2 flex items-center gap-1">
3
3
  <div class="text-xl font-bold">{{header}}</div>
4
4
  <button
5
5
  class="ml-auto px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors"
@@ -7,16 +7,32 @@
7
7
  title="Export PNG">
8
8
  <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.5em;" viewBox="0 -960 960 960" fill="currentColor"><path d="M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
9
9
  </button>
10
+ <button
11
+ class="px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors flex items-center"
12
+ @click="$emit('fullscreen')"
13
+ aria-label="Expand dashboard result">
14
+ <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.5em" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
15
+ <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" />
16
+ </svg>
17
+ </button>
10
18
  </div>
11
- <div v-else class="border-b border-gray-100 px-2 pb-2 text-right">
19
+ <div v-else-if="!fullscreen" class="pt-1 border-b border-gray-100 px-2 pb-2 text-right flex items-center justify-end gap-1 w-full">
12
20
  <button
13
- class="mt-1 px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors"
21
+ class="px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors"
14
22
  @click="exportPNG"
15
23
  title="Export PNG">
16
24
  <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.5em;" viewBox="0 -960 960 960" fill="currentColor"><path d="M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
17
25
  </button>
26
+ <button
27
+ class="px-2 py-1 text-xs bg-ultramarine-600 text-white border-none rounded cursor-pointer hover:bg-ultramarine-500 transition-colors flex items-center"
28
+ @click="$emit('fullscreen')"
29
+ aria-label="Expand dashboard result">
30
+ <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.5em" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
31
+ <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" />
32
+ </svg>
33
+ </button>
18
34
  </div>
19
- <div class="text-xl" :class="responsive ? 'h-full' : ''">
20
- <canvas ref="chart"></canvas>
35
+ <div :class="responsive ? 'relative h-full min-h-0' : ''">
36
+ <canvas ref="chart" class="block w-full h-full"></canvas>
21
37
  </div>
22
38
  </div>
@@ -4,9 +4,11 @@ const template = require('./dashboard-chart.html');
4
4
 
5
5
  module.exports = app => app.component('dashboard-chart', {
6
6
  template: template,
7
- props: ['value', 'responsive'],
7
+ props: ['value', 'fullscreen'],
8
+ emits: ['fullscreen'],
8
9
  data: () => ({
9
- chart: null
10
+ chart: null,
11
+ showDetailModal: false
10
12
  }),
11
13
  mounted() {
12
14
  const ctx = this.$refs.chart.getContext('2d');
@@ -12,10 +12,12 @@
12
12
  <component
13
13
  class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl"
14
14
  :is="getComponentForValue(result)"
15
- :value="result">
15
+ :value="result"
16
+ :fullscreen="fullscreen"
17
+ @fullscreen="$emit('fullscreen')">
16
18
  </component>
17
19
  </div>
18
- <div class="text-right text-sm text-gray-700 mt-1" v-if="finishedEvaluatingAt">
20
+ <div class="text-right text-sm text-gray-700 mt-1" v-if="finishedEvaluatingAt && !fullscreen">
19
21
  Last Evaluated: {{ format.isoToLongDateTime(finishedEvaluatingAt) }}
20
22
  </div>
21
23
  </div>
@@ -7,7 +7,8 @@ const template = require('./dashboard-result.html');
7
7
 
8
8
  module.exports = app => app.component('dashboard-result', {
9
9
  template: template,
10
- props: ['result', 'finishedEvaluatingAt'],
10
+ props: ['result', 'finishedEvaluatingAt', 'fullscreen'],
11
+ emits: ['fullscreen'],
11
12
  mounted: async function() {
12
13
  },
13
14
  methods: {
@@ -194,13 +194,13 @@ module.exports = app => app.component('models', {
194
194
  this.query.search = this.searchText;
195
195
  const query = this.query;
196
196
  const newUrl = this.$router.resolve({ query }).href;
197
- window.history.pushState(null, '', newUrl);
197
+ this.$router.push({ query });
198
198
  } else {
199
199
  this.filter = {};
200
200
  delete this.query.search;
201
201
  const query = this.query;
202
202
  const newUrl = this.$router.resolve({ query }).href;
203
- window.history.pushState(null, '', newUrl);
203
+ this.$router.push({ query });
204
204
  }
205
205
  this.documents = [];
206
206
  this.status = 'loading';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.114",
3
+ "version": "0.0.116",
4
4
  "description": "A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.",
5
5
  "homepage": "https://studio.mongoosejs.io/",
6
6
  "repository": {