diff --git a/wwwroot/api/my_bugs.dspy b/wwwroot/api/my_bugs.dspy new file mode 100644 index 0000000..ecf43df --- /dev/null +++ b/wwwroot/api/my_bugs.dspy @@ -0,0 +1,56 @@ +user_id = await get_user() +if not user_id: + return json.dumps([], ensure_ascii=False) + +try: + async with get_sor_context(request._run_ns, "pipeline") as sor: + sql = """ + SELECT b.id, b.title, b.severity, b.priority, b.status, + b.reporter_type, b.step_name, b.created_at, + i.iteration_name + FROM sd_bugs b + LEFT JOIN sd_iterations i ON b.iteration_id = i.id + WHERE b.reporter_id = ${uid}$ OR b.assignee_id = ${uid}$ + ORDER BY + CASE b.status + WHEN 'open' THEN 1 + WHEN 'confirmed' THEN 2 + WHEN 'in_progress' THEN 3 + WHEN 'resolved' THEN 4 + WHEN 'closed' THEN 5 + ELSE 6 + END, + b.created_at DESC + LIMIT 100 + """ + recs = await sor.sqlExe(sql, {"uid": user_id}) + rows = [] + status_labels = { + "open": "待处理", + "confirmed": "已确认", + "in_progress": "处理中", + "resolved": "已解决", + "closed": "已关闭", + } + severity_labels = { + "critical": "致命", + "major": "严重", + "minor": "一般", + "trivial": "轻微", + } + for r in recs: + rows.append({ + "id": r.id, + "title": r.title or "", + "severity": severity_labels.get(r.severity, r.severity or ""), + "priority": r.priority or "", + "status": status_labels.get(r.status, r.status or ""), + "status_raw": r.status or "", + "reporter_type": "agent" if r.reporter_type == "agent" else "human", + "step_name": r.step_name or "", + "iteration_name": r.iteration_name or "", + "created_at": str(r.created_at)[:16] if r.created_at else "", + }) + return json.dumps(rows, ensure_ascii=False) +except Exception as e: + return json.dumps([], ensure_ascii=False) diff --git a/wwwroot/index.ui b/wwwroot/index.ui index 77be82e..1fbbb2f 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -1,108 +1,244 @@ { "widgettype": "VBox", - "options": {"width": "100%", "height": "100%", "padding": "20px"}, + "options": {"width": "100%", "height": "100%", "padding": "16px", "gap": "12px"}, "subwidgets": [ { "widgettype": "HBox", - "options": {"width": "100%", "justifyContent": "space-between", "alignItems": "center", "marginBottom": "16px"}, + "options": {"width": "100%", "justifyContent": "space-between", "alignItems": "center"}, "subwidgets": [ - {"widgettype": "Title", "options": {"text": "产线任务", "cfontsize": 24}}, - { - "widgettype": "Button", - "options": {"label": "提交新任务", "icon": "plus"}, - "binds": [{ - "wid": "self", "event": "click", "actiontype": "urlwidget", - "target": "app.pipeline_task_content", - "options": {"url": "{{entire_url('task_submit.ui')}}"}, - "mode": "replace" - }] - }, + {"widgettype": "Title", "options": {"text": "开发产线工作台", "cfontsize": 22}}, {"widgettype": "Filler"} ] }, { - "widgettype": "VBox", - "id": "pipeline_task_content", - "options": {"width": "100%", "css": "filler"}, - "subwidgets": [ - { - "widgettype": "Tabular", - "id": "task_table", - "options": { - "width": "100%", - "height": "100%", - "css": "card", - "title": "任务中心", - "data_url": "{{entire_url('api/task_list.dspy')}}", - "data_method": "GET", - "page_rows": 20, - "toolbar": { - "tools": [ - {"name": "refresh", "label": "刷新", "selected_row": false}, - {"name": "view", "label": "查看详情", "selected_row": true} - ] - }, - "search_form": { - "fields": [ - {"name": "pipeline_id", "label": "产线", "uitype": "str", "placeholder": "筛选产线ID"}, - {"name": "state", "label": "状态", "uitype": "code", "data": [ - {"value": "", "text": "全部"}, - {"value": "submitted", "text": "已提交"}, - {"value": "running", "text": "运行中"}, - {"value": "waiting", "text": "等待人工"}, - {"value": "completed", "text": "已完成"}, - {"value": "failed", "text": "失败"}, - {"value": "paused", "text": "已暂停"}, - {"value": "cancelled", "text": "已取消"} - ]} - ] - }, - "row_options": { - "browserfields": { - "exclouded": ["tenant_id", "owner_id", "params"], - "cwidths": {"id": 10, "title": 20, "pipeline_id": 10, "state": 8, "current_version": 6, "created_at": 11}, - "alters": { - "state": { - "uitype": "code", - "data": [ - {"value": "submitted", "text": "已提交"}, - {"value": "running", "text": "运行中"}, - {"value": "waiting", "text": "⚠等待人工"}, - {"value": "completed", "text": "已完成"}, - {"value": "failed", "text": "失败"}, - {"value": "paused", "text": "已暂停"}, - {"value": "cancelled", "text": "已取消"} - ] - } - } - }, - "fields": [ - {"name": "id", "type": "str", "length": 32, "cwidth": 10, "uitype": "str", "label": "任务ID"}, - {"name": "title", "type": "str", "length": 255, "cwidth": 20, "uitype": "str", "label": "标题"}, - {"name": "pipeline_id", "type": "str", "length": 32, "cwidth": 10, "uitype": "str", "label": "产线"}, - {"name": "state", "type": "str", "length": 20, "cwidth": 8, "uitype": "code", "label": "状态"}, - {"name": "current_version", "type": "int", "cwidth": 6, "uitype": "int", "label": "版本"}, - {"name": "created_at", "type": "datetime", "cwidth": 11, "uitype": "str", "label": "创建时间"} - ] + "widgettype": "Form", + "id": "quick_submit_form", + "options": { + "name": "quick_submit", + "width": "100%", + "css": "card", + "padding": "12px", + "submit_url": "{{entire_url('api/task_submit.dspy')}}", + "submit_label": "提交任务", + "submit_css": "primary", + "fields": [ + { + "name": "input_text", + "uitype": "text", + "label": "", + "placeholder": "输入任务描述,如:生成一首中国风歌曲...", + "cwidth": 40 + }, + { + "name": "pipeline_id", + "uitype": "code", + "label": "产线", + "placeholder": "选择产线", + "cwidth": 20, + "dataurl": "{{entire_url('api/pipeline_options.dspy')}}", + "valueField": "pipeline_id", + "textField": "pipeline_id_text", + "params": { + "valueField": "pipeline_id", + "textField": "pipeline_id_text" } }, - "binds": [ + { + "name": "title", + "uitype": "hide", + "value": "" + } + ] + }, + "binds": [ + { + "wid": "self", + "event": "beforesubmit", + "actiontype": "script", + "target": "self", + "script": "var input_el=this.form_element.querySelector('[name=\"input_text\"]');var pipe_el=this.form_element.querySelector('[name=\"pipeline_id\"]');var input_val=input_el?input_el.value:'';var title_el=this.form_element.querySelector('[name=\"title\"]');if(title_el)title_el.value=input_val||'new task';if(!input_val){bricks.show_error({title:'提示',message:'请输入任务描述'});return false;}return true;" + }, + { + "wid": "self", + "event": "submited", + "actiontype": "script", + "target": "self", + "script": "var tbl=bricks.getWidgetById('task_table');setTimeout(function(){if(tbl)tbl.render({});},500);var input_el=this.form_element.querySelector('[name=\"input_text\"]');if(input_el)input_el.value='';" + } + ] + }, + { + "widgettype": "HBox", + "options": {"width": "100%", "css": "filler", "gap": "12px"}, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"width": "60%", "height": "100%", "gap": "4px"}, + "subwidgets": [ { - "wid": "self", - "event": "refresh", - "actiontype": "method", - "target": "self", - "method": "render" + "widgettype": "HBox", + "options": {"width": "100%", "alignItems": "center"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "任务列表", "cfontsize": 14}}, + {"widgettype": "Filler"}, + {"widgettype": "Text", "id": "poll_status", "options": {"text": "", "color": "#888", "cfontsize": 11}} + ] }, { - "wid": "self", - "event": "view", - "actiontype": "urlwidget", - "target": "app.pipeline_task_content", - "mode": "replace", + "widgettype": "Tabular", + "id": "task_table", "options": { - "url": "{{entire_url('task_detail.ui')}}", - "params": {"task_id": "${id}$"} + "width": "100%", + "height": "100%", + "css": "card", + "data_url": "{{entire_url('api/task_list.dspy')}}", + "data_method": "GET", + "page_rows": 15, + "popup": { + "popup_event": "dblclick", + "popup_desc": { + "widgettype": "urlwidget", + "options": { + "url": "/pipeline_task/task_tree_popup.ui?task_id=${id}$&title=${encodeURIComponent(title)}$", + "method": "GET" + } + }, + "popupwindow": true, + "options": {"title": "${title}$"} + }, + "toolbar": { + "tools": [ + {"name": "refresh", "label": "刷新", "selected_row": false}, + {"name": "view", "label": "查看详情", "selected_row": true} + ] + }, + "search_form": { + "fields": [ + { + "name": "pipeline_id", + "label": "产线", + "uitype": "str", + "placeholder": "筛选产线ID" + }, + { + "name": "state", + "label": "状态", + "uitype": "code", + "data": [ + {"value": "", "text": "全部"}, + {"value": "submitted", "text": "已提交"}, + {"value": "running", "text": "运行中"}, + {"value": "waiting", "text": "等待人工"}, + {"value": "completed", "text": "已完成"}, + {"value": "failed", "text": "失败"}, + {"value": "paused", "text": "已暂停"}, + {"value": "cancelled", "text": "已取消"} + ] + } + ] + }, + "row_options": { + "browserfields": { + "exclouded": ["tenant_id", "owner_id", "params"], + "cwidths": { + "id": 16, + "title": 22, + "pipeline_id": 8, + "state": 7, + "current_version": 5, + "created_at": 10 + }, + "alters": { + "state": { + "uitype": "code", + "data": [ + {"value": "submitted", "text": "已提交"}, + {"value": "running", "text": "运行中"}, + {"value": "waiting", "text": "⚠等待"}, + {"value": "completed", "text": "已完成"}, + {"value": "failed", "text": "失败"}, + {"value": "paused", "text": "已暂停"}, + {"value": "cancelled", "text": "已取消"} + ] + } + } + }, + "fields": [ + {"name": "id", "type": "str", "length": 32, "cwidth": 16, "uitype": "str", "label": "任务ID"}, + {"name": "title", "type": "str", "length": 255, "cwidth": 22, "uitype": "str", "label": "标题"}, + {"name": "pipeline_id", "type": "str", "length": 32, "cwidth": 8, "uitype": "str", "label": "产线"}, + {"name": "state", "type": "str", "length": 20, "cwidth": 7, "uitype": "code", "label": "状态"}, + {"name": "current_version", "type": "int", "cwidth": 5, "uitype": "int", "label": "版本"}, + {"name": "created_at", "type": "datetime", "cwidth": 10, "uitype": "str", "label": "创建时间"} + ] + } + }, + "binds": [ + { + "wid": "self", + "event": "refresh", + "actiontype": "method", + "target": "self", + "method": "render" + }, + { + "wid": "self", + "event": "view", + "actiontype": "urlwidget", + "target": "app.pipeline_task_content", + "mode": "replace", + "options": { + "url": "{{entire_url('task_detail.ui')}}", + "params": {"task_id": "${id}$"} + } + } + ] + } + ] + }, + { + "widgettype": "VBox", + "options": {"width": "40%", "height": "100%", "gap": "4px"}, + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"width": "100%", "alignItems": "center"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "我的Bug", "cfontsize": 14}}, + {"widgettype": "Filler"}, + {"widgettype": "Text", "id": "bug_count", "options": {"text": "", "color": "#888", "cfontsize": 11}} + ] + }, + { + "widgettype": "Tabular", + "id": "bug_table", + "options": { + "width": "100%", + "height": "100%", + "css": "card", + "title": "", + "data_url": "{{entire_url('api/my_bugs.dspy')}}", + "data_method": "GET", + "page_rows": 15, + "row_options": { + "browserfields": { + "cwidths": { + "title": 24, + "status": 7, + "severity": 6, + "reporter_type": 6, + "created_at": 9 + } + }, + "fields": [ + {"name": "title", "type": "str", "length": 300, "cwidth": 24, "uitype": "str", "label": "标题"}, + {"name": "status", "type": "str", "length": 20, "cwidth": 7, "uitype": "str", "label": "状态"}, + {"name": "severity", "type": "str", "length": 10, "cwidth": 6, "uitype": "str", "label": "严重度"}, + {"name": "reporter_type", "type": "str", "length": 10, "cwidth": 6, "uitype": "str", "label": "来源"}, + {"name": "created_at", "type": "str", "length": 20, "cwidth": 9, "uitype": "str", "label": "时间"}, + {"name": "id", "type": "str", "length": 32, "uitype": "str", "label": "ID"} + ] + } } } ] diff --git a/wwwroot/task_tree_popup.ui b/wwwroot/task_tree_popup.ui new file mode 100644 index 0000000..352e638 --- /dev/null +++ b/wwwroot/task_tree_popup.ui @@ -0,0 +1,49 @@ +{% set task_id = params_kw.get('task_id', '') %} +{% set task_title = params_kw.get('title', '') or 'Task' %} +{ + "widgettype": "VBox", + "options": {"width": "100%", "padding": "8px"}, + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"width": "100%", "marginBottom": "8px", "alignItems": "center", "gap": "8px"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "\ud83d\udccb {{task_title}}", "cfontsize": 15}}, + {"widgettype": "Filler"}, + { + "widgettype": "Button", + "options": {"label": "\u67e5\u770b\u5b8c\u6574\u8be6\u60c5", "css": "text"}, + "binds": [{ + "wid": "self", "event": "click", "actiontype": "urlwidget", + "target": "app.pipeline_task_content", + "mode": "replace", + "options": {"url": "{{entire_url('task_detail.ui')}}?task_id={{task_id}}"} + }] + } + ] + }, + { + "widgettype": "Tree", + "id": "step_tree", + "options": { + "title": "\u6b65\u9aa4\u6811", + "parentField": "parent_step", + "idField": "id", + "textField": "label", + "dataurl": "{{entire_url('api/task_tree.dspy')}}?task_id={{task_id}}", + "css": "card" + } + }, + { + "widgettype": "VBox", + "id": "step_io_panel", + "options": {"width": "100%", "marginTop": "8px", "css": "card", "padding": "8px"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "\ud83d\udce6 \u70b9\u51fb\u5de6\u4fa7\u6b65\u9aa4\u67e5\u770b\u8f93\u5165\u8f93\u51fa\uff0c\u6216\u70b9\u51fb\u4e0a\u65b9\u201c\u67e5\u770b\u5b8c\u6574\u8be6\u60c5\u201d\u8fdb\u5165\u8be6\u60c5\u9875", "color": "#888"}} + ] + }, + { + "widgettype": "Filler" + } + ] +}