voyageai-cli 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/playground.js +22 -0
- package/src/playground/index.html +74 -72
package/package.json
CHANGED
|
@@ -90,6 +90,28 @@ function createPlaygroundServer() {
|
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// API: Concepts (from vai explain)
|
|
94
|
+
if (req.method === 'GET' && req.url === '/api/concepts') {
|
|
95
|
+
const { concepts } = require('../lib/explanations');
|
|
96
|
+
// Strip picocolors ANSI from content for web display
|
|
97
|
+
// eslint-disable-next-line no-control-regex
|
|
98
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
99
|
+
const stripped = {};
|
|
100
|
+
for (const [key, concept] of Object.entries(concepts)) {
|
|
101
|
+
stripped[key] = {
|
|
102
|
+
title: concept.title,
|
|
103
|
+
summary: concept.summary,
|
|
104
|
+
content: (typeof concept.content === 'string' ? concept.content : concept.content).replace(ANSI_RE, ''),
|
|
105
|
+
links: concept.links || [],
|
|
106
|
+
tryIt: concept.tryIt || [],
|
|
107
|
+
keyPoints: concept.keyPoints || [],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
111
|
+
res.end(JSON.stringify({ concepts: stripped }));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
93
115
|
// API: Config
|
|
94
116
|
if (req.method === 'GET' && req.url === '/api/config') {
|
|
95
117
|
const key = process.env.VOYAGE_API_KEY || getConfigValue('apiKey');
|
|
@@ -1003,6 +1003,9 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1003
1003
|
|
|
1004
1004
|
<!-- ========== EXPLORE TAB ========== -->
|
|
1005
1005
|
<div class="tab-panel" id="tab-explore">
|
|
1006
|
+
<div style="margin-bottom:16px;">
|
|
1007
|
+
<input type="text" id="exploreSearch" placeholder="🔍 Search concepts..." oninput="filterExplore()" style="max-width:400px;">
|
|
1008
|
+
</div>
|
|
1006
1009
|
<div class="explore-grid" id="exploreGrid"></div>
|
|
1007
1010
|
</div>
|
|
1008
1011
|
|
|
@@ -1022,7 +1025,7 @@ let lastEmbedding = null;
|
|
|
1022
1025
|
async function init() {
|
|
1023
1026
|
setupTabs();
|
|
1024
1027
|
await loadConfig();
|
|
1025
|
-
await loadModels();
|
|
1028
|
+
await Promise.all([loadModels(), loadConcepts()]);
|
|
1026
1029
|
populateModelSelects();
|
|
1027
1030
|
buildExploreCards();
|
|
1028
1031
|
}
|
|
@@ -1393,103 +1396,102 @@ function createResultItem(rank, result, maxScore, movement) {
|
|
|
1393
1396
|
}
|
|
1394
1397
|
|
|
1395
1398
|
// ── Explore ──
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
},
|
|
1403
|
-
{
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
content: 'Cosine similarity measures the angle between two vectors, ignoring magnitude. Vectors pointing the same direction score 1, perpendicular score 0, opposite score -1.\n\nFor text embeddings (which are typically normalized), cosine similarity and dot product give identical rankings. Cosine is preferred because it\'s intuitive: it measures how similar the direction (meaning) is, regardless of scale.',
|
|
1428
|
-
tab: 'compare', prefill: () => {
|
|
1429
|
-
document.getElementById('compareA').value = 'The database stores information efficiently';
|
|
1430
|
-
document.getElementById('compareB').value = 'Data is saved in an optimized storage system';
|
|
1431
|
-
}
|
|
1432
|
-
},
|
|
1433
|
-
{
|
|
1434
|
-
key: 'two-stage', icon: '🎯', title: 'Two-Stage Retrieval',
|
|
1435
|
-
summary: 'Embed → Search → Rerank for best results',
|
|
1436
|
-
content: 'Two-stage retrieval combines a fast first stage (embedding search for recall) with a precise second stage (reranking for precision).\n\nStage 1: Embed query, run ANN search, retrieve top-100 candidates (fast, milliseconds). Stage 2: Feed query + candidates to a reranker with cross-attention, return top-5-10 (precise, ~100ms extra). This gives you both speed and accuracy.',
|
|
1437
|
-
tab: 'search', prefill: () => {}
|
|
1438
|
-
},
|
|
1439
|
-
{
|
|
1440
|
-
key: 'input-types', icon: '🏷️', title: 'Input Types',
|
|
1441
|
-
summary: 'Query vs document — why it matters',
|
|
1442
|
-
content: 'The input_type parameter tells the model whether text is a search query or a document being indexed. The model internally prepends different prompt prefixes for each, optimizing embeddings for asymmetric retrieval.\n\nAlways use input_type="query" for search queries and input_type="document" for corpus text. Omitting this parameter degrades retrieval accuracy.',
|
|
1443
|
-
tab: 'embed', prefill: () => {
|
|
1444
|
-
document.getElementById('embedInput').value = 'What is vector search and how does it work?';
|
|
1445
|
-
document.getElementById('embedInputType').value = 'query';
|
|
1446
|
-
}
|
|
1447
|
-
},
|
|
1448
|
-
{
|
|
1449
|
-
key: 'models', icon: '🧠', title: 'Models',
|
|
1450
|
-
summary: 'Choosing the right model for your task',
|
|
1451
|
-
content: 'Voyage 4 Series: voyage-4-large (best quality, $0.12/1M tokens), voyage-4 (balanced, $0.06), voyage-4-lite (budget, $0.02). All share the same embedding space — you can mix models.\n\nDomain-specific: voyage-code-3 (code), voyage-finance-2 (financial), voyage-law-2 (legal). Rerankers: rerank-2.5 (best quality), rerank-2.5-lite (faster). Start with voyage-4 for general use.',
|
|
1452
|
-
tab: 'embed', prefill: () => {}
|
|
1453
|
-
},
|
|
1454
|
-
];
|
|
1399
|
+
// ── Explore: icons and tab mappings per concept ──
|
|
1400
|
+
const CONCEPT_META = {
|
|
1401
|
+
embeddings: { icon: '🧮', tab: 'embed' },
|
|
1402
|
+
reranking: { icon: '🏆', tab: 'search' },
|
|
1403
|
+
'vector-search': { icon: '🔎', tab: 'search' },
|
|
1404
|
+
rag: { icon: '🤖', tab: 'search' },
|
|
1405
|
+
'cosine-similarity': { icon: '📐', tab: 'compare' },
|
|
1406
|
+
'two-stage-retrieval': { icon: '🎯', tab: 'search' },
|
|
1407
|
+
'input-type': { icon: '🏷️', tab: 'embed' },
|
|
1408
|
+
models: { icon: '🧠', tab: 'embed' },
|
|
1409
|
+
'api-keys': { icon: '🔑', tab: 'embed' },
|
|
1410
|
+
'api-access': { icon: '🌐', tab: 'embed' },
|
|
1411
|
+
'batch-processing': { icon: '📦', tab: 'embed' },
|
|
1412
|
+
benchmarking: { icon: '⏱', tab: 'benchmark' },
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1415
|
+
let exploreConcepts = {};
|
|
1416
|
+
|
|
1417
|
+
async function loadConcepts() {
|
|
1418
|
+
try {
|
|
1419
|
+
const res = await fetch('/api/concepts');
|
|
1420
|
+
const data = await res.json();
|
|
1421
|
+
exploreConcepts = data.concepts || {};
|
|
1422
|
+
} catch {
|
|
1423
|
+
console.error('Failed to load concepts');
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
function escapeHtml(str) {
|
|
1428
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
1429
|
+
}
|
|
1455
1430
|
|
|
1456
1431
|
function buildExploreCards() {
|
|
1457
1432
|
const grid = document.getElementById('exploreGrid');
|
|
1458
1433
|
grid.innerHTML = '';
|
|
1459
|
-
|
|
1434
|
+
|
|
1435
|
+
for (const [key, concept] of Object.entries(exploreConcepts)) {
|
|
1436
|
+
const meta = CONCEPT_META[key] || { icon: '📚', tab: 'embed' };
|
|
1460
1437
|
const card = document.createElement('div');
|
|
1461
1438
|
card.className = 'explore-card';
|
|
1439
|
+
card.dataset.key = key;
|
|
1440
|
+
|
|
1441
|
+
// Build links HTML
|
|
1442
|
+
let linksHtml = '';
|
|
1443
|
+
if (concept.links && concept.links.length > 0) {
|
|
1444
|
+
linksHtml = '<div style="margin-top:12px;"><strong style="color:var(--accent);font-size:12px;">LEARN MORE</strong><br>' +
|
|
1445
|
+
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>') +
|
|
1446
|
+
'</div>';
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// Build try-it HTML
|
|
1450
|
+
let tryItHtml = '';
|
|
1451
|
+
if (concept.tryIt && concept.tryIt.length > 0) {
|
|
1452
|
+
tryItHtml = '<div style="margin-top:12px;"><strong style="color:var(--accent);font-size:12px;">TRY IT</strong>' +
|
|
1453
|
+
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('') +
|
|
1454
|
+
'</div>';
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1462
1457
|
card.innerHTML = `
|
|
1463
|
-
<div class="explore-card-icon">${
|
|
1464
|
-
<div class="explore-card-title">${
|
|
1465
|
-
<div class="explore-card-summary">${
|
|
1466
|
-
<div class="explore-card-content">${
|
|
1458
|
+
<div class="explore-card-icon">${meta.icon}</div>
|
|
1459
|
+
<div class="explore-card-title">${escapeHtml(concept.title)}</div>
|
|
1460
|
+
<div class="explore-card-summary">${escapeHtml(concept.summary)}</div>
|
|
1461
|
+
<div class="explore-card-content">${escapeHtml(concept.content)}${linksHtml}${tryItHtml}</div>
|
|
1467
1462
|
<div class="explore-card-actions">
|
|
1468
|
-
<button class="btn btn-small" onclick="tryTopic('${
|
|
1463
|
+
<button class="btn btn-small" onclick="tryTopic('${escapeHtml(key)}')">Try it in playground →</button>
|
|
1469
1464
|
<button class="btn btn-secondary btn-small" onclick="collapseTopic(this)">Collapse</button>
|
|
1470
1465
|
</div>
|
|
1471
1466
|
`;
|
|
1472
1467
|
card.addEventListener('click', function(e) {
|
|
1473
|
-
if (e.target.tagName === 'BUTTON') return;
|
|
1468
|
+
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'A') return;
|
|
1474
1469
|
if (!this.classList.contains('expanded')) {
|
|
1475
1470
|
this.classList.add('expanded');
|
|
1476
1471
|
}
|
|
1477
1472
|
});
|
|
1478
1473
|
grid.appendChild(card);
|
|
1479
|
-
}
|
|
1474
|
+
}
|
|
1480
1475
|
}
|
|
1481
1476
|
|
|
1482
1477
|
window.tryTopic = function(key) {
|
|
1483
|
-
const
|
|
1484
|
-
if (
|
|
1485
|
-
if (topic.prefill) topic.prefill();
|
|
1486
|
-
switchTab(topic.tab);
|
|
1478
|
+
const meta = CONCEPT_META[key];
|
|
1479
|
+
if (meta) switchTab(meta.tab);
|
|
1487
1480
|
};
|
|
1488
1481
|
|
|
1489
1482
|
window.collapseTopic = function(btn) {
|
|
1490
1483
|
btn.closest('.explore-card').classList.remove('expanded');
|
|
1491
1484
|
};
|
|
1492
1485
|
|
|
1486
|
+
window.filterExplore = function() {
|
|
1487
|
+
const q = document.getElementById('exploreSearch').value.toLowerCase().trim();
|
|
1488
|
+
document.querySelectorAll('#exploreGrid .explore-card').forEach(card => {
|
|
1489
|
+
if (!q) { card.style.display = ''; return; }
|
|
1490
|
+
const text = card.textContent.toLowerCase();
|
|
1491
|
+
card.style.display = text.includes(q) ? '' : 'none';
|
|
1492
|
+
});
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1493
1495
|
// ── Benchmark: Sub-panel switching ──
|
|
1494
1496
|
document.querySelectorAll('.bench-panel-btn').forEach(btn => {
|
|
1495
1497
|
btn.addEventListener('click', () => {
|