From 3785622bac2a91f2ae699701efae3b4b9decf524 Mon Sep 17 00:00:00 2001 From: ymq Date: Sat, 22 Aug 2026 11:17:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A1=B9=E7=9B=AE=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE=20CRUD=20=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=EF=BC=88=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E7=95=8C=E9=9D=A2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sd_project_role_models CRUD(index.ui + get/add/update/delete dspy) - 下拉选项:get_model_options(模型)/get_role_options(角色)/get_project_select_options(项目) - load_path 注册权限路径 + models 表模型 --- models/sd_project_role_models.json | 25 +++++++ scripts/load_path.py | 9 +++ wwwroot/api/get_model_options.dspy | 10 +++ wwwroot/api/get_project_select_options.dspy | 10 +++ wwwroot/api/get_role_options.dspy | 12 ++++ .../add_sd_project_role_models.dspy | 58 +++++++++++++++++ .../delete_sd_project_role_models.dspy | 44 +++++++++++++ .../get_sd_project_role_models.dspy | 61 +++++++++++++++++ wwwroot/sd_project_role_models/index.ui | 65 +++++++++++++++++++ .../update_sd_project_role_models.dspy | 47 ++++++++++++++ 10 files changed, 341 insertions(+) create mode 100644 models/sd_project_role_models.json create mode 100644 wwwroot/api/get_model_options.dspy create mode 100644 wwwroot/api/get_project_select_options.dspy create mode 100644 wwwroot/api/get_role_options.dspy create mode 100644 wwwroot/sd_project_role_models/add_sd_project_role_models.dspy create mode 100644 wwwroot/sd_project_role_models/delete_sd_project_role_models.dspy create mode 100644 wwwroot/sd_project_role_models/get_sd_project_role_models.dspy create mode 100644 wwwroot/sd_project_role_models/index.ui create mode 100644 wwwroot/sd_project_role_models/update_sd_project_role_models.dspy diff --git a/models/sd_project_role_models.json b/models/sd_project_role_models.json new file mode 100644 index 0000000..d378ec6 --- /dev/null +++ b/models/sd_project_role_models.json @@ -0,0 +1,25 @@ +{ + "summary": [ + { + "name": "sd_project_role_models", + "title": "项目角色模型配置", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + {"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"}, + {"name": "project_id", "title": "项目ID", "type": "str", "length": 32, "nullable": "no"}, + {"name": "role", "title": "角色名", "type": "str", "length": 64, "nullable": "no"}, + {"name": "model_name", "title": "模型名(llm.name,空=用项目缺省模型)", "type": "str", "length": 128}, + {"name": "created_at", "title": "创建时间", "type": "timestamp", "nullable": "no"}, + {"name": "updated_at", "title": "更新时间", "type": "timestamp"} + ], + "indexes": [ + {"name": "uk_sd_project_role_models_pr", "idxtype": "unique", "idxfields": ["project_id", "role"]} + ], + "codes": [ + {"field": "role", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='sd_role'"}, + {"field": "model_name", "table": "llm", "valuefield": "name", "textfield": "name", "cond": ""} + ] +} diff --git a/scripts/load_path.py b/scripts/load_path.py index 3038107..76aee6b 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -60,6 +60,12 @@ PATHS_LOGINED = [ f"/{MOD}/sd_deploy_envs/update_sd_deploy_envs.dspy", f"/{MOD}/sd_deploy_envs/delete_sd_deploy_envs.dspy", f"/{MOD}/sd_deploy_env_list/index.ui", + # Project Role Models(角色模型配置) + f"/{MOD}/sd_project_role_models/index.ui", + f"/{MOD}/sd_project_role_models/get_sd_project_role_models.dspy", + f"/{MOD}/sd_project_role_models/add_sd_project_role_models.dspy", + f"/{MOD}/sd_project_role_models/update_sd_project_role_models.dspy", + f"/{MOD}/sd_project_role_models/delete_sd_project_role_models.dspy", # Review f"/{MOD}/sd_review/index.ui", # API @@ -69,6 +75,9 @@ PATHS_LOGINED = [ f"/{MOD}/api/env_verify.dspy", f"/{MOD}/api/get_iteration_options.dspy", f"/{MOD}/api/get_project_options.dspy", + f"/{MOD}/api/get_project_select_options.dspy", + f"/{MOD}/api/get_role_options.dspy", + f"/{MOD}/api/get_model_options.dspy", f"/{MOD}/api/get_test_plan_options.dspy", f"/{MOD}/api/submit_bug.dspy", f"/{MOD}/api/sd_bug_create.dspy", diff --git a/wwwroot/api/get_model_options.dspy b/wwwroot/api/get_model_options.dspy new file mode 100644 index 0000000..386e549 --- /dev/null +++ b/wwwroot/api/get_model_options.dspy @@ -0,0 +1,10 @@ +import json +result = json.dumps([{'value': '', 'text': '缺省(用项目默认模型)'}], ensure_ascii=False) +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select name as value, name as text from llm where status='active' order by name") + r = [{'value': '', 'text': '缺省(用项目默认模型)'}] + list(rows) + return json.dumps(r, ensure_ascii=False) +except Exception as e: + debug(f'get_model_options error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_project_select_options.dspy b/wwwroot/api/get_project_select_options.dspy new file mode 100644 index 0000000..906c5f6 --- /dev/null +++ b/wwwroot/api/get_project_select_options.dspy @@ -0,0 +1,10 @@ +import json +result = json.dumps([{'value': '', 'text': '请选择'}], ensure_ascii=False) +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select id as value, name as text from sd_projects order by created_at desc") + r = [{'value': '', 'text': '请选择'}] + list(rows) + return json.dumps(r, ensure_ascii=False) +except Exception as e: + debug(f'get_project_select_options error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_role_options.dspy b/wwwroot/api/get_role_options.dspy new file mode 100644 index 0000000..f063eb2 --- /dev/null +++ b/wwwroot/api/get_role_options.dspy @@ -0,0 +1,12 @@ +import json +roles = [ + {'value': 'agent.pm', 'text': 'PM 项目经理'}, + {'value': 'agent.requirement', 'text': '需求分析师'}, + {'value': 'agent.design', 'text': '系统设计师'}, + {'value': 'agent.develop', 'text': '开发工程师'}, + {'value': 'agent.deploy_test', 'text': '测试部署工程师'}, + {'value': 'agent.test', 'text': '测试工程师'}, + {'value': 'agent.deploy_prod', 'text': '生产部署工程师'}, + {'value': 'agent.qc', 'text': 'QC 质量控制'}, +] +return json.dumps(roles, ensure_ascii=False) diff --git a/wwwroot/sd_project_role_models/add_sd_project_role_models.dspy b/wwwroot/sd_project_role_models/add_sd_project_role_models.dspy new file mode 100644 index 0000000..9bb54a6 --- /dev/null +++ b/wwwroot/sd_project_role_models/add_sd_project_role_models.dspy @@ -0,0 +1,58 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null' or v == '': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + +if not ns.get('project_id'): + return {"widgettype":"Error","options":{"title":"Error","timeout":3,"cwidth":16,"cheight":9,"message":"需要 project_id"}} +if not ns.get('role'): + return {"widgettype":"Error","options":{"title":"Error","timeout":3,"cwidth":16,"cheight":9,"message":"需要 role"}} + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } + +db = DBPools() +dbname = get_module_dbname('pipeline_sdlc') +async with db.sqlorContext(dbname) as sor: + # 已存在则更新(upsert by project_id+role) + r = await sor.sqlExe("select id from sd_project_role_models where project_id=${pid}$ and role=${role}$", {"pid": ns['project_id'], "role": ns['role']}) + if r: + await sor.sqlExe("update sd_project_role_models set model_name=${m}$ where id=${id}$", {"m": ns.get('model_name'), "id": r[0].id}) + else: + await sor.C('sd_project_role_models', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "cwidth":16, + "cheight":9, + "title":"Add Success", + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Add Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} diff --git a/wwwroot/sd_project_role_models/delete_sd_project_role_models.dspy b/wwwroot/sd_project_role_models/delete_sd_project_role_models.dspy new file mode 100644 index 0000000..119ed77 --- /dev/null +++ b/wwwroot/sd_project_role_models/delete_sd_project_role_models.dspy @@ -0,0 +1,44 @@ + +ns = params_kw.copy() +id = params_kw.id +if not id: + return {"widgettype":"Error","options":{"title":"Error","timeout":3,"cwidth":16,"cheight":9,"message":"需要 id"}} + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } + +db = DBPools() +dbname = get_module_dbname('pipeline_sdlc') +async with db.sqlorContext(dbname) as sor: + await sor.D('sd_project_role_models', {'id': id}) + return { + "widgettype":"Message", + "options":{ + "cwidth":16, + "cheight":9, + "title":"Delete Success", + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Delete Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} diff --git a/wwwroot/sd_project_role_models/get_sd_project_role_models.dspy b/wwwroot/sd_project_role_models/get_sd_project_role_models.dspy new file mode 100644 index 0000000..e354304 --- /dev/null +++ b/wwwroot/sd_project_role_models/get_sd_project_role_models.dspy @@ -0,0 +1,61 @@ + +ns = params_kw.copy() + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } + +debug(f'get_sd_project_role_models.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + ns['sort'] = ["role asc"] + +sql = '''select a.*, b.name as project_name +from (select * from sd_project_role_models where 1=1 [[filterstr]]) a +left join sd_projects b on a.project_id = b.id''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None + +if filterjson: + filterdic = ns.copy() + filterdic['filterstr'] = '' + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds + ac = ArgsConvert('[[', ']]') + vars = ac.findAllVariables(sql) + NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr'} + filterdic.update(NameSpace) + sql = ac.convert(sql, filterdic) +else: + ac = ArgsConvert('[[', ']]') + sql = ac.convert(sql, {'filterstr': ''}) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('pipeline_sdlc') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} diff --git a/wwwroot/sd_project_role_models/index.ui b/wwwroot/sd_project_role_models/index.ui new file mode 100644 index 0000000..033da6c --- /dev/null +++ b/wwwroot/sd_project_role_models/index.ui @@ -0,0 +1,65 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"sd_project_role_models_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + "title":"项目角色模型配置", + "css":"card", + "editable":{ + "new_data_url":"{{entire_url('add_sd_project_role_models.dspy')}}", + "delete_data_url":"{{entire_url('delete_sd_project_role_models.dspy')}}", + "update_data_url":"{{entire_url('update_sd_project_role_models.dspy')}}" + }, + "data_url":"{{entire_url('./get_sd_project_role_models.dspy')}}", + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + "browserfields":{ + "alters":{ + "project_id":{ + "uitype":"code", + "dataurl":"{{entire_url('../api/get_project_select_options.dspy')}}", + "valueField":"value", + "textField":"text" + }, + "role":{ + "uitype":"code", + "dataurl":"{{entire_url('../api/get_role_options.dspy')}}", + "valueField":"value", + "textField":"text" + }, + "model_name":{ + "uitype":"code", + "dataurl":"{{entire_url('../api/get_model_options.dspy')}}", + "valueField":"value", + "textField":"text" + } + } + }, + "editexclouded":["id","created_at","updated_at","project_name"], + "fields":[ + {"name":"id","title":"主键ID","type":"str","length":32,"cwidth":18,"uitype":"str","datatype":"str","label":"主键ID"}, + {"name":"project_name","title":"项目","type":"str","length":200,"cwidth":22,"uitype":"str","datatype":"str","label":"项目"}, + {"name":"project_id","title":"项目ID","type":"str","length":32,"cwidth":0,"uitype":"code","valueField":"value","textField":"text","dataurl":"{{entire_url('../api/get_project_select_options.dspy')}}","label":"项目ID"}, + {"name":"role","title":"角色","type":"str","length":64,"cwidth":20,"uitype":"code","valueField":"value","textField":"text","dataurl":"{{entire_url('../api/get_role_options.dspy')}}","label":"角色"}, + {"name":"model_name","title":"模型","type":"str","length":128,"cwidth":20,"uitype":"code","valueField":"value","textField":"text","dataurl":"{{entire_url('../api/get_model_options.dspy')}}","label":"模型(空=缺省)"}, + {"name":"created_at","title":"创建时间","type":"timestamp","uitype":"str","datatype":"timestamp","label":"创建时间"}, + {"name":"updated_at","title":"更新时间","type":"timestamp","uitype":"str","datatype":"timestamp","label":"更新时间"} + ] + }, + "data_filter":{ + "AND":[ + {"field":"role","op":"LIKE","var":"role_input"}, + {"field":"project_id","op":"=","var":"project_id_input"} + ] + }, + "page_rows":100, + "cache_limit":5 + } +}] +} diff --git a/wwwroot/sd_project_role_models/update_sd_project_role_models.dspy b/wwwroot/sd_project_role_models/update_sd_project_role_models.dspy new file mode 100644 index 0000000..f3e3066 --- /dev/null +++ b/wwwroot/sd_project_role_models/update_sd_project_role_models.dspy @@ -0,0 +1,47 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null' or v == '': + ns[k] = None +id = params_kw.id +if not id: + return {"widgettype":"Error","options":{"title":"Error","timeout":3,"cwidth":16,"cheight":9,"message":"需要 id"}} + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } + +db = DBPools() +dbname = get_module_dbname('pipeline_sdlc') +async with db.sqlorContext(dbname) as sor: + await sor.U('sd_project_role_models', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "cwidth":16, + "cheight":9, + "title":"Update Success", + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +}