- 天工阁域 domains/tiangong:backstage_tasks(委托包+验收标准+结果证据);有限分解(parent 链)与受控并行(各自后台 Run);子任务全终态级联父任务进入待验收;按证据 accept/reject - kernel.start_backstage_run(kind=background_task):loop 后台分支不占前台席位、与前台对话并行;断言放宽容纳无人物功能任务;结果只经回调回呈任务——无身份写入路径 - 叩心域 domains/kouxin:knocking_candidates(event/time_anchor/manual 触发 + 草稿 + 裁决快照);四约束裁决(总开关→静默窗口【本地时区判定】→24h 频率→预算),dispatch 未过 429 且快照留痕;候选默认不自动发送;expire_stale 过期推进不触碰缘契 - 辰正 domains/zhengchen:temporal_context() 复用 time_sense 计算核(快照/农历/季节),产出【时态感知】投影段接入镜树生成 prompt(客观时间语境,不附情绪臆测——守一纪律) - 退役:modules/goal_orchestrator|planner|perception|big_cycle、application/planning、services/governance|goal_persist、candidates 表链(imports 候选抽取 API 面)、goals/plans/agent_roster/perception_channels/big_cycles/旧候选表(迁移 009);前端 admin goals/plans/agents/perception/world 页与六组件;governance 只留 GenerationPreferences;admin 治理面收敛为司命+百工两枢;pivots 健康以域表计数重建 - 验收(test_m8_tiangong 五用例):后台任务无身份写入(记忆/魂籍/缘契零新增);后台与前台并行;分解级联;叩心四约束逐项;不回应不降低关系(缘契维度/状态/版本零变化) - 四套件:后端 345、前端 203、BFF 4、神识 5 包全绿(2 个预存环境失败不变);迁移 009 净库干净
106 lines
3.5 KiB
Python
106 lines
3.5 KiB
Python
"""资料导入 API(api-spec §8)。
|
||
|
||
JSON 提交与文件上传分两个端点(FastAPI 无法优雅地在单端点同时吃 JSON body
|
||
与 multipart);两者返回结构一致。路径挂在 ``/characters/{character_id}`` 下。
|
||
候选抽取为异步后台任务:触发即返回 job 状态,前端轮询 ``GET /imports/{id}``
|
||
(extracting_* → reviewing)与候选列表获取结果。
|
||
"""
|
||
|
||
from fastapi import APIRouter, Depends, File, Form, Query, UploadFile
|
||
|
||
from app.api.v1.deps import get_current_user
|
||
from app.schemas.auth import CurrentUser
|
||
from app.schemas.common import Page
|
||
from app.schemas.imports import (
|
||
CreateImportRequest,
|
||
ImportItemResponse,
|
||
ImportJobResponse,
|
||
)
|
||
from app.services.imports import (
|
||
create_import_from_json,
|
||
create_import_from_upload,
|
||
get_import,
|
||
list_import_items,
|
||
list_jobs_page,
|
||
)
|
||
|
||
router = APIRouter(tags=["imports"])
|
||
|
||
|
||
@router.post("/characters/{character_id}/imports", response_model=ImportJobResponse)
|
||
async def create_import_json(
|
||
character_id: str,
|
||
payload: CreateImportRequest,
|
||
current_user: CurrentUser = Depends(get_current_user),
|
||
) -> ImportJobResponse:
|
||
return await create_import_from_json(
|
||
character_id, payload, owner_scope=current_user.owner_scope
|
||
)
|
||
|
||
|
||
@router.post("/characters/{character_id}/imports/upload", response_model=ImportJobResponse)
|
||
async def create_import_upload(
|
||
character_id: str,
|
||
file: UploadFile = File(..., description="上传文件(文本或音频)。"),
|
||
title: str | None = Form(default=None),
|
||
source_description: str | None = Form(default=None),
|
||
speaker_label: str | None = Form(default=None, description="音频说话人标签。"),
|
||
consent_status: str = Form(default="unknown"),
|
||
usage_scope: str = Form(default="reference_only"),
|
||
current_user: CurrentUser = Depends(get_current_user),
|
||
) -> ImportJobResponse:
|
||
return await create_import_from_upload(
|
||
character_id,
|
||
file,
|
||
title=title,
|
||
source_description=source_description,
|
||
speaker_label=speaker_label,
|
||
consent_status=consent_status,
|
||
usage_scope=usage_scope,
|
||
owner_scope=current_user.owner_scope,
|
||
)
|
||
|
||
|
||
@router.get("/characters/{character_id}/imports", response_model=Page[ImportJobResponse])
|
||
async def list_imports(
|
||
character_id: str,
|
||
page: int = Query(default=1, ge=1),
|
||
page_size: int = Query(default=20, ge=1, le=100),
|
||
current_user: CurrentUser = Depends(get_current_user),
|
||
) -> Page[ImportJobResponse]:
|
||
return await list_jobs_page(
|
||
character_id, page=page, page_size=page_size, owner_scope=current_user.owner_scope
|
||
)
|
||
|
||
|
||
@router.get(
|
||
"/characters/{character_id}/imports/{import_id}",
|
||
response_model=ImportJobResponse,
|
||
)
|
||
async def read_import(
|
||
character_id: str,
|
||
import_id: str,
|
||
current_user: CurrentUser = Depends(get_current_user),
|
||
) -> ImportJobResponse:
|
||
return await get_import(character_id, import_id, owner_scope=current_user.owner_scope)
|
||
|
||
|
||
@router.get(
|
||
"/characters/{character_id}/imports/{import_id}/items",
|
||
response_model=Page[ImportItemResponse],
|
||
)
|
||
async def read_import_items(
|
||
character_id: str,
|
||
import_id: str,
|
||
page: int = Query(default=1, ge=1),
|
||
page_size: int = Query(default=20, ge=1, le=100),
|
||
current_user: CurrentUser = Depends(get_current_user),
|
||
) -> Page[ImportItemResponse]:
|
||
return await list_import_items(
|
||
character_id,
|
||
import_id,
|
||
page=page,
|
||
page_size=page_size,
|
||
owner_scope=current_user.owner_scope,
|
||
)
|