feat(persona): add batch sort order update endpoint for personas and folders

Add new API endpoint POST /persona/reorder to batch update sort_order
for both personas and folders. This enables drag-and-drop reordering
in the dashboard UI.

Changes:
- Add abstract batch_update_sort_order method to BaseDatabase
- Implement batch_update_sort_order in SQLiteDatabase
- Add batch_update_sort_order to PersonaManager with cache refresh
- Add reorder_items route handler with input validation
This commit is contained in:
RC-CHN
2026-01-12 10:44:58 +08:00
parent ba0935dcaf
commit 24a40e4132
4 changed files with 107 additions and 0 deletions
+15
View File
@@ -345,6 +345,21 @@ class BaseDatabase(abc.ABC):
"""Get all personas in a specific folder."""
...
@abc.abstractmethod
async def batch_update_sort_order(
self,
items: list[dict],
) -> None:
"""Batch update sort_order for personas and/or folders.
Args:
items: List of dicts with keys:
- id: The persona_id or folder_id
- type: Either "persona" or "folder"
- sort_order: The new sort_order value
"""
...
@abc.abstractmethod
async def insert_preference_or_update(
self,
+39
View File
@@ -776,6 +776,45 @@ class SQLiteDatabase(BaseDatabase):
result = await session.execute(query)
return list(result.scalars().all())
async def batch_update_sort_order(
self,
items: list[dict],
) -> None:
"""Batch update sort_order for personas and/or folders.
Args:
items: List of dicts with keys:
- id: The persona_id or folder_id
- type: Either "persona" or "folder"
- sort_order: The new sort_order value
"""
if not items:
return
async with self.get_db() as session:
session: AsyncSession
async with session.begin():
for item in items:
item_id = item.get("id")
item_type = item.get("type")
sort_order = item.get("sort_order")
if item_id is None or item_type is None or sort_order is None:
continue
if item_type == "persona":
await session.execute(
update(Persona)
.where(col(Persona.persona_id) == item_id)
.values(sort_order=sort_order)
)
elif item_type == "folder":
await session.execute(
update(PersonaFolder)
.where(col(PersonaFolder.folder_id) == item_id)
.values(sort_order=sort_order)
)
async def insert_preference_or_update(self, scope, scope_id, key, value):
"""Insert a new preference record or update if it exists."""
async with self.get_db() as session:
+14
View File
@@ -180,6 +180,20 @@ class PersonaManager:
"""
await self.db.delete_persona_folder(folder_id)
async def batch_update_sort_order(self, items: list[dict]) -> None:
"""批量更新 personas 和/或 folders 的排序顺序
Args:
items: 包含以下键的字典列表:
- id: persona_id 或 folder_id
- type: "persona""folder"
- sort_order: 新的排序顺序值
"""
await self.db.batch_update_sort_order(items)
# 刷新缓存
self.personas = await self.get_all_personas()
self.get_v3_persona_data()
async def get_folder_tree(self) -> list[dict]:
"""获取文件夹树形结构
+39
View File
@@ -24,6 +24,7 @@ class PersonaRoute(Route):
"/persona/update": ("POST", self.update_persona),
"/persona/delete": ("POST", self.delete_persona),
"/persona/move": ("POST", self.move_persona),
"/persona/reorder": ("POST", self.reorder_items),
# Folder routes
"/persona/folder/list": ("GET", self.list_folders),
"/persona/folder/tree": ("GET", self.get_folder_tree),
@@ -368,3 +369,41 @@ class PersonaRoute(Route):
except Exception as e:
logger.error(f"删除文件夹失败: {e!s}\n{traceback.format_exc()}")
return Response().error(f"删除文件夹失败: {e!s}").__dict__
async def reorder_items(self):
"""批量更新排序顺序
请求体格式:
{
"items": [
{"id": "persona_id_1", "type": "persona", "sort_order": 0},
{"id": "persona_id_2", "type": "persona", "sort_order": 1},
{"id": "folder_id_1", "type": "folder", "sort_order": 0},
...
]
}
"""
try:
data = await request.get_json()
items = data.get("items", [])
if not items:
return Response().error("items 不能为空").__dict__
# 验证每个 item 的格式
for item in items:
if not all(k in item for k in ("id", "type", "sort_order")):
return Response().error(
"每个 item 必须包含 id, type, sort_order 字段"
).__dict__
if item["type"] not in ("persona", "folder"):
return Response().error(
"type 字段必须是 'persona''folder'"
).__dict__
await self.persona_mgr.batch_update_sort_order(items)
return Response().ok({"message": "排序更新成功"}).__dict__
except Exception as e:
logger.error(f"更新排序失败: {e!s}\n{traceback.format_exc()}")
return Response().error(f"更新排序失败: {e!s}").__dict__