Skip to content

feat: 实现模型重定向中“键”自动映射到模型配置并优化JSON编辑器布局 - #1658

Closed
AAEE86 wants to merge 6 commits into
QuantumNous:alphafrom
AAEE86:feat1
Closed

feat: 实现模型重定向中“键”自动映射到模型配置并优化JSON编辑器布局#1658
AAEE86 wants to merge 6 commits into
QuantumNous:alphafrom
AAEE86:feat1

Conversation

@AAEE86

@AAEE86 AAEE86 commented Aug 25, 2025

Copy link
Copy Markdown
Contributor

refactor(json-editor): 优化键值对添加逻辑及布局调整

feat(channels): 添加模型映射解析及反向映射显示功能

提示:设置重定向后,系统会自动将模型配置中对应的"值"替换为"键"

可以简化配置模型重定向的操作!

Clipchamp.mp4

Summary by CodeRabbit

  • New Features

    • Added an optional header control in JSON editors to enable features like "auto-sync" for model mapping.
    • Model mapping: toggle to enable automatic synchronization between model mapping and the models list, plus ability to restore originals.
  • Style

    • Wider key input and narrower value input in key/value rows for improved readability.
    • Added an explanatory tip (English locale) about how redirects affect model configuration.
  • Behavioral Changes

    • New JSON rows now start with empty keys and are ignored until a key is entered.

@coderabbitai

coderabbitai Bot commented Aug 25, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Added a header hook to JSONEditor via a new public customSwitch prop and adjusted key/value row layout and empty-key behavior. EditChannelModal gained model-mapping parsing, sync helpers, state (modelOriginalMapping, enableModelMappingSync), a header Switch passed into JSONEditor, and duplicated mapping logic. Added an English i18n tip.

Changes

Cohort / File(s) Summary of changes
JSONEditor — header hook & layout
web/src/components/common/ui/JSONEditor.jsx
Public prop customSwitch (default null) added; header renders customSwitch next to the template button inside a flex container. Key/value row spans adjusted (Col 12 / 10). New rows initialize with { key: "", value: "" }; empty keys are ignored when building output and in duplicate-key checks.
EditChannelModal — model-mapping sync & UI switch (duplicated logic)
web/src/components/table/channels/modals/EditChannelModal.jsx
Introduces modelOriginalMapping and enableModelMappingSync state, parsing/mapping helpers (parseModelMapping, getCurrentModels, updateModelsList, restoreModelsToOriginalNames, applyModelMapping, syncModelMappingToModels), initialization from loaded channel data, sync-on-model_mapping edits, restore on invalid mapping, cleanup on hide/unmount. Imports Switch from UI, renders it and passes it as customSwitch into JSONEditor; extraText updated to reflect sync state. Mapping logic appears duplicated in two places. No external API signature changes.
i18n — English locale addition
web/src/i18n/locales/en.json
Adds an English translation entry for the model-redirection tip used by the UI.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant JSONEditor
  participant Header as HeaderContainer

  User->>JSONEditor: Open editor
  JSONEditor->>Header: Render header (template button)
  alt customSwitch provided
    Header->>Header: Render `customSwitch` (e.g., Switch) adjacent to template
  end
Loading
sequenceDiagram
  autonumber
  participant Loader as loadChannel
  participant Modal as EditChannelModal
  participant JSONEditor
  participant Helpers as MappingHelpers
  participant UI as ModelsList

  Loader->>Modal: channel data (models, model_mapping)
  Modal->>Helpers: parseModelMapping(data.model_mapping)
  alt valid mapping
    Helpers-->>Modal: mapping object
    Modal->>Helpers: applyModelMapping(mapping, data.models)
    Helpers-->>UI: updatedModels
    Modal->>Modal: set models, set modelOriginalMapping
  else invalid mapping
    Helpers-->>Modal: null
    Modal->>Modal: set modelOriginalMapping = {}
  end
  User->>Modal: Toggle "enable sync" (Switch in JSONEditor header)
  Modal->>JSONEditor: pass Switch as `customSwitch`
  User->>Modal: Edit `model_mapping` JSON
  alt sync enabled
    Modal->>Helpers: syncModelMappingToModels(newMapping)
    Helpers-->>UI: merged/deduped models
  else sync disabled
    Modal->>Modal: update mapping only
  end
  Note over Modal: Mapping helpers duplicated in two places within the file
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the two primary changes in this PR—automatic mapping of “键” in model redirection and the JSON editor layout optimization—and directly reflects the main objectives summarized in the changeset without introducing unrelated details.
Description Check ✅ Passed The description clearly outlines both the JSON editor refactoring and the channel model mapping features added in this PR, directly reflecting the changes in the changeset without veering off-topic or including unrelated information.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I hop through code with nimble paws,
A tiny switch sits in the header's jaws.
Empty keys wait, then find their place,
Mappings sync and sometimes double-trace.
I nibble bugs and celebrate the chase. 🐇

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b8a636 and 33ec588.

📒 Files selected for processing (1)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/components/table/channels/modals/EditChannelModal.jsx
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (3)
web/src/components/common/ui/JSONEditor.jsx (2)

252-252: Good UX change: default new key is empty (prevents accidental keys).

Empty keys are intentionally ignored by keyValueArrayToObject, so this won’t pollute the serialized JSON. Behavior aligns with duplicate-key detection that also ignores empty keys.

Optional: auto-focus the newly added key field to speed up entry.

Additional patch (outside the selected range):

--- a/web/src/components/common/ui/JSONEditor.jsx
+++ b/web/src/components/common/ui/JSONEditor.jsx
@@
-                  <Input
+                  <Input
+                    autoFocus={index === keyValuePairs.length - 1 && !pair.key}
                     placeholder={t('键名')}
                     value={pair.key}
                     onChange={(newKey) => updateKey(pair.id, newKey)}
                     status={isDuplicate ? 'warning' : undefined}
                   />

404-404: Layout spans still sum to 24; verify consistency across editors.

Key/value spans changed to 12/10 (+ delete 2) which is valid. In the “region” editor below, spans are 10/12/2. Consider unifying for visual consistency, or extracting constants for spans to avoid drift.

Please quickly eyeball both editors on narrow viewports to ensure truncation doesn’t occur on long keys.

Also applies to: 431-431

web/src/components/table/channels/modals/EditChannelModal.jsx (1)

569-571: Localize the tooltip text and avoid hard-coded Chinese.

Use i18n so non-Chinese locales see a translated hint. Minimal change keeps the title attribute.

Apply:

-            <span title={originalModelName !== displayModelName ? `原始名称: ${originalModelName}` : undefined}>
+            <span title={
+              originalModelName !== displayModelName
+                ? t('原始名称: {{name}}', { name: originalModelName })
+                : undefined
+            }>
               {displayModelName}
             </span>

Optional (preferred): use a proper Tooltip for consistent UX (requires import). Outside selected range:

--- a/web/src/components/table/channels/modals/EditChannelModal.jsx
+++ b/web/src/components/table/channels/modals/EditChannelModal.jsx
@@
-import {
+import {
   SideSheet,
   Space,
   Spin,
   Button,
   Typography,
   Checkbox,
   Banner,
   Modal,
   ImagePreview,
   Card,
   Tag,
   Avatar,
   Form,
   Row,
   Col,
   Highlight,
+  Tooltip,
 } from '@douyinfe/semi-ui';

And replace the inline title span with a Tooltip-wrapped label:

-        label: (
-          <span className="flex items-center gap-1">
-            {icon}
-            <span title={originalModelName !== displayModelName ? t('原始名称: {{name}}', { name: originalModelName }) : undefined}>
-              {displayModelName}
-            </span>
-          </span>
-        ),
+        label: (
+          <Tooltip
+            content={
+              originalModelName !== displayModelName
+                ? t('原始名称: {{name}}', { name: originalModelName })
+                : null
+            }
+          >
+            <span className="flex items-center gap-1">
+              {icon}
+              <span>{displayModelName}</span>
+            </span>
+          </Tooltip>
+        ),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a3c2b28 and 49685da.

📒 Files selected for processing (2)
  • web/src/components/common/ui/JSONEditor.jsx (3 hunks)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)
web/src/components/table/channels/modals/EditTagModal.jsx (2)
  • inputs (73-73)
  • originModelOptions (62-62)
🔇 Additional comments (3)
web/src/components/table/channels/modals/EditChannelModal.jsx (3)

546-549: Correct reactivity: recompute mappings when model_mapping changes.

Parsing the mapping inside this effect and deriving a reverse map is appropriate. Depending on inputs.model_mapping ensures the labels update immediately after edits.


552-555: Preserve filtering accuracy by using the original model id for category match.

Using originalModelName for category.filter keeps icon/category inference stable even when the display name is mapped. Good separation of display vs. identity.

Also applies to: 558-558


577-579: Good: dependency array includes inputs.model_mapping.

This ensures option labels refresh when the mapping changes. Including t also updates labels when language switches.

Comment thread web/src/components/table/channels/modals/EditChannelModal.jsx Outdated
Comment thread web/src/components/table/channels/modals/EditChannelModal.jsx Outdated
Comment thread web/src/components/table/channels/modals/EditChannelModal.jsx
@AAEE86
AAEE86 marked this pull request as draft August 26, 2025 00:54

@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: 0

♻️ Duplicate comments (1)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)

221-236: Harden JSON parsing and sanitize entries; also normalize to {} instead of null.

Return a sanitized plain object and trim k/v. This prevents arrays/strings sneaking through and stabilizes downstream logic. Adjust syncModelMappingToModels to treat an empty object as “no mapping”.

-  const parseModelMapping = (mappingValue) => {
-    if (!mappingValue || typeof mappingValue !== 'string' || mappingValue.trim() === '') {
-      return null;
-    }
-    
-    try {
-      const mapping = JSON.parse(mappingValue);
-      if (typeof mapping !== 'object' || mapping === null) {
-        return null;
-      }
-      return mapping;
-    } catch (error) {
-      console.warn('模型重定向 JSON 解析失败:', error);
-      return null;
-    }
-  };
+  const parseModelMapping = (mappingValue) => {
+    if (!mappingValue || typeof mappingValue !== 'string' || mappingValue.trim() === '') {
+      return {};
+    }
+    try {
+      const parsed = JSON.parse(mappingValue);
+      // 仅允许“非数组的对象”
+      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {};
+      // 仅保留非空 string→string,并做 trim 归一化
+      return Object.fromEntries(
+        Object.entries(parsed).filter(([k, v]) =>
+          typeof k === 'string' &&
+          typeof v === 'string' &&
+          k.trim() !== '' &&
+          v.trim() !== ''
+        ).map(([k, v]) => [k.trim(), v.trim()])
+      );
+    } catch (error) {
+      console.warn('模型重定向 JSON 解析失败:', error);
+      return {};
+    }
+  };
🧹 Nitpick comments (7)
web/src/components/table/channels/modals/EditChannelModal.jsx (7)

217-219: Clarify mapping direction with a brief inline doc (displayName → originalName).

A short comment avoids future confusion: modelOriginalMapping holds { displayName: originalName } used to restore names.

-  // 用于追踪模型的原始名称映射关系 { displayName: originalName }
+  // 用于追踪模型的原始名称映射关系,结构:{ displayName -> originalName }
+  // 注意:displayName 为“映射后的键名”,originalName 为“实际原始模型名/值”

246-255: Sanitize and dedupe model list robustly (string-cast, trim, preserve order).

Casts non-string entries, trims, and preserves insertion order while deduping.

-  const updateModelsList = (newModels, newMapping) => {
-    const uniqueModels = Array.from(new Set(newModels.filter(model => model && model.trim())));
+  const updateModelsList = (newModels, newMapping) => {
+    const seen = new Set();
+    const uniqueModels = [];
+    for (const m of newModels) {
+      const v = typeof m === 'string' ? m.trim() : String(m ?? '').trim();
+      if (!v || seen.has(v)) continue;
+      seen.add(v);
+      uniqueModels.push(v);
+    }

270-322: Make mapping application O(n) and warn on value-collisions.

Pre-index models by their “originalName” to avoid repeated scans, and detect when multiple keys map to the same value (last one silently wins otherwise).

-  const applyModelMapping = (mapping, currentModels, currentMapping) => {
-    let updatedModels = [...currentModels];
-    let newMapping = { ...currentMapping };
-    let hasChanges = false;
-
-    // 遍历重定向映射
-    Object.entries(mapping).forEach(([key, mappedValue]) => {
-      if (typeof key === 'string' && typeof mappedValue === 'string') {
-        const keyTrimmed = key.trim();
-        const valueTrimmed = mappedValue.trim();
-
-        if (keyTrimmed && valueTrimmed) {
-          // 查找模型配置中是否存在重定向的"值"(原始模型名)
-          const valueIndex = updatedModels.findIndex(model => {
-            return model === valueTrimmed || newMapping[model] === valueTrimmed;
-          });
-
-          if (valueIndex !== -1) {
-            const currentDisplayName = updatedModels[valueIndex];
-            if (currentDisplayName !== keyTrimmed) {
-              // 记录原始映射关系
-              if (!newMapping[keyTrimmed]) {
-                newMapping[keyTrimmed] = newMapping[currentDisplayName] || currentDisplayName;
-              }
-              // 清理旧的映射关系
-              if (newMapping[currentDisplayName]) {
-                delete newMapping[currentDisplayName];
-              }
-              // 更新显示名称为重定向的键
-              updatedModels[valueIndex] = keyTrimmed;
-              hasChanges = true;
-            }
-          }
-        }
-      }
-    });
-
-    // 处理不在映射中的模型,恢复为原始名称
-    const mappingKeys = new Set(Object.keys(mapping).map(key => key.trim()));
-    updatedModels = updatedModels.map(model => {
-      if (!mappingKeys.has(model) && newMapping[model]) {
-        const originalName = newMapping[model];
-        delete newMapping[model];
-        hasChanges = true;
-        return originalName;
-      }
-      return model;
-    });
-
-    return { updatedModels, newMapping, hasChanges };
-  };
+  const applyModelMapping = (mapping, currentModels, currentMapping) => {
+    let updatedModels = [...currentModels];
+    let newMapping = { ...currentMapping };
+    let hasChanges = false;
+
+    // 预索引:originalName -> index
+    const originalIndex = new Map();
+    for (let i = 0; i < updatedModels.length; i++) {
+      const name = updatedModels[i];
+      const orig = newMapping[name] || name; // 若已映射,取其原名
+      if (!originalIndex.has(orig)) originalIndex.set(orig, i);
+    }
+
+    // 检测 value 冲突(多个键映射到同一原名)
+    const seenValues = new Map(); // valueTrimmed -> keyTrimmed
+
+    for (const [key, mappedValue] of Object.entries(mapping)) {
+      if (typeof key !== 'string' || typeof mappedValue !== 'string') continue;
+      const keyTrimmed = key.trim();
+      const valueTrimmed = mappedValue.trim();
+      if (!keyTrimmed || !valueTrimmed) continue;
+
+      if (seenValues.has(valueTrimmed) && seenValues.get(valueTrimmed) !== keyTrimmed) {
+        console.warn('Duplicate model_mapping value detected; last one wins:', valueTrimmed, 'from', seenValues.get(valueTrimmed), 'to', keyTrimmed);
+      }
+      seenValues.set(valueTrimmed, keyTrimmed);
+
+      const idx = originalIndex.get(valueTrimmed);
+      if (idx !== undefined) {
+        const currentDisplay = updatedModels[idx];
+        if (currentDisplay !== keyTrimmed) {
+          if (!newMapping[keyTrimmed]) {
+            newMapping[keyTrimmed] = newMapping[currentDisplay] || currentDisplay;
+          }
+          if (newMapping[currentDisplay]) {
+            delete newMapping[currentDisplay];
+          }
+          updatedModels[idx] = keyTrimmed;
+          hasChanges = true;
+          // 更新索引:当前 display 改名了,但 original 仍对应 valueTrimmed
+        }
+      }
+    }
+
+    // 清理:不在 mapping 中的 display 名称恢复为原名
+    const mappingKeys = new Set(Object.keys(mapping).map((k) => k.trim()));
+    updatedModels = updatedModels.map((model) => {
+      if (!mappingKeys.has(model) && newMapping[model]) {
+        const originalName = newMapping[model];
+        delete newMapping[model];
+        hasChanges = true;
+        return originalName;
+      }
+      return model;
+    });
+
+    return { updatedModels, newMapping, hasChanges };
+  };

323-343: Treat empty-object mapping as reset (consistent with sanitized parser); consider debounce.

After normalizing parseModelMapping to return {}, treat an empty mapping as “restore to original names”. Also consider debouncing to reduce flicker while the user is typing JSON.

-  const syncModelMappingToModels = (mappingValue) => {
-    const mapping = parseModelMapping(mappingValue);
-    
-    if (!mapping) {
-      restoreModelsToOriginalNames();
-      return;
-    }
+  const syncModelMappingToModels = (mappingValue) => {
+    const mapping = parseModelMapping(mappingValue);
+    // 空对象代表“无映射/清空映射”
+    if (!mapping || Object.keys(mapping).length === 0) {
+      restoreModelsToOriginalNames();
+      return;
+    }

Additional (optional) outside-change snippet to debounce:

// imports: add useCallback to React import list
const mappingSyncTimer = useRef(null);
const debouncedSyncModelMappingToModels = useCallback((val) => {
  if (mappingSyncTimer.current) clearTimeout(mappingSyncTimer.current);
  mappingSyncTimer.current = setTimeout(() => syncModelMappingToModels(val), 200);
}, [syncModelMappingToModels]);

And call debouncedSyncModelMappingToModels from handleInputChange when name === 'model_mapping'.


364-370: Reduce re-renders: debounce real-time sync on model_mapping edits.

Typing JSON triggers parse/sync on each keystroke. Debouncing ~200ms smooths UX and avoids unnecessary work.

-    if (name === 'model_mapping') {
-      setInputs((inputs) => ({ ...inputs, [name]: value }));
-      syncModelMappingToModels(value);
-      return;
-    }
+    if (name === 'model_mapping') {
+      setInputs((inputs) => ({ ...inputs, [name]: value }));
+      // 若引入了 debounce,替换为 debouncedSyncModelMappingToModels
+      // debouncedSyncModelMappingToModels ? debouncedSyncModelMappingToModels(value) : syncModelMappingToModels(value);
+      syncModelMappingToModels(value);
+      return;
+    }

Note: include the optional debounced helper and add useCallback import if you adopt it.


514-528: Initialize original mapping only for present keys; guard with sanitized parser.

This looks correct. With the sanitized parser (returning {}), this block remains safe. Optionally, trim model names in the includes check to handle stray whitespace.

-        Object.entries(mapping).forEach(([key, value]) => {
-          if (data.models.includes(key)) {
+        Object.entries(mapping).forEach(([key, value]) => {
+          if (data.models.map(m => (m || '').trim()).includes(key.trim())) {
             initialMapping[key] = value;
           }
         });

1769-1777: Copy nit: clarify “键/值” roles to reduce confusion.

Minor wording tweak to align with the behavior: display uses “键名”,原始为“值”。 Consider removing the emoji if it clashes with product tone.

-                        <div className="text-blue-600 text-xs mt-1">
-                          {t('💡 提示:设置重定向后,系统自动将“模型配置”中对应的"值"替换为"键"')}
-                        </div>
+                        <div className="text-blue-600 text-xs mt-1">
+                          {t('提示:设置重定向后,系统会将“模型配置”中与映射“值”(原始模型名)匹配的项,显示为对应的“键”(重定向后的名称)')}
+                        </div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 49685da and 93d29b9.

📒 Files selected for processing (2)
  • web/src/components/common/ui/JSONEditor.jsx (3 hunks)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/components/common/ui/JSONEditor.jsx
🔇 Additional comments (4)
web/src/components/table/channels/modals/EditChannelModal.jsx (4)

238-244: LGTM: Prefer form state with safe fallback.

getCurrentModels correctly prefers live form values and safely falls back to inputs.models.


256-269: LGTM: Restore flow is idempotent and guarded by an efficient change check.

The array-wise comparison avoids stringify and only updates when needed.


746-748: LGTM: Clearing mapping state when modal closes prevents bleed-through.


753-759: LGTM: Cleanup on unmount.

State reset avoids stale mapping when navigating away.

@AAEE86
AAEE86 marked this pull request as ready for review August 26, 2025 04:02
@AAEE86 AAEE86 changed the title feat(channels): 根据模型重定向键名自动修改对应模型配置中的名称 feat: 实现模型重定向中“键”自动映射到模型配置并优化JSON编辑器布局 Aug 26, 2025

@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: 0

♻️ Duplicate comments (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (2)

217-236: Sanitize and validate model_mapping JSON; treat only plain object with non-empty string→string pairs as valid.

Currently arrays or non-plain objects pass the type check and can produce surprising no-ops. Also, keys/values aren’t trimmed or validated. Prefer returning a sanitized object; invalid/empty should degrade gracefully to {}.

Apply:

-  const parseModelMapping = (mappingValue) => {
-    if (!mappingValue || typeof mappingValue !== 'string' || mappingValue.trim() === '') {
-      return null;
-    }
-    
-    try {
-      const mapping = JSON.parse(mappingValue);
-      if (typeof mapping !== 'object' || mapping === null) {
-        return null;
-      }
-      return mapping;
-    } catch (error) {
-      console.warn('模型重定向 JSON 解析失败:', error);
-      return null;
-    }
-  };
+  const parseModelMapping = (mappingStr) => {
+    if (!mappingStr || mappingStr.trim() === '') return {};
+    try {
+      const parsed = JSON.parse(mappingStr);
+      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
+        return {};
+      }
+      // Keep only trimmed, non-empty string→string entries
+      return Object.fromEntries(
+        Object.entries(parsed)
+          .filter(([k, v]) => typeof k === 'string' && typeof v === 'string')
+          .map(([k, v]) => [k.trim(), v.trim()])
+          .filter(([k, v]) => k !== '' && v !== '')
+      );
+    } catch (error) {
+      console.warn('模型重定向 JSON 解析失败:', error);
+      return {};
+    }
+  };

676-694: ReactNode labels break select search unless selectFilter extracts text. Keep label plain-text or add a searchable field.

optionsWithIcon sets label to JSX (icon + text). If selectFilter uses option.label.toString(), search will degrade to “[object Object]”.

Two safe options:

  • Keep label as the plain string, render icon via renderOptionItem; or
  • Add searchText: " " to each option and update selectFilter to include it.

Example (keep label string and add a render prop per option):

-    const optionsWithIcon = Array.from(modelMap.values()).map((opt) => {
-      const modelName = opt.value;
-      let icon = null;
-      ...
-      return {
-        ...opt,
-        label: (
-          <span className="flex items-center gap-1">
-            {icon}
-            {modelName}
-          </span>
-        ),
-      };
-    });
+    const optionsWithIcon = Array.from(modelMap.values()).map((opt) => {
+      const modelName = opt.value;
+      let icon = null;
+      ...
+      return {
+        ...opt,
+        label: modelName, // keep searchable text
+        render: () => (  // Semi supports custom item render via option.render
+          <span className="flex items-center gap-1">
+            {icon}
+            {modelName}
+          </span>
+        ),
+      };
+    });

To confirm selectFilter expectations and whether it reads option.render or a custom searchText, run:

#!/bin/bash
# Inspect selectFilter implementation and its usage of option props
rg -nP -C3 'export\s+const\s+selectFilter|function\s+selectFilter|\bselectFilter\b' web/src/helpers
🧹 Nitpick comments (5)
web/src/components/table/channels/modals/EditChannelModal.jsx (5)

256-269: LGTM with a tiny nit.

Comparing lengths in hasChanges is redundant since restore keeps the count unchanged. Not necessary to change, just noting.


270-321: Guard duplicate targets and normalize key/value trimming once; warn on collisions.

If multiple display keys map to the same original value, last-write wins silently. Emit a warning so surprises are detectable and normalize once to avoid repeated trims.

Apply:

   const applyModelMapping = (mapping, currentModels, currentMapping) => {
     let updatedModels = [...currentModels];
     let newMapping = { ...currentMapping };
     let hasChanges = false;
+    const valueOwners = Object.create(null); // valueTrimmed -> keyTrimmed

     // 遍历重定向映射
-    Object.entries(mapping).forEach(([key, mappedValue]) => {
-      if (typeof key === 'string' && typeof mappedValue === 'string') {
-        const keyTrimmed = key.trim();
-        const valueTrimmed = mappedValue.trim();
+    Object.entries(mapping).forEach(([key, mappedValue]) => {
+      if (typeof key === 'string' && typeof mappedValue === 'string') {
+        const keyTrimmed = key;
+        const valueTrimmed = mappedValue;

         if (keyTrimmed && valueTrimmed) {
+          // Detect duplicate targets (same original value → different display keys)
+          if (valueOwners[valueTrimmed] && valueOwners[valueTrimmed] !== keyTrimmed) {
+            console.warn(
+              'Duplicate model_mapping value detected; last one wins:',
+              valueTrimmed,
+              'from',
+              valueOwners[valueTrimmed],
+              'to',
+              keyTrimmed
+            );
+          }
+          valueOwners[valueTrimmed] = keyTrimmed;
+
           // 查找模型配置中是否存在重定向的"值"(原始模型名)
           const valueIndex = updatedModels.findIndex(model => {
             return model === valueTrimmed || newMapping[model] === valueTrimmed;
           });

           if (valueIndex !== -1) {
             const currentDisplayName = updatedModels[valueIndex];
             if (currentDisplayName !== keyTrimmed) {
               // 记录原始映射关系
               if (!newMapping[keyTrimmed]) {
                 newMapping[keyTrimmed] = newMapping[currentDisplayName] || currentDisplayName;
               }
               // 清理旧的映射关系
               if (newMapping[currentDisplayName]) {
                 delete newMapping[currentDisplayName];
               }
               // 更新显示名称为重定向的键
               updatedModels[valueIndex] = keyTrimmed;
               hasChanges = true;
             }
           }
         }
       }
     });

323-343: Treat empty mapping as a signal to restore originals.

After hardening parseModelMapping to return {} on invalid input, sync should consider “no entries” equivalent to “restore”.

Apply:

-  const syncModelMappingToModels = (mappingValue) => {
-    const mapping = parseModelMapping(mappingValue);
-    
-    if (!mapping) {
-      restoreModelsToOriginalNames();
-      return;
-    }
+  const syncModelMappingToModels = (mappingValue) => {
+    const mapping = parseModelMapping(mappingValue);
+    if (!mapping || Object.keys(mapping).length === 0) {
+      restoreModelsToOriginalNames();
+      return;
+    }

364-370: Also re-apply mapping when the models list changes.

Right now, mapping sync only runs when model_mapping changes. If the user adds/removes models, mapped display names won’t update until the JSON is touched.

Apply:

     // 处理模型重定向变更时自动同步模型配置(实时同步)
-    if (name === 'model_mapping') {
+    if (name === 'model_mapping') {
       setInputs((inputs) => ({ ...inputs, [name]: value }));
       syncModelMappingToModels(value);
       return;
     }
+    // 当模型列表本身发生变化时,同步一次映射,确保新增/删除立即体现在展示名称上
+    if (name === 'models') {
+      setInputs((inputs) => ({ ...inputs, [name]: value }));
+      const currentMappingStr = formApiRef.current?.getValue('model_mapping') ?? inputs.model_mapping ?? '';
+      syncModelMappingToModels(currentMappingStr);
+      return;
+    }

753-759: Avoid setState in unmount cleanup; it’s a no-op and can trigger warnings in some setups.

You don’t need to reset state on unmount. Remove this effect.

Apply:

-  // 组件卸载时清理资源
-  useEffect(() => {
-    return () => {
-      setModelOriginalMapping({});
-    };
-  }, []);
+  // 组件卸载无需显式清理 modelOriginalMapping
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f5882a2 and 0696d29.

📒 Files selected for processing (2)
  • web/src/components/common/ui/JSONEditor.jsx (3 hunks)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/components/common/ui/JSONEditor.jsx
🔇 Additional comments (4)
web/src/components/table/channels/modals/EditChannelModal.jsx (4)

238-255: LGTM: unified models updater is clean and dedups without reordering.

The Set-based dedupe preserves insertion order. Updating both form and local state keeps UI in sync.


746-748: LGTM: clearing mapping state when the sheet closes avoids stale display names.

This matches the lifecycle reset behavior elsewhere.


1769-1777: LGTM: helpful, localized hint text clarifies “键/值” semantics for mapping.

Clear guidance that UI will auto-rewrite “值→键” in 模型配置 aligns with the feature intent.


514-528: Initial load doesn’t apply mapping to the models list; violates the “display mapped names first” goal.

On load, you only seed modelOriginalMapping if models already contain the “key” names. If models are the original names (common case), the UI won’t show mapped names until the user edits mapping. Apply the mapping once during load.

Apply:

-      // 初始化模型原始映射关系
-      const mapping = parseModelMapping(data.model_mapping);
-      if (mapping) {
-        const initialMapping = {};
-        // 根据当前的模型映射和模型列表,建立原始映射关系
-        Object.entries(mapping).forEach(([key, value]) => {
-          if (data.models.includes(key)) {
-            initialMapping[key] = value;
-          }
-        });
-        setModelOriginalMapping(initialMapping);
-      } else {
-        setModelOriginalMapping({});
-      }
+      // 初始化并应用模型映射到模型列表,让“模型配置”优先显示映射后的名称
+      const mapping = parseModelMapping(data.model_mapping);
+      if (mapping && Object.keys(mapping).length > 0) {
+        const { updatedModels, newMapping } = applyModelMapping(mapping, data.models || [], {});
+        updateModelsList(updatedModels, newMapping);
+      } else {
+        setModelOriginalMapping({});
+      }

Likely an incorrect or invalid review comment.

@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: 1

🧹 Nitpick comments (1)
web/src/i18n/locales/en.json (1)

2001-2001: Use a stable, semantic i18n key for the redirect mapping tip

The translation itself is solid, but using the full string (with emoji and curly quotes) as the lookup key can be fragile and error-prone. I recommend introducing a dedicated alias key—e.g. channels.redirect.mappingTip—and updating the UI to reference it instead of the raw string.

Action items:

  • In web/src/i18n/locales/en.json, add the alias key:
    • Line 2001 currently contains the raw lookup.
    • Append:
     "已复制:{{name}}": "Copied: {{name}}",
     "💡 提示:设置重定向后,系统自动将“模型配置”中对应的“值”替换为“键”": "💡 Tip: After setting the redirect, the system automatically replaces the corresponding \"value\" in \"Model Configuration\" with \"key\"",
    +"channels.redirect.mappingTip": "💡 Tip: After setting the redirect, the system automatically replaces the corresponding \"value\" in \"Model Configuration\" with \"key\""
  • Replicate the channels.redirect.mappingTip entry (with appropriate translations) in each of your other locale JSON files under web/src/i18n/locales to avoid missing translations.
  • Update any UI components or lookup calls to use channels.redirect.mappingTip instead of the raw string key.

Verification:

  • The original string exists only in en.json at line 2001.
  • The alias channels.redirect.mappingTip is not yet defined in any locale file.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0696d29 and e81005b.

📒 Files selected for processing (1)
  • web/src/i18n/locales/en.json (1 hunks)

Comment thread web/src/i18n/locales/en.json

@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: 1

♻️ Duplicate comments (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (2)

262-277: Harden JSON parsing in parseModelMapping function.

The current implementation doesn't validate that the parsed JSON is a plain object or sanitize the entries. This could lead to unexpected behavior with arrays or other non-object JSON values.


258-277: Duplicate code detected in model mapping utilities.

The model mapping parsing and related helper functions are defined twice in the component. This creates maintenance issues and violates the DRY principle. The entire block of utility functions (lines 258-383) appears to be a complete duplicate of functionality that should only be defined once.

Remove the duplicate definitions and keep only one set of these utility functions. Consider moving them to the top of the component or to a separate utilities file if they might be reused elsewhere.

🧹 Nitpick comments (1)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)

311-362: Consider extracting complex model mapping logic.

The applyModelMapping function is quite complex with nested conditions and multiple responsibilities. Consider breaking it down into smaller, more focused functions for better maintainability.

Consider extracting the mapping application logic into smaller helper functions:

+const findModelIndex = (models, targetValue, mapping) => {
+  return models.findIndex(model => 
+    model === targetValue || mapping[model] === targetValue
+  );
+};
+
+const updateMappingForKey = (mapping, key, currentDisplayName, newKey) => {
+  const updatedMapping = { ...mapping };
+  if (!updatedMapping[newKey]) {
+    updatedMapping[newKey] = updatedMapping[currentDisplayName] || currentDisplayName;
+  }
+  if (updatedMapping[currentDisplayName]) {
+    delete updatedMapping[currentDisplayName];
+  }
+  return updatedMapping;
+};

 const applyModelMapping = (mapping, currentModels, currentMapping) => {
   let updatedModels = [...currentModels];
   let newMapping = { ...currentMapping };
   let hasChanges = false;
 
   Object.entries(mapping).forEach(([key, mappedValue]) => {
     if (typeof key === 'string' && typeof mappedValue === 'string') {
       const keyTrimmed = key.trim();
       const valueTrimmed = mappedValue.trim();
 
       if (keyTrimmed && valueTrimmed) {
-        const valueIndex = updatedModels.findIndex(model => {
-          return model === valueTrimmed || newMapping[model] === valueTrimmed;
-        });
+        const valueIndex = findModelIndex(updatedModels, valueTrimmed, newMapping);
 
         if (valueIndex !== -1) {
           const currentDisplayName = updatedModels[valueIndex];
           if (currentDisplayName !== keyTrimmed) {
-            // Record original mapping
-            if (!newMapping[keyTrimmed]) {
-              newMapping[keyTrimmed] = newMapping[currentDisplayName] || currentDisplayName;
-            }
-            // Clean up old mapping
-            if (newMapping[currentDisplayName]) {
-              delete newMapping[currentDisplayName];
-            }
+            newMapping = updateMappingForKey(newMapping, currentDisplayName, keyTrimmed);
             updatedModels[valueIndex] = keyTrimmed;
             hasChanges = true;
           }
         }
       }
     }
   });
   // ... rest of the function
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e81005b and 8955f0d.

📒 Files selected for processing (2)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (5 hunks)
  • web/src/i18n/locales/en.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/i18n/locales/en.json
🔇 Additional comments (5)
web/src/components/table/channels/modals/EditChannelModal.jsx (5)

298-309: Optimize array comparison in restoreModelsToOriginalNames.

Using a more explicit array comparison approach improves readability and performance compared to using JSON.stringify for arrays.

The optimization using length check and array.some() is a good improvement over JSON.stringify comparison.


405-410: Real-time model mapping synchronization implemented correctly.

The implementation properly handles model mapping changes and triggers synchronization with the models list. This provides a good user experience with immediate feedback.


837-837: Good cleanup of model mapping state.

Properly resetting modelOriginalMapping when the modal is closed prevents state leakage between editing sessions.


840-845: Component unmount cleanup is properly implemented.

The cleanup effect properly clears the model mapping state when the component unmounts, preventing memory leaks.


1887-1894: User-friendly tooltip for model redirection feature.

The addition of the informative tooltip helps users understand how the automatic model mapping synchronization works. The visual hint with the 💡 emoji makes it more noticeable.

Comment thread web/src/components/table/channels/modals/EditChannelModal.jsx
@Calcium-Ion

Copy link
Copy Markdown
Member

应该作为一个按钮,点击同步到模型列表,自动填充会变成破坏性更新

@AAEE86

AAEE86 commented Sep 9, 2025

Copy link
Copy Markdown
Contributor Author

应该作为一个按钮,点击同步到模型列表,自动填充会变成破坏性更新

好的 我找时间改一下

@AAEE86

AAEE86 commented Sep 9, 2025

Copy link
Copy Markdown
Contributor Author

@Calcium-Ion 改好了
image

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)

403-429: Apply mapping when the user changes “models” (not just when JSON changes).

With auto-sync enabled, adding/clearing models should immediately reflect the mapping; otherwise the switch under-delivers.

Apply:

   const handleInputChange = (name, value) => {
     if (formApiRef.current) {
       formApiRef.current.setValue(name, value);
     }
   if (name === 'models' && Array.isArray(value)) {
-      value = Array.from(new Set(value.map((m) => (m || '').trim())));
+      value = Array.from(new Set(value.map((m) => (m || '').trim())));
+      // persist the trimmed set first
+      setInputs((inputs) => ({ ...inputs, [name]: value }));
+      // if auto-sync is on, apply mapping immediately
+      if (enableModelMappingSync) {
+        const mappingStr =
+          formApiRef.current?.getValue('model_mapping') ?? inputs.model_mapping;
+        syncModelMappingToModels(mappingStr);
+      }
+      return;
     }
♻️ Duplicate comments (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (2)

269-285: Harden JSON parsing: reject arrays and sanitize to string→string pairs.

Accepting arrays or non-string values can create odd edge cases downstream. Return a clean object with trimmed string pairs only.

Apply:

-  const parseModelMapping = (mappingValue) => {
+  const parseModelMapping = (mappingValue) => {
     if (!mappingValue || typeof mappingValue !== 'string' || mappingValue.trim() === '') {
-      return null;
+      return null;
     }
     
     try {
-      const mapping = JSON.parse(mappingValue);
-      if (typeof mapping !== 'object' || mapping === null) {
-        return null;
-      }
-      return mapping;
+      const parsed = JSON.parse(mappingValue);
+      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
+        return {};
+      }
+      // keep only non-empty string→string entries, trimmed
+      return Object.fromEntries(
+        Object.entries(parsed)
+          .filter(([k, v]) => typeof k === 'string' && typeof v === 'string' && k.trim() && v.trim())
+          .map(([k, v]) => [k.trim(), v.trim()])
+      );
     } catch (error) {
       console.warn('模型重定向 JSON 解析失败:', error);
       return null;
     }
   };

Note: Returning {} is fine—applyModelMapping already restores originals when mapping is empty.


776-796: Fix search regression: option.label is ReactNode, but selectFilter expects a string.

Search will fail because label.toString() on a ReactNode becomes “[object Object]”.

Apply this change to keep label as plain text and render icon via a custom renderer:

-    const optionsWithIcon = Array.from(modelMap.values()).map((opt) => {
+    const optionsWithIcon = Array.from(modelMap.values()).map((opt) => {
       const modelName = opt.value;
       let icon = null;
       for (const [key, category] of Object.entries(categories)) {
         if (key !== 'all' && category.filter({ model_name: modelName })) {
           icon = category.icon;
           break;
         }
       }
       return {
         ...opt,
-        label: (
-          <span className='flex items-center gap-1'>
-            {icon}
-            {modelName}
-          </span>
-        ),
+        label: modelName, // keep plain text for selectFilter
+        icon,             // carry icon for custom rendering
       };
     });

And attach a render function to the models Select:

   <Form.Select
     field='models'
@@
-    optionList={modelOptions}
+    optionList={modelOptions}
+    renderOptionItem={(renderProps) => {
+      const { label, value, onClick, onMouseEnter, focused, selected, className, style } = renderProps;
+      const opt = modelOptions.find(o => o.value === value);
+      return (
+        <div
+          style={style}
+          className={['flex items-center gap-2 px-3 py-2 mx-2 my-1 rounded-lg',
+            focused && 'bg-blue-50',
+            selected && 'bg-blue-100 text-blue-700',
+            className].filter(Boolean).join(' ')}
+          onClick={() => onClick()}
+          onMouseEnter={() => onMouseEnter()}
+        >
+          <span className='w-5 h-5 flex items-center justify-center'>{opt?.icon}</span>
+          <span className='truncate'>{label}</span>
+        </div>
+      );
+    }}
#!/bin/bash
# Inspect selectFilter to confirm it assumes string labels
rg -n -C2 "export\s+const\s+selectFilter|function\s+selectFilter|\bselectFilter\b" web/src/helpers

Also applies to: 1951-1963

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

866-872: Avoid setState in unmount cleanup.

Calling setModelOriginalMapping in the unmount cleanup is unnecessary and may trigger “setState on unmounted component” warnings.

Apply:

-useEffect(() => {
-  return () => {
-    setModelOriginalMapping({});
-  };
-}, []);
+// No cleanup needed; reset is handled in resetModalState()

320-375: Optional: document mapping behavior for non-listed models and stale entries.

Logic restores models not present in mapping keys back to originals and prunes stale entries from newMapping. Consider a brief inline comment to prevent future regressions.

Apply:

     // 处理不在映射中的模型,恢复为原始名称
+    // 如果模型未出现在当前映射的“键”中,但仍存在于记录的 original 映射里,
+    // 说明该映射已被移除;将其还原为 original 并清理对应的记录。
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8955f0d and 9b8a636.

📒 Files selected for processing (2)
  • web/src/components/common/ui/JSONEditor.jsx (3 hunks)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/components/common/ui/JSONEditor.jsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the applyModelMapping function transforms the models list by replacing original model names (mapping values) with display names (mapping keys). The database stores this transformed list containing mapped keys. On channel load, data.models contains these mapped display names, making the initialization filter if (data.models.includes(key)) correct.
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the database stores mapped keys (display names) in the models field after applying model mapping transformations. When loading a channel, data.models contains the mapped keys, not the original model names. The filtering logic if (data.models.includes(key)) in the initialization is correct.
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the applyModelMapping function transforms the models list by replacing original model names (mapping values) with display names (mapping keys). The database stores this transformed list containing mapped keys. On channel load, data.models contains these mapped display names, making the initialization filter if (data.models.includes(key)) correct.

Applied to files:

  • web/src/components/table/channels/modals/EditChannelModal.jsx
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the database stores mapped keys (display names) in the models field after applying model mapping transformations. When loading a channel, data.models contains the mapped keys, not the original model names. The filtering logic if (data.models.includes(key)) in the initialization is correct.

Applied to files:

  • web/src/components/table/channels/modals/EditChannelModal.jsx
🔇 Additional comments (1)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)

577-591: Initialization of modelOriginalMapping aligns with stored mapped keys.

Filtering by key (display name) against data.models is correct since the DB stores mapped keys post-application.

Comment thread web/src/components/table/channels/modals/EditChannelModal.jsx
Comment thread web/src/components/table/channels/modals/EditChannelModal.jsx
@AAEE86 AAEE86 closed this Sep 22, 2025
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