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:
yumoqing 2026-05-31 08:00:22 +08:00
parent c36ada56b1
commit 69b7ec5cd0
5 changed files with 110 additions and 0 deletions

View File

@ -238,6 +238,37 @@ async def get_total_orgs(request):
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():
"""Register dashboard functions on ServerEnv"""
g = ServerEnv()
@ -254,3 +285,4 @@ def load_dashboard():
g.get_active_users_today = get_active_users_today
g.get_new_users_month = get_new_users_month
g.get_total_orgs = get_total_orgs
g.get_user_today_models = get_user_today_models

View File

@ -67,9 +67,11 @@ paths = [
# Charts
("/dashboard_for_sage/chart_top_models.ui", "logined"),
("/dashboard_for_sage/top_models_chart.ui", "logined"),
("/dashboard_for_sage/user_today_models_chart.ui", "logined"),
# API endpoints
("/dashboard_for_sage/api/top_models.dspy", "logined"),
("/dashboard_for_sage/api/user_today_models.dspy", "logined"),
]

View 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)

View File

@ -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",
"options": {

View 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
}
}