AI价格计算

结城 AI 3 次阅读 3744 字 发布于 10 天前 预计阅读时间: 17 分钟


做了一个简单的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>
给时光以生命,给岁月以文明
最后更新于 2026-06-16