@mongoosejs/studio 0.0.95 → 0.0.97

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.
@@ -324,12 +324,11 @@ module.exports = app => app.component('async-button', {
324
324
 
325
325
 
326
326
  const api = __webpack_require__(/*! ../../api */ "./frontend/src/api.js");
327
- const marked = (__webpack_require__(/*! marked */ "./node_modules/marked/lib/marked.cjs").marked);
328
327
  const template = __webpack_require__(/*! ./chat-message-script.html */ "./frontend/src/chat/chat-message-script/chat-message-script.html");
329
328
  const vanillatoasts = __webpack_require__(/*! vanillatoasts */ "./node_modules/vanillatoasts/vanillatoasts.js");
330
329
 
331
330
  module.exports = app => app.component('chat-message-script', {
332
- template: template,
331
+ template,
333
332
  props: ['message', 'script', 'language'],
334
333
  data: () => ({ activeTab: 'code', showDetailModal: false }),
335
334
  computed: {
@@ -469,7 +468,8 @@ module.exports = app => app.component('chat', {
469
468
  newMessage: '',
470
469
  chatThreadId: null,
471
470
  chatThreads: [],
472
- chatMessages: []
471
+ chatMessages: [],
472
+ hideSidebar: null
473
473
  }),
474
474
  methods: {
475
475
  async sendMessage() {
@@ -500,6 +500,11 @@ module.exports = app => app.component('chat', {
500
500
  this.chatMessages.push(chatMessages[1]);
501
501
 
502
502
  this.newMessage = '';
503
+ this.$nextTick(() => {
504
+ if (this.$refs.messageInput) {
505
+ this.$refs.messageInput.style.height = 'auto';
506
+ }
507
+ });
503
508
 
504
509
  this.$nextTick(() => {
505
510
  if (this.$refs.messagesContainer) {
@@ -510,6 +515,18 @@ module.exports = app => app.component('chat', {
510
515
  this.sendingMessage = false;
511
516
  }
512
517
  },
518
+ handleEnter(ev) {
519
+ if (!ev.shiftKey) {
520
+ this.sendMessage();
521
+ }
522
+ },
523
+ adjustTextareaHeight(ev) {
524
+ const textarea = ev.target;
525
+ textarea.style.height = 'auto';
526
+ const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight, 10);
527
+ const maxHeight = lineHeight * 5;
528
+ textarea.style.height = Math.min(textarea.scrollHeight, maxHeight) + 'px';
529
+ },
513
530
  selectThread(threadId) {
514
531
  this.$router.push('/chat/' + threadId);
515
532
  },
@@ -1756,6 +1773,7 @@ if (typeof process === 'undefined') {
1756
1773
 
1757
1774
  const api = __webpack_require__(/*! ./api */ "./frontend/src/api.js");
1758
1775
  const mothership = __webpack_require__(/*! ./mothership */ "./frontend/src/mothership.js");
1776
+ const { routes } = __webpack_require__(/*! ./routes */ "./frontend/src/routes.js");
1759
1777
  const vanillatoasts = __webpack_require__(/*! vanillatoasts */ "./node_modules/vanillatoasts/vanillatoasts.js");
1760
1778
 
1761
1779
  const app = Vue.createApp({
@@ -1814,7 +1832,7 @@ app.component('app-component', {
1814
1832
  window.state = this;
1815
1833
 
1816
1834
  if (mothership.hasAPIKey) {
1817
- const hash = window.location.hash.replace(/^#?\/?\??/, '') || '';
1835
+ const hash = window.location.hash.replace(/^#?\/?/, '') || '';
1818
1836
  const hashQuery = hash.split('?')[1] || '';
1819
1837
  const hashParams = new URLSearchParams(hashQuery);
1820
1838
  if (hashParams.has('code')) {
@@ -1857,6 +1875,7 @@ app.component('app-component', {
1857
1875
  const { nodeEnv } = await api.status();
1858
1876
  this.nodeEnv = nodeEnv;
1859
1877
  }
1878
+
1860
1879
  this.status = 'loaded';
1861
1880
  },
1862
1881
  setup() {
@@ -1873,7 +1892,6 @@ app.component('app-component', {
1873
1892
  }
1874
1893
  });
1875
1894
 
1876
- const { routes } = __webpack_require__(/*! ./routes */ "./frontend/src/routes.js");
1877
1895
  const router = VueRouter.createRouter({
1878
1896
  history: VueRouter.createWebHashHistory(),
1879
1897
  routes: routes.map(route => ({
@@ -1883,6 +1901,14 @@ const router = VueRouter.createRouter({
1883
1901
  }))
1884
1902
  });
1885
1903
 
1904
+ router.beforeEach((to, from, next) => {
1905
+ if (to.name === 'root' && window.state.roles && window.state.roles[0] === 'dashboards') {
1906
+ return next({ name: 'dashboards' });
1907
+ } else {
1908
+ next();
1909
+ }
1910
+ });
1911
+
1886
1912
  app.use(router);
1887
1913
 
1888
1914
  app.mount('#content');
@@ -2693,13 +2719,21 @@ module.exports = app => app.component('navbar', {
2693
2719
  inject: ['state'],
2694
2720
  data: () => ({ showFlyout: false }),
2695
2721
  mounted: function() {
2696
- // Redirect to first allowed route if current route is not allowed
2697
- if (!this.hasAccess(this.roles, this.$route.name)) {
2698
- const firstAllowedRoute = this.allowedRoutes[0];
2699
- if (firstAllowedRoute) {
2700
- this.$router.push({ name: firstAllowedRoute.name });
2701
- }
2702
- }
2722
+ const mobileMenuMask = document.querySelector('#mobile-menu-mask');
2723
+ const mobileMenu = document.querySelector('#mobile-menu');
2724
+
2725
+ document.querySelector('#open-mobile-menu').addEventListener('click', (event) => {
2726
+ event.stopPropagation();
2727
+ mobileMenuMask.style.display = 'block';
2728
+ mobileMenu.classList.remove('translate-x-full');
2729
+ mobileMenu.classList.add('translate-x-0');
2730
+ });
2731
+
2732
+ document.querySelector('body').addEventListener('click', () => {
2733
+ mobileMenuMask.style.display = 'none';
2734
+ mobileMenu.classList.remove('translate-x-0');
2735
+ mobileMenu.classList.add('translate-x-full');
2736
+ });
2703
2737
  },
2704
2738
  computed: {
2705
2739
  dashboardView() {
@@ -2723,11 +2757,8 @@ module.exports = app => app.component('navbar', {
2723
2757
  canViewTeam() {
2724
2758
  return this.hasAccess(this.roles, 'team');
2725
2759
  },
2726
- allowedRoutes() {
2727
- return routes.filter(route => this.hasAccess(this.roles, route.name));
2728
- },
2729
2760
  defaultRoute() {
2730
- return this.allowedRoutes[0]?.name || 'dashboards';
2761
+ return this.roles && this.roles[0] === 'dashboards' ? 'dashboards' : 'root';
2731
2762
  }
2732
2763
  },
2733
2764
  methods: {
@@ -2788,10 +2819,12 @@ const roleAccess = {
2788
2819
  dashboards: ['dashboards', 'dashboard']
2789
2820
  };
2790
2821
 
2822
+ const allowedRoutesForLocalDev = ['document', 'root', 'chat'];
2823
+
2791
2824
  // Helper function to check if a role has access to a route
2792
2825
  function hasAccess(roles, routeName) {
2793
2826
  // change to true for local development
2794
- if (!roles) return false;
2827
+ if (!roles) return allowedRoutesForLocalDev.includes(routeName);
2795
2828
  return roles.some(role => roleAccess[role]?.includes(routeName));
2796
2829
  }
2797
2830
 
@@ -3881,7 +3914,7 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
3881
3914
  /***/ ((module) => {
3882
3915
 
3883
3916
  "use strict";
3884
- 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>\n </div>\n\n <pre class=\"p-3 whitespace-pre-wrap max-h-[30vh] max-w-[calc(100vw-25rem)] 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-[30vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-25rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :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\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n</div>\n";
3917
+ 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>\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 <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\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n</div>\n";
3885
3918
 
3886
3919
  /***/ }),
3887
3920
 
@@ -3892,7 +3925,7 @@ module.exports = "<div class=\"relative border rounded bg-gray-100 text-black te
3892
3925
  /***/ ((module) => {
3893
3926
 
3894
3927
  "use strict";
3895
- module.exports = "<div class=\"relative flex items-start space-x-3\" :class=\"{'justify-end': message.role === 'user'}\">\n <div\n class=\"min-w-0 max-w-[calc(100%-6.5rem)]\"\n :class=\"{'text-right': message.role === 'user'}\">\n\n <div class=\"text-sm text-gray-900 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";
3928
+ 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";
3896
3929
 
3897
3930
  /***/ }),
3898
3931
 
@@ -3903,7 +3936,7 @@ module.exports = "<div class=\"relative flex items-start space-x-3\" :class=\"{'
3903
3936
  /***/ ((module) => {
3904
3937
 
3905
3938
  "use strict";
3906
- module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px)\">\n <!-- Sidebar: Chat Threads -->\n <aside class=\"w-64 bg-gray-100 border-r overflow-y-auto h-full\">\n <div class=\"p-4 font-bold text-lg border-b\">Chat Threads</div>\n <div class=\"p-4\">\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 vif=\"status === 'loaded'\">\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\"\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\">\n <input\n v-model=\"newMessage\"\n placeholder=\"Ask something...\"\n class=\"flex-1 border rounded px-4 py-2\"\n />\n <button class=\"bg-blue-600 text-white px-4 py-2 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";
3939
+ module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 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 <!-- 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";
3907
3940
 
3908
3941
  /***/ }),
3909
3942
 
@@ -4431,7 +4464,7 @@ module.exports = "<div class=\"models\">\n <div>\n <div class=\"flex grow fl
4431
4464
  /***/ ((module) => {
4432
4465
 
4433
4466
  "use strict";
4434
- module.exports = ".navbar {\n width: 100%;\n background-color: #eee;\n}\n\n.active {\n text-decoration: underline;\n}\n\n.spacing {\n margin-right: 10px;\n}\n\n.navbar .nav-left {\n float: left;\n line-height: 54px;\n font-size: 20px;\n padding-left: 20px;\n}\n\n.navbar .nav-left a {\n color: #232323;\n}\n\n.navbar {\n border-bottom: 1px solid #ddd;\n height: 55px;\n}\n\n.navbar .nav-left img {\n height: 32px;\n vertical-align: middle;\n margin-right: 0.5em;\n margin-top: 8px;\n}\n\n.navbar .nav-right {\n float: right;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n font-size: 16px;\n line-height: 54px;\n padding-right: 20px;\n}\n\n.navbar .nav-right .nav-item {\n flex-grow: 1;\n padding: 0px 12px;\n position: relative;\n z-index: 21000;\n}\n\n.navbar .nav-right .nav-item:hover {\n flex-grow: 1;\n padding: 0px 12px;\n border-bottom: 1px solid #E1B9A0;\n}\n\n.navbar .nav-right .nav-item.active {\n border-bottom: 1px solid #E1B9A0;\n}\n\n.navbar .nav-action {\n cursor: pointer;\n color: #E1B9A0;\n}\n\n.navbar .nav-action svg {\n height: 1em;\n vertical-align: middle;\n}\n\n.navbar .nav-right .nav-item .flyout {\n position: absolute;\n top: 55px;\n right: 0px;\n visibility: hidden;\n opacity: 0;\n transition: opacity .25s,visibility .25s,transform .25s;\n width: auto;\n box-shadow: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);\n background-color: #393944;\n padding-left: 0.5em;\n padding-right: 0.5em;\n z-index: 1000;\n min-width: 192px;\n font-size: 0.9em;\n}\n\n.navbar .nav-right .nav-item:hover .flyout a {\n color: #E1B9A0;\n margin-top: 0.25em;\n margin-bottom: 0.25em;\n}\n\n.navbar .nav-right .nav-item:hover .flyout a:hover {\n color: #E1B9A0;\n}\n\n.navbar .nav-right .nav-item:hover .flyout .nav-action {\n color: #E1B9A0;\n margin-top: 0.25em;\n margin-bottom: 0.25em;\n}\n\n.navbar .nav-right .nav-item:hover .flyout .nav-action:hover {\n color: #E1B9A0;\n}\n\n.navbar .nav-right .nav-item:hover .flyout {\n visibility: visible;\n opacity: 1;\n}\n\n#bar-1 {\n\ttransform: translateY(-4px);\n}\n#bar-3 {\n\ttransform: translateY(4px);\n}\n.menu {\n display: none;\n}\n.menu {\n\twidth: 35px;\n\theight: 30px;\n\tmargin: 18px 2px 0px 0px;\n\tcursor: pointer;\n float: right;\n}\n.bar {\n\theight: 5px;\n\twidth: 100%;\n\tbackground-color: #fff;\n\tdisplay: block;\n\tborder-radius: 5px;\n\ttransition: 0.4s ease;\n}\n.change-icon #bar-1 {\n transform: translateY(4px) rotateZ(-405deg);\n}\n.change-icon #bar-2 {\n opacity: 0;\n}\n.change-icon #bar-3 {\n transform: translateY(-6px) rotateZ(405deg);\n}\n\n@media (max-width: 767px) {\n .menu {\n display: block;\n }\n\n .change-icon ~ div.nav-right {\n left: 0;\n }\n\n .navbar .nav-right {\n\t\tposition: fixed;\n\t\ttop: 55px;\n\t\tleft: -130%;\n\t\tbackground: #111;\n\t\theight: 100vh;\n\t\twidth: 100%;\n\t\ttext-align: center;\n\t\tdisplay: block;\n\t\ttransition: all 0.3s ease;\n z-index: 10000;\n\t}\n}\n";
4467
+ module.exports = ".active {\n text-decoration: underline;\n}\n\n.navbar .nav-left {\n float: left;\n line-height: 54px;\n font-size: 20px;\n padding-left: 20px;\n}\n\n.navbar .nav-left a {\n color: #232323;\n}\n\n.navbar .nav-right {\n float: right;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n font-size: 16px;\n line-height: 54px;\n padding-right: 20px;\n}\n\n.navbar .nav-right .nav-item {\n flex-grow: 1;\n padding: 0px 12px;\n position: relative;\n z-index: 21000;\n}\n\n.navbar .nav-right .nav-item:hover {\n flex-grow: 1;\n padding: 0px 12px;\n border-bottom: 1px solid #E1B9A0;\n}\n\n.navbar .nav-right .nav-item.active {\n border-bottom: 1px solid #E1B9A0;\n}\n\n.navbar .nav-action {\n cursor: pointer;\n color: #E1B9A0;\n}\n\n.navbar .nav-action svg {\n height: 1em;\n vertical-align: middle;\n}\n\n.navbar .nav-right .nav-item .flyout {\n position: absolute;\n top: 55px;\n right: 0px;\n visibility: hidden;\n opacity: 0;\n transition: opacity .25s,visibility .25s,transform .25s;\n width: auto;\n box-shadow: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);\n background-color: #393944;\n padding-left: 0.5em;\n padding-right: 0.5em;\n z-index: 1000;\n min-width: 192px;\n font-size: 0.9em;\n}\n\n.navbar .nav-right .nav-item:hover .flyout a {\n color: #E1B9A0;\n margin-top: 0.25em;\n margin-bottom: 0.25em;\n}\n\n.navbar .nav-right .nav-item:hover .flyout a:hover {\n color: #E1B9A0;\n}\n\n.navbar .nav-right .nav-item:hover .flyout .nav-action {\n color: #E1B9A0;\n margin-top: 0.25em;\n margin-bottom: 0.25em;\n}\n\n.navbar .nav-right .nav-item:hover .flyout .nav-action:hover {\n color: #E1B9A0;\n}\n\n.navbar .nav-right .nav-item:hover .flyout {\n visibility: visible;\n opacity: 1;\n}\n\n#bar-1 {\n\ttransform: translateY(-4px);\n}\n#bar-3 {\n\ttransform: translateY(4px);\n}\n.menu {\n display: none;\n}\n.menu {\n\twidth: 35px;\n\theight: 30px;\n\tmargin: 18px 2px 0px 0px;\n\tcursor: pointer;\n float: right;\n}\n.bar {\n\theight: 5px;\n\twidth: 100%;\n\tbackground-color: #fff;\n\tdisplay: block;\n\tborder-radius: 5px;\n\ttransition: 0.4s ease;\n}\n.change-icon #bar-1 {\n transform: translateY(4px) rotateZ(-405deg);\n}\n.change-icon #bar-2 {\n opacity: 0;\n}\n.change-icon #bar-3 {\n transform: translateY(-6px) rotateZ(405deg);\n}\n\n@media (max-width: 767px) {\n .menu {\n display: block;\n }\n\n .change-icon ~ div.nav-right {\n left: 0;\n }\n\n .navbar .nav-right {\n\t\tposition: fixed;\n\t\ttop: 55px;\n\t\tleft: -130%;\n\t\tbackground: #111;\n\t\theight: 100vh;\n\t\twidth: 100%;\n\t\ttext-align: center;\n\t\tdisplay: block;\n\t\ttransition: all 0.3s ease;\n z-index: 10000;\n\t}\n}\n";
4435
4468
 
4436
4469
  /***/ }),
4437
4470
 
@@ -4442,7 +4475,7 @@ module.exports = ".navbar {\n width: 100%;\n background-color: #eee;\n}\n\n.ac
4442
4475
  /***/ ((module) => {
4443
4476
 
4444
4477
  "use strict";
4445
- module.exports = "<div class=\"navbar\">\n <div class=\"nav-left flex items-center gap-4 h-full\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!state.nodeEnv\" class=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900\" :class=\"warnEnv ? 'bg-red-300' : 'bg-yellow-300'\">\n {{state.nodeEnv}}\n </div>\n </div>\n <div class=\"nav-right h-full\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"chatView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Chat</a>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-ultramarine-600 px-2 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\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-10 top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\" role=\"menu\" aria-orientation=\"vertical\" aria-labelledby=\"user-menu-button\" tabindex=\"-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n\n </div>\n </div>\n <div style=\"clear: both\"></div>\n</div>\n";
4478
+ module.exports = "<div class=\"navbar w-full bg-gray-50 flex justify-between border-b border-gray-200 !h-[55px]\">\n <div class=\"flex items-center gap-4 h-full pl-4\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-[32px] mr-1\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!state.nodeEnv\" title=\"NODE_ENV\" class=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900\" :class=\"warnEnv ? 'bg-red-300' : 'bg-yellow-300'\">\n {{state.nodeEnv}}\n </div>\n </div>\n <div class=\"h-full pr-4 hidden md:block\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <span v-else class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium text-gray-300 cursor-not-allowed\" aria-disabled=\"true\">\n Documents\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"chatView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Chat</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-ultramarine-600 px-2 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\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-10 top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\" role=\"menu\" aria-orientation=\"vertical\" aria-labelledby=\"user-menu-button\" tabindex=\"-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->\n <button type=\"button\" id=\"open-mobile-menu\" class=\"-ml-2 rounded-md p-2 pr-4 text-gray-400\">\n <span class=\"sr-only\">Open menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Mobile menu mask -->\n <div id=\"mobile-menu-mask\" class=\"fixed inset-0 bg-black bg-opacity-40 z-40 hidden\"></div>\n <!-- Mobile menu drawer -->\n <div id=\"mobile-menu\" class=\"fixed inset-0 bg-white shadow-lg z-50 transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col\">\n <div class=\"flex items-center justify-between px-4 !h-[55px] border-b border-gray-200\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-[32px]\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <button type=\"button\" id=\"close-mobile-menu\" class=\"text-gray-400 p-2 rounded-md\">\n <span class=\"sr-only\">Close menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <nav class=\"flex-1 px-4 py-4 space-y-2\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"documentView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Documents</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Documents\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"dashboardView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Dashboards</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"chatView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Chat</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <div v-if=\"!user && hasAPIKey\" class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"w-full rounded bg-ultramarine-600 px-3 py-2 text-base 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\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"mt-4\">\n <div class=\"flex items-center gap-3 px-3 py-2 bg-gray-50 rounded-md\">\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n <span class=\"text-gray-900 font-medium\">{{ user.name }}</span>\n </div>\n <div class=\"mt-2 space-y-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100\">Team</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100 cursor-pointer\">Sign out</span>\n </div>\n </div>\n </nav>\n </div>\n</div>\n";
4446
4479
 
4447
4480
  /***/ }),
4448
4481
 
@@ -594,6 +594,10 @@ video {
594
594
  pointer-events: none;
595
595
  }
596
596
 
597
+ .fixed {
598
+ position: fixed;
599
+ }
600
+
597
601
  .absolute {
598
602
  position: absolute;
599
603
  }
@@ -610,6 +614,10 @@ video {
610
614
  inset: -0.375rem;
611
615
  }
612
616
 
617
+ .inset-0 {
618
+ inset: 0px;
619
+ }
620
+
613
621
  .right-0 {
614
622
  right: 0px;
615
623
  }
@@ -622,6 +630,10 @@ video {
622
630
  top: 0.25rem;
623
631
  }
624
632
 
633
+ .top-\[65px\] {
634
+ top: 65px;
635
+ }
636
+
625
637
  .top-\[90\%\] {
626
638
  top: 90%;
627
639
  }
@@ -634,6 +646,18 @@ video {
634
646
  z-index: 10;
635
647
  }
636
648
 
649
+ .z-20 {
650
+ z-index: 20;
651
+ }
652
+
653
+ .z-40 {
654
+ z-index: 40;
655
+ }
656
+
657
+ .z-50 {
658
+ z-index: 50;
659
+ }
660
+
637
661
  .col-start-1 {
638
662
  grid-column-start: 1;
639
663
  }
@@ -679,6 +703,10 @@ video {
679
703
  margin-left: -0.125rem;
680
704
  }
681
705
 
706
+ .-ml-2 {
707
+ margin-left: -0.5rem;
708
+ }
709
+
682
710
  .-ml-px {
683
711
  margin-left: -1px;
684
712
  }
@@ -802,6 +830,10 @@ video {
802
830
  height: 2rem;
803
831
  }
804
832
 
833
+ .\!h-\[55px\] {
834
+ height: 55px !important;
835
+ }
836
+
805
837
  .\!h-\[90vh\] {
806
838
  height: 90vh !important;
807
839
  }
@@ -826,6 +858,10 @@ video {
826
858
  height: 1.25rem;
827
859
  }
828
860
 
861
+ .h-6 {
862
+ height: 1.5rem;
863
+ }
864
+
829
865
  .h-8 {
830
866
  height: 2rem;
831
867
  }
@@ -838,6 +874,10 @@ video {
838
874
  height: 300px;
839
875
  }
840
876
 
877
+ .h-\[32px\] {
878
+ height: 32px;
879
+ }
880
+
841
881
  .h-\[42px\] {
842
882
  height: 42px;
843
883
  }
@@ -854,6 +894,14 @@ video {
854
894
  height: 1px;
855
895
  }
856
896
 
897
+ .h-10 {
898
+ height: 2.5rem;
899
+ }
900
+
901
+ .h-11 {
902
+ height: 2.75rem;
903
+ }
904
+
857
905
  .max-h-\[30vh\] {
858
906
  max-height: 30vh;
859
907
  }
@@ -862,10 +910,22 @@ video {
862
910
  max-height: 50vh;
863
911
  }
864
912
 
913
+ .\!w-0 {
914
+ width: 0px !important;
915
+ }
916
+
917
+ .\!w-64 {
918
+ width: 16rem !important;
919
+ }
920
+
865
921
  .\!w-\[90vw\] {
866
922
  width: 90vw !important;
867
923
  }
868
924
 
925
+ .w-0 {
926
+ width: 0px;
927
+ }
928
+
869
929
  .w-16 {
870
930
  width: 4rem;
871
931
  }
@@ -886,6 +946,10 @@ video {
886
946
  width: 1.25rem;
887
947
  }
888
948
 
949
+ .w-6 {
950
+ width: 1.5rem;
951
+ }
952
+
889
953
  .w-64 {
890
954
  width: 16rem;
891
955
  }
@@ -914,12 +978,8 @@ video {
914
978
  max-width: 64rem;
915
979
  }
916
980
 
917
- .max-w-\[70vw\] {
918
- max-width: 70vw;
919
- }
920
-
921
- .max-w-\[calc\(100\%-6\.5rem\)\] {
922
- max-width: calc(100% - 6.5rem);
981
+ .max-w-\[calc\(100vw-4rem\)\] {
982
+ max-width: calc(100vw - 4rem);
923
983
  }
924
984
 
925
985
  .max-w-xs {
@@ -962,6 +1022,16 @@ video {
962
1022
  transform-origin: top right;
963
1023
  }
964
1024
 
1025
+ .translate-x-0 {
1026
+ --tw-translate-x: 0px;
1027
+ 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));
1028
+ }
1029
+
1030
+ .translate-x-full {
1031
+ --tw-translate-x: 100%;
1032
+ 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));
1033
+ }
1034
+
965
1035
  .transform {
966
1036
  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));
967
1037
  }
@@ -974,6 +1044,10 @@ video {
974
1044
  cursor: pointer;
975
1045
  }
976
1046
 
1047
+ .resize-none {
1048
+ resize: none;
1049
+ }
1050
+
977
1051
  .list-disc {
978
1052
  list-style-type: disc;
979
1053
  }
@@ -1004,6 +1078,10 @@ video {
1004
1078
  align-items: flex-start;
1005
1079
  }
1006
1080
 
1081
+ .items-end {
1082
+ align-items: flex-end;
1083
+ }
1084
+
1007
1085
  .items-center {
1008
1086
  align-items: center;
1009
1087
  }
@@ -1080,6 +1158,12 @@ video {
1080
1158
  margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
1081
1159
  }
1082
1160
 
1161
+ .space-y-2 > :not([hidden]) ~ :not([hidden]) {
1162
+ --tw-space-y-reverse: 0;
1163
+ margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
1164
+ margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
1165
+ }
1166
+
1083
1167
  .space-y-4 > :not([hidden]) ~ :not([hidden]) {
1084
1168
  --tw-space-y-reverse: 0;
1085
1169
  margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
@@ -1135,6 +1219,10 @@ video {
1135
1219
  overflow-y: auto;
1136
1220
  }
1137
1221
 
1222
+ .overflow-x-hidden {
1223
+ overflow-x: hidden;
1224
+ }
1225
+
1138
1226
  .truncate {
1139
1227
  overflow: hidden;
1140
1228
  text-overflow: ellipsis;
@@ -1235,9 +1323,9 @@ video {
1235
1323
  border-color: rgb(63 83 255 / var(--tw-border-opacity));
1236
1324
  }
1237
1325
 
1238
- .border-red-500 {
1239
- --tw-border-opacity: 1;
1240
- border-color: rgb(239 68 68 / var(--tw-border-opacity));
1326
+ .bg-black {
1327
+ --tw-bg-opacity: 1;
1328
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
1241
1329
  }
1242
1330
 
1243
1331
  .bg-blue-200 {
@@ -1374,6 +1462,11 @@ video {
1374
1462
  background-color: rgb(229 234 255 / var(--tw-bg-opacity));
1375
1463
  }
1376
1464
 
1465
+ .bg-ultramarine-500 {
1466
+ --tw-bg-opacity: 1;
1467
+ background-color: rgb(63 83 255 / var(--tw-bg-opacity));
1468
+ }
1469
+
1377
1470
  .bg-ultramarine-600 {
1378
1471
  --tw-bg-opacity: 1;
1379
1472
  background-color: rgb(24 35 255 / var(--tw-bg-opacity));
@@ -1399,9 +1492,8 @@ video {
1399
1492
  background-color: rgb(253 224 71 / var(--tw-bg-opacity));
1400
1493
  }
1401
1494
 
1402
- .bg-ultramarine-500 {
1403
- --tw-bg-opacity: 1;
1404
- background-color: rgb(63 83 255 / var(--tw-bg-opacity));
1495
+ .bg-opacity-40 {
1496
+ --tw-bg-opacity: 0.4;
1405
1497
  }
1406
1498
 
1407
1499
  .p-1 {
@@ -1630,6 +1722,11 @@ video {
1630
1722
  color: rgb(0 242 58 / var(--tw-text-opacity));
1631
1723
  }
1632
1724
 
1725
+ .text-gray-300 {
1726
+ --tw-text-opacity: 1;
1727
+ color: rgb(209 213 219 / var(--tw-text-opacity));
1728
+ }
1729
+
1633
1730
  .text-gray-400 {
1634
1731
  --tw-text-opacity: 1;
1635
1732
  color: rgb(156 163 175 / var(--tw-text-opacity));
@@ -1685,6 +1782,11 @@ video {
1685
1782
  color: rgb(0 168 165 / var(--tw-text-opacity));
1686
1783
  }
1687
1784
 
1785
+ .text-ultramarine-700 {
1786
+ --tw-text-opacity: 1;
1787
+ color: rgb(7 19 250 / var(--tw-text-opacity));
1788
+ }
1789
+
1688
1790
  .text-valencia-500 {
1689
1791
  --tw-text-opacity: 1;
1690
1792
  color: rgb(220 73 73 / var(--tw-text-opacity));
@@ -1777,16 +1879,6 @@ video {
1777
1879
  --tw-ring-color: rgb(22 163 74 / 0.2);
1778
1880
  }
1779
1881
 
1780
- .ring-yellow-800 {
1781
- --tw-ring-opacity: 1;
1782
- --tw-ring-color: rgb(133 77 14 / var(--tw-ring-opacity));
1783
- }
1784
-
1785
- .ring-yellow-300 {
1786
- --tw-ring-opacity: 1;
1787
- --tw-ring-color: rgb(253 224 71 / var(--tw-ring-opacity));
1788
- }
1789
-
1790
1882
  .filter {
1791
1883
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1792
1884
  }
@@ -1799,12 +1891,36 @@ video {
1799
1891
  transition-duration: 150ms;
1800
1892
  }
1801
1893
 
1894
+ .transition-all {
1895
+ transition-property: all;
1896
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1897
+ transition-duration: 150ms;
1898
+ }
1899
+
1802
1900
  .transition-colors {
1803
1901
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
1804
1902
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1805
1903
  transition-duration: 150ms;
1806
1904
  }
1807
1905
 
1906
+ .transition-transform {
1907
+ transition-property: transform;
1908
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1909
+ transition-duration: 150ms;
1910
+ }
1911
+
1912
+ .duration-200 {
1913
+ transition-duration: 200ms;
1914
+ }
1915
+
1916
+ .duration-300 {
1917
+ transition-duration: 300ms;
1918
+ }
1919
+
1920
+ .ease-in-out {
1921
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1922
+ }
1923
+
1808
1924
  .placeholder\:text-gray-400::-moz-placeholder {
1809
1925
  --tw-text-opacity: 1;
1810
1926
  color: rgb(156 163 175 / var(--tw-text-opacity));
@@ -1850,6 +1966,11 @@ video {
1850
1966
  background-color: rgb(0 242 58 / var(--tw-bg-opacity));
1851
1967
  }
1852
1968
 
1969
+ .hover\:bg-gray-100:hover {
1970
+ --tw-bg-opacity: 1;
1971
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
1972
+ }
1973
+
1853
1974
  .hover\:bg-gray-200:hover {
1854
1975
  --tw-bg-opacity: 1;
1855
1976
  background-color: rgb(229 231 235 / var(--tw-bg-opacity));
@@ -1930,6 +2051,11 @@ video {
1930
2051
  background-color: rgb(63 83 255 / var(--tw-bg-opacity));
1931
2052
  }
1932
2053
 
2054
+ .hover\:bg-ultramarine-600:hover {
2055
+ --tw-bg-opacity: 1;
2056
+ background-color: rgb(24 35 255 / var(--tw-bg-opacity));
2057
+ }
2058
+
1933
2059
  .hover\:bg-valencia-400:hover {
1934
2060
  --tw-bg-opacity: 1;
1935
2061
  background-color: rgb(235 126 126 / var(--tw-bg-opacity));
@@ -1940,11 +2066,6 @@ video {
1940
2066
  background-color: rgb(220 73 73 / var(--tw-bg-opacity));
1941
2067
  }
1942
2068
 
1943
- .hover\:bg-ultramarine-600:hover {
1944
- --tw-bg-opacity: 1;
1945
- background-color: rgb(24 35 255 / var(--tw-bg-opacity));
1946
- }
1947
-
1948
2069
  .hover\:text-gray-700:hover {
1949
2070
  --tw-text-opacity: 1;
1950
2071
  color: rgb(55 65 81 / var(--tw-text-opacity));
@@ -2075,6 +2196,10 @@ video {
2075
2196
  outline-color: #d1d5db;
2076
2197
  }
2077
2198
 
2199
+ .focus-visible\:outline-gray-500:focus-visible {
2200
+ outline-color: #6b7280;
2201
+ }
2202
+
2078
2203
  .focus-visible\:outline-green-600:focus-visible {
2079
2204
  outline-color: #16a34a;
2080
2205
  }
@@ -2107,14 +2232,6 @@ video {
2107
2232
  outline-color: #1823ff;
2108
2233
  }
2109
2234
 
2110
- .focus-visible\:outline-gray-600:focus-visible {
2111
- outline-color: #4b5563;
2112
- }
2113
-
2114
- .focus-visible\:outline-gray-500:focus-visible {
2115
- outline-color: #6b7280;
2116
- }
2117
-
2118
2235
  .disabled\:cursor-not-allowed:disabled {
2119
2236
  cursor: not-allowed;
2120
2237
  }
@@ -2228,12 +2345,54 @@ video {
2228
2345
  }
2229
2346
  }
2230
2347
 
2348
+ @media (min-width: 768px) {
2349
+ .md\:relative {
2350
+ position: relative;
2351
+ }
2352
+
2353
+ .md\:block {
2354
+ display: block;
2355
+ }
2356
+
2357
+ .md\:hidden {
2358
+ display: none;
2359
+ }
2360
+
2361
+ .md\:w-64 {
2362
+ width: 16rem;
2363
+ }
2364
+
2365
+ .md\:max-w-\[calc\(100vw-16rem\)\] {
2366
+ max-width: calc(100vw - 16rem);
2367
+ }
2368
+
2369
+ .md\:max-w-\[calc\(100vw-20rem\)\] {
2370
+ max-width: calc(100vw - 20rem);
2371
+ }
2372
+
2373
+ .md\:p-3 {
2374
+ padding: 0.75rem;
2375
+ }
2376
+ }
2377
+
2231
2378
  @media (min-width: 1024px) {
2379
+ .lg\:relative {
2380
+ position: relative;
2381
+ }
2382
+
2232
2383
  .lg\:-mx-8 {
2233
2384
  margin-left: -2rem;
2234
2385
  margin-right: -2rem;
2235
2386
  }
2236
2387
 
2388
+ .lg\:w-64 {
2389
+ width: 16rem;
2390
+ }
2391
+
2392
+ .lg\:max-w-\[calc\(100vw-20rem\)\] {
2393
+ max-width: calc(100vw - 20rem);
2394
+ }
2395
+
2237
2396
  .lg\:px-8 {
2238
2397
  padding-left: 2rem;
2239
2398
  padding-right: 2rem;
@@ -1,9 +1,9 @@
1
- <div class="relative flex items-start space-x-3" :class="{'justify-end': message.role === 'user'}">
1
+ <div class="relative flex items-start" :class="{'justify-end': message.role === 'user'}">
2
2
  <div
3
- class="min-w-0 max-w-[calc(100%-6.5rem)]"
3
+ class="min-w-0 max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)]"
4
4
  :class="{'text-right': message.role === 'user'}">
5
5
 
6
- <div class="text-sm text-gray-900 p-3 rounded-md inline-block" :class="styleForMessage">
6
+ <div class="text-sm text-gray-900 md:p-3 rounded-md inline-block" :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>
@@ -37,9 +37,9 @@
37
37
  </div>
38
38
  </div>
39
39
 
40
- <pre class="p-3 whitespace-pre-wrap max-h-[30vh] max-w-[calc(100vw-25rem)] overflow-y-auto" v-show="activeTab === 'code'"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
40
+ <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>
41
41
 
42
- <div class="p-3 whitespace-pre-wrap max-h-[30vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-25rem)] relative" v-show="activeTab === 'output'">
42
+ <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'">
43
43
  <dashboard-chart v-if="message.executionResult?.output?.$chart" :value="message.executionResult?.output" />
44
44
  <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>
45
45
  </div>
@@ -1,12 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const api = require('../../api');
4
- const marked = require('marked').marked;
5
4
  const template = require('./chat-message-script.html');
6
5
  const vanillatoasts = require('vanillatoasts');
7
6
 
8
7
  module.exports = app => app.component('chat-message-script', {
9
- template: template,
8
+ template,
10
9
  props: ['message', 'script', 'language'],
11
10
  data: () => ({ activeTab: 'code', showDetailModal: false }),
12
11
  computed: {
@@ -1,8 +1,20 @@
1
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">
2
+ <div class="fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10" @click="hideSidebar = false">
3
+ <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>
4
+ </div>
5
+ <!-- Sidebar: Chat Threads -->
6
+ <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' : ''">
7
+ <div class="flex items-center border-b border-gray-100 w-64 overflow-x-hidden">
8
+ <div class="p-4 font-bold text-lg">Chat Threads</div>
9
+ <button
10
+ @click="hideSidebar = true"
11
+ class="ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none"
12
+ aria-label="Close sidebar"
13
+ >
14
+ <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>
15
+ </button>
16
+ </div>
17
+ <div class="p-4 w-64">
6
18
  <async-button
7
19
  @click="createNewThread"
8
20
  class="w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
@@ -13,12 +25,12 @@
13
25
  <div v-if="status === 'loaded' && chatThreads.length === 0" class="p-4 text-sm text-gray-700">
14
26
  No threads yet
15
27
  </div>
16
- <ul vif="status === 'loaded'">
28
+ <ul v-if="status === 'loaded'" class="w-64">
17
29
  <li
18
30
  v-for="thread in chatThreads"
19
31
  :key="thread._id"
20
32
  @click="selectThread(thread._id)"
21
- class="p-4 hover:bg-gray-200 cursor-pointer"
33
+ class="p-4 hover:bg-gray-200 cursor-pointer w-64"
22
34
  :class="{ 'bg-gray-300': thread._id === chatThreadId }"
23
35
  >
24
36
  {{ thread.title || 'Untitled Thread' }}
@@ -46,13 +58,17 @@
46
58
 
47
59
  <!-- Input Area -->
48
60
  <div class="border-t p-4">
49
- <form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2">
50
- <input
61
+ <form @submit.prevent="sendMessage" :disabled="sendingMessage" class="flex gap-2 items-end justify-end">
62
+ <textarea
51
63
  v-model="newMessage"
52
64
  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">
65
+ class="flex-1 border rounded px-4 py-2 resize-none overflow-y-auto"
66
+ rows="1"
67
+ ref="messageInput"
68
+ @input="adjustTextareaHeight"
69
+ @keydown.enter.exact.prevent="handleEnter"
70
+ ></textarea>
71
+ <button class="bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600" :disabled="sendingMessage">
56
72
  <svg v-if="sendingMessage" style="height: 1em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
57
73
  <g>
58
74
  <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity="0.3" />
@@ -12,7 +12,8 @@ module.exports = app => app.component('chat', {
12
12
  newMessage: '',
13
13
  chatThreadId: null,
14
14
  chatThreads: [],
15
- chatMessages: []
15
+ chatMessages: [],
16
+ hideSidebar: null
16
17
  }),
17
18
  methods: {
18
19
  async sendMessage() {
@@ -43,6 +44,11 @@ module.exports = app => app.component('chat', {
43
44
  this.chatMessages.push(chatMessages[1]);
44
45
 
45
46
  this.newMessage = '';
47
+ this.$nextTick(() => {
48
+ if (this.$refs.messageInput) {
49
+ this.$refs.messageInput.style.height = 'auto';
50
+ }
51
+ });
46
52
 
47
53
  this.$nextTick(() => {
48
54
  if (this.$refs.messagesContainer) {
@@ -53,6 +59,18 @@ module.exports = app => app.component('chat', {
53
59
  this.sendingMessage = false;
54
60
  }
55
61
  },
62
+ handleEnter(ev) {
63
+ if (!ev.shiftKey) {
64
+ this.sendMessage();
65
+ }
66
+ },
67
+ adjustTextareaHeight(ev) {
68
+ const textarea = ev.target;
69
+ textarea.style.height = 'auto';
70
+ const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight, 10);
71
+ const maxHeight = lineHeight * 5;
72
+ textarea.style.height = Math.min(textarea.scrollHeight, maxHeight) + 'px';
73
+ },
56
74
  selectThread(threadId) {
57
75
  this.$router.push('/chat/' + threadId);
58
76
  },
@@ -6,6 +6,7 @@ if (typeof process === 'undefined') {
6
6
 
7
7
  const api = require('./api');
8
8
  const mothership = require('./mothership');
9
+ const { routes } = require('./routes');
9
10
  const vanillatoasts = require('vanillatoasts');
10
11
 
11
12
  const app = Vue.createApp({
@@ -67,7 +68,7 @@ app.component('app-component', {
67
68
  window.state = this;
68
69
 
69
70
  if (mothership.hasAPIKey) {
70
- const hash = window.location.hash.replace(/^#?\/?\??/, '') || '';
71
+ const hash = window.location.hash.replace(/^#?\/?/, '') || '';
71
72
  const hashQuery = hash.split('?')[1] || '';
72
73
  const hashParams = new URLSearchParams(hashQuery);
73
74
  if (hashParams.has('code')) {
@@ -110,6 +111,7 @@ app.component('app-component', {
110
111
  const { nodeEnv } = await api.status();
111
112
  this.nodeEnv = nodeEnv;
112
113
  }
114
+
113
115
  this.status = 'loaded';
114
116
  },
115
117
  setup() {
@@ -126,7 +128,6 @@ app.component('app-component', {
126
128
  }
127
129
  });
128
130
 
129
- const { routes } = require('./routes');
130
131
  const router = VueRouter.createRouter({
131
132
  history: VueRouter.createWebHashHistory(),
132
133
  routes: routes.map(route => ({
@@ -136,6 +137,14 @@ const router = VueRouter.createRouter({
136
137
  }))
137
138
  });
138
139
 
140
+ router.beforeEach((to, from, next) => {
141
+ if (to.name === 'root' && window.state.roles && window.state.roles[0] === 'dashboards') {
142
+ return next({ name: 'dashboards' });
143
+ } else {
144
+ next();
145
+ }
146
+ });
147
+
139
148
  app.use(router);
140
149
 
141
150
  app.mount('#content');
@@ -1,16 +1,7 @@
1
- .navbar {
2
- width: 100%;
3
- background-color: #eee;
4
- }
5
-
6
1
  .active {
7
2
  text-decoration: underline;
8
3
  }
9
4
 
10
- .spacing {
11
- margin-right: 10px;
12
- }
13
-
14
5
  .navbar .nav-left {
15
6
  float: left;
16
7
  line-height: 54px;
@@ -22,18 +13,6 @@
22
13
  color: #232323;
23
14
  }
24
15
 
25
- .navbar {
26
- border-bottom: 1px solid #ddd;
27
- height: 55px;
28
- }
29
-
30
- .navbar .nav-left img {
31
- height: 32px;
32
- vertical-align: middle;
33
- margin-right: 0.5em;
34
- margin-top: 8px;
35
- }
36
-
37
16
  .navbar .nav-right {
38
17
  float: right;
39
18
  display: flex;
@@ -1,26 +1,44 @@
1
- <div class="navbar">
2
- <div class="nav-left flex items-center gap-4 h-full">
1
+ <div class="navbar w-full bg-gray-50 flex justify-between border-b border-gray-200 !h-[55px]">
2
+ <div class="flex items-center gap-4 h-full pl-4">
3
3
  <router-link :to="{ name: defaultRoute }">
4
- <img src="images/logo.svg" alt="Mongoose Studio Logo" />
4
+ <img src="images/logo.svg" class="h-[32px] mr-1" alt="Mongoose Studio Logo" />
5
5
  </router-link>
6
- <div v-if="!!state.nodeEnv" class="inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900" :class="warnEnv ? 'bg-red-300' : 'bg-yellow-300'">
6
+ <div v-if="!!state.nodeEnv" title="NODE_ENV" class="inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900" :class="warnEnv ? 'bg-red-300' : 'bg-yellow-300'">
7
7
  {{state.nodeEnv}}
8
8
  </div>
9
9
  </div>
10
- <div class="nav-right h-full">
10
+ <div class="h-full pr-4 hidden md:block">
11
11
  <div class="sm:ml-6 sm:flex sm:space-x-8 h-full">
12
12
  <a v-if="hasAccess(roles, 'root')"
13
13
  href="#/"
14
14
  class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
15
15
  :class="documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'">Documents</a>
16
+ <span v-else class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium text-gray-300 cursor-not-allowed" aria-disabled="true">
17
+ Documents
18
+ <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
19
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
20
+ </svg>
21
+ </span>
16
22
  <a v-if="hasAccess(roles, 'dashboards')"
17
23
  href="#/dashboards"
18
24
  class="inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium"
19
25
  :class="dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'">Dashboards</a>
26
+ <span v-else class="inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed">
27
+ Dashboards
28
+ <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
29
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
30
+ </svg>
31
+ </span>
20
32
  <a v-if="hasAccess(roles, 'chat')"
21
33
  href="#/chat"
22
34
  class="inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium"
23
35
  :class="chatView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'">Chat</a>
36
+ <span v-else class="inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed">
37
+ Chat
38
+ <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
39
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
40
+ </svg>
41
+ </span>
24
42
 
25
43
  <div class="h-full flex items-center" v-if="!user && hasAPIKey">
26
44
  <button
@@ -41,11 +59,98 @@
41
59
 
42
60
  <div v-if="showFlyout" class="absolute right-0 z-10 top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
43
61
  <router-link to="/team" v-if="hasAccess(roles, 'team')" @click="showFlyout = false" class="cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200" role="menuitem" tabindex="-1" id="user-menu-item-2">Team</router-link>
62
+ <span v-else class="block px-4 py-2 text-sm text-gray-300 cursor-not-allowed" role="menuitem" tabindex="-1" id="user-menu-item-2">
63
+ Team
64
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
65
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
66
+ </svg>
67
+ </span>
44
68
  <span @click="logout" class="cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</span>
45
69
  </div>
46
70
  </div>
47
71
 
48
72
  </div>
49
73
  </div>
50
- <div style="clear: both"></div>
74
+ <div class="md:hidden flex items-center">
75
+ <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->
76
+ <button type="button" id="open-mobile-menu" class="-ml-2 rounded-md p-2 pr-4 text-gray-400">
77
+ <span class="sr-only">Open menu</span>
78
+ <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
79
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
80
+ </svg>
81
+ </button>
82
+ </div>
83
+
84
+ <!-- Mobile menu mask -->
85
+ <div id="mobile-menu-mask" class="fixed inset-0 bg-black bg-opacity-40 z-40 hidden"></div>
86
+ <!-- Mobile menu drawer -->
87
+ <div id="mobile-menu" class="fixed inset-0 bg-white shadow-lg z-50 transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col">
88
+ <div class="flex items-center justify-between px-4 !h-[55px] border-b border-gray-200">
89
+ <router-link :to="{ name: defaultRoute }">
90
+ <img src="images/logo.svg" class="h-[32px]" alt="Mongoose Studio Logo" />
91
+ </router-link>
92
+ <button type="button" id="close-mobile-menu" class="text-gray-400 p-2 rounded-md">
93
+ <span class="sr-only">Close menu</span>
94
+ <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
95
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
96
+ </svg>
97
+ </button>
98
+ </div>
99
+ <nav class="flex-1 px-4 py-4 space-y-2">
100
+ <a v-if="hasAccess(roles, 'root')"
101
+ href="#/"
102
+ class="block px-3 py-2 rounded-md text-base font-medium"
103
+ :class="documentView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'">Documents</a>
104
+ <span v-else class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed">
105
+ Documents
106
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
107
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
108
+ </svg>
109
+ </span>
110
+ <a v-if="hasAccess(roles, 'dashboards')"
111
+ href="#/dashboards"
112
+ class="block px-3 py-2 rounded-md text-base font-medium"
113
+ :class="dashboardView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'">Dashboards</a>
114
+ <span v-else class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed">
115
+ Dashboards
116
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
117
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
118
+ </svg>
119
+ </span>
120
+ <a v-if="hasAccess(roles, 'chat')"
121
+ href="#/chat"
122
+ class="block px-3 py-2 rounded-md text-base font-medium"
123
+ :class="chatView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'">Chat</a>
124
+ <span v-else class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed">
125
+ Chat
126
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
127
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
128
+ </svg>
129
+ </span>
130
+ <div v-if="!user && hasAPIKey" class="mt-4">
131
+ <button
132
+ type="button"
133
+ @click="loginWithGithub"
134
+ class="w-full rounded bg-ultramarine-600 px-3 py-2 text-base 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">
135
+ Login
136
+ </button>
137
+ </div>
138
+ <div v-if="user && hasAPIKey" class="mt-4">
139
+ <div class="flex items-center gap-3 px-3 py-2 bg-gray-50 rounded-md">
140
+ <img class="size-8 rounded-full" :src="user.picture" alt="">
141
+ <span class="text-gray-900 font-medium">{{ user.name }}</span>
142
+ </div>
143
+ <div class="mt-2 space-y-1">
144
+ <router-link to="/team" v-if="hasAccess(roles, 'team')" class="block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100">Team</router-link>
145
+ <span v-else class="block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed">
146
+ Team
147
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
148
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
149
+ </svg>
150
+ </span>
151
+ <span @click="logout" class="block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100 cursor-pointer">Sign out</span>
152
+ </div>
153
+ </div>
154
+ </nav>
155
+ </div>
51
156
  </div>
@@ -15,13 +15,21 @@ module.exports = app => app.component('navbar', {
15
15
  inject: ['state'],
16
16
  data: () => ({ showFlyout: false }),
17
17
  mounted: function() {
18
- // Redirect to first allowed route if current route is not allowed
19
- if (!this.hasAccess(this.roles, this.$route.name)) {
20
- const firstAllowedRoute = this.allowedRoutes[0];
21
- if (firstAllowedRoute) {
22
- this.$router.push({ name: firstAllowedRoute.name });
23
- }
24
- }
18
+ const mobileMenuMask = document.querySelector('#mobile-menu-mask');
19
+ const mobileMenu = document.querySelector('#mobile-menu');
20
+
21
+ document.querySelector('#open-mobile-menu').addEventListener('click', (event) => {
22
+ event.stopPropagation();
23
+ mobileMenuMask.style.display = 'block';
24
+ mobileMenu.classList.remove('translate-x-full');
25
+ mobileMenu.classList.add('translate-x-0');
26
+ });
27
+
28
+ document.querySelector('body').addEventListener('click', () => {
29
+ mobileMenuMask.style.display = 'none';
30
+ mobileMenu.classList.remove('translate-x-0');
31
+ mobileMenu.classList.add('translate-x-full');
32
+ });
25
33
  },
26
34
  computed: {
27
35
  dashboardView() {
@@ -45,11 +53,8 @@ module.exports = app => app.component('navbar', {
45
53
  canViewTeam() {
46
54
  return this.hasAccess(this.roles, 'team');
47
55
  },
48
- allowedRoutes() {
49
- return routes.filter(route => this.hasAccess(this.roles, route.name));
50
- },
51
56
  defaultRoute() {
52
- return this.allowedRoutes[0]?.name || 'dashboards';
57
+ return this.roles && this.roles[0] === 'dashboards' ? 'dashboards' : 'root';
53
58
  }
54
59
  },
55
60
  methods: {
@@ -9,10 +9,12 @@ const roleAccess = {
9
9
  dashboards: ['dashboards', 'dashboard']
10
10
  };
11
11
 
12
+ const allowedRoutesForLocalDev = ['document', 'root', 'chat'];
13
+
12
14
  // Helper function to check if a role has access to a route
13
15
  function hasAccess(roles, routeName) {
14
16
  // change to true for local development
15
- if (!roles) return false;
17
+ if (!roles) return allowedRoutesForLocalDev.includes(routeName);
16
18
  return roles.some(role => roleAccess[role]?.includes(routeName));
17
19
  }
18
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.95",
3
+ "version": "0.0.97",
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": {