feat:优化演示 #15

Merged
jiawei merged 1 commits from feat/0.3.0 into main 2026-10-18 20:27:34 +08:00
+111 -2
View File
@@ -1,14 +1,24 @@
"""匿名接口,统一放在 /api/public 下,一眼能看出哪些不鉴权""" """匿名接口,统一放在 /api/public 下,一眼能看出哪些不鉴权"""
from typing import Annotated, Any from typing import Annotated, Any, Literal
from fastapi import APIRouter, Query, Response from fastapi import APIRouter, Depends, Query, Request, Response
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from app.api.files import MAX_OFFSET, MAX_PAGE_ROWS, _detail, _public, _service
from app.api.screens import Service, thumbnail_url from app.api.screens import Service, thumbnail_url
from app.common.response import ApiResponse, success_response from app.common.response import ApiResponse, success_response
from app.config import settings from app.config import settings
from app.core.exceptions import BizException from app.core.exceptions import BizException
from app.schemas.conversation import ConversationDetail, ConversationList
from app.schemas.data_file import (
AggregateRequest,
AggregateResult,
DataFileDetail,
DataFileInfo,
DataFileList,
DataTableRows,
)
from app.schemas.saved_screen import ( from app.schemas.saved_screen import (
DemoScreen, DemoScreen,
DemoScreenList, DemoScreenList,
@@ -56,6 +66,105 @@ async def list_demo_screens(
) )
def demo_owner() -> int:
"""演示账号的用户 ID;没配置时没有演示数据,按不存在处理"""
owner_id = settings.demo_owner_id
if owner_id is None:
raise BizException(404, "没有演示数据")
return owner_id
DemoOwner = Annotated[int, Depends(demo_owner)]
# 以下是演示账号的对话和数据文件,只读:形状与登录后的 /conversations、/files 一致,
# 前端未登录时换个前缀就能复用;归属一律按演示账号校验,别人的会话和文件与不存在同样是 404
@router.get("/demo/conversations")
async def list_demo_conversations(
request: Request,
screen_id: Annotated[str, Query(min_length=1)],
limit: Annotated[int, Query(ge=1, le=20)] = 1,
) -> ApiResponse[ConversationList]:
"""演示大屏下的会话,最近更新的在前;没配演示账号时为空"""
owner_id = settings.demo_owner_id
if owner_id is None:
return success_response(ConversationList(conversations=[], total=0))
store = request.app.state.conversation_store
return success_response(
ConversationList(
conversations=await store.list_by_user(owner_id, limit, 0, screen_id),
total=await store.count_by_user(owner_id, screen_id),
)
)
@router.get("/demo/conversations/{conversation_id}")
async def get_demo_conversation(
conversation_id: str, request: Request, owner_id: DemoOwner
) -> ApiResponse[ConversationDetail]:
store = request.app.state.conversation_store
if not await store.exists(conversation_id, owner_id):
raise BizException(404, "会话不存在或已过期")
turns = await store.load_turns(conversation_id, None)
return success_response(
ConversationDetail(conversation_id=conversation_id, turns=turns)
)
@router.get("/demo/files")
async def list_demo_files(
request: Request,
conversation_id: Annotated[str, Query(min_length=1)],
owner_id: DemoOwner,
) -> ApiResponse[DataFileList]:
# 不带上传任务:那是演示账号自己正在进行的,访客不该看到
records = await _service(request).list_files(owner_id, conversation_id)
return success_response(
DataFileList(
files=[DataFileInfo.model_validate(_public(record)) for record in records]
)
)
@router.get("/demo/files/{file_id}")
async def get_demo_file(
file_id: str, request: Request, owner_id: DemoOwner
) -> ApiResponse[DataFileDetail]:
return success_response(_detail(await _service(request).get_file(owner_id, file_id)))
@router.get("/demo/files/{file_id}/tables/{table}/rows")
async def get_demo_table_rows(
file_id: str,
table: str,
request: Request,
owner_id: DemoOwner,
offset: Annotated[int, Query(ge=0, le=MAX_OFFSET)] = 0,
limit: Annotated[int, Query(ge=1, le=MAX_PAGE_ROWS)] = 100,
sort_by: Annotated[str | None, Query(min_length=1)] = None,
order: Annotated[Literal["asc", "desc"], Query()] = "asc",
) -> ApiResponse[DataTableRows]:
return success_response(
await _service(request).get_rows(
owner_id, file_id, table, offset, limit, sort_by, order == "desc"
)
)
@router.post("/demo/files/{file_id}/tables/{table}/aggregate")
async def aggregate_demo_table(
file_id: str,
table: str,
body: AggregateRequest,
request: Request,
owner_id: DemoOwner,
) -> ApiResponse[AggregateResult]:
"""POST 但只读:汇总结果算好就返回,不改任何数据"""
return success_response(
await _service(request).aggregate(owner_id, file_id, table, body)
)
@router.get("/screens/{share_id}") @router.get("/screens/{share_id}")
async def get_public_screen( async def get_public_screen(
share_id: str, service: Service share_id: str, service: Service