@mongoosejs/studio 0.5.1 → 0.5.2
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/Dashboard/getDashboard.js +29 -2
- package/backend/actions/Model/dropCollection.js +29 -0
- package/backend/actions/Model/index.js +1 -0
- package/backend/authorize.js +1 -0
- package/backend/db/dashboardSchema.js +0 -32
- package/backend/index.js +1 -1
- package/backend/integrations/callLLM.js +5 -20
- package/backend/integrations/streamLLM.js +5 -24
- package/backend/sandbox/createSandbox.js +6 -0
- package/backend/vercel.js +3 -0
- package/frontend/index.js +2 -1
- package/frontend/public/app.js +237 -196
- package/frontend/public/tw.css +29 -0
- package/frontend/src/api.js +96 -164
- package/frontend/src/chat/chat.html +1 -1
- package/frontend/src/document/confirm-changes/confirm-changes.js +20 -0
- package/frontend/src/index.js +5 -0
- package/frontend/src/json-node/json-node.html +41 -39
- package/frontend/src/json-node/json-node.js +38 -0
- package/frontend/src/list-json/list-json.html +3 -2
- package/frontend/src/list-json/list-json.js +5 -0
- package/frontend/src/models/models.html +45 -5
- package/frontend/src/models/models.js +26 -0
- package/frontend/src/navbar/navbar.html +19 -3
- package/frontend/src/navbar/navbar.js +6 -0
- package/package.json +1 -1
package/frontend/public/tw.css
CHANGED
|
@@ -1583,6 +1583,16 @@ video {
|
|
|
1583
1583
|
gap: 2rem;
|
|
1584
1584
|
}
|
|
1585
1585
|
|
|
1586
|
+
.gap-x-1 {
|
|
1587
|
+
-moz-column-gap: 0.25rem;
|
|
1588
|
+
column-gap: 0.25rem;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
.gap-x-1\.5 {
|
|
1592
|
+
-moz-column-gap: 0.375rem;
|
|
1593
|
+
column-gap: 0.375rem;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1586
1596
|
.gap-x-4 {
|
|
1587
1597
|
-moz-column-gap: 1rem;
|
|
1588
1598
|
column-gap: 1rem;
|
|
@@ -1867,6 +1877,11 @@ video {
|
|
|
1867
1877
|
border-color: rgb(254 202 202 / var(--tw-border-opacity));
|
|
1868
1878
|
}
|
|
1869
1879
|
|
|
1880
|
+
.border-red-300 {
|
|
1881
|
+
--tw-border-opacity: 1;
|
|
1882
|
+
border-color: rgb(252 165 165 / var(--tw-border-opacity));
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1870
1885
|
.border-red-400 {
|
|
1871
1886
|
--tw-border-opacity: 1;
|
|
1872
1887
|
border-color: rgb(248 113 113 / var(--tw-border-opacity));
|
|
@@ -2446,6 +2461,10 @@ video {
|
|
|
2446
2461
|
font-size: 10px;
|
|
2447
2462
|
}
|
|
2448
2463
|
|
|
2464
|
+
.text-\[11px\] {
|
|
2465
|
+
font-size: 11px;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2449
2468
|
.text-base {
|
|
2450
2469
|
font-size: 1rem;
|
|
2451
2470
|
line-height: 1.5rem;
|
|
@@ -3227,6 +3246,11 @@ video {
|
|
|
3227
3246
|
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
|
|
3228
3247
|
}
|
|
3229
3248
|
|
|
3249
|
+
.hover\:bg-red-50:hover {
|
|
3250
|
+
--tw-bg-opacity: 1;
|
|
3251
|
+
background-color: rgb(254 242 242 / var(--tw-bg-opacity));
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3230
3254
|
.hover\:bg-red-500:hover {
|
|
3231
3255
|
--tw-bg-opacity: 1;
|
|
3232
3256
|
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
|
|
@@ -3487,6 +3511,11 @@ video {
|
|
|
3487
3511
|
--tw-ring-color: var(--color-primary-subtle);
|
|
3488
3512
|
}
|
|
3489
3513
|
|
|
3514
|
+
.focus\:ring-red-200:focus {
|
|
3515
|
+
--tw-ring-opacity: 1;
|
|
3516
|
+
--tw-ring-color: rgb(254 202 202 / var(--tw-ring-opacity));
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3490
3519
|
.focus\:ring-sky-600:focus {
|
|
3491
3520
|
--tw-ring-opacity: 1;
|
|
3492
3521
|
--tw-ring-color: rgb(2 132 199 / var(--tw-ring-opacity));
|
package/frontend/src/api.js
CHANGED
|
@@ -9,6 +9,7 @@ const client = axios.create({
|
|
|
9
9
|
console.log('API baseURL:', window.MONGOOSE_STUDIO_CONFIG.baseURL);
|
|
10
10
|
|
|
11
11
|
window.apiClient = client;
|
|
12
|
+
exports.fetch = fetch;
|
|
12
13
|
client.interceptors.request.use(req => {
|
|
13
14
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
14
15
|
if (accessToken) {
|
|
@@ -24,10 +25,92 @@ client.interceptors.response.use(
|
|
|
24
25
|
if (typeof err?.response?.data === 'string') {
|
|
25
26
|
throw new Error(`Error in ${err.config?.method} ${err.config?.url}: ${err.response.data}`);
|
|
26
27
|
}
|
|
28
|
+
if (typeof err?.response?.data?.message === 'string') {
|
|
29
|
+
throw new Error(`Error in ${err.config?.method} ${err.config?.url}: ${err.response.data.message}`);
|
|
30
|
+
}
|
|
27
31
|
throw err;
|
|
28
32
|
}
|
|
29
33
|
);
|
|
30
34
|
|
|
35
|
+
async function* streamSSE(url, options = {}) {
|
|
36
|
+
const fetch = exports.fetch;
|
|
37
|
+
const response = await fetch(url, {
|
|
38
|
+
method: 'GET',
|
|
39
|
+
...options,
|
|
40
|
+
headers: {
|
|
41
|
+
...(options.headers || {}),
|
|
42
|
+
Accept: 'text/event-stream'
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const reader = response.body.getReader();
|
|
51
|
+
const decoder = new TextDecoder('utf-8');
|
|
52
|
+
let buffer = '';
|
|
53
|
+
|
|
54
|
+
while (true) {
|
|
55
|
+
const { done, value } = await reader.read();
|
|
56
|
+
if (done) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
buffer += decoder.decode(value, { stream: true });
|
|
60
|
+
|
|
61
|
+
let eventEnd;
|
|
62
|
+
while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
|
|
63
|
+
const eventStr = buffer.slice(0, eventEnd);
|
|
64
|
+
buffer = buffer.slice(eventEnd + 2);
|
|
65
|
+
|
|
66
|
+
const event = parseSSEEvent(eventStr);
|
|
67
|
+
if (event == null) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (event.type === 'error') {
|
|
71
|
+
const message = getSSEErrorMessage(event.data);
|
|
72
|
+
throw new Error(message);
|
|
73
|
+
}
|
|
74
|
+
yield event.data;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseSSEEvent(eventStr) {
|
|
80
|
+
const lines = eventStr.split('\n');
|
|
81
|
+
let type = 'message';
|
|
82
|
+
let data = '';
|
|
83
|
+
for (const line of lines) {
|
|
84
|
+
if (line.startsWith('event:')) {
|
|
85
|
+
type = line.slice(6).trim() || 'message';
|
|
86
|
+
} else if (line.startsWith('data:')) {
|
|
87
|
+
data += line.slice(5).trim();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!data) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
return { type, data: JSON.parse(data) };
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return { type, data };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getSSEErrorMessage(data) {
|
|
102
|
+
if (typeof data === 'string') {
|
|
103
|
+
return data || 'Streaming request failed.';
|
|
104
|
+
}
|
|
105
|
+
if (typeof data?.message === 'string' && data.message.length > 0) {
|
|
106
|
+
return data.message;
|
|
107
|
+
}
|
|
108
|
+
if (typeof data?.error === 'string' && data.error.length > 0) {
|
|
109
|
+
return data.error;
|
|
110
|
+
}
|
|
111
|
+
return 'Streaming request failed.';
|
|
112
|
+
}
|
|
113
|
+
|
|
31
114
|
if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
32
115
|
exports.getCapabilities = function getCapabilities() {
|
|
33
116
|
return client.post('', { action: 'getCapabilities' }).then(res => res.data);
|
|
@@ -103,6 +186,7 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
103
186
|
exportQueryResults(params) {
|
|
104
187
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
105
188
|
|
|
189
|
+
const fetch = exports.fetch;
|
|
106
190
|
return fetch(window.MONGOOSE_STUDIO_CONFIG.baseURL + '?' + new URLSearchParams({ ...params, action: 'Model.exportQueryResults' }).toString(), {
|
|
107
191
|
method: 'GET',
|
|
108
192
|
headers: {
|
|
@@ -166,6 +250,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
166
250
|
dropIndex: function dropIndex(params) {
|
|
167
251
|
return client.post('', { action: 'Model.dropIndex', ...params }).then(res => res.data);
|
|
168
252
|
},
|
|
253
|
+
dropCollection: function dropCollection(params) {
|
|
254
|
+
return client.post('', { action: 'Model.dropCollection', ...params }).then(res => res.data);
|
|
255
|
+
},
|
|
169
256
|
listModels: function listModels() {
|
|
170
257
|
return client.post('', { action: 'Model.listModels' }).then(res => res.data);
|
|
171
258
|
},
|
|
@@ -254,50 +341,11 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
254
341
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
255
342
|
const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/ChatThread/streamChatMessage?' + new URLSearchParams(params).toString();
|
|
256
343
|
|
|
257
|
-
|
|
258
|
-
method: 'GET',
|
|
344
|
+
yield* streamSSE(url, {
|
|
259
345
|
headers: {
|
|
260
|
-
Authorization: `${accessToken}
|
|
261
|
-
Accept: 'text/event-stream'
|
|
346
|
+
Authorization: `${accessToken}`
|
|
262
347
|
}
|
|
263
348
|
});
|
|
264
|
-
|
|
265
|
-
if (!response.ok) {
|
|
266
|
-
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const reader = response.body.getReader();
|
|
270
|
-
const decoder = new TextDecoder('utf-8');
|
|
271
|
-
let buffer = '';
|
|
272
|
-
|
|
273
|
-
while (true) {
|
|
274
|
-
const { done, value } = await reader.read();
|
|
275
|
-
if (done) break;
|
|
276
|
-
buffer += decoder.decode(value, { stream: true });
|
|
277
|
-
|
|
278
|
-
let eventEnd;
|
|
279
|
-
while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
|
|
280
|
-
const eventStr = buffer.slice(0, eventEnd);
|
|
281
|
-
buffer = buffer.slice(eventEnd + 2);
|
|
282
|
-
|
|
283
|
-
// Parse SSE event
|
|
284
|
-
const lines = eventStr.split('\n');
|
|
285
|
-
let data = '';
|
|
286
|
-
for (const line of lines) {
|
|
287
|
-
if (line.startsWith('data:')) {
|
|
288
|
-
data += line.slice(5).trim();
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (data) {
|
|
292
|
-
try {
|
|
293
|
-
const res = JSON.parse(data);
|
|
294
|
-
yield res;
|
|
295
|
-
} catch (err) {
|
|
296
|
-
yield data;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
349
|
}
|
|
302
350
|
};
|
|
303
351
|
exports.ChatMessage = {
|
|
@@ -330,6 +378,7 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
330
378
|
exportQueryResults(params) {
|
|
331
379
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
332
380
|
|
|
381
|
+
const fetch = exports.fetch;
|
|
333
382
|
return fetch(window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/exportQueryResults?' + new URLSearchParams(params).toString(), {
|
|
334
383
|
method: 'GET',
|
|
335
384
|
headers: {
|
|
@@ -367,50 +416,11 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
367
416
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
368
417
|
const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/getDocumentsStream?' + new URLSearchParams(params).toString();
|
|
369
418
|
|
|
370
|
-
|
|
371
|
-
method: 'GET',
|
|
419
|
+
yield* streamSSE(url, {
|
|
372
420
|
headers: {
|
|
373
|
-
Authorization: `${accessToken}
|
|
374
|
-
Accept: 'text/event-stream'
|
|
421
|
+
Authorization: `${accessToken}`
|
|
375
422
|
}
|
|
376
423
|
});
|
|
377
|
-
|
|
378
|
-
if (!response.ok) {
|
|
379
|
-
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const reader = response.body.getReader();
|
|
383
|
-
const decoder = new TextDecoder('utf-8');
|
|
384
|
-
let buffer = '';
|
|
385
|
-
|
|
386
|
-
while (true) {
|
|
387
|
-
const { done, value } = await reader.read();
|
|
388
|
-
if (done) break;
|
|
389
|
-
buffer += decoder.decode(value, { stream: true });
|
|
390
|
-
|
|
391
|
-
let eventEnd;
|
|
392
|
-
while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
|
|
393
|
-
const eventStr = buffer.slice(0, eventEnd);
|
|
394
|
-
buffer = buffer.slice(eventEnd + 2);
|
|
395
|
-
|
|
396
|
-
// Parse SSE event
|
|
397
|
-
const lines = eventStr.split('\n');
|
|
398
|
-
let data = '';
|
|
399
|
-
for (const line of lines) {
|
|
400
|
-
if (line.startsWith('data:')) {
|
|
401
|
-
data += line.slice(5).trim();
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
if (data) {
|
|
405
|
-
try {
|
|
406
|
-
yield JSON.parse(data);
|
|
407
|
-
} catch (err) {
|
|
408
|
-
// If not JSON, yield as string
|
|
409
|
-
yield data;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
424
|
},
|
|
415
425
|
getEstimatedDocumentCounts: function getEstimatedDocumentCounts() {
|
|
416
426
|
return client.post('/Model/getEstimatedDocumentCounts', {}).then(res => res.data);
|
|
@@ -419,51 +429,12 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
419
429
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
420
430
|
const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/streamDocumentChanges?' + new URLSearchParams(params).toString();
|
|
421
431
|
|
|
422
|
-
|
|
423
|
-
method: 'GET',
|
|
432
|
+
yield* streamSSE(url, {
|
|
424
433
|
headers: {
|
|
425
|
-
Authorization: `${accessToken}
|
|
426
|
-
Accept: 'text/event-stream'
|
|
434
|
+
Authorization: `${accessToken}`
|
|
427
435
|
},
|
|
428
436
|
signal: options.signal
|
|
429
437
|
});
|
|
430
|
-
|
|
431
|
-
if (!response.ok) {
|
|
432
|
-
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const reader = response.body.getReader();
|
|
436
|
-
const decoder = new TextDecoder('utf-8');
|
|
437
|
-
let buffer = '';
|
|
438
|
-
|
|
439
|
-
while (true) {
|
|
440
|
-
const { done, value } = await reader.read();
|
|
441
|
-
if (done) break;
|
|
442
|
-
buffer += decoder.decode(value, { stream: true });
|
|
443
|
-
|
|
444
|
-
let eventEnd;
|
|
445
|
-
while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
|
|
446
|
-
const eventStr = buffer.slice(0, eventEnd);
|
|
447
|
-
buffer = buffer.slice(eventEnd + 2);
|
|
448
|
-
|
|
449
|
-
// Parse SSE event
|
|
450
|
-
const lines = eventStr.split('\n');
|
|
451
|
-
let data = '';
|
|
452
|
-
for (const line of lines) {
|
|
453
|
-
if (line.startsWith('data:')) {
|
|
454
|
-
data += line.slice(5).trim();
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
if (data) {
|
|
458
|
-
try {
|
|
459
|
-
yield JSON.parse(data);
|
|
460
|
-
} catch (err) {
|
|
461
|
-
// If not JSON, yield as string
|
|
462
|
-
yield data;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
438
|
},
|
|
468
439
|
getCollectionInfo: function getCollectionInfo(params) {
|
|
469
440
|
return client.post('/Model/getCollectionInfo', params).then(res => res.data);
|
|
@@ -484,50 +455,11 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
484
455
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
485
456
|
const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/streamChatMessage?' + new URLSearchParams(params).toString();
|
|
486
457
|
|
|
487
|
-
|
|
488
|
-
method: 'GET',
|
|
458
|
+
yield* streamSSE(url, {
|
|
489
459
|
headers: {
|
|
490
|
-
Authorization: `${accessToken}
|
|
491
|
-
Accept: 'text/event-stream'
|
|
460
|
+
Authorization: `${accessToken}`
|
|
492
461
|
}
|
|
493
462
|
});
|
|
494
|
-
|
|
495
|
-
if (!response.ok) {
|
|
496
|
-
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const reader = response.body.getReader();
|
|
500
|
-
const decoder = new TextDecoder('utf-8');
|
|
501
|
-
let buffer = '';
|
|
502
|
-
|
|
503
|
-
while (true) {
|
|
504
|
-
const { done, value } = await reader.read();
|
|
505
|
-
if (done) break;
|
|
506
|
-
buffer += decoder.decode(value, { stream: true });
|
|
507
|
-
|
|
508
|
-
let eventEnd;
|
|
509
|
-
while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
|
|
510
|
-
const eventStr = buffer.slice(0, eventEnd);
|
|
511
|
-
buffer = buffer.slice(eventEnd + 2);
|
|
512
|
-
|
|
513
|
-
// Parse SSE event
|
|
514
|
-
const lines = eventStr.split('\n');
|
|
515
|
-
let data = '';
|
|
516
|
-
for (const line of lines) {
|
|
517
|
-
if (line.startsWith('data:')) {
|
|
518
|
-
data += line.slice(5).trim();
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (data) {
|
|
522
|
-
try {
|
|
523
|
-
yield JSON.parse(data);
|
|
524
|
-
} catch (err) {
|
|
525
|
-
// If not JSON, yield as string
|
|
526
|
-
yield data;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
463
|
},
|
|
532
464
|
updateDocuments: function updateDocument(params) {
|
|
533
465
|
return client.post('/Model/updateDocuments', params).then(res => res.data);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</button>
|
|
13
13
|
<!-- Sidebar: Chat Threads -->
|
|
14
14
|
<aside
|
|
15
|
-
class="bg-page border-r overflow-hidden
|
|
15
|
+
class="bg-page border-r overflow-hidden transition-all duration-300 ease-in-out z-20 w-64 fixed top-[55px] bottom-0 lg:top-auto lg:bottom-auto lg:relative lg:h-full shrink-0 flex flex-col"
|
|
16
16
|
:class="hideSidebar === false ? 'translate-x-0' : hideSidebar === true ? '-translate-x-full lg:translate-x-0 lg:!w-0 lg:!border-r-0' : '-translate-x-full lg:translate-x-0'"
|
|
17
17
|
style="z-index: 1000">
|
|
18
18
|
<!-- Search + New Thread -->
|
|
@@ -26,9 +26,29 @@ module.exports = app => app.component('confirm-changes', {
|
|
|
26
26
|
},
|
|
27
27
|
startSave() {
|
|
28
28
|
this.$emit('save');
|
|
29
|
+
},
|
|
30
|
+
onEnter(event) {
|
|
31
|
+
if (event.key !== 'Enter') {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const modalMasks = Array.from(document.querySelectorAll('.modal-mask'));
|
|
36
|
+
const currentMask = this.$el?.closest('.modal-mask');
|
|
37
|
+
const isTopMostModal = modalMasks.length > 0 && modalMasks[modalMasks.length - 1] === currentMask;
|
|
38
|
+
|
|
39
|
+
if (!isTopMostModal) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
this.startSave();
|
|
29
45
|
}
|
|
30
46
|
},
|
|
31
47
|
mounted() {
|
|
48
|
+
window.addEventListener('keydown', this.onEnter);
|
|
32
49
|
Prism.highlightElement(this.$refs.code);
|
|
50
|
+
},
|
|
51
|
+
beforeUnmount() {
|
|
52
|
+
window.removeEventListener('keydown', this.onEnter);
|
|
33
53
|
}
|
|
34
54
|
});
|
package/frontend/src/index.js
CHANGED
|
@@ -221,6 +221,11 @@ router.beforeEach((to, from, next) => {
|
|
|
221
221
|
});
|
|
222
222
|
|
|
223
223
|
router.beforeEach((to, from, next) => {
|
|
224
|
+
if (['chat index', 'chat'].includes(to.name) && !window.MONGOOSE_STUDIO_CONFIG.hasLLMAPIKey) {
|
|
225
|
+
next({ name: 'root' });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
224
229
|
if (to.name === 'root' && window.state.roles && window.state.roles[0] === 'dashboards') {
|
|
225
230
|
return next({ name: 'dashboards' });
|
|
226
231
|
} else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<div>
|
|
2
|
-
<div class="flex items-baseline whitespace-pre" :style="indentStyle">
|
|
1
|
+
<div class="min-w-0 max-w-full">
|
|
2
|
+
<div class="flex min-w-0 max-w-full items-baseline whitespace-pre-wrap break-words" :style="indentStyle">
|
|
3
3
|
<button
|
|
4
4
|
v-if="showToggle"
|
|
5
5
|
type="button"
|
|
@@ -23,36 +23,32 @@
|
|
|
23
23
|
</template>
|
|
24
24
|
</template>
|
|
25
25
|
<template v-else>
|
|
26
|
-
<!--
|
|
27
|
-
If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.
|
|
28
|
-
This is done via CSS ellipsis strategy.
|
|
29
|
-
-->
|
|
30
26
|
<span
|
|
31
27
|
v-if="shouldShowReferenceLink"
|
|
32
|
-
class="inline-flex items-baseline
|
|
28
|
+
class="group inline-flex min-w-0 max-w-full items-baseline flex-wrap gap-x-1.5"
|
|
33
29
|
>
|
|
34
|
-
<span
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
? {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
whiteSpace: 'nowrap',
|
|
43
|
-
verticalAlign: 'bottom'
|
|
44
|
-
}
|
|
45
|
-
: {}"
|
|
46
|
-
:title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
|
|
47
|
-
>
|
|
48
|
-
{{ formattedValue }}
|
|
49
|
-
</span>
|
|
50
|
-
<span>
|
|
51
|
-
{{ comma }}
|
|
30
|
+
<span class="min-w-0 max-w-full">
|
|
31
|
+
<span
|
|
32
|
+
:class="[...valueClasses, ...stringWrapperClasses, 'underline', 'decoration-dotted', 'underline-offset-2']"
|
|
33
|
+
:style="isStringValue ? { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } : {}"
|
|
34
|
+
:title="shouldTruncateString && !isStringExpanded ? value : undefined"
|
|
35
|
+
>
|
|
36
|
+
{{ displayedValue }}
|
|
37
|
+
</span><template v-if="!(shouldTruncateString && !isStringExpanded)">{{ comma }}</template>
|
|
52
38
|
</span>
|
|
39
|
+
<button
|
|
40
|
+
v-if="shouldTruncateString"
|
|
41
|
+
type="button"
|
|
42
|
+
class="inline-flex shrink-0 items-center gap-1 rounded-full border border-edge bg-surface px-2 py-0.5 text-[11px] font-medium text-content-secondary shadow-sm transition-colors hover:bg-muted hover:text-content focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30"
|
|
43
|
+
:title="stringToggleLabel"
|
|
44
|
+
:aria-expanded="isStringExpanded"
|
|
45
|
+
@click.stop="toggleStringExpansion"
|
|
46
|
+
>
|
|
47
|
+
<span class="leading-none">{{ stringToggleLabel }}</span>
|
|
48
|
+
</button>
|
|
53
49
|
<a
|
|
54
50
|
href="#"
|
|
55
|
-
class="ml-1 text-sm text-primary opacity-0 group-hover:opacity-100 focus:opacity-100
|
|
51
|
+
class="ml-1 shrink-0 text-sm text-primary opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100"
|
|
56
52
|
@click.stop.prevent="goToReference()"
|
|
57
53
|
>
|
|
58
54
|
View Document
|
|
@@ -60,20 +56,25 @@
|
|
|
60
56
|
</span>
|
|
61
57
|
<span
|
|
62
58
|
v-else
|
|
63
|
-
|
|
64
|
-
:style="typeof value === 'string'
|
|
65
|
-
? {
|
|
66
|
-
display: 'inline-block',
|
|
67
|
-
maxWidth: '100%',
|
|
68
|
-
overflow: 'hidden',
|
|
69
|
-
textOverflow: 'ellipsis',
|
|
70
|
-
whiteSpace: 'nowrap',
|
|
71
|
-
verticalAlign: 'bottom'
|
|
72
|
-
}
|
|
73
|
-
: {}"
|
|
74
|
-
:title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
|
|
59
|
+
class="inline-flex min-w-0 max-w-full items-baseline flex-wrap gap-x-1.5"
|
|
75
60
|
>
|
|
76
|
-
|
|
61
|
+
<span
|
|
62
|
+
:class="[...valueClasses, ...stringWrapperClasses]"
|
|
63
|
+
:style="isStringValue ? { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } : {}"
|
|
64
|
+
:title="shouldTruncateString && !isStringExpanded ? value : undefined"
|
|
65
|
+
>
|
|
66
|
+
{{ displayedValue }}<template v-if="!(shouldTruncateString && !isStringExpanded)">{{ comma }}</template>
|
|
67
|
+
</span>
|
|
68
|
+
<button
|
|
69
|
+
v-if="shouldTruncateString"
|
|
70
|
+
type="button"
|
|
71
|
+
class="inline-flex shrink-0 items-center gap-1 rounded-full border border-edge bg-surface px-2 py-0.5 text-[11px] font-medium text-content-secondary shadow-sm transition-colors hover:bg-muted hover:text-content focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30"
|
|
72
|
+
:title="stringToggleLabel"
|
|
73
|
+
:aria-expanded="isStringExpanded"
|
|
74
|
+
@click.stop="toggleStringExpansion"
|
|
75
|
+
>
|
|
76
|
+
<span class="leading-none">{{ stringToggleLabel }}</span>
|
|
77
|
+
</button>
|
|
77
78
|
</span>
|
|
78
79
|
</template>
|
|
79
80
|
</div>
|
|
@@ -94,6 +95,7 @@
|
|
|
94
95
|
:top-level-expanded="topLevelExpanded"
|
|
95
96
|
:expand-top-level="expandTopLevel"
|
|
96
97
|
:references="references"
|
|
98
|
+
:max-string-length="maxStringLength"
|
|
97
99
|
></json-node>
|
|
98
100
|
<div
|
|
99
101
|
v-if="hasHiddenRootChildren"
|
|
@@ -56,8 +56,17 @@ module.exports = app => app.component('json-node', {
|
|
|
56
56
|
references: {
|
|
57
57
|
type: Object,
|
|
58
58
|
default: () => ({})
|
|
59
|
+
},
|
|
60
|
+
maxStringLength: {
|
|
61
|
+
type: Number,
|
|
62
|
+
default: 200
|
|
59
63
|
}
|
|
60
64
|
},
|
|
65
|
+
data() {
|
|
66
|
+
return {
|
|
67
|
+
isStringExpanded: false
|
|
68
|
+
};
|
|
69
|
+
},
|
|
61
70
|
computed: {
|
|
62
71
|
hasKey() {
|
|
63
72
|
return this.nodeKey !== null && this.nodeKey !== undefined;
|
|
@@ -151,6 +160,29 @@ module.exports = app => app.component('json-node', {
|
|
|
151
160
|
}
|
|
152
161
|
return stringified;
|
|
153
162
|
},
|
|
163
|
+
isStringValue() {
|
|
164
|
+
return typeof this.value === 'string';
|
|
165
|
+
},
|
|
166
|
+
shouldTruncateString() {
|
|
167
|
+
return this.isStringValue && this.formattedValue.length > this.maxStringLength;
|
|
168
|
+
},
|
|
169
|
+
displayedValue() {
|
|
170
|
+
if (!this.shouldTruncateString || this.isStringExpanded) {
|
|
171
|
+
return this.formattedValue;
|
|
172
|
+
}
|
|
173
|
+
const keep = Math.max(0, this.maxStringLength - 1);
|
|
174
|
+
return `${this.formattedValue.slice(0, keep)}…`;
|
|
175
|
+
},
|
|
176
|
+
stringWrapperClasses() {
|
|
177
|
+
const classes = ['min-w-0', 'max-w-full', 'break-words'];
|
|
178
|
+
if (this.shouldTruncateString && this.isStringExpanded) {
|
|
179
|
+
classes.push('rounded-md', 'border', 'border-edge', 'bg-muted/60', 'px-2', 'py-1');
|
|
180
|
+
}
|
|
181
|
+
return classes;
|
|
182
|
+
},
|
|
183
|
+
stringToggleLabel() {
|
|
184
|
+
return this.isStringExpanded ? 'Show less' : 'Show more';
|
|
185
|
+
},
|
|
154
186
|
valueClasses() {
|
|
155
187
|
const classes = ['text-slate-700'];
|
|
156
188
|
if (this.value === null) {
|
|
@@ -267,6 +299,12 @@ module.exports = app => app.component('json-node', {
|
|
|
267
299
|
return;
|
|
268
300
|
}
|
|
269
301
|
this.$router.push({ path: `/model/${this.referenceModel}/document/${id}` });
|
|
302
|
+
},
|
|
303
|
+
toggleStringExpansion() {
|
|
304
|
+
if (!this.shouldTruncateString) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
this.isStringExpanded = !this.isStringExpanded;
|
|
270
308
|
}
|
|
271
309
|
}
|
|
272
310
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<div class="tooltip w-full font-mono text-sm py-1 text-slate-800">
|
|
2
|
-
<div class="w-full">
|
|
1
|
+
<div class="tooltip w-full min-w-0 max-w-full font-mono text-sm py-1 text-slate-800">
|
|
2
|
+
<div class="w-full min-w-0 max-w-full">
|
|
3
3
|
<json-node
|
|
4
4
|
:node-key="null"
|
|
5
5
|
:value="value"
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
:top-level-expanded="topLevelExpanded"
|
|
15
15
|
:expand-top-level="expandTopLevel"
|
|
16
16
|
:references="references"
|
|
17
|
+
:max-string-length="maxStringLength"
|
|
17
18
|
></json-node>
|
|
18
19
|
</div>
|
|
19
20
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const template = require('./list-json.html');
|
|
4
|
+
const DEFAULT_MAX_STRING_LENGTH = 200;
|
|
4
5
|
|
|
5
6
|
module.exports = app => app.component('list-json', {
|
|
6
7
|
template: template,
|
|
@@ -19,6 +20,10 @@ module.exports = app => app.component('list-json', {
|
|
|
19
20
|
expandedFields: {
|
|
20
21
|
type: Array,
|
|
21
22
|
default: () => []
|
|
23
|
+
},
|
|
24
|
+
maxStringLength: {
|
|
25
|
+
type: Number,
|
|
26
|
+
default: DEFAULT_MAX_STRING_LENGTH
|
|
22
27
|
}
|
|
23
28
|
},
|
|
24
29
|
data() {
|