feat(canvas): 画布工作区 - 整合侧栏与风格预设并修复提示词回填
选择性整合 #547,保留主分支节点与工具条设计。新增默认折叠的节点、资产、任务和历史面板,风格与预设保留在节点内,移除重复入口。 补齐工具权限、提示词解析、资源引用保护及数据库迁移,修复提示词清空和连续编辑后的生成配置同步。 验证:前端全量 1925 项测试、构建、类型检查及 lint 通过;后端 go test ./... 通过。真实模型、PostgreSQL 实库与登录态页面验收仍待进行。 Refs #547 Co-authored-by: bensharp <bensharp@qq.com>
This commit is contained in:
@@ -37,6 +37,7 @@ func RegisterCanvasAPI(api *gin.RouterGroup, svc *service.Service) {
|
||||
RegisterTaskRoutes(api, svc)
|
||||
RegisterRunningHubRoutes(api, svc)
|
||||
RegisterSkillRoutes(api, svc)
|
||||
RegisterToolRoutes(api, svc)
|
||||
RegisterUserDataRoutes(api, svc)
|
||||
RegisterChunkedUploadRoutes(api, svc)
|
||||
RegisterDiagnosticsRoutes(api, svc)
|
||||
|
||||
@@ -26,6 +26,62 @@ tags:
|
||||
- name: agent
|
||||
- name: admin
|
||||
paths:
|
||||
/tools:
|
||||
get:
|
||||
tags: [canvas]
|
||||
summary: 列出可见且启用的生成工具
|
||||
parameters:
|
||||
- {in: query, name: scope, schema: {type: string, enum: [public, favorites, custom], default: public}}
|
||||
- {in: query, name: type, schema: {type: string, enum: [style, motion, nine_grid, effect]}}
|
||||
- {in: query, name: tag, schema: {type: string}}
|
||||
- {in: query, name: search, schema: {type: string}}
|
||||
- {in: query, name: page, schema: {type: integer, minimum: 1, default: 1}}
|
||||
- {in: query, name: pageSize, schema: {type: integer, minimum: 1, maximum: 80, default: 20}}
|
||||
responses:
|
||||
"200": {description: "标准信封 data 为 tools(摘要不含 prompt/extraInfo)、totalCount、page、pageSize、hasMore"}
|
||||
"401": {description: 未登录}
|
||||
post:
|
||||
tags: [canvas]
|
||||
summary: 创建当前用户的工具
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ToolMutation'}
|
||||
responses:
|
||||
"200": {description: 标准信封 data 为完整工具}
|
||||
"400": {description: 参数无效或预览资源不符合要求}
|
||||
"401": {description: 未登录}
|
||||
/tools/{id}:
|
||||
parameters:
|
||||
- {in: path, name: id, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||
get:
|
||||
tags: [canvas]
|
||||
summary: 查询可见且启用工具的完整详情
|
||||
responses:
|
||||
"200": {description: 标准信封 data 为完整工具,包含 prompt 和 extraInfo}
|
||||
"404": {description: 工具不存在或不可见}
|
||||
delete:
|
||||
tags: [canvas]
|
||||
summary: 删除自己的用户工具及关联收藏,不删除预览文件
|
||||
responses:
|
||||
"200": {description: "标准信封 data 为 {deleted: true}"}
|
||||
"404": {description: 工具不存在或不能删除}
|
||||
/tools/{id}/favorite:
|
||||
parameters:
|
||||
- {in: path, name: id, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||
post:
|
||||
tags: [canvas]
|
||||
summary: 幂等收藏可见工具
|
||||
responses:
|
||||
"200": {description: 标准信封 data 为完整工具和收藏状态}
|
||||
"404": {description: 工具不存在或不可见}
|
||||
delete:
|
||||
tags: [canvas]
|
||||
summary: 幂等取消收藏可见工具
|
||||
responses:
|
||||
"200": {description: 标准信封 data 为完整工具和收藏状态}
|
||||
"404": {description: 工具不存在或不可见}
|
||||
/model-catalog:
|
||||
get:
|
||||
tags: [models]
|
||||
@@ -971,6 +1027,20 @@ components:
|
||||
in: cookie
|
||||
name: open_ai_canvas_session
|
||||
schemas:
|
||||
ToolMutation:
|
||||
type: object
|
||||
required: [type, label, prompt]
|
||||
properties:
|
||||
type: {type: string, enum: [style, motion, nine_grid, effect]}
|
||||
label: {type: string, description: 名称最多 120 字节}
|
||||
prompt: {type: string, description: 最多 8000 字节,不允许嵌套工具标签}
|
||||
visibility: {type: string, enum: [private, public], default: private}
|
||||
desc: {type: string}
|
||||
tag: {type: string}
|
||||
cover: {type: string, description: 本人已上传完成的资源 URL,仅限私有工具}
|
||||
mediaUrl: {type: string, description: 本人已上传完成的资源 URL,仅限私有工具}
|
||||
extraInfo: {type: array, items: {type: string}}
|
||||
ratio: {type: string}
|
||||
ModelRequestIntent:
|
||||
type: object
|
||||
required: [capability]
|
||||
|
||||
137
backend/internal/handler/tools.go
Normal file
137
backend/internal/handler/tools.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"infinite-canvas/backend/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterToolRoutes 注册画布工具 API:查询、收藏、新增、删除。
|
||||
func RegisterToolRoutes(r *gin.RouterGroup, svc *service.Service) {
|
||||
r.GET("/tools", func(c *gin.Context) {
|
||||
user, err := currentUser(c, svc)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
page, pageSize, err := parsePaginationQuery(c, 20)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
result, err := svc.Tools(user.ID, service.ToolListRequest{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Scope: c.DefaultQuery("scope", "public"),
|
||||
Type: c.Query("type"),
|
||||
Tag: c.Query("tag"),
|
||||
Search: c.Query("search"),
|
||||
})
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
ok(c, result)
|
||||
})
|
||||
|
||||
r.GET("/tools/:id", func(c *gin.Context) {
|
||||
user, err := currentUser(c, svc)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
toolID, err := parseToolID(c)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
item, err := svc.ToolDetail(user.ID, toolID)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
ok(c, item)
|
||||
})
|
||||
|
||||
r.POST("/tools", func(c *gin.Context) {
|
||||
user, err := currentUser(c, svc)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 64<<10)
|
||||
var req service.ToolMutationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
failService(c, service.BadAuthRequest("请求体格式无效"))
|
||||
return
|
||||
}
|
||||
item, err := svc.CreateTool(user.ID, req)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
ok(c, item)
|
||||
})
|
||||
|
||||
r.POST("/tools/:id/favorite", func(c *gin.Context) {
|
||||
setToolFavorite(c, svc, true)
|
||||
})
|
||||
|
||||
r.DELETE("/tools/:id/favorite", func(c *gin.Context) {
|
||||
setToolFavorite(c, svc, false)
|
||||
})
|
||||
|
||||
r.DELETE("/tools/:id", func(c *gin.Context) {
|
||||
user, err := currentUser(c, svc)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
toolID, err := parseToolID(c)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if err := svc.DeleteTool(user.ID, toolID); err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
ok(c, gin.H{"deleted": true})
|
||||
})
|
||||
}
|
||||
|
||||
func setToolFavorite(c *gin.Context, svc *service.Service, favorite bool) {
|
||||
user, err := currentUser(c, svc)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
toolID, err := parseToolID(c)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
item, err := svc.SetToolFavorite(user.ID, toolID, favorite)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
ok(c, item)
|
||||
}
|
||||
|
||||
func parseToolID(c *gin.Context) (int64, error) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
return 0, errInvalidToolID
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
type toolIDError string
|
||||
|
||||
func (e toolIDError) Error() string { return string(e) }
|
||||
|
||||
const errInvalidToolID = toolIDError("工具 ID 无效")
|
||||
Reference in New Issue
Block a user