feat: add user-level model usage chart (我的今日模型使用)
- Add get_user_today_models() function to load_dashboard.py Shows current user's today model call counts and amounts - Create api/user_today_models.dspy endpoint - Create user_today_models_chart.ui ChartBar widget (30s auto-refresh) - Add '我的今日模型使用' card section to index.ui with refresh button - Register new paths in load_path.py (logined permission)
This commit is contained in:
parent
c36ada56b1
commit
69b7ec5cd0
@ -238,6 +238,37 @@ async def get_total_orgs(request):
|
|||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
|
|
||||||
|
async def get_user_today_models(request):
|
||||||
|
"""获取当前用户当天各模型调用次数和金额(用户级监控项)"""
|
||||||
|
env = request._run_ns
|
||||||
|
userid = await env.get_user()
|
||||||
|
if not userid:
|
||||||
|
return []
|
||||||
|
today = env.curDateString()
|
||||||
|
async with get_sor_context(env, 'sage') as sor:
|
||||||
|
sql = """
|
||||||
|
SELECT
|
||||||
|
COALESCE(b.name, 'Unknown') as model_name,
|
||||||
|
COUNT(*) as cnt,
|
||||||
|
COALESCE(SUM(a.amount), 0) as total_amount
|
||||||
|
FROM llmusage a
|
||||||
|
LEFT JOIN llm b ON a.llmid = b.id
|
||||||
|
WHERE a.use_date = ${today}$
|
||||||
|
AND a.userid = ${userid}$
|
||||||
|
GROUP BY a.llmid, b.name
|
||||||
|
ORDER BY cnt DESC
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql, {'today': today, 'userid': userid})
|
||||||
|
result = []
|
||||||
|
for r in recs:
|
||||||
|
result.append({
|
||||||
|
'model_name': r.get('model_name', 'Unknown'),
|
||||||
|
'cnt': int(r.get('cnt', 0)),
|
||||||
|
'total_amount': round(float(r.get('total_amount', 0)), 4)
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def load_dashboard():
|
def load_dashboard():
|
||||||
"""Register dashboard functions on ServerEnv"""
|
"""Register dashboard functions on ServerEnv"""
|
||||||
g = ServerEnv()
|
g = ServerEnv()
|
||||||
@ -254,3 +285,4 @@ def load_dashboard():
|
|||||||
g.get_active_users_today = get_active_users_today
|
g.get_active_users_today = get_active_users_today
|
||||||
g.get_new_users_month = get_new_users_month
|
g.get_new_users_month = get_new_users_month
|
||||||
g.get_total_orgs = get_total_orgs
|
g.get_total_orgs = get_total_orgs
|
||||||
|
g.get_user_today_models = get_user_today_models
|
||||||
|
|||||||
@ -67,9 +67,11 @@ paths = [
|
|||||||
# Charts
|
# Charts
|
||||||
("/dashboard_for_sage/chart_top_models.ui", "logined"),
|
("/dashboard_for_sage/chart_top_models.ui", "logined"),
|
||||||
("/dashboard_for_sage/top_models_chart.ui", "logined"),
|
("/dashboard_for_sage/top_models_chart.ui", "logined"),
|
||||||
|
("/dashboard_for_sage/user_today_models_chart.ui", "logined"),
|
||||||
|
|
||||||
# API endpoints
|
# API endpoints
|
||||||
("/dashboard_for_sage/api/top_models.dspy", "logined"),
|
("/dashboard_for_sage/api/top_models.dspy", "logined"),
|
||||||
|
("/dashboard_for_sage/api/user_today_models.dspy", "logined"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
6
wwwroot/api/user_today_models.dspy
Normal file
6
wwwroot/api/user_today_models.dspy
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""User's today model usage data API for ChartBar"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
models = await get_user_today_models(request)
|
||||||
|
return json.dumps(models, ensure_ascii=False, default=str)
|
||||||
@ -412,6 +412,65 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"css": "card",
|
||||||
|
"width": "100%",
|
||||||
|
"borderRadius": "12px",
|
||||||
|
"padding": "20px",
|
||||||
|
"marginTop": "20px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"alignItems": "center",
|
||||||
|
"marginBottom": "16px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Title4",
|
||||||
|
"options": {
|
||||||
|
"fontWeight": "600",
|
||||||
|
"otext": "我的今日模型使用",
|
||||||
|
"i18n": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Filler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Button",
|
||||||
|
"options": {
|
||||||
|
"label": "刷新",
|
||||||
|
"border": "none",
|
||||||
|
"borderRadius": "6px",
|
||||||
|
"padding": "4px 12px",
|
||||||
|
"fontSize": "12px"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "method",
|
||||||
|
"target": "-@ChartBar",
|
||||||
|
"method": "render_urldata",
|
||||||
|
"params": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('user_today_models_chart.ui')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype": "VBox",
|
||||||
"options": {
|
"options": {
|
||||||
|
|||||||
11
wwwroot/user_today_models_chart.ui
Normal file
11
wwwroot/user_today_models_chart.ui
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"widgettype": "ChartBar",
|
||||||
|
"options": {
|
||||||
|
"height": "280px",
|
||||||
|
"width": "100%",
|
||||||
|
"data_url": "{{entire_url('api/user_today_models.dspy')}}",
|
||||||
|
"nameField": "model_name",
|
||||||
|
"valueFields": ["cnt", "total_amount"],
|
||||||
|
"refresh_period": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user