ForcePilot/backend/package/yuxi/channels/adapters/slack/setup_wizard.py
Kris a2aa782b86 feat(slack adapter): 实现完整的Slack频道适配器基础功能
新增Slack适配器全套核心模块,包括消息处理流水线、会话管理、配置适配、权限控制等完整功能:
1. 新增语音、视觉相关的TTS和图像分析导出接口
2. 实现消息预处理、路由、线程上下文处理的完整流水线
3. 新增账号管理、缓存机制、房间上下文提取功能
4. 支持Webhook和Socket Mode两种事件接收方式
5. 实现权限白名单、审批配对、自动状态管理功能
6. 新增配置迁移、作用域校验、重连策略等辅助模块
2026-05-12 00:48:57 +08:00

135 lines
4.3 KiB
Python

from __future__ import annotations
import os
from dataclasses import dataclass, field
from typing import Any
@dataclass
class SetupWizardStep:
step_id: str
title: str = ""
description: str = ""
required: bool = True
completed: bool = False
config_key: str = ""
config_value: Any = None
@dataclass
class SetupWizardState:
steps: list[SetupWizardStep] = field(default_factory=list)
current_step: int = 0
@property
def is_complete(self) -> bool:
return all(s.completed for s in self.steps if s.required)
@property
def current(self) -> SetupWizardStep | None:
if 0 <= self.current_step < len(self.steps):
return self.steps[self.current_step]
return None
def advance(self) -> None:
if self.current_step < len(self.steps):
self.steps[self.current_step].completed = True
self.current_step += 1
def to_config(self) -> dict[str, Any]:
config: dict[str, Any] = {}
for step in self.steps:
if step.config_key and step.config_value is not None:
config[step.config_key] = step.config_value
return config
_ENV_TOKEN_KEYS = [
("SLACK_BOT_TOKEN", "token", "bot_token"),
("SLACK_APP_TOKEN", "app_token", "app_token"),
("SLACK_SIGNING_SECRET", "signing_secret", "signing_secret"),
]
def detect_env_tokens() -> dict[str, str]:
detected: dict[str, str] = {}
for env_key, _config_key, display_key in _ENV_TOKEN_KEYS:
value = os.environ.get(env_key, "")
if value:
detected[display_key] = value[:8] + "..."
return detected
def is_env_token_configured(key: str) -> bool:
for env_key, _, display_key in _ENV_TOKEN_KEYS:
if display_key == key or env_key == key:
return bool(os.environ.get(env_key, ""))
return False
def create_setup_wizard_steps(config: dict[str, Any] | None = None) -> list[SetupWizardStep]:
existing = config or {}
env_tokens = detect_env_tokens()
token_env = env_tokens.get("bot_token")
token_desc = "Enter your Slack Bot Token (xoxb-...)"
if token_env:
token_desc = f"Bot Token detected from env ({token_env}). Leave blank to use env value."
app_token_env = env_tokens.get("app_token")
app_token_desc = "Enter your Slack App-Level Token (xapp-...) for Socket Mode"
if app_token_env:
app_token_desc = f"App Token detected from env ({app_token_env}). Leave blank to use env value."
signing_env = env_tokens.get("signing_secret")
signing_desc = "Enter your Slack Signing Secret for Events API"
if signing_env:
signing_desc = "Signing Secret detected from env. Leave blank to use env value."
return [
SetupWizardStep(
step_id="token",
title="Bot Token",
description=token_desc,
required=True,
completed=bool(existing.get("token") or is_env_token_configured("bot_token")),
config_key="token",
),
SetupWizardStep(
step_id="app_token",
title="App-Level Token",
description=app_token_desc,
required=True,
completed=bool(existing.get("app_token") or is_env_token_configured("app_token")),
config_key="app_token",
),
SetupWizardStep(
step_id="signing_secret",
title="Signing Secret",
description=signing_desc,
required=False,
completed=bool(existing.get("signing_secret") or is_env_token_configured("signing_secret")),
config_key="signing_secret",
),
SetupWizardStep(
step_id="security_mode",
title="Security Mode",
description="Choose security mode: open or pairing",
required=True,
completed=bool(existing.get("security", {}).get("mode")),
config_key="security.mode",
),
SetupWizardStep(
step_id="allow_from",
title="Allowed Users",
description="Configure allowed users (comma-separated IDs or '*' for all)",
required=False,
completed=bool(existing.get("security", {}).get("allow_from")),
config_key="security.allow_from",
),
]
def setup_wizard_to_config(state: SetupWizardState) -> dict[str, Any]:
return state.to_config()