voyageai-cli 1.19.0 → 1.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/playground/index.html +698 -98
package/package.json
CHANGED
|
@@ -8,27 +8,85 @@
|
|
|
8
8
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
9
|
|
|
10
10
|
:root {
|
|
11
|
-
|
|
12
|
-
--bg
|
|
13
|
-
--bg-
|
|
14
|
-
--bg-
|
|
15
|
-
--
|
|
16
|
-
--accent
|
|
17
|
-
--accent-
|
|
18
|
-
--
|
|
19
|
-
--text
|
|
20
|
-
--text-
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--
|
|
25
|
-
--
|
|
26
|
-
--
|
|
27
|
-
--
|
|
11
|
+
/* MongoDB Design System — Dark Mode Palette (default) */
|
|
12
|
+
--bg: #001E2B; /* MDB Black */
|
|
13
|
+
--bg-surface: #112733; /* Gray Dark 4 */
|
|
14
|
+
--bg-card: #1C2D38; /* Gray Dark 3 */
|
|
15
|
+
--bg-input: #112733; /* Gray Dark 4 */
|
|
16
|
+
--accent: #00ED64; /* Green Base */
|
|
17
|
+
--accent-dim: #00A35C; /* Green Dark 1 */
|
|
18
|
+
--accent-glow: rgba(0, 237, 100, 0.12);
|
|
19
|
+
--text: #E8EDEB; /* Gray Light 2 */
|
|
20
|
+
--text-dim: #C1C7C6; /* Gray Light 1 */
|
|
21
|
+
--text-muted: #889397; /* Gray Base */
|
|
22
|
+
--border: #3D4F58; /* Gray Dark 2 */
|
|
23
|
+
--error: #FF6960; /* Red Light 1 */
|
|
24
|
+
--warning: #FFC010; /* Yellow Base */
|
|
25
|
+
--success: #00ED64; /* Green Base */
|
|
26
|
+
--red: #FF6960; /* Red Light 1 */
|
|
27
|
+
--yellow: #FFC010; /* Yellow Base */
|
|
28
|
+
--green: #00ED64; /* Green Base */
|
|
29
|
+
--blue: #0498EC; /* Blue Light 1 (links) */
|
|
30
|
+
--purple: #B45AF2; /* Purple Base */
|
|
31
|
+
--green-dark: #023430; /* Green Dark 3 */
|
|
28
32
|
--radius: 8px;
|
|
29
|
-
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
30
|
-
--mono: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
|
|
31
|
-
}
|
|
33
|
+
--font: 'Euclid Circular A', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
34
|
+
--mono: 'Source Code Pro', 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* MongoDB Design System — Light Mode Palette */
|
|
38
|
+
[data-theme="light"] {
|
|
39
|
+
--bg: #FFFFFF; /* White */
|
|
40
|
+
--bg-surface: #F9FBFA; /* Gray Light 3 */
|
|
41
|
+
--bg-card: #FFFFFF; /* White */
|
|
42
|
+
--bg-input: #F9FBFA; /* Gray Light 3 */
|
|
43
|
+
--accent: #00A35C; /* Green Dark 1 (better contrast on white) */
|
|
44
|
+
--accent-dim: #00684A; /* Green Dark 2 */
|
|
45
|
+
--accent-glow: rgba(0, 163, 92, 0.08);
|
|
46
|
+
--text: #001E2B; /* MDB Black */
|
|
47
|
+
--text-dim: #5C6C75; /* Gray Dark 1 */
|
|
48
|
+
--text-muted: #889397; /* Gray Base */
|
|
49
|
+
--border: #E8EDEB; /* Gray Light 2 */
|
|
50
|
+
--error: #DB3030; /* Red Base */
|
|
51
|
+
--warning: #944F01; /* Yellow Dark 2 */
|
|
52
|
+
--success: #00684A; /* Green Dark 2 */
|
|
53
|
+
--red: #DB3030; /* Red Base */
|
|
54
|
+
--yellow: #944F01; /* Yellow Dark 2 */
|
|
55
|
+
--green: #00684A; /* Green Dark 2 */
|
|
56
|
+
--blue: #016BF8; /* Blue Base */
|
|
57
|
+
--purple: #5E0C9E; /* Purple Dark 2 */
|
|
58
|
+
--green-dark: #023430; /* Green Dark 3 */
|
|
59
|
+
}
|
|
60
|
+
/* Light mode shadow + card adjustments */
|
|
61
|
+
[data-theme="light"] .explore-card,
|
|
62
|
+
[data-theme="light"] .card,
|
|
63
|
+
[data-theme="light"] .cost-strategy,
|
|
64
|
+
[data-theme="light"] .cost-summary-card {
|
|
65
|
+
box-shadow: 0 1px 4px rgba(0, 30, 43, 0.08);
|
|
66
|
+
}
|
|
67
|
+
[data-theme="light"] .explore-card:hover {
|
|
68
|
+
box-shadow: 0 4px 16px rgba(0, 163, 92, 0.12);
|
|
69
|
+
}
|
|
70
|
+
[data-theme="light"] .cost-modal,
|
|
71
|
+
[data-theme="light"] .explore-modal {
|
|
72
|
+
box-shadow: 0 20px 60px rgba(0, 30, 43, 0.2);
|
|
73
|
+
}
|
|
74
|
+
[data-theme="light"] .cost-modal-overlay,
|
|
75
|
+
[data-theme="light"] .explore-modal-overlay {
|
|
76
|
+
background: rgba(0, 30, 43, 0.4);
|
|
77
|
+
}
|
|
78
|
+
[data-theme="light"] .nav {
|
|
79
|
+
box-shadow: 0 1px 3px rgba(0, 30, 43, 0.06);
|
|
80
|
+
}
|
|
81
|
+
/* Light mode gradient overrides */
|
|
82
|
+
[data-theme="light"] .quant-bar-fill.storage { background: linear-gradient(90deg, #00A35C, #00ED64); }
|
|
83
|
+
[data-theme="light"] .quant-bar-fill.latency { background: linear-gradient(90deg, #016BF8, #0498EC); }
|
|
84
|
+
[data-theme="light"] .quant-meter-fill.perfect { background: linear-gradient(90deg, #00A35C, #00ED64); }
|
|
85
|
+
[data-theme="light"] .quant-meter-fill.good { background: linear-gradient(90deg, #944F01, #FFC010); }
|
|
86
|
+
[data-theme="light"] .quant-meter-fill.degraded { background: linear-gradient(90deg, #DB3030, #FF6960); }
|
|
87
|
+
/* Light mode button text */
|
|
88
|
+
[data-theme="light"] .btn { color: #FFFFFF; }
|
|
89
|
+
[data-theme="light"] .btn:hover { background: #00684A; }
|
|
32
90
|
|
|
33
91
|
html, body { height: 100%; }
|
|
34
92
|
|
|
@@ -63,6 +121,24 @@ body {
|
|
|
63
121
|
|
|
64
122
|
.nav-spacer { flex: 1; }
|
|
65
123
|
|
|
124
|
+
.theme-toggle {
|
|
125
|
+
background: none;
|
|
126
|
+
border: 1px solid var(--border);
|
|
127
|
+
border-radius: 20px;
|
|
128
|
+
padding: 5px 10px;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
font-size: 16px;
|
|
131
|
+
line-height: 1;
|
|
132
|
+
transition: all 0.2s;
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
gap: 4px;
|
|
136
|
+
}
|
|
137
|
+
.theme-toggle:hover {
|
|
138
|
+
border-color: var(--accent);
|
|
139
|
+
background: var(--accent-glow);
|
|
140
|
+
}
|
|
141
|
+
|
|
66
142
|
.status-dot {
|
|
67
143
|
width: 8px; height: 8px;
|
|
68
144
|
border-radius: 50%;
|
|
@@ -168,7 +244,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
168
244
|
|
|
169
245
|
.btn {
|
|
170
246
|
background: var(--accent);
|
|
171
|
-
color:
|
|
247
|
+
color: var(--green-dark);
|
|
172
248
|
border: none;
|
|
173
249
|
padding: 10px 24px;
|
|
174
250
|
border-radius: var(--radius);
|
|
@@ -181,12 +257,12 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
181
257
|
align-items: center;
|
|
182
258
|
gap: 8px;
|
|
183
259
|
}
|
|
184
|
-
.btn:hover { background: #
|
|
260
|
+
.btn:hover { background: #71F6BA; transform: translateY(-1px); }
|
|
185
261
|
.btn:active { transform: translateY(0); }
|
|
186
262
|
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
187
263
|
|
|
188
264
|
.btn-secondary {
|
|
189
|
-
background:
|
|
265
|
+
background: transparent;
|
|
190
266
|
color: var(--accent);
|
|
191
267
|
border: 1px solid var(--accent-dim);
|
|
192
268
|
}
|
|
@@ -319,6 +395,65 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
319
395
|
border-radius: 4px;
|
|
320
396
|
transition: width 0.6s ease, background 0.6s ease;
|
|
321
397
|
}
|
|
398
|
+
.metrics-grid {
|
|
399
|
+
display: grid;
|
|
400
|
+
grid-template-columns: repeat(3, 1fr);
|
|
401
|
+
gap: 16px;
|
|
402
|
+
margin-top: 24px;
|
|
403
|
+
}
|
|
404
|
+
.metric-card {
|
|
405
|
+
background: var(--bg-input);
|
|
406
|
+
border: 1px solid var(--border);
|
|
407
|
+
border-radius: 10px;
|
|
408
|
+
padding: 20px 16px;
|
|
409
|
+
text-align: center;
|
|
410
|
+
transition: border-color 0.2s;
|
|
411
|
+
}
|
|
412
|
+
.metric-card.primary {
|
|
413
|
+
border-color: var(--accent);
|
|
414
|
+
background: var(--accent-glow);
|
|
415
|
+
}
|
|
416
|
+
.metric-card-value {
|
|
417
|
+
font-family: var(--mono);
|
|
418
|
+
font-size: 28px;
|
|
419
|
+
font-weight: 700;
|
|
420
|
+
line-height: 1;
|
|
421
|
+
}
|
|
422
|
+
.metric-card-name {
|
|
423
|
+
font-size: 13px;
|
|
424
|
+
font-weight: 600;
|
|
425
|
+
color: var(--text);
|
|
426
|
+
margin-top: 10px;
|
|
427
|
+
}
|
|
428
|
+
.metric-card-desc {
|
|
429
|
+
font-size: 11px;
|
|
430
|
+
color: var(--text-muted);
|
|
431
|
+
margin-top: 4px;
|
|
432
|
+
line-height: 1.4;
|
|
433
|
+
}
|
|
434
|
+
.metric-bar {
|
|
435
|
+
width: 100%;
|
|
436
|
+
height: 6px;
|
|
437
|
+
background: var(--bg);
|
|
438
|
+
border-radius: 3px;
|
|
439
|
+
margin-top: 12px;
|
|
440
|
+
overflow: hidden;
|
|
441
|
+
}
|
|
442
|
+
.metric-bar-fill {
|
|
443
|
+
height: 100%;
|
|
444
|
+
border-radius: 3px;
|
|
445
|
+
transition: width 0.6s ease, background 0.6s ease;
|
|
446
|
+
}
|
|
447
|
+
.metric-note {
|
|
448
|
+
text-align: center;
|
|
449
|
+
font-size: 12px;
|
|
450
|
+
color: var(--text-muted);
|
|
451
|
+
margin-top: 16px;
|
|
452
|
+
padding: 10px 16px;
|
|
453
|
+
background: var(--bg-input);
|
|
454
|
+
border-radius: 8px;
|
|
455
|
+
line-height: 1.6;
|
|
456
|
+
}
|
|
322
457
|
|
|
323
458
|
/* Search tab */
|
|
324
459
|
.search-results {
|
|
@@ -400,11 +535,10 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
400
535
|
.explore-card:hover {
|
|
401
536
|
border-color: var(--accent);
|
|
402
537
|
transform: translateY(-2px);
|
|
403
|
-
box-shadow: 0 4px 20px rgba(0,
|
|
538
|
+
box-shadow: 0 4px 20px rgba(0, 237, 100, 0.1);
|
|
404
539
|
}
|
|
405
540
|
.explore-card.expanded {
|
|
406
|
-
|
|
407
|
-
cursor: default;
|
|
541
|
+
border-color: var(--accent);
|
|
408
542
|
}
|
|
409
543
|
|
|
410
544
|
.explore-card-icon {
|
|
@@ -423,20 +557,136 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
423
557
|
}
|
|
424
558
|
.explore-card-content {
|
|
425
559
|
display: none;
|
|
426
|
-
|
|
560
|
+
}
|
|
561
|
+
.explore-card-actions {
|
|
562
|
+
display: none;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/* Explore modal */
|
|
566
|
+
.explore-modal-overlay {
|
|
567
|
+
position: fixed;
|
|
568
|
+
inset: 0;
|
|
569
|
+
background: rgba(0, 0, 0, 0.7);
|
|
570
|
+
backdrop-filter: blur(4px);
|
|
571
|
+
z-index: 1000;
|
|
572
|
+
display: flex;
|
|
573
|
+
align-items: center;
|
|
574
|
+
justify-content: center;
|
|
575
|
+
opacity: 0;
|
|
576
|
+
pointer-events: none;
|
|
577
|
+
transition: opacity 0.25s ease;
|
|
578
|
+
}
|
|
579
|
+
.explore-modal-overlay.open {
|
|
580
|
+
opacity: 1;
|
|
581
|
+
pointer-events: auto;
|
|
582
|
+
}
|
|
583
|
+
.explore-modal {
|
|
584
|
+
background: var(--bg-surface);
|
|
585
|
+
border: 1px solid var(--border);
|
|
586
|
+
border-radius: 14px;
|
|
587
|
+
max-width: 720px;
|
|
588
|
+
width: 92%;
|
|
589
|
+
max-height: 85vh;
|
|
590
|
+
overflow-y: auto;
|
|
591
|
+
padding: 0;
|
|
592
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
593
|
+
position: relative;
|
|
594
|
+
animation: exploreModalIn 0.2s ease-out;
|
|
595
|
+
}
|
|
596
|
+
@keyframes exploreModalIn {
|
|
597
|
+
from { opacity: 0; transform: scale(0.95) translateY(10px); }
|
|
598
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
599
|
+
}
|
|
600
|
+
.explore-modal::-webkit-scrollbar { width: 6px; }
|
|
601
|
+
.explore-modal::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
602
|
+
.explore-modal-header {
|
|
603
|
+
display: flex;
|
|
604
|
+
align-items: center;
|
|
605
|
+
gap: 14px;
|
|
606
|
+
padding: 24px 28px 16px;
|
|
607
|
+
border-bottom: 1px solid var(--border);
|
|
608
|
+
}
|
|
609
|
+
.explore-modal-icon { font-size: 32px; }
|
|
610
|
+
.explore-modal-title {
|
|
611
|
+
font-size: 18px;
|
|
612
|
+
font-weight: 600;
|
|
613
|
+
color: var(--text);
|
|
614
|
+
}
|
|
615
|
+
.explore-modal-summary {
|
|
616
|
+
font-size: 13px;
|
|
617
|
+
color: var(--text-dim);
|
|
618
|
+
margin-top: 2px;
|
|
619
|
+
}
|
|
620
|
+
.explore-modal-close {
|
|
621
|
+
position: absolute;
|
|
622
|
+
top: 16px; right: 18px;
|
|
623
|
+
background: none;
|
|
624
|
+
border: none;
|
|
625
|
+
color: var(--text-dim);
|
|
626
|
+
font-size: 22px;
|
|
627
|
+
cursor: pointer;
|
|
628
|
+
padding: 4px 8px;
|
|
629
|
+
border-radius: 6px;
|
|
630
|
+
transition: all 0.15s;
|
|
631
|
+
z-index: 1;
|
|
632
|
+
}
|
|
633
|
+
.explore-modal-close:hover { background: rgba(255,255,255,0.05); color: var(--text); }
|
|
634
|
+
.explore-modal-body {
|
|
635
|
+
padding: 20px 28px 24px;
|
|
427
636
|
font-size: 14px;
|
|
428
|
-
line-height: 1.
|
|
637
|
+
line-height: 1.75;
|
|
429
638
|
color: var(--text);
|
|
430
639
|
white-space: pre-wrap;
|
|
431
640
|
}
|
|
432
|
-
.explore-
|
|
433
|
-
|
|
434
|
-
.explore-card-actions {
|
|
435
|
-
display: none;
|
|
641
|
+
.explore-modal-links {
|
|
436
642
|
margin-top: 16px;
|
|
643
|
+
padding-top: 14px;
|
|
644
|
+
border-top: 1px solid var(--border);
|
|
645
|
+
}
|
|
646
|
+
.explore-modal-links-title {
|
|
647
|
+
font-size: 11px;
|
|
648
|
+
font-weight: 600;
|
|
649
|
+
color: var(--accent);
|
|
650
|
+
text-transform: uppercase;
|
|
651
|
+
letter-spacing: 0.5px;
|
|
652
|
+
margin-bottom: 6px;
|
|
653
|
+
}
|
|
654
|
+
.explore-modal-links a {
|
|
655
|
+
display: block;
|
|
656
|
+
color: var(--blue);
|
|
657
|
+
font-size: 12px;
|
|
658
|
+
word-break: break-all;
|
|
659
|
+
margin-bottom: 4px;
|
|
660
|
+
text-decoration: none;
|
|
661
|
+
}
|
|
662
|
+
.explore-modal-links a:hover { text-decoration: underline; }
|
|
663
|
+
.explore-modal-tryit {
|
|
664
|
+
margin-top: 14px;
|
|
665
|
+
padding-top: 14px;
|
|
666
|
+
border-top: 1px solid var(--border);
|
|
667
|
+
}
|
|
668
|
+
.explore-modal-tryit-title {
|
|
669
|
+
font-size: 11px;
|
|
670
|
+
font-weight: 600;
|
|
671
|
+
color: var(--accent);
|
|
672
|
+
text-transform: uppercase;
|
|
673
|
+
letter-spacing: 0.5px;
|
|
674
|
+
margin-bottom: 8px;
|
|
675
|
+
}
|
|
676
|
+
.explore-modal-tryit-cmd {
|
|
677
|
+
font-family: var(--mono);
|
|
678
|
+
font-size: 12px;
|
|
679
|
+
color: var(--text-dim);
|
|
680
|
+
background: var(--bg);
|
|
681
|
+
padding: 6px 10px;
|
|
682
|
+
border-radius: 5px;
|
|
683
|
+
margin-bottom: 4px;
|
|
684
|
+
}
|
|
685
|
+
.explore-modal-actions {
|
|
686
|
+
display: flex;
|
|
437
687
|
gap: 8px;
|
|
688
|
+
padding: 0 28px 24px;
|
|
438
689
|
}
|
|
439
|
-
.explore-card.expanded .explore-card-actions { display: flex; }
|
|
440
690
|
|
|
441
691
|
/* Benchmark tab */
|
|
442
692
|
.bench-panels { display: flex; gap: 8px; margin-bottom: 16px; }
|
|
@@ -490,7 +740,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
490
740
|
padding: 0 10px;
|
|
491
741
|
font-family: var(--mono);
|
|
492
742
|
font-size: 12px;
|
|
493
|
-
color:
|
|
743
|
+
color: var(--green-dark);
|
|
494
744
|
font-weight: 600;
|
|
495
745
|
white-space: nowrap;
|
|
496
746
|
}
|
|
@@ -571,10 +821,10 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
571
821
|
transition: width 0.8s cubic-bezier(0.22, 1, 0.36, 1);
|
|
572
822
|
display: flex; align-items: center; padding: 0 10px;
|
|
573
823
|
font-family: var(--mono); font-size: 12px; font-weight: 600;
|
|
574
|
-
color:
|
|
824
|
+
color: var(--green-dark); white-space: nowrap; min-width: fit-content;
|
|
575
825
|
}
|
|
576
|
-
.quant-bar-fill.storage { background: linear-gradient(90deg, #
|
|
577
|
-
.quant-bar-fill.latency { background: linear-gradient(90deg, #
|
|
826
|
+
.quant-bar-fill.storage { background: linear-gradient(90deg, #00ED64, #71F6BA); }
|
|
827
|
+
.quant-bar-fill.latency { background: linear-gradient(90deg, #0498EC, #016BF8); }
|
|
578
828
|
.quant-bar-badge {
|
|
579
829
|
position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
|
|
580
830
|
font-size: 12px; color: var(--text-dim); font-family: var(--mono);
|
|
@@ -599,9 +849,9 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
599
849
|
height: 100%; border-radius: 5px;
|
|
600
850
|
transition: width 0.8s cubic-bezier(0.22, 1, 0.36, 1);
|
|
601
851
|
}
|
|
602
|
-
.quant-meter-fill.perfect { background: linear-gradient(90deg, #
|
|
603
|
-
.quant-meter-fill.good { background: linear-gradient(90deg, #
|
|
604
|
-
.quant-meter-fill.degraded { background: linear-gradient(90deg, #
|
|
852
|
+
.quant-meter-fill.perfect { background: linear-gradient(90deg, #00ED64, #71F6BA); }
|
|
853
|
+
.quant-meter-fill.good { background: linear-gradient(90deg, #FFC010, #FFEC9E); }
|
|
854
|
+
.quant-meter-fill.degraded { background: linear-gradient(90deg, #FF6960, #FFCDC7); }
|
|
605
855
|
.quant-meter-detail { font-size: 11px; color: var(--text-muted); margin-top: 4px; font-family: var(--mono); }
|
|
606
856
|
|
|
607
857
|
.quant-rank-cols {
|
|
@@ -695,7 +945,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
695
945
|
font-weight: 600;
|
|
696
946
|
}
|
|
697
947
|
.cost-mode-btn:hover:not(.active) {
|
|
698
|
-
background: rgba(0,
|
|
948
|
+
background: rgba(0, 237, 100, 0.1);
|
|
699
949
|
color: var(--text);
|
|
700
950
|
}
|
|
701
951
|
.cost-select {
|
|
@@ -809,7 +1059,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
809
1059
|
.cost-strategy-total-value { font-family: var(--mono); font-size: 18px; font-weight: 700; color: var(--accent); }
|
|
810
1060
|
.cost-savings {
|
|
811
1061
|
font-size: 11px;
|
|
812
|
-
color:
|
|
1062
|
+
color: var(--success);
|
|
813
1063
|
font-weight: 600;
|
|
814
1064
|
margin-top: 6px;
|
|
815
1065
|
text-align: right;
|
|
@@ -835,7 +1085,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
835
1085
|
border-bottom: 1px solid rgba(42, 53, 80, 0.3);
|
|
836
1086
|
font-family: var(--mono);
|
|
837
1087
|
}
|
|
838
|
-
.cost-table tr:hover { background: rgba(0,
|
|
1088
|
+
.cost-table tr:hover { background: rgba(0, 237, 100, 0.03); }
|
|
839
1089
|
.cost-highlight {
|
|
840
1090
|
color: var(--accent);
|
|
841
1091
|
font-weight: 600;
|
|
@@ -862,12 +1112,136 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
862
1112
|
.cost-tip {
|
|
863
1113
|
font-size: 12px;
|
|
864
1114
|
color: var(--text-muted);
|
|
865
|
-
background: rgba(0,
|
|
1115
|
+
background: rgba(0, 237, 100, 0.05);
|
|
866
1116
|
border-left: 3px solid var(--accent);
|
|
867
1117
|
padding: 10px 14px;
|
|
868
1118
|
border-radius: 0 6px 6px 0;
|
|
869
1119
|
margin-top: 16px;
|
|
870
1120
|
}
|
|
1121
|
+
.cost-help-btn {
|
|
1122
|
+
display: inline-flex;
|
|
1123
|
+
align-items: center;
|
|
1124
|
+
justify-content: center;
|
|
1125
|
+
width: 22px; height: 22px;
|
|
1126
|
+
border-radius: 50%;
|
|
1127
|
+
border: 1.5px solid var(--accent);
|
|
1128
|
+
background: transparent;
|
|
1129
|
+
color: var(--accent);
|
|
1130
|
+
font-size: 13px;
|
|
1131
|
+
font-weight: 700;
|
|
1132
|
+
cursor: pointer;
|
|
1133
|
+
margin-left: 8px;
|
|
1134
|
+
transition: all 0.2s;
|
|
1135
|
+
vertical-align: middle;
|
|
1136
|
+
font-family: var(--mono);
|
|
1137
|
+
line-height: 1;
|
|
1138
|
+
}
|
|
1139
|
+
.cost-help-btn:hover {
|
|
1140
|
+
background: var(--accent);
|
|
1141
|
+
color: var(--bg);
|
|
1142
|
+
box-shadow: 0 0 10px var(--accent-glow);
|
|
1143
|
+
}
|
|
1144
|
+
.cost-modal-overlay {
|
|
1145
|
+
position: fixed;
|
|
1146
|
+
inset: 0;
|
|
1147
|
+
background: rgba(0, 0, 0, 0.7);
|
|
1148
|
+
backdrop-filter: blur(4px);
|
|
1149
|
+
z-index: 1000;
|
|
1150
|
+
display: flex;
|
|
1151
|
+
align-items: center;
|
|
1152
|
+
justify-content: center;
|
|
1153
|
+
opacity: 0;
|
|
1154
|
+
pointer-events: none;
|
|
1155
|
+
transition: opacity 0.25s ease;
|
|
1156
|
+
}
|
|
1157
|
+
.cost-modal-overlay.open {
|
|
1158
|
+
opacity: 1;
|
|
1159
|
+
pointer-events: auto;
|
|
1160
|
+
}
|
|
1161
|
+
.cost-modal {
|
|
1162
|
+
background: var(--bg-surface);
|
|
1163
|
+
border: 1px solid var(--border);
|
|
1164
|
+
border-radius: 14px;
|
|
1165
|
+
max-width: 680px;
|
|
1166
|
+
width: 90%;
|
|
1167
|
+
max-height: 85vh;
|
|
1168
|
+
overflow-y: auto;
|
|
1169
|
+
padding: 32px;
|
|
1170
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
1171
|
+
position: relative;
|
|
1172
|
+
}
|
|
1173
|
+
.cost-modal::-webkit-scrollbar { width: 6px; }
|
|
1174
|
+
.cost-modal::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
1175
|
+
.cost-modal-close {
|
|
1176
|
+
position: absolute;
|
|
1177
|
+
top: 14px; right: 16px;
|
|
1178
|
+
background: none;
|
|
1179
|
+
border: none;
|
|
1180
|
+
color: var(--text-dim);
|
|
1181
|
+
font-size: 22px;
|
|
1182
|
+
cursor: pointer;
|
|
1183
|
+
padding: 4px 8px;
|
|
1184
|
+
border-radius: 6px;
|
|
1185
|
+
transition: all 0.15s;
|
|
1186
|
+
}
|
|
1187
|
+
.cost-modal-close:hover { background: rgba(255,255,255,0.05); color: var(--text); }
|
|
1188
|
+
.cost-modal h2 {
|
|
1189
|
+
font-size: 18px;
|
|
1190
|
+
color: var(--text);
|
|
1191
|
+
margin: 0 0 20px;
|
|
1192
|
+
display: flex;
|
|
1193
|
+
align-items: center;
|
|
1194
|
+
gap: 10px;
|
|
1195
|
+
}
|
|
1196
|
+
.cost-modal h3 {
|
|
1197
|
+
font-size: 14px;
|
|
1198
|
+
color: var(--accent);
|
|
1199
|
+
margin: 22px 0 10px;
|
|
1200
|
+
text-transform: uppercase;
|
|
1201
|
+
letter-spacing: 0.5px;
|
|
1202
|
+
}
|
|
1203
|
+
.cost-modal p, .cost-modal li {
|
|
1204
|
+
font-size: 13px;
|
|
1205
|
+
color: var(--text-dim);
|
|
1206
|
+
line-height: 1.7;
|
|
1207
|
+
}
|
|
1208
|
+
.cost-modal ul { padding-left: 20px; margin: 6px 0; }
|
|
1209
|
+
.cost-modal li { margin-bottom: 4px; }
|
|
1210
|
+
.cost-modal code {
|
|
1211
|
+
background: var(--bg-input);
|
|
1212
|
+
padding: 2px 7px;
|
|
1213
|
+
border-radius: 4px;
|
|
1214
|
+
font-size: 12px;
|
|
1215
|
+
color: var(--accent);
|
|
1216
|
+
font-family: var(--mono);
|
|
1217
|
+
}
|
|
1218
|
+
.cost-modal .formula {
|
|
1219
|
+
background: var(--bg-input);
|
|
1220
|
+
border: 1px solid var(--border);
|
|
1221
|
+
border-radius: 8px;
|
|
1222
|
+
padding: 14px 18px;
|
|
1223
|
+
margin: 10px 0;
|
|
1224
|
+
font-family: var(--mono);
|
|
1225
|
+
font-size: 13px;
|
|
1226
|
+
color: var(--text);
|
|
1227
|
+
line-height: 1.8;
|
|
1228
|
+
}
|
|
1229
|
+
.cost-modal .formula .label {
|
|
1230
|
+
color: var(--text-muted);
|
|
1231
|
+
font-size: 11px;
|
|
1232
|
+
}
|
|
1233
|
+
.cost-modal .formula .accent { color: var(--accent); font-weight: 600; }
|
|
1234
|
+
.cost-modal .example {
|
|
1235
|
+
background: rgba(0, 237, 100, 0.05);
|
|
1236
|
+
border-left: 3px solid var(--accent);
|
|
1237
|
+
border-radius: 0 8px 8px 0;
|
|
1238
|
+
padding: 12px 16px;
|
|
1239
|
+
margin: 12px 0;
|
|
1240
|
+
font-size: 12px;
|
|
1241
|
+
color: var(--text-dim);
|
|
1242
|
+
font-family: var(--mono);
|
|
1243
|
+
line-height: 1.8;
|
|
1244
|
+
}
|
|
871
1245
|
|
|
872
1246
|
/* History chart */
|
|
873
1247
|
.history-empty {
|
|
@@ -954,7 +1328,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
954
1328
|
margin-bottom: 8px;
|
|
955
1329
|
}
|
|
956
1330
|
.about-text { font-size: 14px; line-height: 1.8; color: var(--text); }
|
|
957
|
-
.about-text a { color: var(--
|
|
1331
|
+
.about-text a { color: var(--blue); text-decoration: none; }
|
|
958
1332
|
.about-text a:hover { text-decoration: underline; }
|
|
959
1333
|
.about-disclaimer {
|
|
960
1334
|
background: rgba(255, 215, 61, 0.08);
|
|
@@ -993,6 +1367,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
993
1367
|
<span class="option-label">Default Model</span>
|
|
994
1368
|
<select id="globalModel" class="nav-model-select"></select>
|
|
995
1369
|
</div>
|
|
1370
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark mode">🌙</button>
|
|
996
1371
|
<div style="display:flex;align-items:center;gap:6px;">
|
|
997
1372
|
<div class="status-dot" id="statusDot"></div>
|
|
998
1373
|
<span class="status-label" id="statusLabel">Checking...</span>
|
|
@@ -1112,7 +1487,9 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1112
1487
|
<div class="similarity-bar-inner" id="simBar" style="width:0%"></div>
|
|
1113
1488
|
</div>
|
|
1114
1489
|
</div>
|
|
1115
|
-
<div
|
|
1490
|
+
<div class="metrics-grid" id="metricsGrid"></div>
|
|
1491
|
+
<div class="metric-note" id="metricNote"></div>
|
|
1492
|
+
<div id="compareStats" style="text-align:center;margin-top:16px;"></div>
|
|
1116
1493
|
</div>
|
|
1117
1494
|
</div>
|
|
1118
1495
|
</div>
|
|
@@ -1351,13 +1728,13 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1351
1728
|
<!-- ── Cost Panel ── -->
|
|
1352
1729
|
<div class="bench-view" id="bench-cost">
|
|
1353
1730
|
<div class="card">
|
|
1354
|
-
<div class="card-title">💰 RAG Cost Calculator
|
|
1731
|
+
<div class="card-title">💰 RAG Cost Calculator <button class="cost-help-btn" id="costHelpBtn" title="How the math works">?</button></div>
|
|
1355
1732
|
|
|
1356
1733
|
<!-- Mode toggle -->
|
|
1357
1734
|
<div style="margin-bottom: 20px;">
|
|
1358
1735
|
<div class="cost-mode-toggle">
|
|
1359
|
-
<button class="cost-mode-btn active" data-mode="simple"
|
|
1360
|
-
<button class="cost-mode-btn" data-mode="rag"
|
|
1736
|
+
<button class="cost-mode-btn active" data-mode="simple" id="costModeSimple">Simple</button>
|
|
1737
|
+
<button class="cost-mode-btn" data-mode="rag" id="costModeRag">RAG Planner</button>
|
|
1361
1738
|
</div>
|
|
1362
1739
|
</div>
|
|
1363
1740
|
|
|
@@ -1521,7 +1898,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1521
1898
|
<div class="about-section-title">What You Can Do Here</div>
|
|
1522
1899
|
<div class="about-text">
|
|
1523
1900
|
<strong>⚡ Embed</strong> — Generate vector embeddings for any text<br>
|
|
1524
|
-
<strong>⚖️ Compare</strong> — Measure cosine
|
|
1901
|
+
<strong>⚖️ Compare</strong> — Measure similarity with cosine, dot product & euclidean distance<br>
|
|
1525
1902
|
<strong>🔍 Search</strong> — Semantic search with optional reranking<br>
|
|
1526
1903
|
<strong>⏱ Benchmark</strong> — Compare model latency, ranking quality, and costs<br>
|
|
1527
1904
|
<strong>📚 Explore</strong> — Learn about embeddings, vector search, RAG, and more
|
|
@@ -1554,12 +1931,61 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1554
1931
|
<div class="explore-grid" id="exploreGrid"></div>
|
|
1555
1932
|
</div>
|
|
1556
1933
|
|
|
1934
|
+
<!-- Explore Concept Modal -->
|
|
1935
|
+
<div class="explore-modal-overlay" id="exploreModal">
|
|
1936
|
+
<div class="explore-modal">
|
|
1937
|
+
<button class="explore-modal-close" id="exploreModalClose">×</button>
|
|
1938
|
+
<div class="explore-modal-header">
|
|
1939
|
+
<div class="explore-modal-icon" id="exploreModalIcon"></div>
|
|
1940
|
+
<div>
|
|
1941
|
+
<div class="explore-modal-title" id="exploreModalTitle"></div>
|
|
1942
|
+
<div class="explore-modal-summary" id="exploreModalSummary"></div>
|
|
1943
|
+
</div>
|
|
1944
|
+
</div>
|
|
1945
|
+
<div class="explore-modal-body" id="exploreModalBody"></div>
|
|
1946
|
+
<div class="explore-modal-actions" id="exploreModalActions"></div>
|
|
1947
|
+
</div>
|
|
1948
|
+
</div>
|
|
1949
|
+
|
|
1557
1950
|
</div><!-- .main -->
|
|
1558
1951
|
|
|
1952
|
+
<script>
|
|
1953
|
+
// Apply saved theme immediately to prevent flash
|
|
1954
|
+
(function() {
|
|
1955
|
+
var t = localStorage.getItem('vai-theme') || 'dark';
|
|
1956
|
+
if (t === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
|
1957
|
+
})();
|
|
1958
|
+
</script>
|
|
1559
1959
|
<script>
|
|
1560
1960
|
(function() {
|
|
1561
1961
|
'use strict';
|
|
1562
1962
|
|
|
1963
|
+
// ── Theme Toggle ──
|
|
1964
|
+
function initThemeToggle() {
|
|
1965
|
+
const toggle = document.getElementById('themeToggle');
|
|
1966
|
+
const saved = localStorage.getItem('vai-theme') || 'dark';
|
|
1967
|
+
let current = saved;
|
|
1968
|
+
|
|
1969
|
+
function applyTheme(theme) {
|
|
1970
|
+
current = theme;
|
|
1971
|
+
if (theme === 'light') {
|
|
1972
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
1973
|
+
toggle.textContent = '☀️';
|
|
1974
|
+
toggle.title = 'Switch to dark mode';
|
|
1975
|
+
} else {
|
|
1976
|
+
document.documentElement.removeAttribute('data-theme');
|
|
1977
|
+
toggle.textContent = '🌙';
|
|
1978
|
+
toggle.title = 'Switch to light mode';
|
|
1979
|
+
}
|
|
1980
|
+
localStorage.setItem('vai-theme', theme);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
applyTheme(current);
|
|
1984
|
+
toggle.addEventListener('click', () => {
|
|
1985
|
+
applyTheme(current === 'dark' ? 'light' : 'dark');
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1563
1989
|
// ── State ──
|
|
1564
1990
|
let allModels = [];
|
|
1565
1991
|
let embedModels = [];
|
|
@@ -1801,27 +2227,73 @@ window.doCompare = async function() {
|
|
|
1801
2227
|
const dimensions = dims ? parseInt(dims, 10) : undefined;
|
|
1802
2228
|
|
|
1803
2229
|
const data = await apiPost('/api/similarity', { texts: [a, b], model, dimensions });
|
|
1804
|
-
const sim = data.matrix[0][1];
|
|
1805
|
-
const pct = Math.max(0, sim * 100);
|
|
1806
2230
|
|
|
1807
|
-
//
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
2231
|
+
// Get raw embeddings for all metrics
|
|
2232
|
+
const vecA = data.embeddings[0].embedding;
|
|
2233
|
+
const vecB = data.embeddings[1].embedding;
|
|
2234
|
+
|
|
2235
|
+
const cosine = cosineSim(vecA, vecB);
|
|
2236
|
+
const dot = dotProduct(vecA, vecB);
|
|
2237
|
+
const euclid = euclideanDist(vecA, vecB);
|
|
2238
|
+
|
|
2239
|
+
// Hero display — cosine similarity
|
|
2240
|
+
const cosinePct = Math.max(0, cosine * 100);
|
|
2241
|
+
let cosineColor;
|
|
2242
|
+
if (cosine > 0.7) cosineColor = 'var(--green)';
|
|
2243
|
+
else if (cosine > 0.4) cosineColor = 'var(--yellow)';
|
|
2244
|
+
else cosineColor = 'var(--red)';
|
|
1812
2245
|
|
|
1813
2246
|
const scoreEl = document.getElementById('simScore');
|
|
1814
|
-
scoreEl.textContent =
|
|
1815
|
-
scoreEl.style.color =
|
|
2247
|
+
scoreEl.textContent = cosine.toFixed(4);
|
|
2248
|
+
scoreEl.style.color = cosineColor;
|
|
1816
2249
|
|
|
1817
2250
|
const barEl = document.getElementById('simBar');
|
|
1818
|
-
barEl.style.width =
|
|
1819
|
-
barEl.style.background =
|
|
2251
|
+
barEl.style.width = cosinePct + '%';
|
|
2252
|
+
barEl.style.background = cosineColor;
|
|
2253
|
+
|
|
2254
|
+
// Metric cards — all three
|
|
2255
|
+
const dotColor = dot > 0.7 ? 'var(--green)' : dot > 0.4 ? 'var(--yellow)' : 'var(--red)';
|
|
2256
|
+
// Euclidean: 0 = identical, ~2 = max for normalized vectors. Invert for color.
|
|
2257
|
+
const euclidColor = euclid < 0.6 ? 'var(--green)' : euclid < 1.0 ? 'var(--yellow)' : 'var(--red)';
|
|
2258
|
+
// For euclidean bar, invert: 0 dist = 100% bar, 2.0 dist = 0%
|
|
2259
|
+
const euclidPct = Math.max(0, Math.min(100, (1 - euclid / 2) * 100));
|
|
2260
|
+
|
|
2261
|
+
const metricsEl = document.getElementById('metricsGrid');
|
|
2262
|
+
metricsEl.innerHTML = `
|
|
2263
|
+
<div class="metric-card primary">
|
|
2264
|
+
<div class="metric-card-value" style="color:${cosineColor}">${cosine.toFixed(4)}</div>
|
|
2265
|
+
<div class="metric-card-name">Cosine Similarity</div>
|
|
2266
|
+
<div class="metric-card-desc">Angle between vectors (−1 to 1). Standard for semantic search.</div>
|
|
2267
|
+
<div class="metric-bar"><div class="metric-bar-fill" style="width:${cosinePct}%;background:${cosineColor}"></div></div>
|
|
2268
|
+
</div>
|
|
2269
|
+
<div class="metric-card">
|
|
2270
|
+
<div class="metric-card-value" style="color:${dotColor}">${dot.toFixed(4)}</div>
|
|
2271
|
+
<div class="metric-card-name">Dot Product</div>
|
|
2272
|
+
<div class="metric-card-desc">Sum of element-wise products. Equals cosine for normalized vectors.</div>
|
|
2273
|
+
<div class="metric-bar"><div class="metric-bar-fill" style="width:${Math.max(0, dot * 100)}%;background:${dotColor}"></div></div>
|
|
2274
|
+
</div>
|
|
2275
|
+
<div class="metric-card">
|
|
2276
|
+
<div class="metric-card-value" style="color:${euclidColor}">${euclid.toFixed(4)}</div>
|
|
2277
|
+
<div class="metric-card-name">Euclidean Distance</div>
|
|
2278
|
+
<div class="metric-card-desc">Straight-line distance (0 = identical). Lower is more similar.</div>
|
|
2279
|
+
<div class="metric-bar"><div class="metric-bar-fill" style="width:${euclidPct}%;background:${euclidColor}"></div></div>
|
|
2280
|
+
</div>
|
|
2281
|
+
`;
|
|
2282
|
+
|
|
2283
|
+
// Insight note
|
|
2284
|
+
const noteEl = document.getElementById('metricNote');
|
|
2285
|
+
const diff = Math.abs(cosine - dot);
|
|
2286
|
+
if (diff < 0.001) {
|
|
2287
|
+
noteEl.innerHTML = '💡 <strong>Cosine ≈ Dot Product</strong> — these vectors are L2-normalized (as Voyage AI models produce), so cosine similarity and dot product give identical results. Euclidean distance is <code>√(2 − 2·cosine)</code> for normalized vectors.';
|
|
2288
|
+
} else {
|
|
2289
|
+
noteEl.innerHTML = '💡 Cosine and dot product differ because these vectors are not perfectly L2-normalized. Atlas Vector Search uses cosine by default.';
|
|
2290
|
+
}
|
|
1820
2291
|
|
|
1821
2292
|
// Stats
|
|
1822
2293
|
const statsEl = document.getElementById('compareStats');
|
|
1823
2294
|
statsEl.innerHTML = `
|
|
1824
2295
|
<span class="stat"><span class="stat-label">Model</span><span class="stat-value">${data.model}</span></span>
|
|
2296
|
+
<span class="stat"><span class="stat-label">Dimensions</span><span class="stat-value">${vecA.length}</span></span>
|
|
1825
2297
|
<span class="stat"><span class="stat-label">Tokens</span><span class="stat-value">${data.usage?.total_tokens || '—'}</span></span>
|
|
1826
2298
|
`;
|
|
1827
2299
|
|
|
@@ -1898,6 +2370,18 @@ function cosineSim(a, b) {
|
|
|
1898
2370
|
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
1899
2371
|
}
|
|
1900
2372
|
|
|
2373
|
+
function dotProduct(a, b) {
|
|
2374
|
+
let sum = 0;
|
|
2375
|
+
for (let i = 0; i < a.length; i++) sum += a[i] * b[i];
|
|
2376
|
+
return sum;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
function euclideanDist(a, b) {
|
|
2380
|
+
let sum = 0;
|
|
2381
|
+
for (let i = 0; i < a.length; i++) sum += (a[i] - b[i]) ** 2;
|
|
2382
|
+
return Math.sqrt(sum);
|
|
2383
|
+
}
|
|
2384
|
+
|
|
1901
2385
|
function renderSearchResults(embResults, rerankResults) {
|
|
1902
2386
|
const grid = document.getElementById('searchResultGrid');
|
|
1903
2387
|
grid.innerHTML = '';
|
|
@@ -2003,40 +2487,64 @@ function buildExploreCards() {
|
|
|
2003
2487
|
card.className = 'explore-card';
|
|
2004
2488
|
card.dataset.key = key;
|
|
2005
2489
|
|
|
2006
|
-
// Build links HTML
|
|
2007
|
-
let linksHtml = '';
|
|
2008
|
-
if (concept.links && concept.links.length > 0) {
|
|
2009
|
-
linksHtml = '<div style="margin-top:12px;"><strong style="color:var(--accent);font-size:12px;">LEARN MORE</strong><br>' +
|
|
2010
|
-
concept.links.map(url => `<a href="${escapeHtml(url)}" target="_blank" rel="noopener" style="color:var(--accent);font-size:12px;word-break:break-all;">${escapeHtml(url)}</a>`).join('<br>') +
|
|
2011
|
-
'</div>';
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
// Build try-it HTML
|
|
2015
|
-
let tryItHtml = '';
|
|
2016
|
-
if (concept.tryIt && concept.tryIt.length > 0) {
|
|
2017
|
-
tryItHtml = '<div style="margin-top:12px;"><strong style="color:var(--accent);font-size:12px;">TRY IT</strong>' +
|
|
2018
|
-
concept.tryIt.map(cmd => `<div style="font-family:var(--mono);font-size:12px;color:var(--text-dim);background:var(--bg);padding:4px 8px;border-radius:4px;margin-top:4px;">$ ${escapeHtml(cmd)}</div>`).join('') +
|
|
2019
|
-
'</div>';
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
2490
|
card.innerHTML = `
|
|
2023
2491
|
<div class="explore-card-icon">${meta.icon}</div>
|
|
2024
2492
|
<div class="explore-card-title">${escapeHtml(concept.title)}</div>
|
|
2025
2493
|
<div class="explore-card-summary">${escapeHtml(concept.summary)}</div>
|
|
2026
|
-
<div class="explore-card-content">${escapeHtml(concept.content)}${linksHtml}${tryItHtml}</div>
|
|
2027
|
-
<div class="explore-card-actions">
|
|
2028
|
-
<button class="btn btn-small" onclick="tryTopic('${escapeHtml(key)}')">Try it in playground →</button>
|
|
2029
|
-
<button class="btn btn-secondary btn-small" onclick="collapseTopic(this)">Collapse</button>
|
|
2030
|
-
</div>
|
|
2031
2494
|
`;
|
|
2032
|
-
card.addEventListener('click',
|
|
2033
|
-
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'A') return;
|
|
2034
|
-
if (!this.classList.contains('expanded')) {
|
|
2035
|
-
this.classList.add('expanded');
|
|
2036
|
-
}
|
|
2037
|
-
});
|
|
2495
|
+
card.addEventListener('click', () => openExploreModal(key));
|
|
2038
2496
|
grid.appendChild(card);
|
|
2039
2497
|
}
|
|
2498
|
+
|
|
2499
|
+
// Modal close handlers
|
|
2500
|
+
const modal = document.getElementById('exploreModal');
|
|
2501
|
+
document.getElementById('exploreModalClose').addEventListener('click', closeExploreModal);
|
|
2502
|
+
modal.addEventListener('click', (e) => { if (e.target === modal) closeExploreModal(); });
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
function openExploreModal(key) {
|
|
2506
|
+
const concept = exploreConcepts[key];
|
|
2507
|
+
if (!concept) return;
|
|
2508
|
+
const meta = CONCEPT_META[key] || { icon: '📚', tab: 'embed' };
|
|
2509
|
+
|
|
2510
|
+
document.getElementById('exploreModalIcon').textContent = meta.icon;
|
|
2511
|
+
document.getElementById('exploreModalTitle').textContent = concept.title;
|
|
2512
|
+
document.getElementById('exploreModalSummary').textContent = concept.summary;
|
|
2513
|
+
|
|
2514
|
+
// Build body: content + links + tryIt
|
|
2515
|
+
let bodyHtml = escapeHtml(concept.content);
|
|
2516
|
+
|
|
2517
|
+
if (concept.links && concept.links.length > 0) {
|
|
2518
|
+
bodyHtml += '<div class="explore-modal-links">' +
|
|
2519
|
+
'<div class="explore-modal-links-title">Learn More</div>' +
|
|
2520
|
+
concept.links.map(url =>
|
|
2521
|
+
`<a href="${escapeHtml(url)}" target="_blank" rel="noopener">${escapeHtml(url)}</a>`
|
|
2522
|
+
).join('') + '</div>';
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
if (concept.tryIt && concept.tryIt.length > 0) {
|
|
2526
|
+
bodyHtml += '<div class="explore-modal-tryit">' +
|
|
2527
|
+
'<div class="explore-modal-tryit-title">Try It</div>' +
|
|
2528
|
+
concept.tryIt.map(cmd =>
|
|
2529
|
+
`<div class="explore-modal-tryit-cmd">$ ${escapeHtml(cmd)}</div>`
|
|
2530
|
+
).join('') + '</div>';
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
document.getElementById('exploreModalBody').innerHTML = bodyHtml;
|
|
2534
|
+
|
|
2535
|
+
// Actions
|
|
2536
|
+
const actionsEl = document.getElementById('exploreModalActions');
|
|
2537
|
+
actionsEl.innerHTML = `<button class="btn btn-small" id="exploreModalTry">Try it in playground →</button>`;
|
|
2538
|
+
actionsEl.querySelector('#exploreModalTry').addEventListener('click', () => {
|
|
2539
|
+
closeExploreModal();
|
|
2540
|
+
if (meta.tab) switchTab(meta.tab);
|
|
2541
|
+
});
|
|
2542
|
+
|
|
2543
|
+
document.getElementById('exploreModal').classList.add('open');
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
function closeExploreModal() {
|
|
2547
|
+
document.getElementById('exploreModal').classList.remove('open');
|
|
2040
2548
|
}
|
|
2041
2549
|
|
|
2042
2550
|
window.tryTopic = function(key) {
|
|
@@ -2044,10 +2552,6 @@ window.tryTopic = function(key) {
|
|
|
2044
2552
|
if (meta) switchTab(meta.tab);
|
|
2045
2553
|
};
|
|
2046
2554
|
|
|
2047
|
-
window.collapseTopic = function(btn) {
|
|
2048
|
-
btn.closest('.explore-card').classList.remove('expanded');
|
|
2049
|
-
};
|
|
2050
|
-
|
|
2051
2555
|
window.filterExplore = function() {
|
|
2052
2556
|
const q = document.getElementById('exploreSearch').value.toLowerCase().trim();
|
|
2053
2557
|
document.querySelectorAll('#exploreGrid .explore-card').forEach(card => {
|
|
@@ -2115,8 +2619,8 @@ const BENCH_SAMPLE_TEXTS = [
|
|
|
2115
2619
|
];
|
|
2116
2620
|
|
|
2117
2621
|
const MODEL_COLORS = [
|
|
2118
|
-
'#
|
|
2119
|
-
'#
|
|
2622
|
+
'#00ED64', '#71F6BA', '#0498EC', '#B45AF2', '#FFC010',
|
|
2623
|
+
'#FF6960', '#B45AF2', '#FFC010', '#016BF8', '#C0FAE6',
|
|
2120
2624
|
];
|
|
2121
2625
|
|
|
2122
2626
|
window.doBenchLatency = async function() {
|
|
@@ -2457,7 +2961,7 @@ window.doBenchQuantization = async function() {
|
|
|
2457
2961
|
const baseline = completed.find(r => r.dtype === 'float') || completed[0];
|
|
2458
2962
|
const maxBytes = Math.max(...completed.map(r => r.bytesPerVec));
|
|
2459
2963
|
const maxLatency = Math.max(...completed.map(r => r.latency));
|
|
2460
|
-
const DTYPE_COLORS = { float: '#
|
|
2964
|
+
const DTYPE_COLORS = { float: '#00ED64', int8: '#71F6BA', uint8: '#0498EC', ubinary: '#FFC010', binary: '#FF6960' };
|
|
2461
2965
|
|
|
2462
2966
|
// ── Storage Bar Chart ──
|
|
2463
2967
|
let storageHTML = '';
|
|
@@ -2468,7 +2972,7 @@ window.doBenchQuantization = async function() {
|
|
|
2468
2972
|
const savings = r.bytesPerVec < baseline.bytesPerVec
|
|
2469
2973
|
? `${(baseline.bytesPerVec / r.bytesPerVec).toFixed(0)}× smaller`
|
|
2470
2974
|
: 'baseline';
|
|
2471
|
-
const color = DTYPE_COLORS[r.dtype] || '#
|
|
2975
|
+
const color = DTYPE_COLORS[r.dtype] || '#0498EC';
|
|
2472
2976
|
storageHTML += `<div class="quant-bar-group">
|
|
2473
2977
|
<div class="quant-bar-label">
|
|
2474
2978
|
<span class="dtype-name">${r.dtype}</span>
|
|
@@ -2486,7 +2990,7 @@ window.doBenchQuantization = async function() {
|
|
|
2486
2990
|
const minLatency = Math.min(...completed.map(r => r.latency));
|
|
2487
2991
|
for (const r of completed) {
|
|
2488
2992
|
const pct = Math.max(8, (r.latency / maxLatency) * 100);
|
|
2489
|
-
const color = DTYPE_COLORS[r.dtype] || '#
|
|
2993
|
+
const color = DTYPE_COLORS[r.dtype] || '#0498EC';
|
|
2490
2994
|
const badge = r.latency === minLatency ? ' ⚡' : '';
|
|
2491
2995
|
latencyHTML += `<div class="quant-bar-group">
|
|
2492
2996
|
<div class="quant-bar-label">
|
|
@@ -2621,6 +3125,22 @@ function setCostMode(mode) {
|
|
|
2621
3125
|
// ── Simple Mode (query-only comparison) ──
|
|
2622
3126
|
|
|
2623
3127
|
function initCostCalculator() {
|
|
3128
|
+
// Mode toggle buttons
|
|
3129
|
+
document.getElementById('costModeSimple').addEventListener('click', () => setCostMode('simple'));
|
|
3130
|
+
document.getElementById('costModeRag').addEventListener('click', () => setCostMode('rag'));
|
|
3131
|
+
|
|
3132
|
+
// Help modal
|
|
3133
|
+
const helpModal = document.getElementById('costHelpModal');
|
|
3134
|
+
document.getElementById('costHelpBtn').addEventListener('click', () => helpModal.classList.add('open'));
|
|
3135
|
+
document.getElementById('costHelpClose').addEventListener('click', () => helpModal.classList.remove('open'));
|
|
3136
|
+
helpModal.addEventListener('click', (e) => { if (e.target === helpModal) helpModal.classList.remove('open'); });
|
|
3137
|
+
document.addEventListener('keydown', (e) => {
|
|
3138
|
+
if (e.key === 'Escape') {
|
|
3139
|
+
helpModal.classList.remove('open');
|
|
3140
|
+
closeExploreModal();
|
|
3141
|
+
}
|
|
3142
|
+
});
|
|
3143
|
+
|
|
2624
3144
|
// Simple mode sliders
|
|
2625
3145
|
const tokSlider = document.getElementById('costTokens');
|
|
2626
3146
|
const qSlider = document.getElementById('costQueries');
|
|
@@ -2829,7 +3349,7 @@ function updateRagCalculator() {
|
|
|
2829
3349
|
</div>
|
|
2830
3350
|
<div class="cost-summary-card">
|
|
2831
3351
|
<div class="cost-summary-label">Max potential savings</div>
|
|
2832
|
-
<div class="cost-summary-value" style="color
|
|
3352
|
+
<div class="cost-summary-value" style="color:var(--success)">${maxCost > 0 ? ((1 - strategies[0].totalCost / maxCost) * 100).toFixed(0) + '%' : '0%'}</div>
|
|
2833
3353
|
<div class="cost-summary-detail">vs ${strategies[strategies.length - 1].name.split(':')[1]?.trim() || 'most expensive'}</div>
|
|
2834
3354
|
</div>
|
|
2835
3355
|
`;
|
|
@@ -2985,6 +3505,7 @@ window.clearHistory = function() {
|
|
|
2985
3505
|
const _origInit = init;
|
|
2986
3506
|
init = async function() {
|
|
2987
3507
|
await _origInit();
|
|
3508
|
+
initThemeToggle();
|
|
2988
3509
|
buildModelCheckboxes();
|
|
2989
3510
|
populateBenchRankSelects();
|
|
2990
3511
|
populateQuantModelSelect();
|
|
@@ -2996,5 +3517,84 @@ init = async function() {
|
|
|
2996
3517
|
init();
|
|
2997
3518
|
})();
|
|
2998
3519
|
</script>
|
|
3520
|
+
<!-- Cost Help Modal -->
|
|
3521
|
+
<div class="cost-modal-overlay" id="costHelpModal">
|
|
3522
|
+
<div class="cost-modal">
|
|
3523
|
+
<button class="cost-modal-close" id="costHelpClose">×</button>
|
|
3524
|
+
|
|
3525
|
+
<h2>📐 How the Cost Calculator Works</h2>
|
|
3526
|
+
|
|
3527
|
+
<p>Voyage AI charges per <strong>million tokens</strong> processed. A token is roughly ¾ of a word.
|
|
3528
|
+
The calculator estimates your total embedding cost based on how many documents you embed
|
|
3529
|
+
and how many queries you run over time.</p>
|
|
3530
|
+
|
|
3531
|
+
<h3>💡 Simple Mode</h3>
|
|
3532
|
+
<p>Compares the per-model query cost for a given volume. Useful for quick "which model is cheapest?" checks.</p>
|
|
3533
|
+
<div class="formula">
|
|
3534
|
+
<span class="label">Daily cost =</span><br>
|
|
3535
|
+
<span class="accent">tokens_per_query</span> × <span class="accent">queries_per_day</span> ÷ 1,000,000 × <span class="accent">price_per_M_tokens</span><br><br>
|
|
3536
|
+
<span class="label">Monthly cost =</span> daily cost × 30
|
|
3537
|
+
</div>
|
|
3538
|
+
|
|
3539
|
+
<h3>📊 RAG Planner Mode</h3>
|
|
3540
|
+
<p>Models the full cost of a Retrieval-Augmented Generation (RAG) pipeline, separating
|
|
3541
|
+
the <strong>one-time</strong> document ingestion cost from the <strong>recurring</strong> query cost.</p>
|
|
3542
|
+
|
|
3543
|
+
<div class="formula">
|
|
3544
|
+
<span class="label">Document embedding (one-time):</span><br>
|
|
3545
|
+
<span class="accent">doc_cost</span> = num_docs × tokens_per_doc ÷ 1,000,000 × <span class="accent">doc_model_price</span><br><br>
|
|
3546
|
+
<span class="label">Query embedding (monthly):</span><br>
|
|
3547
|
+
<span class="accent">query_cost/mo</span> = queries_per_month × tokens_per_query ÷ 1,000,000 × <span class="accent">query_model_price</span><br><br>
|
|
3548
|
+
<span class="label">Projected total:</span><br>
|
|
3549
|
+
<span class="accent">total</span> = doc_cost + (query_cost/mo × <span class="accent">months</span>)
|
|
3550
|
+
</div>
|
|
3551
|
+
|
|
3552
|
+
<h3>⚖️ Three Strategies Compared</h3>
|
|
3553
|
+
<ul>
|
|
3554
|
+
<li><strong>Symmetric</strong> — same model for documents and queries. Simple but expensive at scale,
|
|
3555
|
+
because query-heavy workloads pay the full model price on every request.</li>
|
|
3556
|
+
<li><strong>Asymmetric (★ Recommended)</strong> — use a high-quality model (e.g. <code>voyage-4-large</code>)
|
|
3557
|
+
for documents and a cheaper model (e.g. <code>voyage-4-lite</code>) for queries.
|
|
3558
|
+
This works because Voyage 4 models share the same embedding space — vectors from different
|
|
3559
|
+
models are directly comparable.</li>
|
|
3560
|
+
<li><strong>Asymmetric + Local</strong> — embed documents via the API, but run queries locally using
|
|
3561
|
+
<code>voyage-4-nano</code> on HuggingFace (free). Query cost drops to $0.</li>
|
|
3562
|
+
</ul>
|
|
3563
|
+
|
|
3564
|
+
<h3>🔗 Shared Embedding Space</h3>
|
|
3565
|
+
<p>The Voyage 4 family (<code>voyage-4-large</code>, <code>voyage-4</code>, <code>voyage-4-lite</code>,
|
|
3566
|
+
<code>voyage-4-nano</code>) all produce vectors in the <em>same geometric space</em>.
|
|
3567
|
+
A document embedded with <code>voyage-4-large</code> can be searched with a query embedded by
|
|
3568
|
+
<code>voyage-4-lite</code> — cosine similarity still works correctly. This is what makes
|
|
3569
|
+
asymmetric strategies possible.</p>
|
|
3570
|
+
|
|
3571
|
+
<div class="example">
|
|
3572
|
+
<strong>Example:</strong> 100K docs × 500 tok = 50M doc tokens<br>
|
|
3573
|
+
1M queries/mo × 30 tok = 30M query tokens/mo<br><br>
|
|
3574
|
+
<strong>Symmetric</strong> (voyage-4-large @ $0.18/1M):<br>
|
|
3575
|
+
Docs: $9.00 + Queries: $5.40/mo × 12 = <strong>$73.80</strong><br><br>
|
|
3576
|
+
<strong>Asymmetric</strong> (large docs + lite queries @ $0.05/1M):<br>
|
|
3577
|
+
Docs: $9.00 + Queries: $1.50/mo × 12 = <strong>$27.00</strong><br><br>
|
|
3578
|
+
Savings: <strong>63%</strong> — same document quality, cheaper queries.
|
|
3579
|
+
</div>
|
|
3580
|
+
|
|
3581
|
+
<h3>📋 Per-Model Table</h3>
|
|
3582
|
+
<p>The bottom table shows what it would cost to use each model symmetrically (same model for
|
|
3583
|
+
docs and queries). The relative bar shows cost compared to the most expensive option.
|
|
3584
|
+
Use this to understand the price spread across the full model lineup.</p>
|
|
3585
|
+
|
|
3586
|
+
<h3>🎯 Key Assumptions</h3>
|
|
3587
|
+
<ul>
|
|
3588
|
+
<li>Token counts are estimates — actual counts depend on your text. Use <code>vai chunk --stats</code> to measure real token counts.</li>
|
|
3589
|
+
<li>Document embedding is a one-time cost (you embed once, search many times).</li>
|
|
3590
|
+
<li>Re-embedding (e.g. updated docs) is not modeled — add a buffer if your corpus changes frequently.</li>
|
|
3591
|
+
<li>Reranking costs are separate and not included here. Reranking is priced per query pair, not per token.</li>
|
|
3592
|
+
</ul>
|
|
3593
|
+
|
|
3594
|
+
<p style="margin-top:20px;font-size:12px;color:var(--text-muted);">
|
|
3595
|
+
CLI equivalent: <code>vai estimate --docs 100K --queries 1M --doc-model voyage-4-large --query-model voyage-4-lite</code>
|
|
3596
|
+
</p>
|
|
3597
|
+
</div>
|
|
3598
|
+
</div>
|
|
2999
3599
|
</body>
|
|
3000
3600
|
</html>
|