pbl_scense_ext/scripts/gen_overlay_pages.py
2026-09-21 12:20:49 +08:00

136 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成 M9 覆盖层 12 个页面壳 wwwroot/pbl_overlay/<key>.ui(bricks 纯 JSON)。
为什么需要:wwwroot/overlay/pbl_overlay_core.js 的 PAGES 注册表 route 指向
`/pbl_overlay/<key>.ui`,play.js 的 pageUrl() 会拼成
`/pbl_scense_ext/pbl_overlay/<key>.ui`;页面壳文件不存在则导航即 404(QC #3)。
页面壳职责(只做壳,不做业务渲染):
1. 打 `data-pbl-page="<key>"` + `data-pbl-overlay-root="1"` 标记 —— play.js
hostPageKey() 据此识别当前页、boot() 据此挂载覆盖层根节点;
2. 提供 `script` actiontype 按钮调用 `PblOverlayLoader.start({page:...})`,
由 loader 按需注入 core/transport/play JS 与 CSS(不在 .ui 里硬写 <script>/<link>,
遵守 module-development-spec「ahserver auto-serves .css/.js」);
3. 提供返回首页的 `url` actiontype 导航。
route 与 RBAC:新增/删除页面壳必须同步 scripts/load_path.py 的 OVERLAY_PAGES。
"""
import json
import os
MODULE = 'pbl_scense_ext'
LOADER_PATH = '/%s/overlay/pbl_overlay_loader.js' % MODULE
# (key, title, in_world) —— 与 wwwroot/overlay/pbl_overlay_core.js PAGES 注册表同源
PAGES = [
('home', 'PBL 首页', False),
('blueprint_list', '蓝图列表', False),
('blueprint_editor', '蓝图结构视图', False),
('validation_report', '校验报告(14维)', False),
('agent_console', 'Agent 控制台', False),
('approval_center', '人工审批中心', False),
('play_preview', 'Play 预览', True),
('mission_board', '驱动问题板', True),
('role_panel', '角色面板', True),
('artifact_studio', '产出物工坊', True),
('evidence_wall', '证据墙', True),
('assessment_report', '评估报告', False),
]
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'wwwroot', 'pbl_overlay')
def enable_script(key):
"""点击「启用 PBL 覆盖层」:优先用已注入的 loader;未注入则先注入 loader.js(RBAC=any)再启动。"""
return (
"window.PblOverlayLoader"
"?PblOverlayLoader.start({page:'%s'})"
":(function(){"
"var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');"
"var s=document.createElement('script');s.src=b+'%s';s.async=false;"
"s.onload=function(){window.PblOverlayLoader.start({page:'%s'})};"
"s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};"
"document.head.appendChild(s);})()"
) % (key, LOADER_PATH, key)
def build_page(key, title, in_world):
shell_html = (
'<div data-pbl-page="{key}" data-pbl-overlay-root="1" class="pbl-shell">'
'<div class="pbl-shell-hint">'
'{title}({key})· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;'
'{hint}'
'</div></div>'
).format(
key=key, title=title,
hint='本页为世界内覆盖层(广播渲染预算 ≤500ms)。' if in_world
else '本页为教师/学生侧页面(首屏预算 ≤1.5s)。'
)
return {
'widgettype': 'VBox',
'options': {
'width': '100%', 'height': '100%', 'padding': '12px',
'backgroundColor': '#0B1020',
},
'subwidgets': [
{
'widgettype': 'Text',
'options': {'label': 'PBL 覆盖层 · ' + title, 'fontSize': '18px',
'fontWeight': 'bold', 'color': '#E5E7EB'},
},
{
'widgettype': 'Html',
'id': 'pbl_overlay_shell_' + key,
'options': {'html': shell_html},
},
{
'widgettype': 'HBox',
'options': {'width': '100%', 'gap': '8px', 'marginTop': '10px'},
'subwidgets': [
{
'widgettype': 'Button',
'options': {'label': '启用 PBL 覆盖层', 'type': 'primary'},
'binds': [{'wid': 'self', 'event': 'click',
'actiontype': 'script',
'options': {'script': enable_script(key)}}],
},
{
'widgettype': 'Button',
'options': {'label': 'PBL 首页'},
'binds': [{'wid': 'self', 'event': 'click',
'actiontype': 'url',
'options': {'url': "{{entire_url('/%s/pbl_overlay/home.ui')}}" % MODULE}}],
},
],
},
{
'widgettype': 'Text',
'options': {'label': '上下文:URL 携带 session_id / blueprint_id / world_id;'
'覆盖层仅渲染服务端权威状态,不本地预测(Q4)。',
'fontSize': '12px', 'color': '#6B7280', 'marginTop': '10px'},
},
],
}
def main():
if not os.path.isdir(OUT_DIR):
os.makedirs(OUT_DIR)
written = []
for key, title, in_world in PAGES:
obj = build_page(key, title, in_world)
path = os.path.join(OUT_DIR, key + '.ui')
with open(path, 'w', encoding='utf-8') as f:
json.dump(obj, f, ensure_ascii=False, indent=2)
f.write('\n')
written.append(path)
print('generated %d overlay page shells:' % len(written))
for p in written:
print(' ' + os.path.relpath(p, os.path.dirname(OUT_DIR)))
if __name__ == '__main__':
main()