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 6c0e9403a2
commit 540cf6c991
61 changed files with 2012 additions and 1004 deletions

View File

@@ -55,13 +55,20 @@ export const useModelDeploymentSettings = () => {
const isIoNetEnabled = settings['model_deployment.ionet.enabled'];
const buildConnectionError = (rawMessage, fallbackMessage = 'Connection failed') => {
const buildConnectionError = (
rawMessage,
fallbackMessage = 'Connection failed',
) => {
const message = (rawMessage || fallbackMessage).trim();
const normalized = message.toLowerCase();
if (normalized.includes('expired') || normalized.includes('expire')) {
return { type: 'expired', message };
}
if (normalized.includes('invalid') || normalized.includes('unauthorized') || normalized.includes('api key')) {
if (
normalized.includes('invalid') ||
normalized.includes('unauthorized') ||
normalized.includes('api key')
) {
return { type: 'invalid', message };
}
if (normalized.includes('network') || normalized.includes('timeout')) {
@@ -85,7 +92,11 @@ export const useModelDeploymentSettings = () => {
}
const message = response?.data?.message || 'Connection failed';
setConnectionState({ loading: false, ok: false, error: buildConnectionError(message) });
setConnectionState({
loading: false,
ok: false,
error: buildConnectionError(message),
});
} catch (error) {
if (error?.code === 'ERR_NETWORK') {
setConnectionState({
@@ -95,8 +106,13 @@ export const useModelDeploymentSettings = () => {
});
return;
}
const rawMessage = error?.response?.data?.message || error?.message || 'Unknown error';
setConnectionState({ loading: false, ok: false, error: buildConnectionError(rawMessage, 'Connection failed') });
const rawMessage =
error?.response?.data?.message || error?.message || 'Unknown error';
setConnectionState({
loading: false,
ok: false,
error: buildConnectionError(rawMessage, 'Connection failed'),
});
}
}, []);

View File

@@ -231,7 +231,10 @@ export const useApiRequest = (
if (data.choices?.[0]) {
const choice = data.choices[0];
let content = choice.message?.content || '';
let reasoningContent = choice.message?.reasoning_content || choice.message?.reasoning || '';
let reasoningContent =
choice.message?.reasoning_content ||
choice.message?.reasoning ||
'';
const processed = processThinkTags(content, reasoningContent);
@@ -318,8 +321,8 @@ export const useApiRequest = (
isStreamComplete = true; // 标记流正常完成
source.close();
sseSourceRef.current = null;
setDebugData((prev) => ({
...prev,
setDebugData((prev) => ({
...prev,
response: responseData,
sseMessages: [...(prev.sseMessages || []), '[DONE]'], // 添加 DONE 标记
isStreaming: false,

View File

@@ -36,18 +36,23 @@ import { processIncompleteThinkTags } from '../../helpers';
export const usePlaygroundState = () => {
const { t } = useTranslation();
// 使用惰性初始化,确保只在组件首次挂载时加载配置和消息
const [savedConfig] = useState(() => loadConfig());
const [initialMessages] = useState(() => {
const loaded = loadMessages();
// 检查是否是旧的中文默认消息,如果是则清除
if (loaded && loaded.length === 2 && loaded[0].id === '2' && loaded[1].id === '3') {
const hasOldChinese =
loaded[0].content === '你好' ||
if (
loaded &&
loaded.length === 2 &&
loaded[0].id === '2' &&
loaded[1].id === '3'
) {
const hasOldChinese =
loaded[0].content === '你好' ||
loaded[1].content === '你好,请问有什么可以帮助您的吗?' ||
loaded[1].content === '你好!很高兴见到你。有什么我可以帮助你的吗?';
if (hasOldChinese) {
// 清除旧的默认消息
localStorage.removeItem('playground_messages');
@@ -81,8 +86,10 @@ export const usePlaygroundState = () => {
const [status, setStatus] = useState({});
// 消息相关状态 - 使用加载的消息或默认消息初始化
const [message, setMessage] = useState(() => initialMessages || getDefaultMessages(t));
const [message, setMessage] = useState(
() => initialMessages || getDefaultMessages(t),
);
// 当语言改变时,如果是默认消息则更新
useEffect(() => {
// 只在没有保存的消息时才更新默认消息

View File

@@ -112,6 +112,14 @@ export const useLogsData = () => {
const [showUserInfo, setShowUserInfoModal] = useState(false);
const [userInfoData, setUserInfoData] = useState(null);
// Channel affinity usage cache stats modal state (admin only)
const [
showChannelAffinityUsageCacheModal,
setShowChannelAffinityUsageCacheModal,
] = useState(false);
const [channelAffinityUsageCacheTarget, setChannelAffinityUsageCacheTarget] =
useState(null);
// Load saved column preferences from localStorage
useEffect(() => {
const savedColumns = localStorage.getItem(STORAGE_KEY);
@@ -304,6 +312,17 @@ export const useLogsData = () => {
}
};
const openChannelAffinityUsageCacheModal = (affinity) => {
const a = affinity || {};
setChannelAffinityUsageCacheTarget({
rule_name: a.rule_name || a.reason || '',
using_group: a.using_group || '',
key_hint: a.key_hint || '',
key_fp: a.key_fp || '',
});
setShowChannelAffinityUsageCacheModal(true);
};
// Format logs data
const setLogsFormat = (logs) => {
const requestConversionDisplayValue = (conversionChain) => {
@@ -372,9 +391,13 @@ export const useLogsData = () => {
other.cache_ratio || 1.0,
other.cache_creation_ratio || 1.0,
other.cache_creation_tokens_5m || 0,
other.cache_creation_ratio_5m || other.cache_creation_ratio || 1.0,
other.cache_creation_ratio_5m ||
other.cache_creation_ratio ||
1.0,
other.cache_creation_tokens_1h || 0,
other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0,
other.cache_creation_ratio_1h ||
other.cache_creation_ratio ||
1.0,
)
: renderLogContent(
other?.model_ratio,
@@ -524,8 +547,8 @@ export const useLogsData = () => {
localCountMode = t('上游返回');
}
expandDataLocal.push({
key: t('计费模式'),
value: localCountMode,
key: t('计费模式'),
value: localCountMode,
});
}
expandDatesLocal[logs[i].key] = expandDataLocal;
@@ -680,6 +703,12 @@ export const useLogsData = () => {
userInfoData,
showUserInfoFunc,
// Channel affinity usage cache stats modal
showChannelAffinityUsageCacheModal,
setShowChannelAffinityUsageCacheModal,
channelAffinityUsageCacheTarget,
openChannelAffinityUsageCacheModal,
// Functions
loadLogs,
handlePageChange,