From 4b2fd4cd7abf2c0dd027855a9f1a56960fa32528 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 10 Sep 2026 12:33:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(ownership):=20=E4=B8=89=E5=BC=A0=E8=B7=A8?= =?UTF-8?q?=E4=BA=A7=E7=BA=BF=E8=A1=A8=E5=BD=92=E5=B1=9E=E8=BF=81=E5=85=A5?= =?UTF-8?q?=E2=80=94=E2=80=94sd=5Fprojects/sd=5Fproject=5Frole=5Fmodels/pi?= =?UTF-8?q?peline=5Fdeliverables=20models+CRUD=20json+15=E4=B8=AAapi=20dsp?= =?UTF-8?q?y+=E6=89=8B=E5=86=99=E8=A7=92=E8=89=B2=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E9=A1=B5=E8=87=AApipeline-sdlc=E8=BF=81=E5=85=A5(=E7=AC=AC2?= =?UTF-8?q?=E8=BD=AE=E8=BF=81=E5=BD=92=E5=B1=9E)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - models: 3张表JSON迁入(sd_projects全产线项目容器/pipeline_deliverables被bidding+service+core写681行实证/sd_project_role_models被pipeline-service三处消费)——分层倒置修复:core的agent_config.py裸查sd_projects/pipeline_deliverables,低层查高层表 - json: sd_project_list.json/pipeline_deliverables_list.json迁入,subtables迭代/仓库页改绝对路径/pipeline-sdlc/**(跨模块相对路径会解析到core不存在的目录) - api: 15个CRUD页面依赖的dspy迁入(get_project_options等,entire_url('../api/')相对解析跟页面走);get_module_dbname('pipeline_sdlc')→pipeline_core(app层一律返pipeline,行为不变仅语义归位) - init/data.json: 4组字典随表迁(sd_project_status/sd_project_type/deliverable_type/review_status)——normalize幂等键{parentid}_{k}不变,import_init重跑无副作用 - .gitignore: 补wwwroot/sd_projects/+wwwroot/pipeline_deliverables/(xls2ui生成目录,build.sh第7步清单已含pipeline_core,重生成不脏工作树) --- .gitignore | 2 + init/data.json | 146 ++++++++++++++++-- json/pipeline_deliverables_list.json | 67 ++++++++ json/sd_project_list.json | 80 ++++++++++ models/pipeline_deliverables.json | 36 +++++ models/sd_project_role_models.json | 25 +++ models/sd_projects.json | 35 +++++ wwwroot/api/get_model_options.dspy | 16 ++ wwwroot/api/get_pipeline_options.dspy | 10 ++ wwwroot/api/get_project_options.dspy | 9 ++ wwwroot/api/get_project_select_options.dspy | 9 ++ wwwroot/api/get_role_options.dspy | 12 ++ wwwroot/api/get_search_deliverable_type.dspy | 9 ++ wwwroot/api/get_search_project_type.dspy | 9 ++ wwwroot/api/get_search_review_status.dspy | 9 ++ wwwroot/api/get_search_status.dspy | 9 ++ wwwroot/api/sd_deliverable_create.dspy | 26 ++++ wwwroot/api/sd_deliverable_delete.dspy | 14 ++ wwwroot/api/sd_deliverable_update.dspy | 19 +++ wwwroot/api/sd_project_create.dspy | 25 +++ wwwroot/api/sd_project_delete.dspy | 17 ++ wwwroot/api/sd_project_update.dspy | 33 ++++ .../add_sd_project_role_models.dspy | 69 +++++++++ .../delete_sd_project_role_models.dspy | 44 ++++++ .../get_sd_project_role_models.dspy | 87 +++++++++++ wwwroot/sd_project_role_models/index.ui | 66 ++++++++ .../update_sd_project_role_models.dspy | 60 +++++++ 27 files changed, 934 insertions(+), 9 deletions(-) create mode 100644 json/pipeline_deliverables_list.json create mode 100644 json/sd_project_list.json create mode 100644 models/pipeline_deliverables.json create mode 100644 models/sd_project_role_models.json create mode 100644 models/sd_projects.json create mode 100644 wwwroot/api/get_model_options.dspy create mode 100644 wwwroot/api/get_pipeline_options.dspy create mode 100644 wwwroot/api/get_project_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/api/get_search_deliverable_type.dspy create mode 100644 wwwroot/api/get_search_project_type.dspy create mode 100644 wwwroot/api/get_search_review_status.dspy create mode 100644 wwwroot/api/get_search_status.dspy create mode 100644 wwwroot/api/sd_deliverable_create.dspy create mode 100644 wwwroot/api/sd_deliverable_delete.dspy create mode 100644 wwwroot/api/sd_deliverable_update.dspy create mode 100644 wwwroot/api/sd_project_create.dspy create mode 100644 wwwroot/api/sd_project_delete.dspy create mode 100644 wwwroot/api/sd_project_update.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/.gitignore b/.gitignore index c99e572..bf8ded9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ wwwroot/llm/ wwwroot/pipeline_steps/ wwwroot/pipeline_versions/ wwwroot/pipelines/ +wwwroot/sd_projects/ +wwwroot/pipeline_deliverables/ diff --git a/init/data.json b/init/data.json index 2f01ed4..2c14b9f 100644 --- a/init/data.json +++ b/init/data.json @@ -4,21 +4,149 @@ "parentid": "pipeline_type", "parentname": "产线类型", "items": [ - {"k": "text", "v": "文本"}, - {"k": "audio", "v": "音频"}, - {"k": "video", "v": "视频"}, - {"k": "image", "v": "图片"}, - {"k": "multimodal", "v": "多模态"} + { + "k": "text", + "v": "文本" + }, + { + "k": "audio", + "v": "音频" + }, + { + "k": "video", + "v": "视频" + }, + { + "k": "image", + "v": "图片" + }, + { + "k": "multimodal", + "v": "多模态" + } ] }, { "parentid": "pipeline_status", "parentname": "产线状态", "items": [ - {"k": "draft", "v": "草稿"}, - {"k": "published", "v": "已发布"}, - {"k": "archived", "v": "已归档"} + { + "k": "draft", + "v": "草稿" + }, + { + "k": "published", + "v": "已发布" + }, + { + "k": "archived", + "v": "已归档" + } + ] + }, + { + "parentid": "sd_project_status", + "parentname": "项目状态", + "items": [ + { + "k": "draft", + "v": "草稿" + }, + { + "k": "active", + "v": "进行中" + }, + { + "k": "paused", + "v": "已暂停" + }, + { + "k": "completed", + "v": "已完成" + }, + { + "k": "archived", + "v": "已归档" + } + ] + }, + { + "parentid": "sd_project_type", + "parentname": "项目类型", + "items": [ + { + "k": "general", + "v": "通用项目" + }, + { + "k": "bidding", + "v": "投标项目" + }, + { + "k": "web_app", + "v": "Web应用" + }, + { + "k": "api_service", + "v": "API服务" + }, + { + "k": "module", + "v": "Sage模块" + }, + { + "k": "mobile_app", + "v": "移动应用" + }, + { + "k": "cli_tool", + "v": "CLI工具" + }, + { + "k": "data_pipeline", + "v": "数据管道" + } + ] + }, + { + "parentid": "deliverable_type", + "parentname": "交付件类型", + "items": [ + { + "k": "code", + "v": "代码" + }, + { + "k": "doc", + "v": "文档" + }, + { + "k": "config", + "v": "配置" + }, + { + "k": "test-report", + "v": "测试报告" + } + ] + }, + { + "parentid": "review_status", + "parentname": "评审状态", + "items": [ + { + "k": "pending", + "v": "待评审" + }, + { + "k": "approved", + "v": "通过" + }, + { + "k": "rejected", + "v": "驳回" + } ] } ] -} +} \ No newline at end of file diff --git a/json/pipeline_deliverables_list.json b/json/pipeline_deliverables_list.json new file mode 100644 index 0000000..154cc72 --- /dev/null +++ b/json/pipeline_deliverables_list.json @@ -0,0 +1,67 @@ +{ + "tblname": "pipeline_deliverables", + "title": "交付件管理", + "params": { + "sortby": [ + "created_at desc" + ], + "confidential_fields": [], + "browserfields": { + "exclouded": [ + "content", + "file_path", + "target_path" + ], + "alters": { + "project_id": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_project_options.dspy')}}", + "valueField": "value", + "textField": "text" + }, + "deliverable_type": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_search_deliverable_type.dspy')}}", + "valueField": "value", + "textField": "text" + }, + "review_status": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_search_review_status.dspy')}}", + "valueField": "value", + "textField": "text" + } + } + }, + "editexclouded": [ + "id", + "created_at", + "reviewed_by", + "created_by" + ], + "editable": { + "new_data_url": "{{entire_url('../api/sd_deliverable_create.dspy')}}", + "update_data_url": "{{entire_url('../api/sd_deliverable_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/sd_deliverable_delete.dspy')}}" + }, + "data_filter": { + "AND": [ + { + "field": "title", + "op": "LIKE", + "var": "title_input" + }, + { + "field": "review_status", + "op": "=", + "var": "status_input" + }, + { + "field": "deliverable_type", + "op": "=", + "var": "type_input" + } + ] + } + } +} diff --git a/json/sd_project_list.json b/json/sd_project_list.json new file mode 100644 index 0000000..814424b --- /dev/null +++ b/json/sd_project_list.json @@ -0,0 +1,80 @@ +{ + "tblname": "sd_projects", + "title": "项目管理", + "params": { + "sortby": [ + "created_at desc" + ], + "logined_userorgid": "org_id", + "confidential_fields": [], + "browserfields": { + "exclouded": [ + "tech_stack" + ], + "alters": { + "status": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_search_status.dspy')}}", + "valueField": "value", + "textField": "text" + }, + "project_type": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_search_project_type.dspy')}}", + "valueField": "value", + "textField": "text" + }, + "pipeline_id": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_pipeline_options.dspy')}}", + "valueField": "value", + "textField": "text" + } + } + }, + "editexclouded": [ + "id", + "created_at", + "updated_at", + "org_id" + ], + "editable": { + "new_data_url": "{{entire_url('../api/sd_project_create.dspy')}}", + "update_data_url": "{{entire_url('../api/sd_project_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/sd_project_delete.dspy')}}" + }, + "data_filter": { + "AND": [ + { + "field": "name", + "op": "LIKE", + "var": "name_input" + }, + { + "field": "status", + "op": "=", + "var": "status_input" + }, + { + "field": "project_type", + "op": "=", + "var": "type_input" + } + ] + }, + "subtables": [ + { + "field": "project_id", + "title": "迭代列表", + "url": "{{entire_url('/pipeline-sdlc/sd_iterations/index.ui?project_id=${id}')}}", + "subtable": "sd_iterations" + }, + { + "field": "project_id", + "title": "代码仓库", + "url": "{{entire_url('/pipeline-sdlc/sd_project_repos/index.ui?project_id=${id}')}}", + "subtable": "sd_project_repos" + } + ] + } +} \ No newline at end of file diff --git a/models/pipeline_deliverables.json b/models/pipeline_deliverables.json new file mode 100644 index 0000000..119569e --- /dev/null +++ b/models/pipeline_deliverables.json @@ -0,0 +1,36 @@ +{ + "summary": [ + { + "name": "pipeline_deliverables", + "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": "task_id", "title": "任务ID", "type": "str", "length": 32}, + {"name": "title", "title": "交付件标题", "type": "str", "length": 200, "nullable": "no"}, + {"name": "deliverable_type", "title": "交付件类型", "type": "str", "length": 32, "nullable": "no", "default": "'code'"}, + {"name": "repo_name", "title": "目标仓库别名", "type": "str", "length": 100}, + {"name": "target_path", "title": "仓库内相对路径", "type": "str", "length": 500}, + {"name": "file_path", "title": "本地存储路径", "type": "str", "length": 500}, + {"name": "content", "title": "交付件内容", "type": "longtext"}, + {"name": "quality_score", "title": "质量评分(0-100)", "type": "int", "nullable": "no", "default": "80"}, + {"name": "review_status", "title": "评审状态", "type": "str", "length": 20, "nullable": "no", "default": "'pending'"}, + {"name": "review_comment", "title": "评审意见", "type": "text"}, + {"name": "reviewed_by", "title": "评审人", "type": "str", "length": 32}, + {"name": "created_by", "title": "创建人", "type": "str", "length": 32, "nullable": "no"}, + {"name": "created_at", "title": "创建时间", "type": "timestamp", "nullable": "no"} + ], + "indexes": [ + {"name": "idx_pd_project", "idxtype": "index", "idxfields": ["project_id"]}, + {"name": "idx_pd_task", "idxtype": "index", "idxfields": ["task_id"]}, + {"name": "idx_pd_review", "idxtype": "index", "idxfields": ["review_status"]} + ], + "codes": [ + {"field": "deliverable_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='deliverable_type'"}, + {"field": "review_status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='review_status'"} + ] +} 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/models/sd_projects.json b/models/sd_projects.json new file mode 100644 index 0000000..6221e91 --- /dev/null +++ b/models/sd_projects.json @@ -0,0 +1,35 @@ +{ + "summary": [ + { + "name": "sd_projects", + "title": "SDLC项目表", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + {"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"}, + {"name": "name", "title": "项目名称", "type": "str", "length": 200, "nullable": "no"}, + {"name": "description", "title": "项目描述", "type": "text"}, + {"name": "project_type", "title": "项目类型", "type": "str", "length": 50, "nullable": "no", "default": "'general'"}, + {"name": "tech_stack", "title": "技术栈配置(JSON)", "type": "text"}, + {"name": "repo_url", "title": "主仓库地址", "type": "str", "length": 500}, + {"name": "directory_name", "title": "项目目录名(创建时定死,工作空间解析只读此字段)", "type": "str", "length": 200}, + {"name": "workspace_dir", "title": "项目工作目录", "type": "str", "length": 500}, + {"name": "default_model", "title": "项目默认模型(llm_model.name,空=走治理链缺省)", "type": "str", "length": 128}, + {"name": "pipeline_id", "title": "关联Pipeline定义ID(general=通用项目,不挂产线)", "type": "str", "length": 32, "default": "'general'"}, + {"name": "status", "title": "项目状态", "type": "str", "length": 20, "nullable": "no", "default": "'draft'"}, + {"name": "org_id", "title": "组织ID", "type": "str", "length": 32, "nullable": "no", "default": "'0'"}, + {"name": "created_by", "title": "创建人", "type": "str", "length": 32}, + {"name": "created_at", "title": "创建时间", "type": "timestamp", "nullable": "no"}, + {"name": "updated_at", "title": "更新时间", "type": "timestamp"} + ], + "indexes": [ + {"name": "idx_sd_projects_status", "idxtype": "index", "idxfields": ["status"]}, + {"name": "idx_sd_projects_org", "idxtype": "index", "idxfields": ["org_id"]} + ], + "codes": [ + {"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='sd_project_status'"}, + {"field": "project_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='sd_project_type'"} + ] +} diff --git a/wwwroot/api/get_model_options.dspy b/wwwroot/api/get_model_options.dspy new file mode 100644 index 0000000..5a1acaa --- /dev/null +++ b/wwwroot/api/get_model_options.dspy @@ -0,0 +1,16 @@ +# get_model_options.dspy — 角色模型配置下拉(value=模型注册名) +# 2026-09-04 收敛:实现统一在模型治理模块 pipeline_llm.selection.model_options, +# 本文件只作 URL 入口。保留历史格式:首项「缺省(用项目默认模型)」+ value/text 两列。 +# capabilities='chat':角色模型也是会话/任务 agent 用的对话模型,只列 t2t/i2t/m2t +# (2026-09-06 用户定夺;防把 embedding 等非对话模型配给角色后推理链报能力不匹配)。 + +rows = [{'value': '', 'text': '缺省(用项目默认模型)'}] +try: + org_id = await get_userorgid() + if org_id: + opts = await llm_model_options(org_id, value_field='name', capabilities='chat') + rows += [{'value': r['value'], 'text': r['text']} for r in opts] + return json.dumps(rows, ensure_ascii=False) +except Exception as e: + debug(f'get_model_options error: {e}') +return json.dumps(rows, ensure_ascii=False) diff --git a/wwwroot/api/get_pipeline_options.dspy b/wwwroot/api/get_pipeline_options.dspy new file mode 100644 index 0000000..a6b85c8 --- /dev/null +++ b/wwwroot/api/get_pipeline_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 pipelines order by name") + r = [{'value': '', 'text': '请选择'}] + list(rows) + return json.dumps(r, ensure_ascii=False) +except Exception as e: + debug(f'get_pipeline_options error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_project_options.dspy b/wwwroot/api/get_project_options.dspy new file mode 100644 index 0000000..191a412 --- /dev/null +++ b/wwwroot/api/get_project_options.dspy @@ -0,0 +1,9 @@ +result = [] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select id as project_id, name as project_id_text from sd_projects where pipeline_id='sdlc_general' order by name", {}) + result = list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'dropdown 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..010d25e --- /dev/null +++ b/wwwroot/api/get_project_select_options.dspy @@ -0,0 +1,9 @@ +import json +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([{'value': '', 'text': '请选择'}], 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/api/get_search_deliverable_type.dspy b/wwwroot/api/get_search_deliverable_type.dspy new file mode 100644 index 0000000..aedd74f --- /dev/null +++ b/wwwroot/api/get_search_deliverable_type.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'deliverable_type'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_project_type.dspy b/wwwroot/api/get_search_project_type.dspy new file mode 100644 index 0000000..344c0ad --- /dev/null +++ b/wwwroot/api/get_search_project_type.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_project_type'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_review_status.dspy b/wwwroot/api/get_search_review_status.dspy new file mode 100644 index 0000000..e0c2f37 --- /dev/null +++ b/wwwroot/api/get_search_review_status.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'review_status'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_status.dspy b/wwwroot/api/get_search_status.dspy new file mode 100644 index 0000000..8550e52 --- /dev/null +++ b/wwwroot/api/get_search_status.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_project_status'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/sd_deliverable_create.dspy b/wwwroot/api/sd_deliverable_create.dspy new file mode 100644 index 0000000..68e7808 --- /dev/null +++ b/wwwroot/api/sd_deliverable_create.dspy @@ -0,0 +1,26 @@ +user_id = await get_user() +if not user_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}} + +data = { + 'id': getID(), + 'project_id': params_kw.get('project_id', ''), + 'task_id': params_kw.get('task_id', ''), + 'title': params_kw.get('title', ''), + 'deliverable_type': params_kw.get('deliverable_type', 'code'), + 'repo_name': params_kw.get('repo_name', ''), + 'target_path': params_kw.get('target_path', ''), + 'file_path': params_kw.get('file_path', ''), + 'content': params_kw.get('content', ''), + 'quality_score': params_kw.get('quality_score', 80), + 'review_status': params_kw.get('review_status', 'pending'), + 'created_by': user_id, + 'created_at': curDateString(), +} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.C('pipeline_deliverables', data) + return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3,"user_data": data}} +except Exception as e: + return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}} diff --git a/wwwroot/api/sd_deliverable_delete.dspy b/wwwroot/api/sd_deliverable_delete.dspy new file mode 100644 index 0000000..1470a27 --- /dev/null +++ b/wwwroot/api/sd_deliverable_delete.dspy @@ -0,0 +1,14 @@ +user_id = await get_user() +if not user_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}} + +rec_id = params_kw.get('id', '') +if not rec_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.D('pipeline_deliverables', {'id': rec_id}) + return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}} +except Exception as e: + return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}} diff --git a/wwwroot/api/sd_deliverable_update.dspy b/wwwroot/api/sd_deliverable_update.dspy new file mode 100644 index 0000000..b79519f --- /dev/null +++ b/wwwroot/api/sd_deliverable_update.dspy @@ -0,0 +1,19 @@ +user_id = await get_user() +if not user_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}} + +rec_id = params_kw.get('id', '') +if not rec_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}} + +data = {'id': rec_id} +for f in ['project_id', 'task_id', 'title', 'deliverable_type', 'repo_name', 'target_path', 'file_path', 'content', 'quality_score', 'review_status', 'review_comment']: + if f in params_kw: + data[f] = params_kw[f] + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.U('pipeline_deliverables', data) + return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}} +except Exception as e: + return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}} diff --git a/wwwroot/api/sd_project_create.dspy b/wwwroot/api/sd_project_create.dspy new file mode 100644 index 0000000..8f49148 --- /dev/null +++ b/wwwroot/api/sd_project_create.dspy @@ -0,0 +1,25 @@ +user_id = await get_user() +if not user_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}} + +org_id = (await get_userorgid()) or '0' +data = { + 'id': getID(), + 'name': params_kw.get('name', ''), + 'description': params_kw.get('description', ''), + 'project_type': params_kw.get('project_type', ''), + 'tech_stack': params_kw.get('tech_stack', ''), + 'repo_url': params_kw.get('repo_url', ''), + 'pipeline_id': params_kw.get('pipeline_id', '') or 'sdlc_general', + 'status': params_kw.get('status', ''), + 'org_id': org_id, + 'created_by': user_id, + 'created_at': curDateString(), +} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.C('sd_projects', data) + return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3},"user_data": data} +except Exception as e: + return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}} diff --git a/wwwroot/api/sd_project_delete.dspy b/wwwroot/api/sd_project_delete.dspy new file mode 100644 index 0000000..d70bb14 --- /dev/null +++ b/wwwroot/api/sd_project_delete.dspy @@ -0,0 +1,17 @@ +user_id = await get_user() +if not user_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}} + +rec_id = params_kw.get('id', '') +if not rec_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + ok, emsg = await check_project_owner(rec_id, user_id, sor) + if not ok: + return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}} + await sor.D('sd_projects', {'id': rec_id}) + return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}} +except Exception as e: + return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}} diff --git a/wwwroot/api/sd_project_update.dspy b/wwwroot/api/sd_project_update.dspy new file mode 100644 index 0000000..92bc267 --- /dev/null +++ b/wwwroot/api/sd_project_update.dspy @@ -0,0 +1,33 @@ +user_id = await get_user() +if not user_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'未登录',"cwidth":16,"cheight":9,"timeout":3}} + +rec_id = params_kw.get('id', '') +if not rec_id: + return {"widgettype":"Error","options":{"title":"操作失败","message":'缺少id',"cwidth":16,"cheight":9,"timeout":3}} + +data = {'id': rec_id, 'updated_at': curDateString()} +if 'name' in params_kw: + data['name'] = params_kw['name'] +if 'description' in params_kw: + data['description'] = params_kw['description'] +if 'project_type' in params_kw: + data['project_type'] = params_kw['project_type'] +if 'tech_stack' in params_kw: + data['tech_stack'] = params_kw['tech_stack'] +if 'repo_url' in params_kw: + data['repo_url'] = params_kw['repo_url'] +if 'pipeline_id' in params_kw: + data['pipeline_id'] = params_kw['pipeline_id'] +if 'status' in params_kw: + data['status'] = params_kw['status'] + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + ok, emsg = await check_project_owner(rec_id, user_id, sor) + if not ok: + return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}} + await sor.U('sd_projects', data) + return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}} +except Exception as e: + return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}} 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..b2f2af1 --- /dev/null +++ b/wwwroot/sd_project_role_models/add_sd_project_role_models.dspy @@ -0,0 +1,69 @@ + +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 + +# 会话当前项目强制(auto):project_id 用当前项目,忽略前端传值 +_uid = await get_user() +if not _uid: + _uid = 'user-01' +_dbname = get_module_dbname('pipeline_core') +async with DBPools().sqlorContext(_dbname) as _sor: + _prec = await _sor.sqlExe("SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", {"u": _uid}) + await _sor.sqlExe("COMMIT", {}) + _cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else '' +if not _cur_pid: + return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}} +ns['project_id'] = _cur_pid + +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_core') +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..d8d31aa --- /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_core') +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..03c0e1f --- /dev/null +++ b/wwwroot/sd_project_role_models/get_sd_project_role_models.dspy @@ -0,0 +1,87 @@ + +ns = params_kw.copy() + +# 会话当前项目强制过滤(auto):读 current_project_id,无项目报错 +_uid = await get_user() +if not _uid: + _uid = 'user-01' +_cur_pid = '' +_dbname = get_module_dbname('pipeline_core') +async with DBPools().sqlorContext(_dbname) as _sor: + _prec = await _sor.sqlExe( + "SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", + {"u": _uid}) + await _sor.sqlExe("COMMIT", {}) + _cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else '' +if not _cur_pid: + return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}} +ns['_cur_pid'] = _cur_pid + +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, +case a.role + when 'agent.pm' then 'PM 项目经理' + when 'agent.requirement' then '需求分析师' + when 'agent.design' then '系统设计师' + when 'agent.develop' then '开发工程师' + when 'agent.deploy_test' then '测试部署工程师' + when 'agent.test' then '测试工程师' + when 'agent.deploy_prod' then '生产部署工程师' + when 'agent.qc' then 'QC 质量控制' + else a.role end as role_text +from (select * from sd_project_role_models where project_id = ${_cur_pid}$ [[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_core') +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..c80b136 --- /dev/null +++ b/wwwroot/sd_project_role_models/index.ui @@ -0,0 +1,66 @@ + +{ + "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","role_text"], + "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_text","title":"角色","type":"str","length":64,"cwidth":20,"uitype":"str","datatype":"str","label":"角色"}, + {"name":"role","title":"角色名","type":"str","length":64,"cwidth":0,"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..ff36950 --- /dev/null +++ b/wwwroot/sd_project_role_models/update_sd_project_role_models.dspy @@ -0,0 +1,60 @@ + +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"}} + +# 会话当前项目强制(auto):project_id 用当前项目,忽略前端传值 +_uid = await get_user() +if not _uid: + _uid = 'user-01' +_dbname = get_module_dbname('pipeline_core') +async with DBPools().sqlorContext(_dbname) as _sor: + _prec = await _sor.sqlExe("SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", {"u": _uid}) + await _sor.sqlExe("COMMIT", {}) + _cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else '' +if not _cur_pid: + return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}} +ns['project_id'] = _cur_pid + +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_core') +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" + } +}