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 }