404 lines
20 KiB
HTML
404 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Model Benchmark — Nexus One AI</title>
|
||
<link rel="stylesheet" href="style.css?v=4">
|
||
<style>
|
||
.bm-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; padding:28px; margin-bottom:24px; }
|
||
.bm-card-title { font-size:16px; font-weight:700; color:var(--ink); margin-bottom:4px; }
|
||
.bm-card-sub { font-size:13px; color:var(--lt); margin-bottom:18px; }
|
||
|
||
.bm-btn { padding:9px 18px; border-radius:8px; font-size:13px; font-weight:600; cursor:pointer; border:none; font-family:inherit; transition:.15s; }
|
||
.bm-btn.primary { background:var(--teal); color:var(--ink); }
|
||
.bm-btn.primary:hover { background:#0B7A70; }
|
||
.bm-btn.ghost { background:rgba(255,255,255,.03); color:var(--med); border:1px solid var(--bdr); }
|
||
.bm-btn.ghost:hover { border-color:var(--teal); color:var(--teal); }
|
||
.bm-btn:disabled { opacity:.45; cursor:not-allowed; }
|
||
|
||
/* Model chips */
|
||
.bm-model-row { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:10px; }
|
||
.bm-model-chip { display:inline-flex; align-items:center; gap:6px; background:rgba(13,148,136,.12); border:1px solid #99F6E4; border-radius:20px; padding:5px 12px; font-size:13px; font-weight:600; color:#0F766E; }
|
||
.bm-model-chip button { background:none; border:none; cursor:pointer; color:#0F766E; font-size:14px; line-height:1; padding:0; }
|
||
.bm-model-chip button:hover { color:#B91C1C; }
|
||
.bm-add-row { display:flex; gap:8px; align-items:center; }
|
||
.bm-select { padding:8px 12px; border:1.5px solid var(--bdr); border-radius:8px; font-size:13px; font-family:inherit; outline:none; color:var(--ink); }
|
||
.bm-select:focus { border-color:var(--teal); }
|
||
|
||
/* Prompt list */
|
||
.bm-prompt-list { display:flex; flex-direction:column; gap:8px; margin-bottom:10px; }
|
||
.bm-prompt-item { display:flex; gap:8px; align-items:flex-start; }
|
||
.bm-prompt-item textarea { flex:1; padding:8px 12px; border:1.5px solid var(--bdr); border-radius:8px; font-size:13px; font-family:inherit; outline:none; color:var(--ink); resize:vertical; min-height:52px; }
|
||
.bm-prompt-item textarea:focus { border-color:var(--teal); }
|
||
.bm-prompt-item button { padding:8px 10px; background:rgba(185,28,28,.08); color:#B91C1C; border:1px solid rgba(239,68,68,.25); border-radius:8px; cursor:pointer; font-size:14px; font-family:inherit; }
|
||
.bm-prompt-item button:hover { background:#DC2626; color:var(--ink); }
|
||
|
||
/* Results */
|
||
.bm-results { margin-top:4px; }
|
||
.bm-prompt-block { border:1px solid var(--bdr); border-radius:12px; overflow:hidden; margin-bottom:20px; }
|
||
.bm-prompt-header { background:rgba(255,255,255,.03); padding:12px 16px; font-size:13px; font-weight:700; color:var(--ink); border-bottom:1px solid var(--bdr); }
|
||
.bm-responses { display:grid; gap:0; }
|
||
.bm-response-cell { padding:16px; border-bottom:1px solid var(--bdr); }
|
||
.bm-response-cell:last-child { border-bottom:none; }
|
||
.bm-model-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; }
|
||
.bm-model-name { font-size:13px; font-weight:700; color:var(--teal); }
|
||
.bm-metrics { display:flex; gap:14px; flex-wrap:wrap; }
|
||
.bm-metric { font-size:11px; color:var(--lt); }
|
||
.bm-metric strong { color:var(--ink); font-size:13px; font-weight:700; display:block; }
|
||
.bm-response-text { font-size:13px; color:var(--ink); line-height:1.7; white-space:pre-wrap; background:rgba(255,255,255,.03); border-radius:8px; padding:12px; border:1px solid var(--bdr); max-height:280px; overflow-y:auto; }
|
||
.bm-error { font-size:12px; color:#B91C1C; background:rgba(185,28,28,.08); border-radius:8px; padding:10px 12px; }
|
||
|
||
/* Speed bar */
|
||
.bm-speed-bar { height:4px; border-radius:2px; background:var(--teal); margin-top:4px; transition:width .4s; }
|
||
|
||
/* Tier gate */
|
||
.tier-gate { background:#FFFBEB; border:1px solid #FDE68A; border-radius:12px; padding:28px; text-align:center; }
|
||
.tier-gate h3 { color:#92400E; margin:0 0 8px; }
|
||
.tier-gate p { color:#B45309; font-size:13px; margin:0; }
|
||
|
||
/* Running state */
|
||
.bm-running { display:flex; align-items:center; gap:10px; padding:24px; color:var(--teal); font-weight:600; font-size:14px; }
|
||
.bm-spinner { width:20px; height:20px; border:2.5px solid #99F6E4; border-top-color:var(--teal); border-radius:50%; animation:spin .7s linear infinite; }
|
||
@keyframes spin { to { transform:rotate(360deg); } }
|
||
|
||
/* Toast */
|
||
#bm-toast { position:fixed; bottom:24px; right:24px; background:#1E3A5F; color:var(--ink); padding:12px 20px; border-radius:10px; font-size:13px; display:none; z-index:9999; box-shadow:0 8px 24px rgba(0,0,0,.2); max-width:340px; }
|
||
#bm-toast.err { background:#7F1D1D; }
|
||
</style>
|
||
</head>
|
||
<body data-role="admin">
|
||
|
||
<header class="topnav">
|
||
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
|
||
<nav>
|
||
<a href="index.html">Home</a>
|
||
<a href="quickstart.html">Quick Start</a>
|
||
<a href="prompts.html">Prompt Library</a>
|
||
<a href="usecases.html">Use Cases</a>
|
||
<span class="nav-sep"></span>
|
||
<div class="nav-dropdown">
|
||
<button class="nav-drop-btn">Help ▾</button>
|
||
<div class="nav-drop-menu">
|
||
<span class="nav-drop-cat">LEARN /</span>
|
||
<a href="quickstart.html">Quick Start</a>
|
||
<a href="models.html">Models</a>
|
||
<span class="nav-drop-cat">SUPPORT /</span>
|
||
<a href="troubleshooting.html">Troubleshoot</a>
|
||
<a href="faq.html">FAQ</a>
|
||
<span class="nav-drop-cat">MORE /</span>
|
||
<a href="glossary.html">Glossary</a>
|
||
<a href="whats-new.html">What's New</a>
|
||
</div>
|
||
</div>
|
||
<div class="nav-dropdown">
|
||
<button class="nav-drop-btn active">Admin ▾</button>
|
||
<div class="nav-drop-menu nav-drop-menu-wide">
|
||
<span class="nav-drop-cat">DOCS /</span>
|
||
<a href="security.html">Security & Privacy</a>
|
||
<a href="admin.html">Admin Guide</a>
|
||
<span class="nav-drop-cat">MONITOR /</span>
|
||
<a href="dashboard.html">Dashboard</a>
|
||
<a href="analytics.html">Usage Analytics</a>
|
||
<a href="audit.html">Audit Log</a>
|
||
<a href="feedback.html">Feedback & Ratings</a>
|
||
<span class="nav-drop-cat">MANAGE /</span>
|
||
<a href="users.html">Users</a>
|
||
<a href="teams.html">Teams</a>
|
||
<a href="models-admin.html">Model Manager</a>
|
||
<a href="training.html">Training</a>
|
||
<a href="knowledge.html">Knowledge Base</a>
|
||
<span class="nav-drop-cat">TOOLS /</span>
|
||
<a href="apikeys.html">API Keys</a>
|
||
<a href="benchmark.html">Benchmarking</a>
|
||
<a href="model-compare.html">Model Compare</a>
|
||
<a href="api-playground.html">API Playground</a>
|
||
<a href="guardrails.html">Guardrails</a>
|
||
<a href="rag-quality.html">RAG Quality</a>
|
||
<a href="router.html">Model Router</a>
|
||
<a href="connectors.html">Connectors</a>
|
||
<span class="nav-drop-cat">SYSTEM /</span>
|
||
<a href="console.html">Console</a>
|
||
<a href="settings.html">Settings</a>
|
||
</div>
|
||
</div>
|
||
<div class="nav-dropdown">
|
||
<button class="nav-drop-btn">AI Tools ▾</button>
|
||
<div class="nav-drop-menu">
|
||
<span class="nav-drop-cat">INTELLIGENCE /</span>
|
||
<a href="documents.html">Document Intelligence</a>
|
||
<a href="chat-multi.html">Multimodal Chat</a>
|
||
<a href="prompt-studio.html">Prompt Studio</a>
|
||
<a href="meeting.html">Meeting Assistant</a>
|
||
<span class="nav-drop-cat">AUTOMATION /</span>
|
||
<a href="agents.html">Agent Builder</a>
|
||
<a href="schedules.html">Scheduled Jobs</a>
|
||
<a href="workflows.html">Workflow Automation</a>
|
||
<span class="nav-drop-cat">QUALITY /</span>
|
||
<a href="evals.html">AI Eval Suite</a>
|
||
<a href="chatrooms.html">Chat Rooms</a>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
<a href="notifications.html" style="position:relative">🔔</a>
|
||
<span class="badge" data-brand="tier">Basic Tier</span>
|
||
<div id="nav-org-logo" class="nav-org-logo"></div>
|
||
</header>
|
||
|
||
<div class="page-hero">
|
||
<div class="label">Admin · Evaluation</div>
|
||
<h1>Model Benchmarking</h1>
|
||
<p>Compare response quality and inference speed across models side by side using your own test prompts.</p>
|
||
</div>
|
||
|
||
<div class="content">
|
||
|
||
<!-- Tier gate -->
|
||
<div class="tier-gate" id="tier-gate" style="display:none">
|
||
<h3>📊 Pro Tier & Above</h3>
|
||
<p>Model benchmarking requires the hardware throughput of Mid or Advanced tier to produce meaningful latency comparisons.</p>
|
||
</div>
|
||
|
||
<div id="bm-main" style="display:none">
|
||
|
||
<!-- Model selection -->
|
||
<div class="bm-card">
|
||
<div class="bm-card-title">🤖 Select Models</div>
|
||
<div class="bm-card-sub">Choose up to 5 models to compare. Models must be loaded in Ollama.</div>
|
||
<div class="bm-model-row" id="selected-models"></div>
|
||
<div class="bm-add-row">
|
||
<select class="bm-select" id="model-picker" style="min-width:220px">
|
||
<option value="">Loading models…</option>
|
||
</select>
|
||
<button class="bm-btn ghost" onclick="addModel()">+ Add</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Prompt configuration -->
|
||
<div class="bm-card">
|
||
<div class="bm-card-title">💬 Test Prompts</div>
|
||
<div class="bm-card-sub">Add prompts to run against each model. Up to 10 prompts.</div>
|
||
<div class="bm-prompt-list" id="prompt-list"></div>
|
||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||
<button class="bm-btn ghost" onclick="addPrompt('')">+ Add prompt</button>
|
||
<button class="bm-btn ghost" onclick="loadPresets()">📋 Load presets</button>
|
||
<label style="font-size:12px;color:var(--lt);margin-left:8px">Max tokens:
|
||
<input type="number" id="max-tokens" value="256" min="64" max="2048"
|
||
style="width:70px;padding:4px 8px;border:1px solid var(--bdr);border-radius:6px;font-size:12px;margin-left:4px">
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Run button -->
|
||
<div style="display:flex;gap:10px;align-items:center;margin-bottom:28px">
|
||
<button class="bm-btn primary" id="run-btn" onclick="runBenchmark()" style="padding:11px 28px;font-size:14px">▶ Run Benchmark</button>
|
||
<span style="font-size:12px;color:var(--lt)" id="run-hint"></span>
|
||
</div>
|
||
|
||
<!-- Results -->
|
||
<div id="bm-results"></div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="bm-toast"></div>
|
||
|
||
<footer>
|
||
<p>Nexus One AI · Powered by Cezen · <span data-brand="tier">Basic Tier</span></p>
|
||
<p>Questions? <a href="mailto:support@cezentech.com">support@cezentech.com</a> · <a href="https://cezentech.com" target="_blank">cezentech.com</a></p>
|
||
</footer>
|
||
|
||
<script>
|
||
const _API = '/api';
|
||
let selectedModels = [];
|
||
|
||
const PRESET_PROMPTS = [
|
||
"Summarise the following text in 3 bullet points: The Indian Space Research Organisation (ISRO) launched its first rocket in 1963 from Thumba Equatorial Rocket Launching Station. Since then, ISRO has grown to become one of the world's leading space agencies, successfully launching over 100 satellites and developing indigenous cryogenic engine technology.",
|
||
"Draft a one-paragraph formal response acknowledging receipt of a tender document submission.",
|
||
"What are the key differences between RAG (Retrieval Augmented Generation) and fine-tuning for customising an LLM? Answer in 4–5 sentences.",
|
||
"Translate the following to Hindi: Our organisation is committed to delivering high-quality services to all citizens in a timely and transparent manner.",
|
||
"Write a 5-question multiple choice quiz about basic workplace safety procedures."
|
||
];
|
||
|
||
// ── Tier gate ─────────────────────────────────────────────────────────────────
|
||
function checkTier() {
|
||
// Tier gate disabled for testing — all features visible
|
||
document.getElementById("tier-gate").style.display = "none";
|
||
var main = document.getElementById("bm-main");
|
||
if (main) main.style.display = "";
|
||
}
|
||
|
||
// ── Load models ───────────────────────────────────────────────────────────────
|
||
async function loadModels() {
|
||
try {
|
||
const res = await fetch(`${_API}/models/list`);
|
||
const data = res.ok ? await res.json() : { models: [] };
|
||
const picker = document.getElementById('model-picker');
|
||
const models = (data.models || []).map(m => typeof m === 'string' ? m : m.name || m.model || '').filter(Boolean);
|
||
picker.innerHTML = models.length
|
||
? '<option value="">— select model —</option>' + models.map(m => `<option value="${escHtml(m)}">${escHtml(m)}</option>`).join('')
|
||
: '<option value="">llama3:70b</option><option value="llama3:70b">llama3:70b</option><option value="mistral:7b">mistral:7b</option><option value="codellama:34b">codellama:34b</option>';
|
||
} catch(e) {
|
||
document.getElementById('model-picker').innerHTML = '<option value="">— select model —</option><option value="llama3:70b">llama3:70b</option><option value="mistral:7b">mistral:7b</option><option value="codellama:34b">codellama:34b</option>';
|
||
}
|
||
}
|
||
|
||
// ── Model chips ───────────────────────────────────────────────────────────────
|
||
function addModel() {
|
||
const val = document.getElementById('model-picker').value;
|
||
if (!val) return;
|
||
if (selectedModels.includes(val)) { toast('Already added', true); return; }
|
||
if (selectedModels.length >= 5) { toast('Max 5 models', true); return; }
|
||
selectedModels.push(val);
|
||
renderModelChips();
|
||
updateHint();
|
||
}
|
||
|
||
function removeModel(name) {
|
||
selectedModels = selectedModels.filter(m => m !== name);
|
||
renderModelChips();
|
||
updateHint();
|
||
}
|
||
|
||
function renderModelChips() {
|
||
const el = document.getElementById('selected-models');
|
||
el.innerHTML = selectedModels.map(m =>
|
||
`<span class="bm-model-chip">${escHtml(m)}<button onclick="removeModel('${escHtml(m).replace(/'/g,"\\'")}')">×</button></span>`
|
||
).join('');
|
||
}
|
||
|
||
// ── Prompts ───────────────────────────────────────────────────────────────────
|
||
function addPrompt(text) {
|
||
const list = document.getElementById('prompt-list');
|
||
const items = list.querySelectorAll('.bm-prompt-item');
|
||
if (items.length >= 10) { toast('Max 10 prompts', true); return; }
|
||
const div = document.createElement('div');
|
||
div.className = 'bm-prompt-item';
|
||
div.innerHTML = `
|
||
<textarea placeholder="Enter a test prompt…">${escHtml(text)}</textarea>
|
||
<button onclick="this.parentElement.remove(); updateHint()">🗑</button>
|
||
`;
|
||
list.appendChild(div);
|
||
updateHint();
|
||
}
|
||
|
||
function loadPresets() {
|
||
const list = document.getElementById('prompt-list');
|
||
list.innerHTML = '';
|
||
PRESET_PROMPTS.forEach(p => addPrompt(p));
|
||
}
|
||
|
||
function getPrompts() {
|
||
return Array.from(document.querySelectorAll('#prompt-list textarea'))
|
||
.map(t => t.value.trim()).filter(Boolean);
|
||
}
|
||
|
||
function updateHint() {
|
||
const m = selectedModels.length, p = getPrompts().length;
|
||
const hint = document.getElementById('run-hint');
|
||
if (m && p) hint.textContent = `${m} model${m>1?'s':''} × ${p} prompt${p>1?'s':''} = ${m*p} request${m*p>1?'s':''}`;
|
||
else hint.textContent = '';
|
||
}
|
||
|
||
// ── Run ───────────────────────────────────────────────────────────────────────
|
||
async function runBenchmark() {
|
||
const prompts = getPrompts();
|
||
if (!selectedModels.length) { toast('Select at least one model', true); return; }
|
||
if (!prompts.length) { toast('Add at least one prompt', true); return; }
|
||
|
||
const btn = document.getElementById('run-btn');
|
||
btn.disabled = true;
|
||
const resultsEl = document.getElementById('bm-results');
|
||
resultsEl.innerHTML = `<div class="bm-running"><div class="bm-spinner"></div>Running ${selectedModels.length * prompts.length} inference request${selectedModels.length * prompts.length > 1 ? 's' : ''}… this may take a minute.</div>`;
|
||
|
||
try {
|
||
const res = await fetch(`${_API}/benchmark/run`, {
|
||
method: 'POST',
|
||
headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({
|
||
models: selectedModels,
|
||
prompts,
|
||
max_tokens: parseInt(document.getElementById('max-tokens').value) || 256
|
||
})
|
||
});
|
||
if (!res.ok) throw new Error((await res.json()).detail || 'Benchmark failed');
|
||
const data = await res.json();
|
||
renderResults(data);
|
||
} catch(e) {
|
||
resultsEl.innerHTML = `<div style="color:#B91C1C;padding:16px">${escHtml(e.message)}</div>`;
|
||
} finally {
|
||
btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
function renderResults(data) {
|
||
const el = document.getElementById('bm-results');
|
||
if (!data.results?.length) { el.innerHTML = '<div style="color:var(--lt)">No results</div>'; return; }
|
||
|
||
// Find max tokens/sec across all for speed bar scaling
|
||
const allTps = data.results.flatMap(r => data.models.map(m => r.responses[m]?.tokens_per_sec || 0));
|
||
const maxTps = Math.max(...allTps, 1);
|
||
|
||
let html = '<div class="bm-results">';
|
||
data.results.forEach((row, i) => {
|
||
html += `<div class="bm-prompt-block">
|
||
<div class="bm-prompt-header">Prompt ${i+1}: ${escHtml(row.prompt.slice(0,120))}${row.prompt.length > 120 ? '…' : ''}</div>`;
|
||
|
||
// Make grid columns for multi-model
|
||
const cols = data.models.length;
|
||
html += `<div class="bm-responses" style="grid-template-columns:repeat(${cols},1fr);display:grid">`;
|
||
data.models.forEach(model => {
|
||
const r = row.responses[model] || {};
|
||
const tps = r.tokens_per_sec || 0;
|
||
const barPct = maxTps > 0 ? Math.round((tps / maxTps) * 100) : 0;
|
||
html += `<div class="bm-response-cell" style="${cols > 1 ? 'border-right:1px solid var(--bdr)' : ''}">
|
||
<div class="bm-model-header">
|
||
<span class="bm-model-name">${escHtml(model)}</span>
|
||
</div>
|
||
<div class="bm-metrics">
|
||
<div class="bm-metric"><strong>${r.duration_ms ? (r.duration_ms/1000).toFixed(2)+'s' : '—'}</strong>Latency</div>
|
||
<div class="bm-metric"><strong>${tps ? tps.toFixed(1) : '—'}</strong>tok/s
|
||
<div class="bm-speed-bar" style="width:${barPct}%"></div>
|
||
</div>
|
||
<div class="bm-metric"><strong>${r.completion_tokens || '—'}</strong>Output tokens</div>
|
||
</div>
|
||
${r.error
|
||
? `<div class="bm-error">⚠ ${escHtml(r.error)}</div>`
|
||
: `<div class="bm-response-text">${escHtml(r.text || '(empty response)')}</div>`
|
||
}
|
||
</div>`;
|
||
});
|
||
html += '</div></div>';
|
||
});
|
||
html += '</div>';
|
||
el.innerHTML = html;
|
||
|
||
// Scroll to results
|
||
el.scrollIntoView({ behavior:'smooth', block:'start' });
|
||
}
|
||
|
||
// ── Utils ─────────────────────────────────────────────────────────────────────
|
||
function escHtml(s) {
|
||
return (s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
function toast(msg, err) {
|
||
const el = document.getElementById('bm-toast');
|
||
el.textContent = msg; el.className = err ? 'err' : '';
|
||
el.style.display = 'block';
|
||
setTimeout(() => el.style.display = 'none', 3000);
|
||
}
|
||
|
||
document.getElementById('prompt-list').addEventListener('input', updateHint);
|
||
</script>
|
||
|
||
<script src="auth.js"></script>
|
||
<script>
|
||
document.addEventListener('cezenAuthReady', function() {
|
||
setTimeout(checkTier, 200);
|
||
});
|
||
</script>
|
||
<script src="branding.js"></script>
|
||
</body>
|
||
</html>
|