diff --git a/json/llmusage.json b/json/llmusage.json index 3aabe13..a1d7b06 100644 --- a/json/llmusage.json +++ b/json/llmusage.json @@ -50,15 +50,23 @@ } ], "browserfields": { - "exclouded": ["id", "usages", "ioinfo"], + "exclouded": [ + "id", + "usages", + "ioinfo" + ], "alters": {} }, - "editexclouded": ["id", "usages", "ioinfo"], + "editexclouded": [ + "id", + "usages", + "ioinfo" + ], "editable": { - "get_data_url": "{{entire_url('get_llmusage.dspy')}}?pagerows=50", + "get_data_url": "{{entire_url('../api/llmusage_list.dspy')}}?pagerows=50", "new_data_url": "{{entire_url('../api/llmusage_create.dspy')}}", "update_data_url": "{{entire_url('../api/llmusage_update.dspy')}}", "delete_data_url": "{{entire_url('../api/llmusage_delete.dspy')}}" } } -} +} \ No newline at end of file diff --git a/scripts/load_path.py b/scripts/load_path.py index c078ff4..01a739a 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -157,6 +157,7 @@ PATHS_LOGINED = [ f"/{MOD}/llmusage/update_llmusage.dspy", f"/{MOD}/llmusage_usages_display.dspy", f"/{MOD}/llmusage_ioinfo_display.dspy", + f"/{MOD}/api/llmusage_list.dspy", # CRUD 子目录 — llmusage_accounting_failed/ f"/{MOD}/llmusage_accounting_failed/index.ui", diff --git a/wwwroot/api/llmusage_list.dspy b/wwwroot/api/llmusage_list.dspy new file mode 100644 index 0000000..9abb76b --- /dev/null +++ b/wwwroot/api/llmusage_list.dspy @@ -0,0 +1,96 @@ +result = {'success': False, 'rows': [], 'total': 0, 'page': 1, 'page_size': 50} + +try: + ns = params_kw.copy() + + if not ns.get('page'): + ns['page'] = 1 + if not ns.get('sort'): + ns['sort'] = 'use_time desc' + + filterjson = params_kw.get('data_filter') + if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None + + fields_str=r'''[ + {"name": "id", "title": "id", "type": "str", "length": 32}, + {"name": "llmid", "title": "模型id", "type": "str", "length": 32}, + {"name": "use_date", "title": "使用日期", "type": "date"}, + {"name": "use_time", "title": "使用时间", "type": "timestamp"}, + {"name": "userid", "title": "用户id", "type": "str", "length": 32}, + {"name": "usages", "title": "使用信息", "type": "text"}, + {"name": "ioinfo", "title": "交互内容", "type": "text"}, + {"name": "transno", "title": "交易号", "type": "str", "length": 32}, + {"name": "responsed_seconds", "title": "响应时间", "type": "float", "length": 18, "dec": 2}, + {"name": "finish_seconds", "title": "结束时间", "type": "float", "length": 18, "dec": 2}, + {"name": "status", "title": "状态", "type": "str", "length": 32}, + {"name": "taskid", "title": "任务号", "type": "str", "length": 256}, + {"name": "amount", "title": "交易金额", "type": "float", "length": 18, "dec": 2}, + {"name": "cost", "title": "交易成本", "type": "float", "length": 18, "dec": 2}, + {"name": "userorgid", "title": "用户机构id", "type": "str", "length": 32}, + {"name": "ownerid", "title": "模型机构id", "type": "str", "length": 32}, + {"name": "accounting_status", "title": "记账状态", "type": "str", "length": 32} + ]''' + ori_fields = json.loads(fields_str) + + filterdic = ns.copy() + filterdic['filterstr'] = '' + filterdic['userorgid'] = '${userorgid}$' + filterdic['userid'] = '${userid}$' + + if not filterjson: + # 排除大 TEXT 字段,避免生成 LIKE 过滤导致全表扫描 + filter_fields = [f['name'] for f in ori_fields if f['name'] not in ('usages', 'ioinfo')] + filterjson = default_filterjson(filter_fields, ns) + + if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds + + ac = ArgsConvert('[[', ']]') + sql_template = 'select id, llmid, use_date, use_time, userid, transno, responsed_seconds, finish_seconds, status, taskid, amount, cost, userorgid, ownerid, accounting_status from llmusage where 1=1 [[filterstr]]' + vars = ac.findAllVariables(sql_template) + NameSpace = {v: '${' + v + '}$' for v in vars if v != 'filterstr'} + filterdic.update(NameSpace) + where_sql = ac.convert(sql_template, filterdic) + + # 提取 WHERE 条件,避免 sqlPaging 子查询包装 + idx = where_sql.lower().find('where') + where_clause = where_sql[idx:] if idx >= 0 else 'where 1=1' + + sort_field = ns.get('sort', 'use_time desc') + + db = DBPools() + dbname = get_module_dbname('llmage') + + async with db.sqlorContext(dbname) as sor: + # 分离 count 和 data 查询,避免子查询包装 + count_sql = f'select count(*) as rcnt from llmusage {where_clause}' + count_recs = await sor.sqlExe(count_sql, ns) + total = count_recs[0].rcnt if count_recs else 0 + + page = int(ns.get('page', 1)) + rows_per_page = int(ns.get('rows', ns.get('pagerows', 50))) + offset = (page - 1) * rows_per_page + + data_sql = f'select id, llmid, use_date, use_time, userid, transno, responsed_seconds, finish_seconds, status, taskid, amount, cost, userorgid, ownerid, accounting_status from llmusage {where_clause} order by {sort_field} limit {rows_per_page} offset {offset}' + rows = await sor.sqlExe(data_sql, ns) + + result['success'] = True + result['total'] = total + result['rows'] = rows if rows else [] + result['page'] = page + result['page_size'] = rows_per_page + +except Exception as e: + debug(f'error: {traceback.format_exc()}') + result['error'] = str(e) + +return result