Conversation
- 新增"模型价格"按钮,从 models.dev 获取最新价格并同步到本地数据库 - 新增 /api/sync-model-prices API 路由,支持精确匹配、前缀匹配和模糊匹配 - 修复 hydration 错误:将 rangeInit 改为客户端挂载后从 localStorage 恢复
|
@Zic-Wang is attempting to deploy a commit to the sxjeru's projects Team on Vercel. A member of the Team first needs to authorize it. |
审阅者指南添加了一个手动“模型价格”同步流程:从 models.dev 获取模型价格,使用多种策略与 CLIProxy 模型进行匹配,将结果持久化到本地的 modelPrices 表中,并通过仪表盘按钮、toast 提示和弹窗展示进度与结果;同时修复了日期范围 hydration 问题,并简化了上次同步时间的渲染逻辑。 手动模型价格同步流程的时序图sequenceDiagram
actor User
participant DashboardPage
participant SyncModelPricesAPI as Api_sync_model_prices
participant ModelsDev as Models_dev
participant CLIProxyAPI
participant Postgres as Postgres_modelPrices
User->>DashboardPage: click_模型价格_button
DashboardPage->>DashboardPage: syncModelPrices()
DashboardPage->>DashboardPage: resolve_apiKey_from_filterRouteInput_or_routeOptions
alt no_apiKey
DashboardPage->>DashboardPage: setPricesSyncStatus_and_pricesSyncData_error
DashboardPage-->>User: show_error_toast_and_modal
else has_apiKey
DashboardPage->>DashboardPage: setSyncingPrices_true_and_open_modal
DashboardPage->>SyncModelPricesAPI: POST /api/sync-model-prices {apiKey}
Note over SyncModelPricesAPI: validate_config_and_apiKey
SyncModelPricesAPI->>ModelsDev: GET https://models.dev/api.json
ModelsDev-->>SyncModelPricesAPI: models_dev_json
SyncModelPricesAPI->>SyncModelPricesAPI: build_priceMap_from_models
SyncModelPricesAPI->>CLIProxyAPI: GET {baseUrl}/v1/models
CLIProxyAPI-->>SyncModelPricesAPI: models_list
SyncModelPricesAPI->>SyncModelPricesAPI: match_models_exact_prefix_fuzzy
loop for_each_model
alt price_found
SyncModelPricesAPI->>Postgres: upsert_into_modelPrices
Postgres-->>SyncModelPricesAPI: ok_or_error
else price_not_found
SyncModelPricesAPI->>SyncModelPricesAPI: record_skipped_detail
end
end
SyncModelPricesAPI-->>DashboardPage: summary_and_details_json
DashboardPage->>DashboardPage: setPricesSyncData
alt res_ok
DashboardPage->>DashboardPage: setPricesSyncStatus_with_summary
else res_error
DashboardPage->>DashboardPage: setPricesSyncStatus_with_error
end
DashboardPage->>DashboardPage: setSyncingPrices_false
DashboardPage-->>User: update_modal_and_toast
end
新的 sync-model-prices 类型与仪表盘状态的类图classDiagram
class ModelsDevModel {
+string id
+number cost_input
+number cost_output
+number cost_cache_read
}
class ModelsDevProvider {
+Record_models Record~string,ModelsDevModel~
}
class ModelsDevResponse {
+Record_providers Record~string,ModelsDevProvider~
}
class ModelPriceRecord {
+string model
+string inputPricePer1M
+string cachedInputPricePer1M
+string outputPricePer1M
}
class PricesSyncSummary {
+number total
+number updated
+number skipped
+number failed
}
class PricesSyncDetail {
+string model
+string status
+string matchedWith
+string reason
}
class PricesSyncData {
+PricesSyncSummary summary
+PricesSyncDetail[] details
+string error
}
class DashboardPageComponent {
+boolean mounted
+boolean syncingPrices
+string pricesSyncStatus
+boolean pricesSyncModalOpen
+PricesSyncData pricesSyncData
+string filterRouteInput
+string[] routeOptions
+syncModelPrices()
}
class SyncModelPricesRoute {
+POST(request)
-validateApiKey()
-loadModelsDevData()
-buildPriceMap()
-loadCliProxyModels()
-matchAndUpsertPrices()
}
ModelsDevProvider --> ModelsDevModel : contains
ModelsDevResponse --> ModelsDevProvider : providers
SyncModelPricesRoute --> ModelsDevResponse : uses
SyncModelPricesRoute --> ModelPriceRecord : upserts
DashboardPageComponent --> PricesSyncData : holds
DashboardPageComponent --> SyncModelPricesRoute : calls_POST
文件级改动
技巧与命令与 Sourcery 交互
自定义你的体验访问你的 dashboard 来:
获取帮助Original review guide in EnglishReviewer's GuideAdds a manual "模型价格" sync flow that fetches model prices from models.dev, matches them against CLIProxy models with multiple strategies, persists them to the local modelPrices table, and surfaces progress and results via a dashboard button, toast, and modal; also fixes a date range hydration issue and simplifies last-sync rendering. Sequence diagram for manual model price sync flowsequenceDiagram
actor User
participant DashboardPage
participant SyncModelPricesAPI as Api_sync_model_prices
participant ModelsDev as Models_dev
participant CLIProxyAPI
participant Postgres as Postgres_modelPrices
User->>DashboardPage: click_模型价格_button
DashboardPage->>DashboardPage: syncModelPrices()
DashboardPage->>DashboardPage: resolve_apiKey_from_filterRouteInput_or_routeOptions
alt no_apiKey
DashboardPage->>DashboardPage: setPricesSyncStatus_and_pricesSyncData_error
DashboardPage-->>User: show_error_toast_and_modal
else has_apiKey
DashboardPage->>DashboardPage: setSyncingPrices_true_and_open_modal
DashboardPage->>SyncModelPricesAPI: POST /api/sync-model-prices {apiKey}
Note over SyncModelPricesAPI: validate_config_and_apiKey
SyncModelPricesAPI->>ModelsDev: GET https://models.dev/api.json
ModelsDev-->>SyncModelPricesAPI: models_dev_json
SyncModelPricesAPI->>SyncModelPricesAPI: build_priceMap_from_models
SyncModelPricesAPI->>CLIProxyAPI: GET {baseUrl}/v1/models
CLIProxyAPI-->>SyncModelPricesAPI: models_list
SyncModelPricesAPI->>SyncModelPricesAPI: match_models_exact_prefix_fuzzy
loop for_each_model
alt price_found
SyncModelPricesAPI->>Postgres: upsert_into_modelPrices
Postgres-->>SyncModelPricesAPI: ok_or_error
else price_not_found
SyncModelPricesAPI->>SyncModelPricesAPI: record_skipped_detail
end
end
SyncModelPricesAPI-->>DashboardPage: summary_and_details_json
DashboardPage->>DashboardPage: setPricesSyncData
alt res_ok
DashboardPage->>DashboardPage: setPricesSyncStatus_with_summary
else res_error
DashboardPage->>DashboardPage: setPricesSyncStatus_with_error
end
DashboardPage->>DashboardPage: setSyncingPrices_false
DashboardPage-->>User: update_modal_and_toast
end
Class diagram for new sync-model-prices types and dashboard stateclassDiagram
class ModelsDevModel {
+string id
+number cost_input
+number cost_output
+number cost_cache_read
}
class ModelsDevProvider {
+Record_models Record~string,ModelsDevModel~
}
class ModelsDevResponse {
+Record_providers Record~string,ModelsDevProvider~
}
class ModelPriceRecord {
+string model
+string inputPricePer1M
+string cachedInputPricePer1M
+string outputPricePer1M
}
class PricesSyncSummary {
+number total
+number updated
+number skipped
+number failed
}
class PricesSyncDetail {
+string model
+string status
+string matchedWith
+string reason
}
class PricesSyncData {
+PricesSyncSummary summary
+PricesSyncDetail[] details
+string error
}
class DashboardPageComponent {
+boolean mounted
+boolean syncingPrices
+string pricesSyncStatus
+boolean pricesSyncModalOpen
+PricesSyncData pricesSyncData
+string filterRouteInput
+string[] routeOptions
+syncModelPrices()
}
class SyncModelPricesRoute {
+POST(request)
-validateApiKey()
-loadModelsDevData()
-buildPriceMap()
-loadCliProxyModels()
-matchAndUpsertPrices()
}
ModelsDevProvider --> ModelsDevModel : contains
ModelsDevResponse --> ModelsDevProvider : providers
SyncModelPricesRoute --> ModelsDevResponse : uses
SyncModelPricesRoute --> ModelPriceRecord : upserts
DashboardPageComponent --> PricesSyncData : holds
DashboardPageComponent --> SyncModelPricesRoute : calls_POST
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并留下了一些总体反馈:
- 在
syncModelPrices中,toast 的样式和表情符号的选择是通过对状态消息做子串匹配来驱动的(例如includes("失败")/includes("请先"))。建议改为维护一个单独的结构化状态类型(例如'success' | 'error'),这样 UI 逻辑就不会和本地化的文案耦合在一起。 /api/sync-model-prices的 POST 处理函数在对每个模型进行处理时,会在一个顺序执行的for...of循环里调用await db.insert(...)。如果模型列表比较大,这可能会比较慢。建议考虑批量更新(例如带并发上限的Promise.all,或单次批量 upsert/事务),以减少对数据库的往返请求次数。
给 AI Agent 的提示词
Please address the comments from this code review:
## Overall Comments
- In `syncModelPrices`, the toast style and emoji selection are driven by substring checks on the status message (e.g. `includes("失败")` / `includes("请先")`); consider tracking a separate structured status type (e.g. `'success' | 'error'`) so the UI logic isn’t coupled to localized message text.
- The `/api/sync-model-prices` POST handler performs `await db.insert(...)` inside a sequential `for...of` loop for every model; if the model list is large this could be slow, so consider batching updates (e.g. `Promise.all` with a concurrency limit or a single bulk upsert/transaction) to reduce round-trips to the database.
## Individual Comments
### Comment 1
<location> `app/api/sync-model-prices/route.ts:19-21` </location>
<code_context>
+
+type ModelsDevResponse = Record<string, ModelsDevProvider>;
+
+export async function POST(request: Request) {
+ try {
+ const { apiKey } = await request.json();
+
+ if (!apiKey) {
</code_context>
<issue_to_address>
**🚨 issue (security):** 同步端点信任由客户端提供的 `apiKey`,并且看起来允许在未认证的情况下更新 `modelPrices`,这存在风险。
任何能够访问 `/api/sync-model-prices` 并发送任意 `apiKey` 值的调用方,都可以更新 `modelPrices`,因为这里没有进行身份认证/会话检查,也没有在服务端验证该操作是否为内部/可信操作。这实际上是在公共路由上暴露了一个管理员级别的操作。请通过以下方式之一来加固安全性:要求适当的授权(例如使用你现有的认证/会话)、使用来自配置/环境变量的服务端凭证而不是客户端提供的 `apiKey`,或者以其他方式将该路由限制为可信调用方(例如仅限内部调用,或者通过共享密钥 header)。
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码评审。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- In
syncModelPrices, the toast style and emoji selection are driven by substring checks on the status message (e.g.includes("失败")/includes("请先")); consider tracking a separate structured status type (e.g.'success' | 'error') so the UI logic isn’t coupled to localized message text. - The
/api/sync-model-pricesPOST handler performsawait db.insert(...)inside a sequentialfor...ofloop for every model; if the model list is large this could be slow, so consider batching updates (e.g.Promise.allwith a concurrency limit or a single bulk upsert/transaction) to reduce round-trips to the database.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `syncModelPrices`, the toast style and emoji selection are driven by substring checks on the status message (e.g. `includes("失败")` / `includes("请先")`); consider tracking a separate structured status type (e.g. `'success' | 'error'`) so the UI logic isn’t coupled to localized message text.
- The `/api/sync-model-prices` POST handler performs `await db.insert(...)` inside a sequential `for...of` loop for every model; if the model list is large this could be slow, so consider batching updates (e.g. `Promise.all` with a concurrency limit or a single bulk upsert/transaction) to reduce round-trips to the database.
## Individual Comments
### Comment 1
<location> `app/api/sync-model-prices/route.ts:19-21` </location>
<code_context>
+
+type ModelsDevResponse = Record<string, ModelsDevProvider>;
+
+export async function POST(request: Request) {
+ try {
+ const { apiKey } = await request.json();
+
+ if (!apiKey) {
</code_context>
<issue_to_address>
**🚨 issue (security):** The sync endpoint trusts a client-provided `apiKey` and appears to allow unauthenticated updates to `modelPrices`, which is risky.
Any caller that can reach `/api/sync-model-prices` and send any `apiKey` value can update `modelPrices`, since there’s no auth/session check or server-side verification that this is an internal/trusted operation. This effectively exposes an admin operation on a public route. Please secure this by requiring proper authorization (e.g., your existing auth/session), using a server-side credential from config/env instead of a client-provided `apiKey`, or otherwise restricting the route to trusted callers only (e.g., internal-only or via a shared secret header).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| export async function POST(request: Request) { | ||
| try { | ||
| const { apiKey } = await request.json(); |
There was a problem hiding this comment.
🚨 issue (security): 同步端点信任由客户端提供的 apiKey,并且看起来允许在未认证的情况下更新 modelPrices,这存在风险。
任何能够访问 /api/sync-model-prices 并发送任意 apiKey 值的调用方,都可以更新 modelPrices,因为这里没有进行身份认证/会话检查,也没有在服务端验证该操作是否为内部/可信操作。这实际上是在公共路由上暴露了一个管理员级别的操作。请通过以下方式之一来加固安全性:要求适当的授权(例如使用你现有的认证/会话)、使用来自配置/环境变量的服务端凭证而不是客户端提供的 apiKey,或者以其他方式将该路由限制为可信调用方(例如仅限内部调用,或者通过共享密钥 header)。
Original comment in English
🚨 issue (security): The sync endpoint trusts a client-provided apiKey and appears to allow unauthenticated updates to modelPrices, which is risky.
Any caller that can reach /api/sync-model-prices and send any apiKey value can update modelPrices, since there’s no auth/session check or server-side verification that this is an internal/trusted operation. This effectively exposes an admin operation on a public route. Please secure this by requiring proper authorization (e.g., your existing auth/session), using a server-side credential from config/env instead of a client-provided apiKey, or otherwise restricting the route to trusted callers only (e.g., internal-only or via a shared secret header).
|
感谢 pr,不需要手动填价格还是很不错的。 |
Summary by Sourcery
从 models.dev 手动同步模型价格到本地数据库,并修复日期范围在水合过程中的行为问题。
新功能:
/api/sync-model-prices接口,从 models.dev 获取价格信息,将其与可用模型进行匹配,并将价格 upsert 到本地数据库中。错误修复:
改进:
Original summary in English
Summary by Sourcery
Add manual model price synchronization from models.dev to the local database and fix date range hydration behavior.
New Features:
Bug Fixes:
Enhancements: