fix: validate number input in renderQuotaNumberWithDigit and improve data handling in Detail page

- Added input validation to ensure that the `num` parameter in `renderQuotaNumberWithDigit` is a valid number, returning 0 for invalid inputs.
- Updated the `Detail` component to use `datum['rawQuota']` instead of `datum['Usage']` for rendering quota values, ensuring more accurate data representation.
- Enhanced data aggregation logic to handle cases where quota values may be missing or invalid, improving overall data integrity in charts and tables.
- Removed unnecessary time granularity calculations and streamlined the data processing for better performance.
This commit is contained in:
CalciumIon
2024-12-25 23:16:35 +08:00
parent 4fc1fe318e
commit bfba4866a5
3 changed files with 87 additions and 61 deletions

View File

@@ -59,6 +59,9 @@ export function renderNumber(num) {
} }
export function renderQuotaNumberWithDigit(num, digits = 2) { export function renderQuotaNumberWithDigit(num, digits = 2) {
if (typeof num !== 'number' || isNaN(num)) {
return 0;
}
let displayInCurrency = localStorage.getItem('display_in_currency'); let displayInCurrency = localStorage.getItem('display_in_currency');
num = num.toFixed(digits); num = num.toFixed(digits);
if (displayInCurrency) { if (displayInCurrency) {

View File

@@ -180,6 +180,9 @@ export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
let month = (date.getMonth() + 1).toString(); let month = (date.getMonth() + 1).toString();
let day = date.getDate().toString(); let day = date.getDate().toString();
let hour = date.getHours().toString(); let hour = date.getHours().toString();
if (day === '24') {
console.log("timestamp", timestamp);
}
if (month.length === 1) { if (month.length === 1) {
month = '0' + month; month = '0' + month;
} }

View File

@@ -143,8 +143,7 @@ const Detail = (props) => {
content: [ content: [
{ {
key: (datum) => datum['Model'], key: (datum) => datum['Model'],
value: (datum) => value: (datum) => renderQuotaNumberWithDigit(datum['rawQuota'] || 0, 4),
renderQuotaNumberWithDigit(parseFloat(datum['Usage']), 4),
}, },
], ],
}, },
@@ -152,22 +151,28 @@ const Detail = (props) => {
content: [ content: [
{ {
key: (datum) => datum['Model'], key: (datum) => datum['Model'],
value: (datum) => datum['Usage'], value: (datum) => datum['rawQuota'] || 0,
}, },
], ],
updateContent: (array) => { updateContent: (array) => {
array.sort((a, b) => b.value - a.value); array.sort((a, b) => b.value - a.value);
let sum = 0; let sum = 0;
for (let i = 0; i < array.length; i++) { for (let i = 0; i < array.length; i++) {
sum += parseFloat(array[i].value); if (array[i].key == "其他") {
array[i].value = renderQuotaNumberWithDigit( continue;
parseFloat(array[i].value), }
4, let value = parseFloat(array[i].value);
); if (isNaN(value)) {
value = 0;
}
if (array[i].datum && array[i].datum.TimeSum) {
sum = array[i].datum.TimeSum;
}
array[i].value = renderQuota(value, 4);
} }
array.unshift({ array.unshift({
key: t('总计'), key: t('总计'),
value: renderQuotaNumberWithDigit(sum, 4), value: renderQuota(sum, 4),
}); });
return array; return array;
}, },
@@ -212,19 +217,8 @@ const Detail = (props) => {
created_at: now.getTime() / 1000, created_at: now.getTime() / 1000,
}); });
} }
// 根据dataExportDefaultTime重制时间粒度
let timeGranularity = 3600;
if (dataExportDefaultTime === 'day') {
timeGranularity = 86400;
} else if (dataExportDefaultTime === 'week') {
timeGranularity = 604800;
}
// sort created_at // sort created_at
data.sort((a, b) => a.created_at - b.created_at); data.sort((a, b) => a.created_at - b.created_at);
data.forEach((item) => {
item['created_at'] =
Math.floor(item['created_at'] / timeGranularity) * timeGranularity;
});
updateChartData(data); updateChartData(data);
} else { } else {
showError(message); showError(message);
@@ -250,12 +244,12 @@ const Detail = (props) => {
let uniqueModels = new Set(); let uniqueModels = new Set();
let totalTokens = 0; let totalTokens = 0;
// 收集所有唯一的模型名称和时间点 // 收集所有唯一的模型名称
let uniqueTimes = new Set();
data.forEach(item => { data.forEach(item => {
uniqueModels.add(item.model_name); uniqueModels.add(item.model_name);
uniqueTimes.add(timestamp2string1(item.created_at, dataExportDefaultTime));
totalTokens += item.token_used; totalTokens += item.token_used;
totalQuota += item.quota;
totalTimes += item.count;
}); });
// 处理颜色映射 // 处理颜色映射
@@ -267,56 +261,82 @@ const Detail = (props) => {
}); });
setModelColors(newModelColors); setModelColors(newModelColors);
// 处理饼图数据 // 按时间和模型聚合数据
for (let item of data) { let aggregatedData = new Map();
totalQuota += item.quota; data.forEach(item => {
totalTimes += item.count; const timeKey = timestamp2string1(item.created_at, dataExportDefaultTime);
const modelKey = item.model_name;
const key = `${timeKey}-${modelKey}`;
let pieItem = newPieData.find((it) => it.type === item.model_name); if (!aggregatedData.has(key)) {
if (pieItem) { aggregatedData.set(key, {
pieItem.value += item.count; time: timeKey,
} else { model: modelKey,
newPieData.push({ quota: 0,
type: item.model_name, count: 0
value: item.count,
}); });
} }
const existing = aggregatedData.get(key);
existing.quota += item.quota;
existing.count += item.count;
});
// 处理饼图数据
let modelTotals = new Map();
for (let [_, value] of aggregatedData) {
if (!modelTotals.has(value.model)) {
modelTotals.set(value.model, 0);
}
modelTotals.set(value.model, modelTotals.get(value.model) + value.count);
} }
// 处理柱状图数据 newPieData = Array.from(modelTotals).map(([model, count]) => ({
let timePoints = Array.from(uniqueTimes); type: model,
value: count
}));
// 生成时间点序列
let timePoints = Array.from(new Set([...aggregatedData.values()].map(d => d.time)));
if (timePoints.length < 7) { if (timePoints.length < 7) {
// 根据时间粒度生成合适的时间点 const lastTime = Math.max(...data.map(item => item.created_at));
const generateTimePoints = () => { const interval = dataExportDefaultTime === 'hour' ? 3600
let lastTime = Math.max(...data.map(item => item.created_at));
let points = [];
let interval = dataExportDefaultTime === 'hour' ? 3600
: dataExportDefaultTime === 'day' ? 86400 : dataExportDefaultTime === 'day' ? 86400
: 604800; : 604800;
for (let i = 0; i < 7; i++) { timePoints = Array.from({length: 7}, (_, i) =>
points.push(timestamp2string1(lastTime - (i * interval), dataExportDefaultTime)); timestamp2string1(lastTime - (6-i) * interval, dataExportDefaultTime)
} );
return points.reverse();
};
timePoints = generateTimePoints();
} }
// 为每个时间点和模型生成数据 // 生成柱状图数据
timePoints.forEach(time => { timePoints.forEach(time => {
Array.from(uniqueModels).forEach(model => { // 为每个时间点收集所有模型的数据
let existingData = data.find(item => let timeData = Array.from(uniqueModels).map(model => {
timestamp2string1(item.created_at, dataExportDefaultTime) === time && const key = `${time}-${model}`;
item.model_name === model const aggregated = aggregatedData.get(key);
); return {
newLineData.push({
Time: time, Time: time,
Model: model, Model: model,
Usage: existingData ? parseFloat(getQuotaWithUnit(existingData.quota)) : 0 rawQuota: aggregated?.quota || 0,
}); Usage: aggregated?.quota ? getQuotaWithUnit(aggregated.quota, 4) : 0
};
}); });
// 计算该时间点的总计
const timeSum = timeData.reduce((sum, item) => sum + item.rawQuota, 0);
// 按照 rawQuota 从大到小排序
timeData.sort((a, b) => b.rawQuota - a.rawQuota);
// 为每个数据点添加该时间的总计
timeData = timeData.map(item => ({
...item,
TimeSum: timeSum
}));
// 将排序后的数据添加到 newLineData
newLineData.push(...timeData);
}); });
// 排序 // 排序