Conversation
- Replaced the existing JSON parsing logic with a dedicated parseUsageFromResponseText function to enhance clarity and maintainability. - Consolidated usage metrics extraction from both JSON responses and SSE events, improving the handling of various response formats. - Updated logging to provide better insights into usage metrics captured from different sources. This refactor simplifies the response handling process and improves the overall robustness of usage metrics extraction.
问题根源: - getProviderStatistics() 使用字符串日期比较,导致统计数据与首页/排行榜不一致 修复内容: - 移除字符串日期构造(todayLocalStr/tomorrowLocalStr) - 使用 Date 对象进行范围查询(与 overview.ts/leaderboard.ts 一致) - 简化 SQL 查询,移除复杂的 AT TIME ZONE 转换 - 移除未使用的 getEnvConfig 导入 影响: - 供应商页面将显示与其他页面一致的每日用量数据 - 代码简化 14 行,提高可维护性 - 保持函数签名不变,向后兼容 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
完成供应商管理页面的全面 UI/UX 重构,优化列表展示和详情编辑体验。 主要变更: ### 数据模型扩展 (IMPL-1) - 新增 websiteUrl 和 faviconUrl 字段到 Provider 模型 - 创建数据库迁移 0016_curious_paladin.sql - 实现自动 favicon 获取(Google Favicon API) - 更新所有数据层(types, schema, actions, repository) ### 表单折叠优化 (IMPL-2) - 使用 Collapsible 组织 843 行表单的 5 个高级功能区域 - 实现 localStorage 偏好记忆 - 添加展开/折叠全部按钮 - 每个区域显示当前配置状态摘要 ### 搜索功能集成 (IMPL-3) - 在 ProviderManager 中新增搜索框 - 使用 useDebounce (500ms) 优化性能 - 支持模糊匹配 name、url、groupTag 三个字段 - 统一搜索/筛选/排序逻辑到单一 useMemo ### API Key 安全展示 (IMPL-4) - 实现 Dialog 模式的密钥展示和复制功能 - 添加 getUnmaskedProviderKey server action(仅 admin) - 参考 key-list-header 模式实现安全机制 - 复制按钮带 CheckCircle 反馈 ### 列表组件重构 (IMPL-5) - 创建 ProviderRichListItem 组件(水平布局) - 卡片式网格布局改为富文本列表式 - 移除所有 Popover 内联编辑,统一使用 Dialog - 集成 favicon 显示和官网跳转 - 集成 API Key 安全展示 - 优化 Dialog 编辑体验(自动聚焦) - 响应式设计(移动端/桌面端/大屏) 技术改进: - 数据模型向后兼容(所有新字段 nullable) - 性能优化(useMemo 减少重渲染) - 类型安全(TypeScript 完整覆盖) - 代码质量(格式化 + lint 通过) 文件变更统计: - 10 个文件修改 - 新增 889 行,删除 1098 行 - 新增 2 个组件文件,1 个迁移文件 - 旧组件重命名为 .legacy.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fix Disable pino-pretty in Turbopack development environment
| @@ -0,0 +1,2 @@ | |||
| ALTER TABLE "providers" ADD COLUMN "website_url" text;--> statement-breakpoint | |||
| ALTER TABLE "providers" ADD COLUMN "favicon_url" text; No newline at end of file | |||
There was a problem hiding this comment.
缺少文件末尾换行符
根据 Git 和 POSIX 规范,文本文件应该以换行符结尾。
| ALTER TABLE "providers" ADD COLUMN "favicon_url" text; | |
| ALTER TABLE "providers" ADD COLUMN "favicon_url" text; |
(在行末添加一个空行)
| <div className="flex-1 min-w-0"> | ||
| <div className="flex items-center gap-2 flex-wrap"> | ||
| {/* Favicon */} | ||
| {provider.faviconUrl && ( |
There was a problem hiding this comment.
🔐 安全风险:XSS 和隐私泄露
直接使用用户输入的 URL 作为 <img src> 存在多个安全风险:
- XSS 攻击:恶意用户可以输入
javascript:或data:URI - 隐私泄露:外部图片加载会泄露用户 IP 地址
- SSRF 风险:可能探测内网地址
建议修复方案:
// 1. 在 schema 中添加协议验证(推荐)
// src/lib/validation/schemas.ts
faviconUrl: z
.string()
.url()
.refine((url) => url.startsWith('https://'), {
message: 'Favicon URL 必须使用 HTTPS 协议',
})
.optional(),
// 2. 或者添加运行时检查
{provider.faviconUrl?.startsWith('https://') && (
<img
src={provider.faviconUrl}
className="h-4 w-4 flex-shrink-0"
referrerPolicy="no-referrer" // 防止泄露 Referer
loading="lazy" // 性能优化
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/>
)}更安全的方案是通过后端代理加载图片,避免直接暴露用户 IP。
| src={provider.faviconUrl} | ||
| alt="" | ||
| className="h-4 w-4 flex-shrink-0" | ||
| onError={(e) => { |
There was a problem hiding this comment.
代码风格建议:避免直接操作 DOM
直接修改 style.display 不够优雅,建议使用 React 状态管理:
const [faviconError, setFaviconError] = useState(false);
// ...
{provider.faviconUrl && !faviconError && (
<img
src={provider.faviconUrl}
className="h-4 w-4 flex-shrink-0"
onError={() => setFaviconError(true)}
/>
)}或者使用 fallback 图标:
{provider.faviconUrl ? (
<img
src={provider.faviconUrl}
className="h-4 w-4 flex-shrink-0"
onError={(e) => {
// 替换为默认图标
(e.target as HTMLImageElement).src = '/default-provider-icon.svg';
}}
/>
) : (
<Globe className="h-4 w-4 text-muted-foreground" />
)}- Introduced a new error category for non-retryable client errors, specifically for issues like prompt length, content filtering, PDF limits, and formatting errors. - Implemented a whitelist pattern matching system to efficiently identify these errors without recompiling regex on each call. - Updated the error categorization logic to prioritize client input errors, ensuring they are logged and returned immediately without triggering retries or affecting the circuit breaker. - Enhanced the ProxyForwarder to handle these errors distinctly, improving the robustness of the proxy's error management. This refactor aims to streamline error handling and improve user feedback for common client-side issues.
代码审查总结这个 PR 将 dev 分支的最新功能合并到 main,包含了供应商管理页面的重构和多项改进。代码质量整体良好,以下是审查意见: ✅ 优秀的地方
|
- 排行榜 API 添加 session 验证和权限检查(返回 401/403) - 排行榜页面添加服务端权限验证和友好提示 UI - 概览面板根据权限过滤全站指标 - 保持向后兼容:管理员始终有完整权限 - 遵循 statistics.ts 权限模式 Fixes #85 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
修复两个关键安全问题(GitHub Issue #87): 1. 管理员无法清除 API Key 过期日期 - 根因:Zod schema 将空字符串转为 undefined,Repository 层视为"不更新" - 修复:在 Action 层添加 undefined → null 转换逻辑 - 类型:更新 CreateKeyData/UpdateKeyData 支持 null 值(永不过期) 2. 普通用户可修改自己的账户限额(权限提升漏洞) - 根因:editUser 仅检查管理员角色,未限制用户修改敏感字段 - 修复:实现三层权限控制(管理员全部/用户非敏感/其他拒绝) - 敏感字段:rpm, dailyQuota, providerGroup(仅管理员可改) - 普通字段:name, description(用户可改自己的) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…monthly) via date-fns-tz and env TZ
添加 filterPrivateParameters() 函数,在序列化前递归过滤所有下划线前缀的私有参数(如 _canRetryWithOfficialInstructions),防止上游供应商返回 "Unsupported parameter" 错误。 - 支持嵌套对象和数组的递归过滤 - 使用 Pino debug 日志记录被过滤的参数 - 无破坏性变更,保持向后兼容 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Updated the leaderboard API to support a new `scope` parameter, allowing retrieval of user or provider rankings. - Implemented validation for the `scope` parameter, ensuring only valid values are accepted. - Enhanced the leaderboard view component to dynamically switch between user and provider rankings based on the selected scope. - Updated the leaderboard table to support dynamic column definitions for user and provider data. - Improved error handling and user feedback for unauthorized access to provider rankings. This change aims to provide a more flexible and informative leaderboard experience for users and administrators.
🎉 代码审查完成📊 PR 概览变更统计:
PR 状态: ✅ 可以合并(基础分支: main ← dev) ✨ 主要改进1. 供应商管理页面重构 ⭐️⭐️⭐️⭐️⭐️新增组件 provider-rich-list-item.tsx (490 行):
2. 供应商表单增强 ⭐️⭐️⭐️⭐️⭐️功能改进 provider-form.tsx (1,100 行):
3. 错误处理机制优化 ⭐️⭐️⭐️⭐️⭐️新增错误分类 proxy/errors.ts:
关键改进:
4. 数据库 Schema 扩展 ⭐️⭐️⭐️⭐️新增字段:
5. 排行榜功能增强 ⭐️⭐️⭐️⭐️新增 Scope 过滤:
🔍 代码审查发现
|
对于非 admin token 用户,在首页隐藏概览面板,包括: - 当前并发、今日请求、今日消耗、平均响应等统计指标 - 活跃 Session 列表 - 点击查看详情按钮 admin 用户保持原有功能不变。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
修复活跃 Session 组件高度对齐问题,消除底部留白: - 使用 flex 布局让容器填充整个可用高度 - 标题区域设置为 flex-shrink-0 防止被压缩 - 内容区域使用 flex-1 占据所有剩余空间 - 移除固定高度限制,改为自适应父容器高度 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
| @@ -0,0 +1,2 @@ | |||
| ALTER TABLE "providers" ADD COLUMN "website_url" text;--> statement-breakpoint | |||
| ALTER TABLE "providers" ADD COLUMN "favicon_url" text; No newline at end of file | |||
There was a problem hiding this comment.
✅ 数据库迁移设计合理
迁移文件正确使用了 nullable 字段,向后兼容现有数据。建议在生产环境执行前:
- 确认数据库备份已完成
- 在测试环境先验证迁移脚本
- 监控迁移执行时间(尽管这是轻量级迁移)
可选优化: 如果 website_url 和 favicon_url 将来需要搜索功能,可以考虑添加 GIN 索引:
CREATE INDEX idx_providers_website_url ON providers USING GIN (website_url gin_trgm_ops);(需要启用 pg_trgm 扩展)
| <div className="flex items-center gap-2 flex-wrap"> | ||
| {/* Favicon */} | ||
| {provider.faviconUrl && ( | ||
| <img |
There was a problem hiding this comment.
🔒 Favicon 图片加载的安全建议
当前实现使用 <img> 标签直接加载外部 favicon,存在潜在的安全风险:
- XSS 风险: 虽然图片本身不会直接执行 JavaScript,但恶意 SVG 图片可能包含嵌入式脚本
- 隐私泄露: 浏览器加载图片时会发送 Referer 头,可能泄露用户正在访问的页面信息
建议改进:
{provider.faviconUrl && (
<img
src={provider.faviconUrl}
className="h-4 w-4 flex-shrink-0"
referrerPolicy="no-referrer" // 防止隐私泄露
crossOrigin="anonymous" // CORS 安全
loading="lazy" // 性能优化
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/>
)}可选: 如果安全要求更高,可以考虑:
- 使用 Content Security Policy (CSP) 限制图片来源
- 在服务端代理 favicon 请求(避免直接加载第三方资源)
| }; | ||
|
|
||
| // 处理查看密钥 | ||
| const handleShowKey = async () => { |
There was a problem hiding this comment.
⚡ 并发请求优化建议
当前实现在 Dialog 打开时立即发起 API 请求,但如果用户快速点击多个供应商的密钥按钮,可能导致:
- 多个并发请求
- 竞态条件(后发请求先返回)
建议改进:
const [keyLoading, setKeyLoading] = useState(false);
const handleShowKey = async () => {
if (keyLoading) return; // 防止重复请求
setShowKeyDialog(true);
setKeyLoading(true);
try {
const result = await getUnmaskedProviderKey(provider.id);
if (result.ok) {
setUnmaskedKey(result.data.key);
} else {
toast.error("获取密钥失败", {
description: result.error || "未知错误",
});
setShowKeyDialog(false);
}
} finally {
setKeyLoading(false);
}
};额外优化: 可以考虑使用 SWR 或 React Query 缓存密钥数据(设置较短的过期时间,如 30 秒),避免重复请求
| return { ok: false, error: "供应商不存在" }; | ||
| } | ||
|
|
||
| // 记录查看行为(不记录密钥内容) |
There was a problem hiding this comment.
✅ API Key 安全机制设计优秀
代码在安全性方面考虑非常周全:
- ✅ 权限验证: 严格检查 admin 角色
- ✅ 审计日志: 记录管理员查看密钥的行为(不记录密钥内容)
- ✅ Server Action: 使用
"use server"确保逻辑在服务端执行 - ✅ 错误处理: 完整的错误处理和日志记录
建议增强:
// 可以考虑增加频率限制,防止密钥被过度访问
logger.info("Admin viewed provider key", {
userId: session.user.id,
providerId: id,
providerName: provider.name,
userEmail: session.user.email, // 增加邮箱追溯
ip: headers().get("x-forwarded-for") || headers().get("x-real-ip"), // IP 追踪
timestamp: new Date().toISOString(),
});可选: 可以在数据库中添加 provider_key_access_logs 表,永久记录所有密钥访问行为,便于安全审计
| @@ -557,6 +507,138 @@ function extractUsageMetrics(value: unknown): UsageMetrics | null { | |||
| return hasAny ? result : null; | |||
There was a problem hiding this comment.
🎯 Usage Metrics 解析重构 - 架构改进优秀
这个重构显著提升了代码质量:
优点:
- ✅ 单一职责: 将复杂的解析逻辑提取到
parseUsageFromResponseText()函数 - ✅ 可读性:
applyUsageValue()闭包设计简洁,避免重复代码 - ✅ 容错性: 完整的 try-catch 保护,SSE fallback 机制
- ✅ 扩展性: 易于添加新的 usage 格式支持
建议:
// 可以考虑添加单元测试,覆盖各种边界情况
describe('parseUsageFromResponseText', () => {
it('should parse Claude format', () => { ... });
it('should parse Codex format', () => { ... });
it('should parse OpenAI format', () => { ... });
it('should handle SSE events', () => { ... });
it('should return null for invalid input', () => { ... });
});性能优化: 如果 responseText 非常大(>1MB),可以考虑添加早期返回:
if (responseText.length > 1_000_000 && !responseText.includes('usage')) {
return { usageRecord: null, usageMetrics: null };
}
🎉 代码审查总结这是一个高质量的 PR,包含了重要的 UI/UX 重构和关键 bug 修复。代码整体设计合理,实现专业。 ✅ 主要亮点
💡 改进建议我已经在代码中添加了 4 条行内评论,包括:
这些建议都是可选的增强,不影响当前代码的功能性和稳定性。 📊 代码统计
✅ 合并建议推荐合并。此 PR 已通过代码质量检查,建议在以下条件下合并:
感谢你的出色工作! 🚀 |
No description provided.