Skip to content

feat(usage): support importing model pricing from models.dev#4079

Merged
farion1231 merged 3 commits into
farion1231:mainfrom
kingcanfish:feat/models-dev-pricing-import
Jun 16, 2026
Merged

feat(usage): support importing model pricing from models.dev#4079
farion1231 merged 3 commits into
farion1231:mainfrom
kingcanfish:feat/models-dev-pricing-import

Conversation

@kingcanfish

@kingcanfish kingcanfish commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary / 概述

在「使用统计 → 成本定价 → 添加」面板新增 从 models.dev 导入 入口:拉取 https://models.dev/api.json 的模型定价数据(约 4800 个带定价的模型),按发布时间倒序默认展示最新 50 个,支持全量搜索与按供应商筛选,选择单个模型导入。导入复用与手动添加完全相同的 update_model_pricing 命令。

Adds an "Import from models.dev" entry to the Add Pricing panel: fetches model pricing from https://models.dev/api.json (~4800 priced models), shows the 50 most recently released by default, supports full-text search and provider filtering, and imports the selected model through the exact same update_model_pricing command as manual entry.

实现要点 / Key implementation details:

  • 模型 ID 归一化 — 导入前按后端 clean_model_id_for_pricing 的同等规则归一化(去掉 xx/ 厂商前缀、转小写、截断 : 后缀、@-、去 [1m] 标记)。models.dev 约 60% 的原始 ID 含这些字符,原样入库将永远无法被成本归因查询匹配到。
    Model IDs are normalized to mirror the backend's clean_model_id_for_pricing rules before storing; ~60% of raw models.dev IDs would otherwise never match cost-attribution lookups.
  • 回填按原始别名匹配(Rust,回应第一条 review)— 定价更新后的零成本历史回填原先用 SQL 精确字符串匹配过滤行,而日志列存的是原始模型串(如 openrouter/anthropic/claude-sonnet-4.5:free),别名形式的历史行会被漏掉。改为在内存中按归一化候选集合匹配 model/request_model/pricing_model 三列。
    Scoped backfill (Rust, addressing the first review comment) now matches log rows against normalized candidates of all three model columns in memory, instead of exact SQL string equality that skipped alias-form historical rows.
  • 单选导入(回应第二条 review)— 每次仅导入一个模型。update_model_pricing 每次写入后都会触发一次零成本历史回填,该回填会先加载全部零成本日志行再按模型过滤,批量导入 N 个模型会产生 selectedModels × allZeroCostLogs 的重复全表扫描。重新导入定价的场景不多,因此直接收窄为单选,同时也消除了同一归一化 ID 在多个供应商条目间静默互相覆盖的问题。
    Single-select import (addressing the second review comment) — each update_model_pricing call triggers a backfill pass that loads all zero-cost rows before filtering, so bulk import scaled as selectedModels × allZeroCostLogs full scans. Since re-importing pricing is rare, the picker is restricted to one model per import, which also removes the silent last-write-wins risk between provider entries sharing a normalized ID.
  • 交互细节 — 搜索框内按 ESC 不会关闭弹窗丢失已选项(与 FullScreenPanelisTextEditableTarget 约定一致);供应商下拉为 135 项加了 max-height;价格字符串格式化保证不输出科学计数法(后端 Decimal::from_str 不接受)。
    ESC inside the search input no longer discards the selection; the 135-item provider dropdown gets a max-height; price strings are formatted to never produce exponent notation (rejected by Decimal::from_str).
  • 新增 zh / en / zh-TW / ja 四份 i18n 文案,及归一化、价格格式化、数据扁平化逻辑的单元测试。
    Adds i18n strings for all four locales and unit tests for the normalization, price formatting and flattening logic.

Related Issue / 关联 Issue

Fixes #4017

Screenshots / 截图

Before / 修改前 After / 修改后
仅能手动逐条填写模型定价 「添加」面板顶部新增导入入口,弹窗内可搜索/筛选 models.dev 模型定价并选择单个模型导入(下方截图为早期多选版界面,交互已改为单选)
image image image image image

Checklist / 检查清单

  • pnpm typecheck passes / 通过 TypeScript 类型检查
  • pnpm format:check passes / 通过代码格式检查
  • cargo clippy passes (if Rust code changed) / 通过 Clippy 检查(如修改了 Rust 代码)
  • Updated i18n files if user-facing text changed / 如修改了用户可见文本,已更新国际化文件

🤖 Generated with Claude Code

Add an "Import from models.dev" button to the Add Pricing panel that
fetches https://models.dev/api.json, lists priced models sorted by
release date (newest 50 by default, full-text search across ~4800),
and bulk-imports the selected entries through the same
update_model_pricing command as manual entry.

- Normalize imported model IDs to match the backend's
  clean_model_id_for_pricing rules (strip vendor prefix, lowercase,
  truncate ':' suffix, map '@' to '-', drop the [1m] marker) so the
  stored rows actually match cost-attribution lookups
- Dedupe selections that collapse to the same model_id and report
  skipped duplicates in the success toast
- Invalidate usage queries on settled (not just success) so partial
  import failures still refresh the pricing list
- Keep ESC inside the picker's search input from closing the dialog
  and discarding the selection
- Add i18n keys for zh/en/zh-TW/ja and unit tests for the
  normalization, price formatting and flattening logic

Fixes farion1231#4017

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@farion1231

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78ca0380aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/usage/ModelsDevPickerDialog.tsx Outdated
The scoped backfill selected zero-cost rows via exact SQL string match,
but log columns store raw model strings (route prefixes, :free variants,
date suffixes), so alias rows were skipped until the next full backfill
on startup. Filter rows in Rust with the same model_pricing_candidates
normalization used by the pricing lookup; pricing decision logic is
untouched. Pre-existing gap from schema v11, surfaced by bulk import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kingcanfish

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@yovinchen

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 936a776b0f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/services/usage_stats.rs
Each update_model_pricing call triggers a backfill pass that loads
every zero-cost usage row before filtering by model, so bulk-importing
N entries scaled as selectedModels x allZeroCostLogs full scans.
Re-importing pricing is rare, so drop the batch path instead of
optimizing it: the picker is now single-select, one import runs exactly
one update_model_pricing call and one backfill pass. This also removes
the normalized-ID dedup logic and the useImportModelPricing hook in
favor of the existing useUpdateModelPricing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kingcanfish

Copy link
Copy Markdown
Contributor Author

目前来看 批量导入模型定价的场景还是比较少的,如果为了支持批量导入而去更改rust 接口定义的话 改动面就大了 收益不太高,所以暂时还是改回只导入单条模型定价

@kingcanfish

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ce263528c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/services/usage_stats.rs
@kingcanfish

Copy link
Copy Markdown
Contributor Author

i think it's ready for merging into master ,kindly ping @farion1231

@farion1231 farion1231 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

感谢您的贡献!

@farion1231 farion1231 merged commit 0bb3b75 into farion1231:main Jun 16, 2026
5 checks passed
@kingcanfish kingcanfish deleted the feat/models-dev-pricing-import branch June 17, 2026 10:44
gfunc pushed a commit to gfunc/cc-switch that referenced this pull request Jun 19, 2026
…231#4079)

* feat(usage): support importing model pricing from models.dev

Add an "Import from models.dev" button to the Add Pricing panel that
fetches https://models.dev/api.json, lists priced models sorted by
release date (newest 50 by default, full-text search across ~4800),
and bulk-imports the selected entries through the same
update_model_pricing command as manual entry.

- Normalize imported model IDs to match the backend's
  clean_model_id_for_pricing rules (strip vendor prefix, lowercase,
  truncate ':' suffix, map '@' to '-', drop the [1m] marker) so the
  stored rows actually match cost-attribution lookups
- Dedupe selections that collapse to the same model_id and report
  skipped duplicates in the success toast
- Invalidate usage queries on settled (not just success) so partial
  import failures still refresh the pricing list
- Keep ESC inside the picker's search input from closing the dialog
  and discarding the selection
- Add i18n keys for zh/en/zh-TW/ja and unit tests for the
  normalization, price formatting and flattening logic

Fixes farion1231#4017

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(usage): match scoped cost backfill against raw model aliases

The scoped backfill selected zero-cost rows via exact SQL string match,
but log columns store raw model strings (route prefixes, :free variants,
date suffixes), so alias rows were skipped until the next full backfill
on startup. Filter rows in Rust with the same model_pricing_candidates
normalization used by the pricing lookup; pricing decision logic is
untouched. Pre-existing gap from schema v11, surfaced by bulk import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(usage): restrict models.dev pricing import to a single model

Each update_model_pricing call triggers a backfill pass that loads
every zero-cost usage row before filtering by model, so bulk-importing
N entries scaled as selectedModels x allZeroCostLogs full scans.
Re-importing pricing is rare, so drop the batch path instead of
optimizing it: the picker is now single-select, one import runs exactly
one update_model_pricing call and one backfill pass. This also removes
the normalized-ID dedup logic and the useImportModelPricing hook in
favor of the existing useUpdateModelPricing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

可以考虑将模型价格数据从 models.dev 项目中接入

3 participants