diff --git a/pipeline_bidding/bid_ability.py b/pipeline_bidding/bid_ability.py index d297178..5104da9 100644 --- a/pipeline_bidding/bid_ability.py +++ b/pipeline_bidding/bid_ability.py @@ -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), diff --git a/pipeline_bidding/bid_common.py b/pipeline_bidding/bid_common.py index 824bda1..0e98447 100644 --- a/pipeline_bidding/bid_common.py +++ b/pipeline_bidding/bid_common.py @@ -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' " diff --git a/pipeline_bidding/bid_flow.py b/pipeline_bidding/bid_flow.py index b39ca33..f4d73f4 100644 --- a/pipeline_bidding/bid_flow.py +++ b/pipeline_bidding/bid_flow.py @@ -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) diff --git a/pipeline_bidding/bid_tender_capability.py b/pipeline_bidding/bid_tender_capability.py index 3a0004d..780c20d 100644 --- a/pipeline_bidding/bid_tender_capability.py +++ b/pipeline_bidding/bid_tender_capability.py @@ -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) diff --git a/pipeline_bidding/role_tool_schemas.py b/pipeline_bidding/role_tool_schemas.py index 2d424cf..e4136c6 100644 --- a/pipeline_bidding/role_tool_schemas.py +++ b/pipeline_bidding/role_tool_schemas.py @@ -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」", diff --git a/wwwroot/agent/index.ui b/wwwroot/agent/index.ui new file mode 100644 index 0000000..3b98d05 --- /dev/null +++ b/wwwroot/agent/index.ui @@ -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": "输入你的需求..." + } + } + ] + } + } + ] + } + } + ] +}