Date: Wed, 10 Sep 2025 22:36:03 +0800
Subject: [PATCH 05/53] fix: get video task err when Content-Type=json
---
middleware/distributor.go | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/middleware/distributor.go b/middleware/distributor.go
index 1e6df872d..3d929df49 100644
--- a/middleware/distributor.go
+++ b/middleware/distributor.go
@@ -166,9 +166,12 @@ func getModelRequest(c *gin.Context) (*ModelRequest, bool, error) {
c.Set("platform", string(constant.TaskPlatformSuno))
c.Set("relay_mode", relayMode)
} else if strings.Contains(c.Request.URL.Path, "/v1/video/generations") {
- err = common.UnmarshalBodyReusable(c, &modelRequest)
relayMode := relayconstant.RelayModeUnknown
if c.Request.Method == http.MethodPost {
+ err = common.UnmarshalBodyReusable(c, &modelRequest)
+ if err != nil {
+ return nil, false, errors.New("video无效的请求, " + err.Error())
+ }
relayMode = relayconstant.RelayModeVideoSubmit
} else if c.Request.Method == http.MethodGet {
relayMode = relayconstant.RelayModeVideoFetchByID
From 274872b8e5dee8334f7658be14a6df5048f056fd Mon Sep 17 00:00:00 2001
From: DD <1083962986@qq.com>
Date: Mon, 15 Sep 2025 14:31:31 +0800
Subject: [PATCH 06/53] 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 65332701b..c69262582 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 f2e9fd7afb91a5ab7c988401927b5f36e7a960c5 Mon Sep 17 00:00:00 2001
From: wzxjohn
Date: Tue, 16 Sep 2025 17:18:32 +0800
Subject: [PATCH 07/53] fix(relay): wrong URL for claude model in GCP Vertex AI
---
relay/channel/vertex/adaptor.go | 59 ++++++++++++++++++++++-----------
1 file changed, 39 insertions(+), 20 deletions(-)
diff --git a/relay/channel/vertex/adaptor.go b/relay/channel/vertex/adaptor.go
index a424cb1a4..6398b8f62 100644
--- a/relay/channel/vertex/adaptor.go
+++ b/relay/channel/vertex/adaptor.go
@@ -90,7 +90,43 @@ func (a *Adaptor) getRequestUrl(info *relaycommon.RelayInfo, modelName, suffix s
}
a.AccountCredentials = *adc
- if a.RequestMode == RequestModeLlama {
+ if a.RequestMode == RequestModeGemini {
+ if region == "global" {
+ return fmt.Sprintf(
+ "https://aiplatform.googleapis.com/v1/projects/%s/locations/global/publishers/google/models/%s:%s",
+ adc.ProjectID,
+ modelName,
+ suffix,
+ ), nil
+ } else {
+ return fmt.Sprintf(
+ "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:%s",
+ region,
+ adc.ProjectID,
+ region,
+ modelName,
+ suffix,
+ ), nil
+ }
+ } else if a.RequestMode == RequestModeClaude {
+ if region == "global" {
+ return fmt.Sprintf(
+ "https://aiplatform.googleapis.com/v1/projects/%s/locations/global/publishers/anthropic/models/%s:%s",
+ adc.ProjectID,
+ modelName,
+ suffix,
+ ), nil
+ } else {
+ return fmt.Sprintf(
+ "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/anthropic/models/%s:%s",
+ region,
+ adc.ProjectID,
+ region,
+ modelName,
+ suffix,
+ ), nil
+ }
+ } else if a.RequestMode == RequestModeLlama {
return fmt.Sprintf(
"https://%s-aiplatform.googleapis.com/v1beta1/projects/%s/locations/%s/endpoints/openapi/chat/completions",
region,
@@ -98,24 +134,6 @@ func (a *Adaptor) getRequestUrl(info *relaycommon.RelayInfo, modelName, suffix s
region,
), nil
}
-
- if region == "global" {
- return fmt.Sprintf(
- "https://aiplatform.googleapis.com/v1/projects/%s/locations/global/publishers/google/models/%s:%s",
- adc.ProjectID,
- modelName,
- suffix,
- ), nil
- } else {
- return fmt.Sprintf(
- "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:%s",
- region,
- adc.ProjectID,
- region,
- modelName,
- suffix,
- ), nil
- }
} else {
if region == "global" {
return fmt.Sprintf(
@@ -134,6 +152,7 @@ func (a *Adaptor) getRequestUrl(info *relaycommon.RelayInfo, modelName, suffix s
), nil
}
}
+ return "", errors.New("unsupported request mode")
}
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
@@ -187,7 +206,7 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *rel
}
req.Set("Authorization", "Bearer "+accessToken)
}
- if a.AccountCredentials.ProjectID != "" {
+ if a.AccountCredentials.ProjectID != "" {
req.Set("x-goog-user-project", a.AccountCredentials.ProjectID)
}
return nil
From 8d92ce38ed2be32295a69926f843023dabdc7ef0 Mon Sep 17 00:00:00 2001
From: wzxjohn
Date: Fri, 19 Sep 2025 11:22:03 +0800
Subject: [PATCH 08/53] fix(relay): wrong key param while enable sse
---
relay/channel/vertex/adaptor.go | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/relay/channel/vertex/adaptor.go b/relay/channel/vertex/adaptor.go
index 6398b8f62..742366b13 100644
--- a/relay/channel/vertex/adaptor.go
+++ b/relay/channel/vertex/adaptor.go
@@ -135,19 +135,27 @@ func (a *Adaptor) getRequestUrl(info *relaycommon.RelayInfo, modelName, suffix s
), nil
}
} else {
+ var keyPrefix string
+ if strings.HasSuffix(suffix, "?alt=sse") {
+ keyPrefix = "&"
+ } else {
+ keyPrefix = "?"
+ }
if region == "global" {
return fmt.Sprintf(
- "https://aiplatform.googleapis.com/v1/publishers/google/models/%s:%s?key=%s",
+ "https://aiplatform.googleapis.com/v1/publishers/google/models/%s:%s%skey=%s",
modelName,
suffix,
+ keyPrefix,
info.ApiKey,
), nil
} else {
return fmt.Sprintf(
- "https://%s-aiplatform.googleapis.com/v1/publishers/google/models/%s:%s?key=%s",
+ "https://%s-aiplatform.googleapis.com/v1/publishers/google/models/%s:%s%skey=%s",
region,
modelName,
suffix,
+ keyPrefix,
info.ApiKey,
), nil
}
From ec9903e6404ee6dfed5d3ae9e236802cd0445c3c Mon Sep 17 00:00:00 2001
From: JoeyLearnsToCode
Date: Fri, 19 Sep 2025 18:09:26 +0800
Subject: [PATCH 09/53] feat: jump between section on channel edit page
---
.../channels/modals/EditChannelModal.jsx | 138 +++++++++++++++---
1 file changed, 116 insertions(+), 22 deletions(-)
diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx
index c0a216246..07d4f3925 100644
--- a/web/src/components/table/channels/modals/EditChannelModal.jsx
+++ b/web/src/components/table/channels/modals/EditChannelModal.jsx
@@ -66,6 +66,8 @@ import {
IconCode,
IconGlobe,
IconBolt,
+ IconChevronUp,
+ IconChevronDown,
} from '@douyinfe/semi-icons';
const { Text, Title } = Typography;
@@ -184,6 +186,18 @@ const EditChannelModal = (props) => {
const [verifyCode, setVerifyCode] = useState('');
const [verifyLoading, setVerifyLoading] = useState(false);
+ // 表单块导航相关状态
+ const formSectionRefs = useRef({
+ basicInfo: null,
+ apiConfig: null,
+ modelConfig: null,
+ advancedSettings: null,
+ channelExtraSettings: null,
+ });
+ const [currentSectionIndex, setCurrentSectionIndex] = useState(0);
+ const formSections = ['basicInfo', 'apiConfig', 'modelConfig', 'advancedSettings', 'channelExtraSettings'];
+ const formContainerRef = useRef(null);
+
// 2FA状态更新辅助函数
const updateTwoFAState = (updates) => {
setTwoFAState((prev) => ({ ...prev, ...updates }));
@@ -207,6 +221,37 @@ const EditChannelModal = (props) => {
setVerifyLoading(false);
};
+ // 表单导航功能
+ const scrollToSection = (sectionKey) => {
+ const sectionElement = formSectionRefs.current[sectionKey];
+ if (sectionElement) {
+ sectionElement.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start',
+ inline: 'nearest'
+ });
+ }
+ };
+
+ const navigateToSection = (direction) => {
+ const availableSections = formSections.filter(section => {
+ if (section === 'apiConfig') {
+ return showApiConfigCard;
+ }
+ return true;
+ });
+
+ let newIndex;
+ if (direction === 'up') {
+ newIndex = currentSectionIndex > 0 ? currentSectionIndex - 1 : availableSections.length - 1;
+ } else {
+ newIndex = currentSectionIndex < availableSections.length - 1 ? currentSectionIndex + 1 : 0;
+ }
+
+ setCurrentSectionIndex(newIndex);
+ scrollToSection(availableSections[newIndex]);
+ };
+
// 渠道额外设置状态
const [channelSettings, setChannelSettings] = useState({
force_format: false,
@@ -672,6 +717,8 @@ const EditChannelModal = (props) => {
fetchModelGroups();
// 重置手动输入模式状态
setUseManualInput(false);
+ // 重置导航状态
+ setCurrentSectionIndex(0);
} else {
// 统一的模态框关闭重置逻辑
resetModalState();
@@ -1108,7 +1155,41 @@ const EditChannelModal = (props) => {
visible={props.visible}
width={isMobile ? '100%' : 600}
footer={
-
+
+
+ }
+ onClick={() => navigateToSection('up')}
+ style={{
+ borderRadius: '50%',
+ width: '32px',
+ height: '32px',
+ padding: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
+ }}
+ title={t('上一个表单块')}
+ />
+ }
+ onClick={() => navigateToSection('down')}
+ style={{
+ borderRadius: '50%',
+ width: '32px',
+ height: '32px',
+ padding: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
+ }}
+ title={t('下一个表单块')}
+ />
+