Skip to content

feat: Add Xiaomi channel support and update related model ratios#4590

Open
HynoR wants to merge 5 commits intoQuantumNous:mainfrom
HynoR:feat/add-xiaomi
Open

feat: Add Xiaomi channel support and update related model ratios#4590
HynoR wants to merge 5 commits intoQuantumNous:mainfrom
HynoR:feat/add-xiaomi

Conversation

@HynoR
Copy link
Copy Markdown
Contributor

@HynoR HynoR commented May 2, 2026

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。
    支持小米渠道添加(含token plan 版本)

📝 变更描述 / Description

支持小米推理模型,并支持 tts 模型(openai tts 原生调用或者 comletion 官方兼容)

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

Summary by CodeRabbit

  • New Features
    • Added Xiaomi MiMo as a supported AI provider channel with chat, audio, streaming, and TTS capabilities.
    • Included Xiaomi models and default model ratio entries.
    • Added Xiaomi to web channel selection/options and provider lists, plus its channel icon.
    • Default vendor mapping and icon for Xiaomi added for UI consistency.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Walkthrough

Adds Xiaomi MiMo channel: new channel/API constants and mappings, frontend options, vendor/model entries, stream support, a Xiaomi relay adaptor with TTS request/response handling, and wiring into adaptor selection.

Changes

Xiaomi Channel Integration

Layer / File(s) Summary
Constants & Mappings
constant/api_type.go, constant/channel.go
Added APITypeXiaomi and ChannelTypeXiaomi = 58; registered base URL https://api.xiaomimimo.com and display name "Xiaomi".
Type Mapping
common/api_type.go
Added case constant.ChannelTypeXiaomi mapping to constant.APITypeXiaomi.
Relay Channel Constants
relay/channel/xiaomi/constants.go
Introduced ChannelName = "xiaomi", internal audio context key, default MIMO voice, and ModelList of supported MIMO model identifiers.
Relay Adaptor Implementation
relay/channel/xiaomi/adaptor.go
Added Adaptor embedding openai.Adaptor; overrides for request URL routing, header setup (api-key), request conversions, transport selection (DoRequest), response interception (DoResponse), and metadata methods.
TTS Handling
relay/channel/xiaomi/tts.go
Implemented Xiaomi TTS request/response types, convertTTSRequest (format normalization, voice defaulting), handleTTSResponse (unmarshal, base64-decode audio, write Gin response), and content-type mapping; errors returned as *types.NewAPIError.
Relay Wiring
relay/relay_adaptor.go, relay/common/relay_info.go
Imported Xiaomi package; GetAdaptor gains case constant.APITypeXiaomi returning &xiaomi.Adaptor{}; streamSupportedChannels now includes Xiaomi.
Model Ratios & Vendor
setting/ratio_setting/model_ratio.go, model/pricing_default.go
Added default model ratio entries for several MIMO models and added vendor rule "mimo": "小米" with icon "XiaomiMiMo".
Frontend UI
web/classic/.../channel.constants.js, web/default/.../constants.ts, web/classic/src/helpers/render.jsx, web/default/.../channel-utils.ts
Added channel option/type entry for 58 (Xiaomi MiMo), added icon mapping 58: 'XiaomiMiMo', and refactored icon lookup to channelTypeIconMap.
sequenceDiagram
    participant Client
    participant XiaomiAdaptor
    participant XiaomiAPI as Xiaomi API Server
    participant TTSHandler

    Client->>XiaomiAdaptor: Send audio-speech (TTS) request
    XiaomiAdaptor->>XiaomiAdaptor: Normalize format, default voice, build payload
    XiaomiAdaptor->>XiaomiAPI: POST /v1/chat/completions (TTS payload)
    XiaomiAPI-->>TTSHandler: HTTP JSON response with base64 audio
    TTSHandler->>TTSHandler: Unmarshal, validate, base64-decode audio
    TTSHandler->>Client: Return decoded audio with appropriate Content-Type (200)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • creamlike1024
  • seefs001

🐰✨
I hopped through code with tiny paws,
Brought Xiaomi voices without a pause,
Orange flags wave, the models sing,
MiMo hums—what glee it brings,
Carrots applaud the audio laws.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% 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 PR title accurately describes the main changes: adding Xiaomi channel support and updating model ratios, which aligns with the comprehensive changeset across backend constants, adaptor implementations, and frontend UI updates.
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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@model/pricing_default.go`:
- Line 22: defaultVendorIcons is missing an entry for the new vendor string
"小米", so newly created vendors get no icon; add a mapping in the
defaultVendorIcons map that maps the vendor name "小米" to the existing icon key
(e.g., "mimo") used elsewhere. Locate the defaultVendorIcons variable and add an
entry like `"小米": "mimo"` so the new Xiaomi vendor resolves to the proper icon.

In `@relay/channel/xiaomi/tts.go`:
- Around line 65-67: In handleTTSResponse, guard resp and resp.Body before
deferring Close: check if resp == nil or resp.Body == nil and return an
appropriate *types.NewAPIError instead of deferring; only call defer
resp.Body.Close() after verifying both are non-nil. Update the error return path
to produce the same typed API error used elsewhere in this function so callers
receive a consistent error shape.

In `@setting/ratio_setting/model_ratio.go`:
- Around line 252-256: The model ratio map is missing explicit entries for
Xiaomi TTS variants so lookups fall back to the default; add entries for
"mimo-v2-tts" and "mimo-v2.5-tts" to the same ratio values as their non-TTS
counterparts (e.g., mirror "mimo-v2-omni"/"mimo-v2-pro" behavior — use 0.5 for
v2-tts and 0.5 for v2.5-tts or align with the existing ratios scheme used in the
map) by inserting those keys into the same map literal in model_ratio.go so TTS
billing uses the correct ratio instead of the default.
🪄 Autofix (Beta)

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

Run ID: 3465aac7-d9e1-49ed-a0af-17632c911a43

📥 Commits

Reviewing files that changed from the base of the PR and between dac55f0 and c011935.

📒 Files selected for processing (12)
  • common/api_type.go
  • constant/api_type.go
  • constant/channel.go
  • model/pricing_default.go
  • relay/channel/xiaomi/adaptor.go
  • relay/channel/xiaomi/constants.go
  • relay/channel/xiaomi/tts.go
  • relay/common/relay_info.go
  • relay/relay_adaptor.go
  • setting/ratio_setting/model_ratio.go
  • web/classic/src/constants/channel.constants.js
  • web/default/src/features/channels/constants.ts

Comment thread model/pricing_default.go
Comment thread relay/channel/xiaomi/tts.go Outdated
Comment thread setting/ratio_setting/model_ratio.go Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@relay/channel/xiaomi/adaptor.go`:
- Around line 36-41: ConvertOpenAIRequest currently returns the incoming request
unchanged, bypassing the base adaptor's logic that clears StreamOptions for
non-OpenAI/Azure channels; update Xiaomi's ConvertOpenAIRequest to delegate to
the base implementation (call a.Adaptor.ConvertOpenAIRequest(c, info, request))
and return its result so the base logic (which nils request.StreamOptions when
info.ChannelType is not constant.ChannelTypeOpenAI or constant.ChannelTypeAzure)
is applied for ChannelTypeXiaomi; alternatively ensure the method explicitly
clears request.StreamOptions for non-OpenAI/Azure before returning, but prefer
delegating to the base adaptor to keep behavior consistent.
🪄 Autofix (Beta)

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

Run ID: 43d3560a-757e-493a-9288-11705cb1ac55

📥 Commits

Reviewing files that changed from the base of the PR and between c011935 and ba77b90.

📒 Files selected for processing (3)
  • relay/channel/xiaomi/adaptor.go
  • relay/channel/xiaomi/tts.go
  • setting/ratio_setting/model_ratio.go
✅ Files skipped from review due to trivial changes (1)
  • setting/ratio_setting/model_ratio.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • relay/channel/xiaomi/tts.go

Comment on lines +36 to +41
func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) {
if request == nil {
return nil, fmt.Errorf("request is nil")
}
return request, nil
}
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect openai.Adaptor.ConvertOpenAIRequest for StreamOptions injection
# and any other request mutations that Xiaomi's override would silently bypass.

# 1. Find the full implementation of ConvertOpenAIRequest in the openai adaptor
ast-grep --pattern 'func (a *Adaptor) ConvertOpenAIRequest($_, $_, $_) ($_, $_) {
  $$$
}'

# 2. Confirm which channels are in streamSupportedChannels and whether Xiaomi is present
rg -n "streamSupportedChannels\|ChannelTypeXiaomi" relay/common/relay_info.go -A 3 -B 1

# 3. Find where StreamOptions is set / injected into the request body
rg -n "StreamOptions\|stream_options\|StreamOption" relay/channel/openai/adaptor.go -A 3 -B 3

Repository: QuantumNous/new-api

Length of output: 34700


🏁 Script executed:

# Check the base OpenAI adaptor's StreamOptions handling logic
rg -n "StreamOptions" relay/channel/openai/adaptor.go -B 3 -A 3

# Verify if Xiaomi is in streamSupportedChannels
rg -n "streamSupportedChannels" relay/common/relay_info.go -A 30

# Check Xiaomi's channel type constant
rg -n "ChannelTypeXiaomi\|Xiaomi" relay/ -t go | head -20

Repository: QuantumNous/new-api

Length of output: 2983


🏁 Script executed:

# Get full context around lines 76-80 to understand which function sets StreamOptions
sed -n '60,90p' relay/channel/openai/adaptor.go

# Check if there are multiple ConvertOpenAIRequest calls or if one delegates to another
rg -n "ConvertOpenAIRequest" relay/channel/xiaomi/adaptor.go -B 5 -A 10

# Verify Xiaomi's base class/embedding structure
head -50 relay/channel/xiaomi/adaptor.go

Repository: QuantumNous/new-api

Length of output: 3316


🏁 Script executed:

# Check if there are tests or documentation indicating what Xiaomi expects
find relay/channel/xiaomi -type f -name "*.go" -o -name "*.md" -o -name "*.txt" | xargs ls -la

# Look for usage patterns or comments about StreamOptions in Xiaomi context
rg -n "StreamOptions\|stream_options\|include_usage" relay/channel/xiaomi/ -t go

# Check if Xiaomi needs to clear StreamOptions like the base does
rg -n "ChannelTypeXiaomi" relay/channel/ -t go -B 2 -A 2

Repository: QuantumNous/new-api

Length of output: 304


🏁 Script executed:

# View the exact base adaptor logic again to confirm the StreamOptions clearing behavior
sed -n '229,240p' relay/channel/openai/adaptor.go

# Check if there's any conditional logic that would preserve StreamOptions for Xiaomi
rg -n "SupportStreamOptions\|info.ChannelType.*Xiaomi" relay/channel/openai/adaptor.go -A 3 -B 3

Repository: QuantumNous/new-api

Length of output: 579


ConvertOpenAIRequest bypasses required base adaptor logic for StreamOptions handling.

The override returns the request unchanged without delegating to a.Adaptor.ConvertOpenAIRequest. The base OpenAI adaptor includes logic (line 234) to clear StreamOptions from requests for all non-OpenAI/Azure channels:

if info.ChannelType != constant.ChannelTypeOpenAI && info.ChannelType != constant.ChannelTypeAzure {
    request.StreamOptions = nil
}

Since Xiaomi's ChannelType is ChannelTypeXiaomi, the base adaptor would clear StreamOptions to prevent it from being sent upstream. By returning the request unchanged, this override silently bypasses that clearing logic, leaving StreamOptions in the request body when it should be removed.

The simplest fix is to delegate to the base adaptor:

🔧 Proposed fix
 func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) {
-	if request == nil {
-		return nil, fmt.Errorf("request is nil")
-	}
-	return request, nil
+	return a.Adaptor.ConvertOpenAIRequest(c, info, request)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/xiaomi/adaptor.go` around lines 36 - 41, ConvertOpenAIRequest
currently returns the incoming request unchanged, bypassing the base adaptor's
logic that clears StreamOptions for non-OpenAI/Azure channels; update Xiaomi's
ConvertOpenAIRequest to delegate to the base implementation (call
a.Adaptor.ConvertOpenAIRequest(c, info, request)) and return its result so the
base logic (which nils request.StreamOptions when info.ChannelType is not
constant.ChannelTypeOpenAI or constant.ChannelTypeAzure) is applied for
ChannelTypeXiaomi; alternatively ensure the method explicitly clears
request.StreamOptions for non-OpenAI/Azure before returning, but prefer
delegating to the base adaptor to keep behavior consistent.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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/classic/src/helpers/render.jsx (1)

408-414: ⚡ Quick win

Add a dev-time warning when icon resolution falls back to Avatar.

Right now unresolved icon names silently degrade to first-letter Avatar. A non-prod warning makes mapping typos easy to catch during review/testing.

Proposed patch
   if (
     !IconComponent ||
     (typeof IconComponent !== 'function' && typeof IconComponent !== 'object')
   ) {
+    if (process.env.NODE_ENV !== 'production') {
+      console.warn(`[getLobeHubIcon] unresolved icon: ${iconName}`);
+    }
     const firstLetter = String(iconName).charAt(0).toUpperCase();
     return <Avatar size='extra-extra-small'>{firstLetter}</Avatar>;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/classic/src/helpers/render.jsx` around lines 408 - 414, When
IconComponent is missing or invalid and you fall back to Avatar, add a dev-only
warning so typos are visible during testing: inside the existing conditional
that checks IconComponent (the block that currently computes firstLetter and
returns <Avatar>), emit a console.warn (guarded by process.env.NODE_ENV !==
'production') that includes the unresolved iconName and a short hint like "icon
not resolved, falling back to Avatar" so reviewers can spot mapping mistakes;
keep the existing Avatar fallback logic (firstLetter) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/classic/src/helpers/render.jsx`:
- Around line 367-370: Replace the early-return null when an unknown channel
type is encountered so the UI renders a neutral fallback icon instead of blank
space: in the block using channelTypeIconMap and iconName (the lines with const
iconName = channelTypeIconMap[channelType]; if (!iconName) return null;), set
iconName to a defined fallback (e.g., 'generic' or 'unknown') when the lookup
fails and proceed to render the icon as usual; ensure the fallback key exists in
the channelTypeIconMap (or add it) so render logic that consumes iconName
continues to work for new/unsupported channel types.

---

Nitpick comments:
In `@web/classic/src/helpers/render.jsx`:
- Around line 408-414: When IconComponent is missing or invalid and you fall
back to Avatar, add a dev-only warning so typos are visible during testing:
inside the existing conditional that checks IconComponent (the block that
currently computes firstLetter and returns <Avatar>), emit a console.warn
(guarded by process.env.NODE_ENV !== 'production') that includes the unresolved
iconName and a short hint like "icon not resolved, falling back to Avatar" so
reviewers can spot mapping mistakes; keep the existing Avatar fallback logic
(firstLetter) unchanged.
🪄 Autofix (Beta)

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

Run ID: d993a534-d3c9-452c-9f25-10723fcadeb9

📥 Commits

Reviewing files that changed from the base of the PR and between ba77b90 and 3129946.

📒 Files selected for processing (4)
  • model/pricing_default.go
  • relay/channel/xiaomi/tts.go
  • web/classic/src/helpers/render.jsx
  • web/default/src/features/channels/lib/channel-utils.ts
✅ Files skipped from review due to trivial changes (2)
  • model/pricing_default.go
  • web/default/src/features/channels/lib/channel-utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • relay/channel/xiaomi/tts.go

Comment on lines +367 to 370
const iconName = channelTypeIconMap[channelType];
if (!iconName) {
return null;
}
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 | ⚡ Quick win

Use a fallback icon instead of returning null for unknown channel types.

Line 369 returns null, which creates blank icons when a new/unsupported channelType shows up. A neutral fallback keeps list rendering stable.

Proposed patch
 export function getChannelIcon(channelType) {
   const iconSize = 14;
   const iconName = channelTypeIconMap[channelType];
-  if (!iconName) {
-  return null;
-  }
-  return getLobeHubIcon(iconName, iconSize);
+  return getLobeHubIcon(iconName || '', iconSize);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/classic/src/helpers/render.jsx` around lines 367 - 370, Replace the
early-return null when an unknown channel type is encountered so the UI renders
a neutral fallback icon instead of blank space: in the block using
channelTypeIconMap and iconName (the lines with const iconName =
channelTypeIconMap[channelType]; if (!iconName) return null;), set iconName to a
defined fallback (e.g., 'generic' or 'unknown') when the lookup fails and
proceed to render the icon as usual; ensure the fallback key exists in the
channelTypeIconMap (or add it) so render logic that consumes iconName continues
to work for new/unsupported channel types.

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