Skip to content

feat: 可视化模型重定向设置中,允许用户从拉取的模型列表里直接选择模型 - #2610

Merged
seefs001 merged 3 commits into
QuantumNous:mainfrom
Bliod-Cook:main
Jan 26, 2026
Merged

feat: 可视化模型重定向设置中,允许用户从拉取的模型列表里直接选择模型#2610
seefs001 merged 3 commits into
QuantumNous:mainfrom
Bliod-Cook:main

Conversation

@Bliod-Cook

@Bliod-Cook Bliod-Cook commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added a dedicated modal for streamlined model selection during channel configuration.
    • Per-entry model selection controls in channel mapping for granular model assignment.
    • JSON editor now supports customizable per-field suffix rendering for enhanced field UI.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Added a per-field suffix hook to the JSONEditor and updated its internal call signatures; introduced a SingleModelSelectModal and integrated it into EditChannelModal to allow selecting a model for individual model_mapping keys via a per-entry "select model" button and modal flow.

Changes

Cohort / File(s) Summary
JSONEditor enhancement
web/src/components/common/ui/JSONEditor.jsx
Added optional renderStringValueSuffix prop; changed renderValueInput signature to (pairId, pairKey, value) and wired suffix via suffix={renderStringValueSuffix?.({ pairId, pairKey, value })}. Updated internal call sites to pass pair.key.
EditChannelModal integration
web/src/components/table/channels/modals/EditChannelModal.jsx
Added state and handlers for per-key model selection (modal visibility, selected key/model), openModelMappingValueModal, and updated model fetching to support modal. Wired renderStringValueSuffix on JSONEditor to render a "select model" action (Tooltip + IconSearch) and applied selected model back into model_mapping and form state.
New model select modal
web/src/components/table/channels/modals/SingleModelSelectModal.jsx
New component for single-model selection with props (visible, models, selected, onConfirm, onCancel), search/filter, deduplication/normalization, category grouping, radio-grid selection, mobile sizing, and localized text.

Sequence Diagram

sequenceDiagram
    participant User
    participant JSONEditor
    participant EditChannelModal
    participant SingleModelSelectModal

    User->>JSONEditor: Interacts with model_mapping entry
    JSONEditor->>User: Renders string input with suffix button
    User->>EditChannelModal: Clicks suffix "select model" button
    EditChannelModal->>SingleModelSelectModal: Open modal with models list
    SingleModelSelectModal->>SingleModelSelectModal: Filter / categorize models
    User->>SingleModelSelectModal: Selects model and confirms
    SingleModelSelectModal->>EditChannelModal: onConfirm(selectedModel)
    EditChannelModal->>EditChannelModal: Update model_mapping and form state
    EditChannelModal->>JSONEditor: Re-render with updated mapping
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I nibble keys and tweak each line,

I tuck a suffix where values shine,
A modal pops — a model picked!
Hop, click, map — the mapping's fixed,
Hooray for tidy JSON time!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 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: allowing users to directly select models from fetched model lists in the visualized model redirection settings.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

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

165-186: Consider using just key as the unique identifier.

The key and itemKey use ${key}_${index} which includes the index. Since key is already unique (it comes from Object.entries(modelsByCategory) where keys are unique), the index suffix is redundant.

♻️ Suggested simplification
-                <Collapse.Panel
-                  key={`${key}_${index}`}
-                  itemKey={`${key}_${index}`}
+                <Collapse.Panel
+                  key={key}
+                  itemKey={key}
web/src/components/table/channels/modals/EditChannelModal.jsx (1)

187-193: Consider consolidating modal state into a single object.

Multiple related state variables for the same modal could be consolidated for easier state management and to avoid potential race conditions when updating multiple states.

♻️ Alternative pattern using a single state object
const [modelMappingValueModal, setModelMappingValueModal] = useState({
  visible: false,
  models: [],
  key: '',
  selected: '',
});

// Usage:
setModelMappingValueModal({
  visible: true,
  models: normalizedModelsToUse,
  key: mappingKey,
  selected: currentValue,
});
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9301263 and b6313a1.

📒 Files selected for processing (3)
  • web/src/components/common/ui/JSONEditor.jsx
  • web/src/components/table/channels/modals/EditChannelModal.jsx
  • web/src/components/table/channels/modals/SingleModelSelectModal.jsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: AAEE86
Repo: QuantumNous/new-api PR: 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
Repo: QuantumNous/new-api PR: 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
Repo: QuantumNous/new-api PR: 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/SingleModelSelectModal.jsx
  • web/src/components/table/channels/modals/EditChannelModal.jsx
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
Repo: QuantumNous/new-api PR: 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/SingleModelSelectModal.jsx
  • web/src/components/table/channels/modals/EditChannelModal.jsx
🧬 Code graph analysis (2)
web/src/components/table/channels/modals/SingleModelSelectModal.jsx (2)
web/src/components/common/ui/JSONEditor.jsx (2)
  • useTranslation (66-66)
  • Typography (41-41)
web/src/components/table/channels/modals/EditChannelModal.jsx (3)
  • useTranslation (126-126)
  • isMobile (130-130)
  • Typography (79-79)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)
web/src/components/table/channels/modals/SingleModelSelectModal.jsx (2)
  • SingleModelSelectModal (38-193)
  • selectedModel (55-55)
🔇 Additional comments (13)
web/src/components/common/ui/JSONEditor.jsx (3)

63-63: Well-designed optional extension point.

The renderStringValueSuffix prop provides a clean, composable way to inject custom suffix UI into string value inputs without modifying the component's core logic. The optional prop pattern maintains backward compatibility with existing consumers.


339-407: Clean implementation of per-field suffix rendering.

The signature update to include pairKey and the suffix injection via optional chaining are both well-executed. The ?.() pattern ensures graceful handling when the prop is not provided.


475-477: Call site properly updated.

The internal call to renderValueInput correctly passes pair.key as the second argument, aligning with the updated function signature.

web/src/components/table/channels/modals/SingleModelSelectModal.jsx (3)

38-62: Well-structured component initialization.

The component properly handles edge cases with default props, normalizes and deduplicates the model list, and resets state appropriately when the modal becomes visible. The use of useMemo for normalizedModels ensures efficient recalculation only when models prop changes.


70-105: Robust category-based model grouping.

The categorization logic correctly handles:

  • Skipping the 'all' category
  • Grouping uncategorized models under a translated "其他" label
  • Building category metadata with labels, icons, and model lists

The memoization ensures this potentially expensive operation is only recomputed when filteredModels or t changes.


151-159: Defensive Radio.Group onChange handling.

The handler correctly normalizes the value from either an event object (val.target.value) or a direct value, ensuring compatibility with different Semi UI versions or usage patterns.

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

49-49: Imports properly added for new functionality.

The new imports (Tooltip, SingleModelSelectModal, IconSearch) are correctly placed and support the new model mapping value selection feature.

Also applies to: 59-59, 74-74


205-222: Enhanced defensive parsing for model mapping.

The additional type checks (Array.isArray(parsed) check and typeof value === 'string' guard) improve robustness against malformed model_mapping values.


741-748: fetchUpstreamModelList now returns fetched models.

Returning the unique models list enables callers to use the fetched data directly without relying solely on state updates, which is useful for the new openModelMappingValueModal flow.


750-784: Well-structured model selection modal opener.

The function:

  1. Validates the mapping key early
  2. Only proceeds for fetchable channel types
  3. Falls back to fetching models if the cache is empty
  4. Normalizes and deduplicates the final model list
  5. Pre-selects the current value if it exists in the list

The silent fetch approach prevents disruptive error popups while still allowing the flow to proceed when cached models are available.


2716-2736: Proper suffix button implementation.

The button is correctly:

  • Disabled when pairKey is empty (preventing invalid selections)
  • Using e.stopPropagation() to prevent input focus issues
  • Wrapped in a Tooltip for discoverability
  • Only rendered for fetchable channel types

3251-3296: Robust onConfirm handler with proper state synchronization.

The handler:

  1. Validates selected model
  2. Handles edge case where mapping key becomes invalid
  3. Safely parses existing model_mapping with multiple fallback paths
  4. Updates both inputs state via handleInputChange and form via formApiRef.current.setValue

The dual update at lines 3289-3292 ensures consistency between component state and form state, though this pattern could be simplified if the form were the single source of truth.


1722-1745: LGTM on IO.NET banner restructure.

The IO.NET channel banner rendering maintains functionality while adapting to the new component structure. The closeIcon={null} and conditional button rendering are appropriate.

@Bliod-Cook Bliod-Cook changed the title 可视化模型重定向设置中,允许用户从拉取的模型列表里直接选择模型 feat: 可视化模型重定向设置中,允许用户从拉取的模型列表里直接选择模型 Jan 9, 2026
@Bliod-Cook

Copy link
Copy Markdown
Contributor Author
image 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: 1

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)

439-452: Invalidate cached fetched models when credentials/type change.
The new per-entry selector reuses fetchedModels; if users edit type, base_url, or key, stale options can appear. Clearing the cache on those inputs keeps the modal accurate.

🛠️ Suggested fix
   if (formApiRef.current) {
     formApiRef.current.setValue(name, value);
   }
+  if (['type', 'key', 'base_url'].includes(name)) {
+    setFetchedModels([]);
+  }
   if (name === 'models' && Array.isArray(value)) {
     value = Array.from(new Set(value.map((m) => (m || '').trim())));
   }
🤖 Fix all issues with AI agents
In `@web/src/components/table/channels/modals/EditChannelModal.jsx`:
- Around line 2779-2847: The copy button's onClick handler calls the async
copy() without awaiting its result, so change the handler to an async function,
await copy(inputs.models.join(',')), check the returned boolean (const ok =
await copy(...)), and call showSuccess only if ok is true otherwise call
showError; also wrap the await in try/catch to handle thrown errors and call
showError on exception. Target the onClick handler that references
inputs.models, copy, showSuccess, and showError in EditChannelModal.jsx.

Comment on lines +2779 to +2847
theme='light'
onClick={() => setOllamaModalVisible(true)}
>
{group.name}
{t('Ollama 模型管理')}
</Button>
))}
</Space>
}
/>
)}
<Button
size='small'
type='warning'
onClick={() => handleInputChange('models', [])}
>
{t('清除所有模型')}
</Button>
<Button
size='small'
type='tertiary'
onClick={() => {
if (inputs.models.length === 0) {
showInfo(t('没有模型可以复制'));
return;
}
try {
copy(inputs.models.join(','));
showSuccess(t('模型列表已复制到剪贴板'));
} catch (error) {
showError(t('复制失败'));
}
}}
>
{t('复制所有模型')}
</Button>
{modelGroups &&
modelGroups.length > 0 &&
modelGroups.map((group) => (
<Button
key={group.id}
size='small'
type='primary'
onClick={() => {
let items = [];
try {
if (Array.isArray(group.items)) {
items = group.items;
} else if (
typeof group.items === 'string'
) {
const parsed = JSON.parse(
group.items || '[]',
);
if (Array.isArray(parsed)) items = parsed;
}
} catch {}
const current =
formApiRef.current?.getValue('models') ||
inputs.models ||
[];
const merged = Array.from(
new Set(
[...current, ...items]
.map((m) => (m || '').trim())
.filter(Boolean),
),
);
handleInputChange('models', merged);
}}
>
{group.name}
</Button>
))}

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# First, let's find where copy is imported/defined
echo "=== Searching for copy function definition ==="
rg -n "export (async )?function copy|export const copy|^const copy\s*=" web/src --type=js --type=jsx --type=ts --type=tsx -A 3

echo -e "\n=== Searching for copy function in common utility locations ==="
fd -e "js" -e "jsx" -e "ts" -e "tsx" "copy|clipboard" web/src | head -20

Repository: QuantumNous/new-api

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for copy function in the entire web/src directory
echo "=== Searching for copy function definition ==="
rg -n "function copy\s*\(|const copy\s*=" web/src --type=js --type=jsx --type=ts --type=tsx -B 2 -A 10 | head -100

Repository: QuantumNous/new-api

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the specific file and check imports and usages of copy
echo "=== Checking EditChannelModal.jsx imports and copy usages ==="
head -100 web/src/components/table/channels/modals/EditChannelModal.jsx | grep -E "import|copy"

echo -e "\n=== Searching for all copy() calls in EditChannelModal.jsx ==="
rg -n "copy\(" web/src/components/table/channels/modals/EditChannelModal.jsx -B 2 -A 2

Repository: QuantumNous/new-api

Length of output: 1692


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for copy function definition across the entire repository
echo "=== Searching for copy function definition ==="
rg -n "function copy|const copy\s*=" web/src -A 10 --max-count=20

echo -e "\n=== Look for utils/clipboard files ==="
find web/src -name "*copy*" -o -name "*clipboard*" | head -20

Repository: QuantumNous/new-api

Length of output: 821


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the imports in EditChannelModal to see where copy comes from
echo "=== Finding copy import source ==="
grep -n "from.*copy\|import.*copy" web/src/components/table/channels/modals/EditChannelModal.jsx

# Then search for that module
echo -e "\n=== Searching nearby imports ==="
head -50 web/src/components/table/channels/modals/EditChannelModal.jsx | grep -A 5 "import {"

Repository: QuantumNous/new-api

Length of output: 97


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get the full copy function
sed -n '72,100p' web/src/helpers/utils.jsx

Repository: QuantumNous/new-api

Length of output: 930


Make the copy handler async and await the result.

The copy() function is async (defined in web/src/helpers/utils.jsx:72) and returns a boolean indicating success/failure. At line 2731 elsewhere in this file, it's correctly awaited with result checking (const ok = await copy(...)). However, at line 2801, the same function is called without awaiting, and success is shown immediately without checking the result. This means the user sees a success message before the copy operation completes, and errors are never detected.

🛠️ Suggested fix
-                          <Button
+                          <Button
                             size='small'
                             type='tertiary'
-                            onClick={() => {
+                            onClick={async () => {
                               if (inputs.models.length === 0) {
                                 showInfo(t('没有模型可以复制'));
                                 return;
                               }
                               try {
-                                copy(inputs.models.join(','));
-                                showSuccess(t('模型列表已复制到剪贴板'));
+                                const ok = await copy(inputs.models.join(','));
+                                if (ok) {
+                                  showSuccess(t('模型列表已复制到剪贴板'));
+                                } else {
+                                  showError(t('复制失败'));
+                                }
                               } catch (error) {
                                 showError(t('复制失败'));
                               }
                             }}
                           >
🤖 Prompt for AI Agents
In `@web/src/components/table/channels/modals/EditChannelModal.jsx` around lines
2779 - 2847, The copy button's onClick handler calls the async copy() without
awaiting its result, so change the handler to an async function, await
copy(inputs.models.join(',')), check the returned boolean (const ok = await
copy(...)), and call showSuccess only if ok is true otherwise call showError;
also wrap the await in try/catch to handle thrown errors and call showError on
exception. Target the onClick handler that references inputs.models, copy,
showSuccess, and showError in EditChannelModal.jsx.

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