@mongoosejs/studio 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/backend/actions/ChatMessage/executeScript.js +17 -25
- package/backend/actions/Model/getDocuments.js +7 -4
- package/backend/actions/Model/getDocumentsStream.js +7 -4
- package/backend/actions/getCapabilities.js +42 -0
- package/backend/actions/index.js +1 -1
- package/backend/db/chatMessageSchema.js +2 -1
- package/backend/helpers/isBindIPConnection.js +8 -0
- package/backend/helpers/isLocalhostConnection.js +8 -0
- package/backend/helpers/normalizeBindIPOption.js +16 -0
- package/backend/helpers/parseProjectionParam.js +180 -0
- package/backend/sandbox/createSandbox.js +83 -0
- package/backend/sandbox/createScriptDb.js +201 -0
- package/express.js +16 -0
- package/frontend/public/app.js +845 -114
- package/frontend/public/tw.css +4 -0
- package/frontend/src/api.js +6 -0
- package/frontend/src/chat/chat-message/chat-message.html +1 -0
- package/frontend/src/chat/chat-message/chat-message.js +4 -3
- package/frontend/src/chat/chat-message-script/chat-message-script.html +98 -16
- package/frontend/src/chat/chat-message-script/chat-message-script.js +77 -17
- package/frontend/src/chat/chat.html +5 -1
- package/frontend/src/chat/chat.js +11 -2
- package/frontend/src/detail-objectid/detail-objectid.html +1 -0
- package/frontend/src/detail-objectid/detail-objectid.js +67 -0
- package/frontend/src/document-details/document-property/document-property.html +8 -3
- package/frontend/src/document-details/document-property/document-property.js +7 -0
- package/frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.html +12 -0
- package/frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.js +19 -0
- package/frontend/src/modal/modal.css +0 -1
- package/frontend/src/models/document-search/document-search.js +6 -0
- package/frontend/src/models/models.html +6 -8
- package/frontend/src/models/models.js +257 -85
- package/frontend/src/models/projection-search/projection-search.html +27 -0
- package/frontend/src/models/projection-search/projection-search.js +226 -0
- package/index.d.ts +1 -1
- package/package.json +2 -2
- package/backend/actions/Script/execute.js +0 -20
- package/backend/actions/Script/index.js +0 -3
package/frontend/public/tw.css
CHANGED
package/frontend/src/api.js
CHANGED
|
@@ -29,6 +29,9 @@ client.interceptors.response.use(
|
|
|
29
29
|
);
|
|
30
30
|
|
|
31
31
|
if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
32
|
+
exports.getCapabilities = function getCapabilities() {
|
|
33
|
+
return client.post('', { action: 'getCapabilities' }).then(res => res.data);
|
|
34
|
+
};
|
|
32
35
|
exports.status = function status() {
|
|
33
36
|
return client.post('', { action: 'status' }).then(res => res.data);
|
|
34
37
|
};
|
|
@@ -205,6 +208,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
205
208
|
}
|
|
206
209
|
};
|
|
207
210
|
} else {
|
|
211
|
+
exports.getCapabilities = function getCapabilities() {
|
|
212
|
+
return client.get('/getCapabilities').then(res => res.data);
|
|
213
|
+
};
|
|
208
214
|
exports.status = function status() {
|
|
209
215
|
return client.get('/status').then(res => res.data);
|
|
210
216
|
};
|
|
@@ -6,7 +6,7 @@ const template = require('./chat-message.html');
|
|
|
6
6
|
|
|
7
7
|
module.exports = app => app.component('chat-message', {
|
|
8
8
|
template: template,
|
|
9
|
-
props: ['message', 'targetDashboardId'],
|
|
9
|
+
props: ['message', 'targetDashboardId', 'capabilities'],
|
|
10
10
|
computed: {
|
|
11
11
|
styleForMessage() {
|
|
12
12
|
return this.message.role === 'user' ? 'p-3 bg-muted' : 'py-3 pr-3';
|
|
@@ -62,10 +62,11 @@ module.exports = app => app.component('chat-message', {
|
|
|
62
62
|
if (input.limit && input.limit !== 10) parts.push('limit=' + input.limit);
|
|
63
63
|
return parts.length ? '(' + parts.join(', ') + ')' : '';
|
|
64
64
|
},
|
|
65
|
-
async executeScript(message, script) {
|
|
65
|
+
async executeScript(message, script, dryRun) {
|
|
66
66
|
const { chatMessage } = await api.ChatMessage.executeScript({
|
|
67
67
|
chatMessageId: message._id,
|
|
68
|
-
script
|
|
68
|
+
script,
|
|
69
|
+
dryRun
|
|
69
70
|
});
|
|
70
71
|
message.executionResult = chatMessage.executionResult;
|
|
71
72
|
console.log(message);
|
|
@@ -20,17 +20,19 @@
|
|
|
20
20
|
: 'bg-muted hover:bg-muted text-gray-600'
|
|
21
21
|
]"
|
|
22
22
|
@click="activeTab = 'output'">
|
|
23
|
-
Output
|
|
23
|
+
<span>Output</span>
|
|
24
|
+
<span v-if="isDryRunResult" class="ml-1">[Dry Run]</span>
|
|
25
|
+
<span
|
|
26
|
+
v-if="isDryRunResult"
|
|
27
|
+
class="ml-1 inline-flex align-middle cursor-pointer"
|
|
28
|
+
title="This output was produced in dry run mode"
|
|
29
|
+
@click.stop="openDryRunInfoModal">
|
|
30
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
|
|
31
|
+
<path fill-rule="evenodd" d="M18 10A8 8 0 112 10a8 8 0 0116 0zm-7-3a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 102 0v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
|
32
|
+
</svg>
|
|
33
|
+
</span>
|
|
24
34
|
</button>
|
|
25
35
|
<div class="ml-auto mr-1 flex items-center">
|
|
26
|
-
<button
|
|
27
|
-
v-if="activeTab === 'output'"
|
|
28
|
-
class="px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center"
|
|
29
|
-
@click="openDetailModal">
|
|
30
|
-
<svg xmlns="http://www.w3.org/2000/svg" style="height: 1.2em;" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
31
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5" />
|
|
32
|
-
</svg>
|
|
33
|
-
</button>
|
|
34
36
|
<button
|
|
35
37
|
v-if="activeTab === 'code' && !isEditing"
|
|
36
38
|
class="px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center"
|
|
@@ -39,12 +41,58 @@
|
|
|
39
41
|
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z" />
|
|
40
42
|
</svg>
|
|
41
43
|
</button>
|
|
42
|
-
<
|
|
43
|
-
v-if="
|
|
44
|
-
class="px-2 py-1 text-xs bg-
|
|
45
|
-
@click="
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
<button
|
|
45
|
+
v-if="activeTab === 'output'"
|
|
46
|
+
class="px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center"
|
|
47
|
+
@click="openDetailModal">
|
|
48
|
+
<svg xmlns="http://www.w3.org/2000/svg" style="height: 1.2em;" 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
|
+
<div v-if="!isEditing" class="relative mr-1" ref="runDropdown">
|
|
53
|
+
<div class="flex items-center">
|
|
54
|
+
<button
|
|
55
|
+
class="px-2 py-1 text-xs bg-primary hover:bg-primary-hover text-primary-text border-none rounded-l-md cursor-pointer transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed"
|
|
56
|
+
:disabled="isExecuting"
|
|
57
|
+
@click="executeScript()">
|
|
58
|
+
{{ selectedRunLabel }}
|
|
59
|
+
</button>
|
|
60
|
+
<button
|
|
61
|
+
class="px-1 py-1 text-xs bg-primary hover:bg-primary-hover text-primary-text border-l border-black/10 rounded-r-md cursor-pointer transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed"
|
|
62
|
+
:disabled="isExecuting"
|
|
63
|
+
@click.stop="toggleRunDropdown">
|
|
64
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
65
|
+
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
|
|
66
|
+
</svg>
|
|
67
|
+
</button>
|
|
68
|
+
<button
|
|
69
|
+
class="ml-1 px-1 py-1 text-content-secondary hover:bg-muted rounded cursor-pointer transition-colors"
|
|
70
|
+
@click="openRunInfoModal"
|
|
71
|
+
title="How run works">
|
|
72
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
73
|
+
<path fill-rule="evenodd" d="M18 10A8 8 0 112 10a8 8 0 0116 0zm-7-3a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 102 0v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
|
74
|
+
</svg>
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
<div
|
|
78
|
+
v-if="showRunDropdown"
|
|
79
|
+
class="absolute right-0 z-10 mt-1 w-40 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5">
|
|
80
|
+
<button
|
|
81
|
+
class="block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted"
|
|
82
|
+
:disabled="isExecuting"
|
|
83
|
+
@click="selectRunMode('run')">
|
|
84
|
+
Run
|
|
85
|
+
</button>
|
|
86
|
+
<button
|
|
87
|
+
class="block w-full text-left px-4 py-2 text-xs"
|
|
88
|
+
:class="isExecuting ? 'cursor-not-allowed text-gray-400' : canUseDryRun ? 'text-content-secondary hover:bg-muted' : 'cursor-not-allowed text-gray-400'"
|
|
89
|
+
:aria-disabled="!canUseDryRun"
|
|
90
|
+
:title="dryRunDisabledTitle"
|
|
91
|
+
@click="selectRunMode('dryRun')">
|
|
92
|
+
Dry Run
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
48
96
|
<div class="relative ml-1" ref="dropdown">
|
|
49
97
|
<button
|
|
50
98
|
@click.stop="toggleDropdown"
|
|
@@ -100,7 +148,7 @@
|
|
|
100
148
|
</button>
|
|
101
149
|
<async-button
|
|
102
150
|
class="px-2 py-1 text-xs bg-blue-600 text-white border-none rounded cursor-pointer hover:bg-blue-700 transition-colors disabled:bg-gray-400"
|
|
103
|
-
@click="executeScript">
|
|
151
|
+
@click="executeScript(false)">
|
|
104
152
|
Execute
|
|
105
153
|
</async-button>
|
|
106
154
|
</div>
|
|
@@ -134,6 +182,40 @@
|
|
|
134
182
|
</div>
|
|
135
183
|
</template>
|
|
136
184
|
</modal>
|
|
185
|
+
<modal v-if="showRunInfoModal">
|
|
186
|
+
<template #body>
|
|
187
|
+
<div class="modal-exit" @click="showRunInfoModal = false">×</div>
|
|
188
|
+
<div>
|
|
189
|
+
<div class="mt-4 text-content font-semibold">How Run Works</div>
|
|
190
|
+
<p class="mt-4 text-sm text-content-secondary">
|
|
191
|
+
Run executes the script shown in this message on the server with access to your Mongoose models through <code class="bg-muted px-1 rounded">db.models</code>. The latest script is saved back onto the chat message.
|
|
192
|
+
</p>
|
|
193
|
+
<p class="mt-2 text-sm text-content-secondary">
|
|
194
|
+
Output and console logs are stored on the message, and the Output tab updates to show the latest result.
|
|
195
|
+
</p>
|
|
196
|
+
<p class="mt-2 text-sm text-content-secondary">
|
|
197
|
+
Dry Run executes the same script inside a MongoDB transaction. Studio attaches the transaction session to Mongoose model and collection calls, captures the output and logs, then aborts the transaction so writes are rolled back.
|
|
198
|
+
</p>
|
|
199
|
+
<p class="mt-2 text-sm text-content-secondary">
|
|
200
|
+
Dry Run requires MongoDB transaction support. It is intended for previewing what a script would return before persisting changes.
|
|
201
|
+
</p>
|
|
202
|
+
</div>
|
|
203
|
+
</template>
|
|
204
|
+
</modal>
|
|
205
|
+
<modal v-if="showDryRunInfoModal">
|
|
206
|
+
<template #body>
|
|
207
|
+
<div class="modal-exit" @click="showDryRunInfoModal = false">×</div>
|
|
208
|
+
<div>
|
|
209
|
+
<div class="mt-4 text-content font-semibold">Dry Run Output</div>
|
|
210
|
+
<p class="mt-4 text-sm text-content-secondary">
|
|
211
|
+
This output was produced in dry run mode. The script ran on the server inside a MongoDB transaction, and Studio aborted that transaction after the script finished.
|
|
212
|
+
</p>
|
|
213
|
+
<p class="mt-2 text-sm text-content-secondary">
|
|
214
|
+
No data changes from the script were persisted. The displayed output and console logs were saved on the chat message so you can review what the script returned.
|
|
215
|
+
</p>
|
|
216
|
+
</div>
|
|
217
|
+
</template>
|
|
218
|
+
</modal>
|
|
137
219
|
<modal v-if="showCreateDashboardModal">
|
|
138
220
|
<template #body>
|
|
139
221
|
<div class="modal-exit" @click="showCreateDashboardModal = false">×</div>
|
|
@@ -6,15 +6,20 @@ const template = require('./chat-message-script.html');
|
|
|
6
6
|
|
|
7
7
|
module.exports = app => app.component('chat-message-script', {
|
|
8
8
|
template,
|
|
9
|
-
props: ['message', 'script', 'language', 'targetDashboardId'],
|
|
9
|
+
props: ['message', 'script', 'language', 'targetDashboardId', 'capabilities'],
|
|
10
10
|
emits: ['copyMessage'],
|
|
11
11
|
data() {
|
|
12
12
|
return {
|
|
13
13
|
activeTab: 'code',
|
|
14
|
+
selectedRunMode: 'run',
|
|
14
15
|
showDetailModal: false,
|
|
16
|
+
showRunInfoModal: false,
|
|
17
|
+
showDryRunInfoModal: false,
|
|
15
18
|
showCreateDashboardModal: false,
|
|
16
19
|
showOverwriteDashboardConfirmationModal: false,
|
|
20
|
+
showRunDropdown: false,
|
|
17
21
|
showDropdown: false,
|
|
22
|
+
isExecuting: false,
|
|
18
23
|
newDashboardTitle: '',
|
|
19
24
|
dashboardCode: '',
|
|
20
25
|
createError: null,
|
|
@@ -30,32 +35,60 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
30
35
|
},
|
|
31
36
|
canOverwriteDashboard() {
|
|
32
37
|
return !!this.targetDashboardId;
|
|
38
|
+
},
|
|
39
|
+
canUseDryRun() {
|
|
40
|
+
return this.capabilities?.supportsTransactions !== false;
|
|
41
|
+
},
|
|
42
|
+
dryRunDisabledTitle() {
|
|
43
|
+
return this.canUseDryRun ? null : 'dry run mode requires MongoDB transactions support';
|
|
44
|
+
},
|
|
45
|
+
isDryRunResult() {
|
|
46
|
+
return !!this.message.executionResult?.dryRun;
|
|
47
|
+
},
|
|
48
|
+
selectedRunLabel() {
|
|
49
|
+
return this.selectedRunMode === 'dryRun' ? 'Dry Run' : 'Run';
|
|
33
50
|
}
|
|
34
51
|
},
|
|
35
52
|
methods: {
|
|
36
|
-
async executeScript() {
|
|
53
|
+
async executeScript(dryRun = this.selectedRunMode === 'dryRun') {
|
|
37
54
|
const scriptToRun = this.isEditing ? this.editedScript : this.script;
|
|
55
|
+
if (this.isExecuting) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.showRunDropdown = false;
|
|
38
59
|
this.editedScript = scriptToRun;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
this.
|
|
60
|
+
this.isExecuting = true;
|
|
61
|
+
try {
|
|
62
|
+
const { chatMessage } = await api.ChatMessage.executeScript({
|
|
63
|
+
chatMessageId: this.message._id,
|
|
64
|
+
script: scriptToRun,
|
|
65
|
+
dryRun
|
|
66
|
+
});
|
|
67
|
+
this.message.executionResult = chatMessage.executionResult;
|
|
68
|
+
this.message.script = chatMessage.script;
|
|
69
|
+
this.message.content = chatMessage.content;
|
|
70
|
+
this.editedScript = chatMessage.script;
|
|
71
|
+
if (this.isEditing) {
|
|
72
|
+
this.finishEditing();
|
|
73
|
+
} else {
|
|
74
|
+
this.highlightCode();
|
|
75
|
+
}
|
|
76
|
+
this.activeTab = 'output';
|
|
77
|
+
this.$toast.success('Script executed successfully!');
|
|
78
|
+
return chatMessage;
|
|
79
|
+
} finally {
|
|
80
|
+
this.isExecuting = false;
|
|
51
81
|
}
|
|
52
|
-
this.activeTab = 'output';
|
|
53
|
-
this.$toast.success('Script executed successfully!');
|
|
54
|
-
return chatMessage;
|
|
55
82
|
},
|
|
56
83
|
openDetailModal() {
|
|
57
84
|
this.showDetailModal = true;
|
|
58
85
|
},
|
|
86
|
+
openRunInfoModal() {
|
|
87
|
+
this.showRunInfoModal = true;
|
|
88
|
+
},
|
|
89
|
+
openDryRunInfoModal() {
|
|
90
|
+
this.showDryRunInfoModal = true;
|
|
91
|
+
},
|
|
59
92
|
openCreateDashboardModal() {
|
|
60
93
|
this.newDashboardTitle = '';
|
|
61
94
|
this.dashboardCode = this.script;
|
|
@@ -72,6 +105,21 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
72
105
|
},
|
|
73
106
|
toggleDropdown() {
|
|
74
107
|
this.showDropdown = !this.showDropdown;
|
|
108
|
+
this.showRunDropdown = false;
|
|
109
|
+
},
|
|
110
|
+
toggleRunDropdown() {
|
|
111
|
+
this.showRunDropdown = !this.showRunDropdown;
|
|
112
|
+
this.showDropdown = false;
|
|
113
|
+
},
|
|
114
|
+
selectRunMode(mode) {
|
|
115
|
+
if (this.isExecuting) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (mode === 'dryRun' && !this.canUseDryRun) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.selectedRunMode = mode;
|
|
122
|
+
this.showRunDropdown = false;
|
|
75
123
|
},
|
|
76
124
|
startEditing() {
|
|
77
125
|
this.isEditing = true;
|
|
@@ -97,7 +145,11 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
97
145
|
});
|
|
98
146
|
},
|
|
99
147
|
handleBodyClick(event) {
|
|
148
|
+
const runDropdown = this.$refs.runDropdown;
|
|
100
149
|
const dropdown = this.$refs.dropdown;
|
|
150
|
+
if (runDropdown && typeof runDropdown.contains === 'function' && !runDropdown.contains(event.target)) {
|
|
151
|
+
this.showRunDropdown = false;
|
|
152
|
+
}
|
|
101
153
|
if (dropdown && typeof dropdown.contains === 'function' && !dropdown.contains(event.target)) {
|
|
102
154
|
this.showDropdown = false;
|
|
103
155
|
}
|
|
@@ -172,6 +224,14 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
172
224
|
this.editedScript = newScript;
|
|
173
225
|
this.highlightCode();
|
|
174
226
|
}
|
|
227
|
+
},
|
|
228
|
+
capabilities: {
|
|
229
|
+
immediate: true,
|
|
230
|
+
handler(capabilities) {
|
|
231
|
+
if (capabilities?.supportsTransactions === false && this.selectedRunMode === 'dryRun') {
|
|
232
|
+
this.selectedRunMode = 'run';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
175
235
|
}
|
|
176
236
|
},
|
|
177
237
|
mounted() {
|
|
@@ -112,7 +112,11 @@
|
|
|
112
112
|
</div>
|
|
113
113
|
<ul role="list" class="space-y-4" v-else>
|
|
114
114
|
<li v-for="message in chatMessages" :key="message._id">
|
|
115
|
-
<chat-message
|
|
115
|
+
<chat-message
|
|
116
|
+
:message="message"
|
|
117
|
+
:target-dashboard-id="currentThread?.dashboardId"
|
|
118
|
+
:capabilities="capabilities">
|
|
119
|
+
</chat-message>
|
|
116
120
|
</li>
|
|
117
121
|
</ul>
|
|
118
122
|
</div>
|
|
@@ -23,7 +23,12 @@ module.exports = {
|
|
|
23
23
|
sharingThread: false,
|
|
24
24
|
threadSearch: '',
|
|
25
25
|
showProUpgradeModal: false,
|
|
26
|
-
agentTools: agentToolMetadata
|
|
26
|
+
agentTools: agentToolMetadata,
|
|
27
|
+
capabilities: {
|
|
28
|
+
supportsChangeStreams: false,
|
|
29
|
+
supportsTransactions: true,
|
|
30
|
+
supportsAI: false
|
|
31
|
+
}
|
|
27
32
|
}),
|
|
28
33
|
methods: {
|
|
29
34
|
async sendMessage() {
|
|
@@ -300,8 +305,12 @@ module.exports = {
|
|
|
300
305
|
|
|
301
306
|
this.draftAgentMode = this.getAgentModePreference();
|
|
302
307
|
this.chatThreadId = this.threadId;
|
|
303
|
-
const { chatThreads } = await
|
|
308
|
+
const [{ chatThreads }, { capabilities }] = await Promise.all([
|
|
309
|
+
api.ChatThread.listChatThreads(),
|
|
310
|
+
api.getCapabilities()
|
|
311
|
+
]);
|
|
304
312
|
this.chatThreads = chatThreads;
|
|
313
|
+
this.capabilities = capabilities;
|
|
305
314
|
if (this.chatThreadId) {
|
|
306
315
|
await this.syncCurrentThreadAgentMode();
|
|
307
316
|
const { chatMessages } = await api.ChatThread.getChatThread({ chatThreadId: this.chatThreadId });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<pre class="w-full whitespace-pre-wrap break-words font-mono text-sm text-content-secondary m-0">{{displayValue}}</pre>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const template = require('./detail-objectid.html');
|
|
4
|
+
const ObjectId = require('mongoose/lib/types/objectid');
|
|
5
|
+
|
|
6
|
+
module.exports = app => app.component('detail-objectid', {
|
|
7
|
+
template,
|
|
8
|
+
props: ['value', 'viewMode'],
|
|
9
|
+
emits: ['updated'],
|
|
10
|
+
watch: {
|
|
11
|
+
displayValue: {
|
|
12
|
+
immediate: true,
|
|
13
|
+
handler(val) {
|
|
14
|
+
this.$emit('updated', val);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
computed: {
|
|
19
|
+
format() {
|
|
20
|
+
return this.viewMode || 'hex';
|
|
21
|
+
},
|
|
22
|
+
hexValue() {
|
|
23
|
+
if (this.value == null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (typeof this.value === 'string') {
|
|
27
|
+
return this.value;
|
|
28
|
+
}
|
|
29
|
+
if (typeof this.value === 'object') {
|
|
30
|
+
if (typeof this.value.toHexString === 'function') {
|
|
31
|
+
return this.value.toHexString();
|
|
32
|
+
}
|
|
33
|
+
if (typeof this.value.$oid === 'string') {
|
|
34
|
+
return this.value.$oid;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return String(this.value);
|
|
38
|
+
},
|
|
39
|
+
objectIdDate() {
|
|
40
|
+
const hex = this.hexValue;
|
|
41
|
+
return new ObjectId(hex).getTimestamp();
|
|
42
|
+
},
|
|
43
|
+
displayValue() {
|
|
44
|
+
if (this.value == null) {
|
|
45
|
+
return String(this.value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const hex = this.hexValue;
|
|
49
|
+
if (this.format === 'object_call') {
|
|
50
|
+
return `ObjectId('${hex}')`;
|
|
51
|
+
}
|
|
52
|
+
if (this.format === 'unix_seconds') {
|
|
53
|
+
if (!this.objectIdDate) {
|
|
54
|
+
return 'Invalid ObjectId';
|
|
55
|
+
}
|
|
56
|
+
return String(Math.floor(this.objectIdDate.getTime() / 1000));
|
|
57
|
+
}
|
|
58
|
+
if (this.format === 'date') {
|
|
59
|
+
if (!this.objectIdDate) {
|
|
60
|
+
return 'Invalid ObjectId';
|
|
61
|
+
}
|
|
62
|
+
return this.objectIdDate.toISOString();
|
|
63
|
+
}
|
|
64
|
+
return hex;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<span class="ml-2 text-sm text-content-tertiary">({{(path.instance || 'unknown').toLowerCase()}})</span>
|
|
21
21
|
<div v-if="isGeoJsonGeometry" class="ml-3 inline-flex items-center gap-2">
|
|
22
22
|
<div class="inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold">
|
|
23
|
-
|
|
23
|
+
<button
|
|
24
24
|
type="button"
|
|
25
25
|
class="rounded-full px-2.5 py-0.5 transition"
|
|
26
26
|
:class="detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'"
|
|
@@ -76,6 +76,11 @@
|
|
|
76
76
|
:path="path"
|
|
77
77
|
@update:viewMode="dateViewMode = $event"
|
|
78
78
|
></date-view-mode-picker>
|
|
79
|
+
<objectid-view-mode-picker
|
|
80
|
+
v-if="isObjectIdPath"
|
|
81
|
+
:viewMode="objectIdViewMode"
|
|
82
|
+
@update:viewMode="objectIdViewMode = $event"
|
|
83
|
+
></objectid-view-mode-picker>
|
|
79
84
|
<button
|
|
80
85
|
type="button"
|
|
81
86
|
class="flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-edge-strong bg-surface"
|
|
@@ -202,7 +207,7 @@
|
|
|
202
207
|
<component
|
|
203
208
|
:is="getComponentForPath(path)"
|
|
204
209
|
:value="rawValue"
|
|
205
|
-
:viewMode="isDatePath ? dateViewMode : detailViewMode"
|
|
210
|
+
:viewMode="isDatePath ? dateViewMode : (isObjectIdPath ? objectIdViewMode : detailViewMode)"
|
|
206
211
|
@updated="renderedValue = $event"></component>
|
|
207
212
|
<button
|
|
208
213
|
@click="toggleValueExpansion"
|
|
@@ -219,7 +224,7 @@
|
|
|
219
224
|
<component
|
|
220
225
|
:is="getComponentForPath(path)"
|
|
221
226
|
:value="rawValue"
|
|
222
|
-
:viewMode="isDatePath ? dateViewMode : detailViewMode"
|
|
227
|
+
:viewMode="isDatePath ? dateViewMode : (isObjectIdPath ? objectIdViewMode : detailViewMode)"
|
|
223
228
|
@updated="renderedValue = $event"></component>
|
|
224
229
|
</div>
|
|
225
230
|
</div>
|
|
@@ -18,6 +18,7 @@ module.exports = app => app.component('document-property', {
|
|
|
18
18
|
return {
|
|
19
19
|
dateType: 'picker', // picker, iso
|
|
20
20
|
dateViewMode: 'utc_iso',
|
|
21
|
+
objectIdViewMode: 'hex',
|
|
21
22
|
renderedValue: UNSET,
|
|
22
23
|
isCollapsed: false, // Start uncollapsed by default
|
|
23
24
|
isValueExpanded: false, // Track if the value is expanded
|
|
@@ -38,6 +39,9 @@ module.exports = app => app.component('document-property', {
|
|
|
38
39
|
isDatePath() {
|
|
39
40
|
return this.path?.instance === 'Date';
|
|
40
41
|
},
|
|
42
|
+
isObjectIdPath() {
|
|
43
|
+
return this.path?.instance === 'ObjectID' || this.path?.instance === 'ObjectId';
|
|
44
|
+
},
|
|
41
45
|
rawValue() {
|
|
42
46
|
return this.getValueForPath(this.path.path);
|
|
43
47
|
},
|
|
@@ -177,6 +181,9 @@ module.exports = app => app.component('document-property', {
|
|
|
177
181
|
if (schemaPath.instance === 'Date') {
|
|
178
182
|
return 'detail-date';
|
|
179
183
|
}
|
|
184
|
+
if (schemaPath.instance === 'ObjectID' || schemaPath.instance === 'ObjectId') {
|
|
185
|
+
return 'detail-objectid';
|
|
186
|
+
}
|
|
180
187
|
return 'detail-default';
|
|
181
188
|
},
|
|
182
189
|
getEditComponentForPath(path) {
|
package/frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="flex items-center gap-2" @click.stop>
|
|
2
|
+
<select
|
|
3
|
+
:value="format"
|
|
4
|
+
@input="onFormatChange($event.target.value)"
|
|
5
|
+
class="text-xs border border-edge rounded-md bg-surface px-2 py-1"
|
|
6
|
+
>
|
|
7
|
+
<option value="hex">Hex string</option>
|
|
8
|
+
<option value="object_call">ObjectId('...')</option>
|
|
9
|
+
<option value="unix_seconds">Unix timestamp</option>
|
|
10
|
+
<option value="date">Date</option>
|
|
11
|
+
</select>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const template = require('./objectid-view-mode-picker.html');
|
|
4
|
+
|
|
5
|
+
module.exports = app => app.component('objectid-view-mode-picker', {
|
|
6
|
+
template,
|
|
7
|
+
props: ['viewMode'],
|
|
8
|
+
emits: ['update:viewMode'],
|
|
9
|
+
computed: {
|
|
10
|
+
format() {
|
|
11
|
+
return this.viewMode || 'hex';
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
methods: {
|
|
15
|
+
onFormatChange(newFormat) {
|
|
16
|
+
this.$emit('update:viewMode', newFormat);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
@@ -45,6 +45,12 @@ module.exports = app => app.component('document-search', {
|
|
|
45
45
|
this.$refs.searchInput.focus();
|
|
46
46
|
},
|
|
47
47
|
methods: {
|
|
48
|
+
focusInput() {
|
|
49
|
+
const input = this.$refs.searchInput;
|
|
50
|
+
if (input && typeof input.focus === 'function') {
|
|
51
|
+
input.focus();
|
|
52
|
+
}
|
|
53
|
+
},
|
|
48
54
|
emitSearch() {
|
|
49
55
|
this.$emit('input', this.searchText);
|
|
50
56
|
this.$emit('search', this.searchText);
|
|
@@ -263,14 +263,12 @@
|
|
|
263
263
|
</span>
|
|
264
264
|
</div>
|
|
265
265
|
<div v-if="isProjectionMenuSelected && (outputType === 'table' || outputType === 'json')" class="flex items-center gap-2 w-full mt-2 flex-shrink-0">
|
|
266
|
-
<
|
|
267
|
-
ref="
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
@focus="initProjection($event)"
|
|
273
|
-
@keydown.enter="applyProjectionFromInput()"
|
|
266
|
+
<projection-search
|
|
267
|
+
ref="projectionSearch"
|
|
268
|
+
:value="projectionText"
|
|
269
|
+
:schema-paths="schemaPaths"
|
|
270
|
+
@projection-input="projectionText = $event"
|
|
271
|
+
@apply="applyProjectionFromInput"
|
|
274
272
|
/>
|
|
275
273
|
</div>
|
|
276
274
|
</div>
|