@mongoosejs/studio 0.1.7 → 0.1.8
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.
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const callLLM = require('../../integrations/callLLM');
|
|
5
6
|
const getModelDescriptions = require('../../helpers/getModelDescriptions');
|
|
6
7
|
const mongoose = require('mongoose');
|
|
7
8
|
|
|
@@ -13,7 +14,7 @@ const CreateChatMessageParams = new Archetype({
|
|
|
13
14
|
$type: mongoose.Types.ObjectId
|
|
14
15
|
},
|
|
15
16
|
content: {
|
|
16
|
-
$type:
|
|
17
|
+
$type: 'string'
|
|
17
18
|
},
|
|
18
19
|
authorization: {
|
|
19
20
|
$type: 'string'
|
|
@@ -42,29 +43,43 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
42
43
|
const messages = await ChatMessage.find({ chatThreadId }).sort({ createdAt: 1 });
|
|
43
44
|
const llmMessages = messages.map(m => ({
|
|
44
45
|
role: m.role,
|
|
45
|
-
content:
|
|
46
|
+
content: [{
|
|
47
|
+
type: 'text',
|
|
48
|
+
text: m.content
|
|
49
|
+
}]
|
|
46
50
|
}));
|
|
47
|
-
llmMessages.push({ role: 'user', content });
|
|
51
|
+
llmMessages.push({ role: 'user', content: [{ type: 'text', text: content }] });
|
|
48
52
|
|
|
49
53
|
let summarizePromise = Promise.resolve();
|
|
50
54
|
if (chatThread.title == null) {
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
const threadText = messages
|
|
56
|
+
.filter(m => m.role === 'user' || m.role === 'assistant')
|
|
57
|
+
.map(m => `${m.role.toUpperCase()}: ${m.content}`)
|
|
58
|
+
.join('\n')
|
|
59
|
+
.slice(0, 5000);
|
|
60
|
+
summarizePromise = callLLM(
|
|
61
|
+
[{
|
|
62
|
+
role: 'user',
|
|
63
|
+
content: [{
|
|
64
|
+
type: 'text',
|
|
65
|
+
text: 'Summarize the following chat thread into a concise, helpful title (≤ 6 words).\n\n' +
|
|
66
|
+
`${threadText}\n\n` +
|
|
67
|
+
'Return only the title.'
|
|
68
|
+
}]
|
|
69
|
+
}],
|
|
70
|
+
'You are a helpful assistant that summarizes chat threads into titles.',
|
|
71
|
+
options
|
|
72
|
+
).then(res => {
|
|
73
|
+
const title = res.text;
|
|
53
74
|
chatThread.title = title;
|
|
54
75
|
return chatThread.save();
|
|
55
76
|
});
|
|
56
77
|
}
|
|
57
78
|
|
|
58
|
-
if (options?.context) {
|
|
59
|
-
llmMessages.unshift({
|
|
60
|
-
role: 'system',
|
|
61
|
-
content: options.context
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
79
|
const modelDescriptions = getModelDescriptions(db);
|
|
80
|
+
const system = systemPrompt + '\n\n' + modelDescriptions + (options?.context ? '\n\n' + options.context : '');
|
|
66
81
|
|
|
67
|
-
// Create the chat message and get
|
|
82
|
+
// Create the chat message and get LLM response in parallel
|
|
68
83
|
const chatMessages = await Promise.all([
|
|
69
84
|
ChatMessage.create({
|
|
70
85
|
chatThreadId,
|
|
@@ -73,8 +88,8 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
73
88
|
script,
|
|
74
89
|
executionResult: null
|
|
75
90
|
}),
|
|
76
|
-
|
|
77
|
-
const content = res.
|
|
91
|
+
callLLM(llmMessages, system, options).then(res => {
|
|
92
|
+
const content = res.text;
|
|
78
93
|
return ChatMessage.create({
|
|
79
94
|
chatThreadId,
|
|
80
95
|
role: 'assistant',
|
|
@@ -87,157 +102,43 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
87
102
|
return { chatMessages, chatThread };
|
|
88
103
|
};
|
|
89
104
|
|
|
90
|
-
async function summarizeChatThread(messages, authorization, options) {
|
|
91
|
-
if (options?.openAIAPIKey) {
|
|
92
|
-
const response = await callOpenAI({
|
|
93
|
-
apiKey: options.openAIAPIKey,
|
|
94
|
-
model: options.model,
|
|
95
|
-
messages: [
|
|
96
|
-
{
|
|
97
|
-
role: 'system',
|
|
98
|
-
content: 'Summarize the following conversation into a concise title of at most 7 words. Respond with the title only.'
|
|
99
|
-
},
|
|
100
|
-
...messages
|
|
101
|
-
]
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return { response };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
108
|
-
if (authorization) {
|
|
109
|
-
headers.Authorization = authorization;
|
|
110
|
-
}
|
|
111
|
-
const response = await fetch('https://mongoose-js.netlify.app/.netlify/functions/summarizeChatThread', {
|
|
112
|
-
method: 'POST',
|
|
113
|
-
headers,
|
|
114
|
-
body: JSON.stringify({
|
|
115
|
-
messages
|
|
116
|
-
})
|
|
117
|
-
}).then(response => {
|
|
118
|
-
if (response.status < 200 || response.status >= 400) {
|
|
119
|
-
return response.json().then(data => {
|
|
120
|
-
throw new Error(`Mongoose Studio chat thread summarization error: ${data.message}`);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
return response;
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
return await response.json();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function createChatMessageCore(messages, modelDescriptions, model, authorization, options) {
|
|
130
|
-
if (options?.openAIAPIKey) {
|
|
131
|
-
const openAIMessages = [];
|
|
132
|
-
if (modelDescriptions) {
|
|
133
|
-
openAIMessages.push({
|
|
134
|
-
role: 'system',
|
|
135
|
-
content: `${systemPrompt}:\n\n${modelDescriptions}`
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
openAIMessages.push(...messages);
|
|
139
|
-
|
|
140
|
-
const response = await callOpenAI({
|
|
141
|
-
apiKey: options.openAIAPIKey,
|
|
142
|
-
model,
|
|
143
|
-
messages: openAIMessages
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
return { response };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
150
|
-
if (authorization) {
|
|
151
|
-
headers.Authorization = authorization;
|
|
152
|
-
}
|
|
153
|
-
const response = await fetch('https://mongoose-js.netlify.app/.netlify/functions/createChatMessage', {
|
|
154
|
-
method: 'POST',
|
|
155
|
-
headers,
|
|
156
|
-
body: JSON.stringify({
|
|
157
|
-
messages,
|
|
158
|
-
modelDescriptions,
|
|
159
|
-
model
|
|
160
|
-
})
|
|
161
|
-
}).then(response => {
|
|
162
|
-
if (response.status < 200 || response.status >= 400) {
|
|
163
|
-
return response.json().then(data => {
|
|
164
|
-
throw new Error(`Mongoose Studio chat completion error: ${data.message}`);
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
return response;
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
return await response.json();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
105
|
const systemPrompt = `
|
|
174
|
-
You are a data querying assistant who writes scripts for users accessing MongoDB data using Node.js and Mongoose.
|
|
175
|
-
|
|
176
|
-
Keep scripts concise. Avoid unnecessary comments, error handling, and temporary variables.
|
|
177
|
-
|
|
178
|
-
Do not write any imports or require() statements, that will cause the script to break.
|
|
106
|
+
You are a data querying assistant who writes scripts for users accessing MongoDB data using Node.js and Mongoose.
|
|
179
107
|
|
|
180
|
-
|
|
108
|
+
Keep scripts concise. Avoid unnecessary comments, error handling, and temporary variables.
|
|
181
109
|
|
|
182
|
-
|
|
110
|
+
Do not write any imports or require() statements, that will cause the script to break.
|
|
183
111
|
|
|
184
|
-
|
|
112
|
+
If the user approves the script, the script will run in the Node.js server in a sandboxed vm.createContext() call with only 1 global variable: db, which contains the Mongoose connection. The script return value will then send the response via JSON to the client. Be aware that the result of the query will be serialized to JSON before being displayed to the user. MAKE SURE TO RETURN A VALUE FROM THE SCRIPT.
|
|
185
113
|
|
|
186
|
-
|
|
114
|
+
Optimize scripts for readability first, followed by reliability, followed by performance. Avoid using the aggregation framework unless explicitly requested by the user. Use indexed fields in queries where possible.
|
|
187
115
|
|
|
188
|
-
|
|
116
|
+
Assume the user has pre-defined schemas and models. Do not define any new schemas or models for the user.
|
|
189
117
|
|
|
190
|
-
|
|
118
|
+
Use async/await where possible. Assume top-level await is allowed.
|
|
191
119
|
|
|
192
|
-
|
|
120
|
+
Write at most one script, unless the user explicitly asks for multiple scripts.
|
|
193
121
|
|
|
194
|
-
|
|
122
|
+
Think carefully about the user's input and identify the models referred to by the user's query.
|
|
195
123
|
|
|
196
|
-
|
|
124
|
+
Format output as Markdown, including code fences for any scripts the user requested.
|
|
197
125
|
|
|
198
|
-
|
|
126
|
+
Add a brief text description of what the script does.
|
|
199
127
|
|
|
200
|
-
|
|
128
|
+
If the user's query is best answered with a chart, return a Chart.js 4 configuration as \`return { $chart: chartJSConfig };\`. Disable ChartJS animation by default unless user asks for it. Set responsive: true, maintainAspectRatio: false options unless the user explicitly asks.
|
|
201
129
|
|
|
202
|
-
|
|
130
|
+
If the user\'s query is best answered by a map, return an object { $featureCollection } which contains a GeoJSON FeatureCollection
|
|
203
131
|
|
|
204
|
-
|
|
205
|
-
const users = await db.model('User').find({ isDeleted: false });
|
|
206
|
-
return { numUsers: users.length };
|
|
207
|
-
\`\`\`
|
|
132
|
+
Example output:
|
|
208
133
|
|
|
209
|
-
|
|
134
|
+
The following script counts the number of users which are not deleted.
|
|
210
135
|
|
|
211
|
-
|
|
212
|
-
|
|
136
|
+
\`\`\`javascript
|
|
137
|
+
const users = await db.model('User').find({ isDeleted: false });
|
|
138
|
+
return { numUsers: users.length };
|
|
139
|
+
\`\`\`
|
|
213
140
|
|
|
214
|
-
|
|
215
|
-
if (!apiKey) {
|
|
216
|
-
throw new Error('OpenAI API key required');
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
220
|
-
method: 'POST',
|
|
221
|
-
headers: {
|
|
222
|
-
'Content-Type': 'application/json',
|
|
223
|
-
Authorization: `Bearer ${apiKey}`
|
|
224
|
-
},
|
|
225
|
-
body: JSON.stringify({
|
|
226
|
-
model: model || 'gpt-4o-mini',
|
|
227
|
-
messages
|
|
228
|
-
})
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
const data = await response.json();
|
|
232
|
-
|
|
233
|
-
if (response.status < 200 || response.status >= 400) {
|
|
234
|
-
throw new Error(`OpenAI chat completion error ${response.status}: ${data.error?.message || data.message || 'Unknown error'}`);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const content = data?.choices?.[0]?.message?.content;
|
|
238
|
-
if (!content) {
|
|
239
|
-
throw new Error('OpenAI chat completion error: missing response content');
|
|
240
|
-
}
|
|
141
|
+
-----------
|
|
241
142
|
|
|
242
|
-
|
|
243
|
-
|
|
143
|
+
Here is a description of the user's models. Assume these are the only models available in the system unless explicitly instructed otherwise by the user.
|
|
144
|
+
`.trim();
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createAnthropic } = require('@ai-sdk/anthropic');
|
|
4
|
+
const { createOpenAI } = require('@ai-sdk/openai');
|
|
5
|
+
const { generateText } = require('ai');
|
|
6
|
+
|
|
7
|
+
module.exports = async function callLLM(messages, system, options) {
|
|
8
|
+
let provider = null;
|
|
9
|
+
let model = null;
|
|
10
|
+
if (options?.openAIAPIKey && options?.anthropicAPIKey) {
|
|
11
|
+
throw new Error('Cannot set both OpenAI and Anthropic API keys');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (options?.openAIAPIKey) {
|
|
15
|
+
provider = createOpenAI({ apiKey: options.openAIAPIKey });
|
|
16
|
+
model = options?.model ?? 'gpt-4o-mini';
|
|
17
|
+
} else if (options?.anthropicAPIKey) {
|
|
18
|
+
provider = createAnthropic({ apiKey: options.anthropicAPIKey });
|
|
19
|
+
model = options?.model ?? 'claude-haiku-4-5-20251001';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (provider) {
|
|
23
|
+
return generateText({
|
|
24
|
+
model: provider(model),
|
|
25
|
+
system,
|
|
26
|
+
messages
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
31
|
+
const response = await fetch('https://mongoose-js.netlify.app/.netlify/functions/getChatCompletion', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers,
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
messages,
|
|
36
|
+
model: options?.model
|
|
37
|
+
})
|
|
38
|
+
}).then(response => {
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
return response.json().then(data => {
|
|
41
|
+
throw new Error(`Mongoose Studio chat completion error: ${data.message}`);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return response;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return await response.json().then(res => ({ text: res.response }));
|
|
48
|
+
};
|
package/frontend/public/app.js
CHANGED
|
@@ -16709,7 +16709,7 @@ module.exports = function stringToParts(str) {
|
|
|
16709
16709
|
/***/ ((module) => {
|
|
16710
16710
|
|
|
16711
16711
|
"use strict";
|
|
16712
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.
|
|
16712
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.8","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"},"license":"Apache-2.0","dependencies":{"@ai-sdk/openai":"2.x","@ai-sdk/anthropic":"2.x","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","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 || ^9.0.0-0"},"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":"9.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"}}');
|
|
16713
16713
|
|
|
16714
16714
|
/***/ })
|
|
16715
16715
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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": {
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
},
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@ai-sdk/openai": "2.x",
|
|
13
|
+
"@ai-sdk/anthropic": "2.x",
|
|
14
|
+
"ai": "5.x",
|
|
12
15
|
"archetype": "0.13.1",
|
|
13
16
|
"csv-stringify": "6.3.0",
|
|
14
17
|
"ejson": "^2.2.3",
|