Merge remote-tracking branch 'origin/alpha' into alpha
This commit is contained in:
@@ -197,7 +197,6 @@ const ChannelSelectorModal = forwardRef(({
|
|||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={setSearchText}
|
onChange={setSearchText}
|
||||||
showClear
|
showClear
|
||||||
className="!rounded-full"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ const UsersTable = () => {
|
|||||||
<Tooltip content={remark} position="top" showArrow>
|
<Tooltip content={remark} position="top" showArrow>
|
||||||
<Tag color='white' size='large' shape='circle' className="!text-xs">
|
<Tag color='white' size='large' shape='circle' className="!text-xs">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-2 h-2 flex-shrink-0" style={{ backgroundColor: '#10b981' }} />
|
<div className="w-2 h-2 flex-shrink-0 rounded-full" style={{ backgroundColor: '#10b981' }} />
|
||||||
{displayRemark}
|
{displayRemark}
|
||||||
</div>
|
</div>
|
||||||
</Tag>
|
</Tag>
|
||||||
|
|||||||
@@ -876,7 +876,7 @@
|
|||||||
"加载token失败": "Failed to load token",
|
"加载token失败": "Failed to load token",
|
||||||
"配置聊天": "Configure chat",
|
"配置聊天": "Configure chat",
|
||||||
"模型消耗分布": "Model consumption distribution",
|
"模型消耗分布": "Model consumption distribution",
|
||||||
"模型调用次数占比": "Proportion of model calls",
|
"模型调用次数占比": "Model call ratio",
|
||||||
"用户消耗分布": "User consumption distribution",
|
"用户消耗分布": "User consumption distribution",
|
||||||
"时间粒度": "Time granularity",
|
"时间粒度": "Time granularity",
|
||||||
"天": "day",
|
"天": "day",
|
||||||
@@ -1119,6 +1119,10 @@
|
|||||||
"平均TPM": "Average TPM",
|
"平均TPM": "Average TPM",
|
||||||
"消耗分布": "Consumption distribution",
|
"消耗分布": "Consumption distribution",
|
||||||
"调用次数分布": "Models call distribution",
|
"调用次数分布": "Models call distribution",
|
||||||
|
"消耗趋势": "Consumption trend",
|
||||||
|
"模型消耗趋势": "Model consumption trend",
|
||||||
|
"调用次数排行": "Models call ranking",
|
||||||
|
"模型调用次数排行": "Model call ranking",
|
||||||
"添加渠道": "Add channel",
|
"添加渠道": "Add channel",
|
||||||
"测试所有通道": "Test all channels",
|
"测试所有通道": "Test all channels",
|
||||||
"删除禁用通道": "Delete disabled channels",
|
"删除禁用通道": "Delete disabled channels",
|
||||||
@@ -1199,7 +1203,7 @@
|
|||||||
"添加用户": "Add user",
|
"添加用户": "Add user",
|
||||||
"角色": "Role",
|
"角色": "Role",
|
||||||
"已绑定的 Telegram 账户": "Bound Telegram account",
|
"已绑定的 Telegram 账户": "Bound Telegram account",
|
||||||
"新额度": "New quota",
|
"新额度:": "New quota: ",
|
||||||
"需要添加的额度(支持负数)": "Need to add quota (supports negative numbers)",
|
"需要添加的额度(支持负数)": "Need to add quota (supports negative numbers)",
|
||||||
"此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改": "Read-only, user's personal settings, and cannot be modified directly",
|
"此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改": "Read-only, user's personal settings, and cannot be modified directly",
|
||||||
"请输入新的密码,最短 8 位": "Please enter a new password, at least 8 characterss",
|
"请输入新的密码,最短 8 位": "Please enter a new password, at least 8 characterss",
|
||||||
|
|||||||
@@ -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 ==========
|
// ========== Hooks - Memoized Values ==========
|
||||||
const performanceMetrics = useMemo(() => {
|
const performanceMetrics = useMemo(() => {
|
||||||
const timeDiff = (Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000;
|
const timeDiff = (Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000;
|
||||||
@@ -853,6 +933,46 @@ const Detail = (props) => {
|
|||||||
'barData'
|
'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);
|
setPieData(newPieData);
|
||||||
setLineData(newLineData);
|
setLineData(newLineData);
|
||||||
setConsumeQuota(totalQuota);
|
setConsumeQuota(totalQuota);
|
||||||
@@ -1122,28 +1242,53 @@ const Detail = (props) => {
|
|||||||
{t('消耗分布')}
|
{t('消耗分布')}
|
||||||
</span>
|
</span>
|
||||||
} itemKey="1" />
|
} itemKey="1" />
|
||||||
|
<TabPane tab={
|
||||||
|
<span>
|
||||||
|
<IconPulse />
|
||||||
|
{t('消耗趋势')}
|
||||||
|
</span>
|
||||||
|
} itemKey="2" />
|
||||||
<TabPane tab={
|
<TabPane tab={
|
||||||
<span>
|
<span>
|
||||||
<IconPieChart2Stroked />
|
<IconPieChart2Stroked />
|
||||||
{t('调用次数分布')}
|
{t('调用次数分布')}
|
||||||
</span>
|
</span>
|
||||||
} itemKey="2" />
|
} itemKey="3" />
|
||||||
|
<TabPane tab={
|
||||||
|
<span>
|
||||||
|
<IconHistogram />
|
||||||
|
{t('调用次数排行')}
|
||||||
|
</span>
|
||||||
|
} itemKey="4" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div style={{ height: 400 }}>
|
<div style={{ height: 400 }}>
|
||||||
{activeChartTab === '1' ? (
|
{activeChartTab === '1' && (
|
||||||
<VChart
|
<VChart
|
||||||
spec={spec_line}
|
spec={spec_line}
|
||||||
option={CHART_CONFIG}
|
option={CHART_CONFIG}
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
|
{activeChartTab === '2' && (
|
||||||
|
<VChart
|
||||||
|
spec={spec_model_line}
|
||||||
|
option={CHART_CONFIG}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{activeChartTab === '3' && (
|
||||||
<VChart
|
<VChart
|
||||||
spec={spec_pie}
|
spec={spec_pie}
|
||||||
option={CHART_CONFIG}
|
option={CHART_CONFIG}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{activeChartTab === '4' && (
|
||||||
|
<VChart
|
||||||
|
spec={spec_rank_bar}
|
||||||
|
option={CHART_CONFIG}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@@ -272,10 +272,7 @@ const Home = () => {
|
|||||||
className="w-full h-screen border-none"
|
className="w-full h-screen border-none"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div className="mt-[64px]" dangerouslySetInnerHTML={{ __html: homePageContent }} />
|
||||||
className="text-base md:text-lg p-4 md:p-6 lg:p-8 overflow-x-hidden max-w-6xl mx-auto"
|
|
||||||
dangerouslySetInnerHTML={{ __html: homePageContent }}
|
|
||||||
></div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ export default function UpstreamRatioSync(props) {
|
|||||||
<div className="flex flex-col md:flex-row gap-2 w-full md:w-auto order-2 md:order-1">
|
<div className="flex flex-col md:flex-row gap-2 w-full md:w-auto order-2 md:order-1">
|
||||||
<Button
|
<Button
|
||||||
icon={<RefreshCcw size={14} />}
|
icon={<RefreshCcw size={14} />}
|
||||||
className="!rounded-full w-full md:w-auto mt-2"
|
className="w-full md:w-auto mt-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
if (allChannels.length === 0) {
|
if (allChannels.length === 0) {
|
||||||
@@ -393,7 +393,7 @@ export default function UpstreamRatioSync(props) {
|
|||||||
type='secondary'
|
type='secondary'
|
||||||
onClick={applySync}
|
onClick={applySync}
|
||||||
disabled={!hasSelections}
|
disabled={!hasSelections}
|
||||||
className="!rounded-full w-full md:w-auto mt-2"
|
className="w-full md:w-auto mt-2"
|
||||||
>
|
>
|
||||||
{t('应用同步')}
|
{t('应用同步')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -406,7 +406,7 @@ export default function UpstreamRatioSync(props) {
|
|||||||
placeholder={t('搜索模型名称')}
|
placeholder={t('搜索模型名称')}
|
||||||
value={searchKeyword}
|
value={searchKeyword}
|
||||||
onChange={setSearchKeyword}
|
onChange={setSearchKeyword}
|
||||||
className="!rounded-full w-full sm:w-64"
|
className="w-full sm:w-64"
|
||||||
showClear
|
showClear
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ export default function UpstreamRatioSync(props) {
|
|||||||
placeholder={t('按倍率类型筛选')}
|
placeholder={t('按倍率类型筛选')}
|
||||||
value={ratioTypeFilter}
|
value={ratioTypeFilter}
|
||||||
onChange={setRatioTypeFilter}
|
onChange={setRatioTypeFilter}
|
||||||
className="!rounded-full w-full sm:w-48"
|
className="w-full sm:w-48"
|
||||||
showClear
|
showClear
|
||||||
onClear={() => setRatioTypeFilter('')}
|
onClear={() => setRatioTypeFilter('')}
|
||||||
>
|
>
|
||||||
@@ -704,7 +704,6 @@ export default function UpstreamRatioSync(props) {
|
|||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
size='middle'
|
size='middle'
|
||||||
loading={loading || syncLoading}
|
loading={loading || syncLoading}
|
||||||
className="rounded-xl overflow-hidden"
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
Input,
|
Input,
|
||||||
|
InputNumber,
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import {
|
import {
|
||||||
IconUser,
|
IconUser,
|
||||||
@@ -39,7 +40,7 @@ const EditUser = (props) => {
|
|||||||
const userId = props.editingUser.id;
|
const userId = props.editingUser.id;
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [addQuotaModalOpen, setIsModalOpen] = useState(false);
|
const [addQuotaModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [addQuotaLocal, setAddQuotaLocal] = useState('0');
|
const [addQuotaLocal, setAddQuotaLocal] = useState('');
|
||||||
const [groupOptions, setGroupOptions] = useState([]);
|
const [groupOptions, setGroupOptions] = useState([]);
|
||||||
const formApiRef = useRef(null);
|
const formApiRef = useRef(null);
|
||||||
|
|
||||||
@@ -254,7 +255,6 @@ const EditUser = (props) => {
|
|||||||
field='quota'
|
field='quota'
|
||||||
label={t('剩余额度')}
|
label={t('剩余额度')}
|
||||||
placeholder={t('请输入新的剩余额度')}
|
placeholder={t('请输入新的剩余额度')}
|
||||||
min={0}
|
|
||||||
step={500000}
|
step={500000}
|
||||||
extraText={renderQuotaWithPrompt(values.quota || 0)}
|
extraText={renderQuotaWithPrompt(values.quota || 0)}
|
||||||
rules={[{ required: true, message: t('请输入额度') }]}
|
rules={[{ required: true, message: t('请输入额度') }]}
|
||||||
@@ -328,18 +328,19 @@ const EditUser = (props) => {
|
|||||||
const current = formApiRef.current?.getValue('quota') || 0;
|
const current = formApiRef.current?.getValue('quota') || 0;
|
||||||
return (
|
return (
|
||||||
<Text type='secondary' className='block mb-2'>
|
<Text type='secondary' className='block mb-2'>
|
||||||
{`${t('新额度')}${renderQuota(current)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(current + parseInt(addQuotaLocal || 0))}`}
|
{`${t('新额度:')}${renderQuota(current)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(current + parseInt(addQuotaLocal || 0))}`}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<InputNumber
|
||||||
placeholder={t('需要添加的额度(支持负数)')}
|
placeholder={t('需要添加的额度(支持负数)')}
|
||||||
type='number'
|
|
||||||
value={addQuotaLocal}
|
value={addQuotaLocal}
|
||||||
onChange={setAddQuotaLocal}
|
onChange={setAddQuotaLocal}
|
||||||
|
style={{ width: '100%' }}
|
||||||
showClear
|
showClear
|
||||||
|
step={500000}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user