feat: add approvable interactive UML stages

This commit is contained in:
user
2026-07-16 14:10:16 +02:00
parent 41804a8ad1
commit 1c06ca35d6
4 changed files with 440 additions and 36 deletions
+41 -8
View File
@@ -765,6 +765,7 @@ def render_preamble(data: dict) -> list[str]:
r"\newtcolorbox{ESCTaskFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black,boxsep=0pt,left=1.4mm,right=1.4mm,top=0.8mm,bottom=0.8mm,colbacktitle=black!7,coltitle=black,title={\ttfamily\bfseries TASK\quad #1}}",
r"\newtcolorbox{ESCBlockFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black!55,boxsep=0pt,left=1.0mm,right=1.0mm,top=0.6mm,bottom=0.6mm,colbacktitle=black!4,coltitle=black,title={\ttfamily\bfseries BLOCK\quad #1}}",
r"\newtcolorbox{ESCStepFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black!28,boxsep=0pt,left=0.8mm,right=0.8mm,top=0.45mm,bottom=0.45mm,colbacktitle=black!2,coltitle=black,title={\ttfamily STEP\quad #1}}",
r"\newtcolorbox{ESCConclusionFrame}{enhanced,breakable,arc=0pt,boxrule=0.45pt,colback=blue!2,colframe=blue!45!black,boxsep=0pt,left=1.2mm,right=1.2mm,top=0.7mm,bottom=0.7mm,colbacktitle=blue!7,coltitle=black,title={\ttfamily\bfseries WNIOSEK}}",
"",
]
@@ -971,7 +972,6 @@ def render_task_flow_tex(task_ref: str, task: dict) -> list[str]:
def render_tasks(section: dict, data: dict) -> list[str]:
tasks = data["tasks"]
lines: list[str] = []
legacy_refs: list[str] = []
for index, task_ref in enumerate(section["task_refs"]):
if index:
# Kolejny task jest niezależnym etapem karty: jego ramka ma
@@ -981,12 +981,22 @@ def render_tasks(section: dict, data: dict) -> list[str]:
if task.get("flow"):
lines.extend(render_task_flow_tex(task_ref, task))
else:
legacy_refs.append(task_ref)
if legacy_refs:
lines.append(r"\begin{enumerate}[label=\textbf{\arabic*.}]")
for task_ref in legacy_refs:
lines.append(rf" \item {tasks[task_ref]['prompt_tex']}")
lines.append(r"\end{enumerate}")
lines.extend(
[
rf"\begin{{enumerate}}[label=\textbf{{\arabic*.}},start={index + 1}]",
rf" \item {task['prompt_tex']}",
r"\end{enumerate}",
]
)
return lines
def render_task_conclusions_tex(section: dict, data: dict) -> list[str]:
lines: list[str] = []
for task_ref in section.get("task_refs", []) or []:
conclusion = str(data["tasks"][task_ref].get("conclusion_tex", "")).strip()
if conclusion:
lines.extend([r"\begin{ESCConclusionFrame}", conclusion, r"\end{ESCConclusionFrame}"])
return lines
@@ -1844,6 +1854,7 @@ def render_sections(data: dict) -> list[str]:
else:
lines.append(section.get("content_tex", ""))
lines.extend(render_section_assets_tex(section, data))
lines.extend(render_task_conclusions_tex(section, data))
lines.append(r"\ESCSectionBlockEnd")
lines.append("")
return lines
@@ -2801,6 +2812,24 @@ def render_tasks_html(section: dict, data: dict, equation_numbers: dict[str, int
return f'<div class="tasks">{"".join(items)}</div>'
def render_task_conclusions_html(
section: dict,
data: dict,
equation_numbers: dict[str, int],
figure_numbers: dict[str, int],
) -> str:
items: list[str] = []
for task_ref in section.get("task_refs", []) or []:
conclusion = str(data["tasks"][task_ref].get("conclusion_tex", "")).strip()
if conclusion:
items.append(
'<aside class="task-conclusion"><strong>WNIOSEK</strong>'
+ render_latex_fragment_html(conclusion, equation_numbers, figure_numbers)
+ "</aside>"
)
return "".join(items)
def render_learning_tree_overview_html(data: dict) -> str:
requirements = data.get("educational_requirements", {})
chunks: list[str] = []
@@ -2910,6 +2939,7 @@ def render_main_html(data: dict) -> str:
else render_latex_fragment_html(section.get("content_tex", ""), equation_numbers, figure_numbers)
)
content += render_section_assets_html(section, figure_numbers)
content += render_task_conclusions_html(section, data, equation_numbers, figure_numbers)
left_margin, right_margin = render_section_margins_html(section, data)
task_identity = section.get("task_identity", {}) or {}
identity_html = ""
@@ -3026,7 +3056,7 @@ p{font-size:16px;margin:0 0 3mm}.pdf-main ul{font-size:16px;margin:2mm 0 3mm 7mm
figure{margin:24px 0;text-align:center}figure img{max-width:100%;display:block;margin:0 auto;border:0;border-radius:0}figcaption{font-size:14px;color:#444;margin-top:8px;text-align:left}.card-asset.asset-screenshot img{border:1px solid #aaa}.card-asset a{display:block;cursor:zoom-in}
.table-wrap{overflow:auto;margin:14px 0}table{border-collapse:collapse;width:100%;font-size:16px}th,td{border:1px solid #111;padding:5px 8px;text-align:left;vertical-align:top}th{font-weight:400;background:white}
.empty-check{display:inline-block;width:1em;height:1em;border:1px solid #111;vertical-align:-.12em}.answer-line,.dotfill{display:block;border-bottom:1px dotted #111;height:1.45em;min-width:10em}.write-row{display:block;min-height:2.8em}
code{font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace}.tasks{font-size:16px}.task-legacy{margin:2.5mm 0}.task-legacy>strong{display:block;font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#555}.task-flow{border:1px solid #222;margin:1mm 0 2mm;background:#fff}.task-flow-header{display:grid;grid-template-columns:auto 1fr auto;align-items:baseline;gap:6px;padding:5px 7px;background:#eaeaea;border-bottom:1px solid #222}.task-flow-header span{font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#222}.task-flow-header h3{font-size:18px;margin:0}.task-flow-header code{font-size:8px;color:#555}.task-block{margin:6px 7px;padding:5px 6px;border:1px solid #777;background:#fff}.task-block header,.task-exercise header{display:flex;align-items:baseline;gap:5px;margin-bottom:3px}.task-block header span,.task-exercise header span{font:700 9px/1.2 "Latin Modern Mono",ui-monospace,monospace;text-transform:uppercase;color:#555}.task-block h4,.task-exercise h4{font-size:16px;margin:0}.block-steps{margin:4px 0 0 16px!important;padding:0;display:grid;gap:3px}.block-steps li{margin:0;border:1px solid #b9b9b9;padding:4px 5px;background:#fff}.block-steps li::marker{font-family:"Latin Modern Mono",ui-monospace,monospace;font-weight:700}.block-steps li p:last-child{margin-bottom:0}.task-exercise{margin:6px 7px;border:1px solid #777;border-left:1px solid #222;padding:5px 6px;background:#fafafa}.task-exercise header{flex-wrap:wrap}.exercise-based-on{margin-left:auto;font-size:8px;color:#777}.exercise-evidence{border-top:1px dashed #bbb;margin-top:5px;padding-top:4px}.exercise-evidence>strong{font-size:11px;text-transform:uppercase}.exercise-criterion{font-size:13px;margin:4px 0 0}.task-flow>footer{padding:5px 7px;border-top:1px solid #222;font-size:12px;background:#f6f6f6}.we-block{border-top:1px solid #ddd;padding:8px 0}.we-block summary{cursor:pointer}.dictionary-sheet .pdf-margin{display:none}
code{font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace}.tasks{font-size:16px}.task-legacy{margin:2.5mm 0}.task-legacy>strong{display:block;font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#555}.task-flow{border:1px solid #222;margin:1mm 0 2mm;background:#fff}.task-flow-header{display:grid;grid-template-columns:auto 1fr auto;align-items:baseline;gap:6px;padding:5px 7px;background:#eaeaea;border-bottom:1px solid #222}.task-flow-header span{font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#222}.task-flow-header h3{font-size:18px;margin:0}.task-flow-header code{font-size:8px;color:#555}.task-block{margin:6px 7px;padding:5px 6px;border:1px solid #777;background:#fff}.task-block header,.task-exercise header{display:flex;align-items:baseline;gap:5px;margin-bottom:3px}.task-block header span,.task-exercise header span{font:700 9px/1.2 "Latin Modern Mono",ui-monospace,monospace;text-transform:uppercase;color:#555}.task-block h4,.task-exercise h4{font-size:16px;margin:0}.block-steps{margin:4px 0 0 16px!important;padding:0;display:grid;gap:3px}.block-steps li{margin:0;border:1px solid #b9b9b9;padding:4px 5px;background:#fff}.block-steps li::marker{font-family:"Latin Modern Mono",ui-monospace,monospace;font-weight:700}.block-steps li p:last-child{margin-bottom:0}.task-exercise{margin:6px 7px;border:1px solid #777;border-left:1px solid #222;padding:5px 6px;background:#fafafa}.task-exercise header{flex-wrap:wrap}.exercise-based-on{margin-left:auto;font-size:8px;color:#777}.exercise-evidence{border-top:1px dashed #bbb;margin-top:5px;padding-top:4px}.exercise-evidence>strong{font-size:11px;text-transform:uppercase}.exercise-criterion{font-size:13px;margin:4px 0 0}.task-flow>footer{padding:5px 7px;border-top:1px solid #222;font-size:12px;background:#f6f6f6}.task-conclusion{margin:8px 0;padding:7px 9px;border:1px solid #315f78;background:#f3f9fc}.task-conclusion>strong{display:block;margin-bottom:3px;font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#315f78}.task-conclusion p{margin:0}.we-block{border-top:1px solid #ddd;padding:8px 0}.we-block summary{cursor:pointer}.dictionary-sheet .pdf-margin{display:none}
.inspector{position:fixed;right:14px;bottom:14px;width:min(360px,calc(100vw - 28px));max-height:46vh;overflow:auto;background:white;border:1px solid #cfcfcf;box-shadow:0 4px 18px rgba(0,0,0,.18);border-radius:6px;padding:12px;font-family:system-ui,-apple-system,Segoe UI,sans-serif;font-size:13px;z-index:30}.inspector h2{font-size:14px;margin:0 0 8px}.inspector .empty{color:#777}.tree-card h3{font-size:15px;margin:6px 0}.tree-card code{display:inline-block;margin:4px 0}.tree-card .line{font-weight:700}.tree-card .line.OG{color:var(--og)}.tree-card .line.TECH{color:var(--tech)}.kw-list{padding-left:18px}.kw-list code{color:var(--kw)}
@media(max-width:900px){.sheet{display:block;min-height:0}.sheet-content{min-height:0;padding:26px 18px}.pdf-margin{position:static;max-height:none;overflow:visible;margin:10px 0;padding:8px;border:1px solid #ddd}.pdf-margin.left{border-left:3px solid var(--tech)}.pdf-margin.right{border-left:3px solid var(--og)}.hero .byline{float:none;margin:0 0 14px}.meta{grid-template-columns:1fr}.section-heading{flex-wrap:wrap}.task-identity{width:100%;padding:6px 0 0}.inspector{position:static;width:auto;max-height:none;margin:0 12px 18px}.topbar{position:static}.viewer-zoom-status{font-size:16px}}
@media print{html,body{background:white}.topbar,.inspector{display:none}.paper{padding:0}.sheet{position:relative;display:block;width:auto;min-height:0;margin:0;box-shadow:none;break-after:page;padding:0;overflow:visible;zoom:1!important}.sheet-content{position:relative;width:auto;min-height:0;padding:0;transform:none!important}.sheet:last-child{break-after:auto}.resource-header-enabled .sheet-content{padding:0}.sheet-content>.page-resource-header,.sheet-content>.page-resource-footer{display:none!important}.pdf-margin{position:absolute;top:0;width:18mm;max-height:none;overflow:hidden;margin:0;padding:0;border:0}.pdf-margin.left{left:-19.5mm}.pdf-margin.right{right:-19.5mm}.task-block,.task-exercise,.block-steps li,figure{break-inside:avoid}}
@@ -6974,6 +7004,9 @@ def react_task_model(
"title": str(task.get("title") or task_ref),
"uuid": str(task.get("uuid", "")),
"criterion": str(task.get("criterion", "")),
"conclusion_html": render_latex_fragment_html(
str(task.get("conclusion_tex", "")), equation_numbers, figure_numbers
),
"prompt_html": render_latex_fragment_html(
str(task.get("prompt_tex", "")), equation_numbers, figure_numbers
),