From d191eef657b4d81079ae96ada2e64e8f31a303ea Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Fri, 4 Jul 2025 23:42:46 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=90=9B=20fix:=20fix=20the=20header=20?= =?UTF-8?q?height=20calculation=20issue=20in=20the=20custom=20HTML=20style?= =?UTF-8?q?s=20on=20the=20homepage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/Home/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index 7cc4b792..f2db769c 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -272,10 +272,7 @@ const Home = () => { className="w-full h-screen border-none" /> ) : ( -
+
)}
)} From 8945a3a2dd9745eb98de5c53f64c94a045d4b383 Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Fri, 4 Jul 2025 23:49:34 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=96=BC=EF=B8=8F=20style(RatioSync):?= =?UTF-8?q?=20remove=20the=20useless=20`rounded-full`=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/settings/ChannelSelectorModal.js | 1 - web/src/pages/Setting/Ratio/UpstreamRatioSync.js | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/web/src/components/settings/ChannelSelectorModal.js b/web/src/components/settings/ChannelSelectorModal.js index a09eff1c..72fdb970 100644 --- a/web/src/components/settings/ChannelSelectorModal.js +++ b/web/src/components/settings/ChannelSelectorModal.js @@ -197,7 +197,6 @@ const ChannelSelectorModal = forwardRef(({ value={searchText} onChange={setSearchText} showClear - className="!rounded-full" /> @@ -406,7 +406,7 @@ export default function UpstreamRatioSync(props) { placeholder={t('搜索模型名称')} value={searchKeyword} onChange={setSearchKeyword} - className="!rounded-full w-full sm:w-64" + className="w-full sm:w-64" showClear /> @@ -414,7 +414,7 @@ export default function UpstreamRatioSync(props) { placeholder={t('按倍率类型筛选')} value={ratioTypeFilter} onChange={setRatioTypeFilter} - className="!rounded-full w-full sm:w-48" + className="w-full sm:w-48" showClear onClear={() => setRatioTypeFilter('')} > @@ -704,7 +704,6 @@ export default function UpstreamRatioSync(props) { scroll={{ x: 'max-content' }} size='middle' loading={loading || syncLoading} - className="rounded-xl overflow-hidden" /> ); }; From 3049ad47e58c054d1bcbfbf554efdd5f92f023fb Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Sat, 5 Jul 2025 00:03:12 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=94=A2=20feat(user-edit):=20replace?= =?UTF-8?q?=20add-quota=20input=20with=20Semi-UI=20InputNumber?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: • Imported InputNumber from @douyinfe/semi-ui. • Swapped plain Input for InputNumber in “Add Quota” modal. • Added UX tweaks: full-width styling, showClear, step = 500 000. • Initialized addQuotaLocal to an empty string so the field starts blank. • Adjusted state handling and kept quota calculation logic unchanged. This improves numeric input accuracy and overall user experience without breaking existing functionality. --- web/src/components/table/UsersTable.js | 2 +- web/src/i18n/locales/en.json | 2 +- web/src/pages/User/EditUser.js | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/web/src/components/table/UsersTable.js b/web/src/components/table/UsersTable.js index 31a8b2c0..02a19b80 100644 --- a/web/src/components/table/UsersTable.js +++ b/web/src/components/table/UsersTable.js @@ -119,7 +119,7 @@ const UsersTable = () => {
-
+
{displayRemark}
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index de5d9bce..0d49d3ba 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1199,7 +1199,7 @@ "添加用户": "Add user", "角色": "Role", "已绑定的 Telegram 账户": "Bound Telegram account", - "新额度": "New quota", + "新额度:": "New quota: ", "需要添加的额度(支持负数)": "Need to add quota (supports negative numbers)", "此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改": "Read-only, user's personal settings, and cannot be modified directly", "请输入新的密码,最短 8 位": "Please enter a new password, at least 8 characterss", diff --git a/web/src/pages/User/EditUser.js b/web/src/pages/User/EditUser.js index deaefb6a..4a8f46e9 100644 --- a/web/src/pages/User/EditUser.js +++ b/web/src/pages/User/EditUser.js @@ -22,6 +22,7 @@ import { Row, Col, Input, + InputNumber, } from '@douyinfe/semi-ui'; import { IconUser, @@ -39,7 +40,7 @@ const EditUser = (props) => { const userId = props.editingUser.id; const [loading, setLoading] = useState(true); const [addQuotaModalOpen, setIsModalOpen] = useState(false); - const [addQuotaLocal, setAddQuotaLocal] = useState('0'); + const [addQuotaLocal, setAddQuotaLocal] = useState(''); const [groupOptions, setGroupOptions] = useState([]); const formApiRef = useRef(null); @@ -254,7 +255,6 @@ const EditUser = (props) => { field='quota' label={t('剩余额度')} placeholder={t('请输入新的剩余额度')} - min={0} step={500000} extraText={renderQuotaWithPrompt(values.quota || 0)} rules={[{ required: true, message: t('请输入额度') }]} @@ -328,18 +328,19 @@ const EditUser = (props) => { const current = formApiRef.current?.getValue('quota') || 0; return ( - {`${t('新额度')}${renderQuota(current)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(current + parseInt(addQuotaLocal || 0))}`} + {`${t('新额度:')}${renderQuota(current)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(current + parseInt(addQuotaLocal || 0))}`} ); })() }
- From d40fb68500554ba0331b13c4265b1be9925d4b7c Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Sat, 5 Jul 2025 00:37:05 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=8A=20feat(detail):=20add=20model?= =?UTF-8?q?=20consumption=20trend=20&=20call=20ranking=20charts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce two new visualizations to the “Model Data Analysis” panel: 1. Model Consumption Trend (line chart) • Added `spec_model_line` state and legend support. • Calculates per-model counts over time and updates via `updateChartData`. 2. Model Call Ranking (bar chart) • Added `spec_rank_bar` state with `seriesField` and legend enabled. • Ranks models by total call count. Additional changes: • Extended tab navigation with two new `TabPane`s and adjusted chart rendering logic. • Swapped icons/texts to match new chart purposes. • Reused existing color mapping to ensure consistent palette. No breaking changes; UI now offers richer insights into model usage patterns. --- web/src/i18n/locales/en.json | 6 +- web/src/pages/Detail/index.js | 151 +++++++++++++++++++++++++++++++++- 2 files changed, 153 insertions(+), 4 deletions(-) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 0d49d3ba..80f7f3cd 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -876,7 +876,7 @@ "加载token失败": "Failed to load token", "配置聊天": "Configure chat", "模型消耗分布": "Model consumption distribution", - "模型调用次数占比": "Proportion of model calls", + "模型调用次数占比": "Model call ratio", "用户消耗分布": "User consumption distribution", "时间粒度": "Time granularity", "天": "day", @@ -1119,6 +1119,10 @@ "平均TPM": "Average TPM", "消耗分布": "Consumption distribution", "调用次数分布": "Models call distribution", + "消耗趋势": "Consumption trend", + "模型消耗趋势": "Model consumption trend", + "调用次数排行": "Models call ranking", + "模型调用次数排行": "Model call ranking", "添加渠道": "Add channel", "测试所有通道": "Test all channels", "删除禁用通道": "Delete disabled channels", diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index 2463db70..f455abb6 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -366,6 +366,86 @@ const Detail = (props) => { }, }); + // 模型消耗趋势折线图 + const [spec_model_line, setSpecModelLine] = useState({ + type: 'line', + data: [ + { + id: 'lineData', + values: [], + }, + ], + xField: 'Time', + yField: 'Count', + seriesField: 'Model', + legends: { + visible: true, + selectMode: 'single', + }, + title: { + visible: true, + text: t('模型消耗趋势'), + subtext: '', + }, + tooltip: { + mark: { + content: [ + { + key: (datum) => datum['Model'], + value: (datum) => renderNumber(datum['Count']), + }, + ], + }, + }, + color: { + specified: modelColorMap, + }, + }); + + // 模型调用次数排行柱状图 + const [spec_rank_bar, setSpecRankBar] = useState({ + type: 'bar', + data: [ + { + id: 'rankData', + values: [], + }, + ], + xField: 'Model', + yField: 'Count', + seriesField: 'Model', + legends: { + visible: true, + selectMode: 'single', + }, + title: { + visible: true, + text: t('模型调用次数排行'), + subtext: '', + }, + bar: { + state: { + hover: { + stroke: '#000', + lineWidth: 1, + }, + }, + }, + tooltip: { + mark: { + content: [ + { + key: (datum) => datum['Model'], + value: (datum) => renderNumber(datum['Count']), + }, + ], + }, + }, + color: { + specified: modelColorMap, + }, + }); + // ========== Hooks - Memoized Values ========== const performanceMetrics = useMemo(() => { const timeDiff = (Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000; @@ -853,6 +933,46 @@ const Detail = (props) => { 'barData' ); + // ===== 模型调用次数折线图 ===== + let modelLineData = []; + chartTimePoints.forEach((time) => { + const timeData = Array.from(uniqueModels).map((model) => { + const key = `${time}-${model}`; + const aggregated = aggregatedData.get(key); + return { + Time: time, + Model: model, + Count: aggregated?.count || 0, + }; + }); + modelLineData.push(...timeData); + }); + modelLineData.sort((a, b) => a.Time.localeCompare(b.Time)); + + // ===== 模型调用次数排行柱状图 ===== + const rankData = Array.from(modelTotals) + .map(([model, count]) => ({ + Model: model, + Count: count, + })) + .sort((a, b) => b.Count - a.Count); + + updateChartSpec( + setSpecModelLine, + modelLineData, + `${t('总计')}:${renderNumber(totalTimes)}`, + newModelColors, + 'lineData' + ); + + updateChartSpec( + setSpecRankBar, + rankData, + `${t('总计')}:${renderNumber(totalTimes)}`, + newModelColors, + 'rankData' + ); + setPieData(newPieData); setLineData(newLineData); setConsumeQuota(totalQuota); @@ -1122,28 +1242,53 @@ const Detail = (props) => { {t('消耗分布')} } itemKey="1" /> + + + {t('消耗趋势')} + + } itemKey="2" /> {t('调用次数分布')} - } itemKey="2" /> + } itemKey="3" /> + + + {t('调用次数排行')} + + } itemKey="4" />
} >
- {activeChartTab === '1' ? ( + {activeChartTab === '1' && ( - ) : ( + )} + {activeChartTab === '2' && ( + + )} + {activeChartTab === '3' && ( )} + {activeChartTab === '4' && ( + + )}