feat(dashboard): add admin user analytics and fix chart labels
- Add GET /api/data/users endpoint for user-grouped quota data (admin only) - Add user consumption ranking (horizontal bar, top 10) and user consumption trend (area chart) tabs visible only to admin users - Fix mislabeled "消耗趋势" tab to "调用趋势" (shows call counts, not quota) - Add processUserData helper for user ranking and trend data extraction - Add i18n keys for new tabs across all 7 locales
This commit is contained in:
@@ -27,6 +27,21 @@ func GetAllQuotaDates(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetQuotaDatesByUser(c *gin.Context) {
|
||||||
|
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
|
||||||
|
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
|
||||||
|
dates, err := model.GetQuotaDataGroupByUser(startTimestamp, endTimestamp)
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": dates,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func GetUserQuotaDates(c *gin.Context) {
|
func GetUserQuotaDates(c *gin.Context) {
|
||||||
userId := c.GetInt("id")
|
userId := c.GetInt("id")
|
||||||
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
|
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
|
||||||
|
|||||||
@@ -115,6 +115,16 @@ func GetQuotaDataByUserId(userId int, startTime int64, endTime int64) (quotaData
|
|||||||
return quotaDatas, err
|
return quotaDatas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetQuotaDataGroupByUser(startTime int64, endTime int64) (quotaData []*QuotaData, err error) {
|
||||||
|
var quotaDatas []*QuotaData
|
||||||
|
err = DB.Table("quota_data").
|
||||||
|
Select("username, created_at, sum(count) as count, sum(quota) as quota, sum(token_used) as token_used").
|
||||||
|
Where("created_at >= ? and created_at <= ?", startTime, endTime).
|
||||||
|
Group("username, created_at").
|
||||||
|
Find("aDatas).Error
|
||||||
|
return quotaDatas, err
|
||||||
|
}
|
||||||
|
|
||||||
func GetAllQuotaDates(startTime int64, endTime int64, username string) (quotaData []*QuotaData, err error) {
|
func GetAllQuotaDates(startTime int64, endTime int64, username string) (quotaData []*QuotaData, err error) {
|
||||||
if username != "" {
|
if username != "" {
|
||||||
return GetQuotaDataByUsername(username, startTime, endTime)
|
return GetQuotaDataByUsername(username, startTime, endTime)
|
||||||
|
|||||||
@@ -293,6 +293,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
|
|
||||||
dataRoute := apiRouter.Group("/data")
|
dataRoute := apiRouter.Group("/data")
|
||||||
dataRoute.GET("/", middleware.AdminAuth(), controller.GetAllQuotaDates)
|
dataRoute.GET("/", middleware.AdminAuth(), controller.GetAllQuotaDates)
|
||||||
|
dataRoute.GET("/users", middleware.AdminAuth(), controller.GetQuotaDatesByUser)
|
||||||
dataRoute.GET("/self", middleware.UserAuth(), controller.GetUserQuotaDates)
|
dataRoute.GET("/self", middleware.UserAuth(), controller.GetUserQuotaDates)
|
||||||
|
|
||||||
logRoute.Use(middleware.CORS(), middleware.CriticalRateLimit())
|
logRoute.Use(middleware.CORS(), middleware.CriticalRateLimit())
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ const ChartsPanel = ({
|
|||||||
spec_model_line,
|
spec_model_line,
|
||||||
spec_pie,
|
spec_pie,
|
||||||
spec_rank_bar,
|
spec_rank_bar,
|
||||||
|
spec_user_rank,
|
||||||
|
spec_user_trend,
|
||||||
|
isAdminUser,
|
||||||
CARD_PROPS,
|
CARD_PROPS,
|
||||||
CHART_CONFIG,
|
CHART_CONFIG,
|
||||||
FLEX_CENTER_GAP2,
|
FLEX_CENTER_GAP2,
|
||||||
@@ -51,9 +54,15 @@ const ChartsPanel = ({
|
|||||||
onChange={setActiveChartTab}
|
onChange={setActiveChartTab}
|
||||||
>
|
>
|
||||||
<TabPane tab={<span>{t('消耗分布')}</span>} itemKey='1' />
|
<TabPane tab={<span>{t('消耗分布')}</span>} itemKey='1' />
|
||||||
<TabPane tab={<span>{t('消耗趋势')}</span>} itemKey='2' />
|
<TabPane tab={<span>{t('调用趋势')}</span>} itemKey='2' />
|
||||||
<TabPane tab={<span>{t('调用次数分布')}</span>} itemKey='3' />
|
<TabPane tab={<span>{t('调用次数分布')}</span>} itemKey='3' />
|
||||||
<TabPane tab={<span>{t('调用次数排行')}</span>} itemKey='4' />
|
<TabPane tab={<span>{t('调用次数排行')}</span>} itemKey='4' />
|
||||||
|
{isAdminUser && (
|
||||||
|
<TabPane tab={<span>{t('用户消耗排行')}</span>} itemKey='5' />
|
||||||
|
)}
|
||||||
|
{isAdminUser && (
|
||||||
|
<TabPane tab={<span>{t('用户消耗趋势')}</span>} itemKey='6' />
|
||||||
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -72,6 +81,12 @@ const ChartsPanel = ({
|
|||||||
{activeChartTab === '4' && (
|
{activeChartTab === '4' && (
|
||||||
<VChart spec={spec_rank_bar} option={CHART_CONFIG} />
|
<VChart spec={spec_rank_bar} option={CHART_CONFIG} />
|
||||||
)}
|
)}
|
||||||
|
{activeChartTab === '5' && isAdminUser && (
|
||||||
|
<VChart spec={spec_user_rank} option={CHART_CONFIG} />
|
||||||
|
)}
|
||||||
|
{activeChartTab === '6' && isAdminUser && (
|
||||||
|
<VChart spec={spec_user_trend} option={CHART_CONFIG} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -86,12 +86,22 @@ const Dashboard = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ========== 数据处理 ==========
|
// ========== 数据处理 ==========
|
||||||
|
const loadUserData = async () => {
|
||||||
|
if (dashboardData.isAdminUser) {
|
||||||
|
const userData = await dashboardData.loadUserQuotaData();
|
||||||
|
if (userData && userData.length > 0) {
|
||||||
|
dashboardCharts.updateUserChartData(userData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const initChart = async () => {
|
const initChart = async () => {
|
||||||
await dashboardData.loadQuotaData().then((data) => {
|
await dashboardData.loadQuotaData().then((data) => {
|
||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
dashboardCharts.updateChartData(data);
|
dashboardCharts.updateChartData(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
await loadUserData();
|
||||||
await dashboardData.loadUptimeData();
|
await dashboardData.loadUptimeData();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,10 +110,12 @@ const Dashboard = () => {
|
|||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
dashboardCharts.updateChartData(data);
|
dashboardCharts.updateChartData(data);
|
||||||
}
|
}
|
||||||
|
await loadUserData();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchConfirm = async () => {
|
const handleSearchConfirm = async () => {
|
||||||
await dashboardData.handleSearchConfirm(dashboardCharts.updateChartData);
|
await dashboardData.handleSearchConfirm(dashboardCharts.updateChartData);
|
||||||
|
await loadUserData();
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== 数据准备 ==========
|
// ========== 数据准备 ==========
|
||||||
@@ -182,6 +194,9 @@ const Dashboard = () => {
|
|||||||
spec_model_line={dashboardCharts.spec_model_line}
|
spec_model_line={dashboardCharts.spec_model_line}
|
||||||
spec_pie={dashboardCharts.spec_pie}
|
spec_pie={dashboardCharts.spec_pie}
|
||||||
spec_rank_bar={dashboardCharts.spec_rank_bar}
|
spec_rank_bar={dashboardCharts.spec_rank_bar}
|
||||||
|
spec_user_rank={dashboardCharts.spec_user_rank}
|
||||||
|
spec_user_trend={dashboardCharts.spec_user_trend}
|
||||||
|
isAdminUser={dashboardData.isAdminUser}
|
||||||
CARD_PROPS={CARD_PROPS}
|
CARD_PROPS={CARD_PROPS}
|
||||||
CHART_CONFIG={CHART_CONFIG}
|
CHART_CONFIG={CHART_CONFIG}
|
||||||
FLEX_CENTER_GAP2={FLEX_CENTER_GAP2}
|
FLEX_CENTER_GAP2={FLEX_CENTER_GAP2}
|
||||||
|
|||||||
Vendored
+55
@@ -387,3 +387,58 @@ export const generateChartTimePoints = (
|
|||||||
|
|
||||||
return chartTimePoints;
|
return chartTimePoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ========== 用户维度数据处理 ==========
|
||||||
|
export const processUserData = (data, dataExportDefaultTime, limit = 10) => {
|
||||||
|
const userQuotaTotal = new Map();
|
||||||
|
data.forEach((item) => {
|
||||||
|
const prev = userQuotaTotal.get(item.username) || 0;
|
||||||
|
userQuotaTotal.set(item.username, prev + item.quota);
|
||||||
|
});
|
||||||
|
|
||||||
|
const sorted = Array.from(userQuotaTotal.entries()).sort(
|
||||||
|
(a, b) => b[1] - a[1],
|
||||||
|
);
|
||||||
|
const topUsers = sorted.slice(0, limit).map(([u]) => u);
|
||||||
|
const topUserSet = new Set(topUsers);
|
||||||
|
|
||||||
|
const rankingData = sorted.slice(0, limit).map(([username, quota]) => ({
|
||||||
|
User: username,
|
||||||
|
Quota: quota,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const showYear = isDataCrossYear(data.map((item) => item.created_at));
|
||||||
|
|
||||||
|
const timeUserMap = new Map();
|
||||||
|
const allTimePoints = new Set();
|
||||||
|
|
||||||
|
data.forEach((item) => {
|
||||||
|
const timeKey = timestamp2string1(
|
||||||
|
item.created_at,
|
||||||
|
dataExportDefaultTime,
|
||||||
|
showYear,
|
||||||
|
);
|
||||||
|
allTimePoints.add(timeKey);
|
||||||
|
const user = topUserSet.has(item.username) ? item.username : null;
|
||||||
|
if (!user) return;
|
||||||
|
const key = `${timeKey}-${user}`;
|
||||||
|
const prev = timeUserMap.get(key) || { quota: 0 };
|
||||||
|
timeUserMap.set(key, { quota: prev.quota + item.quota });
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedTimePoints = Array.from(allTimePoints).sort();
|
||||||
|
const trendData = [];
|
||||||
|
sortedTimePoints.forEach((time) => {
|
||||||
|
topUsers.forEach((user) => {
|
||||||
|
const key = `${time}-${user}`;
|
||||||
|
const val = timeUserMap.get(key);
|
||||||
|
trendData.push({
|
||||||
|
Time: time,
|
||||||
|
User: user,
|
||||||
|
Quota: val?.quota || 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { rankingData, trendData, topUsers };
|
||||||
|
};
|
||||||
|
|||||||
+125
-6
@@ -34,8 +34,14 @@ import {
|
|||||||
updateChartSpec,
|
updateChartSpec,
|
||||||
updateMapValue,
|
updateMapValue,
|
||||||
initializeMaps,
|
initializeMaps,
|
||||||
|
processUserData,
|
||||||
} from '../../helpers/dashboard';
|
} from '../../helpers/dashboard';
|
||||||
|
|
||||||
|
const USER_COLORS = [
|
||||||
|
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
|
||||||
|
'#ec4899', '#06b6d4', '#f97316', '#6366f1', '#14b8a6',
|
||||||
|
];
|
||||||
|
|
||||||
export const useDashboardCharts = (
|
export const useDashboardCharts = (
|
||||||
dataExportDefaultTime,
|
dataExportDefaultTime,
|
||||||
setTrendData,
|
setTrendData,
|
||||||
@@ -179,7 +185,6 @@ export const useDashboardCharts = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 模型消耗趋势折线图
|
|
||||||
const [spec_model_line, setSpecModelLine] = useState({
|
const [spec_model_line, setSpecModelLine] = useState({
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: [
|
data: [
|
||||||
@@ -197,7 +202,7 @@ export const useDashboardCharts = (
|
|||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
visible: true,
|
visible: true,
|
||||||
text: t('模型消耗趋势'),
|
text: t('调用趋势'),
|
||||||
subtext: '',
|
subtext: '',
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -215,7 +220,6 @@ export const useDashboardCharts = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 模型调用次数排行柱状图
|
|
||||||
const [spec_rank_bar, setSpecRankBar] = useState({
|
const [spec_rank_bar, setSpecRankBar] = useState({
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: [
|
data: [
|
||||||
@@ -259,6 +263,76 @@ export const useDashboardCharts = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ========== Admin: 用户消耗排行 ==========
|
||||||
|
const [spec_user_rank, setSpecUserRank] = useState({
|
||||||
|
type: 'bar',
|
||||||
|
data: [{ id: 'userRankData', values: [] }],
|
||||||
|
xField: 'rawQuota',
|
||||||
|
yField: 'User',
|
||||||
|
seriesField: 'User',
|
||||||
|
direction: 'horizontal',
|
||||||
|
legends: { visible: false },
|
||||||
|
title: {
|
||||||
|
visible: true,
|
||||||
|
text: t('用户消耗排行'),
|
||||||
|
subtext: '',
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
state: { hover: { stroke: '#000', lineWidth: 1 } },
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
visible: true,
|
||||||
|
position: 'outside',
|
||||||
|
formatMethod: (value, datum) => renderQuota(datum['rawQuota'] || 0, 2),
|
||||||
|
},
|
||||||
|
axes: [{
|
||||||
|
orient: 'left',
|
||||||
|
type: 'band',
|
||||||
|
label: { visible: true },
|
||||||
|
}, {
|
||||||
|
orient: 'bottom',
|
||||||
|
type: 'linear',
|
||||||
|
visible: false,
|
||||||
|
}],
|
||||||
|
tooltip: {
|
||||||
|
mark: {
|
||||||
|
content: [{
|
||||||
|
key: (datum) => datum['User'],
|
||||||
|
value: (datum) => renderQuota(datum['rawQuota'] || 0, 4),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
color: { type: 'ordinal', range: USER_COLORS },
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== Admin: 用户消耗趋势 ==========
|
||||||
|
const [spec_user_trend, setSpecUserTrend] = useState({
|
||||||
|
type: 'area',
|
||||||
|
data: [{ id: 'userTrendData', values: [] }],
|
||||||
|
xField: 'Time',
|
||||||
|
yField: 'rawQuota',
|
||||||
|
seriesField: 'User',
|
||||||
|
stack: false,
|
||||||
|
legends: { visible: true, selectMode: 'single' },
|
||||||
|
title: {
|
||||||
|
visible: true,
|
||||||
|
text: t('用户消耗趋势'),
|
||||||
|
subtext: '',
|
||||||
|
},
|
||||||
|
area: { style: { fillOpacity: 0.15 } },
|
||||||
|
line: { style: { lineWidth: 2 } },
|
||||||
|
point: { visible: false },
|
||||||
|
tooltip: {
|
||||||
|
mark: {
|
||||||
|
content: [{
|
||||||
|
key: (datum) => datum['User'],
|
||||||
|
value: (datum) => renderQuota(datum['rawQuota'] || 0, 4),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
color: { type: 'ordinal', range: USER_COLORS },
|
||||||
|
});
|
||||||
|
|
||||||
// ========== 数据处理函数 ==========
|
// ========== 数据处理函数 ==========
|
||||||
const generateModelColors = useCallback((uniqueModels, modelColors) => {
|
const generateModelColors = useCallback((uniqueModels, modelColors) => {
|
||||||
const newModelColors = {};
|
const newModelColors = {};
|
||||||
@@ -426,6 +500,51 @@ export const useDashboardCharts = (
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ========== 用户维度图表数据处理 ==========
|
||||||
|
const updateUserChartData = useCallback(
|
||||||
|
(data) => {
|
||||||
|
const { rankingData, trendData: userTrend } = processUserData(
|
||||||
|
data,
|
||||||
|
dataExportDefaultTime,
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
|
||||||
|
const userRankValues = rankingData.map((item) => ({
|
||||||
|
User: item.User,
|
||||||
|
rawQuota: item.Quota,
|
||||||
|
Quota: getQuotaWithUnit(item.Quota, 4),
|
||||||
|
})).sort((a, b) => a.rawQuota - b.rawQuota);
|
||||||
|
|
||||||
|
const totalUserQuota = rankingData.reduce((s, i) => s + i.Quota, 0);
|
||||||
|
|
||||||
|
setSpecUserRank((prev) => ({
|
||||||
|
...prev,
|
||||||
|
data: [{ id: 'userRankData', values: userRankValues }],
|
||||||
|
title: {
|
||||||
|
...prev.title,
|
||||||
|
subtext: `${t('总计')}:${renderQuota(totalUserQuota, 2)}`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const userTrendValues = userTrend.map((item) => ({
|
||||||
|
Time: item.Time,
|
||||||
|
User: item.User,
|
||||||
|
rawQuota: item.Quota,
|
||||||
|
Usage: item.Quota ? getQuotaWithUnit(item.Quota, 4) : 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setSpecUserTrend((prev) => ({
|
||||||
|
...prev,
|
||||||
|
data: [{ id: 'userTrendData', values: userTrendValues }],
|
||||||
|
title: {
|
||||||
|
...prev.title,
|
||||||
|
subtext: `${t('总计')}:${renderQuota(totalUserQuota, 2)}`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[dataExportDefaultTime, t],
|
||||||
|
);
|
||||||
|
|
||||||
// ========== 初始化图表主题 ==========
|
// ========== 初始化图表主题 ==========
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initVChartSemiTheme({
|
initVChartSemiTheme({
|
||||||
@@ -434,14 +553,14 @@ export const useDashboardCharts = (
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 图表规格
|
|
||||||
spec_pie,
|
spec_pie,
|
||||||
spec_line,
|
spec_line,
|
||||||
spec_model_line,
|
spec_model_line,
|
||||||
spec_rank_bar,
|
spec_rank_bar,
|
||||||
|
spec_user_rank,
|
||||||
// 函数
|
spec_user_trend,
|
||||||
updateChartData,
|
updateChartData,
|
||||||
|
updateUserChartData,
|
||||||
generateModelColors,
|
generateModelColors,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+22
@@ -213,6 +213,27 @@ export const useDashboardData = (userState, userDispatch, statusState) => {
|
|||||||
}
|
}
|
||||||
}, [activeUptimeTab]);
|
}, [activeUptimeTab]);
|
||||||
|
|
||||||
|
const loadUserQuotaData = useCallback(async () => {
|
||||||
|
if (!isAdminUser) return [];
|
||||||
|
try {
|
||||||
|
const { start_timestamp, end_timestamp } = inputs;
|
||||||
|
const localStartTimestamp = Date.parse(start_timestamp) / 1000;
|
||||||
|
const localEndTimestamp = Date.parse(end_timestamp) / 1000;
|
||||||
|
const url = `/api/data/users?start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
|
||||||
|
const res = await API.get(url);
|
||||||
|
const { success, message, data } = res.data;
|
||||||
|
if (success) {
|
||||||
|
return data || [];
|
||||||
|
} else {
|
||||||
|
showError(message);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}, [inputs, isAdminUser]);
|
||||||
|
|
||||||
const getUserData = useCallback(async () => {
|
const getUserData = useCallback(async () => {
|
||||||
let res = await API.get(`/api/user/self`);
|
let res = await API.get(`/api/user/self`);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
@@ -311,6 +332,7 @@ export const useDashboardData = (userState, userDispatch, statusState) => {
|
|||||||
showSearchModal,
|
showSearchModal,
|
||||||
handleCloseModal,
|
handleCloseModal,
|
||||||
loadQuotaData,
|
loadQuotaData,
|
||||||
|
loadUserQuotaData,
|
||||||
loadUptimeData,
|
loadUptimeData,
|
||||||
getUserData,
|
getUserData,
|
||||||
refresh,
|
refresh,
|
||||||
|
|||||||
Vendored
+4
@@ -3066,6 +3066,10 @@
|
|||||||
"调用次数": "Call Count",
|
"调用次数": "Call Count",
|
||||||
"调用次数分布": "Models call distribution",
|
"调用次数分布": "Models call distribution",
|
||||||
"调用次数排行": "Models call ranking",
|
"调用次数排行": "Models call ranking",
|
||||||
|
"调用趋势": "Call trend",
|
||||||
|
"模型排行": "Model ranking",
|
||||||
|
"用户消耗排行": "User consumption ranking",
|
||||||
|
"用户消耗趋势": "User consumption trend",
|
||||||
"调试信息": "Debug information",
|
"调试信息": "Debug information",
|
||||||
"谨慎": "Cautious",
|
"谨慎": "Cautious",
|
||||||
"豆包": "Doubao",
|
"豆包": "Doubao",
|
||||||
|
|||||||
Vendored
+4
@@ -3039,6 +3039,10 @@
|
|||||||
"调用次数": "Nombre d'appels",
|
"调用次数": "Nombre d'appels",
|
||||||
"调用次数分布": "Distribution des appels de modèles",
|
"调用次数分布": "Distribution des appels de modèles",
|
||||||
"调用次数排行": "Classement des appels de modèles",
|
"调用次数排行": "Classement des appels de modèles",
|
||||||
|
"调用趋势": "Tendance des appels",
|
||||||
|
"模型排行": "Classement des modèles",
|
||||||
|
"用户消耗排行": "Classement de consommation des utilisateurs",
|
||||||
|
"用户消耗趋势": "Tendance de consommation des utilisateurs",
|
||||||
"调试信息": "Informations de débogage",
|
"调试信息": "Informations de débogage",
|
||||||
"谨慎": "Prudent",
|
"谨慎": "Prudent",
|
||||||
"豆包": "Doubao",
|
"豆包": "Doubao",
|
||||||
|
|||||||
Vendored
+4
@@ -3020,6 +3020,10 @@
|
|||||||
"调用次数": "呼び出し回数",
|
"调用次数": "呼び出し回数",
|
||||||
"调用次数分布": "呼び出し回数分布",
|
"调用次数分布": "呼び出し回数分布",
|
||||||
"调用次数排行": "呼び出し回数ランキング",
|
"调用次数排行": "呼び出し回数ランキング",
|
||||||
|
"调用趋势": "呼び出し推移",
|
||||||
|
"模型排行": "モデルランキング",
|
||||||
|
"用户消耗排行": "ユーザー消費ランキング",
|
||||||
|
"用户消耗趋势": "ユーザー消費推移",
|
||||||
"调试信息": "デバッグ情報",
|
"调试信息": "デバッグ情報",
|
||||||
"谨慎": "注意",
|
"谨慎": "注意",
|
||||||
"豆包": "豆包",
|
"豆包": "豆包",
|
||||||
|
|||||||
Vendored
+4
@@ -3053,6 +3053,10 @@
|
|||||||
"调用次数": "Количество вызовов",
|
"调用次数": "Количество вызовов",
|
||||||
"调用次数分布": "Распределение количества вызовов",
|
"调用次数分布": "Распределение количества вызовов",
|
||||||
"调用次数排行": "Рейтинг количества вызовов",
|
"调用次数排行": "Рейтинг количества вызовов",
|
||||||
|
"调用趋势": "Тенденция вызовов",
|
||||||
|
"模型排行": "Рейтинг моделей",
|
||||||
|
"用户消耗排行": "Рейтинг потребления пользователей",
|
||||||
|
"用户消耗趋势": "Тенденция потребления пользователей",
|
||||||
"调试信息": "Отладочная информация",
|
"调试信息": "Отладочная информация",
|
||||||
"谨慎": "Осторожно",
|
"谨慎": "Осторожно",
|
||||||
"豆包": "Doubao",
|
"豆包": "Doubao",
|
||||||
|
|||||||
Vendored
+4
@@ -3472,6 +3472,10 @@
|
|||||||
"调用次数": "Số lần gọi",
|
"调用次数": "Số lần gọi",
|
||||||
"调用次数分布": "Phân phối số lần gọi",
|
"调用次数分布": "Phân phối số lần gọi",
|
||||||
"调用次数排行": "Xếp hạng số lần gọi",
|
"调用次数排行": "Xếp hạng số lần gọi",
|
||||||
|
"调用趋势": "Xu hướng cuộc gọi",
|
||||||
|
"模型排行": "Xếp hạng mô hình",
|
||||||
|
"用户消耗排行": "Xếp hạng tiêu thụ người dùng",
|
||||||
|
"用户消耗趋势": "Xu hướng tiêu thụ người dùng",
|
||||||
"调试信息": "Thông tin gỡ lỗi",
|
"调试信息": "Thông tin gỡ lỗi",
|
||||||
"谨慎": "Thận trọng",
|
"谨慎": "Thận trọng",
|
||||||
"豆包": "Doubao",
|
"豆包": "Doubao",
|
||||||
|
|||||||
Vendored
+4
@@ -2314,6 +2314,10 @@
|
|||||||
"调用次数": "调用次数",
|
"调用次数": "调用次数",
|
||||||
"调用次数分布": "调用次数分布",
|
"调用次数分布": "调用次数分布",
|
||||||
"调用次数排行": "调用次数排行",
|
"调用次数排行": "调用次数排行",
|
||||||
|
"调用趋势": "调用趋势",
|
||||||
|
"模型排行": "模型排行",
|
||||||
|
"用户消耗排行": "用户消耗排行",
|
||||||
|
"用户消耗趋势": "用户消耗趋势",
|
||||||
"调试信息": "调试信息",
|
"调试信息": "调试信息",
|
||||||
"谨慎": "谨慎",
|
"谨慎": "谨慎",
|
||||||
"警告": "警告",
|
"警告": "警告",
|
||||||
|
|||||||
Vendored
+4
@@ -2719,6 +2719,10 @@
|
|||||||
"调用次数": "調用次數",
|
"调用次数": "調用次數",
|
||||||
"调用次数分布": "調用次數分佈",
|
"调用次数分布": "調用次數分佈",
|
||||||
"调用次数排行": "調用次數排行",
|
"调用次数排行": "調用次數排行",
|
||||||
|
"调用趋势": "調用趨勢",
|
||||||
|
"模型排行": "模型排行",
|
||||||
|
"用户消耗排行": "用戶消耗排行",
|
||||||
|
"用户消耗趋势": "用戶消耗趨勢",
|
||||||
"调试信息": "除錯訊息",
|
"调试信息": "除錯訊息",
|
||||||
"谨慎": "謹慎",
|
"谨慎": "謹慎",
|
||||||
"豆包": "豆包",
|
"豆包": "豆包",
|
||||||
|
|||||||
Reference in New Issue
Block a user