voyageai-cli 1.19.2 → 1.20.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 +158 -23
- package/package.json +7 -1
- package/src/cli.js +2 -0
- package/src/commands/app.js +155 -0
- package/src/commands/completions.js +19 -6
- package/src/commands/eval.js +353 -32
- package/src/commands/playground.js +45 -0
- package/src/lib/explanations.js +264 -0
- package/src/playground/icons/dark/128.png +0 -0
- package/src/playground/icons/dark/16.png +0 -0
- package/src/playground/icons/dark/256.png +0 -0
- package/src/playground/icons/dark/32.png +0 -0
- package/src/playground/icons/dark/64.png +0 -0
- package/src/playground/icons/glyphs/Bulb.svg +5 -0
- package/src/playground/icons/glyphs/Config.svg +3 -0
- package/src/playground/icons/glyphs/Gauge.svg +4 -0
- package/src/playground/icons/glyphs/InfoWithCircle.svg +3 -0
- package/src/playground/icons/glyphs/LightningBolt.svg +3 -0
- package/src/playground/icons/glyphs/MagnifyingGlass.svg +3 -0
- package/src/playground/icons/glyphs/MultiDirectionArrow.svg +6 -0
- package/src/playground/icons/light/128.png +0 -0
- package/src/playground/icons/light/16.png +0 -0
- package/src/playground/icons/light/256.png +0 -0
- package/src/playground/icons/light/32.png +0 -0
- package/src/playground/icons/light/64.png +0 -0
- package/src/playground/index.html +2396 -177
- package/NOTICE +0 -23
- package/demo-readme.gif +0 -0
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title
|
|
6
|
+
<title>Voyage AI Playground</title>
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/icons/dark/32.png">
|
|
8
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/icons/dark/16.png">
|
|
7
9
|
<style>
|
|
8
10
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
11
|
|
|
@@ -13,7 +15,8 @@
|
|
|
13
15
|
--bg-surface: #112733; /* Gray Dark 4 */
|
|
14
16
|
--bg-card: #1C2D38; /* Gray Dark 3 */
|
|
15
17
|
--bg-input: #112733; /* Gray Dark 4 */
|
|
16
|
-
--accent: #00ED64; /* Green Base */
|
|
18
|
+
--accent: #00ED64; /* Green Base — interactive elements only */
|
|
19
|
+
--accent-text: #FFFFFF; /* Bright white — headings/labels in dark mode */
|
|
17
20
|
--accent-dim: #00A35C; /* Green Dark 1 */
|
|
18
21
|
--accent-glow: rgba(0, 237, 100, 0.12);
|
|
19
22
|
--text: #E8EDEB; /* Gray Light 2 */
|
|
@@ -40,7 +43,8 @@
|
|
|
40
43
|
--bg-surface: #F9FBFA; /* Gray Light 3 */
|
|
41
44
|
--bg-card: #FFFFFF; /* White */
|
|
42
45
|
--bg-input: #F9FBFA; /* Gray Light 3 */
|
|
43
|
-
--accent: #00A35C; /* Green Dark 1
|
|
46
|
+
--accent: #00A35C; /* Green Dark 1 — interactive elements */
|
|
47
|
+
--accent-text: #001E2B; /* MDB Black — headings/labels in light mode */
|
|
44
48
|
--accent-dim: #00684A; /* Green Dark 2 */
|
|
45
49
|
--accent-glow: rgba(0, 163, 92, 0.08);
|
|
46
50
|
--text: #001E2B; /* MDB Black */
|
|
@@ -75,8 +79,8 @@
|
|
|
75
79
|
[data-theme="light"] .explore-modal-overlay {
|
|
76
80
|
background: rgba(0, 30, 43, 0.4);
|
|
77
81
|
}
|
|
78
|
-
[data-theme="light"] .
|
|
79
|
-
box-shadow: 0
|
|
82
|
+
[data-theme="light"] .sidebar {
|
|
83
|
+
box-shadow: 1px 0 3px rgba(0, 30, 43, 0.06);
|
|
80
84
|
}
|
|
81
85
|
/* Light mode gradient overrides */
|
|
82
86
|
[data-theme="light"] .quant-bar-fill.storage { background: linear-gradient(90deg, #00A35C, #00ED64); }
|
|
@@ -98,36 +102,401 @@ body {
|
|
|
98
102
|
overflow-x: hidden;
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
/*
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
/* ── Update banner ── */
|
|
106
|
+
.update-banner {
|
|
107
|
+
display: none;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 10px;
|
|
110
|
+
padding: 8px 16px;
|
|
111
|
+
background: linear-gradient(135deg, rgba(0,163,92,0.12), rgba(4,152,236,0.12));
|
|
112
|
+
border-bottom: 1px solid rgba(0,237,100,0.2);
|
|
113
|
+
font-size: 13px;
|
|
114
|
+
color: var(--text);
|
|
115
|
+
flex-shrink: 0;
|
|
116
|
+
}
|
|
117
|
+
.update-banner.show { display: flex; }
|
|
118
|
+
.update-banner-icon { font-size: 16px; }
|
|
119
|
+
.update-banner-text { flex: 1; }
|
|
120
|
+
.update-banner-text strong { color: var(--accent); }
|
|
121
|
+
.update-banner-btn {
|
|
122
|
+
background: var(--accent);
|
|
123
|
+
color: var(--bg);
|
|
124
|
+
border: none;
|
|
125
|
+
border-radius: 4px;
|
|
126
|
+
padding: 5px 14px;
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
font-weight: 600;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
font-family: var(--font);
|
|
131
|
+
transition: opacity 0.15s;
|
|
132
|
+
}
|
|
133
|
+
.update-banner-btn:hover { opacity: 0.85; }
|
|
134
|
+
.update-banner-dismiss {
|
|
135
|
+
background: none;
|
|
136
|
+
border: none;
|
|
137
|
+
color: var(--text-muted);
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
font-size: 18px;
|
|
140
|
+
line-height: 1;
|
|
141
|
+
padding: 0 4px;
|
|
142
|
+
}
|
|
143
|
+
.update-banner-dismiss:hover { color: var(--text); }
|
|
144
|
+
|
|
145
|
+
/* ── Onboarding Walkthrough ── */
|
|
146
|
+
.onboarding-overlay {
|
|
147
|
+
display: none;
|
|
148
|
+
position: fixed;
|
|
149
|
+
inset: 0;
|
|
150
|
+
z-index: 10000;
|
|
151
|
+
}
|
|
152
|
+
.onboarding-overlay.active { display: block; }
|
|
153
|
+
.onboarding-backdrop {
|
|
154
|
+
position: absolute;
|
|
155
|
+
inset: 0;
|
|
156
|
+
background: rgba(0,0,0,0.6);
|
|
157
|
+
transition: opacity 0.3s;
|
|
158
|
+
}
|
|
159
|
+
.onboarding-spotlight {
|
|
160
|
+
position: absolute;
|
|
161
|
+
border-radius: var(--radius);
|
|
162
|
+
box-shadow: 0 0 0 9999px rgba(0,0,0,0.55);
|
|
163
|
+
transition: all 0.4s cubic-bezier(0.4,0,0.2,1);
|
|
164
|
+
z-index: 10001;
|
|
165
|
+
pointer-events: none;
|
|
166
|
+
}
|
|
167
|
+
.onboarding-tooltip {
|
|
168
|
+
position: absolute;
|
|
169
|
+
z-index: 10002;
|
|
170
|
+
background: var(--bg-card);
|
|
171
|
+
border: 1px solid var(--accent);
|
|
172
|
+
border-radius: 12px;
|
|
173
|
+
padding: 24px 28px 20px;
|
|
174
|
+
width: 380px;
|
|
175
|
+
max-width: 90vw;
|
|
176
|
+
box-shadow: 0 12px 40px rgba(0,0,0,0.4), 0 0 0 1px rgba(0,237,100,0.15);
|
|
177
|
+
transition: all 0.4s cubic-bezier(0.4,0,0.2,1);
|
|
178
|
+
opacity: 0;
|
|
179
|
+
transform: translateY(10px);
|
|
180
|
+
}
|
|
181
|
+
.onboarding-tooltip.visible {
|
|
182
|
+
opacity: 1;
|
|
183
|
+
transform: translateY(0);
|
|
184
|
+
}
|
|
185
|
+
.onboarding-tooltip-arrow {
|
|
186
|
+
position: absolute;
|
|
187
|
+
width: 12px;
|
|
188
|
+
height: 12px;
|
|
189
|
+
background: var(--bg-card);
|
|
190
|
+
border: 1px solid var(--accent);
|
|
191
|
+
transform: rotate(45deg);
|
|
192
|
+
}
|
|
193
|
+
.onboarding-tooltip-arrow.top {
|
|
194
|
+
top: -7px;
|
|
195
|
+
left: 30px;
|
|
196
|
+
border-right: none;
|
|
197
|
+
border-bottom: none;
|
|
198
|
+
}
|
|
199
|
+
.onboarding-tooltip-arrow.bottom {
|
|
200
|
+
bottom: -7px;
|
|
201
|
+
left: 30px;
|
|
202
|
+
border-left: none;
|
|
203
|
+
border-top: none;
|
|
204
|
+
}
|
|
205
|
+
.onboarding-tooltip-arrow.left {
|
|
206
|
+
left: -7px;
|
|
207
|
+
top: 24px;
|
|
208
|
+
border-right: none;
|
|
209
|
+
border-top: none;
|
|
210
|
+
}
|
|
211
|
+
.onboarding-step-icon {
|
|
212
|
+
font-size: 28px;
|
|
213
|
+
margin-bottom: 8px;
|
|
214
|
+
}
|
|
215
|
+
.onboarding-step-title {
|
|
216
|
+
font-size: 16px;
|
|
217
|
+
font-weight: 700;
|
|
218
|
+
color: var(--accent-text);
|
|
219
|
+
margin-bottom: 6px;
|
|
220
|
+
}
|
|
221
|
+
.onboarding-step-body {
|
|
222
|
+
font-size: 13px;
|
|
223
|
+
color: var(--text-dim);
|
|
224
|
+
line-height: 1.6;
|
|
225
|
+
margin-bottom: 18px;
|
|
226
|
+
}
|
|
227
|
+
.onboarding-step-body strong { color: var(--accent); }
|
|
228
|
+
.onboarding-footer {
|
|
107
229
|
display: flex;
|
|
108
230
|
align-items: center;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
231
|
+
justify-content: space-between;
|
|
232
|
+
gap: 12px;
|
|
233
|
+
}
|
|
234
|
+
.onboarding-dots {
|
|
235
|
+
display: flex;
|
|
236
|
+
gap: 6px;
|
|
237
|
+
}
|
|
238
|
+
.onboarding-dot {
|
|
239
|
+
width: 8px;
|
|
240
|
+
height: 8px;
|
|
241
|
+
border-radius: 50%;
|
|
242
|
+
background: var(--border);
|
|
243
|
+
transition: background 0.2s;
|
|
244
|
+
}
|
|
245
|
+
.onboarding-dot.active { background: var(--accent); }
|
|
246
|
+
.onboarding-dot.completed { background: var(--accent-dim); }
|
|
247
|
+
.onboarding-actions {
|
|
248
|
+
display: flex;
|
|
249
|
+
gap: 8px;
|
|
250
|
+
align-items: center;
|
|
251
|
+
}
|
|
252
|
+
.onboarding-skip {
|
|
253
|
+
background: none;
|
|
254
|
+
border: none;
|
|
255
|
+
color: var(--text-muted);
|
|
256
|
+
font-size: 12px;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
font-family: var(--font);
|
|
259
|
+
padding: 6px 10px;
|
|
260
|
+
border-radius: 6px;
|
|
261
|
+
transition: color 0.15s;
|
|
262
|
+
}
|
|
263
|
+
.onboarding-skip:hover { color: var(--text); }
|
|
264
|
+
.onboarding-next {
|
|
265
|
+
background: var(--accent);
|
|
266
|
+
color: var(--bg);
|
|
267
|
+
border: none;
|
|
268
|
+
border-radius: 6px;
|
|
269
|
+
padding: 8px 20px;
|
|
270
|
+
font-size: 13px;
|
|
271
|
+
font-weight: 600;
|
|
272
|
+
cursor: pointer;
|
|
273
|
+
font-family: var(--font);
|
|
274
|
+
transition: opacity 0.15s;
|
|
275
|
+
}
|
|
276
|
+
.onboarding-next:hover { opacity: 0.85; }
|
|
277
|
+
.onboarding-welcome-center {
|
|
278
|
+
position: fixed;
|
|
279
|
+
inset: 0;
|
|
280
|
+
z-index: 10002;
|
|
281
|
+
display: flex;
|
|
282
|
+
align-items: center;
|
|
283
|
+
justify-content: center;
|
|
284
|
+
pointer-events: none;
|
|
285
|
+
}
|
|
286
|
+
.onboarding-welcome-card {
|
|
287
|
+
background: var(--bg-card);
|
|
288
|
+
border: 1px solid var(--accent);
|
|
289
|
+
border-radius: 16px;
|
|
290
|
+
padding: 40px 44px 32px;
|
|
291
|
+
width: 460px;
|
|
292
|
+
max-width: 90vw;
|
|
293
|
+
text-align: center;
|
|
294
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 80px rgba(0,237,100,0.08);
|
|
295
|
+
pointer-events: auto;
|
|
296
|
+
opacity: 0;
|
|
297
|
+
transform: scale(0.95);
|
|
298
|
+
transition: all 0.4s cubic-bezier(0.4,0,0.2,1);
|
|
299
|
+
}
|
|
300
|
+
.onboarding-welcome-card.visible {
|
|
301
|
+
opacity: 1;
|
|
302
|
+
transform: scale(1);
|
|
303
|
+
}
|
|
304
|
+
.onboarding-welcome-logo {
|
|
305
|
+
width: 64px;
|
|
306
|
+
height: 64px;
|
|
307
|
+
margin-bottom: 16px;
|
|
308
|
+
}
|
|
309
|
+
.onboarding-welcome-title {
|
|
310
|
+
font-size: 24px;
|
|
311
|
+
font-weight: 700;
|
|
312
|
+
color: var(--accent-text);
|
|
313
|
+
margin-bottom: 8px;
|
|
314
|
+
}
|
|
315
|
+
.onboarding-welcome-sub {
|
|
316
|
+
font-size: 14px;
|
|
317
|
+
color: var(--text-dim);
|
|
318
|
+
line-height: 1.6;
|
|
319
|
+
margin-bottom: 28px;
|
|
320
|
+
}
|
|
321
|
+
[data-theme="light"] .onboarding-tooltip {
|
|
322
|
+
box-shadow: 0 12px 40px rgba(0,30,43,0.15), 0 0 0 1px rgba(0,163,92,0.2);
|
|
323
|
+
}
|
|
324
|
+
[data-theme="light"] .onboarding-welcome-card {
|
|
325
|
+
box-shadow: 0 20px 60px rgba(0,30,43,0.18), 0 0 80px rgba(0,163,92,0.06);
|
|
326
|
+
}
|
|
327
|
+
[data-theme="light"] .onboarding-backdrop {
|
|
328
|
+
background: rgba(0,30,43,0.45);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* ── App Shell: sidebar + content layout ── */
|
|
332
|
+
.app-shell {
|
|
333
|
+
display: flex;
|
|
334
|
+
height: 100vh;
|
|
335
|
+
overflow: hidden;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* ── Sidebar ── */
|
|
339
|
+
.sidebar {
|
|
340
|
+
width: 220px;
|
|
341
|
+
min-width: 220px;
|
|
342
|
+
background: var(--bg-surface);
|
|
343
|
+
border-right: 1px solid var(--border);
|
|
344
|
+
display: flex;
|
|
345
|
+
flex-direction: column;
|
|
346
|
+
overflow: hidden;
|
|
112
347
|
z-index: 100;
|
|
113
348
|
}
|
|
114
349
|
|
|
115
|
-
.
|
|
116
|
-
|
|
350
|
+
.sidebar-drag-region {
|
|
351
|
+
-webkit-app-region: drag;
|
|
352
|
+
height: 78px;
|
|
353
|
+
min-height: 78px;
|
|
354
|
+
padding: 0 16px;
|
|
355
|
+
padding-top: 52px; /* Clear macOS traffic lights fully */
|
|
356
|
+
display: flex;
|
|
357
|
+
align-items: center;
|
|
358
|
+
gap: 10px;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.sidebar-logo {
|
|
362
|
+
width: 28px;
|
|
363
|
+
height: 28px;
|
|
364
|
+
border-radius: 6px;
|
|
365
|
+
flex-shrink: 0;
|
|
366
|
+
object-fit: contain;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.sidebar-title {
|
|
370
|
+
font-size: 14px;
|
|
117
371
|
font-weight: 700;
|
|
118
|
-
color: var(--accent);
|
|
372
|
+
color: var(--accent-text);
|
|
373
|
+
white-space: nowrap;
|
|
374
|
+
letter-spacing: -0.2px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.sidebar-nav {
|
|
378
|
+
flex: 1;
|
|
379
|
+
overflow-y: auto;
|
|
380
|
+
padding: 8px;
|
|
381
|
+
display: flex;
|
|
382
|
+
flex-direction: column;
|
|
383
|
+
gap: 1px;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.sidebar-nav-group {
|
|
387
|
+
display: flex;
|
|
388
|
+
flex-direction: column;
|
|
389
|
+
gap: 1px;
|
|
390
|
+
}
|
|
391
|
+
.sidebar-nav-divider {
|
|
392
|
+
height: 1px;
|
|
393
|
+
background: var(--border);
|
|
394
|
+
margin: 8px 12px;
|
|
395
|
+
opacity: 0.5;
|
|
396
|
+
}
|
|
397
|
+
.sidebar-nav-label {
|
|
398
|
+
font-size: 10px;
|
|
399
|
+
font-weight: 600;
|
|
400
|
+
text-transform: uppercase;
|
|
401
|
+
letter-spacing: 0.8px;
|
|
402
|
+
color: var(--text-muted);
|
|
403
|
+
padding: 10px 12px 4px;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.tab-btn {
|
|
407
|
+
display: flex;
|
|
408
|
+
align-items: center;
|
|
409
|
+
gap: 10px;
|
|
410
|
+
width: 100%;
|
|
411
|
+
background: none;
|
|
412
|
+
border: none;
|
|
413
|
+
border-left: 3px solid transparent;
|
|
414
|
+
border-radius: 0 6px 6px 0;
|
|
415
|
+
color: var(--text-dim);
|
|
416
|
+
padding: 10px 12px;
|
|
417
|
+
font-size: 13px;
|
|
418
|
+
font-weight: 500;
|
|
419
|
+
font-family: var(--font);
|
|
420
|
+
cursor: pointer;
|
|
421
|
+
transition: all 0.15s;
|
|
119
422
|
white-space: nowrap;
|
|
423
|
+
text-align: left;
|
|
424
|
+
position: relative;
|
|
425
|
+
}
|
|
426
|
+
.tab-btn:hover {
|
|
427
|
+
color: var(--text);
|
|
428
|
+
background: rgba(255,255,255,0.05);
|
|
429
|
+
}
|
|
430
|
+
.tab-btn:hover .tab-btn-icon {
|
|
431
|
+
transform: scale(1.1);
|
|
432
|
+
}
|
|
433
|
+
.tab-btn.active {
|
|
434
|
+
color: var(--accent);
|
|
435
|
+
background: var(--accent-glow);
|
|
436
|
+
border-left-color: var(--accent);
|
|
437
|
+
font-weight: 600;
|
|
438
|
+
}
|
|
439
|
+
.tab-btn-icon {
|
|
440
|
+
width: 18px;
|
|
441
|
+
height: 18px;
|
|
442
|
+
flex-shrink: 0;
|
|
443
|
+
transition: transform 0.15s;
|
|
444
|
+
display: flex;
|
|
445
|
+
align-items: center;
|
|
446
|
+
justify-content: center;
|
|
447
|
+
}
|
|
448
|
+
.tab-btn-icon svg {
|
|
449
|
+
width: 16px;
|
|
450
|
+
height: 16px;
|
|
451
|
+
}
|
|
452
|
+
[data-theme="light"] .tab-btn:hover { background: rgba(0,30,43,0.04); }
|
|
453
|
+
[data-theme="light"] .tab-btn.active { background: rgba(0,163,92,0.08); }
|
|
454
|
+
|
|
455
|
+
.sidebar-footer {
|
|
456
|
+
padding: 12px 16px;
|
|
457
|
+
border-top: 1px solid var(--border);
|
|
458
|
+
display: flex;
|
|
459
|
+
flex-direction: column;
|
|
460
|
+
gap: 8px;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.sidebar-model-group {
|
|
464
|
+
display: flex;
|
|
465
|
+
flex-direction: column;
|
|
466
|
+
gap: 4px;
|
|
467
|
+
}
|
|
468
|
+
.sidebar-model-label {
|
|
469
|
+
font-size: 10px;
|
|
470
|
+
font-weight: 600;
|
|
471
|
+
text-transform: uppercase;
|
|
472
|
+
letter-spacing: 0.5px;
|
|
473
|
+
color: var(--text-muted);
|
|
474
|
+
}
|
|
475
|
+
.nav-model-select {
|
|
476
|
+
background: var(--bg-input);
|
|
477
|
+
border: 1px solid var(--border);
|
|
478
|
+
color: var(--text);
|
|
479
|
+
padding: 6px 10px;
|
|
480
|
+
border-radius: var(--radius);
|
|
481
|
+
font-size: 12px;
|
|
482
|
+
font-family: var(--mono);
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
width: 100%;
|
|
120
485
|
}
|
|
121
486
|
|
|
122
|
-
.
|
|
487
|
+
.sidebar-controls {
|
|
488
|
+
display: flex;
|
|
489
|
+
align-items: center;
|
|
490
|
+
justify-content: space-between;
|
|
491
|
+
}
|
|
123
492
|
|
|
124
493
|
.theme-toggle {
|
|
125
494
|
background: none;
|
|
126
495
|
border: 1px solid var(--border);
|
|
127
|
-
border-radius:
|
|
128
|
-
padding:
|
|
496
|
+
border-radius: 16px;
|
|
497
|
+
padding: 4px 8px;
|
|
129
498
|
cursor: pointer;
|
|
130
|
-
font-size:
|
|
499
|
+
font-size: 14px;
|
|
131
500
|
line-height: 1;
|
|
132
501
|
transition: all 0.2s;
|
|
133
502
|
display: flex;
|
|
@@ -149,47 +518,31 @@ body {
|
|
|
149
518
|
.status-dot.error { background: var(--error); }
|
|
150
519
|
|
|
151
520
|
.status-label {
|
|
152
|
-
font-size:
|
|
521
|
+
font-size: 11px;
|
|
153
522
|
color: var(--text-dim);
|
|
154
523
|
}
|
|
155
524
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
padding: 6px 12px;
|
|
161
|
-
border-radius: var(--radius);
|
|
162
|
-
font-size: 13px;
|
|
163
|
-
font-family: var(--mono);
|
|
164
|
-
cursor: pointer;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/* Tabs */
|
|
168
|
-
.tab-bar {
|
|
525
|
+
/* ── Content area ── */
|
|
526
|
+
.content-area {
|
|
527
|
+
flex: 1;
|
|
528
|
+
overflow-y: auto;
|
|
169
529
|
display: flex;
|
|
170
|
-
|
|
171
|
-
border-bottom: 1px solid var(--border);
|
|
172
|
-
padding: 0 24px;
|
|
173
|
-
gap: 0;
|
|
530
|
+
flex-direction: column;
|
|
174
531
|
}
|
|
175
532
|
|
|
176
|
-
.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
font-size: 14px;
|
|
182
|
-
font-family: var(--font);
|
|
183
|
-
cursor: pointer;
|
|
184
|
-
border-bottom: 2px solid transparent;
|
|
185
|
-
transition: all 0.2s;
|
|
186
|
-
white-space: nowrap;
|
|
533
|
+
.content-drag-region {
|
|
534
|
+
-webkit-app-region: drag;
|
|
535
|
+
height: 38px;
|
|
536
|
+
min-height: 38px;
|
|
537
|
+
flex-shrink: 0;
|
|
187
538
|
}
|
|
188
|
-
.tab-btn:hover { color: var(--text); background: rgba(255,255,255,0.03); }
|
|
189
|
-
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
|
|
190
539
|
|
|
191
540
|
/* Main */
|
|
192
|
-
.main { padding: 24px; max-width: 1200px; margin: 0 auto; }
|
|
541
|
+
.main { padding: 24px; max-width: 1200px; margin: 0 auto; width: 100%; }
|
|
542
|
+
|
|
543
|
+
/* Legacy compat — hide old horizontal bar (replaced by sidebar) */
|
|
544
|
+
.nav { display: none; }
|
|
545
|
+
.tab-bar-horizontal { display: none; }
|
|
193
546
|
|
|
194
547
|
.tab-panel { display: none; }
|
|
195
548
|
.tab-panel.active { display: block; }
|
|
@@ -206,7 +559,7 @@ body {
|
|
|
206
559
|
.card-title {
|
|
207
560
|
font-size: 14px;
|
|
208
561
|
font-weight: 600;
|
|
209
|
-
color: var(--accent);
|
|
562
|
+
color: var(--accent-text);
|
|
210
563
|
margin-bottom: 12px;
|
|
211
564
|
text-transform: uppercase;
|
|
212
565
|
letter-spacing: 0.5px;
|
|
@@ -333,7 +686,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
333
686
|
margin-bottom: 8px;
|
|
334
687
|
}
|
|
335
688
|
.stat-label { color: var(--text-dim); }
|
|
336
|
-
.stat-value { color: var(--accent); font-weight: 600; font-family: var(--mono); }
|
|
689
|
+
.stat-value { color: var(--accent-text); font-weight: 600; font-family: var(--mono); }
|
|
337
690
|
|
|
338
691
|
.vector-preview {
|
|
339
692
|
font-family: var(--mono);
|
|
@@ -481,7 +834,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
481
834
|
.result-rank {
|
|
482
835
|
font-size: 20px;
|
|
483
836
|
font-weight: 700;
|
|
484
|
-
color: var(--accent);
|
|
837
|
+
color: var(--accent-text);
|
|
485
838
|
font-family: var(--mono);
|
|
486
839
|
min-width: 30px;
|
|
487
840
|
}
|
|
@@ -646,7 +999,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
646
999
|
.explore-modal-links-title {
|
|
647
1000
|
font-size: 11px;
|
|
648
1001
|
font-weight: 600;
|
|
649
|
-
color: var(--accent);
|
|
1002
|
+
color: var(--accent-text);
|
|
650
1003
|
text-transform: uppercase;
|
|
651
1004
|
letter-spacing: 0.5px;
|
|
652
1005
|
margin-bottom: 6px;
|
|
@@ -668,7 +1021,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
668
1021
|
.explore-modal-tryit-title {
|
|
669
1022
|
font-size: 11px;
|
|
670
1023
|
font-weight: 600;
|
|
671
|
-
color: var(--accent);
|
|
1024
|
+
color: var(--accent-text);
|
|
672
1025
|
text-transform: uppercase;
|
|
673
1026
|
letter-spacing: 0.5px;
|
|
674
1027
|
margin-bottom: 8px;
|
|
@@ -781,7 +1134,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
781
1134
|
.rank-num {
|
|
782
1135
|
font-size: 16px;
|
|
783
1136
|
font-weight: 700;
|
|
784
|
-
color: var(--accent);
|
|
1137
|
+
color: var(--accent-text);
|
|
785
1138
|
font-family: var(--mono);
|
|
786
1139
|
text-align: center;
|
|
787
1140
|
}
|
|
@@ -810,7 +1163,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
810
1163
|
display: flex; justify-content: space-between; align-items: baseline;
|
|
811
1164
|
margin-bottom: 4px; font-size: 13px;
|
|
812
1165
|
}
|
|
813
|
-
.quant-bar-label .dtype-name { color: var(--accent); font-weight: 600; font-family: var(--mono); }
|
|
1166
|
+
.quant-bar-label .dtype-name { color: var(--accent-text); font-weight: 600; font-family: var(--mono); }
|
|
814
1167
|
.quant-bar-label .dtype-value { color: var(--text-dim); font-family: var(--mono); font-size: 12px; }
|
|
815
1168
|
.quant-bar-track {
|
|
816
1169
|
height: 32px; background: var(--bg-input); border-radius: 6px;
|
|
@@ -835,7 +1188,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
835
1188
|
display: flex; justify-content: space-between; align-items: center;
|
|
836
1189
|
margin-bottom: 6px;
|
|
837
1190
|
}
|
|
838
|
-
.quant-meter-header .dtype-name { color: var(--accent); font-weight: 600; font-family: var(--mono); font-size: 13px; }
|
|
1191
|
+
.quant-meter-header .dtype-name { color: var(--accent-text); font-weight: 600; font-family: var(--mono); font-size: 13px; }
|
|
839
1192
|
.quant-meter-header .verdict-badge {
|
|
840
1193
|
font-size: 12px; padding: 2px 8px; border-radius: 10px; font-weight: 600;
|
|
841
1194
|
}
|
|
@@ -858,7 +1211,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
858
1211
|
display: grid; gap: 12px;
|
|
859
1212
|
}
|
|
860
1213
|
.quant-rank-col-header {
|
|
861
|
-
font-weight: 600; color: var(--accent); font-size: 13px; font-family: var(--mono);
|
|
1214
|
+
font-weight: 600; color: var(--accent-text); font-size: 13px; font-family: var(--mono);
|
|
862
1215
|
margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
|
|
863
1216
|
}
|
|
864
1217
|
.quant-rank-item {
|
|
@@ -873,7 +1226,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
873
1226
|
.quant-rank-pos {
|
|
874
1227
|
display: inline-block; width: 22px; height: 22px; line-height: 22px;
|
|
875
1228
|
text-align: center; border-radius: 50%; background: var(--bg-surface);
|
|
876
|
-
color: var(--accent); font-weight: 700; font-size: 11px; font-family: var(--mono);
|
|
1229
|
+
color: var(--accent-text); font-weight: 700; font-size: 11px; font-family: var(--mono);
|
|
877
1230
|
margin-right: 8px;
|
|
878
1231
|
}
|
|
879
1232
|
.quant-rank-score { color: var(--text-muted); font-size: 11px; font-family: var(--mono); margin-top: 3px; }
|
|
@@ -916,7 +1269,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
916
1269
|
.cost-slider-value {
|
|
917
1270
|
font-family: var(--mono);
|
|
918
1271
|
font-size: 13px;
|
|
919
|
-
color: var(--accent);
|
|
1272
|
+
color: var(--accent-text);
|
|
920
1273
|
min-width: 70px;
|
|
921
1274
|
text-align: right;
|
|
922
1275
|
font-weight: 600;
|
|
@@ -993,7 +1346,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
993
1346
|
font-family: var(--mono);
|
|
994
1347
|
font-size: 20px;
|
|
995
1348
|
font-weight: 700;
|
|
996
|
-
color: var(--accent);
|
|
1349
|
+
color: var(--accent-text);
|
|
997
1350
|
}
|
|
998
1351
|
.cost-summary-detail {
|
|
999
1352
|
font-size: 11px;
|
|
@@ -1056,7 +1409,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1056
1409
|
align-items: center;
|
|
1057
1410
|
}
|
|
1058
1411
|
.cost-strategy-total-label { font-size: 13px; font-weight: 600; color: var(--text); }
|
|
1059
|
-
.cost-strategy-total-value { font-family: var(--mono); font-size: 18px; font-weight: 700; color: var(--accent); }
|
|
1412
|
+
.cost-strategy-total-value { font-family: var(--mono); font-size: 18px; font-weight: 700; color: var(--accent-text); }
|
|
1060
1413
|
.cost-savings {
|
|
1061
1414
|
font-size: 11px;
|
|
1062
1415
|
color: var(--success);
|
|
@@ -1087,7 +1440,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1087
1440
|
}
|
|
1088
1441
|
.cost-table tr:hover { background: rgba(0, 237, 100, 0.03); }
|
|
1089
1442
|
.cost-highlight {
|
|
1090
|
-
color: var(--accent);
|
|
1443
|
+
color: var(--accent-text);
|
|
1091
1444
|
font-weight: 600;
|
|
1092
1445
|
}
|
|
1093
1446
|
.cost-bar-cell { position: relative; }
|
|
@@ -1195,7 +1548,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1195
1548
|
}
|
|
1196
1549
|
.cost-modal h3 {
|
|
1197
1550
|
font-size: 14px;
|
|
1198
|
-
color: var(--accent);
|
|
1551
|
+
color: var(--accent-text);
|
|
1199
1552
|
margin: 22px 0 10px;
|
|
1200
1553
|
text-transform: uppercase;
|
|
1201
1554
|
letter-spacing: 0.5px;
|
|
@@ -1212,7 +1565,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1212
1565
|
padding: 2px 7px;
|
|
1213
1566
|
border-radius: 4px;
|
|
1214
1567
|
font-size: 12px;
|
|
1215
|
-
color: var(--accent);
|
|
1568
|
+
color: var(--accent-text);
|
|
1216
1569
|
font-family: var(--mono);
|
|
1217
1570
|
}
|
|
1218
1571
|
.cost-modal .formula {
|
|
@@ -1295,99 +1648,682 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1295
1648
|
justify-content: space-between;
|
|
1296
1649
|
font-size: 10px;
|
|
1297
1650
|
color: var(--text-muted);
|
|
1298
|
-
margin-top: 4px;
|
|
1651
|
+
margin-top: 4px;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
/* About page */
|
|
1655
|
+
.about-container { max-width: 680px; margin: 0 auto; }
|
|
1656
|
+
.about-header { display: flex; gap: 24px; align-items: center; margin-bottom: 24px; }
|
|
1657
|
+
.about-avatar {
|
|
1658
|
+
width: 120px; height: 120px;
|
|
1659
|
+
border-radius: 50%;
|
|
1660
|
+
border: 3px solid var(--accent);
|
|
1661
|
+
box-shadow: 0 0 20px var(--accent-glow);
|
|
1662
|
+
flex-shrink: 0;
|
|
1663
|
+
}
|
|
1664
|
+
.about-name { font-size: 24px; font-weight: 700; color: var(--text); }
|
|
1665
|
+
.about-role { font-size: 14px; color: var(--accent-text); margin-top: 4px; }
|
|
1666
|
+
.about-links { display: flex; gap: 12px; margin-top: 8px; }
|
|
1667
|
+
.about-links a {
|
|
1668
|
+
color: var(--text-dim);
|
|
1669
|
+
font-size: 13px;
|
|
1670
|
+
text-decoration: none;
|
|
1671
|
+
transition: color 0.2s;
|
|
1672
|
+
}
|
|
1673
|
+
.about-links a:hover { color: var(--accent); }
|
|
1674
|
+
.about-section { margin-bottom: 24px; }
|
|
1675
|
+
.about-section-title {
|
|
1676
|
+
font-size: 13px;
|
|
1677
|
+
font-weight: 600;
|
|
1678
|
+
color: var(--accent-text);
|
|
1679
|
+
text-transform: uppercase;
|
|
1680
|
+
letter-spacing: 0.5px;
|
|
1681
|
+
margin-bottom: 8px;
|
|
1682
|
+
}
|
|
1683
|
+
.about-text { font-size: 14px; line-height: 1.8; color: var(--text); }
|
|
1684
|
+
.about-text a { color: var(--blue); text-decoration: none; }
|
|
1685
|
+
.about-text a:hover { text-decoration: underline; }
|
|
1686
|
+
.about-disclaimer {
|
|
1687
|
+
background: rgba(255, 215, 61, 0.08);
|
|
1688
|
+
border: 1px solid rgba(255, 215, 61, 0.2);
|
|
1689
|
+
border-radius: var(--radius);
|
|
1690
|
+
padding: 16px 20px;
|
|
1691
|
+
margin-top: 24px;
|
|
1692
|
+
}
|
|
1693
|
+
.about-disclaimer-title {
|
|
1694
|
+
font-size: 13px;
|
|
1695
|
+
font-weight: 600;
|
|
1696
|
+
color: var(--warning);
|
|
1697
|
+
margin-bottom: 6px;
|
|
1698
|
+
}
|
|
1699
|
+
.about-disclaimer-text {
|
|
1700
|
+
font-size: 13px;
|
|
1701
|
+
line-height: 1.7;
|
|
1702
|
+
color: var(--text-dim);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
/* ── Page Headers ── */
|
|
1706
|
+
.page-header {
|
|
1707
|
+
margin-bottom: 24px;
|
|
1708
|
+
padding-bottom: 16px;
|
|
1709
|
+
border-bottom: 1px solid var(--border);
|
|
1710
|
+
}
|
|
1711
|
+
.page-header-title {
|
|
1712
|
+
font-size: 22px;
|
|
1713
|
+
font-weight: 700;
|
|
1714
|
+
color: var(--accent-text, var(--text));
|
|
1715
|
+
margin: 0 0 4px;
|
|
1716
|
+
letter-spacing: -0.3px;
|
|
1717
|
+
}
|
|
1718
|
+
.page-header-subtitle {
|
|
1719
|
+
font-size: 14px;
|
|
1720
|
+
color: var(--text-dim);
|
|
1721
|
+
margin: 0 0 6px;
|
|
1722
|
+
font-weight: 500;
|
|
1723
|
+
}
|
|
1724
|
+
.page-header-hint {
|
|
1725
|
+
font-size: 12px;
|
|
1726
|
+
color: var(--text-muted);
|
|
1727
|
+
margin: 0;
|
|
1728
|
+
line-height: 1.5;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
/* ── Settings page ── */
|
|
1732
|
+
.settings-container { max-width: 640px; margin: 0 auto; }
|
|
1733
|
+
.settings-section {
|
|
1734
|
+
background: var(--bg-card);
|
|
1735
|
+
border: 1px solid var(--border);
|
|
1736
|
+
border-radius: var(--radius);
|
|
1737
|
+
padding: 24px;
|
|
1738
|
+
margin-bottom: 20px;
|
|
1739
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
1740
|
+
}
|
|
1741
|
+
[data-theme="light"] .settings-section {
|
|
1742
|
+
box-shadow: 0 1px 4px rgba(0,30,43,0.07);
|
|
1743
|
+
border-color: #DDE4E2;
|
|
1744
|
+
}
|
|
1745
|
+
.settings-section-title {
|
|
1746
|
+
font-size: 11px;
|
|
1747
|
+
font-weight: 700;
|
|
1748
|
+
color: var(--text-muted);
|
|
1749
|
+
text-transform: uppercase;
|
|
1750
|
+
letter-spacing: 1px;
|
|
1751
|
+
margin-bottom: 16px;
|
|
1752
|
+
padding-bottom: 10px;
|
|
1753
|
+
border-bottom: 1px solid var(--border);
|
|
1754
|
+
}
|
|
1755
|
+
/* API key alert banner */
|
|
1756
|
+
.settings-api-banner {
|
|
1757
|
+
background: rgba(255,192,16,0.08);
|
|
1758
|
+
border: 1px solid rgba(255,192,16,0.25);
|
|
1759
|
+
border-radius: var(--radius);
|
|
1760
|
+
padding: 14px 18px;
|
|
1761
|
+
margin-bottom: 16px;
|
|
1762
|
+
display: flex;
|
|
1763
|
+
align-items: center;
|
|
1764
|
+
gap: 10px;
|
|
1765
|
+
font-size: 13px;
|
|
1766
|
+
color: var(--warning);
|
|
1767
|
+
line-height: 1.5;
|
|
1768
|
+
}
|
|
1769
|
+
.settings-api-banner.success {
|
|
1770
|
+
background: rgba(0,237,100,0.06);
|
|
1771
|
+
border-color: rgba(0,237,100,0.2);
|
|
1772
|
+
color: var(--success);
|
|
1773
|
+
}
|
|
1774
|
+
[data-theme="light"] .settings-api-banner {
|
|
1775
|
+
background: rgba(148,79,1,0.06);
|
|
1776
|
+
border-color: rgba(148,79,1,0.2);
|
|
1777
|
+
}
|
|
1778
|
+
[data-theme="light"] .settings-api-banner.success {
|
|
1779
|
+
background: rgba(0,104,74,0.06);
|
|
1780
|
+
border-color: rgba(0,104,74,0.2);
|
|
1781
|
+
}
|
|
1782
|
+
.settings-api-banner-icon { font-size: 20px; flex-shrink: 0; }
|
|
1783
|
+
/* Heatmap preview swatches */
|
|
1784
|
+
.heatmap-preview {
|
|
1785
|
+
display: inline-block;
|
|
1786
|
+
width: 80px;
|
|
1787
|
+
height: 12px;
|
|
1788
|
+
border-radius: 3px;
|
|
1789
|
+
vertical-align: middle;
|
|
1790
|
+
margin-left: 8px;
|
|
1791
|
+
border: 1px solid var(--border);
|
|
1792
|
+
}
|
|
1793
|
+
.heatmap-opt {
|
|
1794
|
+
display: flex;
|
|
1795
|
+
align-items: center;
|
|
1796
|
+
gap: 6px;
|
|
1797
|
+
}
|
|
1798
|
+
/* Get a key link as button */
|
|
1799
|
+
.settings-key-link {
|
|
1800
|
+
display: inline-flex;
|
|
1801
|
+
align-items: center;
|
|
1802
|
+
gap: 4px;
|
|
1803
|
+
color: var(--blue);
|
|
1804
|
+
font-size: 12px;
|
|
1805
|
+
font-weight: 600;
|
|
1806
|
+
text-decoration: none;
|
|
1807
|
+
padding: 4px 10px;
|
|
1808
|
+
border: 1px solid var(--blue);
|
|
1809
|
+
border-radius: 4px;
|
|
1810
|
+
transition: all 0.15s;
|
|
1811
|
+
white-space: nowrap;
|
|
1812
|
+
}
|
|
1813
|
+
.settings-key-link:hover {
|
|
1814
|
+
background: rgba(4,152,236,0.1);
|
|
1815
|
+
text-decoration: none;
|
|
1816
|
+
}
|
|
1817
|
+
/* API key field integration */
|
|
1818
|
+
.settings-api-field {
|
|
1819
|
+
display: flex;
|
|
1820
|
+
align-items: center;
|
|
1821
|
+
border: 1px solid var(--border);
|
|
1822
|
+
border-radius: var(--radius);
|
|
1823
|
+
background: var(--bg-input);
|
|
1824
|
+
overflow: hidden;
|
|
1825
|
+
transition: border-color 0.2s;
|
|
1826
|
+
}
|
|
1827
|
+
.settings-api-field:focus-within { border-color: var(--accent); }
|
|
1828
|
+
.settings-api-field input {
|
|
1829
|
+
flex: 1;
|
|
1830
|
+
border: none;
|
|
1831
|
+
background: transparent;
|
|
1832
|
+
padding: 8px 12px;
|
|
1833
|
+
font-size: 13px;
|
|
1834
|
+
font-family: var(--mono);
|
|
1835
|
+
color: var(--text);
|
|
1836
|
+
min-width: 0;
|
|
1837
|
+
}
|
|
1838
|
+
.settings-api-field input:focus { outline: none; }
|
|
1839
|
+
.settings-api-field button {
|
|
1840
|
+
background: none;
|
|
1841
|
+
border: none;
|
|
1842
|
+
border-left: 1px solid var(--border);
|
|
1843
|
+
padding: 8px 10px;
|
|
1844
|
+
cursor: pointer;
|
|
1845
|
+
font-size: 14px;
|
|
1846
|
+
color: var(--text-dim);
|
|
1847
|
+
transition: all 0.15s;
|
|
1848
|
+
flex-shrink: 0;
|
|
1849
|
+
}
|
|
1850
|
+
.settings-api-field button:hover { color: var(--text); background: rgba(255,255,255,0.05); }
|
|
1851
|
+
[data-theme="light"] .settings-api-field button:hover { background: rgba(0,0,0,0.03); }
|
|
1852
|
+
/* Version badges */
|
|
1853
|
+
.version-badge {
|
|
1854
|
+
display: inline-block;
|
|
1855
|
+
font-family: var(--mono);
|
|
1856
|
+
font-size: 13px;
|
|
1857
|
+
font-weight: 600;
|
|
1858
|
+
color: var(--accent);
|
|
1859
|
+
background: rgba(0,237,100,0.08);
|
|
1860
|
+
border: 1px solid rgba(0,237,100,0.2);
|
|
1861
|
+
border-radius: 6px;
|
|
1862
|
+
padding: 4px 12px;
|
|
1863
|
+
letter-spacing: 0.3px;
|
|
1864
|
+
}
|
|
1865
|
+
[data-theme="light"] .version-badge {
|
|
1866
|
+
background: rgba(0,104,74,0.06);
|
|
1867
|
+
border-color: rgba(0,104,74,0.2);
|
|
1868
|
+
color: #00684A;
|
|
1869
|
+
}
|
|
1870
|
+
.settings-api-field .save-btn {
|
|
1871
|
+
color: var(--accent);
|
|
1872
|
+
font-size: 12px;
|
|
1873
|
+
font-weight: 600;
|
|
1874
|
+
font-family: var(--font);
|
|
1875
|
+
padding: 8px 14px;
|
|
1876
|
+
}
|
|
1877
|
+
.settings-row {
|
|
1878
|
+
display: flex;
|
|
1879
|
+
align-items: center;
|
|
1880
|
+
justify-content: space-between;
|
|
1881
|
+
padding: 12px 0;
|
|
1882
|
+
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
1883
|
+
}
|
|
1884
|
+
.settings-row:last-child { border-bottom: none; }
|
|
1885
|
+
[data-theme="light"] .settings-row { border-bottom-color: rgba(0,0,0,0.04); }
|
|
1886
|
+
.settings-label {
|
|
1887
|
+
display: flex;
|
|
1888
|
+
flex-direction: column;
|
|
1889
|
+
gap: 2px;
|
|
1890
|
+
}
|
|
1891
|
+
.settings-label-text {
|
|
1892
|
+
font-size: 14px;
|
|
1893
|
+
font-weight: 500;
|
|
1894
|
+
color: var(--text);
|
|
1895
|
+
}
|
|
1896
|
+
.settings-label-hint {
|
|
1897
|
+
font-size: 12px;
|
|
1898
|
+
color: var(--text-muted);
|
|
1899
|
+
}
|
|
1900
|
+
.settings-control { flex-shrink: 0; }
|
|
1901
|
+
.settings-select {
|
|
1902
|
+
background: var(--bg-input);
|
|
1903
|
+
border: 1px solid var(--border);
|
|
1904
|
+
color: var(--text);
|
|
1905
|
+
padding: 8px 12px;
|
|
1906
|
+
border-radius: var(--radius);
|
|
1907
|
+
font-size: 13px;
|
|
1908
|
+
font-family: var(--mono);
|
|
1909
|
+
cursor: pointer;
|
|
1910
|
+
min-width: 180px;
|
|
1911
|
+
}
|
|
1912
|
+
.settings-select:focus { outline: none; border-color: var(--accent); }
|
|
1913
|
+
.settings-input {
|
|
1914
|
+
background: var(--bg-input);
|
|
1915
|
+
border: 1px solid var(--border);
|
|
1916
|
+
color: var(--text);
|
|
1917
|
+
padding: 8px 12px;
|
|
1918
|
+
border-radius: var(--radius);
|
|
1919
|
+
font-size: 13px;
|
|
1920
|
+
font-family: var(--mono);
|
|
1921
|
+
min-width: 180px;
|
|
1922
|
+
}
|
|
1923
|
+
.settings-input:focus { outline: none; border-color: var(--accent); }
|
|
1924
|
+
.settings-toggle {
|
|
1925
|
+
position: relative;
|
|
1926
|
+
width: 44px;
|
|
1927
|
+
height: 24px;
|
|
1928
|
+
background: var(--border);
|
|
1929
|
+
border-radius: 12px;
|
|
1930
|
+
border: none;
|
|
1931
|
+
cursor: pointer;
|
|
1932
|
+
transition: background 0.2s;
|
|
1933
|
+
padding: 0;
|
|
1934
|
+
}
|
|
1935
|
+
.settings-toggle.active { background: var(--accent); }
|
|
1936
|
+
.settings-toggle::after {
|
|
1937
|
+
content: '';
|
|
1938
|
+
position: absolute;
|
|
1939
|
+
top: 2px;
|
|
1940
|
+
left: 2px;
|
|
1941
|
+
width: 20px;
|
|
1942
|
+
height: 20px;
|
|
1943
|
+
background: white;
|
|
1944
|
+
border-radius: 50%;
|
|
1945
|
+
transition: transform 0.2s;
|
|
1946
|
+
}
|
|
1947
|
+
.settings-toggle.active::after { transform: translateX(20px); }
|
|
1948
|
+
.settings-saved {
|
|
1949
|
+
display: inline-flex;
|
|
1950
|
+
align-items: center;
|
|
1951
|
+
gap: 4px;
|
|
1952
|
+
font-size: 12px;
|
|
1953
|
+
color: var(--success);
|
|
1954
|
+
opacity: 0;
|
|
1955
|
+
transition: opacity 0.3s;
|
|
1956
|
+
}
|
|
1957
|
+
.settings-saved.show { opacity: 1; }
|
|
1958
|
+
.settings-reset-btn {
|
|
1959
|
+
background: transparent;
|
|
1960
|
+
border: 1px solid var(--border);
|
|
1961
|
+
color: var(--text-dim);
|
|
1962
|
+
padding: 8px 16px;
|
|
1963
|
+
border-radius: var(--radius);
|
|
1964
|
+
font-size: 13px;
|
|
1965
|
+
cursor: pointer;
|
|
1966
|
+
transition: all 0.2s;
|
|
1967
|
+
}
|
|
1968
|
+
.settings-reset-btn:hover { border-color: var(--error); color: var(--error); }
|
|
1969
|
+
.settings-toggle-vis {
|
|
1970
|
+
background: none;
|
|
1971
|
+
border: 1px solid var(--border);
|
|
1972
|
+
border-radius: var(--radius);
|
|
1973
|
+
padding: 6px 8px;
|
|
1974
|
+
cursor: pointer;
|
|
1975
|
+
font-size: 14px;
|
|
1976
|
+
line-height: 1;
|
|
1977
|
+
transition: all 0.2s;
|
|
1978
|
+
color: var(--text-dim);
|
|
1979
|
+
}
|
|
1980
|
+
.settings-toggle-vis:hover { border-color: var(--accent); }
|
|
1981
|
+
.settings-api-key-active { color: var(--success); }
|
|
1982
|
+
.settings-api-key-missing { color: var(--warning); }
|
|
1983
|
+
.settings-row .settings-select, .settings-row .settings-input { text-align: right; }
|
|
1984
|
+
|
|
1985
|
+
/* ── Multimodal Tab ── */
|
|
1986
|
+
.mm-grid {
|
|
1987
|
+
display: grid;
|
|
1988
|
+
grid-template-columns: 1fr 1fr;
|
|
1989
|
+
gap: 16px;
|
|
1990
|
+
}
|
|
1991
|
+
.mm-drop-zone {
|
|
1992
|
+
border: 2px dashed var(--border);
|
|
1993
|
+
border-radius: var(--radius);
|
|
1994
|
+
padding: 40px 24px;
|
|
1995
|
+
text-align: center;
|
|
1996
|
+
cursor: pointer;
|
|
1997
|
+
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
|
|
1998
|
+
background: var(--bg-input);
|
|
1999
|
+
min-height: 200px;
|
|
2000
|
+
display: flex;
|
|
2001
|
+
flex-direction: column;
|
|
2002
|
+
align-items: center;
|
|
2003
|
+
justify-content: center;
|
|
2004
|
+
gap: 12px;
|
|
2005
|
+
}
|
|
2006
|
+
.mm-drop-zone:hover {
|
|
2007
|
+
border-color: var(--accent);
|
|
2008
|
+
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
2009
|
+
}
|
|
2010
|
+
.mm-drop-zone.drag-active {
|
|
2011
|
+
border-color: var(--accent);
|
|
2012
|
+
background: var(--accent-glow);
|
|
2013
|
+
box-shadow: 0 0 0 4px var(--accent-glow);
|
|
2014
|
+
}
|
|
2015
|
+
.mm-drop-zone .mm-drop-icon {
|
|
2016
|
+
font-size: 40px;
|
|
2017
|
+
opacity: 0.6;
|
|
2018
|
+
}
|
|
2019
|
+
.mm-drop-zone .mm-drop-text {
|
|
2020
|
+
color: var(--text-dim);
|
|
2021
|
+
font-size: 14px;
|
|
2022
|
+
}
|
|
2023
|
+
.mm-drop-zone .mm-drop-hint {
|
|
2024
|
+
color: var(--text-muted);
|
|
2025
|
+
font-size: 12px;
|
|
2026
|
+
}
|
|
2027
|
+
.mm-preview {
|
|
2028
|
+
display: none;
|
|
2029
|
+
flex-direction: column;
|
|
2030
|
+
align-items: center;
|
|
2031
|
+
gap: 12px;
|
|
2032
|
+
padding: 16px;
|
|
2033
|
+
}
|
|
2034
|
+
.mm-preview.visible { display: flex; }
|
|
2035
|
+
.mm-preview img {
|
|
2036
|
+
max-height: 300px;
|
|
2037
|
+
max-width: 100%;
|
|
2038
|
+
object-fit: contain;
|
|
2039
|
+
border-radius: var(--radius);
|
|
2040
|
+
border: 1px solid var(--border);
|
|
2041
|
+
}
|
|
2042
|
+
.mm-preview .mm-file-info {
|
|
2043
|
+
font-size: 12px;
|
|
2044
|
+
color: var(--text-muted);
|
|
2045
|
+
font-family: var(--mono);
|
|
2046
|
+
}
|
|
2047
|
+
.mm-preview .mm-clear-btn {
|
|
2048
|
+
background: none;
|
|
2049
|
+
border: 1px solid var(--border);
|
|
2050
|
+
color: var(--text-dim);
|
|
2051
|
+
border-radius: 6px;
|
|
2052
|
+
padding: 4px 14px;
|
|
2053
|
+
font-size: 12px;
|
|
2054
|
+
cursor: pointer;
|
|
2055
|
+
font-family: var(--font);
|
|
2056
|
+
transition: border-color 0.15s, color 0.15s;
|
|
2057
|
+
}
|
|
2058
|
+
.mm-preview .mm-clear-btn:hover {
|
|
2059
|
+
border-color: var(--error);
|
|
2060
|
+
color: var(--error);
|
|
2061
|
+
}
|
|
2062
|
+
.mm-gallery-section {
|
|
2063
|
+
margin-top: 32px;
|
|
2064
|
+
padding-top: 24px;
|
|
2065
|
+
border-top: 1px solid var(--border);
|
|
2066
|
+
}
|
|
2067
|
+
.mm-gallery-grid {
|
|
2068
|
+
display: grid;
|
|
2069
|
+
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
2070
|
+
gap: 12px;
|
|
2071
|
+
margin-bottom: 16px;
|
|
2072
|
+
}
|
|
2073
|
+
.mm-gallery-slot {
|
|
2074
|
+
aspect-ratio: 1;
|
|
2075
|
+
border: 2px dashed var(--border);
|
|
2076
|
+
border-radius: var(--radius);
|
|
2077
|
+
display: flex;
|
|
2078
|
+
align-items: center;
|
|
2079
|
+
justify-content: center;
|
|
2080
|
+
cursor: pointer;
|
|
2081
|
+
transition: border-color 0.2s, background 0.2s;
|
|
2082
|
+
background: var(--bg-input);
|
|
2083
|
+
position: relative;
|
|
2084
|
+
overflow: hidden;
|
|
2085
|
+
}
|
|
2086
|
+
.mm-gallery-slot:hover {
|
|
2087
|
+
border-color: var(--accent);
|
|
2088
|
+
background: var(--accent-glow);
|
|
2089
|
+
}
|
|
2090
|
+
.mm-gallery-slot.filled {
|
|
2091
|
+
border-style: solid;
|
|
2092
|
+
cursor: default;
|
|
2093
|
+
padding: 0;
|
|
2094
|
+
}
|
|
2095
|
+
.mm-gallery-slot.filled img {
|
|
2096
|
+
width: 100%;
|
|
2097
|
+
height: 100%;
|
|
2098
|
+
object-fit: cover;
|
|
2099
|
+
border-radius: calc(var(--radius) - 2px);
|
|
2100
|
+
}
|
|
2101
|
+
.mm-gallery-slot .mm-slot-remove {
|
|
2102
|
+
position: absolute;
|
|
2103
|
+
top: 4px;
|
|
2104
|
+
right: 4px;
|
|
2105
|
+
background: rgba(0,0,0,0.6);
|
|
2106
|
+
color: #fff;
|
|
2107
|
+
border: none;
|
|
2108
|
+
border-radius: 50%;
|
|
2109
|
+
width: 22px;
|
|
2110
|
+
height: 22px;
|
|
2111
|
+
font-size: 13px;
|
|
2112
|
+
cursor: pointer;
|
|
2113
|
+
display: none;
|
|
2114
|
+
align-items: center;
|
|
2115
|
+
justify-content: center;
|
|
2116
|
+
line-height: 1;
|
|
2117
|
+
}
|
|
2118
|
+
.mm-gallery-slot.filled:hover .mm-slot-remove { display: flex; }
|
|
2119
|
+
.mm-gallery-slot .mm-slot-add {
|
|
2120
|
+
font-size: 28px;
|
|
2121
|
+
color: var(--text-muted);
|
|
2122
|
+
pointer-events: none;
|
|
2123
|
+
}
|
|
2124
|
+
.mm-gallery-slot.query-selected {
|
|
2125
|
+
border-color: var(--accent);
|
|
2126
|
+
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
2127
|
+
}
|
|
2128
|
+
.mm-result-list {
|
|
2129
|
+
display: flex;
|
|
2130
|
+
flex-direction: column;
|
|
2131
|
+
gap: 8px;
|
|
2132
|
+
}
|
|
2133
|
+
.mm-result-item {
|
|
2134
|
+
display: flex;
|
|
2135
|
+
align-items: center;
|
|
2136
|
+
gap: 12px;
|
|
2137
|
+
padding: 12px 16px;
|
|
2138
|
+
background: var(--bg-input);
|
|
2139
|
+
border: 1px solid var(--border);
|
|
2140
|
+
border-radius: var(--radius);
|
|
2141
|
+
transition: border-color 0.15s;
|
|
2142
|
+
}
|
|
2143
|
+
.mm-result-item:first-child {
|
|
2144
|
+
border-color: var(--accent);
|
|
2145
|
+
background: var(--accent-glow);
|
|
2146
|
+
}
|
|
2147
|
+
.mm-result-item .mm-result-rank {
|
|
2148
|
+
font-family: var(--mono);
|
|
2149
|
+
font-weight: 700;
|
|
2150
|
+
font-size: 18px;
|
|
2151
|
+
color: var(--text-muted);
|
|
2152
|
+
min-width: 28px;
|
|
2153
|
+
text-align: center;
|
|
1299
2154
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
border
|
|
1307
|
-
border: 3px solid var(--accent);
|
|
1308
|
-
box-shadow: 0 0 20px var(--accent-glow);
|
|
2155
|
+
.mm-result-item:first-child .mm-result-rank { color: var(--accent); }
|
|
2156
|
+
.mm-result-item .mm-result-thumb {
|
|
2157
|
+
width: 48px;
|
|
2158
|
+
height: 48px;
|
|
2159
|
+
border-radius: 6px;
|
|
2160
|
+
object-fit: cover;
|
|
2161
|
+
border: 1px solid var(--border);
|
|
1309
2162
|
flex-shrink: 0;
|
|
1310
2163
|
}
|
|
1311
|
-
.
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
.about-links a {
|
|
1315
|
-
color: var(--text-dim);
|
|
1316
|
-
font-size: 13px;
|
|
1317
|
-
text-decoration: none;
|
|
1318
|
-
transition: color 0.2s;
|
|
2164
|
+
.mm-result-item .mm-result-content {
|
|
2165
|
+
flex: 1;
|
|
2166
|
+
min-width: 0;
|
|
1319
2167
|
}
|
|
1320
|
-
.
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
2168
|
+
.mm-result-item .mm-result-text {
|
|
2169
|
+
font-size: 14px;
|
|
2170
|
+
color: var(--text);
|
|
2171
|
+
white-space: nowrap;
|
|
2172
|
+
overflow: hidden;
|
|
2173
|
+
text-overflow: ellipsis;
|
|
2174
|
+
}
|
|
2175
|
+
.mm-result-item .mm-result-type {
|
|
2176
|
+
font-size: 11px;
|
|
2177
|
+
color: var(--text-muted);
|
|
1326
2178
|
text-transform: uppercase;
|
|
1327
2179
|
letter-spacing: 0.5px;
|
|
1328
|
-
margin-bottom: 8px;
|
|
1329
|
-
}
|
|
1330
|
-
.about-text { font-size: 14px; line-height: 1.8; color: var(--text); }
|
|
1331
|
-
.about-text a { color: var(--blue); text-decoration: none; }
|
|
1332
|
-
.about-text a:hover { text-decoration: underline; }
|
|
1333
|
-
.about-disclaimer {
|
|
1334
|
-
background: rgba(255, 215, 61, 0.08);
|
|
1335
|
-
border: 1px solid rgba(255, 215, 61, 0.2);
|
|
1336
|
-
border-radius: var(--radius);
|
|
1337
|
-
padding: 16px 20px;
|
|
1338
|
-
margin-top: 24px;
|
|
1339
2180
|
}
|
|
1340
|
-
.
|
|
1341
|
-
font-
|
|
2181
|
+
.mm-result-item .mm-result-score {
|
|
2182
|
+
font-family: var(--mono);
|
|
1342
2183
|
font-weight: 600;
|
|
1343
|
-
|
|
1344
|
-
|
|
2184
|
+
font-size: 14px;
|
|
2185
|
+
flex-shrink: 0;
|
|
1345
2186
|
}
|
|
1346
|
-
.
|
|
1347
|
-
|
|
1348
|
-
|
|
2187
|
+
.mm-search-row {
|
|
2188
|
+
display: flex;
|
|
2189
|
+
gap: 8px;
|
|
2190
|
+
align-items: center;
|
|
2191
|
+
margin-bottom: 12px;
|
|
2192
|
+
}
|
|
2193
|
+
.mm-search-row input[type="text"] {
|
|
2194
|
+
flex: 1;
|
|
2195
|
+
}
|
|
2196
|
+
.mm-search-mode {
|
|
2197
|
+
display: flex;
|
|
2198
|
+
gap: 6px;
|
|
2199
|
+
margin-bottom: 12px;
|
|
2200
|
+
}
|
|
2201
|
+
.mm-search-mode button {
|
|
2202
|
+
background: var(--bg-input);
|
|
2203
|
+
border: 1px solid var(--border);
|
|
1349
2204
|
color: var(--text-dim);
|
|
2205
|
+
border-radius: 6px;
|
|
2206
|
+
padding: 4px 12px;
|
|
2207
|
+
font-size: 12px;
|
|
2208
|
+
cursor: pointer;
|
|
2209
|
+
font-family: var(--font);
|
|
2210
|
+
transition: all 0.15s;
|
|
2211
|
+
}
|
|
2212
|
+
.mm-search-mode button.active {
|
|
2213
|
+
border-color: var(--accent);
|
|
2214
|
+
color: var(--accent);
|
|
2215
|
+
background: var(--accent-glow);
|
|
1350
2216
|
}
|
|
1351
2217
|
|
|
1352
2218
|
@media (max-width: 768px) {
|
|
2219
|
+
.mm-grid { grid-template-columns: 1fr; }
|
|
2220
|
+
.mm-gallery-grid { grid-template-columns: repeat(3, 1fr); }
|
|
1353
2221
|
.compare-grid, .search-results { grid-template-columns: 1fr; }
|
|
1354
|
-
.
|
|
2222
|
+
.sidebar { width: 56px; min-width: 56px; }
|
|
2223
|
+
.sidebar-title, .status-label { display: none; }
|
|
2224
|
+
.tab-btn { justify-content: center; padding: 10px; }
|
|
2225
|
+
.tab-btn span:not(.tab-btn-icon) { display: none; }
|
|
1355
2226
|
.main { padding: 16px; }
|
|
1356
|
-
.
|
|
2227
|
+
.settings-row { flex-direction: column; align-items: flex-start; gap: 8px; }
|
|
2228
|
+
.settings-select, .settings-input { min-width: 100%; }
|
|
1357
2229
|
}
|
|
1358
2230
|
</style>
|
|
1359
2231
|
</head>
|
|
1360
2232
|
<body>
|
|
1361
2233
|
|
|
1362
|
-
<!--
|
|
1363
|
-
<
|
|
1364
|
-
<
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
<
|
|
2234
|
+
<!-- LeafyGreen Icon Sprites (mongodb.design) -->
|
|
2235
|
+
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
|
|
2236
|
+
<symbol id="lg-lightning" viewBox="0 0 16 16">
|
|
2237
|
+
<path d="M9.22274 1.99296C9.22274 1.49561 8.56293 1.31233 8.30107 1.73667L4.07384 8.58696C3.87133 8.91513 4.10921 9.33717 4.49748 9.33717H6.77682L6.77682 14.0066C6.77682 14.504 7.43627 14.6879 7.69813 14.2635L11.9262 7.4118C12.1288 7.08363 11.8903 6.66244 11.5021 6.66244H9.22274V1.99296Z" fill="currentColor"/>
|
|
2238
|
+
</symbol>
|
|
2239
|
+
<symbol id="lg-arrows" viewBox="0 0 16 16">
|
|
2240
|
+
<path d="M5 8.57279V13.4272C5 13.9565 4.39241 14.2015 4.0721 13.8014L2.12898 11.3742C1.95701 11.1594 1.95701 10.8406 2.12898 10.6258L4.0721 8.19856C4.39241 7.79846 5 8.04351 5 8.57279Z" fill="currentColor"/>
|
|
2241
|
+
<path d="M5 10H12.5C12.7761 10 13 10.2239 13 10.5V11.5C13 11.7761 12.7761 12 12.5 12H5V10Z" fill="currentColor"/>
|
|
2242
|
+
<path d="M11 7.42721V2.57279C11 2.04351 11.6076 1.79846 11.9279 2.19856L13.871 4.62577C14.043 4.84058 14.043 5.15942 13.871 5.37423L11.9279 7.80144C11.6076 8.20154 11 7.95649 11 7.42721Z" fill="currentColor"/>
|
|
2243
|
+
<path d="M3 4.5C3 4.22386 3.22386 4 3.5 4H11V6H3.5C3.22386 6 3 5.77614 3 5.5V4.5Z" fill="currentColor"/>
|
|
2244
|
+
</symbol>
|
|
2245
|
+
<symbol id="lg-search" viewBox="0 0 16 16">
|
|
2246
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.3234 9.81874C4.07618 11.5715 6.75062 11.8398 8.78588 10.6244L12.93 14.7685C13.4377 15.2762 14.2608 15.2762 14.7685 14.7685C15.2762 14.2608 15.2762 13.4377 14.7685 12.93L10.6244 8.78588C11.8398 6.75062 11.5715 4.07619 9.81873 2.32341C7.74896 0.253628 4.39318 0.253628 2.3234 2.32341C0.253624 4.39319 0.253624 7.74896 2.3234 9.81874ZM7.98026 4.16188C9.03467 5.2163 9.03467 6.92585 7.98026 7.98026C6.92584 9.03468 5.2163 9.03468 4.16188 7.98026C3.10746 6.92585 3.10746 5.2163 4.16188 4.16188C5.2163 3.10747 6.92584 3.10747 7.98026 4.16188Z" fill="currentColor"/>
|
|
2247
|
+
</symbol>
|
|
2248
|
+
<symbol id="lg-gauge" viewBox="0 0 16 16">
|
|
2249
|
+
<path d="M1.041 10.2514C0.996713 10.6632 1.33666 11 1.75088 11H4.2449C4.65912 11 4.98569 10.6591 5.08798 10.2577C5.22027 9.73864 5.49013 9.25966 5.87533 8.87446C6.43906 8.31073 7.20364 7.99403 8.00088 7.99403C8.27011 7.99403 8.53562 8.03015 8.79093 8.0997L11.7818 5.10887C10.6623 4.39046 9.35172 4 8.00088 4C6.14436 4 4.36388 4.7375 3.05113 6.05025C1.91604 7.18534 1.21104 8.67012 1.041 10.2514Z" fill="currentColor"/>
|
|
2250
|
+
<path d="M13.2967 6.42237L10.455 9.26409C10.6678 9.56493 10.8231 9.90191 10.9138 10.2577C11.0161 10.6591 11.3426 11 11.7568 11L14.2509 11C14.6651 11 15.005 10.6632 14.9608 10.2514C14.8087 8.83759 14.229 7.50093 13.2967 6.42237Z" fill="currentColor"/>
|
|
2251
|
+
</symbol>
|
|
2252
|
+
<symbol id="lg-bulb" viewBox="0 0 16 16">
|
|
2253
|
+
<path d="M12.3311 8.5C12.7565 7.76457 13 6.91072 13 6C13 3.23858 10.7614 1 8 1C5.23858 1 3 3.23858 3 6C3 6.94628 3.26287 7.83117 3.71958 8.58561L5.40749 11.501C5.58628 11.8099 5.91607 12 6.27291 12H6.5V6C6.5 5.17157 7.17157 4.5 8 4.5C8.82843 4.5 9.5 5.17157 9.5 6V12H9.72368C10.0793 12 10.4082 11.8111 10.5874 11.5039L12.34 8.5H12.3311Z" fill="currentColor"/>
|
|
2254
|
+
<path d="M7.5 6V12H8.5V6C8.5 5.72386 8.27614 5.5 8 5.5C7.72386 5.5 7.5 5.72386 7.5 6Z" fill="currentColor"/>
|
|
2255
|
+
<path d="M10 14V13H6V14C6 14.5523 6.44772 15 7 15H9C9.55228 15 10 14.5523 10 14Z" fill="currentColor"/>
|
|
2256
|
+
</symbol>
|
|
2257
|
+
<symbol id="lg-info" viewBox="0 0 16 16">
|
|
2258
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM9 4C9 4.55228 8.55228 5 8 5C7.44772 5 7 4.55228 7 4C7 3.44772 7.44772 3 8 3C8.55228 3 9 3.44772 9 4ZM8 6C8.55228 6 9 6.44772 9 7V11H9.5C9.77614 11 10 11.2239 10 11.5C10 11.7761 9.77614 12 9.5 12H6.5C6.22386 12 6 11.7761 6 11.5C6 11.2239 6.22386 11 6.5 11H7V7H6.5C6.22386 7 6 6.77614 6 6.5C6 6.22386 6.22386 6 6.5 6H8Z" fill="currentColor"/>
|
|
2259
|
+
</symbol>
|
|
2260
|
+
<symbol id="lg-image" viewBox="0 0 16 16">
|
|
2261
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 3C2 2.44772 2.44772 2 3 2H13C13.5523 2 14 2.44772 14 3V13C14 13.5523 13.5523 14 13 14H3C2.44772 14 2 13.5523 2 13V3ZM4 4V9.586L5.793 7.793C6.183 7.403 6.817 7.403 7.207 7.793L8.5 9.086L10.293 7.293C10.683 6.903 11.317 6.903 11.707 7.293L12 7.586V4H4ZM12 10.414L10.5 8.914L8.207 11.207C7.817 11.597 7.183 11.597 6.793 11.207L5.5 9.914L4 11.414V12H12V10.414ZM10 5.5C10 6.328 10.672 7 11.5 7C11.776 7 12 6.776 12 6.5V5C12 4.724 11.776 4.5 11.5 4.5H10.5C10.224 4.5 10 4.724 10 5V5.5Z" fill="currentColor"/>
|
|
2262
|
+
</symbol>
|
|
2263
|
+
<symbol id="lg-config" viewBox="0 0 16 16">
|
|
2264
|
+
<path d="M12.7 2.56989C12.41 2.56989 12.17 2.80989 12.17 3.09989V4.34989L11.32 3.49989C9.21 1.39989 5.75 1.51989 3.78 3.75989C2.65 5.03989 2.23 6.82989 2.68 8.48989C3.24 10.5299 4.98 12.0099 7.08 12.2299L8.1 12.3399C8.42 13.2799 9.3 13.9499 10.34 13.9499C11.65 13.9499 12.71 12.8899 12.71 11.5799C12.71 10.2699 11.65 9.20989 10.34 9.20989C9.14 9.20989 8.16 10.0999 8 11.2599L7.19 11.1799C5.53 11.0099 4.14 9.82989 3.7 8.21989C3.34 6.90989 3.67 5.48989 4.58 4.46989C6.15 2.68989 8.9 2.59989 10.57 4.26989L11.42 5.11989H10.17C9.88 5.11989 9.64 5.35989 9.64 5.64989C9.64 5.93989 9.88 6.17989 10.17 6.17989H12.71C13 6.17989 13.24 5.93989 13.24 5.64989V3.09989C13.24 2.80989 13 2.56989 12.71 2.56989H12.7ZM10.34 10.3099C11.04 10.3099 11.6 10.8799 11.6 11.5699C11.6 12.2599 11.03 12.8299 10.34 12.8299C9.65 12.8299 9.08 12.2599 9.08 11.5699C9.08 10.8799 9.65 10.3099 10.34 10.3099Z" fill="currentColor"/>
|
|
2265
|
+
</symbol>
|
|
2266
|
+
</svg>
|
|
2267
|
+
|
|
2268
|
+
<div class="app-shell">
|
|
2269
|
+
|
|
2270
|
+
<!-- Sidebar -->
|
|
2271
|
+
<aside class="sidebar">
|
|
2272
|
+
<div class="sidebar-drag-region">
|
|
2273
|
+
<img class="sidebar-logo" id="sidebarLogo" src="/icons/dark/64.png" alt="Vai">
|
|
2274
|
+
<span class="sidebar-title">Vai</span>
|
|
1369
2275
|
</div>
|
|
1370
|
-
<
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
2276
|
+
<nav class="sidebar-nav">
|
|
2277
|
+
<div class="sidebar-nav-group">
|
|
2278
|
+
<div class="sidebar-nav-label">Tools</div>
|
|
2279
|
+
<button class="tab-btn active" data-tab="embed"><span class="tab-btn-icon"><svg><use href="#lg-lightning"/></svg></span><span>Embed</span></button>
|
|
2280
|
+
<button class="tab-btn" data-tab="compare"><span class="tab-btn-icon"><svg><use href="#lg-arrows"/></svg></span><span>Compare</span></button>
|
|
2281
|
+
<button class="tab-btn" data-tab="search"><span class="tab-btn-icon"><svg><use href="#lg-search"/></svg></span><span>Search</span></button>
|
|
2282
|
+
<button class="tab-btn" data-tab="multimodal"><span class="tab-btn-icon"><svg><use href="#lg-image"/></svg></span><span>Multimodal</span></button>
|
|
2283
|
+
</div>
|
|
2284
|
+
<div class="sidebar-nav-divider"></div>
|
|
2285
|
+
<div class="sidebar-nav-group">
|
|
2286
|
+
<div class="sidebar-nav-label">Resources</div>
|
|
2287
|
+
<button class="tab-btn" data-tab="benchmark"><span class="tab-btn-icon"><svg><use href="#lg-gauge"/></svg></span><span>Benchmark</span></button>
|
|
2288
|
+
<button class="tab-btn" data-tab="explore"><span class="tab-btn-icon"><svg><use href="#lg-bulb"/></svg></span><span>Explore</span></button>
|
|
2289
|
+
<button class="tab-btn" data-tab="about"><span class="tab-btn-icon"><svg><use href="#lg-info"/></svg></span><span>About</span></button>
|
|
2290
|
+
</div>
|
|
2291
|
+
<div class="sidebar-nav-divider"></div>
|
|
2292
|
+
<button class="tab-btn" data-tab="settings"><span class="tab-btn-icon"><svg><use href="#lg-config"/></svg></span><span>Settings</span></button>
|
|
2293
|
+
</nav>
|
|
2294
|
+
<div class="sidebar-footer">
|
|
2295
|
+
<div style="display:flex;align-items:center;gap:8px;justify-content:space-between;">
|
|
2296
|
+
<div style="display:flex;align-items:center;gap:5px;">
|
|
2297
|
+
<div class="status-dot" id="statusDot"></div>
|
|
2298
|
+
<span class="status-label" id="statusLabel">Checking...</span>
|
|
2299
|
+
</div>
|
|
2300
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark mode">🌙</button>
|
|
2301
|
+
</div>
|
|
2302
|
+
<div id="appVersionLabel" style="font-size:10px;color:var(--text-muted);text-align:center;"></div>
|
|
1374
2303
|
</div>
|
|
1375
|
-
</
|
|
1376
|
-
|
|
1377
|
-
<!--
|
|
1378
|
-
<div class="
|
|
1379
|
-
<
|
|
1380
|
-
<
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
</
|
|
1386
|
-
|
|
1387
|
-
<
|
|
2304
|
+
</aside>
|
|
2305
|
+
|
|
2306
|
+
<!-- Content -->
|
|
2307
|
+
<div class="content-area">
|
|
2308
|
+
<div class="content-drag-region"></div>
|
|
2309
|
+
<div class="update-banner" id="updateBanner">
|
|
2310
|
+
<span class="update-banner-icon">🚀</span>
|
|
2311
|
+
<span class="update-banner-text">
|
|
2312
|
+
<strong>Vai <span id="updateVersion"></span></strong> is available.
|
|
2313
|
+
You're on <span id="currentVersion"></span>.
|
|
2314
|
+
</span>
|
|
2315
|
+
<button class="update-banner-btn" id="updateDownloadBtn">Download</button>
|
|
2316
|
+
<button class="update-banner-dismiss" id="updateDismissBtn" title="Dismiss">×</button>
|
|
2317
|
+
</div>
|
|
2318
|
+
<div class="main">
|
|
1388
2319
|
|
|
1389
2320
|
<!-- ========== EMBED TAB ========== -->
|
|
1390
2321
|
<div class="tab-panel active" id="tab-embed">
|
|
2322
|
+
<div class="page-header">
|
|
2323
|
+
<h2 class="page-header-title">Embed</h2>
|
|
2324
|
+
<p class="page-header-subtitle">Generate vector embeddings for text</p>
|
|
2325
|
+
<p class="page-header-hint">Paste or type text below, choose a model, and hit Embed to see the raw vectors and token usage.</p>
|
|
2326
|
+
</div>
|
|
1391
2327
|
<div class="card">
|
|
1392
2328
|
<div class="card-title">Input Text</div>
|
|
1393
2329
|
<textarea id="embedInput" rows="5" placeholder="Enter text to embed...">MongoDB Atlas provides powerful vector search capabilities for AI applications.</textarea>
|
|
@@ -1447,6 +2383,11 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1447
2383
|
|
|
1448
2384
|
<!-- ========== COMPARE TAB ========== -->
|
|
1449
2385
|
<div class="tab-panel" id="tab-compare">
|
|
2386
|
+
<div class="page-header">
|
|
2387
|
+
<h2 class="page-header-title">Compare</h2>
|
|
2388
|
+
<p class="page-header-subtitle">Visualize similarity between text pairs</p>
|
|
2389
|
+
<p class="page-header-hint">Enter two texts and compare their embeddings — see cosine similarity, a heatmap of vector dimensions, and a visual diff.</p>
|
|
2390
|
+
</div>
|
|
1450
2391
|
<div class="compare-grid">
|
|
1451
2392
|
<div class="card">
|
|
1452
2393
|
<div class="card-title">Text A</div>
|
|
@@ -1496,6 +2437,11 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1496
2437
|
|
|
1497
2438
|
<!-- ========== SEARCH TAB ========== -->
|
|
1498
2439
|
<div class="tab-panel" id="tab-search">
|
|
2440
|
+
<div class="page-header">
|
|
2441
|
+
<h2 class="page-header-title">Rerank</h2>
|
|
2442
|
+
<p class="page-header-subtitle">Re-order documents by relevance to a query</p>
|
|
2443
|
+
<p class="page-header-hint">Enter a search query and a set of documents — the reranker scores and sorts them by semantic relevance.</p>
|
|
2444
|
+
</div>
|
|
1499
2445
|
<div class="card">
|
|
1500
2446
|
<div class="card-title">Query</div>
|
|
1501
2447
|
<input type="text" id="searchQuery" placeholder="Enter your search query..." value="How do I build AI-powered search?">
|
|
@@ -1540,8 +2486,128 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
1540
2486
|
</div>
|
|
1541
2487
|
</div>
|
|
1542
2488
|
|
|
2489
|
+
<!-- ========== MULTIMODAL TAB ========== -->
|
|
2490
|
+
<div class="tab-panel" id="tab-multimodal">
|
|
2491
|
+
<div class="page-header">
|
|
2492
|
+
<h2 class="page-header-title">Multimodal</h2>
|
|
2493
|
+
<p class="page-header-subtitle">Compare images and text in the same vector space</p>
|
|
2494
|
+
<p class="page-header-hint">Voyage AI's multimodal models embed images and text into a unified vector space — so you can compare them directly with cosine similarity.</p>
|
|
2495
|
+
</div>
|
|
2496
|
+
|
|
2497
|
+
<!-- Section A: Image ↔ Text Similarity -->
|
|
2498
|
+
<div class="mm-grid">
|
|
2499
|
+
<div class="card">
|
|
2500
|
+
<div class="card-title">Image</div>
|
|
2501
|
+
<div class="mm-drop-zone" id="mmDropZone">
|
|
2502
|
+
<div class="mm-drop-icon">🖼️</div>
|
|
2503
|
+
<div class="mm-drop-text">Drop an image here or click to browse</div>
|
|
2504
|
+
<div class="mm-drop-hint">PNG, JPEG, WebP, GIF — max 20 MB · Paste from clipboard (⌘V)</div>
|
|
2505
|
+
</div>
|
|
2506
|
+
<input type="file" id="mmFileInput" accept="image/png,image/jpeg,image/webp,image/gif" style="display:none">
|
|
2507
|
+
<div class="mm-preview" id="mmPreview">
|
|
2508
|
+
<img id="mmPreviewImg" src="" alt="Preview">
|
|
2509
|
+
<div class="mm-file-info" id="mmFileInfo"></div>
|
|
2510
|
+
<button class="mm-clear-btn" onclick="clearMultimodalImage()">✕ Clear</button>
|
|
2511
|
+
</div>
|
|
2512
|
+
</div>
|
|
2513
|
+
<div class="card">
|
|
2514
|
+
<div class="card-title">Text</div>
|
|
2515
|
+
<textarea id="mmText" rows="8" placeholder="Describe what you see, or enter any text to compare against the image..."></textarea>
|
|
2516
|
+
</div>
|
|
2517
|
+
</div>
|
|
2518
|
+
|
|
2519
|
+
<div class="options-row">
|
|
2520
|
+
<div class="option-group">
|
|
2521
|
+
<span class="option-label">Model</span>
|
|
2522
|
+
<select id="mmModel">
|
|
2523
|
+
<option value="voyage-multimodal-3.5">voyage-multimodal-3.5</option>
|
|
2524
|
+
<option value="voyage-multimodal-3">voyage-multimodal-3</option>
|
|
2525
|
+
</select>
|
|
2526
|
+
</div>
|
|
2527
|
+
<div class="option-group">
|
|
2528
|
+
<span class="option-label">Dimensions</span>
|
|
2529
|
+
<select id="mmDimensions">
|
|
2530
|
+
<option value="">Default</option>
|
|
2531
|
+
<option value="256">256</option>
|
|
2532
|
+
<option value="512">512</option>
|
|
2533
|
+
<option value="1024">1024</option>
|
|
2534
|
+
<option value="2048">2048</option>
|
|
2535
|
+
</select>
|
|
2536
|
+
</div>
|
|
2537
|
+
<button class="btn" id="mmCompareBtn" onclick="doMultimodalCompare()">🔮 Compare</button>
|
|
2538
|
+
</div>
|
|
2539
|
+
|
|
2540
|
+
<div class="error-msg" id="mmError"></div>
|
|
2541
|
+
|
|
2542
|
+
<div class="result-section" id="mmResult">
|
|
2543
|
+
<div class="card">
|
|
2544
|
+
<div class="similarity-display">
|
|
2545
|
+
<div class="similarity-score" id="mmSimScore">—</div>
|
|
2546
|
+
<div class="similarity-label">Cosine Similarity</div>
|
|
2547
|
+
<div class="similarity-bar-outer">
|
|
2548
|
+
<div class="similarity-bar-inner" id="mmSimBar" style="width:0%"></div>
|
|
2549
|
+
</div>
|
|
2550
|
+
</div>
|
|
2551
|
+
<div id="mmStats" style="text-align:center;margin-top:16px;"></div>
|
|
2552
|
+
<div class="metric-note" id="mmNote" style="margin-top:16px;"></div>
|
|
2553
|
+
</div>
|
|
2554
|
+
</div>
|
|
2555
|
+
|
|
2556
|
+
<!-- Section B: Cross-Modal Gallery -->
|
|
2557
|
+
<div class="mm-gallery-section">
|
|
2558
|
+
<h3 style="color:var(--accent-text);margin-bottom:4px;">Cross-Modal Gallery</h3>
|
|
2559
|
+
<p style="color:var(--text-dim);font-size:13px;margin-bottom:16px;">Build a mini corpus of images and texts, then search across both modalities at once.</p>
|
|
2560
|
+
|
|
2561
|
+
<div class="card">
|
|
2562
|
+
<div class="card-title">Image Corpus (up to 6)</div>
|
|
2563
|
+
<div class="mm-gallery-grid" id="mmGalleryGrid"></div>
|
|
2564
|
+
<input type="file" id="mmGalleryFileInput" accept="image/png,image/jpeg,image/webp,image/gif" style="display:none">
|
|
2565
|
+
</div>
|
|
2566
|
+
|
|
2567
|
+
<div class="card" style="margin-top:12px;">
|
|
2568
|
+
<div class="card-title">Text Corpus (one per line)</div>
|
|
2569
|
+
<textarea id="mmCorpusText" rows="5" placeholder="A sunset over the ocean A cat sitting on a windowsill Abstract geometric patterns A city skyline at night"></textarea>
|
|
2570
|
+
</div>
|
|
2571
|
+
|
|
2572
|
+
<div class="card" style="margin-top:12px;">
|
|
2573
|
+
<div class="card-title">Search Query</div>
|
|
2574
|
+
<div class="mm-search-mode" id="mmSearchMode">
|
|
2575
|
+
<button class="active" data-mode="text" onclick="setMmSearchMode('text')">📝 Text Query</button>
|
|
2576
|
+
<button data-mode="image" onclick="setMmSearchMode('image')">🖼️ Image Query</button>
|
|
2577
|
+
</div>
|
|
2578
|
+
<div id="mmSearchTextWrap">
|
|
2579
|
+
<div class="mm-search-row">
|
|
2580
|
+
<input type="text" id="mmSearchQuery" placeholder="Enter a search query...">
|
|
2581
|
+
<button class="btn" id="mmSearchBtn" onclick="doMultimodalSearch()">🔮 Search Corpus</button>
|
|
2582
|
+
</div>
|
|
2583
|
+
</div>
|
|
2584
|
+
<div id="mmSearchImageWrap" style="display:none;">
|
|
2585
|
+
<p style="font-size:13px;color:var(--text-dim);margin-bottom:8px;">Click an image in the corpus above to use it as the search query, then:</p>
|
|
2586
|
+
<div style="display:flex;gap:8px;align-items:center;">
|
|
2587
|
+
<span id="mmSearchImageLabel" style="font-size:13px;color:var(--text-muted);">No image selected</span>
|
|
2588
|
+
<button class="btn" id="mmSearchImgBtn" onclick="doMultimodalSearch()">🔮 Search Corpus</button>
|
|
2589
|
+
</div>
|
|
2590
|
+
</div>
|
|
2591
|
+
</div>
|
|
2592
|
+
|
|
2593
|
+
<div class="error-msg" id="mmSearchError"></div>
|
|
2594
|
+
|
|
2595
|
+
<div class="result-section" id="mmSearchResult">
|
|
2596
|
+
<div class="card">
|
|
2597
|
+
<div class="card-title">Search Results</div>
|
|
2598
|
+
<div class="mm-result-list" id="mmSearchResultList"></div>
|
|
2599
|
+
</div>
|
|
2600
|
+
</div>
|
|
2601
|
+
</div>
|
|
2602
|
+
</div>
|
|
2603
|
+
|
|
1543
2604
|
<!-- ========== BENCHMARK TAB ========== -->
|
|
1544
2605
|
<div class="tab-panel" id="tab-benchmark">
|
|
2606
|
+
<div class="page-header">
|
|
2607
|
+
<h2 class="page-header-title">Benchmark</h2>
|
|
2608
|
+
<p class="page-header-subtitle">Compare model speed, cost, and quality</p>
|
|
2609
|
+
<p class="page-header-hint">Run latency tests, compare ranking accuracy, analyze quantization trade-offs, and estimate costs across models.</p>
|
|
2610
|
+
</div>
|
|
1545
2611
|
|
|
1546
2612
|
<!-- Sub-panel switcher -->
|
|
1547
2613
|
<div class="bench-panels">
|
|
@@ -1894,66 +2960,313 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1894
2960
|
</div>
|
|
1895
2961
|
</div>
|
|
1896
2962
|
|
|
1897
|
-
<div class="about-section">
|
|
1898
|
-
<div class="about-section-title">What You Can Do Here</div>
|
|
1899
|
-
<div class="about-text">
|
|
1900
|
-
<strong>⚡ Embed</strong> — Generate vector embeddings for any text<br>
|
|
1901
|
-
<strong>⚖️ Compare</strong> — Measure similarity with cosine, dot product & euclidean distance<br>
|
|
1902
|
-
<strong>🔍 Search</strong> — Semantic search with optional reranking<br>
|
|
1903
|
-
<strong>⏱ Benchmark</strong> — Compare model latency, ranking quality, and costs<br>
|
|
1904
|
-
<strong>📚 Explore</strong> — Learn about embeddings, vector search, RAG, and more
|
|
2963
|
+
<div class="about-section">
|
|
2964
|
+
<div class="about-section-title">What You Can Do Here</div>
|
|
2965
|
+
<div class="about-text">
|
|
2966
|
+
<strong>⚡ Embed</strong> — Generate vector embeddings for any text<br>
|
|
2967
|
+
<strong>⚖️ Compare</strong> — Measure similarity with cosine, dot product & euclidean distance<br>
|
|
2968
|
+
<strong>🔍 Search</strong> — Semantic search with optional reranking<br>
|
|
2969
|
+
<strong>⏱ Benchmark</strong> — Compare model latency, ranking quality, and costs<br>
|
|
2970
|
+
<strong>📚 Explore</strong> — Learn about embeddings, vector search, RAG, and more
|
|
2971
|
+
</div>
|
|
2972
|
+
</div>
|
|
2973
|
+
|
|
2974
|
+
<div class="about-disclaimer">
|
|
2975
|
+
<div class="about-disclaimer-title">⚠️ Community Tool Disclaimer</div>
|
|
2976
|
+
<div class="about-disclaimer-text">
|
|
2977
|
+
This tool is <strong>not</strong> an official product of MongoDB, Inc. or Voyage AI.
|
|
2978
|
+
It is independently built and maintained by Michael Lynn as a community resource.
|
|
2979
|
+
It is not supported, endorsed, or guaranteed by either company. Use at your own discretion.
|
|
2980
|
+
For official documentation, visit
|
|
2981
|
+
<a href="https://www.mongodb.com/docs/voyageai/" target="_blank" style="color:var(--warning);">mongodb.com/docs/voyageai</a>.
|
|
2982
|
+
</div>
|
|
2983
|
+
</div>
|
|
2984
|
+
</div>
|
|
2985
|
+
|
|
2986
|
+
<div style="text-align:center;margin-top:16px;font-size:12px;color:var(--text-muted);">
|
|
2987
|
+
Made with ☕ and curiosity · <a href="https://github.com/mrlynn/voyageai-cli" target="_blank" style="color:var(--text-dim);">Source on GitHub</a>
|
|
2988
|
+
</div>
|
|
2989
|
+
</div>
|
|
2990
|
+
</div>
|
|
2991
|
+
|
|
2992
|
+
<!-- ========== EXPLORE TAB ========== -->
|
|
2993
|
+
<div class="tab-panel" id="tab-explore">
|
|
2994
|
+
<div class="page-header">
|
|
2995
|
+
<h2 class="page-header-title">Explore</h2>
|
|
2996
|
+
<p class="page-header-subtitle">Learn embedding and vector search concepts</p>
|
|
2997
|
+
<p class="page-header-hint">Browse interactive explanations of key topics — from cosine similarity to quantization to RAG pipelines.</p>
|
|
2998
|
+
</div>
|
|
2999
|
+
<div style="margin-bottom:16px;">
|
|
3000
|
+
<input type="text" id="exploreSearch" placeholder="🔍 Search concepts..." oninput="filterExplore()" style="max-width:400px;">
|
|
3001
|
+
</div>
|
|
3002
|
+
<div class="explore-grid" id="exploreGrid"></div>
|
|
3003
|
+
</div>
|
|
3004
|
+
|
|
3005
|
+
<!-- Explore Concept Modal -->
|
|
3006
|
+
<div class="explore-modal-overlay" id="exploreModal">
|
|
3007
|
+
<div class="explore-modal">
|
|
3008
|
+
<button class="explore-modal-close" id="exploreModalClose">×</button>
|
|
3009
|
+
<div class="explore-modal-header">
|
|
3010
|
+
<div class="explore-modal-icon" id="exploreModalIcon"></div>
|
|
3011
|
+
<div>
|
|
3012
|
+
<div class="explore-modal-title" id="exploreModalTitle"></div>
|
|
3013
|
+
<div class="explore-modal-summary" id="exploreModalSummary"></div>
|
|
3014
|
+
</div>
|
|
3015
|
+
</div>
|
|
3016
|
+
<div class="explore-modal-body" id="exploreModalBody"></div>
|
|
3017
|
+
<div class="explore-modal-actions" id="exploreModalActions"></div>
|
|
3018
|
+
</div>
|
|
3019
|
+
</div>
|
|
3020
|
+
|
|
3021
|
+
<!-- Hidden global model select (used by JS for model sync) -->
|
|
3022
|
+
<select id="globalModel" style="display:none;"></select>
|
|
3023
|
+
|
|
3024
|
+
<!-- ========== SETTINGS TAB ========== -->
|
|
3025
|
+
<div class="tab-panel" id="tab-settings">
|
|
3026
|
+
<div class="settings-container">
|
|
3027
|
+
<div class="page-header">
|
|
3028
|
+
<h2 class="page-header-title">Settings</h2>
|
|
3029
|
+
<p class="page-header-subtitle">Configure appearance, models, and API access</p>
|
|
3030
|
+
</div>
|
|
3031
|
+
|
|
3032
|
+
<div class="settings-section">
|
|
3033
|
+
<div class="settings-section-title">Appearance</div>
|
|
3034
|
+
<div class="settings-row">
|
|
3035
|
+
<div class="settings-label">
|
|
3036
|
+
<span class="settings-label-text">Theme</span>
|
|
3037
|
+
<span class="settings-label-hint">Controls the color scheme of the interface</span>
|
|
3038
|
+
</div>
|
|
3039
|
+
<div class="settings-control">
|
|
3040
|
+
<select class="settings-select" id="settingsTheme">
|
|
3041
|
+
<option value="dark">Dark</option>
|
|
3042
|
+
<option value="light">Light</option>
|
|
3043
|
+
</select>
|
|
3044
|
+
</div>
|
|
3045
|
+
</div>
|
|
3046
|
+
<div class="settings-row">
|
|
3047
|
+
<div class="settings-label">
|
|
3048
|
+
<span class="settings-label-text">Vector Heatmap Colors</span>
|
|
3049
|
+
<span class="settings-label-hint">Color palette for embedding visualizations</span>
|
|
3050
|
+
</div>
|
|
3051
|
+
<div class="settings-control" style="display:flex;align-items:center;gap:8px;">
|
|
3052
|
+
<select class="settings-select" id="settingsHeatmap">
|
|
3053
|
+
<option value="viridis">Viridis</option>
|
|
3054
|
+
<option value="plasma">Plasma</option>
|
|
3055
|
+
<option value="inferno">Inferno</option>
|
|
3056
|
+
<option value="magma">Magma</option>
|
|
3057
|
+
<option value="cividis">Cividis</option>
|
|
3058
|
+
<option value="turbo">Turbo</option>
|
|
3059
|
+
</select>
|
|
3060
|
+
<span class="heatmap-preview" id="heatmapPreview"></span>
|
|
3061
|
+
</div>
|
|
3062
|
+
</div>
|
|
3063
|
+
</div>
|
|
3064
|
+
|
|
3065
|
+
<div class="settings-section">
|
|
3066
|
+
<div class="settings-section-title">Models</div>
|
|
3067
|
+
<div class="settings-row">
|
|
3068
|
+
<div class="settings-label">
|
|
3069
|
+
<span class="settings-label-text">Default Embedding Model</span>
|
|
3070
|
+
<span class="settings-label-hint">Pre-selected model for Embed and Compare tabs</span>
|
|
3071
|
+
</div>
|
|
3072
|
+
<div class="settings-control">
|
|
3073
|
+
<select class="settings-select" id="settingsDefaultModel"></select>
|
|
3074
|
+
</div>
|
|
3075
|
+
</div>
|
|
3076
|
+
<div class="settings-row">
|
|
3077
|
+
<div class="settings-label">
|
|
3078
|
+
<span class="settings-label-text">Default Input Type</span>
|
|
3079
|
+
<span class="settings-label-hint">Pre-selected input type for embedding requests</span>
|
|
3080
|
+
</div>
|
|
3081
|
+
<div class="settings-control">
|
|
3082
|
+
<select class="settings-select" id="settingsInputType">
|
|
3083
|
+
<option value="document">document</option>
|
|
3084
|
+
<option value="query">query</option>
|
|
3085
|
+
</select>
|
|
3086
|
+
</div>
|
|
3087
|
+
</div>
|
|
3088
|
+
</div>
|
|
3089
|
+
|
|
3090
|
+
<div class="settings-section">
|
|
3091
|
+
<div class="settings-section-title">API</div>
|
|
3092
|
+
<div class="settings-api-banner" id="settingsApiBanner">
|
|
3093
|
+
<span class="settings-api-banner-icon" id="settingsApiBannerIcon">⚠️</span>
|
|
3094
|
+
<span id="settingsApiKeyMsg">No API key configured — add your key below to get started.</span>
|
|
3095
|
+
</div>
|
|
3096
|
+
<div class="settings-row">
|
|
3097
|
+
<div class="settings-label">
|
|
3098
|
+
<span class="settings-label-text">API Key</span>
|
|
3099
|
+
<span class="settings-label-hint">Encrypted via OS keychain · <a href="https://dash.voyageai.com" target="_blank" class="settings-key-link">🔑 Get a key</a></span>
|
|
3100
|
+
</div>
|
|
3101
|
+
<div class="settings-control" style="min-width:260px;">
|
|
3102
|
+
<div class="settings-api-field">
|
|
3103
|
+
<input type="password" id="settingsApiKey" placeholder="pa-..." autocomplete="off" spellcheck="false">
|
|
3104
|
+
<button type="button" id="settingsApiKeyToggle" title="Show/hide key">👁</button>
|
|
3105
|
+
<button type="button" id="settingsApiKeySave" class="save-btn" title="Save key">Save</button>
|
|
3106
|
+
</div>
|
|
3107
|
+
</div>
|
|
3108
|
+
</div>
|
|
3109
|
+
<div class="settings-row">
|
|
3110
|
+
<div class="settings-label">
|
|
3111
|
+
<span class="settings-label-text">API Base URL</span>
|
|
3112
|
+
<span class="settings-label-hint">Override the default endpoint (leave empty for default)</span>
|
|
3113
|
+
</div>
|
|
3114
|
+
<div class="settings-control">
|
|
3115
|
+
<input type="text" class="settings-input" id="settingsApiBase" placeholder="https://api.voyageai.com/v1">
|
|
3116
|
+
</div>
|
|
3117
|
+
</div>
|
|
3118
|
+
<div class="settings-row">
|
|
3119
|
+
<div class="settings-label">
|
|
3120
|
+
<span class="settings-label-text">Request Timeout</span>
|
|
3121
|
+
<span class="settings-label-hint">Max seconds to wait for API responses</span>
|
|
3122
|
+
</div>
|
|
3123
|
+
<div class="settings-control">
|
|
3124
|
+
<select class="settings-select" id="settingsTimeout">
|
|
3125
|
+
<option value="15">15 seconds</option>
|
|
3126
|
+
<option value="30" selected>30 seconds</option>
|
|
3127
|
+
<option value="60">60 seconds</option>
|
|
3128
|
+
<option value="120">120 seconds</option>
|
|
3129
|
+
</select>
|
|
3130
|
+
</div>
|
|
3131
|
+
</div>
|
|
3132
|
+
</div>
|
|
3133
|
+
|
|
3134
|
+
<div class="settings-section">
|
|
3135
|
+
<div class="settings-section-title">Benchmark Defaults</div>
|
|
3136
|
+
<div class="settings-row">
|
|
3137
|
+
<div class="settings-label">
|
|
3138
|
+
<span class="settings-label-text">Iterations per Model</span>
|
|
3139
|
+
<span class="settings-label-hint">Number of runs when benchmarking latency</span>
|
|
3140
|
+
</div>
|
|
3141
|
+
<div class="settings-control">
|
|
3142
|
+
<select class="settings-select" id="settingsBenchIter">
|
|
3143
|
+
<option value="3">3 runs</option>
|
|
3144
|
+
<option value="5" selected>5 runs</option>
|
|
3145
|
+
<option value="10">10 runs</option>
|
|
3146
|
+
<option value="20">20 runs</option>
|
|
3147
|
+
</select>
|
|
3148
|
+
</div>
|
|
3149
|
+
</div>
|
|
3150
|
+
<div class="settings-row">
|
|
3151
|
+
<div class="settings-label">
|
|
3152
|
+
<span class="settings-label-text">Show Detailed Timings</span>
|
|
3153
|
+
<span class="settings-label-hint">Display p50/p95/p99 in benchmark results</span>
|
|
3154
|
+
</div>
|
|
3155
|
+
<div class="settings-control">
|
|
3156
|
+
<button class="settings-toggle" id="settingsDetailedTimings" type="button"></button>
|
|
3157
|
+
</div>
|
|
3158
|
+
</div>
|
|
3159
|
+
</div>
|
|
3160
|
+
|
|
3161
|
+
<div class="settings-section">
|
|
3162
|
+
<div class="settings-section-title">Help</div>
|
|
3163
|
+
<div class="settings-row">
|
|
3164
|
+
<div class="settings-label">
|
|
3165
|
+
<span class="settings-label-text">Welcome Tour</span>
|
|
3166
|
+
<span class="settings-label-hint">Replay the onboarding walkthrough</span>
|
|
3167
|
+
</div>
|
|
3168
|
+
<div class="settings-control">
|
|
3169
|
+
<button class="settings-reset-btn" id="settingsShowTour" style="background:var(--accent);color:var(--bg);">Show Welcome Tour</button>
|
|
3170
|
+
</div>
|
|
3171
|
+
</div>
|
|
3172
|
+
</div>
|
|
3173
|
+
|
|
3174
|
+
<div class="settings-section">
|
|
3175
|
+
<div class="settings-section-title">Data & Privacy</div>
|
|
3176
|
+
<div class="settings-row">
|
|
3177
|
+
<div class="settings-label">
|
|
3178
|
+
<span class="settings-label-text">Persist Embeddings Locally</span>
|
|
3179
|
+
<span class="settings-label-hint">Cache embedding results in browser storage to avoid re-fetching</span>
|
|
3180
|
+
</div>
|
|
3181
|
+
<div class="settings-control">
|
|
3182
|
+
<button class="settings-toggle active" id="settingsCacheEmbeddings" type="button"></button>
|
|
3183
|
+
</div>
|
|
3184
|
+
</div>
|
|
3185
|
+
<div class="settings-row">
|
|
3186
|
+
<div class="settings-label">
|
|
3187
|
+
<span class="settings-label-text">Clear Cached Data</span>
|
|
3188
|
+
<span class="settings-label-hint">Remove all locally stored embeddings and preferences</span>
|
|
3189
|
+
</div>
|
|
3190
|
+
<div class="settings-control">
|
|
3191
|
+
<button class="settings-reset-btn" id="settingsClearCache">Clear All Data</button>
|
|
3192
|
+
</div>
|
|
3193
|
+
</div>
|
|
3194
|
+
</div>
|
|
3195
|
+
|
|
3196
|
+
<!-- Version Info (visible in Electron only) -->
|
|
3197
|
+
<div class="settings-section" id="settingsVersionSection" style="display:none;">
|
|
3198
|
+
<div class="settings-section-title">Version</div>
|
|
3199
|
+
<div class="settings-row">
|
|
3200
|
+
<div class="settings-label">
|
|
3201
|
+
<span class="settings-label-text">Vai Desktop</span>
|
|
3202
|
+
<span class="settings-label-hint">Electron desktop application</span>
|
|
3203
|
+
</div>
|
|
3204
|
+
<div class="settings-control">
|
|
3205
|
+
<span class="version-badge" id="settingsAppVersion">—</span>
|
|
1905
3206
|
</div>
|
|
1906
3207
|
</div>
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
For official documentation, visit
|
|
1915
|
-
<a href="https://www.mongodb.com/docs/voyageai/" target="_blank" style="color:var(--warning);">mongodb.com/docs/voyageai</a>.
|
|
3208
|
+
<div class="settings-row">
|
|
3209
|
+
<div class="settings-label">
|
|
3210
|
+
<span class="settings-label-text">Vai CLI Engine</span>
|
|
3211
|
+
<span class="settings-label-hint">Underlying CLI powering embeddings, search & reranking</span>
|
|
3212
|
+
</div>
|
|
3213
|
+
<div class="settings-control">
|
|
3214
|
+
<span class="version-badge" id="settingsCliVersion">—</span>
|
|
1916
3215
|
</div>
|
|
1917
3216
|
</div>
|
|
1918
3217
|
</div>
|
|
1919
3218
|
|
|
1920
|
-
<div style="text-align:center;
|
|
1921
|
-
|
|
3219
|
+
<div style="text-align:center;padding:8px 0;">
|
|
3220
|
+
<span class="settings-saved" id="settingsSavedMsg">✓ Saved</span>
|
|
1922
3221
|
</div>
|
|
1923
|
-
</div>
|
|
1924
|
-
</div>
|
|
1925
3222
|
|
|
1926
|
-
<!-- ========== EXPLORE TAB ========== -->
|
|
1927
|
-
<div class="tab-panel" id="tab-explore">
|
|
1928
|
-
<div style="margin-bottom:16px;">
|
|
1929
|
-
<input type="text" id="exploreSearch" placeholder="🔍 Search concepts..." oninput="filterExplore()" style="max-width:400px;">
|
|
1930
3223
|
</div>
|
|
1931
|
-
<div class="explore-grid" id="exploreGrid"></div>
|
|
1932
3224
|
</div>
|
|
1933
3225
|
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
3226
|
+
</div><!-- .main -->
|
|
3227
|
+
</div><!-- .content-area -->
|
|
3228
|
+
</div><!-- .app-shell -->
|
|
3229
|
+
|
|
3230
|
+
<!-- Onboarding Walkthrough Overlay -->
|
|
3231
|
+
<div class="onboarding-overlay" id="onboardingOverlay">
|
|
3232
|
+
<div class="onboarding-backdrop" id="onboardingBackdrop"></div>
|
|
3233
|
+
<div class="onboarding-spotlight" id="onboardingSpotlight"></div>
|
|
3234
|
+
<!-- Welcome card (step 0) -->
|
|
3235
|
+
<div class="onboarding-welcome-center" id="onboardingWelcomeWrap">
|
|
3236
|
+
<div class="onboarding-welcome-card" id="onboardingWelcomeCard">
|
|
3237
|
+
<img class="onboarding-welcome-logo" id="onboardingLogo" src="/icons/dark/64.png" alt="Vai">
|
|
3238
|
+
<div class="onboarding-welcome-title">Welcome to Vai</div>
|
|
3239
|
+
<div class="onboarding-welcome-sub">Explore embeddings, compare text similarity, and search with vector models — all from one playground.</div>
|
|
3240
|
+
<div class="onboarding-footer" style="justify-content:center;">
|
|
3241
|
+
<button class="onboarding-skip" id="onboardingWelcomeSkip">Skip tour</button>
|
|
3242
|
+
<button class="onboarding-next" id="onboardingWelcomeNext">Take the tour →</button>
|
|
3243
|
+
</div>
|
|
3244
|
+
</div>
|
|
3245
|
+
</div>
|
|
3246
|
+
<!-- Tooltip for steps 1-4 -->
|
|
3247
|
+
<div class="onboarding-tooltip" id="onboardingTooltip">
|
|
3248
|
+
<div class="onboarding-tooltip-arrow top" id="onboardingArrow"></div>
|
|
3249
|
+
<div class="onboarding-step-icon" id="onboardingIcon"></div>
|
|
3250
|
+
<div class="onboarding-step-title" id="onboardingTitle"></div>
|
|
3251
|
+
<div class="onboarding-step-body" id="onboardingBody"></div>
|
|
3252
|
+
<div class="onboarding-footer">
|
|
3253
|
+
<div class="onboarding-dots" id="onboardingDots"></div>
|
|
3254
|
+
<div class="onboarding-actions">
|
|
3255
|
+
<button class="onboarding-skip" id="onboardingSkip">Skip</button>
|
|
3256
|
+
<button class="onboarding-next" id="onboardingNext">Next</button>
|
|
1943
3257
|
</div>
|
|
1944
3258
|
</div>
|
|
1945
|
-
<div class="explore-modal-body" id="exploreModalBody"></div>
|
|
1946
|
-
<div class="explore-modal-actions" id="exploreModalActions"></div>
|
|
1947
3259
|
</div>
|
|
1948
3260
|
</div>
|
|
1949
3261
|
|
|
1950
|
-
</div><!-- .main -->
|
|
1951
|
-
|
|
1952
3262
|
<script>
|
|
1953
3263
|
// Apply saved theme immediately to prevent flash
|
|
1954
3264
|
(function() {
|
|
1955
3265
|
var t = localStorage.getItem('vai-theme') || 'dark';
|
|
1956
3266
|
if (t === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
|
3267
|
+
// Set correct logo immediately
|
|
3268
|
+
var logo = document.getElementById('sidebarLogo');
|
|
3269
|
+
if (logo) logo.src = '/icons/' + (t === 'light' ? 'light' : 'dark') + '/64.png';
|
|
1957
3270
|
})();
|
|
1958
3271
|
</script>
|
|
1959
3272
|
<script>
|
|
@@ -1968,21 +3281,28 @@ function initThemeToggle() {
|
|
|
1968
3281
|
|
|
1969
3282
|
function applyTheme(theme) {
|
|
1970
3283
|
current = theme;
|
|
3284
|
+
const logo = document.getElementById('sidebarLogo');
|
|
1971
3285
|
if (theme === 'light') {
|
|
1972
3286
|
document.documentElement.setAttribute('data-theme', 'light');
|
|
1973
3287
|
toggle.textContent = '☀️';
|
|
1974
3288
|
toggle.title = 'Switch to dark mode';
|
|
3289
|
+
if (logo) logo.src = '/icons/light/64.png';
|
|
1975
3290
|
} else {
|
|
1976
3291
|
document.documentElement.removeAttribute('data-theme');
|
|
1977
3292
|
toggle.textContent = '🌙';
|
|
1978
3293
|
toggle.title = 'Switch to light mode';
|
|
3294
|
+
if (logo) logo.src = '/icons/dark/64.png';
|
|
1979
3295
|
}
|
|
1980
3296
|
localStorage.setItem('vai-theme', theme);
|
|
1981
3297
|
}
|
|
1982
3298
|
|
|
1983
3299
|
applyTheme(current);
|
|
1984
3300
|
toggle.addEventListener('click', () => {
|
|
1985
|
-
|
|
3301
|
+
const next = current === 'dark' ? 'light' : 'dark';
|
|
3302
|
+
applyTheme(next);
|
|
3303
|
+
// Sync settings panel dropdown
|
|
3304
|
+
const themeSel = document.getElementById('settingsTheme');
|
|
3305
|
+
if (themeSel) themeSel.value = next;
|
|
1986
3306
|
});
|
|
1987
3307
|
}
|
|
1988
3308
|
|
|
@@ -2021,6 +3341,8 @@ function switchTab(tab) {
|
|
|
2021
3341
|
p.classList.toggle('active', p.id === 'tab-' + tab);
|
|
2022
3342
|
});
|
|
2023
3343
|
}
|
|
3344
|
+
// Expose globally so Electron main process can call it
|
|
3345
|
+
window.switchTab = switchTab;
|
|
2024
3346
|
|
|
2025
3347
|
// ── Config ──
|
|
2026
3348
|
async function loadConfig() {
|
|
@@ -2459,6 +3781,15 @@ const CONCEPT_META = {
|
|
|
2459
3781
|
'batch-processing': { icon: '📦', tab: 'embed' },
|
|
2460
3782
|
benchmarking: { icon: '⏱', tab: 'benchmark' },
|
|
2461
3783
|
quantization: { icon: '⚗️', tab: 'benchmark' },
|
|
3784
|
+
'mixture-of-experts': { icon: '🧩', tab: 'embed' },
|
|
3785
|
+
'shared-embedding-space': { icon: '🔗', tab: 'compare' },
|
|
3786
|
+
'rteb-benchmarks': { icon: '📊', tab: 'benchmark' },
|
|
3787
|
+
'voyage-4-nano': { icon: '🔬', tab: 'embed' },
|
|
3788
|
+
'rerank-eval': { icon: '📐', tab: 'benchmark' },
|
|
3789
|
+
'multimodal-embeddings': { icon: '🖼️', tab: 'multimodal' },
|
|
3790
|
+
'cross-modal-search': { icon: '🔀', tab: 'multimodal' },
|
|
3791
|
+
'modality-gap': { icon: '🕳️', tab: 'multimodal' },
|
|
3792
|
+
'multimodal-rag': { icon: '📄', tab: 'multimodal' },
|
|
2462
3793
|
};
|
|
2463
3794
|
|
|
2464
3795
|
let exploreConcepts = {};
|
|
@@ -3501,6 +4832,890 @@ window.clearHistory = function() {
|
|
|
3501
4832
|
renderHistory();
|
|
3502
4833
|
};
|
|
3503
4834
|
|
|
4835
|
+
// ── Settings ──
|
|
4836
|
+
const SETTINGS_KEY = 'vai-settings';
|
|
4837
|
+
|
|
4838
|
+
function getSettings() {
|
|
4839
|
+
try {
|
|
4840
|
+
return JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}');
|
|
4841
|
+
} catch { return {}; }
|
|
4842
|
+
}
|
|
4843
|
+
|
|
4844
|
+
function saveSetting(key, value) {
|
|
4845
|
+
const s = getSettings();
|
|
4846
|
+
s[key] = value;
|
|
4847
|
+
localStorage.setItem(SETTINGS_KEY, JSON.stringify(s));
|
|
4848
|
+
flashSaved();
|
|
4849
|
+
}
|
|
4850
|
+
|
|
4851
|
+
function flashSaved() {
|
|
4852
|
+
const el = document.getElementById('settingsSavedMsg');
|
|
4853
|
+
if (!el) return;
|
|
4854
|
+
el.classList.add('show');
|
|
4855
|
+
clearTimeout(el._timer);
|
|
4856
|
+
el._timer = setTimeout(() => el.classList.remove('show'), 1500);
|
|
4857
|
+
}
|
|
4858
|
+
|
|
4859
|
+
function initSettings() {
|
|
4860
|
+
const s = getSettings();
|
|
4861
|
+
|
|
4862
|
+
// Theme sync
|
|
4863
|
+
const themeSelect = document.getElementById('settingsTheme');
|
|
4864
|
+
const currentTheme = localStorage.getItem('vai-theme') || 'dark';
|
|
4865
|
+
if (themeSelect) {
|
|
4866
|
+
themeSelect.value = currentTheme;
|
|
4867
|
+
themeSelect.addEventListener('change', () => {
|
|
4868
|
+
const t = themeSelect.value;
|
|
4869
|
+
saveSetting('theme', t);
|
|
4870
|
+
localStorage.setItem('vai-theme', t);
|
|
4871
|
+
// Reuse existing applyTheme by clicking the toggle if needed
|
|
4872
|
+
const toggle = document.getElementById('themeToggle');
|
|
4873
|
+
const cur = localStorage.getItem('vai-theme');
|
|
4874
|
+
if (cur === 'light') {
|
|
4875
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
4876
|
+
if (toggle) { toggle.textContent = '☀️'; toggle.title = 'Switch to dark mode'; }
|
|
4877
|
+
} else {
|
|
4878
|
+
document.documentElement.removeAttribute('data-theme');
|
|
4879
|
+
if (toggle) { toggle.textContent = '🌙'; toggle.title = 'Switch to light mode'; }
|
|
4880
|
+
}
|
|
4881
|
+
const logo = document.getElementById('sidebarLogo');
|
|
4882
|
+
if (logo) logo.src = '/icons/' + (t === 'light' ? 'light' : 'dark') + '/64.png';
|
|
4883
|
+
});
|
|
4884
|
+
}
|
|
4885
|
+
|
|
4886
|
+
// Heatmap palette — handled below with preview swatch
|
|
4887
|
+
|
|
4888
|
+
// Default model — populate from global model select
|
|
4889
|
+
const settingsModel = document.getElementById('settingsDefaultModel');
|
|
4890
|
+
const globalModel = document.getElementById('globalModel');
|
|
4891
|
+
if (settingsModel && globalModel) {
|
|
4892
|
+
// Clone options from globalModel
|
|
4893
|
+
settingsModel.innerHTML = globalModel.innerHTML;
|
|
4894
|
+
const savedModel = s.defaultModel || globalModel.value;
|
|
4895
|
+
settingsModel.value = savedModel;
|
|
4896
|
+
settingsModel.addEventListener('change', () => {
|
|
4897
|
+
saveSetting('defaultModel', settingsModel.value);
|
|
4898
|
+
globalModel.value = settingsModel.value;
|
|
4899
|
+
// Update individual tab selects too
|
|
4900
|
+
['embedModel', 'compareModel', 'searchEmbedModel'].forEach(id => {
|
|
4901
|
+
const sel = document.getElementById(id);
|
|
4902
|
+
if (sel) sel.value = settingsModel.value;
|
|
4903
|
+
});
|
|
4904
|
+
});
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
// Default input type
|
|
4908
|
+
const inputTypeSel = document.getElementById('settingsInputType');
|
|
4909
|
+
if (inputTypeSel) {
|
|
4910
|
+
inputTypeSel.value = s.inputType || 'document';
|
|
4911
|
+
inputTypeSel.addEventListener('change', () => {
|
|
4912
|
+
saveSetting('inputType', inputTypeSel.value);
|
|
4913
|
+
const embedType = document.getElementById('embedInputType');
|
|
4914
|
+
if (embedType) embedType.value = inputTypeSel.value;
|
|
4915
|
+
});
|
|
4916
|
+
}
|
|
4917
|
+
|
|
4918
|
+
// API base URL
|
|
4919
|
+
const apiBaseInput = document.getElementById('settingsApiBase');
|
|
4920
|
+
if (apiBaseInput) {
|
|
4921
|
+
apiBaseInput.value = s.apiBase || '';
|
|
4922
|
+
apiBaseInput.addEventListener('change', () => saveSetting('apiBase', apiBaseInput.value));
|
|
4923
|
+
}
|
|
4924
|
+
|
|
4925
|
+
// Timeout
|
|
4926
|
+
const timeoutSel = document.getElementById('settingsTimeout');
|
|
4927
|
+
if (timeoutSel) {
|
|
4928
|
+
timeoutSel.value = s.timeout || '30';
|
|
4929
|
+
timeoutSel.addEventListener('change', () => saveSetting('timeout', timeoutSel.value));
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4932
|
+
// Benchmark iterations
|
|
4933
|
+
const benchIterSel = document.getElementById('settingsBenchIter');
|
|
4934
|
+
if (benchIterSel) {
|
|
4935
|
+
benchIterSel.value = s.benchIter || '5';
|
|
4936
|
+
benchIterSel.addEventListener('change', () => saveSetting('benchIter', benchIterSel.value));
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4939
|
+
// Toggle buttons
|
|
4940
|
+
function initToggle(id, settingKey, defaultVal) {
|
|
4941
|
+
const btn = document.getElementById(id);
|
|
4942
|
+
if (!btn) return;
|
|
4943
|
+
const val = s[settingKey] !== undefined ? s[settingKey] : defaultVal;
|
|
4944
|
+
btn.classList.toggle('active', val);
|
|
4945
|
+
btn.addEventListener('click', () => {
|
|
4946
|
+
const isActive = btn.classList.toggle('active');
|
|
4947
|
+
saveSetting(settingKey, isActive);
|
|
4948
|
+
});
|
|
4949
|
+
}
|
|
4950
|
+
initToggle('settingsDetailedTimings', 'detailedTimings', false);
|
|
4951
|
+
initToggle('settingsCacheEmbeddings', 'cacheEmbeddings', true);
|
|
4952
|
+
|
|
4953
|
+
// API Key (Electron only — uses safeStorage via preload bridge)
|
|
4954
|
+
const apiKeyInput = document.getElementById('settingsApiKey');
|
|
4955
|
+
const apiKeyToggle = document.getElementById('settingsApiKeyToggle');
|
|
4956
|
+
const apiKeySave = document.getElementById('settingsApiKeySave');
|
|
4957
|
+
const apiKeyMsg = document.getElementById('settingsApiKeyMsg');
|
|
4958
|
+
const apiBanner = document.getElementById('settingsApiBanner');
|
|
4959
|
+
const apiBannerIcon = document.getElementById('settingsApiBannerIcon');
|
|
4960
|
+
|
|
4961
|
+
function updateApiBanner(hasKey, key) {
|
|
4962
|
+
if (hasKey) {
|
|
4963
|
+
apiBanner.className = 'settings-api-banner success';
|
|
4964
|
+
apiBannerIcon.textContent = '🔐';
|
|
4965
|
+
apiKeyMsg.textContent = 'API key stored securely in OS keychain';
|
|
4966
|
+
apiKeyInput.placeholder = window.vai ? window.vai.apiKey.mask(key) : '••••••••';
|
|
4967
|
+
} else {
|
|
4968
|
+
apiBanner.className = 'settings-api-banner';
|
|
4969
|
+
apiBannerIcon.textContent = '⚠️';
|
|
4970
|
+
apiKeyMsg.textContent = 'No API key configured — add your key below to get started.';
|
|
4971
|
+
apiKeyInput.placeholder = 'pa-...';
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
|
|
4975
|
+
if (window.vai && window.vai.isElectron) {
|
|
4976
|
+
// Load existing key status
|
|
4977
|
+
window.vai.apiKey.get().then(key => updateApiBanner(!!key, key));
|
|
4978
|
+
|
|
4979
|
+
// Show/hide toggle
|
|
4980
|
+
let keyVisible = false;
|
|
4981
|
+
apiKeyToggle.addEventListener('click', async () => {
|
|
4982
|
+
if (!keyVisible && !apiKeyInput.value) {
|
|
4983
|
+
const key = await window.vai.apiKey.get();
|
|
4984
|
+
if (key) {
|
|
4985
|
+
apiKeyInput.value = key;
|
|
4986
|
+
apiKeyInput.type = 'text';
|
|
4987
|
+
apiKeyToggle.textContent = '🙈';
|
|
4988
|
+
keyVisible = true;
|
|
4989
|
+
}
|
|
4990
|
+
} else if (keyVisible) {
|
|
4991
|
+
apiKeyInput.type = 'password';
|
|
4992
|
+
apiKeyToggle.textContent = '👁';
|
|
4993
|
+
keyVisible = false;
|
|
4994
|
+
if (!apiKeyInput.value) {
|
|
4995
|
+
const key = await window.vai.apiKey.get();
|
|
4996
|
+
if (key) apiKeyInput.placeholder = window.vai.apiKey.mask(key);
|
|
4997
|
+
}
|
|
4998
|
+
} else {
|
|
4999
|
+
apiKeyInput.type = apiKeyInput.type === 'password' ? 'text' : 'password';
|
|
5000
|
+
apiKeyToggle.textContent = apiKeyInput.type === 'password' ? '👁' : '🙈';
|
|
5001
|
+
keyVisible = apiKeyInput.type === 'text';
|
|
5002
|
+
}
|
|
5003
|
+
});
|
|
5004
|
+
|
|
5005
|
+
// Save button
|
|
5006
|
+
apiKeySave.addEventListener('click', async () => {
|
|
5007
|
+
const key = apiKeyInput.value.trim();
|
|
5008
|
+
if (!key) {
|
|
5009
|
+
await window.vai.apiKey.delete();
|
|
5010
|
+
updateApiBanner(false);
|
|
5011
|
+
flashSaved();
|
|
5012
|
+
return;
|
|
5013
|
+
}
|
|
5014
|
+
try {
|
|
5015
|
+
await window.vai.apiKey.set(key);
|
|
5016
|
+
apiKeyInput.value = '';
|
|
5017
|
+
apiKeyInput.type = 'password';
|
|
5018
|
+
apiKeyToggle.textContent = '👁';
|
|
5019
|
+
keyVisible = false;
|
|
5020
|
+
updateApiBanner(true, key);
|
|
5021
|
+
flashSaved();
|
|
5022
|
+
loadConfig();
|
|
5023
|
+
} catch (err) {
|
|
5024
|
+
apiBanner.className = 'settings-api-banner';
|
|
5025
|
+
apiBannerIcon.textContent = '❌';
|
|
5026
|
+
apiKeyMsg.textContent = 'Failed to save: ' + err.message;
|
|
5027
|
+
}
|
|
5028
|
+
});
|
|
5029
|
+
|
|
5030
|
+
apiKeyInput.addEventListener('keydown', (e) => {
|
|
5031
|
+
if (e.key === 'Enter') apiKeySave.click();
|
|
5032
|
+
});
|
|
5033
|
+
} else {
|
|
5034
|
+
apiBanner.className = 'settings-api-banner';
|
|
5035
|
+
apiBannerIcon.textContent = '💡';
|
|
5036
|
+
apiKeyMsg.textContent = 'Set VOYAGE_API_KEY environment variable, or use "vai config set key YOUR_KEY"';
|
|
5037
|
+
apiKeySave.style.display = 'none';
|
|
5038
|
+
apiKeyToggle.style.display = 'none';
|
|
5039
|
+
apiKeyInput.disabled = true;
|
|
5040
|
+
apiKeyInput.placeholder = 'Secure storage available in desktop app';
|
|
5041
|
+
}
|
|
5042
|
+
|
|
5043
|
+
// Heatmap palette preview
|
|
5044
|
+
const HEATMAP_GRADIENTS = {
|
|
5045
|
+
viridis: 'linear-gradient(90deg, #440154, #31688e, #35b779, #fde725)',
|
|
5046
|
+
plasma: 'linear-gradient(90deg, #0d0887, #7e03a8, #cc4778, #f89540, #f0f921)',
|
|
5047
|
+
inferno: 'linear-gradient(90deg, #000004, #420a68, #932667, #dd513a, #fca50a, #fcffa4)',
|
|
5048
|
+
magma: 'linear-gradient(90deg, #000004, #3b0f70, #8c2981, #de4968, #fea16e, #fcfdbf)',
|
|
5049
|
+
cividis: 'linear-gradient(90deg, #00224e, #123570, #3b496c, #575d6d, #707173, #a5a88f, #fee838)',
|
|
5050
|
+
turbo: 'linear-gradient(90deg, #30123b, #4662d7, #36aaf9, #1ae4b6, #72fe5e, #c8ef34, #faba39, #f66b19, #d93806, #7a0403)',
|
|
5051
|
+
};
|
|
5052
|
+
const heatmapSel = document.getElementById('settingsHeatmap');
|
|
5053
|
+
const heatmapPreview = document.getElementById('heatmapPreview');
|
|
5054
|
+
function updateHeatmapPreview() {
|
|
5055
|
+
if (heatmapPreview && heatmapSel) {
|
|
5056
|
+
heatmapPreview.style.background = HEATMAP_GRADIENTS[heatmapSel.value] || HEATMAP_GRADIENTS.viridis;
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
if (heatmapSel) {
|
|
5060
|
+
heatmapSel.value = s.heatmap || 'viridis';
|
|
5061
|
+
heatmapSel.addEventListener('change', () => {
|
|
5062
|
+
saveSetting('heatmap', heatmapSel.value);
|
|
5063
|
+
updateHeatmapPreview();
|
|
5064
|
+
});
|
|
5065
|
+
updateHeatmapPreview();
|
|
5066
|
+
}
|
|
5067
|
+
|
|
5068
|
+
// Clear cache
|
|
5069
|
+
const clearBtn = document.getElementById('settingsClearCache');
|
|
5070
|
+
if (clearBtn) {
|
|
5071
|
+
clearBtn.addEventListener('click', () => {
|
|
5072
|
+
if (confirm('Clear all cached data and reset settings to defaults?')) {
|
|
5073
|
+
localStorage.removeItem(SETTINGS_KEY);
|
|
5074
|
+
localStorage.removeItem('vai-theme');
|
|
5075
|
+
localStorage.removeItem('vai-embed-cache');
|
|
5076
|
+
location.reload();
|
|
5077
|
+
}
|
|
5078
|
+
});
|
|
5079
|
+
}
|
|
5080
|
+
|
|
5081
|
+
// Apply saved defaults on load
|
|
5082
|
+
if (s.defaultModel && globalModel) {
|
|
5083
|
+
globalModel.value = s.defaultModel;
|
|
5084
|
+
}
|
|
5085
|
+
if (s.inputType) {
|
|
5086
|
+
const embedType = document.getElementById('embedInputType');
|
|
5087
|
+
if (embedType) embedType.value = s.inputType;
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
// ── Update Checker ──
|
|
5092
|
+
function checkForAppUpdate() {
|
|
5093
|
+
if (!window.vai || !window.vai.isElectron) return;
|
|
5094
|
+
|
|
5095
|
+
// Show version in sidebar + settings page
|
|
5096
|
+
window.vai.getVersion().then(v => {
|
|
5097
|
+
if (!v) return;
|
|
5098
|
+
// v can be string (old) or { app, cli } (new)
|
|
5099
|
+
const appVer = typeof v === 'object' ? v.app : v;
|
|
5100
|
+
const cliVer = typeof v === 'object' ? v.cli : null;
|
|
5101
|
+
|
|
5102
|
+
// Sidebar label
|
|
5103
|
+
const label = document.getElementById('appVersionLabel');
|
|
5104
|
+
if (label && appVer) label.textContent = 'v' + appVer;
|
|
5105
|
+
|
|
5106
|
+
// Settings version section
|
|
5107
|
+
const section = document.getElementById('settingsVersionSection');
|
|
5108
|
+
const appBadge = document.getElementById('settingsAppVersion');
|
|
5109
|
+
const cliBadge = document.getElementById('settingsCliVersion');
|
|
5110
|
+
if (section) section.style.display = '';
|
|
5111
|
+
if (appBadge && appVer) appBadge.textContent = 'v' + appVer;
|
|
5112
|
+
if (cliBadge && cliVer) cliBadge.textContent = 'v' + cliVer;
|
|
5113
|
+
});
|
|
5114
|
+
|
|
5115
|
+
// Don't check more than once per hour
|
|
5116
|
+
const DISMISS_KEY = 'vai-update-dismissed';
|
|
5117
|
+
const dismissed = localStorage.getItem(DISMISS_KEY);
|
|
5118
|
+
if (dismissed) {
|
|
5119
|
+
const dismissedAt = parseInt(dismissed, 10);
|
|
5120
|
+
if (Date.now() - dismissedAt < 3600000) return; // 1 hour cooldown
|
|
5121
|
+
}
|
|
5122
|
+
|
|
5123
|
+
window.vai.updates.check().then(result => {
|
|
5124
|
+
if (!result || !result.hasUpdate) return;
|
|
5125
|
+
|
|
5126
|
+
const banner = document.getElementById('updateBanner');
|
|
5127
|
+
const versionEl = document.getElementById('updateVersion');
|
|
5128
|
+
const currentEl = document.getElementById('currentVersion');
|
|
5129
|
+
const downloadBtn = document.getElementById('updateDownloadBtn');
|
|
5130
|
+
const dismissBtn = document.getElementById('updateDismissBtn');
|
|
5131
|
+
|
|
5132
|
+
if (!banner) return;
|
|
5133
|
+
|
|
5134
|
+
versionEl.textContent = 'v' + result.latestVersion;
|
|
5135
|
+
currentEl.textContent = 'v' + result.currentVersion;
|
|
5136
|
+
banner.classList.add('show');
|
|
5137
|
+
|
|
5138
|
+
downloadBtn.addEventListener('click', () => {
|
|
5139
|
+
window.vai.updates.openRelease(result.releaseUrl);
|
|
5140
|
+
});
|
|
5141
|
+
|
|
5142
|
+
dismissBtn.addEventListener('click', () => {
|
|
5143
|
+
banner.classList.remove('show');
|
|
5144
|
+
localStorage.setItem(DISMISS_KEY, String(Date.now()));
|
|
5145
|
+
});
|
|
5146
|
+
}).catch(() => {
|
|
5147
|
+
// Silent fail — update check is non-critical
|
|
5148
|
+
});
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
// ── Onboarding Walkthrough ──
|
|
5152
|
+
function initOnboarding() {
|
|
5153
|
+
const ONBOARDING_KEY = 'vai-onboarding-complete';
|
|
5154
|
+
const overlay = document.getElementById('onboardingOverlay');
|
|
5155
|
+
const welcomeWrap = document.getElementById('onboardingWelcomeWrap');
|
|
5156
|
+
const welcomeCard = document.getElementById('onboardingWelcomeCard');
|
|
5157
|
+
const tooltip = document.getElementById('onboardingTooltip');
|
|
5158
|
+
const spotlight = document.getElementById('onboardingSpotlight');
|
|
5159
|
+
const arrow = document.getElementById('onboardingArrow');
|
|
5160
|
+
const dotsContainer = document.getElementById('onboardingDots');
|
|
5161
|
+
const titleEl = document.getElementById('onboardingTitle');
|
|
5162
|
+
const bodyEl = document.getElementById('onboardingBody');
|
|
5163
|
+
const iconEl = document.getElementById('onboardingIcon');
|
|
5164
|
+
|
|
5165
|
+
if (!overlay) return;
|
|
5166
|
+
|
|
5167
|
+
const isElectron = !!(window.vai && window.vai.isElectron);
|
|
5168
|
+
|
|
5169
|
+
const steps = [
|
|
5170
|
+
{
|
|
5171
|
+
target: null, // welcome card, no spotlight
|
|
5172
|
+
icon: '🚀',
|
|
5173
|
+
title: 'Welcome to Vai',
|
|
5174
|
+
body: 'Explore embeddings, compare text similarity, and search with vector models — all from one playground.',
|
|
5175
|
+
},
|
|
5176
|
+
{
|
|
5177
|
+
target: '[data-tab="settings"]',
|
|
5178
|
+
icon: '🔑',
|
|
5179
|
+
title: 'API Key Setup',
|
|
5180
|
+
body: 'First, add your <strong>Voyage AI API key</strong> in Settings. You can get one free at <strong>dash.voyageai.com</strong>.' +
|
|
5181
|
+
(isElectron ? '<br><br>💎 On desktop, your key is encrypted via <strong>OS keychain</strong> for secure storage.' : ''),
|
|
5182
|
+
arrow: 'left',
|
|
5183
|
+
},
|
|
5184
|
+
{
|
|
5185
|
+
target: '[data-tab="embed"]',
|
|
5186
|
+
icon: '⚡',
|
|
5187
|
+
title: 'Embed',
|
|
5188
|
+
body: 'Turn any text into a <strong>vector embedding</strong> — a numerical fingerprint that captures meaning. Visualize the raw vectors and explore what models produce.',
|
|
5189
|
+
arrow: 'left',
|
|
5190
|
+
},
|
|
5191
|
+
{
|
|
5192
|
+
target: '[data-tab="compare"]',
|
|
5193
|
+
icon: '🔥',
|
|
5194
|
+
title: 'Compare',
|
|
5195
|
+
body: 'Paste multiple texts and see a <strong>similarity heatmap</strong> — instantly discover which phrases are semantically close and which are far apart.',
|
|
5196
|
+
arrow: 'left',
|
|
5197
|
+
},
|
|
5198
|
+
{
|
|
5199
|
+
target: '[data-tab="search"]',
|
|
5200
|
+
icon: '🔍',
|
|
5201
|
+
title: 'Search & Rerank',
|
|
5202
|
+
body: 'Run <strong>vector search</strong> against a set of documents and optionally <strong>rerank</strong> results for higher precision. Great for building RAG pipelines.',
|
|
5203
|
+
arrow: 'left',
|
|
5204
|
+
},
|
|
5205
|
+
];
|
|
5206
|
+
|
|
5207
|
+
let currentStep = 0;
|
|
5208
|
+
const totalSteps = steps.length;
|
|
5209
|
+
|
|
5210
|
+
function buildDots() {
|
|
5211
|
+
dotsContainer.innerHTML = '';
|
|
5212
|
+
for (let i = 1; i < totalSteps; i++) {
|
|
5213
|
+
const dot = document.createElement('div');
|
|
5214
|
+
dot.className = 'onboarding-dot';
|
|
5215
|
+
if (i < currentStep) dot.classList.add('completed');
|
|
5216
|
+
if (i === currentStep) dot.classList.add('active');
|
|
5217
|
+
dotsContainer.appendChild(dot);
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5221
|
+
function positionTooltipNear(targetEl) {
|
|
5222
|
+
const step = steps[currentStep];
|
|
5223
|
+
const rect = targetEl.getBoundingClientRect();
|
|
5224
|
+
|
|
5225
|
+
// Position spotlight over the target
|
|
5226
|
+
spotlight.style.display = 'block';
|
|
5227
|
+
const pad = 6;
|
|
5228
|
+
spotlight.style.left = (rect.left - pad) + 'px';
|
|
5229
|
+
spotlight.style.top = (rect.top - pad) + 'px';
|
|
5230
|
+
spotlight.style.width = (rect.width + pad * 2) + 'px';
|
|
5231
|
+
spotlight.style.height = (rect.height + pad * 2) + 'px';
|
|
5232
|
+
|
|
5233
|
+
// Reset arrow classes
|
|
5234
|
+
arrow.className = 'onboarding-tooltip-arrow';
|
|
5235
|
+
|
|
5236
|
+
// Position tooltip to the right of sidebar items
|
|
5237
|
+
if (step.arrow === 'left') {
|
|
5238
|
+
arrow.classList.add('left');
|
|
5239
|
+
tooltip.style.left = (rect.right + 16) + 'px';
|
|
5240
|
+
tooltip.style.top = Math.max(8, rect.top - 10) + 'px';
|
|
5241
|
+
tooltip.style.right = 'auto';
|
|
5242
|
+
tooltip.style.bottom = 'auto';
|
|
5243
|
+
} else {
|
|
5244
|
+
// Below the target
|
|
5245
|
+
arrow.classList.add('top');
|
|
5246
|
+
tooltip.style.left = Math.max(8, rect.left) + 'px';
|
|
5247
|
+
tooltip.style.top = (rect.bottom + 14) + 'px';
|
|
5248
|
+
tooltip.style.right = 'auto';
|
|
5249
|
+
tooltip.style.bottom = 'auto';
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
// Ensure tooltip doesn't overflow viewport
|
|
5253
|
+
requestAnimationFrame(() => {
|
|
5254
|
+
const tr = tooltip.getBoundingClientRect();
|
|
5255
|
+
if (tr.bottom > window.innerHeight - 10) {
|
|
5256
|
+
tooltip.style.top = Math.max(8, window.innerHeight - tr.height - 10) + 'px';
|
|
5257
|
+
}
|
|
5258
|
+
if (tr.right > window.innerWidth - 10) {
|
|
5259
|
+
tooltip.style.left = Math.max(8, window.innerWidth - tr.width - 10) + 'px';
|
|
5260
|
+
}
|
|
5261
|
+
});
|
|
5262
|
+
}
|
|
5263
|
+
|
|
5264
|
+
function showStep(idx) {
|
|
5265
|
+
currentStep = idx;
|
|
5266
|
+
const step = steps[idx];
|
|
5267
|
+
|
|
5268
|
+
if (idx === 0) {
|
|
5269
|
+
// Welcome card — centered, no spotlight
|
|
5270
|
+
welcomeWrap.style.display = 'flex';
|
|
5271
|
+
tooltip.classList.remove('visible');
|
|
5272
|
+
tooltip.style.display = 'none';
|
|
5273
|
+
spotlight.style.display = 'none';
|
|
5274
|
+
requestAnimationFrame(() => {
|
|
5275
|
+
welcomeCard.classList.add('visible');
|
|
5276
|
+
});
|
|
5277
|
+
return;
|
|
5278
|
+
}
|
|
5279
|
+
|
|
5280
|
+
// Hide welcome card
|
|
5281
|
+
welcomeWrap.style.display = 'none';
|
|
5282
|
+
welcomeCard.classList.remove('visible');
|
|
5283
|
+
|
|
5284
|
+
// Show tooltip
|
|
5285
|
+
tooltip.style.display = 'block';
|
|
5286
|
+
tooltip.classList.remove('visible');
|
|
5287
|
+
iconEl.textContent = step.icon;
|
|
5288
|
+
titleEl.textContent = step.title;
|
|
5289
|
+
bodyEl.innerHTML = step.body;
|
|
5290
|
+
buildDots();
|
|
5291
|
+
|
|
5292
|
+
// Update next button text
|
|
5293
|
+
const nextBtn = document.getElementById('onboardingNext');
|
|
5294
|
+
nextBtn.textContent = (idx === totalSteps - 1) ? 'Get Started' : 'Next';
|
|
5295
|
+
|
|
5296
|
+
// Find target element and position
|
|
5297
|
+
const targetEl = document.querySelector(step.target);
|
|
5298
|
+
if (targetEl) {
|
|
5299
|
+
positionTooltipNear(targetEl);
|
|
5300
|
+
}
|
|
5301
|
+
|
|
5302
|
+
requestAnimationFrame(() => {
|
|
5303
|
+
tooltip.classList.add('visible');
|
|
5304
|
+
});
|
|
5305
|
+
}
|
|
5306
|
+
|
|
5307
|
+
function finish() {
|
|
5308
|
+
tooltip.classList.remove('visible');
|
|
5309
|
+
welcomeCard.classList.remove('visible');
|
|
5310
|
+
spotlight.style.display = 'none';
|
|
5311
|
+
setTimeout(() => {
|
|
5312
|
+
overlay.classList.remove('active');
|
|
5313
|
+
}, 300);
|
|
5314
|
+
localStorage.setItem(ONBOARDING_KEY, 'true');
|
|
5315
|
+
}
|
|
5316
|
+
|
|
5317
|
+
function nextStep() {
|
|
5318
|
+
if (currentStep < totalSteps - 1) {
|
|
5319
|
+
showStep(currentStep + 1);
|
|
5320
|
+
} else {
|
|
5321
|
+
finish();
|
|
5322
|
+
}
|
|
5323
|
+
}
|
|
5324
|
+
|
|
5325
|
+
// Wire up buttons
|
|
5326
|
+
document.getElementById('onboardingWelcomeNext').addEventListener('click', nextStep);
|
|
5327
|
+
document.getElementById('onboardingWelcomeSkip').addEventListener('click', finish);
|
|
5328
|
+
document.getElementById('onboardingNext').addEventListener('click', nextStep);
|
|
5329
|
+
document.getElementById('onboardingSkip').addEventListener('click', finish);
|
|
5330
|
+
document.getElementById('onboardingBackdrop').addEventListener('click', finish);
|
|
5331
|
+
|
|
5332
|
+
// "Show Welcome Tour" button in settings
|
|
5333
|
+
const tourBtn = document.getElementById('settingsShowTour');
|
|
5334
|
+
if (tourBtn) {
|
|
5335
|
+
tourBtn.addEventListener('click', () => {
|
|
5336
|
+
startOnboarding();
|
|
5337
|
+
});
|
|
5338
|
+
}
|
|
5339
|
+
|
|
5340
|
+
// Auto-start on first visit
|
|
5341
|
+
function startOnboarding() {
|
|
5342
|
+
// Sync onboarding logo with current theme
|
|
5343
|
+
const onboardLogo = document.getElementById('onboardingLogo');
|
|
5344
|
+
if (onboardLogo) {
|
|
5345
|
+
const theme = localStorage.getItem('vai-theme') || 'dark';
|
|
5346
|
+
onboardLogo.src = '/icons/' + (theme === 'light' ? 'light' : 'dark') + '/64.png';
|
|
5347
|
+
}
|
|
5348
|
+
overlay.classList.add('active');
|
|
5349
|
+
showStep(0);
|
|
5350
|
+
}
|
|
5351
|
+
|
|
5352
|
+
if (!localStorage.getItem(ONBOARDING_KEY)) {
|
|
5353
|
+
// Delay slightly so the app is fully rendered
|
|
5354
|
+
setTimeout(startOnboarding, 600);
|
|
5355
|
+
}
|
|
5356
|
+
|
|
5357
|
+
// Expose for manual replay
|
|
5358
|
+
window.startOnboarding = startOnboarding;
|
|
5359
|
+
}
|
|
5360
|
+
|
|
5361
|
+
// ── Multimodal Tab ──
|
|
5362
|
+
let mmImageData = null; // base64 data URL of the uploaded image
|
|
5363
|
+
let mmGalleryImages = []; // array of { dataUrl, name, size }
|
|
5364
|
+
let mmSearchMode = 'text';
|
|
5365
|
+
let mmSearchImageIndex = -1;
|
|
5366
|
+
|
|
5367
|
+
function initMultimodal() {
|
|
5368
|
+
const dropZone = document.getElementById('mmDropZone');
|
|
5369
|
+
const fileInput = document.getElementById('mmFileInput');
|
|
5370
|
+
|
|
5371
|
+
// Click to browse
|
|
5372
|
+
dropZone.addEventListener('click', () => fileInput.click());
|
|
5373
|
+
fileInput.addEventListener('change', (e) => {
|
|
5374
|
+
if (e.target.files && e.target.files[0]) handleMultimodalImage(e.target.files[0]);
|
|
5375
|
+
});
|
|
5376
|
+
|
|
5377
|
+
// Drag and drop
|
|
5378
|
+
['dragenter', 'dragover'].forEach(evt => {
|
|
5379
|
+
dropZone.addEventListener(evt, (e) => {
|
|
5380
|
+
e.preventDefault();
|
|
5381
|
+
e.stopPropagation();
|
|
5382
|
+
dropZone.classList.add('drag-active');
|
|
5383
|
+
});
|
|
5384
|
+
});
|
|
5385
|
+
['dragleave', 'drop'].forEach(evt => {
|
|
5386
|
+
dropZone.addEventListener(evt, (e) => {
|
|
5387
|
+
e.preventDefault();
|
|
5388
|
+
e.stopPropagation();
|
|
5389
|
+
dropZone.classList.remove('drag-active');
|
|
5390
|
+
});
|
|
5391
|
+
});
|
|
5392
|
+
dropZone.addEventListener('drop', (e) => {
|
|
5393
|
+
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
|
|
5394
|
+
handleMultimodalImage(e.dataTransfer.files[0]);
|
|
5395
|
+
}
|
|
5396
|
+
});
|
|
5397
|
+
|
|
5398
|
+
// Paste from clipboard
|
|
5399
|
+
document.addEventListener('paste', (e) => {
|
|
5400
|
+
// Only handle if multimodal tab is active
|
|
5401
|
+
const panel = document.getElementById('tab-multimodal');
|
|
5402
|
+
if (!panel || !panel.classList.contains('active')) return;
|
|
5403
|
+
const items = e.clipboardData?.items;
|
|
5404
|
+
if (!items) return;
|
|
5405
|
+
for (const item of items) {
|
|
5406
|
+
if (item.type.startsWith('image/')) {
|
|
5407
|
+
e.preventDefault();
|
|
5408
|
+
handleMultimodalImage(item.getAsFile());
|
|
5409
|
+
return;
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
});
|
|
5413
|
+
|
|
5414
|
+
// Gallery
|
|
5415
|
+
renderGalleryGrid();
|
|
5416
|
+
|
|
5417
|
+
const galleryInput = document.getElementById('mmGalleryFileInput');
|
|
5418
|
+
galleryInput.addEventListener('change', (e) => {
|
|
5419
|
+
if (e.target.files && e.target.files[0]) {
|
|
5420
|
+
addGalleryImage(e.target.files[0]);
|
|
5421
|
+
}
|
|
5422
|
+
galleryInput.value = '';
|
|
5423
|
+
});
|
|
5424
|
+
}
|
|
5425
|
+
|
|
5426
|
+
function handleMultimodalImage(file) {
|
|
5427
|
+
const VALID_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'];
|
|
5428
|
+
if (!VALID_TYPES.includes(file.type)) {
|
|
5429
|
+
showError('mmError', 'Unsupported image type. Use PNG, JPEG, WebP, or GIF.');
|
|
5430
|
+
return;
|
|
5431
|
+
}
|
|
5432
|
+
if (file.size > 20 * 1024 * 1024) {
|
|
5433
|
+
showError('mmError', 'Image too large. Maximum size is 20 MB.');
|
|
5434
|
+
return;
|
|
5435
|
+
}
|
|
5436
|
+
hideError('mmError');
|
|
5437
|
+
|
|
5438
|
+
const reader = new FileReader();
|
|
5439
|
+
reader.onload = (e) => {
|
|
5440
|
+
mmImageData = e.target.result;
|
|
5441
|
+
const img = document.getElementById('mmPreviewImg');
|
|
5442
|
+
img.src = mmImageData;
|
|
5443
|
+
|
|
5444
|
+
img.onload = () => {
|
|
5445
|
+
const info = document.getElementById('mmFileInfo');
|
|
5446
|
+
const sizeStr = file.size > 1024 * 1024
|
|
5447
|
+
? (file.size / (1024 * 1024)).toFixed(1) + ' MB'
|
|
5448
|
+
: (file.size / 1024).toFixed(0) + ' KB';
|
|
5449
|
+
info.textContent = `${file.name} · ${img.naturalWidth}×${img.naturalHeight} · ${sizeStr}`;
|
|
5450
|
+
};
|
|
5451
|
+
|
|
5452
|
+
document.getElementById('mmDropZone').style.display = 'none';
|
|
5453
|
+
document.getElementById('mmPreview').classList.add('visible');
|
|
5454
|
+
};
|
|
5455
|
+
reader.readAsDataURL(file);
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
window.clearMultimodalImage = function() {
|
|
5459
|
+
mmImageData = null;
|
|
5460
|
+
document.getElementById('mmPreviewImg').src = '';
|
|
5461
|
+
document.getElementById('mmFileInfo').textContent = '';
|
|
5462
|
+
document.getElementById('mmPreview').classList.remove('visible');
|
|
5463
|
+
document.getElementById('mmDropZone').style.display = '';
|
|
5464
|
+
document.getElementById('mmFileInput').value = '';
|
|
5465
|
+
};
|
|
5466
|
+
|
|
5467
|
+
window.doMultimodalCompare = async function() {
|
|
5468
|
+
hideError('mmError');
|
|
5469
|
+
const text = document.getElementById('mmText').value.trim();
|
|
5470
|
+
if (!mmImageData) { showError('mmError', 'Upload an image first'); return; }
|
|
5471
|
+
if (!text) { showError('mmError', 'Enter text to compare against the image'); return; }
|
|
5472
|
+
|
|
5473
|
+
setLoading('mmCompareBtn', true);
|
|
5474
|
+
try {
|
|
5475
|
+
const model = document.getElementById('mmModel').value;
|
|
5476
|
+
const dimsVal = document.getElementById('mmDimensions').value;
|
|
5477
|
+
const body = {
|
|
5478
|
+
inputs: [
|
|
5479
|
+
{ content: [{ type: 'image_base64', image_base64: mmImageData }] },
|
|
5480
|
+
{ content: [{ type: 'text', text: text }] }
|
|
5481
|
+
],
|
|
5482
|
+
model: model,
|
|
5483
|
+
input_type: 'document'
|
|
5484
|
+
};
|
|
5485
|
+
if (dimsVal) body.output_dimension = parseInt(dimsVal, 10);
|
|
5486
|
+
|
|
5487
|
+
const data = await apiPost('/api/multimodal-embed', body);
|
|
5488
|
+
|
|
5489
|
+
const vecA = data.data[0].embedding;
|
|
5490
|
+
const vecB = data.data[1].embedding;
|
|
5491
|
+
const cosine = cosineSim(vecA, vecB);
|
|
5492
|
+
|
|
5493
|
+
// Hero display
|
|
5494
|
+
const cosinePct = Math.max(0, cosine * 100);
|
|
5495
|
+
let cosineColor;
|
|
5496
|
+
if (cosine > 0.7) cosineColor = 'var(--green)';
|
|
5497
|
+
else if (cosine > 0.4) cosineColor = 'var(--yellow)';
|
|
5498
|
+
else cosineColor = 'var(--red)';
|
|
5499
|
+
|
|
5500
|
+
const scoreEl = document.getElementById('mmSimScore');
|
|
5501
|
+
scoreEl.textContent = cosine.toFixed(4);
|
|
5502
|
+
scoreEl.style.color = cosineColor;
|
|
5503
|
+
|
|
5504
|
+
const barEl = document.getElementById('mmSimBar');
|
|
5505
|
+
barEl.style.width = cosinePct + '%';
|
|
5506
|
+
barEl.style.background = cosineColor;
|
|
5507
|
+
|
|
5508
|
+
// Stats
|
|
5509
|
+
const usage = data.usage || {};
|
|
5510
|
+
const statsEl = document.getElementById('mmStats');
|
|
5511
|
+
statsEl.innerHTML = `
|
|
5512
|
+
<span class="stat"><span class="stat-label">Model</span><span class="stat-value">${data.model || model}</span></span>
|
|
5513
|
+
<span class="stat"><span class="stat-label">Dimensions</span><span class="stat-value">${vecA.length}</span></span>
|
|
5514
|
+
<span class="stat"><span class="stat-label">Text Tokens</span><span class="stat-value">${usage.text_tokens || '—'}</span></span>
|
|
5515
|
+
<span class="stat"><span class="stat-label">Image Pixels</span><span class="stat-value">${usage.image_pixels ? usage.image_pixels.toLocaleString() : '—'}</span></span>
|
|
5516
|
+
<span class="stat"><span class="stat-label">Total Tokens</span><span class="stat-value">${usage.total_tokens || '—'}</span></span>
|
|
5517
|
+
`;
|
|
5518
|
+
|
|
5519
|
+
// Insight note
|
|
5520
|
+
const noteEl = document.getElementById('mmNote');
|
|
5521
|
+
if (cosine > 0.7) {
|
|
5522
|
+
noteEl.innerHTML = '💡 <strong>High similarity!</strong> The image and text are closely related in Voyage AI\'s multimodal embedding space. This means the text is a good semantic description of the image.';
|
|
5523
|
+
} else if (cosine > 0.4) {
|
|
5524
|
+
noteEl.innerHTML = '💡 <strong>Moderate similarity.</strong> The image and text share some semantic overlap. They may be related but not a direct match.';
|
|
5525
|
+
} else {
|
|
5526
|
+
noteEl.innerHTML = '💡 <strong>Low similarity.</strong> The image and text are semantically distant. Try a description that matches the image content more closely.';
|
|
5527
|
+
}
|
|
5528
|
+
|
|
5529
|
+
document.getElementById('mmResult').classList.add('visible');
|
|
5530
|
+
} catch (err) {
|
|
5531
|
+
showError('mmError', err.message);
|
|
5532
|
+
} finally {
|
|
5533
|
+
setLoading('mmCompareBtn', false);
|
|
5534
|
+
}
|
|
5535
|
+
};
|
|
5536
|
+
|
|
5537
|
+
// Gallery functions
|
|
5538
|
+
function renderGalleryGrid() {
|
|
5539
|
+
const grid = document.getElementById('mmGalleryGrid');
|
|
5540
|
+
grid.innerHTML = '';
|
|
5541
|
+
|
|
5542
|
+
mmGalleryImages.forEach((img, i) => {
|
|
5543
|
+
const slot = document.createElement('div');
|
|
5544
|
+
slot.className = 'mm-gallery-slot filled' + (mmSearchMode === 'image' && mmSearchImageIndex === i ? ' query-selected' : '');
|
|
5545
|
+
slot.innerHTML = `<img src="${img.dataUrl}" alt="${img.name}"><button class="mm-slot-remove" title="Remove">×</button>`;
|
|
5546
|
+
slot.querySelector('.mm-slot-remove').addEventListener('click', (e) => {
|
|
5547
|
+
e.stopPropagation();
|
|
5548
|
+
removeGalleryImage(i);
|
|
5549
|
+
});
|
|
5550
|
+
if (mmSearchMode === 'image') {
|
|
5551
|
+
slot.style.cursor = 'pointer';
|
|
5552
|
+
slot.addEventListener('click', () => selectGalleryImageAsQuery(i));
|
|
5553
|
+
}
|
|
5554
|
+
grid.appendChild(slot);
|
|
5555
|
+
});
|
|
5556
|
+
|
|
5557
|
+
// Add empty slot if under 6
|
|
5558
|
+
if (mmGalleryImages.length < 6) {
|
|
5559
|
+
const addSlot = document.createElement('div');
|
|
5560
|
+
addSlot.className = 'mm-gallery-slot';
|
|
5561
|
+
addSlot.innerHTML = '<span class="mm-slot-add">+</span>';
|
|
5562
|
+
addSlot.addEventListener('click', () => {
|
|
5563
|
+
document.getElementById('mmGalleryFileInput').click();
|
|
5564
|
+
});
|
|
5565
|
+
grid.appendChild(addSlot);
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
|
|
5569
|
+
function addGalleryImage(file) {
|
|
5570
|
+
const VALID_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'];
|
|
5571
|
+
if (!VALID_TYPES.includes(file.type)) return;
|
|
5572
|
+
if (file.size > 20 * 1024 * 1024) return;
|
|
5573
|
+
if (mmGalleryImages.length >= 6) return;
|
|
5574
|
+
|
|
5575
|
+
const reader = new FileReader();
|
|
5576
|
+
reader.onload = (e) => {
|
|
5577
|
+
mmGalleryImages.push({ dataUrl: e.target.result, name: file.name, size: file.size });
|
|
5578
|
+
renderGalleryGrid();
|
|
5579
|
+
};
|
|
5580
|
+
reader.readAsDataURL(file);
|
|
5581
|
+
}
|
|
5582
|
+
|
|
5583
|
+
function removeGalleryImage(index) {
|
|
5584
|
+
mmGalleryImages.splice(index, 1);
|
|
5585
|
+
if (mmSearchImageIndex === index) mmSearchImageIndex = -1;
|
|
5586
|
+
else if (mmSearchImageIndex > index) mmSearchImageIndex--;
|
|
5587
|
+
renderGalleryGrid();
|
|
5588
|
+
updateSearchImageLabel();
|
|
5589
|
+
}
|
|
5590
|
+
|
|
5591
|
+
function selectGalleryImageAsQuery(index) {
|
|
5592
|
+
mmSearchImageIndex = (mmSearchImageIndex === index) ? -1 : index;
|
|
5593
|
+
renderGalleryGrid();
|
|
5594
|
+
updateSearchImageLabel();
|
|
5595
|
+
}
|
|
5596
|
+
|
|
5597
|
+
function updateSearchImageLabel() {
|
|
5598
|
+
const label = document.getElementById('mmSearchImageLabel');
|
|
5599
|
+
if (mmSearchImageIndex >= 0 && mmSearchImageIndex < mmGalleryImages.length) {
|
|
5600
|
+
label.textContent = '✓ Image ' + (mmSearchImageIndex + 1) + ' selected as query';
|
|
5601
|
+
label.style.color = 'var(--accent)';
|
|
5602
|
+
} else {
|
|
5603
|
+
label.textContent = 'No image selected';
|
|
5604
|
+
label.style.color = 'var(--text-muted)';
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
|
|
5608
|
+
window.setMmSearchMode = function(mode) {
|
|
5609
|
+
mmSearchMode = mode;
|
|
5610
|
+
document.querySelectorAll('#mmSearchMode button').forEach(b => {
|
|
5611
|
+
b.classList.toggle('active', b.dataset.mode === mode);
|
|
5612
|
+
});
|
|
5613
|
+
document.getElementById('mmSearchTextWrap').style.display = mode === 'text' ? '' : 'none';
|
|
5614
|
+
document.getElementById('mmSearchImageWrap').style.display = mode === 'image' ? '' : 'none';
|
|
5615
|
+
renderGalleryGrid();
|
|
5616
|
+
};
|
|
5617
|
+
|
|
5618
|
+
window.doMultimodalSearch = async function() {
|
|
5619
|
+
hideError('mmSearchError');
|
|
5620
|
+
|
|
5621
|
+
const corpusText = document.getElementById('mmCorpusText').value.trim();
|
|
5622
|
+
const textItems = corpusText ? corpusText.split('\n').map(t => t.trim()).filter(Boolean) : [];
|
|
5623
|
+
|
|
5624
|
+
if (mmGalleryImages.length === 0 && textItems.length === 0) {
|
|
5625
|
+
showError('mmSearchError', 'Add at least one image or text to the corpus');
|
|
5626
|
+
return;
|
|
5627
|
+
}
|
|
5628
|
+
|
|
5629
|
+
// Build query input
|
|
5630
|
+
let queryInput;
|
|
5631
|
+
if (mmSearchMode === 'text') {
|
|
5632
|
+
const q = document.getElementById('mmSearchQuery').value.trim();
|
|
5633
|
+
if (!q) { showError('mmSearchError', 'Enter a search query'); return; }
|
|
5634
|
+
queryInput = { content: [{ type: 'text', text: q }] };
|
|
5635
|
+
} else {
|
|
5636
|
+
if (mmSearchImageIndex < 0 || mmSearchImageIndex >= mmGalleryImages.length) {
|
|
5637
|
+
showError('mmSearchError', 'Select an image from the corpus to use as query');
|
|
5638
|
+
return;
|
|
5639
|
+
}
|
|
5640
|
+
queryInput = { content: [{ type: 'image_base64', image_base64: mmGalleryImages[mmSearchImageIndex].dataUrl }] };
|
|
5641
|
+
}
|
|
5642
|
+
|
|
5643
|
+
const btnId = mmSearchMode === 'text' ? 'mmSearchBtn' : 'mmSearchImgBtn';
|
|
5644
|
+
setLoading(btnId, true);
|
|
5645
|
+
|
|
5646
|
+
try {
|
|
5647
|
+
const model = document.getElementById('mmModel').value;
|
|
5648
|
+
const dimsVal = document.getElementById('mmDimensions').value;
|
|
5649
|
+
|
|
5650
|
+
// Build corpus inputs
|
|
5651
|
+
const corpusInputs = [];
|
|
5652
|
+
const corpusMeta = []; // track type/content for display
|
|
5653
|
+
|
|
5654
|
+
mmGalleryImages.forEach((img, i) => {
|
|
5655
|
+
corpusInputs.push({ content: [{ type: 'image_base64', image_base64: img.dataUrl }] });
|
|
5656
|
+
corpusMeta.push({ type: 'image', dataUrl: img.dataUrl, label: img.name || 'Image ' + (i + 1) });
|
|
5657
|
+
});
|
|
5658
|
+
|
|
5659
|
+
textItems.forEach(t => {
|
|
5660
|
+
corpusInputs.push({ content: [{ type: 'text', text: t }] });
|
|
5661
|
+
corpusMeta.push({ type: 'text', label: t });
|
|
5662
|
+
});
|
|
5663
|
+
|
|
5664
|
+
// Embed query + all corpus items in one call
|
|
5665
|
+
const allInputs = [queryInput, ...corpusInputs];
|
|
5666
|
+
const body = {
|
|
5667
|
+
inputs: allInputs,
|
|
5668
|
+
model: model,
|
|
5669
|
+
input_type: 'document'
|
|
5670
|
+
};
|
|
5671
|
+
if (dimsVal) body.output_dimension = parseInt(dimsVal, 10);
|
|
5672
|
+
|
|
5673
|
+
const data = await apiPost('/api/multimodal-embed', body);
|
|
5674
|
+
|
|
5675
|
+
const queryVec = data.data[0].embedding;
|
|
5676
|
+
const results = corpusMeta.map((meta, i) => {
|
|
5677
|
+
const vec = data.data[i + 1].embedding;
|
|
5678
|
+
const sim = cosineSim(queryVec, vec);
|
|
5679
|
+
return { ...meta, similarity: sim };
|
|
5680
|
+
}).sort((a, b) => b.similarity - a.similarity);
|
|
5681
|
+
|
|
5682
|
+
// Render results
|
|
5683
|
+
const listEl = document.getElementById('mmSearchResultList');
|
|
5684
|
+
listEl.innerHTML = '';
|
|
5685
|
+
|
|
5686
|
+
results.forEach((r, i) => {
|
|
5687
|
+
let simColor;
|
|
5688
|
+
if (r.similarity > 0.7) simColor = 'var(--green)';
|
|
5689
|
+
else if (r.similarity > 0.4) simColor = 'var(--yellow)';
|
|
5690
|
+
else simColor = 'var(--red)';
|
|
5691
|
+
|
|
5692
|
+
const item = document.createElement('div');
|
|
5693
|
+
item.className = 'mm-result-item';
|
|
5694
|
+
|
|
5695
|
+
const thumbHtml = r.type === 'image'
|
|
5696
|
+
? `<img class="mm-result-thumb" src="${r.dataUrl}" alt="${r.label}">`
|
|
5697
|
+
: `<div class="mm-result-thumb" style="display:flex;align-items:center;justify-content:center;background:var(--bg-surface);font-size:18px;">📝</div>`;
|
|
5698
|
+
|
|
5699
|
+
item.innerHTML = `
|
|
5700
|
+
<div class="mm-result-rank">#${i + 1}</div>
|
|
5701
|
+
${thumbHtml}
|
|
5702
|
+
<div class="mm-result-content">
|
|
5703
|
+
<div class="mm-result-text" title="${r.label.replace(/"/g, '"')}">${r.label}</div>
|
|
5704
|
+
<div class="mm-result-type">${r.type}</div>
|
|
5705
|
+
</div>
|
|
5706
|
+
<div class="mm-result-score" style="color:${simColor}">${r.similarity.toFixed(4)}</div>
|
|
5707
|
+
`;
|
|
5708
|
+
listEl.appendChild(item);
|
|
5709
|
+
});
|
|
5710
|
+
|
|
5711
|
+
document.getElementById('mmSearchResult').classList.add('visible');
|
|
5712
|
+
} catch (err) {
|
|
5713
|
+
showError('mmSearchError', err.message);
|
|
5714
|
+
} finally {
|
|
5715
|
+
setLoading(btnId, false);
|
|
5716
|
+
}
|
|
5717
|
+
};
|
|
5718
|
+
|
|
3504
5719
|
// ── Patch init to include benchmark setup ──
|
|
3505
5720
|
const _origInit = init;
|
|
3506
5721
|
init = async function() {
|
|
@@ -3511,6 +5726,10 @@ init = async function() {
|
|
|
3511
5726
|
populateQuantModelSelect();
|
|
3512
5727
|
initCostCalculator();
|
|
3513
5728
|
renderHistory();
|
|
5729
|
+
initSettings();
|
|
5730
|
+
initMultimodal();
|
|
5731
|
+
checkForAppUpdate();
|
|
5732
|
+
initOnboarding();
|
|
3514
5733
|
};
|
|
3515
5734
|
|
|
3516
5735
|
// ── Start ──
|