Files
AstrBot/astrbot/dashboard/services/chatui_project_service.py
Weilong Liao 0d8e8682db refactor(core): migrate backend backbone from Quart to FastAPI and introduce more OpenAPI (#8688)
* refactor: migrate to fastapi

* structure refactor

* fix: pyright fix

* refactor: improve error handling and public messages in plugin services

* feat(api): refactor API client integration and enhance request handling

- Updated API client configuration to use a dedicated HTTP client.
- Introduced utility functions for generating options, queries, and form data for API requests.
- Refactored multiple API methods to utilize the new utility functions for improved consistency and readability.
- Renamed types for clarity and updated import statements accordingly.

feat(docs): add script to update OpenAPI JSON from YAML spec

- Created a Python script to convert OpenAPI YAML specification to JSON format.
- The script supports customizable input and output paths.
- Ensured the script handles directory creation for output paths and validates the YAML structure.

* fix

* feat(auth): implement rate limiting for v1 login endpoint and enhance request handling

* Refactor dashboard API routers to use legacy_router for backward compatibility

- Changed all instances of dashboard_router to legacy_router across multiple API modules including platform, plugins, providers, sessions, skills, stats, subagents, t2i, tools, updates, and asgi_runtime.
- Updated route definitions to ensure existing endpoints remain functional under the new router structure.
- Introduced support for Quart request context in asgi_runtime to enhance compatibility with existing Quart-based plugins.
- Added a test case to validate the functionality of the new Quart request context handling in plugin extensions.

* chore: remove cli test

* fix: update dashboard tests for fastapi migration

* chore: satisfy ruff checks

* fix: update openapi api key scopes

* fix: sync config scope chip selection

* fix: restore quart dependency

* docs: clarify quart plugin api compatibility

* docs: update openapi scope documentation

* fix: use singular skill openapi scope

* fix: hide update service exception details

* fix: address fastapi review comments

* fix: address dashboard review findings

* docs: revert unrelated package deployment changes

* docs: update agent api generation guidance

* feat: add plugin page web api helpers

* docs: add plugin page bridge demo

* fix: type plugin upload files

* fix: stabilize plugin page uploads

* fix: type plugin web request proxy

* docs: remove plugin page docs example

* fix: authenticate plugin page SSE bridge
2026-06-14 15:03:26 +08:00

163 lines
5.8 KiB
Python

from __future__ import annotations
from astrbot.core.db import BaseDatabase
from astrbot.core.utils.datetime_utils import to_utc_isoformat
class ChatUIProjectServiceError(Exception):
pass
class ChatUIProjectService:
def __init__(self, db: BaseDatabase) -> None:
self.db = db
async def create_project(self, username: str, data: object) -> dict:
payload = self._as_payload(data)
title = payload.get("title")
emoji = payload.get("emoji", "📁")
description = payload.get("description")
if not title:
raise ChatUIProjectServiceError("Missing key: title")
project = await self.db.create_chatui_project(
creator=username,
title=title,
emoji=emoji,
description=description,
)
return self._serialize_project(project)
async def list_projects(self, username: str) -> list[dict]:
projects = await self.db.get_chatui_projects_by_creator(creator=username)
return [self._serialize_project(project) for project in projects]
async def get_project(self, username: str, project_id: str | None) -> dict:
if not project_id:
raise ChatUIProjectServiceError("Missing key: project_id")
project = await self._get_owned_project(username, project_id)
return self._serialize_project(project)
async def get_project_from_query(
self,
username: str,
project_id: str | None,
) -> dict:
return await self.get_project(username, project_id)
async def update_project(self, username: str, data: object) -> None:
payload = self._as_payload(data)
project_id = payload.get("project_id")
if not project_id:
raise ChatUIProjectServiceError("Missing key: project_id")
await self._get_owned_project(username, project_id)
await self.db.update_chatui_project(
project_id=project_id,
title=payload.get("title"),
emoji=payload.get("emoji"),
description=payload.get("description"),
)
async def delete_project(self, username: str, project_id: str | None) -> None:
if not project_id:
raise ChatUIProjectServiceError("Missing key: project_id")
await self._get_owned_project(username, project_id)
await self.db.delete_chatui_project(project_id)
async def delete_project_from_query(
self,
username: str,
project_id: str | None,
) -> None:
await self.delete_project(username, project_id)
async def add_session_to_project(self, username: str, data: object) -> None:
payload = self._as_payload(data)
session_id = payload.get("session_id")
project_id = payload.get("project_id")
if not session_id:
raise ChatUIProjectServiceError("Missing key: session_id")
if not project_id:
raise ChatUIProjectServiceError("Missing key: project_id")
await self._get_owned_project(username, project_id)
await self._get_owned_session(username, session_id)
await self.db.add_session_to_project(session_id, project_id)
async def remove_session_from_project(self, username: str, data: object) -> None:
payload = self._as_payload(data)
session_id = payload.get("session_id")
if not session_id:
raise ChatUIProjectServiceError("Missing key: session_id")
await self._get_owned_session(username, session_id)
await self.db.remove_session_from_project(session_id)
async def get_project_sessions(
self,
username: str,
project_id: str | None,
) -> list[dict]:
if not project_id:
raise ChatUIProjectServiceError("Missing key: project_id")
await self._get_owned_project(username, project_id)
sessions = await self.db.get_project_sessions(project_id)
return [self._serialize_session(session) for session in sessions]
async def get_project_sessions_from_query(
self,
username: str,
project_id: str | None,
) -> list[dict]:
return await self.get_project_sessions(username, project_id)
async def _get_owned_project(self, username: str, project_id: str):
project = await self.db.get_chatui_project_by_id(project_id)
if not project:
raise ChatUIProjectServiceError(f"Project {project_id} not found")
if project.creator != username:
raise ChatUIProjectServiceError("Permission denied")
return project
async def _get_owned_session(self, username: str, session_id: str):
session = await self.db.get_platform_session_by_id(session_id)
if not session:
raise ChatUIProjectServiceError(f"Session {session_id} not found")
if session.creator != username:
raise ChatUIProjectServiceError("Permission denied")
return session
@staticmethod
def _serialize_project(project) -> dict:
return {
"project_id": project.project_id,
"title": project.title,
"emoji": project.emoji,
"description": project.description,
"created_at": to_utc_isoformat(project.created_at),
"updated_at": to_utc_isoformat(project.updated_at),
}
@staticmethod
def _serialize_session(session) -> dict:
return {
"session_id": session.session_id,
"platform_id": session.platform_id,
"creator": session.creator,
"display_name": session.display_name,
"is_group": session.is_group,
"created_at": to_utc_isoformat(session.created_at),
"updated_at": to_utc_isoformat(session.updated_at),
}
@staticmethod
def _as_payload(data: object) -> dict:
return data if isinstance(data, dict) else {}