feat(mobile): 手机端「更多」产线入口+功能按钮收缩(2026-09-08用户要求:给会话留最大空间)——my_pipelines.dspy 加 fmt=more 返回 MobileSheet 项[{name,label,icon,desc,url}],url 直达 mobile_agent.ui?pipeline_id=X&embed=1 单产线会话页(已购即出现在底部「更多」,无需改 index.ui);mobile_agent.ui 加 embed 分支(隐藏 ChipBar 胶囊条+门禁遮罩,锁定产线,AgentIO 自带 pipeline_id/session_id=m_<pid>,会话区最大化,服务端 agent_chat 硬门禁兜底);agent_menus.dspy 按 _is_mobile 端形态分流(桌面一排 Button 原样不变,手机收缩为单个「⋯ 功能」Button→MobileSheet 竖排,项 popup:true 点击走 agent_menu_open/workspace_popup 的 PopupWindow 自开链路,与桌面完全一致)

This commit is contained in:
yumoqing 2026-09-08 19:33:59 +08:00
parent 2cfbc55dc3
commit 5ad85f1b60
3 changed files with 110 additions and 53 deletions

View File

@ -54,75 +54,93 @@ try:
except Exception:
menus = []
# 3. 构建菜单按钮actiontype=urlwidget 打开通用弹窗 agent_menu_open控制弹窗大小
# 3. 构建功能入口actiontype=urlwidget 打开通用弹窗 agent_menu_open控制弹窗大小
# 注意:这里不预先 urlencode 查询参数。前端 bricks_fetch 的 url_parse 会把 url 里的
# 查询参数提取后再 encodeURIComponent 编码一次,若此处已 urlencode 会导致双重编码
# title 的 emoji/中文会变成 %F0%9F... 明文显示)。
#
# 「需要项目」判断在【点击时】由 agent_menu_open 服务端做(传 require_project/session_id/pipeline_id
# 渲染时固化判断是老 bug——页面加载时还没切项目 → 按钮写死报错脚本,之后切了项目仍报错。
#
# 端形态2026-09-08 用户要求:产线上方的按钮收缩为一个菜单项,给会话留最大空间):
# 桌面:一排 Button原样
# 手机jsoncall 自动带 _is_mobile=1单个「⋯ 功能」Button → 点击弹
# MobileSheet 竖向菜单,项点击走同一 open_urlpopup:trueagent_menu_open
# 返回 PopupWindow 自开,与桌面链路完全一致)
buttons = []
_is_mobile = str((params_kw or {}).get('_is_mobile', '') or '') == '1'
# 通用会话pipeline_id=_generic只给"工作空间"按钮,锁用户专属目录
actions = [] # [{label, url}] 统一收集,最后按端形态渲染
# 通用会话pipeline_id=_generic只给"工作空间"入口,锁用户专属目录
if pipeline_id == '_generic':
buttons.append({
"widgettype": "Button",
"options": {"label": "🗂 工作空间", "css": "small", "id": "menu_btn_ws_generic"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "self",
"options": {"url": entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
+ "?pipeline_id=_generic"},
}],
actions.append({
"label": "🗂 工作空间",
"url": entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
+ "?pipeline_id=_generic",
})
else:
# 固定「工作空间」入口(通用能力,所有 agent 都有;依赖当前项目)
# 带产线参数:入口产线与会话项目跨产线时视为无项目(产线隔离,弹窗端点自身在点击时判断)
_ws_url = entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
_ws_q = []
if session_id:
_ws_q.append("session_id=" + session_id)
if pipeline_id:
_ws_q.append("pipeline_id=" + pipeline_id)
if _ws_q:
_ws_url += "?" + "&".join(_ws_q)
actions.append({"label": "🗂 工作空间", "url": _ws_url})
for i, m in enumerate(menus):
label = str(m.get("label", "") or "")
url = str(m.get("url", "") or "")
if not url:
continue
require_project = bool(m.get("require_project", False))
width = str(m.get("width", "85%") or "85%")
height = str(m.get("height", "80%") or "80%")
open_url = (entire_url("/pipeline_core/api/agent_menu_open.dspy")
+ "?url=" + url + "&title=" + label
+ "&width=" + width + "&height=" + height
+ ("&require_project=1" if require_project else ""))
if session_id:
open_url += "&session_id=" + session_id
if pipeline_id:
open_url += "&pipeline_id=" + pipeline_id
actions.append({"label": label, "url": open_url})
if _is_mobile:
# 手机端:收缩为单个「⋯ 功能」按钮 → MobileSheet 竖向菜单。
# items 内联进脚本JSON 字面量popup:true → 点击 widgetBuild urlwidget
# 服务端返回 PopupWindow 自开agent_menu_open / workspace_popup 同链路)。
_items = [{"name": "act_%d" % i, "label": a["label"], "url": a["url"], "popup": True}
for i, a in enumerate(actions)]
_script = (
"var its=" + _json.dumps(_items, ensure_ascii=False) + ";"
"var sh=new bricks.MobileSheet({title:'功能',items:its});sh.open();"
)
return _json.dumps({
"widgettype": "HBox",
"options": {"width": "100%", "padding": "0 12px 8px 12px", "gap": "8px", "alignItems": "center"},
"subwidgets": buttons,
"options": {"width": "100%", "padding": "0 12px 6px 12px", "gap": "8px", "alignItems": "center"},
"subwidgets": [{
"widgettype": "Button",
"options": {"label": "⋯ 功能", "css": "small", "id": "menu_btn_mobile_more"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": _script,
}],
}],
}, ensure_ascii=False)
# 固定「工作空间」按钮(通用能力,所有 agent 都有;依赖当前项目)
# 带产线参数:入口产线与会话项目跨产线时视为无项目(产线隔离,弹窗端点自身在点击时判断)
_ws_url = entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
_ws_q = []
if session_id:
_ws_q.append("session_id=" + session_id)
if pipeline_id:
_ws_q.append("pipeline_id=" + pipeline_id)
if _ws_q:
_ws_url += "?" + "&".join(_ws_q)
buttons.append({
"widgettype": "Button",
"options": {"label": "🗂 工作空间", "css": "small", "id": "menu_btn_ws"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "self", "options": {"url": _ws_url},
}],
})
for i, m in enumerate(menus):
label = str(m.get("label", "") or "")
url = str(m.get("url", "") or "")
if not url:
continue
require_project = bool(m.get("require_project", False))
width = str(m.get("width", "85%") or "85%")
height = str(m.get("height", "80%") or "80%")
open_url = (entire_url("/pipeline_core/api/agent_menu_open.dspy")
+ "?url=" + url + "&title=" + label
+ "&width=" + width + "&height=" + height
+ ("&require_project=1" if require_project else ""))
if session_id:
open_url += "&session_id=" + session_id
if pipeline_id:
open_url += "&pipeline_id=" + pipeline_id
buttons = []
for i, a in enumerate(actions):
buttons.append({
"widgettype": "Button",
"options": {"label": label, "css": "small", "id": "menu_btn_%d" % i},
"options": {"label": a["label"], "css": "small", "id": "menu_btn_%d" % i},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "self", "options": {"url": open_url},
"target": "self", "options": {"url": a["url"]},
}],
})

View File

@ -17,6 +17,10 @@ dbname = get_module_dbname('pipeline_core')
# 默认(手机端 ChipBar客户只见已购未到期产线2026-09-07 用户定夺)
_mode = ((params_kw or {}).get('mode') or '').strip()
# fmt=more底部 TabBar「更多」菜单返回 MobileSheet 项格式
# [{name,label,icon,desc,url}]url 直达该产线会话页2026-09-08
_fmt = ((params_kw or {}).get('fmt') or '').strip()
items = []
try:
# 平台侧角色owner./reseller.)看全部 published 产线;客户只看已购未到期。
@ -69,8 +73,17 @@ try:
ids = [(getattr(r, 'id', ''), getattr(r, 'name', '')) for r in (prec or [])]
for pid, pname in ids:
items.append({"id": pid, "pipeline_id": pid,
"name": pname or pid, "session_id": "m_" + pid})
if _fmt == 'more':
# 底部 TabBar「更多」菜单项2026-09-08点击 → 内容区直达该产线
# 会话页embed=1隐藏胶囊条/门禁,锁定产线,会话区最大化)。
# url 不预 urlencode前端 url_parse 会再编码一次,双重编码坑)
items.append({"name": "m_pl_" + pid, "label": pname or pid,
"icon": "⚡", "desc": "已购产线",
"url": entire_url("/pipeline_core/mobile_agent.ui")
+ "?pipeline_id=" + pid + "&embed=1"})
else:
items.append({"id": pid, "pipeline_id": pid,
"name": pname or pid, "session_id": "m_" + pid})
except Exception:
items = []

View File

@ -1,3 +1,28 @@
{% set embed = params_kw.get('embed','') == '1' %}
{% set pl_id = params_kw.get('pipeline_id','') or '' %}
{% if embed %}
{
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "padding": "0"},
"subwidgets": [
{
"widgettype": "urlwidget",
"options": {"url": "/pipeline_core/api/agent_menus.dspy?pipeline_id={{pl_id}}&session_id=m_{{pl_id}}"}
},
{
"widgettype": "AgentIO",
"options": {
"css": "filler",
"margin": "0 8px 8px 8px",
"url": "/pipeline_core/api/agent_chat.dspy?pipeline_id={{pl_id}}&session_id=m_{{pl_id}}",
"model_dataurl": "/pipeline_core/api/agent_model_options.dspy?capabilities=chat&pipeline_id={{pl_id}}&session_id=m_{{pl_id}}",
"model_cwidth": 14,
"placeholder": "输入你的需求..."
}
}
]
}
{% else %}
{
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "padding": "0", "css": "pl-gate-root"},
@ -91,3 +116,4 @@
}
]
}
{% endif %}