feat: task_io_render 共享渲染——md交付件MdWidget渲染+下载链接,office/pdf下载链接(全产线统一)
This commit is contained in:
parent
7596f76605
commit
03473e8fbb
152
pipeline_service/task_io_render.py
Normal file
152
pipeline_service/task_io_render.py
Normal file
@ -0,0 +1,152 @@
|
||||
"""任务输入输出的交付件渲染(全产线通用)。
|
||||
|
||||
规则(2026-09-03 用户要求,所有产线统一):
|
||||
- md/txt 文件 → MdWidget 渲染正文 + 提供下载链接;
|
||||
- office/pdf 文件 → 直接提供文件下载链接;
|
||||
- 无文件的交付件 → 内联文本卡片(旧行为兜底)。
|
||||
|
||||
下载/打开走 pipeline-sdlc 的 project_file.dspy(按项目ID+路径,含路径穿越/机构隔离校验),
|
||||
调用方把 entire_url(project_file.dspy) 的结果传入即可跨模块复用。
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from urllib.parse import quote
|
||||
|
||||
MD_EXTS = ('.md', '.txt', '.markdown')
|
||||
OFFICE_EXTS = ('.docx', '.doc', '.xlsx', '.xls', '.pptx', '.ppt', '.pdf',
|
||||
'.odt', '.ods', '.odp')
|
||||
|
||||
# MdWidget 内联渲染上限:超长 md 改走 md_url 拉文件渲染,避免 widget JSON 过大
|
||||
_MD_MAX_INLINE = 60000
|
||||
|
||||
|
||||
def classify_file(path):
|
||||
"""返回 'md' / 'office' / 'other' / ''(无路径)。"""
|
||||
ext = os.path.splitext(path or '')[1].lower()
|
||||
if ext in MD_EXTS:
|
||||
return 'md'
|
||||
if ext in OFFICE_EXTS:
|
||||
return 'office'
|
||||
if path:
|
||||
return 'other'
|
||||
return ''
|
||||
|
||||
|
||||
def _btn(label, css, script):
|
||||
return {"widgettype": "Button", "options": {"label": label, "css": css},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
|
||||
"target": "self", "script": script}]}
|
||||
|
||||
|
||||
def _file_row(fname, dl_url, hint=""):
|
||||
"""文件行:📎 文件名 + (提示) + 下载按钮。"""
|
||||
subs = [
|
||||
{"widgettype": "Text", "options": {"text": "\U0001f4ce " + fname,
|
||||
"cfontsize": 0.85, "color": "#334155", "flex": "1 1 auto",
|
||||
"overflow": "hidden", "textOverflow": "ellipsis",
|
||||
"whiteSpace": "nowrap"}},
|
||||
]
|
||||
if hint:
|
||||
subs.append({"widgettype": "Text", "options": {"text": hint,
|
||||
"cfontsize": 0.72, "color": "#94a3b8"}})
|
||||
subs.append(_btn("下载", "small",
|
||||
"window.open(" + json.dumps(dl_url) + ",'_blank');"))
|
||||
return {"widgettype": "HBox",
|
||||
"options": {"width": "100%", "gap": "8px", "padding": "6px 10px",
|
||||
"alignItems": "center", "bgcolor": "#f8fafc",
|
||||
"border": "1px solid #e2e8f0", "borderRadius": "6px"},
|
||||
"subwidgets": subs}
|
||||
|
||||
|
||||
def _md_block(content, md_url):
|
||||
"""可滚动 MdWidget:有内联正文用 mdtext,否则 md_url 拉取。"""
|
||||
opts = {"width": "100%"}
|
||||
if content and len(content) <= _MD_MAX_INLINE:
|
||||
opts["mdtext"] = content
|
||||
elif md_url:
|
||||
opts["md_url"] = md_url
|
||||
else:
|
||||
opts["mdtext"] = content or "(空文档)"
|
||||
return {"widgettype": "VScrollPanel",
|
||||
"options": {"css": "filler", "width": "100%", "height": "100%",
|
||||
"minHeight": "120px", "maxHeight": "480px",
|
||||
"padding": "6px 12px", "bgcolor": "#ffffff",
|
||||
"border": "1px solid #e2e8f0", "borderRadius": "6px"},
|
||||
"subwidgets": [{"widgettype": "MdWidget", "options": opts}]}
|
||||
|
||||
|
||||
def deliverable_widgets(d, proj_file_url):
|
||||
"""单个交付件 → widget 列表(头部行 + 文件区 + 审核意见)。
|
||||
|
||||
d: dict,键 deliverable_type/title/content/file_path/project_id/
|
||||
review_status/quality_score/review_comment。
|
||||
proj_file_url: entire_url('/pipeline-sdlc/api/project_file.dspy') 的结果。
|
||||
"""
|
||||
def g(k):
|
||||
v = d.get(k)
|
||||
return v if v is not None else ''
|
||||
|
||||
d_type = g('deliverable_type') or ''
|
||||
d_title = g('title') or ''
|
||||
content = g('content') or ''
|
||||
fpath = g('file_path') or ''
|
||||
pid = g('project_id') or ''
|
||||
d_status = g('review_status') or ''
|
||||
d_score = g('quality_score') or ''
|
||||
d_comment = g('review_comment') or ''
|
||||
|
||||
out = []
|
||||
head = [
|
||||
{"widgettype": "Text", "options": {"text": d_type, "cfontsize": 0.8,
|
||||
"color": "#3b82f6"}},
|
||||
{"widgettype": "Text", "options": {"text": d_title[:80], "cfontsize": 0.8,
|
||||
"color": "#1e293b"}},
|
||||
{"widgettype": "Filler"},
|
||||
]
|
||||
if d_status or d_score:
|
||||
head.append({"widgettype": "Text", "options": {
|
||||
"text": (d_status + ((" " + str(d_score)) if d_score else '')),
|
||||
"cfontsize": 0.75, "color": "#94a3b8"}})
|
||||
out.append({"widgettype": "HBox", "options": {"gap": "8px", "width": "100%"},
|
||||
"subwidgets": head})
|
||||
|
||||
kind = classify_file(fpath)
|
||||
if fpath and pid:
|
||||
fname = os.path.basename(fpath)
|
||||
open_url = (proj_file_url + "?project_id=" + quote(str(pid))
|
||||
+ "&path=" + quote(fpath))
|
||||
dl_url = open_url + "&download=1"
|
||||
if kind == 'md':
|
||||
# md/txt:MdWidget 渲染 + 下载链接
|
||||
out.append(_file_row(fname, dl_url, hint="Markdown"))
|
||||
out.append(_md_block(content, open_url))
|
||||
elif kind == 'office':
|
||||
# office/pdf:仅下载链接
|
||||
out.append(_file_row(fname, dl_url, hint="office/pdf 下载查看"))
|
||||
else:
|
||||
out.append(_file_row(fname, dl_url, hint="该文件类型需下载后查看"))
|
||||
if content:
|
||||
out.append({"widgettype": "Text", "options": {
|
||||
"text": content[:3000], "cfontsize": 0.78, "color": "#475569",
|
||||
"wrap": True, "halign": "left"}})
|
||||
elif content:
|
||||
# 无文件:内联文本兜底(旧行为)
|
||||
out.append({"widgettype": "Text", "options": {"text": content[:5000],
|
||||
"cfontsize": 0.78, "color": "#475569", "wrap": True, "halign": "left"}})
|
||||
else:
|
||||
out.append({"widgettype": "Text", "options": {"text": "(无交付件内容)",
|
||||
"cfontsize": 0.78, "color": "#94a3b8"}})
|
||||
|
||||
if d_comment:
|
||||
out.append({"widgettype": "Text", "options": {
|
||||
"text": "审核意见: " + str(d_comment)[:300], "cfontsize": 0.75,
|
||||
"color": "#f0a040", "wrap": True, "halign": "left"}})
|
||||
return out
|
||||
|
||||
|
||||
def deliverable_card(d, proj_file_url):
|
||||
"""单个交付件 → 卡片 VBox(供任务详情右侧面板直接追加)。"""
|
||||
return {"widgettype": "VBox",
|
||||
"options": {"css": "card", "gap": "4px", "width": "100%",
|
||||
"padding": "8px"},
|
||||
"subwidgets": deliverable_widgets(d, proj_file_url)}
|
||||
Loading…
x
Reference in New Issue
Block a user