Files
llm-speed-tester/static/index.html
T

281 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LLM 速度测试台</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header class="topbar">
<div class="brand">
<span class="logo"></span>
<h1>LLM 速度测试台</h1>
<span class="sub">大模型推理性能基准 · 预填充 / 首字 / 解码</span>
</div>
<div class="status-badge" id="run-status"><span class="dot"></span>空闲</div>
</header>
<main class="layout">
<!-- 左侧:配置 -->
<aside class="config-panel">
<section class="card collapsible" id="sec-interface">
<h2 class="collapse-head">🔌 大模型接口配置<span class="collapse-arrow"></span></h2>
<div class="collapse-body">
<div class="field">
<label>提供商类型</label>
<select id="cfg-provider">
<option value="openai">OpenAI 兼容(OpenAI / DeepSeek / 火山方舟…)</option>
<option value="anthropic">AnthropicClaude</option>
<option value="google">Google Gemini</option>
</select>
</div>
<div class="field">
<label>已保存配置</label>
<div class="row">
<select id="cfg-list"><option value="">— 选择配置加载 —</option></select>
<button class="btn small" id="btn-load-config" title="加载选中配置">载入</button>
<button class="btn small primary" id="btn-save-config" title="保存当前输入">保存</button>
<button class="btn small danger" id="btn-del-config" title="删除选中配置">删除</button>
</div>
</div>
<div class="field">
<label>配置名称</label>
<div class="icon-input"><span class="icon">📛</span><input id="cfg-name" placeholder="例如:DeepSeek-V4"></div>
</div>
<div class="field">
<label>Base URL</label>
<div class="icon-input"><span class="icon">🌐</span><input id="cfg-baseurl" placeholder="留空使用官方默认地址"></div>
<div class="hint" id="cfg-default-url"></div>
</div>
<div class="field">
<label>API Key</label>
<div class="row">
<input id="cfg-apikey" type="password" placeholder="sk-... / key...">
<button class="btn small" id="btn-toggle-key" title="显示/隐藏">👁</button>
</div>
</div>
<div class="field">
<label>模型名称 <span class="hint-inline">可直接输入,也可点击「查看模型」实时获取</span></label>
<div class="row">
<div class="icon-input" style="flex:1"><span class="icon">🤖</span><input id="cfg-model" placeholder="如 gpt-4o / deepseek-chat / claude-sonnet-4"></div>
<button class="btn small" id="btn-list-models" title="实时获取此接口(Base URL + API Key)下的所有模型并选择填入">📋 查看模型</button>
</div>
<div class="model-pick" id="model-pick" hidden>
<div class="model-pick-head">
<input id="model-pick-search" placeholder="🔍 搜索模型(可直接输入模型名回车确认)" autocomplete="off">
<button class="btn small" id="model-pick-close" title="关闭"></button>
</div>
<div class="model-pick-list" id="model-pick-list"></div>
</div>
<div class="conn-result" id="model-result" hidden></div>
</div>
<div class="field">
<label>温度 Temperature <b id="tmp-val">0.7</b></label>
<input id="cfg-temp" type="range" min="0" max="2" step="0.1" value="0.7">
</div>
<button class="btn block" id="btn-test-conn">🔍 测试连接</button>
<div class="conn-result" id="conn-result" hidden></div>
</div>
</section>
<section class="card collapsible" id="sec-test">
<h2 class="collapse-head">🚀 速度测试配置<span class="collapse-arrow"></span></h2>
<div class="collapse-body">
<div class="field">
<label>测试名称(主题)</label>
<input id="gen-name" type="text" placeholder="如:DeepSeek-V4 不同上下文长度速度对比">
</div>
<div class="field">
<label>上下文长度(tokens,点击切换启停)</label>
<div class="chips" id="gen-contexts"></div>
<div class="row" style="margin-top:6px">
<input id="gen-context-add" type="number" min="16" step="16" placeholder="自定义长度(≥16">
<button class="btn small" id="btn-context-add"> 添加</button>
</div>
</div>
<div class="field">
<label>解码输出长度(max tokens</label>
<input id="gen-max-tokens" type="number" min="1" step="1" value="128">
</div>
<div class="field">
<label>每个长度×并发采样次数</label>
<input id="gen-samples" type="number" min="1" max="50" value="2">
</div>
<div class="field">
<label>并发数(同时请求流数,默认单流)</label>
<div class="chips" id="gen-concurrency"></div>
<div class="row" style="margin-top:6px">
<input id="gen-conc-add" type="number" min="1" max="64" placeholder="自定义并发数(≥1">
<button class="btn small" id="btn-conc-add"> 添加</button>
</div>
<div class="hint">选中多个并发数时,同一测试会分别跑各并发档并放在一起对比</div>
</div>
<div class="field switch-field">
<label>测试前预热<span class="hint-inline">空转不计速度,避免冷启动偏差</span></label>
<label class="switch">
<input type="checkbox" id="gen-warmup" checked>
<span class="slider"></span>
</label>
</div>
<div class="field switch-field">
<label>避免缓存<span class="hint-inline">随机前缀,测真实预填充</span></label>
<label class="switch">
<input type="checkbox" id="gen-avoid-cache" checked>
<span class="slider"></span>
</label>
</div>
<div class="btn-group">
<button class="btn primary block" id="btn-start">▶ 开始测试</button>
<button class="btn danger block" id="btn-cancel" disabled>■ 停止</button>
</div>
</div>
</section>
<section class="card collapsible" id="sec-eval">
<h2 class="collapse-head">📤 评测站同步<span class="collapse-arrow"></span></h2>
<div class="collapse-body">
<div class="field">
<label>评测站地址</label>
<div class="icon-input"><span class="icon">🌐</span><input id="eval-url" placeholder="http://127.0.0.1:16066"></div>
</div>
<div class="field">
<label>评测站账号(结果挂到该账号下)</label>
<div class="icon-input"><span class="icon">👥</span><input id="eval-account" placeholder="如:性能评测组"></div>
</div>
<div class="field">
<label>硬件 / GPU(自动探测可修改,随结果一并发送)</label>
<div class="row">
<div class="icon-input"><span class="icon">🖥</span><input id="eval-hardware" placeholder="如:NVIDIA A100 80G"></div>
<button class="btn small" id="btn-detect-gpu" title="自动探测本机GPU">🔍 探测</button>
</div>
</div>
<button class="btn block" id="btn-eval-test">🔗 测试连接</button>
<div class="conn-result" id="eval-result" hidden></div>
<div class="hint" style="margin-top:6px">测试完成后,在历史列表或详情里点「📤 发送到评测站」,一键把结果发布到模型评测网站(端口 16066)对应账号下。</div>
</div>
</section>
</aside>
<!-- 右侧:指标 + 日志 + 历史 -->
<section class="main">
<div class="metrics">
<div class="metric-card"><div class="m-label">首字延迟</div><div class="m-value" id="m-ttft"></div><div class="m-unit" id="m-ttft-u">ms</div></div>
<div class="metric-card"><div class="m-label">预填充速度</div><div class="m-value" id="m-prefill"></div><div class="m-unit">tok/s</div></div>
<div class="metric-card"><div class="m-label">解码速度</div><div class="m-value" id="m-decode"></div><div class="m-unit">tok/s</div></div>
<div class="metric-card"><div class="m-label">上文 Tokens</div><div class="m-value" id="m-prompts"></div><div class="m-unit">tok</div></div>
<div class="metric-card"><div class="m-label">输出 Tokens</div><div class="m-value" id="m-outputs"></div><div class="m-unit">tok</div></div>
<div class="metric-card"><div class="m-label">总耗时</div><div class="m-value" id="m-total"></div><div class="m-unit">ms</div></div>
</div>
<div class="card log-card">
<div class="card-head">
<h2>📋 测试日志</h2>
<div class="log-actions">
<span class="hint" id="log-count"></span>
<button class="btn small" id="btn-clear-console">清屏</button>
<button class="btn small" id="btn-export-log" title="导出当前测试完整日志">导出日志</button>
</div>
</div>
<div class="console" id="console"></div>
</div>
<div class="card">
<div class="card-head">
<h2>🗂 测试历史 <span class="hint" id="hist-total"></span></h2>
<div class="log-actions">
<span class="hint" id="hist-selected"></span>
<button class="btn small primary" id="btn-compare">⚖️ 对比所选</button>
<button class="btn small" id="btn-export-history" title="一键导出全部历史记录(JSON">📥 导出全部</button>
<button class="btn small danger" id="btn-clear-history" title="一键清空全部历史记录(不可恢复)">🗑 清空历史</button>
<button class="btn small" id="btn-refresh-history">刷新</button>
</div>
</div>
<div class="hist-filters">
<div class="icon-input" style="flex:2;min-width:160px"><span class="icon">🔍</span><input id="hist-q" placeholder="搜索:编号/名称/模型/提供商/状态/时间"></div>
<select id="hist-status" title="按状态筛选">
<option value="">状态:全部</option>
<option value="running">测试中</option>
<option value="done">完成</option>
<option value="error">出错</option>
<option value="canceled">已取消</option>
</select>
<select id="hist-provider" title="按提供商筛选">
<option value="">提供商:全部</option>
<option value="openai">OpenAI 兼容</option>
<option value="anthropic">Anthropic</option>
<option value="google">Google Gemini</option>
</select>
<select id="hist-size" title="每页条数">
<option value="10">10 条/页</option>
<option value="20" selected>20 条/页</option>
<option value="50">50 条/页</option>
<option value="100">100 条/页</option>
</select>
<button class="btn small" id="hist-reset" title="清空筛选条件并回到第一页">✕ 重置</button>
</div>
<div class="table-wrap">
<table class="history" id="history">
<thead><tr>
<th class="chk-col"><input type="checkbox" id="hist-check-all" title="全选"></th>
<th class="sortable" data-sort="id" title="点击排序">#</th>
<th class="sortable" data-sort="created_at" title="点击排序">时间</th>
<th class="sortable" data-sort="name" title="点击排序">名称</th>
<th class="sortable" data-sort="provider" title="点击排序">提供商</th>
<th class="sortable" data-sort="model" title="点击排序">模型</th>
<th>并发</th>
<th class="sortable" data-sort="samples" title="点击排序">采样</th>
<th class="sortable" data-sort="ttft" title="点击排序">首字 ms</th>
<th class="sortable" data-sort="prefill" title="点击排序">预填充 tok/s</th>
<th class="sortable" data-sort="decode" title="点击排序">解码 tok/s</th>
<th class="sortable" data-sort="status" title="点击排序">状态</th>
<th>操作</th>
</tr></thead>
<tbody></tbody>
</table>
</div>
<div class="hist-pager">
<button class="btn small" id="hist-prev">◀ 上一页</button>
<span class="hint" id="hist-page-info"></span>
<button class="btn small" id="hist-next">下一页 ▶</button>
</div>
</div>
</section>
</main>
<!-- 详情弹窗 -->
<div class="modal-mask" id="detail-mask" hidden>
<div class="modal">
<div class="modal-head">
<h2>测试详情 #<span id="dt-id"></span></h2>
<div>
<button class="btn small primary" id="dt-send-eval">📤 发送到评测站</button>
<button class="btn small primary" id="dt-export-xlsx">⬇ 导出 Excel</button>
<button class="btn small primary" id="dt-export">导出 JSON</button>
<button class="btn small" id="dt-close"></button>
</div>
</div>
<div class="modal-body" id="dt-body"></div>
</div>
</div>
<!-- 多测试对比弹窗 -->
<div class="modal-mask" id="cmp-mask" hidden>
<div class="modal">
<div class="modal-head">
<h2>⚖️ 多测试结果对比</h2>
<button class="btn small" id="cmp-close"></button>
</div>
<div class="modal-body" id="cmp-body"></div>
</div>
</div>
<!-- 右下角:一键置顶 / 置底 -->
<div class="scroll-fab">
<button class="fab" id="fab-top" title="回到顶部"></button>
<button class="fab" id="fab-bottom" title="滚到底部"></button>
</div>
<script src="/js/app.js"></script>
</body>
</html>