Skip to content

About

SillyTavern 用量统计扩展(TokenFlow 修改版):统计 Token / 请求数 / 费用,能抓到酒馆助手插件发出的请求

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SillyToken · SillyTavern 用量统计

统计 SillyTavern(酒馆)的 Token 用量、请求次数与费用。

和常见的用量统计不同,它能抓到装在酒馆助手上的插件 / 前端界面发出的请求 —— 那些脚本跑在 iframe 里,用的是它们自己的 window.fetch / XMLHttpRequest,普通的 fetch 钩子看不见。


⚠️ 来源声明(必读)

本项目是 Operit / TokenFlow 的修改版(fork),不是原创作品。

原项目 ddsadasfdxc/TokenFlow
原作者 Operit
原许可 AGPL-3.0
本修改版许可 AGPL-3.0(继承原许可,完整全文见 LICENSE)
修改时间 2026-09

按 AGPL-3.0 第 5 条的要求,此处明确声明本作品经过修改,改动内容见下一节。 如需再分发,请一并保留本声明与 LICENSE 文件。

本修改版为个人自用而做,同时公开发布。原项目的版权归原作者所有。

与原版的差异(v2.8.0)

1. 请求捕获层重写(这是 fork 的主要原因)

原版只在主窗口上 patch 一次 window.fetch,且只认一个 URL。于是下面这些全部漏掉:

  • 酒馆助手的脚本 / 前端界面跑在无沙盒 iframe 里,有自己的 fetch 与 XMLHttpRequest
  • jQuery.ajax / axios 走的是 XHR,根本不经过 fetch
  • TavernHelper.generateRaw() 这类宿主函数,网络层可能不出现 fetch
  • TauriTavern 或其它扩展重写 window.fetch 时,一次普通赋值就会把捕获层整个丢掉

现在改为每个同源窗口都装一遍,对每个窗口装上 fetch(属性访问器)/ XMLHttpRequest / TavernHelper.generateRaw,并用 500ms 看门狗周期补装。跨层去重保证同一次生成只记一条。

2. 修掉一个致命 bug

原版 recordUsage() 里的 cost 变量声明在 for 循环体内、却在循环外使用, 每次记录都抛 ReferenceError,导致今日统计、历史归档、用量限额、悬浮球角标全部不生效, saveSettingsDebounced() 也从未执行(数据不落盘)。同时修掉了「网络失败时会把同一个请求重发一遍」。

3. 统计面板重做

对齐 DeepSeek 开放平台 / New API 的视图:

  • 时间范围 今天 / 近 7 天 / 近 30 天 / 全部
  • 日柱图按连续日期铺满(缺日补 0,不会错位),点柱子看当天按模型明细(平板没有 hover)
  • 概览:消费金额、总 Token、输入 / 输出 / 缓存命中、缓存命中率
  • 明细:每个模型的费用 / 占比 / Token / 请求数,支持按模型或按厂商分组

4. 费用可追溯重算

原版在记录当时就把费用算死。如果一开始没填价格,之后补上也不会回填。

现在每个模型桶都保留 输入/输出/缓存/请求数,今日与历史归档也存按模型用量, 于是补上单价后 累计 / 会话 / 今日 / 历史 会全部重算。

5. 模型名匹配

  • 归一化:去 vendor/ 前缀、_→-、去日期尾巴、去 :free / -preview 之类的装饰
  • 自动推断:deepseek-flash 能匹配到价格表里的 deepseek-v4-flash
  • 手动覆盖:价格表新增「别名」列,可以自己把外部模型名钉到某一档价格
  • 只有请求名不带版本时才允许跨版本推断 —— gemini-2.5-flash 绝不会被并到 gemini-3.7-flash
  • 界面上会标出每个模型的计价来源(精确 / 别名 / 模糊 / 未设单价)

6. 未设单价的模型会弹窗提醒

检测到价格表里没有的模型时弹一次 Toast,面板顶部也有警告条与「一键补入价格表」。 未定价的模型费用记 0,但会被明确标注出来,不会静默消失。

7. 运行日志

借鉴了 worldbook-manager 的 cache-inspector 做法:定长环形缓冲(400 条)、写入前截断、 暴露全局诊断入口。面板底部有日志区,可复制日志 / 复制诊断 / 导出 / 清空。

没有 DevTools 的设备(比如平板)也能自查:面板上那一行 🛰 已捕获 N · 估算 M · 未捕获 K · 窗口 W 就是抓取健康度。

8. 其它修复

  • 历史归档用 += 反复累加当天总额(n 次请求会膨胀成 n(n+1)/2 倍)
  • 月预算 / 周额度把今天算了两次;跨夜后把昨天的数当成今天
  • 周重置时刻 12:19 被 setMinutes 解析成 00:12
  • OpenAI 语义下 prompt_tokens 已含 cached_tokens,被重复计数 + 重复计费; Anthropic 同时有读写时 cache_read 被丢掉;DeepSeek 的 prompt_cache_hit_tokens 从未被识别
  • Ctrl+Shift+T 等若干小问题;删掉了从未被调用的 280 行死代码

9. 后续版本增量(v2.4.0 / v2.5.0)

  • 时间范围扩到 今天 / 近 7 天 / 本周 / 近 30 天 / 本月 / 全部,外加自定义起止日期
  • 5 套配色主题(横竖屏、平板触屏都验过),文字对比度按 WCAG AA 逐项校验
  • 「精确追踪」开关立刻生效:关掉马上停止记账,打开立刻补装捕获层 —— 不用再重启酒馆(旧写法只在启动时读一次,勾了没反应)
  • 导入数据前二次确认,并把导入的 JSON 当不可信输入处理(挡掉 __proto__ 之类字段)
  • 修掉一个栈溢出:宿主把 fetch 包成「转调我们这一层」时, 非生成端点的透传会绕回自己无限递归;透传现在也走深度保护
  • 同一帧内的多次面板重绘合并成一次,历史记录多时点控件不再卡顿

10. v2.6.0 · 悬浮球在手机上也看得见、能自由摆放

  • 位置从「像素」改成「占可用空间的比例」。旧写法把像素坐标存进设置,而这份设置 由酒馆同步到服务端、PC 和手机共用:在电脑上把球拖到屏幕右侧(x≈1850),换手机 打开时 innerWidth 只有 390 —— 球被画到屏幕外,表现就是**「手机上看不见悬浮球」**
  • 恢复位置时按当前屏幕夹取;横竖屏切换、手机地址栏收放都会重新摆一次
  • 拖动不再无条件吸附到左右边缘:只有松手时贴着边(≤24px)才吸附, 其余位置就停在你放的地方(旧版永远只能待在屏幕两条边上)
  • 窄屏(手机)的默认位置抬到输入栏之上,并留出系统手势安全区

11. v2.6.1 · 概览偶尔整块空白

真机截图:概览那 6 张统计卡占着版面位置,却连同文字一起没画出来(下面的区块 没有被顶上去)。

  • 这 6 张卡原是面板里唯一带 backdrop-filter 的元素,而面板背景在 5 套主题里 全是不透明的 —— 卡片去模糊"背后"其实什么都模糊不到:零收益,却每张卡各多一个 合成层。移动 GPU 上这类层被整块丢掉时,表现正是"占位但不绘制"。已去掉, 背景改走扩展自己的主题变量(不再依赖酒馆的 --SmartThemeBlurTintColor)
  • 遮罩层补上 -webkit-backdrop-filter 前缀(WebKit 内核只认前缀版)
  • 新增渲染后自检:万一还有别的原因让概览渲染不全,会往运行日志写一条 ui.blank,带上容器 / 子节点数 / 视口 / 主题 —— 面板底部就能看到、能复制, 平板上没有 DevTools 也能留证
  • 测试补强:L2 每次交互后都断言「6 张卡都在、都有数字、有实际尺寸、没有 backdrop-filter」,真机测试连点 15 次刷新复查(旧断言只看金额那一块,卡片全没了 照样全绿,所以这个 bug 一直没被测出来)

12. v2.7.0 · 悬浮球重做 + 价格角标

外观:旧版球体背景全靠酒馆的 --SmartThemeBlurTintColor 调,浅色宿主下就是一颗 "白底 + 浅色图标"的白圈,几乎看不见(真机截图实证);而且主题变量块只挂在 [data-tf-theme] 上,悬浮球没这个属性,永远拿不到扩展自己的主题色。

  • 改成深色球体 + 主题强调色,任何宿主主题下都清楚;球本身也挂 data-tf-theme, 跟着面板那 5 套配色一起变
  • 图标换成内联 SVG(上扬折线 + 箭头),不再依赖酒馆的 FontAwesome 是否加载
  • 去掉球体的 backdrop-filter 和无限 box-shadow 动画(每帧重绘,平板纯耗电)
  • 触屏上不再让 :hover 状态粘住

价格角标:真机反馈"那个价格有时候会消失",两个原因:

  • updateOrbBadge 只看今天(dailyStats.cost),等于 0 就 display:none。 跨天、或今天还没产生消费时,角标直接没了。现在今天为 0 就退回显示累计费用, 真的没有任何数据时才隐藏
  • 只要进过一次预算预警态,updateOrbBadge 就 return,价格再也回不来 (clearOrbAlert 只把 display 设成 none)。现在角标渲染收拢到 updateOrbBadge 一个出口,预警解除后价格自动恢复
  • 金额改成紧凑格式:$0.97 / $24.9 / $1.2K(旧版是 $0.974171, 六位小数把角标撑成横条盖住整颗球);不足一分钱显示 <0.01 而不是 0.00
  • 角标从"横着拖出球外的长条"改成右上角的小吊牌,并且球靠到屏幕边缘时不会被挤出去 (贴边留白从 8px 提到 16px 给角标让位)
  • 预警角标的红底加深,白字 ! 才算过 AA(原先 3.1:1,10px 的字根本看不清)

13. v2.7.1 · 估算:报错不再算进去,输出不再恒为 0

问题一:失败的请求被当成消费记账了

原则是「没拿到 usage ≠ 请求成功」。原来只有「网络抛错 / 4xx / 5xx / 流读取失败」 被排除,中转商最常见的三种报错回法全部漏网:

回法 原来 现在
200 + 一页 HTML 错误页 被估算记账 记「未捕获」
200 + {"error":{...}}(New API / one-api 标准报错) 被估算记账 记「未捕获」
200 + SSE 里装 error(假流式中转) 被估算记账 记「未捕获」(有正文也不算)
宿主函数把错误当正常返回值给回来 被估算记账 记「未捕获」

判定顺序固定为 usage > 错误 > 正文/完成形状 > 不估:既不是完成响应、又抠不出正文的 响应,一律只记「未捕获」(运行日志里能看到具体原因,不是静默丢弃)。

问题二:没有 usage 时输出恒按 0 算

非流式路径原来只传了个 reason,没把响应里的正文交下去,于是回复几千字也只算输入。 现在会把正文抠出来估输出,支持 OpenAI / Anthropic / Gemini 三种响应形状; 纯工具调用(正文为空、只有 tool_calls)的参数也算输出 token。

同时补了一个反向漏洞:有的中转声明了 stream: true 却直接回 JSON,那种会被误判成 "流中断"而漏记 —— 现在会回退按 JSON 解析。

怎么自查:面板底部运行日志里找 capture.estimated / capture.missed, reason 字段直接告诉你原因(例如 响应里返回了错误: insufficient quota)。

14. v2.7.2 · 补齐官方价 + 认得中转的模型名前缀

新增 4 档(2026-09-16 查的官方价,USD / 1M tokens):

模型 输入 输出 缓存输入 来源
gemini-3-flash 0.50 3.00 0.05 Google 官方
gemini-2.5-pro 1.25 10.00 0.125 Google 官方(≤200K 上下文;>200K 为 2.50 / 15)
gemma-4-31b-it 0.15 0.60 — Google Vertex AI
gemma-4-26b-a4b-it 0.15 0.60 — Google Vertex AI

gemini-3.1-pro(2.00 / 12.00 / 0.40)原表已有,这次核对过与官方一致。

模型名归一化现在能剥中转前缀:

  • 假流式-gemini-2.5-pro-search → gemini-2.5-pro(中文前缀 + -search 装饰)
  • [NV]gemma-26b-a4b-it → gemma-4-26b-a4b-it(方括号标记)
  • google/gemma-4-31b-it → gemma-4-31b-it(vendor/ 前缀,原本就支持)

顺带修了一个连带 bug:26b / 120b 这类参数量标签原来被当成"代际版本", 于是 gemma-26b-a4b-it 与价格表里的 gemma-4-26b-a4b-it 被判成"跨版本"而拒绝匹配 (表现为一直显示未设单价)。现在参数量标签不参与代际比较,跨版本红线照旧 (gemini-2.5-flash 仍然不会被算成 gemini-3.x-flash)。

没查到官方价的(开源权重,价格由托管方定,没有唯一"官方价"):

  • google/diffusiongemma-26b-a4b-it —— Artificial Analysis 上报价为 $0.00(未收录)
  • nvidia/nemotron-3-super-120b-a12b —— NVIDIA 自家按 GPU 时长计费,第三方按 token 计费

这两个保持「未设单价」,面板会提示你一键补入;要按你中转的实际标价填。

15. v2.7.3 · 改设置后悬浮球角标立刻刷新

最后一轮端到端验收抓出来的:改了币种 / 汇率 / 预算开关 / 限额之后,悬浮球上的 价格角标不会立刻更新,要等下一次请求才变。

  • 勾上「启用预算预警」并填一个很小的限额 → 角标要等下一次请求才变成 !, 看着像开关没生效(旧版只有"取消勾选"才会动角标)
  • 改汇率或币种 → 角标还挂着旧币种的旧数字

现在这几个设置项改完都会立刻重画角标。这一类问题(改了设置但界面没跟着动) 前面已经修过一次(「精确追踪」开关、悬浮球开关),这次把剩下的补齐了。

16. v2.7.4 · 手机端:面板关不掉 + 悬浮球还是看不见

真机反馈两条,都复现出来了:

① 打开统计面板后关不掉

窄屏下面板头部的 7 个按钮摆不下,把最右边的 ✕ 挤出了面板右边缘;而面板是 overflow: hidden,于是关闭按钮被整块裁掉 —— 看得到面板,点不到关闭。

实测(390px 视口):面板 right 375,关闭按钮 right 397(超出 22px)。

改法:

  • 表头允许换行、允许收缩(宁可换到第二行,也不能把关闭按钮推出去)
  • 窄屏(≤560px)面板改成从底部升起:bottom:0 + top:auto + transform:none 是结构上的保证 —— 高度被 max-height 限住,表头就永远在屏幕内
  • 面板高度补三级兜底:82vh → min(82vh,640px) → min(82dvh,640px)。 原来只有中间那条,万一引擎不支持 min(),整条声明会被丢掉 → 面板比屏幕还高 → 居中之后表头(含关闭按钮)被顶到屏幕上方,就是截图里那种"卡在顶部"
  • .tf-panel-body 补 min-height: 0(flex 子项默认 auto,内容高会把父容器顶破)

② 手机上还是没有悬浮球

位置是按比例跨设备同步的,但比例映射原先按整个视口算。手机底部是酒馆的输入栏 加系统手势条 —— 在电脑上把球拖到底部(fy≈1),到手机上就正好贴着视口底边, 落在底栏后面 = 看不见。

改法:位置改在「安全区」内映射 —— 窄屏底部预留 96px(与 CSS 默认位置一致), 于是电脑上拖到底部的球,到手机上会落在输入栏之上。

另外补了两处:

  • 兜底:摆完之后实测一次,球跑出视口就退回默认角落,并把现场写进运行日志 (ui.orb),真机再出问题至少能留证
  • 修掉一个同类漏洞:启动时开关如果是关的,悬浮球压根没被创建,之后把开关打开 只改 display 是没用的(旧版要重启酒馆才有球)—— 现在会当场补建

17. v2.7.5 · 荣耀 Edge:固定定位的包含块被 body 抢走

这条是 v2.7.4 没修好的真正根因,而且我把它在本地复现出来了。

真机日志(荣耀手机 Edge):

ui.orb · 悬浮球位置超出视口 {"left":663,"top":-152,"right":719,"bottom":-96,"viewport":"732x1333"}

right:14px 解出来是对的(732−14≈718),但 bottom:96px 解出来是 −96 —— 只有当包含块的底边在 y=0 时才成立。也就是说:position: fixed 的包含块不是视口, 而是一个「满宽、高度 0、位于页面最顶部」的盒子。

成因:荣耀 Edge 给 body 加了 transform(GPU 合成),body 因此成为 position: fixed 的包含块;而酒馆移动端布局里元素几乎全是 fixed,body 的文档流高度是 0。 于是:

元素 正常 被劫持后
悬浮球(bottom:96px) 视口底部往上 96px 屏幕上方 96px → 看不见
面板(top:50%) 垂直居中 中心跑到 y=0 → 上半截出屏、关不掉
遮罩(inset:0) 铺满视口 缩成 732×0 → 点外面也关不掉

v2.7.4 把窄屏面板改成 bottom:0,在这个环境下正好整块被推到屏幕外 —— 所以你会觉得更严重。

修法(两道防线):

  1. 浮层改挂到 <html> 而不是 <body> —— 直接绕开 body 这个包含块
  2. 摆完实测一次,把「预期位置 − 实际位置」的差值用 margin 补回来。 margin 只挪位置、不干扰 transform,而且不管包含块是谁,这个差值都能测出来 —— 对其它没见过的浏览器怪癖同样有效

顺带把 ensureOrbVisible 的兜底也改了:以前是"清掉内联样式",那只会退回同一套算错的 CSS 默认位置(等于没救);现在直接强制摆到右上角。

复现与回归:测试页新增 ?hostilefixed=1(给 body 加 transform + 让文档流高度为 0), 在 732×1333 下复现出与真机完全一致的几何(球 top −152 / bottom −96), 并把这组断言固化进 L2 扫描和端到端验收 —— 以后这个坑不会再回来。

18. v2.8.0 · 浮层定位重做:不再依赖 CSS 定位

v2.7.5 只是把"body 劫持"这一种情况救回来了。真机继续反馈「面板超过某个缩放就不行、 一屏灰色」——查下去发现根子不在某一行代码,而在方向:

这套 UI 的可见性建立在 7 个隐式假设上,每个都能被某个浏览器单独打破。

# 假设 被谁打破
① position:fixed 相对视口 祖先 transform / 根元素 zoom
② bottom/right 能正确贴边 包含块被劫持时方向就错了
③ vh/dvh = 真实视口高 地址栏、缩放、字体放大
④ transform 居中可用 与校准打架、缩放下有偏差
⑤ margin 能移动元素 对 bottom 锚定的盒子完全无效(v2.7.4 的错)
⑥ 媒体查询切换定位策略 阈值两侧两套代码,只有一套被测到
⑦ env(safe-area-*) 一定可用 不支持的引擎整条 calc 失效

荣耀 Edge 上 ①②③④⑥ 被同时打破(根元素带 transform / 缩放),于是球看不见、 面板跑到屏幕外、关不掉。逐条打补丁只会等来第 8 个假设。

现在改成三条不随环境变化的规矩:

  1. 只认实测值:先把浮层摆到 left/top = 0 量一次,得到「包含块原点」在屏幕上的 真实位置,再把目标位置换算成 left/top 像素。不管包含块是视口还是被劫持的怪盒子, 这个换算都成立
  2. 先钳高度再算位置:H = min(内容高, 视口高 − 2m),位置必然落在 [m, 视口高 − m − H] —— 数学上保证表头与底边永远在屏内, 跟字体多大、内容多长、dvh 取什么值都无关
  3. 摆完就验,验不过降级:正常 → 贴满全屏 → 放弃 fixed 回到文档流。 三档里任何一档都保证「表头在屏内 + 底部关闭按钮可点」

配套改动:

  • CSS 不再参与定位:top/left/right/bottom/transform/width/max-height 全由 JS 用像素写; 窄屏也不再由媒体查询改定位(那正是上一版踩的坑),媒体查询只留外观
  • 底部加大号「关闭」按钮:表头万一被顶出屏幕,这里一定点得到(结构性保险)
  • px 缩放系数:有些浏览器(Edge 的字体大小/缩放)在根元素上做 zoom, 这时写进去的 716px 在屏幕上量回来是 716 × zoom —— 用一根 1000px 探针实测出系数 再除掉;球的安全区也按"屏幕上的实际占位"(56 × zoom)算,不会把自己挤出屏幕
  • 新增诊断日志 ui.place:记录视口、缩放系数、包含块原点、面板最终矩形与降级档位。 真机再出问题,一行日志就能定位

回归矩阵:宿主怪癖(正常 / body 带 transform / html 带 transform / 根级 zoom 0.6 / 根级 zoom 1.5)× 视口(732×1333 / 408×746 / 360×640 / 1280×800)—— 共 24 组, 每组都验「球在屏内、面板在屏内、表头在屏内、两个关闭按钮都能点到、遮罩铺满」。


安装

  1. 酒馆 → 扩展 → 安装扩展 → 粘贴本仓库地址
  2. 或者手动:把整个文件夹放到 public/scripts/extensions/third-party/SillyToken/
  3. 重启酒馆

⚠️ 如果你以前装过 TokenFlow,请先删掉它。 两份捕获层同时存在会让每次请求被记两次。

使用

  • 右下角悬浮球:点击展开统计面板
  • 面板顶部切换时间范围与分组维度
  • 点柱子看当天按模型明细,再点一次取消
  • 设置面板里可以编辑模型价格、别名、汇率、保留天数(默认 365 天)

关于币种

价格表是 USD 基准,面板里通过「显示币种 + 汇率」换算成你想看的货币。 DeepSeek 官方报价是 CNY,想让面板和官方平台对得上,把汇率设成当前汇率即可。

排障

控制台里有全局入口:

__sillyTokenDiagnostics()   // 每个窗口各层是否装在最外层、抓取计数、未定价模型
__sillyTokenLogs()          // 最近的运行日志

面板底部的「复制诊断」会把同样内容复制到剪贴板。

许可

AGPL-3.0,继承自原项目 TokenFlow。 完整全文见 LICENSE。

About

SillyTavern 用量统计扩展(TokenFlow 修改版):统计 Token / 请求数 / 费用,能抓到酒馆助手插件发出的请求

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages