From 5ad85f1b606d7768896d33661435c83efa3a3db4 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 8 Sep 2026 19:33:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(mobile):=20=E6=89=8B=E6=9C=BA=E7=AB=AF?= =?UTF-8?q?=E3=80=8C=E6=9B=B4=E5=A4=9A=E3=80=8D=E4=BA=A7=E7=BA=BF=E5=85=A5?= =?UTF-8?q?=E5=8F=A3+=E5=8A=9F=E8=83=BD=E6=8C=89=E9=92=AE=E6=94=B6?= =?UTF-8?q?=E7=BC=A9(2026-09-08=E7=94=A8=E6=88=B7=E8=A6=81=E6=B1=82:?= =?UTF-8?q?=E7=BB=99=E4=BC=9A=E8=AF=9D=E7=95=99=E6=9C=80=E5=A4=A7=E7=A9=BA?= =?UTF-8?q?=E9=97=B4)=E2=80=94=E2=80=94my=5Fpipelines.dspy=20=E5=8A=A0=20f?= =?UTF-8?q?mt=3Dmore=20=E8=BF=94=E5=9B=9E=20MobileSheet=20=E9=A1=B9[{name,?= =?UTF-8?q?label,icon,desc,url}],url=20=E7=9B=B4=E8=BE=BE=20mobile=5Fagent?= =?UTF-8?q?.ui=3Fpipeline=5Fid=3DX&embed=3D1=20=E5=8D=95=E4=BA=A7=E7=BA=BF?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E9=A1=B5(=E5=B7=B2=E8=B4=AD=E5=8D=B3?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E5=9C=A8=E5=BA=95=E9=83=A8=E3=80=8C=E6=9B=B4?= =?UTF-8?q?=E5=A4=9A=E3=80=8D,=E6=97=A0=E9=9C=80=E6=94=B9=20index.ui);mobi?= =?UTF-8?q?le=5Fagent.ui=20=E5=8A=A0=20embed=20=E5=88=86=E6=94=AF(?= =?UTF-8?q?=E9=9A=90=E8=97=8F=20ChipBar=20=E8=83=B6=E5=9B=8A=E6=9D=A1+?= =?UTF-8?q?=E9=97=A8=E7=A6=81=E9=81=AE=E7=BD=A9,=E9=94=81=E5=AE=9A?= =?UTF-8?q?=E4=BA=A7=E7=BA=BF,AgentIO=20=E8=87=AA=E5=B8=A6=20pipeline=5Fid?= =?UTF-8?q?/session=5Fid=3Dm=5F,=E4=BC=9A=E8=AF=9D=E5=8C=BA=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E5=8C=96,=E6=9C=8D=E5=8A=A1=E7=AB=AF=20agent=5Fchat?= =?UTF-8?q?=20=E7=A1=AC=E9=97=A8=E7=A6=81=E5=85=9C=E5=BA=95);agent=5Fmenus?= =?UTF-8?q?.dspy=20=E6=8C=89=20=5Fis=5Fmobile=20=E7=AB=AF=E5=BD=A2?= =?UTF-8?q?=E6=80=81=E5=88=86=E6=B5=81(=E6=A1=8C=E9=9D=A2=E4=B8=80?= =?UTF-8?q?=E6=8E=92=20Button=20=E5=8E=9F=E6=A0=B7=E4=B8=8D=E5=8F=98,?= =?UTF-8?q?=E6=89=8B=E6=9C=BA=E6=94=B6=E7=BC=A9=E4=B8=BA=E5=8D=95=E4=B8=AA?= =?UTF-8?q?=E3=80=8C=E2=8B=AF=20=E5=8A=9F=E8=83=BD=E3=80=8DButton=E2=86=92?= =?UTF-8?q?MobileSheet=20=E7=AB=96=E6=8E=92,=E9=A1=B9=20popup:true=20?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E8=B5=B0=20agent=5Fmenu=5Fopen/workspace=5Fp?= =?UTF-8?q?opup=20=E7=9A=84=20PopupWindow=20=E8=87=AA=E5=BC=80=E9=93=BE?= =?UTF-8?q?=E8=B7=AF,=E4=B8=8E=E6=A1=8C=E9=9D=A2=E5=AE=8C=E5=85=A8?= =?UTF-8?q?=E4=B8=80=E8=87=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wwwroot/api/agent_menus.dspy | 120 +++++++++++++++++++--------------- wwwroot/api/my_pipelines.dspy | 17 ++++- wwwroot/mobile_agent.ui | 26 ++++++++ 3 files changed, 110 insertions(+), 53 deletions(-) diff --git a/wwwroot/api/agent_menus.dspy b/wwwroot/api/agent_menus.dspy index d9f674d..988443a 100644 --- a/wwwroot/api/agent_menus.dspy +++ b/wwwroot/api/agent_menus.dspy @@ -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_url(popup:true,agent_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"]}, }], }) diff --git a/wwwroot/api/my_pipelines.dspy b/wwwroot/api/my_pipelines.dspy index 0b5e9aa..c468e33 100644 --- a/wwwroot/api/my_pipelines.dspy +++ b/wwwroot/api/my_pipelines.dspy @@ -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 = [] diff --git a/wwwroot/mobile_agent.ui b/wwwroot/mobile_agent.ui index c86f2fc..2ace543 100644 --- a/wwwroot/mobile_agent.ui +++ b/wwwroot/mobile_agent.ui @@ -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 %}