Files
inquiry_robot/inquiry-agent/agent/channel/h5/__init__.py
T

113 lines
3.6 KiB
Python

"""
H5 补问 / 附件上传 / 复制工单号通道。
本文件职责:挂载 /inquiry-form;用 token 绑定身份或工单号(URL 禁止 userid)。
禁止:从 URL 读 userid;禁止在本包改六态或直连 TMS;禁止同步 invoke Graph。
"""
from __future__ import annotations
import logging
from typing import Any, Optional
from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import HTMLResponse
from pydantic import BaseModel, Field
from agent.channel.clarification import validate_token
from agent.channel.h5.copy_page import expired_copy_page, render_copy_page
from agent.channel.h5.copy_store import lookup_copy_token
from agent.handlers.card_action import handle_card_action
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/inquiry-form", tags=["h5"])
class H5SubmitBody(BaseModel):
"""H5 提交体:业务字段放 fields;身份只认服务端 token 绑定。"""
action: str = Field(default="submit_clarify")
fields: dict[str, Any] = Field(default_factory=dict)
@router.get("/ping")
def h5_ping() -> dict[str, str]:
"""存活探测。"""
return {"status": "ok", "channel": "h5"}
@router.get("/copy/{token}", response_class=HTMLResponse)
def h5_copy_ticket(token: str) -> HTMLResponse:
"""
复制工单号页:token 换工单号,页面写入剪贴板。
请求线程只读 token,不进 Graph、不发企微消息。
"""
wo = lookup_copy_token(token)
if not wo:
return HTMLResponse(expired_copy_page(), status_code=410)
return HTMLResponse(render_copy_page(wo))
@router.get("/jsapi-signature")
def h5_jsapi_signature(
url: str = Query(default=""),
kind: str = Query(default="corp"),
) -> dict[str, Any]:
"""
给复制页 ww.register 签名。失败给空对象,页面改走浏览器剪贴板。
"""
from agent.channel.wecom.jsapi import build_jsapi_signature
try:
return build_jsapi_signature(url=url, kind=kind)
except Exception as exc: # noqa: BLE001
logger.warning("jsapi 签名失败 kind=%s err=%s", kind, exc)
raise HTTPException(status_code=503, detail="jsapi_unavailable") from exc
@router.get("/{token}")
def h5_open(token: str) -> dict[str, Any]:
"""
打开补问页:校验 token 未过期。
返回允许动作与 wait_version;不返回内部异常栈。
"""
rec = validate_token(token)
if rec is None:
raise HTTPException(status_code=410, detail="表单已过期或无效,请回企微重新打开")
return {
"status": "ok",
"thread_id": rec.thread_id,
"wait_version": rec.wait_version,
"allowed_actions": rec.allowed_actions,
"note": "骨架:字段列表后续按合同生成",
}
@router.post("/{token}/submit")
def h5_submit(token: str, body: H5SubmitBody) -> dict[str, Any]:
"""
提交补问:消费 token,转入 card_action 骨架(不跑整图)。
副作用:token 一次性;入队/Handler 后续接 Worker。
"""
rec = validate_token(token, action=body.action or "submit_clarify", consume=True)
if rec is None:
raise HTTPException(status_code=410, detail="表单已过期或无效,请回企微重新打开")
handle_card_action(
sender_id=rec.sender_id,
thread_id=rec.thread_id,
wait_version=rec.wait_version,
action=body.action or "submit_clarify",
payload=body.fields,
)
logger.info(
"h5.submit ok thread=%s wait=%s action=%s",
rec.thread_id,
rec.wait_version,
body.action,
)
return {"status": "accepted", "thread_id": rec.thread_id}