367 lines
11 KiB
Go
367 lines
11 KiB
Go
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
|
||
}
|