voyageai-cli 1.18.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/README.md +188 -235
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/completions.js +26 -1
- package/src/commands/eval.js +300 -0
- package/src/commands/models.js +4 -4
- package/src/lib/catalog.js +1 -1
- package/src/lib/explanations.js +6 -6
- package/src/lib/metrics.js +174 -0
- package/src/playground/index.html +1248 -125
|
@@ -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 {
|
|
@@ -629,16 +879,22 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
629
879
|
.quant-rank-score { color: var(--text-muted); font-size: 11px; font-family: var(--mono); margin-top: 3px; }
|
|
630
880
|
|
|
631
881
|
/* Cost calculator */
|
|
882
|
+
.cost-controls {
|
|
883
|
+
display: grid;
|
|
884
|
+
grid-template-columns: 1fr 1fr;
|
|
885
|
+
gap: 16px 32px;
|
|
886
|
+
margin-bottom: 20px;
|
|
887
|
+
}
|
|
888
|
+
.cost-controls-full { grid-column: 1 / -1; }
|
|
632
889
|
.cost-slider-row {
|
|
633
890
|
display: flex;
|
|
634
891
|
align-items: center;
|
|
635
|
-
gap:
|
|
636
|
-
margin-bottom: 16px;
|
|
892
|
+
gap: 12px;
|
|
637
893
|
}
|
|
638
894
|
.cost-slider-label {
|
|
639
|
-
font-size:
|
|
895
|
+
font-size: 12px;
|
|
640
896
|
color: var(--text-dim);
|
|
641
|
-
min-width:
|
|
897
|
+
min-width: 110px;
|
|
642
898
|
}
|
|
643
899
|
.cost-slider {
|
|
644
900
|
flex: 1;
|
|
@@ -659,12 +915,155 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
659
915
|
}
|
|
660
916
|
.cost-slider-value {
|
|
661
917
|
font-family: var(--mono);
|
|
662
|
-
font-size:
|
|
918
|
+
font-size: 13px;
|
|
663
919
|
color: var(--accent);
|
|
664
|
-
min-width:
|
|
920
|
+
min-width: 70px;
|
|
665
921
|
text-align: right;
|
|
666
922
|
font-weight: 600;
|
|
667
923
|
}
|
|
924
|
+
.cost-mode-toggle {
|
|
925
|
+
display: flex;
|
|
926
|
+
gap: 0;
|
|
927
|
+
border: 1px solid var(--border);
|
|
928
|
+
border-radius: 8px;
|
|
929
|
+
overflow: hidden;
|
|
930
|
+
width: fit-content;
|
|
931
|
+
}
|
|
932
|
+
.cost-mode-btn {
|
|
933
|
+
padding: 8px 20px;
|
|
934
|
+
background: transparent;
|
|
935
|
+
border: none;
|
|
936
|
+
color: var(--text-dim);
|
|
937
|
+
font-size: 13px;
|
|
938
|
+
cursor: pointer;
|
|
939
|
+
transition: all 0.2s;
|
|
940
|
+
font-family: var(--mono);
|
|
941
|
+
}
|
|
942
|
+
.cost-mode-btn.active {
|
|
943
|
+
background: var(--accent);
|
|
944
|
+
color: var(--bg);
|
|
945
|
+
font-weight: 600;
|
|
946
|
+
}
|
|
947
|
+
.cost-mode-btn:hover:not(.active) {
|
|
948
|
+
background: rgba(0, 237, 100, 0.1);
|
|
949
|
+
color: var(--text);
|
|
950
|
+
}
|
|
951
|
+
.cost-select {
|
|
952
|
+
background: var(--bg-input);
|
|
953
|
+
border: 1px solid var(--border);
|
|
954
|
+
border-radius: 6px;
|
|
955
|
+
color: var(--text);
|
|
956
|
+
font-family: var(--mono);
|
|
957
|
+
font-size: 12px;
|
|
958
|
+
padding: 6px 10px;
|
|
959
|
+
min-width: 160px;
|
|
960
|
+
}
|
|
961
|
+
.cost-select:focus { border-color: var(--accent); outline: none; }
|
|
962
|
+
.cost-model-row {
|
|
963
|
+
display: flex;
|
|
964
|
+
align-items: center;
|
|
965
|
+
gap: 12px;
|
|
966
|
+
margin-bottom: 12px;
|
|
967
|
+
}
|
|
968
|
+
.cost-model-label {
|
|
969
|
+
font-size: 12px;
|
|
970
|
+
color: var(--text-dim);
|
|
971
|
+
min-width: 110px;
|
|
972
|
+
}
|
|
973
|
+
.cost-summary {
|
|
974
|
+
display: grid;
|
|
975
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
976
|
+
gap: 12px;
|
|
977
|
+
margin-bottom: 20px;
|
|
978
|
+
}
|
|
979
|
+
.cost-summary-card {
|
|
980
|
+
background: var(--bg-input);
|
|
981
|
+
border-radius: 8px;
|
|
982
|
+
padding: 14px 16px;
|
|
983
|
+
border: 1px solid var(--border);
|
|
984
|
+
}
|
|
985
|
+
.cost-summary-label {
|
|
986
|
+
font-size: 11px;
|
|
987
|
+
color: var(--text-muted);
|
|
988
|
+
text-transform: uppercase;
|
|
989
|
+
letter-spacing: 0.5px;
|
|
990
|
+
margin-bottom: 4px;
|
|
991
|
+
}
|
|
992
|
+
.cost-summary-value {
|
|
993
|
+
font-family: var(--mono);
|
|
994
|
+
font-size: 20px;
|
|
995
|
+
font-weight: 700;
|
|
996
|
+
color: var(--accent);
|
|
997
|
+
}
|
|
998
|
+
.cost-summary-detail {
|
|
999
|
+
font-size: 11px;
|
|
1000
|
+
color: var(--text-dim);
|
|
1001
|
+
margin-top: 4px;
|
|
1002
|
+
font-family: var(--mono);
|
|
1003
|
+
}
|
|
1004
|
+
.cost-strategy-cards {
|
|
1005
|
+
display: grid;
|
|
1006
|
+
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
1007
|
+
gap: 16px;
|
|
1008
|
+
margin-top: 16px;
|
|
1009
|
+
}
|
|
1010
|
+
.cost-strategy {
|
|
1011
|
+
background: var(--bg-input);
|
|
1012
|
+
border-radius: 10px;
|
|
1013
|
+
padding: 18px;
|
|
1014
|
+
border: 1px solid var(--border);
|
|
1015
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
1016
|
+
position: relative;
|
|
1017
|
+
}
|
|
1018
|
+
.cost-strategy.recommended {
|
|
1019
|
+
border-color: var(--accent);
|
|
1020
|
+
box-shadow: 0 0 16px var(--accent-glow);
|
|
1021
|
+
}
|
|
1022
|
+
.cost-strategy-badge {
|
|
1023
|
+
position: absolute;
|
|
1024
|
+
top: -10px;
|
|
1025
|
+
right: 16px;
|
|
1026
|
+
background: var(--accent);
|
|
1027
|
+
color: var(--bg);
|
|
1028
|
+
font-size: 10px;
|
|
1029
|
+
font-weight: 700;
|
|
1030
|
+
padding: 3px 10px;
|
|
1031
|
+
border-radius: 10px;
|
|
1032
|
+
text-transform: uppercase;
|
|
1033
|
+
letter-spacing: 0.5px;
|
|
1034
|
+
}
|
|
1035
|
+
.cost-strategy-name {
|
|
1036
|
+
font-size: 14px;
|
|
1037
|
+
font-weight: 600;
|
|
1038
|
+
color: var(--text);
|
|
1039
|
+
margin-bottom: 12px;
|
|
1040
|
+
}
|
|
1041
|
+
.cost-strategy-row {
|
|
1042
|
+
display: flex;
|
|
1043
|
+
justify-content: space-between;
|
|
1044
|
+
align-items: center;
|
|
1045
|
+
padding: 4px 0;
|
|
1046
|
+
font-size: 12px;
|
|
1047
|
+
}
|
|
1048
|
+
.cost-strategy-row-label { color: var(--text-dim); }
|
|
1049
|
+
.cost-strategy-row-value { font-family: var(--mono); color: var(--text); font-weight: 500; }
|
|
1050
|
+
.cost-strategy-total {
|
|
1051
|
+
border-top: 1px solid var(--border);
|
|
1052
|
+
margin-top: 10px;
|
|
1053
|
+
padding-top: 10px;
|
|
1054
|
+
display: flex;
|
|
1055
|
+
justify-content: space-between;
|
|
1056
|
+
align-items: center;
|
|
1057
|
+
}
|
|
1058
|
+
.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); }
|
|
1060
|
+
.cost-savings {
|
|
1061
|
+
font-size: 11px;
|
|
1062
|
+
color: var(--success);
|
|
1063
|
+
font-weight: 600;
|
|
1064
|
+
margin-top: 6px;
|
|
1065
|
+
text-align: right;
|
|
1066
|
+
}
|
|
668
1067
|
.cost-table {
|
|
669
1068
|
width: 100%;
|
|
670
1069
|
border-collapse: collapse;
|
|
@@ -686,7 +1085,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
686
1085
|
border-bottom: 1px solid rgba(42, 53, 80, 0.3);
|
|
687
1086
|
font-family: var(--mono);
|
|
688
1087
|
}
|
|
689
|
-
.cost-table tr:hover { background: rgba(0,
|
|
1088
|
+
.cost-table tr:hover { background: rgba(0, 237, 100, 0.03); }
|
|
690
1089
|
.cost-highlight {
|
|
691
1090
|
color: var(--accent);
|
|
692
1091
|
font-weight: 600;
|
|
@@ -701,6 +1100,148 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
701
1100
|
border-radius: 3px;
|
|
702
1101
|
transition: width 0.4s ease;
|
|
703
1102
|
}
|
|
1103
|
+
.cost-section-title {
|
|
1104
|
+
font-size: 13px;
|
|
1105
|
+
font-weight: 600;
|
|
1106
|
+
color: var(--text);
|
|
1107
|
+
margin: 20px 0 8px;
|
|
1108
|
+
display: flex;
|
|
1109
|
+
align-items: center;
|
|
1110
|
+
gap: 8px;
|
|
1111
|
+
}
|
|
1112
|
+
.cost-tip {
|
|
1113
|
+
font-size: 12px;
|
|
1114
|
+
color: var(--text-muted);
|
|
1115
|
+
background: rgba(0, 237, 100, 0.05);
|
|
1116
|
+
border-left: 3px solid var(--accent);
|
|
1117
|
+
padding: 10px 14px;
|
|
1118
|
+
border-radius: 0 6px 6px 0;
|
|
1119
|
+
margin-top: 16px;
|
|
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
|
+
}
|
|
704
1245
|
|
|
705
1246
|
/* History chart */
|
|
706
1247
|
.history-empty {
|
|
@@ -787,7 +1328,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
787
1328
|
margin-bottom: 8px;
|
|
788
1329
|
}
|
|
789
1330
|
.about-text { font-size: 14px; line-height: 1.8; color: var(--text); }
|
|
790
|
-
.about-text a { color: var(--
|
|
1331
|
+
.about-text a { color: var(--blue); text-decoration: none; }
|
|
791
1332
|
.about-text a:hover { text-decoration: underline; }
|
|
792
1333
|
.about-disclaimer {
|
|
793
1334
|
background: rgba(255, 215, 61, 0.08);
|
|
@@ -826,6 +1367,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
826
1367
|
<span class="option-label">Default Model</span>
|
|
827
1368
|
<select id="globalModel" class="nav-model-select"></select>
|
|
828
1369
|
</div>
|
|
1370
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark mode">🌙</button>
|
|
829
1371
|
<div style="display:flex;align-items:center;gap:6px;">
|
|
830
1372
|
<div class="status-dot" id="statusDot"></div>
|
|
831
1373
|
<span class="status-label" id="statusLabel">Checking...</span>
|
|
@@ -945,7 +1487,9 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
945
1487
|
<div class="similarity-bar-inner" id="simBar" style="width:0%"></div>
|
|
946
1488
|
</div>
|
|
947
1489
|
</div>
|
|
948
|
-
<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>
|
|
949
1493
|
</div>
|
|
950
1494
|
</div>
|
|
951
1495
|
</div>
|
|
@@ -1184,30 +1728,116 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1184
1728
|
<!-- ── Cost Panel ── -->
|
|
1185
1729
|
<div class="bench-view" id="bench-cost">
|
|
1186
1730
|
<div class="card">
|
|
1187
|
-
<div class="card-title"
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
<
|
|
1731
|
+
<div class="card-title">💰 RAG Cost Calculator <button class="cost-help-btn" id="costHelpBtn" title="How the math works">?</button></div>
|
|
1732
|
+
|
|
1733
|
+
<!-- Mode toggle -->
|
|
1734
|
+
<div style="margin-bottom: 20px;">
|
|
1735
|
+
<div class="cost-mode-toggle">
|
|
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>
|
|
1738
|
+
</div>
|
|
1739
|
+
</div>
|
|
1740
|
+
|
|
1741
|
+
<!-- Simple mode (query cost comparison) -->
|
|
1742
|
+
<div id="costSimpleMode">
|
|
1743
|
+
<div class="cost-controls cost-controls-full">
|
|
1744
|
+
<div class="cost-slider-row">
|
|
1745
|
+
<span class="cost-slider-label">Tokens / query</span>
|
|
1746
|
+
<input type="range" class="cost-slider" id="costTokens" min="50" max="5000" value="500" step="50">
|
|
1747
|
+
<span class="cost-slider-value" id="costTokensValue">500</span>
|
|
1748
|
+
</div>
|
|
1749
|
+
<div class="cost-slider-row">
|
|
1750
|
+
<span class="cost-slider-label">Queries / day</span>
|
|
1751
|
+
<input type="range" class="cost-slider" id="costQueries" min="10" max="500000" value="1000" step="10">
|
|
1752
|
+
<span class="cost-slider-value" id="costQueriesValue">1,000</span>
|
|
1753
|
+
</div>
|
|
1754
|
+
</div>
|
|
1755
|
+
<table class="cost-table" id="costTable">
|
|
1756
|
+
<thead>
|
|
1757
|
+
<tr>
|
|
1758
|
+
<th>Model</th>
|
|
1759
|
+
<th>Type</th>
|
|
1760
|
+
<th>$/1M tokens</th>
|
|
1761
|
+
<th>Daily Cost</th>
|
|
1762
|
+
<th>Monthly Cost</th>
|
|
1763
|
+
<th style="width:30%">Relative</th>
|
|
1764
|
+
</tr>
|
|
1765
|
+
</thead>
|
|
1766
|
+
<tbody id="costTableBody"></tbody>
|
|
1767
|
+
</table>
|
|
1192
1768
|
</div>
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
<
|
|
1769
|
+
|
|
1770
|
+
<!-- RAG Planner mode (full TCO) -->
|
|
1771
|
+
<div id="costRagMode" style="display:none;">
|
|
1772
|
+
<div class="cost-section-title">📄 Documents (one-time ingestion)</div>
|
|
1773
|
+
<div class="cost-controls">
|
|
1774
|
+
<div class="cost-slider-row">
|
|
1775
|
+
<span class="cost-slider-label">Documents</span>
|
|
1776
|
+
<input type="range" class="cost-slider" id="ragDocs" min="1000" max="10000000" value="100000" step="1000">
|
|
1777
|
+
<span class="cost-slider-value" id="ragDocsValue">100K</span>
|
|
1778
|
+
</div>
|
|
1779
|
+
<div class="cost-slider-row">
|
|
1780
|
+
<span class="cost-slider-label">Tokens / doc</span>
|
|
1781
|
+
<input type="range" class="cost-slider" id="ragDocTokens" min="50" max="5000" value="500" step="50">
|
|
1782
|
+
<span class="cost-slider-value" id="ragDocTokensValue">500</span>
|
|
1783
|
+
</div>
|
|
1784
|
+
</div>
|
|
1785
|
+
|
|
1786
|
+
<div class="cost-section-title">🔍 Queries (recurring)</div>
|
|
1787
|
+
<div class="cost-controls">
|
|
1788
|
+
<div class="cost-slider-row">
|
|
1789
|
+
<span class="cost-slider-label">Queries / month</span>
|
|
1790
|
+
<input type="range" class="cost-slider" id="ragQueries" min="1000" max="50000000" value="1000000" step="1000">
|
|
1791
|
+
<span class="cost-slider-value" id="ragQueriesValue">1M</span>
|
|
1792
|
+
</div>
|
|
1793
|
+
<div class="cost-slider-row">
|
|
1794
|
+
<span class="cost-slider-label">Tokens / query</span>
|
|
1795
|
+
<input type="range" class="cost-slider" id="ragQueryTokens" min="10" max="500" value="30" step="5">
|
|
1796
|
+
<span class="cost-slider-value" id="ragQueryTokensValue">30</span>
|
|
1797
|
+
</div>
|
|
1798
|
+
</div>
|
|
1799
|
+
|
|
1800
|
+
<div class="cost-section-title">⚙️ Configuration</div>
|
|
1801
|
+
<div class="cost-controls">
|
|
1802
|
+
<div class="cost-model-row">
|
|
1803
|
+
<span class="cost-model-label">Doc model</span>
|
|
1804
|
+
<select class="cost-select" id="ragDocModel"></select>
|
|
1805
|
+
</div>
|
|
1806
|
+
<div class="cost-model-row">
|
|
1807
|
+
<span class="cost-model-label">Query model</span>
|
|
1808
|
+
<select class="cost-select" id="ragQueryModel"></select>
|
|
1809
|
+
</div>
|
|
1810
|
+
<div class="cost-slider-row">
|
|
1811
|
+
<span class="cost-slider-label">Projection</span>
|
|
1812
|
+
<input type="range" class="cost-slider" id="ragMonths" min="1" max="36" value="12" step="1">
|
|
1813
|
+
<span class="cost-slider-value" id="ragMonthsValue">12 mo</span>
|
|
1814
|
+
</div>
|
|
1815
|
+
</div>
|
|
1816
|
+
|
|
1817
|
+
<!-- Summary cards -->
|
|
1818
|
+
<div class="cost-summary" id="ragSummary"></div>
|
|
1819
|
+
|
|
1820
|
+
<!-- Strategy comparison -->
|
|
1821
|
+
<div class="cost-section-title">📊 Strategy Comparison</div>
|
|
1822
|
+
<div class="cost-strategy-cards" id="ragStrategies"></div>
|
|
1823
|
+
|
|
1824
|
+
<!-- Per-model table -->
|
|
1825
|
+
<div class="cost-section-title">📋 Per-Model Breakdown</div>
|
|
1826
|
+
<table class="cost-table" id="ragTable">
|
|
1827
|
+
<thead>
|
|
1828
|
+
<tr>
|
|
1829
|
+
<th>Model</th>
|
|
1830
|
+
<th>Doc Cost</th>
|
|
1831
|
+
<th>Query $/mo</th>
|
|
1832
|
+
<th>Total (projected)</th>
|
|
1833
|
+
<th style="width:25%">Relative</th>
|
|
1834
|
+
</tr>
|
|
1835
|
+
</thead>
|
|
1836
|
+
<tbody id="ragTableBody"></tbody>
|
|
1837
|
+
</table>
|
|
1838
|
+
|
|
1839
|
+
<div class="cost-tip" id="ragTip"></div>
|
|
1197
1840
|
</div>
|
|
1198
|
-
<table class="cost-table" id="costTable">
|
|
1199
|
-
<thead>
|
|
1200
|
-
<tr>
|
|
1201
|
-
<th>Model</th>
|
|
1202
|
-
<th>Type</th>
|
|
1203
|
-
<th>$/1M tokens</th>
|
|
1204
|
-
<th>Daily Cost</th>
|
|
1205
|
-
<th>Monthly Cost</th>
|
|
1206
|
-
<th style="width:30%">Relative</th>
|
|
1207
|
-
</tr>
|
|
1208
|
-
</thead>
|
|
1209
|
-
<tbody id="costTableBody"></tbody>
|
|
1210
|
-
</table>
|
|
1211
1841
|
</div>
|
|
1212
1842
|
</div>
|
|
1213
1843
|
|
|
@@ -1268,7 +1898,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1268
1898
|
<div class="about-section-title">What You Can Do Here</div>
|
|
1269
1899
|
<div class="about-text">
|
|
1270
1900
|
<strong>⚡ Embed</strong> — Generate vector embeddings for any text<br>
|
|
1271
|
-
<strong>⚖️ Compare</strong> — Measure cosine
|
|
1901
|
+
<strong>⚖️ Compare</strong> — Measure similarity with cosine, dot product & euclidean distance<br>
|
|
1272
1902
|
<strong>🔍 Search</strong> — Semantic search with optional reranking<br>
|
|
1273
1903
|
<strong>⏱ Benchmark</strong> — Compare model latency, ranking quality, and costs<br>
|
|
1274
1904
|
<strong>📚 Explore</strong> — Learn about embeddings, vector search, RAG, and more
|
|
@@ -1301,12 +1931,61 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1301
1931
|
<div class="explore-grid" id="exploreGrid"></div>
|
|
1302
1932
|
</div>
|
|
1303
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
|
+
|
|
1304
1950
|
</div><!-- .main -->
|
|
1305
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>
|
|
1306
1959
|
<script>
|
|
1307
1960
|
(function() {
|
|
1308
1961
|
'use strict';
|
|
1309
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
|
+
|
|
1310
1989
|
// ── State ──
|
|
1311
1990
|
let allModels = [];
|
|
1312
1991
|
let embedModels = [];
|
|
@@ -1548,27 +2227,73 @@ window.doCompare = async function() {
|
|
|
1548
2227
|
const dimensions = dims ? parseInt(dims, 10) : undefined;
|
|
1549
2228
|
|
|
1550
2229
|
const data = await apiPost('/api/similarity', { texts: [a, b], model, dimensions });
|
|
1551
|
-
const sim = data.matrix[0][1];
|
|
1552
|
-
const pct = Math.max(0, sim * 100);
|
|
1553
2230
|
|
|
1554
|
-
//
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
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)';
|
|
1559
2245
|
|
|
1560
2246
|
const scoreEl = document.getElementById('simScore');
|
|
1561
|
-
scoreEl.textContent =
|
|
1562
|
-
scoreEl.style.color =
|
|
2247
|
+
scoreEl.textContent = cosine.toFixed(4);
|
|
2248
|
+
scoreEl.style.color = cosineColor;
|
|
1563
2249
|
|
|
1564
2250
|
const barEl = document.getElementById('simBar');
|
|
1565
|
-
barEl.style.width =
|
|
1566
|
-
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
|
+
}
|
|
1567
2291
|
|
|
1568
2292
|
// Stats
|
|
1569
2293
|
const statsEl = document.getElementById('compareStats');
|
|
1570
2294
|
statsEl.innerHTML = `
|
|
1571
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>
|
|
1572
2297
|
<span class="stat"><span class="stat-label">Tokens</span><span class="stat-value">${data.usage?.total_tokens || '—'}</span></span>
|
|
1573
2298
|
`;
|
|
1574
2299
|
|
|
@@ -1645,6 +2370,18 @@ function cosineSim(a, b) {
|
|
|
1645
2370
|
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
1646
2371
|
}
|
|
1647
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
|
+
|
|
1648
2385
|
function renderSearchResults(embResults, rerankResults) {
|
|
1649
2386
|
const grid = document.getElementById('searchResultGrid');
|
|
1650
2387
|
grid.innerHTML = '';
|
|
@@ -1750,40 +2487,64 @@ function buildExploreCards() {
|
|
|
1750
2487
|
card.className = 'explore-card';
|
|
1751
2488
|
card.dataset.key = key;
|
|
1752
2489
|
|
|
1753
|
-
// Build links HTML
|
|
1754
|
-
let linksHtml = '';
|
|
1755
|
-
if (concept.links && concept.links.length > 0) {
|
|
1756
|
-
linksHtml = '<div style="margin-top:12px;"><strong style="color:var(--accent);font-size:12px;">LEARN MORE</strong><br>' +
|
|
1757
|
-
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>') +
|
|
1758
|
-
'</div>';
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
// Build try-it HTML
|
|
1762
|
-
let tryItHtml = '';
|
|
1763
|
-
if (concept.tryIt && concept.tryIt.length > 0) {
|
|
1764
|
-
tryItHtml = '<div style="margin-top:12px;"><strong style="color:var(--accent);font-size:12px;">TRY IT</strong>' +
|
|
1765
|
-
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('') +
|
|
1766
|
-
'</div>';
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
2490
|
card.innerHTML = `
|
|
1770
2491
|
<div class="explore-card-icon">${meta.icon}</div>
|
|
1771
2492
|
<div class="explore-card-title">${escapeHtml(concept.title)}</div>
|
|
1772
2493
|
<div class="explore-card-summary">${escapeHtml(concept.summary)}</div>
|
|
1773
|
-
<div class="explore-card-content">${escapeHtml(concept.content)}${linksHtml}${tryItHtml}</div>
|
|
1774
|
-
<div class="explore-card-actions">
|
|
1775
|
-
<button class="btn btn-small" onclick="tryTopic('${escapeHtml(key)}')">Try it in playground →</button>
|
|
1776
|
-
<button class="btn btn-secondary btn-small" onclick="collapseTopic(this)">Collapse</button>
|
|
1777
|
-
</div>
|
|
1778
2494
|
`;
|
|
1779
|
-
card.addEventListener('click',
|
|
1780
|
-
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'A') return;
|
|
1781
|
-
if (!this.classList.contains('expanded')) {
|
|
1782
|
-
this.classList.add('expanded');
|
|
1783
|
-
}
|
|
1784
|
-
});
|
|
2495
|
+
card.addEventListener('click', () => openExploreModal(key));
|
|
1785
2496
|
grid.appendChild(card);
|
|
1786
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');
|
|
1787
2548
|
}
|
|
1788
2549
|
|
|
1789
2550
|
window.tryTopic = function(key) {
|
|
@@ -1791,10 +2552,6 @@ window.tryTopic = function(key) {
|
|
|
1791
2552
|
if (meta) switchTab(meta.tab);
|
|
1792
2553
|
};
|
|
1793
2554
|
|
|
1794
|
-
window.collapseTopic = function(btn) {
|
|
1795
|
-
btn.closest('.explore-card').classList.remove('expanded');
|
|
1796
|
-
};
|
|
1797
|
-
|
|
1798
2555
|
window.filterExplore = function() {
|
|
1799
2556
|
const q = document.getElementById('exploreSearch').value.toLowerCase().trim();
|
|
1800
2557
|
document.querySelectorAll('#exploreGrid .explore-card').forEach(card => {
|
|
@@ -1862,8 +2619,8 @@ const BENCH_SAMPLE_TEXTS = [
|
|
|
1862
2619
|
];
|
|
1863
2620
|
|
|
1864
2621
|
const MODEL_COLORS = [
|
|
1865
|
-
'#
|
|
1866
|
-
'#
|
|
2622
|
+
'#00ED64', '#71F6BA', '#0498EC', '#B45AF2', '#FFC010',
|
|
2623
|
+
'#FF6960', '#B45AF2', '#FFC010', '#016BF8', '#C0FAE6',
|
|
1867
2624
|
];
|
|
1868
2625
|
|
|
1869
2626
|
window.doBenchLatency = async function() {
|
|
@@ -2204,7 +2961,7 @@ window.doBenchQuantization = async function() {
|
|
|
2204
2961
|
const baseline = completed.find(r => r.dtype === 'float') || completed[0];
|
|
2205
2962
|
const maxBytes = Math.max(...completed.map(r => r.bytesPerVec));
|
|
2206
2963
|
const maxLatency = Math.max(...completed.map(r => r.latency));
|
|
2207
|
-
const DTYPE_COLORS = { float: '#
|
|
2964
|
+
const DTYPE_COLORS = { float: '#00ED64', int8: '#71F6BA', uint8: '#0498EC', ubinary: '#FFC010', binary: '#FF6960' };
|
|
2208
2965
|
|
|
2209
2966
|
// ── Storage Bar Chart ──
|
|
2210
2967
|
let storageHTML = '';
|
|
@@ -2215,7 +2972,7 @@ window.doBenchQuantization = async function() {
|
|
|
2215
2972
|
const savings = r.bytesPerVec < baseline.bytesPerVec
|
|
2216
2973
|
? `${(baseline.bytesPerVec / r.bytesPerVec).toFixed(0)}× smaller`
|
|
2217
2974
|
: 'baseline';
|
|
2218
|
-
const color = DTYPE_COLORS[r.dtype] || '#
|
|
2975
|
+
const color = DTYPE_COLORS[r.dtype] || '#0498EC';
|
|
2219
2976
|
storageHTML += `<div class="quant-bar-group">
|
|
2220
2977
|
<div class="quant-bar-label">
|
|
2221
2978
|
<span class="dtype-name">${r.dtype}</span>
|
|
@@ -2233,7 +2990,7 @@ window.doBenchQuantization = async function() {
|
|
|
2233
2990
|
const minLatency = Math.min(...completed.map(r => r.latency));
|
|
2234
2991
|
for (const r of completed) {
|
|
2235
2992
|
const pct = Math.max(8, (r.latency / maxLatency) * 100);
|
|
2236
|
-
const color = DTYPE_COLORS[r.dtype] || '#
|
|
2993
|
+
const color = DTYPE_COLORS[r.dtype] || '#0498EC';
|
|
2237
2994
|
const badge = r.latency === minLatency ? ' ⚡' : '';
|
|
2238
2995
|
latencyHTML += `<div class="quant-bar-group">
|
|
2239
2996
|
<div class="quant-bar-label">
|
|
@@ -2326,7 +3083,65 @@ window.doBenchQuantization = async function() {
|
|
|
2326
3083
|
};
|
|
2327
3084
|
|
|
2328
3085
|
// ── Benchmark: Cost Calculator ──
|
|
3086
|
+
// ── Cost Calculator: Shared Helpers ──
|
|
3087
|
+
|
|
3088
|
+
function costFormatDollars(n) {
|
|
3089
|
+
if (n === 0) return '$0.00';
|
|
3090
|
+
if (n < 0.01 && n > 0) return '$' + n.toFixed(4);
|
|
3091
|
+
if (n < 1) return '$' + n.toFixed(2);
|
|
3092
|
+
return '$' + n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
function costShortNum(n) {
|
|
3096
|
+
if (n >= 1e9) return (n / 1e9).toFixed(n % 1e9 === 0 ? 0 : 1) + 'B';
|
|
3097
|
+
if (n >= 1e6) return (n / 1e6).toFixed(n % 1e6 === 0 ? 0 : 1) + 'M';
|
|
3098
|
+
if (n >= 1e3) return (n / 1e3).toFixed(n % 1e3 === 0 ? 0 : 1) + 'K';
|
|
3099
|
+
return String(n);
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
function costGetPricePerM(model) {
|
|
3103
|
+
const match = model.price.match(/\$([0-9.]+)\/1M/);
|
|
3104
|
+
return match ? parseFloat(match[1]) : null;
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
function costGetV4Models() {
|
|
3108
|
+
return allModels.filter(m => !m.legacy && !m.unreleased && costGetPricePerM(m) !== null);
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// ── Cost Mode Toggle ──
|
|
3112
|
+
|
|
3113
|
+
let currentCostMode = 'simple';
|
|
3114
|
+
|
|
3115
|
+
function setCostMode(mode) {
|
|
3116
|
+
currentCostMode = mode;
|
|
3117
|
+
document.querySelectorAll('.cost-mode-btn').forEach(btn => {
|
|
3118
|
+
btn.classList.toggle('active', btn.dataset.mode === mode);
|
|
3119
|
+
});
|
|
3120
|
+
document.getElementById('costSimpleMode').style.display = mode === 'simple' ? '' : 'none';
|
|
3121
|
+
document.getElementById('costRagMode').style.display = mode === 'rag' ? '' : 'none';
|
|
3122
|
+
if (mode === 'rag') updateRagCalculator();
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
// ── Simple Mode (query-only comparison) ──
|
|
3126
|
+
|
|
2329
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
|
+
|
|
3144
|
+
// Simple mode sliders
|
|
2330
3145
|
const tokSlider = document.getElementById('costTokens');
|
|
2331
3146
|
const qSlider = document.getElementById('costQueries');
|
|
2332
3147
|
const tokValue = document.getElementById('costTokensValue');
|
|
@@ -2342,22 +3157,22 @@ function initCostCalculator() {
|
|
|
2342
3157
|
|
|
2343
3158
|
tokSlider.addEventListener('input', updateCost);
|
|
2344
3159
|
qSlider.addEventListener('input', updateCost);
|
|
2345
|
-
|
|
2346
|
-
// Initialize
|
|
2347
3160
|
updateCost();
|
|
3161
|
+
|
|
3162
|
+
// RAG mode init
|
|
3163
|
+
initRagCalculator();
|
|
2348
3164
|
}
|
|
2349
3165
|
|
|
2350
3166
|
function renderCostTable(tokensPerQuery, queriesPerDay) {
|
|
2351
3167
|
const tbody = document.getElementById('costTableBody');
|
|
2352
3168
|
tbody.innerHTML = '';
|
|
2353
3169
|
|
|
2354
|
-
const models = allModels.filter(m => !m.legacy);
|
|
3170
|
+
const models = allModels.filter(m => !m.legacy && !m.unreleased);
|
|
2355
3171
|
const rows = [];
|
|
2356
3172
|
|
|
2357
3173
|
models.forEach(m => {
|
|
2358
|
-
const
|
|
2359
|
-
if (
|
|
2360
|
-
const pricePerM = parseFloat(match[1]);
|
|
3174
|
+
const pricePerM = costGetPricePerM(m);
|
|
3175
|
+
if (pricePerM === null) return;
|
|
2361
3176
|
const dailyTokens = tokensPerQuery * queriesPerDay;
|
|
2362
3177
|
const dailyCost = (dailyTokens / 1_000_000) * pricePerM;
|
|
2363
3178
|
const monthlyCost = dailyCost * 30;
|
|
@@ -2388,6 +3203,234 @@ function renderCostTable(tokensPerQuery, queriesPerDay) {
|
|
|
2388
3203
|
});
|
|
2389
3204
|
}
|
|
2390
3205
|
|
|
3206
|
+
// ── RAG Planner Mode (full TCO with strategies) ──
|
|
3207
|
+
|
|
3208
|
+
function initRagCalculator() {
|
|
3209
|
+
// Populate model dropdowns
|
|
3210
|
+
const embeddingModels = allModels.filter(m =>
|
|
3211
|
+
m.type === 'embedding' && !m.legacy && !m.unreleased && costGetPricePerM(m) !== null
|
|
3212
|
+
);
|
|
3213
|
+
|
|
3214
|
+
const docSelect = document.getElementById('ragDocModel');
|
|
3215
|
+
const querySelect = document.getElementById('ragQueryModel');
|
|
3216
|
+
|
|
3217
|
+
embeddingModels.forEach(m => {
|
|
3218
|
+
const pricePerM = costGetPricePerM(m);
|
|
3219
|
+
const opt1 = document.createElement('option');
|
|
3220
|
+
opt1.value = m.name;
|
|
3221
|
+
opt1.textContent = `${m.name} ($${pricePerM.toFixed(2)}/1M)`;
|
|
3222
|
+
docSelect.appendChild(opt1);
|
|
3223
|
+
|
|
3224
|
+
const opt2 = document.createElement('option');
|
|
3225
|
+
opt2.value = m.name;
|
|
3226
|
+
opt2.textContent = `${m.name} ($${pricePerM.toFixed(2)}/1M)`;
|
|
3227
|
+
querySelect.appendChild(opt2);
|
|
3228
|
+
});
|
|
3229
|
+
|
|
3230
|
+
// Set defaults: voyage-4-large for docs, voyage-4-lite for queries
|
|
3231
|
+
docSelect.value = 'voyage-4-large';
|
|
3232
|
+
querySelect.value = 'voyage-4-lite';
|
|
3233
|
+
|
|
3234
|
+
// Bind all sliders and selects
|
|
3235
|
+
const ids = ['ragDocs', 'ragDocTokens', 'ragQueries', 'ragQueryTokens', 'ragMonths'];
|
|
3236
|
+
ids.forEach(id => {
|
|
3237
|
+
document.getElementById(id).addEventListener('input', updateRagCalculator);
|
|
3238
|
+
});
|
|
3239
|
+
docSelect.addEventListener('change', updateRagCalculator);
|
|
3240
|
+
querySelect.addEventListener('change', updateRagCalculator);
|
|
3241
|
+
|
|
3242
|
+
updateRagCalculator();
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
function updateRagCalculator() {
|
|
3246
|
+
const numDocs = parseInt(document.getElementById('ragDocs').value, 10);
|
|
3247
|
+
const docTokens = parseInt(document.getElementById('ragDocTokens').value, 10);
|
|
3248
|
+
const numQueries = parseInt(document.getElementById('ragQueries').value, 10);
|
|
3249
|
+
const queryTokens = parseInt(document.getElementById('ragQueryTokens').value, 10);
|
|
3250
|
+
const months = parseInt(document.getElementById('ragMonths').value, 10);
|
|
3251
|
+
const docModelName = document.getElementById('ragDocModel').value;
|
|
3252
|
+
const queryModelName = document.getElementById('ragQueryModel').value;
|
|
3253
|
+
|
|
3254
|
+
// Update slider display values
|
|
3255
|
+
document.getElementById('ragDocsValue').textContent = costShortNum(numDocs);
|
|
3256
|
+
document.getElementById('ragDocTokensValue').textContent = docTokens.toLocaleString();
|
|
3257
|
+
document.getElementById('ragQueriesValue').textContent = costShortNum(numQueries);
|
|
3258
|
+
document.getElementById('ragQueryTokensValue').textContent = queryTokens.toLocaleString();
|
|
3259
|
+
document.getElementById('ragMonthsValue').textContent = months + ' mo';
|
|
3260
|
+
|
|
3261
|
+
const docTotalTokens = numDocs * docTokens;
|
|
3262
|
+
const queryTotalTokensPerMonth = numQueries * queryTokens;
|
|
3263
|
+
|
|
3264
|
+
// Get model prices
|
|
3265
|
+
const docModel = allModels.find(m => m.name === docModelName);
|
|
3266
|
+
const queryModel = allModels.find(m => m.name === queryModelName);
|
|
3267
|
+
const docPrice = docModel ? costGetPricePerM(docModel) : 0;
|
|
3268
|
+
const queryPrice = queryModel ? costGetPricePerM(queryModel) : 0;
|
|
3269
|
+
|
|
3270
|
+
// ── Build strategies (same logic as CLI) ──
|
|
3271
|
+
const strategies = [];
|
|
3272
|
+
const v4Embedding = allModels.filter(m =>
|
|
3273
|
+
m.type === 'embedding' && !m.legacy && !m.unreleased &&
|
|
3274
|
+
(m.sharedSpace === 'voyage-4' || m.name.startsWith('voyage-4')) &&
|
|
3275
|
+
costGetPricePerM(m) !== null && costGetPricePerM(m) > 0
|
|
3276
|
+
);
|
|
3277
|
+
|
|
3278
|
+
// Strategy group 1: Symmetric with each V4 model
|
|
3279
|
+
v4Embedding.forEach(m => {
|
|
3280
|
+
const price = costGetPricePerM(m);
|
|
3281
|
+
const docCost = (docTotalTokens / 1e6) * price;
|
|
3282
|
+
const queryCostPerMonth = (queryTotalTokensPerMonth / 1e6) * price;
|
|
3283
|
+
const totalCost = docCost + (queryCostPerMonth * months);
|
|
3284
|
+
strategies.push({
|
|
3285
|
+
name: `Symmetric: ${m.name}`,
|
|
3286
|
+
type: 'symmetric',
|
|
3287
|
+
docModel: m.name,
|
|
3288
|
+
queryModel: m.name,
|
|
3289
|
+
docCost,
|
|
3290
|
+
queryCostPerMonth,
|
|
3291
|
+
totalCost,
|
|
3292
|
+
months,
|
|
3293
|
+
});
|
|
3294
|
+
});
|
|
3295
|
+
|
|
3296
|
+
// Strategy 2: Asymmetric — user-selected combo
|
|
3297
|
+
const asymDocCost = (docTotalTokens / 1e6) * docPrice;
|
|
3298
|
+
const asymQueryCostPerMonth = (queryTotalTokensPerMonth / 1e6) * queryPrice;
|
|
3299
|
+
const asymTotalCost = asymDocCost + (asymQueryCostPerMonth * months);
|
|
3300
|
+
|
|
3301
|
+
// Only add if it's actually asymmetric (different models)
|
|
3302
|
+
if (docModelName !== queryModelName) {
|
|
3303
|
+
strategies.push({
|
|
3304
|
+
name: `Asymmetric: ${docModelName} + ${queryModelName}`,
|
|
3305
|
+
type: 'asymmetric',
|
|
3306
|
+
docModel: docModelName,
|
|
3307
|
+
queryModel: queryModelName,
|
|
3308
|
+
docCost: asymDocCost,
|
|
3309
|
+
queryCostPerMonth: asymQueryCostPerMonth,
|
|
3310
|
+
totalCost: asymTotalCost,
|
|
3311
|
+
months,
|
|
3312
|
+
recommended: true,
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
// Strategy 3: Asymmetric with nano (local, free queries)
|
|
3317
|
+
strategies.push({
|
|
3318
|
+
name: `Asymmetric: ${docModelName} + nano (local)`,
|
|
3319
|
+
type: 'asymmetric-local',
|
|
3320
|
+
docModel: docModelName,
|
|
3321
|
+
queryModel: 'voyage-4-nano',
|
|
3322
|
+
docCost: asymDocCost,
|
|
3323
|
+
queryCostPerMonth: 0,
|
|
3324
|
+
totalCost: asymDocCost,
|
|
3325
|
+
months,
|
|
3326
|
+
localNote: 'Query cost = $0 (runs locally via HuggingFace)',
|
|
3327
|
+
});
|
|
3328
|
+
|
|
3329
|
+
strategies.sort((a, b) => a.totalCost - b.totalCost);
|
|
3330
|
+
const maxCost = Math.max(...strategies.map(s => s.totalCost), 0.01);
|
|
3331
|
+
|
|
3332
|
+
// ── Render summary cards ──
|
|
3333
|
+
const summaryEl = document.getElementById('ragSummary');
|
|
3334
|
+
summaryEl.innerHTML = `
|
|
3335
|
+
<div class="cost-summary-card">
|
|
3336
|
+
<div class="cost-summary-label">Document tokens</div>
|
|
3337
|
+
<div class="cost-summary-value">${costShortNum(docTotalTokens)}</div>
|
|
3338
|
+
<div class="cost-summary-detail">${costShortNum(numDocs)} docs × ${docTokens.toLocaleString()} tok</div>
|
|
3339
|
+
</div>
|
|
3340
|
+
<div class="cost-summary-card">
|
|
3341
|
+
<div class="cost-summary-label">Query tokens / mo</div>
|
|
3342
|
+
<div class="cost-summary-value">${costShortNum(queryTotalTokensPerMonth)}</div>
|
|
3343
|
+
<div class="cost-summary-detail">${costShortNum(numQueries)} queries × ${queryTokens} tok</div>
|
|
3344
|
+
</div>
|
|
3345
|
+
<div class="cost-summary-card">
|
|
3346
|
+
<div class="cost-summary-label">Best ${months}-mo total</div>
|
|
3347
|
+
<div class="cost-summary-value">${costFormatDollars(strategies[0].totalCost)}</div>
|
|
3348
|
+
<div class="cost-summary-detail">${strategies[0].name}</div>
|
|
3349
|
+
</div>
|
|
3350
|
+
<div class="cost-summary-card">
|
|
3351
|
+
<div class="cost-summary-label">Max potential savings</div>
|
|
3352
|
+
<div class="cost-summary-value" style="color:var(--success)">${maxCost > 0 ? ((1 - strategies[0].totalCost / maxCost) * 100).toFixed(0) + '%' : '0%'}</div>
|
|
3353
|
+
<div class="cost-summary-detail">vs ${strategies[strategies.length - 1].name.split(':')[1]?.trim() || 'most expensive'}</div>
|
|
3354
|
+
</div>
|
|
3355
|
+
`;
|
|
3356
|
+
|
|
3357
|
+
// ── Render strategy cards ──
|
|
3358
|
+
const stratEl = document.getElementById('ragStrategies');
|
|
3359
|
+
stratEl.innerHTML = strategies.map(s => {
|
|
3360
|
+
const savings = maxCost > 0 ? ((1 - s.totalCost / maxCost) * 100) : 0;
|
|
3361
|
+
const savingsHtml = savings > 0 ? `<div class="cost-savings">↓ ${savings.toFixed(0)}% savings</div>` : '';
|
|
3362
|
+
const badgeHtml = s.recommended ? '<div class="cost-strategy-badge">★ Recommended</div>' : '';
|
|
3363
|
+
const localHtml = s.localNote ? `<div style="font-size:11px;color:var(--text-muted);margin-top:6px;">⚡ ${s.localNote}</div>` : '';
|
|
3364
|
+
|
|
3365
|
+
return `
|
|
3366
|
+
<div class="cost-strategy${s.recommended ? ' recommended' : ''}">
|
|
3367
|
+
${badgeHtml}
|
|
3368
|
+
<div class="cost-strategy-name">${s.name}</div>
|
|
3369
|
+
<div class="cost-strategy-row">
|
|
3370
|
+
<span class="cost-strategy-row-label">Doc embedding</span>
|
|
3371
|
+
<span class="cost-strategy-row-value">${costFormatDollars(s.docCost)} <span style="color:var(--text-muted);font-size:11px">(one-time)</span></span>
|
|
3372
|
+
</div>
|
|
3373
|
+
<div class="cost-strategy-row">
|
|
3374
|
+
<span class="cost-strategy-row-label">Query cost</span>
|
|
3375
|
+
<span class="cost-strategy-row-value">${costFormatDollars(s.queryCostPerMonth)}/mo</span>
|
|
3376
|
+
</div>
|
|
3377
|
+
<div class="cost-strategy-total">
|
|
3378
|
+
<span class="cost-strategy-total-label">${s.months}-month total</span>
|
|
3379
|
+
<span class="cost-strategy-total-value">${costFormatDollars(s.totalCost)}</span>
|
|
3380
|
+
</div>
|
|
3381
|
+
${savingsHtml}
|
|
3382
|
+
${localHtml}
|
|
3383
|
+
</div>
|
|
3384
|
+
`;
|
|
3385
|
+
}).join('');
|
|
3386
|
+
|
|
3387
|
+
// ── Render per-model table ──
|
|
3388
|
+
const ragTbody = document.getElementById('ragTableBody');
|
|
3389
|
+
ragTbody.innerHTML = '';
|
|
3390
|
+
|
|
3391
|
+
const allEmbedding = allModels.filter(m =>
|
|
3392
|
+
m.type === 'embedding' && !m.legacy && !m.unreleased && costGetPricePerM(m) !== null && costGetPricePerM(m) > 0
|
|
3393
|
+
);
|
|
3394
|
+
|
|
3395
|
+
const tableRows = allEmbedding.map(m => {
|
|
3396
|
+
const price = costGetPricePerM(m);
|
|
3397
|
+
const dCost = (docTotalTokens / 1e6) * price;
|
|
3398
|
+
const qCostMo = (queryTotalTokensPerMonth / 1e6) * price;
|
|
3399
|
+
const total = dCost + (qCostMo * months);
|
|
3400
|
+
return { name: m.name, dCost, qCostMo, total };
|
|
3401
|
+
}).sort((a, b) => a.total - b.total);
|
|
3402
|
+
|
|
3403
|
+
const maxTable = Math.max(...tableRows.map(r => r.total), 0.01);
|
|
3404
|
+
|
|
3405
|
+
tableRows.forEach(r => {
|
|
3406
|
+
const tr = document.createElement('tr');
|
|
3407
|
+
const barPct = Math.max(2, (r.total / maxTable) * 100);
|
|
3408
|
+
tr.innerHTML = `
|
|
3409
|
+
<td style="color:var(--text)">${r.name}</td>
|
|
3410
|
+
<td>${costFormatDollars(r.dCost)}</td>
|
|
3411
|
+
<td>${costFormatDollars(r.qCostMo)}</td>
|
|
3412
|
+
<td class="cost-highlight">${costFormatDollars(r.total)}</td>
|
|
3413
|
+
<td class="cost-bar-cell" style="position:relative;padding-left:8px;">
|
|
3414
|
+
<div class="cost-bar" style="width:${barPct}%;"></div>
|
|
3415
|
+
<span style="position:relative;z-index:1;font-size:12px;color:var(--text-dim);">${costFormatDollars(r.total)}</span>
|
|
3416
|
+
</td>
|
|
3417
|
+
`;
|
|
3418
|
+
ragTbody.appendChild(tr);
|
|
3419
|
+
});
|
|
3420
|
+
|
|
3421
|
+
// ── Tip ──
|
|
3422
|
+
const bestSym = strategies.find(s => s.type === 'symmetric' && s.docModel === 'voyage-4-large');
|
|
3423
|
+
const bestAsym = strategies.find(s => s.recommended);
|
|
3424
|
+
const tipEl = document.getElementById('ragTip');
|
|
3425
|
+
if (bestSym && bestAsym && bestSym.totalCost > bestAsym.totalCost) {
|
|
3426
|
+
const saved = bestSym.totalCost - bestAsym.totalCost;
|
|
3427
|
+
const pct = ((saved / bestSym.totalCost) * 100).toFixed(0);
|
|
3428
|
+
tipEl.innerHTML = `💡 <strong>Asymmetric retrieval saves ${costFormatDollars(saved)} (${pct}%)</strong> over symmetric voyage-4-large — same document quality, lower query costs. The shared embedding space makes this possible. <code>vai explain shared-space</code>`;
|
|
3429
|
+
} else {
|
|
3430
|
+
tipEl.innerHTML = '💡 Try selecting different doc and query models to see asymmetric cost savings. <code>vai explain shared-space</code>';
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
|
|
2391
3434
|
// ── Benchmark: History ──
|
|
2392
3435
|
const HISTORY_KEY = 'vai-bench-history';
|
|
2393
3436
|
|
|
@@ -2462,6 +3505,7 @@ window.clearHistory = function() {
|
|
|
2462
3505
|
const _origInit = init;
|
|
2463
3506
|
init = async function() {
|
|
2464
3507
|
await _origInit();
|
|
3508
|
+
initThemeToggle();
|
|
2465
3509
|
buildModelCheckboxes();
|
|
2466
3510
|
populateBenchRankSelects();
|
|
2467
3511
|
populateQuantModelSelect();
|
|
@@ -2473,5 +3517,84 @@ init = async function() {
|
|
|
2473
3517
|
init();
|
|
2474
3518
|
})();
|
|
2475
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>
|
|
2476
3599
|
</body>
|
|
2477
3600
|
</html>
|