feat(mobile): 未购产线门禁——mobile_agent.ui加遮罩引导层(尚未购买任何产线→去商城/用通用助手,遮罩desktop_hidden+select有产线即hide)+agent_chat.dspy服务端订阅门禁(未购/过期拒绝对话,平台角色豁免,DB异常fail-open)+my_pipelines加mode=full+agent_menus通用模式工作空间按钮+agent_generic工作空间改_generic锁专属目录(2026-09-08用户三条要求)
This commit is contained in:
parent
d3c5b4f7e1
commit
2cfbc55dc3
@ -18,8 +18,9 @@
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "🗂 工作空间", "css": "small"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "self", "options": {"url": "/pipeline-sdlc/api/workspace_popup.dspy?pipeline_id=sdlc_general"}}]
|
||||
}
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "self", "options": {"url": "/pipeline-sdlc/api/workspace_popup.dspy?pipeline_id=_generic"}}]
|
||||
},
|
||||
{"widgettype": "Text", "options": {"text": "工作空间为通用会话专属目录,不进入项目空间", "cfontsize": 0.75, "color": "#94a3b8"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -72,6 +72,40 @@ if action == 'send_message':
|
||||
# ── 产线默认能力(入口指定,如 bidding_general):无当前项目时装载该产线能力 ──
|
||||
pipeline_id = (params_kw or {}).get('pipeline_id', '') or ''
|
||||
|
||||
# ── 订阅门禁(2026-09-08 用户要求:未购买不能进入对话模式)──
|
||||
# 产线对话须持有该产线有效(未到期)订阅;平台侧角色(owner./reseller.)
|
||||
# 豁免;通用会话(空/_generic)豁免。桌面/手机所有入口在此统一拦截,
|
||||
# 前端遮罩只做引导。DB 异常时 fail-open(可用性优先,门禁是商业限制非安全边界)。
|
||||
if pipeline_id and pipeline_id != '_generic':
|
||||
_gate_ok = True
|
||||
try:
|
||||
_roles = await get_user_roles(uid)
|
||||
_is_ops = any(str(r).startswith('owner.') or str(r).startswith('reseller.')
|
||||
for r in (_roles or []))
|
||||
except Exception:
|
||||
_is_ops = False
|
||||
if not _is_ops:
|
||||
_orgid = await get_userorgid()
|
||||
try:
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
_sub = await sor.sqlExe(
|
||||
"SELECT ps.id FROM product_subscription ps"
|
||||
" JOIN product pr ON ps.product_id = pr.id"
|
||||
" WHERE ps.user_org_id=${org}$ AND ps.status='1'"
|
||||
" AND ps.end_date >= CURDATE()"
|
||||
" AND pr.product_type='pipeline'"
|
||||
" AND pr.resource_ref_id=${pid}$ LIMIT 1",
|
||||
{"org": _orgid, "pid": pipeline_id})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
_gate_ok = bool(_sub)
|
||||
except Exception:
|
||||
_gate_ok = True
|
||||
if not _gate_ok:
|
||||
async def _gate_stream():
|
||||
yield json.dumps({"error": "尚未购买该产线或订阅已到期,请到产品商城购买后再开始对话"},
|
||||
ensure_ascii=False) + '\n'
|
||||
return await stream_response(request, _gate_stream, 'text/plain; charset=utf-8')
|
||||
|
||||
# ── 上传文件落盘(有项目→项目根;无项目→workspace 会话目录)+ 生成显式截断上下文 ──
|
||||
file_ctx = ''
|
||||
if _uploads:
|
||||
|
||||
@ -64,6 +64,24 @@ except Exception:
|
||||
|
||||
buttons = []
|
||||
|
||||
# 通用会话(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"},
|
||||
}],
|
||||
})
|
||||
return _json.dumps({
|
||||
"widgettype": "HBox",
|
||||
"options": {"width": "100%", "padding": "0 12px 8px 12px", "gap": "8px", "alignItems": "center"},
|
||||
"subwidgets": buttons,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
# 固定「工作空间」按钮(通用能力,所有 agent 都有;依赖当前项目)
|
||||
# 带产线参数:入口产线与会话项目跨产线时视为无项目(产线隔离,弹窗端点自身在点击时判断)
|
||||
_ws_url = entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
|
||||
|
||||
@ -13,21 +13,26 @@ if not uid:
|
||||
orgid = await get_userorgid()
|
||||
dbname = get_module_dbname('pipeline_core')
|
||||
|
||||
# mode=full(桌面侧边栏/管理场景):不过滤订阅,返回全部 published 产线;
|
||||
# 默认(手机端 ChipBar):客户只见已购未到期产线(2026-09-07 用户定夺)
|
||||
_mode = ((params_kw or {}).get('mode') or '').strip()
|
||||
|
||||
items = []
|
||||
try:
|
||||
# 平台侧角色(owner./reseller.)看全部 published 产线;客户只看已购未到期。
|
||||
# get_user_roles 由 rbac.init 注册到 ServerEnv,dspy run_ns 会带入;
|
||||
# 探测失败/不可用时默认按客户处理(只显示已购,安全侧,不放大可见面)。
|
||||
is_ops = False
|
||||
try:
|
||||
roles = await get_user_roles(uid)
|
||||
for r in (roles or []):
|
||||
rs = str(r)
|
||||
if rs.startswith('owner.') or rs.startswith('reseller.'):
|
||||
is_ops = True
|
||||
break
|
||||
except Exception:
|
||||
is_ops = False
|
||||
is_ops = _mode == 'full'
|
||||
if not is_ops:
|
||||
try:
|
||||
roles = await get_user_roles(uid)
|
||||
for r in (roles or []):
|
||||
rs = str(r)
|
||||
if rs.startswith('owner.') or rs.startswith('reseller.'):
|
||||
is_ops = True
|
||||
break
|
||||
except Exception:
|
||||
is_ops = False
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
if is_ops:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "height": "100%", "padding": "0"},
|
||||
"options": {"width": "100%", "height": "100%", "padding": "0", "css": "pl-gate-root"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "ChipBar",
|
||||
@ -11,7 +11,7 @@
|
||||
"dataurl": "/pipeline_core/api/my_pipelines.dspy",
|
||||
"valueField": "id",
|
||||
"textField": "name",
|
||||
"empty_text": "暂无已购产线 · 请到「我的 → 产品商城」购买"
|
||||
"empty_text": "暂无已购产线"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
@ -27,6 +27,13 @@
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.mobile_menus_wrap",
|
||||
"options": {"url": "/pipeline_core/api/agent_menus.dspy", "method": "GET"}
|
||||
},
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "select",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "var ov=bricks.getWidgetById('mobile_gate_overlay',bricks.app);if(!ov)return;var has=!!(event.params&&(event.params.id||event.params.pipeline_id));if(has){ov.hide();}else{ov.show();}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -47,6 +54,40 @@
|
||||
"model_cwidth": 14,
|
||||
"placeholder": "输入你的需求..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "mobile_gate_overlay",
|
||||
"options": {
|
||||
"css": "pl-gate",
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"bgcolor": "#0b1120",
|
||||
"opacity": 0.97,
|
||||
"zIndex": 500,
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"gap": "14px",
|
||||
"padding": "24px",
|
||||
"desktop_hidden": true
|
||||
},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Title3", "options": {"otext": "尚未购买任何产线", "i18n": true, "color": "#f1f5f9"}},
|
||||
{"widgettype": "Text", "options": {"otext": "购买产线后即可在此对话;通用助手无需购买。", "i18n": true, "cfontsize": 0.9, "color": "#94a3b8", "textAlign": "center", "width": "100%"}},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "去产品商城购买", "otext": "去产品商城购买", "i18n": true, "css": "primary", "bgcolor": "#2563eb", "color": "#ffffff", "padding": "12px 28px", "borderRadius": "999px"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "PopupWindow",
|
||||
"popup_options": {"title": "产品商城"},
|
||||
"options": {"url": "/product_management/storefront/index.ui"}}]
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "使用通用助手", "otext": "使用通用助手", "i18n": true, "css": "small", "bgcolor": "transparent", "color": "#94a3b8"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self",
|
||||
"script": "var tp=bricks.getWidgetById('main_content',bricks.app);if(!tp)return;tp.open_tab({name:'m_generic',label:'助手',url:'/pipeline_core/agent_generic',removable:false,refresh:false});if(tp.mobile_bar)tp.mobile_bar.set_active('m_generic');"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user