From cfbc2df3f8b5f08dea7ba56c5ed5f8a4d8a54b70 Mon Sep 17 00:00:00 2001 From: DD <1083962986@qq.com> Date: Mon, 8 Sep 2025 16:21:21 +0800 Subject: [PATCH 01/13] add submodel.ai --- common/api_type.go | 2 + constant/api_type.go | 1 + constant/channel.go | 2 + relay/channel/submodel/adaptor.go | 82 ++++++++++++++++++++++++++ relay/channel/submodel/constants.go | 16 +++++ relay/relay_adaptor.go | 3 + setting/ratio_setting/model_ratio.go | 13 ++++ web/src/constants/channel.constants.js | 5 ++ web/src/helpers/render.js | 2 + web/src/pages/Channel/EditTagModal.js | 3 + 10 files changed, 129 insertions(+) create mode 100644 relay/channel/submodel/adaptor.go create mode 100644 relay/channel/submodel/constants.go diff --git a/common/api_type.go b/common/api_type.go index f045866a..6204451d 100644 --- a/common/api_type.go +++ b/common/api_type.go @@ -65,6 +65,8 @@ func ChannelType2APIType(channelType int) (int, bool) { apiType = constant.APITypeCoze case constant.ChannelTypeJimeng: apiType = constant.APITypeJimeng + case constant.ChannelTypeSubmodel: + apiType = constant.APITypeSubmodel } if apiType == -1 { return constant.APITypeOpenAI, false diff --git a/constant/api_type.go b/constant/api_type.go index 6ba5f257..0c7b1fdd 100644 --- a/constant/api_type.go +++ b/constant/api_type.go @@ -31,5 +31,6 @@ const ( APITypeXai APITypeCoze APITypeJimeng + APITypeSubmodel APITypeDummy // this one is only for count, do not add any channel after this ) diff --git a/constant/channel.go b/constant/channel.go index 224121e7..3d7158b1 100644 --- a/constant/channel.go +++ b/constant/channel.go @@ -49,6 +49,7 @@ const ( ChannelTypeCoze = 49 ChannelTypeKling = 50 ChannelTypeJimeng = 51 + ChannelTypeSubmodel = 52 ChannelTypeDummy // this one is only for count, do not add any channel after this ) @@ -106,4 +107,5 @@ var ChannelBaseURLs = []string{ "https://api.coze.cn", //49 "https://api.klingai.com", //50 "https://visual.volcengineapi.com", //51 + "https://llm.submodel.ai", //52 } diff --git a/relay/channel/submodel/adaptor.go b/relay/channel/submodel/adaptor.go new file mode 100644 index 00000000..371fb055 --- /dev/null +++ b/relay/channel/submodel/adaptor.go @@ -0,0 +1,82 @@ +package submodel + +import ( + "errors" + "io" + "net/http" + "one-api/dto" + "one-api/relay/channel" + "one-api/relay/channel/openai" + relaycommon "one-api/relay/common" + "one-api/types" + + "github.com/gin-gonic/gin" +) + +type Adaptor struct { +} + +func (a *Adaptor) ConvertClaudeRequest(*gin.Context, *relaycommon.RelayInfo, *dto.ClaudeRequest) (any, error) { + return nil, errors.New("not implemented") +} + +func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) { + return nil, errors.New("not implemented") +} + +func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) { + return nil, errors.New("not implemented") +} + +func (a *Adaptor) Init(info *relaycommon.RelayInfo) { +} + +func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { + return relaycommon.GetFullRequestURL(info.BaseUrl, info.RequestURLPath, info.ChannelType), nil +} + +func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error { + channel.SetupApiRequestHeader(info, c, req) + req.Set("Authorization", "Bearer "+info.ApiKey) + return nil +} + +func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) { + if request == nil { + return nil, errors.New("request is nil") + } + return request, nil +} + +func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) { + return nil, errors.New("not implemented") +} + +func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) { + return nil, errors.New("not implemented") +} + +func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) { + return nil, errors.New("not implemented") +} + +func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) { + return channel.DoApiRequest(a, c, info, requestBody) +} + +func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) { + if info.IsStream { + usage, err = openai.OaiStreamHandler(c, info, resp) + } else { + usage, err = openai.OpenaiHandler(c, info, resp) + } + return +} + +func (a *Adaptor) GetModelList() []string { + return ModelList +} + +func (a *Adaptor) GetChannelName() string { + return ChannelName +} \ No newline at end of file diff --git a/relay/channel/submodel/constants.go b/relay/channel/submodel/constants.go new file mode 100644 index 00000000..962682bb --- /dev/null +++ b/relay/channel/submodel/constants.go @@ -0,0 +1,16 @@ +package submodel + +var ModelList = []string{ + "NousResearch/Hermes-4-405B-FP8", + "Qwen/Qwen3-235B-A22B-Thinking-2507", + "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", + "Qwen/Qwen3-235B-A22B-Instruct-2507", + "zai-org/GLM-4.5-FP8", + "openai/gpt-oss-120b", + "deepseek-ai/DeepSeek-R1-0528", + "deepseek-ai/DeepSeek-R1", + "deepseek-ai/DeepSeek-V3-0324", + "deepseek-ai/DeepSeek-V3.1", +} + +var ChannelName = "submodel" \ No newline at end of file diff --git a/relay/relay_adaptor.go b/relay/relay_adaptor.go index 2ce12a87..946053a2 100644 --- a/relay/relay_adaptor.go +++ b/relay/relay_adaptor.go @@ -34,6 +34,7 @@ import ( "one-api/relay/channel/xunfei" "one-api/relay/channel/zhipu" "one-api/relay/channel/zhipu_4v" + "one-api/relay/channel/submodel" ) func GetAdaptor(apiType int) channel.Adaptor { @@ -96,6 +97,8 @@ func GetAdaptor(apiType int) channel.Adaptor { return &coze.Adaptor{} case constant.APITypeJimeng: return &jimeng.Adaptor{} + case constant.APITypeSubmodel: + return &submodel.Adaptor{} } return nil } diff --git a/setting/ratio_setting/model_ratio.go b/setting/ratio_setting/model_ratio.go index 8a1d6aae..0bcb6ff5 100644 --- a/setting/ratio_setting/model_ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -223,6 +223,19 @@ var defaultModelRatio = map[string]float64{ "grok-vision-beta": 2.5, "grok-3-fast-beta": 2.5, "grok-3-mini-fast-beta": 0.3, + + // submodel + "NousResearch/Hermes-4-405B-FP8": 0.8, + "Qwen/Qwen3-235B-A22B-Thinking-2507": 0.6, + "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8": 0.8, + "Qwen/Qwen3-235B-A22B-Instruct-2507": 0.3, + "zai-org/GLM-4.5-FP8": 0.8, + "openai/gpt-oss-120b": 0.5, + "deepseek-ai/DeepSeek-R1-0528": 0.8, + "deepseek-ai/DeepSeek-R1": 0.8, + "deepseek-ai/DeepSeek-V3-0324": 0.8, + "deepseek-ai/DeepSeek-V3.1": 0.8 + } var defaultModelPrice = map[string]float64{ diff --git a/web/src/constants/channel.constants.js b/web/src/constants/channel.constants.js index b145ea11..6a4566a7 100644 --- a/web/src/constants/channel.constants.js +++ b/web/src/constants/channel.constants.js @@ -135,6 +135,11 @@ export const CHANNEL_OPTIONS = [ color: 'blue', label: '即梦', }, + { + value: 52, + color: 'blue', + label: 'SubModel', + }, ]; export const MODEL_TABLE_PAGE_SIZE = 10; diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 34ba78d7..abf246b8 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -398,6 +398,8 @@ export function getChannelIcon(channelType) { return ; case 21: // 知识库:AI Proxy case 44: // 嵌入模型:MokaAI M3E + case 52: // SubModel + return null; default: return null; // 未知类型或自定义渠道不显示图标 } diff --git a/web/src/pages/Channel/EditTagModal.js b/web/src/pages/Channel/EditTagModal.js index 433d4f09..35fc1646 100644 --- a/web/src/pages/Channel/EditTagModal.js +++ b/web/src/pages/Channel/EditTagModal.js @@ -98,6 +98,9 @@ const EditTagModal = (props) => { case 36: localModels = ['suno_music', 'suno_lyrics']; break; + case 52: + localModels = ['NousResearch/Hermes-4-405B-FP8', 'Qwen/Qwen3-235B-A22B-Thinking-2507', 'Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8', 'zai-org/GLM-4.5-FP8', 'openai/gpt-oss-120b', 'deepseek-ai/DeepSeek-R1-0528', 'deepseek-ai/DeepSeek-R1', 'deepseek-ai/DeepSeek-V3-0324', 'deepseek-ai/DeepSeek-V3.1']; + break; default: localModels = getChannelModels(value); break; From 62a25874fa8ed03f64d1416b0519508d84fc2721 Mon Sep 17 00:00:00 2001 From: DD <1083962986@qq.com> Date: Mon, 8 Sep 2025 17:33:15 +0800 Subject: [PATCH 02/13] merge --- web/src/pages/Channel/EditTagModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Channel/EditTagModal.js b/web/src/pages/Channel/EditTagModal.js index 35fc1646..aedf991c 100644 --- a/web/src/pages/Channel/EditTagModal.js +++ b/web/src/pages/Channel/EditTagModal.js @@ -99,7 +99,7 @@ const EditTagModal = (props) => { localModels = ['suno_music', 'suno_lyrics']; break; case 52: - localModels = ['NousResearch/Hermes-4-405B-FP8', 'Qwen/Qwen3-235B-A22B-Thinking-2507', 'Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8', 'zai-org/GLM-4.5-FP8', 'openai/gpt-oss-120b', 'deepseek-ai/DeepSeek-R1-0528', 'deepseek-ai/DeepSeek-R1', 'deepseek-ai/DeepSeek-V3-0324', 'deepseek-ai/DeepSeek-V3.1']; + localModels = ['NousResearch/Hermes-4-405B-FP8', 'Qwen/Qwen3-235B-A22B-Thinking-2507', 'Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8','Qwen/Qwen3-235B-A22B-Instruct-2507', 'zai-org/GLM-4.5-FP8', 'openai/gpt-oss-120b', 'deepseek-ai/DeepSeek-R1-0528', 'deepseek-ai/DeepSeek-R1', 'deepseek-ai/DeepSeek-V3-0324', 'deepseek-ai/DeepSeek-V3.1']; break; default: localModels = getChannelModels(value); From 116d070bbf0c196d5b13d9333a54269cfc70fa09 Mon Sep 17 00:00:00 2001 From: DD <1083962986@qq.com> Date: Wed, 10 Sep 2025 18:37:55 +0800 Subject: [PATCH 03/13] merge --- constant/channel.go | 6 ++---- web/src/helpers/render.jsx | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/constant/channel.go b/constant/channel.go index 0cfd90cd..34fb20f4 100644 --- a/constant/channel.go +++ b/constant/channel.go @@ -49,13 +49,11 @@ const ( ChannelTypeCoze = 49 ChannelTypeKling = 50 ChannelTypeJimeng = 51 -<<<<<<< HEAD - ChannelTypeSubmodel = 52 -======= ChannelTypeVidu = 52 ->>>>>>> 041782c49e0289b9d2e64a318e81e4f75754dabf + ChannelTypeSubmodel = 53 ChannelTypeDummy // this one is only for count, do not add any channel after this + ) var ChannelBaseURLs = []string{ diff --git a/web/src/helpers/render.jsx b/web/src/helpers/render.jsx index 49e87f66..676a582b 100644 --- a/web/src/helpers/render.jsx +++ b/web/src/helpers/render.jsx @@ -342,7 +342,7 @@ export function getChannelIcon(channelType) { return ; case 21: // 知识库:AI Proxy case 44: // 嵌入模型:MokaAI M3E - case 52: // SubModel + case 53: // SubModel return null; default: return null; // 未知类型或自定义渠道不显示图标 From 68d30225e5e5f9be628b3303db809da25a80cbb5 Mon Sep 17 00:00:00 2001 From: DD <1083962986@qq.com> Date: Wed, 10 Sep 2025 19:11:58 +0800 Subject: [PATCH 04/13] merge --- relay/channel/submodel/constants.go | 2 +- setting/ratio_setting/model_ratio.go | 4 +--- web/src/components/table/channels/modals/EditTagModal.jsx | 2 +- web/src/helpers/render.jsx | 2 -- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/relay/channel/submodel/constants.go b/relay/channel/submodel/constants.go index 962682bb..f5e1feb8 100644 --- a/relay/channel/submodel/constants.go +++ b/relay/channel/submodel/constants.go @@ -13,4 +13,4 @@ var ModelList = []string{ "deepseek-ai/DeepSeek-V3.1", } -var ChannelName = "submodel" \ No newline at end of file +const ChannelName = "submodel" \ No newline at end of file diff --git a/setting/ratio_setting/model_ratio.go b/setting/ratio_setting/model_ratio.go index 26db8168..c427cfe2 100644 --- a/setting/ratio_setting/model_ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -251,7 +251,6 @@ var defaultModelRatio = map[string]float64{ "grok-vision-beta": 2.5, "grok-3-fast-beta": 2.5, "grok-3-mini-fast-beta": 0.3, - // submodel "NousResearch/Hermes-4-405B-FP8": 0.8, "Qwen/Qwen3-235B-A22B-Thinking-2507": 0.6, @@ -262,8 +261,7 @@ var defaultModelRatio = map[string]float64{ "deepseek-ai/DeepSeek-R1-0528": 0.8, "deepseek-ai/DeepSeek-R1": 0.8, "deepseek-ai/DeepSeek-V3-0324": 0.8, - "deepseek-ai/DeepSeek-V3.1": 0.8 - + "deepseek-ai/DeepSeek-V3.1": 0.8, } var defaultModelPrice = map[string]float64{ diff --git a/web/src/components/table/channels/modals/EditTagModal.jsx b/web/src/components/table/channels/modals/EditTagModal.jsx index 727b1909..752ff3dc 100644 --- a/web/src/components/table/channels/modals/EditTagModal.jsx +++ b/web/src/components/table/channels/modals/EditTagModal.jsx @@ -118,7 +118,7 @@ const EditTagModal = (props) => { case 36: localModels = ['suno_music', 'suno_lyrics']; break; - case 52: + case 53: localModels = ['NousResearch/Hermes-4-405B-FP8', 'Qwen/Qwen3-235B-A22B-Thinking-2507', 'Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8','Qwen/Qwen3-235B-A22B-Instruct-2507', 'zai-org/GLM-4.5-FP8', 'openai/gpt-oss-120b', 'deepseek-ai/DeepSeek-R1-0528', 'deepseek-ai/DeepSeek-R1', 'deepseek-ai/DeepSeek-V3-0324', 'deepseek-ai/DeepSeek-V3.1']; break; default: diff --git a/web/src/helpers/render.jsx b/web/src/helpers/render.jsx index 676a582b..3d9d8d71 100644 --- a/web/src/helpers/render.jsx +++ b/web/src/helpers/render.jsx @@ -342,8 +342,6 @@ export function getChannelIcon(channelType) { return ; case 21: // 知识库:AI Proxy case 44: // 嵌入模型:MokaAI M3E - case 53: // SubModel - return null; default: return null; // 未知类型或自定义渠道不显示图标 } From 1c48391cdb08c71efcc156a8a09e1394f1eb43ed Mon Sep 17 00:00:00 2001 From: DD <1083962986@qq.com> Date: Mon, 15 Sep 2025 14:31:31 +0800 Subject: [PATCH 05/13] add submodel icon --- web/src/helpers/render.jsx | 139 +++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/web/src/helpers/render.jsx b/web/src/helpers/render.jsx index 65332701..c6926258 100644 --- a/web/src/helpers/render.jsx +++ b/web/src/helpers/render.jsx @@ -54,6 +54,7 @@ import { FastGPT, Kling, Jimeng, + SubModel, } from '@lobehub/icons'; import { @@ -342,6 +343,8 @@ export function getChannelIcon(channelType) { return ; case 21: // 知识库:AI Proxy case 44: // 嵌入模型:MokaAI M3E + case 53: // 嵌入模型:SubModel + return ; default: return null; // 未知类型或自定义渠道不显示图标 } @@ -1191,25 +1194,25 @@ export function renderModelPrice( const extraServices = [ webSearch && webSearchCallCount > 0 ? i18next.t( - ' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', - { - count: webSearchCallCount, - price: webSearchPrice, - ratio: groupRatio, - ratioType: ratioLabel, - }, - ) + ' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', + { + count: webSearchCallCount, + price: webSearchPrice, + ratio: groupRatio, + ratioType: ratioLabel, + }, + ) : '', fileSearch && fileSearchCallCount > 0 ? i18next.t( - ' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', - { - count: fileSearchCallCount, - price: fileSearchPrice, - ratio: groupRatio, - ratioType: ratioLabel, - }, - ) + ' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', + { + count: fileSearchCallCount, + price: fileSearchPrice, + ratio: groupRatio, + ratioType: ratioLabel, + }, + ) : '', ].join(''); @@ -1379,10 +1382,10 @@ export function renderAudioModelPrice( let audioPrice = (audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio + (audioCompletionTokens / 1000000) * - inputRatioPrice * - audioRatio * - audioCompletionRatio * - groupRatio; + inputRatioPrice * + audioRatio * + audioCompletionRatio * + groupRatio; let price = textPrice + audioPrice; return ( <> @@ -1438,27 +1441,27 @@ export function renderAudioModelPrice(

{cacheTokens > 0 ? i18next.t( - '文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', - { - nonCacheInput: inputTokens - cacheTokens, - cacheInput: cacheTokens, - cachePrice: inputRatioPrice * cacheRatio, - price: inputRatioPrice, - completion: completionTokens, - compPrice: completionRatioPrice, - total: textPrice.toFixed(6), - }, - ) + '文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', + { + nonCacheInput: inputTokens - cacheTokens, + cacheInput: cacheTokens, + cachePrice: inputRatioPrice * cacheRatio, + price: inputRatioPrice, + completion: completionTokens, + compPrice: completionRatioPrice, + total: textPrice.toFixed(6), + }, + ) : i18next.t( - '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', - { - input: inputTokens, - price: inputRatioPrice, - completion: completionTokens, - compPrice: completionRatioPrice, - total: textPrice.toFixed(6), - }, - )} + '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', + { + input: inputTokens, + price: inputRatioPrice, + completion: completionTokens, + compPrice: completionRatioPrice, + total: textPrice.toFixed(6), + }, + )}

{i18next.t( @@ -1598,35 +1601,35 @@ export function renderClaudeModelPrice(

{cacheTokens > 0 || cacheCreationTokens > 0 ? i18next.t( - '提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', - { - nonCacheInput: nonCachedTokens, - cacheInput: cacheTokens, - cacheRatio: cacheRatio, - cacheCreationInput: cacheCreationTokens, - cacheCreationRatio: cacheCreationRatio, - cachePrice: cacheRatioPrice, - cacheCreationPrice: cacheCreationRatioPrice, - price: inputRatioPrice, - completion: completionTokens, - compPrice: completionRatioPrice, - ratio: groupRatio, - ratioType: ratioLabel, - total: price.toFixed(6), - }, - ) + '提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', + { + nonCacheInput: nonCachedTokens, + cacheInput: cacheTokens, + cacheRatio: cacheRatio, + cacheCreationInput: cacheCreationTokens, + cacheCreationRatio: cacheCreationRatio, + cachePrice: cacheRatioPrice, + cacheCreationPrice: cacheCreationRatioPrice, + price: inputRatioPrice, + completion: completionTokens, + compPrice: completionRatioPrice, + ratio: groupRatio, + ratioType: ratioLabel, + total: price.toFixed(6), + }, + ) : i18next.t( - '提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', - { - input: inputTokens, - price: inputRatioPrice, - completion: completionTokens, - compPrice: completionRatioPrice, - ratio: groupRatio, - ratioType: ratioLabel, - total: price.toFixed(6), - }, - )} + '提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', + { + input: inputTokens, + price: inputRatioPrice, + completion: completionTokens, + compPrice: completionRatioPrice, + ratio: groupRatio, + ratioType: ratioLabel, + total: price.toFixed(6), + }, + )}

{i18next.t('仅供参考,以实际扣费为准')}

From 64e2ae56a2fb5cb098190c1fa0c3375491cf5569 Mon Sep 17 00:00:00 2001 From: dd <1083962986@qq.com> Date: Mon, 29 Sep 2025 15:11:17 +0800 Subject: [PATCH 06/13] Update relay_adaptor.go --- relay/relay_adaptor.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/relay/relay_adaptor.go b/relay/relay_adaptor.go index 6099385c..406074c5 100644 --- a/relay/relay_adaptor.go +++ b/relay/relay_adaptor.go @@ -37,12 +37,8 @@ import ( "one-api/relay/channel/zhipu" "one-api/relay/channel/zhipu_4v" "strconv" -<<<<<<< HEAD "one-api/relay/channel/submodel" -======= - "github.com/gin-gonic/gin" ->>>>>>> 4f760a8d407d321bf7f011331ecffb2744b555fd ) func GetAdaptor(apiType int) channel.Adaptor { From d46e6d42efda916646f7bab27fc6ceb2013b0a8e Mon Sep 17 00:00:00 2001 From: dd <1083962986@qq.com> Date: Mon, 29 Sep 2025 15:14:02 +0800 Subject: [PATCH 07/13] Update render.jsx --- web/src/helpers/render.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/web/src/helpers/render.jsx b/web/src/helpers/render.jsx index 92fecde4..82d164b3 100644 --- a/web/src/helpers/render.jsx +++ b/web/src/helpers/render.jsx @@ -54,7 +54,6 @@ import { FastGPT, Kling, Jimeng, - SubModel, } from '@lobehub/icons'; import { @@ -343,8 +342,6 @@ export function getChannelIcon(channelType) { return ; case 21: // 知识库:AI Proxy case 44: // 嵌入模型:MokaAI M3E - case 53: // 嵌入模型:SubModel - return ; default: return null; // 未知类型或自定义渠道不显示图标 } From 509b23982dcb162a87f8a1e99e0564b615dca455 Mon Sep 17 00:00:00 2001 From: dd <1083962986@qq.com> Date: Mon, 29 Sep 2025 19:37:04 +0800 Subject: [PATCH 08/13] Update constant/api_type.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- constant/api_type.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/constant/api_type.go b/constant/api_type.go index b3c0a5c7..0ea5048f 100644 --- a/constant/api_type.go +++ b/constant/api_type.go @@ -31,7 +31,7 @@ const ( APITypeXai APITypeCoze APITypeJimeng - APITypeMoonshot // this one is only for count, do not add any channel after this - APITypeDummy // this one is only for count, do not add any channel after this - APITypeSubmodel + APITypeMoonshot + APITypeSubmodel + APITypeDummy // this one is only for count, do not add any channel after this ) From 60045cdceba10aa798e97aaa27528d97926f33e4 Mon Sep 17 00:00:00 2001 From: Seefs Date: Mon, 29 Sep 2025 21:55:34 +0800 Subject: [PATCH 09/13] fix: submodel adapter --- relay/channel/submodel/adaptor.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/relay/channel/submodel/adaptor.go b/relay/channel/submodel/adaptor.go index 152391d0..db58fe64 100644 --- a/relay/channel/submodel/adaptor.go +++ b/relay/channel/submodel/adaptor.go @@ -16,6 +16,10 @@ import ( type Adaptor struct { } +func (a *Adaptor) ConvertGeminiRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeminiChatRequest) (any, error) { + return nil, errors.New("submodel channel: endpoint not supported") +} + func (a *Adaptor) ConvertClaudeRequest(*gin.Context, *relaycommon.RelayInfo, *dto.ClaudeRequest) (any, error) { return nil, errors.New("submodel channel: endpoint not supported") } @@ -32,7 +36,7 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) { } func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { - return relaycommon.GetFullRequestURL(info.BaseUrl, info.RequestURLPath, info.ChannelType), nil + return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, info.RequestURLPath, info.ChannelType), nil } func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error { From c00e5ca201af52284ce469b0fe6812a7f9d5f834 Mon Sep 17 00:00:00 2001 From: RedwindA Date: Mon, 29 Sep 2025 22:16:25 +0800 Subject: [PATCH 10/13] fix: Optimize sidebar refresh to avoid redundant loading states --- web/src/components/layout/SiderBar.jsx | 2 +- web/src/hooks/common/useHeaderBar.js | 2 +- web/src/hooks/common/useSidebar.js | 46 +++++++++++++++++++++----- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/web/src/components/layout/SiderBar.jsx b/web/src/components/layout/SiderBar.jsx index 793e4835..39d6d448 100644 --- a/web/src/components/layout/SiderBar.jsx +++ b/web/src/components/layout/SiderBar.jsx @@ -58,7 +58,7 @@ const SiderBar = ({ onNavigate = () => {} }) => { loading: sidebarLoading, } = useSidebar(); - const showSkeleton = useMinimumLoadingTime(sidebarLoading); + const showSkeleton = useMinimumLoadingTime(sidebarLoading, 200); const [selectedKeys, setSelectedKeys] = useState(['home']); const [chatItems, setChatItems] = useState([]); diff --git a/web/src/hooks/common/useHeaderBar.js b/web/src/hooks/common/useHeaderBar.js index 3458a1d1..f3ec8696 100644 --- a/web/src/hooks/common/useHeaderBar.js +++ b/web/src/hooks/common/useHeaderBar.js @@ -40,7 +40,7 @@ export const useHeaderBar = ({ onMobileMenuToggle, drawerOpen }) => { const location = useLocation(); const loading = statusState?.status === undefined; - const isLoading = useMinimumLoadingTime(loading); + const isLoading = useMinimumLoadingTime(loading, 200); const systemName = getSystemName(); const logo = getLogo(); diff --git a/web/src/hooks/common/useSidebar.js b/web/src/hooks/common/useSidebar.js index 13d76fd8..0ccc5835 100644 --- a/web/src/hooks/common/useSidebar.js +++ b/web/src/hooks/common/useSidebar.js @@ -17,7 +17,7 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import { useState, useEffect, useMemo, useContext } from 'react'; +import { useState, useEffect, useMemo, useContext, useRef } from 'react'; import { StatusContext } from '../../context/Status'; import { API } from '../../helpers'; @@ -29,6 +29,13 @@ export const useSidebar = () => { const [statusState] = useContext(StatusContext); const [userConfig, setUserConfig] = useState(null); const [loading, setLoading] = useState(true); + const instanceIdRef = useRef(null); + const hasLoadedOnceRef = useRef(false); + + if (!instanceIdRef.current) { + const randomPart = Math.random().toString(16).slice(2); + instanceIdRef.current = `sidebar-${Date.now()}-${randomPart}`; + } // 默认配置 const defaultAdminConfig = { @@ -74,9 +81,17 @@ export const useSidebar = () => { }, [statusState?.status?.SidebarModulesAdmin]); // 加载用户配置的通用方法 - const loadUserConfig = async () => { + const loadUserConfig = async ({ withLoading } = {}) => { + const shouldShowLoader = + typeof withLoading === 'boolean' + ? withLoading + : !hasLoadedOnceRef.current; + try { - setLoading(true); + if (shouldShowLoader) { + setLoading(true); + } + const res = await API.get('/api/user/self'); if (res.data.success && res.data.data.sidebar_modules) { let config; @@ -122,18 +137,25 @@ export const useSidebar = () => { }); setUserConfig(defaultUserConfig); } finally { - setLoading(false); + if (shouldShowLoader) { + setLoading(false); + } + hasLoadedOnceRef.current = true; } }; // 刷新用户配置的方法(供外部调用) const refreshUserConfig = async () => { - if (Object.keys(adminConfig).length > 0) { - await loadUserConfig(); + if (Object.keys(adminConfig).length > 0) { + await loadUserConfig({ withLoading: false }); } // 触发全局刷新事件,通知所有useSidebar实例更新 - sidebarEventTarget.dispatchEvent(new CustomEvent(SIDEBAR_REFRESH_EVENT)); + sidebarEventTarget.dispatchEvent( + new CustomEvent(SIDEBAR_REFRESH_EVENT, { + detail: { sourceId: instanceIdRef.current, skipLoader: true }, + }), + ); }; // 加载用户配置 @@ -146,9 +168,15 @@ export const useSidebar = () => { // 监听全局刷新事件 useEffect(() => { - const handleRefresh = () => { + const handleRefresh = (event) => { + if (event?.detail?.sourceId === instanceIdRef.current) { + return; + } + if (Object.keys(adminConfig).length > 0) { - loadUserConfig(); + loadUserConfig({ + withLoading: event?.detail?.skipLoader ? false : undefined, + }); } }; From 112ab0b17ca75ee09968e4d6404b975cfff0461b Mon Sep 17 00:00:00 2001 From: papersnake Date: Tue, 30 Sep 2025 09:14:12 +0800 Subject: [PATCH 11/13] feat: support claude-sonnet-4-5-20250929 --- relay/channel/aws/constants.go | 6 ++++++ relay/channel/claude/constants.go | 2 ++ relay/channel/vertex/adaptor.go | 1 + setting/ratio_setting/cache_ratio.go | 4 ++++ setting/ratio_setting/model_ratio.go | 1 + 5 files changed, 14 insertions(+) diff --git a/relay/channel/aws/constants.go b/relay/channel/aws/constants.go index 5ac7ce99..45112d23 100644 --- a/relay/channel/aws/constants.go +++ b/relay/channel/aws/constants.go @@ -16,6 +16,7 @@ var awsModelIDMap = map[string]string{ "claude-sonnet-4-20250514": "anthropic.claude-sonnet-4-20250514-v1:0", "claude-opus-4-20250514": "anthropic.claude-opus-4-20250514-v1:0", "claude-opus-4-1-20250805": "anthropic.claude-opus-4-1-20250805-v1:0", + "claude-sonnet-4-5-20250929": "anthropic.claude-sonnet-4-5-20250929-v1:0", // Nova models "nova-micro-v1:0": "amazon.nova-micro-v1:0", "nova-lite-v1:0": "amazon.nova-lite-v1:0", @@ -69,6 +70,11 @@ var awsModelCanCrossRegionMap = map[string]map[string]bool{ "anthropic.claude-opus-4-1-20250805-v1:0": { "us": true, }, + "anthropic.claude-sonnet-4-5-20250929-v1:0": { + "us": true, + "ap": true, + "eu": true, + }, // Nova models - all support three major regions "amazon.nova-micro-v1:0": { "us": true, diff --git a/relay/channel/claude/constants.go b/relay/channel/claude/constants.go index a23543d2..d0b36fe4 100644 --- a/relay/channel/claude/constants.go +++ b/relay/channel/claude/constants.go @@ -19,6 +19,8 @@ var ModelList = []string{ "claude-opus-4-20250514-thinking", "claude-opus-4-1-20250805", "claude-opus-4-1-20250805-thinking", + "claude-sonnet-4-5-20250929", + "claude-sonnet-4-5-20250929-thinking", } var ChannelName = "claude" diff --git a/relay/channel/vertex/adaptor.go b/relay/channel/vertex/adaptor.go index a424cb1a..91a7f88c 100644 --- a/relay/channel/vertex/adaptor.go +++ b/relay/channel/vertex/adaptor.go @@ -37,6 +37,7 @@ var claudeModelMap = map[string]string{ "claude-sonnet-4-20250514": "claude-sonnet-4@20250514", "claude-opus-4-20250514": "claude-opus-4@20250514", "claude-opus-4-1-20250805": "claude-opus-4-1@20250805", + "claude-sonnet-4-5-20250929": "claude-sonnet-4-5@20250929", } const anthropicVersion = "vertex-2023-10-16" diff --git a/setting/ratio_setting/cache_ratio.go b/setting/ratio_setting/cache_ratio.go index 5993cdee..8e4b227a 100644 --- a/setting/ratio_setting/cache_ratio.go +++ b/setting/ratio_setting/cache_ratio.go @@ -52,6 +52,8 @@ var defaultCacheRatio = map[string]float64{ "claude-opus-4-20250514-thinking": 0.1, "claude-opus-4-1-20250805": 0.1, "claude-opus-4-1-20250805-thinking": 0.1, + "claude-sonnet-4-5-20250929": 0.1, + "claude-sonnet-4-5-20250929-thinking": 0.1, } var defaultCreateCacheRatio = map[string]float64{ @@ -69,6 +71,8 @@ var defaultCreateCacheRatio = map[string]float64{ "claude-opus-4-20250514-thinking": 1.25, "claude-opus-4-1-20250805": 1.25, "claude-opus-4-1-20250805-thinking": 1.25, + "claude-sonnet-4-5-20250929": 1.25, + "claude-sonnet-4-5-20250929-thinking": 1.25, } //var defaultCreateCacheRatio = map[string]float64{} diff --git a/setting/ratio_setting/model_ratio.go b/setting/ratio_setting/model_ratio.go index 887c5bd5..2244593a 100644 --- a/setting/ratio_setting/model_ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -141,6 +141,7 @@ var defaultModelRatio = map[string]float64{ "claude-3-7-sonnet-20250219": 1.5, "claude-3-7-sonnet-20250219-thinking": 1.5, "claude-sonnet-4-20250514": 1.5, + "claude-sonnet-4-5-20250929": 1.5, "claude-3-opus-20240229": 7.5, // $15 / 1M tokens "claude-opus-4-20250514": 7.5, "claude-opus-4-1-20250805": 7.5, From a7f6065f425e9a94c727f7d8aa59c1e106cff522 Mon Sep 17 00:00:00 2001 From: Seefs Date: Tue, 30 Sep 2025 09:22:40 +0800 Subject: [PATCH 12/13] feat: claude context editing --- dto/claude.go | 9 +++++---- relay/channel/aws/adaptor.go | 4 ++++ relay/channel/claude/adaptor.go | 4 ++++ web/src/i18n/locales/en.json | 1 + web/src/i18n/locales/fr.json | 1 + web/src/pages/Setting/Model/SettingClaudeModel.jsx | 3 +-- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/dto/claude.go b/dto/claude.go index 963e588b..42774226 100644 --- a/dto/claude.go +++ b/dto/claude.go @@ -196,10 +196,11 @@ type ClaudeRequest struct { TopP float64 `json:"top_p,omitempty"` TopK int `json:"top_k,omitempty"` //ClaudeMetadata `json:"metadata,omitempty"` - Stream bool `json:"stream,omitempty"` - Tools any `json:"tools,omitempty"` - ToolChoice any `json:"tool_choice,omitempty"` - Thinking *Thinking `json:"thinking,omitempty"` + Stream bool `json:"stream,omitempty"` + Tools any `json:"tools,omitempty"` + ContextManagement json.RawMessage `json:"context_management,omitempty"` + ToolChoice any `json:"tool_choice,omitempty"` + Thinking *Thinking `json:"thinking,omitempty"` } func (c *ClaudeRequest) GetTokenCountMeta() *types.TokenCountMeta { diff --git a/relay/channel/aws/adaptor.go b/relay/channel/aws/adaptor.go index 9d5e5891..6202c9fc 100644 --- a/relay/channel/aws/adaptor.go +++ b/relay/channel/aws/adaptor.go @@ -52,6 +52,10 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { } func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error { + anthropicBeta := c.Request.Header.Get("anthropic-beta") + if anthropicBeta != "" { + req.Set("anthropic-beta", anthropicBeta) + } model_setting.GetClaudeSettings().WriteHeaders(info.OriginModelName, req) return nil } diff --git a/relay/channel/claude/adaptor.go b/relay/channel/claude/adaptor.go index 959327e1..fca26966 100644 --- a/relay/channel/claude/adaptor.go +++ b/relay/channel/claude/adaptor.go @@ -67,6 +67,10 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *rel anthropicVersion = "2023-06-01" } req.Set("anthropic-version", anthropicVersion) + anthropicBeta := c.Request.Header.Get("anthropic-beta") + if anthropicBeta != "" { + req.Set("anthropic-beta", anthropicBeta) + } model_setting.GetClaudeSettings().WriteHeaders(info.OriginModelName, req) return nil } diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 8a9d32f2..4e6c0ba8 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1404,6 +1404,7 @@ "Claude思考适配 BudgetTokens = MaxTokens * BudgetTokens 百分比": "Claude thinking adaptation BudgetTokens = MaxTokens * BudgetTokens percentage", "思考适配 BudgetTokens 百分比": "Thinking adaptation BudgetTokens percentage", "0.1-1之间的小数": "Decimal between 0.1 and 1", + "0.1以上的小数": "Decimal above 0.1", "模型相关设置": "Model related settings", "收起侧边栏": "Collapse sidebar", "展开侧边栏": "Expand sidebar", diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index 35a635bd..3a216e53 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -1404,6 +1404,7 @@ "Claude思考适配 BudgetTokens = MaxTokens * BudgetTokens 百分比": "Adaptation de la pensée Claude BudgetTokens = MaxTokens * BudgetTokens pourcentage", "思考适配 BudgetTokens 百分比": "Adaptation de la pensée BudgetTokens pourcentage", "0.1-1之间的小数": "Décimal entre 0,1 et 1", + "0.1以上的小数": "Décimal supérieur à 0,1", "模型相关设置": "Paramètres liés au modèle", "收起侧边栏": "Réduire la barre latérale", "展开侧边栏": "Développer la barre latérale", diff --git a/web/src/pages/Setting/Model/SettingClaudeModel.jsx b/web/src/pages/Setting/Model/SettingClaudeModel.jsx index 04d7956a..688fc2d3 100644 --- a/web/src/pages/Setting/Model/SettingClaudeModel.jsx +++ b/web/src/pages/Setting/Model/SettingClaudeModel.jsx @@ -202,9 +202,8 @@ export default function SettingClaudeModel(props) { label={t('思考适配 BudgetTokens 百分比')} field={'claude.thinking_adapter_budget_tokens_percentage'} initValue={''} - extraText={t('0.1-1之间的小数')} + extraText={t('0.1以上的小数')} min={0.1} - max={1} onChange={(value) => setInputs({ ...inputs, From 946da49721194018860e49fc51d24e01ba8e0604 Mon Sep 17 00:00:00 2001 From: Seefs Date: Tue, 30 Sep 2025 09:46:46 +0800 Subject: [PATCH 13/13] fix: claude beta=true --- relay/channel/claude/adaptor.go | 9 +++++++-- relay/common/relay_info.go | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/relay/channel/claude/adaptor.go b/relay/channel/claude/adaptor.go index 959327e1..59f7dd0a 100644 --- a/relay/channel/claude/adaptor.go +++ b/relay/channel/claude/adaptor.go @@ -52,11 +52,16 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) { } func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { + baseURL := "" if a.RequestMode == RequestModeMessage { - return fmt.Sprintf("%s/v1/messages", info.ChannelBaseUrl), nil + baseURL = fmt.Sprintf("%s/v1/messages", info.ChannelBaseUrl) } else { - return fmt.Sprintf("%s/v1/complete", info.ChannelBaseUrl), nil + baseURL = fmt.Sprintf("%s/v1/complete", info.ChannelBaseUrl) } + if info.IsClaudeBetaQuery { + baseURL = baseURL + "?beta=true" + } + return baseURL, nil } func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error { diff --git a/relay/common/relay_info.go b/relay/common/relay_info.go index 99925dc5..f4ffaee2 100644 --- a/relay/common/relay_info.go +++ b/relay/common/relay_info.go @@ -105,7 +105,8 @@ type RelayInfo struct { UserQuota int RelayFormat types.RelayFormat SendResponseCount int - FinalPreConsumedQuota int // 最终预消耗的配额 + FinalPreConsumedQuota int // 最终预消耗的配额 + IsClaudeBetaQuery bool // /v1/messages?beta=true PriceData types.PriceData @@ -279,6 +280,9 @@ func GenRelayInfoClaude(c *gin.Context, request dto.Request) *RelayInfo { info.ClaudeConvertInfo = &ClaudeConvertInfo{ LastMessagesType: LastMessageTypeNone, } + if c.Query("beta") == "true" { + info.IsClaudeBetaQuery = true + } return info }