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
This commit is contained in:
parent
04913dbe42
commit
9aa917bce5
@ -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",
|
||||
|
||||
20
wwwroot/api/get_organizations.dspy
Normal file
20
wwwroot/api/get_organizations.dspy
Normal file
@ -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)
|
||||
18
wwwroot/api/get_upapps.dspy
Normal file
18
wwwroot/api/get_upapps.dspy
Normal file
@ -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)
|
||||
21
wwwroot/api/llm_create.dspy
Normal file
21
wwwroot/api/llm_create.dspy
Normal file
@ -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)
|
||||
23
wwwroot/api/llm_delete.dspy
Normal file
23
wwwroot/api/llm_delete.dspy
Normal file
@ -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)
|
||||
78
wwwroot/api/llm_list.dspy
Normal file
78
wwwroot/api/llm_list.dspy
Normal file
@ -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
|
||||
23
wwwroot/api/llm_update.dspy
Normal file
23
wwwroot/api/llm_update.dspy
Normal file
@ -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)
|
||||
Loading…
x
Reference in New Issue
Block a user