@mongoosejs/studio 0.2.13 → 0.3.0

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.
Files changed (81) hide show
  1. package/backend/actions/ChatMessage/executeScript.js +5 -1
  2. package/backend/actions/ChatThread/createChatMessage.js +2 -1
  3. package/backend/actions/ChatThread/streamChatMessage.js +2 -2
  4. package/eslint.config.js +4 -1
  5. package/frontend/public/app.js +24642 -543
  6. package/frontend/public/dark-theme.css +365 -0
  7. package/frontend/public/images/mongoose-studio.svg +4 -0
  8. package/frontend/public/index.html +21 -1
  9. package/frontend/public/style.css +5 -7
  10. package/frontend/public/theme-variables.css +294 -0
  11. package/frontend/public/tw.css +305 -252
  12. package/frontend/src/ace-editor/ace-editor.html +4 -0
  13. package/frontend/src/ace-editor/ace-editor.js +89 -0
  14. package/frontend/src/aceEditor.js +69 -0
  15. package/frontend/src/chat/chat-message/chat-message.html +1 -1
  16. package/frontend/src/chat/chat-message/chat-message.js +1 -1
  17. package/frontend/src/chat/chat-message-script/chat-message-script.html +51 -34
  18. package/frontend/src/chat/chat-message-script/chat-message-script.js +12 -55
  19. package/frontend/src/chat/chat.html +68 -39
  20. package/frontend/src/chat/chat.js +26 -2
  21. package/frontend/src/clone-document/clone-document.html +7 -2
  22. package/frontend/src/clone-document/clone-document.js +1 -8
  23. package/frontend/src/create-dashboard/create-dashboard.html +11 -6
  24. package/frontend/src/create-dashboard/create-dashboard.js +0 -7
  25. package/frontend/src/create-document/create-document.html +15 -9
  26. package/frontend/src/create-document/create-document.js +5 -12
  27. package/frontend/src/dashboard/dashboard.html +14 -12
  28. package/frontend/src/dashboard/dashboard.js +12 -4
  29. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.html +13 -7
  30. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +13 -21
  31. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html +19 -17
  32. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js +97 -2
  33. package/frontend/src/dashboard-result/dashboard-map/dashboard-map.js +27 -3
  34. package/frontend/src/dashboard-result/dashboard-result.html +3 -3
  35. package/frontend/src/dashboards/dashboards.html +101 -109
  36. package/frontend/src/dashboards/dashboards.js +25 -1
  37. package/frontend/src/detail-default/detail-default.html +2 -2
  38. package/frontend/src/detail-default/detail-default.js +24 -3
  39. package/frontend/src/document/confirm-changes/confirm-changes.html +1 -1
  40. package/frontend/src/document/confirm-delete/confirm-delete.html +1 -1
  41. package/frontend/src/document/document.css +1 -1
  42. package/frontend/src/document/document.html +28 -28
  43. package/frontend/src/document/execute-script/execute-script.html +20 -21
  44. package/frontend/src/document/execute-script/execute-script.js +1 -43
  45. package/frontend/src/document-details/document-details.css +4 -9
  46. package/frontend/src/document-details/document-details.html +34 -33
  47. package/frontend/src/document-details/document-details.js +2 -53
  48. package/frontend/src/document-details/document-property/document-property.html +12 -12
  49. package/frontend/src/edit-array/edit-array.html +7 -6
  50. package/frontend/src/edit-array/edit-array.js +10 -50
  51. package/frontend/src/edit-boolean/edit-boolean.html +12 -12
  52. package/frontend/src/edit-date/edit-date.html +2 -2
  53. package/frontend/src/edit-default/edit-default.html +1 -1
  54. package/frontend/src/edit-string/edit-string.html +3 -3
  55. package/frontend/src/edit-subdocument/edit-subdocument.html +5 -3
  56. package/frontend/src/edit-subdocument/edit-subdocument.js +1 -15
  57. package/frontend/src/export-query-results/export-query-results.html +3 -3
  58. package/frontend/src/json-node/json-node.html +3 -3
  59. package/frontend/src/list-json/json-node.html +1 -1
  60. package/frontend/src/models/document-search/document-search.html +3 -3
  61. package/frontend/src/models/model-switcher/model-switcher.html +53 -0
  62. package/frontend/src/models/model-switcher/model-switcher.js +123 -0
  63. package/frontend/src/models/models.css +3 -10
  64. package/frontend/src/models/models.html +146 -80
  65. package/frontend/src/models/models.js +108 -4
  66. package/frontend/src/navbar/navbar.html +157 -97
  67. package/frontend/src/navbar/navbar.js +31 -12
  68. package/frontend/src/routes.js +1 -1
  69. package/frontend/src/splash/splash.html +5 -5
  70. package/frontend/src/task-single/task-single.html +29 -29
  71. package/frontend/src/task-single/task-single.js +10 -10
  72. package/frontend/src/tasks/task-details/task-details.html +38 -38
  73. package/frontend/src/tasks/task-details/task-details.js +7 -2
  74. package/frontend/src/tasks/tasks.html +36 -35
  75. package/frontend/src/tasks/tasks.js +2 -25
  76. package/frontend/src/team/new-invitation/new-invitation.html +8 -8
  77. package/frontend/src/team/team.html +27 -27
  78. package/frontend/src/update-document/update-document.html +7 -2
  79. package/frontend/src/update-document/update-document.js +2 -11
  80. package/package.json +2 -1
  81. package/tailwind.config.js +75 -11
@@ -0,0 +1,4 @@
1
+ <div
2
+ ref="container"
3
+ class="ace-editor-container w-full min-h-[120px] border border-edge-strong rounded-md focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-transparent font-mono"
4
+ ></div>
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ const template = require('./ace-editor.html');
4
+ const { createAceEditor, destroyAceEditor } = require('../aceEditor');
5
+
6
+ module.exports = app => app.component('ace-editor', {
7
+ template,
8
+ props: {
9
+ modelValue: {
10
+ type: String,
11
+ default: ''
12
+ },
13
+ // Support :value for explicit binding
14
+ value: {
15
+ type: String,
16
+ default: ''
17
+ },
18
+ mode: {
19
+ type: String,
20
+ default: 'javascript',
21
+ validator: (v) => ['javascript', 'json'].includes(v)
22
+ },
23
+ lineNumbers: {
24
+ type: Boolean,
25
+ default: true
26
+ },
27
+ readOnly: {
28
+ type: Boolean,
29
+ default: false
30
+ },
31
+ wrap: {
32
+ type: Boolean,
33
+ default: false
34
+ },
35
+ minLines: { type: Number, default: null },
36
+ maxLines: { type: Number, default: null }
37
+ },
38
+ emits: ['input', 'update:modelValue'],
39
+ data() {
40
+ return { editor: null };
41
+ },
42
+ mounted() {
43
+ this.$nextTick(() => {
44
+ const container = this.$refs.container;
45
+ if (!container) return;
46
+ this.editor = createAceEditor(container, {
47
+ value: this.modelValue !== '' ? this.modelValue : this.value,
48
+ mode: this.mode,
49
+ lineNumbers: this.lineNumbers,
50
+ readOnly: this.readOnly,
51
+ wrap: this.wrap,
52
+ minLines: this.minLines,
53
+ maxLines: this.maxLines
54
+ });
55
+ this.editor.session.on('change', () => {
56
+ const val = this.editor.getValue();
57
+ this.$emit('input', val);
58
+ this.$emit('update:modelValue', val);
59
+ });
60
+ });
61
+ },
62
+ beforeDestroy() {
63
+ if (this.editor) {
64
+ destroyAceEditor(this.editor);
65
+ this.editor = null;
66
+ }
67
+ },
68
+ watch: {
69
+ modelValue(newVal) {
70
+ const val = newVal ?? '';
71
+ if (this.editor && this.editor.getValue() !== val) {
72
+ this.editor.setValue(val, -1);
73
+ }
74
+ },
75
+ value(newVal) {
76
+ const val = newVal ?? '';
77
+ if (this.editor && this.editor.getValue() !== val) {
78
+ this.editor.setValue(val, -1);
79
+ }
80
+ }
81
+ },
82
+ methods: {
83
+ setValue(val) {
84
+ if (this.editor) {
85
+ this.editor.setValue(val ?? '', -1);
86
+ }
87
+ }
88
+ }
89
+ });
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+
3
+ const ace = require('ace-builds');
4
+ require('ace-builds/src-noconflict/mode-javascript');
5
+ require('ace-builds/src-noconflict/mode-json');
6
+ require('ace-builds/src-noconflict/theme-chrome');
7
+ require('ace-builds/src-noconflict/theme-one_dark');
8
+
9
+ const LIGHT_THEME = 'ace/theme/chrome';
10
+ const DARK_THEME = 'ace/theme/one_dark';
11
+
12
+ function isDarkMode() {
13
+ return typeof document !== 'undefined' && document.documentElement.classList.contains('dark');
14
+ }
15
+
16
+ /**
17
+ * Create an Ace editor on a container element (div). The container must have
18
+ * explicit dimensions (e.g. height/min-height and width).
19
+ * @param {HTMLElement} container - A div element to attach the editor to
20
+ * @param {Object} options - { value: string, mode: 'javascript'|'json', lineNumbers: boolean, ... }
21
+ * @returns {ace.Ace.Editor} The Ace editor instance
22
+ */
23
+ function createAceEditor(container, options = {}) {
24
+ const {
25
+ value = '',
26
+ mode = 'javascript',
27
+ lineNumbers = true,
28
+ minLines,
29
+ maxLines,
30
+ readOnly = false,
31
+ wrap = false
32
+ } = options;
33
+
34
+ const editor = ace.edit(container);
35
+ editor.setTheme(isDarkMode() ? DARK_THEME : LIGHT_THEME);
36
+ editor.session.setMode(mode === 'json' ? 'ace/mode/json' : 'ace/mode/javascript');
37
+ editor.setValue(value, -1);
38
+ editor.setOptions({
39
+ showLineNumbers: lineNumbers,
40
+ readOnly,
41
+ wrap
42
+ });
43
+ if (minLines != null) editor.setOption('minLines', minLines);
44
+ if (maxLines != null) editor.setOption('maxLines', maxLines);
45
+
46
+ // Listen for theme toggles
47
+ const onThemeChanged = (e) => {
48
+ editor.setTheme(e.detail?.dark ? DARK_THEME : LIGHT_THEME);
49
+ };
50
+ document.documentElement.addEventListener('studio-theme-changed', onThemeChanged);
51
+ editor._studioThemeHandler = onThemeChanged;
52
+
53
+ return editor;
54
+ }
55
+
56
+ /**
57
+ * Destroy an Ace editor and release resources.
58
+ * @param {ace.Ace.Editor|null} editor - The editor instance from createAceEditor
59
+ */
60
+ function destroyAceEditor(editor) {
61
+ if (editor) {
62
+ if (editor._studioThemeHandler) {
63
+ document.documentElement.removeEventListener('studio-theme-changed', editor._studioThemeHandler);
64
+ }
65
+ editor.destroy();
66
+ }
67
+ }
68
+
69
+ module.exports = { createAceEditor, destroyAceEditor };
@@ -3,7 +3,7 @@
3
3
  class="min-w-0 max-w-[calc(100vw-3rem)] lg:max-w-[calc(100vw-15rem)]"
4
4
  :class="{'text-right': message.role === 'user'}">
5
5
 
6
- <div class="text-sm text-gray-900 rounded-md inline-block relative" :class="styleForMessage">
6
+ <div class="text-sm text-content rounded-md inline-block relative" :class="styleForMessage">
7
7
  <div v-for="part in contentSplitByScripts">
8
8
  <div v-if="part.type === 'text'" v-html="marked(part.content)">
9
9
  </div>
@@ -9,7 +9,7 @@ module.exports = app => app.component('chat-message', {
9
9
  props: ['message', 'targetDashboardId'],
10
10
  computed: {
11
11
  styleForMessage() {
12
- return this.message.role === 'user' ? 'p-3 bg-gray-100' : 'py-3 pr-3';
12
+ return this.message.role === 'user' ? 'p-3 bg-muted' : 'py-3 pr-3';
13
13
  },
14
14
  contentSplitByScripts() {
15
15
  const content = this.message.content;
@@ -1,39 +1,46 @@
1
- <div class="relative border rounded bg-white my-1 text-black text-sm overflow-hidden">
2
- <div class="flex border-b py-1 pl-1 text-xs font-medium bg-white">
1
+ <div class="chat-message-script relative border rounded bg-surface my-1 text-content text-sm overflow-hidden">
2
+ <div class="flex border-b py-1 pl-1 text-xs font-medium bg-surface">
3
3
  <button
4
- class="px-4 py-1 border-r border-gray-300 text-gray-700 font-semibold transition-colors duration-200 focus:outline-none"
4
+ class="px-4 py-1 border-r border-edge-strong text-content-secondary font-semibold transition-colors duration-200 focus:outline-none"
5
5
  :class="[
6
6
  'rounded-l-md',
7
7
  activeTab === 'code'
8
8
  ? 'bg-gray-700 text-white shadow'
9
- : 'bg-gray-100 hover:bg-gray-200 text-gray-600'
9
+ : 'bg-muted hover:bg-muted text-gray-600'
10
10
  ]"
11
11
  @click="activeTab = 'code'">
12
12
  Code
13
13
  </button>
14
14
  <button
15
- class="px-4 py-1 text-gray-700 font-semibold transition-colors duration-200 focus:outline-none"
15
+ class="px-4 py-1 text-content-secondary font-semibold transition-colors duration-200 focus:outline-none"
16
16
  :class="[
17
17
  'rounded-r-md',
18
18
  activeTab === 'output'
19
19
  ? 'bg-gray-700 text-white shadow'
20
- : 'bg-gray-100 hover:bg-gray-200 text-gray-600'
20
+ : 'bg-muted hover:bg-muted text-gray-600'
21
21
  ]"
22
22
  @click="activeTab = 'output'">
23
23
  Output
24
24
  </button>
25
- <div class="ml-auto mr-1 flex">
25
+ <div class="ml-auto mr-1 flex items-center">
26
+ <button
27
+ v-if="activeTab === 'output' && isChartOutput"
28
+ class="px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center"
29
+ @click="exportChartPNG"
30
+ title="Export PNG">
31
+ <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.2em;" viewBox="0 -960 960 960" fill="currentColor"><path d="M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
32
+ </button>
26
33
  <button
27
34
  v-if="activeTab === 'output'"
28
- 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"
35
+ class="px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center"
29
36
  @click="openDetailModal">
30
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
37
+ <svg xmlns="http://www.w3.org/2000/svg" style="height: 1.2em;" fill="none" viewBox="0 0 24 24" stroke="currentColor">
31
38
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5" />
32
39
  </svg>
33
40
  </button>
34
41
  <button
35
42
  v-if="activeTab === 'code' && !isEditing"
36
- 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"
43
+ class="px-2 py-1 mr-1 text-xs bg-page0 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center"
37
44
  @click.stop="startEditing">
38
45
  <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
39
46
  <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" />
@@ -41,7 +48,7 @@
41
48
  </button>
42
49
  <async-button
43
50
  v-if="!isEditing"
44
- class="px-2 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white border-none rounded cursor-pointer transition-colors disabled:bg-gray-400"
51
+ class="px-2 py-1 text-xs bg-primary hover:bg-primary-hover text-primary-text border-none rounded cursor-pointer transition-colors disabled:bg-gray-400"
45
52
  @click="executeScript">
46
53
  Run
47
54
  </async-button>
@@ -55,25 +62,25 @@
55
62
  </button>
56
63
  <div
57
64
  v-if="showDropdown"
58
- 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">
65
+ class="absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5">
59
66
  <button
60
- class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
67
+ class="block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted"
61
68
  @click="openCreateDashboardModal(); showDropdown = false">
62
69
  Create Dashboard
63
70
  </button>
64
71
  <button
65
- class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
72
+ class="block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted"
66
73
  @click="copyOutput(); showDropdown = false">
67
74
  Copy Output As Text
68
75
  </button>
69
76
  <button
70
77
  v-if="canOverwriteDashboard"
71
- class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
78
+ class="block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted"
72
79
  @click="openOverwriteDashboardConfirmation(); showDropdown = false">
73
80
  Overwrite Dashboard
74
81
  </button>
75
82
  <button
76
- class="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100"
83
+ class="block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted"
77
84
  @click="$emit('copyMessage'); showDropdown = false">
78
85
  Copy Full Message
79
86
  </button>
@@ -83,9 +90,14 @@
83
90
  </div>
84
91
 
85
92
  <div class="p-0 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto" v-show="activeTab === 'code'">
86
- <div v-if="isEditing" class="flex flex-col space-y-2">
87
- <div class="border border-gray-200">
88
- <textarea ref="scriptEditor" class="w-full h-[45vh]" @input="handleScriptInput"></textarea>
93
+ <div v-if="isEditing" class="flex flex-col space-y-2 chat-script-editor-wrap">
94
+ <div class="border border-edge">
95
+ <ace-editor
96
+ v-model="editedScript"
97
+ mode="javascript"
98
+ :line-numbers="true"
99
+ class="w-full h-[45vh] min-h-[200px]"
100
+ />
89
101
  </div>
90
102
  <div class="flex justify-end gap-2 pb-2">
91
103
  <button
@@ -100,17 +112,17 @@
100
112
  </async-button>
101
113
  </div>
102
114
  </div>
103
- <pre v-else class="whitespace-pre-wrap !my-0 !bg-zinc-50"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
115
+ <pre v-else class="whitespace-pre-wrap !my-0 bg-muted"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
104
116
  </div>
105
117
 
106
- <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'">
107
- <dashboard-chart v-if="message.executionResult?.output?.$chart" :value="message.executionResult?.output" />
118
+ <div class="p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-surface border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative" v-show="activeTab === 'output'">
119
+ <dashboard-chart v-if="message.executionResult?.output?.$chart" :value="message.executionResult?.output" ref="chartOutput" />
108
120
  <dashboard-map v-else-if="message.executionResult?.output?.$featureCollection" :value="message.executionResult?.output" />
109
121
  <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>
110
122
 
111
- <div v-if="message.executionResult?.logs?.length" class="mt-3 pt-3 border-t border-gray-200">
123
+ <div v-if="message.executionResult?.logs?.length" class="mt-3 pt-3 border-t border-edge">
112
124
  <div class="text-xs font-semibold text-gray-600 uppercase tracking-wide">Console</div>
113
- <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>
125
+ <pre class="mt-1 bg-muted text-content p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]">{{ message.executionResult.logs }}</pre>
114
126
  </div>
115
127
  </div>
116
128
 
@@ -131,19 +143,24 @@
131
143
  <template #body>
132
144
  <div class="modal-exit" @click="showCreateDashboardModal = false">&times;</div>
133
145
  <div>
134
- <div class="mt-4 text-gray-900 font-semibold">Create Dashboard</div>
146
+ <div class="mt-4 text-content font-semibold">Create Dashboard</div>
135
147
  <div class="mt-4">
136
- <label class="block text-sm font-medium leading-6 text-gray-900">Title</label>
148
+ <label class="block text-sm font-medium leading-6 text-content">Title</label>
137
149
  <div class="mt-2">
138
150
  <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">
139
- <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">
151
+ <input type="text" v-model="newDashboardTitle" class="outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-content placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6" placeholder="My Dashboard">
140
152
  </div>
141
153
  </div>
142
154
  </div>
143
155
  <div class="my-4">
144
- <label class="block text-sm font-medium leading-6 text-gray-900">Code</label>
145
- <div class="border border-gray-200">
146
- <textarea class="p-2 h-[300px] w-full" ref="dashboardCodeEditor"></textarea>
156
+ <label class="block text-sm font-medium leading-6 text-content">Code</label>
157
+ <div class="border border-edge">
158
+ <ace-editor
159
+ v-model="dashboardCode"
160
+ mode="javascript"
161
+ :line-numbers="true"
162
+ class="h-[300px] w-full"
163
+ />
147
164
  </div>
148
165
  </div>
149
166
  <async-button
@@ -173,14 +190,14 @@
173
190
  <template #body>
174
191
  <div class="modal-exit" @click="showOverwriteDashboardConfirmationModal = false">&times;</div>
175
192
  <div>
176
- <div class="mt-4 text-gray-900 font-semibold">Overwrite Dashboard</div>
177
- <p class="mt-2 text-sm text-gray-700">
193
+ <div class="mt-4 text-content font-semibold">Overwrite Dashboard</div>
194
+ <p class="mt-2 text-sm text-content-secondary">
178
195
  This will replace the linked dashboard's code with the script below.
179
196
  </p>
180
197
  <p class="mt-1 text-xs text-gray-600 break-all" v-if="targetDashboardId">
181
198
  Dashboard ID: {{ targetDashboardId }}
182
199
  </p>
183
- <div class="my-4 border border-gray-200 bg-gray-50 rounded">
200
+ <div class="my-4 border border-edge bg-page rounded">
184
201
  <pre class="p-2 h-[300px] overflow-auto whitespace-pre-wrap text-xs">{{ overwriteDashboardCode }}</pre>
185
202
  </div>
186
203
  <div class="flex items-center gap-2">
@@ -190,7 +207,7 @@
190
207
  Confirm Overwrite
191
208
  </async-button>
192
209
  <button
193
- class="px-2.5 py-1.5 rounded-md text-sm font-semibold text-gray-700 bg-gray-200 hover:bg-gray-300"
210
+ class="px-2.5 py-1.5 rounded-md text-sm font-semibold text-content-secondary bg-gray-200 hover:bg-gray-300"
194
211
  @click="showOverwriteDashboardConfirmationModal = false">
195
212
  Cancel
196
213
  </button>
@@ -1,4 +1,4 @@
1
- /* global CodeMirror, Prism */
1
+ /* global Prism */
2
2
  'use strict';
3
3
 
4
4
  const api = require('../../api');
@@ -18,9 +18,7 @@ module.exports = app => app.component('chat-message-script', {
18
18
  newDashboardTitle: '',
19
19
  dashboardCode: '',
20
20
  createError: null,
21
- dashboardEditor: null,
22
21
  isEditing: false,
23
- codeEditor: null,
24
22
  editedScript: null,
25
23
  overwriteDashboardCode: '',
26
24
  overwriteError: null
@@ -28,18 +26,21 @@ module.exports = app => app.component('chat-message-script', {
28
26
  },
29
27
  computed: {
30
28
  styleForMessage() {
31
- return this.message.role === 'user' ? 'bg-gray-100' : '';
29
+ return this.message.role === 'user' ? 'bg-muted' : '';
32
30
  },
33
31
  canOverwriteDashboard() {
34
32
  return !!this.targetDashboardId;
33
+ },
34
+ isChartOutput() {
35
+ return !!this.message.executionResult?.output?.$chart;
35
36
  }
36
37
  },
37
38
  methods: {
39
+ exportChartPNG() {
40
+ this.$refs.chartOutput?.exportPNG?.();
41
+ },
38
42
  async executeScript() {
39
- let scriptToRun = this.script;
40
- if (this.isEditing) {
41
- scriptToRun = this.codeEditor ? this.codeEditor.getValue() : this.editedScript;
42
- }
43
+ const scriptToRun = this.isEditing ? this.editedScript : this.script;
43
44
  this.editedScript = scriptToRun;
44
45
  const { chatMessage } = await api.ChatMessage.executeScript({
45
46
  chatMessageId: this.message._id,
@@ -66,25 +67,12 @@ module.exports = app => app.component('chat-message-script', {
66
67
  this.dashboardCode = this.script;
67
68
  this.createError = null;
68
69
  this.showCreateDashboardModal = true;
69
- this.$nextTick(() => {
70
- if (this.dashboardEditor) {
71
- this.dashboardEditor.toTextArea();
72
- }
73
- this.$refs.dashboardCodeEditor.value = this.dashboardCode;
74
- this.dashboardEditor = CodeMirror.fromTextArea(this.$refs.dashboardCodeEditor, {
75
- mode: 'javascript',
76
- lineNumbers: true
77
- });
78
- this.dashboardEditor.on('change', () => {
79
- this.dashboardCode = this.dashboardEditor.getValue();
80
- });
81
- });
82
70
  },
83
71
  openOverwriteDashboardConfirmation() {
84
72
  if (!this.canOverwriteDashboard) {
85
73
  return;
86
74
  }
87
- this.overwriteDashboardCode = this.codeEditor?.getValue?.() ?? this.script;
75
+ this.overwriteDashboardCode = this.isEditing ? this.editedScript : this.script;
88
76
  this.overwriteError = null;
89
77
  this.showOverwriteDashboardConfirmationModal = true;
90
78
  },
@@ -94,39 +82,16 @@ module.exports = app => app.component('chat-message-script', {
94
82
  startEditing() {
95
83
  this.isEditing = true;
96
84
  this.editedScript = this.script;
97
- this.$nextTick(() => {
98
- if (!this.$refs.scriptEditor) {
99
- return;
100
- }
101
- this.$refs.scriptEditor.value = this.editedScript;
102
- if (typeof CodeMirror === 'undefined') {
103
- return;
104
- }
105
- this.destroyCodeMirror();
106
- this.codeEditor = CodeMirror.fromTextArea(this.$refs.scriptEditor, {
107
- mode: 'javascript',
108
- lineNumbers: true,
109
- smartIndent: false
110
- });
111
- });
112
85
  },
113
86
  cancelEditing() {
114
87
  this.isEditing = false;
115
- this.destroyCodeMirror();
116
88
  this.editedScript = this.script;
117
89
  this.highlightCode();
118
90
  },
119
91
  finishEditing() {
120
92
  this.isEditing = false;
121
- this.destroyCodeMirror();
122
93
  this.highlightCode();
123
94
  },
124
- destroyCodeMirror() {
125
- if (this.codeEditor) {
126
- this.codeEditor.toTextArea();
127
- this.codeEditor = null;
128
- }
129
- },
130
95
  handleScriptInput(event) {
131
96
  this.editedScript = event?.target?.value || '';
132
97
  },
@@ -144,9 +109,8 @@ module.exports = app => app.component('chat-message-script', {
144
109
  }
145
110
  },
146
111
  async createDashboardFromScript() {
147
- this.dashboardCode = this.dashboardEditor.getValue();
148
112
  const { dashboard } = await api.Dashboard.createDashboard({
149
- code: this.dashboardCode,
113
+ code: this.dashboardCode || '',
150
114
  title: this.newDashboardTitle
151
115
  }).catch(err => {
152
116
  if (err.response?.data?.message) {
@@ -167,7 +131,7 @@ module.exports = app => app.component('chat-message-script', {
167
131
  return;
168
132
  }
169
133
 
170
- this.overwriteDashboardCode = this.codeEditor?.getValue?.() ?? this.script;
134
+ this.overwriteDashboardCode = this.isEditing ? this.editedScript : this.script;
171
135
 
172
136
  const params = {
173
137
  dashboardId: this.targetDashboardId,
@@ -209,12 +173,6 @@ module.exports = app => app.component('chat-message-script', {
209
173
  }
210
174
  },
211
175
  watch: {
212
- showCreateDashboardModal(val) {
213
- if (!val && this.dashboardEditor) {
214
- this.dashboardEditor.toTextArea();
215
- this.dashboardEditor = null;
216
- }
217
- },
218
176
  script(newScript) {
219
177
  if (!this.isEditing) {
220
178
  this.editedScript = newScript;
@@ -232,7 +190,6 @@ module.exports = app => app.component('chat-message-script', {
232
190
  }
233
191
  },
234
192
  unmounted() {
235
- this.destroyCodeMirror();
236
193
  document.body.removeEventListener('click', this.handleBodyClick);
237
194
  }
238
195
  });