From 9aa917bce57d69dd7e4c376ff76ab8af9181d1e9 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 26 May 2026 14:26:38 +0800 Subject: [PATCH] feat: add data_filter and CRUD endpoints for llm table - Add data_filter with 4 searchable fields (name LIKE, model LIKE, providerid, upappid) - Add filter_labels for search form display - Create llm_list.dspy with DBFilter support and LIKE wildcard handling - Create llm_create.dspy, llm_update.dspy, llm_delete.dspy - Create get_organizations.dspy and get_upapps.dspy for dropdown options - Add browserfields alters for providerid and upappid dropdowns - Add editable URLs for DataViewer CRUD operations --- json/llm.json | 36 +++++++++++++- wwwroot/api/get_organizations.dspy | 20 ++++++++ wwwroot/api/get_upapps.dspy | 18 +++++++ wwwroot/api/llm_create.dspy | 21 ++++++++ wwwroot/api/llm_delete.dspy | 23 +++++++++ wwwroot/api/llm_list.dspy | 78 ++++++++++++++++++++++++++++++ wwwroot/api/llm_update.dspy | 23 +++++++++ 7 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 wwwroot/api/get_organizations.dspy create mode 100644 wwwroot/api/get_upapps.dspy create mode 100644 wwwroot/api/llm_create.dspy create mode 100644 wwwroot/api/llm_delete.dspy create mode 100644 wwwroot/api/llm_list.dspy create mode 100644 wwwroot/api/llm_update.dspy diff --git a/json/llm.json b/json/llm.json index 5ec5086..8f3ea63 100644 --- a/json/llm.json +++ b/json/llm.json @@ -4,6 +4,21 @@ "params": { "sortby":"model", "logined_userorgid": "ownerid", + "data_url": "{{entire_url('../api/llm_list.dspy')}}", + "data_filter": { + "AND": [ + {"field": "name", "op": "LIKE", "var": "name_input"}, + {"field": "model", "op": "LIKE", "var": "model_input"}, + {"field": "providerid", "op": "=", "var": "providerid_input"}, + {"field": "upappid", "op": "=", "var": "upappid_input"} + ] + }, + "filter_labels": { + "name_input": "名称", + "model_input": "识别名", + "providerid_input": "供应商", + "upappid_input": "上位系统" + }, "browserfields": { "exclouded": ["id", "ownerid"], "alters": { @@ -11,7 +26,21 @@ "dataurl":"{{entire_url('/pricing/get_all_pricing_programs.dspy')}}", "textField": "name", "valueField": "id" - } + }, + "providerid": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_organizations.dspy')}}", + "data_field": "organizations", + "textField": "text", + "valueField": "id" + }, + "upappid": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_upapps.dspy')}}", + "data_field": "upapps", + "textField": "text", + "valueField": "id" + } } }, "toolbar": { @@ -45,6 +74,11 @@ "editexclouded": [ "id", "ownerid" ], + "editable": { + "new_data_url": "{{entire_url('../api/llm_create.dspy')}}", + "update_data_url": "{{entire_url('../api/llm_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/llm_delete.dspy')}}" + }, "subtables":[ { "field":"llmid", diff --git a/wwwroot/api/get_organizations.dspy b/wwwroot/api/get_organizations.dspy new file mode 100644 index 0000000..d4a51b7 --- /dev/null +++ b/wwwroot/api/get_organizations.dspy @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +import json + +result = {'success': False, 'data': {'organizations': []}} + +try: + async with get_sor_context(request._run_ns, 'rbac') as sor: + user_orgid = await get_userorgid() + orgs = await sor.R('organization', {'id': user_orgid}) + # Get current org and its children + all_orgs = await sor.sqlExe( + "select id, orgname from organization where id = ${id}$ or parentid = ${id}$ order by orgname", + {'id': user_orgid} + ) + result['data']['organizations'] = [{'id': r['id'], 'text': r['orgname']} for r in (all_orgs or [])] + result['success'] = True +except Exception as e: + result['error'] = str(e) + +return json.dumps(result, ensure_ascii=False, default=str) diff --git a/wwwroot/api/get_upapps.dspy b/wwwroot/api/get_upapps.dspy new file mode 100644 index 0000000..b7a9a02 --- /dev/null +++ b/wwwroot/api/get_upapps.dspy @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import json + +result = {'success': False, 'data': {'upapps': []}} + +try: + async with get_sor_context(request._run_ns, 'uapi') as sor: + user_orgid = await get_userorgid() + apps = await sor.sqlExe( + "select id, name from upapp where ownerid = ${ownerid}$ or ownerid is null order by name", + {'ownerid': user_orgid} + ) + result['data']['upapps'] = [{'id': r['id'], 'text': r['name']} for r in (apps or [])] + result['success'] = True +except Exception as e: + result['error'] = str(e) + +return json.dumps(result, ensure_ascii=False, default=str) diff --git a/wwwroot/api/llm_create.dspy b/wwwroot/api/llm_create.dspy new file mode 100644 index 0000000..e45f74a --- /dev/null +++ b/wwwroot/api/llm_create.dspy @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import json +from appPublic.uniqueID import getID + +result = {'success': False, 'message': ''} + +try: + dbname = get_module_dbname('llmage') + async with DBPools().sqlorContext(dbname) as sor: + data = params_kw.copy() + data.pop('page', None) + data.pop('rows', None) + data.pop('data_filter', None) + data['id'] = getID() + await sor.C('llm', data) + result['success'] = True + result['message'] = '创建成功' +except Exception as e: + result['message'] = str(e) + +return json.dumps(result, ensure_ascii=False, default=str) diff --git a/wwwroot/api/llm_delete.dspy b/wwwroot/api/llm_delete.dspy new file mode 100644 index 0000000..21793b8 --- /dev/null +++ b/wwwroot/api/llm_delete.dspy @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import json + +result = {'success': False, 'message': ''} + +try: + dbname = get_module_dbname('llmage') + async with DBPools().sqlorContext(dbname) as sor: + data = params_kw.copy() + data.pop('page', None) + data.pop('rows', None) + data.pop('data_filter', None) + record_id = data.get('id') + if not record_id: + result['message'] = '缺少id' + else: + await sor.D('llm', {'id': record_id}) + result['success'] = True + result['message'] = '删除成功' +except Exception as e: + result['message'] = str(e) + +return json.dumps(result, ensure_ascii=False, default=str) diff --git a/wwwroot/api/llm_list.dspy b/wwwroot/api/llm_list.dspy new file mode 100644 index 0000000..f6ef7cf --- /dev/null +++ b/wwwroot/api/llm_list.dspy @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +import json +from sqlor.filter import DBFilter + +result = {'success': False, 'rows': [], 'total': 0} + +try: + dbname = get_module_dbname('llmage') + page = int(params_kw.get('page', 1)) + rows_per_page = int(params_kw.get('rows', 20)) + offset = (page - 1) * rows_per_page + + # Get data_filter JSON from frontend + filterjson_str = params_kw.get('data_filter') + filterjson = None + if filterjson_str: + try: + filterjson = json.loads(filterjson_str) + except (json.JSONDecodeError, TypeError): + filterjson = None + + async with DBPools().sqlorContext(dbname) as sor: + where_clause = '' + filterdic = {} + if filterjson: + # Preprocess LIKE values: add wildcards if user didn't provide them + ns = dict(params_kw) + for key, val in ns.items(): + # Check if this var is used with LIKE op in filterjson + if _is_like_var(filterjson, key) and val and '%' not in val: + ns[key] = f'%{val}%' + + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + where_clause = f' WHERE {conds}' + filterdic = ns + + # Total count + count_sql = f"select count(*) as cnt from llm{where_clause}" + count_rows = await sor.sqlExe(count_sql, filterdic) + total = count_rows[0]['cnt'] if count_rows else 0 + + # Paginated data + data_sql = f""" + select id, name, model, description, iconid, upappid, providerid, + ownerid, enabled_date, expired_date, min_balance + from llm{where_clause} + order by model + limit ${limit}$ offset ${offset}$ + """ + rows = await sor.sqlExe(data_sql, {**filterdic, 'limit': rows_per_page, 'offset': offset}) + result['rows'] = [dict(r) for r in (rows or [])] + result['total'] = total + result['success'] = True + +except Exception as e: + result['error'] = str(e) + +return json.dumps(result, ensure_ascii=False, default=str) + + +def _is_like_var(filterjson, varname): + """Check if a var is used with LIKE operator in the filter tree.""" + if not filterjson: + return False + for key, val in filterjson.items(): + if key.upper() in ('AND', 'OR') and isinstance(val, list): + for item in val: + if _is_like_var(item, varname): + return True + elif key.upper() == 'NOT' and isinstance(val, dict): + if _is_like_var(val, varname): + return True + elif isinstance(val, dict) and val.get('var') == varname: + if val.get('op', '').upper() == 'LIKE': + return True + return False diff --git a/wwwroot/api/llm_update.dspy b/wwwroot/api/llm_update.dspy new file mode 100644 index 0000000..cffcba7 --- /dev/null +++ b/wwwroot/api/llm_update.dspy @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import json + +result = {'success': False, 'message': ''} + +try: + dbname = get_module_dbname('llmage') + async with DBPools().sqlorContext(dbname) as sor: + data = params_kw.copy() + data.pop('page', None) + data.pop('rows', None) + data.pop('data_filter', None) + record_id = data.pop('id', None) + if not record_id: + result['message'] = '缺少id' + else: + await sor.U('llm', data, {'id': record_id}) + result['success'] = True + result['message'] = '更新成功' +except Exception as e: + result['message'] = str(e) + +return json.dumps(result, ensure_ascii=False, default=str)