ForcePilot/backend/package/yuxi/channels/adapters/mattermost/model_picker.py
Kris d7fe152dae feat(mattermost): 完成 Mattermost 适配器多账号支持与功能增强
本次提交对 Mattermost 适配器进行了全面升级:
1.  重构为多账号架构,支持同时管理多个 Mattermost 机器人账号
2.  更新安全策略默认配置为配对模式和白名单模式
3.  新增 WebSocket 心跳、重连配置项与连接监控
4.  扩展 Agent 工具支持 pin/unpin、获取反应、搜索消息等操作
5.  重构交互按钮构建逻辑,新增分页与提供商筛选功能
6.  优化 SSRF 防护代码,复用公共工具库实现
7.  新增配置兼容性迁移与可变白名单项检测
8.  完善错误处理与日志输出,添加重复消息去重统计
9.  新增发送临时消息(ephemeral)支持
10. 修复提及检测逻辑,正确处理用户名大小写
2026-05-13 16:12:02 +08:00

239 lines
7.5 KiB
Python

from __future__ import annotations
import hashlib
from dataclasses import dataclass
from enum import StrEnum
MODEL_PICKER_PAGE_SIZE = 8
class PickerState(StrEnum):
PROVIDERS = "providers"
LIST = "list"
SELECT = "select"
BACK = "back"
@dataclass
class ModelOption:
model_id: str
display_name: str
provider: str = ""
description: str = ""
@property
def button_id(self) -> str:
return hashlib.sha256(self.model_id.encode()).hexdigest()[:8]
def get_default_model_options() -> list[ModelOption]:
return [
ModelOption("gpt-4o-mini", "GPT-4o Mini", "openai", "轻量快速模型"),
ModelOption("gpt-4o", "GPT-4o", "openai", "旗舰多模态模型"),
ModelOption("claude-sonnet-4-20250514", "Claude Sonnet 4", "anthropic", "高性能推理模型"),
ModelOption("deepseek-v3", "DeepSeek V3", "deepseek", "国产高性能模型"),
]
def get_available_providers(models: list[ModelOption] | None = None) -> list[str]:
if models is None:
models = get_default_model_options()
seen: dict[str, bool] = {}
result: list[str] = []
for m in models:
if m.provider and m.provider not in seen:
seen[m.provider] = True
result.append(m.provider)
return result
def build_model_picker_actions(
models: list[ModelOption] | None = None,
callback_id: str = "model_picker",
state: PickerState = PickerState.LIST,
page: int = 0,
) -> list[dict]:
if models is None:
models = get_default_model_options()
actions: list[dict] = []
if state == PickerState.PROVIDERS:
providers = get_available_providers(models)
for provider in providers:
actions.append(
{
"id": f"provider_{provider}",
"name": f"查看 {provider} 模型",
"integration": {
"url": "",
"context": {
"action": "list_provider",
"provider": provider,
"callback_id": callback_id,
"state": PickerState.LIST.value,
},
},
"type": "button",
"text": f"🤖 {provider.upper()}",
"style": "",
}
)
actions.append(
{
"id": "view_all",
"name": "查看全部模型",
"integration": {
"url": "",
"context": {
"action": "list_all",
"callback_id": callback_id,
"state": PickerState.LIST.value,
},
},
"type": "button",
"text": "📋 全部模型",
"style": "",
}
)
return actions
start = page * MODEL_PICKER_PAGE_SIZE
page_models = models[start : start + MODEL_PICKER_PAGE_SIZE]
total_pages = (len(models) + MODEL_PICKER_PAGE_SIZE - 1) // MODEL_PICKER_PAGE_SIZE
for m in page_models:
actions.append(
{
"id": f"model_{m.button_id}",
"name": f"选择 {m.display_name}",
"integration": {
"url": "",
"context": {
"action": "select_model",
"model_id": m.model_id,
"callback_id": callback_id,
"state": PickerState.SELECT.value,
},
},
"type": "button",
"text": f"🤖 {m.display_name}",
"style": "",
}
)
if total_pages > 1:
nav_actions: list[dict] = []
if page > 0:
nav_actions.append(
{
"id": "prev_page",
"name": "上一页",
"integration": {
"url": "",
"context": {
"action": "page",
"page": str(page - 1),
"callback_id": callback_id,
"state": PickerState.LIST.value,
},
},
"type": "button",
"text": "⬅️ 上一页",
"style": "",
}
)
if page < total_pages - 1:
nav_actions.append(
{
"id": "next_page",
"name": "下一页",
"integration": {
"url": "",
"context": {
"action": "page",
"page": str(page + 1),
"callback_id": callback_id,
"state": PickerState.LIST.value,
},
},
"type": "button",
"text": "➡️ 下一页",
"style": "",
}
)
actions.extend(nav_actions)
actions.append(
{
"id": "back_to_providers",
"name": "按提供商筛选",
"integration": {
"url": "",
"context": {
"action": "back",
"callback_id": callback_id,
"state": PickerState.PROVIDERS.value,
},
},
"type": "button",
"text": "🔙 按提供商筛选",
"style": "",
}
)
return actions
def build_model_picker_attachment(
current_model: str = "",
callback_id: str = "model_picker",
state: PickerState = PickerState.PROVIDERS,
page: int = 0,
provider_filter: str = "",
) -> dict:
models = get_default_model_options()
if provider_filter:
models = [m for m in models if m.provider == provider_filter]
if state == PickerState.PROVIDERS:
providers = get_available_providers(models)
lines = [f"• **{p.upper()}**" for p in providers]
text = "按 AI 提供商筛选模型:\n\n" + "\n".join(lines)
else:
start = page * MODEL_PICKER_PAGE_SIZE
page_models = models[start : start + MODEL_PICKER_PAGE_SIZE]
total_pages = (len(models) + MODEL_PICKER_PAGE_SIZE - 1) // MODEL_PICKER_PAGE_SIZE
lines = []
for m in page_models:
marker = "" if m.model_id == current_model else ""
lines.append(f"• **{m.display_name}** ({m.provider}) — {m.description}{marker}")
text = f"当前可用模型 (页 {page + 1}/{total_pages}):\n\n" + "\n".join(lines)
actions = build_model_picker_actions(models, callback_id, state, page)
return {
"fallback": "模型选择器",
"title": "🤖 模型选择",
"text": text,
"callback_id": callback_id,
"actions": actions,
}
def build_model_select_attachment(provider: str, callback_id: str = "model_select") -> dict:
all_models = get_default_model_options()
provider_models = [m for m in all_models if m.provider == provider]
actions = build_model_picker_actions(provider_models, callback_id, PickerState.LIST, 0)
return {
"fallback": f"选择 {provider} 模型",
"title": f"🤖 {provider} 模型",
"text": f"选择 {provider} 下的模型:",
"callback_id": callback_id,
"actions": actions,
}