Skip to content

feat: show removed upstream models in fetch models modal - #4470

Merged
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
seefs001:feature/show-removed-upstream-models
Apr 26, 2026
Merged

feat: show removed upstream models in fetch models modal#4470
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
seefs001:feature/show-removed-upstream-models

Conversation

@seefs001

@seefs001 seefs001 commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator

⚠️ 提交说明 / PR Notice

Important

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

📝 变更描述 / Description

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

获取上游模型列表新增 上游已经删除的模型 tab,方便主动去除模型

🚀 变更类型 / 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

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

Summary by CodeRabbit

New Features

  • Added a "removed" tab in the model selection interface to display unavailable models.
  • Enhanced model mapping to display original model names alongside mapped values.

@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The PR extends model selection functionality by parsing model mapping configurations to track source model names. EditChannelModal extracts mapping keys and passes them to ModelSelectModal via a new redirectSourceModels prop. ModelSelectModal uses this to identify and display removed models (those previously selected but no longer in the fetched list) in a dedicated tab.

Changes

Cohort / File(s) Summary
Model Mapping Source Extraction
web/src/components/table/channels/modals/EditChannelModal.jsx
Parses model_mapping JSON to extract keys (trimmed and de-duplicated), then passes the resulting list to ModelSelectModal via new redirectSourceModels prop. Includes type checks and try/catch guards for invalid inputs.
Removed Models Tracking and Display
web/src/components/table/channels/modals/ModelSelectModal.jsx
Accepts redirectSourceModels prop to compute normalized source model names. Adds logic to identify and track removed models (selected but absent from fetched list), introduces new "removed" tab with dedicated rendering, and updates empty-state handling and footer selection counts to support the tab. Loading spinner conditionally hidden when removed models exist.

Sequence Diagram

sequenceDiagram
    participant EC as EditChannelModal
    participant Mapping as model_mapping<br/>(JSON data)
    participant MSM as ModelSelectModal
    participant Tabs as Tab Rendering

    EC->>Mapping: Parse JSON to extract keys
    Mapping-->>EC: Source model names (trimmed,<br/>de-duplicated)
    EC->>MSM: Pass redirectSourceModels prop
    MSM->>MSM: Compute normalized source<br/>model set
    MSM->>MSM: Identify removed models:<br/>selected but not in fetched list
    MSM->>Tabs: Include "removed" tab<br/>if removed models exist
    Tabs-->>MSM: Render active tab content<br/>(available/removed models)
    MSM-->>EC: User confirms selection
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 A rabbit's map of models found,
With sources tracked and keys unwound,
Removed tabs bloom like springtime flowers,
No model lost—we've found our powers!
Hop-hop-hooray for mapping grace! 🌱

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature addition: a new tab in the fetch models modal to display removed upstream models.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 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 and usage tips.

@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.

🧹 Nitpick comments (3)
web/src/components/table/channels/modals/ModelSelectModal.jsx (2)

132-141: Consider memoizing removedModels for consistency.

fetchedModelSet is useMemo-ized but removedModels (which is also derived from props) is recomputed every render and feeds both the tabList rendering and the default-tab effect's deps. Wrapping it in useMemo keeps the pattern consistent with the surrounding sets and makes the effect dep semantics clearer.

♻️ Proposed memoization
-  const fetchedModelSet = useMemo(
-    () => new Set(normalizeModelList(models)),
-    [models],
-  );
-  const removedModels = normalizeModelList(selected).filter(
-    (model) =>
-      !fetchedModelSet.has(model) &&
-      !normalizedRedirectSourceSet.has(model) &&
-      model.toLowerCase().includes(keyword.toLowerCase()),
-  );
+  const fetchedModelSet = useMemo(
+    () => new Set(normalizeModelList(models)),
+    [models],
+  );
+  const removedModels = useMemo(() => {
+    const lowered = keyword.toLowerCase();
+    return normalizeModelList(selected).filter(
+      (model) =>
+        !fetchedModelSet.has(model) &&
+        !normalizedRedirectSourceSet.has(model) &&
+        model.toLowerCase().includes(lowered),
+    );
+  }, [selected, fetchedModelSet, normalizedRedirectSourceSet, keyword]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/table/channels/modals/ModelSelectModal.jsx` around lines
132 - 141, Wrap the computed removedModels in useMemo to avoid recomputing each
render: replace the direct computation of removedModels with a useMemo that
returns normalizeModelList(selected).filter(...) and include its dependencies
(selected, models-derived fetchedModelSet source if needed,
normalizedRedirectSourceSet, and keyword) so the value only recalculates when
inputs change; reference the existing symbols removedModels, fetchedModelSet,
normalizeModelList, normalizedRedirectSourceSet, keyword, and ensure any effects
or tabList rendering that depend on removedModels use the memoized value.

151-161: Tab auto-switch can override the user's tab selection while typing.

The effect re-evaluates the active tab whenever newModels.length or removedModels.length changes, which happens on every keystroke in the search box. If the user is on existing/removed and types a keyword that happens to also match a "new" model, their tab gets force-switched to new. This pattern was already present for new/existing; the added removed branch inherits the same behavior.

If you want the auto-pick to fire only on open (not on every search), gate it on visible transitioning to true:

♻️ Suggested guard
   useEffect(() => {
-    if (visible) {
-      if (newModels.length > 0) {
-        setActiveTab('new');
-      } else if (removedModels.length > 0) {
-        setActiveTab('removed');
-      } else {
-        setActiveTab('existing');
-      }
-    }
-  }, [visible, newModels.length, removedModels.length, selected]);
+    if (!visible) return;
+    if (newModels.length > 0) setActiveTab('new');
+    else if (removedModels.length > 0) setActiveTab('removed');
+    else setActiveTab('existing');
+    // Only re-pick the default tab when the modal opens or the
+    // underlying selection changes — not on every keystroke.
+  }, [visible, selected]);
web/src/components/table/channels/modals/EditChannelModal.jsx (1)

254-289: Combine the two model_mapping parsers to avoid parsing the same JSON twice.

redirectModelKeyList and redirectModelList (lines 254‑271) duplicate ~95% of the same logic and parse inputs.model_mapping independently on every change. A single useMemo returning both lists is cleaner and avoids the duplicate try/parse on every keystroke in the mapping editor.

♻️ Proposed consolidation
-  const redirectModelList = useMemo(() => {
-    const mapping = inputs.model_mapping;
-    if (typeof mapping !== 'string') return [];
-    const trimmed = mapping.trim();
-    if (!trimmed) return [];
-    try {
-      const parsed = JSON.parse(trimmed);
-      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
-        return [];
-      }
-      const values = Object.values(parsed)
-        .map((value) => (typeof value === 'string' ? value.trim() : undefined))
-        .filter((value) => value);
-      return Array.from(new Set(values));
-    } catch (error) {
-      return [];
-    }
-  }, [inputs.model_mapping]);
-  const redirectModelKeyList = useMemo(() => {
-    const mapping = inputs.model_mapping;
-    if (typeof mapping !== 'string') return [];
-    const trimmed = mapping.trim();
-    if (!trimmed) return [];
-    try {
-      const parsed = JSON.parse(trimmed);
-      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
-        return [];
-      }
-      const keys = Object.keys(parsed)
-        .map((key) => key.trim())
-        .filter((key) => key);
-      return Array.from(new Set(keys));
-    } catch (error) {
-      return [];
-    }
-  }, [inputs.model_mapping]);
+  const { redirectModelList, redirectModelKeyList } = useMemo(() => {
+    const empty = { redirectModelList: [], redirectModelKeyList: [] };
+    const mapping = inputs.model_mapping;
+    if (typeof mapping !== 'string') return empty;
+    const trimmed = mapping.trim();
+    if (!trimmed) return empty;
+    try {
+      const parsed = JSON.parse(trimmed);
+      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
+        return empty;
+      }
+      const values = Object.values(parsed)
+        .map((v) => (typeof v === 'string' ? v.trim() : ''))
+        .filter(Boolean);
+      const keys = Object.keys(parsed)
+        .map((k) => k.trim())
+        .filter(Boolean);
+      return {
+        redirectModelList: Array.from(new Set(values)),
+        redirectModelKeyList: Array.from(new Set(keys)),
+      };
+    } catch {
+      return empty;
+    }
+  }, [inputs.model_mapping]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/table/channels/modals/EditChannelModal.jsx` around lines
254 - 289, The two useMemo hooks redirectModelList and redirectModelKeyList
duplicate parsing of inputs.model_mapping; replace them with a single useMemo
that reads and trims inputs.model_mapping once, JSON.parse inside one try/catch,
validates parsed is a non-array object, then build unique key list (Object.keys
-> trim -> filter -> Set -> Array) and unique value list (Object.values -> keep
strings -> trim -> filter -> Set -> Array) and return both lists (e.g., { keys,
values }) so the component can destructure to redirectModelKeyList and
redirectModelList; keep the same dependency [inputs.model_mapping] and ensure on
parse error you return empty arrays for both.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@web/src/components/table/channels/modals/EditChannelModal.jsx`:
- Around line 254-289: The two useMemo hooks redirectModelList and
redirectModelKeyList duplicate parsing of inputs.model_mapping; replace them
with a single useMemo that reads and trims inputs.model_mapping once, JSON.parse
inside one try/catch, validates parsed is a non-array object, then build unique
key list (Object.keys -> trim -> filter -> Set -> Array) and unique value list
(Object.values -> keep strings -> trim -> filter -> Set -> Array) and return
both lists (e.g., { keys, values }) so the component can destructure to
redirectModelKeyList and redirectModelList; keep the same dependency
[inputs.model_mapping] and ensure on parse error you return empty arrays for
both.

In `@web/src/components/table/channels/modals/ModelSelectModal.jsx`:
- Around line 132-141: Wrap the computed removedModels in useMemo to avoid
recomputing each render: replace the direct computation of removedModels with a
useMemo that returns normalizeModelList(selected).filter(...) and include its
dependencies (selected, models-derived fetchedModelSet source if needed,
normalizedRedirectSourceSet, and keyword) so the value only recalculates when
inputs change; reference the existing symbols removedModels, fetchedModelSet,
normalizeModelList, normalizedRedirectSourceSet, keyword, and ensure any effects
or tabList rendering that depend on removedModels use the memoized value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 96562d2c-8da1-4fd2-a40c-0d9446bf60c8

📥 Commits

Reviewing files that changed from the base of the PR and between f2f3410 and 4c21c4c.

📒 Files selected for processing (2)
  • web/src/components/table/channels/modals/EditChannelModal.jsx
  • web/src/components/table/channels/modals/ModelSelectModal.jsx

@Calcium-Ion
Calcium-Ion merged commit 34afe9b into QuantumNous:main Apr 26, 2026
2 checks passed
Jinxuans referenced this pull request in TokFlux-Org/TokFlux May 9, 2026
…models

feat: show removed upstream models in fetch models modal
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.

2 participants