Просмотр исходного кода

feat(logs): make cost column toggleable with improved type safety (#715)

close #713
泠音 1 неделя назад
Родитель
Сommit
4735d62cef

+ 1 - 0
.gitignore

@@ -91,3 +91,4 @@ docs-site/node_modules/
 tmp/
 .trae/
 .sisyphus
+.ace-tool/

+ 25 - 0
CHANGELOG.md

@@ -4,6 +4,31 @@
 
 ---
 
+## v0.5.3 (2026-02-03)
+
+### 新增
+
+- 扩展只读密钥访问权限,支持更多 API 端点访问 (#704) [@AptS:1547](https://github.com/AptS1547)
+- 支持 Zeabur 一键部署 (#679) [@h7ml](https://github.com/h7ml)
+- Anthropic 供应商支持参数覆写功能,可自定义 API 请求参数 (#689)
+
+### 优化
+
+- 重构代理架构,移除格式转换器并强制同格式路由,提升性能和稳定性 (#709)
+- 优化 Thinking Budget 整流器,改进思考模式下的令牌预算管理
+
+### 修复
+
+- 修复 Gemini 供应商 buildProxyUrl 重复拼接版本前缀的问题 (#693) [@sunxyw](https://github.com/sunxyw)
+- 修复 Gemini SSE 响应中 usageMetadata 提取逻辑,采用 last-wins 策略 (#691) [@sususu98](https://github.com/sususu98)
+
+### 其他
+
+- 新增 Thinking Budget 整流器单元测试覆盖
+- 更新 i18n 翻译和系统配置
+
+---
+
 ## [v0.5.2](https://github.com/ding113/claude-code-hub/releases/tag/v0.5.2) - 2026-01-29
 
 ### 新增

+ 1 - 0
src/app/[locale]/dashboard/logs/_components/column-visibility-dropdown.tsx

@@ -33,6 +33,7 @@ const COLUMN_LABEL_KEYS: Record<LogsTableColumn, string> = {
   sessionId: "logs.columns.sessionId",
   provider: "logs.columns.provider",
   tokens: "logs.columns.tokens",
+  cost: "logs.columns.cost",
   cache: "logs.columns.cache",
   performance: "logs.columns.performance",
 };

+ 1 - 1
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx

@@ -361,7 +361,7 @@ export function UsageLogsTable({
                         </TooltipProvider>
                       ) : isNonBilling ? (
                         "-"
-                      ) : log.costUsd ? (
+                      ) : log.costUsd != null ? (
                         <TooltipProvider>
                           <Tooltip delayDuration={250}>
                             <TooltipTrigger asChild>

+ 53 - 53
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx

@@ -11,6 +11,7 @@ import { Button } from "@/components/ui/button";
 import { RelativeTime } from "@/components/ui/relative-time";
 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
 import { useVirtualizer } from "@/hooks/use-virtualizer";
+import type { LogsTableColumn } from "@/lib/column-visibility";
 import { cn, formatTokenAmount } from "@/lib/utils";
 import { copyTextToClipboard } from "@/lib/utils/clipboard";
 import type { CurrencyCode } from "@/lib/utils/currency";
@@ -44,15 +45,6 @@ export interface VirtualizedLogsTableFilters {
   minRetryCount?: number;
 }
 
-type VirtualizedLogsTableColumn =
-  | "user"
-  | "key"
-  | "sessionId"
-  | "provider"
-  | "tokens"
-  | "cache"
-  | "performance";
-
 interface VirtualizedLogsTableProps {
   filters: VirtualizedLogsTableFilters;
   currencyCode?: CurrencyCode;
@@ -61,7 +53,7 @@ interface VirtualizedLogsTableProps {
   autoRefreshIntervalMs?: number;
   hideStatusBar?: boolean;
   hideScrollToTop?: boolean;
-  hiddenColumns?: VirtualizedLogsTableColumn[];
+  hiddenColumns?: LogsTableColumn[];
   bodyClassName?: string;
 }
 
@@ -87,6 +79,7 @@ export function VirtualizedLogsTable({
   const hideSessionIdColumn = hiddenColumns?.includes("sessionId") ?? false;
   const hideTokensColumn = hiddenColumns?.includes("tokens") ?? false;
   const hideCacheColumn = hiddenColumns?.includes("cache") ?? false;
+  const hideCostColumn = hiddenColumns?.includes("cost") ?? false;
   const hidePerformanceColumn = hiddenColumns?.includes("performance") ?? false;
 
   // Dialog state for model redirect click and chain item click
@@ -273,12 +266,14 @@ export function VirtualizedLogsTable({
                   {t("logs.columns.cache")}
                 </div>
               )}
-              <div
-                className="flex-[0.7] min-w-[60px] text-right px-1.5 truncate"
-                title={t("logs.columns.cost")}
-              >
-                {t("logs.columns.cost")}
-              </div>
+              {hideCostColumn ? null : (
+                <div
+                  className="flex-[0.7] min-w-[60px] text-right px-1.5 truncate"
+                  title={t("logs.columns.cost")}
+                >
+                  {t("logs.columns.cost")}
+                </div>
+              )}
               {hidePerformanceColumn ? null : (
                 <div
                   className="flex-[0.8] min-w-[80px] text-right px-1.5 truncate"
@@ -612,46 +607,51 @@ export function VirtualizedLogsTable({
                     )}
 
                     {/* Cost */}
-                    <div className="flex-[0.7] min-w-[60px] text-right font-mono text-xs px-1.5">
-                      {isNonBilling ? (
-                        "-"
-                      ) : log.costUsd ? (
-                        <TooltipProvider>
-                          <Tooltip delayDuration={250}>
-                            <TooltipTrigger asChild>
-                              <span className="cursor-help inline-flex items-center gap-1">
-                                {formatCurrency(log.costUsd, currencyCode, 6)}
+                    {hideCostColumn ? null : (
+                      <div className="flex-[0.7] min-w-[60px] text-right font-mono text-xs px-1.5">
+                        {isNonBilling ? (
+                          "-"
+                        ) : log.costUsd != null ? (
+                          <TooltipProvider>
+                            <Tooltip delayDuration={250}>
+                              <TooltipTrigger asChild>
+                                <span className="cursor-help inline-flex items-center gap-1">
+                                  {formatCurrency(log.costUsd, currencyCode, 6)}
+                                  {log.context1mApplied && (
+                                    <Badge
+                                      variant="outline"
+                                      className="text-[10px] leading-tight px-1 bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950/30 dark:text-purple-300 dark:border-purple-800"
+                                    >
+                                      1M
+                                    </Badge>
+                                  )}
+                                </span>
+                              </TooltipTrigger>
+                              <TooltipContent
+                                align="end"
+                                className="text-xs space-y-1 max-w-[300px]"
+                              >
                                 {log.context1mApplied && (
-                                  <Badge
-                                    variant="outline"
-                                    className="text-[10px] leading-tight px-1 bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950/30 dark:text-purple-300 dark:border-purple-800"
-                                  >
-                                    1M
-                                  </Badge>
+                                  <div className="text-purple-600 dark:text-purple-400 font-medium">
+                                    {t("logs.billingDetails.context1m")}
+                                  </div>
                                 )}
-                              </span>
-                            </TooltipTrigger>
-                            <TooltipContent align="end" className="text-xs space-y-1 max-w-[300px]">
-                              {log.context1mApplied && (
-                                <div className="text-purple-600 dark:text-purple-400 font-medium">
-                                  {t("logs.billingDetails.context1m")}
+                                <div>
+                                  {t("logs.billingDetails.input")}:{" "}
+                                  {formatTokenAmount(log.inputTokens)} tokens
                                 </div>
-                              )}
-                              <div>
-                                {t("logs.billingDetails.input")}:{" "}
-                                {formatTokenAmount(log.inputTokens)} tokens
-                              </div>
-                              <div>
-                                {t("logs.billingDetails.output")}:{" "}
-                                {formatTokenAmount(log.outputTokens)} tokens
-                              </div>
-                            </TooltipContent>
-                          </Tooltip>
-                        </TooltipProvider>
-                      ) : (
-                        "-"
-                      )}
-                    </div>
+                                <div>
+                                  {t("logs.billingDetails.output")}:{" "}
+                                  {formatTokenAmount(log.outputTokens)} tokens
+                                </div>
+                              </TooltipContent>
+                            </Tooltip>
+                          </TooltipProvider>
+                        ) : (
+                          "-"
+                        )}
+                      </div>
+                    )}
 
                     {/* Performance */}
                     {hidePerformanceColumn ? null : (

+ 11 - 0
src/lib/column-visibility.test.ts

@@ -179,6 +179,16 @@ describe("column-visibility", () => {
       const result3 = toggleColumn(userId, tableId, "user");
       expect(result3).toEqual(["provider"]);
     });
+
+    test("toggles cost column visibility", () => {
+      const hiddenAfterToggle = toggleColumn(userId, tableId, "cost");
+      expect(hiddenAfterToggle).toContain("cost");
+      expect(getVisibleColumns(userId, tableId)).not.toContain("cost");
+
+      const visibleAfterToggleBack = toggleColumn(userId, tableId, "cost");
+      expect(visibleAfterToggleBack).not.toContain("cost");
+      expect(getVisibleColumns(userId, tableId)).toContain("cost");
+    });
   });
 
   describe("resetColumns", () => {
@@ -209,6 +219,7 @@ describe("column-visibility", () => {
       expect(DEFAULT_VISIBLE_COLUMNS).toContain("sessionId");
       expect(DEFAULT_VISIBLE_COLUMNS).toContain("provider");
       expect(DEFAULT_VISIBLE_COLUMNS).toContain("tokens");
+      expect(DEFAULT_VISIBLE_COLUMNS).toContain("cost");
       expect(DEFAULT_VISIBLE_COLUMNS).toContain("cache");
       expect(DEFAULT_VISIBLE_COLUMNS).toContain("performance");
     });

+ 4 - 2
src/lib/column-visibility.ts

@@ -17,7 +17,8 @@ export type LogsTableColumn =
   | "provider"
   | "tokens"
   | "cache"
-  | "performance";
+  | "performance"
+  | "cost";
 
 /**
  * Default visible columns (all visible by default)
@@ -30,12 +31,13 @@ export const DEFAULT_VISIBLE_COLUMNS: LogsTableColumn[] = [
   "tokens",
   "cache",
   "performance",
+  "cost",
 ];
 
 /**
  * Columns that cannot be hidden (always visible)
  */
-export const ALWAYS_VISIBLE_COLUMNS = ["time", "model", "cost", "status"] as const;
+export const ALWAYS_VISIBLE_COLUMNS = ["time", "model", "status"] as const;
 
 /**
  * Get the storage key for a specific user and table