Skip to content

删除 Entry 后测试状态显示错位

背景

ServiceProvidersPage.tsx 中,点击"全部检测"后可以看到所有 API Key 的测试结果状态(成功/失败)。此时如果删除中间某条 entry,删除后剩余 entry 的测试状态显示不正确。

问题复现

  1. 有 6 条 API Key Entry,索引 0-5
  2. 点击"全部检测",所有 entry 都有测试状态
  3. 删除索引 4(第 5 条,假设是失败状态)
  4. 原来的索引 5 变成新的索引 4
  5. Bug:新的索引 4 显示的是旧的索引 4 的状态(error),而不是原来索引 5 的状态(success)

问题原因

keyOf 函数使用 provider.name + entryIdx(数组索引)作为 testStatuses 的 key:

typescript
const keyOf = (provider: OpenAIProviderConfig, entryIdx: number): string =>
  `${provider.name}__${entryIdx}`;

删除 entry 后,数组索引发生变化,但 testStatuses 中的 key 没有同步更新:

删除前:
  testStatuses = {
    "provider__0": "success",
    "provider__1": "success",
    "provider__2": "success",
    "provider__3": "success",
    "provider__4": "error",     ← 这条被删除
    "provider__5": "success",   ← 这条变成索引 4
  }

删除后(数组变化):
  apiKeyEntries = [entry0, entry1, entry2, entry3, entry5]
  新索引 4 对应原来的 entry5

渲染时:
  新索引 4 查 testStatuses["provider__4"] → 得到 "error"(旧值)
  但 entry5 的实际状态应该是 "success"

根本原因testStatuses 用数组索引作为 key,删除后索引变化,但状态没有同步更新。

修复方案

方案 A:删除时清理对应状态(不推荐)

handleDeletehandleBatchDelete 中,删除 entry 后同时清理 testStatusestestErrors 中对应的 key。

问题:这只清理了被删除的那条,后面的索引仍然会错位。

方案 B:用 apiKey 作为 key(推荐)

keyOf 改为使用 entry.apiKey 而不是数组索引:

typescript
const keyOf = (provider: OpenAIProviderConfig, entry: ApiKeyEntry): string =>
  `${provider.name}__${entry.apiKey}`;

这样无论 entry 在数组中的位置如何变化,key 都是稳定的。

实现细节

1. 修改 keyOf 函数签名

typescript
// 修改前
const keyOf = (provider: OpenAIProviderConfig, entryIdx: number): string =>
  `${provider.name}__${entryIdx}`;

// 修改后
const keyOf = (provider: OpenAIProviderConfig, entry: ApiKeyEntry): string =>
  `${provider.name}__${entry.apiKey}`;

2. 修改所有调用 keyOf 的地方

位置修改前修改后
第 322 行(filter 逻辑)keyOf(selectedProvider, indexOf(e))keyOf(selectedProvider, e)
第 653 行(testEntry)keyOf(provider, entryIdx)keyOf(provider, entry)
第 1026 行(渲染)keyOf(selectedProvider, realIdx)keyOf(selectedProvider, entry)

3. 修改 testEntry 函数

typescript
// 修改前
const testEntry = useCallback(
  async (provider: OpenAIProviderConfig, entry: ApiKeyEntry, entryIdx: number) => {
    const k = keyOf(provider, entryIdx);
    // ...
  }
);

// 修改后
const testEntry = useCallback(
  async (provider: OpenAIProviderConfig, entry: ApiKeyEntry) => {
    const k = keyOf(provider, entry);
    // ...
  }
);

4. 修改调用 testEntry 的地方

typescript
// 修改前
onClick={() => testEntry(selectedProvider, entry, realIdx)}

// 修改后
onClick={() => testEntry(selectedProvider, entry)}

涉及文件

文件改动
ServiceProvidersPage.tsx修改 keyOf 函数和所有调用处

注意事项

  • apiKey 作为 key 的前提是同一个 provider 下 apiKey 不重复(当前业务逻辑已保证)
  • 如果 apiKey 为空字符串,key 会是 providerName__,多个空 key 会冲突,但当前业务中空 apiKey 的 entry 不会被测试(testEntry 中有校验)