diff --git a/dashboard_for_sage/load_dashboard.py b/dashboard_for_sage/load_dashboard.py
index bef6f17..457564a 100644
--- a/dashboard_for_sage/load_dashboard.py
+++ b/dashboard_for_sage/load_dashboard.py
@@ -26,6 +26,70 @@ async def get_today_amount(request):
return amount
+async def get_usage_trend(request):
+ """计算今日调用量趋势(与昨日对比)"""
+ env = request._run_ns
+ today = env.curDateString()
+ yesterday = (date.today() - timedelta(days=1)).isoformat()
+ async with get_sor_context(env, 'sage') as sor:
+ # 今日
+ sql_today = "SELECT COUNT(*) as cnt FROM llmusage WHERE use_date = ${today}$"
+ recs_today = await sor.sqlExe(sql_today, {'today': today})
+ today_cnt = int(recs_today[0].get('cnt', 0)) if recs_today else 0
+
+ # 昨日
+ sql_yesterday = "SELECT COUNT(*) as cnt FROM llmusage WHERE use_date = ${yesterday}$"
+ recs_yesterday = await sor.sqlExe(sql_yesterday, {'yesterday': yesterday})
+ yesterday_cnt = int(recs_yesterday[0].get('cnt', 0)) if recs_yesterday else 0
+
+ # 计算趋势
+ if yesterday_cnt == 0:
+ return {'trend': 'flat', 'percentage': 0, 'value': today_cnt}
+
+ change_pct = ((today_cnt - yesterday_cnt) / yesterday_cnt) * 100
+
+ if change_pct > 5:
+ trend = 'up'
+ elif change_pct < -5:
+ trend = 'down'
+ else:
+ trend = 'flat'
+
+ return {'trend': trend, 'percentage': abs(change_pct), 'value': today_cnt}
+
+
+async def get_amount_trend(request):
+ """计算今日金额趋势(与昨日对比)"""
+ env = request._run_ns
+ today = env.curDateString()
+ yesterday = (date.today() - timedelta(days=1)).isoformat()
+ async with get_sor_context(env, 'sage') as sor:
+ # 今日
+ sql_today = "SELECT COALESCE(SUM(amount), 0) as total_amount FROM llmusage WHERE use_date = ${today}$"
+ recs_today = await sor.sqlExe(sql_today, {'today': today})
+ today_amount = float(recs_today[0].get('total_amount', 0)) if recs_today else 0.0
+
+ # 昨日
+ sql_yesterday = "SELECT COALESCE(SUM(amount), 0) as total_amount FROM llmusage WHERE use_date = ${yesterday}$"
+ recs_yesterday = await sor.sqlExe(sql_yesterday, {'yesterday': yesterday})
+ yesterday_amount = float(recs_yesterday[0].get('total_amount', 0)) if recs_yesterday else 0.0
+
+ # 计算趋势
+ if yesterday_amount == 0:
+ return {'trend': 'flat', 'percentage': 0, 'value': today_amount}
+
+ change_pct = ((today_amount - yesterday_amount) / yesterday_amount) * 100
+
+ if change_pct > 5:
+ trend = 'up'
+ elif change_pct < -5:
+ trend = 'down'
+ else:
+ trend = 'flat'
+
+ return {'trend': trend, 'percentage': abs(change_pct), 'value': today_amount}
+
+
async def get_total_users(request):
"""获取用户总数"""
env = request._run_ns
@@ -526,6 +590,8 @@ def load_dashboard():
g = ServerEnv()
g.get_today_usage = get_today_usage
g.get_today_amount = get_today_amount
+ g.get_usage_trend = get_usage_trend
+ g.get_amount_trend = get_amount_trend
g.get_total_users = get_total_users
g.get_concurrent_users = get_concurrent_users
g.get_top_models = get_top_models
diff --git a/wwwroot/stat_active_users.ui b/wwwroot/stat_active_users.ui
index 76c276f..fbfe264 100644
--- a/wwwroot/stat_active_users.ui
+++ b/wwwroot/stat_active_users.ui
@@ -5,7 +5,8 @@
"padding": "20px",
"borderRadius": "12px",
"flex": "1",
- "minHeight": "110px"
+ "minHeight": "110px",
+ "borderLeft": "4px solid #22c55e"
},
"subwidgets": [
{
diff --git a/wwwroot/stat_concurrent.ui b/wwwroot/stat_concurrent.ui
index dbe99e7..0ecfdeb 100644
--- a/wwwroot/stat_concurrent.ui
+++ b/wwwroot/stat_concurrent.ui
@@ -4,7 +4,8 @@
"padding": "20px",
"borderRadius": "12px",
"flex": "1",
- "minHeight": "110px"
+ "minHeight": "110px",
+ "borderLeft": "4px solid #06b6d4"
},
"subwidgets": [
{
diff --git a/wwwroot/stat_errors.ui b/wwwroot/stat_errors.ui
index 1dcb2ac..132a25c 100644
--- a/wwwroot/stat_errors.ui
+++ b/wwwroot/stat_errors.ui
@@ -5,7 +5,8 @@
"padding": "20px",
"borderRadius": "12px",
"flex": "1",
- "minHeight": "110px"
+ "minHeight": "110px",
+ "borderLeft": "4px solid #ef4444"
},
"subwidgets": [
{
diff --git a/wwwroot/stat_new_users_month.ui b/wwwroot/stat_new_users_month.ui
index 57390fc..20fee02 100644
--- a/wwwroot/stat_new_users_month.ui
+++ b/wwwroot/stat_new_users_month.ui
@@ -1,10 +1,12 @@
{
"widgettype": "VBox",
"options": {
+ "css": "stat-card",
"padding": "20px",
"borderRadius": "12px",
"flex": "1",
- "minHeight": "110px"
+ "minHeight": "110px",
+ "borderLeft": "4px solid #10b981"
},
"subwidgets": [
{
diff --git a/wwwroot/stat_today_amount.ui b/wwwroot/stat_today_amount.ui
index 8eaa019..0e1ed18 100644
--- a/wwwroot/stat_today_amount.ui
+++ b/wwwroot/stat_today_amount.ui
@@ -1,3 +1,4 @@
+{% set trend = get_amount_trend(request) %}
{
"widgettype": "VBox",
"options": {
@@ -5,7 +6,8 @@
"padding": "20px",
"borderRadius": "12px",
"flex": "1",
- "minHeight": "110px"
+ "minHeight": "110px",
+ "borderLeft": "4px solid {% if trend.trend == 'up' %}#22c55e{% elif trend.trend == 'down' %}#ef4444{% else %}#8b5cf6{% endif %}"
},
"subwidgets": [
{
@@ -20,7 +22,8 @@
"options": {
"svg": "",
"width": "24px",
- "height": "24px"
+ "height": "24px",
+ "color": "#8b5cf6"
}
},
{
@@ -32,21 +35,58 @@
"widgettype": "Text",
"options": {
"css": "stat-value",
- "text": "¥{{get_today_amount(request)|round(2)}}",
+ "text": "¥{{trend.value|round(2)}}",
"fontSize": "32px",
"fontWeight": "700",
"lineHeight": "1.1"
}
},
{
- "widgettype": "Text",
+ "widgettype": "HBox",
"options": {
- "css": "stat-label",
- "fontSize": "14px",
- "marginTop": "4px",
- "otext": "今日交易金额",
- "i18n": true
- }
+ "alignItems": "center",
+ "marginTop": "4px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Text",
+ "options": {
+ "css": "stat-label",
+ "fontSize": "14px",
+ "otext": "今日交易金额",
+ "i18n": true
+ }
+ },
+ {
+ "widgettype": "Filler"
+ },
+ {
+ "widgettype": "HBox",
+ "options": {
+ "alignItems": "center",
+ "gap": "4px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": "{% if trend.trend == 'up' %}{% elif trend.trend == 'down' %}{% else %}{% endif %}",
+ "width": "16px",
+ "height": "16px"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "{{trend.percentage|round(1)}}%",
+ "fontSize": "12px",
+ "fontWeight": "600",
+ "color": "{% if trend.trend == 'up' %}#22c55e{% elif trend.trend == 'down' %}#ef4444{% else %}#94a3b8{% endif %}"
+ }
+ }
+ ]
+ }
+ ]
}
]
}
diff --git a/wwwroot/stat_today_usage.ui b/wwwroot/stat_today_usage.ui
index 2b99454..dc9e7fe 100644
--- a/wwwroot/stat_today_usage.ui
+++ b/wwwroot/stat_today_usage.ui
@@ -1,3 +1,4 @@
+{% set trend = get_usage_trend(request) %}
{
"widgettype": "VBox",
"options": {
@@ -6,7 +7,8 @@
"borderRadius": "12px",
"flex": "1",
"minHeight": "110px",
- "cursor": "pointer"
+ "cursor": "pointer",
+ "borderLeft": "4px solid {% if trend.trend == 'up' %}#22c55e{% elif trend.trend == 'down' %}#ef4444{% else %}#3b82f6{% endif %}"
},
"subwidgets": [
{
@@ -21,7 +23,8 @@
"options": {
"svg": "",
"width": "24px",
- "height": "24px"
+ "height": "24px",
+ "color": "#3b82f6"
}
},
{
@@ -33,21 +36,58 @@
"widgettype": "Text",
"options": {
"css": "stat-value",
- "text": "{{get_today_usage(request)}}",
+ "text": "{{trend.value}}",
"fontSize": "32px",
"fontWeight": "700",
"lineHeight": "1.1"
}
},
{
- "widgettype": "Text",
+ "widgettype": "HBox",
"options": {
- "css": "stat-label",
- "fontSize": "14px",
- "marginTop": "4px",
- "otext": "今日调用笔数",
- "i18n": true
- }
+ "alignItems": "center",
+ "marginTop": "4px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Text",
+ "options": {
+ "css": "stat-label",
+ "fontSize": "14px",
+ "otext": "今日调用笔数",
+ "i18n": true
+ }
+ },
+ {
+ "widgettype": "Filler"
+ },
+ {
+ "widgettype": "HBox",
+ "options": {
+ "alignItems": "center",
+ "gap": "4px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": "{% if trend.trend == 'up' %}{% elif trend.trend == 'down' %}{% else %}{% endif %}",
+ "width": "16px",
+ "height": "16px"
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "{{trend.percentage|round(1)}}%",
+ "fontSize": "12px",
+ "fontWeight": "600",
+ "color": "{% if trend.trend == 'up' %}#22c55e{% elif trend.trend == 'down' %}#ef4444{% else %}#94a3b8{% endif %}"
+ }
+ }
+ ]
+ }
+ ]
}
]
}
\ No newline at end of file
diff --git a/wwwroot/stat_total_orgs.ui b/wwwroot/stat_total_orgs.ui
index f074316..c01b569 100644
--- a/wwwroot/stat_total_orgs.ui
+++ b/wwwroot/stat_total_orgs.ui
@@ -1,10 +1,12 @@
{
"widgettype": "VBox",
"options": {
+ "css": "stat-card",
"padding": "20px",
"borderRadius": "12px",
"flex": "1",
- "minHeight": "110px"
+ "minHeight": "110px",
+ "borderLeft": "4px solid #8b5cf6"
},
"subwidgets": [
{
diff --git a/wwwroot/stat_total_users.ui b/wwwroot/stat_total_users.ui
index 6b7dcc4..2b7f824 100644
--- a/wwwroot/stat_total_users.ui
+++ b/wwwroot/stat_total_users.ui
@@ -5,7 +5,8 @@
"padding": "20px",
"borderRadius": "12px",
"flex": "1",
- "minHeight": "110px"
+ "minHeight": "110px",
+ "borderLeft": "4px solid #3b82f6"
},
"subwidgets": [
{
@@ -20,7 +21,8 @@
"options": {
"svg": "",
"width": "24px",
- "height": "24px"
+ "height": "24px",
+ "color": "#3b82f6"
}
},
{