feat(canvas): 画布工作区 - 整合侧栏与风格预设并修复提示词回填

选择性整合 #547,保留主分支节点与工具条设计。新增默认折叠的节点、资产、任务和历史面板,风格与预设保留在节点内,移除重复入口。

补齐工具权限、提示词解析、资源引用保护及数据库迁移,修复提示词清空和连续编辑后的生成配置同步。

验证:前端全量 1925 项测试、构建、类型检查及 lint 通过;后端 go test ./... 通过。真实模型、PostgreSQL 实库与登录态页面验收仍待进行。

Refs #547

Co-authored-by: bensharp <bensharp@qq.com>
This commit is contained in:
ddCat
2026-09-20 16:40:08 +08:00
parent be5f2733b6
commit e93bf3ca39
66 changed files with 6127 additions and 97 deletions

View 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 无效")