@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
@@ -1,16 +1,18 @@
1
1
  <div class="flex" style="height: calc(100vh - 55px); height: calc(100dvh - 55px)">
2
2
  <button
3
3
  type="button"
4
- class="fixed top-[65px] left-0 cursor-pointer bg-gray-100 rounded-r-md z-10 p-1 lg:hidden"
5
- @click="hideSidebar = false"
6
- v-show="hideSidebar !== false"
7
- aria-label="Open chat history"
8
- title="Open chat history">
9
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" class="w-5 h-5" fill="#5f6368"><path d="M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z"/></svg>
4
+ class="fixed top-[65px] left-0 cursor-pointer bg-muted rounded-r-md p-1"
5
+ :class="hideSidebar === true ? 'z-[10001]' : hideSidebar === false ? 'z-10' : 'z-30 lg:hidden'"
6
+ @click="hideSidebar = hideSidebar === false ? true : false"
7
+ aria-label="Toggle chat history"
8
+ title="Toggle chat history">
9
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-content-tertiary">
10
+ <path stroke-linecap="round" stroke-linejoin="round" d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5" />
11
+ </svg>
10
12
  </button>
11
13
  <button
12
- class="fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white"
13
- :class="hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'"
14
+ class="fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-surface"
15
+ :class="hasWorkspace ? 'text-content-secondary hover:bg-muted' : 'text-gray-300 cursor-not-allowed bg-page'"
14
16
  @click="toggleShareThread"
15
17
  :disabled="!hasWorkspace || !chatThreadId || sharingThread"
16
18
  aria-label="Share thread with workspace"
@@ -21,41 +23,68 @@
21
23
  </button>
22
24
  <!-- Sidebar: Chat Threads -->
23
25
  <aside
24
- class="bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-transform duration-300 ease-in-out z-20 w-64 fixed lg:relative"
25
- :class="hideSidebar === false ? 'translate-x-0' : hideSidebar === true ? '-translate-x-full lg:hidden' : '-translate-x-full lg:translate-x-0'"
26
- style="z-index: 10000">
27
- <div class="flex items-center border-b border-gray-100 w-64 overflow-x-hidden">
28
- <div class="p-1 ml-2 font-bold">Chat Threads</div>
26
+ class="bg-page border-r overflow-hidden h-full transition-all duration-300 ease-in-out z-20 w-64 fixed lg:relative shrink-0 flex flex-col"
27
+ :class="hideSidebar === false ? 'translate-x-0' : hideSidebar === true ? '-translate-x-full lg:translate-x-0 lg:!w-0 lg:!border-r-0' : '-translate-x-full lg:translate-x-0'"
28
+ style="z-index: 1000">
29
+ <!-- Search + New Thread -->
30
+ <div class="p-3 shrink-0">
31
+ <div class="relative flex items-center gap-2">
32
+ <div class="relative flex-1">
33
+ <svg class="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
34
+ <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
35
+ </svg>
36
+ <input
37
+ v-model="threadSearch"
38
+ type="text"
39
+ placeholder="Search threads..."
40
+ @keydown.esc="threadSearch = ''"
41
+ class="w-full rounded-md border border-edge bg-surface py-1.5 pl-8 pr-3 text-sm text-content placeholder:text-gray-400 focus:border-edge-strong focus:outline-none focus:ring-1 focus:ring-gray-300"
42
+ />
43
+ </div>
44
+ <async-button
45
+ @click="createNewThread"
46
+ class="shrink-0 rounded-md border border-edge bg-surface p-1.5 text-content-tertiary hover:bg-muted hover:text-content-secondary"
47
+ title="New thread"
48
+ >
49
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4">
50
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
51
+ </svg>
52
+ </async-button>
53
+ </div>
54
+ </div>
55
+ <!-- Thread list (scrollable) -->
56
+ <nav class="flex-1 overflow-y-auto px-2 pb-2">
57
+ <div class="px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider">Threads</div>
58
+ <div v-if="status === 'loaded' && filteredThreads.length === 0" class="px-2 py-2 text-sm text-content-tertiary">
59
+ <template v-if="threadSearch.trim()">No threads match "{{threadSearch}}"</template>
60
+ <template v-else>No threads yet</template>
61
+ </div>
62
+ <ul role="list">
63
+ <li
64
+ v-for="thread in filteredThreads"
65
+ :key="thread._id"
66
+ @click="selectThread(thread._id)"
67
+ class="rounded-md py-2 px-2 cursor-pointer mb-0.5"
68
+ :class="thread._id === chatThreadId ? 'bg-gray-200 text-content' : 'hover:bg-muted text-content-secondary'"
69
+ >
70
+ <div class="text-sm font-medium truncate">{{ thread.title || 'Untitled Thread' }}</div>
71
+ <div class="text-xs text-gray-400 mt-0.5">{{ formatThreadDate(thread.updatedAt || thread.createdAt) }}</div>
72
+ </li>
73
+ </ul>
74
+ </nav>
75
+ <!-- Bottom toolbar -->
76
+ <div class="shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1">
29
77
  <button
78
+ type="button"
30
79
  @click="hideSidebar = true"
31
- class="ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none lg:hidden"
32
- aria-label="Close sidebar"
80
+ class="rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted"
81
+ title="Hide sidebar"
33
82
  >
34
- <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>
83
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
84
+ <path stroke-linecap="round" stroke-linejoin="round" d="m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5" />
85
+ </svg>
35
86
  </button>
36
87
  </div>
37
- <div class="p-4 w-64">
38
- <async-button
39
- @click="createNewThread"
40
- class="w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
41
- >
42
- Create New Thread
43
- </async-button>
44
- </div>
45
- <div v-if="status === 'loaded' && chatThreads.length === 0" class="p-4 text-sm text-gray-700">
46
- No threads yet
47
- </div>
48
- <ul v-if="status === 'loaded'" class="w-full">
49
- <li
50
- v-for="thread in chatThreads"
51
- :key="thread._id"
52
- @click="selectThread(thread._id)"
53
- class="w-full p-2 hover:bg-ultramarine-100 cursor-pointer text-sm text-gray-700"
54
- :class="{ 'bg-ultramarine-200 text-gray-900': thread._id === chatThreadId }"
55
- >
56
- {{ thread.title || 'Untitled Thread' }}
57
- </li>
58
- </ul>
59
88
  </aside>
60
89
 
61
90
  <!-- Main Chat Area -->
@@ -63,7 +92,7 @@
63
92
  <div class="flex-1 overflow-y-auto p-6 space-y-4" ref="messagesContainer">
64
93
  <div v-if="chatMessages?.length === 0">
65
94
  <div class="flex items-center w-full h-full justify-center py-3 mb-4">
66
- <p class="mx-4 font-bold text-gray-900">
95
+ <p class="mx-4 font-bold text-content">
67
96
  Ask Mongoose Studio to analyze your data or generate a script
68
97
  </p>
69
98
  </div>
@@ -14,7 +14,8 @@ module.exports = {
14
14
  chatThreads: [],
15
15
  chatMessages: [],
16
16
  hideSidebar: null,
17
- sharingThread: false
17
+ sharingThread: false,
18
+ threadSearch: ''
18
19
  }),
19
20
  methods: {
20
21
  async sendMessage() {
@@ -125,13 +126,29 @@ module.exports = {
125
126
  this.$router.push('/chat/' + threadId);
126
127
  },
127
128
  styleForMessage(message) {
128
- return message.role === 'user' ? 'bg-gray-100' : '';
129
+ return message.role === 'user' ? 'bg-muted' : '';
129
130
  },
130
131
  async createNewThread() {
131
132
  const { chatThread } = await api.ChatThread.createChatThread();
132
133
  this.$toast.success('Chat thread created!');
133
134
  this.$router.push('/chat/' + chatThread._id);
134
135
  },
136
+ formatThreadDate(dateStr) {
137
+ if (!dateStr) return '';
138
+ const date = new Date(dateStr);
139
+ const now = new Date();
140
+ const diff = now - date;
141
+ const oneDay = 24 * 60 * 60 * 1000;
142
+ const isToday = date.toDateString() === now.toDateString();
143
+ const isYesterday = new Date(now - oneDay).toDateString() === date.toDateString();
144
+ const timeStr = date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
145
+ if (isToday) return 'Today, ' + timeStr;
146
+ if (isYesterday) return 'Yesterday, ' + timeStr;
147
+ if (diff < 7 * oneDay) {
148
+ return date.toLocaleDateString(undefined, { weekday: 'long' }) + ', ' + timeStr;
149
+ }
150
+ return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ', ' + timeStr;
151
+ },
135
152
  async toggleShareThread() {
136
153
  if (!this.chatThreadId || !this.hasWorkspace) {
137
154
  return;
@@ -165,6 +182,13 @@ module.exports = {
165
182
  },
166
183
  sharedWithWorkspace() {
167
184
  return !!this.currentThread?.sharingOptions?.sharedWithWorkspace;
185
+ },
186
+ filteredThreads() {
187
+ const search = this.threadSearch.trim().toLowerCase();
188
+ if (!search) {
189
+ return this.chatThreads;
190
+ }
191
+ return this.chatThreads.filter(t => (t.title || 'Untitled Thread').toLowerCase().includes(search));
168
192
  }
169
193
  },
170
194
  async mounted() {
@@ -1,8 +1,13 @@
1
1
  <div>
2
2
  <div class="mb-2">
3
- <textarea class="border border-gray-200 p-2 h-[300px] w-full" ref="codeEditor"></textarea>
3
+ <ace-editor
4
+ v-model="documentData"
5
+ mode="javascript"
6
+ :line-numbers="true"
7
+ class="border border-edge h-[300px] w-full"
8
+ />
4
9
  </div>
5
- <button @click="cloneDocument()" class="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-teal-600">Submit</button>
10
+ <button @click="cloneDocument()" class="rounded-md bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">Submit</button>
6
11
  <div v-if="errors.length > 0" class="rounded-md bg-red-50 p-4 mt-1">
7
12
  <div class="flex">
8
13
  <div class="flex-shrink-0">
@@ -22,13 +22,12 @@ module.exports = app => app.component('clone-document', {
22
22
  data: function() {
23
23
  return {
24
24
  documentData: '',
25
- editor: null,
26
25
  errors: []
27
26
  };
28
27
  },
29
28
  methods: {
30
29
  async cloneDocument() {
31
- const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
30
+ const data = EJSON.serialize(eval(`(${this.documentData})`));
32
31
  try {
33
32
  const { doc } = await api.Model.createDocument({ model: this.currentModel, data });
34
33
  this.errors.length = 0;
@@ -64,11 +63,5 @@ module.exports = app => app.component('clone-document', {
64
63
  }
65
64
 
66
65
  this.documentData = JSON.stringify(filteredDoc, null, 2);
67
- this.$refs.codeEditor.value = this.documentData;
68
- this.editor = CodeMirror.fromTextArea(this.$refs.codeEditor, {
69
- mode: 'javascript',
70
- lineNumbers: true,
71
- smartIndent: false
72
- });
73
66
  }
74
67
  });
@@ -1,19 +1,24 @@
1
1
  <div>
2
- <div class="mt-4 text-gray-900 font-semibold">
2
+ <div class="mt-4 text-content font-semibold">
3
3
  Create New Dashboard
4
4
  </div>
5
5
  <div class="mt-4">
6
- <label class="block text-sm font-medium leading-6 text-gray-900">Title</label>
6
+ <label class="block text-sm font-medium leading-6 text-content">Title</label>
7
7
  <div class="mt-2">
8
8
  <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">
9
- <input type="text" v-model="title" 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="ACME-123">
9
+ <input type="text" v-model="title" 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="ACME-123">
10
10
  </div>
11
11
  </div>
12
12
  </div>
13
13
  <div class="my-4">
14
- <label class="block text-sm font-medium leading-6 text-gray-900">Code</label>
15
- <div class="border border-gray-200">
16
- <textarea class="p-2 h-[300px] w-full" ref="codeEditor"></textarea>
14
+ <label class="block text-sm font-medium leading-6 text-content">Code</label>
15
+ <div class="border border-edge">
16
+ <ace-editor
17
+ v-model="code"
18
+ mode="javascript"
19
+ :line-numbers="true"
20
+ class="h-[300px] w-full"
21
+ />
17
22
  </div>
18
23
  </div>
19
24
  <async-button
@@ -15,7 +15,6 @@ module.exports = app => app.component('create-dashboard', {
15
15
  },
16
16
  methods: {
17
17
  async createDashboard() {
18
- this.code = this._editor.getValue();
19
18
  const { dashboard } = await api.Dashboard.createDashboard({ code: this.code, title: this.title }).catch(err => {
20
19
  if (err.response?.data?.message) {
21
20
  console.log(err.response.data);
@@ -31,11 +30,5 @@ module.exports = app => app.component('create-dashboard', {
31
30
  this.$toast.success('Dashboard created!');
32
31
  this.$emit('close', dashboard);
33
32
  }
34
- },
35
- mounted: function() {
36
- this._editor = CodeMirror.fromTextArea(this.$refs.codeEditor, {
37
- mode: 'javascript',
38
- lineNumbers: true
39
- });
40
33
  }
41
34
  });
@@ -1,23 +1,23 @@
1
- <div>
1
+ <div class="px-1">
2
2
  <div class="mb-4">
3
- <label class="block text-sm font-bold text-gray-900">AI Mode</label>
3
+ <label class="block text-sm font-bold text-content">AI Mode</label>
4
4
  <div class="mt-2 flex flex-col gap-2 sm:flex-row sm:items-center">
5
5
  <input
6
6
  v-model="aiPrompt"
7
7
  type="text"
8
8
  placeholder="Describe the document you'd like to create..."
9
9
  @keydown.enter.prevent="requestAiSuggestion()"
10
- class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-ultramarine-500 focus:outline-none focus:ring-1 focus:ring-ultramarine-500"
10
+ class="w-full rounded-md border border-edge-strong px-3 py-2 text-sm shadow-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
11
11
  />
12
12
  <button
13
13
  @click="requestAiSuggestion()"
14
14
  :disabled="aiStreaming || !aiPrompt.trim()"
15
- class="inline-flex items-center justify-center rounded-md bg-ultramarine-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 disabled:cursor-not-allowed disabled:opacity-50"
15
+ class="inline-flex items-center justify-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover disabled:cursor-not-allowed disabled:opacity-50"
16
16
  >
17
17
  {{ aiStreaming ? 'Generating...' : 'Generate' }}
18
18
  </button>
19
19
  </div>
20
- <p class="mt-2 text-xs text-gray-500">Use AI to draft the document. You can accept or reject the suggestion once it finishes.</p>
20
+ <p class="mt-2 text-xs text-content-tertiary">Use AI to draft the document. You can accept or reject the suggestion once it finishes.</p>
21
21
  <div v-if="aiSuggestionReady" class="mt-3 flex flex-wrap gap-2">
22
22
  <button
23
23
  @click="acceptAiSuggestion()"
@@ -27,17 +27,23 @@
27
27
  </button>
28
28
  <button
29
29
  @click="rejectAiSuggestion()"
30
- class="rounded-md bg-gray-100 px-2.5 py-1.5 text-sm font-semibold text-gray-700 shadow-sm hover:bg-gray-200"
30
+ class="rounded-md bg-muted px-2.5 py-1.5 text-sm font-semibold text-content-secondary shadow-sm hover:bg-muted"
31
31
  >
32
32
  Reject suggestion
33
33
  </button>
34
34
  </div>
35
35
  </div>
36
36
  <div class="mb-2">
37
- <label class="block text-sm font-bold text-gray-900">Document to Create</label>
38
- <textarea class="border border-gray-200 p-2 h-[300px] w-full mt-2" ref="codeEditor"></textarea>
37
+ <label class="block text-sm font-bold text-content">Document to Create</label>
38
+ <ace-editor
39
+ ref="codeEditor"
40
+ v-model="documentData"
41
+ mode="javascript"
42
+ :line-numbers="true"
43
+ class="h-[300px] w-full mt-2"
44
+ />
39
45
  </div>
40
- <button @click="createDocument()" class="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-teal-600">Submit</button>
46
+ <button @click="createDocument()" class="rounded-md bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">Submit</button>
41
47
  <div v-if="errors.length > 0" class="rounded-md bg-red-50 p-4 mt-1">
42
48
  <div class="flex">
43
49
  <div class="flex-shrink-0">
@@ -22,7 +22,6 @@ module.exports = app => app.component('create-document', {
22
22
  data: function() {
23
23
  return {
24
24
  documentData: '',
25
- editor: null,
26
25
  errors: [],
27
26
  aiPrompt: '',
28
27
  aiSuggestion: '',
@@ -41,7 +40,7 @@ module.exports = app => app.component('create-document', {
41
40
  return;
42
41
  }
43
42
 
44
- this.aiOriginalDocument = this.editor.getValue();
43
+ this.aiOriginalDocument = this.documentData;
45
44
  this.aiSuggestion = '';
46
45
  this.aiSuggestionReady = false;
47
46
  this.aiStreaming = true;
@@ -56,10 +55,10 @@ module.exports = app => app.component('create-document', {
56
55
  this.aiSuggestion += event.textPart;
57
56
  }
58
57
  }
59
- this.editor.setValue(this.aiSuggestion);
58
+ this.$refs.codeEditor.setValue(this.aiSuggestion);
60
59
  this.aiSuggestionReady = true;
61
60
  } catch (err) {
62
- this.editor.setValue(this.aiOriginalDocument);
61
+ this.$refs.codeEditor.setValue(this.aiOriginalDocument);
63
62
  this.$toast.error('Failed to generate a document suggestion.');
64
63
  throw err;
65
64
  } finally {
@@ -72,13 +71,13 @@ module.exports = app => app.component('create-document', {
72
71
  this.aiOriginalDocument = '';
73
72
  },
74
73
  rejectAiSuggestion() {
75
- this.editor.setValue(this.aiOriginalDocument);
74
+ this.$refs.codeEditor.setValue(this.aiOriginalDocument);
76
75
  this.aiSuggestionReady = false;
77
76
  this.aiSuggestion = '';
78
77
  this.aiOriginalDocument = '';
79
78
  },
80
79
  async createDocument() {
81
- const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
80
+ const data = EJSON.serialize(eval(`(${this.documentData})`));
82
81
  try {
83
82
  const { doc } = await api.Model.createDocument({ model: this.currentModel, data });
84
83
  this.errors.length = 0;
@@ -104,11 +103,5 @@ module.exports = app => app.component('create-document', {
104
103
  this.documentData += ` ${requiredPaths[i].path}: ${isLast ? '' : ','}\n`;
105
104
  }
106
105
  this.documentData += '}';
107
- this.$refs.codeEditor.value = this.documentData;
108
- this.editor = CodeMirror.fromTextArea(this.$refs.codeEditor, {
109
- mode: 'javascript',
110
- lineNumbers: true,
111
- smartIndent: false
112
- });
113
106
  }
114
107
  });
@@ -1,16 +1,17 @@
1
- <div class="dashboard px-1">
1
+ <div class="dashboard-root">
2
+ <div class="dashboard px-1">
2
3
  <div v-if="status === 'loading'" class="max-w-5xl mx-auto text-center">
3
4
  <img src="images/loader.gif" class="inline mt-10">
4
5
  </div>
5
6
  <div v-if="dashboard && status !== 'loading'" class="max-w-5xl mx-auto">
6
7
  <div class="flex items-center w-full" v-if="!showEditor">
7
- <h2 class="mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink">{{title}}</h2>
8
+ <h2 class="mt-4 mb-4 text-content font-semibold text-xl grow shrink">{{title}}</h2>
8
9
  <div class="flex gap-2">
9
10
  <button
10
11
  @click="showEditor = true"
11
12
  type="button"
12
13
  :disabled="status === 'evaluating'"
13
- 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">
14
+ class="flex items-center rounded-md bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:bg-gray-600">
14
15
  <img src="images/edit.svg" class="inline h-[1.25em] mr-1" /> Edit
15
16
  </button>
16
17
 
@@ -18,7 +19,7 @@
18
19
  @click="evaluateDashboard"
19
20
  type="button"
20
21
  :disabled="status === 'evaluating'"
21
- 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"
22
+ class="flex items-center rounded-md bg-primary px-4 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:bg-gray-600"
22
23
  >
23
24
  <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
25
  Evaluate
@@ -27,7 +28,7 @@
27
28
  <button
28
29
  type="button"
29
30
  @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
+ class="flex items-center rounded-md bg-muted px-3 py-1.5 text-sm font-semibold text-content-secondary shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500"
31
32
  aria-haspopup="true"
32
33
  :aria-expanded="showActionsMenu"
33
34
  >
@@ -38,7 +39,7 @@
38
39
  </button>
39
40
  <div
40
41
  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
+ class="absolute right-0 mt-2 w-44 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black/5 z-20"
42
43
  role="menu"
43
44
  aria-label="Dashboard actions"
44
45
  >
@@ -46,7 +47,7 @@
46
47
  type="button"
47
48
  :disabled="startingChat"
48
49
  @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
+ class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-content-secondary hover:bg-primary-subtle disabled:cursor-not-allowed disabled:text-gray-400"
50
51
  role="menuitem"
51
52
  >
52
53
  <svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -61,12 +62,12 @@
61
62
  <div v-if="!showEditor" class="mt-4 mb-4">
62
63
  <div v-if="dashboardResults.length === 0">
63
64
  <div class="flex flex-col items-center justify-center py-8">
64
- <p class="text-gray-700 text-base mb-4">This dashboard hasn't been evaluated yet.</p>
65
+ <p class="text-content-secondary text-base mb-4">This dashboard hasn't been evaluated yet.</p>
65
66
  <async-button
66
67
  @click="evaluateDashboard"
67
68
  type="button"
68
69
  :disabled="status === 'evaluating'"
69
- 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"
70
+ class="rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:bg-gray-600"
70
71
  >
71
72
  Evaluate Dashboard
72
73
  </async-button>
@@ -74,7 +75,7 @@
74
75
  </div>
75
76
  <div v-else-if="dashboardResult">
76
77
  <div class="relative">
77
- <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">
78
+ <div v-if="status === 'evaluating'" class="absolute inset-0 flex items-center justify-center bg-surface bg-opacity-60 z-10 flex flex-col gap-2">
78
79
  <div>Evaluating Dashboard...</div>
79
80
  <img src="images/loader.gif" class="h-12 w-12" alt="Loading..." />
80
81
  </div>
@@ -116,9 +117,9 @@
116
117
  <div v-if="!dashboard && status !== 'loading'">
117
118
  No dashboard with the given id could be found.
118
119
  </div>
119
- </div>
120
+ </div>
120
121
 
121
- <modal
122
+ <modal
122
123
  v-if="showDetailModal"
123
124
  containerClass="!h-[90vh] !w-[90vw]"
124
125
  role="dialog"
@@ -138,3 +139,4 @@
138
139
  </div>
139
140
  </template>
140
141
  </modal>
142
+ </div>
@@ -37,14 +37,22 @@ module.exports = {
37
37
  async evaluateDashboard() {
38
38
  this.status = 'evaluating';
39
39
  try {
40
- const { dashboard, dashboardResult } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: true });
40
+ const { dashboard, dashboardResult, result, error } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: true });
41
41
  this.dashboard = dashboard;
42
- this.code = this.dashboard.code;
43
- this.title = this.dashboard.title;
44
- this.description = this.dashboard.description ?? '';
42
+ if (dashboard) {
43
+ this.code = this.dashboard.code;
44
+ this.title = this.dashboard.title;
45
+ this.description = this.dashboard.description ?? '';
46
+ }
45
47
  if (dashboardResult) {
46
48
  this.dashboardResults.unshift(dashboardResult);
49
+ } else if (result !== undefined) {
50
+ this.dashboardResults.unshift({ result, finishedEvaluatingAt: new Date() });
51
+ } else if (error) {
52
+ this.dashboardResults.unshift({ error: { message: error.message || 'Evaluation failed' }, finishedEvaluatingAt: new Date() });
47
53
  }
54
+ } catch (err) {
55
+ this.$toast.error(err?.response?.data?.message || err?.message || 'Dashboard evaluation failed');
48
56
  } finally {
49
57
  this.status = 'loaded';
50
58
  }
@@ -1,21 +1,27 @@
1
- <div class="p-4 bg-gray-100 rounded-lg shadow-lg">
1
+ <div class="dashboard-editor p-4 bg-muted rounded-lg shadow-lg">
2
2
  <div v-show="status === 'loading'" class="max-w-5xl mx-auto text-center">
3
3
  <img src="images/loader.gif" class="inline mt-10">
4
4
  </div>
5
5
  <div v-show="status !== 'loading'" class="flex flex-col gap-4">
6
6
  <div>
7
- <input v-model="title" class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Title"/>
7
+ <input v-model="title" class="w-full p-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Title"/>
8
8
  </div>
9
9
  <div>
10
- <textarea v-model="description" class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" rows="4" placeholder="Description">{{description}}</textarea>
10
+ <textarea v-model="description" class="w-full p-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" rows="4" placeholder="Description">{{description}}</textarea>
11
11
  </div>
12
- <div class="border border-gray-300 rounded-md">
13
- <textarea ref="codeEditor" class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" rows="6">{{code}}</textarea>
14
- <textarea v-model="chatMessage" class="w-full p-2"></textarea>
12
+ <div class="border border-edge-strong rounded-md">
13
+ <ace-editor
14
+ ref="codeEditor"
15
+ v-model="editCode"
16
+ mode="javascript"
17
+ :line-numbers="true"
18
+ :wrap="true"
19
+ class="w-full min-h-[200px]"
20
+ />
15
21
  </div>
16
22
  <div class="flex space-x-2">
17
23
  <async-button @click="updateCode" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500">Submit</async-button>
18
- <button @click="closeEditor" class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500">Cancel</button>
24
+ <button @click="closeEditor" class="px-4 py-2 bg-page0 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500">Cancel</button>
19
25
  </div>
20
26
  </div>
21
27
  </div>
@@ -10,11 +10,16 @@ module.exports = app => app.component('edit-dashboard', {
10
10
  data: function() {
11
11
  return {
12
12
  status: 'loaded',
13
- editor: null,
14
13
  title: '',
15
- description: ''
14
+ description: '',
15
+ editCode: ''
16
16
  };
17
17
  },
18
+ mounted() {
19
+ this.editCode = this.code || '';
20
+ this.description = this.currentDescription;
21
+ this.title = this.currentTitle;
22
+ },
18
23
  methods: {
19
24
  closeEditor() {
20
25
  this.$emit('close');
@@ -22,15 +27,19 @@ module.exports = app => app.component('edit-dashboard', {
22
27
  async updateCode() {
23
28
  this.status = 'loading';
24
29
  try {
30
+ const codeToSave = this.$refs.codeEditor ? this.$refs.codeEditor.getValue() : this.editCode;
25
31
  const { doc } = await api.Dashboard.updateDashboard({
26
32
  dashboardId: this.dashboardId,
27
- code: this.editor.getValue(),
33
+ code: codeToSave,
28
34
  title: this.title,
29
35
  description: this.description,
30
36
  evaluate: false
31
37
  });
32
38
  this.$emit('update', { doc });
33
- this.editor.setValue(doc.code);
39
+ this.editCode = doc.code;
40
+ if (this.$refs.codeEditor) {
41
+ this.$refs.codeEditor.setValue(doc.code);
42
+ }
34
43
  this.$toast.success('Dashboard updated!');
35
44
  this.closeEditor();
36
45
  } catch (err) {
@@ -39,22 +48,5 @@ module.exports = app => app.component('edit-dashboard', {
39
48
  this.status = 'loaded';
40
49
  }
41
50
  }
42
- },
43
- mounted: async function() {
44
- this.editor = CodeMirror.fromTextArea(this.$refs.codeEditor, {
45
- mode: 'javascript',
46
- lineNumbers: true,
47
- indentUnit: 4,
48
- smartIndent: true,
49
- tabsize: 4,
50
- indentWithTabs: true,
51
- cursorBlinkRate: 300,
52
- lineWrapping: true,
53
- showCursorWhenSelecting: true
54
- });
55
- // this.editor.focus();
56
- // this.editor.refresh(); // if anything weird happens on load, this usually fixes it. However, this breaks it in this case.
57
- this.description = this.currentDescription;
58
- this.title = this.currentTitle;
59
51
  }
60
52
  });