ForcePilot/backend/package/yuxi/channels/adapters/wechat/setup_wizard.py

163 lines
6.1 KiB
Python
Raw Normal View History

from __future__ import annotations
from enum import Enum
from typing import Any
from yuxi.utils.logging_config import logger
class WizardStep(Enum):
MODE_SELECTION = "mode_selection"
CREDENTIALS = "credentials"
VALIDATION = "validation"
WEBHOOK = "webhook"
CONFIRMATION = "confirmation"
class WeChatSetupWizard:
def __init__(self):
self._current_step = WizardStep.MODE_SELECTION
self._selected_mode: str = ""
self._config_snapshot: dict[str, Any] = {}
def get_available_modes(self) -> list[dict[str, Any]]:
return [
{
"id": "wecom",
"label": "企业微信 (WeCom)",
"description": "使用企业微信应用消息 API需要 corp_id、corp_secret、agent_id",
"required_fields": ["corp_id", "corp_secret", "agent_id"],
},
{
"id": "mp",
"label": "公众号 (MP)",
"description": "使用微信公众号客服消息 API需要 app_id、app_secret",
"required_fields": ["app_id", "app_secret"],
},
{
"id": "personal",
"label": "个人微信 (Bridge)",
"description": "通过桥接服务连接个人微信,需要 bridge_url",
"required_fields": ["bridge_url"],
},
]
async def select_mode(self, mode: str) -> dict[str, Any]:
if mode not in ("wecom", "mp", "personal"):
return {
"success": False,
"error": f"Invalid mode: {mode}",
"available": [m["id"] for m in self.get_available_modes()],
}
self._selected_mode = mode
self._current_step = WizardStep.CREDENTIALS
return {"success": True, "mode": mode, "next_step": "credentials"}
async def set_credentials(self, credentials: dict[str, Any]) -> dict[str, Any]:
if not self._selected_mode:
return {"success": False, "error": "Please select a mode first"}
required = {
"wecom": ["corp_id", "corp_secret", "agent_id"],
"mp": ["app_id", "app_secret"],
"personal": ["bridge_url"],
}.get(self._selected_mode, [])
missing = [k for k in required if not credentials.get(k)]
if missing:
return {"success": False, "error": f"Missing required fields: {missing}"}
self._config_snapshot = {}
for k in required:
self._config_snapshot[k] = credentials[k]
self._current_step = WizardStep.VALIDATION
return {"success": True, "config": self._config_snapshot, "next_step": "validation"}
async def validate_connection(self, http_client_factory=None) -> dict[str, Any]:
if not self._config_snapshot:
return {"success": False, "error": "No credentials configured"}
if self._selected_mode == "wecom":
valid = all(self._config_snapshot.get(k) for k in ("corp_id", "corp_secret"))
elif self._selected_mode == "mp":
valid = all(self._config_snapshot.get(k) for k in ("app_id", "app_secret"))
elif self._selected_mode == "personal":
valid = bool(self._config_snapshot.get("bridge_url"))
else:
valid = False
self._current_step = WizardStep.WEBHOOK if valid else WizardStep.CREDENTIALS
return {
"success": valid,
"mode": self._selected_mode,
"message": "Credentials validated" if valid else "Invalid credentials",
"next_step": "webhook" if valid else "credentials",
}
async def configure_webhook(self, webhook_url: str) -> dict[str, Any]:
if not self._config_snapshot:
return {"success": False, "error": "No credentials configured"}
self._config_snapshot["webhook_url"] = webhook_url
self._config_snapshot["token"] = self._config_snapshot.get("token", "")
self._current_step = WizardStep.CONFIRMATION
return {
"success": True,
"webhook_url": webhook_url,
"next_step": "confirmation",
"note": "请在企业微信后台/公众号配置服务器地址为以上 webhook_url并配置相同的 Token / EncodingAESKey"
if self._selected_mode == "wecom"
else "",
}
async def confirm_and_apply(self) -> dict[str, Any]:
if self._current_step != WizardStep.CONFIRMATION:
return {"success": False, "error": "Please complete all steps before confirming"}
config = {
"enabled": True,
"mode": self._selected_mode,
**self._config_snapshot,
}
logger.info(f"[WeChat/SetupWizard] Configuration confirmed for mode={self._selected_mode}")
return {
"success": True,
"mode": self._selected_mode,
"config": config,
"message": f"微信 {self._selected_mode} 模式配置完成,请启动适配器以验证连接",
}
def get_current_step_info(self) -> dict[str, Any]:
return {
"step": self._current_step.value,
"mode": self._selected_mode,
"config_snapshot": dict(self._config_snapshot),
}
def reset(self) -> None:
self._current_step = WizardStep.MODE_SELECTION
self._selected_mode = ""
self._config_snapshot = {}
class WeChatSetupAdapter:
ROBUST_READ_COMMANDS = [
"wechat config validate",
"wechat config show",
"wechat status",
]
@staticmethod
def get_wizard_summary(config: dict[str, Any]) -> dict[str, Any]:
has_corp = all(config.get(k) for k in ("corp_id", "corp_secret", "agent_id"))
has_mp = all(config.get(k) for k in ("app_id", "app_secret"))
has_bridge = bool(config.get("bridge_url"))
mode = "wecom" if has_corp else "mp" if has_mp else "personal" if has_bridge else "unconfigured"
return {
"mode": mode,
"configured": mode != "unconfigured",
"webhook_configured": bool(config.get("webhook_url")),
"enabled": config.get("enabled", False),
}