Files
NovalRedot/backend/app/routers/skills.py
ROOG b39377e487
All checks were successful
Deploy Noval to K3s / deploy (push) Successful in 47s
新增文件管理与技能管理功能:
- **文件管理**:
  - 支持文件上传、解析和删除。
  - 限制支持格式(txt, md, docx, pdf),最大 10MB。
  - 提供前端上传区、文件列表及后端解析服务。

- **技能管理**:
  - 新增技能的创建、更新及删除功能。
  - 支持分配自定义提示词和工具权限。
  - 提供技能列表及分组工具选择交互界面。

同时调整相关 API 和前端组件,优化协作体验。
2026-03-22 23:50:14 +08:00

112 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Skill 插件管理路由"""
import json
from datetime import datetime, timezone
from fastapi import APIRouter, HTTPException
from sqlmodel import Session, select, or_
from ..database import engine
from ..models import Skill
from ..schemas import SkillCreate, SkillUpdate, SkillRead, SkillList, ToolInfo
from ..services.tools import TOOL_DEFINITIONS
router = APIRouter(prefix="/api/skills", tags=["skills"])
def _skill_to_read(skill: Skill) -> SkillRead:
"""将 Skill ORM 对象转为 SkillRead反序列化 allowed_tools"""
try:
tools = json.loads(skill.allowed_tools) if skill.allowed_tools else []
except (json.JSONDecodeError, TypeError):
tools = []
return SkillRead(
id=skill.id, # type: ignore
name=skill.name,
description=skill.description,
system_prompt=skill.system_prompt,
allowed_tools=tools,
is_builtin=skill.is_builtin,
novel_id=skill.novel_id,
created_at=skill.created_at,
updated_at=skill.updated_at,
)
@router.get("", response_model=SkillList)
def list_skills(novel_id: int | None = None):
"""列出 Skill返回全局 + 指定小说的"""
with Session(engine) as session:
if novel_id is not None:
stmt = (
select(Skill)
.where(or_(Skill.novel_id.is_(None), Skill.novel_id == novel_id)) # type: ignore
.order_by(Skill.is_builtin.desc(), Skill.created_at) # type: ignore
)
else:
stmt = select(Skill).order_by(Skill.is_builtin.desc(), Skill.created_at) # type: ignore
skills = session.exec(stmt).all()
items = [_skill_to_read(s) for s in skills]
return SkillList(items=items, total=len(items))
@router.post("", response_model=SkillRead, status_code=201)
def create_skill(data: SkillCreate):
"""创建 Skill"""
with Session(engine) as session:
skill = Skill(
name=data.name,
description=data.description,
system_prompt=data.system_prompt,
allowed_tools=json.dumps(data.allowed_tools, ensure_ascii=False),
is_builtin=False,
novel_id=data.novel_id,
)
session.add(skill)
session.commit()
session.refresh(skill)
return _skill_to_read(skill)
@router.get("/tools", response_model=list[ToolInfo])
def get_available_tools():
"""返回所有可用工具名和描述(供前端工具 checkbox 使用)"""
return [
ToolInfo(name=t["name"], description=t["description"])
for t in TOOL_DEFINITIONS
]
@router.patch("/{skill_id}", response_model=SkillRead)
def update_skill(skill_id: int, data: SkillUpdate):
"""更新 Skill"""
with Session(engine) as session:
skill = session.get(Skill, skill_id)
if not skill:
raise HTTPException(status_code=404, detail="Skill 不存在")
if data.name is not None:
skill.name = data.name
if data.description is not None:
skill.description = data.description
if data.system_prompt is not None:
skill.system_prompt = data.system_prompt
if data.allowed_tools is not None:
skill.allowed_tools = json.dumps(data.allowed_tools, ensure_ascii=False)
skill.updated_at = datetime.now(timezone.utc)
session.add(skill)
session.commit()
session.refresh(skill)
return _skill_to_read(skill)
@router.delete("/{skill_id}", status_code=204)
def delete_skill(skill_id: int):
"""删除 Skill"""
with Session(engine) as session:
skill = session.get(Skill, skill_id)
if not skill:
raise HTTPException(status_code=404, detail="Skill 不存在")
session.delete(skill)
session.commit()