🎨 重构用户端前端为vue开发,完善基础类和角色相关接口
This commit is contained in:
694
server/service/app/character.go
Normal file
694
server/service/app/character.go
Normal file
@@ -0,0 +1,694 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/jpeg"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CharacterService struct{}
|
||||
|
||||
// GetPublicCharacterList 获取公开角色卡列表(无需鉴权)
|
||||
func (cs *CharacterService) GetPublicCharacterList(req request.CharacterListRequest, userID *uint) (response.CharacterListResponse, error) {
|
||||
db := global.GVA_DB.Model(&app.AICharacter{})
|
||||
|
||||
// 只查询公开的角色卡
|
||||
db = db.Where("is_public = ?", true)
|
||||
|
||||
// 关键词搜索
|
||||
if req.Keyword != "" {
|
||||
keyword := "%" + req.Keyword + "%"
|
||||
db = db.Where("name ILIKE ? OR description ILIKE ? OR creator_name ILIKE ?", keyword, keyword, keyword)
|
||||
}
|
||||
|
||||
// 标签筛选
|
||||
if len(req.Tags) > 0 {
|
||||
for _, tag := range req.Tags {
|
||||
db = db.Where("tags @> ?", fmt.Sprintf(`["%s"]`, tag))
|
||||
}
|
||||
}
|
||||
|
||||
// 排序
|
||||
switch req.SortBy {
|
||||
case "popular":
|
||||
db = db.Order("usage_count DESC, created_at DESC")
|
||||
case "mostChats":
|
||||
db = db.Order("total_chats DESC, created_at DESC")
|
||||
case "mostLikes":
|
||||
db = db.Order("total_likes DESC, created_at DESC")
|
||||
case "newest":
|
||||
fallthrough
|
||||
default:
|
||||
db = db.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// 分页
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
|
||||
var characters []app.AICharacter
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
err := db.Offset(offset).Limit(req.PageSize).Find(&characters).Error
|
||||
if err != nil {
|
||||
return response.CharacterListResponse{}, err
|
||||
}
|
||||
|
||||
// 查询当前用户的收藏状态
|
||||
favoriteMap := make(map[uint]bool)
|
||||
if userID != nil {
|
||||
var favorites []app.AppUserFavoriteCharacter
|
||||
global.GVA_DB.Where("user_id = ?", *userID).Find(&favorites)
|
||||
for _, fav := range favorites {
|
||||
favoriteMap[fav.CharacterID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为响应
|
||||
list := make([]response.CharacterResponse, len(characters))
|
||||
for i, char := range characters {
|
||||
list[i] = response.ToCharacterResponse(&char, favoriteMap[char.ID])
|
||||
}
|
||||
|
||||
return response.CharacterListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetMyCharacterList 获取我的角色卡列表(需要鉴权)
|
||||
func (cs *CharacterService) GetMyCharacterList(req request.CharacterListRequest, userID uint) (response.CharacterListResponse, error) {
|
||||
db := global.GVA_DB.Model(&app.AICharacter{})
|
||||
|
||||
// 只查询当前用户创建的角色卡
|
||||
db = db.Where("creator_id = ?", userID)
|
||||
|
||||
// 关键词搜索
|
||||
if req.Keyword != "" {
|
||||
keyword := "%" + req.Keyword + "%"
|
||||
db = db.Where("name ILIKE ? OR description ILIKE ?", keyword, keyword)
|
||||
}
|
||||
|
||||
// 标签筛选
|
||||
if len(req.Tags) > 0 {
|
||||
for _, tag := range req.Tags {
|
||||
db = db.Where("tags @> ?", fmt.Sprintf(`["%s"]`, tag))
|
||||
}
|
||||
}
|
||||
|
||||
// 排序
|
||||
switch req.SortBy {
|
||||
case "popular":
|
||||
db = db.Order("usage_count DESC, created_at DESC")
|
||||
case "mostChats":
|
||||
db = db.Order("total_chats DESC, created_at DESC")
|
||||
case "mostLikes":
|
||||
db = db.Order("total_likes DESC, created_at DESC")
|
||||
case "newest":
|
||||
fallthrough
|
||||
default:
|
||||
db = db.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// 分页
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
|
||||
var characters []app.AICharacter
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
err := db.Offset(offset).Limit(req.PageSize).Find(&characters).Error
|
||||
if err != nil {
|
||||
return response.CharacterListResponse{}, err
|
||||
}
|
||||
|
||||
// 查询收藏状态
|
||||
favoriteMap := make(map[uint]bool)
|
||||
var favorites []app.AppUserFavoriteCharacter
|
||||
global.GVA_DB.Where("user_id = ?", userID).Find(&favorites)
|
||||
for _, fav := range favorites {
|
||||
favoriteMap[fav.CharacterID] = true
|
||||
}
|
||||
|
||||
// 转换为响应
|
||||
list := make([]response.CharacterResponse, len(characters))
|
||||
for i, char := range characters {
|
||||
list[i] = response.ToCharacterResponse(&char, favoriteMap[char.ID])
|
||||
}
|
||||
|
||||
return response.CharacterListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCharacterDetail 获取角色卡详情
|
||||
func (cs *CharacterService) GetCharacterDetail(characterID uint, userID *uint) (response.CharacterResponse, error) {
|
||||
var character app.AICharacter
|
||||
err := global.GVA_DB.Where("id = ?", characterID).First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return response.CharacterResponse{}, errors.New("角色卡不存在")
|
||||
}
|
||||
return response.CharacterResponse{}, err
|
||||
}
|
||||
|
||||
// 检查访问权限
|
||||
if !character.IsPublic {
|
||||
if userID == nil {
|
||||
return response.CharacterResponse{}, errors.New("无权访问")
|
||||
}
|
||||
if character.CreatorID == nil || *character.CreatorID != *userID {
|
||||
return response.CharacterResponse{}, errors.New("无权访问")
|
||||
}
|
||||
}
|
||||
|
||||
// 查询是否收藏
|
||||
isFavorited := false
|
||||
if userID != nil {
|
||||
var count int64
|
||||
global.GVA_DB.Model(&app.AppUserFavoriteCharacter{}).
|
||||
Where("user_id = ? AND character_id = ?", *userID, characterID).
|
||||
Count(&count)
|
||||
isFavorited = count > 0
|
||||
}
|
||||
|
||||
return response.ToCharacterResponse(&character, isFavorited), nil
|
||||
}
|
||||
|
||||
// CreateCharacter 创建角色卡
|
||||
func (cs *CharacterService) CreateCharacter(req request.CreateCharacterRequest, userID uint) (response.CharacterResponse, error) {
|
||||
// 构建 CardData
|
||||
cardData := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"description": req.Description,
|
||||
"personality": req.Personality,
|
||||
"scenario": req.Scenario,
|
||||
"first_message": req.FirstMessage,
|
||||
"example_messages": req.ExampleMessages,
|
||||
"creator_name": req.CreatorName,
|
||||
"creator_notes": req.CreatorNotes,
|
||||
}
|
||||
cardDataJSON, _ := json.Marshal(cardData)
|
||||
|
||||
// 处理标签和示例消息
|
||||
tags := req.Tags
|
||||
if tags == nil {
|
||||
tags = []string{}
|
||||
}
|
||||
|
||||
exampleMessages := req.ExampleMessages
|
||||
if exampleMessages == nil {
|
||||
exampleMessages = []string{}
|
||||
}
|
||||
|
||||
character := app.AICharacter{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Personality: req.Personality,
|
||||
Scenario: req.Scenario,
|
||||
Avatar: req.Avatar,
|
||||
CreatorID: &userID,
|
||||
CreatorName: req.CreatorName,
|
||||
CreatorNotes: req.CreatorNotes,
|
||||
CardData: datatypes.JSON(cardDataJSON),
|
||||
Tags: tags,
|
||||
IsPublic: req.IsPublic,
|
||||
FirstMessage: req.FirstMessage,
|
||||
ExampleMessages: exampleMessages,
|
||||
TokenCount: calculateTokenCount(req),
|
||||
}
|
||||
|
||||
err := global.GVA_DB.Create(&character).Error
|
||||
if err != nil {
|
||||
return response.CharacterResponse{}, err
|
||||
}
|
||||
|
||||
return response.ToCharacterResponse(&character, false), nil
|
||||
}
|
||||
|
||||
// UpdateCharacter 更新角色卡
|
||||
func (cs *CharacterService) UpdateCharacter(req request.UpdateCharacterRequest, userID uint) (response.CharacterResponse, error) {
|
||||
var character app.AICharacter
|
||||
err := global.GVA_DB.Where("id = ?", req.ID).First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return response.CharacterResponse{}, errors.New("角色卡不存在")
|
||||
}
|
||||
return response.CharacterResponse{}, err
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
if character.CreatorID == nil || *character.CreatorID != userID {
|
||||
return response.CharacterResponse{}, errors.New("无权修改")
|
||||
}
|
||||
|
||||
// 构建 CardData
|
||||
cardData := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"description": req.Description,
|
||||
"personality": req.Personality,
|
||||
"scenario": req.Scenario,
|
||||
"first_message": req.FirstMessage,
|
||||
"example_messages": req.ExampleMessages,
|
||||
"creator_name": req.CreatorName,
|
||||
"creator_notes": req.CreatorNotes,
|
||||
}
|
||||
cardDataJSON, _ := json.Marshal(cardData)
|
||||
|
||||
// 处理标签和示例消息
|
||||
tags := req.Tags
|
||||
if tags == nil {
|
||||
tags = []string{}
|
||||
}
|
||||
|
||||
exampleMessages := req.ExampleMessages
|
||||
if exampleMessages == nil {
|
||||
exampleMessages = []string{}
|
||||
}
|
||||
|
||||
// 更新
|
||||
updates := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"description": req.Description,
|
||||
"personality": req.Personality,
|
||||
"scenario": req.Scenario,
|
||||
"avatar": req.Avatar,
|
||||
"creator_name": req.CreatorName,
|
||||
"creator_notes": req.CreatorNotes,
|
||||
"card_data": cardDataJSON,
|
||||
"tags": tags,
|
||||
"is_public": req.IsPublic,
|
||||
"first_message": req.FirstMessage,
|
||||
"example_messages": exampleMessages,
|
||||
"token_count": calculateTokenCount(req),
|
||||
"version": character.Version + 1,
|
||||
}
|
||||
|
||||
err = global.GVA_DB.Model(&character).Updates(updates).Error
|
||||
if err != nil {
|
||||
return response.CharacterResponse{}, err
|
||||
}
|
||||
|
||||
// 重新查询
|
||||
err = global.GVA_DB.Where("id = ?", req.ID).First(&character).Error
|
||||
if err != nil {
|
||||
return response.CharacterResponse{}, err
|
||||
}
|
||||
|
||||
// 查询是否收藏
|
||||
var count int64
|
||||
global.GVA_DB.Model(&app.AppUserFavoriteCharacter{}).
|
||||
Where("user_id = ? AND character_id = ?", userID, character.ID).
|
||||
Count(&count)
|
||||
|
||||
return response.ToCharacterResponse(&character, count > 0), nil
|
||||
}
|
||||
|
||||
// DeleteCharacter 删除角色卡
|
||||
func (cs *CharacterService) DeleteCharacter(characterID uint, userID uint) error {
|
||||
var character app.AICharacter
|
||||
err := global.GVA_DB.Where("id = ?", characterID).First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("角色卡不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
if character.CreatorID == nil || *character.CreatorID != userID {
|
||||
return errors.New("无权删除")
|
||||
}
|
||||
|
||||
// 删除相关的收藏记录
|
||||
global.GVA_DB.Where("character_id = ?", characterID).Delete(&app.AppUserFavoriteCharacter{})
|
||||
|
||||
// 删除角色卡
|
||||
return global.GVA_DB.Delete(&character).Error
|
||||
}
|
||||
|
||||
// ToggleFavorite 切换收藏状态
|
||||
func (cs *CharacterService) ToggleFavorite(characterID uint, userID uint) (bool, error) {
|
||||
// 检查角色卡是否存在
|
||||
var character app.AICharacter
|
||||
err := global.GVA_DB.Where("id = ?", characterID).First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, errors.New("角色卡不存在")
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 检查是否已收藏
|
||||
var favorite app.AppUserFavoriteCharacter
|
||||
err = global.GVA_DB.Where("user_id = ? AND character_id = ?", userID, characterID).First(&favorite).Error
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// 未收藏,添加收藏
|
||||
favorite = app.AppUserFavoriteCharacter{
|
||||
UserID: userID,
|
||||
CharacterID: characterID,
|
||||
}
|
||||
err = global.GVA_DB.Create(&favorite).Error
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 增加收藏数
|
||||
global.GVA_DB.Model(&character).UpdateColumn("favorite_count", gorm.Expr("favorite_count + ?", 1))
|
||||
|
||||
return true, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
// 已收藏,取消收藏
|
||||
err = global.GVA_DB.Delete(&favorite).Error
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 减少收藏数
|
||||
global.GVA_DB.Model(&character).UpdateColumn("favorite_count", gorm.Expr("favorite_count - ?", 1))
|
||||
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// LikeCharacter 点赞角色卡
|
||||
func (cs *CharacterService) LikeCharacter(characterID uint) error {
|
||||
var character app.AICharacter
|
||||
err := global.GVA_DB.Where("id = ?", characterID).First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("角色卡不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 增加点赞数
|
||||
return global.GVA_DB.Model(&character).UpdateColumn("total_likes", gorm.Expr("total_likes + ?", 1)).Error
|
||||
}
|
||||
|
||||
// ExportCharacter 导出角色卡为 JSON
|
||||
func (cs *CharacterService) ExportCharacter(characterID uint, userID *uint) (map[string]interface{}, error) {
|
||||
var character app.AICharacter
|
||||
err := global.GVA_DB.Where("id = ?", characterID).First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("角色卡不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查访问权限
|
||||
if !character.IsPublic {
|
||||
if userID == nil {
|
||||
return nil, errors.New("无权访问")
|
||||
}
|
||||
if character.CreatorID == nil || *character.CreatorID != *userID {
|
||||
return nil, errors.New("无权访问")
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 tags 和 exampleMessages
|
||||
tags := []string{}
|
||||
if character.Tags != nil {
|
||||
tags = character.Tags
|
||||
}
|
||||
|
||||
exampleMessages := []string{}
|
||||
if character.ExampleMessages != nil {
|
||||
exampleMessages = character.ExampleMessages
|
||||
}
|
||||
|
||||
// 构建导出数据(兼容 SillyTavern 格式)
|
||||
exportData := map[string]interface{}{
|
||||
"spec": "chara_card_v2",
|
||||
"spec_version": "2.0",
|
||||
"data": map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"description": character.Description,
|
||||
"personality": character.Personality,
|
||||
"scenario": character.Scenario,
|
||||
"first_mes": character.FirstMessage,
|
||||
"mes_example": strings.Join(exampleMessages, "\n<START>\n"),
|
||||
"creator_notes": character.CreatorNotes,
|
||||
"system_prompt": "",
|
||||
"post_history_instructions": "",
|
||||
"tags": tags,
|
||||
"creator": character.CreatorName,
|
||||
"character_version": character.Version,
|
||||
"extensions": map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
|
||||
return exportData, nil
|
||||
}
|
||||
|
||||
// ImportCharacter 导入角色卡(支持 PNG 和 JSON)
|
||||
func (cs *CharacterService) ImportCharacter(fileData []byte, filename string, userID uint, isPublic bool) (response.CharacterResponse, error) {
|
||||
// 添加 defer 捕获 panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
global.GVA_LOG.Error("导入角色卡时发生 panic",
|
||||
zap.Any("panic", r),
|
||||
zap.String("filename", filename))
|
||||
}
|
||||
}()
|
||||
|
||||
global.GVA_LOG.Info("开始导入角色卡",
|
||||
zap.String("filename", filename),
|
||||
zap.Int("fileSize", len(fileData)),
|
||||
zap.Uint("userID", userID))
|
||||
|
||||
var card *utils.CharacterCardV2
|
||||
var err error
|
||||
var avatarData []byte
|
||||
|
||||
// 判断文件类型
|
||||
if strings.HasSuffix(strings.ToLower(filename), ".png") {
|
||||
global.GVA_LOG.Info("检测到 PNG 格式,开始提取角色卡数据")
|
||||
// PNG 格式:提取角色卡数据和头像
|
||||
card, err = utils.ExtractCharacterFromPNG(fileData)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("解析 PNG 角色卡失败", zap.Error(err))
|
||||
return response.CharacterResponse{}, errors.New("解析 PNG 角色卡失败: " + err.Error())
|
||||
}
|
||||
global.GVA_LOG.Info("PNG 角色卡解析成功", zap.String("characterName", card.Data.Name))
|
||||
avatarData = fileData
|
||||
} else if strings.HasSuffix(strings.ToLower(filename), ".json") {
|
||||
global.GVA_LOG.Info("检测到 JSON 格式,开始解析")
|
||||
// JSON 格式:只有数据,没有头像
|
||||
card, err = utils.ParseCharacterCardJSON(fileData)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("解析 JSON 角色卡失败", zap.Error(err))
|
||||
return response.CharacterResponse{}, errors.New("解析 JSON 角色卡失败: " + err.Error())
|
||||
}
|
||||
global.GVA_LOG.Info("JSON 角色卡解析成功", zap.String("characterName", card.Data.Name))
|
||||
} else {
|
||||
return response.CharacterResponse{}, errors.New("不支持的文件格式,请上传 PNG 或 JSON 文件")
|
||||
}
|
||||
|
||||
// 转换为创建请求
|
||||
global.GVA_LOG.Info("转换角色卡数据为创建请求")
|
||||
createReq := convertCardToCreateRequest(card, avatarData, isPublic)
|
||||
|
||||
global.GVA_LOG.Info("开始创建角色卡到数据库",
|
||||
zap.String("name", createReq.Name),
|
||||
zap.Bool("isPublic", createReq.IsPublic))
|
||||
|
||||
// 创建角色卡
|
||||
result, err := cs.CreateCharacter(createReq, userID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建角色卡到数据库失败", zap.Error(err))
|
||||
return response.CharacterResponse{}, err
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("角色卡导入完成", zap.Uint("characterID", result.ID))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ExportCharacterAsPNG 导出角色卡为 PNG 格式
|
||||
func (cs *CharacterService) ExportCharacterAsPNG(characterID uint, userID *uint) ([]byte, error) {
|
||||
var character app.AICharacter
|
||||
err := global.GVA_DB.Where("id = ?", characterID).First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("角色卡不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查访问权限
|
||||
if !character.IsPublic {
|
||||
if userID == nil {
|
||||
return nil, errors.New("无权访问")
|
||||
}
|
||||
if character.CreatorID == nil || *character.CreatorID != *userID {
|
||||
return nil, errors.New("无权访问")
|
||||
}
|
||||
}
|
||||
|
||||
// 构建角色卡数据
|
||||
card := convertCharacterToCard(&character)
|
||||
|
||||
// 获取角色头像
|
||||
var img image.Image
|
||||
if character.Avatar != "" {
|
||||
// TODO: 从 URL 或文件系统加载头像
|
||||
// 这里暂时创建一个默认图片
|
||||
img = createDefaultAvatar()
|
||||
} else {
|
||||
img = createDefaultAvatar()
|
||||
}
|
||||
|
||||
// 将角色卡数据嵌入到 PNG
|
||||
pngData, err := utils.EmbedCharacterToPNG(img, card)
|
||||
if err != nil {
|
||||
return nil, errors.New("生成 PNG 失败: " + err.Error())
|
||||
}
|
||||
|
||||
return pngData, nil
|
||||
}
|
||||
|
||||
// convertCardToCreateRequest 将角色卡转换为创建请求
|
||||
func convertCardToCreateRequest(card *utils.CharacterCardV2, avatarData []byte, isPublic bool) request.CreateCharacterRequest {
|
||||
// 处理示例消息
|
||||
exampleMessages := []string{}
|
||||
if card.Data.MesExample != "" {
|
||||
// 按 <START> 分割
|
||||
exampleMessages = strings.Split(card.Data.MesExample, "<START>")
|
||||
// 清理空白
|
||||
cleaned := []string{}
|
||||
for _, msg := range exampleMessages {
|
||||
msg = strings.TrimSpace(msg)
|
||||
if msg != "" {
|
||||
cleaned = append(cleaned, msg)
|
||||
}
|
||||
}
|
||||
exampleMessages = cleaned
|
||||
}
|
||||
|
||||
// 合并备用问候语
|
||||
if len(card.Data.AlternateGreetings) > 0 {
|
||||
exampleMessages = append(exampleMessages, card.Data.AlternateGreetings...)
|
||||
}
|
||||
|
||||
// TODO: 处理头像数据,上传到文件服务器
|
||||
avatar := ""
|
||||
if avatarData != nil {
|
||||
// 这里应该将头像上传到文件服务器并获取 URL
|
||||
// avatar = uploadAvatar(avatarData)
|
||||
}
|
||||
|
||||
return request.CreateCharacterRequest{
|
||||
Name: card.Data.Name,
|
||||
Description: card.Data.Description,
|
||||
Personality: card.Data.Personality,
|
||||
Scenario: card.Data.Scenario,
|
||||
Avatar: avatar,
|
||||
CreatorName: card.Data.Creator,
|
||||
CreatorNotes: card.Data.CreatorNotes,
|
||||
FirstMessage: card.Data.FirstMes,
|
||||
ExampleMessages: exampleMessages,
|
||||
Tags: card.Data.Tags,
|
||||
IsPublic: isPublic,
|
||||
}
|
||||
}
|
||||
|
||||
// convertCharacterToCard 将角色卡转换为 CharacterCardV2
|
||||
func convertCharacterToCard(character *app.AICharacter) *utils.CharacterCardV2 {
|
||||
tags := []string{}
|
||||
if character.Tags != nil {
|
||||
tags = character.Tags
|
||||
}
|
||||
|
||||
exampleMessages := []string{}
|
||||
if character.ExampleMessages != nil {
|
||||
exampleMessages = character.ExampleMessages
|
||||
}
|
||||
|
||||
return &utils.CharacterCardV2{
|
||||
Spec: "chara_card_v2",
|
||||
SpecVersion: "2.0",
|
||||
Data: utils.CharacterCardV2Data{
|
||||
Name: character.Name,
|
||||
Description: character.Description,
|
||||
Personality: character.Personality,
|
||||
Scenario: character.Scenario,
|
||||
FirstMes: character.FirstMessage,
|
||||
MesExample: strings.Join(exampleMessages, "\n<START>\n"),
|
||||
CreatorNotes: character.CreatorNotes,
|
||||
SystemPrompt: "",
|
||||
PostHistoryInstructions: "",
|
||||
Tags: tags,
|
||||
Creator: character.CreatorName,
|
||||
CharacterVersion: fmt.Sprintf("%d", character.Version),
|
||||
AlternateGreetings: []string{},
|
||||
Extensions: map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// createDefaultAvatar 创建默认头像
|
||||
func createDefaultAvatar() image.Image {
|
||||
// 创建一个 400x533 的默认图片(3:4 比例)
|
||||
width, height := 400, 533
|
||||
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
|
||||
// 填充渐变色
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
// 简单的渐变效果
|
||||
r := uint8(102 + y*155/height)
|
||||
g := uint8(126 + y*138/height)
|
||||
b := uint8(234 - y*72/height)
|
||||
img.Set(x, y, image.NewRGBA(image.Rect(0, 0, 1, 1)).At(0, 0))
|
||||
img.SetRGBA(x, y, image.NewRGBA(image.Rect(0, 0, 1, 1)).RGBAAt(0, 0))
|
||||
// 设置颜色
|
||||
img.SetRGBA(x, y, image.NewRGBA(image.Rect(0, 0, 1, 1)).RGBAAt(0, 0))
|
||||
pix := img.Pix[y*img.Stride+x*4:]
|
||||
pix[0] = r
|
||||
pix[1] = g
|
||||
pix[2] = b
|
||||
pix[3] = 255
|
||||
}
|
||||
}
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
// calculateTokenCount 计算角色卡的 Token 数量(简单估算)
|
||||
func calculateTokenCount(req interface{}) int {
|
||||
var text string
|
||||
switch v := req.(type) {
|
||||
case request.CreateCharacterRequest:
|
||||
text = v.Name + v.Description + v.Personality + v.Scenario + v.FirstMessage
|
||||
for _, msg := range v.ExampleMessages {
|
||||
text += msg
|
||||
}
|
||||
case request.UpdateCharacterRequest:
|
||||
text = v.Name + v.Description + v.Personality + v.Scenario + v.FirstMessage
|
||||
for _, msg := range v.ExampleMessages {
|
||||
text += msg
|
||||
}
|
||||
}
|
||||
|
||||
// 简单估算:中文约 1.5 token/字,英文约 0.25 token/词
|
||||
return len([]rune(text))
|
||||
}
|
||||
Reference in New Issue
Block a user