// ==UserScript== // @name DeepSeek Usage 缓存命中率 // @namespace http://tampermonkey.net/ // @version 4.0 // @description 鼠标悬浮柱状图时显示该天缓存命中率 // @author Dingtaiqi // @match https://platform.deepseek.com/usage // @icon https://platform.deepseek.com/favicon.ico // @grant none // @homepageURL https://github.com/Dingtaiqi // @license MIT // ==/UserScript== (function () { 'use strict'; // ---- 浮动标签 ---- let label = null; function ensureLabel() { if (label) return label; label = document.createElement('div'); label.id = 'cs-hitrate-label'; label.style.cssText = ` position: fixed; z-index: 100000; padding: 4px 10px; border-radius: 6px; font-size: 12px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-weight: 600; white-space: nowrap; pointer-events: none; transition: opacity .15s; box-shadow: 0 2px 8px rgba(0,0,0,.12); `; label.style.display = 'none'; document.body.appendChild(label); return label; } function showLabel(hitRate, missPct, x, y) { const el = ensureLabel(); const clr = hitRate >= 95 ? '#fff' : hitRate >= 80 ? '#fff' : '#fff'; const bg = hitRate >= 95 ? '#38a169' : hitRate >= 80 ? '#dd6b20' : '#e53e3e'; el.style.background = bg; el.style.color = clr; el.textContent = `命中 ${hitRate.toFixed(1)}% | 未命中占比 ${missPct.toFixed(2)}%`; el.style.display = ''; el.style.top = (y - 10) + 'px'; el.style.left = x + 'px'; el.style.transform = 'translate(-50%, -100%)'; } function hideLabel() { if (label) label.style.display = 'none'; } // ---- 解析 tooltip ---- function parseNum(text) { const n = parseInt(String(text).replace(/[,,\s]/g, ''), 10); return isNaN(n) ? 0 : n; } function isTooltip(el) { const cs = getComputedStyle(el); if (cs.position !== 'absolute' && cs.position !== 'fixed') return false; if (cs.visibility === 'hidden' || cs.display === 'none') return false; if (parseFloat(cs.opacity) === 0) return false; const text = el.textContent || ''; return /\d{4}-\d{2}-\d{2}/.test(text) && /tokens?/i.test(text); } function collectLeafTexts(root) { const result = []; function walk(node) { if (node.nodeType === Node.TEXT_NODE) { const t = node.textContent.trim(); if (t) result.push({ el: node.parentElement, text: t }); return; } if (node.nodeType !== Node.ELEMENT_NODE) return; if (node.classList && node.classList.contains('cs-hitrate-label')) return; for (const child of node.childNodes) walk(child); } walk(root); return result; } function parseTooltip(el) { const leaves = collectLeafTexts(el); const values = []; for (let i = 0; i < leaves.length; i++) { const t = leaves[i].text; let m = t.match(/^([\d,]+)\s*tokens?$/i); if (m) { values.push(parseNum(m[1])); continue; } m = t.match(/^([\d,]+)$/); if (m && i + 1 < leaves.length && /^tokens?$/i.test(leaves[i + 1].text)) { values.push(parseNum(m[1])); i++; } } if (values.length < 3) return null; let hit = 0, miss = 0, output = 0; if (values.length >= 4) { let totalIdx = -1; for (let i = 0; i < values.length; i++) { const rest = values.filter((_, j) => j !== i); if (Math.abs(values[i] - rest.reduce((a, b) => a + b, 0)) < 100) { totalIdx = i; break; } } let rest; if (totalIdx >= 0) { rest = values.filter((_, i) => i !== totalIdx); } else { const max = Math.max(...values); rest = values.filter(v => v !== max); if (rest.length < 3) rest = values.filter((_, i) => values.indexOf(max) !== i); if (rest.length < 3) rest = [...values].sort((a, b) => b - a).slice(0, 3); } rest.sort((a, b) => b - a); [hit, miss, output] = rest; } else { const sorted = [...values].sort((a, b) => b - a); [hit, miss, output] = sorted; } const total = hit + miss + output; const inputTotal = hit + miss; return { hitRate: inputTotal > 0 ? (hit / inputTotal * 100) : 0, missTotalPct: total > 0 ? (miss / total * 100) : 0, }; } // ---- 主逻辑 ---- function getTooltipPosition(ttEl) { const rect = ttEl.getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top }; } function processTooltip(ttEl) { if (!isTooltip(ttEl)) return false; const data = parseTooltip(ttEl); if (!data) return false; const pos = getTooltipPosition(ttEl); showLabel(data.hitRate, data.missTotalPct, pos.x, pos.y); return true; } // ---- 监听 ---- let activeTooltip = null; // 跟踪当前活跃的 tooltip 元素 function handleMutations(mutations) { for (const m of mutations) { // tooltip 消失(节点被移除) for (const node of m.removedNodes) { if (node === activeTooltip || (node instanceof HTMLElement && node.contains(activeTooltip))) { hideLabel(); activeTooltip = null; return; } } // tooltip 出现(新增节点) for (const node of m.addedNodes) { if (!(node instanceof HTMLElement)) continue; if (processTooltip(node)) { activeTooltip = node; return; } if (node.querySelectorAll) { for (const child of node.querySelectorAll('div, span, section')) { if (processTooltip(child)) { activeTooltip = child; return; } } } } // tooltip 变为可见/隐藏(style/class 变化) if (m.type === 'attributes' && m.target instanceof HTMLElement) { if (m.target === activeTooltip || (activeTooltip && m.target.contains(activeTooltip))) { if (getComputedStyle(activeTooltip).display === 'none') { hideLabel(); activeTooltip = null; return; } } if (processTooltip(m.target)) { activeTooltip = m.target; return; } } } } const observer = new MutationObserver(handleMutations); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'], }); // 轻量 mousemove:仅检查已追踪的 tooltip 是否还在 document.addEventListener('mousemove', () => { if (!activeTooltip) return; if (!document.contains(activeTooltip) || getComputedStyle(activeTooltip).display === 'none') { hideLabel(); activeTooltip = null; } }, { passive: true }); })();