Files
st/server/api/v1/app/world_info.go
2026-02-11 23:44:09 +08:00

518 lines
15 KiB
Go
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.

package app
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"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)
// 从路径参数获取 ID
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
sysResponse.FailWithMessage("无效的世界书ID", c)
return
}
bookID := uint(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)
// 从路径参数获取 ID
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
sysResponse.FailWithMessage("无效的世界书ID", c)
return
}
bookID := uint(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)
// 从路径参数获取 ID
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
sysResponse.FailWithMessage("无效的世界书ID", c)
return
}
bookID := uint(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)
// 从路径参数获取 ID
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
sysResponse.FailWithMessage("无效的世界书ID", c)
return
}
bookID := uint(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)
// 从路径参数获取 ID
idStr := c.Param("characterId")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
sysResponse.FailWithMessage("无效的角色ID", c)
return
}
characterID := uint(id)
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)
// 从路径参数获取 ID
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
sysResponse.FailWithMessage("无效的世界书ID", c)
return
}
bookID := uint(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)
}