feat(bidding): 多session会话入口 + 从上传招标文件开始(砍邮件采集/审批)

- 新增 /pipeline-bidding/agent/index.ui 多 session 会话界面(复用通用 agent)
- bid_ability 砍 fetch_tenders/import_tender/list_tenders/tender_detail/summarize/set_fields/submit/approve/reject/archive/reopen 等邮件采集+审批工具
- create_bid_project 改为直接创建项目+发上传招标文件任务(不依赖 tender)
- 删除招标信息员角色,菜单改招标文件/评分项/章节/资质/知识库/标书
- bid_flow 去掉审批门禁,无招标文件则自动发上传任务作为产线起点
This commit is contained in:
ymq 2026-08-27 13:12:58 +08:00
parent e134ecde4a
commit 7ceedefeb7
6 changed files with 117 additions and 225 deletions

View File

@ -22,39 +22,9 @@ PIPELINE_ID = "bidding_general"
# ══════════════════ 工具定义(主 agent / 驾驶舱)══════════════════
BID_TOOLS = [
# ── 招标情报 ──
ToolDefinition(name="fetch_tenders", description="从配置的招标邮箱采集招标信息入库(按关键词过滤+去重)",
parameters={"days": "回溯天数(可选,默认按邮箱配置)", "account_id": "邮箱配置ID(可选,默认全部启用)"},
category="tender"),
ToolDefinition(name="import_tender", description="手工导入一条招标信息(邮箱不可用或线下拿到公告时用)",
parameters={"title": "项目名称", "content": "公告正文", "purchaser": "招标人(可选)",
"tender_no": "招标编号(可选)"}, category="tender"),
ToolDefinition(name="list_tenders", description="列出招标信息可按状态筛选new/summarized/pending_approval/approved/rejected/archived",
parameters={"status": "状态(可选)", "limit": "条数(可选,默认50)"}, category="tender"),
ToolDefinition(name="tender_detail", description="查看一条招标信息详情(含公告正文)",
parameters={"tender_id": "招标信息ID"}, category="tender"),
ToolDefinition(name="summarize_tender", description="对招标信息做内容摘要与要素抽取(投标开始时间/项目周期/招标金额/保证金/资质要求)",
parameters={"tender_id": "招标信息ID"}, category="tender"),
ToolDefinition(name="set_tender_fields", description="修正招标信息要素(摘要抽错时人工纠正)",
parameters={"tender_id": "招标信息ID", "title": "项目名称(可选)", "purchaser": "招标人(可选)",
"bid_start_at": "投标开始时间(可选)", "bid_deadline": "投标截止(可选)",
"project_period": "项目周期(可选)", "budget_amount": "招标金额(可选)"},
category="tender"),
ToolDefinition(name="submit_tender_approval", description="提交招标信息审批(生成人类审批任务,等待通过/不通过)",
parameters={"tender_id": "招标信息ID", "assignee_role": "审批角色(可选,默认owner.superuser)",
"note": "提交说明(可选)"}, category="tender"),
ToolDefinition(name="approve_tender", description="审批通过招标信息 → 自动立项并发布「上传招标文件」任务",
parameters={"tender_id": "招标信息ID", "comment": "审批意见(可选)"}, category="tender"),
ToolDefinition(name="reject_tender", description="审批不通过招标信息 → 该招标信息自动存档作废",
parameters={"tender_id": "招标信息ID", "comment": "不通过原因"}, category="tender"),
ToolDefinition(name="archive_tender", description="存档作废一条招标信息(不再跟进)",
parameters={"tender_id": "招标信息ID", "reason": "原因"}, category="tender"),
ToolDefinition(name="reopen_tender", description="重新评估已作废/已驳回的招标信息",
parameters={"tender_id": "招标信息ID", "reason": "原因(可选)"}, category="tender"),
# ── 立项 / 人员 / 招标文件 ──
ToolDefinition(name="create_bid_project", description="由已审批通过的招标信息立项(创建投标项目)",
parameters={"tender_id": "招标信息ID", "name": "项目名称(可选)"}, category="project"),
# ── 立项 / 人员 / 招标文件(产线起点:从上传招标文件开始,无邮件采集)──
ToolDefinition(name="create_bid_project", description="创建投标项目(直接立项,不依赖招标信息)。用户说「创建/新建投标项目」时调用",
parameters={"name": "项目名称", "description": "项目描述(可选)"}, category="project"),
ToolDefinition(name="assign_members", description="设定投标项目参与人员,如「张三:负责人,李四:writer」",
parameters={"members": "人员清单(姓名:角色 逗号分隔或JSON数组)"}, category="project"),
ToolDefinition(name="list_members", description="查看投标项目参与人员", parameters={}, category="project"),
@ -103,38 +73,33 @@ BID_TOOLS = [
# ══════════════════ 产线 prompt 片段 ══════════════════
BID_PROMPT = """你是「投标产线」的驾驶舱 agent负责从招标信息到标书交付的全流程管理:
招标信息采集 内容摘要 审批 立项与人员 上传招标文件 解析评分项/得分规则/资质/投标文件要求
BID_PROMPT = """你是「投标产线」的驾驶舱 agent负责从上传招标文件到标书交付的全流程管理:
创建投标项目 上传招标文件 解析评分项/得分规则/资质/投标文件要求/章节骨架
资质文档准备 分章节编写 章节评审 合成标书 整书评分 交付确认
## 流转由机制驱动(重要)
角色任务由系统状态对账器自动派发 15 秒一轮**不需要**手动为每个章节创建任务
- 章节评审不达标 章节自动退回重写
- 整书评分不达标 改进意见自动落到问题章节并退回重写 重新合成 重新评分
你的职责是采集与摘要推动审批立项与配人处理卡点与提问回答评审/评分中的人类决策
你的职责是立项与配人推动上传招标文件处理卡点与提问回答评审/评分中的人类决策
只有诊断到机制该派任务却没派才用 advance_bid 手动触发一次对账或用 diagnose_bid 查根因
## 阻塞门禁(不要绕过)
以下人类任务 pending 系统不会给该项目派发任何 agent 任务
- 招标信息审批未决tender_approval
- 等待上传招标文件tender_file_upload上传后自动完成
- 等待人类提供资质文件human_docs_request
遇到项目"不动了" diagnose_bid 看是不是卡在这个门禁上缺什么就催人补什么不要硬推
遇到项目"不动了" diagnose_bid 看是不是卡在这个门禁上缺什么就催人补什么不要硬推
## 典型场景
- 用户说看看有什么新招标 / 收一下招标邮件 fetch_tenders list_tenders 看结果
- 用户说这条招标怎么样 / 帮我分析下 tender_detail + summarize_tender把投标开始时间
项目周期招标金额保证金资质门槛风险点讲清楚再问是否提交审批
- 用户说提交审批 submit_tender_approval用户直接拍板通过/不通过 approve_tender / reject_tender
**审批不通过就是终态**招标信息自动存档作废不要再立项
- 审批通过后 自动立项并发上传招标文件任务接着 assign_members 设定参与人员
- 用户说创建一个投标项目 XXX create_bid_project(name=XXX)随后提示上传招标文件
- 用户问招标文件上传了吗 / 解析出来了吗 list_tender_files 看上传与抽取状态
- 用户问进展 diagnose_bid含章节分布在办任务最近评分不要只报"正在进行"
- 用户问能得多少分 / 差在哪 bid_score_report + list_reviews讲清失分项与改进意见落到了哪个章节
- 用户要补资质/案例 add_kb_doc 录入公司投标知识库知识库缺的用 request_human_docs 让人工提供
## 硬规则
- 招标金额投标时间资质要求这些要素只能来自招标公告/招标文件原文或人工确认**禁止编造**
抽取不到就如实说"公告未载明"必要时 ask_user
- 招标金额投标时间资质要求这些要素只能来自招标文件原文或人工确认**禁止编造**
抽取不到就如实说"文件未载明"必要时 ask_user
- 标书内容的案例与业绩必须来自公司投标知识库的真实合同bid_kb_docs doc_type=case
禁止虚构客户名合同金额历史成果数字
- 章节没全部评审通过不允许合成标书评分没过门限不允许当作可交付"""
@ -142,14 +107,6 @@ BID_PROMPT = """你是「投标产线」的驾驶舱 agent负责从招标信
# ══════════════════ 角色集 ══════════════════
BID_ROLES = [
RoleSpec(
name="agent.tender_scout",
description="招标信息员",
aliases=["tender_scout", "scout", "tender", "tender_intake"],
system_prompt="你是招标信息员。先 load_skill 加载 role 技能,按其中的职责与规范执行任务。",
next_role="",
task_title="招标信息采集与摘要",
),
RoleSpec(
name="agent.tender_analyst",
description="招标文件分析师",
@ -234,98 +191,9 @@ def _fmt_rows(rows, empty="(空)"):
return "\n".join(json.dumps(r, ensure_ascii=False, default=str) for r in rows[:80])
async def _h_fetch_tenders(sor, p, ctx):
from .bid_tender_capability import fetch_tenders
ok, msg = await fetch_tenders(org_id=ctx.get("org_id", "") or "",
account_id=p.get("account_id", "") or "",
days=p.get("days") or None,
who="agent.main_agent")
return _fmt(ok, msg)
async def _h_import_tender(sor, p, ctx):
from .bid_tender_capability import import_tender
ok, msg = await import_tender(p.get("title", ""), p.get("content", ""),
purchaser=p.get("purchaser", ""),
tender_no=p.get("tender_no", ""),
org_id=ctx.get("org_id", "") or "0",
who="agent.main_agent",
created_by=ctx.get("user_id", "") or "")
return _fmt(ok, "已导入招标信息 id=%s" % msg if ok else msg)
async def _h_list_tenders(sor, p, ctx):
from .bid_tender_capability import list_tenders
return _fmt_rows(await list_tenders(status=p.get("status", "") or "",
org_id=ctx.get("org_id", "") or "",
limit=p.get("limit") or 50),
"暂无招标信息(可用 fetch_tenders 采集或 import_tender 手工导入)")
async def _h_tender_detail(sor, p, ctx):
from .bid_tender_capability import tender_detail
return await tender_detail(p.get("tender_id", ""))
async def _h_summarize_tender(sor, p, ctx):
from .bid_tender_capability import summarize_tender
ok, msg = await summarize_tender(p.get("tender_id", ""),
model_name=ctx.get("model_name", "") or "",
who="agent.main_agent")
return _fmt(ok, msg)
async def _h_set_tender_fields(sor, p, ctx):
from .bid_tender_capability import set_tender_fields
ok, msg = await set_tender_fields(
p.get("tender_id", ""), title=p.get("title", ""), purchaser=p.get("purchaser", ""),
tender_no=p.get("tender_no", ""), bid_start_at=p.get("bid_start_at", ""),
bid_deadline=p.get("bid_deadline", ""), project_period=p.get("project_period", ""),
budget_amount=p.get("budget_amount", ""), deposit_amount=p.get("deposit_amount", ""),
summary=p.get("summary", ""), qualification_req=p.get("qualification_req", ""),
who="agent.main_agent")
return _fmt(ok, msg)
async def _h_submit_tender_approval(sor, p, ctx):
from .bid_tender_capability import submit_tender_approval
ok, msg = await submit_tender_approval(
p.get("tender_id", ""), assignee_role=p.get("assignee_role", "") or "owner.superuser",
note=p.get("note", ""), who="agent.main_agent")
return _fmt(ok, msg)
async def _h_approve_tender(sor, p, ctx):
from .bid_tender_capability import approve_tender
ok, msg = await approve_tender(p.get("tender_id", ""), comment=p.get("comment", ""),
who=ctx.get("user_id", "") or "owner.superuser")
return _fmt(ok, msg)
async def _h_reject_tender(sor, p, ctx):
from .bid_tender_capability import reject_tender
ok, msg = await reject_tender(p.get("tender_id", ""), comment=p.get("comment", ""),
who=ctx.get("user_id", "") or "owner.superuser")
return _fmt(ok, msg)
async def _h_archive_tender(sor, p, ctx):
from .bid_tender_capability import archive_tender
ok, msg = await archive_tender(p.get("tender_id", ""), reason=p.get("reason", ""),
who="agent.main_agent")
return _fmt(ok, msg)
async def _h_reopen_tender(sor, p, ctx):
from .bid_tender_capability import reopen_tender
ok, msg = await reopen_tender(p.get("tender_id", ""), reason=p.get("reason", ""),
who="agent.main_agent")
return _fmt(ok, msg)
async def _h_create_bid_project(sor, p, ctx):
from .bid_tender_capability import create_bid_project
ok, msg = await create_bid_project(p.get("tender_id", ""), name=p.get("name", ""),
ok, msg = await create_bid_project(p.get("name", ""), description=p.get("description", ""),
org_id=ctx.get("org_id", "") or "",
created_by=ctx.get("user_id", "") or "",
who="agent.main_agent")
@ -488,17 +356,6 @@ async def _h_advance_bid(sor, p, ctx):
BID_HANDLERS = {
"fetch_tenders": _h_fetch_tenders,
"import_tender": _h_import_tender,
"list_tenders": _h_list_tenders,
"tender_detail": _h_tender_detail,
"summarize_tender": _h_summarize_tender,
"set_tender_fields": _h_set_tender_fields,
"submit_tender_approval": _h_submit_tender_approval,
"approve_tender": _h_approve_tender,
"reject_tender": _h_reject_tender,
"archive_tender": _h_archive_tender,
"reopen_tender": _h_reopen_tender,
"create_bid_project": _h_create_bid_project,
"assign_members": _h_assign_members,
"list_members": _h_list_members,
@ -546,8 +403,8 @@ def register_bid_ability():
handlers=handlers,
roles=BID_ROLES,
menus=[
{"label": "📨 招标信息", "icon": "", "url": "/pipeline-bidding/bid_tenders/index.ui",
"type": "popup", "width": "88%", "height": "82%"},
{"label": "📄 招标文件", "icon": "", "url": "/pipeline-bidding/bid_tender_files/index.ui",
"type": "popup", "width": "88%", "height": "82%", "require_project": True},
{"label": "📑 评分项", "icon": "", "url": "/pipeline-bidding/bid_scoring_items/index.ui",
"type": "popup", "width": "88%", "height": "82%", "require_project": True},
{"label": "📝 标书章节", "icon": "", "url": "/pipeline-bidding/bid_chapters/index.ui",
@ -558,8 +415,6 @@ def register_bid_ability():
"type": "popup", "width": "88%", "height": "82%"},
{"label": "📦 标书", "icon": "", "url": "/pipeline-bidding/bid_documents/index.ui",
"type": "popup", "width": "88%", "height": "82%", "require_project": True},
{"label": "✉️ 邮箱配置", "icon": "", "url": "/pipeline-bidding/bid_mail_accounts/index.ui",
"type": "popup", "width": "88%", "height": "82%"},
],
)
register_ability(ability)
@ -568,11 +423,11 @@ def register_bid_ability():
# ══════════════════ slash 命令(产线专属)══════════════════
async def _slash_tenders(args, ctx):
async def _slash_files(args, ctx):
ex = ctx.get("executor")
if not ex:
return "无执行器上下文"
return await ex._execute_ability_tool("list_tenders", {"status": (args or "").strip()})
return await ex._execute_ability_tool("list_tender_files", {})
async def _slash_bid(args, ctx):
@ -598,7 +453,7 @@ async def _slash_score(args, ctx):
def register_bid_slash_commands():
for cmd in [
SlashCommand("tenders", "列出招标信息(可带状态)", _slash_tenders, "pipeline", PIPELINE_ID),
SlashCommand("files", "列出招标文件及解析状态", _slash_files, "pipeline", PIPELINE_ID),
SlashCommand("bid", "诊断当前投标项目卡点", _slash_bid, "pipeline", PIPELINE_ID),
SlashCommand("chapters", "列出标书章节及状态", _slash_chapters, "pipeline", PIPELINE_ID),
SlashCommand("score", "查看最近一轮整书评分报告", _slash_score, "pipeline", PIPELINE_ID),

View File

@ -252,7 +252,7 @@ async def has_done_task(sor, project_id, role):
async def has_pending_blocking_human_task(sor, project_id):
"""项目是否有阻塞中的人类任务(投标产线无迭代,按 project_id 判定)。"""
types = "','".join(BLOCKING_HT_TYPES + (HT_TENDER_APPROVAL,))
types = "','".join(BLOCKING_HT_TYPES)
recs = await sor.sqlExe(
"SELECT COUNT(*) AS c FROM pipeline_human_tasks "
"WHERE project_id=${pid}$ AND status='pending' "

View File

@ -20,7 +20,7 @@ from .bid_common import (
get_db, rows_to_dicts, rec_to_dict, to_int, to_float, json_loads,
create_role_task, count_open_tasks, has_done_task,
has_pending_blocking_human_task, find_human_task, get_thresholds,
create_human_task, PIPELINE_ID,
create_human_task, get_project, PIPELINE_ID,
CH_PENDING, CH_WRITING, CH_WRITTEN, CH_REJECTED, CH_APPROVED,
DOC_DRAFT, DOC_REVIEWING, DOC_PASSED, DOC_REJECTED, DOC_DELIVERED,
HT_TENDER_FILE, HT_HUMAN_DOCS, HT_DELIVERY_CONFIRM,
@ -106,8 +106,23 @@ async def reconcile_project(sor, project_id, project_name=""):
# ── 0b. 招标文件到位则自动关闭上传任务,再判阻塞门禁 ──
await _auto_close_file_upload_task(sor, project_id)
# 产线起点兜底:项目无任何招标文件且无待办上传任务 → 自动发「上传招标文件」任务
# (无论项目经 create_bid_project 还是通用 create_project 创建,都从上传文件起步)
_n_files_pre = await _count(sor, "SELECT COUNT(*) AS c FROM bid_tender_files "
"WHERE project_id=${p}$", {"p": project_id})
if _n_files_pre <= 0:
_up_ht = await find_human_task(sor, project_id, HT_TENDER_FILE, status="pending")
if not _up_ht:
p = await get_project(sor, project_id)
hid = await create_human_task(
sor, project_id, HT_TENDER_FILE,
"上传招标文件:%s" % (p.get("name", "") or project_id),
("请上传招标文件(含答疑/补遗文件)。\n"
"上传后系统自动进入招标文件解析(抽取评分项、得分规则、所需资质、投标文件要求、章节骨架)。"))
await sor.sqlExe("COMMIT", {})
return acts + ["created: 上传招标文件任务 %s" % hid]
if await has_pending_blocking_human_task(sor, project_id):
return ["blocked: 有待办人类任务(等招标文件/等人类资料/等审批),本轮不派发"]
return ["blocked: 有待办人类任务(等招标文件/等人类资料),本轮不派发"]
th = await get_thresholds(sor)

View File

@ -332,45 +332,41 @@ async def _close_approval_task(sor, tender_id, status, comment=""):
# ══════════════ 立项 / 人员 / 招标文件 ══════════════
async def create_bid_project(tender_id, name="", org_id="", created_by="",
async def create_bid_project(name, description="", org_id="", created_by="",
who=None, agent_id=None):
"""由招标信息立项:写 sd_projects(pipeline_id=bidding_general) + 发「上传招标文件」人类任务。"""
"""直接创建投标项目(不依赖招标信息前置)+ 发「上传招标文件」任务。
产线入口改为从上传招标文件开始立项不再经过邮件采集/审批
而是用户/agent 直接创建投标项目随后上传招标文件进入解析
"""
if not name or not str(name).strip():
return False, "缺少项目名称name"
db, dbname = get_db()
async with db.sqlorContext(dbname) as sor:
t = await _get_tender(sor, tender_id)
if not t:
return False, "招标信息不存在: %s" % tender_id
if t.get("project_id"):
return True, "项目已存在: %s" % t.get("project_id")
if t.get("status") != TD_APPROVED:
return False, "招标信息未审批通过(当前 %s),不可立项" % t.get("status")
pid = new_id()
pname = (name or t.get("title") or t.get("mail_subject") or "投标项目")[:190]
pname = str(name).strip()[:190]
await sor.C("sd_projects", {
"id": pid,
"name": pname,
"description": (t.get("summary") or "")[:4000],
"description": (description or "")[:4000],
"project_type": "bidding",
"pipeline_id": PIPELINE_ID,
"status": "in_progress",
"org_id": org_id or t.get("org_id") or "0",
"created_by": created_by or (who or "agent.tender_scout"),
"org_id": org_id or "0",
"created_by": created_by or (who or "agent.pm"),
})
await sor.sqlExe(
"UPDATE " + TABLE + " SET project_id=${pid}$, updated_at=NOW() WHERE id=${tid}$",
{"pid": pid, "tid": tender_id})
await sor.sqlExe("COMMIT", {})
await record(sor, pid, "sd_projects", pid, "create_bid_project",
to_state="in_progress", who=who, agent_id=agent_id,
detail="tender_id=" + tender_id)
detail="direct create (no tender)")
hid = await create_human_task(
sor, pid, HT_TENDER_FILE,
"上传招标文件:%s" % pname,
("项目已立项,请上传招标文件(含答疑/补遗文件)。\n"
"上传后系统自动进入招标文件解析(抽取评分项、得分规则、所需资质、投标文件要求)。\n"
("项目已创建,请上传招标文件(含答疑/补遗文件)。\n"
"上传后系统自动进入招标文件解析(抽取评分项、得分规则、所需资质、投标文件要求、章节骨架)。\n"
"上传入口:投标产线 → 招标文件bid_tender_files"),
assignee_role="owner.superuser",
form_schema={"project_id": pid, "tender_id": tender_id})
form_schema={"project_id": pid})
return True, "project_id=%s(已发上传招标文件任务 %s" % (pid, hid)

View File

@ -15,46 +15,7 @@ logger = logging.getLogger("pipeline.bidding.tools")
_M = "pipeline_bidding"
BID_ROLE_TOOL_SCHEMAS = {
# ── bid_tender_capability招标信息员 ──
"fetch_tenders": {
"module": f"{_M}.bid_tender_capability",
"description": "从配置的招标邮箱采集招标信息入库(按关键词过滤+去重)",
"params": {"days": "回溯天数(可选)", "account_id": "邮箱配置ID(可选)"},
"required": [],
},
"summarize_tender": {
"module": f"{_M}.bid_tender_capability",
"description": "对招标信息做内容摘要与要素抽取(投标开始时间/项目周期/招标金额/保证金/资质要求)并落库",
"params": {"tender_id": "招标信息ID", "model_name": "模型(可选)"},
"required": ["tender_id"],
},
"set_tender_fields": {
"module": f"{_M}.bid_tender_capability",
"description": "修正招标信息要素(摘要抽错时纠正)",
"params": {"tender_id": "招标信息ID", "title": "项目名称(可选)", "purchaser": "招标人(可选)",
"bid_start_at": "投标开始时间(可选)", "bid_deadline": "投标截止(可选)",
"project_period": "项目周期(可选)", "budget_amount": "招标金额(可选)",
"summary": "摘要(可选)", "qualification_req": "资质要求(可选)"},
"required": ["tender_id"],
},
"submit_tender_approval": {
"module": f"{_M}.bid_tender_capability",
"description": "提交招标信息审批(生成人类审批任务)",
"params": {"tender_id": "招标信息ID", "note": "提交说明(可选)"},
"required": ["tender_id"],
},
"list_tenders": {
"module": f"{_M}.bid_tender_capability",
"description": "列出招标信息",
"params": {"status": "状态(可选)", "limit": "条数(可选)"},
"required": [],
},
"archive_tender": {
"module": f"{_M}.bid_tender_capability",
"description": "存档作废招标信息",
"params": {"tender_id": "招标信息ID", "reason": "原因"},
"required": ["tender_id"],
},
# ── bid_tender_capability立项/人员(产线从上传招标文件开始,无邮件采集/审批)──
"assign_members": {
"module": f"{_M}.bid_tender_capability",
"description": "设定投标项目参与人员,如「张三:负责人,李四:writer」",

65
wwwroot/agent/index.ui Normal file
View File

@ -0,0 +1,65 @@
{
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "padding": "0"},
"subwidgets": [
{
"widgettype": "HBox",
"options": {"width": "100%", "alignItems": "center", "padding": "16px 24px 8px 24px", "cheight": 6, "gap": "12px"},
"subwidgets": [
{"widgettype": "Title2", "options": {"text": "投标产线"}},
{"widgettype": "Text", "options": {"text": "上传招标文件 → 解析 → 资质准备 → 分章节编写 → 评审 → 合成 → 评分 → 交付", "cfontsize": 0.9, "color": "#94a3b8"}},
{"widgettype": "Filler"},
{
"widgettype": "Button",
"id": "new_session_btn",
"options": {"label": " 新建会话", "css": "small"},
"binds": [{
"wid": "self",
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var tp=bricks.getWidgetById('session_tabs',bricks.app);if(!tp)return;var sid='s'+Date.now()+'_'+Math.floor(Math.random()*100000);var n=tp.opts.items.length+1;tp.open_tab({name:'session_'+sid,label:'会话 '+n,removable:true,content:{widgettype:'VBox',options:{css:'filler',width:'100%',height:'100%'},subwidgets:[{widgettype:'urlwidget',options:{url:'/pipeline_core/api/agent_menus.dspy?session_id='+sid+'&pipeline_id=bidding_general',method:'GET'}},{widgettype:'AgentIO',options:{css:'filler',margin:'0 24px 24px 24px',url:'/pipeline_core/api/agent_chat.dspy?session_id='+sid+'&pipeline_id=bidding_general',model_dataurl:'/pipeline_core/api/agent_model_options.dspy',model_cwidth:14,placeholder:'输入你的需求...'}}]}});"
}]
}
]
},
{
"widgettype": "TabPanel",
"id": "session_tabs",
"options": {
"css": "filler",
"width": "100%",
"height": "100%",
"tab_pos": "top",
"items": [
{
"name": "session_default",
"label": "会话 1",
"removable": false,
"content": {
"widgettype": "VBox",
"options": {"css": "filler", "width": "100%", "height": "100%"},
"subwidgets": [
{
"widgettype": "urlwidget",
"options": {"url": "{{entire_url('/pipeline_core/api/agent_menus.dspy')}}?session_id=default&pipeline_id=bidding_general", "method": "GET"}
},
{
"widgettype": "AgentIO",
"options": {
"css": "filler",
"margin": "0 24px 24px 24px",
"url": "/pipeline_core/api/agent_chat.dspy?session_id=default&pipeline_id=bidding_general",
"model_dataurl": "/pipeline_core/api/agent_model_options.dspy",
"model_cwidth": 14,
"placeholder": "输入你的需求..."
}
}
]
}
}
]
}
}
]
}