Files
st-react/server/service/app/character.go
Echo f4e166c5ee 🎉 初始化项目
Signed-off-by: Echo <1711788888@qq.com>
2026-02-27 21:52:00 +08:00

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