Merge origin/main into nightly

Resolve conflicts:
- .gitignore: keep nightly additions (.test, skills-lock.json)
- relay/helper/price.go: keep both billingexpr and model imports
- en.json / zh-CN.json: keep nightly's superset of i18n entries
- service/billing_session.go: add missing 3rd arg to DecreaseUserQuota
- en.json / zh-CN.json: deduplicate 129+320 duplicate i18n keys
This commit is contained in:
CaIon
2026-04-23 21:23:40 +08:00
117 changed files with 9031 additions and 2910 deletions
+5 -3
View File
@@ -890,7 +890,7 @@ export const useChannelsData = () => {
return Promise.resolve();
}
const { success, message, time } = res.data;
const { success, message, time, error_code } = res.data;
// 更新测试结果
setModelTestResults((prev) => ({
@@ -900,6 +900,7 @@ export const useChannelsData = () => {
message,
time: time || 0,
timestamp: Date.now(),
errorCode: error_code || null,
},
}));
@@ -927,7 +928,7 @@ export const useChannelsData = () => {
);
}
} else {
showError(`${t('模型')} ${model}: ${message}`);
showError(message);
}
} catch (error) {
// 处理网络错误
@@ -939,9 +940,10 @@ export const useChannelsData = () => {
message: error.message || t('网络错误'),
time: 0,
timestamp: Date.now(),
errorCode: null,
},
}));
showError(`${t('模型')} ${model}: ${error.message || t('测试失败')}`);
showError(error.message || t('测试失败'));
} finally {
// 从正在测试的模型集合中移除
setTestingModels((prev) => {
+57 -1
View File
@@ -214,6 +214,29 @@ export const useDashboardCharts = (
},
],
},
dimension: {
content: [
{
key: (datum) => datum['Model'],
value: (datum) => datum['Count'] || 0,
},
],
updateContent: (array) => {
array.sort((a, b) => b.value - a.value);
let sum = 0;
for (let i = 0; i < array.length; i++) {
let value = parseFloat(array[i].value);
if (isNaN(value)) value = 0;
sum += value;
array[i].value = renderNumber(value);
}
array.unshift({
key: t('总计'),
value: renderNumber(sum),
});
return array;
},
},
},
color: {
specified: modelColorMap,
@@ -335,6 +358,27 @@ export const useDashboardCharts = (
value: (datum) => renderQuota(datum['rawQuota'] || 0, 4),
}],
},
dimension: {
content: [{
key: (datum) => datum['User'],
value: (datum) => datum['rawQuota'] || 0,
}],
updateContent: (array) => {
array.sort((a, b) => b.value - a.value);
let sum = 0;
for (let i = 0; i < array.length; i++) {
let value = parseFloat(array[i].value);
if (isNaN(value)) value = 0;
sum += value;
array[i].value = renderQuota(value, 4);
}
array.unshift({
key: t('总计'),
value: renderQuota(sum, 4),
});
return array;
},
},
},
color: { type: 'ordinal', range: USER_COLORS },
});
@@ -463,13 +507,25 @@ export const useDashboardCharts = (
modelLineData.sort((a, b) => a.Time.localeCompare(b.Time));
// ===== 模型调用次数排行柱状图 =====
const rankData = Array.from(modelTotals)
const MAX_RANK_MODELS = 20;
const allRankData = Array.from(modelTotals)
.map(([model, count]) => ({
Model: model,
Count: count,
}))
.sort((a, b) => b.Count - a.Count);
let rankData;
if (allRankData.length > MAX_RANK_MODELS) {
const topModels = allRankData.slice(0, MAX_RANK_MODELS);
const otherCount = allRankData
.slice(MAX_RANK_MODELS)
.reduce((sum, item) => sum + item.Count, 0);
rankData = [...topModels, { Model: t('其他'), Count: otherCount }];
} else {
rankData = allRankData;
}
updateChartSpec(
setSpecModelLine,
modelLineData,
+43 -6
View File
@@ -196,10 +196,17 @@ export const useApiRequest = (
if (!response.ok) {
let errorBody = '';
let parsedError = null;
try {
errorBody = await response.text();
const errorJson = JSON.parse(errorBody);
if (errorJson?.error) {
parsedError = errorJson.error;
}
} catch (e) {
errorBody = '无法读取错误响应体';
if (!errorBody) {
errorBody = '无法读取错误响应体';
}
}
const errorInfo = handleApiError(
@@ -215,9 +222,13 @@ export const useApiRequest = (
}));
setActiveDebugTab(DEBUG_TABS.RESPONSE);
throw new Error(
`HTTP error! status: ${response.status}, body: ${errorBody}`,
const err = new Error(
parsedError?.message ||
`HTTP error! status: ${response.status}, body: ${errorBody}`,
);
err.errorCode = parsedError?.code || null;
err.errorType = parsedError?.type || null;
throw err;
}
const data = await response.json();
@@ -277,6 +288,7 @@ export const useApiRequest = (
newMessages[newMessages.length - 1] = {
...lastMessage,
content: t('请求发生错误: ') + error.message,
errorCode: error.errorCode || null,
status: MESSAGE_STATUS.ERROR,
...autoCollapseState,
};
@@ -379,7 +391,20 @@ export const useApiRequest = (
// 只有在流没有正常完成且连接状态异常时才处理错误
if (!isStreamComplete && source.readyState !== 2) {
console.error('SSE Error:', e);
const errorMessage = e.data || t('请求发生错误');
let errorMessage = e.data || t('请求发生错误');
let errorCode = null;
if (e.data) {
try {
const errorJson = JSON.parse(e.data);
if (errorJson?.error) {
errorMessage = errorJson.error.message || errorMessage;
errorCode = errorJson.error.code || null;
}
} catch (_) {
// not JSON, use raw data as error message
}
}
const errorInfo = handleApiError(new Error(errorMessage));
errorInfo.readyState = source.readyState;
@@ -393,8 +418,19 @@ export const useApiRequest = (
}));
setActiveDebugTab(DEBUG_TABS.RESPONSE);
streamMessageUpdate(errorMessage, 'content');
completeMessage(MESSAGE_STATUS.ERROR);
setMessage((prevMessage) => {
const newMessages = [...prevMessage];
const lastMessage = newMessages[newMessages.length - 1];
if (lastMessage && lastMessage.status !== MESSAGE_STATUS.COMPLETE && lastMessage.status !== MESSAGE_STATUS.ERROR) {
newMessages[newMessages.length - 1] = {
...lastMessage,
content: (lastMessage.content || '') + errorMessage,
errorCode: errorCode,
status: MESSAGE_STATUS.ERROR,
};
}
return newMessages;
});
sseSourceRef.current = null;
source.close();
}
@@ -446,6 +482,7 @@ export const useApiRequest = (
[
setDebugData,
setActiveDebugTab,
setMessage,
streamMessageUpdate,
completeMessage,
t,
+79 -2
View File
@@ -622,13 +622,13 @@ export const useLogsData = () => {
),
});
}
if (isAdminUser && logs[i].type !== 6) {
if (isAdminUser && logs[i].type !== 6 && logs[i].type !== 1) {
expandDataLocal.push({
key: t('请求转换'),
value: requestConversionDisplayValue(other?.request_conversion),
});
}
if (isAdminUser && logs[i].type !== 6) {
if (isAdminUser && logs[i].type !== 6 && logs[i].type !== 1) {
let localCountMode = '';
if (other?.admin_info?.local_count_tokens) {
localCountMode = t('本地计费');
@@ -640,6 +640,83 @@ export const useLogsData = () => {
value: localCountMode,
});
}
if (isAdminUser && logs[i].type === 1) {
const adminInfo = other?.admin_info;
if (adminInfo) {
if (adminInfo.payment_method) {
expandDataLocal.push({
key: t('订单支付方式'),
value: adminInfo.payment_method,
});
}
if (adminInfo.callback_payment_method) {
expandDataLocal.push({
key: t('回调支付方式'),
value: adminInfo.callback_payment_method,
});
}
if (adminInfo.caller_ip) {
expandDataLocal.push({
key: t('回调调用者IP'),
value: adminInfo.caller_ip,
});
}
if (adminInfo.server_ip) {
expandDataLocal.push({
key: t('服务器IP'),
value: adminInfo.server_ip,
});
}
if (adminInfo.node_name) {
expandDataLocal.push({
key: t('节点名称'),
value: adminInfo.node_name,
});
}
if (adminInfo.version) {
expandDataLocal.push({
key: t('系统版本'),
value: adminInfo.version,
});
}
} else {
expandDataLocal.push({
key: t('审计信息'),
value: (
<span style={{ color: 'var(--semi-color-warning)' }}>
{t(
'该记录由旧版本实例写入,缺少审计信息,建议将实例升级至最新版本以便记录服务器IP、回调IP、支付方式与系统版本等审计字段。',
)}
</span>
),
});
}
}
if (isAdminUser && logs[i].type === 3 && other?.admin_info) {
const adminInfo = other.admin_info;
const hasUsername =
adminInfo.admin_username !== undefined &&
adminInfo.admin_username !== null &&
adminInfo.admin_username !== '';
const hasId =
adminInfo.admin_id !== undefined &&
adminInfo.admin_id !== null &&
adminInfo.admin_id !== '';
if (hasUsername || hasId) {
let operatorValue = '';
if (hasUsername && hasId) {
operatorValue = `${adminInfo.admin_username} (ID: ${adminInfo.admin_id})`;
} else if (hasUsername) {
operatorValue = String(adminInfo.admin_username);
} else {
operatorValue = `ID: ${adminInfo.admin_id}`;
}
expandDataLocal.push({
key: t('操作管理员'),
value: operatorValue,
});
}
}
expandDatesLocal[logs[i].key] = expandDataLocal;
}