Architecture: - index.ui: title + RefreshWidget(cards) + ChartBar with refresh_period - RefreshWidget wraps dashboard_cards.dspy → returns full card widget tree with live data (cnt, amount, total_users, concurrent_users) - ChartBar handles its own auto-refresh via refresh_period: 10 - No more JS polling file needed .dspy import fixes: - get_today_usage.dspy: remove import json, from datetime import date - get_user_stats.dspy: remove from datetime import datetime, timedelta - get_top_models.dspy: remove from datetime import date - All use pre-loaded datetime module (datetime.date.today(), etc.) - dashboard_cards.dspy: same pattern, no imports Permission: - load_path.py: add dashboard_cards.dspy logined
74 lines
3.8 KiB
Plaintext
74 lines
3.8 KiB
Plaintext
"""获取当天llmusage笔数和交易金额 + 用户统计 — 返回完整卡片widget树供RefreshWidget加载"""
|
|
# datetime, json, DBPools 由 ahserver 预加载,无需 import
|
|
|
|
today = datetime.date.today().isoformat()
|
|
now = datetime.datetime.now()
|
|
five_min_ago = (now - datetime.timedelta(minutes=5)).strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
db = DBPools()
|
|
|
|
# 今日用量
|
|
async with db.sqlorContext('sage') as sor:
|
|
recs = await sor.sqlExe(
|
|
"SELECT COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total_amount FROM llmusage WHERE use_date = ${today}$",
|
|
{'today': today}
|
|
)
|
|
cnt = int(recs[0].get('cnt', 0)) if recs else 0
|
|
total_amount = float(recs[0].get('total_amount', 0)) if recs else 0.0
|
|
|
|
# 总用户数
|
|
async with db.sqlorContext('sage') as sor:
|
|
user_recs = await sor.sqlExe("SELECT COUNT(*) as total_users FROM users", {})
|
|
total_users = int(user_recs[0].get('total_users', 0)) if user_recs 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].get('concurrent_users', 0)) if conc else 0
|
|
|
|
return {
|
|
"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": "\u00a5%.2f" % total_amount, "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"}}
|
|
]
|
|
}
|
|
]
|
|
}
|