feat(ai_auto_response): add admin-controlled persona switching

This commit is contained in:
liuwei
2026-04-10 13:15:35 +08:00
parent c280fa8dab
commit a1aa05e3b9
6 changed files with 293 additions and 12 deletions

View File

@@ -131,6 +131,10 @@ class AIAutoResponsePlugin(MessagePluginInterface):
self.cooldown = CooldownManager(self.cooldown_config)
self.image_config = self._config.get("image", {}) or {}
self.spam_config = self._config.get("spam_guard", {}) or {}
try:
self.redis_client = self.db_manager.get_redis_connection() if self.db_manager else None
except Exception:
self.redis_client = None
self._synced_member_context_versions: Dict[str, str] = {}
self.log_debug = bool((self._config.get("logging", {}) or {}).get("debug", True))
self.LOG.debug(f"[{self.name}] 初始化完成")
@@ -164,6 +168,8 @@ class AIAutoResponsePlugin(MessagePluginInterface):
content = self._normalize_content(message)
if not content:
return False
if self._parse_persona_command(content):
return True
if should_ignore(content, self.filters):
return False
if is_targeting_other_user(message):
@@ -188,6 +194,10 @@ class AIAutoResponsePlugin(MessagePluginInterface):
)
return False, "duplicate_message"
try:
command = self._parse_persona_command(content)
if command:
handled = await self._handle_persona_command(message, command)
return False, handled
if is_prompt_attack(content):
self._log_event(
"skip",
@@ -241,6 +251,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
name_map=group_name_map,
)
group_profile = group_memory_bundle.get("group_profile", {}) or {}
group_profile = self._apply_persona_override(room_id, group_profile)
social_context = group_memory_bundle.get("social_context", {}) or {"items": [], "prompt": ""}
group_facts = group_memory_bundle.get("group_facts", {}) or {"items": [], "prompt": ""}
self._log_event(
@@ -539,6 +550,104 @@ class AIAutoResponsePlugin(MessagePluginInterface):
if len(items) > size:
self.group_messages[room_id] = items[-size:]
@staticmethod
def _parse_persona_command(content: str) -> Dict[str, str] | None:
text = str(content or "").strip()
if not text.startswith("#"):
return None
if text in {"#人格列表", "#人格", "#personas"}:
return {"type": "list"}
if text in {"#当前人格", "#人格状态", "#persona"}:
return {"type": "current"}
if text.startswith("#切换人格"):
target = text[len("#切换人格"):].strip()
if target:
return {"type": "switch", "target": target}
return {"type": "switch", "target": ""}
return None
async def _handle_persona_command(self, message: Dict[str, Any], command: Dict[str, str]) -> str:
room_id = str(message.get("roomid", "") or "")
sender = str(message.get("sender", "") or "")
bot: WechatAPIClient = message.get("bot")
command_type = str(command.get("type", "") or "")
if command_type == "list":
items = []
for preset in self.persona_engine.list_personas():
aliases = " / ".join((preset.get("aliases", []) or [])[:3])
line = f"{preset.get('name')}{preset.get('id')}"
if aliases:
line += f" - {aliases}"
items.append(line)
text = "可用人格:\n" + "\n".join(f"- {item}" for item in items)
await bot.send_text_message(room_id, text, sender)
return "persona_list"
current_id = self._get_room_persona_id(room_id) or self.persona_engine.default_persona_id
current_preset = self.persona_engine.presets.get(current_id, {})
if command_type == "current":
await bot.send_text_message(
room_id,
f"当前人格:{current_preset.get('name', current_id)}{current_id}",
sender,
)
return "persona_current"
if command_type == "switch":
if not GroupBotManager.is_admin(sender):
await bot.send_text_message(room_id, "只有管理员才能切换人格。", sender)
self._log_event(
"skip",
room_id=room_id,
sender=sender,
reason="persona_switch_no_permission",
trigger_type="persona_command",
reply_mode="admin_guard",
)
return "persona_switch_no_permission"
target = str(command.get("target", "") or "").strip()
if not target:
await bot.send_text_message(room_id, "写法:#切换人格 于谦", sender)
return "persona_switch_missing"
target_id = self.persona_engine.resolve_persona_id(target)
if not target_id:
await bot.send_text_message(room_id, f"没找到这个人格:{target},先发 #人格列表 看看。", sender)
return "persona_switch_invalid"
self._set_room_persona_id(room_id, target_id)
target_preset = self.persona_engine.presets.get(target_id, {})
await bot.send_text_message(
room_id,
f"已切到 {target_preset.get('name', target_id)}{target_id}",
sender,
)
return "persona_switch"
return "persona_unknown"
def _persona_redis_key(self, room_id: str) -> str:
return f"ai_auto_response:persona:{room_id}"
def _get_room_persona_id(self, room_id: str) -> str:
if not room_id or not self.redis_client:
return ""
try:
value = self.redis_client.get(self._persona_redis_key(room_id))
return str(value or "").strip()
except Exception:
return ""
def _set_room_persona_id(self, room_id: str, persona_id: str) -> bool:
if not room_id or not persona_id or not self.redis_client:
return False
try:
return bool(self.redis_client.set(self._persona_redis_key(room_id), persona_id))
except Exception:
return False
def _apply_persona_override(self, room_id: str, group_profile: Dict) -> Dict:
profile = dict(group_profile or {})
persona_id = self._get_room_persona_id(room_id)
if persona_id and persona_id in self.persona_engine.presets:
profile["persona_id"] = persona_id
return profile
def _build_message_key(self, message: Dict[str, Any], content: str) -> str:
full_msg = message.get("full_wx_msg")
if full_msg is not None: