pipeline_core/wwwroot/api/agent_menus.dspy
yumoqing 5955232ed5 fix(menu): require_project判断从渲染时移到点击时——切项目后菜单按钮不再永远报无项目
- agent_menus: 删除渲染时固化的报错脚本绑定,require_project/session_id/pipeline_id
  作为参数传给 agent_menu_open,点击时实时校验
- agent_menu_open: 点击时实时查会话项目(含产线隔离),无项目才提示切换
- 根治:页面加载时未切项目→按钮写死报错脚本→切了项目仍报「请先在会话中切换项目」
2026-08-31 18:38:55 +08:00

116 lines
4.8 KiB
Plaintext
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.

# agent_menus.dspy - 返回当前产线的功能菜单AgentIO 上方菜单/按钮)
# 由 core 通用 agent 的 index.ui 通过 urlwidget 加载,动态渲染产线固化的联机功能入口
import json as _json
uid = await get_user()
if not uid:
uid = 'user-01'
# 会话内唯一标识web 多 tab工作空间按钮按会话解析项目
session_id = (params_kw or {}).get('session_id', '') or ''
# 产线默认能力(入口指定,如 bidding_general无当前项目时用该产线菜单/能力
default_pipeline_id = (params_kw or {}).get('pipeline_id', '') or ''
# 1. 解析当前产线:当前会话项目 → sd_projects.pipeline_id
# 统一走 get_session_project_id含悬空引用防护指向已删除项目的记录自愈清理并回退全局
# 禁止在此重复实现 session→项目解析——多份实现语义漂移正是「会话有项目、入口说没有」的根因。
# 产线隔离:入口指定产线(如商机/投标)时,会话项目必须同产线才生效——
# 否则残留的跨产线项目(如开发产线的元景项目)会劫持本产线菜单/工作空间。
pipeline_id = ''
pid = ''
try:
from pipeline_service.workspace import get_session_project_id
dbname = get_module_dbname('pipeline_core')
async with DBPools().sqlorContext(dbname) as sor:
pid = await get_session_project_id(sor, uid, session_id) or ''
if pid:
precs = await sor.sqlExe(
"SELECT pipeline_id FROM sd_projects WHERE id=${p}$", {"p": pid})
if precs:
pipeline_id = getattr(precs[0], 'pipeline_id', '') or ''
if default_pipeline_id and pipeline_id != default_pipeline_id:
# 跨产线项目劫持:清空项目,用入口产线
pid = ''
pipeline_id = default_pipeline_id
except Exception:
pass
if not pipeline_id:
if default_pipeline_id:
pipeline_id = default_pipeline_id
else:
try:
from pipeline_core import DEFAULT_ABILITY_ID
pipeline_id = DEFAULT_ABILITY_ID
except Exception:
pipeline_id = 'sdlc_general'
# 2. 读产线功能菜单
try:
from pipeline_core.ability import get_ability_menus
menus = get_ability_menus(pipeline_id)
except Exception:
menus = []
# 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——页面加载时还没切项目 → 按钮写死报错脚本,之后切了项目仍报错。
buttons = []
# 固定「工作空间」按钮(通用能力,所有 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.append({
"widgettype": "Button",
"options": {"label": label, "css": "small", "id": "menu_btn_%d" % i},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "self", "options": {"url": open_url},
}],
})
return _json.dumps({
"widgettype": "HBox",
"options": {"width": "100%", "padding": "0 24px 8px 24px", "gap": "8px", "alignItems": "center"},
"subwidgets": buttons,
}, ensure_ascii=False)