mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-17 01:49:15 +08:00
284 lines
10 KiB
Python
284 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
|
from astrbot.core.sentinels import NOT_GIVEN
|
|
|
|
|
|
class PersonaServiceError(Exception):
|
|
pass
|
|
|
|
|
|
class PersonaService:
|
|
def __init__(self, core_lifecycle: AstrBotCoreLifecycle) -> None:
|
|
self.persona_mgr = core_lifecycle.persona_mgr
|
|
|
|
async def list_personas(
|
|
self,
|
|
folder_id: str | None,
|
|
filter_by_folder: bool,
|
|
) -> list[dict]:
|
|
if filter_by_folder:
|
|
personas = await self.persona_mgr.get_personas_by_folder(
|
|
folder_id if folder_id else None
|
|
)
|
|
else:
|
|
personas = await self.persona_mgr.get_all_personas()
|
|
return [self.serialize_persona(persona) for persona in personas]
|
|
|
|
async def get_persona_detail(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
persona_id = payload.get("persona_id")
|
|
|
|
if not persona_id:
|
|
raise PersonaServiceError("缺少必要参数: persona_id")
|
|
|
|
persona = await self.persona_mgr.get_persona(persona_id)
|
|
if not persona:
|
|
raise PersonaServiceError("人格不存在")
|
|
|
|
return self.serialize_persona(persona)
|
|
|
|
async def create_persona(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
raw_persona_id = payload.get("persona_id")
|
|
raw_system_prompt = payload.get("system_prompt")
|
|
persona_id = str(raw_persona_id).strip() if raw_persona_id is not None else ""
|
|
system_prompt = (
|
|
str(raw_system_prompt).strip() if raw_system_prompt is not None else ""
|
|
)
|
|
begin_dialogs = payload.get("begin_dialogs", [])
|
|
tools = payload.get("tools")
|
|
skills = payload.get("skills")
|
|
custom_error_message = self._normalize_custom_error_message(
|
|
payload.get("custom_error_message")
|
|
)
|
|
folder_id = payload.get("folder_id")
|
|
sort_order = payload.get("sort_order", 0)
|
|
|
|
if not persona_id:
|
|
raise PersonaServiceError("人格ID不能为空")
|
|
if not system_prompt:
|
|
raise PersonaServiceError("系统提示词不能为空")
|
|
|
|
self._validate_begin_dialogs(begin_dialogs)
|
|
|
|
persona = await self.persona_mgr.create_persona(
|
|
persona_id=persona_id,
|
|
system_prompt=system_prompt,
|
|
begin_dialogs=begin_dialogs if begin_dialogs else None,
|
|
tools=tools if tools else None,
|
|
skills=skills if skills else None,
|
|
custom_error_message=custom_error_message,
|
|
folder_id=folder_id,
|
|
sort_order=sort_order,
|
|
)
|
|
|
|
return {
|
|
"message": "人格创建成功",
|
|
"persona": self.serialize_persona(persona, empty_lists_for_tools=True),
|
|
}
|
|
|
|
async def update_persona(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
persona_id = payload.get("persona_id")
|
|
system_prompt = payload.get("system_prompt")
|
|
begin_dialogs = payload.get("begin_dialogs")
|
|
has_tools = "tools" in payload
|
|
tools = payload.get("tools")
|
|
has_skills = "skills" in payload
|
|
skills = payload.get("skills")
|
|
has_custom_error_message = "custom_error_message" in payload
|
|
custom_error_message = payload.get("custom_error_message")
|
|
|
|
if not persona_id:
|
|
raise PersonaServiceError("缺少必要参数: persona_id")
|
|
|
|
if has_custom_error_message:
|
|
custom_error_message = self._normalize_custom_error_message(
|
|
custom_error_message
|
|
)
|
|
|
|
if begin_dialogs is not None:
|
|
self._validate_begin_dialogs(begin_dialogs)
|
|
|
|
update_kwargs = {
|
|
"persona_id": persona_id,
|
|
"system_prompt": system_prompt,
|
|
"begin_dialogs": begin_dialogs,
|
|
}
|
|
if has_tools:
|
|
update_kwargs["tools"] = tools
|
|
if has_skills:
|
|
update_kwargs["skills"] = skills
|
|
if has_custom_error_message:
|
|
update_kwargs["custom_error_message"] = custom_error_message
|
|
|
|
await self.persona_mgr.update_persona(**update_kwargs)
|
|
return {"message": "人格更新成功"}
|
|
|
|
async def delete_persona(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
persona_id = payload.get("persona_id")
|
|
|
|
if not persona_id:
|
|
raise PersonaServiceError("缺少必要参数: persona_id")
|
|
|
|
await self.persona_mgr.delete_persona(persona_id)
|
|
return {"message": "人格删除成功"}
|
|
|
|
async def move_persona(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
persona_id = payload.get("persona_id")
|
|
folder_id = payload.get("folder_id")
|
|
|
|
if not persona_id:
|
|
raise PersonaServiceError("缺少必要参数: persona_id")
|
|
|
|
await self.persona_mgr.move_persona_to_folder(persona_id, folder_id)
|
|
return {"message": "人格移动成功"}
|
|
|
|
async def list_folders(self, parent_id: str | None) -> list[dict]:
|
|
if parent_id == "":
|
|
parent_id = None
|
|
folders = await self.persona_mgr.get_folders(parent_id)
|
|
return [self.serialize_folder(folder) for folder in folders]
|
|
|
|
async def get_folder_tree(self):
|
|
return await self.persona_mgr.get_folder_tree()
|
|
|
|
async def get_folder_detail(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
folder_id = payload.get("folder_id")
|
|
|
|
if not folder_id:
|
|
raise PersonaServiceError("缺少必要参数: folder_id")
|
|
|
|
folder = await self.persona_mgr.get_folder(folder_id)
|
|
if not folder:
|
|
raise PersonaServiceError("文件夹不存在")
|
|
|
|
return self.serialize_folder(folder)
|
|
|
|
async def create_folder(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
name = str(payload.get("name", "")).strip()
|
|
parent_id = payload.get("parent_id")
|
|
description = payload.get("description")
|
|
sort_order = payload.get("sort_order", 0)
|
|
|
|
if not name:
|
|
raise PersonaServiceError("文件夹名称不能为空")
|
|
|
|
folder = await self.persona_mgr.create_folder(
|
|
name=name,
|
|
parent_id=parent_id,
|
|
description=description,
|
|
sort_order=sort_order,
|
|
)
|
|
|
|
return {
|
|
"message": "文件夹创建成功",
|
|
"folder": self.serialize_folder(folder),
|
|
}
|
|
|
|
async def update_folder(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
folder_id = payload.get("folder_id")
|
|
name = payload.get("name")
|
|
parent_id = payload.get("parent_id") if "parent_id" in payload else NOT_GIVEN
|
|
description = (
|
|
payload.get("description") if "description" in payload else NOT_GIVEN
|
|
)
|
|
sort_order = payload.get("sort_order")
|
|
|
|
if not folder_id:
|
|
raise PersonaServiceError("缺少必要参数: folder_id")
|
|
|
|
await self.persona_mgr.update_folder(
|
|
folder_id=folder_id,
|
|
name=name,
|
|
parent_id=parent_id,
|
|
description=description,
|
|
sort_order=sort_order,
|
|
)
|
|
|
|
return {"message": "文件夹更新成功"}
|
|
|
|
async def delete_folder(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
folder_id = payload.get("folder_id")
|
|
|
|
if not folder_id:
|
|
raise PersonaServiceError("缺少必要参数: folder_id")
|
|
|
|
await self.persona_mgr.delete_folder(folder_id)
|
|
return {"message": "文件夹删除成功"}
|
|
|
|
async def reorder_items(self, data: object) -> dict:
|
|
payload = self._payload(data)
|
|
items = payload.get("items", [])
|
|
|
|
if not items:
|
|
raise PersonaServiceError("items 不能为空")
|
|
|
|
for item in items:
|
|
if not all(key in item for key in ("id", "type", "sort_order")):
|
|
raise PersonaServiceError(
|
|
"每个 item 必须包含 id, type, sort_order 字段"
|
|
)
|
|
if item["type"] not in ("persona", "folder"):
|
|
raise PersonaServiceError("type 字段必须是 'persona' 或 'folder'")
|
|
|
|
await self.persona_mgr.batch_update_sort_order(items)
|
|
return {"message": "排序更新成功"}
|
|
|
|
@staticmethod
|
|
def serialize_persona(persona, empty_lists_for_tools: bool = False) -> dict:
|
|
return {
|
|
"persona_id": persona.persona_id,
|
|
"system_prompt": persona.system_prompt,
|
|
"begin_dialogs": persona.begin_dialogs or [],
|
|
"tools": (persona.tools or []) if empty_lists_for_tools else persona.tools,
|
|
"skills": (persona.skills or [])
|
|
if empty_lists_for_tools
|
|
else persona.skills,
|
|
"custom_error_message": persona.custom_error_message,
|
|
"folder_id": persona.folder_id,
|
|
"sort_order": persona.sort_order,
|
|
"created_at": persona.created_at.isoformat()
|
|
if persona.created_at
|
|
else None,
|
|
"updated_at": persona.updated_at.isoformat()
|
|
if persona.updated_at
|
|
else None,
|
|
}
|
|
|
|
@staticmethod
|
|
def serialize_folder(folder) -> dict:
|
|
return {
|
|
"folder_id": folder.folder_id,
|
|
"name": folder.name,
|
|
"parent_id": folder.parent_id,
|
|
"description": folder.description,
|
|
"sort_order": folder.sort_order,
|
|
"created_at": folder.created_at.isoformat() if folder.created_at else None,
|
|
"updated_at": folder.updated_at.isoformat() if folder.updated_at else None,
|
|
}
|
|
|
|
@staticmethod
|
|
def _normalize_custom_error_message(value):
|
|
if value is not None:
|
|
if not isinstance(value, str):
|
|
raise PersonaServiceError("自定义报错回复信息必须是字符串")
|
|
return value.strip() or None
|
|
return None
|
|
|
|
@staticmethod
|
|
def _validate_begin_dialogs(begin_dialogs) -> None:
|
|
if begin_dialogs and len(begin_dialogs) % 2 != 0:
|
|
raise PersonaServiceError("预设对话数量必须为偶数(用户和助手轮流对话)")
|
|
|
|
@staticmethod
|
|
def _payload(data: object) -> dict:
|
|
return data if isinstance(data, dict) else {}
|