合并 0.3.6 分支全部内容 (36 提交, 详细历史见 0.3.6 分支): ■ 数据与插件 - TdxQuant 数据源: 通达信量化接入 (实时/五档/日K/复权/分钟), tdxaidata 收进默认依赖 - 扩展表两级拉取配方: 概念/行业出厂即带完整可拉配置, 可作自定义接入参考示例 - 能力门禁日志带生效源; 标的池解析只走 instruments 维表 ■ 页面扩展化 - 11 个官方页面全量迁移为单文件扩展 (custom/*/extension.tsx), 删目录即卸载 - 运行时页面包系统: 打包产物上传即挂载, 免重建宿主 - 新装用户扩展页默认隐藏, 老用户升级菜单无感 ■ AI - Responses 协议接入; 会话历史与长任务跨设备同步 (服务端注册表) - 助手头部快捷入口 + 悬浮球拖拽 zoom 坐标系修复 + 系统设置开关 ■ 界面 - 设置「关于」子菜单: 版权/更新/打赏 + 开源许可弹窗与合规声明双保险 - 侧栏两态收起、数据/AI 助手快捷行 (同步呼吸光晕)、核心/扩展 ext 标记 - 界面缩放默认 90%; 菜单设置徽标 Swiss 化; 策略池「叠加」分组 - 情绪评分口径可配置; 财务画像 5 张子表手动拉取 ■ 文档与其他 - README 重构「核心+扩展」主线 + 架构图重画; 开放端点数核实为 65 - 版本号 bump 0.3.6; tdx 物化库测试夹具补 macOS dylib
64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
"""进行中任务注册表 API — 前端 fire-and-forget 上报 + 页面挂载时查询。
|
|
|
|
PUT/DELETE 由任务启动/终态触发 (不阻塞 UI, 失败静默); GET 在任务页挂载时
|
|
最多调用一次。全部是小 JSON 文件读写, 不触碰任务执行路径。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.services import task_registry
|
|
|
|
router = APIRouter(prefix="/api/task-registry", tags=["task-registry"])
|
|
|
|
# id 由前端按 kind+参数指纹生成 (字母/数字/冒号/下划线/短横)
|
|
_VALID_ID = re.compile(r"^[A-Za-z0-9:_-]{1,128}$")
|
|
|
|
|
|
class TaskUpsertRequest(BaseModel):
|
|
kind: str = Field(max_length=32)
|
|
status: str = Field(max_length=16)
|
|
label: str = Field(default="", max_length=120)
|
|
payload: dict = Field(default_factory=dict)
|
|
|
|
|
|
def _data_dir(request: Request):
|
|
repo = getattr(request.app.state, "repo", None)
|
|
if repo is None:
|
|
raise HTTPException(status_code=503, detail="数据目录不可用")
|
|
return repo.store.data_dir
|
|
|
|
|
|
@router.get("")
|
|
def list_tasks(request: Request, kind: str | None = None) -> dict:
|
|
if kind is not None and kind not in task_registry.TASK_KINDS:
|
|
raise HTTPException(status_code=400, detail=f"未知的任务类型: {kind}")
|
|
return {"items": task_registry.list_items(_data_dir(request), kind)}
|
|
|
|
|
|
@router.put("/{task_id}")
|
|
def upsert_task(request: Request, task_id: str, req: TaskUpsertRequest) -> dict:
|
|
if not _VALID_ID.match(task_id):
|
|
raise HTTPException(status_code=400, detail="非法的任务 id")
|
|
try:
|
|
entry = task_registry.upsert(
|
|
_data_dir(request),
|
|
task_id,
|
|
kind=req.kind,
|
|
status=req.status,
|
|
label=req.label,
|
|
payload=req.payload,
|
|
)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
return {"ok": True, "item": entry}
|
|
|
|
|
|
@router.delete("/{task_id}")
|
|
def delete_task(request: Request, task_id: str) -> dict:
|
|
removed = task_registry.delete(_data_dir(request), task_id)
|
|
return {"ok": True, "removed": removed}
|