fix: channel affinity (#2799)

* fix: channel affinity log styles

* fix: Issue with incorrect data storage when switching key sources

* feat: support not retrying after a single rule configuration fails

* fix: render channel affinity tooltip as multiline content

* feat: channel affinity cache hit

* fix: prevent ChannelAffinityUsageCacheModal infinite loading and hide data before fetch

* chore: format backend with gofmt and frontend with prettier/eslint autofix
This commit is contained in:
Seefs
2026-02-02 14:37:31 +08:00
committed by GitHub
parent 80a609b7c6
commit f244a9e661
61 changed files with 2012 additions and 1004 deletions
@@ -199,9 +199,9 @@ export default function SettingGlobalModel(props) {
'global.pass_through_request_enabled': value,
})
}
extraText={
t('开启后,所有请求将直接透传给上游,不会进行任何处理(重定向和渠道适配也将失效),请谨慎开启')
}
extraText={t(
'开启后,所有请求将直接透传给上游,不会进行任何处理(重定向和渠道适配也将失效),请谨慎开启',
)}
/>
</Col>
</Row>
@@ -210,11 +210,7 @@ export default function SettingGlobalModel(props) {
<Form.TextArea
label={t('禁用思考处理的模型列表')}
field={'global.thinking_model_blacklist'}
placeholder={
t('例如:') +
'\n' +
thinkingExample
}
placeholder={t('例如:') + '\n' + thinkingExample}
rows={4}
rules={[
{
@@ -270,12 +266,12 @@ export default function SettingGlobalModel(props) {
<Row style={{ marginTop: 10 }}>
<Col span={24}>
<Form.TextArea
label={t('参数配置')}
field={chatCompletionsToResponsesPolicyKey}
placeholder={
t('例如(指定渠道):') +
'\n' +
<Form.TextArea
label={t('参数配置')}
field={chatCompletionsToResponsesPolicyKey}
placeholder={
t('例如(指定渠道):') +
'\n' +
chatCompletionsToResponsesPolicyExample +
'\n\n' +
t('例如(全渠道):') +
@@ -370,7 +366,9 @@ export default function SettingGlobalModel(props) {
<Col span={24}>
<Banner
type='warning'
description={t('警告:启用保活后,如果已经写入保活数据后渠道出错,系统无法重试,如果必须开启,推荐设置尽可能大的Ping间隔')}
description={t(
'警告:启用保活后,如果已经写入保活数据后渠道出错,系统无法重试,如果必须开启,推荐设置尽可能大的Ping间隔',
)}
/>
</Col>
</Row>
@@ -49,8 +49,7 @@ export default function SettingGrokModel(props) {
.validate()
.then(() => {
const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length)
return showWarning(t('你似乎并没有修改什么'));
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => {
const value = String(inputs[item.key]);
@@ -18,7 +18,15 @@ For commercial licensing, please contact support@quantumnous.com
*/
import React, { useEffect, useState, useRef } from 'react';
import { Button, Col, Form, Row, Spin, Card, Typography } from '@douyinfe/semi-ui';
import {
Button,
Col,
Form,
Row,
Spin,
Card,
Typography,
} from '@douyinfe/semi-ui';
import {
compareObjects,
API,
@@ -88,9 +96,7 @@ export default function SettingModelDeployment(props) {
showError(t('网络连接失败,请检查网络设置或稍后重试'));
} else {
const rawMessage =
error?.response?.data?.message ||
error?.message ||
'';
error?.response?.data?.message || error?.message || '';
const localizedMessage = rawMessage
? getLocalizedMessage(rawMessage)
: t('未知错误');
@@ -104,7 +110,7 @@ export default function SettingModelDeployment(props) {
function onSubmit() {
const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => {
let value = String(inputs[item.key]);
return API.put('/api/option/', {
@@ -112,7 +118,7 @@ export default function SettingModelDeployment(props) {
value,
});
});
setLoading(true);
Promise.all(requestQueue)
.then((res) => {
@@ -141,7 +147,7 @@ export default function SettingModelDeployment(props) {
'model_deployment.ionet.api_key': '',
'model_deployment.ionet.enabled': false,
};
const currentInputs = {};
for (let key in defaultInputs) {
if (props.options.hasOwnProperty(key)) {
@@ -150,7 +156,7 @@ export default function SettingModelDeployment(props) {
currentInputs[key] = defaultInputs[key];
}
}
setInputs(currentInputs);
setInputsRow(structuredClone(currentInputs));
refForm.current?.setValues(currentInputs);
@@ -165,9 +171,11 @@ export default function SettingModelDeployment(props) {
getFormApi={(formAPI) => (refForm.current = formAPI)}
style={{ marginBottom: 15 }}
>
<Form.Section
<Form.Section
text={
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
>
<span>{t('模型部署设置')}</span>
</div>
}
@@ -186,7 +194,9 @@ export default function SettingModelDeployment(props) {
<Card
title={
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
>
<Cloud size={18} />
<span>io.net</span>
</div>
@@ -226,18 +236,16 @@ export default function SettingModelDeployment(props) {
}
disabled={!inputs['model_deployment.ionet.enabled']}
extraText={t('请使用 Project 为 io.cloud 的密钥')}
mode="password"
mode='password'
/>
<div style={{ display: 'flex', gap: '12px' }}>
<Button
type="outline"
size="small"
type='outline'
size='small'
icon={<Zap size={16} />}
onClick={testApiKey}
loading={testing}
disabled={
!inputs['model_deployment.ionet.enabled']
}
disabled={!inputs['model_deployment.ionet.enabled']}
style={{
height: '32px',
fontSize: '13px',
@@ -271,7 +279,10 @@ export default function SettingModelDeployment(props) {
}}
>
<div>
<Text strong style={{ display: 'block', marginBottom: '8px' }}>
<Text
strong
style={{ display: 'block', marginBottom: '8px' }}
>
{t('获取 io.net API Key')}
</Text>
<ul
@@ -287,14 +298,16 @@ export default function SettingModelDeployment(props) {
}}
>
<li>{t('访问 io.net 控制台的 API Keys 页面')}</li>
<li>{t('创建或选择密钥时,将 Project 设置为 io.cloud')}</li>
<li>
{t('创建或选择密钥时,将 Project 设置为 io.cloud')}
</li>
<li>{t('复制生成的密钥并粘贴到此处')}</li>
</ul>
</div>
<Button
icon={<ArrowUpRight size={16} />}
type="primary"
theme="solid"
type='primary'
theme='solid'
style={{ width: '100%' }}
onClick={() =>
window.open('https://ai.io.net/ai/api-keys', '_blank')
@@ -308,7 +321,7 @@ export default function SettingModelDeployment(props) {
</Card>
<Row>
<Button size='default' type="primary" onClick={onSubmit}>
<Button size='default' type='primary' onClick={onSubmit}>
{t('保存设置')}
</Button>
</Row>