做了一个简单的AI比价工具,用于计算AI中转的供应商价格对比。
数据均可自定义,默认数据仅供参考,实际消费请以供应商最终计算为准。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 供应商多维度成本对比模拟器 v2.2</title>
<style>
/* ... 保留原有样式 ... */
:root {
--primary: #3b82f6;
--primary-hover: #2563eb;
--success: #10b981;
--success-bg: #ecfdf5;
--success-border: #10b981;
--neutral-bg: #f8fafc;
--card-bg: #ffffff;
--border-color: #e2e8f0;
--text-main: #0f172a;
--text-muted: #64748b;
--merchant-a-color: #6366f1;
--merchant-b-color: #f59e0b;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: var(--neutral-bg); color: var(--text-main); line-height: 1.5; padding: 40px 20px; }
.container { max-width: 1200px; margin: 0 auto; }
header { text-align: center; margin-bottom: 40px; }
header h1 { font-size: 28px; font-weight: 700; color: var(--text-main); margin-bottom: 10px; }
header p { color: var(--text-muted); font-size: 15px; }
.dashboard-grid { display: grid; grid-template-columns: 1fr; gap: 30px; }
@media (min-width: 1024px) { .dashboard-grid { grid-template-columns: 7fr 5fr; } }
.card { background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 12px; padding: 24px; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05), 0 1px 2px 0 rgba(0, 0, 0, 0.03); margin-bottom: 25px; }
.card-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px solid var(--neutral-bg); display: flex; align-items: center; justify-content: space-between; }
.config-grid { display: grid; grid-template-columns: 1fr; gap: 20px; }
@media (min-width: 640px) { .config-grid { grid-template-columns: 1fr 1fr; } }
.merchant-box { border: 1px solid var(--border-color); border-radius: 8px; padding: 16px; background-color: #fafafa; }
.merchant-box.a { border-top: 4px solid var(--merchant-a-color); }
.merchant-box.b { border-top: 4px solid var(--merchant-b-color); }
.merchant-box h3 { font-size: 15px; margin-bottom: 15px; color: var(--text-main); display: flex; justify-content: space-between; }
.form-group { margin-bottom: 12px; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.form-group label { font-size: 13px; color: var(--text-muted); white-space: nowrap; }
.input-wrapper { position: relative; display: flex; align-items: center; }
.input-wrapper span { position: absolute; left: 8px; font-size: 12px; color: var(--text-muted); }
/* 去除数字输入框的上下箭头 (Spin Buttons) */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield; /* Firefox */
}
.form-control { width: 100px; padding: 6px 8px 6px 20px; border: 1px solid var(--border-color); border-radius: 6px; font-size: 13px; text-align: right; }
.form-control:focus { outline: none; border-color: var(--primary); }
.ratio-group { margin-top: 15px; padding-top: 12px; border-top: 1px dashed var(--border-color); background: #fff; padding: 10px; border-radius: 6px; }
.ratio-inputs { display: flex; align-items: center; gap: 5px; margin-top: 5px; }
.ratio-inputs input { width: 70px; padding: 4px 6px; font-size: 12px; border: 1px solid var(--border-color); border-radius: 4px; text-align: center; }
.slider-group { margin-bottom: 20px; }
.slider-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 14px; font-weight: 500; }
.slider-header .value-display { color: var(--primary); font-weight: 600; }
.slider-control-container { display: flex; align-items: center; gap: 15px; }
input[type="range"] { flex: 1; height: 6px; background: #e2e8f0; border-radius: 3px; outline: none; -webkit-appearance: none; }
input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: var(--primary); cursor: pointer; transition: transform 0.1s; }
input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.2); }
.slider-control-container input[type="number"] { width: 80px; padding: 4px 8px; border: 1px solid var(--border-color); border-radius: 6px; text-align: right; font-size: 13px; }
.result-card { border: 2px solid var(--border-color); border-radius: 12px; padding: 24px; margin-bottom: 20px; position: relative; transition: all 0.3s ease; }
.result-card.winner { border-color: var(--success-border); background-color: var(--success-bg); box-shadow: 0 4px 12px rgba(16, 185, 129, 0.1); }
.result-card .badge { display: none; position: absolute; top: -12px; right: 20px; background-color: var(--success); color: white; padding: 2px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; }
.result-card.winner .badge { display: block; }
.result-title { font-size: 16px; font-weight: 600; margin-bottom: 5px; display: flex; align-items: center; gap: 8px; }
.result-title .dot { width: 8px; height: 8px; border-radius: 50%; }
.result-title .dot.a { background-color: var(--merchant-a-color); }
.result-title .dot.b { background-color: var(--merchant-b-color); }
.result-price { font-size: 32px; font-weight: 800; color: var(--text-main); margin: 10px 0; }
.result-ratio-hint { font-size: 12px; color: var(--text-muted); margin-bottom: 15px; }
.breakdown-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 10px; }
.breakdown-table th, .breakdown-table td { padding: 8px 10px; text-align: left; }
.breakdown-table th { color: var(--text-muted); font-weight: 500; border-bottom: 1px solid var(--border-color); }
.breakdown-table td { border-bottom: 1px dashed var(--border-color); }
.breakdown-table tr:last-child td { border-bottom: none; }
.text-right { text-align: right !important; }
.savings-banner { background: #eff6ff; border: 1px solid #bfdbfe; color: #1e40af; padding: 12px 16px; border-radius: 8px; font-size: 14px; font-weight: 500; margin-top: 20px; text-align: center; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>动态 AI 供应商成本对比模拟器</h1>
<p>支持自主配置两家供应商的官方标价与充值比例,动态计算各种 Token 场景下的真实人民币消耗。</p>
</header>
<div class="dashboard-grid">
<!-- 左侧:动态配置与流量模拟 -->
<div class="left-panel">
<!-- 1. 价格配置卡片 -->
<div class="card">
<div class="card-title">⚙️ 供应商价格与充值配置 (每百万计费 / 刀)</div>
<div class="config-grid">
<!-- 商家 A 配置 -->
<div class="merchant-box a">
<h3>商家 A 配置 <span style="color: var(--merchant-a-color);">●</span></h3>
<div class="form-group"><label>常规输入 ($/M)</label><div class="input-wrapper"><span>$</span><input type="number" id="p_a_in" class="form-control price-input" value="0.5" step="0.01" min="0"></div></div>
<div class="form-group"><label>常规输出 ($/M)</label><div class="input-wrapper"><span>$</span><input type="number" id="p_a_out" class="form-control price-input" value="3.0" step="0.01" min="0"></div></div>
<div class="form-group"><label>缓存创建 ($/M)</label><div class="input-wrapper"><span>$</span><input type="number" id="p_a_cw" class="form-control price-input" value="0.0" step="0.01" min="0"></div></div>
<div class="form-group"><label>缓存读取 ($/M)</label><div class="input-wrapper"><span>$</span><input type="number" id="p_a_cr" class="form-control price-input" value="0.05" step="0.01" min="0"></div></div>
<div class="ratio-group">
<label style="font-size: 12px; font-weight: 600;">充值比例配置:</label>
<div class="ratio-inputs">
<input type="number" id="r_a_pay" value="1" step="0.1" min="0" class="price-input"> <span style="font-size:12px;color:var(--text-muted)">元充</span>
<input type="number" id="r_a_get" value="1" step="1" min="0.01" class="price-input"> <span style="font-size:12px;color:var(--text-muted)">刀额度</span>
</div>
</div>
</div>
<!-- 商家 B 配置 -->
<div class="merchant-box b">
<h3>商家 B 配置 <span style="color: var(--merchant-b-color);">●</span></h3>
<div class="form-group"><label>常规输入 ($/M)</label><div class="input-wrapper"><span>$</span><input type="number" id="p_b_in" class="form-control price-input" value="2.5" step="0.01" min="0"></div></div>
<div class="form-group"><label>常规输出 ($/M)</label><div class="input-wrapper"><span>$</span><input type="number" id="p_b_out" class="form-control price-input" value="15.0" step="0.01" min="0"></div></div>
<div class="form-group"><label>缓存创建 ($/M)</label><div class="input-wrapper"><span>$</span><input type="number" id="p_b_cw" class="form-control price-input" value="0.25" step="0.01" min="0"></div></div>
<div class="form-group"><label>缓存读取 ($/M)</label><div class="input-wrapper"><span>$</span><input type="number" id="p_b_cr" class="form-control price-input" value="1.0" step="0.01" min="0"></div></div>
<div class="ratio-group">
<label style="font-size: 12px; font-weight: 600;">充值比例配置:</label>
<div class="ratio-inputs">
<input type="number" id="r_b_pay" value="7.5" step="0.1" min="0" class="price-input"> <span style="font-size:12px;color:var(--text-muted)">元充</span>
<input type="number" id="r_b_get" value="50" step="1" min="0.01" class="price-input"> <span style="font-size:12px;color:var(--text-muted)">刀额度</span>
</div>
</div>
</div>
</div>
</div>
<!-- 2. 流量模拟卡片 -->
<div class="card">
<div class="card-title">📊 业务 Token 消耗量模拟 (单位:百万 / M Tokens)</div>
<div class="slider-group">
<div class="slider-header"><label>常规输入量 (Prompt)</label><span class="value-display"><span id="v_in_txt">1.0</span> M</span></div>
<div class="slider-control-container">
<input type="range" id="v_in_range" min="0" max="50" step="0.1" value="1.0">
<input type="number" id="v_in_num" min="0" step="0.1" value="1.0" class="volume-input">
</div>
</div>
<div class="slider-group">
<div class="slider-header"><label>常规输出量 (Completion)</label><span class="value-display"><span id="v_out_txt">1.0</span> M</span></div>
<div class="slider-control-container">
<input type="range" id="v_out_range" min="0" max="50" step="0.1" value="1.0">
<input type="number" id="v_out_num" min="0" step="0.1" value="1.0" class="volume-input">
</div>
</div>
<div class="slider-group">
<div class="slider-header"><label>缓存创建量 (Cache Write)</label><span class="value-display"><span id="v_cw_txt">1.0</span> M</span></div>
<div class="slider-control-container">
<input type="range" id="v_cw_range" min="0" max="50" step="0.1" value="1.0">
<input type="number" id="v_cw_num" min="0" step="0.1" value="1.0" class="volume-input">
</div>
</div>
<div class="slider-group">
<div class="slider-header"><label>缓存读取量 (Cache Hit)</label><span class="value-display"><span id="v_cr_txt">1.0</span> M</span></div>
<div class="slider-control-container">
<input type="range" id="v_cr_range" min="0" max="50" step="0.1" value="1.0">
<input type="number" id="v_cr_num" min="0" step="0.1" value="1.0" class="volume-input">
</div>
</div>
</div>
</div>
<!-- 右侧:实时计算结果与明细对比 -->
<div class="right-panel">
<div class="card" style="position: sticky; top: 20px;">
<div class="card-title">💰 实时开销测算结果 (实际人民币支付)</div>
<div id="res_card_a" class="result-card">
<div class="badge">更具性价比</div>
<div class="result-title"><span class="dot a"></span> 商家 A 预估总额</div>
<div class="result-price">¥ <span id="res_total_a">0.00</span></div>
<div class="result-ratio-hint" id="res_hint_a">折算实际汇率:1 刀 = 1.0000 元</div>
<table class="breakdown-table">
<thead><tr><th>计费项</th><th>实际单价(/M)</th><th class="text-right">小计</th></tr></thead>
<tbody>
<tr><td>常规输入</td><td id="u_a_in">¥0.00</td><td class="text-right" id="sub_a_in">¥0.00</td></tr>
<tr><td>常规输出</td><td id="u_a_out">¥0.00</td><td class="text-right" id="sub_a_out">¥0.00</td></tr>
<tr><td>缓存创建</td><td id="u_a_cw">¥0.00</td><td class="text-right" id="sub_a_cw">¥0.00</td></tr>
<tr><td>缓存读取</td><td id="u_a_cr">¥0.00</td><td class="text-right" id="sub_a_cr">¥0.00</td></tr>
</tbody>
</table>
</div>
<div id="res_card_b" class="result-card">
<div class="badge">更具性价比</div>
<div class="result-title"><span class="dot b"></span> 商家 B 预估总额</div>
<div class="result-price">¥ <span id="res_total_b">0.00</span></div>
<div class="result-ratio-hint" id="res_hint_b">折算实际汇率:1 刀 = 0.1500 元</div>
<table class="breakdown-table">
<thead><tr><th>计费项</th><th>实际单价(/M)</th><th class="text-right">小计</th></tr></thead>
<tbody>
<tr><td>常规输入</td><td id="u_b_in">¥0.00</td><td class="text-right" id="sub_b_in">¥0.00</td></tr>
<tr><td>常规输出</td><td id="u_b_out">¥0.00</td><td class="text-right" id="sub_b_out">¥0.00</td></tr>
<tr><td>缓存创建</td><td id="u_b_cw">¥0.00</td><td class="text-right" id="sub_b_cw">¥0.00</td></tr>
<tr><td>缓存读取</td><td id="u_b_cr">¥0.00</td><td class="text-right" id="sub_b_cr">¥0.00</td></tr>
</tbody>
</table>
</div>
<div id="savings_banner" class="savings-banner">
选择更优方案预计可节省 ¥0.00
</div>
</div>
</div>
</div>
</div>
<script>
function sanitizeInput(el) {
let val = parseFloat(el.value);
if (isNaN(val) || val < 0) {
el.value = 0;
}
if ((el.id === 'r_a_get' || el.id === 'r_b_get') && parseFloat(el.value) === 0) {
el.value = 0.01;
}
}
const volumeTypes = ['in', 'out', 'cw', 'cr'];
volumeTypes.forEach(type => {
const rangeEl = document.getElementById(`v_${type}_range`);
const numEl = document.getElementById(`v_${type}_num`);
const txtEl = document.getElementById(`v_${type}_txt`);
rangeEl.addEventListener('input', () => {
numEl.value = rangeEl.value;
txtEl.innerText = parseFloat(rangeEl.value).toFixed(1);
calculate();
});
numEl.addEventListener('input', (e) => {
sanitizeInput(e.target);
if(parseFloat(numEl.value) > parseFloat(rangeEl.max)) {
rangeEl.max = numEl.value;
}
rangeEl.value = numEl.value;
txtEl.innerText = (parseFloat(numEl.value) || 0).toFixed(1);
calculate();
});
});
document.querySelectorAll('.price-input').forEach(el => {
el.addEventListener('input', (e) => {
sanitizeInput(e.target);
calculate();
});
});
function calculate() {
const r_a_pay = parseFloat(document.getElementById('r_a_pay').value) || 0;
const r_a_get = parseFloat(document.getElementById('r_a_get').value) || 1;
const rate_A = r_a_get > 0 ? (r_a_pay / r_a_get) : 0;
const r_b_pay = parseFloat(document.getElementById('r_b_pay').value) || 0;
const r_b_get = parseFloat(document.getElementById('r_b_get').value) || 1;
const rate_B = r_b_get > 0 ? (r_b_pay / r_b_get) : 0;
document.getElementById('res_hint_a').innerText = `折算实际汇率:1 刀 ≈ ¥${rate_A.toFixed(4)}`;
document.getElementById('res_hint_b').innerText = `折算实际汇率:1 刀 ≈ ¥${rate_B.toFixed(4)}`;
const p_A = {
in: parseFloat(document.getElementById('p_a_in').value) || 0,
out: parseFloat(document.getElementById('p_a_out').value) || 0,
cw: parseFloat(document.getElementById('p_a_cw').value) || 0,
cr: parseFloat(document.getElementById('p_a_cr').value) || 0
};
const p_B = {
in: parseFloat(document.getElementById('p_b_in').value) || 0,
out: parseFloat(document.getElementById('p_b_out').value) || 0,
cw: parseFloat(document.getElementById('p_b_cw').value) || 0,
cr: parseFloat(document.getElementById('p_b_cr').value) || 0
};
const real_p_A = {};
const real_p_B = {};
for(let key in p_A) {
real_p_A[key] = p_A[key] * rate_A;
real_p_B[key] = p_B[key] * rate_B;
document.getElementById(`u_a_${key}`).innerText = `¥${real_p_A[key].toFixed(4)}`;
document.getElementById(`u_b_${key}`).innerText = `¥${real_p_B[key].toFixed(4)}`;
}
const vol = {
in: parseFloat(document.getElementById('v_in_num').value) || 0,
out: parseFloat(document.getElementById('v_out_num').value) || 0,
cw: parseFloat(document.getElementById('v_cw_num').value) || 0,
cr: parseFloat(document.getElementById('v_cr_num').value) || 0
};
let total_A = 0;
let total_B = 0;
volumeTypes.forEach(k => {
const sub_a = vol[k] * real_p_A[k];
const sub_b = vol[k] * real_p_B[k];
total_A += sub_a;
total_B += sub_b;
document.getElementById(`sub_a_${k}`).innerText = `¥${sub_a.toFixed(2)}`;
document.getElementById(`sub_b_${k}`).innerText = `¥${sub_b.toFixed(2)}`;
});
document.getElementById('res_total_a').innerText = total_A.toFixed(2);
document.getElementById('res_total_b').innerText = total_B.toFixed(2);
const cardA = document.getElementById('res_card_a');
const cardB = document.getElementById('res_card_b');
const savingsBanner = document.getElementById('savings_banner');
cardA.classList.remove('winner');
cardB.classList.remove('winner');
const diff = Math.abs(total_A - total_B);
if (diff < 0.01) {
savingsBanner.innerText = `两家供应商在当前配置下成本完全相同`;
savingsBanner.style.background = '#f1f5f9';
savingsBanner.style.color = '#475569';
savingsBanner.style.borderColor = '#cbd5e1';
} else {
savingsBanner.style.background = '#eff6ff';
savingsBanner.style.color = '#1e40af';
savingsBanner.style.borderColor = '#bfdbfe';
if (total_A < total_B) {
cardA.classList.add('winner');
savingsBanner.innerText = `选择 【商家 A】 预计可为您节省约 ¥${diff.toFixed(2)}`;
} else {
cardB.classList.add('winner');
savingsBanner.innerText = `选择 【商家 B】 预计可为您节省约 ¥${diff.toFixed(2)}`;
}
}
}
calculate();
</script>
</body>
</html>

Comments NOTHING