From 6d14347ebcc5578bf247b1e3e0366a4c760144de Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 25 May 2026 18:43:35 +0800 Subject: [PATCH] feat: add llmage module stat cards - model count, today's usage, amount, catalog count - Create stats.py with get_llmage_stats() helper function - Add 4 stat widgets: stat_total_models, stat_today_calls, stat_today_amount, stat_catelog_count - Update index.ui to display stat cards row above navigation cards - Register get_llmage_stats in load_llmage() --- dat/qwen3.7-max.txt | 53 ++++++++++++++ llmage/__pycache__/accounting.cpython-310.pyc | Bin 0 -> 9974 bytes llmage/init.py | 2 + llmage/stats.py | 69 ++++++++++++++++++ wwwroot/index.ui | 34 +++++++++ wwwroot/stat_catelog_count.ui | 53 ++++++++++++++ wwwroot/stat_today_amount.ui | 53 ++++++++++++++ wwwroot/stat_today_calls.ui | 53 ++++++++++++++ wwwroot/stat_total_models.ui | 53 ++++++++++++++ 9 files changed, 370 insertions(+) create mode 100644 dat/qwen3.7-max.txt create mode 100644 llmage/__pycache__/accounting.cpython-310.pyc create mode 100644 llmage/stats.py create mode 100644 wwwroot/stat_catelog_count.ui create mode 100644 wwwroot/stat_today_amount.ui create mode 100644 wwwroot/stat_today_calls.ui create mode 100644 wwwroot/stat_total_models.ui diff --git a/dat/qwen3.7-max.txt b/dat/qwen3.7-max.txt new file mode 100644 index 0000000..f07f135 --- /dev/null +++ b/dat/qwen3.7-max.txt @@ -0,0 +1,53 @@ + 已生成完整的 qwen3.7-max 配置SQL。以下是配置方案: + + 模型摘要 + - 模型名称: 千问3.7-Max + - API model: qwen3.7-max (严格按API文档填写) + - 分类: text2text (文生文) + - 供应商: 阿里百炼 (ali-qwen) + - 接口: OpenAI兼容 /chat/completions,同步流式 + + 复用vs新建 + - upapp: 复用 ali-qwen (阿里百炼) + - upappkey: 复用 gCMl-BnrTrfoqWAEPPoH8 (API Key已配置) + - uapi: 复用 t2t (BY2cA4RD_axNxTG4m9Coa) + - uapiio: 复用 Is8l4TGkcZcqFSjbbeIK2 (文本会话) + - llm: 新建 + - llm_api_map: 新建 + - pricing_program: 新建 + - pricing_program_timing: 新建 + + 生成的SQL (4条) + + sql + -- 1. llm表 + INSERT INTO llm VALUES ('Jt26VKlUFsJxABuVLauBc','千问3.7-Max','qwen3.7-max','通义千问3.7系列旗舰模型,相较3.6版本在推理能力、代码能力、多模态理解等方面全面升级。适用于复杂任务处理、智能体开发、代码生成等高级应用场景。','text2text','qwen','ali-qwen','t2t','6fadgewjraOyvxC_EkHou','0','2026-05-23','9999-12-31','',NULL,10.00); + + -- 2. llm_api_map表 + INSERT INTO llm_api_map VALUES ('rFS0Ag-epBUxE5LCF6glW','Jt26VKlUFsJxABuVLauBc','text2text','t2t',NULL,NULL,'0B-JNSUEelsY5TQ_Sawa7','1'); + + -- 3. pricing_program表 + INSERT INTO pricing_program VALUES ('0B-JNSUEelsY5TQ_Sawa7','qwen3.7-max定价','fields:\r\n formula:\r\n label: 计算公式\r\n type: str\r\n model:\r\n label: 模型\r\n type: str\r\n prompt_tokens:\r\n label: 输入词元数\r\n type: int\r\n value_mode: between\r\nformula: null\r\npricings:\r\n- formula: 2.5 prompt_tokens / 1000000 + completion_tokens 10 /1000000\r\n model: qwen3.7-max\r\n prompt_tokens: -0.1 ~= 32000\r\n- formula: 4 prompt_tokens / 1000000 + completion_tokens 16 /1000000\r\n model: qwen3.7-max\r\n prompt_tokens: 32000 ~= 128000\r\n- formula: 7 prompt_tokens / 1000000 + completion_tokens 28 /1000000\r\n model: qwen3.7-max\r\n prompt_tokens: 128000 ~= 256000\r\n- formula: 10 prompt_tokens / 1000000 + completion_tokens 40 /1000000\r\n model: qwen3.7-max\r\n prompt_tokens: 256000 ~= 1000000','2026-05-23','9999-12-31'); + + -- 4. pricing_program_timing表 + INSERT INTO pricing_program_timing VALUES ('C-IIEG_9_O9beN8S2vdUI','0B-JNSUEelsY5TQ_Sawa7','qwen3.7-max计费','fields:\r\n formula:\r\n label: 计算公式\r\n type: str\r\n model:\r\n label: 模型\r\n type: str\r\n prompt_tokens:\r\n label: 输入词元数\r\n type: int\r\n value_mode: between\r\nformula: null\r\npricings:\r\n- formula: 2.5 prompt_tokens / 1000000 + completion_tokens 10 /1000000\r\n model: qwen3.7-max\r\n prompt_tokens: -0.1 ~= 32000\r\n- formula: 4 prompt_tokens / 1000000 + completion_tokens 16 /1000000\r\n model: qwen3.7-max\r\n prompt_tokens: 32000 ~= 128000\r\n- formula: 7 prompt_tokens / 1000000 + completion_tokens 28 /1000000\r\n model: qwen3.7-max\r\n prompt_tokens: 128000 ~= 256000\r\n- formula: 10 prompt_tokens / 1000000 + completion_tokens 40 /1000000\r\n model: qwen3.7-max\r\n prompt_tokens: 256000 ~= 1000000','2026-05-23','9999-12-31'); + + + 验证查询 + sql + SELECT id, name, model, upappid, apiname FROM llm WHERE model = 'qwen3.7-max'; + SELECT id, llmid, llmcatelogid, apiname, ppid FROM llm_api_map WHERE llmid = 'Jt26VKlUFsJxABuVLauBc'; + SELECT id, name FROM pricing_program WHERE id = '0B-JNSUEelsY5TQ_Sawa7'; + + + ppid三处一致 + - pricing_program.id = 0B-JNSUEelsY5TQ_Sawa7 + - pricing_program_timing.ppid = 0B-JNSUEelsY5TQ_Sawa7 + - llm_api_map.ppid = 0B-JNSUEelsY5TQ_Sawa7 + + 注意事项 + 1. 定价参考qwen3.6-plus的阶梯式计费模式,具体价格需根据百炼官方定价调整 + 2. qwen3.7-max使用已有的t2t uapi,OpenAI兼容接口 + 3. 如果qwen3.7-max有特殊的推理模式(如思考/非思考切换),可能需要额外的配置 + 4. 之前提到的 startReasoning is not defined 前端报错问题需要单独排查,可能与harnessed_reasoning的bricks前端代码有关 + diff --git a/llmage/__pycache__/accounting.cpython-310.pyc b/llmage/__pycache__/accounting.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3958bc87be1ee569db36d9d9ae5561fe35271beb GIT binary patch literal 9974 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 ${today}$ + """ + recs = await sor.sqlExe(sql_models, {'today': today}) + if recs: + stats['total_models'] = int(recs[0].cnt or 0) + + # Today's usage count + sql_usage = """ + SELECT COUNT(*) as cnt FROM llmusage + WHERE userorgid = ${userorgid}$ + AND use_date >= ${today}$ + AND use_date < ${tomorrow}$ + """ + recs = await sor.sqlExe(sql_usage, { + 'userorgid': userorgid, + 'today': today, + 'tomorrow': tomorrow + }) + if recs: + stats['today_usage_count'] = int(recs[0].cnt or 0) + + # Today's total amount + sql_amount = """ + SELECT COALESCE(SUM(amount), 0) as total FROM llmusage + WHERE userorgid = ${userorgid}$ + AND use_date >= ${today}$ + AND use_date < ${tomorrow}$ + """ + recs = await sor.sqlExe(sql_amount, { + 'userorgid': userorgid, + 'today': today, + 'tomorrow': tomorrow + }) + if recs: + stats['today_amount'] = float(recs[0].total or 0) + + # Catalog count + sql_catelog = """ + SELECT COUNT(*) as cnt FROM llmcatelog + """ + recs = await sor.sqlExe(sql_catelog, {}) + if recs: + stats['catelog_count'] = int(recs[0].cnt or 0) + + return stats diff --git a/wwwroot/index.ui b/wwwroot/index.ui index 86137d7..c9dcf6e 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -35,6 +35,40 @@ } ] }, + { + "widgettype": "ResponsableBox", + "options": { + "gap": "16px", + "minWidth": "200px", + "marginBottom": "24px" + }, + "subwidgets": [ + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('/llmage/stat_total_models.ui')}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('/llmage/stat_today_calls.ui')}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('/llmage/stat_today_amount.ui')}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('/llmage/stat_catelog_count.ui')}}" + } + } + ] + }, { "widgettype": "ResponsableBox", "options": { diff --git a/wwwroot/stat_catelog_count.ui b/wwwroot/stat_catelog_count.ui new file mode 100644 index 0000000..3bd121f --- /dev/null +++ b/wwwroot/stat_catelog_count.ui @@ -0,0 +1,53 @@ +{% set stats = get_llmage_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "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": { + "text": "{{stats.catelog_count}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "模型分类", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +} diff --git a/wwwroot/stat_today_amount.ui b/wwwroot/stat_today_amount.ui new file mode 100644 index 0000000..2ac8469 --- /dev/null +++ b/wwwroot/stat_today_amount.ui @@ -0,0 +1,53 @@ +{% set stats = get_llmage_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "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": { + "text": "¥{{'%.2f' % stats.today_amount}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "今日消费", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +} diff --git a/wwwroot/stat_today_calls.ui b/wwwroot/stat_today_calls.ui new file mode 100644 index 0000000..7edd862 --- /dev/null +++ b/wwwroot/stat_today_calls.ui @@ -0,0 +1,53 @@ +{% set stats = get_llmage_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "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": { + "text": "{{stats.today_usage_count}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "今日调用", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +} diff --git a/wwwroot/stat_total_models.ui b/wwwroot/stat_total_models.ui new file mode 100644 index 0000000..3861e52 --- /dev/null +++ b/wwwroot/stat_total_models.ui @@ -0,0 +1,53 @@ +{% set stats = get_llmage_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "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": { + "text": "{{stats.total_models}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "可用模型数", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +}