// ==UserScript== // @name DeepSeek 用量增强统计 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 在 DeepSeek 用量页面增加高级分析图表:每日费用趋势、缓存命中率、Token分布、Key对比等 // @author opencode // @match https://platform.deepseek.com/usage* // @grant none // @run-at document-start // ==/UserScript== (function () { 'use strict'; // ============ 配置 ============ const STATS_TZ_OFFSET_SECONDS = 28800; const MODEL_COLORS = ['#6366f1', '#f59e0b', '#10b981', '#ef4444', '#8b5cf6', '#06b6d4', '#f97316', '#ec4899']; const MODEL_NAMES_CN = { 'deepseek-v4-flash': 'V4-Flash', 'deepseek-v4-pro': 'V4-Pro', 'deepseek-chat & deepseek-reasoner': 'Chat & Reasoner', 'deepseek-chat': 'Chat', 'deepseek-reasoner': 'Reasoner', }; const MODEL_RATES = { 'deepseek-v4-flash': { input: 2, cache_hit: 0.2, output: 8 }, 'deepseek-v4-pro': { input: 4, cache_hit: 0.4, output: 16 }, 'deepseek-chat & deepseek-reasoner': { input: 2, cache_hit: 0.2, output: 8 }, }; // ============ 主题 ============ let isDark = true; function detectTheme() { const pref = localStorage.getItem('__appKit_@deepseek/platform_themePreference'); if (pref === 'light') return false; if (pref === 'dark') return true; return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? true; } isDark = detectTheme(); function theme() { return isDark ? { bg: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)', cardBg: 'rgba(255,255,255,0.04)', cardBorder: 'rgba(255,255,255,0.06)', modelBg: 'rgba(255,255,255,0.03)', modelBorder: 'rgba(255,255,255,0.06)', text: '#e0e0e0', textDim: '#888', textFaint: '#666', titleColor: '#fff', shadow: '0 4px 20px rgba(0,0,0,0.3)', border: '1px solid rgba(255,255,255,0.08)', barText: '#aaa', barDate: '#666', chartBorder: 'rgba(255,255,255,0.08)', } : { bg: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)', cardBg: 'rgba(0,0,0,0.06)', cardBorder: 'rgba(0,0,0,0.08)', modelBg: 'rgba(0,0,0,0.04)', modelBorder: 'rgba(0,0,0,0.1)', text: '#1e293b', textDim: '#475569', textFaint: '#94a3b8', titleColor: '#0f172a', shadow: '0 4px 20px rgba(0,0,0,0.1)', border: '1px solid rgba(0,0,0,0.1)', barText: '#334155', barDate: '#94a3b8', chartBorder: 'rgba(0,0,0,0.1)', }; } // ============ 工具函数 ============ function fmt(n) { if (n >= 1e9) return (n / 1e9).toFixed(2) + 'B'; if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M'; if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K'; return n.toLocaleString(); } function pct(v) { return (v * 100).toFixed(1) + '%'; } function yuan(v) { return '¥' + v.toFixed(2); } function escapeHTML(value) { return String(value ?? '').replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', }[ch])); } function formatStatsDate(timestamp) { return new Date((timestamp + STATS_TZ_OFFSET_SECONDS) * 1000).toISOString().slice(0, 10); } function buildUsageUrl(capturedUrl, start, end) { const url = new URL(capturedUrl, window.location?.origin || 'https://platform.deepseek.com'); url.searchParams.set('start', String(start)); url.searchParams.set('end', String(end)); url.searchParams.set('tz', String(STATS_TZ_OFFSET_SECONDS)); return url.toString(); } // ============ 数据缓存 ============ let cachedAmount = null; let cachedCost = null; let capturedAmountUrl = null; let capturedCostUrl = null; let refreshInterval = 300000; let refreshTimer = null; let lastUrl = location.href; function isUsagePage() { return location.origin === 'https://platform.deepseek.com' && location.pathname === '/usage'; } function removeDashboard() { document.getElementById('ds-enhanced-stats')?.remove(); if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; } } function handleRouteChange() { if (lastUrl === location.href) return; lastUrl = location.href; if (!isUsagePage()) removeDashboard(); else tryInject(); } // ============ 真实数据刷新 ============ async function fetchData() { if (!capturedAmountUrl && !capturedCostUrl) return; const now = Math.floor(Date.now() / 1000); const thirtyDaysAgo = now - 30 * 86400; try { if (capturedAmountUrl) { const r = await origFetch(buildUsageUrl(capturedAmountUrl, thirtyDaysAgo, now), { credentials: 'include' }); const j = await r.json(); if (j.code === 0) cachedAmount = j.data?.biz_data || j.data; } if (capturedCostUrl) { const r = await origFetch(buildUsageUrl(capturedCostUrl, thirtyDaysAgo, now), { credentials: 'include' }); const j = await r.json(); if (j.code === 0) cachedCost = j.data?.biz_data || j.data; } } catch (e) { console.warn('[DS Stats] fetchData error:', e); } } // ============ 拦截 fetch ============ const origFetch = window.fetch; window.fetch = function (...args) { const result = origFetch.apply(this, args); const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || ''; if (url.includes('/api/v0/usage/by_api_key/amount')) { if (!capturedAmountUrl) capturedAmountUrl = url; result.then(r => r.clone().json().then(j => { if (j.code === 0) { cachedAmount = j.data?.biz_data || j.data; tryInject(); } })).catch(() => {}); } if (url.includes('/api/v0/usage/by_api_key/cost')) { if (!capturedCostUrl) capturedCostUrl = url; result.then(r => r.clone().json().then(j => { if (j.code === 0) { cachedCost = j.data?.biz_data || j.data; tryInject(); } })).catch(() => {}); } return result; }; // ============ 拦截 XMLHttpRequest ============ const origOpen = XMLHttpRequest.prototype.open; const origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url, ...rest) { this._dsUrl = url; return origOpen.call(this, method, url, ...rest); }; XMLHttpRequest.prototype.send = function (...args) { this.addEventListener('load', function () { try { const data = JSON.parse(this.responseText); if (this._dsUrl?.includes('/api/v0/usage/by_api_key/amount') && data.code === 0) { if (!capturedAmountUrl) capturedAmountUrl = this._dsUrl; cachedAmount = data.data?.biz_data || data.data; tryInject(); } if (this._dsUrl?.includes('/api/v0/usage/by_api_key/cost') && data.code === 0) { if (!capturedCostUrl) capturedCostUrl = this._dsUrl; cachedCost = data.data?.biz_data || data.data; tryInject(); } } catch (e) { } }); return origSend.apply(this, args); }; // ============ 数据解析 ============ function parseData(amountData, costData) { if (!amountData || !costData) return null; const result = { keys: [], models: [], daily: {}, totalTokens: 0, totalRequests: 0, totalCost: 0 }; // 解析 amount:按日、按模型聚合 token 消耗 for (const series of amountData.series || []) { const keyId = series.api_key?.tracking_id || ''; const keyName = series.api_key?.name || 'unknown'; const modelName = series.model || 'unknown'; if (!result.keys.find(k => k.id === keyId)) result.keys.push({ id: keyId, name: keyName }); if (!result.models.includes(modelName)) result.models.push(modelName); for (const bucket of series.buckets || []) { const day = amountData.start <= bucket.time && bucket.time <= amountData.end ? formatStatsDate(bucket.time) : null; if (!day) continue; if (!result.daily[day]) result.daily[day] = { date: day, time: bucket.time, models: {}, totalTokens: 0, totalRequests: 0, cost: 0, promptCacheHit: 0, promptCacheMiss: 0 }; if (!result.daily[day].models[modelName]) result.daily[day].models[modelName] = { promptCacheHit: 0, promptCacheMiss: 0, responseToken: 0, requests: 0, keyCosts: {} }; const m = result.daily[day].models[modelName]; const u = bucket.usage || {}; m.promptCacheHit += u.PROMPT_CACHE_HIT_TOKEN || 0; m.promptCacheMiss += u.PROMPT_CACHE_MISS_TOKEN || 0; m.responseToken += u.RESPONSE_TOKEN || 0; m.requests += u.REQUEST || 0; result.daily[day].promptCacheHit += u.PROMPT_CACHE_HIT_TOKEN || 0; result.daily[day].promptCacheMiss += u.PROMPT_CACHE_MISS_TOKEN || 0; result.daily[day].totalTokens += (u.PROMPT_CACHE_HIT_TOKEN || 0) + (u.PROMPT_CACHE_MISS_TOKEN || 0) + (u.RESPONSE_TOKEN || 0); result.daily[day].totalRequests += u.REQUEST || 0; } } // 解析 cost:按日累加费用与 Key 维度 for (const currency of costData.data || []) { for (const series of currency.series || []) { const modelName = series.model || 'unknown'; for (const bucket of series.buckets || []) { const day = formatStatsDate(bucket.time); if (!result.daily[day]) continue; result.daily[day].cost += parseFloat(bucket.cost || '0'); if (result.daily[day].models[modelName]) { const keyId = series.api_key?.tracking_id || ''; result.daily[day].models[modelName].keyCosts[keyId] = (result.daily[day].models[modelName].keyCosts[keyId] || 0) + parseFloat(bucket.cost || '0'); } } } } // 汇总 const days = Object.values(result.daily); result.totalTokens = days.reduce((s, d) => s + d.totalTokens, 0); result.totalRequests = days.reduce((s, d) => s + d.totalRequests, 0); result.totalCost = days.reduce((s, d) => s + d.cost, 0); result.days = days.sort((a, b) => a.date.localeCompare(b.date)); // 每日指标 for (const day of result.days) { const dayInput = day.promptCacheHit + day.promptCacheMiss; day.hitRate = dayInput > 0 ? day.promptCacheHit / dayInput : 0; day.modelBreakdown = {}; for (const [modelName, mData] of Object.entries(day.models)) { const totalInput = mData.promptCacheHit + mData.promptCacheMiss; const hitRate = totalInput > 0 ? mData.promptCacheHit / totalInput : 0; day.modelBreakdown[modelName] = { hitRate, avgTokensPerReq: mData.requests > 0 ? (totalInput + mData.responseToken) / mData.requests : 0, ...mData }; } day.avgTokensPerReq = day.totalRequests > 0 ? day.totalTokens / day.totalRequests : 0; } // 模型维度汇总 const modelMap = {}; for (const day of result.days) { for (const [name, m] of Object.entries(day.models)) { if (!modelMap[name]) modelMap[name] = { hit: 0, miss: 0, output: 0, reqs: 0, cost: 0 }; modelMap[name].hit += m.promptCacheHit; modelMap[name].miss += m.promptCacheMiss; modelMap[name].output += m.responseToken; modelMap[name].reqs += m.requests; modelMap[name].cost += Object.values(m.keyCosts || {}).reduce((s, c) => s + c, 0); } } result.modelStats = Object.entries(modelMap) .map(([name, d]) => ({ name, ...d, total: d.hit + d.miss + d.output, hitRate: (d.hit + d.miss) > 0 ? d.hit / (d.hit + d.miss) : 0, })) .sort((a, b) => b.total - a.total); return result; } // ============ 渲染 ============ function metricBox(label, value, color, sub) { const t = theme(); return `