dashboard_for_sage/wwwroot/api/dashboard_content.dspy
yumoqing 99e6fed5ef refactor: replace JS polling with RefreshWidget
- Delete dashboard_refresh.js (no longer needed)
- Add api/dashboard_content.dspy returns dynamic UI with live data
  (queries llmusage + users tables server-side, returns full widget tree)
- Simplify index.ui to use RefreshWidget with period_seconds=10
- ChartBar in dashboard_content.dspy auto-fetches top models via data_url
- load_path.py updated separately in sage repo
2026-05-24 16:31:29 +08:00

101 lines
5.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""返回 dashboard 动态 UI 描述(含实时数据)"""
from datetime import date, datetime, timedelta
db = DBPools()
today = date.today().isoformat()
now = datetime.now()
five_min_ago = (now - timedelta(minutes=5)).strftime('%Y-%m-%d %H:%M:%S')
# 今日用量
async with db.sqlorContext('sage') as sor:
usage = await sor.sqlExe(
"SELECT COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total_amount FROM llmusage WHERE use_date = ${today}$",
{'today': today}
)
cnt = int(usage[0]['cnt']) if usage else 0
total_amount = float(usage[0]['total_amount']) if usage else 0.0
# 总用户数
async with db.sqlorContext('sage') as sor:
users = await sor.sqlExe("SELECT COUNT(*) as total_users FROM users", {})
total_users = int(users[0]['total_users']) if users else 0
# 并发用户
async with db.sqlorContext('sage') as sor:
conc = await sor.sqlExe(
"SELECT COUNT(DISTINCT userid) as concurrent_users FROM llmusage WHERE use_date = ${today}$ AND use_time >= ${five_min_ago}$",
{'today': today, 'five_min_ago': five_min_ago}
)
concurrent_users = int(conc[0]['concurrent_users']) if conc else 0
# ChartBar data_url — 用相对路径bricks 会基于当前上下文解析
chart_data_url = 'api/get_top_models.dspy'
return {
"widgettype": "VBox",
"options": {"width": "100%"},
"subwidgets": [
{
"widgettype": "ResponsableBox",
"options": {"gap": "16px", "minWidth": "250px"},
"subwidgets": [
{
"widgettype": "VBox",
"id": "card_today_cnt",
"options": {"bgcolor": "#FFFFFF", "padding": "24px", "borderRadius": "8px", "flex": "1", "minHeight": "120px", "boxShadow": "0 2px 8px rgba(0,0,0,0.1)"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "今日调用笔数", "fontSize": "14px", "color": "#888", "marginBottom": "8px"}},
{"widgettype": "Text", "id": "today_cnt_value", "options": {"text": str(cnt), "fontSize": "32px", "fontWeight": "bold", "color": "#1890ff"}}
]
},
{
"widgettype": "VBox",
"id": "card_today_amount",
"options": {"bgcolor": "#FFFFFF", "padding": "24px", "borderRadius": "8px", "flex": "1", "minHeight": "120px", "boxShadow": "0 2px 8px rgba(0,0,0,0.1)"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "今日交易金额", "fontSize": "14px", "color": "#888", "marginBottom": "8px"}},
{"widgettype": "Text", "id": "today_amount_value", "options": {"text": f"\u00a5{total_amount:.2f}", "fontSize": "32px", "fontWeight": "bold", "color": "#52c41a"}}
]
},
{
"widgettype": "VBox",
"id": "card_total_users",
"options": {"bgcolor": "#FFFFFF", "padding": "24px", "borderRadius": "8px", "flex": "1", "minHeight": "120px", "boxShadow": "0 2px 8px rgba(0,0,0,0.1)"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "用户总数", "fontSize": "14px", "color": "#888", "marginBottom": "8px"}},
{"widgettype": "Text", "id": "total_users_value", "options": {"text": str(total_users), "fontSize": "32px", "fontWeight": "bold", "color": "#722ed1"}}
]
},
{
"widgettype": "VBox",
"id": "card_concurrent_users",
"options": {"bgcolor": "#FFFFFF", "padding": "24px", "borderRadius": "8px", "flex": "1", "minHeight": "120px", "boxShadow": "0 2px 8px rgba(0,0,0,0.1)"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "当前并发用户", "fontSize": "14px", "color": "#888", "marginBottom": "8px"}},
{"widgettype": "Text", "id": "concurrent_users_value", "options": {"text": str(concurrent_users), "fontSize": "32px", "fontWeight": "bold", "color": "#fa8c16"}}
]
}
]
},
{
"widgettype": "VBox",
"id": "chart_section",
"options": {"bgcolor": "#FFFFFF", "padding": "24px", "borderRadius": "8px", "marginTop": "20px", "minHeight": "350px", "boxShadow": "0 2px 8px rgba(0,0,0,0.1)"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "Top 3 模型(今日)", "fontSize": "18px", "fontWeight": "bold", "color": "#333", "marginBottom": "16px"}},
{
"widgettype": "ChartBar",
"id": "top_models_chart",
"options": {
"height": "300px",
"width": "100%",
"data_url": chart_data_url,
"nameField": "model_name",
"valueFields": ["cnt", "total_amount"]
}
}
]
}
]
}