Bläddra i källkod

feat(dashboard): enhance logs UI with modular filters and error details (#654)

* feat(dashboard): enhance logs UI with modular filters and error details

- Refactor error-details-dialog into modular tab-based components:
  - SummaryTab: key metrics overview with cost, tokens, duration
  - LogicTraceTab: provider decision chain visualization with steps
  - PerformanceTab: TTFB gauge, latency breakdown, output rate
  - MetadataTab: session, client, billing info with timeline

- Extract filters into dedicated components for better maintainability:
  - TimeFilters, IdentityFilters, RequestFilters, StatusFilters
  - QuickFiltersBar for common filter presets (today, errors, retries)
  - ActiveFiltersDisplay for showing/clearing active filter tags

- Add column visibility management with localStorage persistence
- Add message redaction utility for sensitive data masking
- Add scroll-area component for consistent scrollable containers
- Add session reuse indicator (Link2 icon) in provider chain display
- Update i18n messages for all 5 languages

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* feat(i18n): add session reuse details translations

Add additional i18n keys for session reuse UI including:
- Session info labels (session ID, request sequence, age)
- Session reuse selection descriptions
- Cache optimization hint text

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* feat(dashboard): enhance provider chain popover with session reuse UI

- Add session reuse indicator and details in provider chain popover
- Add i18n translations for ja, ru, zh-TW locales
- Minor table display improvements

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* chore: format code (feat-logs-ui-enhancement-d94d11d)

* refactor(dashboard): improve provider chain tooltip with detailed context

- Enhance tooltip for single requests with session reuse details
  (session age, priority, cost multiplier)
- Add selection funnel visualization for initial selection
  (total -> enabled -> healthy providers)
- Show candidate providers with probability when multiple at same priority
- Display cost multiplier and group tag badges in retry popover trigger
- Remove redundant provider summary tooltip from logs table

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* chore: format code (feat-logs-ui-enhancement-6b4f036)

* fix(dashboard): address bugbot review comments

- LogicTraceTab: include all filtered providers, not just rate_limited/circuit_open
- LogicTraceTab: add error handling for clipboard copy
- MetadataTab: fix costUsd === 0 being treated as no data
- PerformanceTab: include outputTokens in hasData check
- error-details-dialog: fix race condition in session messages check using requestId ref
- provider-chain-popover: normalize probability display (0-1 to percentage)
- usage-logs-view: add aria-label for icon-only buttons (accessibility)
- active-sessions-cards: use Link component instead of router.push to preserve locale

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* chore: format code (feat-logs-ui-enhancement-e222e7a)

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Ding 2 veckor sedan
förälder
incheckning
9bd1cc593e
50 ändrade filer med 7257 tillägg och 2439 borttagningar
  1. 103 8
      messages/en/dashboard.json
  2. 41 0
      messages/en/provider-chain.json
  3. 104 8
      messages/ja/dashboard.json
  4. 41 0
      messages/ja/provider-chain.json
  5. 104 8
      messages/ru/dashboard.json
  6. 41 0
      messages/ru/provider-chain.json
  7. 103 8
      messages/zh-CN/dashboard.json
  8. 41 0
      messages/zh-CN/provider-chain.json
  9. 103 8
      messages/zh-TW/dashboard.json
  10. 41 0
      messages/zh-TW/provider-chain.json
  11. 2 0
      package.json
  12. 41 11
      src/app/[locale]/dashboard/logs/_components/active-sessions-skeleton.tsx
  13. 123 0
      src/app/[locale]/dashboard/logs/_components/column-visibility-dropdown.tsx
  14. 232 167
      src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  15. 0 816
      src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx
  16. 101 0
      src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LatencyBreakdownBar.tsx
  17. 685 0
      src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx
  18. 322 0
      src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/MetadataTab.tsx
  19. 266 0
      src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/PerformanceTab.tsx
  20. 182 0
      src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/StepCard.tsx
  21. 439 0
      src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx
  22. 5 0
      src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/index.ts
  23. 291 0
      src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx
  24. 146 0
      src/app/[locale]/dashboard/logs/_components/error-details-dialog/types.ts
  25. 194 0
      src/app/[locale]/dashboard/logs/_components/filters/active-filters-display.tsx
  26. 101 0
      src/app/[locale]/dashboard/logs/_components/filters/filter-section.tsx
  27. 287 0
      src/app/[locale]/dashboard/logs/_components/filters/identity-filters.tsx
  28. 14 0
      src/app/[locale]/dashboard/logs/_components/filters/index.ts
  29. 72 0
      src/app/[locale]/dashboard/logs/_components/filters/quick-filters-bar.tsx
  30. 362 0
      src/app/[locale]/dashboard/logs/_components/filters/request-filters.tsx
  31. 107 0
      src/app/[locale]/dashboard/logs/_components/filters/status-filters.tsx
  32. 152 0
      src/app/[locale]/dashboard/logs/_components/filters/time-filters.tsx
  33. 55 0
      src/app/[locale]/dashboard/logs/_components/filters/types.ts
  34. 325 46
      src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
  35. 215 797
      src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx
  36. 2 2
      src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx
  37. 68 69
      src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
  38. 70 22
      src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
  39. 472 433
      src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  40. 10 15
      src/app/[locale]/dashboard/logs/page.tsx
  41. 5 2
      src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-details-tabs.tsx
  42. 2 0
      src/app/providers.tsx
  43. 207 0
      src/components/customs/active-sessions-cards.tsx
  44. 45 0
      src/components/ui/scroll-area.tsx
  45. 7 10
      src/components/ui/tag-input.tsx
  46. 216 0
      src/lib/column-visibility.test.ts
  47. 144 0
      src/lib/column-visibility.ts
  48. 297 0
      src/lib/utils/message-redaction.test.ts
  49. 174 0
      src/lib/utils/message-redaction.ts
  50. 97 9
      tests/unit/error-details-dialog-warmup-ui.test.tsx

+ 103 - 8
messages/en/dashboard.json

@@ -21,7 +21,8 @@
     "usersQuotas": "User Quota Statistics",
     "keysQuotas": "Key Quota Statistics",
     "providersQuotas": "Provider Quota Statistics",
-    "filterCriteria": "Filter Criteria"
+    "filterCriteria": "Filter Criteria",
+    "filterCriteriaDescription": "Narrow down logs by time, user, provider, and more"
   },
   "description": {
     "viewApiCallLogs": "View API call logs and usage statistics",
@@ -93,7 +94,28 @@
       "export": "Export",
       "exporting": "Exporting...",
       "exportSuccess": "Export completed successfully",
-      "exportError": "Export failed"
+      "exportError": "Export failed",
+      "quickFilters": {
+        "today": "Today",
+        "thisWeek": "This Week",
+        "errorsOnly": "Errors Only",
+        "showRetries": "With Retries"
+      },
+      "activeFilters": {
+        "title": "Active Filters",
+        "remove": "Remove filter",
+        "clearAll": "Clear all"
+      },
+      "groups": {
+        "time": "Time Range",
+        "timeDesc": "Filter by date and time",
+        "identity": "Identity",
+        "identityDesc": "Filter by user and key",
+        "request": "Request",
+        "requestDesc": "Filter by provider, model, endpoint",
+        "status": "Status",
+        "statusDesc": "Filter by status code and retry"
+      }
     },
     "columns": {
       "time": "Time",
@@ -116,9 +138,7 @@
     },
     "stats": {
       "title": "Statistics Summary",
-      "description": "Click to view aggregated statistics for the current filters",
-      "expand": "Expand",
-      "collapse": "Collapse",
+      "description": "Aggregated statistics for the current filters",
       "totalRequests": "Total Requests",
       "totalAmount": "Total Consumption",
       "totalTokens": "Total Tokens",
@@ -144,7 +164,8 @@
       "noMoreData": "All records loaded",
       "scrollToTop": "Back to top",
       "hideProviderColumn": "Hide Provider Column",
-      "showProviderColumn": "Show Provider Column"
+      "showProviderColumn": "Show Provider Column",
+      "columnVisibility": "Column Visibility"
     },
     "actions": {
       "refresh": "Refresh",
@@ -161,12 +182,27 @@
     },
     "details": {
       "title": "Request Details",
-      "statusTitle": "Request Details - Status Code {status}",
+      "statusTitle": "Status: {status}",
       "inProgress": "In Progress",
       "unknown": "Unknown",
       "success": "Request completed successfully",
       "error": "Request failed, here are the detailed error messages and provider decision chain",
       "processing": "Request is in progress, not yet completed",
+      "tabs": {
+        "summary": "Summary",
+        "logicTrace": "Logic Trace",
+        "performance": "Performance",
+        "metadata": "Metadata"
+      },
+      "summary": {
+        "keyMetrics": "Key Metrics",
+        "totalCost": "Total Cost",
+        "totalTokens": "Total Tokens",
+        "duration": "Duration",
+        "outputRate": "Output Rate",
+        "viewFullError": "View full error",
+        "viewSession": "View Session"
+      },
       "specialSettings": {
         "title": "Special settings"
       },
@@ -224,13 +260,72 @@
         "circuitOpen": "Circuit Breaker Open"
       },
       "billingDetails": {
-        "title": "Billing Details"
+        "title": "Billing Details",
+        "input": "Input",
+        "output": "Output",
+        "cacheWrite5m": "Cache Write (5m)",
+        "cacheWrite1h": "Cache Write (1h)",
+        "cacheRead": "Cache Read",
+        "cacheTtl": "Cache TTL",
+        "multiplier": "Provider Multiplier",
+        "totalCost": "Total Cost",
+        "context1m": "1M Context",
+        "context1mPricing": "Input 2x >200k, Output 1.5x >200k"
       },
       "performance": {
         "title": "Performance",
         "ttfb": "TTFB",
         "duration": "Total Duration",
         "outputRate": "Output Rate"
+      },
+      "performanceTab": {
+        "noPerformanceData": "No performance data available",
+        "ttfbGauge": "Time to First Byte",
+        "outputRateGauge": "Output Rate",
+        "latencyBreakdown": "Latency Breakdown",
+        "generationTime": "Generation Time",
+        "assessment": {
+          "excellent": "Excellent",
+          "good": "Good",
+          "warning": "Warning",
+          "poor": "Poor"
+        },
+        "thresholds": {
+          "ttfbGood": "TTFB < 1s",
+          "ttfbWarning": "TTFB 1-2s",
+          "ttfbPoor": "TTFB > 3s"
+        }
+      },
+      "metadata": {
+        "noMetadata": "No metadata available",
+        "sessionInfo": "Session Info",
+        "clientInfo": "Client Info",
+        "billingInfo": "Billing Info",
+        "technicalTimeline": "Technical Timeline",
+        "copyTimeline": "Copy Timeline"
+      },
+      "logicTrace": {
+        "title": "Decision Chain",
+        "noDecisionData": "No decision data available",
+        "providersCount": "{count} providers",
+        "healthyCount": "{count} healthy",
+        "initialSelection": "Initial Selection",
+        "healthCheck": "Health Check",
+        "prioritySelection": "Priority Selection",
+        "attemptProvider": "Attempt: {provider}",
+        "retryAttempt": "Retry #{number}",
+        "sessionReuse": "Session Reuse",
+        "sessionReuseDesc": "Reusing provider from session cache",
+        "sessionReuseTitle": "Session Binding",
+        "sessionReuseSelection": "Session Reuse Selection",
+        "sessionReuseSelectionDesc": "Provider selected from session cache",
+        "sessionInfo": "Session Information",
+        "sessionIdLabel": "Session ID",
+        "requestSequence": "Request Sequence",
+        "sessionAge": "Session Age",
+        "reusedProvider": "Reused Provider",
+        "executeRequest": "Execute Request",
+        "cacheOptimizationHint": "Session reuse optimizes performance by maintaining provider affinity within the same conversation, reducing selection overhead and improving cache hit rates."
       }
     },
     "providerChain": {

+ 41 - 0
messages/en/provider-chain.json

@@ -39,6 +39,47 @@
     "http2Fallback": "HTTP/2 Fallback",
     "clientError": "Client Error"
   },
+  "reasons": {
+    "request_success": "Success",
+    "retry_success": "Retry Success",
+    "retry_failed": "Retry Failed",
+    "system_error": "System Error",
+    "client_error_non_retryable": "Client Error",
+    "concurrent_limit_failed": "Concurrent Limit",
+    "http2_fallback": "HTTP/2 Fallback",
+    "session_reuse": "Session Reuse",
+    "initial_selection": "Initial Selection"
+  },
+  "filterReasons": {
+    "rate_limited": "Rate Limited",
+    "circuit_open": "Circuit Open",
+    "disabled": "Disabled",
+    "model_not_supported": "Model Not Supported",
+    "group_mismatch": "Group Mismatch",
+    "health_check_failed": "Health Check Failed"
+  },
+  "details": {
+    "selectionMethod": "Selection",
+    "attemptNumber": "Attempt",
+    "endpoint": "Endpoint",
+    "config": "Configuration",
+    "priority": "Priority",
+    "weight": "Weight",
+    "costMultiplier": "Cost",
+    "groupTag": "Group",
+    "circuitBreaker": "Circuit Breaker",
+    "failures": "failures",
+    "modelRedirect": "Model Redirect",
+    "error": "Error",
+    "errorDetails": "Error Details",
+    "decisionContext": "Decision Context",
+    "beforeHealthCheck": "Before Health Check",
+    "afterHealthCheck": "After Health Check",
+    "filteredProviders": "Filtered Providers",
+    "priorityLevels": "Priority Levels",
+    "candidates": "Candidates"
+  },
+  "technicalTimeline": "Technical Timeline",
   "timeline": {
     "sessionReuse": "Session Reuse",
     "sessionReuseSelection": "Session Reuse - Provider Selection",

+ 104 - 8
messages/ja/dashboard.json

@@ -21,7 +21,8 @@
     "keysQuotas": "キー クォータ統計",
     "providersQuotas": "プロバイダー クォータ統計",
     "usageLogsDescription": "API 呼び出しログと使用統計を表示します",
-    "filterCriteria": "フィルター条件"
+    "filterCriteria": "フィルター条件",
+    "filterCriteriaDescription": "時間、ユーザー、プロバイダーなどでログを絞り込む"
   },
   "description": {
     "viewApiCallLogs": "API 呼び出しログと使用統計を表示します",
@@ -93,7 +94,28 @@
       "export": "エクスポート",
       "exporting": "エクスポート中...",
       "exportSuccess": "エクスポートが完了しました",
-      "exportError": "エクスポートに失敗しました"
+      "exportError": "エクスポートに失敗しました",
+      "quickFilters": {
+        "today": "今日",
+        "thisWeek": "今週",
+        "errorsOnly": "エラーのみ",
+        "showRetries": "リトライあり"
+      },
+      "activeFilters": {
+        "title": "有効なフィルター",
+        "remove": "フィルターを削除",
+        "clearAll": "すべてクリア"
+      },
+      "groups": {
+        "time": "時間範囲",
+        "timeDesc": "日付と時間でフィルター",
+        "identity": "ID情報",
+        "identityDesc": "ユーザーとキーでフィルター",
+        "request": "リクエスト",
+        "requestDesc": "プロバイダー、モデル、エンドポイントでフィルター",
+        "status": "ステータス",
+        "statusDesc": "ステータスコードとリトライでフィルター"
+      }
     },
     "columns": {
       "time": "時間",
@@ -116,9 +138,7 @@
     },
     "stats": {
       "title": "統計サマリー",
-      "description": "クリックして現在のフィルター条件の集計統計を表示",
-      "expand": "展開",
-      "collapse": "折りたたむ",
+      "description": "現在のフィルター条件の集計統計",
       "totalAmount": "総消費金額",
       "totalTokens": "総トークン数",
       "cacheTokens": "キャッシュトークン",
@@ -144,7 +164,8 @@
       "noMoreData": "すべてのレコードを読み込みました",
       "scrollToTop": "トップへ戻る",
       "hideProviderColumn": "プロバイダー列を非表示",
-      "showProviderColumn": "プロバイダー列を表示"
+      "showProviderColumn": "プロバイダー列を表示",
+      "columnVisibility": "列の表示/非表示"
     },
     "actions": {
       "refresh": "更新",
@@ -161,12 +182,27 @@
     },
     "details": {
       "title": "リクエスト詳細",
-      "statusTitle": "リクエスト詳細 - ステータスコード {status}",
+      "statusTitle": "ステータス: {status}",
       "inProgress": "処理中",
       "unknown": "不明",
       "success": "リクエストが正常に完了しました",
       "error": "リクエスト失敗、詳細なエラー情報とプロバイダー決定チェーンは以下の通りです",
       "processing": "リクエストは処理中であり、まだ完了していません",
+      "tabs": {
+        "summary": "概要",
+        "logicTrace": "決定チェーン",
+        "performance": "パフォーマンス",
+        "metadata": "メタデータ"
+      },
+      "summary": {
+        "keyMetrics": "主要指標",
+        "totalCost": "総コスト",
+        "totalTokens": "総トークン数",
+        "duration": "所要時間",
+        "outputRate": "出力速度",
+        "viewFullError": "完全なエラーを表示",
+        "viewSession": "セッションを表示"
+      },
       "specialSettings": {
         "title": "特殊設定"
       },
@@ -207,6 +243,7 @@
         "billingRedirected": "課金: 実際"
       },
       "errorMessage": "エラーメッセージ",
+      "filteredProviders": "フィルタされたプロバイダー",
       "providerChain": {
         "title": "プロバイダー決定チェーンタイムライン",
         "totalDuration": "合計所要時間: {duration}ms"
@@ -223,13 +260,72 @@
         "circuitOpen": "サーキットブレーカー開放"
       },
       "billingDetails": {
-        "title": "課金詳細"
+        "title": "課金詳細",
+        "input": "入力",
+        "output": "出力",
+        "cacheWrite5m": "キャッシュ書き込み (5m)",
+        "cacheWrite1h": "キャッシュ書き込み (1h)",
+        "cacheRead": "キャッシュ読み取り",
+        "cacheTtl": "キャッシュ TTL",
+        "multiplier": "プロバイダー倍率",
+        "totalCost": "総コスト",
+        "context1m": "1M コンテキスト",
+        "context1mPricing": "入力 2x >200k, 出力 1.5x >200k"
       },
       "performance": {
         "title": "パフォーマンス",
         "ttfb": "TTFB",
         "duration": "総所要時間",
         "outputRate": "出力速度"
+      },
+      "performanceTab": {
+        "noPerformanceData": "パフォーマンスデータがありません",
+        "ttfbGauge": "初バイト到達時間",
+        "outputRateGauge": "出力速度",
+        "latencyBreakdown": "レイテンシ内訳",
+        "generationTime": "生成時間",
+        "assessment": {
+          "excellent": "優秀",
+          "good": "良好",
+          "warning": "警告",
+          "poor": "不良"
+        },
+        "thresholds": {
+          "ttfbGood": "TTFB < 1s",
+          "ttfbWarning": "TTFB 1-2s",
+          "ttfbPoor": "TTFB > 3s"
+        }
+      },
+      "metadata": {
+        "noMetadata": "メタデータがありません",
+        "sessionInfo": "セッション情報",
+        "clientInfo": "クライアント情報",
+        "billingInfo": "課金情報",
+        "technicalTimeline": "技術タイムライン",
+        "copyTimeline": "タイムラインをコピー"
+      },
+      "logicTrace": {
+        "title": "決定チェーン",
+        "noDecisionData": "決定データがありません",
+        "providersCount": "{count} プロバイダー",
+        "healthyCount": "{count} 健全",
+        "initialSelection": "初期選択",
+        "healthCheck": "ヘルスチェック",
+        "prioritySelection": "優先度選択",
+        "attemptProvider": "試行: {provider}",
+        "retryAttempt": "再試行 #{number}",
+        "sessionReuse": "セッション再利用",
+        "sessionReuseDesc": "セッションキャッシュからプロバイダーを再利用",
+        "sessionReuseTitle": "セッションバインディング",
+        "sessionReuseSelection": "セッション再利用選択",
+        "sessionReuseSelectionDesc": "セッションキャッシュからプロバイダーを選択",
+        "sessionInfo": "セッション情報",
+        "sessionIdLabel": "セッション ID",
+        "requestSequence": "リクエスト番号",
+        "sessionAge": "セッション経過時間",
+        "reusedProvider": "再利用プロバイダー",
+        "executeRequest": "リクエスト実行",
+        "cacheOptimizationHint": "セッション再利用は、同じ会話内でプロバイダーの親和性を維持することでパフォーマンスを最適化し、選択オーバーヘッドを削減してキャッシュヒット率を向上させます。"
       }
     },
     "providerChain": {

+ 41 - 0
messages/ja/provider-chain.json

@@ -39,6 +39,47 @@
     "http2Fallback": "HTTP/2 フォールバック",
     "clientError": "クライアントエラー"
   },
+  "reasons": {
+    "request_success": "成功",
+    "retry_success": "リトライ成功",
+    "retry_failed": "リトライ失敗",
+    "system_error": "システムエラー",
+    "client_error_non_retryable": "クライアントエラー",
+    "concurrent_limit_failed": "同時実行制限",
+    "http2_fallback": "HTTP/2 フォールバック",
+    "session_reuse": "セッション再利用",
+    "initial_selection": "初期選択"
+  },
+  "filterReasons": {
+    "rate_limited": "レート制限",
+    "circuit_open": "サーキットオープン",
+    "disabled": "無効",
+    "model_not_supported": "モデル非対応",
+    "group_mismatch": "グループ不一致",
+    "health_check_failed": "ヘルスチェック失敗"
+  },
+  "details": {
+    "selectionMethod": "選択方法",
+    "attemptNumber": "試行回数",
+    "endpoint": "エンドポイント",
+    "config": "設定",
+    "priority": "優先度",
+    "weight": "重み",
+    "costMultiplier": "コスト倍率",
+    "groupTag": "グループタグ",
+    "circuitBreaker": "サーキットブレーカー",
+    "failures": "回失敗",
+    "modelRedirect": "モデルリダイレクト",
+    "error": "エラー",
+    "errorDetails": "エラー詳細",
+    "decisionContext": "決定コンテキスト",
+    "beforeHealthCheck": "ヘルスチェック前",
+    "afterHealthCheck": "ヘルスチェック後",
+    "filteredProviders": "フィルタされたプロバイダー",
+    "priorityLevels": "優先度レベル",
+    "candidates": "候補プロバイダー"
+  },
+  "technicalTimeline": "技術タイムライン",
   "timeline": {
     "sessionReuse": "セッション再利用",
     "sessionReuseSelection": "セッション再利用 - プロバイダー選択",

+ 104 - 8
messages/ru/dashboard.json

@@ -21,7 +21,8 @@
     "keysQuotas": "Статистика квот ключей",
     "providersQuotas": "Статистика квот поставщиков",
     "usageLogsDescription": "Просмотр журналов вызовов API и статистики использования",
-    "filterCriteria": "Критерии фильтрации"
+    "filterCriteria": "Критерии фильтрации",
+    "filterCriteriaDescription": "Фильтруйте журналы по времени, пользователю, поставщику и другим параметрам"
   },
   "description": {
     "viewApiCallLogs": "Просмотр журналов вызовов API и статистики использования",
@@ -93,7 +94,28 @@
       "export": "Экспорт",
       "exporting": "Экспорт...",
       "exportSuccess": "Экспорт завершен",
-      "exportError": "Ошибка экспорта"
+      "exportError": "Ошибка экспорта",
+      "quickFilters": {
+        "today": "Сегодня",
+        "thisWeek": "Эта неделя",
+        "errorsOnly": "Только ошибки",
+        "showRetries": "С ретраями"
+      },
+      "activeFilters": {
+        "title": "Активные фильтры",
+        "remove": "Удалить фильтр",
+        "clearAll": "Очистить все"
+      },
+      "groups": {
+        "time": "Период времени",
+        "timeDesc": "Фильтр по дате и времени",
+        "identity": "Идентификация",
+        "identityDesc": "Фильтр по пользователю и ключу",
+        "request": "Запрос",
+        "requestDesc": "Фильтр по поставщику, модели, эндпоинту",
+        "status": "Статус",
+        "statusDesc": "Фильтр по коду состояния и ретраям"
+      }
     },
     "columns": {
       "time": "Время",
@@ -116,9 +138,7 @@
     },
     "stats": {
       "title": "Сводка статистики",
-      "description": "Нажмите для просмотра агрегированной статистики по текущим фильтрам",
-      "expand": "Развернуть",
-      "collapse": "Свернуть",
+      "description": "Агрегированная статистика по текущим фильтрам",
       "totalAmount": "Общая сумма расходов",
       "totalTokens": "Общее количество токенов",
       "cacheTokens": "Токены кэша",
@@ -144,7 +164,8 @@
       "noMoreData": "Все записи загружены",
       "scrollToTop": "Наверх",
       "hideProviderColumn": "Скрыть столбец провайдера",
-      "showProviderColumn": "Показать столбец провайдера"
+      "showProviderColumn": "Показать столбец провайдера",
+      "columnVisibility": "Видимость столбцов"
     },
     "actions": {
       "refresh": "Обновить",
@@ -161,12 +182,27 @@
     },
     "details": {
       "title": "Детали запроса",
-      "statusTitle": "Детали запроса - код состояния {status}",
+      "statusTitle": "Статус: {status}",
       "inProgress": "В процессе",
       "unknown": "Неизвестно",
       "success": "Запрос успешно выполнен",
       "error": "Запрос не выполнен, ниже подробная информация об ошибке и цепочке решений поставщика",
       "processing": "Запрос находится в процессе выполнения и еще не завершен",
+      "tabs": {
+        "summary": "Обзор",
+        "logicTrace": "Цепочка решений",
+        "performance": "Производительность",
+        "metadata": "Метаданные"
+      },
+      "summary": {
+        "keyMetrics": "Ключевые показатели",
+        "totalCost": "Общая стоимость",
+        "totalTokens": "Всего токенов",
+        "duration": "Длительность",
+        "outputRate": "Скорость вывода",
+        "viewFullError": "Показать полную ошибку",
+        "viewSession": "Просмотр сеанса"
+      },
       "specialSettings": {
         "title": "Особые настройки"
       },
@@ -207,6 +243,7 @@
         "billingRedirected": "оплата: факт."
       },
       "errorMessage": "Сообщение об ошибке",
+      "filteredProviders": "Отфильтрованные поставщики",
       "providerChain": {
         "title": "Хронология цепочки решений поставщика",
         "totalDuration": "Общая продолжительность: {duration}мс"
@@ -223,13 +260,72 @@
         "circuitOpen": "Размыкатель цепи открыт"
       },
       "billingDetails": {
-        "title": "Детали биллинга"
+        "title": "Детали биллинга",
+        "input": "Входные",
+        "output": "Выходные",
+        "cacheWrite5m": "Запись кэша (5m)",
+        "cacheWrite1h": "Запись кэша (1h)",
+        "cacheRead": "Чтение кэша",
+        "cacheTtl": "TTL кэша",
+        "multiplier": "Множитель поставщика",
+        "totalCost": "Общая стоимость",
+        "context1m": "1M контекст",
+        "context1mPricing": "Вход 2x >200k, Выход 1.5x >200k"
       },
       "performance": {
         "title": "Производительность",
         "ttfb": "TTFB",
         "duration": "Общее время",
         "outputRate": "Скорость вывода"
+      },
+      "performanceTab": {
+        "noPerformanceData": "Нет данных о производительности",
+        "ttfbGauge": "Время до первого байта",
+        "outputRateGauge": "Скорость вывода",
+        "latencyBreakdown": "Разбивка задержки",
+        "generationTime": "Время генерации",
+        "assessment": {
+          "excellent": "Отлично",
+          "good": "Хорошо",
+          "warning": "Предупреждение",
+          "poor": "Плохо"
+        },
+        "thresholds": {
+          "ttfbGood": "TTFB < 1с",
+          "ttfbWarning": "TTFB 1-2с",
+          "ttfbPoor": "TTFB > 3с"
+        }
+      },
+      "metadata": {
+        "noMetadata": "Нет метаданных",
+        "sessionInfo": "Информация о сеансе",
+        "clientInfo": "Информация о клиенте",
+        "billingInfo": "Информация о биллинге",
+        "technicalTimeline": "Техническая хронология",
+        "copyTimeline": "Копировать хронологию"
+      },
+      "logicTrace": {
+        "title": "Цепочка решений",
+        "noDecisionData": "Нет данных о решениях",
+        "providersCount": "{count} поставщиков",
+        "healthyCount": "{count} исправных",
+        "initialSelection": "Начальный выбор",
+        "healthCheck": "Проверка работоспособности",
+        "prioritySelection": "Выбор по приоритету",
+        "attemptProvider": "Попытка: {provider}",
+        "retryAttempt": "Повтор #{number}",
+        "sessionReuse": "Повторное использование сессии",
+        "sessionReuseDesc": "Провайдер из кэша сессии",
+        "sessionReuseTitle": "Привязка сессии",
+        "sessionReuseSelection": "Выбор с повторным использованием сессии",
+        "sessionReuseSelectionDesc": "Провайдер выбран из кэша сессии",
+        "sessionInfo": "Информация о сессии",
+        "sessionIdLabel": "ID сессии",
+        "requestSequence": "Номер запроса",
+        "sessionAge": "Возраст сессии",
+        "reusedProvider": "Повторно используемый провайдер",
+        "executeRequest": "Выполнить запрос",
+        "cacheOptimizationHint": "Повторное использование сессии оптимизирует производительность, поддерживая привязку к провайдеру в рамках одного разговора, снижая накладные расходы на выбор и повышая частоту попаданий в кэш."
       }
     },
     "providerChain": {

+ 41 - 0
messages/ru/provider-chain.json

@@ -39,6 +39,47 @@
     "http2Fallback": "Откат HTTP/2",
     "clientError": "Ошибка клиента"
   },
+  "reasons": {
+    "request_success": "Успешно",
+    "retry_success": "Повтор успешен",
+    "retry_failed": "Повтор не удался",
+    "system_error": "Системная ошибка",
+    "client_error_non_retryable": "Ошибка клиента",
+    "concurrent_limit_failed": "Лимит параллельных запросов",
+    "http2_fallback": "Откат HTTP/2",
+    "session_reuse": "Повторное использование сессии",
+    "initial_selection": "Первоначальный выбор"
+  },
+  "filterReasons": {
+    "rate_limited": "Ограничение скорости",
+    "circuit_open": "Автомат открыт",
+    "disabled": "Отключен",
+    "model_not_supported": "Модель не поддерживается",
+    "group_mismatch": "Несоответствие группы",
+    "health_check_failed": "Проверка состояния не пройдена"
+  },
+  "details": {
+    "selectionMethod": "Метод выбора",
+    "attemptNumber": "Номер попытки",
+    "endpoint": "Конечная точка",
+    "config": "Конфигурация",
+    "priority": "Приоритет",
+    "weight": "Вес",
+    "costMultiplier": "Множитель стоимости",
+    "groupTag": "Тег группы",
+    "circuitBreaker": "Автомат защиты",
+    "failures": "ошибок",
+    "modelRedirect": "Перенаправление модели",
+    "error": "Ошибка",
+    "errorDetails": "Детали ошибки",
+    "decisionContext": "Контекст решения",
+    "beforeHealthCheck": "До проверки состояния",
+    "afterHealthCheck": "После проверки состояния",
+    "filteredProviders": "Отфильтрованные провайдеры",
+    "priorityLevels": "Уровни приоритета",
+    "candidates": "Кандидаты провайдеров"
+  },
+  "technicalTimeline": "Техническая временная шкала",
   "timeline": {
     "sessionReuse": "Повторное использование сессии",
     "sessionReuseSelection": "Повторное использование сессии - Выбор провайдера",

+ 103 - 8
messages/zh-CN/dashboard.json

@@ -21,7 +21,8 @@
     "usersQuotas": "用户限额统计",
     "keysQuotas": "密钥限额统计",
     "providersQuotas": "供应商限额统计",
-    "filterCriteria": "筛选条件"
+    "filterCriteria": "筛选条件",
+    "filterCriteriaDescription": "按时间、用户、供应商等条件缩小日志范围"
   },
   "description": {
     "viewApiCallLogs": "查看 API 调用日志和使用统计",
@@ -93,7 +94,28 @@
       "export": "导出",
       "exporting": "导出中...",
       "exportSuccess": "导出成功",
-      "exportError": "导出失败"
+      "exportError": "导出失败",
+      "quickFilters": {
+        "today": "今天",
+        "thisWeek": "本周",
+        "errorsOnly": "仅错误",
+        "showRetries": "有重试"
+      },
+      "activeFilters": {
+        "title": "已激活筛选",
+        "remove": "移除筛选",
+        "clearAll": "清除全部"
+      },
+      "groups": {
+        "time": "时间范围",
+        "timeDesc": "按日期和时间筛选",
+        "identity": "身份信息",
+        "identityDesc": "按用户和密钥筛选",
+        "request": "请求参数",
+        "requestDesc": "按供应商、模型、端点筛选",
+        "status": "状态信息",
+        "statusDesc": "按状态码和重试次数筛选"
+      }
     },
     "columns": {
       "time": "时间",
@@ -116,9 +138,7 @@
     },
     "stats": {
       "title": "统计汇总",
-      "description": "点击展开查看当前筛选条件下的聚合统计",
-      "expand": "展开",
-      "collapse": "收起",
+      "description": "当前筛选条件下的聚合统计",
       "totalRequests": "总请求数",
       "totalAmount": "总消耗金额",
       "totalTokens": "总 Token 数",
@@ -144,7 +164,8 @@
       "noMoreData": "已加载全部记录",
       "scrollToTop": "回到顶部",
       "hideProviderColumn": "隐藏供应商列",
-      "showProviderColumn": "显示供应商列"
+      "showProviderColumn": "显示供应商列",
+      "columnVisibility": "显示/隐藏列"
     },
     "actions": {
       "refresh": "刷新",
@@ -161,12 +182,27 @@
     },
     "details": {
       "title": "请求详情",
-      "statusTitle": "请求详情 - 状态码 {status}",
+      "statusTitle": "状态: {status}",
       "inProgress": "请求中",
       "unknown": "未知",
       "success": "请求成功完成",
       "error": "请求失败,以下是详细的错误信息和供应商决策链",
       "processing": "请求正在进行中,尚未完成",
+      "tabs": {
+        "summary": "概览",
+        "logicTrace": "决策链",
+        "performance": "性能",
+        "metadata": "元数据"
+      },
+      "summary": {
+        "keyMetrics": "关键指标",
+        "totalCost": "总费用",
+        "totalTokens": "总令牌数",
+        "duration": "耗时",
+        "outputRate": "输出速率",
+        "viewFullError": "查看完整错误",
+        "viewSession": "查看会话"
+      },
       "specialSettings": {
         "title": "特殊设置"
       },
@@ -224,13 +260,72 @@
         "circuitOpen": "熔断器打开"
       },
       "billingDetails": {
-        "title": "计费详情"
+        "title": "计费详情",
+        "input": "输入",
+        "output": "输出",
+        "cacheWrite5m": "缓存写入 (5m)",
+        "cacheWrite1h": "缓存写入 (1h)",
+        "cacheRead": "缓存读取",
+        "cacheTtl": "缓存 TTL",
+        "multiplier": "供应商倍率",
+        "totalCost": "总费用",
+        "context1m": "1M 上下文",
+        "context1mPricing": "输入 2x >200k, 输出 1.5x >200k"
       },
       "performance": {
         "title": "性能数据",
         "ttfb": "首字节时间(TTFB)",
         "duration": "总耗时",
         "outputRate": "输出速率"
+      },
+      "performanceTab": {
+        "noPerformanceData": "暂无性能数据",
+        "ttfbGauge": "首字节时间",
+        "outputRateGauge": "输出速率",
+        "latencyBreakdown": "延迟分解",
+        "generationTime": "生成时间",
+        "assessment": {
+          "excellent": "优秀",
+          "good": "良好",
+          "warning": "警告",
+          "poor": "较差"
+        },
+        "thresholds": {
+          "ttfbGood": "TTFB < 1s",
+          "ttfbWarning": "TTFB 1-2s",
+          "ttfbPoor": "TTFB > 3s"
+        }
+      },
+      "metadata": {
+        "noMetadata": "暂无元数据",
+        "sessionInfo": "会话信息",
+        "clientInfo": "客户端信息",
+        "billingInfo": "计费信息",
+        "technicalTimeline": "技术时间线",
+        "copyTimeline": "复制时间线"
+      },
+      "logicTrace": {
+        "title": "决策链",
+        "noDecisionData": "暂无决策数据",
+        "providersCount": "{count} 个供应商",
+        "healthyCount": "{count} 个健康",
+        "initialSelection": "初始选择",
+        "healthCheck": "健康检查",
+        "prioritySelection": "优先级选择",
+        "attemptProvider": "尝试: {provider}",
+        "retryAttempt": "重试 #{number}",
+        "sessionReuse": "会话复用",
+        "sessionReuseDesc": "从会话缓存复用供应商",
+        "sessionReuseTitle": "会话绑定",
+        "sessionReuseSelection": "会话复用选择",
+        "sessionReuseSelectionDesc": "从会话缓存中选择供应商",
+        "sessionInfo": "会话信息",
+        "sessionIdLabel": "会话 ID",
+        "requestSequence": "请求序号",
+        "sessionAge": "会话年龄",
+        "reusedProvider": "复用的供应商",
+        "executeRequest": "执行请求",
+        "cacheOptimizationHint": "会话复用通过在同一对话中保持供应商亲和性来优化性能,减少选择开销并提高缓存命中率。"
       }
     },
     "providerChain": {

+ 41 - 0
messages/zh-CN/provider-chain.json

@@ -39,6 +39,47 @@
     "http2Fallback": "HTTP/2 回退",
     "clientError": "客户端错误"
   },
+  "reasons": {
+    "request_success": "成功",
+    "retry_success": "重试成功",
+    "retry_failed": "重试失败",
+    "system_error": "系统错误",
+    "client_error_non_retryable": "客户端错误",
+    "concurrent_limit_failed": "并发限制",
+    "http2_fallback": "HTTP/2 回退",
+    "session_reuse": "会话复用",
+    "initial_selection": "首次选择"
+  },
+  "filterReasons": {
+    "rate_limited": "速率限制",
+    "circuit_open": "熔断器打开",
+    "disabled": "已禁用",
+    "model_not_supported": "不支持该模型",
+    "group_mismatch": "分组不匹配",
+    "health_check_failed": "健康检查失败"
+  },
+  "details": {
+    "selectionMethod": "选择方式",
+    "attemptNumber": "尝试次数",
+    "endpoint": "端点",
+    "config": "配置",
+    "priority": "优先级",
+    "weight": "权重",
+    "costMultiplier": "成本倍数",
+    "groupTag": "分组标签",
+    "circuitBreaker": "熔断器",
+    "failures": "次失败",
+    "modelRedirect": "模型重定向",
+    "error": "错误",
+    "errorDetails": "错误详情",
+    "decisionContext": "决策上下文",
+    "beforeHealthCheck": "健康检查前",
+    "afterHealthCheck": "健康检查后",
+    "filteredProviders": "被过滤供应商",
+    "priorityLevels": "优先级层级",
+    "candidates": "候选供应商"
+  },
+  "technicalTimeline": "技术时间线",
   "timeline": {
     "sessionReuse": "会话复用",
     "sessionReuseSelection": "会话复用选择供应商",

+ 103 - 8
messages/zh-TW/dashboard.json

@@ -21,7 +21,8 @@
     "keysQuotas": "密鑰額度統計",
     "providersQuotas": "供應商額度統計",
     "usageLogsDescription": "查看 API 調用日誌和使用統計",
-    "filterCriteria": "篩選條件"
+    "filterCriteria": "篩選條件",
+    "filterCriteriaDescription": "依時間、用戶、供應商等條件縮小日誌範圍"
   },
   "description": {
     "viewApiCallLogs": "查看 API 呼叫日誌和使用統計",
@@ -93,7 +94,28 @@
       "export": "匯出",
       "exporting": "匯出中...",
       "exportSuccess": "匯出成功",
-      "exportError": "匯出失敗"
+      "exportError": "匯出失敗",
+      "quickFilters": {
+        "today": "今天",
+        "thisWeek": "本週",
+        "errorsOnly": "僅錯誤",
+        "showRetries": "有重試"
+      },
+      "activeFilters": {
+        "title": "已啟用篩選",
+        "remove": "移除篩選",
+        "clearAll": "清除全部"
+      },
+      "groups": {
+        "time": "時間範圍",
+        "timeDesc": "依日期與時間篩選",
+        "identity": "身分資訊",
+        "identityDesc": "依使用者與金鑰篩選",
+        "request": "請求參數",
+        "requestDesc": "依供應商、模型、端點篩選",
+        "status": "狀態資訊",
+        "statusDesc": "依狀態碼與重試次數篩選"
+      }
     },
     "columns": {
       "time": "時間",
@@ -116,9 +138,7 @@
     },
     "stats": {
       "title": "統計匯總",
-      "description": "點擊展開查看當前篩選條件下的彙總統計",
-      "expand": "展開",
-      "collapse": "收合",
+      "description": "當前篩選條件下的彙總統計",
       "totalAmount": "總消耗金額",
       "totalTokens": "總 Token 數",
       "cacheTokens": "快取 Token",
@@ -144,7 +164,8 @@
       "noMoreData": "已載入全部記錄",
       "scrollToTop": "回到頂端",
       "hideProviderColumn": "隱藏供應商欄",
-      "showProviderColumn": "顯示供應商欄"
+      "showProviderColumn": "顯示供應商欄",
+      "columnVisibility": "顯示/隱藏欄位"
     },
     "actions": {
       "refresh": "重新整理",
@@ -161,12 +182,27 @@
     },
     "details": {
       "title": "請求詳情",
-      "statusTitle": "請求詳情 - 狀態碼 {status}",
+      "statusTitle": "狀態: {status}",
       "inProgress": "請求中",
       "unknown": "未知狀態",
       "success": "請求成功完成",
       "error": "請求失敗,以下是詳細的錯誤訊息和供應商決策鏈",
       "processing": "請求正在進行中,尚未完成",
+      "tabs": {
+        "summary": "概覽",
+        "logicTrace": "決策鏈",
+        "performance": "效能",
+        "metadata": "中繼資料"
+      },
+      "summary": {
+        "keyMetrics": "關鍵指標",
+        "totalCost": "總費用",
+        "totalTokens": "總令牌數",
+        "duration": "耗時",
+        "outputRate": "輸出速率",
+        "viewFullError": "查看完整錯誤",
+        "viewSession": "查看工作階段"
+      },
       "specialSettings": {
         "title": "特殊設定"
       },
@@ -224,13 +260,72 @@
         "circuitOpen": "斷路器開啟"
       },
       "billingDetails": {
-        "title": "計費詳情"
+        "title": "計費詳情",
+        "input": "輸入",
+        "output": "輸出",
+        "cacheWrite5m": "快取寫入(5m)",
+        "cacheWrite1h": "快取寫入(1h)",
+        "cacheRead": "快取讀取",
+        "cacheTtl": "快取 TTL",
+        "multiplier": "供應商倍率",
+        "totalCost": "總費用",
+        "context1m": "1M 上下文",
+        "context1mPricing": "輸入 2x >200k, 輸出 1.5x >200k"
       },
       "performance": {
         "title": "效能資料",
         "ttfb": "首字節時間(TTFB)",
         "duration": "總耗時",
         "outputRate": "輸出速率"
+      },
+      "performanceTab": {
+        "noPerformanceData": "暫無效能資料",
+        "ttfbGauge": "首字節時間",
+        "outputRateGauge": "輸出速率",
+        "latencyBreakdown": "延遲分解",
+        "generationTime": "生成時間",
+        "assessment": {
+          "excellent": "優秀",
+          "good": "良好",
+          "warning": "警告",
+          "poor": "較差"
+        },
+        "thresholds": {
+          "ttfbGood": "TTFB < 1s",
+          "ttfbWarning": "TTFB 1-2s",
+          "ttfbPoor": "TTFB > 3s"
+        }
+      },
+      "metadata": {
+        "noMetadata": "暫無中繼資料",
+        "sessionInfo": "工作階段資訊",
+        "clientInfo": "用戶端資訊",
+        "billingInfo": "計費資訊",
+        "technicalTimeline": "技術時間軸",
+        "copyTimeline": "複製時間軸"
+      },
+      "logicTrace": {
+        "title": "決策鏈",
+        "noDecisionData": "暫無決策資料",
+        "providersCount": "{count} 個供應商",
+        "healthyCount": "{count} 個健康",
+        "initialSelection": "初始選擇",
+        "healthCheck": "健康檢查",
+        "prioritySelection": "優先順序選擇",
+        "attemptProvider": "嘗試: {provider}",
+        "retryAttempt": "重試 #{number}",
+        "sessionReuse": "會話複用",
+        "sessionReuseDesc": "從會話快取複用供應商",
+        "sessionReuseTitle": "會話綁定",
+        "sessionReuseSelection": "會話複用選擇",
+        "sessionReuseSelectionDesc": "從會話快取中選擇供應商",
+        "sessionInfo": "會話資訊",
+        "sessionIdLabel": "會話 ID",
+        "requestSequence": "請求序號",
+        "sessionAge": "會話年齡",
+        "reusedProvider": "複用的供應商",
+        "executeRequest": "執行請求",
+        "cacheOptimizationHint": "會話複用通過在同一對話中保持供應商親和性來優化效能,減少選擇開銷並提高快取命中率。"
       }
     },
     "providerChain": {

+ 41 - 0
messages/zh-TW/provider-chain.json

@@ -39,6 +39,47 @@
     "http2Fallback": "HTTP/2 回退",
     "clientError": "客戶端錯誤"
   },
+  "reasons": {
+    "request_success": "成功",
+    "retry_success": "重試成功",
+    "retry_failed": "重試失敗",
+    "system_error": "系統錯誤",
+    "client_error_non_retryable": "客戶端錯誤",
+    "concurrent_limit_failed": "並發限制",
+    "http2_fallback": "HTTP/2 回退",
+    "session_reuse": "會話複用",
+    "initial_selection": "首次選擇"
+  },
+  "filterReasons": {
+    "rate_limited": "速率限制",
+    "circuit_open": "熔斷器開啟",
+    "disabled": "已停用",
+    "model_not_supported": "不支援該模型",
+    "group_mismatch": "分組不匹配",
+    "health_check_failed": "健康檢查失敗"
+  },
+  "details": {
+    "selectionMethod": "選擇方式",
+    "attemptNumber": "嘗試次數",
+    "endpoint": "端點",
+    "config": "配置",
+    "priority": "優先級",
+    "weight": "權重",
+    "costMultiplier": "成本倍數",
+    "groupTag": "分組標籤",
+    "circuitBreaker": "熔斷器",
+    "failures": "次失敗",
+    "modelRedirect": "模型重定向",
+    "error": "錯誤",
+    "errorDetails": "錯誤詳情",
+    "decisionContext": "決策上下文",
+    "beforeHealthCheck": "健康檢查前",
+    "afterHealthCheck": "健康檢查後",
+    "filteredProviders": "被過濾供應商",
+    "priorityLevels": "優先級層級",
+    "candidates": "候選供應商"
+  },
+  "technicalTimeline": "技術時間線",
   "timeline": {
     "sessionReuse": "會話複用",
     "sessionReuseSelection": "會話複用選擇供應商",

+ 2 - 0
package.json

@@ -54,6 +54,7 @@
     "@radix-ui/react-label": "^2",
     "@radix-ui/react-popover": "^1",
     "@radix-ui/react-progress": "^1",
+    "@radix-ui/react-scroll-area": "^1",
     "@radix-ui/react-select": "^2",
     "@radix-ui/react-slider": "^1",
     "@radix-ui/react-slot": "^1",
@@ -64,6 +65,7 @@
     "@tanstack/react-query": "^5",
     "@tanstack/react-virtual": "^3",
     "@tanstack/virtual-core": "^3",
+    "agentation": "^1.3.2",
     "antd": "^6",
     "bull": "^4",
     "class-variance-authority": "^0.7",

+ 41 - 11
src/app/[locale]/dashboard/logs/_components/active-sessions-skeleton.tsx

@@ -1,17 +1,47 @@
-import { ListSkeleton, LoadingState } from "@/components/loading/page-skeletons";
+import { Card, CardContent, CardHeader } from "@/components/ui/card";
 import { Skeleton } from "@/components/ui/skeleton";
 
+function CardSkeleton() {
+  return (
+    <Card className="w-[280px] shrink-0">
+      <CardContent className="p-4 space-y-3">
+        <div className="flex items-center justify-between">
+          <Skeleton className="h-5 w-24" />
+          <Skeleton className="h-5 w-16" />
+        </div>
+        <Skeleton className="h-4 w-40" />
+        <Skeleton className="h-4 w-32" />
+        <div className="flex items-center justify-between pt-2 border-t">
+          <Skeleton className="h-4 w-24" />
+          <Skeleton className="h-4 w-16" />
+        </div>
+      </CardContent>
+    </Card>
+  );
+}
+
 export function ActiveSessionsSkeleton() {
   return (
-    <div className="rounded-lg border bg-card">
-      <div className="flex items-center justify-between border-b px-4 py-3">
-        <Skeleton className="h-4 w-28" />
-        <Skeleton className="h-3 w-20" />
-      </div>
-      <div className="p-4 space-y-3">
-        <ListSkeleton rows={5} />
-        <LoadingState />
-      </div>
-    </div>
+    <Card className="border-border/50">
+      <CardHeader className="pb-3">
+        <div className="flex items-center justify-between">
+          <div className="flex items-center gap-2">
+            <Skeleton className="h-8 w-8 rounded-lg" />
+            <div className="space-y-1">
+              <Skeleton className="h-5 w-28" />
+              <Skeleton className="h-3 w-40" />
+            </div>
+          </div>
+          <Skeleton className="h-4 w-20" />
+        </div>
+      </CardHeader>
+      <CardContent className="pt-0">
+        <div className="flex gap-3 pb-3 overflow-hidden">
+          {[1, 2, 3].map((i) => (
+            <CardSkeleton key={i} />
+          ))}
+        </div>
+      </CardContent>
+    </Card>
   );
 }

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

@@ -0,0 +1,123 @@
+"use client";
+
+import { Columns3, RotateCcw } from "lucide-react";
+import { useTranslations } from "next-intl";
+import { useCallback, useEffect, useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Checkbox } from "@/components/ui/checkbox";
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuLabel,
+  DropdownMenuSeparator,
+  DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+  DEFAULT_VISIBLE_COLUMNS,
+  getHiddenColumns,
+  type LogsTableColumn,
+  setHiddenColumns,
+} from "@/lib/column-visibility";
+
+interface ColumnVisibilityDropdownProps {
+  userId: number;
+  tableId: string;
+  onVisibilityChange?: (hiddenColumns: LogsTableColumn[]) => void;
+}
+
+// Column label keys for i18n
+const COLUMN_LABEL_KEYS: Record<LogsTableColumn, string> = {
+  user: "logs.columns.user",
+  key: "logs.columns.key",
+  sessionId: "logs.columns.sessionId",
+  provider: "logs.columns.provider",
+  tokens: "logs.columns.tokens",
+  cache: "logs.columns.cache",
+  performance: "logs.columns.performance",
+};
+
+export function ColumnVisibilityDropdown({
+  userId,
+  tableId,
+  onVisibilityChange,
+}: ColumnVisibilityDropdownProps) {
+  const t = useTranslations("dashboard");
+  const tCommon = useTranslations("common");
+  const [hiddenColumns, setHiddenColumnsState] = useState<LogsTableColumn[]>([]);
+
+  // Load initial state from localStorage
+  useEffect(() => {
+    const stored = getHiddenColumns(userId, tableId);
+    setHiddenColumnsState(stored);
+  }, [userId, tableId]);
+
+  const handleToggle = useCallback(
+    (column: LogsTableColumn) => {
+      const isHidden = hiddenColumns.includes(column);
+      const newHidden = isHidden
+        ? hiddenColumns.filter((c) => c !== column)
+        : [...hiddenColumns, column];
+
+      setHiddenColumnsState(newHidden);
+      setHiddenColumns(userId, tableId, newHidden);
+      onVisibilityChange?.(newHidden);
+    },
+    [hiddenColumns, userId, tableId, onVisibilityChange]
+  );
+
+  const handleReset = useCallback(() => {
+    setHiddenColumnsState([]);
+    setHiddenColumns(userId, tableId, []);
+    onVisibilityChange?.([]);
+  }, [userId, tableId, onVisibilityChange]);
+
+  const visibleCount = DEFAULT_VISIBLE_COLUMNS.length - hiddenColumns.length;
+  const totalCount = DEFAULT_VISIBLE_COLUMNS.length;
+
+  return (
+    <DropdownMenu>
+      <DropdownMenuTrigger asChild>
+        <Button variant="outline" size="sm" className="gap-1.5 h-8">
+          <Columns3 className="h-3.5 w-3.5" />
+          <span className="hidden sm:inline">
+            {visibleCount}/{totalCount}
+          </span>
+        </Button>
+      </DropdownMenuTrigger>
+      <DropdownMenuContent align="end" className="w-48">
+        <DropdownMenuLabel className="text-xs font-medium">
+          {t("logs.table.columnVisibility")}
+        </DropdownMenuLabel>
+        <DropdownMenuSeparator />
+        {DEFAULT_VISIBLE_COLUMNS.map((column) => {
+          const isVisible = !hiddenColumns.includes(column);
+          return (
+            <DropdownMenuItem
+              key={column}
+              className="flex items-center gap-2 cursor-pointer"
+              onSelect={(e) => {
+                e.preventDefault();
+                handleToggle(column);
+              }}
+            >
+              <Checkbox checked={isVisible} className="pointer-events-none" aria-hidden="true" />
+              <span className="text-sm">{t(COLUMN_LABEL_KEYS[column])}</span>
+            </DropdownMenuItem>
+          );
+        })}
+        <DropdownMenuSeparator />
+        <DropdownMenuItem
+          className="flex items-center gap-2 cursor-pointer text-muted-foreground"
+          onSelect={(e) => {
+            e.preventDefault();
+            handleReset();
+          }}
+        >
+          <RotateCcw className="h-3.5 w-3.5" />
+          <span className="text-sm">{tCommon("reset")}</span>
+        </DropdownMenuItem>
+      </DropdownMenuContent>
+    </DropdownMenu>
+  );
+}

+ 232 - 167
src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx

@@ -16,44 +16,97 @@ vi.mock("@/i18n/routing", () => ({
   ),
 }));
 
-vi.mock("@/components/ui/dialog", () => {
+// Mock Sheet to render content directly (not via portal)
+vi.mock("@/components/ui/sheet", () => {
   type PropsWithChildren = { children?: ReactNode };
 
-  function Dialog({ children }: PropsWithChildren) {
-    return <div data-slot="dialog-root">{children}</div>;
+  function Sheet({ children, open }: PropsWithChildren & { open?: boolean }) {
+    return (
+      <div data-slot="sheet-root" data-open={open}>
+        {children}
+      </div>
+    );
+  }
+
+  function SheetTrigger({ children }: PropsWithChildren) {
+    return <div data-slot="sheet-trigger">{children}</div>;
+  }
+
+  function SheetContent({ children, className }: PropsWithChildren & { className?: string }) {
+    return (
+      <div data-slot="sheet-content" className={className}>
+        {children}
+      </div>
+    );
   }
 
-  function DialogTrigger({ children }: PropsWithChildren) {
-    return <div data-slot="dialog-trigger">{children}</div>;
+  function SheetHeader({ children }: PropsWithChildren) {
+    return <div data-slot="sheet-header">{children}</div>;
   }
 
-  function DialogContent({ children, className }: PropsWithChildren & { className?: string }) {
+  function SheetTitle({ children }: PropsWithChildren) {
+    return <div data-slot="sheet-title">{children}</div>;
+  }
+
+  return {
+    Sheet,
+    SheetContent,
+    SheetHeader,
+    SheetTitle,
+    SheetTrigger,
+  };
+});
+
+// Mock Tabs to render all content for testing
+vi.mock("@/components/ui/tabs", () => {
+  type PropsWithChildren = { children?: ReactNode };
+
+  function Tabs({
+    children,
+    className,
+  }: PropsWithChildren & { className?: string; value?: string }) {
     return (
-      <div data-slot="dialog-content" className={className}>
+      <div data-slot="tabs-root" className={className}>
         {children}
       </div>
     );
   }
 
-  function DialogHeader({ children }: PropsWithChildren) {
-    return <div data-slot="dialog-header">{children}</div>;
+  function TabsList({ children, className }: PropsWithChildren & { className?: string }) {
+    return (
+      <div data-slot="tabs-list" className={className}>
+        {children}
+      </div>
+    );
   }
 
-  function DialogTitle({ children }: PropsWithChildren) {
-    return <div data-slot="dialog-title">{children}</div>;
+  function TabsTrigger({
+    children,
+    className,
+  }: PropsWithChildren & { className?: string; value?: string }) {
+    return (
+      <div data-slot="tabs-trigger" className={className}>
+        {children}
+      </div>
+    );
   }
 
-  function DialogDescription({ children }: PropsWithChildren) {
-    return <div data-slot="dialog-description">{children}</div>;
+  function TabsContent({
+    children,
+    className,
+  }: PropsWithChildren & { className?: string; value?: string }) {
+    return (
+      <div data-slot="tabs-content" className={className}>
+        {children}
+      </div>
+    );
   }
 
   return {
-    Dialog,
-    DialogContent,
-    DialogDescription,
-    DialogHeader,
-    DialogTitle,
-    DialogTrigger,
+    Tabs,
+    TabsList,
+    TabsTrigger,
+    TabsContent,
   };
 });
 
@@ -70,27 +123,38 @@ const messages = {
         endpoint: "Endpoint",
       },
       details: {
+        title: "Request Details",
         inProgress: "In progress",
         statusTitle: "Status: {status}",
         unknown: "Unknown",
         processing: "Processing",
         success: "Success",
         error: "Error",
+        tabs: {
+          summary: "Summary",
+          logicTrace: "Logic Trace",
+          performance: "Performance",
+          metadata: "Metadata",
+        },
+        summary: {
+          keyMetrics: "Key Metrics",
+          totalCost: "Total Cost",
+          totalTokens: "Total Tokens",
+          duration: "Duration",
+          outputRate: "Output Rate",
+          viewFullError: "View full error",
+          viewSession: "View Session",
+        },
         skipped: {
           title: "Skipped",
-          reason: "Reason",
           warmup: "Warmup",
           desc: "Warmup skipped",
         },
         blocked: {
           title: "Blocked",
-          type: "Type",
           sensitiveWord: "Sensitive word",
           word: "Word",
           matchType: "Match type",
-          matchTypeContains: "Contains",
-          matchTypeExact: "Exact",
-          matchTypeRegex: "Regex",
           matchedText: "Matched text",
         },
         modelRedirect: {
@@ -101,46 +165,105 @@ const messages = {
         specialSettings: {
           title: "Special settings",
         },
-        billingDetails: {
-          title: "Billing details",
-        },
         performance: {
           title: "Performance",
           ttfb: "TTFB",
           duration: "Duration",
           outputRate: "Output rate",
         },
+        performanceTab: {
+          noPerformanceData: "No performance data",
+          ttfbGauge: "Time to First Byte",
+          outputRateGauge: "Output Rate",
+          latencyBreakdown: "Latency Breakdown",
+          generationTime: "Generation Time",
+          assessment: {
+            excellent: "Excellent",
+            good: "Good",
+            warning: "Warning",
+            poor: "Poor",
+          },
+          thresholds: {
+            ttfbGood: "TTFB < 300ms",
+            ttfbWarning: "TTFB 300-600ms",
+            ttfbPoor: "TTFB > 1000ms",
+          },
+        },
+        metadata: {
+          noMetadata: "No metadata",
+          sessionInfo: "Session Info",
+          clientInfo: "Client Info",
+          billingInfo: "Billing Info",
+          technicalTimeline: "Technical Timeline",
+          copyTimeline: "Copy Timeline",
+        },
+        logicTrace: {
+          title: "Decision Chain",
+          noDecisionData: "No decision data",
+          providersCount: "{count} providers",
+          healthyCount: "{count} healthy",
+          initialSelection: "Initial Selection",
+          healthCheck: "Health Check",
+          prioritySelection: "Priority Selection",
+          attemptProvider: "Attempt: {provider}",
+          retryAttempt: "Retry #{number}",
+        },
         noError: {
           processing: "No error (processing)",
           success: "No error (success)",
           default: "No error",
         },
         errorMessage: "Error message",
+        viewDetails: "View details",
         filteredProviders: "Filtered providers",
         providerChain: {
           title: "Provider chain",
-          totalDuration: "Total duration: {duration}",
+          totalDuration: "Total duration: {duration}ms",
         },
         reasons: {
           rateLimited: "Rate limited",
           circuitOpen: "Circuit open",
         },
+        billingDetails: {
+          title: "Billing details",
+          input: "Input",
+          output: "Output",
+          cacheWrite5m: "Cache write 5m",
+          cacheWrite1h: "Cache write 1h",
+          cacheRead: "Cache read",
+          cacheTtl: "Cache TTL",
+          context1m: "1M Context",
+          context1mPricing: "special pricing",
+          multiplier: "Multiplier",
+          totalCost: "Total cost",
+        },
       },
-      billingDetails: {
-        input: "Input",
-        output: "Output",
-        cacheWrite5m: "Cache write 5m",
-        cacheWrite1h: "Cache write 1h",
-        cacheRead: "Cache read",
-        cacheTtl: "Cache TTL",
-        context1m: "1M Context",
-        context1mPricing: "special pricing",
-        multiplier: "Multiplier",
-        totalCost: "Total cost",
-      },
     },
   },
-  "provider-chain": {},
+  "provider-chain": {
+    technicalTimeline: "Technical Timeline",
+    reasons: {
+      request_success: "Request success",
+      retry_success: "Retry success",
+      retry_failed: "Retry failed",
+      system_error: "System error",
+      client_error_non_retryable: "Client error",
+      concurrent_limit_failed: "Concurrent limit",
+    },
+    filterReasons: {
+      rate_limited: "Rate limited",
+      circuit_open: "Circuit open",
+    },
+    details: {
+      selectionMethod: "Selection method",
+      endpoint: "Endpoint",
+      circuitBreaker: "Circuit breaker",
+      failures: "failures",
+      modelRedirect: "Model redirect",
+      error: "Error",
+      errorDetails: "Error details",
+    },
+  },
 };
 
 function renderWithIntl(node: ReactNode) {
@@ -151,16 +274,13 @@ function renderWithIntl(node: ReactNode) {
   );
 }
 
+// Note: parseHtml uses innerHTML for test purposes only, parsing trusted test output
 function parseHtml(html: string) {
   const window = new Window();
   window.document.body.innerHTML = html;
   return window.document;
 }
 
-function getBillingAndPerformanceGrid(document: ReturnType<typeof parseHtml>) {
-  return document.querySelector("div.grid.gap-4");
-}
-
 describe("error-details-dialog layout", () => {
   test("renders special settings section when specialSettings exists", () => {
     const html = renderWithIntl(
@@ -189,7 +309,7 @@ describe("error-details-dialog layout", () => {
     expect(html).toContain("provider_parameter_override");
   });
 
-  test("renders billing + performance as two-column grid on md when both present", () => {
+  test("renders key metrics when cost and duration are present", () => {
     const html = renderWithIntl(
       <ErrorDetailsDialog
         externalOpen
@@ -205,21 +325,11 @@ describe("error-details-dialog layout", () => {
       />
     );
 
-    const document = parseHtml(html);
-    const grid = getBillingAndPerformanceGrid(document);
-
-    expect(grid).not.toBeNull();
-    expect(grid?.getAttribute("class")).toContain("grid-cols-1");
-    expect(grid?.getAttribute("class")).toContain("md:grid-cols-2");
-
-    const headings = Array.from(grid?.querySelectorAll("h4") ?? []).map((node) =>
-      node.textContent?.trim()
-    );
-    expect(headings).toContain("Billing details");
-    expect(headings).toContain("Performance");
+    expect(html).toContain("Key Metrics");
+    expect(html).toContain("Total Cost");
   });
 
-  test("renders only billing in single-column grid when performance is absent", () => {
+  test("renders billing info when cost is present", () => {
     const html = renderWithIntl(
       <ErrorDetailsDialog
         externalOpen
@@ -235,21 +345,11 @@ describe("error-details-dialog layout", () => {
       />
     );
 
-    const document = parseHtml(html);
-    const grid = getBillingAndPerformanceGrid(document);
-
-    expect(grid).not.toBeNull();
-    expect(grid?.getAttribute("class")).toContain("grid-cols-1");
-    expect(grid?.getAttribute("class")).not.toContain("md:grid-cols-2");
-
-    const headings = Array.from(grid?.querySelectorAll("h4") ?? []).map((node) =>
-      node.textContent?.trim()
-    );
-    expect(headings).toEqual(["Billing details"]);
+    expect(html).toContain("Billing Info");
     expect(html).toContain("$0.000001");
   });
 
-  test("renders only performance in single-column grid when billing is absent", () => {
+  test("renders performance metrics when duration is present", () => {
     const html = renderWithIntl(
       <ErrorDetailsDialog
         externalOpen
@@ -265,60 +365,10 @@ describe("error-details-dialog layout", () => {
       />
     );
 
-    const document = parseHtml(html);
-    const grid = getBillingAndPerformanceGrid(document);
-
-    expect(grid).not.toBeNull();
-    expect(grid?.getAttribute("class")).toContain("grid-cols-1");
-    expect(grid?.getAttribute("class")).not.toContain("md:grid-cols-2");
-
-    const headings = Array.from(grid?.querySelectorAll("h4") ?? []).map((node) =>
-      node.textContent?.trim()
-    );
-    expect(headings).toEqual(["Performance"]);
+    expect(html).toContain("Output Rate");
     expect(html).toContain("100.0 tok/s");
   });
 
-  test("toggles responsive breakpoint class based on section count", () => {
-    const both = parseHtml(
-      renderWithIntl(
-        <ErrorDetailsDialog
-          externalOpen
-          statusCode={500}
-          errorMessage={null}
-          providerChain={null}
-          sessionId={null}
-          costUsd={"0.000001"}
-          inputTokens={100}
-          outputTokens={80}
-          durationMs={900}
-          ttfbMs={100}
-        />
-      )
-    );
-    expect(getBillingAndPerformanceGrid(both)?.getAttribute("class")).toContain("md:grid-cols-2");
-
-    const single = parseHtml(
-      renderWithIntl(
-        <ErrorDetailsDialog
-          externalOpen
-          statusCode={500}
-          errorMessage={null}
-          providerChain={null}
-          sessionId={null}
-          costUsd={"0.000001"}
-          inputTokens={100}
-          outputTokens={0}
-          durationMs={null}
-          ttfbMs={null}
-        />
-      )
-    );
-    expect(getBillingAndPerformanceGrid(single)?.getAttribute("class")).not.toContain(
-      "md:grid-cols-2"
-    );
-  });
-
   test("uses gray status class for unexpected statusCode (e.g., 100)", () => {
     const html = renderWithIntl(
       <ErrorDetailsDialog
@@ -372,7 +422,7 @@ describe("error-details-dialog layout", () => {
     expect(html).toContain("Processing");
   });
 
-  test("renders filtered providers and provider chain timeline when present", () => {
+  test("renders provider chain timeline when present", () => {
     const html = renderWithIntl(
       <ErrorDetailsDialog
         externalOpen
@@ -402,52 +452,12 @@ describe("error-details-dialog layout", () => {
       />
     );
 
-    expect(html).toContain("Filtered providers");
-    expect(html).toContain("filtered-provider");
-    expect(html).toContain("Provider chain");
+    expect(html).toContain("Decision Chain");
     expect(html).toContain("timeline");
     expect(html).toContain("Total duration");
   });
 
-  test("formats JSON rate limit error message and filtered providers", () => {
-    const html = renderWithIntl(
-      <ErrorDetailsDialog
-        externalOpen
-        statusCode={429}
-        errorMessage={JSON.stringify({
-          code: "rate_limit_exceeded",
-          message: "Rate limited",
-          details: {
-            filteredProviders: [{ id: 1, name: "p", reason: "rate_limited", details: "$1" }],
-          },
-        })}
-        providerChain={null}
-        sessionId={null}
-      />
-    );
-
-    expect(html).toContain("Error message");
-    expect(html).toContain("Rate limited");
-    expect(html).toContain("p");
-    expect(html).toContain("$1");
-  });
-
-  test("formats non-rate-limit JSON error as pretty JSON", () => {
-    const html = renderWithIntl(
-      <ErrorDetailsDialog
-        externalOpen
-        statusCode={500}
-        errorMessage={JSON.stringify({ error: "E", code: "other" })}
-        providerChain={null}
-        sessionId={null}
-      />
-    );
-
-    expect(html).toContain("Error message");
-    expect(html).toContain("&quot;error&quot;");
-  });
-
-  test("falls back to raw error message when it is not JSON", () => {
+  test("renders error message in summary tab", () => {
     const html = renderWithIntl(
       <ErrorDetailsDialog
         externalOpen
@@ -462,7 +472,7 @@ describe("error-details-dialog layout", () => {
     expect(html).toContain("not-json");
   });
 
-  test("renders warmup skipped and blocked sections when applicable", () => {
+  test("renders warmup skipped info in logic trace tab", () => {
     const html = renderWithIntl(
       <ErrorDetailsDialog
         externalOpen
@@ -475,7 +485,9 @@ describe("error-details-dialog layout", () => {
     );
     expect(html).toContain("Skipped");
     expect(html).toContain("Warmup");
+  });
 
+  test("renders blocked info in logic trace tab", () => {
     const htmlBlocked = renderWithIntl(
       <ErrorDetailsDialog
         externalOpen
@@ -490,7 +502,6 @@ describe("error-details-dialog layout", () => {
     expect(htmlBlocked).toContain("Blocked");
     expect(htmlBlocked).toContain("Sensitive word");
     expect(htmlBlocked).toContain("bad");
-    expect(htmlBlocked).toContain("Contains");
   });
 
   test("renders model redirect section when originalModel != currentModel", () => {
@@ -656,3 +667,57 @@ describe("error-details-dialog multiplier", () => {
     expect(html).toContain("0.20x");
   });
 });
+
+describe("error-details-dialog tabs", () => {
+  test("renders all three tabs", () => {
+    const html = renderWithIntl(
+      <ErrorDetailsDialog
+        externalOpen
+        statusCode={200}
+        errorMessage={null}
+        providerChain={null}
+        sessionId={null}
+      />
+    );
+
+    expect(html).toContain("Summary");
+    expect(html).toContain("Logic Trace");
+    expect(html).toContain("Performance");
+  });
+
+  test("renders performance gauges when TTFB and output rate are present", () => {
+    const html = renderWithIntl(
+      <ErrorDetailsDialog
+        externalOpen
+        statusCode={200}
+        errorMessage={null}
+        providerChain={null}
+        sessionId={null}
+        durationMs={1000}
+        ttfbMs={200}
+        outputTokens={500}
+      />
+    );
+
+    expect(html).toContain("Time to First Byte");
+    expect(html).toContain("Output Rate");
+    expect(html).toContain("Latency Breakdown");
+  });
+
+  test("renders session info in summary tab when sessionId is present", () => {
+    const html = renderWithIntl(
+      <ErrorDetailsDialog
+        externalOpen
+        statusCode={200}
+        errorMessage={null}
+        providerChain={null}
+        sessionId={"test-session-123"}
+        requestSequence={5}
+      />
+    );
+
+    expect(html).toContain("Session Info");
+    expect(html).toContain("test-session-123");
+    expect(html).toContain("#5");
+  });
+});

+ 0 - 816
src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx

@@ -1,816 +0,0 @@
-"use client";
-
-import {
-  AlertCircle,
-  ArrowRight,
-  CheckCircle,
-  DollarSign,
-  ExternalLink,
-  Gauge,
-  Loader2,
-  Monitor,
-} from "lucide-react";
-import { useTranslations } from "next-intl";
-import { useEffect, useState } from "react";
-import { hasSessionMessages } from "@/actions/active-sessions";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import {
-  Dialog,
-  DialogContent,
-  DialogDescription,
-  DialogHeader,
-  DialogTitle,
-  DialogTrigger,
-} from "@/components/ui/dialog";
-import { Link } from "@/i18n/routing";
-import { cn, formatTokenAmount } from "@/lib/utils";
-import { formatCurrency } from "@/lib/utils/currency";
-import { formatProviderTimeline } from "@/lib/utils/provider-chain-formatter";
-import type { ProviderChainItem } from "@/types/message";
-import type { SpecialSetting } from "@/types/special-settings";
-import type { BillingModelSource } from "@/types/system-config";
-
-interface ErrorDetailsDialogProps {
-  statusCode: number | null;
-  errorMessage: string | null;
-  providerChain: ProviderChainItem[] | null;
-  sessionId: string | null;
-  requestSequence?: number | null; // Request Sequence(Session 内请求序号)
-  blockedBy?: string | null; // 拦截类型
-  blockedReason?: string | null; // 拦截原因(JSON 字符串)
-  originalModel?: string | null; // 原始模型(重定向前)
-  currentModel?: string | null; // 当前模型(重定向后)
-  userAgent?: string | null; // User-Agent
-  messagesCount?: number | null; // Messages 数量
-  endpoint?: string | null; // API 端点
-  billingModelSource?: BillingModelSource; // 计费模型来源
-  specialSettings?: SpecialSetting[] | null; // 特殊设置(审计/展示)
-  // 计费详情
-  inputTokens?: number | null;
-  outputTokens?: number | null;
-  cacheCreationInputTokens?: number | null; // 缓存创建总量
-  cacheCreation5mInputTokens?: number | null;
-  cacheCreation1hInputTokens?: number | null;
-  cacheReadInputTokens?: number | null;
-  cacheTtlApplied?: string | null;
-  costUsd?: string | null;
-  costMultiplier?: string | null;
-  context1mApplied?: boolean | null; // 1M上下文窗口是否已应用
-  durationMs?: number | null;
-  ttfbMs?: number | null;
-  externalOpen?: boolean; // 外部控制弹窗开关
-  onExternalOpenChange?: (open: boolean) => void; // 外部控制回调
-  scrollToRedirect?: boolean; // 是否滚动到重定向部分
-}
-
-export function ErrorDetailsDialog({
-  statusCode,
-  errorMessage,
-  providerChain,
-  sessionId,
-  requestSequence,
-  blockedBy,
-  blockedReason,
-  originalModel,
-  currentModel,
-  userAgent,
-  messagesCount,
-  endpoint,
-  billingModelSource = "original",
-  specialSettings,
-  inputTokens,
-  outputTokens,
-  cacheCreationInputTokens,
-  cacheCreation5mInputTokens,
-  cacheCreation1hInputTokens,
-  cacheReadInputTokens,
-  cacheTtlApplied,
-  costUsd,
-  costMultiplier,
-  context1mApplied,
-  durationMs,
-  ttfbMs,
-  externalOpen,
-  onExternalOpenChange,
-  scrollToRedirect,
-}: ErrorDetailsDialogProps) {
-  const t = useTranslations("dashboard");
-  const tChain = useTranslations("provider-chain");
-  const [internalOpen, setInternalOpen] = useState(false);
-  const [hasMessages, setHasMessages] = useState(false);
-  const [checkingMessages, setCheckingMessages] = useState(false);
-
-  // 支持外部控制和内部控制
-  const isControlled = externalOpen !== undefined;
-  const open = isControlled ? externalOpen : internalOpen;
-  const setOpen = (value: boolean) => {
-    if (isControlled) {
-      onExternalOpenChange?.(value);
-    } else {
-      setInternalOpen(value);
-    }
-  };
-
-  const isSuccess = statusCode && statusCode >= 200 && statusCode < 300;
-  const isInProgress = !statusCode; // 没有状态码表示请求进行中
-  const isWarmupSkipped = blockedBy === "warmup";
-  const isBlocked = !!blockedBy && !isWarmupSkipped; // 是否被拦截(不含 warmup 跳过)
-
-  const specialSettingsContent =
-    specialSettings && specialSettings.length > 0 ? JSON.stringify(specialSettings, null, 2) : null;
-
-  const outputTokensPerSecond = (() => {
-    if (
-      outputTokens === null ||
-      outputTokens === undefined ||
-      outputTokens <= 0 ||
-      durationMs === null ||
-      durationMs === undefined ||
-      ttfbMs === null ||
-      ttfbMs === undefined ||
-      ttfbMs >= durationMs
-    ) {
-      return null;
-    }
-    const seconds = (durationMs - ttfbMs) / 1000;
-    if (seconds <= 0) return null;
-    return outputTokens / seconds;
-  })();
-
-  // 解析 blockedReason JSON
-  let parsedBlockedReason: { word?: string; matchType?: string; matchedText?: string } | null =
-    null;
-  if (blockedReason) {
-    try {
-      parsedBlockedReason = JSON.parse(blockedReason);
-    } catch {
-      // 解析失败,忽略
-    }
-  }
-
-  // 检查 session 是否有 messages 数据
-  useEffect(() => {
-    if (open && sessionId) {
-      setCheckingMessages(true);
-      hasSessionMessages(sessionId, requestSequence ?? undefined)
-        .then((result) => {
-          if (result.ok) {
-            setHasMessages(result.data);
-          }
-        })
-        .catch((err) => {
-          console.error("Failed to check session messages:", err);
-        })
-        .finally(() => {
-          setCheckingMessages(false);
-        });
-    } else {
-      // 弹窗关闭时重置状态
-      setHasMessages(false);
-      setCheckingMessages(false);
-    }
-  }, [open, sessionId, requestSequence]);
-
-  // 滚动到重定向部分
-  useEffect(() => {
-    if (open && scrollToRedirect) {
-      // 等待 DOM 渲染完成后滚动
-      const timer = setTimeout(() => {
-        const element = document.getElementById("model-redirect-section");
-        element?.scrollIntoView({ behavior: "smooth", block: "start" });
-      }, 100);
-      return () => clearTimeout(timer);
-    }
-  }, [open, scrollToRedirect]);
-
-  /**
-   * 根据 HTTP 状态码返回对应的 Badge 样式类名
-   * 参考:new-api 和 gpt-load 的颜色方案,使用更明显的颜色区分
-   *
-   * 颜色方案:
-   * - 2xx (成功) - 绿色
-   * - 3xx (重定向) - 蓝色
-   * - 4xx (客户端错误) - 黄色
-   * - 5xx (服务器错误) - 红色
-   * - 进行中 - 灰色
-   */
-  const getStatusBadgeClassName = () => {
-    if (isInProgress) {
-      // 进行中 - 灰色
-      return "bg-gray-100 text-gray-700 border-gray-300 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600";
-    }
-
-    if (!statusCode) {
-      return "bg-gray-100 text-gray-700 border-gray-300 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600";
-    }
-
-    // 2xx - 成功 (绿色)
-    if (statusCode >= 200 && statusCode < 300) {
-      return "bg-green-100 text-green-700 border-green-300 dark:bg-green-900/30 dark:text-green-400 dark:border-green-700";
-    }
-
-    // 3xx - 重定向 (蓝色)
-    if (statusCode >= 300 && statusCode < 400) {
-      return "bg-blue-100 text-blue-700 border-blue-300 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-700";
-    }
-
-    // 4xx - 客户端错误 (黄色)
-    if (statusCode >= 400 && statusCode < 500) {
-      return "bg-yellow-100 text-yellow-700 border-yellow-300 dark:bg-yellow-900/30 dark:text-yellow-400 dark:border-yellow-700";
-    }
-
-    // 5xx - 服务器错误 (红色)
-    if (statusCode >= 500) {
-      return "bg-red-100 text-red-700 border-red-300 dark:bg-red-900/30 dark:text-red-400 dark:border-red-700";
-    }
-
-    // 其他 - 灰色
-    return "bg-gray-100 text-gray-700 border-gray-300 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600";
-  };
-
-  return (
-    <Dialog open={open} onOpenChange={setOpen}>
-      <DialogTrigger asChild>
-        <Button variant="ghost" className="h-auto p-0 font-normal hover:bg-transparent">
-          <Badge variant="outline" className={getStatusBadgeClassName()}>
-            {isInProgress ? t("logs.details.inProgress") : statusCode}
-          </Badge>
-        </Button>
-      </DialogTrigger>
-      <DialogContent className="max-w-4xl max-h-[85vh] overflow-y-auto">
-        <DialogHeader>
-          <DialogTitle className="flex items-center gap-2">
-            {isInProgress ? (
-              <Loader2 className="h-5 w-5 text-muted-foreground animate-spin" />
-            ) : isSuccess ? (
-              <CheckCircle className="h-5 w-5 text-green-600" />
-            ) : (
-              <AlertCircle className="h-5 w-5 text-destructive" />
-            )}
-            {t("logs.details.statusTitle", {
-              status: isInProgress
-                ? t("logs.details.inProgress")
-                : statusCode || t("logs.details.unknown"),
-            })}
-          </DialogTitle>
-          <DialogDescription>
-            {isInProgress
-              ? t("logs.details.processing")
-              : isSuccess
-                ? t("logs.details.success")
-                : t("logs.details.error")}
-          </DialogDescription>
-        </DialogHeader>
-
-        <div className="space-y-6 mt-4">
-          {/* Warmup 跳过信息 */}
-          {isWarmupSkipped && (
-            <div className="space-y-2">
-              <h4 className="font-semibold text-sm flex items-center gap-2">
-                <CheckCircle className="h-4 w-4 text-blue-600" />
-                {t("logs.details.skipped.title")}
-              </h4>
-              <div className="rounded-md border bg-blue-50 dark:bg-blue-950/20 p-4 space-y-2">
-                <div className="flex items-center gap-2">
-                  <span className="text-xs font-medium text-blue-900 dark:text-blue-100">
-                    {t("logs.details.skipped.reason")}:
-                  </span>
-                  <Badge variant="outline" className="border-blue-600 text-blue-700">
-                    {t("logs.details.skipped.warmup")}
-                  </Badge>
-                </div>
-                <p className="text-xs text-blue-900/80 dark:text-blue-100/80">
-                  {t("logs.details.skipped.desc")}
-                </p>
-              </div>
-            </div>
-          )}
-
-          {/* 拦截信息 */}
-          {isBlocked && blockedBy && (
-            <div className="space-y-2">
-              <h4 className="font-semibold text-sm flex items-center gap-2">
-                <AlertCircle className="h-4 w-4 text-orange-600" />
-                {t("logs.details.blocked.title")}
-              </h4>
-              <div className="rounded-md border bg-orange-50 dark:bg-orange-950/20 p-4 space-y-2">
-                <div className="flex items-center gap-2">
-                  <span className="text-xs font-medium text-orange-900 dark:text-orange-100">
-                    {t("logs.details.blocked.type")}:
-                  </span>
-                  <Badge variant="outline" className="border-orange-600 text-orange-600">
-                    {blockedBy === "sensitive_word"
-                      ? t("logs.details.blocked.sensitiveWord")
-                      : blockedBy}
-                  </Badge>
-                </div>
-                {parsedBlockedReason && (
-                  <div className="space-y-1 text-xs">
-                    {parsedBlockedReason.word && (
-                      <div className="flex items-baseline gap-2">
-                        <span className="font-medium text-orange-900 dark:text-orange-100">
-                          {t("logs.details.blocked.word")}:
-                        </span>
-                        <code className="bg-orange-100 dark:bg-orange-900/50 px-2 py-0.5 rounded text-orange-900 dark:text-orange-100">
-                          {parsedBlockedReason.word}
-                        </code>
-                      </div>
-                    )}
-                    {parsedBlockedReason.matchType && (
-                      <div className="flex items-baseline gap-2">
-                        <span className="font-medium text-orange-900 dark:text-orange-100">
-                          {t("logs.details.blocked.matchType")}:
-                        </span>
-                        <span className="text-orange-800 dark:text-orange-200">
-                          {parsedBlockedReason.matchType === "contains" &&
-                            t("logs.details.blocked.matchTypeContains")}
-                          {parsedBlockedReason.matchType === "exact" &&
-                            t("logs.details.blocked.matchTypeExact")}
-                          {parsedBlockedReason.matchType === "regex" &&
-                            t("logs.details.blocked.matchTypeRegex")}
-                        </span>
-                      </div>
-                    )}
-                    {parsedBlockedReason.matchedText && (
-                      <div className="flex flex-col gap-1">
-                        <span className="font-medium text-orange-900 dark:text-orange-100">
-                          {t("logs.details.blocked.matchedText")}:
-                        </span>
-                        <pre className="bg-orange-100 dark:bg-orange-900/50 px-2 py-1 rounded text-orange-900 dark:text-orange-100 whitespace-pre-wrap break-words">
-                          {parsedBlockedReason.matchedText}
-                        </pre>
-                      </div>
-                    )}
-                  </div>
-                )}
-              </div>
-            </div>
-          )}
-
-          {/* Session 信息 */}
-          {sessionId && (
-            <div className="space-y-2">
-              <h4 className="font-semibold text-sm">{t("logs.details.sessionId")}</h4>
-              <div className="flex items-center gap-3">
-                <div className="flex-1 rounded-md border bg-muted/50 p-3">
-                  <div className="flex items-center gap-2">
-                    <code className="text-xs font-mono break-all">{sessionId}</code>
-                    {requestSequence && (
-                      <Badge variant="outline" className="text-xs shrink-0">
-                        #{requestSequence}
-                      </Badge>
-                    )}
-                  </div>
-                </div>
-                {hasMessages && !checkingMessages && (
-                  <Link
-                    href={
-                      requestSequence
-                        ? `/dashboard/sessions/${sessionId}/messages?seq=${requestSequence}`
-                        : `/dashboard/sessions/${sessionId}/messages`
-                    }
-                  >
-                    <Button variant="outline" size="sm">
-                      <ExternalLink className="h-4 w-4 mr-2" />
-                      {t("logs.details.viewDetails")}
-                    </Button>
-                  </Link>
-                )}
-              </div>
-            </div>
-          )}
-
-          {/* Messages 数量 */}
-          {messagesCount !== null && messagesCount !== undefined && (
-            <div className="space-y-2">
-              <h4 className="font-semibold text-sm">{t("logs.details.messagesCount")}</h4>
-              <div className="rounded-md border bg-muted/50 p-3">
-                <div className="text-sm">
-                  <span className="font-medium">{t("logs.details.messagesLabel")}:</span>{" "}
-                  <code className="text-base font-mono font-semibold">{messagesCount}</code>{" "}
-                  {t("logs.details.messagesUnit")}
-                </div>
-              </div>
-            </div>
-          )}
-
-          {/* User-Agent 信息 */}
-          {userAgent && (
-            <div className="space-y-2">
-              <h4 className="font-semibold text-sm flex items-center gap-2">
-                <Monitor className="h-4 w-4 text-blue-600" />
-                {t("logs.details.clientInfo")}
-              </h4>
-              <div className="rounded-md border bg-muted/50 p-3">
-                <code className="text-xs font-mono break-all">{userAgent}</code>
-              </div>
-            </div>
-          )}
-
-          {/* Endpoint 信息 */}
-          {endpoint && (
-            <div className="space-y-2">
-              <h4 className="font-semibold text-sm">{t("logs.columns.endpoint")}</h4>
-              <div className="rounded-md border bg-muted/50 p-3">
-                <code className="text-xs font-mono break-all">{endpoint}</code>
-              </div>
-            </div>
-          )}
-
-          {/* 特殊设置(审计) */}
-          {specialSettingsContent && (
-            <div className="space-y-2">
-              <h4 className="font-semibold text-sm">{t("logs.details.specialSettings.title")}</h4>
-              <div className="rounded-md border bg-muted/50 p-3">
-                <pre className="text-xs whitespace-pre-wrap break-words font-mono">
-                  {specialSettingsContent}
-                </pre>
-              </div>
-            </div>
-          )}
-
-          {/* 计费详情 + 性能数据并排布局 */}
-          {(() => {
-            const showBilling = !!costUsd;
-            const showPerformance = durationMs != null || ttfbMs != null || (outputTokens ?? 0) > 0;
-            const showBothSections = showBilling && showPerformance;
-            return (
-              <div
-                className={cn(
-                  "grid gap-4",
-                  showBothSections ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1"
-                )}
-              >
-                {/* 计费详情 */}
-                {costUsd && (
-                  <div className="space-y-2">
-                    <h4 className="font-semibold text-sm flex items-center gap-2">
-                      <DollarSign className="h-4 w-4 text-green-600" />
-                      {t("logs.details.billingDetails.title")}
-                    </h4>
-                    <div className="rounded-md border bg-muted/50 p-4">
-                      <div className="grid grid-cols-1 gap-x-6 gap-y-2 text-sm">
-                        <div className="flex justify-between">
-                          <span className="text-muted-foreground">
-                            {t("logs.billingDetails.input")}:
-                          </span>
-                          <span className="font-mono">{formatTokenAmount(inputTokens)} tokens</span>
-                        </div>
-                        <div className="flex justify-between">
-                          <span className="text-muted-foreground">
-                            {t("logs.billingDetails.output")}:
-                          </span>
-                          <span className="font-mono">
-                            {formatTokenAmount(outputTokens)} tokens
-                          </span>
-                        </div>
-                        {((cacheCreation5mInputTokens ?? 0) > 0 ||
-                          ((cacheCreationInputTokens ?? 0) > 0 && cacheTtlApplied !== "1h")) && (
-                          <div className="flex justify-between">
-                            <span className="text-muted-foreground">
-                              {t("logs.billingDetails.cacheWrite5m")}:
-                            </span>
-                            <span className="font-mono">
-                              {formatTokenAmount(
-                                (cacheCreation5mInputTokens ?? 0) > 0
-                                  ? cacheCreation5mInputTokens
-                                  : cacheCreationInputTokens
-                              )}{" "}
-                              tokens <span className="text-orange-600">(1.25x)</span>
-                            </span>
-                          </div>
-                        )}
-                        {((cacheCreation1hInputTokens ?? 0) > 0 ||
-                          ((cacheCreationInputTokens ?? 0) > 0 && cacheTtlApplied === "1h")) && (
-                          <div className="flex justify-between">
-                            <span className="text-muted-foreground">
-                              {t("logs.billingDetails.cacheWrite1h")}:
-                            </span>
-                            <span className="font-mono">
-                              {formatTokenAmount(
-                                (cacheCreation1hInputTokens ?? 0) > 0
-                                  ? cacheCreation1hInputTokens
-                                  : cacheCreationInputTokens
-                              )}{" "}
-                              tokens <span className="text-orange-600">(2x)</span>
-                            </span>
-                          </div>
-                        )}
-                        {(cacheReadInputTokens ?? 0) > 0 && (
-                          <div className="flex justify-between">
-                            <span className="text-muted-foreground">
-                              {t("logs.billingDetails.cacheRead")}:
-                            </span>
-                            <span className="font-mono">
-                              {formatTokenAmount(cacheReadInputTokens)} tokens{" "}
-                              <span className="text-green-600">(0.1x)</span>
-                            </span>
-                          </div>
-                        )}
-                        {cacheTtlApplied && (
-                          <div className="flex justify-between">
-                            <span className="text-muted-foreground">
-                              {t("logs.billingDetails.cacheTtl")}:
-                            </span>
-                            <Badge variant="outline" className="text-xs">
-                              {cacheTtlApplied}
-                            </Badge>
-                          </div>
-                        )}
-                        {context1mApplied && (
-                          <div className="flex justify-between items-center gap-2">
-                            <span className="text-muted-foreground shrink-0">
-                              {t("logs.billingDetails.context1m")}:
-                            </span>
-                            <div className="flex items-center gap-2 min-w-0">
-                              <Badge
-                                variant="outline"
-                                className="text-xs bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950/30 dark:text-purple-300 dark:border-purple-800 shrink-0"
-                              >
-                                1M Context
-                              </Badge>
-                              <span className="text-xs text-muted-foreground truncate">
-                                ({t("logs.billingDetails.context1mPricing")})
-                              </span>
-                            </div>
-                          </div>
-                        )}
-                        {(() => {
-                          if (costMultiplier === "" || costMultiplier == null) return null;
-                          const multiplier = Number(costMultiplier);
-                          if (!Number.isFinite(multiplier) || multiplier === 1) return null;
-
-                          return (
-                            <div className="flex justify-between">
-                              <span className="text-muted-foreground">
-                                {t("logs.billingDetails.multiplier")}:
-                              </span>
-                              <span className="font-mono">{multiplier.toFixed(2)}x</span>
-                            </div>
-                          );
-                        })()}
-                      </div>
-                      <div className="mt-3 pt-3 border-t flex justify-between items-center">
-                        <span className="font-medium">{t("logs.billingDetails.totalCost")}:</span>
-                        <span className="font-mono text-lg font-semibold text-green-600">
-                          {formatCurrency(costUsd, "USD", 6)}
-                        </span>
-                      </div>
-                    </div>
-                  </div>
-                )}
-
-                {/* 性能数据 */}
-                {(durationMs != null || ttfbMs != null || (outputTokens ?? 0) > 0) && (
-                  <div className="space-y-2">
-                    <h4 className="font-semibold text-sm flex items-center gap-2">
-                      <Gauge className="h-4 w-4 text-purple-600" />
-                      {t("logs.details.performance.title")}
-                    </h4>
-                    <div className="rounded-md border bg-muted/50 p-4">
-                      <div className="grid grid-cols-1 gap-x-6 gap-y-2 text-sm">
-                        <div className="flex justify-between">
-                          <span className="text-muted-foreground">
-                            {t("logs.details.performance.ttfb")}:
-                          </span>
-                          <span className="font-mono">
-                            {ttfbMs != null ? `${Math.round(ttfbMs).toLocaleString()} ms` : "-"}
-                          </span>
-                        </div>
-                        <div className="flex justify-between">
-                          <span className="text-muted-foreground">
-                            {t("logs.details.performance.duration")}:
-                          </span>
-                          <span className="font-mono">
-                            {durationMs != null
-                              ? `${Math.round(durationMs).toLocaleString()} ms`
-                              : "-"}
-                          </span>
-                        </div>
-                        <div className="flex justify-between">
-                          <span className="text-muted-foreground">
-                            {t("logs.details.performance.outputRate")}:
-                          </span>
-                          <span className="font-mono">
-                            {outputTokensPerSecond !== null
-                              ? `${outputTokensPerSecond.toFixed(1)} tok/s`
-                              : "-"}
-                          </span>
-                        </div>
-                      </div>
-                    </div>
-                  </div>
-                )}
-              </div>
-            );
-          })()}
-
-          {/* 模型重定向信息 */}
-          {originalModel && currentModel && originalModel !== currentModel && (
-            <div id="model-redirect-section" className="space-y-1.5">
-              <h4 className="font-semibold text-sm flex items-center gap-2">
-                <ArrowRight className="h-4 w-4 text-blue-600" />
-                {t("logs.details.modelRedirect.title")}
-              </h4>
-              <div className="rounded-md border bg-blue-50 dark:bg-blue-950/20 px-3 py-2">
-                <div className="flex items-center gap-2 text-sm flex-wrap">
-                  <code
-                    className={cn(
-                      "px-1.5 py-0.5 rounded text-xs",
-                      billingModelSource === "original"
-                        ? "bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200 ring-1 ring-green-300 dark:ring-green-700"
-                        : "bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200"
-                    )}
-                  >
-                    {originalModel}
-                  </code>
-                  <ArrowRight className="h-3.5 w-3.5 text-blue-500 flex-shrink-0" />
-                  <code
-                    className={cn(
-                      "px-1.5 py-0.5 rounded text-xs",
-                      billingModelSource === "redirected"
-                        ? "bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200 ring-1 ring-green-300 dark:ring-green-700"
-                        : "bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200"
-                    )}
-                  >
-                    {currentModel}
-                  </code>
-                  <span className="text-xs text-muted-foreground ml-1">
-                    (
-                    {billingModelSource === "original"
-                      ? t("logs.details.modelRedirect.billingOriginal")
-                      : t("logs.details.modelRedirect.billingRedirected")}
-                    )
-                  </span>
-                </div>
-              </div>
-            </div>
-          )}
-
-          {/* 最终错误信息 */}
-          {errorMessage && (
-            <div className="space-y-2">
-              <h4 className="font-semibold text-sm flex items-center gap-2">
-                <AlertCircle className="h-4 w-4" />
-                {t("logs.details.errorMessage")}
-              </h4>
-
-              {/* 尝试解析 JSON 错误 */}
-              {(() => {
-                try {
-                  const error = JSON.parse(errorMessage);
-
-                  // 检查是否是限流错误
-                  if (
-                    error.code === "rate_limit_exceeded" ||
-                    error.code === "circuit_breaker_open" ||
-                    error.code === "mixed_unavailable"
-                  ) {
-                    return (
-                      <div className="rounded-md border bg-orange-50 dark:bg-orange-950/20 p-4 space-y-3">
-                        <div className="font-semibold text-orange-900 dark:text-orange-100 flex items-center gap-2">
-                          <DollarSign className="h-4 w-4" />
-                          {error.message}
-                        </div>
-                        {error.details?.filteredProviders &&
-                          error.details.filteredProviders.length > 0 && (
-                            <div className="space-y-2">
-                              <div className="text-sm font-medium text-orange-900 dark:text-orange-100">
-                                {t("logs.details.filteredProviders")}:
-                              </div>
-                              <ul className="text-sm space-y-1">
-                                {error.details.filteredProviders
-                                  .filter(
-                                    (p: { reason: string }) =>
-                                      p.reason === "rate_limited" || p.reason === "circuit_open"
-                                  )
-                                  .map((p: { id: number; name: string; details: string }) => (
-                                    <li
-                                      key={p.id}
-                                      className="text-orange-800 dark:text-orange-200 flex items-center gap-2"
-                                    >
-                                      <span className="text-orange-600">•</span>
-                                      <span className="font-medium">{p.name}</span>
-                                      <span className="text-xs">({p.details})</span>
-                                    </li>
-                                  ))}
-                              </ul>
-                            </div>
-                          )}
-                      </div>
-                    );
-                  }
-
-                  // 其他 JSON 错误,格式化显示
-                  return (
-                    <div className="rounded-md border bg-destructive/10 p-4">
-                      <pre className="text-xs text-destructive whitespace-pre-wrap break-words font-mono">
-                        {JSON.stringify(error, null, 2)}
-                      </pre>
-                    </div>
-                  );
-                } catch {
-                  // 解析失败,显示原始消息
-                  return (
-                    <div className="rounded-md border bg-destructive/10 p-4">
-                      <pre className="text-xs text-destructive whitespace-pre-wrap break-words font-mono">
-                        {errorMessage}
-                      </pre>
-                    </div>
-                  );
-                }
-              })()}
-            </div>
-          )}
-
-          {/* 被过滤的供应商(仅在成功请求时显示) */}
-          {isSuccess &&
-            providerChain &&
-            providerChain.length > 0 &&
-            (() => {
-              // 从决策链中提取被过滤的供应商
-              const filteredProviders = providerChain
-                .flatMap((item) => item.decisionContext?.filteredProviders || [])
-                .filter((p) => p.reason === "rate_limited" || p.reason === "circuit_open");
-
-              if (filteredProviders.length === 0) return null;
-
-              return (
-                <div className="space-y-2">
-                  <h4 className="font-semibold text-sm flex items-center gap-2">
-                    <AlertCircle className="h-4 w-4 text-orange-600" />
-                    {t("logs.details.filteredProviders")}
-                  </h4>
-                  <div className="rounded-md border bg-orange-50 dark:bg-orange-950/20 p-4">
-                    <ul className="text-sm space-y-2">
-                      {filteredProviders.map((p, index) => (
-                        <li
-                          key={`${p.id}-${index}`}
-                          className="text-orange-800 dark:text-orange-200 flex items-start gap-2"
-                        >
-                          <DollarSign className="h-4 w-4 text-orange-600 mt-0.5 flex-shrink-0" />
-                          <div className="flex-1">
-                            <span className="font-medium">{p.name}</span>
-                            <span className="text-xs ml-2">
-                              (
-                              {t(
-                                `logs.details.reasons.${p.reason === "rate_limited" ? "rateLimited" : "circuitOpen"}`
-                              )}
-                              )
-                            </span>
-                            {p.details && (
-                              <div className="text-xs text-orange-700 dark:text-orange-300 mt-0.5">
-                                {p.details}
-                              </div>
-                            )}
-                          </div>
-                        </li>
-                      ))}
-                    </ul>
-                  </div>
-                </div>
-              );
-            })()}
-
-          {/* 供应商决策链时间线 */}
-          {providerChain && providerChain.length > 0 && (
-            <div className="space-y-2">
-              <h4 className="font-semibold text-sm">{t("logs.details.providerChain.title")}</h4>
-
-              {(() => {
-                const { timeline, totalDuration } = formatProviderTimeline(providerChain, tChain);
-                return (
-                  <>
-                    <div className="rounded-md border bg-muted/50 p-4 max-h-[500px] overflow-y-auto overflow-x-hidden">
-                      <pre className="text-xs whitespace-pre-wrap break-words font-mono leading-relaxed">
-                        {timeline}
-                      </pre>
-                    </div>
-
-                    {totalDuration > 0 && (
-                      <div className="text-xs text-muted-foreground text-right">
-                        {t("logs.details.providerChain.totalDuration", { duration: totalDuration })}
-                      </div>
-                    )}
-                  </>
-                );
-              })()}
-            </div>
-          )}
-
-          {/* 无错误信息的情况 */}
-          {!errorMessage && (!providerChain || providerChain.length === 0) && (
-            <div className="text-center py-8 text-muted-foreground">
-              {isInProgress
-                ? t("logs.details.noError.processing")
-                : isSuccess
-                  ? t("logs.details.noError.success")
-                  : t("logs.details.noError.default")}
-            </div>
-          )}
-        </div>
-      </DialogContent>
-    </Dialog>
-  );
-}

+ 101 - 0
src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LatencyBreakdownBar.tsx

@@ -0,0 +1,101 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import { cn } from "@/lib/utils";
+
+interface LatencyBreakdownBarProps {
+  /** Time to first byte in milliseconds */
+  ttfbMs: number | null;
+  /** Total duration in milliseconds */
+  durationMs: number | null;
+  /** Optional className */
+  className?: string;
+  /** Whether to show labels below the bar */
+  showLabels?: boolean;
+}
+
+function formatMs(ms: number): string {
+  if (ms >= 1000) {
+    return `${(ms / 1000).toFixed(2)}s`;
+  }
+  return `${Math.round(ms)}ms`;
+}
+
+export function LatencyBreakdownBar({
+  ttfbMs,
+  durationMs,
+  className,
+  showLabels = true,
+}: LatencyBreakdownBarProps) {
+  const t = useTranslations("dashboard.logs.details.performanceTab");
+
+  // Handle null/invalid values
+  if (
+    ttfbMs === null ||
+    durationMs === null ||
+    ttfbMs < 0 ||
+    durationMs <= 0 ||
+    ttfbMs > durationMs
+  ) {
+    return null;
+  }
+
+  const generationMs = durationMs - ttfbMs;
+  const ttfbPercent = (ttfbMs / durationMs) * 100;
+  const generationPercent = 100 - ttfbPercent;
+
+  // Minimum width for visibility (3%)
+  const minWidth = 3;
+  const adjustedTtfbPercent = Math.max(ttfbPercent, ttfbMs > 0 ? minWidth : 0);
+  const adjustedGenerationPercent = Math.max(generationPercent, generationMs > 0 ? minWidth : 0);
+
+  return (
+    <div className={cn("space-y-2", className)}>
+      {/* Bar container */}
+      <div className="flex h-6 w-full overflow-hidden rounded-lg bg-muted/50">
+        {/* TTFB segment */}
+        {ttfbMs > 0 && (
+          <div
+            className="flex items-center justify-center bg-blue-500 text-white text-[10px] font-medium transition-all duration-300"
+            style={{ width: `${adjustedTtfbPercent}%` }}
+            title={`TTFB: ${formatMs(ttfbMs)} (${ttfbPercent.toFixed(1)}%)`}
+          >
+            {ttfbPercent >= 15 && <span>TTFB</span>}
+          </div>
+        )}
+
+        {/* Generation segment */}
+        {generationMs > 0 && (
+          <div
+            className="flex items-center justify-center bg-emerald-500 text-white text-[10px] font-medium transition-all duration-300"
+            style={{ width: `${adjustedGenerationPercent}%` }}
+            title={`Generation: ${formatMs(generationMs)} (${generationPercent.toFixed(1)}%)`}
+          >
+            {generationPercent >= 15 && <span>Generation</span>}
+          </div>
+        )}
+      </div>
+
+      {/* Labels */}
+      {showLabels && (
+        <div className="flex justify-between text-xs">
+          <div className="flex items-center gap-1.5">
+            <div className="h-2.5 w-2.5 rounded-sm bg-blue-500" />
+            <span className="text-muted-foreground">TTFB:</span>
+            <span className="font-mono font-medium">{formatMs(ttfbMs)}</span>
+          </div>
+          <div className="flex items-center gap-1.5">
+            <div className="h-2.5 w-2.5 rounded-sm bg-emerald-500" />
+            <span className="text-muted-foreground">{t("generationTime")}:</span>
+            <span className="font-mono font-medium">{formatMs(generationMs)}</span>
+          </div>
+        </div>
+      )}
+
+      {/* Total */}
+      <div className="text-xs text-muted-foreground text-center">
+        Total: <span className="font-mono font-medium">{formatMs(durationMs)}</span>
+      </div>
+    </div>
+  );
+}

+ 685 - 0
src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx

@@ -0,0 +1,685 @@
+"use client";
+
+import {
+  AlertCircle,
+  ArrowRight,
+  Check,
+  CheckCircle,
+  Clock,
+  Copy,
+  Database,
+  Filter,
+  GitBranch,
+  Globe,
+  Layers,
+  Link2,
+  RefreshCw,
+  Server,
+  XCircle,
+  Zap,
+} from "lucide-react";
+import { useTranslations } from "next-intl";
+import { useState } from "react";
+import { Badge } from "@/components/ui/badge";
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
+import { cn } from "@/lib/utils";
+import { redactJsonString } from "@/lib/utils/message-redaction";
+import { formatProviderTimeline } from "@/lib/utils/provider-chain-formatter";
+import type { ProviderChainItem } from "@/types/message";
+import { type LogicTraceTabProps, parseBlockedReason } from "../types";
+import { StepCard, type StepStatus } from "./StepCard";
+
+/**
+ * Redact sensitive message content in errorDetails.request.body
+ */
+function redactErrorDetails(
+  errorDetails: NonNullable<ProviderChainItem["errorDetails"]>
+): NonNullable<ProviderChainItem["errorDetails"]> {
+  if (!errorDetails.request?.body) {
+    return errorDetails;
+  }
+
+  return {
+    ...errorDetails,
+    request: {
+      ...errorDetails.request,
+      body: redactJsonString(errorDetails.request.body),
+    },
+  };
+}
+
+function getRequestStatus(item: ProviderChainItem): StepStatus {
+  // Check for session reuse first
+  if (item.reason === "session_reuse" || item.selectionMethod === "session_reuse") {
+    return "session_reuse";
+  }
+  if (item.reason === "request_success" || item.reason === "retry_success") {
+    return "success";
+  }
+  if (
+    item.reason === "retry_failed" ||
+    item.reason === "system_error" ||
+    item.reason === "client_error_non_retryable" ||
+    item.reason === "concurrent_limit_failed"
+  ) {
+    return "failure";
+  }
+  // http2_fallback and other retry-related reasons are treated as pending/in-progress
+  return "pending";
+}
+
+export function LogicTraceTab({
+  statusCode,
+  providerChain,
+  blockedBy,
+  blockedReason,
+  requestSequence,
+}: LogicTraceTabProps) {
+  const t = useTranslations("dashboard.logs.details");
+  const tChain = useTranslations("provider-chain");
+  const [timelineCopied, setTimelineCopied] = useState(false);
+
+  const handleCopyTimeline = async () => {
+    if (!providerChain) return;
+    const { timeline } = formatProviderTimeline(providerChain, tChain);
+    try {
+      await navigator.clipboard.writeText(timeline);
+      setTimelineCopied(true);
+      setTimeout(() => setTimelineCopied(false), 2000);
+    } catch {
+      // Clipboard write failed - ignore silently
+    }
+  };
+
+  const isWarmupSkipped = blockedBy === "warmup";
+  const isBlocked = !!blockedBy && !isWarmupSkipped;
+  const parsedBlockedReason = parseBlockedReason(blockedReason);
+
+  // Check if this is a session reuse flow (provider reused from session cache)
+  const isSessionReuseFlow =
+    providerChain?.[0]?.reason === "session_reuse" ||
+    providerChain?.[0]?.selectionMethod === "session_reuse";
+
+  // Extract session reuse context from first chain item
+  const sessionReuseContext = isSessionReuseFlow ? providerChain?.[0]?.decisionContext : undefined;
+  const sessionReuseProvider = isSessionReuseFlow ? providerChain?.[0] : undefined;
+
+  // Extract decision context from first chain item (not used for session reuse)
+  const decisionContext = isSessionReuseFlow ? undefined : providerChain?.[0]?.decisionContext;
+
+  // Extract filtered providers from all chain items (not applicable for session reuse)
+  const filteredProviders = isSessionReuseFlow
+    ? []
+    : providerChain?.flatMap((item) => item.decisionContext?.filteredProviders || []) || [];
+
+  // Get base timestamp for relative time calculations
+  const baseTimestamp = providerChain?.[0]?.timestamp || 0;
+
+  // Count providers at each stage
+  const totalProviders = decisionContext?.totalProviders || 0;
+  const afterHealthCheck = decisionContext?.afterHealthCheck || 0;
+
+  // Calculate step offset for session reuse flow
+  const sessionReuseStepOffset = isSessionReuseFlow ? 1 : 0;
+
+  return (
+    <div className="space-y-6">
+      {/* Warmup Skip Info */}
+      {isWarmupSkipped && (
+        <div className="rounded-lg border bg-blue-50 dark:bg-blue-950/20 border-blue-200 dark:border-blue-800 p-4">
+          <div className="flex items-center gap-2 mb-2">
+            <CheckCircle className="h-4 w-4 text-blue-600" />
+            <span className="text-sm font-medium text-blue-900 dark:text-blue-100">
+              {t("skipped.title")}
+            </span>
+            <Badge variant="outline" className="border-blue-600 text-blue-700">
+              {t("skipped.warmup")}
+            </Badge>
+          </div>
+          <p className="text-xs text-blue-800 dark:text-blue-200">{t("skipped.desc")}</p>
+        </div>
+      )}
+
+      {/* Block Info */}
+      {isBlocked && blockedBy && (
+        <div className="rounded-lg border bg-orange-50 dark:bg-orange-950/20 border-orange-200 dark:border-orange-800 p-4 space-y-2">
+          <div className="flex items-center gap-2">
+            <AlertCircle className="h-4 w-4 text-orange-600" />
+            <span className="text-sm font-medium text-orange-900 dark:text-orange-100">
+              {t("blocked.title")}
+            </span>
+            <Badge variant="outline" className="border-orange-600 text-orange-600">
+              {blockedBy === "sensitive_word" ? t("blocked.sensitiveWord") : blockedBy}
+            </Badge>
+          </div>
+          {parsedBlockedReason && (
+            <div className="space-y-1 text-xs">
+              {parsedBlockedReason.word && (
+                <div className="flex items-center gap-2">
+                  <span className="text-orange-900 dark:text-orange-100">{t("blocked.word")}:</span>
+                  <code className="bg-orange-100 dark:bg-orange-900/50 px-2 py-0.5 rounded">
+                    {parsedBlockedReason.word}
+                  </code>
+                </div>
+              )}
+              {parsedBlockedReason.matchedText && (
+                <div className="mt-2">
+                  <span className="text-orange-900 dark:text-orange-100">
+                    {t("blocked.matchedText")}:
+                  </span>
+                  <pre className="bg-orange-100 dark:bg-orange-900/50 px-2 py-1 rounded mt-1 whitespace-pre-wrap break-words">
+                    {parsedBlockedReason.matchedText}
+                  </pre>
+                </div>
+              )}
+            </div>
+          )}
+        </div>
+      )}
+
+      {/* Decision Chain Header */}
+      {providerChain && providerChain.length > 0 && (
+        <div className="space-y-2">
+          <div className="flex items-center justify-between">
+            <h4 className="text-sm font-semibold flex items-center gap-2">
+              {isSessionReuseFlow ? (
+                <Link2 className="h-4 w-4 text-violet-600" />
+              ) : (
+                <GitBranch className="h-4 w-4 text-blue-600" />
+              )}
+              {t("logicTrace.title")}
+            </h4>
+            <div className="flex items-center gap-2 text-xs text-muted-foreground">
+              {isSessionReuseFlow ? (
+                <Badge
+                  variant="outline"
+                  className="text-[10px] bg-violet-50 dark:bg-violet-950/20 border-violet-200 dark:border-violet-800 text-violet-700 dark:text-violet-300"
+                >
+                  {t("logicTrace.sessionReuse")}
+                </Badge>
+              ) : (
+                <>
+                  <Badge variant="outline" className="text-[10px]">
+                    {t("logicTrace.providersCount", { count: totalProviders })}
+                  </Badge>
+                  {afterHealthCheck > 0 && (
+                    <Badge
+                      variant="outline"
+                      className="text-[10px] bg-emerald-50 dark:bg-emerald-950/20"
+                    >
+                      {t("logicTrace.healthyCount", { count: afterHealthCheck })}
+                    </Badge>
+                  )}
+                </>
+              )}
+            </div>
+          </div>
+        </div>
+      )}
+
+      {/* Decision Steps */}
+      {providerChain && providerChain.length > 0 && (
+        <div className="space-y-0">
+          {/* Session Reuse Step (Step 1 for session reuse flow) */}
+          {isSessionReuseFlow && sessionReuseProvider && (
+            <StepCard
+              step={1}
+              icon={Link2}
+              title={t("logicTrace.sessionReuseSelection")}
+              subtitle={t("logicTrace.sessionReuseSelectionDesc")}
+              status="session_reuse"
+              details={
+                <div className="space-y-3 text-xs">
+                  {/* Session Information */}
+                  <div>
+                    <div className="flex items-center gap-1 text-violet-600 dark:text-violet-400 mb-2">
+                      <Database className="h-3 w-3" />
+                      <span className="font-medium">{t("logicTrace.sessionInfo")}</span>
+                    </div>
+                    <div className="grid grid-cols-1 sm:grid-cols-2 gap-1.5 pl-4">
+                      {sessionReuseContext?.sessionId && (
+                        <div className="flex items-center gap-2">
+                          <span className="text-muted-foreground">
+                            {t("logicTrace.sessionIdLabel")}:
+                          </span>
+                          <code className="text-[10px] px-1.5 py-0.5 bg-violet-100 dark:bg-violet-900/30 rounded font-mono truncate max-w-[120px]">
+                            {sessionReuseContext.sessionId.slice(0, 8)}...
+                          </code>
+                        </div>
+                      )}
+                      {requestSequence !== undefined && requestSequence !== null && (
+                        <div className="flex items-center gap-2">
+                          <span className="text-muted-foreground">
+                            {t("logicTrace.requestSequence")}:
+                          </span>
+                          <span className="font-mono">#{requestSequence}</span>
+                        </div>
+                      )}
+                      {sessionReuseContext?.sessionAge !== undefined && (
+                        <div className="flex items-center gap-2">
+                          <span className="text-muted-foreground">
+                            {t("logicTrace.sessionAge")}:
+                          </span>
+                          <span className="font-mono">{sessionReuseContext.sessionAge}s</span>
+                        </div>
+                      )}
+                    </div>
+                  </div>
+
+                  {/* Reused Provider Information */}
+                  <div className="pt-2 border-t border-muted/50">
+                    <div className="flex items-center gap-1 text-violet-600 dark:text-violet-400 mb-2">
+                      <Server className="h-3 w-3" />
+                      <span className="font-medium">{t("logicTrace.reusedProvider")}</span>
+                    </div>
+                    <div className="grid grid-cols-2 gap-1.5 pl-4">
+                      <div>
+                        <span className="text-muted-foreground">Provider:</span>{" "}
+                        <span className="font-medium">{sessionReuseProvider.name}</span>
+                      </div>
+                      <div>
+                        <span className="text-muted-foreground">ID:</span>{" "}
+                        <span className="font-mono">{sessionReuseProvider.id}</span>
+                      </div>
+                      {sessionReuseProvider.priority !== undefined && (
+                        <div>
+                          <span className="text-muted-foreground">
+                            {tChain("details.priority")}:
+                          </span>{" "}
+                          <span className="font-mono">P{sessionReuseProvider.priority}</span>
+                        </div>
+                      )}
+                      {sessionReuseProvider.costMultiplier !== undefined && (
+                        <div>
+                          <span className="text-muted-foreground">
+                            {tChain("details.costMultiplier")}:
+                          </span>{" "}
+                          <span className="font-mono">x{sessionReuseProvider.costMultiplier}</span>
+                        </div>
+                      )}
+                    </div>
+                  </div>
+
+                  {/* Cache Optimization Hint */}
+                  <div className="pt-2 border-t border-muted/50">
+                    <div className="flex items-start gap-2 text-muted-foreground">
+                      <Zap className="h-3 w-3 mt-0.5 shrink-0 text-violet-500" />
+                      <span className="text-[10px]">{t("logicTrace.cacheOptimizationHint")}</span>
+                    </div>
+                  </div>
+                </div>
+              }
+            />
+          )}
+
+          {/* Step 1: Initial Selection (only for non-session-reuse flow) */}
+          {decisionContext && (
+            <StepCard
+              step={1}
+              icon={Database}
+              title={t("logicTrace.initialSelection")}
+              subtitle={`${decisionContext.totalProviders} -> ${decisionContext.afterModelFilter || decisionContext.afterHealthCheck}`}
+              status="success"
+              details={
+                <div className="grid grid-cols-2 gap-2 text-xs">
+                  <div>
+                    <span className="text-muted-foreground">Total:</span>{" "}
+                    <span className="font-mono">{decisionContext.totalProviders}</span>
+                  </div>
+                  <div>
+                    <span className="text-muted-foreground">Enabled:</span>{" "}
+                    <span className="font-mono">{decisionContext.enabledProviders}</span>
+                  </div>
+                  {decisionContext.afterGroupFilter !== undefined && (
+                    <div>
+                      <span className="text-muted-foreground">After Group:</span>{" "}
+                      <span className="font-mono">{decisionContext.afterGroupFilter}</span>
+                    </div>
+                  )}
+                  {decisionContext.afterModelFilter !== undefined && (
+                    <div>
+                      <span className="text-muted-foreground">After Model:</span>{" "}
+                      <span className="font-mono">{decisionContext.afterModelFilter}</span>
+                    </div>
+                  )}
+                </div>
+              }
+            />
+          )}
+
+          {/* Step 2: Health Check (if there are filtered providers) */}
+          {filteredProviders.length > 0 && (
+            <StepCard
+              step={2}
+              icon={Filter}
+              title={t("logicTrace.healthCheck")}
+              subtitle={`${filteredProviders.length} providers filtered`}
+              status="warning"
+              details={
+                <div className="space-y-1">
+                  {filteredProviders.map((p, idx) => (
+                    <div key={`${p.id}-${idx}`} className="flex items-center gap-2 text-xs">
+                      <Badge variant="outline" className="text-[10px]">
+                        {p.name}
+                      </Badge>
+                      <span className="text-rose-600">{tChain(`filterReasons.${p.reason}`)}</span>
+                      {p.details && <span className="text-muted-foreground">({p.details})</span>}
+                    </div>
+                  ))}
+                </div>
+              }
+            />
+          )}
+
+          {/* Step 3: Priority Selection */}
+          {decisionContext?.priorityLevels && decisionContext.priorityLevels.length > 0 && (
+            <StepCard
+              step={filteredProviders.length > 0 ? 3 : 2}
+              icon={Layers}
+              title={t("logicTrace.prioritySelection")}
+              subtitle={`Priority ${decisionContext.selectedPriority}`}
+              status="success"
+              details={
+                <div className="space-y-2">
+                  <div className="flex gap-1 flex-wrap">
+                    {decisionContext.priorityLevels.map((p) => (
+                      <Badge
+                        key={p}
+                        variant={p === decisionContext?.selectedPriority ? "default" : "outline"}
+                        className="text-[10px]"
+                      >
+                        P{p}
+                      </Badge>
+                    ))}
+                  </div>
+                  {decisionContext.candidatesAtPriority &&
+                    decisionContext.candidatesAtPriority.length > 0 && (
+                      <div className="space-y-1 mt-2">
+                        {decisionContext.candidatesAtPriority.map((c, idx) => (
+                          <div
+                            key={`${c.id}-${idx}`}
+                            className="flex items-center justify-between text-xs"
+                          >
+                            <span className="font-medium">{c.name}</span>
+                            <div className="flex items-center gap-2">
+                              <span className="text-muted-foreground">W:{c.weight}</span>
+                              <span className="text-muted-foreground">x{c.costMultiplier}</span>
+                              {c.probability !== undefined && (
+                                <Badge variant="secondary" className="text-[10px]">
+                                  {(c.probability * 100).toFixed(1)}%
+                                </Badge>
+                              )}
+                            </div>
+                          </div>
+                        ))}
+                      </div>
+                    )}
+                </div>
+              }
+            />
+          )}
+
+          {/* Request Execution Steps */}
+          {providerChain.map((item, index) => {
+            // For session reuse flow, step numbering starts from 2 (session reuse is step 1)
+            // For normal flow, calculate based on decision steps
+            const stepNum = isSessionReuseFlow
+              ? sessionReuseStepOffset + index + 1
+              : (decisionContext ? 1 : 0) +
+                (filteredProviders.length > 0 ? 1 : 0) +
+                (decisionContext?.priorityLevels?.length ? 1 : 0) +
+                index +
+                1;
+
+            const status = getRequestStatus(item);
+            const isRetry = item.attemptNumber && item.attemptNumber > 1;
+            const isSessionReuse =
+              item.reason === "session_reuse" || item.selectionMethod === "session_reuse";
+
+            // Determine icon based on type
+            const stepIcon = isSessionReuse
+              ? Link2
+              : isRetry
+                ? RefreshCw
+                : status === "success"
+                  ? CheckCircle
+                  : status === "failure"
+                    ? XCircle
+                    : Server;
+
+            // Determine title based on type
+            // For session reuse flow, show simplified "Execute Request" title for the first item
+            const stepTitle = isSessionReuse
+              ? t("logicTrace.executeRequest")
+              : isRetry
+                ? t("logicTrace.retryAttempt", { number: item.attemptNumber ?? 1 })
+                : t("logicTrace.attemptProvider", { provider: item.name });
+
+            return (
+              <StepCard
+                key={`${item.id}-${index}`}
+                step={stepNum}
+                icon={stepIcon}
+                title={stepTitle}
+                subtitle={
+                  isSessionReuse
+                    ? item.statusCode
+                      ? `HTTP ${item.statusCode}`
+                      : item.name
+                    : item.statusCode
+                      ? `HTTP ${item.statusCode}`
+                      : item.reason
+                        ? tChain(`reasons.${item.reason}`)
+                        : undefined
+                }
+                status={status}
+                timestamp={item.timestamp}
+                baseTimestamp={baseTimestamp}
+                isLast={index === providerChain.length - 1}
+                details={
+                  <div className="space-y-2 text-xs">
+                    {/* Session Reuse Info */}
+                    {isSessionReuse && item.decisionContext && (
+                      <div className="pb-2 border-b border-muted/50">
+                        <div className="flex items-center gap-1 text-violet-600 dark:text-violet-400 mb-2">
+                          <Link2 className="h-3 w-3" />
+                          <span className="font-medium">{t("logicTrace.sessionReuseTitle")}</span>
+                        </div>
+                        <div className="grid grid-cols-1 gap-1.5">
+                          {item.decisionContext.sessionId && (
+                            <div className="flex items-center gap-2">
+                              <span className="text-muted-foreground">
+                                {tChain("timeline.sessionId", { id: "" }).replace(": ", ":")}
+                              </span>
+                              <code className="text-[10px] px-1.5 py-0.5 bg-violet-100 dark:bg-violet-900/30 rounded font-mono">
+                                {item.decisionContext.sessionId}
+                              </code>
+                            </div>
+                          )}
+                          <div className="text-muted-foreground text-[10px] mt-1">
+                            {tChain("timeline.basedOnCache")}
+                          </div>
+                        </div>
+                      </div>
+                    )}
+
+                    {/* Basic Info */}
+                    <div className="grid grid-cols-2 gap-2">
+                      <div>
+                        <span className="text-muted-foreground">Provider ID:</span>{" "}
+                        <span className="font-mono">{item.id}</span>
+                      </div>
+                      {item.selectionMethod && !isSessionReuse && (
+                        <div>
+                          <span className="text-muted-foreground">
+                            {tChain("details.selectionMethod")}:
+                          </span>{" "}
+                          <span className="font-mono">{item.selectionMethod}</span>
+                        </div>
+                      )}
+                      {isSessionReuse && (
+                        <div>
+                          <span className="text-muted-foreground">Provider:</span>{" "}
+                          <span className="font-mono">{item.name}</span>
+                        </div>
+                      )}
+                    </div>
+
+                    {/* Endpoint */}
+                    {(item.endpointId || item.endpointUrl) && (
+                      <div className="pt-2 border-t border-muted/50">
+                        <div className="flex items-center gap-1 text-muted-foreground mb-1">
+                          <Globe className="h-3 w-3" />
+                          <span>{tChain("details.endpoint")}</span>
+                        </div>
+                        {item.endpointUrl && (
+                          <code className="text-[10px] break-all">{item.endpointUrl}</code>
+                        )}
+                      </div>
+                    )}
+
+                    {/* Circuit Breaker */}
+                    {(item.circuitState || item.circuitFailureCount !== undefined) && (
+                      <div className="pt-2 border-t border-muted/50">
+                        <div className="flex items-center gap-2 flex-wrap">
+                          <div className="flex items-center gap-1 text-muted-foreground">
+                            <Zap className="h-3 w-3" />
+                            <span>{tChain("details.circuitBreaker")}:</span>
+                          </div>
+                          {item.circuitState && (
+                            <Badge
+                              variant={
+                                item.circuitState === "closed"
+                                  ? "default"
+                                  : item.circuitState === "open"
+                                    ? "destructive"
+                                    : "secondary"
+                              }
+                              className="text-[10px]"
+                            >
+                              {item.circuitState}
+                            </Badge>
+                          )}
+                          {item.circuitFailureCount !== undefined &&
+                            item.circuitFailureThreshold !== undefined && (
+                              <span className="font-mono text-muted-foreground">
+                                {item.circuitFailureCount}/{item.circuitFailureThreshold}{" "}
+                                {tChain("details.failures")}
+                              </span>
+                            )}
+                        </div>
+                      </div>
+                    )}
+
+                    {/* Model Redirect */}
+                    {item.modelRedirect && (
+                      <div className="pt-2 border-t border-muted/50">
+                        <div className="flex items-center gap-1 text-muted-foreground mb-1">
+                          <ArrowRight className="h-3 w-3" />
+                          <span>{tChain("details.modelRedirect")}</span>
+                        </div>
+                        <div className="flex items-center gap-2">
+                          <code className="text-[10px] px-1 py-0.5 bg-muted rounded">
+                            {item.modelRedirect.originalModel}
+                          </code>
+                          <ArrowRight className="h-3 w-3 text-muted-foreground" />
+                          <code className="text-[10px] px-1 py-0.5 bg-muted rounded">
+                            {item.modelRedirect.redirectedModel}
+                          </code>
+                        </div>
+                      </div>
+                    )}
+
+                    {/* Error Message */}
+                    {item.errorMessage && (
+                      <div className="pt-2 border-t border-muted/50">
+                        <div className="flex items-center gap-1 text-rose-600 mb-1">
+                          <AlertCircle className="h-3 w-3" />
+                          <span>{tChain("details.error")}</span>
+                        </div>
+                        <pre className="text-[10px] bg-rose-50 dark:bg-rose-950/20 p-2 rounded whitespace-pre-wrap break-words">
+                          {item.errorMessage}
+                        </pre>
+                      </div>
+                    )}
+
+                    {/* Error Details */}
+                    {item.errorDetails && (
+                      <Collapsible>
+                        <CollapsibleTrigger className="flex items-center gap-1 text-muted-foreground hover:text-foreground text-[10px]">
+                          <span>{tChain("details.errorDetails")}</span>
+                        </CollapsibleTrigger>
+                        <CollapsibleContent className="mt-1">
+                          <pre className="text-[10px] bg-rose-50 dark:bg-rose-950/20 p-2 rounded whitespace-pre-wrap break-words font-mono">
+                            {JSON.stringify(redactErrorDetails(item.errorDetails), null, 2)}
+                          </pre>
+                        </CollapsibleContent>
+                      </Collapsible>
+                    )}
+                  </div>
+                }
+              />
+            );
+          })}
+        </div>
+      )}
+
+      {/* No Data */}
+      {(!providerChain || providerChain.length === 0) && !isWarmupSkipped && !isBlocked && (
+        <div className="text-center py-8 text-muted-foreground">
+          <GitBranch className="h-8 w-8 mx-auto mb-2 opacity-50" />
+          <p className="text-sm">{t("logicTrace.noDecisionData")}</p>
+        </div>
+      )}
+
+      {/* Technical Timeline */}
+      {providerChain && providerChain.length > 0 && (
+        <div className="space-y-2 mt-6 pt-6 border-t">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <Clock className="h-4 w-4 text-slate-600" />
+            {t("metadata.technicalTimeline")}
+          </h4>
+          <Collapsible defaultOpen>
+            <CollapsibleTrigger className="text-xs text-muted-foreground hover:text-foreground">
+              {tChain("technicalTimeline")}
+            </CollapsibleTrigger>
+            <CollapsibleContent className="mt-2">
+              {(() => {
+                const { timeline, totalDuration } = formatProviderTimeline(providerChain, tChain);
+                return (
+                  <>
+                    <div className="rounded-lg border bg-muted/50 p-4 max-h-[400px] overflow-y-auto overflow-x-hidden relative group">
+                      <button
+                        type="button"
+                        onClick={handleCopyTimeline}
+                        className={cn(
+                          "absolute top-2 right-2 p-1.5 rounded-md bg-background/80 border transition-opacity hover:bg-muted",
+                          timelineCopied ? "opacity-100" : "opacity-0 group-hover:opacity-100"
+                        )}
+                        title={t("metadata.copyTimeline")}
+                      >
+                        {timelineCopied ? (
+                          <Check className="h-3.5 w-3.5 text-emerald-600" />
+                        ) : (
+                          <Copy className="h-3.5 w-3.5" />
+                        )}
+                      </button>
+                      <pre className="text-xs whitespace-pre-wrap break-words font-mono leading-relaxed">
+                        {timeline}
+                      </pre>
+                    </div>
+                    {totalDuration > 0 && (
+                      <div className="text-xs text-muted-foreground text-right mt-1">
+                        {t("providerChain.totalDuration", { duration: totalDuration })}
+                      </div>
+                    )}
+                  </>
+                );
+              })()}
+            </CollapsibleContent>
+          </Collapsible>
+        </div>
+      )}
+    </div>
+  );
+}

+ 322 - 0
src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/MetadataTab.tsx

@@ -0,0 +1,322 @@
+"use client";
+
+import {
+  Check,
+  Clock,
+  Copy,
+  DollarSign,
+  ExternalLink,
+  Globe,
+  Inbox,
+  Monitor,
+  Settings2,
+} from "lucide-react";
+import { useTranslations } from "next-intl";
+import { useState } from "react";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
+import { Link } from "@/i18n/routing";
+import { cn, formatTokenAmount } from "@/lib/utils";
+import { formatCurrency } from "@/lib/utils/currency";
+import { formatProviderTimeline } from "@/lib/utils/provider-chain-formatter";
+import type { MetadataTabProps } from "../types";
+
+export function MetadataTab({
+  sessionId,
+  requestSequence,
+  userAgent,
+  endpoint,
+  specialSettings,
+  inputTokens,
+  outputTokens,
+  cacheCreationInputTokens,
+  cacheCreation5mInputTokens,
+  cacheCreation1hInputTokens,
+  cacheReadInputTokens,
+  cacheTtlApplied,
+  costUsd,
+  costMultiplier,
+  context1mApplied,
+  providerChain,
+  hasMessages,
+  checkingMessages,
+}: MetadataTabProps) {
+  const t = useTranslations("dashboard.logs.details");
+  const tChain = useTranslations("provider-chain");
+  const [timelineCopied, setTimelineCopied] = useState(false);
+
+  const specialSettingsContent =
+    specialSettings && specialSettings.length > 0 ? JSON.stringify(specialSettings, null, 2) : null;
+
+  const handleCopyTimeline = () => {
+    if (!providerChain) return;
+    const { timeline } = formatProviderTimeline(providerChain, tChain);
+    navigator.clipboard.writeText(timeline).then(() => {
+      setTimelineCopied(true);
+      setTimeout(() => setTimelineCopied(false), 2000);
+    });
+  };
+
+  const hasAnyData =
+    sessionId ||
+    userAgent ||
+    endpoint ||
+    specialSettingsContent ||
+    costUsd != null ||
+    (providerChain && providerChain.length > 0);
+
+  if (!hasAnyData) {
+    return (
+      <div className="text-center py-8 text-muted-foreground">
+        <Inbox className="h-8 w-8 mx-auto mb-2 opacity-50" />
+        <p className="text-sm">{t("metadata.noMetadata")}</p>
+      </div>
+    );
+  }
+
+  return (
+    <div className="space-y-6">
+      {/* Session Info */}
+      {sessionId && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold">{t("metadata.sessionInfo")}</h4>
+          <div className="rounded-lg border bg-card p-4">
+            <div className="flex items-center gap-3">
+              <div className="flex-1 min-w-0">
+                <div className="flex items-center gap-2">
+                  <code className="text-xs font-mono break-all">{sessionId}</code>
+                  {requestSequence && (
+                    <Badge variant="outline" className="text-xs shrink-0">
+                      #{requestSequence}
+                    </Badge>
+                  )}
+                </div>
+              </div>
+              {hasMessages && !checkingMessages && (
+                <Link
+                  href={
+                    requestSequence
+                      ? `/dashboard/sessions/${sessionId}/messages?seq=${requestSequence}`
+                      : `/dashboard/sessions/${sessionId}/messages`
+                  }
+                >
+                  <Button variant="outline" size="sm">
+                    <ExternalLink className="h-4 w-4 mr-2" />
+                    {t("viewDetails")}
+                  </Button>
+                </Link>
+              )}
+            </div>
+          </div>
+        </div>
+      )}
+
+      {/* Client Info */}
+      {(userAgent || endpoint) && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <Monitor className="h-4 w-4 text-blue-600" />
+            {t("metadata.clientInfo")}
+          </h4>
+          <div className="rounded-lg border bg-card divide-y">
+            {userAgent && (
+              <div className="p-3">
+                <p className="text-xs text-muted-foreground mb-1">User-Agent</p>
+                <code className="text-xs font-mono break-all">{userAgent}</code>
+              </div>
+            )}
+            {endpoint && (
+              <div className="p-3">
+                <p className="text-xs text-muted-foreground mb-1 flex items-center gap-1">
+                  <Globe className="h-3 w-3" />
+                  Endpoint
+                </p>
+                <code className="text-xs font-mono break-all">{endpoint}</code>
+              </div>
+            )}
+          </div>
+        </div>
+      )}
+
+      {/* Billing Details */}
+      {costUsd != null && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <DollarSign className="h-4 w-4 text-emerald-600" />
+            {t("metadata.billingInfo")}
+          </h4>
+          <div className="rounded-lg border bg-card p-4 space-y-3">
+            {/* Token breakdown */}
+            <div className="grid grid-cols-2 gap-x-6 gap-y-2 text-sm">
+              <div className="flex justify-between">
+                <span className="text-muted-foreground">{t("billingDetails.input")}:</span>
+                <span className="font-mono">{formatTokenAmount(inputTokens)} tokens</span>
+              </div>
+              <div className="flex justify-between">
+                <span className="text-muted-foreground">{t("billingDetails.output")}:</span>
+                <span className="font-mono">{formatTokenAmount(outputTokens)} tokens</span>
+              </div>
+
+              {/* Cache Write 5m */}
+              {((cacheCreation5mInputTokens ?? 0) > 0 ||
+                ((cacheCreationInputTokens ?? 0) > 0 && cacheTtlApplied !== "1h")) && (
+                <div className="flex justify-between col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.cacheWrite5m")}:</span>
+                  <span className="font-mono">
+                    {formatTokenAmount(
+                      (cacheCreation5mInputTokens ?? 0) > 0
+                        ? cacheCreation5mInputTokens
+                        : cacheCreationInputTokens
+                    )}{" "}
+                    tokens <span className="text-orange-600">(1.25x)</span>
+                  </span>
+                </div>
+              )}
+
+              {/* Cache Write 1h */}
+              {((cacheCreation1hInputTokens ?? 0) > 0 ||
+                ((cacheCreationInputTokens ?? 0) > 0 && cacheTtlApplied === "1h")) && (
+                <div className="flex justify-between col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.cacheWrite1h")}:</span>
+                  <span className="font-mono">
+                    {formatTokenAmount(
+                      (cacheCreation1hInputTokens ?? 0) > 0
+                        ? cacheCreation1hInputTokens
+                        : cacheCreationInputTokens
+                    )}{" "}
+                    tokens <span className="text-orange-600">(2x)</span>
+                  </span>
+                </div>
+              )}
+
+              {/* Cache Read */}
+              {(cacheReadInputTokens ?? 0) > 0 && (
+                <div className="flex justify-between col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.cacheRead")}:</span>
+                  <span className="font-mono">
+                    {formatTokenAmount(cacheReadInputTokens)} tokens{" "}
+                    <span className="text-emerald-600">(0.1x)</span>
+                  </span>
+                </div>
+              )}
+
+              {/* Cache TTL */}
+              {cacheTtlApplied && (
+                <div className="flex justify-between items-center col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.cacheTtl")}:</span>
+                  <Badge variant="outline" className="text-xs">
+                    {cacheTtlApplied}
+                  </Badge>
+                </div>
+              )}
+
+              {/* 1M Context */}
+              {context1mApplied && (
+                <div className="flex justify-between items-center col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.context1m")}:</span>
+                  <div className="flex items-center gap-2">
+                    <Badge
+                      variant="outline"
+                      className="text-xs bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950/30 dark:text-purple-300 dark:border-purple-800"
+                    >
+                      1M Context
+                    </Badge>
+                    <span className="text-xs text-muted-foreground">
+                      ({t("billingDetails.context1mPricing")})
+                    </span>
+                  </div>
+                </div>
+              )}
+
+              {/* Cost Multiplier */}
+              {(() => {
+                if (costMultiplier === "" || costMultiplier == null) return null;
+                const multiplier = Number(costMultiplier);
+                if (!Number.isFinite(multiplier) || multiplier === 1) return null;
+                return (
+                  <div className="flex justify-between col-span-2">
+                    <span className="text-muted-foreground">{t("billingDetails.multiplier")}:</span>
+                    <span className="font-mono">{multiplier.toFixed(2)}x</span>
+                  </div>
+                );
+              })()}
+            </div>
+
+            {/* Total Cost */}
+            <div className="pt-3 border-t flex justify-between items-center">
+              <span className="font-medium">{t("billingDetails.totalCost")}:</span>
+              <span className="font-mono text-lg font-semibold text-emerald-600">
+                {formatCurrency(costUsd, "USD", 6)}
+              </span>
+            </div>
+          </div>
+        </div>
+      )}
+
+      {/* Special Settings */}
+      {specialSettingsContent && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <Settings2 className="h-4 w-4 text-purple-600" />
+            {t("specialSettings.title")}
+          </h4>
+          <div className="rounded-lg border bg-card p-4">
+            <pre className="text-xs whitespace-pre-wrap break-words font-mono">
+              {specialSettingsContent}
+            </pre>
+          </div>
+        </div>
+      )}
+
+      {/* Technical Timeline */}
+      {providerChain && providerChain.length > 0 && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <Clock className="h-4 w-4 text-slate-600" />
+            {t("metadata.technicalTimeline")}
+          </h4>
+          <Collapsible defaultOpen>
+            <CollapsibleTrigger className="text-xs text-muted-foreground hover:text-foreground">
+              {tChain("technicalTimeline")}
+            </CollapsibleTrigger>
+            <CollapsibleContent className="mt-2">
+              {(() => {
+                const { timeline, totalDuration } = formatProviderTimeline(providerChain, tChain);
+                return (
+                  <>
+                    <div className="rounded-lg border bg-muted/50 p-4 max-h-[400px] overflow-y-auto overflow-x-hidden relative group">
+                      <button
+                        type="button"
+                        onClick={handleCopyTimeline}
+                        className={cn(
+                          "absolute top-2 right-2 p-1.5 rounded-md bg-background/80 border transition-opacity hover:bg-muted",
+                          timelineCopied ? "opacity-100" : "opacity-0 group-hover:opacity-100"
+                        )}
+                        title={t("metadata.copyTimeline")}
+                      >
+                        {timelineCopied ? (
+                          <Check className="h-3.5 w-3.5 text-emerald-600" />
+                        ) : (
+                          <Copy className="h-3.5 w-3.5" />
+                        )}
+                      </button>
+                      <pre className="text-xs whitespace-pre-wrap break-words font-mono leading-relaxed">
+                        {timeline}
+                      </pre>
+                    </div>
+                    {totalDuration > 0 && (
+                      <div className="text-xs text-muted-foreground text-right mt-1">
+                        {t("providerChain.totalDuration", { duration: totalDuration })}
+                      </div>
+                    )}
+                  </>
+                );
+              })()}
+            </CollapsibleContent>
+          </Collapsible>
+        </div>
+      )}
+    </div>
+  );
+}

+ 266 - 0
src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/PerformanceTab.tsx

@@ -0,0 +1,266 @@
+"use client";
+
+import { Clock, Gauge, Zap } from "lucide-react";
+import { useTranslations } from "next-intl";
+import { Badge } from "@/components/ui/badge";
+import { CircularProgress } from "@/components/ui/circular-progress";
+import { cn, formatTokenAmount } from "@/lib/utils";
+import { calculateOutputRate, type PerformanceTabProps } from "../types";
+import { LatencyBreakdownBar } from "./LatencyBreakdownBar";
+
+/**
+ * Get TTFB performance assessment
+ * Thresholds: <1s excellent, <2s good, <3s warning, >=3s poor
+ */
+function getTtfbAssessment(ttfbMs: number | null): {
+  label: string;
+  color: string;
+  bgColor: string;
+} | null {
+  if (ttfbMs === null) return null;
+
+  if (ttfbMs < 1000) {
+    return {
+      label: "excellent",
+      color: "text-emerald-600",
+      bgColor: "bg-emerald-50 dark:bg-emerald-950/20",
+    };
+  }
+  if (ttfbMs < 2000) {
+    return {
+      label: "good",
+      color: "text-blue-600",
+      bgColor: "bg-blue-50 dark:bg-blue-950/20",
+    };
+  }
+  if (ttfbMs < 3000) {
+    return {
+      label: "warning",
+      color: "text-amber-600",
+      bgColor: "bg-amber-50 dark:bg-amber-950/20",
+    };
+  }
+  return {
+    label: "poor",
+    color: "text-rose-600",
+    bgColor: "bg-rose-50 dark:bg-rose-950/20",
+  };
+}
+
+/**
+ * Get output rate assessment
+ */
+function getOutputRateAssessment(rate: number | null): {
+  label: string;
+  color: string;
+  bgColor: string;
+} | null {
+  if (rate === null) return null;
+
+  if (rate >= 80) {
+    return {
+      label: "excellent",
+      color: "text-emerald-600",
+      bgColor: "bg-emerald-50 dark:bg-emerald-950/20",
+    };
+  }
+  if (rate >= 50) {
+    return {
+      label: "good",
+      color: "text-blue-600",
+      bgColor: "bg-blue-50 dark:bg-blue-950/20",
+    };
+  }
+  if (rate >= 30) {
+    return {
+      label: "warning",
+      color: "text-amber-600",
+      bgColor: "bg-amber-50 dark:bg-amber-950/20",
+    };
+  }
+  return {
+    label: "poor",
+    color: "text-rose-600",
+    bgColor: "bg-rose-50 dark:bg-rose-950/20",
+  };
+}
+
+export function PerformanceTab({ durationMs, ttfbMs, outputTokens }: PerformanceTabProps) {
+  const t = useTranslations("dashboard.logs.details");
+
+  // Normalize undefined to null for consistent handling
+  const normalizedDurationMs = durationMs ?? null;
+  const normalizedTtfbMs = ttfbMs ?? null;
+  const normalizedOutputTokens = outputTokens ?? null;
+
+  const outputRate = calculateOutputRate(
+    normalizedOutputTokens,
+    normalizedDurationMs,
+    normalizedTtfbMs
+  );
+  const generationMs =
+    normalizedDurationMs !== null && normalizedTtfbMs !== null
+      ? normalizedDurationMs - normalizedTtfbMs
+      : null;
+
+  const ttfbAssessment = getTtfbAssessment(normalizedTtfbMs);
+  const rateAssessment = getOutputRateAssessment(outputRate);
+
+  const hasData =
+    normalizedDurationMs !== null ||
+    normalizedTtfbMs !== null ||
+    outputRate !== null ||
+    normalizedOutputTokens !== null;
+
+  if (!hasData) {
+    return (
+      <div className="text-center py-8 text-muted-foreground">
+        <Gauge className="h-8 w-8 mx-auto mb-2 opacity-50" />
+        <p className="text-sm">{t("performanceTab.noPerformanceData")}</p>
+      </div>
+    );
+  }
+
+  return (
+    <div className="space-y-6">
+      {/* Gauges Row */}
+      <div className="flex flex-col sm:flex-row gap-4">
+        {/* TTFB Gauge */}
+        {normalizedTtfbMs !== null && (
+          <div
+            className={cn(
+              "flex-1 flex items-center gap-4 p-4 rounded-lg border",
+              ttfbAssessment?.bgColor || "bg-muted/50"
+            )}
+          >
+            <div className="relative">
+              <CircularProgress
+                value={Math.min(normalizedTtfbMs, 3000)}
+                max={3000}
+                size={64}
+                strokeWidth={5}
+                showPercentage={false}
+              />
+              <div className="absolute inset-0 flex items-center justify-center">
+                <Clock
+                  className={cn("h-5 w-5", ttfbAssessment?.color || "text-muted-foreground")}
+                />
+              </div>
+            </div>
+            <div className="flex-1 min-w-0">
+              <p className="text-xs text-muted-foreground">{t("performanceTab.ttfbGauge")}</p>
+              <p className="text-xl font-bold font-mono">
+                {normalizedTtfbMs >= 1000
+                  ? `${(normalizedTtfbMs / 1000).toFixed(2)}s`
+                  : `${Math.round(normalizedTtfbMs)}ms`}
+              </p>
+              {ttfbAssessment && (
+                <Badge variant="outline" className={cn("text-[10px] mt-1", ttfbAssessment.color)}>
+                  {t(`performanceTab.assessment.${ttfbAssessment.label}`)}
+                </Badge>
+              )}
+            </div>
+          </div>
+        )}
+
+        {/* Output Rate Gauge */}
+        {outputRate !== null && (
+          <div
+            className={cn(
+              "flex-1 flex items-center gap-4 p-4 rounded-lg border",
+              rateAssessment?.bgColor || "bg-muted/50"
+            )}
+          >
+            <div className="relative">
+              <CircularProgress
+                value={Math.min(outputRate, 100)}
+                max={100}
+                size={64}
+                strokeWidth={5}
+                showPercentage={false}
+              />
+              <div className="absolute inset-0 flex items-center justify-center">
+                <Zap className={cn("h-5 w-5", rateAssessment?.color || "text-muted-foreground")} />
+              </div>
+            </div>
+            <div className="flex-1 min-w-0">
+              <p className="text-xs text-muted-foreground">{t("performanceTab.outputRateGauge")}</p>
+              <p className="text-xl font-bold font-mono">{outputRate.toFixed(1)} tok/s</p>
+              {rateAssessment && (
+                <Badge variant="outline" className={cn("text-[10px] mt-1", rateAssessment.color)}>
+                  {t(`performanceTab.assessment.${rateAssessment.label}`)}
+                </Badge>
+              )}
+            </div>
+          </div>
+        )}
+      </div>
+
+      {/* Latency Breakdown Bar */}
+      {normalizedTtfbMs !== null && normalizedDurationMs !== null && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <Gauge className="h-4 w-4 text-purple-600" />
+            {t("performanceTab.latencyBreakdown")}
+          </h4>
+          <div className="p-4 rounded-lg border bg-card">
+            <LatencyBreakdownBar ttfbMs={normalizedTtfbMs} durationMs={normalizedDurationMs} />
+          </div>
+        </div>
+      )}
+
+      {/* Detailed Metrics Table */}
+      <div className="space-y-2">
+        <h4 className="text-sm font-semibold">{t("performance.title")}</h4>
+        <div className="rounded-lg border bg-card divide-y">
+          {normalizedTtfbMs !== null && (
+            <div className="flex justify-between items-center px-4 py-3">
+              <span className="text-sm text-muted-foreground">{t("performance.ttfb")}</span>
+              <span className="text-sm font-mono font-medium">
+                {normalizedTtfbMs >= 1000
+                  ? `${(normalizedTtfbMs / 1000).toFixed(2)}s`
+                  : `${Math.round(normalizedTtfbMs)}ms`}
+              </span>
+            </div>
+          )}
+          {generationMs !== null && (
+            <div className="flex justify-between items-center px-4 py-3">
+              <span className="text-sm text-muted-foreground">
+                {t("performanceTab.generationTime")}
+              </span>
+              <span className="text-sm font-mono font-medium">
+                {generationMs >= 1000
+                  ? `${(generationMs / 1000).toFixed(2)}s`
+                  : `${Math.round(generationMs)}ms`}
+              </span>
+            </div>
+          )}
+          {normalizedDurationMs !== null && (
+            <div className="flex justify-between items-center px-4 py-3">
+              <span className="text-sm text-muted-foreground">{t("performance.duration")}</span>
+              <span className="text-sm font-mono font-medium">
+                {normalizedDurationMs >= 1000
+                  ? `${(normalizedDurationMs / 1000).toFixed(2)}s`
+                  : `${Math.round(normalizedDurationMs)}ms`}
+              </span>
+            </div>
+          )}
+          {normalizedOutputTokens !== null && (
+            <div className="flex justify-between items-center px-4 py-3">
+              <span className="text-sm text-muted-foreground">Output Tokens</span>
+              <span className="text-sm font-mono font-medium">
+                {formatTokenAmount(normalizedOutputTokens)}
+              </span>
+            </div>
+          )}
+          {outputRate !== null && (
+            <div className="flex justify-between items-center px-4 py-3">
+              <span className="text-sm text-muted-foreground">{t("performance.outputRate")}</span>
+              <span className="text-sm font-mono font-medium">{outputRate.toFixed(1)} tok/s</span>
+            </div>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+}

+ 182 - 0
src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/StepCard.tsx

@@ -0,0 +1,182 @@
+"use client";
+
+import type { LucideIcon } from "lucide-react";
+import { ChevronDown, ChevronRight } from "lucide-react";
+import { useState } from "react";
+import { cn } from "@/lib/utils";
+
+export type StepStatus =
+  | "success"
+  | "failure"
+  | "warning"
+  | "pending"
+  | "skipped"
+  | "session_reuse";
+
+interface StepCardProps {
+  /** Step number (1-based) */
+  step: number;
+  /** Icon to display */
+  icon: LucideIcon;
+  /** Step title */
+  title: string;
+  /** Optional subtitle */
+  subtitle?: string;
+  /** Step status */
+  status: StepStatus;
+  /** Timestamp offset in ms (relative to baseTimestamp) */
+  timestamp?: number;
+  /** Base timestamp for calculating relative time */
+  baseTimestamp?: number;
+  /** Expandable details content */
+  details?: React.ReactNode;
+  /** Whether this is the last step (hides connector line) */
+  isLast?: boolean;
+  /** Optional className */
+  className?: string;
+}
+
+const statusConfig: Record<StepStatus, { dot: string; bg: string; text: string }> = {
+  success: {
+    dot: "bg-emerald-500",
+    bg: "bg-emerald-50 dark:bg-emerald-950/20 border-emerald-200 dark:border-emerald-800",
+    text: "text-emerald-700 dark:text-emerald-300",
+  },
+  failure: {
+    dot: "bg-rose-500",
+    bg: "bg-rose-50 dark:bg-rose-950/20 border-rose-200 dark:border-rose-800",
+    text: "text-rose-700 dark:text-rose-300",
+  },
+  warning: {
+    dot: "bg-amber-500",
+    bg: "bg-amber-50 dark:bg-amber-950/20 border-amber-200 dark:border-amber-800",
+    text: "text-amber-700 dark:text-amber-300",
+  },
+  pending: {
+    dot: "bg-slate-400",
+    bg: "bg-slate-50 dark:bg-slate-800/50 border-slate-200 dark:border-slate-700",
+    text: "text-slate-600 dark:text-slate-400",
+  },
+  skipped: {
+    dot: "bg-blue-500",
+    bg: "bg-blue-50 dark:bg-blue-950/20 border-blue-200 dark:border-blue-800",
+    text: "text-blue-700 dark:text-blue-300",
+  },
+  session_reuse: {
+    dot: "bg-violet-500",
+    bg: "bg-violet-50 dark:bg-violet-950/20 border-violet-200 dark:border-violet-800",
+    text: "text-violet-700 dark:text-violet-300",
+  },
+};
+
+export function StepCard({
+  step,
+  icon: Icon,
+  title,
+  subtitle,
+  status,
+  timestamp,
+  baseTimestamp,
+  details,
+  isLast = false,
+  className,
+}: StepCardProps) {
+  const [isExpanded, setIsExpanded] = useState(false);
+  const config = statusConfig[status];
+
+  const relativeTime =
+    timestamp !== undefined && baseTimestamp !== undefined ? timestamp - baseTimestamp : null;
+
+  const hasDetails = !!details;
+
+  return (
+    <div className={cn("relative flex gap-3", className)}>
+      {/* Left side: Step indicator and connector line */}
+      <div className="flex flex-col items-center">
+        {/* Step number circle */}
+        <div
+          className={cn(
+            "flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2",
+            config.bg
+          )}
+        >
+          <span className={cn("text-xs font-semibold", config.text)}>{step}</span>
+        </div>
+
+        {/* Connector line */}
+        {!isLast && (
+          <div
+            className={cn(
+              "w-0.5 flex-1 min-h-[20px]",
+              status === "skipped"
+                ? "border-l-2 border-dashed border-slate-300 dark:border-slate-600"
+                : config.dot
+            )}
+          />
+        )}
+      </div>
+
+      {/* Right side: Content card */}
+      <div className={cn("flex-1 pb-4", isLast && "pb-0")}>
+        <div
+          className={cn(
+            "rounded-lg border p-3 sm:p-4 transition-colors",
+            config.bg,
+            hasDetails && "cursor-pointer hover:shadow-sm"
+          )}
+          onClick={hasDetails ? () => setIsExpanded(!isExpanded) : undefined}
+          onKeyDown={
+            hasDetails
+              ? (e) => {
+                  if (e.key === "Enter" || e.key === " ") {
+                    e.preventDefault();
+                    setIsExpanded(!isExpanded);
+                  }
+                }
+              : undefined
+          }
+          tabIndex={hasDetails ? 0 : undefined}
+          role={hasDetails ? "button" : undefined}
+          aria-expanded={hasDetails ? isExpanded : undefined}
+        >
+          {/* Header */}
+          <div className="flex items-start gap-2">
+            {/* Icon */}
+            <Icon className={cn("h-4 w-4 mt-0.5 shrink-0", config.text)} />
+
+            {/* Title and subtitle */}
+            <div className="flex-1 min-w-0">
+              <div className="flex items-center gap-2 flex-wrap">
+                <span className={cn("text-sm font-medium", config.text)}>{title}</span>
+                {relativeTime !== null && (
+                  <span className="text-xs text-muted-foreground font-mono">
+                    +{relativeTime.toFixed(0)}ms
+                  </span>
+                )}
+              </div>
+              {subtitle && (
+                <p className="text-xs text-muted-foreground mt-0.5 line-clamp-2">{subtitle}</p>
+              )}
+            </div>
+
+            {/* Expand/collapse indicator */}
+            {hasDetails && (
+              <div className="shrink-0">
+                {isExpanded ? (
+                  <ChevronDown className="h-4 w-4 text-muted-foreground" />
+                ) : (
+                  <ChevronRight className="h-4 w-4 text-muted-foreground" />
+                )}
+              </div>
+            )}
+          </div>
+
+          {/* Expandable details */}
+          {hasDetails && isExpanded && (
+            <div className="mt-3 pt-3 border-t border-current/10">{details}</div>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+}

+ 439 - 0
src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx

@@ -0,0 +1,439 @@
+"use client";
+
+import {
+  AlertCircle,
+  ArrowRight,
+  CheckCircle,
+  Clock,
+  DollarSign,
+  ExternalLink,
+  Globe,
+  Loader2,
+  Monitor,
+  Settings2,
+  Zap,
+} from "lucide-react";
+import { useTranslations } from "next-intl";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Link } from "@/i18n/routing";
+import { cn, formatTokenAmount } from "@/lib/utils";
+import { formatCurrency } from "@/lib/utils/currency";
+import {
+  calculateOutputRate,
+  isInProgressStatus,
+  isSuccessStatus,
+  type SummaryTabProps,
+} from "../types";
+
+export function SummaryTab({
+  statusCode,
+  errorMessage,
+  originalModel,
+  currentModel,
+  billingModelSource = "original",
+  inputTokens,
+  outputTokens,
+  cacheCreationInputTokens,
+  cacheCreation5mInputTokens,
+  cacheCreation1hInputTokens,
+  cacheReadInputTokens,
+  cacheTtlApplied,
+  costUsd,
+  costMultiplier,
+  context1mApplied,
+  durationMs,
+  ttfbMs,
+  sessionId,
+  requestSequence,
+  userAgent,
+  endpoint,
+  specialSettings,
+  hasMessages,
+  checkingMessages,
+  onViewLogicTrace,
+}: SummaryTabProps) {
+  const t = useTranslations("dashboard.logs.details");
+
+  const isSuccess = isSuccessStatus(statusCode);
+  const isInProgress = isInProgressStatus(statusCode);
+  const outputRate = calculateOutputRate(outputTokens, durationMs, ttfbMs);
+  const totalTokens = (inputTokens ?? 0) + (outputTokens ?? 0);
+  const hasRedirect = originalModel && currentModel && originalModel !== currentModel;
+  const specialSettingsContent =
+    specialSettings && specialSettings.length > 0 ? JSON.stringify(specialSettings, null, 2) : null;
+
+  return (
+    <div className="space-y-6">
+      {/* Status Hero */}
+      <div
+        className={cn(
+          "flex items-center gap-4 p-4 rounded-lg border",
+          isInProgress && "bg-muted/50 border-muted",
+          isSuccess &&
+            "bg-emerald-50 dark:bg-emerald-950/20 border-emerald-200 dark:border-emerald-800",
+          !isInProgress &&
+            !isSuccess &&
+            "bg-rose-50 dark:bg-rose-950/20 border-rose-200 dark:border-rose-800"
+        )}
+      >
+        <div
+          className={cn(
+            "flex h-12 w-12 items-center justify-center rounded-full",
+            isInProgress && "bg-muted",
+            isSuccess && "bg-emerald-100 dark:bg-emerald-900/30",
+            !isInProgress && !isSuccess && "bg-rose-100 dark:bg-rose-900/30"
+          )}
+        >
+          {isInProgress ? (
+            <Loader2 className="h-6 w-6 text-muted-foreground animate-spin" />
+          ) : isSuccess ? (
+            <CheckCircle className="h-6 w-6 text-emerald-600" />
+          ) : (
+            <AlertCircle className="h-6 w-6 text-rose-600" />
+          )}
+        </div>
+        <div className="flex-1">
+          <div className="flex items-center gap-2">
+            <span className="text-lg font-semibold">
+              {isInProgress ? t("inProgress") : statusCode || t("unknown")}
+            </span>
+            {statusCode && (
+              <Badge variant={isSuccess ? "default" : "destructive"} className="text-xs">
+                {isSuccess ? "OK" : "Error"}
+              </Badge>
+            )}
+          </div>
+          <p className="text-sm text-muted-foreground">
+            {isInProgress ? t("processing") : isSuccess ? t("success") : t("error")}
+          </p>
+        </div>
+      </div>
+
+      {/* Key Metrics Grid */}
+      {(costUsd || totalTokens > 0 || durationMs || outputRate) && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold text-muted-foreground">{t("summary.keyMetrics")}</h4>
+          <div className="grid grid-cols-2 gap-3">
+            {/* Total Cost */}
+            {costUsd && (
+              <div className="flex items-center gap-3 p-3 rounded-lg border bg-card">
+                <div className="flex h-9 w-9 items-center justify-center rounded-lg bg-emerald-100 dark:bg-emerald-900/30">
+                  <DollarSign className="h-4 w-4 text-emerald-600" />
+                </div>
+                <div>
+                  <p className="text-xs text-muted-foreground">{t("summary.totalCost")}</p>
+                  <p className="text-sm font-semibold font-mono">
+                    {formatCurrency(costUsd, "USD", 6)}
+                  </p>
+                </div>
+              </div>
+            )}
+
+            {/* Total Tokens */}
+            {totalTokens > 0 && (
+              <div className="flex items-center gap-3 p-3 rounded-lg border bg-card">
+                <div className="flex h-9 w-9 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900/30">
+                  <Zap className="h-4 w-4 text-blue-600" />
+                </div>
+                <div>
+                  <p className="text-xs text-muted-foreground">{t("summary.totalTokens")}</p>
+                  <p className="text-sm font-semibold font-mono">
+                    {formatTokenAmount(totalTokens)}
+                  </p>
+                </div>
+              </div>
+            )}
+
+            {/* Duration */}
+            {durationMs !== null && durationMs !== undefined && (
+              <div className="flex items-center gap-3 p-3 rounded-lg border bg-card">
+                <div className="flex h-9 w-9 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900/30">
+                  <Clock className="h-4 w-4 text-purple-600" />
+                </div>
+                <div>
+                  <p className="text-xs text-muted-foreground">{t("summary.duration")}</p>
+                  <p className="text-sm font-semibold font-mono">
+                    {durationMs >= 1000
+                      ? `${(durationMs / 1000).toFixed(2)}s`
+                      : `${Math.round(durationMs)}ms`}
+                  </p>
+                </div>
+              </div>
+            )}
+
+            {/* Output Rate */}
+            {outputRate !== null && (
+              <div className="flex items-center gap-3 p-3 rounded-lg border bg-card">
+                <div className="flex h-9 w-9 items-center justify-center rounded-lg bg-amber-100 dark:bg-amber-900/30">
+                  <Zap className="h-4 w-4 text-amber-600" />
+                </div>
+                <div>
+                  <p className="text-xs text-muted-foreground">{t("summary.outputRate")}</p>
+                  <p className="text-sm font-semibold font-mono">{outputRate.toFixed(1)} tok/s</p>
+                </div>
+              </div>
+            )}
+          </div>
+        </div>
+      )}
+
+      {/* Session Info */}
+      {sessionId && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold">{t("metadata.sessionInfo")}</h4>
+          <div className="rounded-lg border bg-card p-4">
+            <div className="flex items-center gap-3">
+              <div className="flex-1 min-w-0">
+                <div className="flex items-center gap-2">
+                  <code className="text-xs font-mono break-all">{sessionId}</code>
+                  {requestSequence && (
+                    <Badge variant="outline" className="text-xs shrink-0">
+                      #{requestSequence}
+                    </Badge>
+                  )}
+                </div>
+              </div>
+              {hasMessages && !checkingMessages && (
+                <Link
+                  href={
+                    requestSequence
+                      ? `/dashboard/sessions/${sessionId}/messages?seq=${requestSequence}`
+                      : `/dashboard/sessions/${sessionId}/messages`
+                  }
+                >
+                  <Button variant="outline" size="sm">
+                    <ExternalLink className="h-4 w-4 mr-2" />
+                    {t("viewDetails")}
+                  </Button>
+                </Link>
+              )}
+            </div>
+          </div>
+        </div>
+      )}
+
+      {/* Client Info */}
+      {(userAgent || endpoint) && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <Monitor className="h-4 w-4 text-blue-600" />
+            {t("metadata.clientInfo")}
+          </h4>
+          <div className="rounded-lg border bg-card divide-y">
+            {userAgent && (
+              <div className="p-3">
+                <p className="text-xs text-muted-foreground mb-1">User-Agent</p>
+                <code className="text-xs font-mono break-all">{userAgent}</code>
+              </div>
+            )}
+            {endpoint && (
+              <div className="p-3">
+                <p className="text-xs text-muted-foreground mb-1 flex items-center gap-1">
+                  <Globe className="h-3 w-3" />
+                  Endpoint
+                </p>
+                <code className="text-xs font-mono break-all">{endpoint}</code>
+              </div>
+            )}
+          </div>
+        </div>
+      )}
+
+      {/* Billing Details */}
+      {costUsd && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <DollarSign className="h-4 w-4 text-emerald-600" />
+            {t("metadata.billingInfo")}
+          </h4>
+          <div className="rounded-lg border bg-card p-4 space-y-3">
+            {/* Token breakdown */}
+            <div className="grid grid-cols-2 gap-x-6 gap-y-2 text-sm">
+              <div className="flex justify-between">
+                <span className="text-muted-foreground">{t("billingDetails.input")}:</span>
+                <span className="font-mono">{formatTokenAmount(inputTokens)} tokens</span>
+              </div>
+              <div className="flex justify-between">
+                <span className="text-muted-foreground">{t("billingDetails.output")}:</span>
+                <span className="font-mono">{formatTokenAmount(outputTokens)} tokens</span>
+              </div>
+
+              {/* Cache Write 5m */}
+              {((cacheCreation5mInputTokens ?? 0) > 0 ||
+                ((cacheCreationInputTokens ?? 0) > 0 && cacheTtlApplied !== "1h")) && (
+                <div className="flex justify-between col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.cacheWrite5m")}:</span>
+                  <span className="font-mono">
+                    {formatTokenAmount(
+                      (cacheCreation5mInputTokens ?? 0) > 0
+                        ? cacheCreation5mInputTokens
+                        : cacheCreationInputTokens
+                    )}{" "}
+                    tokens <span className="text-orange-600">(1.25x)</span>
+                  </span>
+                </div>
+              )}
+
+              {/* Cache Write 1h */}
+              {((cacheCreation1hInputTokens ?? 0) > 0 ||
+                ((cacheCreationInputTokens ?? 0) > 0 && cacheTtlApplied === "1h")) && (
+                <div className="flex justify-between col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.cacheWrite1h")}:</span>
+                  <span className="font-mono">
+                    {formatTokenAmount(
+                      (cacheCreation1hInputTokens ?? 0) > 0
+                        ? cacheCreation1hInputTokens
+                        : cacheCreationInputTokens
+                    )}{" "}
+                    tokens <span className="text-orange-600">(2x)</span>
+                  </span>
+                </div>
+              )}
+
+              {/* Cache Read */}
+              {(cacheReadInputTokens ?? 0) > 0 && (
+                <div className="flex justify-between col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.cacheRead")}:</span>
+                  <span className="font-mono">
+                    {formatTokenAmount(cacheReadInputTokens)} tokens{" "}
+                    <span className="text-emerald-600">(0.1x)</span>
+                  </span>
+                </div>
+              )}
+
+              {/* Cache TTL */}
+              {cacheTtlApplied && (
+                <div className="flex justify-between items-center col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.cacheTtl")}:</span>
+                  <Badge variant="outline" className="text-xs">
+                    {cacheTtlApplied}
+                  </Badge>
+                </div>
+              )}
+
+              {/* 1M Context */}
+              {context1mApplied && (
+                <div className="flex justify-between items-center col-span-2">
+                  <span className="text-muted-foreground">{t("billingDetails.context1m")}:</span>
+                  <div className="flex items-center gap-2">
+                    <Badge
+                      variant="outline"
+                      className="text-xs bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950/30 dark:text-purple-300 dark:border-purple-800"
+                    >
+                      1M Context
+                    </Badge>
+                    <span className="text-xs text-muted-foreground">
+                      ({t("billingDetails.context1mPricing")})
+                    </span>
+                  </div>
+                </div>
+              )}
+
+              {/* Cost Multiplier */}
+              {(() => {
+                if (costMultiplier === "" || costMultiplier == null) return null;
+                const multiplier = Number(costMultiplier);
+                if (!Number.isFinite(multiplier) || multiplier === 1) return null;
+                return (
+                  <div className="flex justify-between col-span-2">
+                    <span className="text-muted-foreground">{t("billingDetails.multiplier")}:</span>
+                    <span className="font-mono">{multiplier.toFixed(2)}x</span>
+                  </div>
+                );
+              })()}
+            </div>
+
+            {/* Total Cost */}
+            <div className="pt-3 border-t flex justify-between items-center">
+              <span className="font-medium">{t("billingDetails.totalCost")}:</span>
+              <span className="font-mono text-lg font-semibold text-emerald-600">
+                {formatCurrency(costUsd, "USD", 6)}
+              </span>
+            </div>
+          </div>
+        </div>
+      )}
+
+      {/* Special Settings */}
+      {specialSettingsContent && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <Settings2 className="h-4 w-4 text-purple-600" />
+            {t("specialSettings.title")}
+          </h4>
+          <div className="rounded-lg border bg-card p-4">
+            <pre className="text-xs whitespace-pre-wrap break-words font-mono">
+              {specialSettingsContent}
+            </pre>
+          </div>
+        </div>
+      )}
+
+      {/* Model Redirect */}
+      {hasRedirect && (
+        <div id="model-redirect-section" className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <ArrowRight className="h-4 w-4 text-blue-600" />
+            {t("modelRedirect.title")}
+          </h4>
+          <div className="rounded-lg border bg-blue-50 dark:bg-blue-950/20 p-3">
+            <div className="flex items-center gap-2 text-sm flex-wrap">
+              <code
+                className={cn(
+                  "px-1.5 py-0.5 rounded text-xs",
+                  billingModelSource === "original"
+                    ? "bg-emerald-100 dark:bg-emerald-900/50 text-emerald-800 dark:text-emerald-200 ring-1 ring-emerald-300 dark:ring-emerald-700"
+                    : "bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200"
+                )}
+              >
+                {originalModel}
+              </code>
+              <ArrowRight className="h-3.5 w-3.5 text-blue-500 flex-shrink-0" />
+              <code
+                className={cn(
+                  "px-1.5 py-0.5 rounded text-xs",
+                  billingModelSource === "redirected"
+                    ? "bg-emerald-100 dark:bg-emerald-900/50 text-emerald-800 dark:text-emerald-200 ring-1 ring-emerald-300 dark:ring-emerald-700"
+                    : "bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200"
+                )}
+              >
+                {currentModel}
+              </code>
+            </div>
+            <p className="text-xs text-muted-foreground mt-2">
+              {billingModelSource === "original"
+                ? t("modelRedirect.billingOriginal")
+                : t("modelRedirect.billingRedirected")}
+            </p>
+          </div>
+        </div>
+      )}
+
+      {/* Error Summary */}
+      {errorMessage && (
+        <div className="space-y-2">
+          <h4 className="text-sm font-semibold flex items-center gap-2">
+            <AlertCircle className="h-4 w-4 text-rose-600" />
+            {t("errorMessage")}
+          </h4>
+          <div className="rounded-lg border bg-rose-50 dark:bg-rose-950/20 p-3">
+            <p className="text-xs text-rose-800 dark:text-rose-200 line-clamp-3 font-mono">
+              {errorMessage.length > 200 ? `${errorMessage.slice(0, 200)}...` : errorMessage}
+            </p>
+            {errorMessage.length > 200 && onViewLogicTrace && (
+              <Button
+                variant="link"
+                size="sm"
+                className="h-auto p-0 mt-2 text-xs"
+                onClick={onViewLogicTrace}
+              >
+                {t("summary.viewFullError")}
+              </Button>
+            )}
+          </div>
+        </div>
+      )}
+    </div>
+  );
+}

+ 5 - 0
src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/index.ts

@@ -0,0 +1,5 @@
+export { LatencyBreakdownBar } from "./LatencyBreakdownBar";
+export { LogicTraceTab } from "./LogicTraceTab";
+export { PerformanceTab } from "./PerformanceTab";
+export { StepCard, type StepStatus } from "./StepCard";
+export { SummaryTab } from "./SummaryTab";

+ 291 - 0
src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx

@@ -0,0 +1,291 @@
+"use client";
+
+import { FileText, Gauge, GitBranch } from "lucide-react";
+import { useTranslations } from "next-intl";
+import { useCallback, useEffect, useRef, useState } from "react";
+import { hasSessionMessages } from "@/actions/active-sessions";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
+import type { ProviderChainItem } from "@/types/message";
+import type { SpecialSetting } from "@/types/special-settings";
+import type { BillingModelSource } from "@/types/system-config";
+import { LogicTraceTab, PerformanceTab, SummaryTab } from "./components";
+
+interface ErrorDetailsDialogProps {
+  statusCode: number | null;
+  errorMessage: string | null;
+  providerChain: ProviderChainItem[] | null;
+  sessionId: string | null;
+  requestSequence?: number | null;
+  blockedBy?: string | null;
+  blockedReason?: string | null;
+  originalModel?: string | null;
+  currentModel?: string | null;
+  userAgent?: string | null;
+  messagesCount?: number | null;
+  endpoint?: string | null;
+  billingModelSource?: BillingModelSource;
+  specialSettings?: SpecialSetting[] | null;
+  inputTokens?: number | null;
+  outputTokens?: number | null;
+  cacheCreationInputTokens?: number | null;
+  cacheCreation5mInputTokens?: number | null;
+  cacheCreation1hInputTokens?: number | null;
+  cacheReadInputTokens?: number | null;
+  cacheTtlApplied?: string | null;
+  costUsd?: string | null;
+  costMultiplier?: string | null;
+  context1mApplied?: boolean | null;
+  durationMs?: number | null;
+  ttfbMs?: number | null;
+  externalOpen?: boolean;
+  onExternalOpenChange?: (open: boolean) => void;
+  scrollToRedirect?: boolean;
+}
+
+type TabValue = "summary" | "logic-trace" | "performance";
+
+export function ErrorDetailsDialog({
+  statusCode,
+  errorMessage,
+  providerChain,
+  sessionId,
+  requestSequence,
+  blockedBy,
+  blockedReason,
+  originalModel,
+  currentModel,
+  userAgent,
+  messagesCount,
+  endpoint,
+  billingModelSource = "original",
+  specialSettings,
+  inputTokens,
+  outputTokens,
+  cacheCreationInputTokens,
+  cacheCreation5mInputTokens,
+  cacheCreation1hInputTokens,
+  cacheReadInputTokens,
+  cacheTtlApplied,
+  costUsd,
+  costMultiplier,
+  context1mApplied,
+  durationMs,
+  ttfbMs,
+  externalOpen,
+  onExternalOpenChange,
+  scrollToRedirect,
+}: ErrorDetailsDialogProps) {
+  const t = useTranslations("dashboard.logs.details");
+  const [internalOpen, setInternalOpen] = useState(false);
+  const [hasMessages, setHasMessages] = useState(false);
+  const [checkingMessages, setCheckingMessages] = useState(false);
+  const [activeTab, setActiveTab] = useState<TabValue>("summary");
+  const messageCheckRequestIdRef = useRef(0);
+
+  // Support external and internal control
+  const isControlled = externalOpen !== undefined;
+  const open = isControlled ? externalOpen : internalOpen;
+  const setOpen = useCallback(
+    (value: boolean) => {
+      if (isControlled) {
+        onExternalOpenChange?.(value);
+      } else {
+        setInternalOpen(value);
+      }
+    },
+    [isControlled, onExternalOpenChange]
+  );
+
+  const isInProgress = statusCode === null;
+
+  // Check if session has messages data
+  useEffect(() => {
+    if (open && sessionId) {
+      const requestId = ++messageCheckRequestIdRef.current;
+      setCheckingMessages(true);
+      hasSessionMessages(sessionId, requestSequence ?? undefined)
+        .then((result) => {
+          if (requestId !== messageCheckRequestIdRef.current) return;
+          if (result.ok) {
+            setHasMessages(result.data);
+          }
+        })
+        .catch((err) => {
+          if (requestId !== messageCheckRequestIdRef.current) return;
+          console.error("Failed to check session messages:", err);
+        })
+        .finally(() => {
+          if (requestId === messageCheckRequestIdRef.current) {
+            setCheckingMessages(false);
+          }
+        });
+    } else {
+      setHasMessages(false);
+      setCheckingMessages(false);
+    }
+  }, [open, sessionId, requestSequence]);
+
+  // Handle scrollToRedirect - switch to metadata tab when redirect info needs focus
+  useEffect(() => {
+    if (open && scrollToRedirect) {
+      // Switch to summary tab where model redirect info is displayed
+      setActiveTab("summary");
+      // Scroll to model redirect section after DOM render
+      const timer = setTimeout(() => {
+        const element = document.getElementById("model-redirect-section");
+        element?.scrollIntoView({ behavior: "smooth", block: "start" });
+      }, 100);
+      return () => clearTimeout(timer);
+    }
+  }, [open, scrollToRedirect]);
+
+  // Reset tab when dialog closes
+  useEffect(() => {
+    if (!open) {
+      setActiveTab("summary");
+    }
+  }, [open]);
+
+  /**
+   * Get status badge className based on HTTP status code
+   */
+  const getStatusBadgeClassName = () => {
+    if (isInProgress) {
+      return "bg-gray-100 text-gray-700 border-gray-300 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600";
+    }
+
+    if (!statusCode) {
+      return "bg-gray-100 text-gray-700 border-gray-300 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600";
+    }
+
+    if (statusCode >= 200 && statusCode < 300) {
+      return "bg-green-100 text-green-700 border-green-300 dark:bg-green-900/30 dark:text-green-400 dark:border-green-700";
+    }
+
+    if (statusCode >= 300 && statusCode < 400) {
+      return "bg-blue-100 text-blue-700 border-blue-300 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-700";
+    }
+
+    if (statusCode >= 400 && statusCode < 500) {
+      return "bg-yellow-100 text-yellow-700 border-yellow-300 dark:bg-yellow-900/30 dark:text-yellow-400 dark:border-yellow-700";
+    }
+
+    if (statusCode >= 500) {
+      return "bg-red-100 text-red-700 border-red-300 dark:bg-red-900/30 dark:text-red-400 dark:border-red-700";
+    }
+
+    return "bg-gray-100 text-gray-700 border-gray-300 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600";
+  };
+
+  // Shared props for all tabs
+  const sharedProps = {
+    statusCode,
+    errorMessage,
+    providerChain,
+    sessionId,
+    requestSequence,
+    blockedBy,
+    blockedReason,
+    originalModel,
+    currentModel,
+    userAgent,
+    messagesCount,
+    endpoint,
+    billingModelSource,
+    specialSettings,
+    inputTokens,
+    outputTokens,
+    cacheCreationInputTokens,
+    cacheCreation5mInputTokens,
+    cacheCreation1hInputTokens,
+    cacheReadInputTokens,
+    cacheTtlApplied,
+    costUsd,
+    costMultiplier,
+    context1mApplied,
+    durationMs,
+    ttfbMs,
+  };
+
+  return (
+    <Sheet open={open} onOpenChange={setOpen}>
+      <SheetTrigger asChild>
+        <Button variant="ghost" className="h-auto p-0 font-normal hover:bg-transparent">
+          <Badge variant="outline" className={getStatusBadgeClassName()}>
+            {isInProgress ? t("inProgress") : statusCode}
+          </Badge>
+        </Button>
+      </SheetTrigger>
+      <SheetContent
+        side="right"
+        className="w-[95vw] sm:w-[480px] md:w-[540px] lg:w-[600px] xl:w-[640px] sm:max-w-none overflow-y-auto px-4 sm:px-6"
+      >
+        <SheetHeader className="pb-2">
+          <SheetTitle>{t("title")}</SheetTitle>
+        </SheetHeader>
+
+        <div className="pb-8">
+          <Tabs
+            value={activeTab}
+            onValueChange={(v) => setActiveTab(v as TabValue)}
+            className="w-full"
+          >
+            <TabsList className="w-full grid grid-cols-3 h-auto p-1">
+              <TabsTrigger
+                value="summary"
+                className={cn(
+                  "flex items-center gap-1.5 px-2 py-1.5 text-xs sm:text-sm",
+                  "data-[state=active]:bg-background"
+                )}
+              >
+                <FileText className="h-3.5 w-3.5 sm:h-4 sm:w-4 shrink-0" />
+                <span className="hidden sm:inline">{t("tabs.summary")}</span>
+              </TabsTrigger>
+              <TabsTrigger
+                value="logic-trace"
+                className={cn(
+                  "flex items-center gap-1.5 px-2 py-1.5 text-xs sm:text-sm",
+                  "data-[state=active]:bg-background"
+                )}
+              >
+                <GitBranch className="h-3.5 w-3.5 sm:h-4 sm:w-4 shrink-0" />
+                <span className="hidden sm:inline">{t("tabs.logicTrace")}</span>
+              </TabsTrigger>
+              <TabsTrigger
+                value="performance"
+                className={cn(
+                  "flex items-center gap-1.5 px-2 py-1.5 text-xs sm:text-sm",
+                  "data-[state=active]:bg-background"
+                )}
+              >
+                <Gauge className="h-3.5 w-3.5 sm:h-4 sm:w-4 shrink-0" />
+                <span className="hidden sm:inline">{t("tabs.performance")}</span>
+              </TabsTrigger>
+            </TabsList>
+
+            <TabsContent value="summary" className="mt-4">
+              <SummaryTab
+                {...sharedProps}
+                hasMessages={hasMessages}
+                checkingMessages={checkingMessages}
+                onViewLogicTrace={() => setActiveTab("logic-trace")}
+              />
+            </TabsContent>
+
+            <TabsContent value="logic-trace" className="mt-4">
+              <LogicTraceTab {...sharedProps} />
+            </TabsContent>
+
+            <TabsContent value="performance" className="mt-4">
+              <PerformanceTab {...sharedProps} />
+            </TabsContent>
+          </Tabs>
+        </div>
+      </SheetContent>
+    </Sheet>
+  );
+}

+ 146 - 0
src/app/[locale]/dashboard/logs/_components/error-details-dialog/types.ts

@@ -0,0 +1,146 @@
+import type { ProviderChainItem } from "@/types/message";
+import type { SpecialSetting } from "@/types/special-settings";
+import type { BillingModelSource } from "@/types/system-config";
+
+/**
+ * Shared props interface for all tab components
+ */
+export interface TabSharedProps {
+  /** HTTP status code */
+  statusCode: number | null;
+  /** Error message if request failed */
+  errorMessage: string | null;
+  /** Provider decision chain */
+  providerChain: ProviderChainItem[] | null;
+  /** Session ID */
+  sessionId: string | null;
+  /** Request sequence number within session */
+  requestSequence?: number | null;
+  /** Block type (e.g., "sensitive_word", "warmup") */
+  blockedBy?: string | null;
+  /** Block reason (JSON string) */
+  blockedReason?: string | null;
+  /** Original model before redirect */
+  originalModel?: string | null;
+  /** Current model after redirect */
+  currentModel?: string | null;
+  /** User-Agent header */
+  userAgent?: string | null;
+  /** Number of messages in request */
+  messagesCount?: number | null;
+  /** API endpoint */
+  endpoint?: string | null;
+  /** Billing model source */
+  billingModelSource?: BillingModelSource;
+  /** Special settings applied */
+  specialSettings?: SpecialSetting[] | null;
+  /** Input tokens */
+  inputTokens?: number | null;
+  /** Output tokens */
+  outputTokens?: number | null;
+  /** Cache creation input tokens (total) */
+  cacheCreationInputTokens?: number | null;
+  /** Cache creation 5m input tokens */
+  cacheCreation5mInputTokens?: number | null;
+  /** Cache creation 1h input tokens */
+  cacheCreation1hInputTokens?: number | null;
+  /** Cache read input tokens */
+  cacheReadInputTokens?: number | null;
+  /** Cache TTL applied */
+  cacheTtlApplied?: string | null;
+  /** Total cost in USD */
+  costUsd?: string | null;
+  /** Cost multiplier */
+  costMultiplier?: string | null;
+  /** Whether 1M context pricing was applied */
+  context1mApplied?: boolean | null;
+  /** Total request duration in ms */
+  durationMs?: number | null;
+  /** Time to first byte in ms */
+  ttfbMs?: number | null;
+}
+
+/**
+ * Props for SummaryTab with additional handlers
+ */
+export interface SummaryTabProps extends TabSharedProps {
+  /** Whether session has messages data */
+  hasMessages: boolean;
+  /** Whether messages check is loading */
+  checkingMessages: boolean;
+  /** Callback to switch to Logic Trace tab */
+  onViewLogicTrace?: () => void;
+}
+
+/**
+ * Props for LogicTraceTab
+ */
+export interface LogicTraceTabProps extends TabSharedProps {}
+
+/**
+ * Props for PerformanceTab
+ */
+export interface PerformanceTabProps extends TabSharedProps {}
+
+/**
+ * Props for MetadataTab
+ */
+export interface MetadataTabProps extends TabSharedProps {
+  /** Whether session has messages data */
+  hasMessages: boolean;
+  /** Whether messages check is loading */
+  checkingMessages: boolean;
+}
+
+/**
+ * Parse blocked reason JSON string
+ */
+export function parseBlockedReason(
+  blockedReason: string | null | undefined
+): { word?: string; matchType?: string; matchedText?: string } | null {
+  if (!blockedReason) return null;
+  try {
+    return JSON.parse(blockedReason);
+  } catch {
+    return null;
+  }
+}
+
+/**
+ * Calculate output tokens per second
+ */
+export function calculateOutputRate(
+  outputTokens: number | null | undefined,
+  durationMs: number | null | undefined,
+  ttfbMs: number | null | undefined
+): number | null {
+  if (
+    outputTokens === null ||
+    outputTokens === undefined ||
+    outputTokens <= 0 ||
+    durationMs === null ||
+    durationMs === undefined ||
+    ttfbMs === null ||
+    ttfbMs === undefined ||
+    ttfbMs >= durationMs
+  ) {
+    return null;
+  }
+  const seconds = (durationMs - ttfbMs) / 1000;
+  if (seconds <= 0) return null;
+  return outputTokens / seconds;
+}
+
+/**
+ * Check if request is successful (2xx status)
+ */
+export function isSuccessStatus(statusCode: number | null): boolean {
+  return statusCode !== null && statusCode >= 200 && statusCode < 300;
+}
+
+/**
+ * Check if request is in progress (no status code)
+ */
+export function isInProgressStatus(statusCode: number | null): boolean {
+  return statusCode === null;
+}

+ 194 - 0
src/app/[locale]/dashboard/logs/_components/filters/active-filters-display.tsx

@@ -0,0 +1,194 @@
+"use client";
+
+import { format } from "date-fns";
+import { X } from "lucide-react";
+import { useTranslations } from "next-intl";
+import { useMemo } from "react";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+import type { FilterDisplayNames, UsageLogFilters } from "./types";
+
+interface ActiveFiltersDisplayProps {
+  filters: UsageLogFilters;
+  onRemove: (key: keyof UsageLogFilters) => void;
+  onClearAll: () => void;
+  displayNames: FilterDisplayNames;
+  isAdmin: boolean;
+  className?: string;
+}
+
+interface ActiveFilter {
+  key: keyof UsageLogFilters;
+  label: string;
+  value: string;
+}
+
+export function ActiveFiltersDisplay({
+  filters,
+  onRemove,
+  onClearAll,
+  displayNames,
+  isAdmin,
+  className,
+}: ActiveFiltersDisplayProps) {
+  const t = useTranslations("dashboard.logs.filters");
+
+  const activeFilters = useMemo(() => {
+    const result: ActiveFilter[] = [];
+
+    // User filter (admin only)
+    if (isAdmin && filters.userId !== undefined) {
+      const userName = displayNames.getUserName(filters.userId);
+      result.push({
+        key: "userId",
+        label: t("user"),
+        value: userName ?? filters.userId.toString(),
+      });
+    }
+
+    // Key filter
+    if (filters.keyId !== undefined) {
+      const keyName = displayNames.getKeyName(filters.keyId);
+      result.push({
+        key: "keyId",
+        label: t("apiKey"),
+        value: keyName ?? filters.keyId.toString(),
+      });
+    }
+
+    // Provider filter (admin only)
+    if (isAdmin && filters.providerId !== undefined) {
+      const providerName = displayNames.getProviderName(filters.providerId);
+      result.push({
+        key: "providerId",
+        label: t("provider"),
+        value: providerName ?? filters.providerId.toString(),
+      });
+    }
+
+    // Session ID filter
+    if (filters.sessionId) {
+      result.push({
+        key: "sessionId",
+        label: t("sessionId"),
+        value:
+          filters.sessionId.length > 12
+            ? `${filters.sessionId.slice(0, 12)}...`
+            : filters.sessionId,
+      });
+    }
+
+    // Date range filter
+    if (filters.startTime && filters.endTime) {
+      const startDate = format(new Date(filters.startTime), "MM/dd");
+      const endDate = format(new Date(filters.endTime - 1000), "MM/dd");
+      result.push({
+        key: "startTime",
+        label: t("dateRange"),
+        value: startDate === endDate ? startDate : `${startDate} - ${endDate}`,
+      });
+    }
+
+    // Model filter
+    if (filters.model) {
+      result.push({
+        key: "model",
+        label: t("model"),
+        value: filters.model,
+      });
+    }
+
+    // Endpoint filter
+    if (filters.endpoint) {
+      result.push({
+        key: "endpoint",
+        label: t("endpoint"),
+        value: filters.endpoint,
+      });
+    }
+
+    // Status code filter
+    if (filters.excludeStatusCode200) {
+      result.push({
+        key: "excludeStatusCode200",
+        label: t("statusCode"),
+        value: "!200",
+      });
+    } else if (filters.statusCode !== undefined) {
+      result.push({
+        key: "statusCode",
+        label: t("statusCode"),
+        value: filters.statusCode.toString(),
+      });
+    }
+
+    // Min retry count filter
+    if (filters.minRetryCount !== undefined && filters.minRetryCount > 0) {
+      result.push({
+        key: "minRetryCount",
+        label: t("minRetryCount"),
+        value: `>=${filters.minRetryCount}`,
+      });
+    }
+
+    return result;
+  }, [filters, displayNames, isAdmin, t]);
+
+  if (activeFilters.length === 0) {
+    return null;
+  }
+
+  const handleRemove = (key: keyof UsageLogFilters) => {
+    // Special handling for date range - clear both start and end time
+    if (key === "startTime") {
+      onRemove("startTime");
+      onRemove("endTime");
+    } else {
+      onRemove(key);
+    }
+  };
+
+  return (
+    <div
+      className={cn(
+        "flex flex-wrap items-center gap-2",
+        // Mobile: horizontal scroll
+        "overflow-x-auto scrollbar-hide pb-1 -mb-1",
+        className
+      )}
+    >
+      <span className="text-xs text-muted-foreground font-medium shrink-0">
+        {t("activeFilters.title")}:
+      </span>
+
+      {activeFilters.map(({ key, label, value }) => (
+        <Badge key={key} variant="secondary" className="gap-1 pr-1.5 pl-2 py-1 h-auto shrink-0">
+          <span className="text-xs">
+            {label}: <span className="font-semibold">{value}</span>
+          </span>
+          <button
+            type="button"
+            onClick={() => handleRemove(key)}
+            className="ml-1 rounded-full outline-none hover:bg-muted-foreground/20 focus:ring-2 focus:ring-ring/50 cursor-pointer"
+            aria-label={t("activeFilters.remove")}
+          >
+            <X className="h-3 w-3" />
+          </button>
+        </Badge>
+      ))}
+
+      {activeFilters.length > 2 && (
+        <Button
+          type="button"
+          variant="ghost"
+          size="sm"
+          onClick={onClearAll}
+          className="h-auto py-1 px-2 text-xs shrink-0"
+        >
+          {t("activeFilters.clearAll")}
+        </Button>
+      )}
+    </div>
+  );
+}

+ 101 - 0
src/app/[locale]/dashboard/logs/_components/filters/filter-section.tsx

@@ -0,0 +1,101 @@
+"use client";
+
+import { ChevronDown } from "lucide-react";
+import type { LucideIcon } from "lucide-react";
+import type { ReactNode } from "react";
+import { useState } from "react";
+import { Badge } from "@/components/ui/badge";
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
+import { cn } from "@/lib/utils";
+
+interface FilterSectionProps {
+  title: string;
+  description?: string;
+  icon: LucideIcon;
+  defaultOpen?: boolean;
+  children: ReactNode;
+  activeCount?: number;
+  className?: string;
+}
+
+export function FilterSection({
+  title,
+  description,
+  icon: Icon,
+  defaultOpen = false,
+  children,
+  activeCount = 0,
+  className,
+}: FilterSectionProps) {
+  const [isOpen, setIsOpen] = useState(defaultOpen);
+
+  return (
+    <Collapsible open={isOpen} onOpenChange={setIsOpen}>
+      <div
+        className={cn(
+          // Glass morphism base
+          "relative overflow-hidden rounded-xl border bg-card/30 backdrop-blur-sm",
+          "transition-all duration-200",
+          "border-border/50 hover:border-border",
+          className
+        )}
+      >
+        {/* Glassmorphism gradient overlay */}
+        <div className="absolute inset-0 bg-gradient-to-br from-white/[0.02] to-transparent pointer-events-none" />
+
+        <div className="relative z-10">
+          {/* Header - always visible */}
+          <CollapsibleTrigger asChild>
+            <button
+              type="button"
+              className={cn(
+                "flex w-full items-center justify-between gap-3 px-4 py-3",
+                "text-left transition-colors cursor-pointer",
+                "active:bg-muted/30"
+              )}
+            >
+              <div className="flex items-center gap-3">
+                <span
+                  className={cn(
+                    "flex items-center justify-center w-8 h-8 rounded-lg shrink-0",
+                    "bg-muted text-muted-foreground"
+                  )}
+                >
+                  <Icon className="h-4 w-4" />
+                </span>
+                <div className="space-y-0.5">
+                  <h3 className="text-sm font-semibold text-foreground leading-none">{title}</h3>
+                  {description && (
+                    <p className="text-xs text-muted-foreground leading-relaxed hidden sm:block">
+                      {description}
+                    </p>
+                  )}
+                </div>
+              </div>
+
+              <div className="flex items-center gap-2">
+                {activeCount > 0 && (
+                  <Badge variant="secondary" className="bg-primary/10 text-primary">
+                    {activeCount}
+                  </Badge>
+                )}
+                {/* Chevron visible on all screen sizes */}
+                <ChevronDown
+                  className={cn(
+                    "h-4 w-4 text-muted-foreground transition-transform duration-200",
+                    isOpen && "rotate-180"
+                  )}
+                />
+              </div>
+            </button>
+          </CollapsibleTrigger>
+
+          {/* Content - collapsible on all screen sizes */}
+          <CollapsibleContent className="px-4 pb-4">
+            <div className="pt-1">{children}</div>
+          </CollapsibleContent>
+        </div>
+      </div>
+    </Collapsible>
+  );
+}

+ 287 - 0
src/app/[locale]/dashboard/logs/_components/filters/identity-filters.tsx

@@ -0,0 +1,287 @@
+"use client";
+
+import { Check, ChevronsUpDown } from "lucide-react";
+import { useTranslations } from "next-intl";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { toast } from "sonner";
+import { getKeys } from "@/actions/keys";
+import { searchUsersForFilter } from "@/actions/users";
+import { Button } from "@/components/ui/button";
+import {
+  Command,
+  CommandEmpty,
+  CommandGroup,
+  CommandInput,
+  CommandItem,
+  CommandList,
+} from "@/components/ui/command";
+import { Label } from "@/components/ui/label";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import { useDebounce } from "@/lib/hooks/use-debounce";
+import type { Key } from "@/types/key";
+import type { UsageLogFilters } from "./types";
+
+interface IdentityFiltersProps {
+  isAdmin: boolean;
+  filters: UsageLogFilters;
+  onFiltersChange: (filters: UsageLogFilters) => void;
+  initialKeys: Key[];
+  isKeysLoading?: boolean;
+  onKeysChange?: (keys: Key[]) => void;
+  onUsersChange?: (users: Array<{ id: number; name: string }>) => void;
+}
+
+export function IdentityFilters({
+  isAdmin,
+  filters,
+  onFiltersChange,
+  initialKeys,
+  isKeysLoading = false,
+  onKeysChange,
+  onUsersChange,
+}: IdentityFiltersProps) {
+  const t = useTranslations("dashboard");
+
+  const [isUsersLoading, setIsUsersLoading] = useState(false);
+  const [userSearchTerm, setUserSearchTerm] = useState("");
+  const debouncedUserSearchTerm = useDebounce(userSearchTerm, 300);
+  const [availableUsers, setAvailableUsers] = useState<Array<{ id: number; name: string }>>([]);
+  const userSearchRequestIdRef = useRef(0);
+  const lastLoadedUserSearchTermRef = useRef<string | undefined>(undefined);
+  const isMountedRef = useRef(true);
+
+  const [keys, setKeys] = useState<Key[]>(initialKeys);
+  const [userPopoverOpen, setUserPopoverOpen] = useState(false);
+
+  const userMap = useMemo(
+    () => new Map(availableUsers.map((user) => [user.id, user.name])),
+    [availableUsers]
+  );
+
+  useEffect(() => {
+    isMountedRef.current = true;
+    return () => {
+      isMountedRef.current = false;
+    };
+  }, []);
+
+  const loadUsersForFilter = useCallback(
+    async (term?: string) => {
+      const requestId = ++userSearchRequestIdRef.current;
+      setIsUsersLoading(true);
+      lastLoadedUserSearchTermRef.current = term;
+
+      try {
+        const result = await searchUsersForFilter(term);
+        if (!isMountedRef.current || requestId !== userSearchRequestIdRef.current) return;
+
+        if (result.ok) {
+          setAvailableUsers(result.data);
+          onUsersChange?.(result.data);
+        } else {
+          console.error("Failed to load users for filter:", result.error);
+          setAvailableUsers([]);
+        }
+      } catch (error) {
+        if (!isMountedRef.current || requestId !== userSearchRequestIdRef.current) return;
+
+        console.error("Failed to load users for filter:", error);
+        setAvailableUsers([]);
+      } finally {
+        if (isMountedRef.current && requestId === userSearchRequestIdRef.current) {
+          setIsUsersLoading(false);
+        }
+      }
+    },
+    [onUsersChange]
+  );
+
+  useEffect(() => {
+    if (!isAdmin) return;
+    void loadUsersForFilter(undefined);
+  }, [isAdmin, loadUsersForFilter]);
+
+  useEffect(() => {
+    if (!isAdmin || !userPopoverOpen) return;
+
+    const term = debouncedUserSearchTerm.trim() || undefined;
+    if (term === lastLoadedUserSearchTermRef.current) return;
+
+    void loadUsersForFilter(term);
+  }, [isAdmin, userPopoverOpen, debouncedUserSearchTerm, loadUsersForFilter]);
+
+  useEffect(() => {
+    if (!isAdmin) return;
+    if (!userPopoverOpen) {
+      setUserSearchTerm("");
+    }
+  }, [isAdmin, userPopoverOpen]);
+
+  useEffect(() => {
+    if (initialKeys.length > 0) {
+      setKeys(initialKeys);
+    }
+  }, [initialKeys]);
+
+  // Load initial keys if userId is set
+  // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally run only on mount
+  useEffect(() => {
+    const loadInitialKeys = async () => {
+      if (isAdmin && filters.userId && initialKeys.length === 0) {
+        try {
+          const keysResult = await getKeys(filters.userId);
+          if (keysResult.ok && keysResult.data) {
+            setKeys(keysResult.data);
+            onKeysChange?.(keysResult.data);
+          }
+        } catch (error) {
+          console.error("Failed to load initial keys:", error);
+        }
+      }
+    };
+    loadInitialKeys();
+  }, []);
+
+  const handleUserChange = async (userId: string) => {
+    const newUserId = userId ? parseInt(userId, 10) : undefined;
+    const newFilters = { ...filters, userId: newUserId, keyId: undefined };
+    onFiltersChange(newFilters);
+
+    if (newUserId) {
+      try {
+        const keysResult = await getKeys(newUserId);
+        if (keysResult.ok && keysResult.data) {
+          setKeys(keysResult.data);
+          onKeysChange?.(keysResult.data);
+        }
+      } catch (error) {
+        console.error("Failed to load keys:", error);
+        toast.error(t("logs.error.loadKeysFailed"));
+      }
+    } else {
+      setKeys([]);
+      onKeysChange?.([]);
+    }
+  };
+
+  const handleKeyChange = (value: string) => {
+    onFiltersChange({
+      ...filters,
+      keyId: value && value !== "__all__" ? parseInt(value, 10) : undefined,
+    });
+  };
+
+  return (
+    <div className="grid gap-4 sm:grid-cols-2">
+      {/* User selector (Admin only) */}
+      {isAdmin && (
+        <div className="space-y-2">
+          <Label>{t("logs.filters.user")}</Label>
+          <Popover open={userPopoverOpen} onOpenChange={setUserPopoverOpen}>
+            <PopoverTrigger asChild>
+              <Button
+                variant="outline"
+                role="combobox"
+                aria-expanded={userPopoverOpen}
+                type="button"
+                className="w-full justify-between"
+              >
+                {filters.userId ? (
+                  (userMap.get(filters.userId) ?? filters.userId.toString())
+                ) : (
+                  <span className="text-muted-foreground">
+                    {isUsersLoading ? t("logs.stats.loading") : t("logs.filters.allUsers")}
+                  </span>
+                )}
+                <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+              </Button>
+            </PopoverTrigger>
+            <PopoverContent
+              className="w-[320px] p-0"
+              align="start"
+              onWheel={(e) => e.stopPropagation()}
+              onTouchMove={(e) => e.stopPropagation()}
+            >
+              <Command shouldFilter={false}>
+                <CommandInput
+                  placeholder={t("logs.filters.searchUser")}
+                  value={userSearchTerm}
+                  onValueChange={(value) => setUserSearchTerm(value)}
+                />
+                <CommandList className="max-h-[250px] overflow-y-auto">
+                  <CommandEmpty>
+                    {isUsersLoading ? t("logs.stats.loading") : t("logs.filters.noUserFound")}
+                  </CommandEmpty>
+                  <CommandGroup>
+                    <CommandItem
+                      value={t("logs.filters.allUsers")}
+                      onSelect={() => {
+                        void handleUserChange("");
+                        setUserPopoverOpen(false);
+                      }}
+                      className="cursor-pointer"
+                    >
+                      <span className="flex-1">{t("logs.filters.allUsers")}</span>
+                      {!filters.userId && <Check className="h-4 w-4 text-primary" />}
+                    </CommandItem>
+                    {availableUsers.map((user) => (
+                      <CommandItem
+                        key={user.id}
+                        value={user.name}
+                        onSelect={() => {
+                          void handleUserChange(user.id.toString());
+                          setUserPopoverOpen(false);
+                        }}
+                        className="cursor-pointer"
+                      >
+                        <span className="flex-1">{user.name}</span>
+                        {filters.userId === user.id && <Check className="h-4 w-4 text-primary" />}
+                      </CommandItem>
+                    ))}
+                  </CommandGroup>
+                </CommandList>
+              </Command>
+            </PopoverContent>
+          </Popover>
+        </div>
+      )}
+
+      {/* Key selector */}
+      <div className="space-y-2">
+        <Label>{t("logs.filters.apiKey")}</Label>
+        <Select
+          value={filters.keyId?.toString() || "__all__"}
+          onValueChange={handleKeyChange}
+          disabled={isKeysLoading || (isAdmin && !filters.userId && keys.length === 0)}
+        >
+          <SelectTrigger>
+            <SelectValue
+              placeholder={
+                isKeysLoading
+                  ? t("logs.stats.loading")
+                  : isAdmin && !filters.userId && keys.length === 0
+                    ? t("logs.filters.selectUserFirst")
+                    : t("logs.filters.allKeys")
+              }
+            />
+          </SelectTrigger>
+          <SelectContent>
+            <SelectItem value="__all__">{t("logs.filters.allKeys")}</SelectItem>
+            {keys.map((key) => (
+              <SelectItem key={key.id} value={key.id.toString()}>
+                {key.name}
+              </SelectItem>
+            ))}
+          </SelectContent>
+        </Select>
+      </div>
+    </div>
+  );
+}

+ 14 - 0
src/app/[locale]/dashboard/logs/_components/filters/index.ts

@@ -0,0 +1,14 @@
+// Filter panel components
+export { FilterSection } from "./filter-section";
+export { QuickFiltersBar } from "./quick-filters-bar";
+export { ActiveFiltersDisplay } from "./active-filters-display";
+
+// Filter group components
+export { TimeFilters } from "./time-filters";
+export { IdentityFilters } from "./identity-filters";
+export { RequestFilters } from "./request-filters";
+export { StatusFilters } from "./status-filters";
+
+// Types
+export type { FilterPreset } from "./quick-filters-bar";
+export type { UsageLogFilters } from "./types";

+ 72 - 0
src/app/[locale]/dashboard/logs/_components/filters/quick-filters-bar.tsx

@@ -0,0 +1,72 @@
+"use client";
+
+import { AlertCircle, Calendar, CalendarDays, RefreshCw } from "lucide-react";
+import { useTranslations } from "next-intl";
+import { Button } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+
+export type FilterPreset = "today" | "this-week" | "errors-only" | "show-retries";
+
+interface QuickFiltersBarProps {
+  activePreset: FilterPreset | null;
+  onPresetToggle: (preset: FilterPreset) => void;
+  className?: string;
+}
+
+export function QuickFiltersBar({ activePreset, onPresetToggle, className }: QuickFiltersBarProps) {
+  const t = useTranslations("dashboard.logs.filters");
+
+  const timePresets: Array<{ id: FilterPreset; label: string; icon: typeof Calendar }> = [
+    { id: "today", label: t("quickFilters.today"), icon: Calendar },
+    { id: "this-week", label: t("quickFilters.thisWeek"), icon: CalendarDays },
+  ];
+
+  const filterPresets: Array<{ id: FilterPreset; label: string; icon: typeof AlertCircle }> = [
+    { id: "errors-only", label: t("quickFilters.errorsOnly"), icon: AlertCircle },
+    { id: "show-retries", label: t("quickFilters.showRetries"), icon: RefreshCw },
+  ];
+
+  return (
+    <div
+      className={cn(
+        "flex flex-wrap items-center gap-2",
+        // Mobile: horizontal scroll
+        "overflow-x-auto scrollbar-hide pb-1 mb-3",
+        className
+      )}
+    >
+      {/* Time presets */}
+      {timePresets.map(({ id, label, icon: Icon }) => (
+        <Button
+          key={id}
+          type="button"
+          variant={activePreset === id ? "default" : "outline"}
+          size="sm"
+          onClick={() => onPresetToggle(id)}
+          className="shrink-0"
+        >
+          <Icon className="h-4 w-4 mr-1.5" />
+          {label}
+        </Button>
+      ))}
+
+      {/* Separator */}
+      <div className="h-5 w-px bg-border shrink-0 hidden sm:block" />
+
+      {/* Filter presets */}
+      {filterPresets.map(({ id, label, icon: Icon }) => (
+        <Button
+          key={id}
+          type="button"
+          variant={activePreset === id ? "default" : "outline"}
+          size="sm"
+          onClick={() => onPresetToggle(id)}
+          className="shrink-0"
+        >
+          <Icon className="h-4 w-4 mr-1.5" />
+          {label}
+        </Button>
+      ))}
+    </div>
+  );
+}

+ 362 - 0
src/app/[locale]/dashboard/logs/_components/filters/request-filters.tsx

@@ -0,0 +1,362 @@
+"use client";
+
+import { Check, ChevronsUpDown } from "lucide-react";
+import { useTranslations } from "next-intl";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { getUsageLogSessionIdSuggestions } from "@/actions/usage-logs";
+import { Button } from "@/components/ui/button";
+import {
+  Command,
+  CommandEmpty,
+  CommandGroup,
+  CommandInput,
+  CommandItem,
+  CommandList,
+} from "@/components/ui/command";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import { SESSION_ID_SUGGESTION_MIN_LEN } from "@/lib/constants/usage-logs.constants";
+import { useDebounce } from "@/lib/hooks/use-debounce";
+import type { ProviderDisplay } from "@/types/provider";
+import { useLazyEndpoints, useLazyModels } from "../../_hooks/use-lazy-filter-options";
+import type { UsageLogFilters } from "./types";
+
+interface RequestFiltersProps {
+  isAdmin: boolean;
+  filters: UsageLogFilters;
+  onFiltersChange: (filters: UsageLogFilters) => void;
+  providers: ProviderDisplay[];
+  isProvidersLoading?: boolean;
+}
+
+export function RequestFilters({
+  isAdmin,
+  filters,
+  onFiltersChange,
+  providers,
+  isProvidersLoading = false,
+}: RequestFiltersProps) {
+  const t = useTranslations("dashboard");
+
+  const [providerPopoverOpen, setProviderPopoverOpen] = useState(false);
+  const [sessionIdPopoverOpen, setSessionIdPopoverOpen] = useState(false);
+  const [isSessionIdsLoading, setIsSessionIdsLoading] = useState(false);
+  const [availableSessionIds, setAvailableSessionIds] = useState<string[]>([]);
+  const debouncedSessionIdSearchTerm = useDebounce(filters.sessionId ?? "", 300);
+  const sessionIdSearchRequestIdRef = useRef(0);
+  const lastLoadedSessionIdSuggestionsKeyRef = useRef<string | undefined>(undefined);
+  const isMountedRef = useRef(true);
+
+  const {
+    data: models,
+    isLoading: isModelsLoading,
+    onOpenChange: onModelsOpenChange,
+  } = useLazyModels();
+
+  const {
+    data: endpoints,
+    isLoading: isEndpointsLoading,
+    onOpenChange: onEndpointsOpenChange,
+  } = useLazyEndpoints();
+
+  const providerMap = useMemo(
+    () => new Map(providers.map((provider) => [provider.id, provider.name])),
+    [providers]
+  );
+
+  useEffect(() => {
+    isMountedRef.current = true;
+    return () => {
+      isMountedRef.current = false;
+    };
+  }, []);
+
+  const loadSessionIdsForFilter = useCallback(
+    async (term: string) => {
+      const requestId = ++sessionIdSearchRequestIdRef.current;
+      setIsSessionIdsLoading(true);
+      const requestKey = [
+        term,
+        isAdmin ? (filters.userId ?? "").toString() : "",
+        (filters.keyId ?? "").toString(),
+        (filters.providerId ?? "").toString(),
+        isAdmin ? "1" : "0",
+      ].join("|");
+      lastLoadedSessionIdSuggestionsKeyRef.current = requestKey;
+
+      try {
+        const result = await getUsageLogSessionIdSuggestions({
+          term,
+          userId: isAdmin ? filters.userId : undefined,
+          keyId: filters.keyId,
+          providerId: filters.providerId,
+        });
+
+        if (!isMountedRef.current || requestId !== sessionIdSearchRequestIdRef.current) return;
+
+        if (result.ok) {
+          setAvailableSessionIds(result.data);
+        } else {
+          console.error("Failed to load sessionId suggestions:", result.error);
+          setAvailableSessionIds([]);
+        }
+      } catch (error) {
+        if (!isMountedRef.current || requestId !== sessionIdSearchRequestIdRef.current) return;
+        console.error("Failed to load sessionId suggestions:", error);
+        setAvailableSessionIds([]);
+      } finally {
+        if (isMountedRef.current && requestId === sessionIdSearchRequestIdRef.current) {
+          setIsSessionIdsLoading(false);
+        }
+      }
+    },
+    [isAdmin, filters.keyId, filters.providerId, filters.userId]
+  );
+
+  useEffect(() => {
+    if (!sessionIdPopoverOpen) return;
+
+    const term = debouncedSessionIdSearchTerm.trim();
+    if (term.length < SESSION_ID_SUGGESTION_MIN_LEN) {
+      setAvailableSessionIds([]);
+      lastLoadedSessionIdSuggestionsKeyRef.current = undefined;
+      return;
+    }
+
+    const requestKey = [
+      term,
+      isAdmin ? (filters.userId ?? "").toString() : "",
+      (filters.keyId ?? "").toString(),
+      (filters.providerId ?? "").toString(),
+      isAdmin ? "1" : "0",
+    ].join("|");
+    if (requestKey === lastLoadedSessionIdSuggestionsKeyRef.current) return;
+    void loadSessionIdsForFilter(term);
+  }, [
+    sessionIdPopoverOpen,
+    debouncedSessionIdSearchTerm,
+    isAdmin,
+    filters.userId,
+    filters.keyId,
+    filters.providerId,
+    loadSessionIdsForFilter,
+  ]);
+
+  useEffect(() => {
+    if (!sessionIdPopoverOpen) {
+      setAvailableSessionIds([]);
+      lastLoadedSessionIdSuggestionsKeyRef.current = undefined;
+    }
+  }, [sessionIdPopoverOpen]);
+
+  return (
+    <div className="grid gap-4 sm:grid-cols-2">
+      {/* Provider selector (Admin only) */}
+      {isAdmin && (
+        <div className="space-y-2">
+          <Label>{t("logs.filters.provider")}</Label>
+          <Popover open={providerPopoverOpen} onOpenChange={setProviderPopoverOpen}>
+            <PopoverTrigger asChild>
+              <Button
+                variant="outline"
+                role="combobox"
+                aria-expanded={providerPopoverOpen}
+                disabled={isProvidersLoading}
+                type="button"
+                className="w-full justify-between"
+              >
+                {filters.providerId ? (
+                  (providerMap.get(filters.providerId) ?? filters.providerId.toString())
+                ) : (
+                  <span className="text-muted-foreground">
+                    {isProvidersLoading ? t("logs.stats.loading") : t("logs.filters.allProviders")}
+                  </span>
+                )}
+                <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+              </Button>
+            </PopoverTrigger>
+            <PopoverContent
+              className="w-[320px] p-0"
+              align="start"
+              onWheel={(e) => e.stopPropagation()}
+              onTouchMove={(e) => e.stopPropagation()}
+            >
+              <Command shouldFilter={true}>
+                <CommandInput placeholder={t("logs.filters.searchProvider")} />
+                <CommandList className="max-h-[250px] overflow-y-auto">
+                  <CommandEmpty>
+                    {isProvidersLoading
+                      ? t("logs.stats.loading")
+                      : t("logs.filters.noProviderFound")}
+                  </CommandEmpty>
+                  <CommandGroup>
+                    <CommandItem
+                      value={t("logs.filters.allProviders")}
+                      onSelect={() => {
+                        onFiltersChange({
+                          ...filters,
+                          providerId: undefined,
+                        });
+                        setProviderPopoverOpen(false);
+                      }}
+                      className="cursor-pointer"
+                    >
+                      <span className="flex-1">{t("logs.filters.allProviders")}</span>
+                      {!filters.providerId && <Check className="h-4 w-4 text-primary" />}
+                    </CommandItem>
+                    {providers.map((provider) => (
+                      <CommandItem
+                        key={provider.id}
+                        value={provider.name}
+                        onSelect={() => {
+                          onFiltersChange({
+                            ...filters,
+                            providerId: provider.id,
+                          });
+                          setProviderPopoverOpen(false);
+                        }}
+                        className="cursor-pointer"
+                      >
+                        <span className="flex-1">{provider.name}</span>
+                        {filters.providerId === provider.id && (
+                          <Check className="h-4 w-4 text-primary" />
+                        )}
+                      </CommandItem>
+                    ))}
+                  </CommandGroup>
+                </CommandList>
+              </Command>
+            </PopoverContent>
+          </Popover>
+        </div>
+      )}
+
+      {/* Model selector */}
+      <div className="space-y-2">
+        <Label>{t("logs.filters.model")}</Label>
+        <Select
+          value={filters.model || "all"}
+          onValueChange={(value: string) =>
+            onFiltersChange({ ...filters, model: value === "all" ? undefined : value })
+          }
+          onOpenChange={onModelsOpenChange}
+        >
+          <SelectTrigger>
+            <SelectValue
+              placeholder={isModelsLoading ? t("logs.stats.loading") : t("logs.filters.allModels")}
+            />
+          </SelectTrigger>
+          <SelectContent>
+            <SelectItem value="all">{t("logs.filters.allModels")}</SelectItem>
+            {models.map((model) => (
+              <SelectItem key={model} value={model}>
+                {model}
+              </SelectItem>
+            ))}
+            {isModelsLoading && (
+              <div className="p-2 text-center text-muted-foreground text-sm">
+                {t("logs.stats.loading")}
+              </div>
+            )}
+          </SelectContent>
+        </Select>
+      </div>
+
+      {/* Endpoint selector */}
+      <div className="space-y-2">
+        <Label>{t("logs.filters.endpoint")}</Label>
+        <Select
+          value={filters.endpoint || "all"}
+          onValueChange={(value: string) =>
+            onFiltersChange({ ...filters, endpoint: value === "all" ? undefined : value })
+          }
+          onOpenChange={onEndpointsOpenChange}
+        >
+          <SelectTrigger>
+            <SelectValue
+              placeholder={
+                isEndpointsLoading ? t("logs.stats.loading") : t("logs.filters.allEndpoints")
+              }
+            />
+          </SelectTrigger>
+          <SelectContent>
+            <SelectItem value="all">{t("logs.filters.allEndpoints")}</SelectItem>
+            {endpoints.map((endpoint) => (
+              <SelectItem key={endpoint} value={endpoint}>
+                {endpoint}
+              </SelectItem>
+            ))}
+            {isEndpointsLoading && (
+              <div className="p-2 text-center text-muted-foreground text-sm">
+                {t("logs.stats.loading")}
+              </div>
+            )}
+          </SelectContent>
+        </Select>
+      </div>
+
+      {/* Session ID with suggestions */}
+      <div className="space-y-2">
+        <Label>{t("logs.filters.sessionId")}</Label>
+        <Popover open={sessionIdPopoverOpen} onOpenChange={setSessionIdPopoverOpen}>
+          <PopoverAnchor asChild>
+            <Input
+              value={filters.sessionId ?? ""}
+              placeholder={t("logs.filters.searchSessionId")}
+              onFocus={() => {
+                const term = (filters.sessionId ?? "").trim();
+                setSessionIdPopoverOpen(term.length >= SESSION_ID_SUGGESTION_MIN_LEN);
+              }}
+              onChange={(e) => {
+                const next = e.target.value.trim();
+                onFiltersChange({ ...filters, sessionId: next || undefined });
+                setSessionIdPopoverOpen(next.length >= SESSION_ID_SUGGESTION_MIN_LEN);
+              }}
+            />
+          </PopoverAnchor>
+          <PopoverContent
+            className="w-[320px] p-0"
+            align="start"
+            onOpenAutoFocus={(e) => e.preventDefault()}
+            onWheel={(e) => e.stopPropagation()}
+            onTouchMove={(e) => e.stopPropagation()}
+          >
+            <Command shouldFilter={false}>
+              <CommandList className="max-h-[250px] overflow-y-auto">
+                <CommandEmpty>
+                  {isSessionIdsLoading ? t("logs.stats.loading") : t("logs.filters.noSessionFound")}
+                </CommandEmpty>
+                <CommandGroup>
+                  {availableSessionIds.map((sessionId) => (
+                    <CommandItem
+                      key={sessionId}
+                      value={sessionId}
+                      onSelect={() => {
+                        onFiltersChange({ ...filters, sessionId });
+                        setSessionIdPopoverOpen(false);
+                      }}
+                      className="cursor-pointer"
+                    >
+                      <span className="flex-1 font-mono text-xs truncate">{sessionId}</span>
+                      {filters.sessionId === sessionId && (
+                        <Check className="h-4 w-4 text-primary" />
+                      )}
+                    </CommandItem>
+                  ))}
+                </CommandGroup>
+              </CommandList>
+            </Command>
+          </PopoverContent>
+        </Popover>
+      </div>
+    </div>
+  );
+}

+ 107 - 0
src/app/[locale]/dashboard/logs/_components/filters/status-filters.tsx

@@ -0,0 +1,107 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import { useMemo } from "react";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import { useLazyStatusCodes } from "../../_hooks/use-lazy-filter-options";
+import type { UsageLogFilters } from "./types";
+
+// Common status codes (shown immediately without loading)
+const COMMON_STATUS_CODES: number[] = [200, 400, 401, 429, 500];
+
+interface StatusFiltersProps {
+  filters: UsageLogFilters;
+  onFiltersChange: (filters: UsageLogFilters) => void;
+}
+
+export function StatusFilters({ filters, onFiltersChange }: StatusFiltersProps) {
+  const t = useTranslations("dashboard");
+
+  const {
+    data: dynamicStatusCodes,
+    isLoading: isStatusCodesLoading,
+    onOpenChange: onStatusCodesOpenChange,
+  } = useLazyStatusCodes();
+
+  // Merge hard-coded and dynamic status codes (deduplicated)
+  const allStatusCodes = useMemo(() => {
+    const dynamicOnly = dynamicStatusCodes.filter((code) => !COMMON_STATUS_CODES.includes(code));
+    return dynamicOnly;
+  }, [dynamicStatusCodes]);
+
+  const handleStatusCodeChange = (value: string) => {
+    onFiltersChange({
+      ...filters,
+      statusCode:
+        value && value !== "!200" && value !== "__all__" ? parseInt(value, 10) : undefined,
+      excludeStatusCode200: value === "!200",
+    });
+  };
+
+  const handleMinRetryCountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    onFiltersChange({
+      ...filters,
+      minRetryCount: e.target.value ? parseInt(e.target.value, 10) : undefined,
+    });
+  };
+
+  return (
+    <div className="grid gap-4 sm:grid-cols-2">
+      {/* Status code selector */}
+      <div className="space-y-2">
+        <Label>{t("logs.filters.statusCode")}</Label>
+        <Select
+          value={
+            filters.excludeStatusCode200 ? "!200" : filters.statusCode?.toString() || "__all__"
+          }
+          onValueChange={handleStatusCodeChange}
+          onOpenChange={onStatusCodesOpenChange}
+        >
+          <SelectTrigger>
+            <SelectValue placeholder={t("logs.filters.allStatusCodes")} />
+          </SelectTrigger>
+          <SelectContent>
+            <SelectItem value="__all__">{t("logs.filters.allStatusCodes")}</SelectItem>
+            <SelectItem value="!200">{t("logs.statusCodes.not200")}</SelectItem>
+            <SelectItem value="200">{t("logs.statusCodes.200")}</SelectItem>
+            <SelectItem value="400">{t("logs.statusCodes.400")}</SelectItem>
+            <SelectItem value="401">{t("logs.statusCodes.401")}</SelectItem>
+            <SelectItem value="429">{t("logs.statusCodes.429")}</SelectItem>
+            <SelectItem value="500">{t("logs.statusCodes.500")}</SelectItem>
+            {allStatusCodes.map((code) => (
+              <SelectItem key={code} value={code.toString()}>
+                {code}
+              </SelectItem>
+            ))}
+            {isStatusCodesLoading && (
+              <div className="p-2 text-center text-muted-foreground text-sm">
+                {t("logs.stats.loading")}
+              </div>
+            )}
+          </SelectContent>
+        </Select>
+      </div>
+
+      {/* Min retry count input */}
+      <div className="space-y-2">
+        <Label>{t("logs.filters.minRetryCount")}</Label>
+        <Input
+          type="number"
+          min={0}
+          inputMode="numeric"
+          value={filters.minRetryCount?.toString() ?? ""}
+          placeholder={t("logs.filters.minRetryCountPlaceholder")}
+          onChange={handleMinRetryCountChange}
+        />
+      </div>
+    </div>
+  );
+}

+ 152 - 0
src/app/[locale]/dashboard/logs/_components/filters/time-filters.tsx

@@ -0,0 +1,152 @@
+"use client";
+
+import { format } from "date-fns";
+import { useTranslations } from "next-intl";
+import { useCallback, useMemo } from "react";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+  dateStringWithClockToTimestamp,
+  formatClockFromTimestamp,
+  inclusiveEndTimestampFromExclusive,
+} from "../../_utils/time-range";
+import { LogsDateRangePicker } from "../logs-date-range-picker";
+import type { UsageLogFilters } from "./types";
+
+interface TimeFiltersProps {
+  filters: UsageLogFilters;
+  onFiltersChange: (filters: UsageLogFilters) => void;
+}
+
+export function TimeFilters({ filters, onFiltersChange }: TimeFiltersProps) {
+  const t = useTranslations("dashboard.logs.filters");
+
+  // Helper: convert timestamp to display date string (YYYY-MM-DD)
+  const timestampToDateString = useCallback((timestamp: number): string => {
+    const date = new Date(timestamp);
+    return format(date, "yyyy-MM-dd");
+  }, []);
+
+  // Memoized startDate for display (from timestamp)
+  const displayStartDate = useMemo(() => {
+    if (!filters.startTime) return undefined;
+    return timestampToDateString(filters.startTime);
+  }, [filters.startTime, timestampToDateString]);
+
+  const displayStartClock = useMemo(() => {
+    if (!filters.startTime) return undefined;
+    return formatClockFromTimestamp(filters.startTime);
+  }, [filters.startTime]);
+
+  // Memoized endDate calculation: endTime is exclusive, use endTime-1s to infer inclusive display end date
+  const displayEndDate = useMemo(() => {
+    if (!filters.endTime) return undefined;
+    const inclusiveEndTime = inclusiveEndTimestampFromExclusive(filters.endTime);
+    return format(new Date(inclusiveEndTime), "yyyy-MM-dd");
+  }, [filters.endTime]);
+
+  const displayEndClock = useMemo(() => {
+    if (!filters.endTime) return undefined;
+    const inclusiveEndTime = inclusiveEndTimestampFromExclusive(filters.endTime);
+    return formatClockFromTimestamp(inclusiveEndTime);
+  }, [filters.endTime]);
+
+  // Memoized callback for date range changes
+  const handleDateRangeChange = useCallback(
+    (range: { startDate?: string; endDate?: string }) => {
+      if (range.startDate && range.endDate) {
+        const startClock = displayStartClock ?? "00:00:00";
+        const endClock = displayEndClock ?? "23:59:59";
+        const startTimestamp = dateStringWithClockToTimestamp(range.startDate, startClock);
+        const endInclusiveTimestamp = dateStringWithClockToTimestamp(range.endDate, endClock);
+        if (startTimestamp === undefined || endInclusiveTimestamp === undefined) {
+          onFiltersChange({
+            ...filters,
+            startTime: undefined,
+            endTime: undefined,
+          });
+          return;
+        }
+        const endTimestamp = endInclusiveTimestamp + 1000;
+        onFiltersChange({
+          ...filters,
+          startTime: startTimestamp,
+          endTime: endTimestamp,
+        });
+      } else {
+        onFiltersChange({
+          ...filters,
+          startTime: undefined,
+          endTime: undefined,
+        });
+      }
+    },
+    [displayEndClock, displayStartClock, filters, onFiltersChange]
+  );
+
+  const handleStartTimeChange = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>) => {
+      const nextClock = e.target.value || "00:00:00";
+      if (!filters.startTime) return;
+      const dateStr = timestampToDateString(filters.startTime);
+      const startTime = dateStringWithClockToTimestamp(dateStr, nextClock);
+      if (startTime === undefined) return;
+      onFiltersChange({
+        ...filters,
+        startTime,
+      });
+    },
+    [filters, onFiltersChange, timestampToDateString]
+  );
+
+  const handleEndTimeChange = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>) => {
+      const nextClock = e.target.value || "23:59:59";
+      if (!filters.endTime) return;
+      const inclusiveEndTime = inclusiveEndTimestampFromExclusive(filters.endTime);
+      const endDateStr = timestampToDateString(inclusiveEndTime);
+      const endInclusiveTimestamp = dateStringWithClockToTimestamp(endDateStr, nextClock);
+      if (endInclusiveTimestamp === undefined) return;
+      onFiltersChange({
+        ...filters,
+        endTime: endInclusiveTimestamp + 1000,
+      });
+    },
+    [filters, onFiltersChange, timestampToDateString]
+  );
+
+  return (
+    <div className="space-y-3">
+      <div className="space-y-2">
+        <Label>{t("dateRange")}</Label>
+        <LogsDateRangePicker
+          startDate={displayStartDate}
+          endDate={displayEndDate}
+          onDateRangeChange={handleDateRangeChange}
+        />
+      </div>
+      <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
+        <div className="space-y-1">
+          <Label className="text-xs text-muted-foreground">{t("startTime")}</Label>
+          <Input
+            type="time"
+            step={1}
+            value={displayStartClock ?? ""}
+            disabled={!displayStartDate}
+            onChange={handleStartTimeChange}
+          />
+        </div>
+        <div className="space-y-1">
+          <Label className="text-xs text-muted-foreground">{t("endTime")}</Label>
+          <Input
+            type="time"
+            step={1}
+            value={displayEndClock ?? ""}
+            disabled={!displayEndDate}
+            onChange={handleEndTimeChange}
+          />
+        </div>
+      </div>
+    </div>
+  );
+}

+ 55 - 0
src/app/[locale]/dashboard/logs/_components/filters/types.ts

@@ -0,0 +1,55 @@
+import type { Key } from "@/types/key";
+import type { ProviderDisplay } from "@/types/provider";
+
+/**
+ * Filter values for usage logs
+ */
+export interface UsageLogFilters {
+  userId?: number;
+  keyId?: number;
+  providerId?: number;
+  sessionId?: string;
+  /** Start timestamp (ms, local timezone 00:00:00) */
+  startTime?: number;
+  /** End timestamp (ms, local timezone next day 00:00:00, for < comparison) */
+  endTime?: number;
+  statusCode?: number;
+  excludeStatusCode200?: boolean;
+  model?: string;
+  endpoint?: string;
+  minRetryCount?: number;
+}
+
+/**
+ * Props passed to filter section components
+ */
+export interface FilterSectionProps {
+  isAdmin: boolean;
+  filters: UsageLogFilters;
+  onFiltersChange: (filters: UsageLogFilters) => void;
+}
+
+/**
+ * Props for identity filters (user + key)
+ */
+export interface IdentityFiltersProps extends FilterSectionProps {
+  initialKeys: Key[];
+  isKeysLoading?: boolean;
+}
+
+/**
+ * Props for request filters (provider + model + endpoint + session)
+ */
+export interface RequestFiltersProps extends FilterSectionProps {
+  providers: ProviderDisplay[];
+  isProvidersLoading?: boolean;
+}
+
+/**
+ * Display names resolver
+ */
+export interface FilterDisplayNames {
+  getUserName: (id: number) => string | undefined;
+  getKeyName: (id: number) => string | undefined;
+  getProviderName: (id: number) => string | undefined;
+}

+ 325 - 46
src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx

@@ -1,40 +1,85 @@
 "use client";
 
-import { InfoIcon } from "lucide-react";
+import {
+  AlertTriangle,
+  CheckCircle,
+  ChevronRight,
+  InfoIcon,
+  Link2,
+  RefreshCw,
+  XCircle,
+  Zap,
+} from "lucide-react";
 import { useTranslations } from "next-intl";
 import { Badge } from "@/components/ui/badge";
 import { Button } from "@/components/ui/button";
 import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
-import { formatProviderDescription } from "@/lib/utils/provider-chain-formatter";
+import { cn } from "@/lib/utils";
 import type { ProviderChainItem } from "@/types/message";
 
 interface ProviderChainPopoverProps {
   chain: ProviderChainItem[];
   finalProvider: string;
-  /** 是否会显示倍率 Badge,影响名称最大宽度 */
+  /** Whether a cost badge is displayed, affects name max width */
   hasCostBadge?: boolean;
 }
 
 /**
- * 判断是否为实际请求记录(排除中间状态)
+ * Determine if this is an actual request record (excluding intermediate states)
  */
 function isActualRequest(item: ProviderChainItem): boolean {
-  // 并发限制失败:算作一次尝试
   if (item.reason === "concurrent_limit_failed") return true;
-
-  // 失败记录
   if (item.reason === "retry_failed" || item.reason === "system_error") return true;
-
-  // 成功记录:必须有 statusCode
   if ((item.reason === "request_success" || item.reason === "retry_success") && item.statusCode) {
     return true;
   }
-
-  // 其他都是中间状态
   return false;
 }
 
+/**
+ * Get status icon and color for a provider chain item
+ */
+function getItemStatus(item: ProviderChainItem): {
+  icon: React.ElementType;
+  color: string;
+  bgColor: string;
+} {
+  if ((item.reason === "request_success" || item.reason === "retry_success") && item.statusCode) {
+    return {
+      icon: CheckCircle,
+      color: "text-emerald-600",
+      bgColor: "bg-emerald-50 dark:bg-emerald-950/30",
+    };
+  }
+  if (item.reason === "retry_failed" || item.reason === "system_error") {
+    return {
+      icon: XCircle,
+      color: "text-rose-600",
+      bgColor: "bg-rose-50 dark:bg-rose-950/30",
+    };
+  }
+  if (item.reason === "concurrent_limit_failed") {
+    return {
+      icon: Zap,
+      color: "text-amber-600",
+      bgColor: "bg-amber-50 dark:bg-amber-950/30",
+    };
+  }
+  if (item.reason === "client_error_non_retryable") {
+    return {
+      icon: AlertTriangle,
+      color: "text-orange-600",
+      bgColor: "bg-orange-50 dark:bg-orange-950/30",
+    };
+  }
+  return {
+    icon: RefreshCw,
+    color: "text-slate-500",
+    bgColor: "bg-slate-50 dark:bg-slate-800/50",
+  };
+}
+
 export function ProviderChainPopover({
   chain,
   finalProvider,
@@ -43,28 +88,181 @@ export function ProviderChainPopover({
   const t = useTranslations("dashboard");
   const tChain = useTranslations("provider-chain");
 
-  // 计算实际请求次数(排除中间状态)
+  // Calculate actual request count (excluding intermediate states)
   const requestCount = chain.filter(isActualRequest).length;
 
-  // 空字符串兜底
+  // Fallback for empty string
   const displayName = finalProvider || "-";
 
-  // 根据是否有倍率 Badge 决定名称最大宽度
+  // Determine max width based on whether cost badge is present
   const maxWidthClass = hasCostBadge ? "max-w-[140px]" : "max-w-[180px]";
 
-  // 如果只有一次请求,不显示 popover,只显示带 Tooltip 的名称
+  // Check if this is a session reuse
+  const isSessionReuse =
+    chain[0]?.reason === "session_reuse" || chain[0]?.selectionMethod === "session_reuse";
+
+  // Get initial selection context for tooltip
+  const initialSelection = chain.find((item) => item.reason === "initial_selection");
+  const selectionContext = initialSelection?.decisionContext;
+
+  // Single request: show name with icon and compact tooltip
   if (requestCount <= 1) {
+    // Get session reuse context for detailed tooltip
+    const sessionReuseItem = chain.find(
+      (item) => item.reason === "session_reuse" || item.selectionMethod === "session_reuse"
+    );
+    const sessionReuseContext = sessionReuseItem?.decisionContext;
+
     return (
       <div className={`${maxWidthClass} min-w-0 w-full`}>
         <TooltipProvider>
           <Tooltip delayDuration={300}>
             <TooltipTrigger asChild>
-              <span className="truncate block cursor-help" dir="auto">
-                {displayName}
+              <span className="truncate flex items-center gap-1 cursor-help" dir="auto">
+                {/* Session reuse indicator */}
+                {isSessionReuse && <Link2 className="h-3 w-3 shrink-0 text-violet-500" />}
+                {/* Initial selection: show compact priority badge before name */}
+                {!isSessionReuse && selectionContext && (
+                  <span className="shrink-0 text-[10px] text-emerald-600 dark:text-emerald-400 font-mono font-medium">
+                    P{selectionContext.selectedPriority}
+                  </span>
+                )}
+                <span className="truncate">{displayName}</span>
               </span>
             </TooltipTrigger>
-            <TooltipContent side="bottom" align="start">
-              <p className="text-xs">{displayName}</p>
+            <TooltipContent side="bottom" align="start" className="max-w-[320px]">
+              <div className="space-y-2">
+                {/* Provider name */}
+                <div className="font-medium text-xs">{displayName}</div>
+
+                {/* Session reuse detailed info */}
+                {isSessionReuse && (
+                  <div className="space-y-1.5 pt-1 border-t border-zinc-600 dark:border-zinc-300">
+                    <div className="flex items-center gap-1.5 text-[10px] text-violet-400 dark:text-violet-600 font-medium">
+                      <Link2 className="h-3 w-3" />
+                      <span>{tChain("reasons.session_reuse")}</span>
+                    </div>
+                    <div className="grid grid-cols-2 gap-x-3 gap-y-1 text-[10px] pl-1">
+                      {sessionReuseContext?.sessionAge !== undefined && (
+                        <div>
+                          <span className="text-zinc-400 dark:text-zinc-500">
+                            {tChain("timeline.sessionAge") || "Age"}:
+                          </span>{" "}
+                          <span className="text-zinc-200 dark:text-zinc-700">
+                            {sessionReuseContext.sessionAge}s
+                          </span>
+                        </div>
+                      )}
+                      {sessionReuseItem?.priority !== undefined && (
+                        <div>
+                          <span className="text-zinc-400 dark:text-zinc-500">
+                            {tChain("details.priority")}:
+                          </span>{" "}
+                          <span className="text-zinc-200 dark:text-zinc-700">
+                            P{sessionReuseItem.priority}
+                          </span>
+                        </div>
+                      )}
+                      {sessionReuseItem?.costMultiplier !== undefined && (
+                        <div>
+                          <span className="text-zinc-400 dark:text-zinc-500">
+                            {tChain("details.costMultiplier")}:
+                          </span>{" "}
+                          <span className="text-zinc-200 dark:text-zinc-700">
+                            x{sessionReuseItem.costMultiplier}
+                          </span>
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                )}
+
+                {/* Initial selection detailed info */}
+                {!isSessionReuse && selectionContext && (
+                  <div className="space-y-1.5 pt-1 border-t border-zinc-600 dark:border-zinc-300">
+                    <div className="text-[10px] text-zinc-300 dark:text-zinc-600 font-medium">
+                      {tChain("timeline.initialSelection") || "Initial Selection"}
+                    </div>
+                    {/* Selection funnel */}
+                    <div className="flex items-center gap-1 text-[10px] text-zinc-200 dark:text-zinc-700">
+                      <span>{selectionContext.totalProviders}</span>
+                      <span className="text-zinc-400 dark:text-zinc-500">total</span>
+                      <ChevronRight className="h-2.5 w-2.5" />
+                      <span>{selectionContext.enabledProviders}</span>
+                      <span className="text-zinc-400 dark:text-zinc-500">enabled</span>
+                      <ChevronRight className="h-2.5 w-2.5" />
+                      <span>{selectionContext.afterHealthCheck}</span>
+                      <span className="text-zinc-400 dark:text-zinc-500">healthy</span>
+                    </div>
+                    {/* Priority and candidates */}
+                    <div className="text-[10px] space-y-0.5 pl-1">
+                      <div className="flex items-center gap-1">
+                        <span className="text-zinc-400 dark:text-zinc-500">
+                          {tChain("details.priority")}:
+                        </span>
+                        <span className="text-zinc-200 dark:text-zinc-700 font-medium">
+                          P{selectionContext.selectedPriority}
+                        </span>
+                        {selectionContext.candidatesAtPriority && (
+                          <span className="text-zinc-400 dark:text-zinc-500">
+                            ({selectionContext.candidatesAtPriority.length} candidates)
+                          </span>
+                        )}
+                      </div>
+                      {/* Show candidates with probability */}
+                      {selectionContext.candidatesAtPriority &&
+                        selectionContext.candidatesAtPriority.length > 1 && (
+                          <div className="text-zinc-400 dark:text-zinc-500">
+                            {selectionContext.candidatesAtPriority.map((c, i) => (
+                              <span key={c.id}>
+                                {i > 0 && ", "}
+                                <span
+                                  className={
+                                    c.name === displayName
+                                      ? "text-zinc-200 dark:text-zinc-700 font-medium"
+                                      : ""
+                                  }
+                                >
+                                  {c.name}
+                                </span>
+                                {c.probability !== undefined && (
+                                  <span className="text-zinc-500 dark:text-zinc-400">
+                                    (
+                                    {c.probability <= 1
+                                      ? (c.probability * 100).toFixed(0)
+                                      : c.probability}
+                                    %)
+                                  </span>
+                                )}
+                              </span>
+                            ))}
+                          </div>
+                        )}
+                    </div>
+                    {/* Provider config */}
+                    {initialSelection && (
+                      <div className="grid grid-cols-3 gap-x-2 text-[10px] text-zinc-400 dark:text-zinc-500 pt-1">
+                        {initialSelection.weight !== undefined && (
+                          <div>
+                            <span>{tChain("details.weight")}:</span>{" "}
+                            <span className="text-zinc-200 dark:text-zinc-700">
+                              {initialSelection.weight}
+                            </span>
+                          </div>
+                        )}
+                        {initialSelection.costMultiplier !== undefined && (
+                          <div>
+                            <span>{tChain("details.costMultiplier")}:</span>{" "}
+                            <span className="text-zinc-200 dark:text-zinc-700">
+                              x{initialSelection.costMultiplier}
+                            </span>
+                          </div>
+                        )}
+                      </div>
+                    )}
+                  </div>
+                )}
+              </div>
             </TooltipContent>
           </Tooltip>
         </TooltipProvider>
@@ -72,6 +270,21 @@ export function ProviderChainPopover({
     );
   }
 
+  // Multiple requests: show popover with visual chain
+  const actualRequests = chain.filter(isActualRequest);
+
+  // Get the successful provider's costMultiplier and groupTag
+  const successfulProvider = [...chain]
+    .reverse()
+    .find((item) => item.reason === "request_success" || item.reason === "retry_success");
+  const finalCostMultiplier = successfulProvider?.costMultiplier;
+  const finalGroupTag = successfulProvider?.groupTag;
+  const hasFinalCostBadge =
+    finalCostMultiplier !== undefined &&
+    finalCostMultiplier !== null &&
+    Number.isFinite(finalCostMultiplier) &&
+    finalCostMultiplier !== 1;
+
   return (
     <Popover>
       <PopoverTrigger asChild>
@@ -82,48 +295,114 @@ export function ProviderChainPopover({
           aria-label={`${displayName} - ${requestCount}${t("logs.table.times")}`}
         >
           <span className="flex w-full items-center gap-1 min-w-0">
-            <div className={`${maxWidthClass} min-w-0 flex-1`}>
-              <TooltipProvider>
-                <Tooltip delayDuration={300}>
-                  <TooltipTrigger asChild>
-                    <span className="truncate block cursor-help" dir="auto">
-                      {displayName}
-                    </span>
-                  </TooltipTrigger>
-                  <TooltipContent side="bottom" align="start">
-                    <p className="text-xs">{displayName}</p>
-                  </TooltipContent>
-                </Tooltip>
-              </TooltipProvider>
-            </div>
-            <Badge variant="secondary" className="shrink-0 ml-1">
+            {/* Request count badge */}
+            <Badge variant="secondary" className="shrink-0">
               {requestCount}
               {t("logs.table.times")}
             </Badge>
+            {/* Provider name */}
+            <span className="truncate min-w-0" dir="auto">
+              {displayName}
+            </span>
+            {/* Cost multiplier badge (if not 1) */}
+            {hasFinalCostBadge && (
+              <Badge
+                variant="outline"
+                className={cn(
+                  "text-[10px] px-1 py-0 shrink-0",
+                  finalCostMultiplier > 1
+                    ? "bg-orange-50 text-orange-700 border-orange-200 dark:bg-orange-950/30 dark:text-orange-300 dark:border-orange-800"
+                    : "bg-green-50 text-green-700 border-green-200 dark:bg-green-950/30 dark:text-green-300 dark:border-green-800"
+                )}
+              >
+                x{finalCostMultiplier.toFixed(2)}
+              </Badge>
+            )}
+            {/* Group tag badge (if present) */}
+            {finalGroupTag && (
+              <Badge
+                variant="outline"
+                className="text-[10px] px-1 py-0 shrink-0 bg-slate-50 text-slate-600 border-slate-200 dark:bg-slate-900/30 dark:text-slate-400 dark:border-slate-700"
+              >
+                {finalGroupTag}
+              </Badge>
+            )}
+            {/* Info icon */}
             <InfoIcon className="h-3 w-3 text-muted-foreground shrink-0" aria-hidden="true" />
           </span>
         </Button>
       </PopoverTrigger>
 
-      <PopoverContent className="w-[500px] max-w-[calc(100vw-2rem)]" align="start">
-        <div className="space-y-3">
+      <PopoverContent className="w-[360px] max-w-[calc(100vw-2rem)] p-0" align="start">
+        <div className="p-3 border-b">
           <div className="flex items-center justify-between">
             <h4 className="font-semibold text-sm">{t("logs.providerChain.decisionChain")}</h4>
-            <Badge variant="outline">
-              {requestCount}
-              {t("logs.table.times")}
+            <Badge variant="outline" className="text-[10px]">
+              {requestCount} {t("logs.table.times")}
             </Badge>
           </div>
+        </div>
 
-          <div className="rounded-md border bg-muted/50 p-4 max-h-[300px] overflow-y-auto overflow-x-hidden">
-            <pre className="text-xs whitespace-pre-wrap break-words leading-relaxed">
-              {formatProviderDescription(chain, tChain)}
-            </pre>
-          </div>
+        {/* Visual chain */}
+        <div className="p-3 space-y-0 max-h-[300px] overflow-y-auto">
+          {actualRequests.map((item, index) => {
+            const status = getItemStatus(item);
+            const Icon = status.icon;
+            const isLast = index === actualRequests.length - 1;
+
+            return (
+              <div key={`${item.id}-${index}`} className="relative flex gap-2">
+                {/* Timeline connector */}
+                <div className="flex flex-col items-center">
+                  <div
+                    className={cn(
+                      "flex h-6 w-6 shrink-0 items-center justify-center rounded-full border",
+                      status.bgColor
+                    )}
+                  >
+                    <Icon className={cn("h-3 w-3", status.color)} />
+                  </div>
+                  {!isLast && <div className="w-0.5 flex-1 min-h-[8px] bg-border" />}
+                </div>
 
-          <div className="text-xs text-muted-foreground text-center">
+                {/* Content */}
+                <div className={cn("flex-1 pb-3", isLast && "pb-0")}>
+                  <div className="flex items-center gap-2">
+                    <span className="text-xs font-medium">{item.name}</span>
+                    {item.statusCode && (
+                      <Badge
+                        variant="outline"
+                        className={cn(
+                          "text-[10px] px-1 py-0",
+                          item.statusCode >= 200 && item.statusCode < 300
+                            ? "border-emerald-500 text-emerald-600"
+                            : "border-rose-500 text-rose-600"
+                        )}
+                      >
+                        {item.statusCode}
+                      </Badge>
+                    )}
+                    {item.reason && !item.statusCode && (
+                      <span className="text-[10px] text-muted-foreground">
+                        {tChain(`reasons.${item.reason}`)}
+                      </span>
+                    )}
+                  </div>
+                  {item.errorMessage && (
+                    <p className="text-[10px] text-muted-foreground mt-0.5 line-clamp-1">
+                      {item.errorMessage}
+                    </p>
+                  )}
+                </div>
+              </div>
+            );
+          })}
+        </div>
+
+        <div className="p-2 border-t bg-muted/30">
+          <p className="text-[10px] text-muted-foreground text-center">
             {t("logs.details.clickStatusCode")}
-          </div>
+          </p>
         </div>
       </PopoverContent>
     </Popover>

+ 215 - 797
src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx

@@ -1,51 +1,47 @@
 "use client";
 
-import { format } from "date-fns";
-import { Check, ChevronsUpDown, Download } from "lucide-react";
+import { format, startOfDay, startOfWeek } from "date-fns";
+import { Clock, Download, Network, Server, User } from "lucide-react";
 import { useTranslations } from "next-intl";
-
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { useCallback, useMemo, useState } from "react";
 import { toast } from "sonner";
-import { getKeys } from "@/actions/keys";
-import { exportUsageLogs, getUsageLogSessionIdSuggestions } from "@/actions/usage-logs";
-import { searchUsersForFilter } from "@/actions/users";
+import { exportUsageLogs } from "@/actions/usage-logs";
 import { Button } from "@/components/ui/button";
-import {
-  Command,
-  CommandEmpty,
-  CommandGroup,
-  CommandInput,
-  CommandItem,
-  CommandList,
-} from "@/components/ui/command";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
-import {
-  Select,
-  SelectContent,
-  SelectItem,
-  SelectTrigger,
-  SelectValue,
-} from "@/components/ui/select";
-import { SESSION_ID_SUGGESTION_MIN_LEN } from "@/lib/constants/usage-logs.constants";
-import { useDebounce } from "@/lib/hooks/use-debounce";
 import type { Key } from "@/types/key";
 import type { ProviderDisplay } from "@/types/provider";
-import {
-  useLazyEndpoints,
-  useLazyModels,
-  useLazyStatusCodes,
-} from "../_hooks/use-lazy-filter-options";
-import {
-  dateStringWithClockToTimestamp,
-  formatClockFromTimestamp,
-  inclusiveEndTimestampFromExclusive,
-} from "../_utils/time-range";
-import { LogsDateRangePicker } from "./logs-date-range-picker";
-
-// 硬编码常用状态码(首次渲染时显示,无需等待加载)
-const COMMON_STATUS_CODES: number[] = [200, 400, 401, 429, 500];
+import { ActiveFiltersDisplay } from "./filters/active-filters-display";
+import { FilterSection } from "./filters/filter-section";
+import { IdentityFilters } from "./filters/identity-filters";
+import { type FilterPreset, QuickFiltersBar } from "./filters/quick-filters-bar";
+import { RequestFilters } from "./filters/request-filters";
+import { StatusFilters } from "./filters/status-filters";
+import { TimeFilters } from "./filters/time-filters";
+import type { UsageLogFilters } from "./filters/types";
+
+// Valid keys for UsageLogFilters - strip any runtime-leaked fields like 'page'
+const VALID_FILTER_KEYS: (keyof UsageLogFilters)[] = [
+  "userId",
+  "keyId",
+  "providerId",
+  "sessionId",
+  "startTime",
+  "endTime",
+  "statusCode",
+  "excludeStatusCode200",
+  "model",
+  "endpoint",
+  "minRetryCount",
+];
+
+function sanitizeFilters(filters: UsageLogFilters): UsageLogFilters {
+  const result: UsageLogFilters = {};
+  for (const key of VALID_FILTER_KEYS) {
+    if (filters[key] !== undefined) {
+      (result as Record<string, unknown>)[key] = filters[key];
+    }
+  }
+  return result;
+}
 
 interface UsageLogsFiltersProps {
   isAdmin: boolean;
@@ -53,22 +49,8 @@ interface UsageLogsFiltersProps {
   initialKeys: Key[];
   isProvidersLoading?: boolean;
   isKeysLoading?: boolean;
-  filters: {
-    userId?: number;
-    keyId?: number;
-    providerId?: number;
-    sessionId?: string;
-    /** 开始时间戳(毫秒,浏览器本地时区的 00:00:00) */
-    startTime?: number;
-    /** 结束时间戳(毫秒,浏览器本地时区的次日 00:00:00,用于 < 比较) */
-    endTime?: number;
-    statusCode?: number;
-    excludeStatusCode200?: boolean;
-    model?: string;
-    endpoint?: string;
-    minRetryCount?: number;
-  };
-  onChange: (filters: UsageLogsFiltersProps["filters"]) => void;
+  filters: UsageLogFilters;
+  onChange: (filters: UsageLogFilters) => void;
   onReset: () => void;
 }
 
@@ -84,275 +66,81 @@ export function UsageLogsFilters({
 }: UsageLogsFiltersProps) {
   const t = useTranslations("dashboard");
 
-  const [isUsersLoading, setIsUsersLoading] = useState(false);
-  const [userSearchTerm, setUserSearchTerm] = useState("");
-  const debouncedUserSearchTerm = useDebounce(userSearchTerm, 300);
+  const [localFilters, setLocalFilters] = useState<UsageLogFilters>(filters);
+  const [isExporting, setIsExporting] = useState(false);
+  const [activePreset, setActivePreset] = useState<FilterPreset | null>(null);
+
+  // Track users and keys for display name resolution
   const [availableUsers, setAvailableUsers] = useState<Array<{ id: number; name: string }>>([]);
-  const userSearchRequestIdRef = useRef(0);
-  const lastLoadedUserSearchTermRef = useRef<string | undefined>(undefined);
-  const isMountedRef = useRef(true);
-
-  // 惰性加载 hooks - 下拉展开时才加载数据
-  const {
-    data: models,
-    isLoading: isModelsLoading,
-    onOpenChange: onModelsOpenChange,
-  } = useLazyModels();
-
-  const {
-    data: dynamicStatusCodes,
-    isLoading: isStatusCodesLoading,
-    onOpenChange: onStatusCodesOpenChange,
-  } = useLazyStatusCodes();
-
-  const {
-    data: endpoints,
-    isLoading: isEndpointsLoading,
-    onOpenChange: onEndpointsOpenChange,
-  } = useLazyEndpoints();
-
-  // 合并硬编码和动态状态码(去重)
-  const allStatusCodes = useMemo(() => {
-    const dynamicOnly = dynamicStatusCodes.filter((code) => !COMMON_STATUS_CODES.includes(code));
-    return dynamicOnly;
-  }, [dynamicStatusCodes]);
+  const [keys, setKeys] = useState<Key[]>(initialKeys);
 
   const userMap = useMemo(
     () => new Map(availableUsers.map((user) => [user.id, user.name])),
     [availableUsers]
   );
 
+  const keyMap = useMemo(() => new Map(keys.map((key) => [key.id, key.name])), [keys]);
+
   const providerMap = useMemo(
     () => new Map(providers.map((provider) => [provider.id, provider.name])),
     [providers]
   );
 
-  const [keys, setKeys] = useState<Key[]>(initialKeys);
-  const [localFilters, setLocalFilters] = useState(filters);
-  const [isExporting, setIsExporting] = useState(false);
-  const [userPopoverOpen, setUserPopoverOpen] = useState(false);
-  const [providerPopoverOpen, setProviderPopoverOpen] = useState(false);
-  const [sessionIdPopoverOpen, setSessionIdPopoverOpen] = useState(false);
-  const [isSessionIdsLoading, setIsSessionIdsLoading] = useState(false);
-  const [availableSessionIds, setAvailableSessionIds] = useState<string[]>([]);
-  const debouncedSessionIdSearchTerm = useDebounce(localFilters.sessionId ?? "", 300);
-  const sessionIdSearchRequestIdRef = useRef(0);
-  const lastLoadedSessionIdSuggestionsKeyRef = useRef<string | undefined>(undefined);
-
-  useEffect(() => {
-    isMountedRef.current = true;
-    return () => {
-      isMountedRef.current = false;
-    };
-  }, []);
-
-  const loadUsersForFilter = useCallback(async (term?: string) => {
-    const requestId = ++userSearchRequestIdRef.current;
-    setIsUsersLoading(true);
-    lastLoadedUserSearchTermRef.current = term;
-
-    try {
-      const result = await searchUsersForFilter(term);
-      if (!isMountedRef.current || requestId !== userSearchRequestIdRef.current) return;
-
-      if (result.ok) {
-        setAvailableUsers(result.data);
-      } else {
-        console.error("Failed to load users for filter:", result.error);
-        setAvailableUsers([]);
-      }
-    } catch (error) {
-      if (!isMountedRef.current || requestId !== userSearchRequestIdRef.current) return;
-
-      console.error("Failed to load users for filter:", error);
-      setAvailableUsers([]);
-    } finally {
-      if (isMountedRef.current && requestId === userSearchRequestIdRef.current) {
-        setIsUsersLoading(false);
-      }
-    }
-  }, []);
-
-  useEffect(() => {
-    if (!isAdmin) return;
-    void loadUsersForFilter(undefined);
-  }, [isAdmin, loadUsersForFilter]);
-
-  useEffect(() => {
-    if (!isAdmin || !userPopoverOpen) return;
-
-    const term = debouncedUserSearchTerm.trim() || undefined;
-    if (term === lastLoadedUserSearchTermRef.current) return;
-
-    void loadUsersForFilter(term);
-  }, [isAdmin, userPopoverOpen, debouncedUserSearchTerm, loadUsersForFilter]);
-
-  useEffect(() => {
-    if (!isAdmin) return;
-    if (!userPopoverOpen) {
-      setUserSearchTerm("");
-    }
-  }, [isAdmin, userPopoverOpen]);
-
-  const loadSessionIdsForFilter = useCallback(
-    async (term: string) => {
-      const requestId = ++sessionIdSearchRequestIdRef.current;
-      setIsSessionIdsLoading(true);
-      const requestKey = [
-        term,
-        isAdmin ? (localFilters.userId ?? "").toString() : "",
-        (localFilters.keyId ?? "").toString(),
-        (localFilters.providerId ?? "").toString(),
-        isAdmin ? "1" : "0",
-      ].join("|");
-      lastLoadedSessionIdSuggestionsKeyRef.current = requestKey;
-
-      try {
-        const result = await getUsageLogSessionIdSuggestions({
-          term,
-          userId: isAdmin ? localFilters.userId : undefined,
-          keyId: localFilters.keyId,
-          providerId: localFilters.providerId,
-        });
-
-        if (!isMountedRef.current || requestId !== sessionIdSearchRequestIdRef.current) return;
-
-        if (result.ok) {
-          setAvailableSessionIds(result.data);
-        } else {
-          console.error("Failed to load sessionId suggestions:", result.error);
-          setAvailableSessionIds([]);
-        }
-      } catch (error) {
-        if (!isMountedRef.current || requestId !== sessionIdSearchRequestIdRef.current) return;
-        console.error("Failed to load sessionId suggestions:", error);
-        setAvailableSessionIds([]);
-      } finally {
-        if (isMountedRef.current && requestId === sessionIdSearchRequestIdRef.current) {
-          setIsSessionIdsLoading(false);
-        }
-      }
-    },
-    [isAdmin, localFilters.keyId, localFilters.providerId, localFilters.userId]
+  const displayNames = useMemo(
+    () => ({
+      getUserName: (id: number) => userMap.get(id),
+      getKeyName: (id: number) => keyMap.get(id),
+      getProviderName: (id: number) => providerMap.get(id),
+    }),
+    [userMap, keyMap, providerMap]
   );
 
-  useEffect(() => {
-    if (!sessionIdPopoverOpen) return;
-
-    const term = debouncedSessionIdSearchTerm.trim();
-    if (term.length < SESSION_ID_SUGGESTION_MIN_LEN) {
-      setAvailableSessionIds([]);
-      lastLoadedSessionIdSuggestionsKeyRef.current = undefined;
-      return;
-    }
-
-    const requestKey = [
-      term,
-      isAdmin ? (localFilters.userId ?? "").toString() : "",
-      (localFilters.keyId ?? "").toString(),
-      (localFilters.providerId ?? "").toString(),
-      isAdmin ? "1" : "0",
-    ].join("|");
-    if (requestKey === lastLoadedSessionIdSuggestionsKeyRef.current) return;
-    void loadSessionIdsForFilter(term);
+  // Count active filters for each section
+  const timeActiveCount = useMemo(() => {
+    let count = 0;
+    if (localFilters.startTime && localFilters.endTime) count++;
+    return count;
+  }, [localFilters.startTime, localFilters.endTime]);
+
+  const identityActiveCount = useMemo(() => {
+    let count = 0;
+    if (isAdmin && localFilters.userId !== undefined) count++;
+    if (localFilters.keyId !== undefined) count++;
+    return count;
+  }, [isAdmin, localFilters.userId, localFilters.keyId]);
+
+  const requestActiveCount = useMemo(() => {
+    let count = 0;
+    if (isAdmin && localFilters.providerId !== undefined) count++;
+    if (localFilters.model) count++;
+    if (localFilters.endpoint) count++;
+    if (localFilters.sessionId) count++;
+    return count;
   }, [
-    sessionIdPopoverOpen,
-    debouncedSessionIdSearchTerm,
     isAdmin,
-    localFilters.userId,
-    localFilters.keyId,
     localFilters.providerId,
-    loadSessionIdsForFilter,
+    localFilters.model,
+    localFilters.endpoint,
+    localFilters.sessionId,
   ]);
 
-  useEffect(() => {
-    if (!sessionIdPopoverOpen) {
-      setAvailableSessionIds([]);
-      lastLoadedSessionIdSuggestionsKeyRef.current = undefined;
-    }
-  }, [sessionIdPopoverOpen]);
-
-  useEffect(() => {
-    if (initialKeys.length > 0) {
-      setKeys(initialKeys);
-    }
-  }, [initialKeys]);
-
-  // 管理员用户首次加载时,如果 URL 中有 userId 参数,需要加载该用户的 keys
-  // biome-ignore lint/correctness/useExhaustiveDependencies: 故意仅在组件挂载时执行一次
-  useEffect(() => {
-    const loadInitialKeys = async () => {
-      if (isAdmin && filters.userId && initialKeys.length === 0) {
-        try {
-          const keysResult = await getKeys(filters.userId);
-          if (keysResult.ok && keysResult.data) {
-            setKeys(keysResult.data);
-          }
-        } catch (error) {
-          console.error("Failed to load initial keys:", error);
-        }
-      }
-    };
-    loadInitialKeys();
-  }, []);
-
-  // 处理用户选择变更
-  const handleUserChange = async (userId: string) => {
-    const newUserId = userId ? parseInt(userId, 10) : undefined;
-    const newFilters = { ...localFilters, userId: newUserId, keyId: undefined };
-    setLocalFilters(newFilters);
-
-    // 加载该用户的 keys
-    if (newUserId) {
-      try {
-        const keysResult = await getKeys(newUserId);
-        if (keysResult.ok && keysResult.data) {
-          setKeys(keysResult.data);
-        }
-      } catch (error) {
-        console.error("Failed to load keys:", error);
-        toast.error(t("logs.error.loadKeysFailed"));
-      }
-    } else {
-      setKeys([]);
-    }
-  };
+  const statusActiveCount = useMemo(() => {
+    let count = 0;
+    if (localFilters.statusCode !== undefined || localFilters.excludeStatusCode200) count++;
+    if (localFilters.minRetryCount !== undefined && localFilters.minRetryCount > 0) count++;
+    return count;
+  }, [localFilters.statusCode, localFilters.excludeStatusCode200, localFilters.minRetryCount]);
 
-  const handleApply = () => {
-    const {
-      userId,
-      keyId,
-      providerId,
-      sessionId,
-      startTime,
-      endTime,
-      statusCode,
-      excludeStatusCode200,
-      model,
-      endpoint,
-      minRetryCount,
-    } = localFilters;
-
-    onChange({
-      userId,
-      keyId,
-      providerId,
-      sessionId,
-      startTime,
-      endTime,
-      statusCode,
-      excludeStatusCode200,
-      model,
-      endpoint,
-      minRetryCount,
-    });
-  };
+  const handleApply = useCallback(() => {
+    onChange(sanitizeFilters(localFilters));
+  }, [localFilters, onChange]);
 
-  const handleReset = () => {
+  const handleReset = useCallback(() => {
     setLocalFilters({});
     setKeys([]);
+    setActivePreset(null);
     onReset();
-  };
+  }, [onReset]);
 
   const handleExport = async () => {
     setIsExporting(true);
@@ -363,7 +151,6 @@ export function UsageLogsFilters({
         return;
       }
 
-      // Create and download the file
       const blob = new Blob([result.data], { type: "text/csv;charset=utf-8;" });
       const url = window.URL.createObjectURL(blob);
       const a = document.createElement("a");
@@ -383,517 +170,148 @@ export function UsageLogsFilters({
     }
   };
 
-  // Helper: convert timestamp to display date string (YYYY-MM-DD)
-  const timestampToDateString = useCallback((timestamp: number): string => {
-    const date = new Date(timestamp);
-    return format(date, "yyyy-MM-dd");
-  }, []);
+  const handlePresetToggle = useCallback(
+    (preset: FilterPreset) => {
+      const now = new Date();
+
+      if (preset === activePreset) {
+        // Toggle off - clear the preset-related filters
+        setActivePreset(null);
+        setLocalFilters((prev) => {
+          const next = { ...prev };
+          if (preset === "today" || preset === "this-week") {
+            delete next.startTime;
+            delete next.endTime;
+          } else if (preset === "errors-only") {
+            delete next.excludeStatusCode200;
+          } else if (preset === "show-retries") {
+            delete next.minRetryCount;
+          }
+          return next;
+        });
+        return;
+      }
+
+      setActivePreset(preset);
 
-  // Memoized startDate for display (from timestamp)
-  const displayStartDate = useMemo(() => {
-    if (!localFilters.startTime) return undefined;
-    return timestampToDateString(localFilters.startTime);
-  }, [localFilters.startTime, timestampToDateString]);
-
-  const displayStartClock = useMemo(() => {
-    if (!localFilters.startTime) return undefined;
-    return formatClockFromTimestamp(localFilters.startTime);
-  }, [localFilters.startTime]);
-
-  // Memoized endDate calculation: endTime is exclusive, use endTime-1s to infer inclusive display end date
-  const displayEndDate = useMemo(() => {
-    if (!localFilters.endTime) return undefined;
-    const inclusiveEndTime = inclusiveEndTimestampFromExclusive(localFilters.endTime);
-    return format(new Date(inclusiveEndTime), "yyyy-MM-dd");
-  }, [localFilters.endTime]);
-
-  const displayEndClock = useMemo(() => {
-    if (!localFilters.endTime) return undefined;
-    const inclusiveEndTime = inclusiveEndTimestampFromExclusive(localFilters.endTime);
-    return formatClockFromTimestamp(inclusiveEndTime);
-  }, [localFilters.endTime]);
-
-  // Memoized callback for date range changes
-  const handleDateRangeChange = useCallback(
-    (range: { startDate?: string; endDate?: string }) => {
-      if (range.startDate && range.endDate) {
-        // Convert to millisecond timestamps:
-        // startTime: startDate + startClock (default 00:00:00)
-        // endTime: endDate + endClock as exclusive upper bound (endClock default 23:59:59)
-        const startClock = displayStartClock ?? "00:00:00";
-        const endClock = displayEndClock ?? "23:59:59";
-        const startTimestamp = dateStringWithClockToTimestamp(range.startDate, startClock);
-        const endInclusiveTimestamp = dateStringWithClockToTimestamp(range.endDate, endClock);
-        if (startTimestamp === undefined || endInclusiveTimestamp === undefined) {
-          setLocalFilters((prev) => ({
-            ...prev,
-            startTime: undefined,
-            endTime: undefined,
-          }));
-          return;
-        }
-        const endTimestamp = endInclusiveTimestamp + 1000;
+      if (preset === "today") {
+        const todayStart = startOfDay(now).getTime();
+        const todayEnd = todayStart + 24 * 60 * 60 * 1000;
         setLocalFilters((prev) => ({
           ...prev,
-          startTime: startTimestamp,
-          endTime: endTimestamp,
+          startTime: todayStart,
+          endTime: todayEnd,
         }));
-      } else {
+      } else if (preset === "this-week") {
+        const weekStart = startOfWeek(now, { weekStartsOn: 1 }).getTime();
+        const weekEnd = weekStart + 7 * 24 * 60 * 60 * 1000;
         setLocalFilters((prev) => ({
           ...prev,
-          startTime: undefined,
-          endTime: undefined,
+          startTime: weekStart,
+          endTime: weekEnd,
+        }));
+      } else if (preset === "errors-only") {
+        setLocalFilters((prev) => ({
+          ...prev,
+          excludeStatusCode200: true,
+          statusCode: undefined,
+        }));
+      } else if (preset === "show-retries") {
+        setLocalFilters((prev) => ({
+          ...prev,
+          minRetryCount: 1,
         }));
       }
     },
-    [displayEndClock, displayStartClock]
+    [activePreset]
   );
 
+  const handleRemoveFilter = useCallback((key: keyof UsageLogFilters) => {
+    setLocalFilters((prev) => {
+      const next = { ...prev };
+      delete next[key];
+      return next;
+    });
+    setActivePreset(null);
+  }, []);
+
   return (
     <div className="space-y-4">
-      <div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-12">
-        {/* 时间范围 - 使用日期范围选择器 */}
-        <div className="space-y-2 lg:col-span-4">
-          <Label>{t("logs.filters.dateRange")}</Label>
-          <LogsDateRangePicker
-            startDate={displayStartDate}
-            endDate={displayEndDate}
-            onDateRangeChange={handleDateRangeChange}
+      {/* Quick Filters Bar */}
+      <QuickFiltersBar activePreset={activePreset} onPresetToggle={handlePresetToggle} />
+
+      {/* Active Filters Display */}
+      <ActiveFiltersDisplay
+        filters={localFilters}
+        onRemove={handleRemoveFilter}
+        onClearAll={handleReset}
+        displayNames={displayNames}
+        isAdmin={isAdmin}
+      />
+
+      {/* Filter Sections */}
+      <div className="grid gap-4 lg:grid-cols-2">
+        {/* Time Range Section */}
+        <FilterSection
+          title={t("logs.filters.groups.time")}
+          description={t("logs.filters.groups.timeDesc")}
+          icon={Clock}
+          activeCount={timeActiveCount}
+          defaultOpen={true}
+        >
+          <TimeFilters filters={localFilters} onFiltersChange={setLocalFilters} />
+        </FilterSection>
+
+        {/* Identity Section (Admin only for User, all for Key) */}
+        <FilterSection
+          title={t("logs.filters.groups.identity")}
+          description={t("logs.filters.groups.identityDesc")}
+          icon={User}
+          activeCount={identityActiveCount}
+          defaultOpen={true}
+        >
+          <IdentityFilters
+            isAdmin={isAdmin}
+            filters={localFilters}
+            onFiltersChange={setLocalFilters}
+            initialKeys={initialKeys}
+            isKeysLoading={isKeysLoading}
+            onKeysChange={setKeys}
+            onUsersChange={setAvailableUsers}
           />
-          <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
-            <div className="space-y-1">
-              <Label className="text-xs text-muted-foreground">{t("logs.filters.startTime")}</Label>
-              <Input
-                type="time"
-                step={1}
-                value={displayStartClock ?? ""}
-                disabled={!displayStartDate}
-                onChange={(e) => {
-                  const nextClock = e.target.value || "00:00:00";
-                  setLocalFilters((prev) => {
-                    if (!prev.startTime) return prev;
-                    const dateStr = timestampToDateString(prev.startTime);
-                    const startTime = dateStringWithClockToTimestamp(dateStr, nextClock);
-                    if (startTime === undefined) return prev;
-                    return {
-                      ...prev,
-                      startTime,
-                    };
-                  });
-                }}
-              />
-            </div>
-            <div className="space-y-1">
-              <Label className="text-xs text-muted-foreground">{t("logs.filters.endTime")}</Label>
-              <Input
-                type="time"
-                step={1}
-                value={displayEndClock ?? ""}
-                disabled={!displayEndDate}
-                onChange={(e) => {
-                  const nextClock = e.target.value || "23:59:59";
-                  setLocalFilters((prev) => {
-                    if (!prev.endTime) return prev;
-                    const inclusiveEndTime = inclusiveEndTimestampFromExclusive(prev.endTime);
-                    const endDateStr = timestampToDateString(inclusiveEndTime);
-                    const endInclusiveTimestamp = dateStringWithClockToTimestamp(
-                      endDateStr,
-                      nextClock
-                    );
-                    if (endInclusiveTimestamp === undefined) return prev;
-                    return {
-                      ...prev,
-                      endTime: endInclusiveTimestamp + 1000,
-                    };
-                  });
-                }}
-              />
-            </div>
-          </div>
-        </div>
-
-        {/* 用户选择(仅 Admin) */}
-        {isAdmin && (
-          <div className="space-y-2 lg:col-span-4">
-            <Label>{t("logs.filters.user")}</Label>
-            <Popover open={userPopoverOpen} onOpenChange={setUserPopoverOpen}>
-              <PopoverTrigger asChild>
-                <Button
-                  variant="outline"
-                  role="combobox"
-                  aria-expanded={userPopoverOpen}
-                  type="button"
-                  className="w-full justify-between"
-                >
-                  {localFilters.userId ? (
-                    (userMap.get(localFilters.userId) ?? localFilters.userId.toString())
-                  ) : (
-                    <span className="text-muted-foreground">
-                      {isUsersLoading ? t("logs.stats.loading") : t("logs.filters.allUsers")}
-                    </span>
-                  )}
-                  <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
-                </Button>
-              </PopoverTrigger>
-              <PopoverContent
-                className="w-[320px] p-0"
-                align="start"
-                onWheel={(e) => e.stopPropagation()}
-                onTouchMove={(e) => e.stopPropagation()}
-              >
-                <Command shouldFilter={false}>
-                  <CommandInput
-                    placeholder={t("logs.filters.searchUser")}
-                    value={userSearchTerm}
-                    onValueChange={(value) => setUserSearchTerm(value)}
-                  />
-                  <CommandList className="max-h-[250px] overflow-y-auto">
-                    <CommandEmpty>
-                      {isUsersLoading ? t("logs.stats.loading") : t("logs.filters.noUserFound")}
-                    </CommandEmpty>
-                    <CommandGroup>
-                      <CommandItem
-                        value={t("logs.filters.allUsers")}
-                        onSelect={() => {
-                          void handleUserChange("");
-                          setUserPopoverOpen(false);
-                        }}
-                        className="cursor-pointer"
-                      >
-                        <span className="flex-1">{t("logs.filters.allUsers")}</span>
-                        {!localFilters.userId && <Check className="h-4 w-4 text-primary" />}
-                      </CommandItem>
-                      {availableUsers.map((user) => (
-                        <CommandItem
-                          key={user.id}
-                          value={user.name}
-                          onSelect={() => {
-                            void handleUserChange(user.id.toString());
-                            setUserPopoverOpen(false);
-                          }}
-                          className="cursor-pointer"
-                        >
-                          <span className="flex-1">{user.name}</span>
-                          {localFilters.userId === user.id && (
-                            <Check className="h-4 w-4 text-primary" />
-                          )}
-                        </CommandItem>
-                      ))}
-                    </CommandGroup>
-                  </CommandList>
-                </Command>
-              </PopoverContent>
-            </Popover>
-          </div>
-        )}
-
-        {/* Key 选择 */}
-        <div className="space-y-2 lg:col-span-4">
-          <Label>{t("logs.filters.apiKey")}</Label>
-          <Select
-            value={localFilters.keyId?.toString() || "__all__"}
-            onValueChange={(value: string) =>
-              setLocalFilters({
-                ...localFilters,
-                keyId: value && value !== "__all__" ? parseInt(value, 10) : undefined,
-              })
-            }
-            disabled={isKeysLoading || (isAdmin && !localFilters.userId && keys.length === 0)}
-          >
-            <SelectTrigger>
-              <SelectValue
-                placeholder={
-                  isKeysLoading
-                    ? t("logs.stats.loading")
-                    : isAdmin && !localFilters.userId && keys.length === 0
-                      ? t("logs.filters.selectUserFirst")
-                      : t("logs.filters.allKeys")
-                }
-              />
-            </SelectTrigger>
-            <SelectContent>
-              <SelectItem value="__all__">{t("logs.filters.allKeys")}</SelectItem>
-              {keys.map((key) => (
-                <SelectItem key={key.id} value={key.id.toString()}>
-                  {key.name}
-                </SelectItem>
-              ))}
-            </SelectContent>
-          </Select>
-        </div>
-
-        {/* 供应商选择 */}
-        {isAdmin && (
-          <div className="space-y-2 lg:col-span-4">
-            <Label>{t("logs.filters.provider")}</Label>
-            <Popover open={providerPopoverOpen} onOpenChange={setProviderPopoverOpen}>
-              <PopoverTrigger asChild>
-                <Button
-                  variant="outline"
-                  role="combobox"
-                  aria-expanded={providerPopoverOpen}
-                  disabled={isProvidersLoading}
-                  type="button"
-                  className="w-full justify-between"
-                >
-                  {localFilters.providerId ? (
-                    (providerMap.get(localFilters.providerId) ?? localFilters.providerId.toString())
-                  ) : (
-                    <span className="text-muted-foreground">
-                      {isProvidersLoading
-                        ? t("logs.stats.loading")
-                        : t("logs.filters.allProviders")}
-                    </span>
-                  )}
-                  <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
-                </Button>
-              </PopoverTrigger>
-              <PopoverContent
-                className="w-[320px] p-0"
-                align="start"
-                onWheel={(e) => e.stopPropagation()}
-                onTouchMove={(e) => e.stopPropagation()}
-              >
-                <Command shouldFilter={true}>
-                  <CommandInput placeholder={t("logs.filters.searchProvider")} />
-                  <CommandList className="max-h-[250px] overflow-y-auto">
-                    <CommandEmpty>
-                      {isProvidersLoading
-                        ? t("logs.stats.loading")
-                        : t("logs.filters.noProviderFound")}
-                    </CommandEmpty>
-                    <CommandGroup>
-                      <CommandItem
-                        value={t("logs.filters.allProviders")}
-                        onSelect={() => {
-                          setLocalFilters({
-                            ...localFilters,
-                            providerId: undefined,
-                          });
-                          setProviderPopoverOpen(false);
-                        }}
-                        className="cursor-pointer"
-                      >
-                        <span className="flex-1">{t("logs.filters.allProviders")}</span>
-                        {!localFilters.providerId && <Check className="h-4 w-4 text-primary" />}
-                      </CommandItem>
-                      {providers.map((provider) => (
-                        <CommandItem
-                          key={provider.id}
-                          value={provider.name}
-                          onSelect={() => {
-                            setLocalFilters({
-                              ...localFilters,
-                              providerId: provider.id,
-                            });
-                            setProviderPopoverOpen(false);
-                          }}
-                          className="cursor-pointer"
-                        >
-                          <span className="flex-1">{provider.name}</span>
-                          {localFilters.providerId === provider.id && (
-                            <Check className="h-4 w-4 text-primary" />
-                          )}
-                        </CommandItem>
-                      ))}
-                    </CommandGroup>
-                  </CommandList>
-                </Command>
-              </PopoverContent>
-            </Popover>
-          </div>
-        )}
-
-        {/* Session ID 联想 */}
-        <div className="space-y-2 lg:col-span-4">
-          <Label>{t("logs.filters.sessionId")}</Label>
-          <Popover open={sessionIdPopoverOpen} onOpenChange={setSessionIdPopoverOpen}>
-            <PopoverAnchor asChild>
-              <Input
-                value={localFilters.sessionId ?? ""}
-                placeholder={t("logs.filters.searchSessionId")}
-                onFocus={() => {
-                  const term = (localFilters.sessionId ?? "").trim();
-                  setSessionIdPopoverOpen(term.length >= SESSION_ID_SUGGESTION_MIN_LEN);
-                }}
-                onChange={(e) => {
-                  const next = e.target.value.trim();
-                  setLocalFilters((prev) => ({ ...prev, sessionId: next || undefined }));
-                  setSessionIdPopoverOpen(next.length >= SESSION_ID_SUGGESTION_MIN_LEN);
-                }}
-              />
-            </PopoverAnchor>
-            <PopoverContent
-              className="w-[320px] p-0"
-              align="start"
-              onOpenAutoFocus={(e) => e.preventDefault()}
-              onWheel={(e) => e.stopPropagation()}
-              onTouchMove={(e) => e.stopPropagation()}
-            >
-              <Command shouldFilter={false}>
-                <CommandList className="max-h-[250px] overflow-y-auto">
-                  <CommandEmpty>
-                    {isSessionIdsLoading
-                      ? t("logs.stats.loading")
-                      : t("logs.filters.noSessionFound")}
-                  </CommandEmpty>
-                  <CommandGroup>
-                    {availableSessionIds.map((sessionId) => (
-                      <CommandItem
-                        key={sessionId}
-                        value={sessionId}
-                        onSelect={() => {
-                          setLocalFilters((prev) => ({ ...prev, sessionId }));
-                          setSessionIdPopoverOpen(false);
-                        }}
-                        className="cursor-pointer"
-                      >
-                        <span className="flex-1 font-mono text-xs truncate">{sessionId}</span>
-                        {localFilters.sessionId === sessionId && (
-                          <Check className="h-4 w-4 text-primary" />
-                        )}
-                      </CommandItem>
-                    ))}
-                  </CommandGroup>
-                </CommandList>
-              </Command>
-            </PopoverContent>
-          </Popover>
-        </div>
-
-        {/* 模型选择 */}
-        <div className="space-y-2 lg:col-span-4">
-          <Label>{t("logs.filters.model")}</Label>
-          <Select
-            value={localFilters.model || "all"}
-            onValueChange={(value: string) =>
-              setLocalFilters({ ...localFilters, model: value === "all" ? undefined : value })
-            }
-            onOpenChange={onModelsOpenChange}
-          >
-            <SelectTrigger>
-              <SelectValue
-                placeholder={
-                  isModelsLoading ? t("logs.stats.loading") : t("logs.filters.allModels")
-                }
-              />
-            </SelectTrigger>
-            <SelectContent>
-              <SelectItem value="all">{t("logs.filters.allModels")}</SelectItem>
-              {models.map((model) => (
-                <SelectItem key={model} value={model}>
-                  {model}
-                </SelectItem>
-              ))}
-              {isModelsLoading && (
-                <div className="p-2 text-center text-muted-foreground text-sm">
-                  {t("logs.stats.loading")}
-                </div>
-              )}
-            </SelectContent>
-          </Select>
-        </div>
-
-        {/* Endpoint 选择 */}
-        <div className="space-y-2 lg:col-span-4">
-          <Label>{t("logs.filters.endpoint")}</Label>
-          <Select
-            value={localFilters.endpoint || "all"}
-            onValueChange={(value: string) =>
-              setLocalFilters({ ...localFilters, endpoint: value === "all" ? undefined : value })
-            }
-            onOpenChange={onEndpointsOpenChange}
-          >
-            <SelectTrigger>
-              <SelectValue
-                placeholder={
-                  isEndpointsLoading ? t("logs.stats.loading") : t("logs.filters.allEndpoints")
-                }
-              />
-            </SelectTrigger>
-            <SelectContent>
-              <SelectItem value="all">{t("logs.filters.allEndpoints")}</SelectItem>
-              {endpoints.map((endpoint) => (
-                <SelectItem key={endpoint} value={endpoint}>
-                  {endpoint}
-                </SelectItem>
-              ))}
-              {isEndpointsLoading && (
-                <div className="p-2 text-center text-muted-foreground text-sm">
-                  {t("logs.stats.loading")}
-                </div>
-              )}
-            </SelectContent>
-          </Select>
-        </div>
-
-        {/* 状态码选择 */}
-        <div className="space-y-2 lg:col-span-4">
-          <Label>{t("logs.filters.statusCode")}</Label>
-          <Select
-            value={
-              localFilters.excludeStatusCode200
-                ? "!200"
-                : localFilters.statusCode?.toString() || "__all__"
-            }
-            onValueChange={(value: string) =>
-              setLocalFilters({
-                ...localFilters,
-                statusCode:
-                  value && value !== "!200" && value !== "__all__"
-                    ? parseInt(value, 10)
-                    : undefined,
-                excludeStatusCode200: value === "!200",
-              })
-            }
-            onOpenChange={onStatusCodesOpenChange}
-          >
-            <SelectTrigger>
-              <SelectValue placeholder={t("logs.filters.allStatusCodes")} />
-            </SelectTrigger>
-            <SelectContent>
-              <SelectItem value="__all__">{t("logs.filters.allStatusCodes")}</SelectItem>
-              <SelectItem value="!200">{t("logs.statusCodes.not200")}</SelectItem>
-              <SelectItem value="200">{t("logs.statusCodes.200")}</SelectItem>
-              <SelectItem value="400">{t("logs.statusCodes.400")}</SelectItem>
-              <SelectItem value="401">{t("logs.statusCodes.401")}</SelectItem>
-              <SelectItem value="429">{t("logs.statusCodes.429")}</SelectItem>
-              <SelectItem value="500">{t("logs.statusCodes.500")}</SelectItem>
-              {allStatusCodes.map((code) => (
-                <SelectItem key={code} value={code.toString()}>
-                  {code}
-                </SelectItem>
-              ))}
-              {isStatusCodesLoading && (
-                <div className="p-2 text-center text-muted-foreground text-sm">
-                  {t("logs.stats.loading")}
-                </div>
-              )}
-            </SelectContent>
-          </Select>
-        </div>
-
-        {/* 重试次数下限 */}
-        <div className="space-y-2 lg:col-span-4">
-          <Label>{t("logs.filters.minRetryCount")}</Label>
-          <Input
-            type="number"
-            min={0}
-            inputMode="numeric"
-            value={localFilters.minRetryCount?.toString() ?? ""}
-            placeholder={t("logs.filters.minRetryCountPlaceholder")}
-            onChange={(e) =>
-              setLocalFilters({
-                ...localFilters,
-                minRetryCount: e.target.value ? parseInt(e.target.value, 10) : undefined,
-              })
-            }
+        </FilterSection>
+
+        {/* Request Section */}
+        <FilterSection
+          title={t("logs.filters.groups.request")}
+          description={t("logs.filters.groups.requestDesc")}
+          icon={Network}
+          activeCount={requestActiveCount}
+          defaultOpen={false}
+        >
+          <RequestFilters
+            isAdmin={isAdmin}
+            filters={localFilters}
+            onFiltersChange={setLocalFilters}
+            providers={providers}
+            isProvidersLoading={isProvidersLoading}
           />
-        </div>
+        </FilterSection>
+
+        {/* Status Section */}
+        <FilterSection
+          title={t("logs.filters.groups.status")}
+          description={t("logs.filters.groups.statusDesc")}
+          icon={Server}
+          activeCount={statusActiveCount}
+          defaultOpen={false}
+        >
+          <StatusFilters filters={localFilters} onFiltersChange={setLocalFilters} />
+        </FilterSection>
       </div>
 
-      {/* 操作按钮 */}
-      <div className="flex flex-wrap items-center gap-2">
+      {/* Action Buttons */}
+      <div className="flex flex-wrap items-center gap-2 pt-2">
         <Button onClick={handleApply}>{t("logs.filters.apply")}</Button>
         <Button variant="outline" onClick={handleReset}>
           {t("logs.filters.reset")}

+ 2 - 2
src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx

@@ -1,5 +1,5 @@
 import { cache } from "react";
-import { ActiveSessionsPanel } from "@/components/customs/active-sessions-panel";
+import { ActiveSessionsCards } from "@/components/customs/active-sessions-cards";
 import { getSystemSettings } from "@/repository/system-config";
 import { UsageLogsViewVirtualized } from "./usage-logs-view-virtualized";
 
@@ -13,7 +13,7 @@ interface UsageLogsDataSectionProps {
 
 export async function UsageLogsActiveSessionsSection() {
   const systemSettings = await getCachedSystemSettings();
-  return <ActiveSessionsPanel currencyCode={systemSettings.currencyDisplay} />;
+  return <ActiveSessionsCards currencyCode={systemSettings.currencyDisplay} />;
 }
 
 export async function UsageLogsDataSection({

+ 68 - 69
src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx

@@ -1,13 +1,11 @@
 "use client";
 
-import { ChevronDown, ChevronUp } from "lucide-react";
+import { BarChart3 } from "lucide-react";
 import { useTranslations } from "next-intl";
 import { useCallback, useEffect, useState } from "react";
 import { getUsageLogsStats } from "@/actions/usage-logs";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
 import { Skeleton } from "@/components/ui/skeleton";
-import { formatTokenAmount } from "@/lib/utils";
+import { cn, formatTokenAmount } from "@/lib/utils";
 import type { CurrencyCode } from "@/lib/utils/currency";
 import { formatCurrency } from "@/lib/utils/currency";
 import type { UsageLogSummary } from "@/repository/usage-logs";
@@ -30,31 +28,21 @@ interface UsageLogsStatsPanelProps {
 }
 
 /**
- * 可折叠统计面板组件
- * 默认折叠,展开时按需加载聚合统计数据
- * 筛选条件变更时清除缓存,再次展开时重新加载
+ * Stats panel component with glass morphism UI
+ * Always expanded (not collapsible), loads data asynchronously
+ * Re-fetches when filters change
  */
 export function UsageLogsStatsPanel({ filters, currencyCode = "USD" }: UsageLogsStatsPanelProps) {
   const t = useTranslations("dashboard");
-  const [isOpen, setIsOpen] = useState(false);
   const [stats, setStats] = useState<UsageLogSummary | null>(null);
-  const [isLoading, setIsLoading] = useState(false);
+  const [isLoading, setIsLoading] = useState(true);
   const [error, setError] = useState<string | null>(null);
 
-  // 创建筛选条件的稳定键用于依赖比较
+  // Create stable filter key for dependency comparison
   const filtersKey = JSON.stringify(filters);
 
-  // 筛选条件变更时清除缓存
-  // biome-ignore lint/correctness/useExhaustiveDependencies: filtersKey is used to detect filter changes
-  useEffect(() => {
-    setStats(null);
-    setError(null);
-  }, [filtersKey]);
-
-  // 加载统计数据
+  // Load stats data
   const loadStats = useCallback(async () => {
-    if (stats !== null || isLoading) return;
-
     setIsLoading(true);
     setError(null);
 
@@ -71,59 +59,70 @@ export function UsageLogsStatsPanel({ filters, currencyCode = "USD" }: UsageLogs
     } finally {
       setIsLoading(false);
     }
-  }, [filters, stats, isLoading, t]);
+  }, [filters, t]);
 
-  // 展开时加载数据
+  // Load data on mount and when filters change
+  // biome-ignore lint/correctness/useExhaustiveDependencies: filtersKey is used to detect filter changes
   useEffect(() => {
-    if (isOpen && stats === null && !isLoading) {
-      loadStats();
-    }
-  }, [isOpen, stats, isLoading, loadStats]);
+    loadStats();
+  }, [filtersKey, loadStats]);
 
   return (
-    <Collapsible open={isOpen} onOpenChange={setIsOpen}>
-      <Card>
-        <CollapsibleTrigger asChild>
-          <CardHeader className="cursor-pointer hover:bg-muted/50 transition-colors">
-            <div className="flex items-center justify-between">
-              <div>
-                <CardTitle className="text-lg">{t("logs.stats.title")}</CardTitle>
-                <CardDescription>{t("logs.stats.description")}</CardDescription>
-              </div>
-              <div className="flex items-center gap-2 text-muted-foreground">
-                <span className="text-sm">
-                  {isOpen ? t("logs.stats.collapse") : t("logs.stats.expand")}
-                </span>
-                {isOpen ? <ChevronUp className="h-5 w-5" /> : <ChevronDown className="h-5 w-5" />}
-              </div>
-            </div>
-          </CardHeader>
-        </CollapsibleTrigger>
-
-        <CollapsibleContent>
-          <CardContent className="pt-0">
-            {isLoading ? (
-              <StatsSkeletons />
-            ) : error ? (
-              <div className="text-center py-4 text-destructive">{error}</div>
-            ) : stats ? (
-              <StatsContent stats={stats} currencyCode={currencyCode} />
-            ) : null}
-          </CardContent>
-        </CollapsibleContent>
-      </Card>
-    </Collapsible>
+    <div
+      className={cn(
+        // Glass morphism base
+        "relative overflow-hidden rounded-xl border bg-card/30 backdrop-blur-sm",
+        "transition-all duration-200",
+        "border-border/50 hover:border-border"
+      )}
+    >
+      {/* Glassmorphism gradient overlay */}
+      <div className="absolute inset-0 bg-gradient-to-br from-white/[0.02] to-transparent pointer-events-none" />
+
+      <div className="relative z-10">
+        {/* Header */}
+        <div className="flex items-center gap-3 px-4 py-3 border-b border-border/30">
+          <span
+            className={cn(
+              "flex items-center justify-center w-8 h-8 rounded-lg shrink-0",
+              "bg-muted text-muted-foreground"
+            )}
+          >
+            <BarChart3 className="h-4 w-4" />
+          </span>
+          <div className="space-y-0.5">
+            <h3 className="text-sm font-semibold text-foreground leading-none">
+              {t("logs.stats.title")}
+            </h3>
+            <p className="text-xs text-muted-foreground leading-relaxed hidden sm:block">
+              {t("logs.stats.description")}
+            </p>
+          </div>
+        </div>
+
+        {/* Content */}
+        <div className="px-4 py-4">
+          {isLoading ? (
+            <StatsSkeletons />
+          ) : error ? (
+            <div className="text-center py-4 text-destructive">{error}</div>
+          ) : stats ? (
+            <StatsContent stats={stats} currencyCode={currencyCode} />
+          ) : null}
+        </div>
+      </div>
+    </div>
   );
 }
 
 /**
- * 统计数据骨架屏
+ * Stats data skeletons
  */
 function StatsSkeletons() {
   return (
     <div className="grid gap-4 md:grid-cols-4">
       {[1, 2, 3, 4].map((i) => (
-        <div key={i} className="space-y-2 p-4 border rounded-lg">
+        <div key={i} className="space-y-2 p-4 border border-border/50 rounded-lg bg-card/20">
           <Skeleton className="h-4 w-24" />
           <Skeleton className="h-8 w-32" />
         </div>
@@ -133,7 +132,7 @@ function StatsSkeletons() {
 }
 
 /**
- * 统计数据内容
+ * Stats data content
  */
 function StatsContent({
   stats,
@@ -146,24 +145,24 @@ function StatsContent({
 
   return (
     <div className="grid gap-4 md:grid-cols-4">
-      {/* 总请求数 */}
-      <div className="p-4 border rounded-lg">
+      {/* Total Requests */}
+      <div className="p-4 border border-border/50 rounded-lg bg-card/20">
         <div className="text-sm text-muted-foreground mb-1">{t("logs.stats.totalRequests")}</div>
         <div className="text-2xl font-mono font-semibold">
           {stats.totalRequests.toLocaleString()}
         </div>
       </div>
 
-      {/* 总金额 */}
-      <div className="p-4 border rounded-lg">
+      {/* Total Amount */}
+      <div className="p-4 border border-border/50 rounded-lg bg-card/20">
         <div className="text-sm text-muted-foreground mb-1">{t("logs.stats.totalAmount")}</div>
         <div className="text-2xl font-mono font-semibold">
           {formatCurrency(stats.totalCost, currencyCode)}
         </div>
       </div>
 
-      {/*  Tokens */}
-      <div className="p-4 border rounded-lg">
+      {/* Total Tokens */}
+      <div className="p-4 border border-border/50 rounded-lg bg-card/20">
         <div className="text-sm text-muted-foreground mb-1">{t("logs.stats.totalTokens")}</div>
         <div className="text-2xl font-mono font-semibold">
           {formatTokenAmount(stats.totalTokens)}
@@ -180,8 +179,8 @@ function StatsContent({
         </div>
       </div>
 
-      {/* 缓存 Tokens */}
-      <div className="p-4 border rounded-lg">
+      {/* Cache Tokens */}
+      <div className="p-4 border border-border/50 rounded-lg bg-card/20">
         <div className="text-sm text-muted-foreground mb-1">{t("logs.stats.cacheTokens")}</div>
         <div className="text-2xl font-mono font-semibold">
           {formatTokenAmount(stats.totalCacheCreationTokens + stats.totalCacheReadTokens)}

+ 70 - 22
src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx

@@ -1,7 +1,7 @@
 "use client";
 
 import { QueryClient, QueryClientProvider, useQuery, useQueryClient } from "@tanstack/react-query";
-import { Expand, Minimize2, Pause, Play, RefreshCw } from "lucide-react";
+import { Expand, Filter, ListOrdered, Minimize2, Pause, Play, RefreshCw } from "lucide-react";
 import { useRouter, useSearchParams } from "next/navigation";
 import { useLocale, useTranslations } from "next-intl";
 import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -11,15 +11,17 @@ import type { OverviewData } from "@/actions/overview";
 import { getOverviewData } from "@/actions/overview";
 import { getProviders } from "@/actions/providers";
 import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
 import { Switch } from "@/components/ui/switch";
 import { useFullscreen } from "@/hooks/use-fullscreen";
+import { getHiddenColumns, type LogsTableColumn } from "@/lib/column-visibility";
 import type { CurrencyCode } from "@/lib/utils/currency";
 import { formatCurrency } from "@/lib/utils/currency";
 import type { Key } from "@/types/key";
 import type { ProviderDisplay } from "@/types/provider";
 import type { BillingModelSource, SystemSettings } from "@/types/system-config";
 import { buildLogsUrlQuery, parseLogsUrlFilters } from "../_utils/logs-query";
+import { ColumnVisibilityDropdown } from "./column-visibility-dropdown";
 import { UsageLogsFilters } from "./usage-logs-filters";
 import { UsageLogsStatsPanel } from "./usage-logs-stats-panel";
 import { VirtualizedLogsTable, type VirtualizedLogsTableFilters } from "./virtualized-logs-table";
@@ -83,6 +85,13 @@ function UsageLogsViewContent({
   const [isFullscreenOpen, setIsFullscreenOpen] = useState(false);
   const [hideProviderColumn, setHideProviderColumn] = useState(false);
   const wasInFullscreenRef = useRef(false);
+  const [hiddenColumns, setHiddenColumns] = useState<LogsTableColumn[]>([]);
+
+  // Load initial hidden columns from localStorage
+  useEffect(() => {
+    const stored = getHiddenColumns(userId, "usage-logs");
+    setHiddenColumns(stored);
+  }, [userId]);
 
   const resetFullscreenState = useCallback(() => {
     setIsFullscreenOpen(false);
@@ -258,7 +267,8 @@ function UsageLogsViewContent({
 
   return (
     <>
-      <div className="space-y-6">
+      <div className="space-y-4">
+        {/* Stats Summary - Collapsible */}
         <UsageLogsStatsPanel
           filters={{
             userId: filters.userId,
@@ -276,11 +286,22 @@ function UsageLogsViewContent({
           currencyCode={resolvedCurrencyCode}
         />
 
-        <Card>
-          <CardHeader>
-            <CardTitle>{t("title.filterCriteria")}</CardTitle>
+        {/* Filter Criteria */}
+        <Card className="border-border/50">
+          <CardHeader className="pb-3">
+            <div className="flex items-center gap-2">
+              <div className="flex items-center justify-center w-8 h-8 rounded-lg bg-muted/50">
+                <Filter className="h-4 w-4 text-muted-foreground" />
+              </div>
+              <div>
+                <CardTitle className="text-base">{t("title.filterCriteria")}</CardTitle>
+                <CardDescription className="text-xs">
+                  {t("title.filterCriteriaDescription")}
+                </CardDescription>
+              </div>
+            </div>
           </CardHeader>
-          <CardContent>
+          <CardContent className="pt-0">
             <UsageLogsFilters
               isAdmin={isAdmin}
               providers={resolvedProviders}
@@ -294,61 +315,88 @@ function UsageLogsViewContent({
           </CardContent>
         </Card>
 
-        <Card>
-          <CardHeader>
+        {/* Usage Records Table */}
+        <Card className="border-border/50">
+          <CardHeader className="pb-3">
             <div className="flex items-center justify-between">
-              <CardTitle>{t("title.usageLogs")}</CardTitle>
               <div className="flex items-center gap-2">
+                <div className="flex items-center justify-center w-8 h-8 rounded-lg bg-muted/50">
+                  <ListOrdered className="h-4 w-4 text-muted-foreground" />
+                </div>
+                <div>
+                  <CardTitle className="text-base">{t("title.usageLogs")}</CardTitle>
+                  <CardDescription className="text-xs">
+                    {t("title.usageLogsDescription")}
+                  </CardDescription>
+                </div>
+              </div>
+              <div className="flex items-center gap-2">
+                <ColumnVisibilityDropdown
+                  userId={userId}
+                  tableId="usage-logs"
+                  onVisibilityChange={setHiddenColumns}
+                />
+
                 <Button
                   variant="outline"
                   size="sm"
                   onClick={() => void handleEnterFullscreen()}
-                  className="gap-2"
+                  className="gap-1.5 h-8"
+                  aria-label={t("logs.actions.fullscreen")}
                 >
-                  <Expand className="h-4 w-4" />
-                  {t("logs.actions.fullscreen")}
+                  <Expand className="h-3.5 w-3.5" />
+                  <span className="hidden sm:inline">{t("logs.actions.fullscreen")}</span>
                 </Button>
 
                 <Button
                   variant="outline"
                   size="sm"
                   onClick={handleManualRefresh}
-                  className="gap-2"
+                  className="gap-1.5 h-8"
                   disabled={isFullscreenOpen}
+                  aria-label={t("logs.actions.refresh")}
                 >
-                  <RefreshCw className={`h-4 w-4 ${isManualRefreshing ? "animate-spin" : ""}`} />
-                  {t("logs.actions.refresh")}
+                  <RefreshCw
+                    className={`h-3.5 w-3.5 ${isManualRefreshing ? "animate-spin" : ""}`}
+                  />
+                  <span className="hidden sm:inline">{t("logs.actions.refresh")}</span>
                 </Button>
 
                 <Button
                   variant={isAutoRefresh ? "default" : "outline"}
                   size="sm"
                   onClick={() => setIsAutoRefresh(!isAutoRefresh)}
-                  className="gap-2"
+                  className="gap-1.5 h-8"
                   disabled={isFullscreenOpen}
+                  aria-label={
+                    isAutoRefresh
+                      ? t("logs.actions.stopAutoRefresh")
+                      : t("logs.actions.startAutoRefresh")
+                  }
                 >
                   {isAutoRefresh ? (
                     <>
-                      <Pause className="h-4 w-4" />
-                      {t("logs.actions.stopAutoRefresh")}
+                      <Pause className="h-3.5 w-3.5" />
+                      <span className="hidden sm:inline">{t("logs.actions.stopAutoRefresh")}</span>
                     </>
                   ) : (
                     <>
-                      <Play className="h-4 w-4" />
-                      {t("logs.actions.startAutoRefresh")}
+                      <Play className="h-3.5 w-3.5" />
+                      <span className="hidden sm:inline">{t("logs.actions.startAutoRefresh")}</span>
                     </>
                   )}
                 </Button>
               </div>
             </div>
           </CardHeader>
-          <CardContent className="px-0">
+          <CardContent className="px-0 pt-0">
             <VirtualizedLogsTable
               filters={filters}
               currencyCode={resolvedCurrencyCode}
               billingModelSource={resolvedBillingModelSource}
               autoRefreshEnabled={!isFullscreenOpen && isAutoRefresh}
               autoRefreshIntervalMs={5000}
+              hiddenColumns={hiddenColumns}
             />
           </CardContent>
         </Card>

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

@@ -20,7 +20,6 @@ import {
   formatDuration,
   NON_BILLING_ENDPOINT,
 } from "@/lib/utils/performance-formatter";
-import { formatProviderSummary } from "@/lib/utils/provider-chain-formatter";
 import type { BillingModelSource } from "@/types/system-config";
 import { ErrorDetailsDialog } from "./error-details-dialog";
 import { ModelDisplayWithRedirect } from "./model-display-with-redirect";
@@ -43,7 +42,14 @@ export interface VirtualizedLogsTableFilters {
   minRetryCount?: number;
 }
 
-type VirtualizedLogsTableColumn = "provider";
+type VirtualizedLogsTableColumn =
+  | "user"
+  | "key"
+  | "sessionId"
+  | "provider"
+  | "tokens"
+  | "cache"
+  | "performance";
 
 interface VirtualizedLogsTableProps {
   filters: VirtualizedLogsTableFilters;
@@ -74,6 +80,12 @@ export function VirtualizedLogsTable({
   const [showScrollToTop, setShowScrollToTop] = useState(false);
 
   const hideProviderColumn = hiddenColumns?.includes("provider") ?? false;
+  const hideUserColumn = hiddenColumns?.includes("user") ?? false;
+  const hideKeyColumn = hiddenColumns?.includes("key") ?? false;
+  const hideSessionIdColumn = hiddenColumns?.includes("sessionId") ?? false;
+  const hideTokensColumn = hiddenColumns?.includes("tokens") ?? false;
+  const hideCacheColumn = hiddenColumns?.includes("cache") ?? false;
+  const hidePerformanceColumn = hiddenColumns?.includes("performance") ?? false;
 
   // Dialog state for model redirect click
   const [dialogState, setDialogState] = useState<{
@@ -195,87 +207,132 @@ export function VirtualizedLogsTable({
       )}
 
       {/* Table with virtual scrolling */}
-      <div className="rounded-md border overflow-hidden">
-        {/* Fixed header */}
-        <div className="bg-muted/50 border-b">
-          <div className="flex items-center h-10 text-sm font-medium text-muted-foreground">
-            <div className="flex-[0.6] min-w-[56px] pl-2 truncate" title={t("logs.columns.time")}>
-              {t("logs.columns.time")}
-            </div>
-            <div className="flex-[0.8] min-w-[50px] px-1 truncate" title={t("logs.columns.user")}>
-              {t("logs.columns.user")}
-            </div>
-            <div className="flex-[0.6] min-w-[50px] px-1 truncate" title={t("logs.columns.key")}>
-              {t("logs.columns.key")}
-            </div>
-            <div
-              className="flex-[0.8] min-w-[80px] px-1 truncate"
-              title={t("logs.columns.sessionId")}
-            >
-              {t("logs.columns.sessionId")}
-            </div>
-            {hideProviderColumn ? null : (
+      <div className="border-t overflow-x-auto">
+        <div className="min-w-[800px]">
+          {/* Fixed header */}
+          <div className="bg-muted/40 border-b sticky top-0 z-10">
+            <div className="flex items-center h-9 text-xs font-medium text-muted-foreground uppercase tracking-wider">
+              <div className="flex-[0.6] min-w-[56px] pl-3 truncate" title={t("logs.columns.time")}>
+                {t("logs.columns.time")}
+              </div>
+              {hideUserColumn ? null : (
+                <div
+                  className="flex-[0.8] min-w-[50px] px-1.5 truncate"
+                  title={t("logs.columns.user")}
+                >
+                  {t("logs.columns.user")}
+                </div>
+              )}
+              {hideKeyColumn ? null : (
+                <div
+                  className="flex-[0.6] min-w-[50px] px-1.5 truncate"
+                  title={t("logs.columns.key")}
+                >
+                  {t("logs.columns.key")}
+                </div>
+              )}
+              {hideSessionIdColumn ? null : (
+                <div
+                  className="flex-[0.8] min-w-[80px] px-1.5 truncate"
+                  title={t("logs.columns.sessionId")}
+                >
+                  {t("logs.columns.sessionId")}
+                </div>
+              )}
+              {hideProviderColumn ? null : (
+                <div
+                  className="flex-[1.5] min-w-[100px] px-1.5 truncate"
+                  title={t("logs.columns.provider")}
+                >
+                  {t("logs.columns.provider")}
+                </div>
+              )}
               <div
-                className="flex-[1.5] min-w-[100px] px-1 truncate"
-                title={t("logs.columns.provider")}
+                className="flex-[1] min-w-[80px] px-1.5 truncate"
+                title={t("logs.columns.model")}
               >
-                {t("logs.columns.provider")}
+                {t("logs.columns.model")}
+              </div>
+              {hideTokensColumn ? null : (
+                <div
+                  className="flex-[0.7] min-w-[70px] text-right px-1.5 truncate"
+                  title={t("logs.columns.tokens")}
+                >
+                  {t("logs.columns.tokens")}
+                </div>
+              )}
+              {hideCacheColumn ? null : (
+                <div
+                  className="flex-[0.8] min-w-[70px] text-right px-1.5 truncate"
+                  title={t("logs.columns.cache")}
+                >
+                  {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>
+              {hidePerformanceColumn ? null : (
+                <div
+                  className="flex-[0.8] min-w-[80px] text-right px-1.5 truncate"
+                  title={t("logs.columns.performance")}
+                >
+                  {t("logs.columns.performance")}
+                </div>
+              )}
+              <div
+                className="flex-[0.7] min-w-[70px] pr-3 truncate"
+                title={t("logs.columns.status")}
+              >
+                {t("logs.columns.status")}
               </div>
-            )}
-            <div className="flex-[1] min-w-[80px] px-1 truncate" title={t("logs.columns.model")}>
-              {t("logs.columns.model")}
-            </div>
-            <div
-              className="flex-[0.7] min-w-[70px] text-right px-1 truncate"
-              title={t("logs.columns.tokens")}
-            >
-              {t("logs.columns.tokens")}
-            </div>
-            <div
-              className="flex-[0.8] min-w-[70px] text-right px-1 truncate"
-              title={t("logs.columns.cache")}
-            >
-              {t("logs.columns.cache")}
-            </div>
-            <div
-              className="flex-[0.7] min-w-[60px] text-right px-1 truncate"
-              title={t("logs.columns.cost")}
-            >
-              {t("logs.columns.cost")}
-            </div>
-            <div
-              className="flex-[0.8] min-w-[80px] text-right px-1 truncate"
-              title={t("logs.columns.performance")}
-            >
-              {t("logs.columns.performance")}
-            </div>
-            <div className="flex-[0.7] min-w-[70px] pr-2 truncate" title={t("logs.columns.status")}>
-              {t("logs.columns.status")}
             </div>
           </div>
-        </div>
 
-        {/* Virtualized body */}
-        <div
-          ref={parentRef}
-          className={cn("h-[600px] overflow-auto", bodyClassName)}
-          onScroll={handleScroll}
-        >
+          {/* Virtualized body */}
           <div
-            style={{
-              height: `${rowVirtualizer.getTotalSize()}px`,
-              width: "100%",
-              position: "relative",
-            }}
+            ref={parentRef}
+            className={cn("h-[600px] overflow-auto", bodyClassName)}
+            onScroll={handleScroll}
           >
-            {virtualItems.map((virtualRow) => {
-              const isLoaderRow = virtualRow.index >= allLogs.length;
-              const log = allLogs[virtualRow.index];
+            <div
+              style={{
+                height: `${rowVirtualizer.getTotalSize()}px`,
+                width: "100%",
+                position: "relative",
+              }}
+            >
+              {virtualItems.map((virtualRow) => {
+                const isLoaderRow = virtualRow.index >= allLogs.length;
+                const log = allLogs[virtualRow.index];
+
+                if (isLoaderRow) {
+                  return (
+                    <div
+                      key="loader"
+                      style={{
+                        position: "absolute",
+                        top: 0,
+                        left: 0,
+                        width: "100%",
+                        height: `${virtualRow.size}px`,
+                        transform: `translateY(${virtualRow.start}px)`,
+                      }}
+                      className="flex items-center justify-center"
+                    >
+                      <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
+                    </div>
+                  );
+                }
+
+                const isNonBilling = log.endpoint === NON_BILLING_ENDPOINT;
 
-              if (isLoaderRow) {
                 return (
                   <div
-                    key="loader"
+                    key={log.id}
                     style={{
                       position: "absolute",
                       top: 0,
@@ -284,400 +341,382 @@ export function VirtualizedLogsTable({
                       height: `${virtualRow.size}px`,
                       transform: `translateY(${virtualRow.start}px)`,
                     }}
-                    className="flex items-center justify-center"
+                    className={cn(
+                      "flex items-center text-sm border-b transition-colors hover:bg-muted/30",
+                      isNonBilling ? "bg-muted/40 text-muted-foreground dark:bg-muted/20" : ""
+                    )}
                   >
-                    <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
-                  </div>
-                );
-              }
+                    {/* Time */}
+                    <div className="flex-[0.6] min-w-[56px] font-mono text-xs truncate pl-3">
+                      <RelativeTime date={log.createdAt} fallback="-" format="short" />
+                    </div>
 
-              const isNonBilling = log.endpoint === NON_BILLING_ENDPOINT;
+                    {/* User */}
+                    {hideUserColumn ? null : (
+                      <div
+                        className="flex-[0.8] min-w-[50px] text-sm truncate px-1.5"
+                        title={log.userName}
+                      >
+                        {log.userName}
+                      </div>
+                    )}
 
-              return (
-                <div
-                  key={log.id}
-                  style={{
-                    position: "absolute",
-                    top: 0,
-                    left: 0,
-                    width: "100%",
-                    height: `${virtualRow.size}px`,
-                    transform: `translateY(${virtualRow.start}px)`,
-                  }}
-                  className={cn(
-                    "flex items-center text-sm border-b hover:bg-muted/50",
-                    isNonBilling ? "bg-muted/60 text-muted-foreground dark:bg-muted/20" : ""
-                  )}
-                >
-                  {/* Time */}
-                  <div className="flex-[0.6] min-w-[56px] font-mono text-xs truncate pl-2">
-                    <RelativeTime date={log.createdAt} fallback="-" format="short" />
-                  </div>
+                    {/* Key */}
+                    {hideKeyColumn ? null : (
+                      <div
+                        className="flex-[0.6] min-w-[50px] font-mono text-xs truncate px-1.5"
+                        title={log.keyName}
+                      >
+                        {log.keyName}
+                      </div>
+                    )}
 
-                  {/* User */}
-                  <div className="flex-[0.8] min-w-[50px] truncate px-1" title={log.userName}>
-                    {log.userName}
-                  </div>
+                    {/* Session ID */}
+                    {hideSessionIdColumn ? null : (
+                      <div className="flex-[0.8] min-w-[80px] px-1.5">
+                        {log.sessionId ? (
+                          <TooltipProvider>
+                            <Tooltip delayDuration={300}>
+                              <TooltipTrigger asChild>
+                                <button
+                                  type="button"
+                                  className="w-full text-left font-mono text-xs truncate cursor-pointer hover:underline"
+                                  data-session-id={log.sessionId}
+                                  onClick={handleCopySessionIdClick}
+                                >
+                                  {log.sessionId}
+                                </button>
+                              </TooltipTrigger>
+                              <TooltipContent side="bottom" align="start" className="max-w-[500px]">
+                                <p className="text-xs whitespace-normal break-words font-mono">
+                                  {log.sessionId}
+                                </p>
+                              </TooltipContent>
+                            </Tooltip>
+                          </TooltipProvider>
+                        ) : (
+                          <span className="font-mono text-xs text-muted-foreground">-</span>
+                        )}
+                      </div>
+                    )}
 
-                  {/* Key */}
-                  <div
-                    className="flex-[0.6] min-w-[50px] font-mono text-xs truncate px-1"
-                    title={log.keyName}
-                  >
-                    {log.keyName}
-                  </div>
+                    {/* Provider */}
+                    {hideProviderColumn ? null : (
+                      <div className="flex-[1.5] min-w-[100px] px-1.5">
+                        {log.blockedBy ? (
+                          <span className="inline-flex items-center gap-1 rounded-md bg-orange-100 dark:bg-orange-950 px-2 py-1 text-xs font-medium text-orange-700 dark:text-orange-300">
+                            <span className="h-1.5 w-1.5 rounded-full bg-orange-600 dark:bg-orange-400" />
+                            {t("logs.table.blocked")}
+                          </span>
+                        ) : (
+                          <div className="flex flex-col items-start gap-0.5 min-w-0">
+                            <div className="flex items-center gap-1 min-w-0 w-full overflow-hidden">
+                              {(() => {
+                                // 计算倍率,用于判断是否显示 Badge
+                                const successfulProvider =
+                                  log.providerChain && log.providerChain.length > 0
+                                    ? [...log.providerChain]
+                                        .reverse()
+                                        .find(
+                                          (item) =>
+                                            item.reason === "request_success" ||
+                                            item.reason === "retry_success"
+                                        )
+                                    : null;
+                                const actualCostMultiplier =
+                                  successfulProvider?.costMultiplier ?? log.costMultiplier;
+                                const multiplier = Number(actualCostMultiplier);
+                                const hasCostBadge =
+                                  actualCostMultiplier !== "" &&
+                                  actualCostMultiplier != null &&
+                                  Number.isFinite(multiplier) &&
+                                  multiplier !== 1;
+
+                                return (
+                                  <>
+                                    <div className="flex-1 min-w-0 overflow-hidden">
+                                      <ProviderChainPopover
+                                        chain={log.providerChain ?? []}
+                                        finalProvider={
+                                          (log.providerChain && log.providerChain.length > 0
+                                            ? log.providerChain[log.providerChain.length - 1].name
+                                            : null) ||
+                                          log.providerName ||
+                                          tChain("circuit.unknown")
+                                        }
+                                        hasCostBadge={hasCostBadge}
+                                      />
+                                    </div>
+                                    {/* Cost multiplier badge */}
+                                    {hasCostBadge && (
+                                      <Badge
+                                        variant="outline"
+                                        className={
+                                          multiplier > 1
+                                            ? "text-xs bg-orange-50 text-orange-700 border-orange-200 dark:bg-orange-950/30 dark:text-orange-300 dark:border-orange-800 shrink-0"
+                                            : "text-xs bg-green-50 text-green-700 border-green-200 dark:bg-green-950/30 dark:text-green-300 dark:border-green-800 shrink-0"
+                                        }
+                                      >
+                                        x{multiplier.toFixed(2)}
+                                      </Badge>
+                                    )}
+                                  </>
+                                );
+                              })()}
+                            </div>
+                          </div>
+                        )}
+                      </div>
+                    )}
 
-                  {/* Session ID */}
-                  <div className="flex-[0.8] min-w-[80px] px-1">
-                    {log.sessionId ? (
+                    {/* Model */}
+                    <div className="flex-[1] min-w-[80px] font-mono text-xs px-1.5">
                       <TooltipProvider>
-                        <Tooltip delayDuration={300}>
+                        <Tooltip>
                           <TooltipTrigger asChild>
-                            <button
-                              type="button"
-                              className="w-full text-left font-mono text-xs truncate cursor-pointer hover:underline"
-                              data-session-id={log.sessionId}
-                              onClick={handleCopySessionIdClick}
-                            >
-                              {log.sessionId}
-                            </button>
+                            <div className="flex items-center gap-1 min-w-0 cursor-help truncate">
+                              <ModelDisplayWithRedirect
+                                originalModel={log.originalModel}
+                                currentModel={log.model}
+                                billingModelSource={billingModelSource}
+                                onRedirectClick={() =>
+                                  setDialogState({ logId: log.id, scrollToRedirect: true })
+                                }
+                              />
+                            </div>
                           </TooltipTrigger>
-                          <TooltipContent side="bottom" align="start" className="max-w-[500px]">
-                            <p className="text-xs whitespace-normal break-words font-mono">
-                              {log.sessionId}
-                            </p>
+                          <TooltipContent>
+                            <p className="text-xs">{log.originalModel || log.model || "-"}</p>
                           </TooltipContent>
                         </Tooltip>
                       </TooltipProvider>
-                    ) : (
-                      <span className="font-mono text-xs text-muted-foreground">-</span>
+                    </div>
+
+                    {/* Tokens */}
+                    {hideTokensColumn ? null : (
+                      <div className="flex-[0.7] min-w-[70px] text-right font-mono text-xs px-1.5">
+                        <TooltipProvider>
+                          <Tooltip delayDuration={250}>
+                            <TooltipTrigger asChild>
+                              <div className="cursor-help flex flex-col items-end leading-tight tabular-nums">
+                                <span>{formatTokenAmount(log.inputTokens)}</span>
+                                <span className="text-muted-foreground">
+                                  {formatTokenAmount(log.outputTokens)}
+                                </span>
+                              </div>
+                            </TooltipTrigger>
+                            <TooltipContent align="end" className="text-xs space-y-1">
+                              <div>
+                                {t("logs.billingDetails.input")}:{" "}
+                                {formatTokenAmount(log.inputTokens)}
+                              </div>
+                              <div>
+                                {t("logs.billingDetails.output")}:{" "}
+                                {formatTokenAmount(log.outputTokens)}
+                              </div>
+                            </TooltipContent>
+                          </Tooltip>
+                        </TooltipProvider>
+                      </div>
                     )}
-                  </div>
 
-                  {/* Provider */}
-                  {hideProviderColumn ? null : (
-                    <div className="flex-[1.5] min-w-[100px] px-1">
-                      {log.blockedBy ? (
-                        <span className="inline-flex items-center gap-1 rounded-md bg-orange-100 dark:bg-orange-950 px-2 py-1 text-xs font-medium text-orange-700 dark:text-orange-300">
-                          <span className="h-1.5 w-1.5 rounded-full bg-orange-600 dark:bg-orange-400" />
-                          {t("logs.table.blocked")}
-                        </span>
-                      ) : (
-                        <div className="flex flex-col items-start gap-0.5 min-w-0">
-                          <div className="flex items-center gap-1 min-w-0 w-full overflow-hidden">
-                            {(() => {
-                              // 计算倍率,用于判断是否显示 Badge
-                              const successfulProvider =
-                                log.providerChain && log.providerChain.length > 0
-                                  ? [...log.providerChain]
-                                      .reverse()
-                                      .find(
-                                        (item) =>
-                                          item.reason === "request_success" ||
-                                          item.reason === "retry_success"
-                                      )
-                                  : null;
-                              const actualCostMultiplier =
-                                successfulProvider?.costMultiplier ?? log.costMultiplier;
-                              const multiplier = Number(actualCostMultiplier);
-                              const hasCostBadge =
-                                actualCostMultiplier !== "" &&
-                                actualCostMultiplier != null &&
-                                Number.isFinite(multiplier) &&
-                                multiplier !== 1;
-
-                              return (
-                                <>
-                                  <div className="flex-1 min-w-0 overflow-hidden">
-                                    <ProviderChainPopover
-                                      chain={log.providerChain ?? []}
-                                      finalProvider={
-                                        (log.providerChain && log.providerChain.length > 0
-                                          ? log.providerChain[log.providerChain.length - 1].name
-                                          : null) ||
-                                        log.providerName ||
-                                        tChain("circuit.unknown")
-                                      }
-                                      hasCostBadge={hasCostBadge}
-                                    />
-                                  </div>
-                                  {/* Cost multiplier badge */}
-                                  {hasCostBadge && (
+                    {/* Cache */}
+                    {hideCacheColumn ? null : (
+                      <div className="flex-[0.8] min-w-[70px] text-right font-mono text-xs px-1.5">
+                        <TooltipProvider>
+                          <Tooltip delayDuration={250}>
+                            <TooltipTrigger asChild>
+                              <div className="cursor-help flex flex-col items-end leading-tight tabular-nums">
+                                <div className="flex items-center gap-1">
+                                  {log.cacheTtlApplied ? (
                                     <Badge
                                       variant="outline"
-                                      className={
-                                        multiplier > 1
-                                          ? "text-xs bg-orange-50 text-orange-700 border-orange-200 dark:bg-orange-950/30 dark:text-orange-300 dark:border-orange-800 shrink-0"
-                                          : "text-xs bg-green-50 text-green-700 border-green-200 dark:bg-green-950/30 dark:text-green-300 dark:border-green-800 shrink-0"
-                                      }
+                                      className="text-[10px] leading-tight px-1 bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950/30 dark:text-blue-300 dark:border-blue-800"
                                     >
-                                      x{multiplier.toFixed(2)}
+                                      {log.cacheTtlApplied}
                                     </Badge>
-                                  )}
-                                </>
-                              );
-                            })()}
-                          </div>
-                          {log.providerChain &&
-                            log.providerChain.length > 0 &&
-                            formatProviderSummary(log.providerChain, tChain) && (
-                              <TooltipProvider>
-                                <Tooltip delayDuration={300}>
-                                  <TooltipTrigger asChild>
-                                    <span className="text-xs text-muted-foreground cursor-help truncate max-w-[180px] block text-left">
-                                      {formatProviderSummary(log.providerChain, tChain)}
-                                    </span>
-                                  </TooltipTrigger>
-                                  <TooltipContent
-                                    side="bottom"
-                                    align="start"
-                                    className="max-w-[500px]"
-                                  >
-                                    <p className="text-xs whitespace-normal break-words font-mono">
-                                      {formatProviderSummary(log.providerChain, tChain)}
-                                    </p>
-                                  </TooltipContent>
-                                </Tooltip>
-                              </TooltipProvider>
-                            )}
-                        </div>
-                      )}
-                    </div>
-                  )}
-
-                  {/* Model */}
-                  <div className="flex-[1] min-w-[80px] font-mono text-xs px-1">
-                    <TooltipProvider>
-                      <Tooltip>
-                        <TooltipTrigger asChild>
-                          <div className="flex items-center gap-1 min-w-0 cursor-help truncate">
-                            <ModelDisplayWithRedirect
-                              originalModel={log.originalModel}
-                              currentModel={log.model}
-                              billingModelSource={billingModelSource}
-                              onRedirectClick={() =>
-                                setDialogState({ logId: log.id, scrollToRedirect: true })
-                              }
-                            />
-                          </div>
-                        </TooltipTrigger>
-                        <TooltipContent>
-                          <p className="text-xs">{log.originalModel || log.model || "-"}</p>
-                        </TooltipContent>
-                      </Tooltip>
-                    </TooltipProvider>
-                  </div>
-
-                  {/* Tokens */}
-                  <div className="flex-[0.7] min-w-[70px] text-right font-mono text-xs px-1">
-                    <TooltipProvider>
-                      <Tooltip delayDuration={250}>
-                        <TooltipTrigger asChild>
-                          <div className="cursor-help flex flex-col items-end leading-tight tabular-nums">
-                            <span>{formatTokenAmount(log.inputTokens)}</span>
-                            <span className="text-muted-foreground">
-                              {formatTokenAmount(log.outputTokens)}
-                            </span>
-                          </div>
-                        </TooltipTrigger>
-                        <TooltipContent align="end" className="text-xs space-y-1">
-                          <div>
-                            {t("logs.billingDetails.input")}: {formatTokenAmount(log.inputTokens)}
-                          </div>
-                          <div>
-                            {t("logs.billingDetails.output")}: {formatTokenAmount(log.outputTokens)}
-                          </div>
-                        </TooltipContent>
-                      </Tooltip>
-                    </TooltipProvider>
-                  </div>
-
-                  {/* Cache */}
-                  <div className="flex-[0.8] min-w-[70px] text-right font-mono text-xs px-1">
-                    <TooltipProvider>
-                      <Tooltip delayDuration={250}>
-                        <TooltipTrigger asChild>
-                          <div className="cursor-help flex flex-col items-end leading-tight tabular-nums">
-                            <div className="flex items-center gap-1">
-                              <span>{formatTokenAmount(log.cacheCreationInputTokens)}</span>
-                              {log.cacheTtlApplied ? (
-                                <Badge variant="outline" className="text-[10px] leading-tight px-1">
-                                  {log.cacheTtlApplied}
-                                </Badge>
-                              ) : null}
-                            </div>
-                            <span className="text-muted-foreground">
-                              {formatTokenAmount(log.cacheReadInputTokens)}
-                            </span>
-                          </div>
-                        </TooltipTrigger>
-                        <TooltipContent align="end" className="text-xs space-y-1">
-                          <div className="font-medium">{t("logs.columns.cacheWrite")}</div>
-                          <div className="pl-2">
-                            5m:{" "}
-                            {formatTokenAmount(
-                              (log.cacheCreation5mInputTokens ?? 0) > 0
-                                ? log.cacheCreation5mInputTokens
-                                : log.cacheTtlApplied !== "1h"
-                                  ? log.cacheCreationInputTokens
-                                  : 0
-                            )}
-                          </div>
-                          <div className="pl-2">
-                            1h:{" "}
-                            {formatTokenAmount(
-                              (log.cacheCreation1hInputTokens ?? 0) > 0
-                                ? log.cacheCreation1hInputTokens
-                                : log.cacheTtlApplied === "1h"
-                                  ? log.cacheCreationInputTokens
-                                  : 0
-                            )}
-                          </div>
-                          <div className="font-medium mt-1">{t("logs.columns.cacheRead")}</div>
-                          <div className="pl-2">{formatTokenAmount(log.cacheReadInputTokens)}</div>
-                        </TooltipContent>
-                      </Tooltip>
-                    </TooltipProvider>
-                  </div>
-
-                  {/* Cost */}
-                  <div className="flex-[0.7] min-w-[60px] text-right font-mono text-xs px-1">
-                    {isNonBilling ? (
-                      "-"
-                    ) : log.costUsd ? (
-                      <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 && (
-                              <div className="text-purple-600 dark:text-purple-400 font-medium">
-                                {t("logs.billingDetails.context1m")}
+                                  ) : null}
+                                  <span>{formatTokenAmount(log.cacheCreationInputTokens)}</span>
+                                </div>
+                                <span className="text-muted-foreground">
+                                  {formatTokenAmount(log.cacheReadInputTokens)}
+                                </span>
                               </div>
-                            )}
-                            <div>
-                              {t("logs.billingDetails.input")}: {formatTokenAmount(log.inputTokens)}{" "}
-                              tokens
-                            </div>
-                            <div>
-                              {t("logs.billingDetails.output")}:{" "}
-                              {formatTokenAmount(log.outputTokens)} tokens
-                            </div>
-                          </TooltipContent>
-                        </Tooltip>
-                      </TooltipProvider>
-                    ) : (
-                      "-"
+                            </TooltipTrigger>
+                            <TooltipContent align="end" className="text-xs space-y-1">
+                              <div className="font-medium">{t("logs.columns.cacheWrite")}</div>
+                              <div className="pl-2">
+                                5m:{" "}
+                                {formatTokenAmount(
+                                  (log.cacheCreation5mInputTokens ?? 0) > 0
+                                    ? log.cacheCreation5mInputTokens
+                                    : log.cacheTtlApplied !== "1h"
+                                      ? log.cacheCreationInputTokens
+                                      : 0
+                                )}
+                              </div>
+                              <div className="pl-2">
+                                1h:{" "}
+                                {formatTokenAmount(
+                                  (log.cacheCreation1hInputTokens ?? 0) > 0
+                                    ? log.cacheCreation1hInputTokens
+                                    : log.cacheTtlApplied === "1h"
+                                      ? log.cacheCreationInputTokens
+                                      : 0
+                                )}
+                              </div>
+                              <div className="font-medium mt-1">{t("logs.columns.cacheRead")}</div>
+                              <div className="pl-2">
+                                {formatTokenAmount(log.cacheReadInputTokens)}
+                              </div>
+                            </TooltipContent>
+                          </Tooltip>
+                        </TooltipProvider>
+                      </div>
                     )}
-                  </div>
 
-                  {/* Performance */}
-                  <div className="flex-[0.8] min-w-[80px] text-right font-mono text-xs px-1">
-                    {(() => {
-                      const rate = calculateOutputRate(
-                        log.outputTokens,
-                        log.durationMs,
-                        log.ttfbMs
-                      );
-                      const ttfbLine =
-                        log.ttfbMs != null && log.ttfbMs > 0
-                          ? `TTFB ${formatDuration(log.ttfbMs)}`
-                          : null;
-                      const rateLine = rate !== null ? `${rate.toFixed(0)} tok/s` : null;
-
-                      return (
+                    {/* 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>
-                              <div className="flex flex-col items-end cursor-help">
-                                <span>{formatDuration(log.durationMs)}</span>
-                                {ttfbLine && (
-                                  <span className="text-muted-foreground text-[10px]">
-                                    {ttfbLine}
-                                  </span>
-                                )}
-                                {rateLine && (
-                                  <span className="text-muted-foreground text-[10px]">
-                                    {rateLine}
-                                  </span>
+                              <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>
                                 )}
-                              </div>
+                              </span>
                             </TooltipTrigger>
-                            <TooltipContent align="end" className="text-xs space-y-1">
-                              <div>
-                                {t("logs.details.performance.duration")}:{" "}
-                                {formatDuration(log.durationMs)}
-                              </div>
-                              {log.ttfbMs != null && (
-                                <div>
-                                  {t("logs.details.performance.ttfb")}: {formatDuration(log.ttfbMs)}
-                                </div>
-                              )}
-                              {rate !== null && (
-                                <div>
-                                  {t("logs.details.performance.outputRate")}: {rate.toFixed(1)}{" "}
-                                  tok/s
+                            <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>
                               )}
+                              <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>
 
-                  {/* Status */}
-                  <div className="flex-[0.7] min-w-[70px] pr-2">
-                    <ErrorDetailsDialog
-                      statusCode={log.statusCode}
-                      errorMessage={log.errorMessage}
-                      providerChain={log.providerChain}
-                      sessionId={log.sessionId}
-                      requestSequence={log.requestSequence}
-                      blockedBy={log.blockedBy}
-                      blockedReason={log.blockedReason}
-                      originalModel={log.originalModel}
-                      currentModel={log.model}
-                      userAgent={log.userAgent}
-                      messagesCount={log.messagesCount}
-                      endpoint={log.endpoint}
-                      billingModelSource={billingModelSource}
-                      specialSettings={log.specialSettings}
-                      inputTokens={log.inputTokens}
-                      outputTokens={log.outputTokens}
-                      cacheCreationInputTokens={log.cacheCreationInputTokens}
-                      cacheCreation5mInputTokens={log.cacheCreation5mInputTokens}
-                      cacheCreation1hInputTokens={log.cacheCreation1hInputTokens}
-                      cacheReadInputTokens={log.cacheReadInputTokens}
-                      cacheTtlApplied={log.cacheTtlApplied}
-                      costUsd={log.costUsd}
-                      costMultiplier={log.costMultiplier}
-                      context1mApplied={log.context1mApplied}
-                      durationMs={log.durationMs}
-                      ttfbMs={log.ttfbMs}
-                      externalOpen={dialogState.logId === log.id ? true : undefined}
-                      onExternalOpenChange={(open) => {
-                        if (!open) setDialogState({ logId: null, scrollToRedirect: false });
-                      }}
-                      scrollToRedirect={
-                        dialogState.logId === log.id && dialogState.scrollToRedirect
-                      }
-                    />
+                    {/* Performance */}
+                    {hidePerformanceColumn ? null : (
+                      <div className="flex-[0.8] min-w-[80px] text-right font-mono text-xs px-1.5">
+                        {(() => {
+                          const rate = calculateOutputRate(
+                            log.outputTokens,
+                            log.durationMs,
+                            log.ttfbMs
+                          );
+                          const ttfbLine =
+                            log.ttfbMs != null && log.ttfbMs > 0
+                              ? `TTFB ${formatDuration(log.ttfbMs)}`
+                              : null;
+                          const rateLine = rate !== null ? `${rate.toFixed(0)} tok/s` : null;
+
+                          return (
+                            <TooltipProvider>
+                              <Tooltip delayDuration={250}>
+                                <TooltipTrigger asChild>
+                                  <div className="flex flex-col items-end cursor-help">
+                                    <span>{formatDuration(log.durationMs)}</span>
+                                    {ttfbLine && (
+                                      <span className="text-muted-foreground text-[10px]">
+                                        {ttfbLine}
+                                      </span>
+                                    )}
+                                    {rateLine && (
+                                      <span className="text-muted-foreground text-[10px]">
+                                        {rateLine}
+                                      </span>
+                                    )}
+                                  </div>
+                                </TooltipTrigger>
+                                <TooltipContent align="end" className="text-xs space-y-1">
+                                  <div>
+                                    {t("logs.details.performance.duration")}:{" "}
+                                    {formatDuration(log.durationMs)}
+                                  </div>
+                                  {log.ttfbMs != null && (
+                                    <div>
+                                      {t("logs.details.performance.ttfb")}:{" "}
+                                      {formatDuration(log.ttfbMs)}
+                                    </div>
+                                  )}
+                                  {rate !== null && (
+                                    <div>
+                                      {t("logs.details.performance.outputRate")}: {rate.toFixed(1)}{" "}
+                                      tok/s
+                                    </div>
+                                  )}
+                                </TooltipContent>
+                              </Tooltip>
+                            </TooltipProvider>
+                          );
+                        })()}
+                      </div>
+                    )}
+
+                    {/* Status */}
+                    <div className="flex-[0.7] min-w-[70px] pr-3">
+                      <ErrorDetailsDialog
+                        statusCode={log.statusCode}
+                        errorMessage={log.errorMessage}
+                        providerChain={log.providerChain}
+                        sessionId={log.sessionId}
+                        requestSequence={log.requestSequence}
+                        blockedBy={log.blockedBy}
+                        blockedReason={log.blockedReason}
+                        originalModel={log.originalModel}
+                        currentModel={log.model}
+                        userAgent={log.userAgent}
+                        messagesCount={log.messagesCount}
+                        endpoint={log.endpoint}
+                        billingModelSource={billingModelSource}
+                        specialSettings={log.specialSettings}
+                        inputTokens={log.inputTokens}
+                        outputTokens={log.outputTokens}
+                        cacheCreationInputTokens={log.cacheCreationInputTokens}
+                        cacheCreation5mInputTokens={log.cacheCreation5mInputTokens}
+                        cacheCreation1hInputTokens={log.cacheCreation1hInputTokens}
+                        cacheReadInputTokens={log.cacheReadInputTokens}
+                        cacheTtlApplied={log.cacheTtlApplied}
+                        costUsd={log.costUsd}
+                        costMultiplier={log.costMultiplier}
+                        context1mApplied={log.context1mApplied}
+                        durationMs={log.durationMs}
+                        ttfbMs={log.ttfbMs}
+                        externalOpen={dialogState.logId === log.id ? true : undefined}
+                        onExternalOpenChange={(open) => {
+                          if (!open) setDialogState({ logId: null, scrollToRedirect: false });
+                        }}
+                        scrollToRedirect={
+                          dialogState.logId === log.id && dialogState.scrollToRedirect
+                        }
+                      />
+                    </div>
                   </div>
-                </div>
-              );
-            })}
+                );
+              })}
+            </div>
           </div>
         </div>
       </div>

+ 10 - 15
src/app/[locale]/dashboard/logs/page.tsx

@@ -1,6 +1,4 @@
-import { getTranslations } from "next-intl/server";
 import { Suspense } from "react";
-import { Section } from "@/components/section";
 import { redirect } from "@/i18n/routing";
 import { getSession } from "@/lib/auth";
 import { ActiveSessionsSkeleton } from "./_components/active-sessions-skeleton";
@@ -19,7 +17,6 @@ export default async function UsageLogsPage({
   params: Promise<{ locale: string }>;
   searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
 }) {
-  // Await params to ensure locale is available in the async context
   const { locale } = await params;
 
   const session = await getSession();
@@ -29,23 +26,21 @@ export default async function UsageLogsPage({
 
   const isAdmin = session.user.role === "admin";
 
-  const t = await getTranslations("dashboard");
-
   return (
-    <div className="space-y-6">
+    <div className="space-y-4">
+      {/* Active Sessions - Horizontal scrolling cards */}
       <Suspense fallback={<ActiveSessionsSkeleton />}>
         <UsageLogsActiveSessionsSection />
       </Suspense>
 
-      <Section title={t("title.usageLogs")} description={t("title.usageLogsDescription")}>
-        <Suspense fallback={<UsageLogsSkeleton />}>
-          <UsageLogsDataSection
-            isAdmin={isAdmin}
-            userId={session.user.id}
-            searchParams={searchParams}
-          />
-        </Suspense>
-      </Section>
+      {/* Stats + Filters + Logs Table */}
+      <Suspense fallback={<UsageLogsSkeleton />}>
+        <UsageLogsDataSection
+          isAdmin={isAdmin}
+          userId={session.user.id}
+          searchParams={searchParams}
+        />
+      </Suspense>
     </div>
   );
 }

+ 5 - 2
src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-details-tabs.tsx

@@ -17,6 +17,7 @@ import { Button } from "@/components/ui/button";
 import { CodeDisplay } from "@/components/ui/code-display";
 import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { redactMessages, redactRequestBody } from "@/lib/utils/message-redaction";
 import { isSSEText } from "@/lib/utils/sse";
 
 export type SessionMessages = Record<string, unknown> | Record<string, unknown>[];
@@ -72,12 +73,14 @@ export function SessionMessagesDetailsTabs({
 
   const requestBodyContent = useMemo(() => {
     if (requestBody === null) return null;
-    return JSON.stringify(requestBody, null, 2);
+    const redacted = redactRequestBody(requestBody);
+    return JSON.stringify(redacted, null, 2);
   }, [requestBody]);
 
   const requestMessagesContent = useMemo(() => {
     if (messages === null) return null;
-    return JSON.stringify(messages, null, 2);
+    const redacted = redactMessages(messages);
+    return JSON.stringify(redacted, null, 2);
   }, [messages]);
 
   const specialSettingsContent = useMemo(() => {

+ 2 - 0
src/app/providers.tsx

@@ -1,6 +1,7 @@
 "use client";
 
 import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { Agentation } from "agentation";
 import { ThemeProvider } from "next-themes";
 import { type ReactNode, useState } from "react";
 
@@ -22,6 +23,7 @@ export function AppProviders({ children }: AppProvidersProps) {
         disableTransitionOnChange
       >
         {children}
+        {process.env.NODE_ENV === "development" && <Agentation />}
       </ThemeProvider>
     </QueryClientProvider>
   );

+ 207 - 0
src/components/customs/active-sessions-cards.tsx

@@ -0,0 +1,207 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { Activity, ChevronRight, Clock, Cpu, Key, Loader2, User, XCircle } from "lucide-react";
+import { useTranslations } from "next-intl";
+import { getActiveSessions } from "@/actions/active-sessions";
+import { Badge } from "@/components/ui/badge";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Link } from "@/i18n/routing";
+import { cn, formatTokenAmount } from "@/lib/utils";
+import type { CurrencyCode } from "@/lib/utils/currency";
+import { formatCurrency } from "@/lib/utils/currency";
+import type { ActiveSessionInfo } from "@/types/session";
+
+const REFRESH_INTERVAL = 5000;
+
+async function fetchActiveSessions(): Promise<ActiveSessionInfo[]> {
+  const result = await getActiveSessions();
+  if (!result.ok) {
+    throw new Error(result.error || "Failed to fetch active sessions");
+  }
+  return result.data;
+}
+
+function formatDuration(durationMs: number | undefined): string {
+  if (!durationMs) return "-";
+  if (durationMs < 1000) return `${durationMs}ms`;
+  if (durationMs < 60000) return `${(Number(durationMs) / 1000).toFixed(1)}s`;
+  const minutes = Math.floor(durationMs / 60000);
+  const seconds = Math.floor((durationMs % 60000) / 1000);
+  return `${minutes}m ${seconds}s`;
+}
+
+interface ActiveSessionCardProps {
+  session: ActiveSessionInfo;
+  currencyCode: CurrencyCode;
+}
+
+function ActiveSessionCard({ session, currencyCode }: ActiveSessionCardProps) {
+  const isError = session.status === "error" || (session.statusCode && session.statusCode >= 400);
+  const isInProgress = session.status === "in_progress";
+
+  return (
+    <Link href={`/dashboard/sessions/${session.sessionId}/messages`} className="block group">
+      <Card
+        className={cn(
+          "w-[280px] shrink-0 transition-all duration-200 hover:shadow-md hover:border-primary/30",
+          isError && "border-destructive/40 bg-destructive/5",
+          isInProgress && "border-blue-500/40 bg-blue-500/5"
+        )}
+      >
+        <CardContent className="p-4 space-y-3">
+          {/* Status indicator + User */}
+          <div className="flex items-center justify-between">
+            <div className="flex items-center gap-2 min-w-0 flex-1">
+              {isInProgress ? (
+                <Loader2 className="h-3.5 w-3.5 text-blue-500 animate-spin shrink-0" />
+              ) : isError ? (
+                <XCircle className="h-3.5 w-3.5 text-destructive shrink-0" />
+              ) : (
+                <div className="h-2 w-2 rounded-full bg-green-500 shrink-0" />
+              )}
+              <div className="flex items-center gap-1.5 min-w-0">
+                <User className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
+                <span className="font-medium text-sm truncate" title={session.userName}>
+                  {session.userName}
+                </span>
+              </div>
+            </div>
+            <Badge variant="outline" className="text-xs shrink-0">
+              <Clock className="h-3 w-3 mr-1" />
+              {formatDuration(session.durationMs)}
+            </Badge>
+          </div>
+
+          {/* Model + Provider */}
+          <div className="flex items-center gap-1.5 text-xs">
+            <Cpu className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
+            <span className="font-mono truncate" title={session.model ?? undefined}>
+              {session.model}
+            </span>
+            {session.providerName && (
+              <span className="text-muted-foreground truncate">@ {session.providerName}</span>
+            )}
+          </div>
+
+          {/* Key */}
+          <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
+            <Key className="h-3 w-3 shrink-0" />
+            <span className="font-mono truncate" title={session.keyName}>
+              {session.keyName}
+            </span>
+          </div>
+
+          {/* Tokens + Cost */}
+          <div className="flex items-center justify-between text-xs pt-2 border-t">
+            <div className="font-mono text-muted-foreground">
+              {session.inputTokens !== undefined && (
+                <span className="mr-2">{formatTokenAmount(session.inputTokens)} in</span>
+              )}
+              {session.outputTokens !== undefined && (
+                <span>{formatTokenAmount(session.outputTokens)} out</span>
+              )}
+            </div>
+            {session.costUsd && (
+              <span className="font-mono font-medium">
+                {formatCurrency(session.costUsd, currencyCode, 4)}
+              </span>
+            )}
+          </div>
+        </CardContent>
+      </Card>
+    </Link>
+  );
+}
+
+function ActiveSessionCardSkeleton() {
+  return (
+    <Card className="w-[280px] shrink-0">
+      <CardContent className="p-4 space-y-3">
+        <div className="flex items-center justify-between">
+          <Skeleton className="h-5 w-24" />
+          <Skeleton className="h-5 w-16" />
+        </div>
+        <Skeleton className="h-4 w-40" />
+        <Skeleton className="h-4 w-32" />
+        <div className="flex items-center justify-between pt-2 border-t">
+          <Skeleton className="h-4 w-24" />
+          <Skeleton className="h-4 w-16" />
+        </div>
+      </CardContent>
+    </Card>
+  );
+}
+
+interface ActiveSessionsCardsProps {
+  currencyCode?: CurrencyCode;
+  className?: string;
+}
+
+export function ActiveSessionsCards({ currencyCode = "USD", className }: ActiveSessionsCardsProps) {
+  const tc = useTranslations("customs");
+
+  const { data = [], isLoading } = useQuery<ActiveSessionInfo[], Error>({
+    queryKey: ["active-sessions"],
+    queryFn: fetchActiveSessions,
+    refetchInterval: REFRESH_INTERVAL,
+  });
+
+  return (
+    <Card className={cn("border-border/50", className)}>
+      <CardHeader className="pb-3">
+        <div className="flex items-center justify-between">
+          <div className="flex items-center gap-2">
+            <div className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10">
+              <Activity className="h-4 w-4 text-primary" />
+            </div>
+            <div>
+              <CardTitle className="text-base">{tc("activeSessions.title")}</CardTitle>
+              <CardDescription className="text-xs">
+                {tc("activeSessions.summary", { count: data.length, minutes: 5 })}
+              </CardDescription>
+            </div>
+          </div>
+          <Link
+            href="/dashboard/sessions"
+            className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
+          >
+            {tc("activeSessions.viewAll")}
+            <ChevronRight className="h-3.5 w-3.5" />
+          </Link>
+        </div>
+      </CardHeader>
+      <CardContent className="pt-0">
+        {isLoading && data.length === 0 ? (
+          <ScrollArea className="w-full">
+            <div className="flex gap-3 pb-3">
+              {[1, 2, 3].map((i) => (
+                <ActiveSessionCardSkeleton key={i} />
+              ))}
+            </div>
+            <ScrollBar orientation="horizontal" />
+          </ScrollArea>
+        ) : data.length === 0 ? (
+          <div className="flex items-center justify-center h-24 text-muted-foreground text-sm">
+            {tc("activeSessions.empty")}
+          </div>
+        ) : (
+          <ScrollArea className="w-full">
+            <div className="flex gap-3 pb-3">
+              {data.map((session) => (
+                <ActiveSessionCard
+                  key={session.sessionId}
+                  session={session}
+                  currencyCode={currencyCode}
+                />
+              ))}
+            </div>
+            <ScrollBar orientation="horizontal" />
+          </ScrollArea>
+        )}
+      </CardContent>
+    </Card>
+  );
+}

+ 45 - 0
src/components/ui/scroll-area.tsx

@@ -0,0 +1,45 @@
+"use client";
+
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
+import * as React from "react";
+import { cn } from "@/lib/utils";
+
+const ScrollArea = React.forwardRef<
+  React.ElementRef<typeof ScrollAreaPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
+>(({ className, children, ...props }, ref) => (
+  <ScrollAreaPrimitive.Root
+    ref={ref}
+    className={cn("relative overflow-hidden", className)}
+    {...props}
+  >
+    <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
+      {children}
+    </ScrollAreaPrimitive.Viewport>
+    <ScrollBar />
+    <ScrollAreaPrimitive.Corner />
+  </ScrollAreaPrimitive.Root>
+));
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
+
+const ScrollBar = React.forwardRef<
+  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
+  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
+>(({ className, orientation = "vertical", ...props }, ref) => (
+  <ScrollAreaPrimitive.ScrollAreaScrollbar
+    ref={ref}
+    orientation={orientation}
+    className={cn(
+      "flex touch-none select-none transition-colors",
+      orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
+      orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
+      className
+    )}
+    {...props}
+  >
+    <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
+  </ScrollAreaPrimitive.ScrollAreaScrollbar>
+));
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
+
+export { ScrollArea, ScrollBar };

+ 7 - 10
src/components/ui/tag-input.tsx

@@ -1,8 +1,8 @@
 "use client";
 
+import * as Portal from "@radix-ui/react-portal";
 import { X } from "lucide-react";
 import * as React from "react";
-import { createPortal } from "react-dom";
 import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
 import { cn } from "@/lib/utils";
 import { Badge } from "./badge";
@@ -468,12 +468,9 @@ export function TagInput({
           <X className="h-3.5 w-3.5" />
         </button>
       )}
-      {/* 建议下拉列表 */}
-      {showSuggestions &&
-        filteredSuggestions.length > 0 &&
-        dropdownPosition &&
-        typeof document !== "undefined" &&
-        createPortal(
+      {/* 建议下拉列表 - 使用 Radix Portal 确保在 Dialog 中正确渲染 */}
+      {showSuggestions && filteredSuggestions.length > 0 && dropdownPosition && (
+        <Portal.Root>
           <div
             ref={dropdownRef}
             className="fixed z-[9999] rounded-md border bg-popover shadow-md max-h-48 overflow-auto"
@@ -500,9 +497,9 @@ export function TagInput({
                 {suggestion.label}
               </button>
             ))}
-          </div>,
-          document.body
-        )}
+          </div>
+        </Portal.Root>
+      )}
     </div>
   );
 }

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

@@ -0,0 +1,216 @@
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
+import {
+  DEFAULT_VISIBLE_COLUMNS,
+  getHiddenColumns,
+  getVisibleColumns,
+  resetColumns,
+  setHiddenColumns,
+  toggleColumn,
+  type LogsTableColumn,
+} from "./column-visibility";
+
+// Mock localStorage
+const mockStorage: Record<string, string> = {};
+const mockLocalStorage = {
+  getItem: vi.fn((key: string) => mockStorage[key] ?? null),
+  setItem: vi.fn((key: string, value: string) => {
+    mockStorage[key] = value;
+  }),
+  removeItem: vi.fn((key: string) => {
+    delete mockStorage[key];
+  }),
+  clear: vi.fn(() => {
+    for (const key of Object.keys(mockStorage)) {
+      delete mockStorage[key];
+    }
+  }),
+  length: 0,
+  key: vi.fn(),
+};
+
+Object.defineProperty(globalThis, "localStorage", {
+  value: mockLocalStorage,
+  writable: true,
+});
+
+describe("column-visibility", () => {
+  const userId = 123;
+  const tableId = "usage-logs";
+  const storageKey = `claude-code-hub-columns:${tableId}:${userId}`;
+
+  beforeEach(() => {
+    // Clear mock storage before each test
+    for (const key of Object.keys(mockStorage)) {
+      delete mockStorage[key];
+    }
+    vi.clearAllMocks();
+  });
+
+  afterEach(() => {
+    vi.restoreAllMocks();
+  });
+
+  describe("getHiddenColumns", () => {
+    test("returns empty array when no data stored", () => {
+      const result = getHiddenColumns(userId, tableId);
+      expect(result).toEqual([]);
+    });
+
+    test("returns stored hidden columns", () => {
+      const hidden: LogsTableColumn[] = ["user", "key"];
+      mockStorage[storageKey] = JSON.stringify(hidden);
+
+      const result = getHiddenColumns(userId, tableId);
+      expect(result).toEqual(hidden);
+    });
+
+    test("filters out invalid column names", () => {
+      const stored = ["user", "invalid_column", "key"];
+      mockStorage[storageKey] = JSON.stringify(stored);
+
+      const result = getHiddenColumns(userId, tableId);
+      expect(result).toEqual(["user", "key"]);
+    });
+
+    test("handles JSON parse errors gracefully", () => {
+      mockStorage[storageKey] = "not-valid-json";
+
+      const result = getHiddenColumns(userId, tableId);
+      expect(result).toEqual([]);
+    });
+
+    test("scopes by user ID", () => {
+      const user1Hidden: LogsTableColumn[] = ["user"];
+      const user2Hidden: LogsTableColumn[] = ["provider", "tokens"];
+
+      mockStorage[`claude-code-hub-columns:${tableId}:1`] = JSON.stringify(user1Hidden);
+      mockStorage[`claude-code-hub-columns:${tableId}:2`] = JSON.stringify(user2Hidden);
+
+      expect(getHiddenColumns(1, tableId)).toEqual(user1Hidden);
+      expect(getHiddenColumns(2, tableId)).toEqual(user2Hidden);
+    });
+
+    test("scopes by table ID", () => {
+      const table1Hidden: LogsTableColumn[] = ["user"];
+      const table2Hidden: LogsTableColumn[] = ["provider"];
+
+      mockStorage[`claude-code-hub-columns:table1:${userId}`] = JSON.stringify(table1Hidden);
+      mockStorage[`claude-code-hub-columns:table2:${userId}`] = JSON.stringify(table2Hidden);
+
+      expect(getHiddenColumns(userId, "table1")).toEqual(table1Hidden);
+      expect(getHiddenColumns(userId, "table2")).toEqual(table2Hidden);
+    });
+  });
+
+  describe("getVisibleColumns", () => {
+    test("returns all columns when none hidden", () => {
+      const result = getVisibleColumns(userId, tableId);
+      expect(result).toEqual(DEFAULT_VISIBLE_COLUMNS);
+    });
+
+    test("excludes hidden columns", () => {
+      const hidden: LogsTableColumn[] = ["user", "provider"];
+      mockStorage[storageKey] = JSON.stringify(hidden);
+
+      const result = getVisibleColumns(userId, tableId);
+      expect(result).not.toContain("user");
+      expect(result).not.toContain("provider");
+      expect(result).toContain("key");
+      expect(result).toContain("sessionId");
+      expect(result).toContain("tokens");
+    });
+  });
+
+  describe("setHiddenColumns", () => {
+    test("stores hidden columns in localStorage", () => {
+      const hidden: LogsTableColumn[] = ["user", "key"];
+      setHiddenColumns(userId, tableId, hidden);
+
+      expect(mockStorage[storageKey]).toBe(JSON.stringify(hidden));
+    });
+
+    test("removes storage key when array is empty", () => {
+      // First set some hidden columns
+      mockStorage[storageKey] = JSON.stringify(["user"]);
+
+      // Then reset to empty
+      setHiddenColumns(userId, tableId, []);
+
+      expect(mockStorage[storageKey]).toBeUndefined();
+    });
+
+    test("handles localStorage errors gracefully", () => {
+      mockLocalStorage.setItem.mockImplementationOnce(() => {
+        throw new Error("QuotaExceededError");
+      });
+
+      // Should not throw
+      expect(() => setHiddenColumns(userId, tableId, ["user"])).not.toThrow();
+    });
+  });
+
+  describe("toggleColumn", () => {
+    test("hides visible column", () => {
+      const result = toggleColumn(userId, tableId, "user");
+
+      expect(result).toContain("user");
+      expect(getHiddenColumns(userId, tableId)).toContain("user");
+    });
+
+    test("shows hidden column", () => {
+      // First hide the column
+      setHiddenColumns(userId, tableId, ["user", "key"]);
+
+      // Then toggle it back
+      const result = toggleColumn(userId, tableId, "user");
+
+      expect(result).not.toContain("user");
+      expect(result).toContain("key");
+      expect(getHiddenColumns(userId, tableId)).toEqual(["key"]);
+    });
+
+    test("returns updated hidden columns array", () => {
+      const result1 = toggleColumn(userId, tableId, "user");
+      expect(result1).toEqual(["user"]);
+
+      const result2 = toggleColumn(userId, tableId, "provider");
+      expect(result2).toEqual(["user", "provider"]);
+
+      const result3 = toggleColumn(userId, tableId, "user");
+      expect(result3).toEqual(["provider"]);
+    });
+  });
+
+  describe("resetColumns", () => {
+    test("removes all hidden columns", () => {
+      // Set some hidden columns
+      setHiddenColumns(userId, tableId, ["user", "key", "provider"]);
+      expect(getHiddenColumns(userId, tableId)).toHaveLength(3);
+
+      // Reset
+      resetColumns(userId, tableId);
+
+      expect(getHiddenColumns(userId, tableId)).toEqual([]);
+      expect(mockStorage[storageKey]).toBeUndefined();
+    });
+
+    test("is idempotent when no columns hidden", () => {
+      resetColumns(userId, tableId);
+      resetColumns(userId, tableId);
+
+      expect(getHiddenColumns(userId, tableId)).toEqual([]);
+    });
+  });
+
+  describe("DEFAULT_VISIBLE_COLUMNS", () => {
+    test("contains all expected toggleable columns", () => {
+      expect(DEFAULT_VISIBLE_COLUMNS).toContain("user");
+      expect(DEFAULT_VISIBLE_COLUMNS).toContain("key");
+      expect(DEFAULT_VISIBLE_COLUMNS).toContain("sessionId");
+      expect(DEFAULT_VISIBLE_COLUMNS).toContain("provider");
+      expect(DEFAULT_VISIBLE_COLUMNS).toContain("tokens");
+      expect(DEFAULT_VISIBLE_COLUMNS).toContain("cache");
+      expect(DEFAULT_VISIBLE_COLUMNS).toContain("performance");
+    });
+  });
+});

+ 144 - 0
src/lib/column-visibility.ts

@@ -0,0 +1,144 @@
+/**
+ * Column visibility persistence utility
+ *
+ * Manages user preferences for table column visibility.
+ * Uses localStorage with user ID scoping for persistence.
+ */
+
+const STORAGE_PREFIX = "claude-code-hub-columns";
+
+/**
+ * All toggleable column types across the application
+ */
+export type LogsTableColumn =
+  | "user"
+  | "key"
+  | "sessionId"
+  | "provider"
+  | "tokens"
+  | "cache"
+  | "performance";
+
+/**
+ * Default visible columns (all visible by default)
+ */
+export const DEFAULT_VISIBLE_COLUMNS: LogsTableColumn[] = [
+  "user",
+  "key",
+  "sessionId",
+  "provider",
+  "tokens",
+  "cache",
+  "performance",
+];
+
+/**
+ * Columns that cannot be hidden (always visible)
+ */
+export const ALWAYS_VISIBLE_COLUMNS = ["time", "model", "cost", "status"] as const;
+
+/**
+ * Get the storage key for a specific user and table
+ */
+function getStorageKey(userId: number, tableId: string): string {
+  return `${STORAGE_PREFIX}:${tableId}:${userId}`;
+}
+
+/**
+ * Get hidden columns from localStorage
+ *
+ * @param userId - User ID for scoping
+ * @param tableId - Table identifier (e.g., "usage-logs")
+ * @returns Array of hidden column names
+ */
+export function getHiddenColumns(userId: number, tableId: string): LogsTableColumn[] {
+  if (typeof window === "undefined") {
+    return [];
+  }
+
+  try {
+    const stored = localStorage.getItem(getStorageKey(userId, tableId));
+    if (!stored) {
+      return [];
+    }
+    const parsed = JSON.parse(stored) as LogsTableColumn[];
+    // Validate that all items are valid column names
+    return parsed.filter((col) => DEFAULT_VISIBLE_COLUMNS.includes(col));
+  } catch {
+    return [];
+  }
+}
+
+/**
+ * Get visible columns (inverse of hidden)
+ *
+ * @param userId - User ID for scoping
+ * @param tableId - Table identifier
+ * @returns Array of visible column names
+ */
+export function getVisibleColumns(userId: number, tableId: string): LogsTableColumn[] {
+  const hidden = getHiddenColumns(userId, tableId);
+  return DEFAULT_VISIBLE_COLUMNS.filter((col) => !hidden.includes(col));
+}
+
+/**
+ * Set hidden columns in localStorage
+ *
+ * @param userId - User ID for scoping
+ * @param tableId - Table identifier
+ * @param hiddenColumns - Array of column names to hide
+ */
+export function setHiddenColumns(
+  userId: number,
+  tableId: string,
+  hiddenColumns: LogsTableColumn[]
+): void {
+  if (typeof window === "undefined") {
+    return;
+  }
+
+  try {
+    const key = getStorageKey(userId, tableId);
+    if (hiddenColumns.length === 0) {
+      localStorage.removeItem(key);
+    } else {
+      localStorage.setItem(key, JSON.stringify(hiddenColumns));
+    }
+  } catch {
+    // localStorage not available, silently fail
+  }
+}
+
+/**
+ * Toggle a single column's visibility
+ *
+ * @param userId - User ID for scoping
+ * @param tableId - Table identifier
+ * @param column - Column to toggle
+ * @returns New array of hidden columns
+ */
+export function toggleColumn(
+  userId: number,
+  tableId: string,
+  column: LogsTableColumn
+): LogsTableColumn[] {
+  const currentHidden = getHiddenColumns(userId, tableId);
+  const isCurrentlyHidden = currentHidden.includes(column);
+
+  const newHidden = isCurrentlyHidden
+    ? currentHidden.filter((c) => c !== column)
+    : [...currentHidden, column];
+
+  setHiddenColumns(userId, tableId, newHidden);
+  return newHidden;
+}
+
+/**
+ * Reset all columns to visible
+ *
+ * @param userId - User ID for scoping
+ * @param tableId - Table identifier
+ */
+export function resetColumns(userId: number, tableId: string): void {
+  setHiddenColumns(userId, tableId, []);
+}

+ 297 - 0
src/lib/utils/message-redaction.test.ts

@@ -0,0 +1,297 @@
+import { describe, expect, test } from "vitest";
+import {
+  REDACTED_MARKER,
+  redactJsonString,
+  redactMessages,
+  redactRequestBody,
+} from "@/lib/utils/message-redaction";
+
+describe("message-redaction", () => {
+  describe("redactRequestBody", () => {
+    test("should redact simple string message content", () => {
+      const body = {
+        model: "claude-3-opus",
+        messages: [
+          { role: "user", content: "Hello, this is a secret message" },
+          { role: "assistant", content: "I understand your secret" },
+        ],
+      };
+
+      const result = redactRequestBody(body);
+
+      expect(result).toEqual({
+        model: "claude-3-opus",
+        messages: [
+          { role: "user", content: REDACTED_MARKER },
+          { role: "assistant", content: REDACTED_MARKER },
+        ],
+      });
+    });
+
+    test("should redact array content with text blocks", () => {
+      const body = {
+        messages: [
+          {
+            role: "user",
+            content: [
+              { type: "text", text: "Secret text content" },
+              { type: "text", text: "Another secret" },
+            ],
+          },
+        ],
+      };
+
+      const result = redactRequestBody(body) as { messages: Array<{ content: unknown[] }> };
+
+      expect(result.messages[0].content).toEqual([
+        { type: "text", text: REDACTED_MARKER },
+        { type: "text", text: REDACTED_MARKER },
+      ]);
+    });
+
+    test("should redact image source data", () => {
+      const body = {
+        messages: [
+          {
+            role: "user",
+            content: [
+              {
+                type: "image",
+                source: {
+                  type: "base64",
+                  media_type: "image/png",
+                  data: "base64encodedimagedata",
+                },
+              },
+            ],
+          },
+        ],
+      };
+
+      const result = redactRequestBody(body) as { messages: Array<{ content: unknown[] }> };
+      const imageBlock = result.messages[0].content[0] as { source: { data: string } };
+
+      expect(imageBlock.source.data).toBe(REDACTED_MARKER);
+    });
+
+    test("should redact tool_use input", () => {
+      const body = {
+        messages: [
+          {
+            role: "assistant",
+            content: [
+              {
+                type: "tool_use",
+                id: "toolu_123",
+                name: "search",
+                input: { query: "secret search query" },
+              },
+            ],
+          },
+        ],
+      };
+
+      const result = redactRequestBody(body) as { messages: Array<{ content: unknown[] }> };
+      const toolBlock = result.messages[0].content[0] as { input: string };
+
+      expect(toolBlock.input).toBe(REDACTED_MARKER);
+    });
+
+    test("should redact tool_result content", () => {
+      const body = {
+        messages: [
+          {
+            role: "user",
+            content: [
+              {
+                type: "tool_result",
+                tool_use_id: "toolu_123",
+                content: "Secret tool result",
+              },
+            ],
+          },
+        ],
+      };
+
+      const result = redactRequestBody(body) as { messages: Array<{ content: unknown[] }> };
+      const toolResultBlock = result.messages[0].content[0] as { content: string };
+
+      expect(toolResultBlock.content).toBe(REDACTED_MARKER);
+    });
+
+    test("should redact system prompt string", () => {
+      const body = {
+        system: "You are a helpful assistant with secret instructions",
+        messages: [{ role: "user", content: "Hello" }],
+      };
+
+      const result = redactRequestBody(body);
+
+      expect(result).toEqual({
+        system: REDACTED_MARKER,
+        messages: [{ role: "user", content: REDACTED_MARKER }],
+      });
+    });
+
+    test("should redact system prompt array", () => {
+      const body = {
+        system: [
+          { type: "text", text: "Secret system instruction 1" },
+          { type: "text", text: "Secret system instruction 2" },
+        ],
+        messages: [],
+      };
+
+      const result = redactRequestBody(body) as { system: unknown[] };
+
+      expect(result.system).toEqual([
+        { type: "text", text: REDACTED_MARKER },
+        { type: "text", text: REDACTED_MARKER },
+      ]);
+    });
+
+    test("should redact input array (Response API format)", () => {
+      const body = {
+        model: "claude-3-opus",
+        input: [
+          { role: "user", content: "Secret input content" },
+          { role: "assistant", content: "Secret response" },
+        ],
+      };
+
+      const result = redactRequestBody(body);
+
+      expect(result).toEqual({
+        model: "claude-3-opus",
+        input: [
+          { role: "user", content: REDACTED_MARKER },
+          { role: "assistant", content: REDACTED_MARKER },
+        ],
+      });
+    });
+
+    test("should preserve non-content fields", () => {
+      const body = {
+        model: "claude-3-opus",
+        max_tokens: 1024,
+        temperature: 0.7,
+        messages: [{ role: "user", content: "Secret" }],
+      };
+
+      const result = redactRequestBody(body) as Record<string, unknown>;
+
+      expect(result.model).toBe("claude-3-opus");
+      expect(result.max_tokens).toBe(1024);
+      expect(result.temperature).toBe(0.7);
+    });
+
+    test("should handle empty messages array", () => {
+      const body = { model: "test", messages: [] };
+
+      const result = redactRequestBody(body);
+
+      expect(result).toEqual({ model: "test", messages: [] });
+    });
+
+    test("should return non-object input as-is", () => {
+      expect(redactRequestBody(null)).toBe(null);
+      expect(redactRequestBody("string")).toBe("string");
+      expect(redactRequestBody(123)).toBe(123);
+      expect(redactRequestBody(undefined)).toBe(undefined);
+    });
+
+    test("should handle mixed content array", () => {
+      const body = {
+        messages: [
+          {
+            role: "user",
+            content: [
+              "plain string content",
+              { type: "text", text: "text block" },
+              { type: "image", source: { type: "url", url: "https://example.com/image.png" } },
+            ],
+          },
+        ],
+      };
+
+      const result = redactRequestBody(body) as { messages: Array<{ content: unknown[] }> };
+
+      expect(result.messages[0].content[0]).toBe(REDACTED_MARKER);
+      expect((result.messages[0].content[1] as { text: string }).text).toBe(REDACTED_MARKER);
+      // URL-based images don't have data to redact
+      expect(result.messages[0].content[2]).toEqual({
+        type: "image",
+        source: { type: "url", url: "https://example.com/image.png" },
+      });
+    });
+  });
+
+  describe("redactJsonString", () => {
+    test("should redact JSON string and return formatted JSON", () => {
+      const json = JSON.stringify({
+        messages: [{ role: "user", content: "Secret" }],
+      });
+
+      const result = redactJsonString(json);
+      const parsed = JSON.parse(result);
+
+      expect(parsed.messages[0].content).toBe(REDACTED_MARKER);
+    });
+
+    test("should return original string if JSON parsing fails", () => {
+      const invalidJson = "not valid json";
+
+      const result = redactJsonString(invalidJson);
+
+      expect(result).toBe(invalidJson);
+    });
+
+    test("should handle empty JSON object", () => {
+      const json = "{}";
+
+      const result = redactJsonString(json);
+
+      expect(result).toBe("{}");
+    });
+  });
+
+  describe("redactMessages", () => {
+    test("should redact messages array directly", () => {
+      const messages = [
+        { role: "user", content: "Hello secret" },
+        { role: "assistant", content: "Hi there" },
+      ];
+
+      const result = redactMessages(messages) as Array<{ content: string }>;
+
+      expect(result[0].content).toBe(REDACTED_MARKER);
+      expect(result[1].content).toBe(REDACTED_MARKER);
+    });
+
+    test("should return non-array input as-is", () => {
+      expect(redactMessages(null)).toBe(null);
+      expect(redactMessages("string")).toBe("string");
+      expect(redactMessages({})).toEqual({});
+    });
+
+    test("should handle messages with nested tool_result content array", () => {
+      const messages = [
+        {
+          role: "user",
+          content: [
+            {
+              type: "tool_result",
+              tool_use_id: "toolu_123",
+              content: [{ type: "text", text: "Tool output text" }],
+            },
+          ],
+        },
+      ];
+
+      const result = redactMessages(messages) as Array<{ content: unknown[] }>;
+      const toolResult = result[0].content[0] as { content: unknown[] };
+
+      expect((toolResult.content[0] as { text: string }).text).toBe(REDACTED_MARKER);
+    });
+  });
+});

+ 174 - 0
src/lib/utils/message-redaction.ts

@@ -0,0 +1,174 @@
+/**
+ * Message Content Redaction Utility
+ *
+ * Redacts message content in API request/response bodies to protect user privacy.
+ * Replaces messages[].content with [REDACTED] while preserving structure.
+ */
+
+const REDACTED_MARKER = "[REDACTED]";
+
+/**
+ * Check if a value is a plain object
+ */
+function isPlainObject(value: unknown): value is Record<string, unknown> {
+  return typeof value === "object" && value !== null && !Array.isArray(value);
+}
+
+/**
+ * Redact content field in a message block
+ */
+function redactMessageContent(message: Record<string, unknown>): Record<string, unknown> {
+  const result = { ...message };
+
+  // Redact string content
+  if (typeof result.content === "string") {
+    result.content = REDACTED_MARKER;
+    return result;
+  }
+
+  // Redact array content (content blocks)
+  if (Array.isArray(result.content)) {
+    result.content = result.content.map((block) => {
+      if (typeof block === "string") {
+        return REDACTED_MARKER;
+      }
+
+      if (isPlainObject(block)) {
+        const redactedBlock = { ...block };
+
+        // Redact text content in text blocks
+        if ("text" in redactedBlock && typeof redactedBlock.text === "string") {
+          redactedBlock.text = REDACTED_MARKER;
+        }
+
+        // Redact source data in image blocks
+        if ("source" in redactedBlock && isPlainObject(redactedBlock.source)) {
+          const source = redactedBlock.source as Record<string, unknown>;
+          if ("data" in source) {
+            redactedBlock.source = { ...source, data: REDACTED_MARKER };
+          }
+        }
+
+        // Redact input in tool_use blocks
+        if ("input" in redactedBlock) {
+          redactedBlock.input = REDACTED_MARKER;
+        }
+
+        // Redact content in tool_result blocks
+        if ("content" in redactedBlock) {
+          if (typeof redactedBlock.content === "string") {
+            redactedBlock.content = REDACTED_MARKER;
+          } else if (Array.isArray(redactedBlock.content)) {
+            redactedBlock.content = redactedBlock.content.map((item) => {
+              if (typeof item === "string") return REDACTED_MARKER;
+              if (isPlainObject(item) && "text" in item) {
+                return { ...item, text: REDACTED_MARKER };
+              }
+              return item;
+            });
+          }
+        }
+
+        return redactedBlock;
+      }
+
+      return block;
+    });
+  }
+
+  return result;
+}
+
+/**
+ * Redact messages array in request body
+ */
+function redactMessagesArray(messages: unknown[]): unknown[] {
+  return messages.map((msg) => {
+    if (!isPlainObject(msg)) return msg;
+    return redactMessageContent(msg);
+  });
+}
+
+/**
+ * Redact system prompt content
+ */
+function redactSystemPrompt(system: unknown): unknown {
+  if (typeof system === "string") {
+    return REDACTED_MARKER;
+  }
+
+  if (Array.isArray(system)) {
+    return system.map((block) => {
+      if (typeof block === "string") return REDACTED_MARKER;
+      if (isPlainObject(block) && "text" in block) {
+        return { ...block, text: REDACTED_MARKER };
+      }
+      return block;
+    });
+  }
+
+  return system;
+}
+
+/**
+ * Redact message content in a request body object
+ *
+ * @param body - The request body object (parsed JSON)
+ * @returns A new object with message content redacted
+ */
+export function redactRequestBody(body: unknown): unknown {
+  if (!isPlainObject(body)) {
+    return body;
+  }
+
+  const result = { ...body };
+
+  // Redact messages array
+  if ("messages" in result && Array.isArray(result.messages)) {
+    result.messages = redactMessagesArray(result.messages);
+  }
+
+  // Redact system prompt
+  if ("system" in result) {
+    result.system = redactSystemPrompt(result.system);
+  }
+
+  // Redact input array (Response API format)
+  if ("input" in result && Array.isArray(result.input)) {
+    result.input = redactMessagesArray(result.input);
+  }
+
+  return result;
+}
+
+/**
+ * Redact message content in a JSON string
+ *
+ * @param jsonString - The JSON string to redact
+ * @returns A new JSON string with message content redacted
+ */
+export function redactJsonString(jsonString: string): string {
+  try {
+    const parsed = JSON.parse(jsonString);
+    const redacted = redactRequestBody(parsed);
+    return JSON.stringify(redacted, null, 2);
+  } catch {
+    // If parsing fails, return original string
+    return jsonString;
+  }
+}
+
+/**
+ * Redact messages array for display
+ *
+ * @param messages - The messages array
+ * @returns A new array with content redacted
+ */
+export function redactMessages(messages: unknown): unknown {
+  if (!Array.isArray(messages)) {
+    return messages;
+  }
+  return redactMessagesArray(messages);
+}
+
+export { REDACTED_MARKER };

+ 97 - 9
tests/unit/error-details-dialog-warmup-ui.test.tsx

@@ -9,13 +9,100 @@ import { act } from "react";
 import { createRoot } from "react-dom/client";
 import { NextIntlClientProvider } from "next-intl";
 import { describe, expect, test, vi } from "vitest";
-import { ErrorDetailsDialog } from "@/app/[locale]/dashboard/logs/_components/error-details-dialog";
 
-// 测试环境不加载 next-intl/navigation -> next/navigation 的真实实现(避免 Next.js 运行时依赖)
+// Mock routing
 vi.mock("@/i18n/routing", () => ({
   Link: ({ children }: { children: ReactNode }) => children,
 }));
 
+// Mock Sheet to render content directly (not via portal)
+vi.mock("@/components/ui/sheet", () => {
+  type PropsWithChildren = { children?: ReactNode };
+
+  function Sheet({ children }: PropsWithChildren & { open?: boolean }) {
+    return <div data-slot="sheet-root">{children}</div>;
+  }
+
+  function SheetTrigger({ children }: PropsWithChildren) {
+    return <div data-slot="sheet-trigger">{children}</div>;
+  }
+
+  function SheetContent({ children, className }: PropsWithChildren & { className?: string }) {
+    return (
+      <div data-slot="sheet-content" className={className}>
+        {children}
+      </div>
+    );
+  }
+
+  function SheetHeader({ children }: PropsWithChildren) {
+    return <div data-slot="sheet-header">{children}</div>;
+  }
+
+  function SheetTitle({ children }: PropsWithChildren) {
+    return <div data-slot="sheet-title">{children}</div>;
+  }
+
+  function SheetDescription({ children }: PropsWithChildren) {
+    return <div data-slot="sheet-description">{children}</div>;
+  }
+
+  return {
+    Sheet,
+    SheetContent,
+    SheetDescription,
+    SheetHeader,
+    SheetTitle,
+    SheetTrigger,
+  };
+});
+
+// Mock Tabs to render all content for testing
+vi.mock("@/components/ui/tabs", () => {
+  type PropsWithChildren = { children?: ReactNode };
+
+  function Tabs({ children, className }: PropsWithChildren & { className?: string }) {
+    return (
+      <div data-slot="tabs-root" className={className}>
+        {children}
+      </div>
+    );
+  }
+
+  function TabsList({ children, className }: PropsWithChildren & { className?: string }) {
+    return (
+      <div data-slot="tabs-list" className={className}>
+        {children}
+      </div>
+    );
+  }
+
+  function TabsTrigger({ children, className }: PropsWithChildren & { className?: string }) {
+    return (
+      <div data-slot="tabs-trigger" className={className}>
+        {children}
+      </div>
+    );
+  }
+
+  function TabsContent({ children, className }: PropsWithChildren & { className?: string }) {
+    return (
+      <div data-slot="tabs-content" className={className}>
+        {children}
+      </div>
+    );
+  }
+
+  return {
+    Tabs,
+    TabsList,
+    TabsTrigger,
+    TabsContent,
+  };
+});
+
+import { ErrorDetailsDialog } from "@/app/[locale]/dashboard/logs/_components/error-details-dialog";
+
 const dashboardMessages = JSON.parse(
   fs.readFileSync(path.join(process.cwd(), "messages/en/dashboard.json"), "utf8")
 );
@@ -49,9 +136,9 @@ function renderWithIntl(node: ReactNode) {
   };
 }
 
-describe("ErrorDetailsDialog - warmup 跳过标注", () => {
-  test("blockedBy=warmup 时应展示 Skipped/Warmup Fast Response 提示,且不应显示 Blocking Information", () => {
-    const { unmount } = renderWithIntl(
+describe("ErrorDetailsDialog - warmup skip indicator", () => {
+  test("blockedBy=warmup should display Skipped/Warmup Fast Response and not show Blocking Information", () => {
+    const { container, unmount } = renderWithIntl(
       <ErrorDetailsDialog
         statusCode={200}
         errorMessage={null}
@@ -82,10 +169,11 @@ describe("ErrorDetailsDialog - warmup 跳过标注", () => {
       />
     );
 
-    // DialogContent 通常通过 Portal 渲染到 document.body
-    expect(document.body.textContent).toContain("Warmup Fast Response (CCH)");
-    expect(document.body.textContent).toContain("Skipped");
-    expect(document.body.textContent).not.toContain("Blocking Information");
+    // With mocked Sheet and Tabs, content is rendered in-place (not via Portal)
+    const textContent = container.textContent || "";
+    expect(textContent).toContain("Warmup Fast Response (CCH)");
+    expect(textContent).toContain("Skipped");
+    expect(textContent).not.toContain("Blocking Information");
 
     unmount();
   });