Skip to content

feat(packages): 为 usage 增加输出 token 趋势指令#895

Merged
dingyi222666 merged 9 commits into
ChatLunaLab:v1-devfrom
Procyon-Nan:feat/usage-tokens-command
Jun 10, 2026
Merged

feat(packages): 为 usage 增加输出 token 趋势指令#895
dingyi222666 merged 9 commits into
ChatLunaLab:v1-devfrom
Procyon-Nan:feat/usage-tokens-command

Conversation

@Procyon-Nan

@Procyon-Nan Procyon-Nan commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

概览

  • chatluna-usage 增加 tokens 指令,支持 dayweekmonthall 范围参数,以及可选插件用量明细
  • 基于 chatluna_usage 表生成 token 用量统计,包含累计 token、累计请求、TPM、RPM
  • 图表同时展示总 token、输入 token、输出 token 三条趋势线,并提供颜色图例
  • 新增 puppeteer HTML/SVG 图表渲染,支持浅色和深色主题,可在插件配置中切换
  • puppeteer 作为可选服务接入;未启用时仍返回文字统计并提示需要启用 puppeteer

验证

  • yarn workspace @root/chatluna-koishi fast-build extension-usage

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new "tokens" command to visualize ChatLuna token usage trends and plugin breakdowns, rendering charts via Puppeteer. Feedback highlights a potential call stack overflow risk in "index.ts" when using "Math.max" with the spread operator on large datasets. Additionally, in "renderer.ts", it is recommended to make the "__dirname" resolution safer for ESM environments and to delete temporary HTML files immediately in the "finally" block rather than using a delayed "setTimeout" to prevent disk clutter and memory leaks.

Comment thread packages/extension-usage/src/index.ts Outdated
Comment thread packages/extension-usage/src/renderer.ts Outdated
Comment thread packages/extension-usage/src/renderer.ts Outdated
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

新增 token 报表与趋势功能:定义数据类型与时间计算、实现报表聚合与趋势点生成、新增 JSX 运行时用于 HTML 构建、生成 SVG 图表与页面、通过 Puppeteer 截图,并在命令/配置中集成 /tokens。

Changes

Token 趋势图渲染功能

Layer / File(s) Summary
Token 数据模型与基础工具
packages/extension-usage/src/tokens.ts
定义 TokenRange/TokenTheme/TokenPoint/PluginUsage/TokenReport 类型;实现 tokenRange()tokenStart()formatDate()formatTokenReport() 与基础格式化工具。
报表聚合与趋势点生成
packages/extension-usage/src/tokens.ts
实现 createTokenReport():按 createdAt 排序、按分钟桶聚合 tokens 与 calls、计算最大 TPM/RPM;实现 tokenPoints()pluginUsage()tokenAlignedStart()tokenStep()
简化 JSX 运行时
packages/extension-usage/src/html/jsx-runtime.ts
新增 RawHtml/Fragmentraw()jsx()/jsxsrenderHtml()escapeHtml(),用于在无框架环境下安全生成原始 HTML 字符串并导出 JSX 命名空间类型。
SVG 图表与页面拼装
packages/extension-usage/src/renderer.tsx
新增内联 CSS 与配色、monotonePath() 生成平滑曲线、chart() 绘制面积与三条折线、pluginCard() 渲染插件用量卡片、pageHtml() 组装完整 HTML 模板。
Puppeteer 页面与截图
packages/extension-usage/src/renderer.tsx
新增 renderTokenTrend():在 Puppeteer 中创建页面、设置内容、等待字体就绪、定位 .stage 并截图;处理容器缺失与异常并关闭页面。
命令注册与运行时集成
packages/extension-usage/src/index.ts
注册 /tokens 命令(支持 day/week/month/allplugin/-p);实现 tokenReport()chatluna_usage 根据时间范围查询并调用 createTokenReport();实现 sendTokens() 发送文本并在可用 puppeteer 下渲染并发送图片。
配置扩展与类型导出
packages/extension-usage/src/index.ts
ChatLunaUsage.Config 中新增 tokensTheme(`auto

序列图

sequenceDiagram
  participant User
  participant TokensCommand as /tokens 命令
  participant tokenReport
  participant Database as chatluna_usage
  participant renderTokenTrend
  participant Response

  User->>TokensCommand: /tokens day/week/month/all [plugin]
  TokensCommand->>tokenReport: 调用 tokenReport(range, withPlugins)
  tokenReport->>Database: 按 createdAt 范围查询记录
  Database-->>tokenReport: 返回使用数据
  tokenReport-->>TokensCommand: 返回 TokenReport
  TokensCommand->>Response: 发送文本统计结果
  alt 有 Puppeteer
    TokensCommand->>renderTokenTrend: 渲染趋势图(theme: light/dark/auto)
    renderTokenTrend-->>TokensCommand: 返回图片 buffer
    TokensCommand->>Response: 发送图片
  end
Loading

🎯 4 (复杂) | ⏱️ ~45 分钟

  • 建议审查者:
    • dingyi222666

🐰 我数着时间与 token,轻轻跳进数据林,
SVG 折线在草间起舞,月昼光影渐新,
Puppeteer 摘下画面,插件卡片低声吟,
文本先行,图像随后,趋势尽在兔子笔心,
欢庆一段代码的春辰与进阶。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 标题准确总结了主要变更:为 usage 扩展 token 趋势指令功能,标题简洁明了地表达了核心改动点。
Description check ✅ Passed PR 描述清晰地对应了变更内容,包括新增指令、统计功能、图表渲染、主题支持等多个方面,与代码变更高度相关。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- 在 chatluna-usage 中新增 tokens 指令,支持 day、week、month、all 统计范围及插件明细参数。

- 基于 chatluna_usage 表聚合 total、input、output token 与请求峰值,按不同范围生成分桶趋势数据。

- 新增 puppeteer HTML 图表渲染模板,支持浅色和深色主题以及颜色图例。

- 将 puppeteer 声明为可选依赖服务,未启用时保留文字统计输出。
@Procyon-Nan Procyon-Nan force-pushed the feat/usage-tokens-command branch from 1efe8d3 to fb89391 Compare June 1, 2026 11:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/extension-usage/src/index.ts (1)

469-488: range === 'all' 会全表扫描,建议加上观测/治理手段。

rangealltime = { $lt: end },相当于把 chatluna_usage 全表拉进内存再聚合。随着记录增长,单次 tokens -a 可能造成明显的内存与延迟峰值。表上已有 createdAt 索引,但无范围过滤时索引无济于事。可考虑:为 all 设定一个上限时间窗(或最大行数),或在数据库侧做聚合统计而非全量取回。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension-usage/src/index.ts` around lines 469 - 488, The
tokenReport method currently sets time = { $lt: end } for range === 'all',
causing a full-table fetch of chatluna_usage by createdAt; change tokenReport to
avoid full-table scans by applying a sensible cap or DB-side aggregation: either
compute a start = end - MAX_TOKEN_REPORT_WINDOW (configurable, e.g. using Time
constants) or add a limit/aggregation in the database query (e.g. request
aggregated token sums or top-N rows instead of pulling all records) before
calling createTokenReport; ensure the change references tokenReport,
createTokenReport, chatluna_usage, createdAt and use a configurable MAX_ROWS or
MAX_WINDOW so future growth is governed and include a warning/log when the “all”
request is capped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/extension-usage/src/index.ts`:
- Around line 469-488: The tokenReport method currently sets time = { $lt: end }
for range === 'all', causing a full-table fetch of chatluna_usage by createdAt;
change tokenReport to avoid full-table scans by applying a sensible cap or
DB-side aggregation: either compute a start = end - MAX_TOKEN_REPORT_WINDOW
(configurable, e.g. using Time constants) or add a limit/aggregation in the
database query (e.g. request aggregated token sums or top-N rows instead of
pulling all records) before calling createTokenReport; ensure the change
references tokenReport, createTokenReport, chatluna_usage, createdAt and use a
configurable MAX_ROWS or MAX_WINDOW so future growth is governed and include a
warning/log when the “all” request is capped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f33ed7f6-cc8e-48e0-acb4-596d7be53841

📥 Commits

Reviewing files that changed from the base of the PR and between 2229393 and 1efe8d3.

⛔ Files ignored due to path filters (1)
  • packages/extension-usage/package.json is excluded by !**/*.json
📒 Files selected for processing (3)
  • packages/extension-usage/resources/token-trend/template.html
  • packages/extension-usage/src/index.ts
  • packages/extension-usage/src/renderer.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/extension-usage/src/index.ts`:
- Line 66: The assignment to variable from mixes a ternary and nullish
coalescing without grouping, which triggers prettier; update the expression in
index.ts so the conditional is wrapped in parentheses (i.e., parenthesize the
RHS ternary so the nullish coalescing binds as intended) for the declaration
using range, sorted, start and end.

In `@packages/extension-usage/src/renderer.ts`:
- Around line 240-244: Compute dirname without referencing __dirname at
evaluation time: replace the current __dirname?.length check with a safe runtime
check like typeof __dirname !== 'undefined' ? __dirname :
path.dirname(fileURLToPath(import.meta.url)), and then build templatePath using
path.resolve(dirname, '..', 'resources', 'token-trend', 'template.html') (use
path.dirname(fileURLToPath(import.meta.url)) for ESM) so that the resolved
templatePath points to
packages/extension-usage/resources/token-trend/template.html rather than
src/resources/..., referencing the existing symbols __dirname,
fileURLToPath(import.meta.url), dirname, templatePath, and path.resolve to
locate the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 38469b34-be9c-432c-a787-978f51cfda8c

📥 Commits

Reviewing files that changed from the base of the PR and between 1efe8d3 and fb89391.

⛔ Files ignored due to path filters (1)
  • packages/extension-usage/package.json is excluded by !**/*.json
📒 Files selected for processing (3)
  • packages/extension-usage/resources/token-trend/template.html
  • packages/extension-usage/src/index.ts
  • packages/extension-usage/src/renderer.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/extension-usage/resources/token-trend/template.html

Comment thread packages/extension-usage/src/index.ts Outdated
Comment thread packages/extension-usage/src/renderer.ts Outdated
- 调整 chatluna-usage tokens 报表中 TPM 与 RPM 的计算方式,在统计循环内维护单分钟峰值,避免大量分钟桶通过参数展开传入 Math.max。

- 修复 token 趋势图渲染模块在 ESM 环境下的目录解析逻辑,使用 import.meta.url 时转换为所在目录。

- 调整 puppeteer 渲染临时 HTML 的清理时机,在渲染流程结束后立即删除临时文件,避免延迟定时器残留。
Comment thread packages/extension-usage/src/index.ts Outdated
Comment thread packages/extension-usage/src/index.ts Outdated
Comment thread packages/extension-usage/src/index.ts Outdated
Comment thread packages/extension-usage/src/index.ts Outdated
Comment thread packages/extension-usage/src/index.ts Outdated
Comment thread packages/extension-usage/src/renderer.ts Outdated
@dingyi222666

Copy link
Copy Markdown
Member

接着改啊,问题不大,改完下周再合并就行了,下周我继续做新功能

- 将 tokens 指令参数解析、报表聚合、格式化和趋势分桶逻辑移动到独立 tokens 模块,减少 index.ts 复杂度。

- 将 tokens 指令 action 下沉到 ChatLunaUsage.sendTokens,并保留 tokenReport 负责数据库查询。

- 将趋势图渲染器改为 TSX 和包内 HTML JSX runtime,改用 page.setContent 直接渲染截图,移除资源模板依赖。

- 增加 auto/light/dark tokensTheme;auto 通过控制台客户端同步的色彩模式解析,light/dark 保持强制主题。

- 保留原有 range 简写、plugin 明细、先发送文本和未启用 puppeteer 时提示的行为。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/extension-usage/src/index.ts`:
- Around line 26-27: The code currently stores theme in a single process-wide
field private _theme, causing 'auto' to reflect the last-reported client theme
across all sessions; change this to a session-scoped theme mapping and use that
instead: replace the single _theme with a Map keyed by connection/session id (or
attach theme to the existing session object), update the code that records
client-reported theme (the handler that writes to _theme at the reported site
around lines ~121-124) to set the session-specific entry, and update the /tokens
rendering path (the code that reads _theme around lines ~333-336) to read the
theme from the current request's session/connection key; if a session key is
unavailable, compute 'auto' per-request (e.g., from request headers or default)
rather than falling back to a process-level shared value. Ensure all
reads/writes reference the new session-scoped lookup (Map.get/Map.set or
session.theme) and remove reliance on the global _theme.

In `@packages/extension-usage/src/tokens.ts`:
- Around line 148-158: The trend buckets are misaligned because the loop starts
from the raw start timestamp but labels (and later idx math) assume
whole-hour/whole-day boundaries; fix by computing an alignedStart (floor start
to the hour when range === 'day' or to midnight when range === 'month'/'year' as
appropriate) and use that alignedStart as the initial value for the for-loop
variable `at` (instead of `+start`), and use the same aligned reference when
computing labels and the `idx` logic later (functions/vars to edit: the `hourly`
flag, `tokenStep`, the `for (let at = +start; ...)` initialization, the
`result.push({... label: ...})` label generation, and the idx calculation around
lines 165-167) so buckets and displayed labels share the same boundary origin.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 364ecb63-2677-4400-bf39-3aa20db84fca

📥 Commits

Reviewing files that changed from the base of the PR and between fb89391 and ec3d65d.

⛔ Files ignored due to path filters (1)
  • packages/extension-usage/package.json is excluded by !**/*.json
📒 Files selected for processing (5)
  • packages/extension-usage/client/index.ts
  • packages/extension-usage/src/html/jsx-runtime.ts
  • packages/extension-usage/src/index.ts
  • packages/extension-usage/src/renderer.tsx
  • packages/extension-usage/src/tokens.ts

Comment thread packages/extension-usage/src/index.ts Outdated
Comment thread packages/extension-usage/src/tokens.ts Outdated
@Procyon-Nan Procyon-Nan requested a review from dingyi222666 June 7, 2026 11:16

@dingyi222666 dingyi222666 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

解决一下 #895 (review)

还有合并冲突

@Procyon-Nan

Copy link
Copy Markdown
Contributor Author

got

移除 usage 控制台向后端同步主题的逻辑,并删除服务端进程级主题状态,避免 tokens 命令在
  auto 主题下受其他控制台连接影响。

调整 tokens 趋势分桶的起始时间对齐逻辑,使分桶基准与图表标签保持一致,避免整点或整日标签与实际桶范围错位。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/extension-usage/src/index.ts (1)

24-24: 💤 Low value

可选优化:考虑内联常量。

DEFAULT_TOKEN_THEME 仅在 329 行使用一次。根据编码规范,未被多次复用的常量建议内联。不过当前写法通过类型 Exclude<UsageTokenTheme, 'auto'> 明确文档化了 'auto' 的回退语义,也具有合理性。

♻️ 如选择内联的参考实现
-const DEFAULT_TOKEN_THEME: Exclude<UsageTokenTheme, 'auto'> = 'light'

然后在 329 行:

-                    ? DEFAULT_TOKEN_THEME
+                    ? 'light'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension-usage/src/index.ts` at line 24, DEFAULT_TOKEN_THEME is
declared but only used once; inline it at its single use site to reduce
indirection while preserving the documented type. Replace the reference to
DEFAULT_TOKEN_THEME with the literal 'light' and, if necessary to satisfy
typing, annotate or cast it as Exclude<UsageTokenTheme, 'auto'> (referencing the
UsageTokenTheme type) where the value is currently consumed; remove the
standalone DEFAULT_TOKEN_THEME declaration afterwards.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/extension-usage/src/index.ts`:
- Line 24: DEFAULT_TOKEN_THEME is declared but only used once; inline it at its
single use site to reduce indirection while preserving the documented type.
Replace the reference to DEFAULT_TOKEN_THEME with the literal 'light' and, if
necessary to satisfy typing, annotate or cast it as Exclude<UsageTokenTheme,
'auto'> (referencing the UsageTokenTheme type) where the value is currently
consumed; remove the standalone DEFAULT_TOKEN_THEME declaration afterwards.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 81fc166c-e060-4d9d-bf2f-7ff21c4d3691

📥 Commits

Reviewing files that changed from the base of the PR and between ec3d65d and be6f3f0.

📒 Files selected for processing (2)
  • packages/extension-usage/src/index.ts
  • packages/extension-usage/src/tokens.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/extension-usage/src/tokens.ts

@Procyon-Nan Procyon-Nan requested a review from dingyi222666 June 8, 2026 15:28
@dingyi222666

Copy link
Copy Markdown
Member

拉取最新提交测一下看看有无问题,没问题我明天合并

@Procyon-Nan

Copy link
Copy Markdown
Contributor Author

got,有点晚了,明早测

修复 extension-usage 插件入口导出方式,移除默认导出以避免 Koishi loader 优先解析默认导出时丢失模块级 Config。

为 extension-usage 声明 @satorijs/element 运行时依赖,并在包级 tsconfig 中显式加载其 JSX 类型,确保 renderer.tsx 使用的 Satori JSX 属性能够被正确识别。

影响文件包括 packages/extension-usage/package.json、packages/extension-usage/src/index.ts 和 packages/extension-usage/tsconfig.json。
修复 usage 插件 tokens 图表渲染中的装饰元素闭合问题。

修正 packages/extension-usage/src/renderer.tsx:
- 为图例、插件色点和进度条填充元素加入显式文本子节点,避免普通 HTML 元素被序列化为自闭合标签。
- 为装饰元素设置零字号和零行高,保持原有视觉尺寸。
- 修复浏览器解析自闭合普通 HTML 标签时导致的图例换行、插件名称覆盖和进度条裁切问题。
@Procyon-Nan Procyon-Nan requested a review from dingyi222666 June 10, 2026 09:39
@dingyi222666 dingyi222666 merged commit 55438c2 into ChatLunaLab:v1-dev Jun 10, 2026
4 of 5 checks passed
@Procyon-Nan Procyon-Nan deleted the feat/usage-tokens-command branch June 10, 2026 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[功能建议]: 建议新增一键查询最新 Token 消耗趋势图的指令

2 participants