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, } // 创建角色卡 resp, err := s.CreateCharacter(userID, req) if err != nil { return nil, err } // 处理扩展数据中的正则脚本 if card.Data.Extensions != nil { s.processRegexScriptsFromExtensions(userID, resp.ID, card.Data.Extensions) } return resp, nil } // 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, } // 创建角色卡 resp, err := s.CreateCharacter(userID, req) if err != nil { return nil, err } // 处理扩展数据中的正则脚本 if card.Data.Extensions != nil { s.processRegexScriptsFromExtensions(userID, resp.ID, card.Data.Extensions) } return resp, nil } // 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) // 查询角色关联的正则脚本并添加到 extensions var regexScripts []app.RegexScript global.GVA_DB.Where("owner_char_id = ? AND scope = 1", characterID).Find(®exScripts) if len(regexScripts) > 0 { if extensions == nil { extensions = make(map[string]interface{}) } extensions["regex_scripts"] = regexScripts } // 构建 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 } // processRegexScriptsFromExtensions 从扩展数据中提取并创建正则脚本 func (s *CharacterService) processRegexScriptsFromExtensions(userID, characterID uint, extensions map[string]interface{}) { // 检查是否包含正则脚本 regexScriptsData, ok := extensions["regex_scripts"] if !ok { return } // 转换为 JSON 以便解析 scriptsJSON, err := json.Marshal(regexScriptsData) if err != nil { global.GVA_LOG.Error("序列化正则脚本失败: " + err.Error()) return } // 解析正则脚本数组 var scripts []map[string]interface{} if err := json.Unmarshal(scriptsJSON, &scripts); err != nil { global.GVA_LOG.Error("解析正则脚本失败: " + err.Error()) return } // 创建正则脚本记录 for _, scriptData := range scripts { script := app.RegexScript{ UserID: userID, Scope: 1, // 角色作用域 OwnerCharID: &characterID, } // 提取字段 - 兼容 SillyTavern 的字段名 // 脚本名称:优先使用 scriptName,其次 name if scriptName, ok := scriptData["scriptName"].(string); ok { script.Name = scriptName } else if name, ok := scriptData["name"].(string); ok { script.Name = name } // 查找正则表达式 if findRegex, ok := scriptData["findRegex"].(string); ok { script.FindRegex = findRegex } // 替换字符串:优先使用 replaceString,其次 replaceWith if replaceString, ok := scriptData["replaceString"].(string); ok { script.ReplaceWith = replaceString } else if replaceWith, ok := scriptData["replaceWith"].(string); ok { script.ReplaceWith = replaceWith } if placement, ok := scriptData["placement"].(float64); ok { script.Placement = int(placement) } if disabled, ok := scriptData["disabled"].(bool); ok { script.Disabled = disabled } if markdownOnly, ok := scriptData["markdownOnly"].(bool); ok { script.MarkdownOnly = markdownOnly } if runOnEdit, ok := scriptData["runOnEdit"].(bool); ok { script.RunOnEdit = runOnEdit } if promptOnly, ok := scriptData["promptOnly"].(bool); ok { script.PromptOnly = promptOnly } if substituteRegex, ok := scriptData["substituteRegex"].(bool); ok { script.SubstituteRegex = substituteRegex } if order, ok := scriptData["order"].(float64); ok { script.Order = int(order) } // 处理可选的整数字段 if minDepth, ok := scriptData["minDepth"].(float64); ok { depth := int(minDepth) script.MinDepth = &depth } if maxDepth, ok := scriptData["maxDepth"].(float64); ok { depth := int(maxDepth) script.MaxDepth = &depth } // 处理 trimStrings 数组 if trimStrings, ok := scriptData["trimStrings"].([]interface{}); ok { trimStringsJSON, _ := json.Marshal(trimStrings) script.TrimStrings = datatypes.JSON(trimStringsJSON) } // 处理扩展字段 if scriptExtensions, ok := scriptData["extensions"].(map[string]interface{}); ok { extensionsJSON, _ := json.Marshal(scriptExtensions) script.Extensions = datatypes.JSON(extensionsJSON) } // 创建记录 if err := global.GVA_DB.Create(&script).Error; err != nil { global.GVA_LOG.Error("创建正则脚本失败: " + err.Error()) } } }