✨ 新增世界书模块
This commit is contained in:
468
server/api/v1/app/world_info.go
Normal file
468
server/api/v1/app/world_info.go
Normal file
@@ -0,0 +1,468 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"git.echol.cn/loser/st/server/middleware"
|
||||
"git.echol.cn/loser/st/server/model/app"
|
||||
"git.echol.cn/loser/st/server/model/app/request"
|
||||
"git.echol.cn/loser/st/server/model/app/response"
|
||||
sysResponse "git.echol.cn/loser/st/server/model/common/response"
|
||||
"git.echol.cn/loser/st/server/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type WorldInfoApi struct{}
|
||||
|
||||
var worldInfoService = service.ServiceGroupApp.AppServiceGroup.WorldInfoService
|
||||
|
||||
// CreateWorldBook 创建世界书
|
||||
// @Summary 创建世界书
|
||||
// @Description 创建一个新的世界书
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body request.CreateWorldBookRequest true "世界书信息"
|
||||
// @Success 200 {object} response.Response{data=app.AIWorldInfo}
|
||||
// @Router /app/worldbook [post]
|
||||
func (a *WorldInfoApi) CreateWorldBook(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
|
||||
var req request.CreateWorldBookRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
sysResponse.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
book, err := worldInfoService.CreateWorldBook(userID, &req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("创建失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithData(response.ToWorldBookResponse(book), c)
|
||||
}
|
||||
|
||||
// UpdateWorldBook 更新世界书
|
||||
// @Summary 更新世界书
|
||||
// @Description 更新世界书信息
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "世界书ID"
|
||||
// @Param data body request.UpdateWorldBookRequest true "世界书信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /app/worldbook/:id [put]
|
||||
func (a *WorldInfoApi) UpdateWorldBook(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
bookID := c.GetUint("id")
|
||||
|
||||
var req request.UpdateWorldBookRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
sysResponse.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := worldInfoService.UpdateWorldBook(userID, bookID, &req); err != nil {
|
||||
global.GVA_LOG.Error("更新世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("更新失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// DeleteWorldBook 删除世界书
|
||||
// @Summary 删除世界书
|
||||
// @Description 删除世界书
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "世界书ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /app/worldbook/:id [delete]
|
||||
func (a *WorldInfoApi) DeleteWorldBook(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
bookID := c.GetUint("id")
|
||||
|
||||
if err := worldInfoService.DeleteWorldBook(userID, bookID); err != nil {
|
||||
global.GVA_LOG.Error("删除世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("删除失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// GetWorldBook 获取世界书详情
|
||||
// @Summary 获取世界书详情
|
||||
// @Description 获取世界书详细信息
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "世界书ID"
|
||||
// @Success 200 {object} response.Response{data=response.WorldBookResponse}
|
||||
// @Router /app/worldbook/:id [get]
|
||||
func (a *WorldInfoApi) GetWorldBook(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
bookID := c.GetUint("id")
|
||||
|
||||
book, err := worldInfoService.GetWorldBook(userID, bookID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("获取失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithData(response.ToWorldBookResponse(book), c)
|
||||
}
|
||||
|
||||
// GetWorldBookList 获取世界书列表
|
||||
// @Summary 获取世界书列表
|
||||
// @Description 获取世界书列表
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data query request.WorldBookListRequest true "查询参数"
|
||||
// @Success 200 {object} response.Response{data=response.WorldBookListResponse}
|
||||
// @Router /app/worldbook/list [get]
|
||||
func (a *WorldInfoApi) GetWorldBookList(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
|
||||
var req request.WorldBookListRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
sysResponse.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 默认分页
|
||||
if req.Page < 1 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize < 1 {
|
||||
req.PageSize = 10
|
||||
}
|
||||
|
||||
result, err := worldInfoService.GetWorldBookList(userID, &req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取世界书列表失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("获取失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithData(result, c)
|
||||
}
|
||||
|
||||
// CreateWorldEntry 创建世界书条目
|
||||
// @Summary 创建世界书条目
|
||||
// @Description 在世界书中创建一个新条目
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body request.CreateWorldEntryRequest true "条目信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /app/worldbook/entry [post]
|
||||
func (a *WorldInfoApi) CreateWorldEntry(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
|
||||
var req request.CreateWorldEntryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
sysResponse.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := worldInfoService.CreateWorldEntry(userID, req.BookID, &req.Entry); err != nil {
|
||||
global.GVA_LOG.Error("创建条目失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("创建失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// UpdateWorldEntry 更新世界书条目
|
||||
// @Summary 更新世界书条目
|
||||
// @Description 更新世界书条目信息
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body request.UpdateWorldEntryRequest true "条目信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /app/worldbook/entry [put]
|
||||
func (a *WorldInfoApi) UpdateWorldEntry(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
|
||||
var req request.UpdateWorldEntryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
sysResponse.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := worldInfoService.UpdateWorldEntry(userID, req.BookID, &req.Entry); err != nil {
|
||||
global.GVA_LOG.Error("更新条目失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("更新失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// DeleteWorldEntry 删除世界书条目
|
||||
// @Summary 删除世界书条目
|
||||
// @Description 删除世界书条目
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body request.DeleteWorldEntryRequest true "删除参数"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /app/worldbook/entry [delete]
|
||||
func (a *WorldInfoApi) DeleteWorldEntry(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
|
||||
var req request.DeleteWorldEntryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
sysResponse.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := worldInfoService.DeleteWorldEntry(userID, req.BookID, req.EntryID); err != nil {
|
||||
global.GVA_LOG.Error("删除条目失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("删除失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// LinkCharactersToWorldBook 关联角色到世界书
|
||||
// @Summary 关联角色到世界书
|
||||
// @Description 将角色关联到世界书
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body request.LinkCharacterRequest true "关联参数"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /app/worldbook/link [post]
|
||||
func (a *WorldInfoApi) LinkCharactersToWorldBook(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
|
||||
var req request.LinkCharacterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
sysResponse.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := worldInfoService.LinkCharactersToWorldBook(userID, req.BookID, req.CharacterIDs); err != nil {
|
||||
global.GVA_LOG.Error("关联角色失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("关联失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithMessage("关联成功", c)
|
||||
}
|
||||
|
||||
// ImportWorldBook 导入世界书
|
||||
// @Summary 导入世界书
|
||||
// @Description 从文件导入世界书
|
||||
// @Tags 世界书管理
|
||||
// @Accept multipart/form-data
|
||||
// @Produce json
|
||||
// @Param file formData file true "世界书文件(JSON)"
|
||||
// @Param bookName formData string false "世界书名称"
|
||||
// @Success 200 {object} response.Response{data=app.AIWorldInfo}
|
||||
// @Router /app/worldbook/import [post]
|
||||
func (a *WorldInfoApi) ImportWorldBook(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
|
||||
// 获取文件
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
sysResponse.FailWithMessage("请上传世界书文件", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 文件大小限制(10MB)
|
||||
if file.Size > 10<<20 {
|
||||
sysResponse.FailWithMessage("文件大小不能超过 10MB", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("打开文件失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("文件读取失败", c)
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
fileData, err := io.ReadAll(src)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("读取文件内容失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("文件读取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取世界书名称
|
||||
bookName := c.PostForm("bookName")
|
||||
|
||||
// 导入世界书
|
||||
book, err := worldInfoService.ImportWorldBook(userID, bookName, fileData, "json")
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("导入世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("导入失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithData(response.ToWorldBookResponse(book), c)
|
||||
}
|
||||
|
||||
// ExportWorldBook 导出世界书
|
||||
// @Summary 导出世界书
|
||||
// @Description 导出世界书为 JSON 文件
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce application/json
|
||||
// @Param id path int true "世界书ID"
|
||||
// @Success 200 {object} response.WorldBookExportData
|
||||
// @Router /app/worldbook/:id/export [get]
|
||||
func (a *WorldInfoApi) ExportWorldBook(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
bookID := c.GetUint("id")
|
||||
|
||||
exportData, err := worldInfoService.ExportWorldBook(userID, bookID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("导出世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("导出失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
filename := fmt.Sprintf("%s.json", exportData.Name)
|
||||
c.Header("Content-Type", "application/json")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
||||
|
||||
// 直接返回 JSON 数据
|
||||
c.JSON(http.StatusOK, exportData)
|
||||
}
|
||||
|
||||
// MatchWorldInfo 匹配世界书条目(用于聊天)
|
||||
// @Summary 匹配世界书条目
|
||||
// @Description 根据消息内容匹配激活的世界书条目
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body request.MatchWorldInfoRequest true "匹配参数"
|
||||
// @Success 200 {object} response.Response{data=response.MatchWorldInfoResponse}
|
||||
// @Router /app/worldbook/match [post]
|
||||
func (a *WorldInfoApi) MatchWorldInfo(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
|
||||
var req request.MatchWorldInfoRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
sysResponse.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 默认值
|
||||
if req.ScanDepth < 1 {
|
||||
req.ScanDepth = 10
|
||||
}
|
||||
if req.MaxTokens < 100 {
|
||||
req.MaxTokens = 2000
|
||||
}
|
||||
|
||||
result, err := worldInfoService.MatchWorldInfo(userID, &req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("匹配世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("匹配失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithData(result, c)
|
||||
}
|
||||
|
||||
// GetCharacterWorldBooks 获取角色关联的世界书列表
|
||||
// @Summary 获取角色关联的世界书列表
|
||||
// @Description 获取特定角色关联的所有世界书
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param characterId path int true "角色ID"
|
||||
// @Success 200 {object} response.Response{data=[]response.WorldBookResponse}
|
||||
// @Router /app/worldbook/character/:characterId [get]
|
||||
func (a *WorldInfoApi) GetCharacterWorldBooks(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
characterID := c.GetUint("characterId")
|
||||
|
||||
var books []app.AIWorldInfo
|
||||
err := global.GVA_DB.
|
||||
Where("user_id = ? AND (is_global = true OR ? = ANY(linked_chars))", userID, fmt.Sprintf("%d", characterID)).
|
||||
Find(&books).Error
|
||||
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取角色世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("获取失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
result := make([]response.WorldBookResponse, 0, len(books))
|
||||
for i := range books {
|
||||
result = append(result, response.ToWorldBookResponse(&books[i]))
|
||||
}
|
||||
|
||||
sysResponse.OkWithData(result, c)
|
||||
}
|
||||
|
||||
// DuplicateWorldBook 复制世界书
|
||||
// @Summary 复制世界书
|
||||
// @Description 创建世界书的副本
|
||||
// @Tags 世界书管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "世界书ID"
|
||||
// @Success 200 {object} response.Response{data=app.AIWorldInfo}
|
||||
// @Router /app/worldbook/:id/duplicate [post]
|
||||
func (a *WorldInfoApi) DuplicateWorldBook(c *gin.Context) {
|
||||
userID := middleware.GetAppUserID(c)
|
||||
bookID := c.GetUint("id")
|
||||
|
||||
// 获取原世界书
|
||||
book, err := worldInfoService.GetWorldBook(userID, bookID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("获取失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析条目
|
||||
var entries []app.AIWorldInfoEntry
|
||||
if err := json.Unmarshal([]byte(book.Entries), &entries); err != nil {
|
||||
global.GVA_LOG.Error("解析条目失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("解析失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建副本
|
||||
req := request.CreateWorldBookRequest{
|
||||
BookName: book.BookName + " (副本)",
|
||||
IsGlobal: book.IsGlobal,
|
||||
Entries: entries,
|
||||
LinkedChars: book.LinkedChars,
|
||||
}
|
||||
|
||||
newBook, err := worldInfoService.CreateWorldBook(userID, &req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("复制世界书失败", zap.Error(err))
|
||||
sysResponse.FailWithMessage("复制失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
sysResponse.OkWithData(response.ToWorldBookResponse(newBook), c)
|
||||
}
|
||||
Reference in New Issue
Block a user