# task_io.dspy - 任务的输入输出(ResourceBrowser 右侧面板,5 秒自动刷新) import json uid = await get_user() if not uid: uid = 'user-01' task_id = (params_kw or {}).get('id', '').strip() dbname = get_module_dbname('pipeline-sdlc') _state_colors = { 'completed': '#16a34a', 'approved': '#16a34a', 'running': '#2563eb', 'failed': '#dc2626', 'waiting': '#f0a040', 'submitted': '#64748b', 'review': '#2196f3', 'rejected': '#dc2626', 'paused': '#f0a040', 'cancelled': '#64748b', 'qc_review': '#8b5cf6', } if not task_id: return json.dumps({ "widgettype": "Text", "options": {"text": "请选择左侧任务查看输入输出", "cfontsize": 0.9, "color": "#94a3b8", "padding": "16px"}, }, ensure_ascii=False) async with DBPools().sqlorContext(dbname) as sor: recs = await sor.sqlExe( "SELECT id, title, role, state, params, last_error, created_at, updated_at " "FROM pipeline_tasks WHERE id=${t}$", {"t": task_id}) if not recs: return json.dumps({ "widgettype": "Text", "options": {"text": "任务不存在", "cfontsize": 0.9, "color": "#94a3b8", "padding": "16px"}, }, ensure_ascii=False) task = recs[0] title = getattr(task, 'title', '') or '' role = getattr(task, 'role', '') or '' state = getattr(task, 'state', '') or '' last_error = getattr(task, 'last_error', '') or '' created_at = str(getattr(task, 'created_at', '') or '')[:16] updated_at = str(getattr(task, 'updated_at', '') or '')[:16] # 输入:params 里的 description / input_text params_str = getattr(task, 'params', '') or '' description = '' try: p = json.loads(params_str) if params_str else {} description = p.get('description', '') or p.get('input_text', '') or p.get('prompt', '') or '' except Exception: description = params_str # 输出:交付件 dels = await sor.sqlExe( "SELECT deliverable_type, title, content, review_status, quality_score, review_comment " "FROM pipeline_deliverables WHERE task_id=${t}$ ORDER BY created_at DESC", {"t": task_id}) sc = _state_colors.get(state, '#64748b') subwidgets = [] # 标题行:任务名 + 状态 subwidgets.append({ "widgettype": "Text", "options": {"text": title, "halign": "left", "cfontsize": 1.15, "style": {"fontWeight": "bold", "color": "#1e293b"}}, }) subwidgets.append({ "widgettype": "HBox", "options": {"gap": "12px", "width": "100%"}, "subwidgets": [ {"widgettype": "Text", "options": {"text": state, "cfontsize": 0.75, "color": "#ffffff", "halign": "left", "padding": "1px 10px", "style": {"background": sc, "borderRadius": "12px", "fontWeight": "bold"}}}, {"widgettype": "Text", "options": {"text": "角色: " + role, "cfontsize": 0.8, "color": "#64748b", "halign": "left"}}, {"widgettype": "Text", "options": {"text": "更新: " + updated_at, "cfontsize": 0.75, "color": "#94a3b8", "halign": "left"}}, {"widgettype": "Filler"}, ], }) if last_error: subwidgets.append({ "widgettype": "Text", "options": {"text": "错误: " + last_error[:300], "halign": "left", "cfontsize": 0.8, "css": "resp-error", "wrap": True}, }) # 输入 subwidgets.append({ "widgettype": "Text", "options": {"text": "—— 输入 ——", "halign": "left", "cfontsize": 0.85, "style": {"fontWeight": "bold", "color": "#3b82f6", "marginTop": "8px"}}, }) subwidgets.append({ "widgettype": "Text", "options": {"text": (description or "(无)")[:4000], "halign": "left", "cfontsize": 0.85, "color": "#334155", "wrap": True, "css": "card"}, }) # 输出 subwidgets.append({ "widgettype": "Text", "options": {"text": "—— 输出(交付件)——", "halign": "left", "cfontsize": 0.85, "style": {"fontWeight": "bold", "color": "#16a34a", "marginTop": "8px"}}, }) if not dels: subwidgets.append({ "widgettype": "Text", "options": {"text": "(暂无交付件)", "halign": "left", "cfontsize": 0.8, "color": "#94a3b8"}, }) else: for d in dels: d_type = getattr(d, 'deliverable_type', '') or '' d_title = getattr(d, 'title', '') or '' d_content = getattr(d, 'content', '') or '' d_status = getattr(d, 'review_status', '') or '' d_score = getattr(d, 'quality_score', '') or '' d_comment = getattr(d, 'review_comment', '') or '' card = { "widgettype": "VBox", "options": {"css": "card", "gap": "4px", "width": "100%", "padding": "8px"}, "subwidgets": [ {"widgettype": "HBox", "options": {"gap": "8px", "width": "100%"}, "subwidgets": [ {"widgettype": "Text", "options": {"text": d_type, "cfontsize": 0.8, "color": "#3b82f6", "halign": "left"}}, {"widgettype": "Text", "options": {"text": d_title, "cfontsize": 0.8, "color": "#1e293b", "halign": "left"}}, {"widgettype": "Filler"}, {"widgettype": "Text", "options": {"text": d_status, "cfontsize": 0.75, "color": "#94a3b8", "halign": "left"}}, ]}, {"widgettype": "Text", "options": {"text": (d_content or "")[:3000], "cfontsize": 0.78, "color": "#475569", "wrap": True, "halign": "left"}}, ], } if d_comment: card["subwidgets"].append({ "widgettype": "Text", "options": {"text": "审核意见: " + d_comment[:300], "cfontsize": 0.75, "color": "#f0a040", "wrap": True, "halign": "left"}, }) subwidgets.append(card) # 5 秒刷新:仅非终态任务刷新。终态(completed/approved/failed/rejected/cancelled)不再变化, # 切到终态任务时清除定时器;非终态任务设置定时器。params.state 由 binds.params 传入 script。 refresh_script = ( "var _term=['completed','approved','failed','rejected','cancelled'];" "if(_term.indexOf(params.state)>=0){" "if(window._task_io_timer){clearInterval(window._task_io_timer);window._task_io_timer=null;}" "}else if(!window._task_io_timer){" "window._task_io_timer=setInterval(function(){" "var rb=bricks.getWidgetById('task_rb',bricks.app);" "if(rb&&rb.current_id){var cid=rb.current_id;rb.current_id=undefined;rb.render_browser(cid);}" "},5000);}" ) resp = { "widgettype": "VBox", "options": {"width": "100%", "height": "100%", "padding": "12px", "gap": "6px", "overflowY": "auto"}, "subwidgets": subwidgets, "binds": [{ "wid": "self", "event": "on_parent", "actiontype": "script", "target": "self", "params": {"state": state}, "script": refresh_script, }], } return json.dumps(resp, ensure_ascii=False)