feat(core): 会话「📁 项目」统一入口——agent_project_popup.dspy(menu/new/switch/create四op);新建=输项目名→create_project能力函数(alloc目录+审计)→start激活+初始迭代→会话进入;切换=本产线+本人创建列表点选→归属校验→双写session+全局指针→会话进入;agent_menus硬编码项目按钮(所有专业产线通用);无项目提示升级为'选择或新建'带直达按钮(agent_menu_open/workspace_popup同口径);menu_open透传session_id/pipeline_id给内嵌页修多tab串项目;模型配置三件套升级会话级解析get_session_project_id
This commit is contained in:
parent
f5cf90eb17
commit
166e282d26
@ -32,29 +32,63 @@ if require_project:
|
||||
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})
|
||||
proj_pl = getattr(precs[0], 'pipeline_id', '') or '' if precs else ''
|
||||
_has_project = (not pipeline_id) or (proj_pl == pipeline_id)
|
||||
_pid = await get_session_project_id(sor, uid, session_id, pipeline_id) or ''
|
||||
_has_project = bool(_pid)
|
||||
except Exception:
|
||||
_has_project = True # 解析异常保守放行(不因校验故障误伤功能)
|
||||
if not _has_project:
|
||||
# 2026-09-11 用户要求:没当前项目 → 提示选择或新建项目(给直达按钮,不给死胡同)
|
||||
_proj_q = []
|
||||
if session_id:
|
||||
_proj_q.append("session_id=" + session_id)
|
||||
if pipeline_id:
|
||||
_proj_q.append("pipeline_id=" + pipeline_id)
|
||||
_proj_base = entire_url("/pipeline_core/api/agent_project_popup.dspy")
|
||||
|
||||
def _pbtn(label, css, op):
|
||||
_js = ("var r=await fetch(" + json.dumps(_proj_base + "?op=" + op +
|
||||
(("&" + "&".join(_proj_q)) if _proj_q else "")) + ");"
|
||||
"var d=await r.json();if(d){bricks.widgetBuild(d,bricks.app);}")
|
||||
return {"widgettype": "Button", "options": {"label": label, "css": css},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
|
||||
"target": "self", "script": _js}]}
|
||||
|
||||
return json.dumps({
|
||||
"widgettype": "PopupWindow",
|
||||
"options": {"title": "提示", "cwidth": 30, "cheight": 8, "auto_open": True},
|
||||
"id": "agent_menu_no_project_pw",
|
||||
"options": {"title": "提示", "cwidth": 38, "cheight": 10, "auto_open": True},
|
||||
"subwidgets": [{
|
||||
"widgettype": "Text",
|
||||
"options": {"text": "请先在会话中切换项目", "cfontsize": 1, "color": "#64748b", "padding": "20px"}
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "gap": "12px", "padding": "18px 22px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text",
|
||||
"options": {"text": "当前会话还没有项目,请先选择或新建项目",
|
||||
"cfontsize": 1, "color": "#64748b"}},
|
||||
{"widgettype": "HBox",
|
||||
"options": {"width": "100%", "gap": "10px"},
|
||||
"subwidgets": [_pbtn("✨ 新建项目", "primary", "new"),
|
||||
_pbtn("🔀 切换项目", "small", "switch")]}
|
||||
]
|
||||
}]
|
||||
}, ensure_ascii=False)
|
||||
|
||||
# 把 session_id/pipeline_id 透传给内嵌页面(多 tab 会话级项目隔离):
|
||||
# 内嵌 index.ui 的 data_params 会把请求参数转发给 get_*.dspy,注入的项目过滤
|
||||
# 代码据此按会话解析当前项目;不透传会退回全局指针(串 tab)。
|
||||
# ⚠️ 查询串拼在 entire_url() 之后(workspace_popup 同款写法),不把 query 喂给 entire_url。
|
||||
_inner_url = entire_url(url)
|
||||
_fwd = []
|
||||
if session_id:
|
||||
_fwd.append("session_id=" + session_id)
|
||||
if pipeline_id:
|
||||
_fwd.append("pipeline_id=" + pipeline_id)
|
||||
if _fwd:
|
||||
_inner_url += ("&" if "?" in _inner_url else "?") + "&".join(_fwd)
|
||||
|
||||
return json.dumps({
|
||||
"widgettype": "PopupWindow",
|
||||
"options": {"title": title, "width": width, "height": height, "auto_open": True},
|
||||
"subwidgets": [
|
||||
{"widgettype": "urlwidget", "options": {"url": entire_url(url)}}
|
||||
{"widgettype": "urlwidget", "options": {"url": _inner_url}}
|
||||
]
|
||||
}, ensure_ascii=False)
|
||||
|
||||
@ -80,6 +80,18 @@ if pipeline_id == '_generic':
|
||||
+ "?pipeline_id=_generic",
|
||||
})
|
||||
else:
|
||||
# 固定「📁 项目」入口(2026-09-11 用户要求,所有专业产线通用):
|
||||
# 点击弹出「新建项目 / 切换项目」;新建输项目名后关闭、会话进入新项目;
|
||||
# 切换列出本产线全部项目、选择后关闭、会话进入所选项目。
|
||||
_proj_q = []
|
||||
if session_id:
|
||||
_proj_q.append("session_id=" + session_id)
|
||||
if pipeline_id:
|
||||
_proj_q.append("pipeline_id=" + pipeline_id)
|
||||
_proj_url = (entire_url("/pipeline_core/api/agent_project_popup.dspy") + "?op=menu"
|
||||
+ (("&" + "&".join(_proj_q)) if _proj_q else ""))
|
||||
actions.append({"label": "📁 项目", "url": _proj_url})
|
||||
|
||||
# 固定「工作空间」入口(通用能力,所有 agent 都有;依赖当前项目)
|
||||
# 带产线参数:入口产线与会话项目跨产线时视为无项目(产线隔离,弹窗端点自身在点击时判断)
|
||||
_ws_url = entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
|
||||
|
||||
333
wwwroot/api/agent_project_popup.dspy
Normal file
333
wwwroot/api/agent_project_popup.dspy
Normal file
@ -0,0 +1,333 @@
|
||||
# agent_project_popup.dspy - 会话「📁 项目」统一入口弹窗(2026-09-11 用户要求)
|
||||
# GET op=menu → 小弹窗:「新建项目」「切换项目」两个按钮
|
||||
# GET op=new → 新建项目弹窗:输入项目名 → 创建成功后关闭,会话进入新项目
|
||||
# GET op=switch → 项目切换弹窗:列出本产线全部项目 → 选择后关闭,会话进入该项目
|
||||
# POST op=create → 创建项目(走 project_capability.create_project,带 alloc_project_dir+审计,
|
||||
# 禁止裸 INSERT)+ start_project + 初始迭代 + 写会话项目指针
|
||||
# POST op=switch → 校验项目归属(产线/机构)后写会话项目指针
|
||||
# 参数:session_id(会话级隔离,多 tab 各自项目) pipeline_id(产线隔离)
|
||||
# 会话「进入项目」= 写 pipeline_session_settings + pipeline_agent_settings(与
|
||||
# AgentExecutor._persist_project 同口径);gateway/菜单/工作空间每次请求实时解析,写完即生效。
|
||||
|
||||
import json
|
||||
import re as _re
|
||||
|
||||
_op = (params_kw or {}).get('op', 'menu') or 'menu'
|
||||
session_id = (params_kw or {}).get('session_id', '') or ''
|
||||
pipeline_id = (params_kw or {}).get('pipeline_id', '') or ''
|
||||
|
||||
uid = await get_user()
|
||||
dbname = get_module_dbname('pipeline_core')
|
||||
|
||||
_STATUS_TXT = {
|
||||
'draft': ('草稿', '#94a3b8'),
|
||||
'active': ('进行中', '#16a34a'),
|
||||
'in_progress': ('进行中', '#16a34a'),
|
||||
'paused': ('已暂停', '#d97706'),
|
||||
'completed': ('已完成', '#2563eb'),
|
||||
'archived': ('已归档', '#64748b'),
|
||||
}
|
||||
|
||||
|
||||
# ══════════════════ POST:创建 / 切换 ══════════════════
|
||||
|
||||
if _op in ('create', 'switch'):
|
||||
if not uid:
|
||||
return json.dumps({"success": False, "error": "请先登录"}, ensure_ascii=False)
|
||||
if not pipeline_id or pipeline_id == '_generic':
|
||||
return json.dumps({"success": False, "error": "当前会话不支持项目管理"}, ensure_ascii=False)
|
||||
userorg = (await get_userorgid()) or '0'
|
||||
|
||||
from appPublic.uniqueID import getID as _getID
|
||||
|
||||
async def _enter_project(sor, pid):
|
||||
"""会话进入项目:会话级 + 全局兜底双写,迭代上下文清空(_persist_project 同口径)。"""
|
||||
await sor.sqlExe(
|
||||
"UPDATE pipeline_agent_settings SET current_project_id=${pid}$, current_iteration_id='' "
|
||||
"WHERE user_id=${uid}$", {"pid": pid, "uid": uid})
|
||||
_ex = await sor.sqlExe(
|
||||
"SELECT 1 FROM pipeline_agent_settings WHERE user_id=${uid}$", {"uid": uid})
|
||||
if not _ex:
|
||||
await sor.C('pipeline_agent_settings', {
|
||||
'id': _getID(), 'user_id': uid,
|
||||
'current_project_id': pid, 'current_iteration_id': ''})
|
||||
if session_id:
|
||||
await sor.sqlExe(
|
||||
"UPDATE pipeline_session_settings SET current_project_id=${pid}$, current_iteration_id='' "
|
||||
"WHERE user_id=${uid}$ AND session_id=${sid}$",
|
||||
{"pid": pid, "uid": uid, "sid": session_id})
|
||||
_sex = await sor.sqlExe(
|
||||
"SELECT 1 FROM pipeline_session_settings WHERE user_id=${uid}$ AND session_id=${sid}$",
|
||||
{"uid": uid, "sid": session_id})
|
||||
if not _sex:
|
||||
await sor.C('pipeline_session_settings', {
|
||||
'id': _getID(), 'user_id': uid, 'session_id': session_id,
|
||||
'current_project_id': pid, 'current_iteration_id': ''})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
|
||||
if _op == 'create':
|
||||
name = ((params_kw or {}).get('name', '') or '').strip()
|
||||
if not name:
|
||||
return json.dumps({"success": False, "error": "请输入项目名"}, ensure_ascii=False)
|
||||
if len(name) > 64:
|
||||
return json.dumps({"success": False, "error": "项目名过长(最多 64 字符)"}, ensure_ascii=False)
|
||||
# 项目名会作为工作空间目录名(alloc_project_dir):拒绝路径穿越与非法字符
|
||||
if ('/' in name) or ('\\' in name) or ('..' in name) or name.startswith('.') or \
|
||||
(not _re.match(r'^[\w\u4e00-\u9fff .\-()+()]+$', name)):
|
||||
return json.dumps({"success": False,
|
||||
"error": "项目名含非法字符(仅允许中文、字母、数字、空格 . - _ ( ) +)"},
|
||||
ensure_ascii=False)
|
||||
|
||||
# 同产线重名预检:提示走切换,不静默建重名项目
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
_dup = await sor.sqlExe(
|
||||
"SELECT id FROM sd_projects WHERE name=${n}$ AND pipeline_id=${pl}$ LIMIT 1",
|
||||
{"n": name, "pl": pipeline_id})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
if _dup:
|
||||
return json.dumps({"success": False,
|
||||
"error": "该产线已存在同名项目,请用「切换项目」直接进入"},
|
||||
ensure_ascii=False)
|
||||
|
||||
# 铁律:创建项目走能力函数(alloc_project_dir 定死目录 + record_audit 审计)
|
||||
from pipeline_service.project_capability import create_project, start_project
|
||||
ok, pid = await create_project(
|
||||
name, project_type='web_app', description='',
|
||||
org_id=userorg, created_by=uid, who=uid,
|
||||
pipeline_id=pipeline_id)
|
||||
if not ok:
|
||||
return json.dumps({"success": False, "error": str(pid)}, ensure_ascii=False)
|
||||
# draft → active(CAS+审计),与会话 agent 创建的项目状态一致
|
||||
await start_project(pid, who=uid)
|
||||
|
||||
# 初始迭代(与 agent_loop_v2._t_create_project 行为一致;
|
||||
# sd_iterations 无 goal 列,范围写 scope;iteration_type 用 'default')
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
await sor.C('sd_iterations', {
|
||||
'id': _getID(), 'project_id': pid,
|
||||
'iteration_name': name + '-初始迭代',
|
||||
'iteration_type': 'default', 'status': 'in_progress',
|
||||
'priority': 1, 'seq_no': 1})
|
||||
await _enter_project(sor, pid)
|
||||
return json.dumps({"success": True, "project_id": pid, "name": name}, ensure_ascii=False)
|
||||
|
||||
else: # switch
|
||||
pid = (params_kw or {}).get('project_id', '') or ''
|
||||
if not pid:
|
||||
return json.dumps({"success": False, "error": "缺少 project_id"}, ensure_ascii=False)
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, name, org_id, pipeline_id, created_by FROM sd_projects "
|
||||
"WHERE id=${p}$ LIMIT 1", {"p": pid})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
if not recs:
|
||||
return json.dumps({"success": False, "error": "项目不存在"}, ensure_ascii=False)
|
||||
r0 = recs[0]
|
||||
p_pl = getattr(r0, 'pipeline_id', '') or ''
|
||||
p_org = getattr(r0, 'org_id', '') or ''
|
||||
p_by = getattr(r0, 'created_by', '') or ''
|
||||
# 产线隔离:跨产线项目不得切入本会话(与 get_session_project_id 语义一致)
|
||||
if p_pl and p_pl != pipeline_id:
|
||||
return json.dumps({"success": False,
|
||||
"error": "该项目属于其他产线,不能切入当前会话"},
|
||||
ensure_ascii=False)
|
||||
# 可见范围(2026-09-11 用户拍板):本产线 + 本人创建
|
||||
if p_by != uid:
|
||||
return json.dumps({"success": False,
|
||||
"error": "无权切换到该项目(仅限本人创建的项目)"},
|
||||
ensure_ascii=False)
|
||||
await _enter_project(sor, pid)
|
||||
return json.dumps({"success": True, "project_id": pid,
|
||||
"name": getattr(r0, 'name', '') or pid}, ensure_ascii=False)
|
||||
|
||||
|
||||
# ══════════════════ GET:弹窗 widget ══════════════════
|
||||
|
||||
if not uid:
|
||||
return json.dumps({
|
||||
"widgettype": "PopupWindow",
|
||||
"id": "agent_project_menu_pw",
|
||||
"options": {"title": "📁 项目", "cwidth": 30, "cheight": 8, "auto_open": True},
|
||||
"subwidgets": [{"widgettype": "Text",
|
||||
"options": {"text": "请先登录", "cfontsize": 1, "color": "#64748b",
|
||||
"padding": "20px"}}]
|
||||
}, ensure_ascii=False)
|
||||
|
||||
_self_url = entire_url("/pipeline_core/api/agent_project_popup.dspy")
|
||||
_qb = []
|
||||
if session_id:
|
||||
_qb.append("session_id=" + session_id)
|
||||
if pipeline_id:
|
||||
_qb.append("pipeline_id=" + pipeline_id)
|
||||
_qs_base = ("&" + "&".join(_qb)) if _qb else ""
|
||||
|
||||
|
||||
def _destroy_js(pw_id):
|
||||
return ("var _pw=bricks.getWidgetById(" + json.dumps(pw_id) + ",bricks.app);"
|
||||
"if(_pw){_pw.destroy();}")
|
||||
|
||||
|
||||
def _open_get_js(op):
|
||||
"""fetch 本端点 GET op=xxx → widgetBuild 弹出(PopupWindow 挂 body,从 bricks.app 搜)。"""
|
||||
return ("var r=await fetch(" + json.dumps(_self_url + "?op=" + op + _qs_base) + ");"
|
||||
"var d=await r.json();if(d){bricks.widgetBuild(d,bricks.app);}")
|
||||
|
||||
|
||||
def _post_js(fields):
|
||||
js = "var body=new URLSearchParams();"
|
||||
for k, v in fields:
|
||||
js += "body.append(" + json.dumps(k) + "," + v + ");"
|
||||
js += ("var rp=await fetch(" + json.dumps(_self_url) + ",{method:'POST',"
|
||||
"headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body});"
|
||||
"var d=await rp.json();")
|
||||
return js
|
||||
|
||||
|
||||
def _btn(label, css, script):
|
||||
return {"widgettype": "Button", "options": {"label": label, "css": css},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
|
||||
"target": "self", "script": script}]}
|
||||
|
||||
|
||||
# ── op=menu:新建 / 切换 两按钮 ──
|
||||
if _op == 'menu':
|
||||
return json.dumps({
|
||||
"widgettype": "PopupWindow",
|
||||
"id": "agent_project_menu_pw",
|
||||
"options": {"title": "📁 项目", "cwidth": 36, "cheight": 11, "auto_open": True},
|
||||
"subwidgets": [{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "gap": "12px", "padding": "18px 22px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text",
|
||||
"options": {"text": "新建一个项目,或把当前会话切换到已有项目",
|
||||
"cfontsize": 0.85, "color": "#64748b"}},
|
||||
{"widgettype": "HBox",
|
||||
"options": {"width": "100%", "gap": "10px"},
|
||||
"subwidgets": [
|
||||
_btn("✨ 新建项目", "primary",
|
||||
_destroy_js("agent_project_menu_pw") + _open_get_js("new")),
|
||||
_btn("🔀 切换项目", "small",
|
||||
_destroy_js("agent_project_menu_pw") + _open_get_js("switch")),
|
||||
]}
|
||||
]
|
||||
}]
|
||||
}, ensure_ascii=False)
|
||||
|
||||
# ── op=new:输入项目名 ──
|
||||
if _op == 'new':
|
||||
_create_js = (
|
||||
"var cw=bricks.getWidgetById('proj_new_name',bricks.app);var cv='';"
|
||||
"if(cw){cv=(typeof cw.resultValue==='function')?cw.resultValue():"
|
||||
"((cw.dom_element&&cw.dom_element.value)||'');}"
|
||||
"cv=(cv===null||cv===undefined)?'':String(cv).trim();"
|
||||
"if(!cv){var mn=new bricks.Message({title:'提示',message:'请输入项目名'});mn.open();return;}"
|
||||
+ _post_js([('op', "'create'"), ('name', 'cv'),
|
||||
('session_id', json.dumps(session_id)),
|
||||
('pipeline_id', json.dumps(pipeline_id))])
|
||||
+ "if(d.success){"
|
||||
+ _destroy_js("agent_project_new_pw")
|
||||
+ "var mo=new bricks.Message({title:'已创建',message:'项目「'+d.name+'」已创建,会话已进入该项目'});mo.open();"
|
||||
"}else{var mf=new bricks.Message({title:'创建失败',message:d.error||'未知错误'});mf.open();}")
|
||||
return json.dumps({
|
||||
"widgettype": "PopupWindow",
|
||||
"id": "agent_project_new_pw",
|
||||
"options": {"title": "✨ 新建项目", "cwidth": 40, "cheight": 13, "auto_open": True},
|
||||
"subwidgets": [{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "gap": "10px", "padding": "16px 20px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text",
|
||||
"options": {"text": "项目名(将同时作为项目工作空间目录名)",
|
||||
"cfontsize": 0.8, "color": "#64748b"}},
|
||||
{"widgettype": "UiStr", "id": "proj_new_name",
|
||||
"options": {"name": "proj_new_name", "placeholder": "输入项目名",
|
||||
"width": "100%", "cfontsize": 0.9}},
|
||||
{"widgettype": "HBox",
|
||||
"options": {"width": "100%", "gap": "10px", "halign": "right"},
|
||||
"subwidgets": [
|
||||
_btn("创建", "primary", _create_js),
|
||||
_btn("取消", "small", _destroy_js("agent_project_new_pw")),
|
||||
]}
|
||||
]
|
||||
}]
|
||||
}, ensure_ascii=False)
|
||||
|
||||
# ── op=switch:本产线项目列表 ──
|
||||
projects = []
|
||||
cur_pid = ''
|
||||
try:
|
||||
from pipeline_service.workspace import get_session_project_id
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
cur_pid = await get_session_project_id(sor, uid, session_id, pipeline_id) or ''
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, name, status, created_at FROM sd_projects "
|
||||
"WHERE pipeline_id=${pl}$ AND created_by=${uid}$ "
|
||||
"ORDER BY created_at DESC LIMIT 100",
|
||||
{"pl": pipeline_id, "uid": uid})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
projects = [dict(r) for r in (recs or [])]
|
||||
except Exception as e:
|
||||
debug('agent_project_popup switch list error: ' + str(e))
|
||||
projects = []
|
||||
|
||||
rows = []
|
||||
if not projects:
|
||||
rows.append({"widgettype": "Text",
|
||||
"options": {"text": "本产线暂无项目,请先「新建项目」",
|
||||
"cfontsize": 0.9, "color": "#64748b", "padding": "16px"}})
|
||||
for p in projects:
|
||||
_pid_v = str(p.get('id') or '')
|
||||
_pname = str(p.get('name') or _pid_v)
|
||||
_st = str(p.get('status') or '')
|
||||
_st_txt, _st_color = _STATUS_TXT.get(_st, (_st or '—', '#64748b'))
|
||||
_is_cur = (_pid_v == cur_pid)
|
||||
_switch_js = (
|
||||
_post_js([('op', "'switch'"), ('project_id', json.dumps(_pid_v)),
|
||||
('session_id', json.dumps(session_id)),
|
||||
('pipeline_id', json.dumps(pipeline_id))])
|
||||
+ "if(d.success){"
|
||||
+ _destroy_js("agent_project_switch_pw")
|
||||
+ "var mo=new bricks.Message({title:'已切换',message:'会话已进入项目「'+d.name+'」'});mo.open();"
|
||||
"}else{var mf=new bricks.Message({title:'切换失败',message:d.error||'未知错误'});mf.open();}")
|
||||
rows.append({
|
||||
"widgettype": "HBox",
|
||||
"options": {"width": "100%", "gap": "10px", "alignItems": "center",
|
||||
"padding": "10px 12px", "borderRadius": "6px",
|
||||
"bgcolor": "#f0fdf4" if _is_cur else "#ffffff",
|
||||
"border": "1px solid #e2e8f0",
|
||||
"style": {"cursor": "pointer"}},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text",
|
||||
"options": {"text": ("✓ " if _is_cur else "") + _pname,
|
||||
"cfontsize": 0.95, "color": "#0f172a", "css": "filler",
|
||||
"halign": "left"}},
|
||||
{"widgettype": "Text",
|
||||
"options": {"text": _st_txt, "cfontsize": 0.75, "color": "#ffffff",
|
||||
"bgcolor": _st_color, "padding": "2px 8px",
|
||||
"borderRadius": "8px", "whiteSpace": "nowrap"}},
|
||||
{"widgettype": "Text",
|
||||
"options": {"text": str(p.get('created_at') or '')[:16],
|
||||
"cfontsize": 0.75, "color": "#94a3b8"}},
|
||||
],
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
|
||||
"target": "self", "script": _switch_js}],
|
||||
})
|
||||
|
||||
return json.dumps({
|
||||
"widgettype": "PopupWindow",
|
||||
"id": "agent_project_switch_pw",
|
||||
"options": {"title": "🔀 切换项目", "width": "60%", "height": "70%", "auto_open": True},
|
||||
"subwidgets": [{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "filler", "width": "100%", "height": "100%", "gap": "0px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text",
|
||||
"options": {"text": "点击项目行即切换(仅显示本产线、本人创建的项目)",
|
||||
"cfontsize": 0.8, "color": "#94a3b8", "padding": "10px 14px 4px 14px"}},
|
||||
{"widgettype": "VScrollPanel",
|
||||
"options": {"css": "filler", "width": "100%", "padding": "8px 14px", "gap": "6px"},
|
||||
"subwidgets": rows}
|
||||
]
|
||||
}]
|
||||
}, ensure_ascii=False)
|
||||
@ -8,17 +8,22 @@ if not id or len(id) > 32:
|
||||
id = uuid()
|
||||
ns['id'] = id
|
||||
|
||||
# 会话当前项目强制(auto):project_id 用当前项目,忽略前端传值
|
||||
# 会话当前项目强制过滤(2026-09-11 升级 v2):会话级解析(session→全局兜底+产线隔离+悬空自愈),无项目报错
|
||||
_uid = await get_user()
|
||||
if not _uid:
|
||||
_uid = 'user-01'
|
||||
_dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(_dbname) as _sor:
|
||||
_prec = await _sor.sqlExe("SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", {"u": _uid})
|
||||
await _sor.sqlExe("COMMIT", {})
|
||||
_cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else ''
|
||||
_sid = (params_kw or {}).get('session_id', '') or ''
|
||||
_pl = (params_kw or {}).get('pipeline_id', '') or 'sdlc_general'
|
||||
_cur_pid = ''
|
||||
try:
|
||||
from pipeline_service.workspace import get_session_project_id
|
||||
_dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(_dbname) as _sor:
|
||||
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
|
||||
except Exception:
|
||||
_cur_pid = ''
|
||||
if not _cur_pid:
|
||||
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}}
|
||||
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
|
||||
ns['project_id'] = _cur_pid
|
||||
|
||||
if not ns.get('role'):
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
|
||||
ns = params_kw.copy()
|
||||
|
||||
# 会话当前项目强制过滤(auto):读 current_project_id,无项目报错
|
||||
# 会话当前项目强制过滤(2026-09-11 升级 v2):会话级解析(session→全局兜底+产线隔离+悬空自愈),无项目报错
|
||||
_uid = await get_user()
|
||||
if not _uid:
|
||||
_uid = 'user-01'
|
||||
_sid = (params_kw or {}).get('session_id', '') or ''
|
||||
_pl = (params_kw or {}).get('pipeline_id', '') or 'sdlc_general'
|
||||
_cur_pid = ''
|
||||
_dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(_dbname) as _sor:
|
||||
_prec = await _sor.sqlExe(
|
||||
"SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$",
|
||||
{"u": _uid})
|
||||
await _sor.sqlExe("COMMIT", {})
|
||||
_cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else ''
|
||||
try:
|
||||
from pipeline_service.workspace import get_session_project_id
|
||||
_dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(_dbname) as _sor:
|
||||
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
|
||||
except Exception:
|
||||
_cur_pid = ''
|
||||
if not _cur_pid:
|
||||
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}}
|
||||
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
|
||||
ns['_cur_pid'] = _cur_pid
|
||||
|
||||
userorgid = await get_userorgid()
|
||||
|
||||
@ -7,17 +7,22 @@ id = params_kw.id
|
||||
if not id:
|
||||
return {"widgettype":"Error","options":{"title":"Error","timeout":3,"cwidth":16,"cheight":9,"message":"需要 id"}}
|
||||
|
||||
# 会话当前项目强制(auto):project_id 用当前项目,忽略前端传值
|
||||
# 会话当前项目强制过滤(2026-09-11 升级 v2):会话级解析(session→全局兜底+产线隔离+悬空自愈),无项目报错
|
||||
_uid = await get_user()
|
||||
if not _uid:
|
||||
_uid = 'user-01'
|
||||
_dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(_dbname) as _sor:
|
||||
_prec = await _sor.sqlExe("SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", {"u": _uid})
|
||||
await _sor.sqlExe("COMMIT", {})
|
||||
_cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else ''
|
||||
_sid = (params_kw or {}).get('session_id', '') or ''
|
||||
_pl = (params_kw or {}).get('pipeline_id', '') or 'sdlc_general'
|
||||
_cur_pid = ''
|
||||
try:
|
||||
from pipeline_service.workspace import get_session_project_id
|
||||
_dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(_dbname) as _sor:
|
||||
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
|
||||
except Exception:
|
||||
_cur_pid = ''
|
||||
if not _cur_pid:
|
||||
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}}
|
||||
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
|
||||
ns['project_id'] = _cur_pid
|
||||
|
||||
userorgid = await get_userorgid()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user