# bid_task_io.dspy - 投标任务详情(任务树右侧面板,与开发产线 task_io 同构) # 入参 id 可能为:任务ID / step:步骤ID / del:交付件ID import json uid = await get_user() if not uid: uid = 'user-01' raw_id = (params_kw or {}).get('id', '').strip() dbname = get_module_dbname('pipeline-bidding') _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 raw_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: # ── 步骤/交付件节点:先按自身查,查到就展示;查不到回落任务视图 ── task_id = raw_id step_focus = '' del_focus = '' if raw_id.startswith('step:'): sid = raw_id[5:] recs = await sor.sqlExe( "SELECT id, task_id, step_name, display_name, step_type, state, error_msg, " "started_at, completed_at FROM pipeline_task_steps WHERE id=${s}$", {"s": sid}) if recs: s = recs[0] task_id = getattr(s, 'task_id', '') or '' step_focus = sid st = getattr(s, 'state', '') or '' subwidgets = [ {"widgettype": "Text", "options": {"text": "\u2699\ufe0f " + (getattr(s, 'display_name', '') or getattr(s, 'step_name', '') or ''), "halign": "left", "cfontsize": 1.1, "style": {"fontWeight": "bold", "color": "#1e293b"}}}, {"widgettype": "HBox", "options": {"gap": "12px", "width": "100%"}, "subwidgets": [ {"widgettype": "Text", "options": {"text": st, "cfontsize": 0.75, "color": "#ffffff", "padding": "1px 10px", "style": {"background": _state_colors.get(st, '#64748b'), "borderRadius": "12px", "fontWeight": "bold"}}}, {"widgettype": "Text", "options": { "text": "开始: " + str(getattr(s, 'started_at', '') or '')[:16], "cfontsize": 0.75, "color": "#94a3b8"}}, {"widgettype": "Text", "options": { "text": "结束: " + str(getattr(s, 'completed_at', '') or '')[:16], "cfontsize": 0.75, "color": "#94a3b8"}}, {"widgettype": "Filler"}, ]}, ] if getattr(s, 'error_msg', ''): subwidgets.append({"widgettype": "Text", "options": {"text": "错误: " + str(getattr(s, 'error_msg', ''))[:500], "cfontsize": 0.8, "color": "#dc2626", "wrap": True, "halign": "left"}}) subwidgets.append({"widgettype": "Text", "options": {"text": "(以下为所属任务详情)", "cfontsize": 0.75, "color": "#94a3b8", "marginTop": "8px"}}) return json.dumps({ "widgettype": "VBox", "options": {"width": "100%", "padding": "12px", "gap": "6px"}, "subwidgets": subwidgets, }, ensure_ascii=False) task_id = raw_id elif raw_id.startswith('del:'): did = raw_id[4:] recs = await sor.sqlExe( "SELECT id, task_id, deliverable_type, title, content, file_path, review_status, " "review_comment, quality_score FROM pipeline_deliverables WHERE id=${d}$", {"d": did}) if recs: d = recs[0] task_id = getattr(d, 'task_id', '') or '' del_focus = did sc = '#16a34a' subwidgets = [ {"widgettype": "Text", "options": {"text": "\U0001f4e4 " + (getattr(d, 'title', '') or getattr(d, 'deliverable_type', '') or ''), "halign": "left", "cfontsize": 1.1, "style": {"fontWeight": "bold", "color": "#1e293b"}}}, {"widgettype": "HBox", "options": {"gap": "12px", "width": "100%"}, "subwidgets": [ {"widgettype": "Text", "options": {"text": getattr(d, 'deliverable_type', '') or '', "cfontsize": 0.8, "color": "#3b82f6"}}, {"widgettype": "Text", "options": {"text": getattr(d, 'review_status', '') or '', "cfontsize": 0.75, "color": "#94a3b8"}}, {"widgettype": "Text", "options": {"text": "评分: " + str(getattr(d, 'quality_score', '') or '-'), "cfontsize": 0.75, "color": "#94a3b8"}}, {"widgettype": "Filler"}, ]}, ] fp = getattr(d, 'file_path', '') or '' if fp: subwidgets.append({"widgettype": "Text", "options": {"text": "文件: " + fp, "cfontsize": 0.78, "color": "#64748b", "wrap": True, "halign": "left"}}) subwidgets.append({"widgettype": "Text", "options": {"text": (getattr(d, 'content', '') or '')[:8000], "cfontsize": 0.82, "color": "#334155", "wrap": True, "halign": "left", "css": "card"}}) if getattr(d, 'review_comment', ''): subwidgets.append({"widgettype": "Text", "options": {"text": "审核意见: " + str(getattr(d, 'review_comment', ''))[:500], "cfontsize": 0.78, "color": "#f0a040", "wrap": True, "halign": "left"}}) return json.dumps({ "widgettype": "VBox", "options": {"width": "100%", "padding": "12px", "gap": "6px", "overflowY": "auto"}, "subwidgets": subwidgets, }, ensure_ascii=False) task_id = raw_id # ── 任务视图 ── 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 '' updated_at = str(getattr(task, 'updated_at', '') or '')[:16] created_at = str(getattr(task, 'created_at', '') or '')[:16] # 输入:params 里的任务描述与 QC 改进意见 params_str = getattr(task, 'params', '') or '' description = '' qc_imp = '' stage = '' dim = '' try: p = json.loads(params_str) if params_str else {} description = p.get('description', '') or p.get('input_text', '') or p.get('prompt', '') or '' qc_imp = p.get('qc_improvements', '') or '' if isinstance(qc_imp, list): qc_imp = '\n'.join(str(x) for x in qc_imp) stage = p.get('stage', '') or '' dim = p.get('analysis_dim', '') or '' except Exception: description = params_str # 输出:交付件 dels = await sor.sqlExe( "SELECT deliverable_type, title, content, file_path, review_status, quality_score, review_comment " "FROM pipeline_deliverables WHERE task_id=${t}$ ORDER BY created_at DESC", {"t": task_id}) # 执行步骤 steps = await sor.sqlExe( "SELECT step_name, display_name, state, error_msg, started_at, completed_at " "FROM pipeline_task_steps WHERE task_id=${t}$ ORDER BY step_order ASC", {"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"}}, }) info_row = [ {"widgettype": "Text", "options": {"text": state, "cfontsize": 0.75, "color": "#ffffff", "padding": "1px 10px", "style": {"background": sc, "borderRadius": "12px", "fontWeight": "bold"}}}, {"widgettype": "Text", "options": {"text": "角色: " + role, "cfontsize": 0.8, "color": "#64748b"}}, ] if stage: info_row.append({"widgettype": "Text", "options": {"text": "阶段: " + stage + ((" / " + dim) if dim else ""), "cfontsize": 0.75, "color": "#94a3b8"}}) info_row.append({"widgettype": "Text", "options": {"text": "创建: " + created_at + " | 更新: " + updated_at, "cfontsize": 0.72, "color": "#94a3b8"}}) info_row.append({"widgettype": "Filler"}) subwidgets.append({"widgettype": "HBox", "options": {"gap": "12px", "width": "100%"}, "subwidgets": info_row}) if last_error: subwidgets.append({ "widgettype": "Text", "options": {"text": "\u274c 错误: " + last_error[:500], "cfontsize": 0.8, "color": "#dc2626", "wrap": True, "halign": "left", "css": "card"}, }) # 输入 subwidgets.append({ "widgettype": "Text", "options": {"text": "\u2500\u2500 输入 \u2500\u2500", "cfontsize": 0.85, "style": {"fontWeight": "bold", "color": "#3b82f6", "marginTop": "8px"}}, }) if description: subwidgets.append({ "widgettype": "Text", "options": {"text": description[:4000], "cfontsize": 0.85, "color": "#334155", "wrap": True, "halign": "left", "css": "card"}, }) if qc_imp: subwidgets.append({ "widgettype": "Text", "options": {"text": "QC改进意见(重做输入):\n" + str(qc_imp)[:3000], "cfontsize": 0.82, "color": "#b45309", "wrap": True, "halign": "left", "css": "card"}, }) if not description and not qc_imp: subwidgets.append({"widgettype": "Text", "options": {"text": "(无任务描述)", "cfontsize": 0.8, "color": "#94a3b8"}}) # 执行步骤 if steps: subwidgets.append({ "widgettype": "Text", "options": {"text": "\u2500\u2500 执行步骤 \u2500\u2500", "cfontsize": 0.85, "style": {"fontWeight": "bold", "color": "#8b5cf6", "marginTop": "8px"}}, }) for s in steps: sn = getattr(s, 'step_name', '') or '' dn = getattr(s, 'display_name', '') or '' st = getattr(s, 'state', '') or '' err = getattr(s, 'error_msg', '') or '' label = dn or sn s_color = _state_colors.get(st, '#64748b') subwidgets.append({ "widgettype": "HBox", "options": {"gap": "8px", "width": "100%", "padding": "4px 8px", "css": "card"}, "subwidgets": [ {"widgettype": "Text", "options": {"text": label[:80], "cfontsize": 0.82, "color": "#1e293b"}}, {"widgettype": "Filler"}, {"widgettype": "Text", "options": {"text": st, "cfontsize": 0.75, "color": s_color}}, ], }) if err: subwidgets.append({"widgettype": "Text", "options": {"text": " " + err[:300], "cfontsize": 0.72, "color": "#dc2626", "wrap": True, "halign": "left"}}) # 输出(交付件) subwidgets.append({ "widgettype": "Text", "options": {"text": "\u2500\u2500 输出(交付件)\u2500\u2500", "cfontsize": 0.85, "style": {"fontWeight": "bold", "color": "#16a34a", "marginTop": "8px"}}, }) if not dels: subwidgets.append({"widgettype": "Text", "options": {"text": "(暂无交付件)", "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_file = getattr(d, 'file_path', '') 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"}}, {"widgettype": "Text", "options": {"text": d_title[:60], "cfontsize": 0.8, "color": "#1e293b"}}, {"widgettype": "Filler"}, {"widgettype": "Text", "options": {"text": (d_status or '') + ((" " + str(d_score)) if d_score else ''), "cfontsize": 0.75, "color": "#94a3b8"}}, ]}, {"widgettype": "Text", "options": {"text": d_content[:5000], "cfontsize": 0.78, "color": "#475569", "wrap": True, "halign": "left"}}, ], } if d_file: card["subwidgets"].append({"widgettype": "Text", "options": {"text": "\U0001f4ce " + d_file, "cfontsize": 0.75, "color": "#64748b", "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) return json.dumps({ "widgettype": "VBox", "options": {"width": "100%", "height": "100%", "padding": "12px", "gap": "6px", "overflowY": "auto"}, "subwidgets": subwidgets, }, ensure_ascii=False)