feat(skills): 技能库 - 内置技能目录迁移为 Markdown 包

This commit is contained in:
ddcat666 committed 2026-09-27 09:45:38 +08:00
1 parent eb43d3754f
commit 1080b22196
107 files changed
+15402 -3667

No files matched your search

+74
View File
@@ -25,6 +25,7 @@ tags:
- name: models
- name: agent
- name: admin
- name: skills
paths:
/admin/settings/appearance/live2d:
post:
@@ -123,6 +124,79 @@ paths:
"200":
description: 标准信封 data 为 presets 数组;预设只引用公开且启用的种子技能
"500": {$ref: "#/components/responses/Error"}
/skills:
get:
tags: [skills]
summary: 列出技能;在个人技能视图按技能库分类筛选
parameters:
- {in: query, name: scope, schema: {type: string, enum: [public, mine, created, favorites], default: public}}
- {in: query, name: libraryCategoryId, description: 用户技能库分类 ID,仅支持 mine 或 created 视图;与 libraryUncategorized 互斥, schema: {type: string}}
- {in: query, name: libraryUncategorized, description: 仅列出未归类技能,仅支持 mine 或 created 视图;与 libraryCategoryId 互斥, schema: {type: boolean}}
responses:
"200": {description: 标准信封 data 为技能列表及分页信息;categories 含共享分类 value、label 和技能广场公开技能 count}
"400": {$ref: "#/components/responses/Error"}
"401": {$ref: "#/components/responses/Error"}
"403": {$ref: "#/components/responses/Error"}
"404": {$ref: "#/components/responses/Error"}
/skills/library-categories:
get:
tags: [skills]
summary: 获取当前用户可见的技能库分类和数量
parameters:
- {in: query, name: scope, schema: {type: string, enum: [mine, created], default: mine}}
responses:
"200": {description: 标准信封 data 为 categories、totalCount、uncategorizedCount;分类含 id、name、scope、count}
"401": {$ref: "#/components/responses/Error"}
post:
tags: [skills]
summary: 创建个人或平台技能库分类
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: {type: string, minLength: 1, maxLength: 64}
scope: {type: string, enum: [personal, platform], default: personal}
responses:
"200": {description: 标准信封 data.category 为新分类}
"400": {$ref: "#/components/responses/Error"}
"401": {$ref: "#/components/responses/Error"}
"403": {$ref: "#/components/responses/Error"}
/skills/library-categories/{id}:
parameters:
- {in: path, name: id, required: true, schema: {type: string}}
delete:
tags: [skills]
summary: 删除技能库分类并解除相关技能归类,不删除技能
responses:
"200": {description: "标准信封 data 为 {deleted: true}"}
"401": {$ref: "#/components/responses/Error"}
"403": {$ref: "#/components/responses/Error"}
"404": {$ref: "#/components/responses/Error"}
/skills/{id}/library-category:
parameters:
- {in: path, name: id, required: true, description: 技能 ID, schema: {type: string}}
patch:
tags: [skills]
summary: 为当前用户已加入或自建的技能设置/清除技能库分类
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [categoryId]
properties:
categoryId: {type: string, description: 个人分类或可见平台分类 ID;空字符串表示取消归类}
responses:
"200": {description: 标准信封 data.skill 为更新后的技能}
"400": {$ref: "#/components/responses/Error"}
"401": {$ref: "#/components/responses/Error"}
"403": {$ref: "#/components/responses/Error"}
"404": {$ref: "#/components/responses/Error"}
/tools/{id}:
parameters:
- {in: path, name: id, required: true, schema: {type: integer, format: int64, minimum: 1}}
+82
View File
@@ -1,12 +1,14 @@
package handler
import (
"errors"
"mime"
"net/http"
"path/filepath"
"strconv"
"strings"
"infinite-canvas/backend/internal/model"
"infinite-canvas/backend/internal/service"
"github.com/gin-gonic/gin"
@@ -80,9 +82,15 @@ func RegisterSkillRoutes(r *gin.RouterGroup, svc *service.Service) {
fail(c, http.StatusBadRequest, err)
return
}
libraryUncategorized, err := parseOptionalBool(c.Query("libraryUncategorized"))
if err != nil {
failService(c, service.BadAuthRequest("libraryUncategorized 必须是布尔值"))
return
}
result, err := svc.Skills(user.ID, service.SkillListRequest{
Page: page, PageSize: pageSize, Scope: c.DefaultQuery("scope", "public"),
Search: c.Query("search"), Tag: c.Query("tag"), Sort: c.DefaultQuery("sort", "popular"),
LibraryCategoryID: c.Query("libraryCategoryId"), LibraryUncategorized: libraryUncategorized,
})
if err != nil {
failService(c, err)
@@ -91,6 +99,52 @@ func RegisterSkillRoutes(r *gin.RouterGroup, svc *service.Service) {
ok(c, result)
})
r.GET("/skills/library-categories", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
result, err := svc.SkillLibraryCategories(user.ID, c.DefaultQuery("scope", "mine"))
if err != nil {
failService(c, err)
return
}
ok(c, result)
})
r.POST("/skills/library-categories", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
var req service.SkillLibraryCategoryMutationRequest
if err := c.ShouldBindJSON(&req); err != nil {
failService(c, service.BadAuthRequest("技能库分类数据格式无效"))
return
}
category, err := svc.CreateSkillLibraryCategory(user, req)
if err != nil {
failService(c, err)
return
}
ok(c, gin.H{"category": category})
})
r.DELETE("/skills/library-categories/:id", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
if err := svc.DeleteSkillLibraryCategory(user, c.Param("id")); err != nil {
failService(c, err)
return
}
ok(c, gin.H{"deleted": true})
})
r.GET("/skills/presets", func(c *gin.Context) {
// 场景预设目录:公开只读的市场元数据(与 /openapi.yaml 同模式,无用户数据)。
presets, err := svc.SkillPresets()
@@ -129,6 +183,25 @@ func RegisterSkillRoutes(r *gin.RouterGroup, svc *service.Service) {
ok(c, gin.H{"skill": skill})
})
r.PATCH("/skills/:id/library-category", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
var req service.SkillLibraryCategoryAssignmentRequest
if err := c.ShouldBindJSON(&req); err != nil {
failService(c, service.BadAuthRequest("技能库分类数据格式无效"))
return
}
skill, err := svc.SetSkillLibraryCategory(user.ID, c.Param("id"), req.CategoryID)
if err != nil {
failService(c, err)
return
}
ok(c, gin.H{"skill": skill})
})
r.GET("/skills/:id/files", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
@@ -262,6 +335,15 @@ func RegisterSkillRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
if user.Role == model.UserRoleAdmin {
if err := svc.DeleteBuiltinSkill(user, c.Param("id")); err == nil {
ok(c, gin.H{"deleted": true})
return
} else if !errors.Is(err, service.ErrNotBuiltinSkill) {
failService(c, err)
return
}
}
if err := svc.DeleteSkill(user.ID, c.Param("id")); err != nil {
failService(c, err)
return