Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
c17439a1
authored
Jul 05, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/alpha' into alpha
parents
95775a5a
8bb1522d
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
166 additions
and
21 deletions
+166
-21
web/src/components/settings/ChannelSelectorModal.js
+0
-1
web/src/components/table/UsersTable.js
+1
-1
web/src/i18n/locales/en.json
+6
-2
web/src/pages/Detail/index.js
+148
-3
web/src/pages/Home/index.js
+1
-4
web/src/pages/Setting/Ratio/UpstreamRatioSync.js
+4
-5
web/src/pages/User/EditUser.js
+6
-5
No files found.
web/src/components/settings/ChannelSelectorModal.js
View file @
c17439a1
...
@@ -197,7 +197,6 @@ const ChannelSelectorModal = forwardRef(({
...
@@ -197,7 +197,6 @@ const ChannelSelectorModal = forwardRef(({
value
=
{
searchText
}
value
=
{
searchText
}
onChange
=
{
setSearchText
}
onChange
=
{
setSearchText
}
showClear
showClear
className
=
"!rounded-full"
/>
/>
<
Table
<
Table
...
...
web/src/components/table/UsersTable.js
View file @
c17439a1
...
@@ -119,7 +119,7 @@ const UsersTable = () => {
...
@@ -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
>
...
...
web/src/i18n/locales/en.json
View file @
c17439a1
...
@@ -876,7 +876,7 @@
...
@@ -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 @@
...
@@ -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 @@
...
@@ -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"
,
...
...
web/src/pages/Detail/index.js
View file @
c17439a1
...
@@ -366,6 +366,86 @@ const Detail = (props) => {
...
@@ -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) => {
...
@@ -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
);
...
@@ -1124,26 +1244,51 @@ const Detail = (props) => {
...
@@ -1124,26 +1244,51 @@ const Detail = (props) => {
}
itemKey
=
"1"
/>
}
itemKey
=
"1"
/>
<
TabPane
tab
=
{
<
TabPane
tab
=
{
<
span
>
<
span
>
<
IconPulse
/>
{
t
(
'消耗趋势'
)}
<
/span
>
}
itemKey
=
"2"
/>
<
TabPane
tab
=
{
<
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
>
...
...
web/src/pages/Home/index.js
View file @
c17439a1
...
@@ -272,10 +272,7 @@ const Home = () => {
...
@@ -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
>
)}
)}
...
...
web/src/pages/Setting/Ratio/UpstreamRatioSync.js
View file @
c17439a1
...
@@ -373,7 +373,7 @@ export default function UpstreamRatioSync(props) {
...
@@ -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) {
...
@@ -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) {
...
@@ -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) {
...
@@ -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) {
...
@@ -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"
/
>
/
>
);
);
};
};
...
...
web/src/pages/User/EditUser.js
View file @
c17439a1
...
@@ -22,6 +22,7 @@ import {
...
@@ -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) => {
...
@@ -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) => {
...
@@ -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) => {
...
@@ -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
<
Input
Number
placeholder
=
{
t
(
'需要添加的额度(支持负数)'
)}
placeholder
=
{
t
(
'需要添加的额度(支持负数)'
)}
type
=
'number'
value
=
{
addQuotaLocal
}
value
=
{
addQuotaLocal
}
onChange
=
{
setAddQuotaLocal
}
onChange
=
{
setAddQuotaLocal
}
style
=
{{
width
:
'100%'
}}
showClear
showClear
step
=
{
500000
}
/
>
/
>
<
/Modal
>
<
/Modal
>
<
/
>
<
/
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment