@mongoosejs/studio 0.0.112 → 0.0.114
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/backend/netlify.js +1 -0
- package/frontend/public/app.js +61 -6
- package/frontend/public/tw.css +10 -0
- package/frontend/src/api.js +6 -0
- 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/backend/netlify.js
CHANGED
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() {
|
|
@@ -1096,7 +1147,8 @@ module.exports = app => app.component('dashboard', {
|
|
|
1096
1147
|
showEditor: false,
|
|
1097
1148
|
dashboard: null,
|
|
1098
1149
|
dashboardResults: [],
|
|
1099
|
-
errorMessage: null
|
|
1150
|
+
errorMessage: null,
|
|
1151
|
+
showDetailModal: false
|
|
1100
1152
|
};
|
|
1101
1153
|
},
|
|
1102
1154
|
methods: {
|
|
@@ -1128,6 +1180,9 @@ module.exports = app => app.component('dashboard', {
|
|
|
1128
1180
|
} finally {
|
|
1129
1181
|
this.status = 'loaded';
|
|
1130
1182
|
}
|
|
1183
|
+
},
|
|
1184
|
+
openDetailModal() {
|
|
1185
|
+
this.showDetailModal = true;
|
|
1131
1186
|
}
|
|
1132
1187
|
},
|
|
1133
1188
|
computed: {
|
|
@@ -4136,7 +4191,7 @@ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-en
|
|
|
4136
4191
|
/***/ ((module) => {
|
|
4137
4192
|
|
|
4138
4193
|
"use strict";
|
|
4139
|
-
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";
|
|
4140
4195
|
|
|
4141
4196
|
/***/ }),
|
|
4142
4197
|
|
|
@@ -4268,7 +4323,7 @@ module.exports = "<div class=\"py-2\">\n <div v-if=\"header\" class=\"border-b
|
|
|
4268
4323
|
/***/ ((module) => {
|
|
4269
4324
|
|
|
4270
4325
|
"use strict";
|
|
4271
|
-
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";
|
|
4272
4327
|
|
|
4273
4328
|
/***/ }),
|
|
4274
4329
|
|
|
@@ -14782,7 +14837,7 @@ var bson = /*#__PURE__*/Object.freeze({
|
|
|
14782
14837
|
/***/ ((module) => {
|
|
14783
14838
|
|
|
14784
14839
|
"use strict";
|
|
14785
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.
|
|
14840
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.114","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"}}');
|
|
14786
14841
|
|
|
14787
14842
|
/***/ })
|
|
14788
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 = {
|
|
@@ -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.114",
|
|
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