diff --git a/dashboard_for_sage/load_dashboard.py b/dashboard_for_sage/load_dashboard.py index 69d0a6a..a584453 100644 --- a/dashboard_for_sage/load_dashboard.py +++ b/dashboard_for_sage/load_dashboard.py @@ -586,6 +586,99 @@ async def get_customer_daily_trend(request): return result +async def get_customer_users_today(request): + """获取当前客户组织今日各用户使用统计(含总计行)""" + env = request._run_ns + org_id = await _get_org_id(request) + today = env.curDateString() + async with get_sor_context(env, 'sage') as sor: + sql = """ + SELECT + a.userid, + COALESCE(b.nick_name, b.username) as user_name, + COUNT(*) as call_cnt, + COALESCE(SUM(a.amount), 0) as total_amount + FROM llmusage a + LEFT JOIN users b ON a.userid = b.id + WHERE a.use_date = ${today}$ + AND a.userorgid = ${org_id}$ + GROUP BY a.userid, b.nick_name, b.username + ORDER BY call_cnt DESC + """ + recs = await sor.sqlExe(sql, {'today': today, 'org_id': org_id}) + result = [] + total_calls = 0 + total_amount = 0.0 + for r in recs: + c = int(r.get('call_cnt', 0)) + a = round(float(r.get('total_amount', 0)), 4) + total_calls += c + total_amount += a + result.append({ + 'user_name': r.get('user_name', 'Unknown'), + 'call_cnt': c, + 'total_amount': a + }) + # 追加总计行 + result.append({ + 'user_name': '【合计】', + 'call_cnt': total_calls, + 'total_amount': round(total_amount, 4) + }) + return result + + +async def get_customer_users_month(request): + """获取当前客户组织当月各用户使用统计(含总计行)""" + env = request._run_ns + org_id = await _get_org_id(request) + now = datetime.now() + month_start = now.strftime('%Y-%m-01') + if now.month == 12: + month_end = f'{now.year + 1}-01-01' + else: + month_end = f'{now.year}-{now.month + 1:02d}-01' + async with get_sor_context(env, 'sage') as sor: + sql = """ + SELECT + a.userid, + COALESCE(b.nick_name, b.username) as user_name, + COUNT(*) as call_cnt, + COALESCE(SUM(a.amount), 0) as total_amount + FROM llmusage a + LEFT JOIN users b ON a.userid = b.id + WHERE a.use_date >= ${month_start}$ + AND a.use_date < ${month_end}$ + AND a.userorgid = ${org_id}$ + GROUP BY a.userid, b.nick_name, b.username + ORDER BY call_cnt DESC + """ + recs = await sor.sqlExe(sql, { + 'month_start': month_start, + 'month_end': month_end, + 'org_id': org_id + }) + result = [] + total_calls = 0 + total_amount = 0.0 + for r in recs: + c = int(r.get('call_cnt', 0)) + a = round(float(r.get('total_amount', 0)), 4) + total_calls += c + total_amount += a + result.append({ + 'user_name': r.get('user_name', 'Unknown'), + 'call_cnt': c, + 'total_amount': a + }) + result.append({ + 'user_name': '【合计】', + 'call_cnt': total_calls, + 'total_amount': round(total_amount, 4) + }) + return result + + def load_dashboard(): """Register dashboard functions on ServerEnv""" g = ServerEnv() @@ -613,3 +706,5 @@ def load_dashboard(): g.get_customer_daily_summary = get_customer_daily_summary g.get_customer_month_summary = get_customer_month_summary g.get_customer_daily_trend = get_customer_daily_trend + g.get_customer_users_today = get_customer_users_today + g.get_customer_users_month = get_customer_users_month diff --git a/scripts/load_path.py b/scripts/load_path.py index 122ed38..4ee9713 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -79,6 +79,7 @@ paths = [ ("/dashboard_for_sage/customer_daily_chart.ui", "logined"), ("/dashboard_for_sage/customer_monthly_chart.ui", "logined"), ("/dashboard_for_sage/customer_daily_trend.ui", "logined"), + ("/dashboard_for_sage/customer_users_table.ui", "logined"), # API endpoints ("/dashboard_for_sage/api/top_models.dspy", "logined"), @@ -87,6 +88,8 @@ paths = [ ("/dashboard_for_sage/api/customer_daily_models.dspy", "logined"), ("/dashboard_for_sage/api/customer_monthly_models.dspy", "logined"), ("/dashboard_for_sage/api/customer_daily_trend.dspy", "logined"), + ("/dashboard_for_sage/api/customer_users_today.dspy", "logined"), + ("/dashboard_for_sage/api/customer_users_month.dspy", "logined"), ] diff --git a/wwwroot/api/customer_users_month.dspy b/wwwroot/api/customer_users_month.dspy new file mode 100644 index 0000000..45d91ba --- /dev/null +++ b/wwwroot/api/customer_users_month.dspy @@ -0,0 +1,6 @@ +# coding=utf-8 +"""Customer per-user month usage API""" +import json + +users = await get_customer_users_month(request) +return json.dumps(users, ensure_ascii=False, default=str) diff --git a/wwwroot/api/customer_users_today.dspy b/wwwroot/api/customer_users_today.dspy new file mode 100644 index 0000000..1163e9f --- /dev/null +++ b/wwwroot/api/customer_users_today.dspy @@ -0,0 +1,6 @@ +# coding=utf-8 +"""Customer per-user today usage API""" +import json + +users = await get_customer_users_today(request) +return json.dumps(users, ensure_ascii=False, default=str) diff --git a/wwwroot/customer_usage.ui b/wwwroot/customer_usage.ui index 05acf05..f6f5027 100644 --- a/wwwroot/customer_usage.ui +++ b/wwwroot/customer_usage.ui @@ -10,290 +10,20 @@ "options": { "css": "filler" }, - "subwidgets": [ - { - "widgettype": "HBox", - "options": { - "width": "100%", - "alignItems": "center", - "marginBottom": "24px" - }, - "subwidgets": [ - { - "widgettype": "Title2", - "options": { - "fontWeight": "700", - "otext": "客户专属监控", - "i18n": true - } - }, - { - "widgettype": "Filler" - }, - { - "widgettype": "Button", - "options": { - "label": "返回首页", - "borderRadius": "6px", - "padding": "6px 16px", - "fontSize": "13px" - }, - "binds": [ - { - "wid": "self", - "event": "click", - "actiontype": "urlwidget", - "target": "app.sage_main_content", - "options": { - "url": "{{entire_url('/dashboard_for_sage/index.ui')}}" - }, - "mode": "replace" - } - ] - } - ] - }, - { - "widgettype": "ResponsableBox", - "options": { - "gap": "16px", - "minWidth": "220px", - "marginBottom": "24px" - }, - "subwidgets": [ - { - "widgettype": "VBox", - "options": { - "css": "stat-card", - "padding": "20px", - "borderRadius": "12px", - "flex": "1", - "minHeight": "110px" - }, - "subwidgets": [ - { - "widgettype": "HBox", - "options": { - "alignItems": "center", - "marginBottom": "12px" - }, - "subwidgets": [ - { - "widgettype": "Svg", - "options": { - "svg": "", - "width": "24px", - "height": "24px" - } - }, - { - "widgettype": "Filler" - } - ] - }, - { - "widgettype": "Text", - "options": { - "css": "stat-value", - "text": "{{get_customer_daily_summary(request).cnt}}", - "fontSize": "32px", - "fontWeight": "700", - "lineHeight": "1.1" - } - }, - { - "widgettype": "Text", - "options": { - "css": "stat-label", - "fontSize": "14px", - "marginTop": "4px", - "otext": "今日调用次数", - "i18n": true - } - } - ] - }, - { - "widgettype": "VBox", - "options": { - "css": "stat-card", - "padding": "20px", - "borderRadius": "12px", - "flex": "1", - "minHeight": "110px" - }, - "subwidgets": [ - { - "widgettype": "HBox", - "options": { - "alignItems": "center", - "marginBottom": "12px" - }, - "subwidgets": [ - { - "widgettype": "Svg", - "options": { - "svg": "", - "width": "24px", - "height": "24px" - } - }, - { - "widgettype": "Filler" - } - ] - }, - { - "widgettype": "Text", - "options": { - "css": "stat-value", - "text": "¥{{get_customer_daily_summary(request).total_amount|round(2)}}", - "fontSize": "32px", - "fontWeight": "700", - "lineHeight": "1.1" - } - }, - { - "widgettype": "Text", - "options": { - "css": "stat-label", - "fontSize": "14px", - "marginTop": "4px", - "otext": "今日消费金额", - "i18n": true - } - } - ] - }, - { - "widgettype": "VBox", - "options": { - "css": "stat-card", - "padding": "20px", - "borderRadius": "12px", - "flex": "1", - "minHeight": "110px" - }, - "subwidgets": [ - { - "widgettype": "HBox", - "options": { - "alignItems": "center", - "marginBottom": "12px" - }, - "subwidgets": [ - { - "widgettype": "Svg", - "options": { - "svg": "", - "width": "24px", - "height": "24px" - } - }, - { - "widgettype": "Filler" - } - ] - }, - { - "widgettype": "Text", - "options": { - "css": "stat-value", - "text": "{{get_customer_month_summary(request).cnt}}", - "fontSize": "32px", - "fontWeight": "700", - "lineHeight": "1.1" - } - }, - { - "widgettype": "Text", - "options": { - "css": "stat-label", - "fontSize": "14px", - "marginTop": "4px", - "otext": "本月调用次数", - "i18n": true - } - } - ] - }, - { - "widgettype": "VBox", - "options": { - "css": "stat-card", - "padding": "20px", - "borderRadius": "12px", - "flex": "1", - "minHeight": "110px" - }, - "subwidgets": [ - { - "widgettype": "HBox", - "options": { - "alignItems": "center", - "marginBottom": "12px" - }, - "subwidgets": [ - { - "widgettype": "Svg", - "options": { - "svg": "", - "width": "24px", - "height": "24px" - } - }, - { - "widgettype": "Filler" - } - ] - }, - { - "widgettype": "Text", - "options": { - "css": "stat-value", - "text": "¥{{get_customer_month_summary(request).total_amount|round(2)}}", - "fontSize": "32px", - "fontWeight": "700", - "lineHeight": "1.1" - } - }, - { - "widgettype": "Text", - "options": { - "css": "stat-label", - "fontSize": "14px", - "marginTop": "4px", - "otext": "本月消费金额", - "i18n": true - } - } - ] - } - ] - }, - { - "widgettype": "VBox", - "options": { - "css": "card", - "width": "100%", - "borderRadius": "12px", - "padding": "20px", - "marginBottom": "20px" - }, "subwidgets": [ { "widgettype": "HBox", "options": { "width": "100%", "alignItems": "center", - "marginBottom": "16px" + "marginBottom": "24px" }, "subwidgets": [ { - "widgettype": "Title4", + "widgettype": "Title2", "options": { - "fontWeight": "600", - "otext": "本月每日调用趋势", + "fontWeight": "700", + "otext": "客户专属监控", "i18n": true } }, @@ -303,20 +33,424 @@ { "widgettype": "Button", "options": { - "label": "刷新", - "border": "none", + "label": "返回首页", "borderRadius": "6px", - "padding": "4px 12px", - "fontSize": "12px" + "padding": "6px 16px", + "fontSize": "13px" }, "binds": [ { "wid": "self", "event": "click", - "actiontype": "method", - "target": "-@ChartLine", - "method": "render_urldata", - "params": {} + "actiontype": "urlwidget", + "target": "app.sage_main_content", + "options": { + "url": "{{entire_url('/dashboard_for_sage/index.ui')}}" + }, + "mode": "replace" + } + ] + } + ] + }, + { + "widgettype": "ResponsableBox", + "options": { + "gap": "16px", + "minWidth": "220px", + "marginBottom": "24px" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": { + "css": "stat-card", + "padding": "20px", + "borderRadius": "12px", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "css": "stat-value", + "text": "{{get_customer_daily_summary(request).cnt}}", + "fontSize": "32px", + "fontWeight": "700", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "css": "stat-label", + "fontSize": "14px", + "marginTop": "4px", + "otext": "今日调用次数", + "i18n": true + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "css": "stat-card", + "padding": "20px", + "borderRadius": "12px", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "css": "stat-value", + "text": "¥{{get_customer_daily_summary(request).total_amount|round(2)}}", + "fontSize": "32px", + "fontWeight": "700", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "css": "stat-label", + "fontSize": "14px", + "marginTop": "4px", + "otext": "今日消费金额", + "i18n": true + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "css": "stat-card", + "padding": "20px", + "borderRadius": "12px", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "css": "stat-value", + "text": "{{get_customer_month_summary(request).cnt}}", + "fontSize": "32px", + "fontWeight": "700", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "css": "stat-label", + "fontSize": "14px", + "marginTop": "4px", + "otext": "本月调用次数", + "i18n": true + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "css": "stat-card", + "padding": "20px", + "borderRadius": "12px", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "css": "stat-value", + "text": "¥{{get_customer_month_summary(request).total_amount|round(2)}}", + "fontSize": "32px", + "fontWeight": "700", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "css": "stat-label", + "fontSize": "14px", + "marginTop": "4px", + "otext": "本月消费金额", + "i18n": true + } + } + ] + } + ] + }, + { + "widgettype": "VBox", + "options": { + "css": "card", + "width": "100%", + "borderRadius": "12px", + "padding": "20px", + "marginBottom": "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": "-@ChartLine", + "method": "render_urldata", + "params": {} + } + ] + } + ] + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('customer_daily_trend.ui')}}" + } + } + ] + }, + { + "widgettype": "HBox", + "options": { + "width": "100%", + "gap": "20px", + "marginBottom": "20px" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": { + "css": "card", + "width": "50%", + "borderRadius": "12px", + "padding": "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('customer_daily_chart.ui')}}" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "css": "card", + "width": "50%", + "borderRadius": "12px", + "padding": "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('customer_monthly_chart.ui')}}" + } } ] } @@ -325,138 +459,10 @@ { "widgettype": "urlwidget", "options": { - "url": "{{entire_url('customer_daily_trend.ui')}}" + "url": "{{entire_url('customer_users_table.ui')}}" } } ] - }, - { - "widgettype": "HBox", - "options": { - "width": "100%", - "gap": "20px", - "marginBottom": "20px" - }, - "subwidgets": [ - { - "widgettype": "VBox", - "options": { - "css": "card", - "width": "50%", - "borderRadius": "12px", - "padding": "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('customer_daily_chart.ui')}}" - } - } - ] - }, - { - "widgettype": "VBox", - "options": { - "css": "card", - "width": "50%", - "borderRadius": "12px", - "padding": "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('customer_monthly_chart.ui')}}" - } - } - ] - } - ] - } - ] } ] -} +} \ No newline at end of file diff --git a/wwwroot/customer_users_table.ui b/wwwroot/customer_users_table.ui new file mode 100644 index 0000000..47bda65 --- /dev/null +++ b/wwwroot/customer_users_table.ui @@ -0,0 +1,120 @@ +{ + "widgettype": "VBox", + "options": { + "css": "card", + "width": "100%", + "borderRadius": "12px", + "padding": "20px", + "marginBottom": "20px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "width": "100%", + "alignItems": "center", + "marginBottom": "16px" + }, + "subwidgets": [ + { + "widgettype": "Title4", + "options": { + "fontWeight": "600", + "otext": "按用户统计", + "i18n": true + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "HBox", + "options": { + "width": "100%", + "gap": "20px" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": { + "width": "50%" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "otext": "今日", + "i18n": true, + "fontSize": "14px", + "fontWeight": "600", + "marginBottom": "8px" + } + }, + { + "widgettype": "Tabular", + "options": { + "data_url": "{{entire_url('api/customer_users_today.dspy')}}", + "data_method": "GET", + "width": "100%", + "height": "auto", + "row_options": { + "browserfields": { + "exclouded": [], + "cwidth": {} + }, + "fields": [ + {"name": "user_name", "title": "用户", "type": "str", "uitype": "str", "label": "用户", "cwidth": 20}, + {"name": "call_cnt", "title": "调用次数", "type": "int", "uitype": "int", "label": "调用次数", "cwidth": 12}, + {"name": "total_amount", "title": "金额", "type": "float", "uitype": "float", "label": "金额", "cwidth": 12} + ] + }, + "page_rows": 50 + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "width": "50%" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "otext": "本月", + "i18n": true, + "fontSize": "14px", + "fontWeight": "600", + "marginBottom": "8px" + } + }, + { + "widgettype": "Tabular", + "options": { + "data_url": "{{entire_url('api/customer_users_month.dspy')}}", + "data_method": "GET", + "width": "100%", + "height": "auto", + "row_options": { + "browserfields": { + "exclouded": [], + "cwidth": {} + }, + "fields": [ + {"name": "user_name", "title": "用户", "type": "str", "uitype": "str", "label": "用户", "cwidth": 20}, + {"name": "call_cnt", "title": "调用次数", "type": "int", "uitype": "int", "label": "调用次数", "cwidth": 12}, + {"name": "total_amount", "title": "金额", "type": "float", "uitype": "float", "label": "金额", "cwidth": 12} + ] + }, + "page_rows": 50 + } + } + ] + } + ] + } + ] +}