Skip to content

refactor(web): prevent query failures from forcing 500 redirects - #6762

Draft
seefs001 wants to merge 2 commits into
QuantumNous:mainfrom
seefs001:fix/react-query-500
Draft

refactor(web): prevent query failures from forcing 500 redirects#6762
seefs001 wants to merge 2 commits into
QuantumNous:mainfrom
seefs001:fix/react-query-500

Conversation

@seefs001

@seefs001 seefs001 commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

调整前端针对接口返回报错的行为

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

Summary by CodeRabbit

  • New Features

    • Added consistent error screens with retry actions across dashboards, tables, forms, settings, profile, wallet, pricing, and other pages.
    • Added dedicated messaging for rate-limited requests.
    • Added retry behavior that avoids retrying most client errors while retrying eligible transient failures.
  • Bug Fixes

    • Failed or invalid API responses now display meaningful errors instead of empty or misleading content.
    • Loading, submission, and pagination controls are safely disabled or hidden during errors.
    • Improved handling of incomplete metric and response data.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The web application now validates failed API responses, propagates errors through React Query and custom hooks, and renders retryable error states across tables, dashboards, pages, and data-entry drawers. Shared retry logic now replaces the global query-cache error handler.

Changes

Unified error handling

Layer / File(s) Summary
Shared error contracts and retry policy
web/src/components/data-table/layout/data-table-page.tsx, web/src/components/error-state.tsx, web/src/features/errors/*, web/src/lib/query-retry.ts, web/src/main.tsx
Shared components classify errors, display rate-limit messages, support table retries, and centralize query retry behavior.
Validated API response contracts
web/src/features/*/api.ts, web/src/features/performance-metrics/types.ts, web/src/lib/api.ts
API functions validate success flags and response shapes. Invalid responses now throw translated or server-provided errors.
Query-backed table error propagation
web/src/features/channels/components/channels-table.tsx, web/src/features/keys/components/api-keys-table.tsx, web/src/features/models/components/*-table.tsx, web/src/features/redemption-codes/components/redemptions-table.tsx, web/src/features/subscriptions/components/subscriptions-table.tsx, web/src/features/usage-logs/components/usage-logs-table.tsx, web/src/features/users/components/users-table.tsx
Table queries expose errors and refetch callbacks. DataTablePage renders errors only when no data is available.
Feature-level retryable error surfaces
web/src/features/about/index.tsx, web/src/features/dashboard/components/*, web/src/features/legal/legal-document.tsx, web/src/features/pricing/*, web/src/features/system-settings/*, web/src/features/wallet/index.tsx
Pages and panels render retryable error states when requests fail without usable data.
Detail query and form safeguards
web/src/features/channels/components/drawers/channel-mutate-drawer.tsx, web/src/features/models/components/drawers/model-mutate-drawer.tsx, web/src/features/users/components/users-mutate-drawer.tsx
Detail queries expose loading and error states. Forms remain hidden or disabled until required data is available.
Stateful fetch error handling
web/src/features/profile/hooks/use-profile.ts, web/src/features/profile/index.tsx, web/src/features/wallet/hooks/use-topup-info.ts
Custom hooks retain fetch errors, clear them after success, and expose retry state to their pages.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: calcium-ion

Poem

A rabbit checks each failing call,
Adds retry buttons for one and all.
Errors hop into view,
With translated text in queue,
And tables wait till data calls.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing query failures from forcing 500-page redirects.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/features/dashboard/components/overview/overview-dashboard.tsx (1)

481-496: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Render an error state for initial query failures.

Both components swallow rejected useQuery failures and show fallback UI as if data is absent.

  • web/src/features/dashboard/components/overview/overview-dashboard.tsx#L480: handle apiKeysQuery/modelsQuery.isError with retry controls; avoid leaving the model signal at Loading or using request-example defaults.
  • web/src/features/pricing/components/model-details.tsx#L185: handle metricsQuery.isError before deriving TPS/latency/success-rate metrics; avoid showing zero or unavailable values.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/dashboard/components/overview/overview-dashboard.tsx` around
lines 481 - 496, Handle initial query errors in
web/src/features/dashboard/components/overview/overview-dashboard.tsx:481-496 by
checking apiKeysQuery.isError and modelsQuery.isError before deriving fallback
UI, rendering an error state with retry controls, and preventing the model
signal from remaining Loading or using request-example defaults. In
web/src/features/pricing/components/model-details.tsx:185, check
metricsQuery.isError before deriving TPS, latency, or success-rate metrics and
render the corresponding error state with retry controls instead of zero or
unavailable values.
🧹 Nitpick comments (2)
web/src/features/users/components/users-mutate-drawer.tsx (1)

293-297: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use cn() for the conditional form class.

Replace the template literal with cn(sideDrawerFormClassName(), (drawerLoading || drawerError) && 'hidden').

As per coding guidelines: “动态类名使用 cn() 合并。”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/users/components/users-mutate-drawer.tsx` around lines 293 -
297, Update the form className in the Form submission block to use cn() for
combining sideDrawerFormClassName() with the conditional 'hidden' class when
drawerLoading or drawerError is true, replacing the current template literal.

Source: Coding guidelines

web/src/features/about/api.ts (1)

25-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit return types to the changed exported functions and hook.

These exports rely on inferred return types. Declare their return types to make their public contracts stable.

  • web/src/features/about/api.ts#L25-L30: declare the Promise<AboutResponse> return type.
  • web/src/features/system-settings/api.ts#L36-L41: declare the successful system-options response return type.
  • web/src/features/system-settings/auth/custom-oauth/hooks/use-custom-oauth-providers.ts#L24-L34: declare the React Query result type.

Run the repository Bun typecheck and lint scripts after adding the types.

As per coding guidelines, “参数和返回值应显式标注类型.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/about/api.ts` around lines 25 - 30, Declare explicit return
types for getAboutContent in web/src/features/about/api.ts lines 25-30 as
Promise<AboutResponse>; for the changed system-options API function in
web/src/features/system-settings/api.ts lines 36-41, use its successful response
type; and for the custom OAuth provider hook in
web/src/features/system-settings/auth/custom-oauth/hooks/use-custom-oauth-providers.ts
lines 24-34, declare the appropriate React Query result type. Run the repository
Bun typecheck and lint scripts afterward.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/features/channels/components/drawers/channel-mutate-drawer.tsx`:
- Line 865: Update channel-mutate-drawer.tsx:865 so isChannelDetailError is true
only when channelQuery.isError and channelQuery.data?.data is missing. Apply the
same cached-data guard to isModelDetailError in model-mutate-drawer.tsx:288-289.
In users-mutate-drawer.tsx:179-188, set drawerLoading and drawerError only when
the groups, permission catalog, or user-details query fails without usable
cached data; preserve editing and saving when cached data remains available.

In `@web/src/features/performance-metrics/api.ts`:
- Around line 30-45: Strengthen requirePerformanceMetricsResponse by validating
every required field of each groups item, including group, avg_ttft_ms, and
series, before casting to SuccessfulPerformanceMetricsData. Reject malformed
entries such as empty objects while preserving the existing failure error
behavior and valid-response path.

In `@web/src/features/profile/hooks/use-profile.ts`:
- Around line 54-58: The silent error path in fetchProfile currently only logs
failures, leaving refreshProfile and callers such as
updateProfile/updateSettings with stale profile data and no user-visible
recovery. Update fetchProfile and refreshProfile so silent-refresh failures are
stored and exposed through the existing profile error state, with an appropriate
retry or stale-data recovery path; otherwise make the silent-refresh contract
explicit and ensure callers do not report success when refresh fails.

In `@web/src/features/rankings/api.ts`:
- Around line 42-45: Validate all required fields before narrowing API
responses: in web/src/features/rankings/api.ts lines 42-45, update the guard
around the rankings response so RankingsSnapshot requires models, vendors,
top_movers, top_droppers, models_history, and vendor_share_history; in
web/src/features/pricing/api.ts lines 32-39, extend the existing guard to
require group_ratio, usable_group, supported_endpoint, and auto_groups in
addition to data and vendors, while returning the unchanged response when valid.

In `@web/src/features/system-settings/components/settings-page.tsx`:
- Around line 142-154: Only render the settings-page ErrorState when
optionsQuery.isError and data is undefined, preserving cached data during failed
refetches; update the corresponding condition in
web/src/features/system-settings/components/settings-page.tsx lines 142-154.
Apply the same guard to the custom OAuth ErrorState in
web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx
lines 78-88, requiring providersQuery.isError and providersQuery.data ===
undefined.

In `@web/src/features/users/api.ts`:
- Line 178: Update the API error handling around the fallback in the relevant
users API validator to replace the hardcoded “Request failed” text with the
existing i18next translation function t('Request failed'), preserving the
server-provided res.data?.message when available.

In `@web/src/features/users/components/users-mutate-drawer.tsx`:
- Around line 117-129: Update the groups query in the users mutate drawer to use
a unique, consistently scoped array query key that identifies this user-scoped
string-list query rather than the shared ['groups'] key. Keep the existing
getGroups response validation and groupsQuery.data fallback unchanged, and
ensure the key does not collide with other groups queries.

In `@web/src/features/wallet/hooks/use-topup-info.ts`:
- Line 179: Update the error handling in useTopupInfo so the fallback for
response.message uses the project i18n instance instead of the hard-coded
“Request failed” text, and add the translation key to locale resources if
absent. Preserve response.message when provided.

---

Outside diff comments:
In `@web/src/features/dashboard/components/overview/overview-dashboard.tsx`:
- Around line 481-496: Handle initial query errors in
web/src/features/dashboard/components/overview/overview-dashboard.tsx:481-496 by
checking apiKeysQuery.isError and modelsQuery.isError before deriving fallback
UI, rendering an error state with retry controls, and preventing the model
signal from remaining Loading or using request-example defaults. In
web/src/features/pricing/components/model-details.tsx:185, check
metricsQuery.isError before deriving TPS, latency, or success-rate metrics and
render the corresponding error state with retry controls instead of zero or
unavailable values.

---

Nitpick comments:
In `@web/src/features/about/api.ts`:
- Around line 25-30: Declare explicit return types for getAboutContent in
web/src/features/about/api.ts lines 25-30 as Promise<AboutResponse>; for the
changed system-options API function in web/src/features/system-settings/api.ts
lines 36-41, use its successful response type; and for the custom OAuth provider
hook in
web/src/features/system-settings/auth/custom-oauth/hooks/use-custom-oauth-providers.ts
lines 24-34, declare the appropriate React Query result type. Run the repository
Bun typecheck and lint scripts afterward.

In `@web/src/features/users/components/users-mutate-drawer.tsx`:
- Around line 293-297: Update the form className in the Form submission block to
use cn() for combining sideDrawerFormClassName() with the conditional 'hidden'
class when drawerLoading or drawerError is true, replacing the current template
literal.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 34cec0d1-a20d-4064-b363-865267e631d3

📥 Commits

Reviewing files that changed from the base of the PR and between 9c97e78 and 63dfdfd.

📒 Files selected for processing (43)
  • web/src/components/data-table/layout/data-table-page.tsx
  • web/src/components/error-state.tsx
  • web/src/features/about/api.ts
  • web/src/features/about/index.tsx
  • web/src/features/channels/components/channels-table.tsx
  • web/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/src/features/dashboard/components/models/performance-overview.tsx
  • web/src/features/dashboard/components/overview/overview-dashboard.tsx
  • web/src/features/dashboard/components/overview/performance-health-panel.tsx
  • web/src/features/dashboard/components/users/user-charts.tsx
  • web/src/features/errors/general-error-status.ts
  • web/src/features/errors/general-error.tsx
  • web/src/features/keys/components/api-keys-table.tsx
  • web/src/features/legal/legal-document.tsx
  • web/src/features/models/components/deployments-table.tsx
  • web/src/features/models/components/drawers/model-mutate-drawer.tsx
  • web/src/features/models/components/models-table.tsx
  • web/src/features/performance-metrics/api.ts
  • web/src/features/performance-metrics/types.ts
  • web/src/features/playground/api.ts
  • web/src/features/pricing/api.ts
  • web/src/features/pricing/components/model-details-performance.tsx
  • web/src/features/pricing/components/model-details.tsx
  • web/src/features/pricing/index.tsx
  • web/src/features/profile/hooks/use-profile.ts
  • web/src/features/profile/index.tsx
  • web/src/features/rankings/api.ts
  • web/src/features/redemption-codes/components/redemptions-table.tsx
  • web/src/features/subscriptions/components/subscriptions-table.tsx
  • web/src/features/system-settings/api.ts
  • web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx
  • web/src/features/system-settings/auth/custom-oauth/hooks/use-custom-oauth-providers.ts
  • web/src/features/system-settings/components/settings-page.tsx
  • web/src/features/usage-logs/components/common-logs-stats.tsx
  • web/src/features/usage-logs/components/usage-logs-table.tsx
  • web/src/features/users/api.ts
  • web/src/features/users/components/users-mutate-drawer.tsx
  • web/src/features/users/components/users-table.tsx
  • web/src/features/wallet/hooks/use-topup-info.ts
  • web/src/features/wallet/index.tsx
  • web/src/lib/api.ts
  • web/src/lib/query-retry.ts
  • web/src/main.tsx

const isBatchMode =
multiKeyMode === 'batch' || multiKeyMode === 'multi_to_single'
const isChannelDetailLoading = isEditing && isChannelLoading
const isChannelDetailError = isEditing && channelQuery.isError

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

According to official TanStack Query v5 documentation, when a background refetch fails after cached query data exists, are dataretained andisError set to true?

💡 Result:

According to the official TanStack Query v5 documentation, when a background refetch fails and cached data already exists, the library retains the previous successful data while also updating the state to reflect the error [1]. Specifically, in this scenario: - Data retention: The previously fetched data remains available to the application [1]. - Error state: The query will enter an error state, meaning isError will be set to true and the error property will be populated with the failure information [2][3][1]. - Refetch-specific state: To help distinguish this from a "hard" error (where no data was ever successfully fetched), the library provides the isRefetchError boolean, which will be true when a background refetch fails [2][4]. This behavior is designed to allow developers to continue displaying the existing data to the user while simultaneously acknowledging the background update failure [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  "web/src/features/channels/components/drawers/channel-mutate-drawer.tsx" \
  "web/src/features/models/components/drawers/model-mutate-drawer.tsx" \
  "web/src/features/users/components/users-mutate-drawer.tsx"
do
  echo "===== $f ====="
  wc -l "$f"
  ast-grep outline "$f" --view concise | sed -n '1,220p'
  echo
done

Repository: QuantumNous/new-api

Length of output: 467


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "===== relevant channel mutate drawer excerpts ====="
sed -n '830,885p' web/src/features/channels/components/drawers/channel-mutate-drawer.tsx
echo
sed -n '1,120p' web/src/features/channels/components/drawers/channel-mutate-drawer.tsx

echo "===== relevant model mutate drawer excerpts ====="
sed -n '260,310p' web/src/features/models/components/drawers/model-mutate-drawer.tsx
echo
sed -n '1,110p' web/src/features/models/components/drawers/model-mutate-drawer.tsx

echo "===== relevant users mutate drawer excerpts ====="
sed -n '155,205p' web/src/features/users/components/users-mutate-drawer.tsx
echo
sed -n '1,120p' web/src/features/users/components/users-mutate-drawer.tsx

Repository: QuantumNous/new-api

Length of output: 15941


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  "web/src/features/channels/components/drawers/channel-mutate-drawer.tsx" \
  "web/src/features/models/components/drawers/model-mutate-drawer.tsx" \
  "web/src/features/users/components/users-mutate-drawer.tsx"
do
  echo "===== usages in $f ====="
  python3 - <<'PY' "$f"
import re, sys
p = sys.argv[1]
text = open(p, encoding='utf-8').read()
for name in ['isChannelDetailError','isModelDetailError','drawerError','drawerLoading']:
    if name not in text:
        continue
    print(f'--- {name} ---')
    for m in re.finditer(re.escape(f'{{{name} |') | re.escape(f'{{ {name}') | re.escape(f',{name}') | re.escape(f'{name}:'), text):
        i = text.rfind('\n', 0, m.start())
        print(f'{i+1-1}:', text[i+1:i+1000].split('\n', 1)[0])
        print(f'{i+1+1}:', text[i+1:i+1000].split('\n', 1, maxsplit=1)[1].split('\n', 1)[0])
        break
PY
  echo
done

Repository: QuantumNous/new-api

Length of output: 414


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "===== channel drawer query refs and key definitions ====="
rg -n "channelQuery|isEditing|isChannelDetailError|isChannelDetailLoading|channel.*Query|queryKeys" web/src/features/channels/components/drawers/channel-mutate-drawer.tsx
echo

echo "===== model drawer query refs ====="
rg -n "modelQuery|isEditing|isModelDetailError|isModelDetailLoading|model.*Query|queryKeys" web/src/features/models/components/drawers/model-mutate-drawer.tsx
echo

echo "===== users drawer query refs ====="
rg -n "userDetailQuery|groupsQuery|permissionCatalogQuery|drawerError|drawerLoading|isUpdate" web/src/features/users/components/users-mutate-drawer.tsx
echo

echo "===== error/loading state components ====="
rg -n "ErrorState|LoadingState" web/src/features/channels/components/drawers/channel-mutate-drawer.tsx web/src/features/models/components/drawers/model-mutate-drawer.tsx web/src/features/users/components/users-mutate-drawer.tsx

Repository: QuantumNous/new-api

Length of output: 8455


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "===== channel drawer surrounding fetch/conditionals ====="
sed -n '630,690p' web/src/features/channels/components/drawers/channel-mutate-drawer.tsx
echo
sed -n '1230,1285p' web/src/features/channels/components/drawers/channel-mutate-drawer.tsx
echo

echo "===== model drawer surrounding fetch/conditionals ====="
sed -n '225,290p' web/src/features/models/components/drawers/model-mutate-drawer.tsx
echo
sed -n '390,460p' web/src/features/models/components/drawers/model-mutate-drawer.tsx
echo
sed -n '738,765p' web/src/features/models/components/drawers/model-mutate-drawer.tsx

echo "===== users drawer surrounding fetch/conditionals ====="
sed -n '105,195p' web/src/features/users/components/users-mutate-drawer.tsx
echo
sed -n '265,300p' web/src/features/users/components/users-mutate-drawer.tsx
echo
sed -n '628,645p' web/src/features/users/components/users-mutate-drawer.tsx

echo "===== error state behavior from source ====="
sed -n '1,140p' web/src/components/error-state.tsx
echo
sed -n '1,80p' web/src/components/loading-state.tsx

Repository: QuantumNous/new-api

Length of output: 19675


Keep cached detail data editable after a refetch error.

TanStack Query can keep previous data on a failed background refetch while isError becomes true; these flags hide usable forms and disable saving. Only treat a query error as blocking when the required data is missing.

  • web/src/features/channels/components/drawers/channel-mutate-drawer.tsx#L865: require missing channelQuery.data?.data before setting isChannelDetailError.
  • web/src featuring/models/components/drawers/model-mutate-drawer.tsx#L288/289: require missing modelQuery.data?.data before setting isModelDetailError.
  • web/src/features/users/components/users-mutate-drawer.tsx#L179/183/186: only set drawerLoading / drawerError when groups, permission catalog, or user details fail and have no usable cached data.
📍 Affects 3 files
  • web/src/features/channels/components/drawers/channel-mutate-drawer.tsx#L865-L865 (this comment)
  • web/src/features/models/components/drawers/model-mutate-drawer.tsx#L288-L289
  • web/src/features/users/components/users-mutate-drawer.tsx#L179-L188
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/channels/components/drawers/channel-mutate-drawer.tsx` at
line 865, Update channel-mutate-drawer.tsx:865 so isChannelDetailError is true
only when channelQuery.isError and channelQuery.data?.data is missing. Apply the
same cached-data guard to isModelDetailError in model-mutate-drawer.tsx:288-289.
In users-mutate-drawer.tsx:179-188, set drawerLoading and drawerError only when
the groups, permission catalog, or user-details query fails without usable
cached data; preserve editing and saving when cached data remains available.

Comment on lines +30 to +45
export function requirePerformanceSummaryResponse(
response: PerfSummaryAllData
): SuccessfulPerfSummaryData {
if (!response.success || !Array.isArray(response.data?.models)) {
throw new Error(response.message || t('Request failed') || 'Request failed')
}
return response as SuccessfulPerfSummaryData
}

export function requirePerformanceMetricsResponse(
response: PerformanceMetricsData
): SuccessfulPerformanceMetricsData {
if (!response.success || !Array.isArray(response.data?.groups)) {
throw new Error(response.message || t('Request failed') || 'Request failed')
}
return response as SuccessfulPerformanceMetricsData

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline web/src/features/performance-metrics/api.ts --items all
rg -n -C 3 'Array\.isArray|as Successful|group\.series|avg_ttft_ms|success_rate' \
  web/src/features/performance-metrics \
  web/src/features/pricing/components/model-details-performance.tsx

Repository: QuantumNous/new-api

Length of output: 10778


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- performance-metrics files ---\n'
fd -a 'types\.ts|api\.ts' web/src/features/performance-metrics

printf '\n--- api.ts ---\n'
cat -n web/src/features/performance-metrics/api.ts

printf '\n--- types.ts ---\n'
cat -n web/src/features/performance-metrics/types.ts

printf '\n--- model-details-performance relevant code ---\n'
sed -n '1,260p' web/src/features/pricing/components/model-details-performance.tsx | cat -n

printf '\n--- static probe: nested required-field usage vs assertion and array validation ---\n'
python3 - <<'PY'
from pathlib import Path
api = Path('web/src/features/performance-metrics/api.ts').read_text()
types = Path('web/src/features/performance-metrics/types.ts').read_text()
component = Path('web/src/features/pricing/components/model-details-performance.tsx').read_text()

checks = {
    "api_asserts_successful_metrics": "return response as SuccessfulPerformanceMetricsData" in api,
    "api_asserts_successful_summary": "return response as SuccessfulPerfSummaryData" in api,
    "api_validates_groups_is_array": '!Array.isArray(response.data?.groups)' in api,
    "api_validates_models_is_array": '!Array.isArray(response.data?.models)' in api,
    "component_iterates_group_series": ".group.series" in component,
    "component_reads_series_point_fields": all(x in component for x in ["point.ts", "point.avg_ttft_ms", "point.success_rate"]),
    "component_reads_group_fields": all(x in component for x in ["group.group", "group.avg_ttft_ms", "group.success_rate"]),
    "type_group_contains_required_group_fields": all(x in types for x in ["group: string", "avg_ttft_ms: number", "avg_latency_ms: number", "success_rate: number", "avg_tps: number"]),
    "type_series_point_contains_required_fields": all(x in types for x in ["ts: number", "avg_ttft_ms: number", "avg_latency_ms: number", "success_rate: number", "avg_tps: number"]),
}
for name, ok in checks.items():
    print(f"{name}={ok}")
PY

Repository: QuantumNous/new-api

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- performance-metrics files ---'
fd 'types\.ts|api\.ts' web/src/features/performance-metrics || true

printf '%s\n' ''
printf '%s\n' '--- api.ts ---'
cat -n web/src/features/performance-metrics/api.ts

printf '%s\n' ''
printf '%s\n' '--- types.ts ---'
cat -n web/src/features/performance-metrics/types.ts

printf '%s\n' ''
printf '%s\n' '--- model-details-performance relevant code ---'
sed -n '1,260p' web/src/features/pricing/components/model-details-performance.tsx | cat -n

printf '%s\n' ''
printf '%s\n' '--- static probe: nested required-field usage vs assertion and array validation ---'
python3 - <<'PY'
from pathlib import Path
api = Path('web/src/features/performance-metrics/api.ts').read_text()
types = Path('web/src/features/performance-metrics/types.ts').read_text()
component = Path('web/src/features/pricing/components/model-details-performance.tsx').read_text()

checks = {
    "api_asserts_successful_metrics": "return response as SuccessfulPerformanceMetricsData" in api,
    "api_asserts_successful_summary": "return response as SuccessfulPerfSummaryData" in api,
    "api_validates_groups_is_array": '!Array.isArray(response.data?.groups)' in api,
    "api_validates_models_is_array": '!Array.isArray(response.data?.models)' in api,
    "component_iterates_group_series": ".group.series" in component,
    "component_reads_series_point_fields": all(x in component for x in ["point.ts", "point.avg_ttft_ms", "point.success_rate"]),
    "component_reads_group_fields": all(x in component for x in ["group.group", "group.avg_ttft_ms", "group.success_rate"]),
    "type_group_contains_required_group_fields": all(x in types for x in ["group: string", "avg_ttft_ms: number", "avg_latency_ms: number", "success_rate: number", "avg_tps: number"]),
    "type_series_point_contains_required_fields": all(x in types for x in ["ts: number", "avg_ttft_ms: number", "avg_latency_ms: number", "success_rate: number", "avg_tps: number"]),
}
for name, ok in checks.items():
    print(f"{name}={ok}")
PY

Repository: QuantumNous/new-api

Length of output: 15809


Validate response payloads before narrowing their types.

requirePerformanceMetricsResponse() only checks response.data.groups is an array, then asserts SuccessfulPerformanceMetricsData. A payload such as { success: true, data: { groups: [{}] } } bypasses the guard, while ModelDetailsPerformance() reads group.group, group.avg_ttft_ms, and group.series fields. Validate each required field and nested item shape before the type assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/performance-metrics/api.ts` around lines 30 - 45, Strengthen
requirePerformanceMetricsResponse by validating every required field of each
groups item, including group, avg_ttft_ms, and series, before casting to
SuccessfulPerformanceMetricsData. Reject malformed entries such as empty objects
while preserving the existing failure error behavior and valid-response path.

Comment on lines +54 to +58
} catch (fetchError) {
// eslint-disable-next-line no-console
console.error('Failed to fetch profile:', error)
console.error('Failed to fetch profile:', fetchError)
if (!silent) {
toast.error(i18next.t('Failed to load profile'))
setError(fetchError)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'use-profile\.ts$|profile|Profile' web/src 2>/dev/null | sed 's#^\./##' | head -200

echo
echo "== use-profile outline =="
ast-grep outline web/src/features/profile/hooks/use-profile.ts --view expanded || true

echo
echo "== use-profile relevant lines =="
wc -l web/src/features/profile/hooks/use-profile.ts
sed -n '1,220p' web/src/features/profile/hooks/use-profile.ts

echo
echo "== profile feature files outline snippets =="
for f in $(fd -e ts -e tsx '.*' web/src/features/profile | head -100); do
  echo "--- $f"
  ast-grep outline "$f" --view compact || true
done

echo
echo "== search Profile render and ErrorState usage =="
rg -n "ErrorState|useProfile|isLoading|failed|retry|refreshProfile|updateProfile|updateSettings" web/src/features/profile web/src -g '*.ts' -g '*.tsx' | head -300

Repository: QuantumNous/new-api

Length of output: 45428


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== web/src/features/profile/index.tsx =="
sed -n '1,140p' web/src/features/profile/index.tsx

echo
echo "== web/src/features/profile/routes =="
fd -e ts -e tsx '^.*$' web/src/routes/_authenticated/profile -x sh -c 'echo "--- $1"; sed -n "1,160p" "$1"' sh {}

echo
echo "== error-state behavior =="
sed -n '1,120p' web/src/components/error-state.tsx

Repository: QuantumNous/new-api

Length of output: 7943


Expose silent profile refresh failures to the user.

refreshProfile() calls fetchProfile(true), so errors are only logged and do not update error. The Profile route only renders ErrorState when !loading && error && !profile, so a failed silent refresh after an initial load is invisible and updateProfile / updateSettings can report success while still showing the stale cached profile. Store silent-refresh failures and expose a retry or stale-data recovery path, or make the silent-refresh API contract explicit if that behavior is intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/profile/hooks/use-profile.ts` around lines 54 - 58, The
silent error path in fetchProfile currently only logs failures, leaving
refreshProfile and callers such as updateProfile/updateSettings with stale
profile data and no user-visible recovery. Update fetchProfile and
refreshProfile so silent-refresh failures are stored and exposed through the
existing profile error state, with an appropriate retry or stale-data recovery
path; otherwise make the silent-refresh contract explicit and ensure callers do
not report success when refresh fails.

Comment on lines +42 to +45
if (!res.data.success || !res.data.data) {
throw new Error(res.data.message || t('Request failed') || 'Request failed')
}
return res.data as SuccessfulRankingsResponse

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline web/src/features/rankings --items all
rg -n -C 4 '\b(getRankings|models|vendors|top_movers|top_droppers|models_history|vendor_share_history)\b' web/src/features/rankings

ast-grep outline web/src/features/pricing --items all
rg -n -C 4 '\b(getPricing|group_ratio|usable_group|supported_endpoint|auto_groups)\b' web/src/features/pricing

Repository: QuantumNous/new-api

Length of output: 35455


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant API guard implementations and the exact type definitions used by consumers.
sed -n '1,80p' web/src/features/rankings/api.ts
printf '\n--- rankings/types.ts ---\n'
sed -n '1,150p' web/src/features/rankings/types.ts
printf '\n--- pricing/api.ts ---\n'
sed -n '1,80p' web/src/features/pricing/api.ts
printf '\n--- pricing/types.ts ---\n'
sed -n '1,130p' web/src/features/pricing/types.ts

# Read-only behavioral probe: show payloads that pass the current guards but are not full SuccessfulRankingsResponse/PricingData shapes.
node - <<'JS'
const payloadTypes = [
  {
    name: 'rankings-incomplete-empty',
    passesGetRankingsGuard: (data) => data.success && data.data && data.data.success && Array.isArray(data.data.data.models) && Array.isArray(data.data.data.vendors),
    data: { success: true, data: { success: true, models: [], vendors: [] } }
  },
  {
    name: 'rankings-only-array-fields',
    passesGetRankingsGuard: (data) => data.success && data.data && data.data.success && Array.isArray(data.data.data.models) && Array.isArray(data.data.data.vendors),
    data: { success: true, data: { success: true, models: [], vendors: [], top_movers: [] } }
  },
  {
    name: 'pricing-incomplete-object',
    passesGetPricingGuard: (data) => data.success && Array.isArray(data.data) && Array.isArray(data.vendors),
    data: { success: true, data: [], vendors: [] }
  }
]

for (const p of payloadTypes) {
  console.log(`${p.name}: current-guard=${p.passesGetRankingsGuard ? p.passesGetRankingsGuard(p.data) : 'n/a'} object=${typeof p.data}`)
}
JS

Repository: QuantumNous/new-api

Length of output: 11334


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read-only behavioral probe: payloads that satisfy the current guards but do not satisfy the declared response contracts.
node - <<'JS'
function passesCurrentGetRankingsGuard(data) {
  return data.success && data.data
}
function passesCurrentGetPricingGuard(data) {
  return data.success && Array.isArray(data.data) && Array.isArray(data.vendors)
}

const incompleteRankingsSnapshot = {
  success: true,
  data: { success: true, models: [], vendors: [] },
}
const incompleteSuccessArray = { success: true }
const incompleteRankings = {
  success: true,
  data: { success: true, models: [], vendors: [], top_movers: [], top_droppers: [], models_history: { buckets: 0 }, vendor_share_history: { buckets: 0 } },
}
const insufficientPricing = { success: true, data: [], vendors: [] }

console.log(JSON.stringify({
  incompleteRankingsSnapshotPassesCurrentRankingsGuard: passesCurrentGetRankingsGuard(incompleteRankingsSnapshot),
  incompleteRankingsSnapshotIsRankingsSnapshot: JSON.stringify(incompleteRankingsSnapshot.data) === JSON.stringify({ success: true, models: [], vendors: [] }),
  incompleteSuccessArrayPassesCurrentRankingsGuard: passesCurrentGetRankingsGuard(incompleteSuccessArray),
  incompleteSuccessArrayIsRecordOrArray: (typeof incompleteSuccessArray === 'object' && incompleteSuccessArray !== null && !Array.isArray(incompleteSuccessArray)) || Array.isArray(incompleteSuccessArray),
  incompleteRankingsPassesCurrentRankingsGuard: passesCurrentGetRankingsGuard(incompleteRankings),
  incompleteRankingsMissingHistorySeriesValues: incompleteRankings.data.models_history.points === undefined || incompleteRankings.data.vendor_share_history.points === undefined,
  insufficientPricingPassesCurrentPricingGuard: passesCurrentGetPricingGuard(insufficientPricing),
  insufficientPricingMissingRequiredRecordsAndArray: insufficientPricing.group_ratio === undefined || insufficientPricing.usable_group === undefined || insufficientPricing.supported_endpoint === undefined || insufficientPricing.auto_groups === undefined,
  pass: passesCurrentGetRankingsGuard(incompleteRankingsSnapshot) && passesCurrentGetPricingGuard(insufficientPricing),
}, null, 2))
JS

Repository: QuantumNous/new-api

Length of output: 842


Validate every required response field before assigning the narrow return type.

The current guards let incomplete payloads pass.

  • web/src/features/rankings/api.ts#L42-L45: res.data.data is narrowed to SuccessfulRankingsResponse, but the guard only checks existence. Reject RankingsSnapshot when required fields such as models, vendors, top_movers, top_droppers, models_history, or vendor_share_history are missing before returning.
  • web/src/features/pricing/api.ts#L32-L39: the guard checks data and vendors arrays, but PricingData also requires group_ratio, usable_group, supported_endpoint, and auto_groups. Check those fields before returning unchanged res.data.
📍 Affects 2 files
  • web/src/features/rankings/api.ts#L42-L45 (this comment)
  • web/src/features/pricing/api.ts#L32-L39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/rankings/api.ts` around lines 42 - 45, Validate all required
fields before narrowing API responses: in web/src/features/rankings/api.ts lines
42-45, update the guard around the rankings response so RankingsSnapshot
requires models, vendors, top_movers, top_droppers, models_history, and
vendor_share_history; in web/src/features/pricing/api.ts lines 32-39, extend the
existing guard to require group_ratio, usable_group, supported_endpoint, and
auto_groups in addition to data and vendors, while returning the unchanged
response when valid.

Comment on lines +142 to +154
if (optionsQuery.isError) {
return (
<SettingsPageFrame title={t(sectionMeta.titleKey)}>
<ErrorState
error={optionsQuery.error}
title={t('Failed to load')}
description={t('Please try again later.')}
onRetry={() => void optionsQuery.refetch()}
className='min-h-0 flex-1'
/>
</SettingsPageFrame>
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 \
  'optionsQuery\.isError|providersQuery\.isError|data === undefined' \
  web/src/features/system-settings/components/settings-page.tsx \
  web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx

fd -a package.json web -d 2 -x sh -c \
  'printf "\n--- %s ---\n" "$1"; rg -n "`@tanstack/react-query`" "$1"' sh {}

Repository: QuantumNous/new-api

Length of output: 2003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- settings-page query data and error handling ---'
sed -n '1,230p' web/src/features/system-settings/components/settings-page.tsx | nl -ba | sed -n '1,230p'

printf '%s\n' '--- custom-oauth query data and error handling ---'
sed -n '1,140p' web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx | nl -ba | sed -n '1,140p'

printf '%s\n' '--- related error handling in same files ---'
rg -n -C 3 'status === '\''success'\''|useQuery|data|isError|useSuspenseQuery|ErrorState' \
  web/src/features/system-settings/components/settings-page.tsx \
  web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx

Repository: QuantumNous/new-api

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- settings-page query data and error handling ---'
sed -n '1,230p' web/src/features/system-settings/components/settings-page.tsx

printf '%s\n' '--- custom-oauth query data and error handling ---'
sed -n '1,140p' web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx

printf '%s\n' '--- related error handling in same files ---'
rg -n -C 3 'status === '\''success'\''|useQuery|data|isError|useSuspenseQuery|ErrorState' \
  web/src/features/system-settings/components/settings-page.tsx \
  web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx

Repository: QuantumNous/new-api

Length of output: 16981


🌐 Web query:

TanStack Query v5 preserves previous data on background refetch error placeholderData isError

💡 Result:

In TanStack Query v5, placeholderData is designed to provide data only while a query is in a pending state (i.e., while it is fetching) [1][2][3]. When using placeholderData—including when using the identity function (previousData) => previousData to replicate the former keepPreviousData behavior—the placeholder data is removed as soon as the query finishes, regardless of whether it finishes successfully or with an error [4][5][3]. If the background refetch fails, the query transitions to an error state, the placeholder data is discarded, and the UI will reflect the error state (typically resulting in data becoming undefined) [4][5][6][3]. This is an intentional design choice to maintain the distinction between "real" data, which is persisted to the cache, and "placeholder" data, which is not [1][5][3]. To preserve data when a background refetch encounters an error, consider these alternatives: 1. Use initialData: Unlike placeholderData, initialData is persisted to the cache [1][3]. If you provide initialData (e.g., from the cache of another query), that data will remain available even if a background refetch fails [3]. 2. Standard Cache Behavior: TanStack Query's default behavior for stale-while-revalidate is to keep existing data in the cache until a new successful fetch replaces it [7]. If you are not changing query keys, the existing data remains available in the data property even during a failed background refetch [7]. The need for placeholderData (or the former keepPreviousData) typically arises specifically when the queryKey changes, causing the observer to treat the new key as a fresh start [8][7]. If your goal is to show an error alongside old data during a key transition, TanStack Query v5 does not provide a built-in configuration to maintain the placeholder during the error state [3]. The library assumes that once the transition to the new key is complete, the placeholder is no longer relevant [3].

Citations:


🌐 Web query:

TanStack Query v5 useQuery isError data undefined stale background refetch cache

💡 Result:

In TanStack Query v5, the behavior of useQuery regarding data, errors, and cache status is governed by specific state transitions [1]. Data and Undefined State The data property defaults to undefined [2][3]. Because TanStack Query caches data, it will persist the last successfully resolved data even if a subsequent background refetch fails [2][3]. If no data has been successfully fetched yet and the query encounters an error, the data remains undefined [2][3]. In TypeScript, the data property is typed as TData | undefined, which necessitates handling the undefined case in your components [4][5]. isError and Cache Behavior When a query enters an error state (isError is true), the error property will contain the error object, and the data property will hold the most recent successfully fetched data (if any) [2][1]. TanStack Query does not automatically clear the cache when an error occurs; instead, it preserves the last known good data to allow for "stale-while-revalidate" patterns, enabling you to display stale data while showing an error indicator [2][3]. Background Refetch and Staleness By default, queries are considered stale immediately [6]. When a query is stale, TanStack Query triggers background refetches under certain conditions, such as component mounting, window refocus, or network reconnection [6]. - If a refetch is triggered in the background and fails, the query transitions to the isError state, but the previous (stale) data remains available in the data property [2][1]. - To control these refetches, you can adjust the staleTime option [6]. A higher staleTime keeps the query fresh for longer, preventing background refetches and keeping the UI consistent with the cache without unnecessary network activity [6]. Key Considerations If you encounter a scenario where a query seems stuck in an error state and refuses to refetch, ensure that the component has not suppressed retries or triggered an error boundary that requires manual reset [7][8]. The retryOnMount option (default: true) controls whether a query should attempt to refetch when a component mounts if it is currently in an error state [8]. If you wish to prevent background refetches of stale data, setting a larger staleTime or configuring refetchOnMount/refetchOnWindowFocus to false are the recommended approaches [6][9].

Citations:


Keep cached query data visible on an initial fetch failure.

TanStack Query preserves the last successful data when a stale background refetch fails. These branches should only render ErrorState when no cache data exists.

  • web/src/features/system-settings/components/settings-page.tsx: render ErrorState only when optionsQuery.isError && data === undefined.
  • web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx: render ErrorState only when providersQuery.isError && providersQuery.data === undefined.
📍 Affects 2 files
  • web/src/features/system-settings/components/settings-page.tsx#L142-L154 (this comment)
  • web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx#L78-L88
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/system-settings/components/settings-page.tsx` around lines
142 - 154, Only render the settings-page ErrorState when optionsQuery.isError
and data is undefined, preserving cached data during failed refetches; update
the corresponding condition in
web/src/features/system-settings/components/settings-page.tsx lines 142-154.
Apply the same guard to the custom OAuth ErrorState in
web/src/features/system-settings/auth/custom-oauth/custom-oauth-section.tsx
lines 78-88, requiring providersQuery.isError and providersQuery.data ===
undefined.

!Array.isArray(resources) ||
!Array.isArray(roles)
) {
throw new Error(res.data?.message || 'Request failed')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the fallback error message.

Line 178 hardcodes Request failed. Use t('Request failed') so this fallback follows the same localization contract as the other API validators.

As per coding guidelines, “Frontend user-facing text must use i18next/react-i18next.”

Proposed fix
-    throw new Error(res.data?.message || 'Request failed')
+    throw new Error(res.data?.message || t('Request failed'))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/users/api.ts` at line 178, Update the API error handling
around the fallback in the relevant users API validator to replace the hardcoded
“Request failed” text with the existing i18next translation function t('Request
failed'), preserving the server-provided res.data?.message when available.

Source: Coding guidelines

Comment on lines +117 to +129
const groupsQuery = useQuery({
queryKey: ['groups'],
queryFn: getGroups,
queryFn: async () => {
const result = await getGroups()
if (!result.success || !Array.isArray(result.data)) {
throw new Error(result.message || t(ERROR_MESSAGES.LOAD_FAILED))
}
return result.data
},
staleTime: 5 * 60 * 1000,
})

const groups = groupsData?.data || []
const groups = groupsQuery.data ?? []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 "queryKey:\s*\['groups'\]|groupsData\?\.data|const groups = groupsQuery\.data" \
  web/src/features/channels/components/drawers/channel-mutate-drawer.tsx \
  web/src/features/users/components/users-mutate-drawer.tsx

Repository: QuantumNous/new-api

Length of output: 3745


🌐 Web query:

TanStack Query same query key different shape returns cached data from another query documentation

💡 Result:

In TanStack Query, the query key is the unique identifier for your query data in the cache [1][2]. If you use the same query key for two different queries, TanStack Query will treat them as the same query and serve the cached data from the first one to the second, regardless of whether the shape of the data returned by the query functions differs [2]. This occurs because the library uses the query key to generate a deterministic hash that serves as the internal key for the query cache [1][3][2]. If multiple components or hooks use the same query key, they all subscribe to the same cache entry [4]. If the underlying query functions produce data in different formats (e.g., one returns an object and the other returns an array, or they have different properties), this will result in unexpected behavior, type errors, or runtime crashes because the cached data will not match the expected structure of the second query [2]. To avoid this, you must ensure that your query keys are unique to the specific data they represent [1][5]. A common best practice is to include all variables that affect the data result in the query key [1][2]. For example, if you have two different ways of fetching "todos," you should distinguish them in the key: // Query 1: Returns a list of todo objects useQuery({ queryKey: ['todos', 'list'], queryFn: fetchTodoList }) // Query 2: Returns a summary object useQuery({ queryKey: ['todos', 'summary'], queryFn: fetchTodoSummary }) By including unique identifiers (like strings, IDs, or configuration objects) in the query key, you ensure that TanStack Query maintains separate cache entries for distinct data shapes [1][6][5].

Citations:


Use a user-scoped query key for groups.

['groups'] identifies the channel drawer query that passes through getGroups()’s API response envelope. This drawer transforms and caches only string[]. React Query can serve the incompatible cached value, and groups.map(...) can fail.

Proposed fix
-    queryKey: ['groups'],
+    queryKey: ['users', 'groups'],

As per coding guidelines: “每个查询必须有唯一且层级一致的数组形式 queryKey。”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const groupsQuery = useQuery({
queryKey: ['groups'],
queryFn: getGroups,
queryFn: async () => {
const result = await getGroups()
if (!result.success || !Array.isArray(result.data)) {
throw new Error(result.message || t(ERROR_MESSAGES.LOAD_FAILED))
}
return result.data
},
staleTime: 5 * 60 * 1000,
})
const groups = groupsData?.data || []
const groups = groupsQuery.data ?? []
const groupsQuery = useQuery({
queryKey: ['users', 'groups'],
queryFn: async () => {
const result = await getGroups()
if (!result.success || !Array.isArray(result.data)) {
throw new Error(result.message || t(ERROR_MESSAGES.LOAD_FAILED))
}
return result.data
},
staleTime: 5 * 60 * 1000,
})
const groups = groupsQuery.data ?? []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/users/components/users-mutate-drawer.tsx` around lines 117 -
129, Update the groups query in the users mutate drawer to use a unique,
consistently scoped array query key that identifies this user-scoped string-list
query rather than the shared ['groups'] key. Keep the existing getGroups
response validation and groupsQuery.data fallback unchanged, and ensure the key
does not collide with other groups queries.

Source: Coding guidelines

// eslint-disable-next-line no-console
console.error('Failed to fetch topup info:', response.message)
return
throw new Error(response.message || 'Request failed')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Translate the fallback error message.

'Request failed' is a hard-coded fallback stored in the hook's error state. Use the project's i18n instance, such as i18next.t('Request failed'), and add the key to the locale resources if it is missing.

As per coding guidelines, front-end user-facing text must use i18n; this fallback is currently hard-coded.

Proposed fix
-        throw new Error(response.message || 'Request failed')
+        throw new Error(response.message || i18next.t('Request failed'))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/features/wallet/hooks/use-topup-info.ts` at line 179, Update the
error handling in useTopupInfo so the fallback for response.message uses the
project i18n instance instead of the hard-coded “Request failed” text, and add
the translation key to locale resources if absent. Preserve response.message
when provided.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant