@mongoosejs/studio 0.1.12 → 0.1.13

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.
@@ -14,12 +14,21 @@ const CreateChatThreadParams = new Archetype({
14
14
  $workspaceId: {
15
15
  $type: mongoose.Types.ObjectId,
16
16
  $required: false
17
+ },
18
+ initialMessage: {
19
+ $type: 'string',
20
+ $required: false
21
+ },
22
+ dashboardId: {
23
+ $type: mongoose.Types.ObjectId,
24
+ $required: false
17
25
  }
18
26
  }).compile('CreateChatThreadParams');
19
27
 
20
28
  module.exports = ({ studioConnection }) => async function createChatThread(params) {
21
- const { initiatedById, roles, $workspaceId } = new CreateChatThreadParams(params);
29
+ const { initiatedById, roles, $workspaceId, initialMessage, dashboardId } = new CreateChatThreadParams(params);
22
30
  const ChatThread = studioConnection.model('__Studio_ChatThread');
31
+ const ChatMessage = studioConnection.model('__Studio_ChatMessage');
23
32
 
24
33
  await authorize('ChatThread.createChatThread', roles);
25
34
 
@@ -30,7 +39,20 @@ module.exports = ({ studioConnection }) => async function createChatThread(param
30
39
  if ($workspaceId && !initiatedById) {
31
40
  throw new Error('initiatedById is required when creating a chat thread in a workspace');
32
41
  }
42
+ if (dashboardId) {
43
+ doc.dashboardId = dashboardId;
44
+ }
33
45
  const chatThread = await ChatThread.create(doc);
34
46
 
47
+ if (initialMessage != null && initialMessage.trim() !== '') {
48
+ await ChatMessage.create({
49
+ chatThreadId: chatThread._id,
50
+ role: 'user',
51
+ content: initialMessage.trim(),
52
+ script: null,
53
+ executionResult: null
54
+ });
55
+ }
56
+
35
57
  return { chatThread };
36
58
  };
@@ -10,6 +10,10 @@ const chatThreadSchema = new mongoose.Schema({
10
10
  type: mongoose.ObjectId,
11
11
  ref: 'User'
12
12
  },
13
+ dashboardId: {
14
+ type: mongoose.ObjectId,
15
+ ref: 'Dashboard'
16
+ },
13
17
  workspaceId: {
14
18
  type: mongoose.ObjectId,
15
19
  ref: 'Workspace'
@@ -668,7 +668,7 @@ module.exports = app => app.component('async-button', {
668
668
  /***/ ((module) => {
669
669
 
670
670
  "use strict";
671
- 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 <button\n v-if=\"activeTab === 'code' && !isEditing\"\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.stop=\"startEditing\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z\" />\n </svg>\n </button>\n <async-button\n v-if=\"!isEditing\"\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\">\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 <div class=\"p-3 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\">\n <div v-if=\"isEditing\" class=\"flex flex-col space-y-2\">\n <div class=\"border border-gray-200\">\n <textarea ref=\"scriptEditor\" class=\"w-full h-[45vh]\" @input=\"handleScriptInput\"></textarea>\n </div>\n <div class=\"flex justify-end gap-2\">\n <button\n class=\"px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors\"\n @click.stop=\"cancelEditing\">\n Cancel\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\">\n Execute\n </async-button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n </div>\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\n <div v-if=\"message.executionResult?.logs?.length\" class=\"mt-3 pt-3 border-t border-gray-200\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-gray-100 text-gray-900 p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ message.executionResult.logs }}</pre>\n </div>\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 <dashboard-map\n v-else-if=\"message.executionResult?.output?.$featureCollection\"\n :value=\"message.executionResult?.output\"\n height=\"80vh\" />\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";
671
+ 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 <button\n v-if=\"activeTab === 'code' && !isEditing\"\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.stop=\"startEditing\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z\" />\n </svg>\n </button>\n <async-button\n v-if=\"!isEditing\"\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\">\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 v-if=\"canOverwriteDashboard\"\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openOverwriteDashboardConfirmation(); showDropdown = false\">\n Overwrite 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 <div class=\"p-3 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\">\n <div v-if=\"isEditing\" class=\"flex flex-col space-y-2\">\n <div class=\"border border-gray-200\">\n <textarea ref=\"scriptEditor\" class=\"w-full h-[45vh]\" @input=\"handleScriptInput\"></textarea>\n </div>\n <div class=\"flex justify-end gap-2\">\n <button\n class=\"px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors\"\n @click.stop=\"cancelEditing\">\n Cancel\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\">\n Execute\n </async-button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n </div>\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\n <div v-if=\"message.executionResult?.logs?.length\" class=\"mt-3 pt-3 border-t border-gray-200\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-gray-100 text-gray-900 p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ message.executionResult.logs }}</pre>\n </div>\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 <dashboard-map\n v-else-if=\"message.executionResult?.output?.$featureCollection\"\n :value=\"message.executionResult?.output\"\n height=\"80vh\" />\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=\"createError\" 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 <modal v-if=\"showOverwriteDashboardConfirmationModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showOverwriteDashboardConfirmationModal = false\">&times;</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Overwrite Dashboard</div>\n <p class=\"mt-2 text-sm text-gray-700\">\n This will replace the linked dashboard's code with the script below.\n </p>\n <p class=\"mt-1 text-xs text-gray-600 break-all\" v-if=\"targetDashboardId\">\n Dashboard ID: {{ targetDashboardId }}\n </p>\n <div class=\"my-4 border border-gray-200 bg-gray-50 rounded\">\n <pre class=\"p-2 h-[300px] overflow-auto whitespace-pre-wrap text-xs\">{{ overwriteDashboardCode }}</pre>\n </div>\n <div class=\"flex items-center gap-2\">\n <async-button\n @click=\"confirmOverwriteDashboard\"\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 Confirm Overwrite\n </async-button>\n <button\n class=\"px-2.5 py-1.5 rounded-md text-sm font-semibold text-gray-700 bg-gray-200 hover:bg-gray-300\"\n @click=\"showOverwriteDashboardConfirmationModal = false\">\n Cancel\n </button>\n </div>\n <div v-if=\"overwriteError\" class=\"rounded-md bg-red-50 p-4 mt-3\">\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 {{overwriteError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
672
672
 
673
673
  /***/ }),
674
674
 
@@ -688,13 +688,14 @@ const vanillatoasts = __webpack_require__(/*! vanillatoasts */ "./node_modules/v
688
688
 
689
689
  module.exports = app => app.component('chat-message-script', {
690
690
  template,
691
- props: ['message', 'script', 'language'],
691
+ props: ['message', 'script', 'language', 'targetDashboardId'],
692
692
  emits: ['copyMessage'],
693
693
  data() {
694
694
  return {
695
695
  activeTab: 'code',
696
696
  showDetailModal: false,
697
697
  showCreateDashboardModal: false,
698
+ showOverwriteDashboardConfirmationModal: false,
698
699
  showDropdown: false,
699
700
  newDashboardTitle: '',
700
701
  dashboardCode: '',
@@ -702,12 +703,17 @@ module.exports = app => app.component('chat-message-script', {
702
703
  dashboardEditor: null,
703
704
  isEditing: false,
704
705
  codeEditor: null,
705
- editedScript: null
706
+ editedScript: null,
707
+ overwriteDashboardCode: '',
708
+ overwriteError: null
706
709
  };
707
710
  },
708
711
  computed: {
709
712
  styleForMessage() {
710
713
  return this.message.role === 'user' ? 'bg-gray-100' : '';
714
+ },
715
+ canOverwriteDashboard() {
716
+ return !!this.targetDashboardId;
711
717
  }
712
718
  },
713
719
  methods: {
@@ -739,7 +745,7 @@ module.exports = app => app.component('chat-message-script', {
739
745
  openCreateDashboardModal() {
740
746
  this.newDashboardTitle = '';
741
747
  this.dashboardCode = this.script;
742
- this.createErrors = [];
748
+ this.createError = null;
743
749
  this.showCreateDashboardModal = true;
744
750
  this.$nextTick(() => {
745
751
  if (this.dashboardEditor) {
@@ -755,6 +761,14 @@ module.exports = app => app.component('chat-message-script', {
755
761
  });
756
762
  });
757
763
  },
764
+ openOverwriteDashboardConfirmation() {
765
+ if (!this.canOverwriteDashboard) {
766
+ return;
767
+ }
768
+ this.overwriteDashboardCode = this.codeEditor?.getValue?.() ?? this.script;
769
+ this.overwriteError = null;
770
+ this.showOverwriteDashboardConfirmationModal = true;
771
+ },
758
772
  toggleDropdown() {
759
773
  this.showDropdown = !this.showDropdown;
760
774
  },
@@ -827,6 +841,32 @@ module.exports = app => app.component('chat-message-script', {
827
841
  this.showCreateDashboardModal = false;
828
842
  this.$router.push('/dashboard/' + dashboard._id);
829
843
  },
844
+ async confirmOverwriteDashboard() {
845
+ if (!this.canOverwriteDashboard) {
846
+ this.overwriteError = 'This chat is not linked to a dashboard.';
847
+ return;
848
+ }
849
+
850
+ this.overwriteDashboardCode = this.codeEditor?.getValue?.() ?? this.script;
851
+
852
+ const params = {
853
+ dashboardId: this.targetDashboardId,
854
+ code: this.overwriteDashboardCode
855
+ };
856
+
857
+ const { doc } = await api.Dashboard.updateDashboard(params).catch(err => {
858
+ if (err.response?.data?.message) {
859
+ const message = err.response.data.message.split(': ').slice(1).join(': ');
860
+ this.overwriteError = message;
861
+ throw new Error(err.response?.data?.message);
862
+ }
863
+ throw err;
864
+ });
865
+
866
+ this.overwriteError = null;
867
+ this.showOverwriteDashboardConfirmationModal = false;
868
+ this.$router.push('/dashboard/' + doc._id);
869
+ },
830
870
  async copyOutput() {
831
871
  const executionResult = this.message.executionResult || {};
832
872
  let output = executionResult.output;
@@ -892,7 +932,7 @@ module.exports = app => app.component('chat-message-script', {
892
932
  /***/ ((module) => {
893
933
 
894
934
  "use strict";
895
- 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";
935
+ 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\n :message=\"message\"\n :script=\"part.content\"\n :language=\"part.language\"\n :target-dashboard-id=\"targetDashboardId\"\n @copyMessage=\"copyMessage\"></chat-message-script>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
896
936
 
897
937
  /***/ }),
898
938
 
@@ -912,7 +952,7 @@ const template = __webpack_require__(/*! ./chat-message.html */ "./frontend/src/
912
952
 
913
953
  module.exports = app => app.component('chat-message', {
914
954
  template: template,
915
- props: ['message'],
955
+ props: ['message', 'targetDashboardId'],
916
956
  computed: {
917
957
  styleForMessage() {
918
958
  return this.message.role === 'user' ? 'p-3 bg-gray-100' : 'py-3 pr-3';
@@ -1012,7 +1052,7 @@ module.exports = app => app.component('chat-message', {
1012
1052
  /***/ ((module) => {
1013
1053
 
1014
1054
  "use strict";
1015
- 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";
1055
+ 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\" :target-dashboard-id=\"currentThread?.dashboardId\"></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";
1016
1056
 
1017
1057
  /***/ }),
1018
1058
 
@@ -1832,7 +1872,7 @@ module.exports = app => app.component('dashboard-text', {
1832
1872
  /***/ ((module) => {
1833
1873
 
1834
1874
  "use strict";
1835
- 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-if=\"dashboardResult\">\n <div class=\"relative\">\n <div v-if=\"status === 'evaluating'\" class=\"absolute inset-0 flex items-center justify-center bg-white bg-opacity-60 z-10 flex flex-col gap-2\">\n <div>Evaluating Dashboard...</div>\n <img src=\"images/loader.gif\" class=\"h-12 w-12\" alt=\"Loading...\" />\n </div>\n <div v-else-if=\"dashboardResult.error\" 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\">{{dashboardResult.error.message || 'Unknown Error'}}</h3>\n </div>\n </div>\n </div>\n <dashboard-result\n v-else\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\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";
1875
+ 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 class=\"relative\" ref=\"actionsMenu\">\n <button\n type=\"button\"\n @click.stop=\"toggleActionsMenu\"\n class=\"flex items-center rounded-md bg-gray-100 px-3 py-1.5 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\"\n aria-haspopup=\"true\"\n :aria-expanded=\"showActionsMenu\"\n >\n More\n <svg class=\"ml-1 h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n class=\"absolute right-0 mt-2 w-44 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black/5 z-20\"\n role=\"menu\"\n aria-label=\"Dashboard actions\"\n >\n <button\n type=\"button\"\n :disabled=\"startingChat\"\n @click.stop=\"startChatWithDashboard\"\n class=\"flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-gray-700 hover:bg-ultramarine-100 disabled:cursor-not-allowed disabled:text-gray-400\"\n role=\"menuitem\"\n >\n <svg class=\"h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M8 10h.01M12 10h.01M16 10h.01m-8 4h8m-9 5 10-3.333L7 12.333 3 14l.667-4.333L9 7l4-3 4.333 2.667L21 11l-3 3.667V19a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-1z\" />\n </svg>\n Start Chat\n </button>\n </div>\n </div>\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-if=\"dashboardResult\">\n <div class=\"relative\">\n <div v-if=\"status === 'evaluating'\" class=\"absolute inset-0 flex items-center justify-center bg-white bg-opacity-60 z-10 flex flex-col gap-2\">\n <div>Evaluating Dashboard...</div>\n <img src=\"images/loader.gif\" class=\"h-12 w-12\" alt=\"Loading...\" />\n </div>\n <div v-else-if=\"dashboardResult.error\" 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\">{{dashboardResult.error.message || 'Unknown Error'}}</h3>\n </div>\n </div>\n </div>\n <dashboard-result\n v-else\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\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";
1836
1876
 
1837
1877
  /***/ }),
1838
1878
 
@@ -1861,7 +1901,9 @@ module.exports = app => app.component('dashboard', {
1861
1901
  dashboard: null,
1862
1902
  dashboardResults: [],
1863
1903
  errorMessage: null,
1864
- showDetailModal: false
1904
+ showDetailModal: false,
1905
+ startingChat: false,
1906
+ showActionsMenu: false
1865
1907
  };
1866
1908
  },
1867
1909
  methods: {
@@ -1908,6 +1950,55 @@ module.exports = app => app.component('dashboard', {
1908
1950
  }
1909
1951
 
1910
1952
  return finishedAt < sixHoursAgo;
1953
+ },
1954
+ toggleActionsMenu() {
1955
+ this.showActionsMenu = !this.showActionsMenu;
1956
+ },
1957
+ closeActionsMenu() {
1958
+ this.showActionsMenu = false;
1959
+ },
1960
+ handleDocumentClick(ev) {
1961
+ if (!this.showActionsMenu) {
1962
+ return;
1963
+ }
1964
+ const menuEl = this.$refs.actionsMenu;
1965
+ if (!menuEl) {
1966
+ return;
1967
+ }
1968
+ if (!menuEl.contains(ev.target)) {
1969
+ this.closeActionsMenu();
1970
+ }
1971
+ },
1972
+ async startChatWithDashboard() {
1973
+ if (this.startingChat) {
1974
+ return;
1975
+ }
1976
+
1977
+ this.startingChat = true;
1978
+ try {
1979
+ const description = this.description?.trim();
1980
+ const parts = [
1981
+ 'I want to edit this dashboard.',
1982
+ `Title: ${this.title || 'Untitled Dashboard'}`
1983
+ ];
1984
+
1985
+ if (description) {
1986
+ parts.push(`Description: ${description}`);
1987
+ }
1988
+
1989
+ parts.push('Here is the current dashboard code:', '```javascript', this.code, '```');
1990
+
1991
+ const initialMessage = parts.join('\n\n');
1992
+
1993
+ const { chatThread } = await api.ChatThread.createChatThread({
1994
+ initialMessage,
1995
+ dashboardId: this.dashboard?._id
1996
+ });
1997
+ this.$router.push('/chat/' + chatThread._id);
1998
+ } finally {
1999
+ this.startingChat = false;
2000
+ this.showActionsMenu = false;
2001
+ }
1911
2002
  }
1912
2003
  },
1913
2004
  computed: {
@@ -1916,6 +2007,7 @@ module.exports = app => app.component('dashboard', {
1916
2007
  }
1917
2008
  },
1918
2009
  mounted: async function() {
2010
+ document.addEventListener('click', this.handleDocumentClick);
1919
2011
  this.showEditor = this.$route.query.edit;
1920
2012
  const { dashboard, dashboardResults, error } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: false });
1921
2013
  if (!dashboard) {
@@ -1931,7 +2023,10 @@ module.exports = app => app.component('dashboard', {
1931
2023
  return;
1932
2024
  }
1933
2025
  this.status = 'loaded';
1934
- }
2026
+ },
2027
+ beforeDestroy() {
2028
+ document.removeEventListener('click', this.handleDocumentClick);
2029
+ },
1935
2030
  });
1936
2031
 
1937
2032
 
@@ -16939,7 +17034,7 @@ module.exports = function stringToParts(str) {
16939
17034
  /***/ ((module) => {
16940
17035
 
16941
17036
  "use strict";
16942
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.12","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"},"license":"Apache-2.0","dependencies":{"@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","@ai-sdk/anthropic":"2.x","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"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":"9.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"}}');
17037
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.13","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"},"license":"Apache-2.0","dependencies":{"@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","@ai-sdk/anthropic":"2.x","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"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":"9.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"}}');
16943
17038
 
16944
17039
  /***/ })
16945
17040
 
@@ -1016,6 +1016,10 @@ video {
1016
1016
  width: 1rem;
1017
1017
  }
1018
1018
 
1019
+ .w-44 {
1020
+ width: 11rem;
1021
+ }
1022
+
1019
1023
  .w-48 {
1020
1024
  width: 12rem;
1021
1025
  }
@@ -1364,6 +1368,10 @@ video {
1364
1368
  overflow-wrap: break-word;
1365
1369
  }
1366
1370
 
1371
+ .break-all {
1372
+ word-break: break-all;
1373
+ }
1374
+
1367
1375
  .rounded {
1368
1376
  border-radius: 0.25rem;
1369
1377
  }
@@ -2698,6 +2706,11 @@ video {
2698
2706
  color: rgb(209 213 219 / var(--tw-text-opacity));
2699
2707
  }
2700
2708
 
2709
+ .disabled\:text-gray-400:disabled {
2710
+ --tw-text-opacity: 1;
2711
+ color: rgb(156 163 175 / var(--tw-text-opacity));
2712
+ }
2713
+
2701
2714
  .disabled\:opacity-50:disabled {
2702
2715
  opacity: 0.5;
2703
2716
  }
@@ -8,7 +8,12 @@
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" @copyMessage="copyMessage"></chat-message-script>
11
+ <chat-message-script
12
+ :message="message"
13
+ :script="part.content"
14
+ :language="part.language"
15
+ :target-dashboard-id="targetDashboardId"
16
+ @copyMessage="copyMessage"></chat-message-script>
12
17
  </div>
13
18
  </div>
14
19
  </div>
@@ -7,7 +7,7 @@ const template = require('./chat-message.html');
7
7
 
8
8
  module.exports = app => app.component('chat-message', {
9
9
  template: template,
10
- props: ['message'],
10
+ props: ['message', 'targetDashboardId'],
11
11
  computed: {
12
12
  styleForMessage() {
13
13
  return this.message.role === 'user' ? 'p-3 bg-gray-100' : 'py-3 pr-3';
@@ -59,6 +59,12 @@
59
59
  @click="openCreateDashboardModal(); showDropdown = false">
60
60
  Create Dashboard
61
61
  </button>
62
+ <button
63
+ v-if="canOverwriteDashboard"
64
+ class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
65
+ @click="openOverwriteDashboardConfirmation(); showDropdown = false">
66
+ Overwrite Dashboard
67
+ </button>
62
68
  <button
63
69
  class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
64
70
  @click="$emit('copyMessage'); showDropdown = false">
@@ -138,7 +144,7 @@
138
144
  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">
139
145
  Submit
140
146
  </async-button>
141
- <div v-if="createErrors.length > 0" class="rounded-md bg-red-50 p-4 mt-1">
147
+ <div v-if="createError" class="rounded-md bg-red-50 p-4 mt-1">
142
148
  <div class="flex">
143
149
  <div class="flex-shrink-0">
144
150
  <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
@@ -156,4 +162,48 @@
156
162
  </div>
157
163
  </template>
158
164
  </modal>
165
+ <modal v-if="showOverwriteDashboardConfirmationModal">
166
+ <template #body>
167
+ <div class="modal-exit" @click="showOverwriteDashboardConfirmationModal = false">&times;</div>
168
+ <div>
169
+ <div class="mt-4 text-gray-900 font-semibold">Overwrite Dashboard</div>
170
+ <p class="mt-2 text-sm text-gray-700">
171
+ This will replace the linked dashboard's code with the script below.
172
+ </p>
173
+ <p class="mt-1 text-xs text-gray-600 break-all" v-if="targetDashboardId">
174
+ Dashboard ID: {{ targetDashboardId }}
175
+ </p>
176
+ <div class="my-4 border border-gray-200 bg-gray-50 rounded">
177
+ <pre class="p-2 h-[300px] overflow-auto whitespace-pre-wrap text-xs">{{ overwriteDashboardCode }}</pre>
178
+ </div>
179
+ <div class="flex items-center gap-2">
180
+ <async-button
181
+ @click="confirmOverwriteDashboard"
182
+ 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">
183
+ Confirm Overwrite
184
+ </async-button>
185
+ <button
186
+ class="px-2.5 py-1.5 rounded-md text-sm font-semibold text-gray-700 bg-gray-200 hover:bg-gray-300"
187
+ @click="showOverwriteDashboardConfirmationModal = false">
188
+ Cancel
189
+ </button>
190
+ </div>
191
+ <div v-if="overwriteError" class="rounded-md bg-red-50 p-4 mt-3">
192
+ <div class="flex">
193
+ <div class="flex-shrink-0">
194
+ <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
195
+ <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" />
196
+ </svg>
197
+ </div>
198
+ <div class="ml-3">
199
+ <h3 class="text-sm font-medium text-red-800">Error</h3>
200
+ <div class="mt-2 text-sm text-red-700">
201
+ {{overwriteError}}
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </template>
208
+ </modal>
159
209
  </div>
@@ -7,13 +7,14 @@ const vanillatoasts = require('vanillatoasts');
7
7
 
8
8
  module.exports = app => app.component('chat-message-script', {
9
9
  template,
10
- props: ['message', 'script', 'language'],
10
+ props: ['message', 'script', 'language', 'targetDashboardId'],
11
11
  emits: ['copyMessage'],
12
12
  data() {
13
13
  return {
14
14
  activeTab: 'code',
15
15
  showDetailModal: false,
16
16
  showCreateDashboardModal: false,
17
+ showOverwriteDashboardConfirmationModal: false,
17
18
  showDropdown: false,
18
19
  newDashboardTitle: '',
19
20
  dashboardCode: '',
@@ -21,12 +22,17 @@ module.exports = app => app.component('chat-message-script', {
21
22
  dashboardEditor: null,
22
23
  isEditing: false,
23
24
  codeEditor: null,
24
- editedScript: null
25
+ editedScript: null,
26
+ overwriteDashboardCode: '',
27
+ overwriteError: null
25
28
  };
26
29
  },
27
30
  computed: {
28
31
  styleForMessage() {
29
32
  return this.message.role === 'user' ? 'bg-gray-100' : '';
33
+ },
34
+ canOverwriteDashboard() {
35
+ return !!this.targetDashboardId;
30
36
  }
31
37
  },
32
38
  methods: {
@@ -58,7 +64,7 @@ module.exports = app => app.component('chat-message-script', {
58
64
  openCreateDashboardModal() {
59
65
  this.newDashboardTitle = '';
60
66
  this.dashboardCode = this.script;
61
- this.createErrors = [];
67
+ this.createError = null;
62
68
  this.showCreateDashboardModal = true;
63
69
  this.$nextTick(() => {
64
70
  if (this.dashboardEditor) {
@@ -74,6 +80,14 @@ module.exports = app => app.component('chat-message-script', {
74
80
  });
75
81
  });
76
82
  },
83
+ openOverwriteDashboardConfirmation() {
84
+ if (!this.canOverwriteDashboard) {
85
+ return;
86
+ }
87
+ this.overwriteDashboardCode = this.codeEditor?.getValue?.() ?? this.script;
88
+ this.overwriteError = null;
89
+ this.showOverwriteDashboardConfirmationModal = true;
90
+ },
77
91
  toggleDropdown() {
78
92
  this.showDropdown = !this.showDropdown;
79
93
  },
@@ -146,6 +160,32 @@ module.exports = app => app.component('chat-message-script', {
146
160
  this.showCreateDashboardModal = false;
147
161
  this.$router.push('/dashboard/' + dashboard._id);
148
162
  },
163
+ async confirmOverwriteDashboard() {
164
+ if (!this.canOverwriteDashboard) {
165
+ this.overwriteError = 'This chat is not linked to a dashboard.';
166
+ return;
167
+ }
168
+
169
+ this.overwriteDashboardCode = this.codeEditor?.getValue?.() ?? this.script;
170
+
171
+ const params = {
172
+ dashboardId: this.targetDashboardId,
173
+ code: this.overwriteDashboardCode
174
+ };
175
+
176
+ const { doc } = await api.Dashboard.updateDashboard(params).catch(err => {
177
+ if (err.response?.data?.message) {
178
+ const message = err.response.data.message.split(': ').slice(1).join(': ');
179
+ this.overwriteError = message;
180
+ throw new Error(err.response?.data?.message);
181
+ }
182
+ throw err;
183
+ });
184
+
185
+ this.overwriteError = null;
186
+ this.showOverwriteDashboardConfirmationModal = false;
187
+ this.$router.push('/dashboard/' + doc._id);
188
+ },
149
189
  async copyOutput() {
150
190
  const executionResult = this.message.executionResult || {};
151
191
  let output = executionResult.output;
@@ -61,7 +61,7 @@
61
61
  </div>
62
62
  </div>
63
63
  <li v-for="message in chatMessages" :key="message._id">
64
- <chat-message :message="message"></chat-message>
64
+ <chat-message :message="message" :target-dashboard-id="currentThread?.dashboardId"></chat-message>
65
65
  </li>
66
66
  </ul>
67
67
  </div>
@@ -23,6 +23,39 @@
23
23
  <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>
24
24
  Evaluate
25
25
  </async-button>
26
+ <div class="relative" ref="actionsMenu">
27
+ <button
28
+ type="button"
29
+ @click.stop="toggleActionsMenu"
30
+ class="flex items-center rounded-md bg-gray-100 px-3 py-1.5 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500"
31
+ aria-haspopup="true"
32
+ :aria-expanded="showActionsMenu"
33
+ >
34
+ More
35
+ <svg class="ml-1 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
36
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m6 9 6 6 6-6" />
37
+ </svg>
38
+ </button>
39
+ <div
40
+ v-if="showActionsMenu"
41
+ class="absolute right-0 mt-2 w-44 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black/5 z-20"
42
+ role="menu"
43
+ aria-label="Dashboard actions"
44
+ >
45
+ <button
46
+ type="button"
47
+ :disabled="startingChat"
48
+ @click.stop="startChatWithDashboard"
49
+ class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-gray-700 hover:bg-ultramarine-100 disabled:cursor-not-allowed disabled:text-gray-400"
50
+ role="menuitem"
51
+ >
52
+ <svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
53
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 10h.01M12 10h.01M16 10h.01m-8 4h8m-9 5 10-3.333L7 12.333 3 14l.667-4.333L9 7l4-3 4.333 2.667L21 11l-3 3.667V19a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-1z" />
54
+ </svg>
55
+ Start Chat
56
+ </button>
57
+ </div>
58
+ </div>
26
59
  </div>
27
60
  </div>
28
61
  <div v-if="!showEditor" class="mt-4 mb-4">
@@ -16,7 +16,9 @@ module.exports = app => app.component('dashboard', {
16
16
  dashboard: null,
17
17
  dashboardResults: [],
18
18
  errorMessage: null,
19
- showDetailModal: false
19
+ showDetailModal: false,
20
+ startingChat: false,
21
+ showActionsMenu: false
20
22
  };
21
23
  },
22
24
  methods: {
@@ -63,6 +65,55 @@ module.exports = app => app.component('dashboard', {
63
65
  }
64
66
 
65
67
  return finishedAt < sixHoursAgo;
68
+ },
69
+ toggleActionsMenu() {
70
+ this.showActionsMenu = !this.showActionsMenu;
71
+ },
72
+ closeActionsMenu() {
73
+ this.showActionsMenu = false;
74
+ },
75
+ handleDocumentClick(ev) {
76
+ if (!this.showActionsMenu) {
77
+ return;
78
+ }
79
+ const menuEl = this.$refs.actionsMenu;
80
+ if (!menuEl) {
81
+ return;
82
+ }
83
+ if (!menuEl.contains(ev.target)) {
84
+ this.closeActionsMenu();
85
+ }
86
+ },
87
+ async startChatWithDashboard() {
88
+ if (this.startingChat) {
89
+ return;
90
+ }
91
+
92
+ this.startingChat = true;
93
+ try {
94
+ const description = this.description?.trim();
95
+ const parts = [
96
+ 'I want to edit this dashboard.',
97
+ `Title: ${this.title || 'Untitled Dashboard'}`
98
+ ];
99
+
100
+ if (description) {
101
+ parts.push(`Description: ${description}`);
102
+ }
103
+
104
+ parts.push('Here is the current dashboard code:', '```javascript', this.code, '```');
105
+
106
+ const initialMessage = parts.join('\n\n');
107
+
108
+ const { chatThread } = await api.ChatThread.createChatThread({
109
+ initialMessage,
110
+ dashboardId: this.dashboard?._id
111
+ });
112
+ this.$router.push('/chat/' + chatThread._id);
113
+ } finally {
114
+ this.startingChat = false;
115
+ this.showActionsMenu = false;
116
+ }
66
117
  }
67
118
  },
68
119
  computed: {
@@ -71,6 +122,7 @@ module.exports = app => app.component('dashboard', {
71
122
  }
72
123
  },
73
124
  mounted: async function() {
125
+ document.addEventListener('click', this.handleDocumentClick);
74
126
  this.showEditor = this.$route.query.edit;
75
127
  const { dashboard, dashboardResults, error } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: false });
76
128
  if (!dashboard) {
@@ -86,5 +138,8 @@ module.exports = app => app.component('dashboard', {
86
138
  return;
87
139
  }
88
140
  this.status = 'loaded';
89
- }
141
+ },
142
+ beforeDestroy() {
143
+ document.removeEventListener('click', this.handleDocumentClick);
144
+ },
90
145
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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": {