366
server/service/app/character.go
Normal file
366
server/service/app/character.go
Normal file
@@ -0,0 +1,366 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"mime/multipart"
|
||||
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"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"
|
||||
"git.echol.cn/loser/st/server/utils"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CharacterService struct{}
|
||||
|
||||
// CreateCharacter 创建角色卡
|
||||
func (s *CharacterService) CreateCharacter(userID uint, req *request.CreateCharacterRequest) (*response.CharacterResponse, error) {
|
||||
// 序列化 JSON 字段
|
||||
tagsJSON, _ := json.Marshal(req.Tags)
|
||||
alternateGreetingsJSON, _ := json.Marshal(req.AlternateGreetings)
|
||||
characterBookJSON, _ := json.Marshal(req.CharacterBook)
|
||||
extensionsJSON, _ := json.Marshal(req.Extensions)
|
||||
|
||||
character := app.AICharacter{
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
Avatar: req.Avatar,
|
||||
Creator: req.Creator,
|
||||
Version: req.Version,
|
||||
Description: req.Description,
|
||||
Personality: req.Personality,
|
||||
Scenario: req.Scenario,
|
||||
FirstMes: req.FirstMes,
|
||||
MesExample: req.MesExample,
|
||||
CreatorNotes: req.CreatorNotes,
|
||||
SystemPrompt: req.SystemPrompt,
|
||||
PostHistoryInstructions: req.PostHistoryInstructions,
|
||||
Tags: datatypes.JSON(tagsJSON),
|
||||
AlternateGreetings: datatypes.JSON(alternateGreetingsJSON),
|
||||
CharacterBook: datatypes.JSON(characterBookJSON),
|
||||
Extensions: datatypes.JSON(extensionsJSON),
|
||||
IsPublic: req.IsPublic,
|
||||
Spec: "chara_card_v2",
|
||||
SpecVersion: "2.0",
|
||||
}
|
||||
|
||||
err := global.GVA_DB.Create(&character).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := response.ToCharacterResponse(&character)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetCharacterList 获取角色卡列表
|
||||
func (s *CharacterService) GetCharacterList(userID uint, req *request.GetCharacterListRequest) (*response.CharacterListResponse, error) {
|
||||
var characters []app.AICharacter
|
||||
var total int64
|
||||
|
||||
db := global.GVA_DB.Model(&app.AICharacter{})
|
||||
|
||||
// 筛选条件
|
||||
if req.IsPublic != nil {
|
||||
if *req.IsPublic {
|
||||
db = db.Where("is_public = ?", true)
|
||||
} else {
|
||||
db = db.Where("user_id = ?", userID)
|
||||
}
|
||||
} else {
|
||||
db = db.Where("user_id = ? OR is_public = ?", userID, true)
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if req.Keyword != "" {
|
||||
db = db.Where("name LIKE ? OR description LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
|
||||
}
|
||||
|
||||
// 标签筛选
|
||||
if req.Tag != "" {
|
||||
db = db.Where("tags @> ?", datatypes.JSON(`["`+req.Tag+`"]`))
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
err := db.Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
err = db.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&characters).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换响应
|
||||
list := make([]response.CharacterResponse, len(characters))
|
||||
for i, char := range characters {
|
||||
list[i] = response.ToCharacterResponse(&char)
|
||||
}
|
||||
|
||||
return &response.CharacterListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCharacterByID 获取角色卡详情
|
||||
func (s *CharacterService) GetCharacterByID(userID, characterID uint) (*response.CharacterResponse, error) {
|
||||
var character app.AICharacter
|
||||
|
||||
err := global.GVA_DB.Where("id = ? AND (user_id = ? OR is_public = ?)", characterID, userID, true).
|
||||
First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("角色卡不存在或无权访问")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := response.ToCharacterResponse(&character)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// UpdateCharacter 更新角色卡
|
||||
func (s *CharacterService) UpdateCharacter(userID, characterID uint, req *request.UpdateCharacterRequest) error {
|
||||
var character app.AICharacter
|
||||
|
||||
// 检查权限
|
||||
err := global.GVA_DB.Where("id = ? AND user_id = ?", characterID, userID).First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("角色卡不存在或无权修改")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
updates := map[string]interface{}{}
|
||||
|
||||
if req.Name != "" {
|
||||
updates["name"] = req.Name
|
||||
}
|
||||
if req.Avatar != "" {
|
||||
updates["avatar"] = req.Avatar
|
||||
}
|
||||
if req.Creator != "" {
|
||||
updates["creator"] = req.Creator
|
||||
}
|
||||
if req.Version != "" {
|
||||
updates["version"] = req.Version
|
||||
}
|
||||
if req.Description != "" {
|
||||
updates["description"] = req.Description
|
||||
}
|
||||
if req.Personality != "" {
|
||||
updates["personality"] = req.Personality
|
||||
}
|
||||
if req.Scenario != "" {
|
||||
updates["scenario"] = req.Scenario
|
||||
}
|
||||
if req.FirstMes != "" {
|
||||
updates["first_mes"] = req.FirstMes
|
||||
}
|
||||
if req.MesExample != "" {
|
||||
updates["mes_example"] = req.MesExample
|
||||
}
|
||||
if req.CreatorNotes != "" {
|
||||
updates["creator_notes"] = req.CreatorNotes
|
||||
}
|
||||
if req.SystemPrompt != "" {
|
||||
updates["system_prompt"] = req.SystemPrompt
|
||||
}
|
||||
if req.PostHistoryInstructions != "" {
|
||||
updates["post_history_instructions"] = req.PostHistoryInstructions
|
||||
}
|
||||
|
||||
if req.Tags != nil {
|
||||
tagsJSON, _ := json.Marshal(req.Tags)
|
||||
updates["tags"] = datatypes.JSON(tagsJSON)
|
||||
}
|
||||
if req.AlternateGreetings != nil {
|
||||
alternateGreetingsJSON, _ := json.Marshal(req.AlternateGreetings)
|
||||
updates["alternate_greetings"] = datatypes.JSON(alternateGreetingsJSON)
|
||||
}
|
||||
if req.CharacterBook != nil {
|
||||
characterBookJSON, _ := json.Marshal(req.CharacterBook)
|
||||
updates["character_book"] = datatypes.JSON(characterBookJSON)
|
||||
}
|
||||
if req.Extensions != nil {
|
||||
extensionsJSON, _ := json.Marshal(req.Extensions)
|
||||
updates["extensions"] = datatypes.JSON(extensionsJSON)
|
||||
}
|
||||
|
||||
updates["is_public"] = req.IsPublic
|
||||
|
||||
return global.GVA_DB.Model(&character).Updates(updates).Error
|
||||
}
|
||||
|
||||
// DeleteCharacter 删除角色卡
|
||||
func (s *CharacterService) DeleteCharacter(userID, characterID uint) error {
|
||||
result := global.GVA_DB.Where("id = ? AND user_id = ?", characterID, userID).Delete(&app.AICharacter{})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New("角色卡不存在或无权删除")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportCharacterFromPNG 从 PNG 文件导入角色卡
|
||||
func (s *CharacterService) ImportCharacterFromPNG(userID uint, file *multipart.FileHeader) (*response.CharacterResponse, error) {
|
||||
// 读取文件内容
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, errors.New("打开文件失败")
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// 读取文件数据
|
||||
fileData := make([]byte, file.Size)
|
||||
_, err = src.Read(fileData)
|
||||
if err != nil {
|
||||
return nil, errors.New("读取文件失败")
|
||||
}
|
||||
|
||||
// 提取角色卡数据
|
||||
card, err := utils.ExtractCharacterFromPNG(fileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 上传 PNG 图片到 OSS(替代 Base64)
|
||||
var uploadService UploadService
|
||||
avatarURL, err := uploadService.UploadImage(file)
|
||||
if err != nil {
|
||||
// 如果上传失败,回退到 Base64(向后兼容)
|
||||
avatarURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(fileData)
|
||||
}
|
||||
|
||||
// 转换为创建请求
|
||||
req := &request.CreateCharacterRequest{
|
||||
Name: card.Data.Name,
|
||||
Avatar: avatarURL,
|
||||
Creator: card.Data.Creator,
|
||||
Version: card.Data.CharacterVersion,
|
||||
Description: card.Data.Description,
|
||||
Personality: card.Data.Personality,
|
||||
Scenario: card.Data.Scenario,
|
||||
FirstMes: card.Data.FirstMes,
|
||||
MesExample: card.Data.MesExample,
|
||||
CreatorNotes: card.Data.CreatorNotes,
|
||||
SystemPrompt: card.Data.SystemPrompt,
|
||||
PostHistoryInstructions: card.Data.PostHistoryInstructions,
|
||||
Tags: card.Data.Tags,
|
||||
AlternateGreetings: card.Data.AlternateGreetings,
|
||||
CharacterBook: card.Data.CharacterBook,
|
||||
Extensions: card.Data.Extensions,
|
||||
IsPublic: false,
|
||||
}
|
||||
|
||||
return s.CreateCharacter(userID, req)
|
||||
}
|
||||
|
||||
// ImportCharacterFromJSON 从 JSON 文件导入角色卡
|
||||
func (s *CharacterService) ImportCharacterFromJSON(userID uint, file *multipart.FileHeader) (*response.CharacterResponse, error) {
|
||||
// 读取文件内容
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, errors.New("打开文件失败")
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// 读取文件数据
|
||||
fileData := make([]byte, file.Size)
|
||||
_, err = src.Read(fileData)
|
||||
if err != nil {
|
||||
return nil, errors.New("读取文件失败")
|
||||
}
|
||||
|
||||
// 解析 JSON
|
||||
card, err := utils.ParseCharacterCardJSON(fileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为创建请求
|
||||
req := &request.CreateCharacterRequest{
|
||||
Name: card.Data.Name,
|
||||
Creator: card.Data.Creator,
|
||||
Version: card.Data.CharacterVersion,
|
||||
Description: card.Data.Description,
|
||||
Personality: card.Data.Personality,
|
||||
Scenario: card.Data.Scenario,
|
||||
FirstMes: card.Data.FirstMes,
|
||||
MesExample: card.Data.MesExample,
|
||||
CreatorNotes: card.Data.CreatorNotes,
|
||||
SystemPrompt: card.Data.SystemPrompt,
|
||||
PostHistoryInstructions: card.Data.PostHistoryInstructions,
|
||||
Tags: card.Data.Tags,
|
||||
AlternateGreetings: card.Data.AlternateGreetings,
|
||||
CharacterBook: card.Data.CharacterBook,
|
||||
Extensions: card.Data.Extensions,
|
||||
IsPublic: false,
|
||||
}
|
||||
|
||||
return s.CreateCharacter(userID, req)
|
||||
}
|
||||
|
||||
// ExportCharacterToJSON 导出角色卡为 JSON
|
||||
func (s *CharacterService) ExportCharacterToJSON(userID, characterID uint) (*utils.CharacterCardV2, error) {
|
||||
var character app.AICharacter
|
||||
|
||||
err := global.GVA_DB.Where("id = ? AND (user_id = ? OR is_public = ?)", characterID, userID, true).
|
||||
First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("角色卡不存在或无权访问")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 解析 JSON 字段
|
||||
var tags []string
|
||||
var alternateGreetings []string
|
||||
var characterBook map[string]interface{}
|
||||
var extensions map[string]interface{}
|
||||
|
||||
json.Unmarshal(character.Tags, &tags)
|
||||
json.Unmarshal(character.AlternateGreetings, &alternateGreetings)
|
||||
json.Unmarshal(character.CharacterBook, &characterBook)
|
||||
json.Unmarshal(character.Extensions, &extensions)
|
||||
|
||||
// 构建 V2 格式
|
||||
card := &utils.CharacterCardV2{
|
||||
Spec: character.Spec,
|
||||
SpecVersion: character.SpecVersion,
|
||||
Data: utils.CharacterCardV2Data{
|
||||
Name: character.Name,
|
||||
Description: character.Description,
|
||||
Personality: character.Personality,
|
||||
Scenario: character.Scenario,
|
||||
FirstMes: character.FirstMes,
|
||||
MesExample: character.MesExample,
|
||||
CreatorNotes: character.CreatorNotes,
|
||||
SystemPrompt: character.SystemPrompt,
|
||||
PostHistoryInstructions: character.PostHistoryInstructions,
|
||||
Tags: tags,
|
||||
Creator: character.Creator,
|
||||
CharacterVersion: character.Version,
|
||||
AlternateGreetings: alternateGreetings,
|
||||
CharacterBook: characterBook,
|
||||
Extensions: extensions,
|
||||
},
|
||||
}
|
||||
|
||||
return card, nil
|
||||
}
|
||||
Reference in New Issue
Block a user