695 lines
20 KiB
Go
695 lines
20 KiB
Go
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))
|
||
}
|