@mongoosejs/studio 0.0.111 → 0.0.113
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.
- package/backend/actions/ChatMessage/executeScript.js +6 -2
- package/backend/actions/ChatThread/createChatMessage.js +7 -5
- package/backend/actions/ChatThread/createChatThread.js +14 -3
- package/backend/actions/ChatThread/getChatThread.js +10 -4
- package/backend/actions/ChatThread/index.js +1 -0
- package/backend/actions/ChatThread/listChatThreads.js +5 -3
- package/backend/actions/ChatThread/shareChatThread.js +46 -0
- package/backend/authorize.js +1 -0
- package/backend/db/chatThreadSchema.js +10 -0
- package/frontend/public/app.js +66 -8
- package/frontend/public/tw.css +10 -0
- package/frontend/src/api.js +6 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +1 -1
- package/frontend/src/chat/chat.html +11 -0
- package/frontend/src/chat/chat.js +47 -2
- package/frontend/src/dashboard/dashboard.html +34 -5
- package/frontend/src/dashboard/dashboard.js +5 -1
- package/package.json +1 -1
- package/valnotes.md +2 -0
|
@@ -6,7 +6,7 @@ const mongoose = require('mongoose');
|
|
|
6
6
|
const vm = require('vm');
|
|
7
7
|
|
|
8
8
|
const ExecuteScriptParams = new Archetype({
|
|
9
|
-
|
|
9
|
+
initiatedById: {
|
|
10
10
|
$type: mongoose.Types.ObjectId
|
|
11
11
|
},
|
|
12
12
|
chatMessageId: {
|
|
@@ -21,7 +21,7 @@ const ExecuteScriptParams = new Archetype({
|
|
|
21
21
|
}).compile('ExecuteScriptParams');
|
|
22
22
|
|
|
23
23
|
module.exports = ({ db, studioConnection }) => async function executeScript(params) {
|
|
24
|
-
const {
|
|
24
|
+
const { initiatedById, chatMessageId, script, roles } = new ExecuteScriptParams(params);
|
|
25
25
|
const ChatMessage = studioConnection.model('__Studio_ChatMessage');
|
|
26
26
|
|
|
27
27
|
await authorize('ChatMessage.executeScript', roles);
|
|
@@ -31,6 +31,10 @@ module.exports = ({ db, studioConnection }) => async function executeScript(para
|
|
|
31
31
|
throw new Error('Chat message not found');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
if (initiatedById && chatMessage.userId?.toString() !== initiatedById.toString()) {
|
|
35
|
+
throw new Error('Unauthorized');
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
// Create a sandbox with the db object
|
|
35
39
|
const logs = [];
|
|
36
40
|
const sandbox = { db, console: {}, ObjectId: mongoose.Types.ObjectId };
|
|
@@ -9,7 +9,7 @@ const CreateChatMessageParams = new Archetype({
|
|
|
9
9
|
chatThreadId: {
|
|
10
10
|
$type: mongoose.Types.ObjectId
|
|
11
11
|
},
|
|
12
|
-
|
|
12
|
+
initiatedById: {
|
|
13
13
|
$type: mongoose.Types.ObjectId
|
|
14
14
|
},
|
|
15
15
|
content: {
|
|
@@ -24,7 +24,7 @@ const CreateChatMessageParams = new Archetype({
|
|
|
24
24
|
}).compile('CreateChatMessageParams');
|
|
25
25
|
|
|
26
26
|
module.exports = ({ db, studioConnection, options }) => async function createChatMessage(params) {
|
|
27
|
-
const { chatThreadId,
|
|
27
|
+
const { chatThreadId, initiatedById, content, script, authorization, roles } = new CreateChatMessageParams(params);
|
|
28
28
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
29
29
|
const ChatMessage = studioConnection.model('__Studio_ChatMessage');
|
|
30
30
|
|
|
@@ -35,7 +35,7 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
35
35
|
if (!chatThread) {
|
|
36
36
|
throw new Error('Chat thread not found');
|
|
37
37
|
}
|
|
38
|
-
if (
|
|
38
|
+
if (initiatedById != null && chatThread.userId.toString() !== initiatedById.toString()) {
|
|
39
39
|
throw new Error('Not authorized');
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -46,12 +46,13 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
46
46
|
}));
|
|
47
47
|
llmMessages.push({ role: 'user', content });
|
|
48
48
|
|
|
49
|
+
let summarizePromise = Promise.resolve();
|
|
49
50
|
if (chatThread.title == null) {
|
|
50
|
-
summarizeChatThread(llmMessages).then(res => {
|
|
51
|
+
summarizePromise = summarizeChatThread(llmMessages, authorization).then(res => {
|
|
51
52
|
const title = res.response;
|
|
52
53
|
chatThread.title = title;
|
|
53
54
|
return chatThread.save();
|
|
54
|
-
})
|
|
55
|
+
});
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
if (options.context) {
|
|
@@ -82,6 +83,7 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
82
83
|
})
|
|
83
84
|
]);
|
|
84
85
|
|
|
86
|
+
await summarizePromise;
|
|
85
87
|
return { chatMessages, chatThread };
|
|
86
88
|
};
|
|
87
89
|
|
|
@@ -5,21 +5,32 @@ const authorize = require('../../authorize');
|
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
6
|
|
|
7
7
|
const CreateChatThreadParams = new Archetype({
|
|
8
|
-
|
|
8
|
+
initiatedById: {
|
|
9
9
|
$type: mongoose.Types.ObjectId
|
|
10
10
|
},
|
|
11
11
|
roles: {
|
|
12
12
|
$type: ['string']
|
|
13
|
+
},
|
|
14
|
+
$workspaceId: {
|
|
15
|
+
$type: mongoose.Types.ObjectId,
|
|
16
|
+
$required: false
|
|
13
17
|
}
|
|
14
18
|
}).compile('CreateChatThreadParams');
|
|
15
19
|
|
|
16
20
|
module.exports = ({ studioConnection }) => async function createChatThread(params) {
|
|
17
|
-
const {
|
|
21
|
+
const { initiatedById, roles, $workspaceId } = new CreateChatThreadParams(params);
|
|
18
22
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
19
23
|
|
|
20
24
|
await authorize('ChatThread.createChatThread', roles);
|
|
21
25
|
|
|
22
|
-
const
|
|
26
|
+
const doc = { userId: initiatedById };
|
|
27
|
+
if ($workspaceId) {
|
|
28
|
+
doc.workspaceId = $workspaceId;
|
|
29
|
+
}
|
|
30
|
+
if ($workspaceId && !initiatedById) {
|
|
31
|
+
throw new Error('initiatedById is required when creating a chat thread in a workspace');
|
|
32
|
+
}
|
|
33
|
+
const chatThread = await ChatThread.create(doc);
|
|
23
34
|
|
|
24
35
|
return { chatThread };
|
|
25
36
|
};
|
|
@@ -8,16 +8,19 @@ const GetChatThreadParams = new Archetype({
|
|
|
8
8
|
chatThreadId: {
|
|
9
9
|
$type: mongoose.Types.ObjectId
|
|
10
10
|
},
|
|
11
|
-
|
|
11
|
+
initiatedById: {
|
|
12
12
|
$type: mongoose.Types.ObjectId
|
|
13
13
|
},
|
|
14
14
|
roles: {
|
|
15
15
|
$type: ['string']
|
|
16
|
+
},
|
|
17
|
+
$workspaceId: {
|
|
18
|
+
$type: mongoose.Types.ObjectId
|
|
16
19
|
}
|
|
17
20
|
}).compile('GetChatThreadParams');
|
|
18
21
|
|
|
19
22
|
module.exports = ({ db, studioConnection }) => async function getChatThread(params) {
|
|
20
|
-
const { chatThreadId,
|
|
23
|
+
const { chatThreadId, initiatedById, roles, $workspaceId } = new GetChatThreadParams(params);
|
|
21
24
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
22
25
|
const ChatMessage = studioConnection.model('__Studio_ChatMessage');
|
|
23
26
|
|
|
@@ -28,8 +31,11 @@ module.exports = ({ db, studioConnection }) => async function getChatThread(para
|
|
|
28
31
|
if (!chatThread) {
|
|
29
32
|
throw new Error('Chat thread not found');
|
|
30
33
|
}
|
|
31
|
-
if (
|
|
32
|
-
|
|
34
|
+
if (initiatedById && chatThread.userId?.toString() !== initiatedById.toString()) {
|
|
35
|
+
|
|
36
|
+
if (!$workspaceId || chatThread.workspaceId?.toString() !== $workspaceId.toString() || !chatThread.sharingOptions?.sharedWithWorkspace) {
|
|
37
|
+
throw new Error('Not authorized');
|
|
38
|
+
}
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
const chatMessages = await ChatMessage.find({ chatThreadId })
|
|
@@ -4,3 +4,4 @@ exports.createChatMessage = require('./createChatMessage');
|
|
|
4
4
|
exports.createChatThread = require('./createChatThread');
|
|
5
5
|
exports.getChatThread = require('./getChatThread');
|
|
6
6
|
exports.listChatThreads = require('./listChatThreads');
|
|
7
|
+
exports.shareChatThread = require('./shareChatThread');
|
|
@@ -5,7 +5,7 @@ const authorize = require('../../authorize');
|
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
6
|
|
|
7
7
|
const ListChatThreadsParams = new Archetype({
|
|
8
|
-
|
|
8
|
+
initiatedById: {
|
|
9
9
|
$type: mongoose.Types.ObjectId
|
|
10
10
|
},
|
|
11
11
|
roles: {
|
|
@@ -15,13 +15,15 @@ const ListChatThreadsParams = new Archetype({
|
|
|
15
15
|
|
|
16
16
|
module.exports = ({ db, studioConnection }) => async function listChatThreads(params) {
|
|
17
17
|
// Validate the params object
|
|
18
|
-
const {
|
|
18
|
+
const { initiatedById, roles } = new ListChatThreadsParams(params);
|
|
19
19
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
20
20
|
|
|
21
21
|
await authorize('ChatThread.listChatThreads', roles);
|
|
22
22
|
|
|
23
|
+
const query = { userId: initiatedById };
|
|
24
|
+
|
|
23
25
|
// Get all chat threads
|
|
24
|
-
const chatThreads = await ChatThread.find(
|
|
26
|
+
const chatThreads = await ChatThread.find(query)
|
|
25
27
|
.sort({ updatedAt: -1 }); // Sort by most recently updated
|
|
26
28
|
|
|
27
29
|
return {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
|
+
const mongoose = require('mongoose');
|
|
6
|
+
|
|
7
|
+
const ShareChatThreadParams = new Archetype({
|
|
8
|
+
chatThreadId: {
|
|
9
|
+
$type: mongoose.Types.ObjectId
|
|
10
|
+
},
|
|
11
|
+
share: {
|
|
12
|
+
$type: 'boolean'
|
|
13
|
+
},
|
|
14
|
+
initiatedById: {
|
|
15
|
+
$type: mongoose.Types.ObjectId
|
|
16
|
+
},
|
|
17
|
+
roles: {
|
|
18
|
+
$type: ['string']
|
|
19
|
+
},
|
|
20
|
+
$workspaceId: {
|
|
21
|
+
$type: mongoose.Types.ObjectId
|
|
22
|
+
}
|
|
23
|
+
}).compile('ShareChatThreadParams');
|
|
24
|
+
|
|
25
|
+
module.exports = ({ studioConnection }) => async function shareChatThread(params) {
|
|
26
|
+
const { chatThreadId, share, initiatedById, roles, $workspaceId } = new ShareChatThreadParams(params);
|
|
27
|
+
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
28
|
+
|
|
29
|
+
await authorize('ChatThread.shareChatThread', roles);
|
|
30
|
+
|
|
31
|
+
const chatThread = await ChatThread.findById(chatThreadId).orFail();
|
|
32
|
+
if (initiatedById != null && chatThread.userId?.toString() !== initiatedById.toString()) {
|
|
33
|
+
throw new Error('Not authorized');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!$workspaceId || !chatThread.workspaceId || chatThread.workspaceId.toString() !== $workspaceId.toString()) {
|
|
37
|
+
throw new Error('Workspace required to share');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
chatThread.sharingOptions = chatThread.sharingOptions || {};
|
|
41
|
+
chatThread.sharingOptions.sharedWithWorkspace = !!share;
|
|
42
|
+
|
|
43
|
+
await chatThread.save();
|
|
44
|
+
|
|
45
|
+
return { chatThread };
|
|
46
|
+
};
|
package/backend/authorize.js
CHANGED
|
@@ -6,6 +6,7 @@ const actionsToRequiredRoles = {
|
|
|
6
6
|
'ChatThread.createChatThread': ['owner', 'admin', 'member'],
|
|
7
7
|
'ChatThread.getChatThread': ['owner', 'admin', 'member'],
|
|
8
8
|
'ChatThread.listChatThreads': ['owner', 'admin', 'member'],
|
|
9
|
+
'ChatThread.shareChatThread': ['owner', 'admin', 'member'],
|
|
9
10
|
'Dashboard.createDashboard': ['owner', 'admin', 'member'],
|
|
10
11
|
'Dashboard.deleteDashboard': ['owner', 'admin', 'member'],
|
|
11
12
|
'Dashboard.getDashboard': ['owner', 'admin', 'member', 'readonly', 'dashboards'],
|
|
@@ -9,6 +9,16 @@ const chatThreadSchema = new mongoose.Schema({
|
|
|
9
9
|
userId: {
|
|
10
10
|
type: mongoose.ObjectId,
|
|
11
11
|
ref: 'User'
|
|
12
|
+
},
|
|
13
|
+
workspaceId: {
|
|
14
|
+
type: mongoose.ObjectId,
|
|
15
|
+
ref: 'Workspace'
|
|
16
|
+
},
|
|
17
|
+
sharingOptions: {
|
|
18
|
+
sharedWithWorkspace: {
|
|
19
|
+
type: Boolean,
|
|
20
|
+
default: false
|
|
21
|
+
}
|
|
12
22
|
}
|
|
13
23
|
}, { timestamps: true });
|
|
14
24
|
|
package/frontend/public/app.js
CHANGED
|
@@ -68,6 +68,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
68
68
|
},
|
|
69
69
|
listChatThreads(params) {
|
|
70
70
|
return client.post('', { action: 'ChatThread.listChatThreads', ...params }).then(res => res.data);
|
|
71
|
+
},
|
|
72
|
+
shareChatThread(params) {
|
|
73
|
+
return client.post('', { action: 'ChatThread.shareChatThread', ...params }).then(res => res.data);
|
|
71
74
|
}
|
|
72
75
|
};
|
|
73
76
|
exports.ChatMessage = {
|
|
@@ -170,6 +173,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
170
173
|
},
|
|
171
174
|
listChatThreads: function listChatThreads(params) {
|
|
172
175
|
return client.post('/ChatThread/listChatThreads', params).then(res => res.data);
|
|
176
|
+
},
|
|
177
|
+
shareChatThread: function shareChatThread(params) {
|
|
178
|
+
return client.post('/ChatThread/shareChatThread', params).then(res => res.data);
|
|
173
179
|
}
|
|
174
180
|
};
|
|
175
181
|
exports.ChatMessage = {
|
|
@@ -528,6 +534,7 @@ module.exports = app => app.component('chat-message', {
|
|
|
528
534
|
|
|
529
535
|
const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
|
|
530
536
|
const template = __webpack_require__(/*! ./chat.html */ "./frontend/src/chat/chat.html");
|
|
537
|
+
const vanillatoasts = __webpack_require__(/*! vanillatoasts */ "./node_modules/vanillatoasts/vanillatoasts.js");
|
|
531
538
|
|
|
532
539
|
module.exports = app => app.component('chat', {
|
|
533
540
|
template: template,
|
|
@@ -539,7 +546,8 @@ module.exports = app => app.component('chat', {
|
|
|
539
546
|
chatThreadId: null,
|
|
540
547
|
chatThreads: [],
|
|
541
548
|
chatMessages: [],
|
|
542
|
-
hideSidebar: null
|
|
549
|
+
hideSidebar: null,
|
|
550
|
+
sharingThread: false
|
|
543
551
|
}),
|
|
544
552
|
methods: {
|
|
545
553
|
async sendMessage() {
|
|
@@ -563,11 +571,16 @@ module.exports = app => app.component('chat', {
|
|
|
563
571
|
}
|
|
564
572
|
});
|
|
565
573
|
|
|
566
|
-
const { chatMessages } = await api.ChatThread.createChatMessage({
|
|
574
|
+
const { chatMessages, chatThread } = await api.ChatThread.createChatMessage({
|
|
567
575
|
chatThreadId: this.chatThreadId,
|
|
568
576
|
content: this.newMessage
|
|
569
577
|
});
|
|
570
578
|
this.chatMessages.push(chatMessages[1]);
|
|
579
|
+
for (const thread of this.chatThreads) {
|
|
580
|
+
if (thread._id === chatThread._id) {
|
|
581
|
+
thread.title = chatThread.title;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
571
584
|
|
|
572
585
|
this.newMessage = '';
|
|
573
586
|
this.$nextTick(() => {
|
|
@@ -606,6 +619,44 @@ module.exports = app => app.component('chat', {
|
|
|
606
619
|
async createNewThread() {
|
|
607
620
|
const { chatThread } = await api.ChatThread.createChatThread();
|
|
608
621
|
this.$router.push('/chat/' + chatThread._id);
|
|
622
|
+
},
|
|
623
|
+
async toggleShareThread() {
|
|
624
|
+
if (!this.chatThreadId || !this.hasWorkspace) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
this.sharingThread = true;
|
|
628
|
+
try {
|
|
629
|
+
const share = true;
|
|
630
|
+
const { chatThread } = await api.ChatThread.shareChatThread({ chatThreadId: this.chatThreadId, share });
|
|
631
|
+
const idx = this.chatThreads.findIndex(t => t._id === chatThread._id);
|
|
632
|
+
if (idx !== -1) {
|
|
633
|
+
this.chatThreads.splice(idx, 1, chatThread);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Copy current URL to clipboard and show a toast
|
|
637
|
+
const url = window.location.href;
|
|
638
|
+
await navigator.clipboard.writeText(url);
|
|
639
|
+
vanillatoasts.create({
|
|
640
|
+
title: 'Share link copied!',
|
|
641
|
+
type: 'success',
|
|
642
|
+
timeout: 3000,
|
|
643
|
+
icon: 'images/success.png',
|
|
644
|
+
positionClass: 'bottomRight'
|
|
645
|
+
});
|
|
646
|
+
} finally {
|
|
647
|
+
this.sharingThread = false;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
computed: {
|
|
652
|
+
currentThread() {
|
|
653
|
+
return this.chatThreads.find(t => t._id === this.chatThreadId);
|
|
654
|
+
},
|
|
655
|
+
hasWorkspace() {
|
|
656
|
+
return !!window.MONGOOSE_STUDIO_CONFIG.workspace?._id;
|
|
657
|
+
},
|
|
658
|
+
sharedWithWorkspace() {
|
|
659
|
+
return !!this.currentThread?.sharingOptions?.sharedWithWorkspace;
|
|
609
660
|
}
|
|
610
661
|
},
|
|
611
662
|
async mounted() {
|
|
@@ -953,7 +1004,10 @@ module.exports = app => app.component('dashboard-map', {
|
|
|
953
1004
|
|
|
954
1005
|
this.$nextTick(() => {
|
|
955
1006
|
map.invalidateSize();
|
|
956
|
-
|
|
1007
|
+
const bounds = layer.getBounds();
|
|
1008
|
+
if (bounds.isValid()) {
|
|
1009
|
+
map.fitBounds(bounds);
|
|
1010
|
+
}
|
|
957
1011
|
});
|
|
958
1012
|
},
|
|
959
1013
|
computed: {
|
|
@@ -1093,7 +1147,8 @@ module.exports = app => app.component('dashboard', {
|
|
|
1093
1147
|
showEditor: false,
|
|
1094
1148
|
dashboard: null,
|
|
1095
1149
|
dashboardResults: [],
|
|
1096
|
-
errorMessage: null
|
|
1150
|
+
errorMessage: null,
|
|
1151
|
+
showDetailModal: false
|
|
1097
1152
|
};
|
|
1098
1153
|
},
|
|
1099
1154
|
methods: {
|
|
@@ -1125,6 +1180,9 @@ module.exports = app => app.component('dashboard', {
|
|
|
1125
1180
|
} finally {
|
|
1126
1181
|
this.status = 'loaded';
|
|
1127
1182
|
}
|
|
1183
|
+
},
|
|
1184
|
+
openDetailModal() {
|
|
1185
|
+
this.showDetailModal = true;
|
|
1128
1186
|
}
|
|
1129
1187
|
},
|
|
1130
1188
|
computed: {
|
|
@@ -4111,7 +4169,7 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
|
|
|
4111
4169
|
/***/ ((module) => {
|
|
4112
4170
|
|
|
4113
4171
|
"use strict";
|
|
4114
|
-
module.exports = "<div class=\"relative border rounded bg-gray-100 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b pt-[1px] text-xs font-medium bg-gray-200\">\n <button\n class=\"px-3 py-1 border-r border-gray-300 hover:bg-green-300\"\n :class=\"{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-3 py-1 hover:bg-green-300\"\n :class=\"{'bg-green-300': activeTab === 'output'}\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click=\"copyOutput\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript(message, script)\">\n Execute\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <pre class=\"p-3 whitespace-pre-wrap max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <dashboard-map v-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">×</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">×</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";
|
|
4172
|
+
module.exports = "<div class=\"relative border rounded bg-gray-100 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b pt-[1px] text-xs font-medium bg-gray-200\">\n <button\n class=\"px-3 py-1 border-r border-gray-300 hover:bg-green-300\"\n :class=\"{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-3 py-1 hover:bg-green-300\"\n :class=\"{'bg-green-300': activeTab === 'output'}\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click=\"copyOutput\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript(message, script)\">\n Execute\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <pre class=\"p-3 whitespace-pre-wrap max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <dashboard-map v-else-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">×</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">×</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";
|
|
4115
4173
|
|
|
4116
4174
|
/***/ }),
|
|
4117
4175
|
|
|
@@ -4133,7 +4191,7 @@ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-en
|
|
|
4133
4191
|
/***/ ((module) => {
|
|
4134
4192
|
|
|
4135
4193
|
"use strict";
|
|
4136
|
-
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 <!-- Sidebar: Chat Threads -->\n <aside class=\"bg-gray-50 border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Chat Threads</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-64\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"p-4 hover:bg-gray-200 cursor-pointer w-64\"\n :class=\"{ 'bg-gray-300': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <ul role=\"list\" class=\"space-y-4\">\n <div v-if=\"true\">\n <div class=\"flex items-center justify-center py-3 mb-4\">\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n <p class=\"mx-4 text-sm font-medium text-gray-500\">This is the beginning of the message thread</p>\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n </div>\n </div>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n placeholder=\"Ask something...\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto\"\n rows=\"1\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
|
|
4194
|
+
module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <button\n class=\"fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white\"\n :class=\"hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'\"\n @click=\"toggleShareThread\"\n :disabled=\"!hasWorkspace || !chatThreadId || sharingThread\"\n aria-label=\"Share thread with workspace\"\n title=\"Share thread with workspace\"\n >\n <svg v-if=\"hasWorkspace\" xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z\"/></svg>\n </button>\n <!-- Sidebar: Chat Threads -->\n <aside class=\"bg-gray-50 border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Chat Threads</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-64\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"p-4 hover:bg-gray-200 cursor-pointer w-64\"\n :class=\"{ 'bg-gray-300': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <ul role=\"list\" class=\"space-y-4\">\n <div v-if=\"true\">\n <div class=\"flex items-center justify-center py-3 mb-4\">\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n <p class=\"mx-4 text-sm font-medium text-gray-500\">This is the beginning of the message thread</p>\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n </div>\n </div>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n placeholder=\"Ask something...\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto\"\n rows=\"1\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
|
|
4137
4195
|
|
|
4138
4196
|
/***/ }),
|
|
4139
4197
|
|
|
@@ -4265,7 +4323,7 @@ module.exports = "<div class=\"py-2\">\n <div v-if=\"header\" class=\"border-b
|
|
|
4265
4323
|
/***/ ((module) => {
|
|
4266
4324
|
|
|
4267
4325
|
"use strict";
|
|
4268
|
-
module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"status === 'loading'\" class=\"max-w-5xl mx-auto text-center\">\n <img src=\"images/loader.gif\" class=\"inline mt-10\">\n </div>\n <div v-if=\"dashboard && status !== 'loading'\" class=\"max-w-5xl mx-auto\">\n <div class=\"flex items-center w-full\" v-if=\"!showEditor\">\n <h2 class=\"mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink\">{{title}}</h2>\n <div class=\"flex gap-2\">\n <button\n @click=\"showEditor = true\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\">\n <img src=\"images/edit.svg\" class=\"inline h-[1.25em] mr-1\" /> Edit\n </button>\n\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center rounded-md bg-ultramarine-600 px-4 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n <svg class=\"inline h-[1.25em] mr-1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"m670-140 160-100-160-100v200ZM240-600h480v-80H240v80ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40ZM120-80v-680q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v267q-19-9-39-15t-41-9v-243H200v562h243q5 31 15.5 59T486-86l-6 6-60-60-60 60-60-60-60 60-60-60-60 60Zm120-200h203q3-21 9-41t15-39H240v80Zm0-160h284q38-37 88.5-58.5T720-520H240v80Zm-40 242v-562 562Z\"/></svg>\n Evaluate\n </async-button>\n </div>\n </div>\n <div v-if=\"!showEditor\" class=\"mt-4 mb-4\">\n <div v-if=\"dashboardResults.length === 0\">\n <div class=\"flex flex-col items-center justify-center py-8\">\n <p class=\"text-gray-700 text-base mb-4\">This dashboard hasn't been evaluated yet.</p>\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"rounded-md bg-ultramarine-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n Evaluate Dashboard\n </async-button>\n </div>\n </div>\n <div v-else>\n <dashboard-result\n
|
|
4326
|
+
module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"status === 'loading'\" class=\"max-w-5xl mx-auto text-center\">\n <img src=\"images/loader.gif\" class=\"inline mt-10\">\n </div>\n <div v-if=\"dashboard && status !== 'loading'\" class=\"max-w-5xl mx-auto\">\n <div class=\"flex items-center w-full\" v-if=\"!showEditor\">\n <h2 class=\"mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink\">{{title}}</h2>\n <div class=\"flex gap-2\">\n <button\n @click=\"showEditor = true\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\">\n <img src=\"images/edit.svg\" class=\"inline h-[1.25em] mr-1\" /> Edit\n </button>\n\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"flex items-center rounded-md bg-ultramarine-600 px-4 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n <svg class=\"inline h-[1.25em] mr-1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"m670-140 160-100-160-100v200ZM240-600h480v-80H240v80ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40ZM120-80v-680q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v267q-19-9-39-15t-41-9v-243H200v562h243q5 31 15.5 59T486-86l-6 6-60-60-60 60-60-60-60 60-60-60-60 60Zm120-200h203q3-21 9-41t15-39H240v80Zm0-160h284q38-37 88.5-58.5T720-520H240v80Zm-40 242v-562 562Z\"/></svg>\n Evaluate\n </async-button>\n </div>\n </div>\n <div v-if=\"!showEditor\" class=\"mt-4 mb-4\">\n <div v-if=\"dashboardResults.length === 0\">\n <div class=\"flex flex-col items-center justify-center py-8\">\n <p class=\"text-gray-700 text-base mb-4\">This dashboard hasn't been evaluated yet.</p>\n <async-button\n @click=\"evaluateDashboard\"\n type=\"button\"\n :disabled=\"status === 'evaluating'\"\n class=\"rounded-md bg-ultramarine-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600 disabled:cursor-not-allowed disabled:bg-gray-600\"\n >\n Evaluate Dashboard\n </async-button>\n </div>\n </div>\n <div v-else>\n <div class=\"relative\">\n <button\n class=\"absolute top-2 right-2 px-2 py-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\"\n aria-label=\"Expand dashboard result\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <dashboard-result\n :key=\"dashboardResult.finishedEvaluatingAt\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\">\n </dashboard-result>\n </div>\n </div>\n </div>\n <div v-if=\"showEditor\" class=\"mt-4\">\n <edit-dashboard\n :dashboardId=\"dashboard._id\"\n :code=\"code\"\n :currentDescription=\"description\"\n :currentTitle=\"title\"\n @close=\"showEditor=false;\"\n @update=\"updateCode\"></edit-dashboard>\n </div>\n <div v-if=\"errorMessage\" class=\"rounded-md bg-red-50 p-4 mt-4\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">{{errorMessage}}</h3>\n </div>\n </div>\n </div>\n\n </div>\n <div v-if=\"!dashboard && status !== 'loading'\">\n No dashboard with the given id could be found.\n </div>\n</div>\n\n<modal\n v-if=\"showDetailModal\"\n containerClass=\"!h-[90vh] !w-[90vw]\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Dashboard Details\"\n>\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-result\n v-if=\"dashboardResult\"\n :result=\"dashboardResult.result\"\n :finishedEvaluatingAt=\"dashboardResult.finishedEvaluatingAt\">\n </dashboard-result>\n </div>\n </template>\n</modal>\n";
|
|
4269
4327
|
|
|
4270
4328
|
/***/ }),
|
|
4271
4329
|
|
|
@@ -14779,7 +14837,7 @@ var bson = /*#__PURE__*/Object.freeze({
|
|
|
14779
14837
|
/***/ ((module) => {
|
|
14780
14838
|
|
|
14781
14839
|
"use strict";
|
|
14782
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.
|
|
14840
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.113","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"0.0.26","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
|
|
14783
14841
|
|
|
14784
14842
|
/***/ })
|
|
14785
14843
|
|
package/frontend/public/tw.css
CHANGED
|
@@ -626,6 +626,10 @@ video {
|
|
|
626
626
|
right: 0.25rem;
|
|
627
627
|
}
|
|
628
628
|
|
|
629
|
+
.right-4 {
|
|
630
|
+
right: 1rem;
|
|
631
|
+
}
|
|
632
|
+
|
|
629
633
|
.top-1 {
|
|
630
634
|
top: 0.25rem;
|
|
631
635
|
}
|
|
@@ -1788,6 +1792,12 @@ video {
|
|
|
1788
1792
|
opacity: 0.5;
|
|
1789
1793
|
}
|
|
1790
1794
|
|
|
1795
|
+
.shadow {
|
|
1796
|
+
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
1797
|
+
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
|
1798
|
+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1791
1801
|
.shadow-lg {
|
|
1792
1802
|
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
1793
1803
|
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
package/frontend/src/api.js
CHANGED
|
@@ -58,6 +58,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
58
58
|
},
|
|
59
59
|
listChatThreads(params) {
|
|
60
60
|
return client.post('', { action: 'ChatThread.listChatThreads', ...params }).then(res => res.data);
|
|
61
|
+
},
|
|
62
|
+
shareChatThread(params) {
|
|
63
|
+
return client.post('', { action: 'ChatThread.shareChatThread', ...params }).then(res => res.data);
|
|
61
64
|
}
|
|
62
65
|
};
|
|
63
66
|
exports.ChatMessage = {
|
|
@@ -160,6 +163,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
160
163
|
},
|
|
161
164
|
listChatThreads: function listChatThreads(params) {
|
|
162
165
|
return client.post('/ChatThread/listChatThreads', params).then(res => res.data);
|
|
166
|
+
},
|
|
167
|
+
shareChatThread: function shareChatThread(params) {
|
|
168
|
+
return client.post('/ChatThread/shareChatThread', params).then(res => res.data);
|
|
163
169
|
}
|
|
164
170
|
};
|
|
165
171
|
exports.ChatMessage = {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
|
|
60
60
|
<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'">
|
|
61
61
|
<dashboard-chart v-if="message.executionResult?.output?.$chart" :value="message.executionResult?.output" />
|
|
62
|
-
<dashboard-map v-if="message.executionResult?.output?.$featureCollection" :value="message.executionResult?.output" />
|
|
62
|
+
<dashboard-map v-else-if="message.executionResult?.output?.$featureCollection" :value="message.executionResult?.output" />
|
|
63
63
|
<pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>
|
|
64
64
|
</div>
|
|
65
65
|
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
<div class="fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10" @click="hideSidebar = false">
|
|
3
3
|
<svg xmlns="http://www.w3.org/2000/svg" style="h-5 w-5" viewBox="0 -960 960 960" class="w-5" fill="#5f6368"><path d="M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z"/></svg>
|
|
4
4
|
</div>
|
|
5
|
+
<button
|
|
6
|
+
class="fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white"
|
|
7
|
+
:class="hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'"
|
|
8
|
+
@click="toggleShareThread"
|
|
9
|
+
:disabled="!hasWorkspace || !chatThreadId || sharingThread"
|
|
10
|
+
aria-label="Share thread with workspace"
|
|
11
|
+
title="Share thread with workspace"
|
|
12
|
+
>
|
|
13
|
+
<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>
|
|
14
|
+
<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>
|
|
15
|
+
</button>
|
|
5
16
|
<!-- Sidebar: Chat Threads -->
|
|
6
17
|
<aside class="bg-gray-50 border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative" :class="hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''">
|
|
7
18
|
<div class="flex items-center border-b border-gray-100 w-64 overflow-x-hidden">
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
4
|
const template = require('./chat.html');
|
|
5
|
+
const vanillatoasts = require('vanillatoasts');
|
|
5
6
|
|
|
6
7
|
module.exports = app => app.component('chat', {
|
|
7
8
|
template: template,
|
|
@@ -13,7 +14,8 @@ module.exports = app => app.component('chat', {
|
|
|
13
14
|
chatThreadId: null,
|
|
14
15
|
chatThreads: [],
|
|
15
16
|
chatMessages: [],
|
|
16
|
-
hideSidebar: null
|
|
17
|
+
hideSidebar: null,
|
|
18
|
+
sharingThread: false
|
|
17
19
|
}),
|
|
18
20
|
methods: {
|
|
19
21
|
async sendMessage() {
|
|
@@ -37,11 +39,16 @@ module.exports = app => app.component('chat', {
|
|
|
37
39
|
}
|
|
38
40
|
});
|
|
39
41
|
|
|
40
|
-
const { chatMessages } = await api.ChatThread.createChatMessage({
|
|
42
|
+
const { chatMessages, chatThread } = await api.ChatThread.createChatMessage({
|
|
41
43
|
chatThreadId: this.chatThreadId,
|
|
42
44
|
content: this.newMessage
|
|
43
45
|
});
|
|
44
46
|
this.chatMessages.push(chatMessages[1]);
|
|
47
|
+
for (const thread of this.chatThreads) {
|
|
48
|
+
if (thread._id === chatThread._id) {
|
|
49
|
+
thread.title = chatThread.title;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
45
52
|
|
|
46
53
|
this.newMessage = '';
|
|
47
54
|
this.$nextTick(() => {
|
|
@@ -80,6 +87,44 @@ module.exports = app => app.component('chat', {
|
|
|
80
87
|
async createNewThread() {
|
|
81
88
|
const { chatThread } = await api.ChatThread.createChatThread();
|
|
82
89
|
this.$router.push('/chat/' + chatThread._id);
|
|
90
|
+
},
|
|
91
|
+
async toggleShareThread() {
|
|
92
|
+
if (!this.chatThreadId || !this.hasWorkspace) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.sharingThread = true;
|
|
96
|
+
try {
|
|
97
|
+
const share = true;
|
|
98
|
+
const { chatThread } = await api.ChatThread.shareChatThread({ chatThreadId: this.chatThreadId, share });
|
|
99
|
+
const idx = this.chatThreads.findIndex(t => t._id === chatThread._id);
|
|
100
|
+
if (idx !== -1) {
|
|
101
|
+
this.chatThreads.splice(idx, 1, chatThread);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Copy current URL to clipboard and show a toast
|
|
105
|
+
const url = window.location.href;
|
|
106
|
+
await navigator.clipboard.writeText(url);
|
|
107
|
+
vanillatoasts.create({
|
|
108
|
+
title: 'Share link copied!',
|
|
109
|
+
type: 'success',
|
|
110
|
+
timeout: 3000,
|
|
111
|
+
icon: 'images/success.png',
|
|
112
|
+
positionClass: 'bottomRight'
|
|
113
|
+
});
|
|
114
|
+
} finally {
|
|
115
|
+
this.sharingThread = false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
computed: {
|
|
120
|
+
currentThread() {
|
|
121
|
+
return this.chatThreads.find(t => t._id === this.chatThreadId);
|
|
122
|
+
},
|
|
123
|
+
hasWorkspace() {
|
|
124
|
+
return !!window.MONGOOSE_STUDIO_CONFIG.workspace?._id;
|
|
125
|
+
},
|
|
126
|
+
sharedWithWorkspace() {
|
|
127
|
+
return !!this.currentThread?.sharingOptions?.sharedWithWorkspace;
|
|
83
128
|
}
|
|
84
129
|
},
|
|
85
130
|
async mounted() {
|
|
@@ -40,11 +40,21 @@
|
|
|
40
40
|
</div>
|
|
41
41
|
</div>
|
|
42
42
|
<div v-else>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
<div class="relative">
|
|
44
|
+
<button
|
|
45
|
+
class="absolute top-2 right-2 px-2 py-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center"
|
|
46
|
+
@click="openDetailModal"
|
|
47
|
+
aria-label="Expand dashboard result">
|
|
48
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
49
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5" />
|
|
50
|
+
</svg>
|
|
51
|
+
</button>
|
|
52
|
+
<dashboard-result
|
|
53
|
+
:key="dashboardResult.finishedEvaluatingAt"
|
|
54
|
+
:result="dashboardResult.result"
|
|
55
|
+
:finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt">
|
|
56
|
+
</dashboard-result>
|
|
57
|
+
</div>
|
|
48
58
|
</div>
|
|
49
59
|
</div>
|
|
50
60
|
<div v-if="showEditor" class="mt-4">
|
|
@@ -74,3 +84,22 @@
|
|
|
74
84
|
No dashboard with the given id could be found.
|
|
75
85
|
</div>
|
|
76
86
|
</div>
|
|
87
|
+
|
|
88
|
+
<modal
|
|
89
|
+
v-if="showDetailModal"
|
|
90
|
+
containerClass="!h-[90vh] !w-[90vw]"
|
|
91
|
+
role="dialog"
|
|
92
|
+
aria-modal="true"
|
|
93
|
+
aria-label="Dashboard Details"
|
|
94
|
+
>
|
|
95
|
+
<template #body>
|
|
96
|
+
<div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="showDetailModal = false;" role="button" aria-label="Close modal">×</div>
|
|
97
|
+
<div class="h-full overflow-auto">
|
|
98
|
+
<dashboard-result
|
|
99
|
+
v-if="dashboardResult"
|
|
100
|
+
:result="dashboardResult.result"
|
|
101
|
+
:finishedEvaluatingAt="dashboardResult.finishedEvaluatingAt">
|
|
102
|
+
</dashboard-result>
|
|
103
|
+
</div>
|
|
104
|
+
</template>
|
|
105
|
+
</modal>
|
|
@@ -15,7 +15,8 @@ module.exports = app => app.component('dashboard', {
|
|
|
15
15
|
showEditor: false,
|
|
16
16
|
dashboard: null,
|
|
17
17
|
dashboardResults: [],
|
|
18
|
-
errorMessage: null
|
|
18
|
+
errorMessage: null,
|
|
19
|
+
showDetailModal: false
|
|
19
20
|
};
|
|
20
21
|
},
|
|
21
22
|
methods: {
|
|
@@ -47,6 +48,9 @@ module.exports = app => app.component('dashboard', {
|
|
|
47
48
|
} finally {
|
|
48
49
|
this.status = 'loaded';
|
|
49
50
|
}
|
|
51
|
+
},
|
|
52
|
+
openDetailModal() {
|
|
53
|
+
this.showDetailModal = true;
|
|
50
54
|
}
|
|
51
55
|
},
|
|
52
56
|
computed: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.113",
|
|
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": {
|
package/valnotes.md
ADDED