pipeline-task/wwwroot/task_detail.ui
yumoqing b556ab0cdc feat: task center - human task interaction, artifact view, restart capability
- Add human task dspy: human_complete, human_list, approval_approve, approval_reject
- Add task_restart.dspy for restarting completed/failed tasks
- Enhance task_detail.ui: inline human task forms, approval buttons, restart
- New step_panel.ui: view/modify step artifacts with cascade rerun
- Update task_list.ui: Tabular with search_form filter by pipeline/state
- Update index.ui: embed task list Tabular inline
- Update load_path.py: register new dspy/ui paths
2026-07-08 21:03:32 +08:00

251 lines
14 KiB
XML

{% set ns = namespace() %}
{% set task_data = get_task_data(request) %}
{% if not task_data.success %}
{
"widgettype": "VBox",
"options": {"width": "100%", "padding": "16px"},
"subwidgets": [
{
"widgettype": "Button",
"options": {"label": "返回列表"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "app.pipeline_task_content",
"options": {"url": "{{entire_url('task_list.ui')}}"},
"mode": "replace"
}]
},
{"widgettype": "Title", "options": {"text": "任务详情", "cfontsize": 20}},
{"widgettype": "Text", "options": {"text": "{{task_data.message or '加载失败'}}"}}
]
}
{% else %}
{% set task = task_data.task %}
{% set steps = task.steps or [] %}
{% set state_labels = {'submitted':'已提交','running':'运行中','completed':'已完成','failed':'失败','paused':'已暂停','cancelled':'已取消','waiting':'等待人工'} %}
{% set step_state_labels = {'pending':'待执行','running':'执行中','completed':'已完成','failed':'失败','skipped':'已跳过','waiting':'等待人工','rejected':'已驳回'} %}
{% set is_done = task.state in ('completed', 'failed', 'cancelled') %}
{
"widgettype": "VBox",
"options": {"width": "100%", "padding": "16px"},
"subwidgets": [
{
"widgettype": "HBox",
"options": {"width": "100%", "marginBottom": "12px", "gap": "12px", "alignItems": "center"},
"subwidgets": [
{
"widgettype": "Button",
"options": {"label": "返回列表"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "app.pipeline_task_content",
"options": {"url": "{{entire_url('index.ui')}}"},
"mode": "replace"
}]
},
{"widgettype": "Title", "options": {"text": "任务详情 - {{json.dumps(task.title or '', ensure_ascii=False)}}", "cfontsize": 18}},
{"widgettype": "Text", "options": {"text": "状态: {{state_labels.get(task.state, task.state)}} | 版本: v{{task.current_version or 1}}"}}
]
},
{
"widgettype": "HBox",
"options": {"width": "100%", "marginBottom": "12px", "gap": "8px"},
"subwidgets": [
{% if task.state == 'running' %}
{
"widgettype": "Button",
"options": {"label": "暂停", "css": "warning"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "var u='{{entire_url('api/task_control.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('action','pause');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
}]
},
{% endif %}
{% if task.state == 'paused' %}
{
"widgettype": "Button",
"options": {"label": "恢复", "css": "primary"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "var u='{{entire_url('api/task_control.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('action','resume');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
}]
},
{% endif %}
{% if not is_done %}
{
"widgettype": "Button",
"options": {"label": "取消", "css": "danger"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "if(!confirm('确定取消任务?'))return;var u='{{entire_url('api/task_control.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('action','cancel');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
}]
},
{% endif %}
{% if is_done %}
{
"widgettype": "Button",
"options": {"label": "重新开始", "css": "primary"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "if(!confirm('确定重新开始?'))return;var u='{{entire_url('api/task_restart.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
}]
}
{% endif %}
]
},
{
"widgettype": "Text",
"options": {"text": "步骤列表 ({{steps|length}} 个步骤)", "cfontsize": 14}
},
{% for step in steps %}
{
"widgettype": "VBox",
"options": {"width": "100%", "bgcolor": "var(--card-bg)", "padding": "12px", "marginBottom": "8px"},
"subwidgets": [
{
"widgettype": "HBox",
"options": {"width": "100%", "justifyContent": "space-between", "alignItems": "center", "marginBottom": "8px"},
"subwidgets": [
{
"widgettype": "Text",
"options": {"text": "{{json.dumps(step.display_name or step.step_name, ensure_ascii=False)}}{% if step.state == 'waiting' %} ⚠{% endif %}", "cfontsize": 14}
},
{
"widgettype": "Text",
"options": {"text": "{{step_state_labels.get(step.state, step.state)}}", "color": "{% if step.state == 'completed' %}#4caf50{% elif step.state == 'waiting' %}#ff9800{% elif step.state == 'failed' %}#f44336{% else %}#888{% endif %}"}
}
]
},
{
"widgettype": "Text",
"options": {"text": "类型: {{step.step_type or '-'}} | 依赖: {{json.dumps(step.deps if step.deps is string else (step.deps or [])|join(',') or '无', ensure_ascii=False)}}"}
},
{% if step.state == 'waiting' and step.human_task %}
{
"widgettype": "HBox",
"options": {"width": "100%", "marginTop": "8px", "gap": "8px", "alignItems": "flex-start"},
"subwidgets": [
{
"widgettype": "VBox",
"options": {"width": "50%"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "⚠ 人工任务: {{step.human_task.task_type or '-'}}", "color": "#ff9800"}},
{"widgettype": "Text", "options": {"text": "处理状态: {{step.human_task.status or '-'}}"}},
{% if step.human_task.assignee_role %}
{"widgettype": "Text", "options": {"text": "处理角色: {{step.human_task.assignee_role}}"}},
{% endif %}
{% if step.human_task.form_schema %}
{"widgettype": "Text", "options": {"text": "表单: {{json.dumps(step.human_task.form_schema, ensure_ascii=False)}}"}},
{% endif %}
{
"widgettype": "Button",
"options": {"label": "查看产物", "css": "text"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "app.pipeline_task_content",
"options": {"url": "{{entire_url('step_panel.ui')}}?task_id={{task.id}}&step_name={{step.step_name}}"},
"mode": "replace"
}]
}
]
},
{% if step.human_task.task_type == 'human_task' and step.human_task.status == 'pending' %}
{
"widgettype": "Form",
"options": {
"width": "50%",
"name": "human_form_{{loop.index}}",
"submit_url": "{{entire_url('api/human_complete.dspy')}}",
"fields": [
{"name": "task_id", "uitype": "hide", "value": "{{task.id}}"},
{"name": "step_name", "uitype": "hide", "value": "{{step.step_name}}"},
{"name": "result", "uitype": "text", "label": "处理结果 (JSON)", "required": true, "placeholder": "{\"status\":\"ok\",\"note\":\"已完成\"}"}
]
},
"binds": [{
"wid": "self", "event": "submited",
"actiontype": "script",
"target": "self",
"script": "location.reload()"
}]
},
{% elif step.human_task.task_type == 'approval_gate' and step.human_task.status == 'pending' %}
{
"widgettype": "VBox",
"options": {"width": "50%", "gap": "8px"},
"subwidgets": [
{
"widgettype": "Button",
"options": {"label": "✓ 通过", "css": "primary"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "var u='{{entire_url('api/approval_approve.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('step_name','{{step.step_name}}');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
}]
},
{
"widgettype": "Button",
"options": {"label": "✗ 驳回", "css": "danger"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "var reason=prompt('驳回理由:');if(!reason)return;var u='{{entire_url('api/approval_reject.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('step_name','{{step.step_name}}');fd.append('comments',reason);fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
}]
}
]
},
{% endif %}
{"widgettype": "Filler"}
]
},
{% elif step.state in ('completed', 'failed') %}
{
"widgettype": "HBox",
"options": {"width": "100%", "marginTop": "8px", "gap": "8px"},
"subwidgets": [
{
"widgettype": "Button",
"options": {"label": "查看产物", "css": "text"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "app.pipeline_task_content",
"options": {"url": "{{entire_url('step_panel.ui')}}?task_id={{task.id}}&step_name={{step.step_name}}"},
"mode": "replace"
}]
},
{
"widgettype": "Button",
"options": {"label": "修改并重跑", "css": "warning"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "app.pipeline_task_content",
"options": {"url": "{{entire_url('step_panel.ui')}}?task_id={{task.id}}&step_name={{step.step_name}}&mode=edit"},
"mode": "replace"
}]
}
]
},
{% else %}
{
"widgettype": "Button",
"options": {"label": "查看详情", "css": "text"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "app.pipeline_task_content",
"options": {"url": "{{entire_url('step_panel.ui')}}?task_id={{task.id}}&step_name={{step.step_name}}"},
"mode": "replace"
}]
},
{% endif %}
{"widgettype": "Filler"}
]
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{% endif %}