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,12 +4,41 @@
|
|||||||
"params": {
|
"params": {
|
||||||
"sortby":"model",
|
"sortby":"model",
|
||||||
"logined_userorgid": "ownerid",
|
"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": {
|
"browserfields": {
|
||||||
"exclouded": ["id", "ownerid"],
|
"exclouded": ["id", "ownerid"],
|
||||||
"alters": {
|
"alters": {
|
||||||
"ppid":{
|
"ppid":{
|
||||||
"dataurl":"{{entire_url('/pricing/get_all_pricing_programs.dspy')}}",
|
"dataurl":"{{entire_url('/pricing/get_all_pricing_programs.dspy')}}",
|
||||||
"textField": "name",
|
"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"
|
"valueField": "id"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,6 +74,11 @@
|
|||||||
"editexclouded": [
|
"editexclouded": [
|
||||||
"id", "ownerid"
|
"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":[
|
"subtables":[
|
||||||
{
|
{
|
||||||
"field":"llmid",
|
"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