🎨 优化角色卡功能模块,后续待优化图像上传&前端流畅性待优化
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -174,3 +174,4 @@ dist
|
|||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
.claude
|
||||||
@@ -164,14 +164,28 @@ oracle:
|
|||||||
max-open-conns: 100
|
max-open-conns: 100
|
||||||
singular: false
|
singular: false
|
||||||
log-zap: false
|
log-zap: false
|
||||||
|
#pgsql:
|
||||||
|
# prefix: ""
|
||||||
|
# port: "5432"
|
||||||
|
# config: sslmode=disable TimeZone=Asia/Shanghai
|
||||||
|
# db-name: st_dev
|
||||||
|
# username: postgres
|
||||||
|
# password: loser765911.
|
||||||
|
# path: 149.88.74.188
|
||||||
|
# engine: ""
|
||||||
|
# log-mode: error
|
||||||
|
# max-idle-conns: 10
|
||||||
|
# max-open-conns: 100
|
||||||
|
# singular: false
|
||||||
|
# log-zap: false
|
||||||
pgsql:
|
pgsql:
|
||||||
prefix: ""
|
prefix: ""
|
||||||
port: "5432"
|
port: "5432"
|
||||||
config: sslmode=disable TimeZone=Asia/Shanghai
|
config: sslmode=disable TimeZone=Asia/Shanghai
|
||||||
db-name: st_dev
|
db-name: st_dev
|
||||||
username: postgres
|
username: loser
|
||||||
password: loser765911.
|
password: loser765911.
|
||||||
path: 149.88.74.188
|
path: pg.echol.top
|
||||||
engine: ""
|
engine: ""
|
||||||
log-mode: error
|
log-mode: error
|
||||||
max-idle-conns: 10
|
max-idle-conns: 10
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ func RegisterTables() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修复 PostgreSQL 序列(确保序列值与现有数据一致)
|
||||||
|
fixPostgresSequences()
|
||||||
|
|
||||||
// 创建向量索引(必须在 AutoMigrate 之后)
|
// 创建向量索引(必须在 AutoMigrate 之后)
|
||||||
CreateVectorIndexes()
|
CreateVectorIndexes()
|
||||||
|
|
||||||
@@ -111,3 +114,27 @@ func RegisterTables() {
|
|||||||
}
|
}
|
||||||
global.GVA_LOG.Info("register table success")
|
global.GVA_LOG.Info("register table success")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixPostgresSequences 修复 PostgreSQL 自增序列与现有数据不一致的问题
|
||||||
|
// AutoMigrate 后序列可能落后于已有数据的 max(id),导致插入时主键冲突
|
||||||
|
func fixPostgresSequences() {
|
||||||
|
if global.GVA_CONFIG.System.DbType != "pgsql" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要修复序列的表名列表
|
||||||
|
tables := []string{
|
||||||
|
"ai_characters",
|
||||||
|
"app_users",
|
||||||
|
"app_user_sessions",
|
||||||
|
"app_user_favorite_characters",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
seqName := table + "_id_seq"
|
||||||
|
sql := "SELECT setval('" + seqName + "', COALESCE((SELECT MAX(id) FROM " + table + "), 0) + 1, false)"
|
||||||
|
if err := global.GVA_DB.Exec(sql).Error; err != nil {
|
||||||
|
global.GVA_LOG.Warn("fix sequence failed", zap.String("table", table), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ type AICharacter struct {
|
|||||||
Version int `json:"version" gorm:"default:1;comment:角色版本"`
|
Version int `json:"version" gorm:"default:1;comment:角色版本"`
|
||||||
FirstMessage string `json:"firstMessage" gorm:"type:text;comment:第一条消息"`
|
FirstMessage string `json:"firstMessage" gorm:"type:text;comment:第一条消息"`
|
||||||
ExampleMessages pq.StringArray `json:"exampleMessages" gorm:"type:text[];comment:消息示例"`
|
ExampleMessages pq.StringArray `json:"exampleMessages" gorm:"type:text[];comment:消息示例"`
|
||||||
|
SystemPrompt string `json:"systemPrompt" gorm:"type:text;comment:系统提示词"`
|
||||||
|
PostHistoryInstructions string `json:"postHistoryInstructions" gorm:"type:text;comment:后置历史指令"`
|
||||||
|
AlternateGreetings pq.StringArray `json:"alternateGreetings" gorm:"type:text[];comment:备用问候语"`
|
||||||
|
CharacterBook datatypes.JSON `json:"characterBook" gorm:"type:jsonb;comment:角色书/世界信息"`
|
||||||
|
Extensions datatypes.JSON `json:"extensions" gorm:"type:jsonb;comment:扩展数据(depth_prompt等)"`
|
||||||
TotalChats int `json:"totalChats" gorm:"default:0;comment:对话总数"`
|
TotalChats int `json:"totalChats" gorm:"default:0;comment:对话总数"`
|
||||||
TotalLikes int `json:"totalLikes" gorm:"default:0;comment:点赞总数"`
|
TotalLikes int `json:"totalLikes" gorm:"default:0;comment:点赞总数"`
|
||||||
UsageCount int `json:"usageCount" gorm:"default:0;comment:使用次数"`
|
UsageCount int `json:"usageCount" gorm:"default:0;comment:使用次数"`
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ type CreateCharacterRequest struct {
|
|||||||
ExampleMessages []string `json:"exampleMessages"`
|
ExampleMessages []string `json:"exampleMessages"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
IsPublic bool `json:"isPublic"`
|
IsPublic bool `json:"isPublic"`
|
||||||
|
SystemPrompt string `json:"systemPrompt"`
|
||||||
|
PostHistoryInstructions string `json:"postHistoryInstructions"`
|
||||||
|
AlternateGreetings []string `json:"alternateGreetings"`
|
||||||
|
CharacterBook map[string]interface{} `json:"characterBook"`
|
||||||
|
Extensions map[string]interface{} `json:"extensions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCharacterRequest 更新角色卡请求
|
// UpdateCharacterRequest 更新角色卡请求
|
||||||
@@ -31,6 +36,11 @@ type UpdateCharacterRequest struct {
|
|||||||
ExampleMessages []string `json:"exampleMessages"`
|
ExampleMessages []string `json:"exampleMessages"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
IsPublic bool `json:"isPublic"`
|
IsPublic bool `json:"isPublic"`
|
||||||
|
SystemPrompt string `json:"systemPrompt"`
|
||||||
|
PostHistoryInstructions string `json:"postHistoryInstructions"`
|
||||||
|
AlternateGreetings []string `json:"alternateGreetings"`
|
||||||
|
CharacterBook map[string]interface{} `json:"characterBook"`
|
||||||
|
Extensions map[string]interface{} `json:"extensions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CharacterListRequest 角色卡列表请求
|
// CharacterListRequest 角色卡列表请求
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package response
|
package response
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"git.echol.cn/loser/st/server/model/app"
|
"git.echol.cn/loser/st/server/model/app"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -21,6 +22,11 @@ type CharacterResponse struct {
|
|||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
FirstMessage string `json:"firstMessage"`
|
FirstMessage string `json:"firstMessage"`
|
||||||
ExampleMessages []string `json:"exampleMessages"`
|
ExampleMessages []string `json:"exampleMessages"`
|
||||||
|
SystemPrompt string `json:"systemPrompt"`
|
||||||
|
PostHistoryInstructions string `json:"postHistoryInstructions"`
|
||||||
|
AlternateGreetings []string `json:"alternateGreetings"`
|
||||||
|
CharacterBook json.RawMessage `json:"characterBook"`
|
||||||
|
Extensions json.RawMessage `json:"extensions"`
|
||||||
TotalChats int `json:"totalChats"`
|
TotalChats int `json:"totalChats"`
|
||||||
TotalLikes int `json:"totalLikes"`
|
TotalLikes int `json:"totalLikes"`
|
||||||
UsageCount int `json:"usageCount"`
|
UsageCount int `json:"usageCount"`
|
||||||
@@ -52,6 +58,22 @@ func ToCharacterResponse(character *app.AICharacter, isFavorited bool) Character
|
|||||||
exampleMessages = character.ExampleMessages
|
exampleMessages = character.ExampleMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alternateGreetings := []string{}
|
||||||
|
if character.AlternateGreetings != nil {
|
||||||
|
alternateGreetings = character.AlternateGreetings
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 JSON 字段,确保返回有效 JSON(nil 时返回 null)
|
||||||
|
characterBook := json.RawMessage(character.CharacterBook)
|
||||||
|
if len(characterBook) == 0 {
|
||||||
|
characterBook = json.RawMessage("null")
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions := json.RawMessage(character.Extensions)
|
||||||
|
if len(extensions) == 0 {
|
||||||
|
extensions = json.RawMessage("null")
|
||||||
|
}
|
||||||
|
|
||||||
return CharacterResponse{
|
return CharacterResponse{
|
||||||
ID: character.ID,
|
ID: character.ID,
|
||||||
Name: character.Name,
|
Name: character.Name,
|
||||||
@@ -67,6 +89,11 @@ func ToCharacterResponse(character *app.AICharacter, isFavorited bool) Character
|
|||||||
Version: character.Version,
|
Version: character.Version,
|
||||||
FirstMessage: character.FirstMessage,
|
FirstMessage: character.FirstMessage,
|
||||||
ExampleMessages: exampleMessages,
|
ExampleMessages: exampleMessages,
|
||||||
|
SystemPrompt: character.SystemPrompt,
|
||||||
|
PostHistoryInstructions: character.PostHistoryInstructions,
|
||||||
|
AlternateGreetings: alternateGreetings,
|
||||||
|
CharacterBook: characterBook,
|
||||||
|
Extensions: extensions,
|
||||||
TotalChats: character.TotalChats,
|
TotalChats: character.TotalChats,
|
||||||
TotalLikes: character.TotalLikes,
|
TotalLikes: character.TotalLikes,
|
||||||
UsageCount: character.UsageCount,
|
UsageCount: character.UsageCount,
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.echol.cn/loser/st/server/global"
|
"git.echol.cn/loser/st/server/global"
|
||||||
"git.echol.cn/loser/st/server/model/app"
|
"git.echol.cn/loser/st/server/model/app"
|
||||||
@@ -192,7 +196,7 @@ func (cs *CharacterService) GetCharacterDetail(characterID uint, userID *uint) (
|
|||||||
|
|
||||||
// CreateCharacter 创建角色卡
|
// CreateCharacter 创建角色卡
|
||||||
func (cs *CharacterService) CreateCharacter(req request.CreateCharacterRequest, userID uint) (response.CharacterResponse, error) {
|
func (cs *CharacterService) CreateCharacter(req request.CreateCharacterRequest, userID uint) (response.CharacterResponse, error) {
|
||||||
// 构建 CardData
|
// 构建 CardData(包含完整 V2 数据)
|
||||||
cardData := map[string]interface{}{
|
cardData := map[string]interface{}{
|
||||||
"name": req.Name,
|
"name": req.Name,
|
||||||
"description": req.Description,
|
"description": req.Description,
|
||||||
@@ -202,6 +206,11 @@ func (cs *CharacterService) CreateCharacter(req request.CreateCharacterRequest,
|
|||||||
"example_messages": req.ExampleMessages,
|
"example_messages": req.ExampleMessages,
|
||||||
"creator_name": req.CreatorName,
|
"creator_name": req.CreatorName,
|
||||||
"creator_notes": req.CreatorNotes,
|
"creator_notes": req.CreatorNotes,
|
||||||
|
"system_prompt": req.SystemPrompt,
|
||||||
|
"post_history_instructions": req.PostHistoryInstructions,
|
||||||
|
"alternate_greetings": req.AlternateGreetings,
|
||||||
|
"character_book": req.CharacterBook,
|
||||||
|
"extensions": req.Extensions,
|
||||||
}
|
}
|
||||||
cardDataJSON, _ := json.Marshal(cardData)
|
cardDataJSON, _ := json.Marshal(cardData)
|
||||||
|
|
||||||
@@ -216,6 +225,20 @@ func (cs *CharacterService) CreateCharacter(req request.CreateCharacterRequest,
|
|||||||
exampleMessages = []string{}
|
exampleMessages = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alternateGreetings := req.AlternateGreetings
|
||||||
|
if alternateGreetings == nil {
|
||||||
|
alternateGreetings = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化 JSON 字段
|
||||||
|
var characterBookJSON, extensionsJSON datatypes.JSON
|
||||||
|
if req.CharacterBook != nil {
|
||||||
|
characterBookJSON, _ = json.Marshal(req.CharacterBook)
|
||||||
|
}
|
||||||
|
if req.Extensions != nil {
|
||||||
|
extensionsJSON, _ = json.Marshal(req.Extensions)
|
||||||
|
}
|
||||||
|
|
||||||
character := app.AICharacter{
|
character := app.AICharacter{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
@@ -230,6 +253,11 @@ func (cs *CharacterService) CreateCharacter(req request.CreateCharacterRequest,
|
|||||||
IsPublic: req.IsPublic,
|
IsPublic: req.IsPublic,
|
||||||
FirstMessage: req.FirstMessage,
|
FirstMessage: req.FirstMessage,
|
||||||
ExampleMessages: exampleMessages,
|
ExampleMessages: exampleMessages,
|
||||||
|
SystemPrompt: req.SystemPrompt,
|
||||||
|
PostHistoryInstructions: req.PostHistoryInstructions,
|
||||||
|
AlternateGreetings: alternateGreetings,
|
||||||
|
CharacterBook: characterBookJSON,
|
||||||
|
Extensions: extensionsJSON,
|
||||||
TokenCount: calculateTokenCount(req),
|
TokenCount: calculateTokenCount(req),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +285,7 @@ func (cs *CharacterService) UpdateCharacter(req request.UpdateCharacterRequest,
|
|||||||
return response.CharacterResponse{}, errors.New("无权修改")
|
return response.CharacterResponse{}, errors.New("无权修改")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建 CardData
|
// 构建 CardData(包含完整 V2 数据)
|
||||||
cardData := map[string]interface{}{
|
cardData := map[string]interface{}{
|
||||||
"name": req.Name,
|
"name": req.Name,
|
||||||
"description": req.Description,
|
"description": req.Description,
|
||||||
@@ -267,10 +295,15 @@ func (cs *CharacterService) UpdateCharacter(req request.UpdateCharacterRequest,
|
|||||||
"example_messages": req.ExampleMessages,
|
"example_messages": req.ExampleMessages,
|
||||||
"creator_name": req.CreatorName,
|
"creator_name": req.CreatorName,
|
||||||
"creator_notes": req.CreatorNotes,
|
"creator_notes": req.CreatorNotes,
|
||||||
|
"system_prompt": req.SystemPrompt,
|
||||||
|
"post_history_instructions": req.PostHistoryInstructions,
|
||||||
|
"alternate_greetings": req.AlternateGreetings,
|
||||||
|
"character_book": req.CharacterBook,
|
||||||
|
"extensions": req.Extensions,
|
||||||
}
|
}
|
||||||
cardDataJSON, _ := json.Marshal(cardData)
|
cardDataJSON, _ := json.Marshal(cardData)
|
||||||
|
|
||||||
// 处理标签和示例消息
|
// 处理标签和示例消息,转换为 pq.StringArray
|
||||||
tags := req.Tags
|
tags := req.Tags
|
||||||
if tags == nil {
|
if tags == nil {
|
||||||
tags = []string{}
|
tags = []string{}
|
||||||
@@ -281,25 +314,42 @@ func (cs *CharacterService) UpdateCharacter(req request.UpdateCharacterRequest,
|
|||||||
exampleMessages = []string{}
|
exampleMessages = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新
|
alternateGreetings := req.AlternateGreetings
|
||||||
updates := map[string]interface{}{
|
if alternateGreetings == nil {
|
||||||
"name": req.Name,
|
alternateGreetings = []string{}
|
||||||
"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
|
// 序列化 JSON 字段
|
||||||
|
var characterBookJSON, extensionsJSON datatypes.JSON
|
||||||
|
if req.CharacterBook != nil {
|
||||||
|
characterBookJSON, _ = json.Marshal(req.CharacterBook)
|
||||||
|
}
|
||||||
|
if req.Extensions != nil {
|
||||||
|
extensionsJSON, _ = json.Marshal(req.Extensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新字段 - 直接更新到结构体以避免类型转换问题
|
||||||
|
character.Name = req.Name
|
||||||
|
character.Description = req.Description
|
||||||
|
character.Personality = req.Personality
|
||||||
|
character.Scenario = req.Scenario
|
||||||
|
character.Avatar = req.Avatar
|
||||||
|
character.CreatorName = req.CreatorName
|
||||||
|
character.CreatorNotes = req.CreatorNotes
|
||||||
|
character.CardData = cardDataJSON
|
||||||
|
character.Tags = tags
|
||||||
|
character.IsPublic = req.IsPublic
|
||||||
|
character.FirstMessage = req.FirstMessage
|
||||||
|
character.ExampleMessages = exampleMessages
|
||||||
|
character.SystemPrompt = req.SystemPrompt
|
||||||
|
character.PostHistoryInstructions = req.PostHistoryInstructions
|
||||||
|
character.AlternateGreetings = alternateGreetings
|
||||||
|
character.CharacterBook = characterBookJSON
|
||||||
|
character.Extensions = extensionsJSON
|
||||||
|
character.TokenCount = calculateTokenCount(req)
|
||||||
|
character.Version = character.Version + 1
|
||||||
|
|
||||||
|
err = global.GVA_DB.Save(&character).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.CharacterResponse{}, err
|
return response.CharacterResponse{}, err
|
||||||
}
|
}
|
||||||
@@ -436,11 +486,25 @@ func (cs *CharacterService) ExportCharacter(characterID uint, userID *uint) (map
|
|||||||
exampleMessages = character.ExampleMessages
|
exampleMessages = character.ExampleMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alternateGreetings := []string{}
|
||||||
|
if character.AlternateGreetings != nil {
|
||||||
|
alternateGreetings = character.AlternateGreetings
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 character_book JSON
|
||||||
|
var characterBook map[string]interface{}
|
||||||
|
if len(character.CharacterBook) > 0 {
|
||||||
|
json.Unmarshal(character.CharacterBook, &characterBook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 extensions JSON
|
||||||
|
extensions := map[string]interface{}{}
|
||||||
|
if len(character.Extensions) > 0 {
|
||||||
|
json.Unmarshal(character.Extensions, &extensions)
|
||||||
|
}
|
||||||
|
|
||||||
// 构建导出数据(兼容 SillyTavern 格式)
|
// 构建导出数据(兼容 SillyTavern 格式)
|
||||||
exportData := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"spec": "chara_card_v2",
|
|
||||||
"spec_version": "2.0",
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"name": character.Name,
|
"name": character.Name,
|
||||||
"description": character.Description,
|
"description": character.Description,
|
||||||
"personality": character.Personality,
|
"personality": character.Personality,
|
||||||
@@ -448,13 +512,24 @@ func (cs *CharacterService) ExportCharacter(characterID uint, userID *uint) (map
|
|||||||
"first_mes": character.FirstMessage,
|
"first_mes": character.FirstMessage,
|
||||||
"mes_example": strings.Join(exampleMessages, "\n<START>\n"),
|
"mes_example": strings.Join(exampleMessages, "\n<START>\n"),
|
||||||
"creator_notes": character.CreatorNotes,
|
"creator_notes": character.CreatorNotes,
|
||||||
"system_prompt": "",
|
"system_prompt": character.SystemPrompt,
|
||||||
"post_history_instructions": "",
|
"post_history_instructions": character.PostHistoryInstructions,
|
||||||
"tags": tags,
|
"tags": tags,
|
||||||
"creator": character.CreatorName,
|
"creator": character.CreatorName,
|
||||||
"character_version": character.Version,
|
"character_version": character.Version,
|
||||||
"extensions": map[string]interface{}{},
|
"alternate_greetings": alternateGreetings,
|
||||||
},
|
"extensions": extensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅在存在时添加 character_book
|
||||||
|
if characterBook != nil {
|
||||||
|
data["character_book"] = characterBook
|
||||||
|
}
|
||||||
|
|
||||||
|
exportData := map[string]interface{}{
|
||||||
|
"spec": "chara_card_v2",
|
||||||
|
"spec_version": "2.0",
|
||||||
|
"data": data,
|
||||||
}
|
}
|
||||||
|
|
||||||
return exportData, nil
|
return exportData, nil
|
||||||
@@ -572,28 +647,30 @@ func convertCardToCreateRequest(card *utils.CharacterCardV2, avatarData []byte,
|
|||||||
exampleMessages := []string{}
|
exampleMessages := []string{}
|
||||||
if card.Data.MesExample != "" {
|
if card.Data.MesExample != "" {
|
||||||
// 按 <START> 分割
|
// 按 <START> 分割
|
||||||
exampleMessages = strings.Split(card.Data.MesExample, "<START>")
|
parts := strings.Split(card.Data.MesExample, "<START>")
|
||||||
// 清理空白
|
for _, msg := range parts {
|
||||||
cleaned := []string{}
|
|
||||||
for _, msg := range exampleMessages {
|
|
||||||
msg = strings.TrimSpace(msg)
|
msg = strings.TrimSpace(msg)
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
cleaned = append(cleaned, msg)
|
exampleMessages = append(exampleMessages, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exampleMessages = cleaned
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 合并备用问候语
|
// 备用问候语独立存储,不再合并到 exampleMessages
|
||||||
if len(card.Data.AlternateGreetings) > 0 {
|
alternateGreetings := card.Data.AlternateGreetings
|
||||||
exampleMessages = append(exampleMessages, card.Data.AlternateGreetings...)
|
if alternateGreetings == nil {
|
||||||
|
alternateGreetings = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 处理头像数据,上传到文件服务器
|
// 保存头像到本地文件
|
||||||
avatar := ""
|
avatar := ""
|
||||||
if avatarData != nil {
|
if avatarData != nil {
|
||||||
// 这里应该将头像上传到文件服务器并获取 URL
|
savedPath, err := saveAvatarFromBytes(avatarData, card.Data.Name, ".png")
|
||||||
// avatar = uploadAvatar(avatarData)
|
if err != nil {
|
||||||
|
global.GVA_LOG.Warn("保存角色卡头像失败", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
avatar = savedPath
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return request.CreateCharacterRequest{
|
return request.CreateCharacterRequest{
|
||||||
@@ -608,6 +685,11 @@ func convertCardToCreateRequest(card *utils.CharacterCardV2, avatarData []byte,
|
|||||||
ExampleMessages: exampleMessages,
|
ExampleMessages: exampleMessages,
|
||||||
Tags: card.Data.Tags,
|
Tags: card.Data.Tags,
|
||||||
IsPublic: isPublic,
|
IsPublic: isPublic,
|
||||||
|
SystemPrompt: card.Data.SystemPrompt,
|
||||||
|
PostHistoryInstructions: card.Data.PostHistoryInstructions,
|
||||||
|
AlternateGreetings: alternateGreetings,
|
||||||
|
CharacterBook: card.Data.CharacterBook,
|
||||||
|
Extensions: card.Data.Extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,6 +705,23 @@ func convertCharacterToCard(character *app.AICharacter) *utils.CharacterCardV2 {
|
|||||||
exampleMessages = character.ExampleMessages
|
exampleMessages = character.ExampleMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alternateGreetings := []string{}
|
||||||
|
if character.AlternateGreetings != nil {
|
||||||
|
alternateGreetings = character.AlternateGreetings
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 character_book JSON
|
||||||
|
var characterBook map[string]interface{}
|
||||||
|
if len(character.CharacterBook) > 0 {
|
||||||
|
json.Unmarshal(character.CharacterBook, &characterBook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 extensions JSON
|
||||||
|
extensions := map[string]interface{}{}
|
||||||
|
if len(character.Extensions) > 0 {
|
||||||
|
json.Unmarshal(character.Extensions, &extensions)
|
||||||
|
}
|
||||||
|
|
||||||
return &utils.CharacterCardV2{
|
return &utils.CharacterCardV2{
|
||||||
Spec: "chara_card_v2",
|
Spec: "chara_card_v2",
|
||||||
SpecVersion: "2.0",
|
SpecVersion: "2.0",
|
||||||
@@ -634,13 +733,14 @@ func convertCharacterToCard(character *app.AICharacter) *utils.CharacterCardV2 {
|
|||||||
FirstMes: character.FirstMessage,
|
FirstMes: character.FirstMessage,
|
||||||
MesExample: strings.Join(exampleMessages, "\n<START>\n"),
|
MesExample: strings.Join(exampleMessages, "\n<START>\n"),
|
||||||
CreatorNotes: character.CreatorNotes,
|
CreatorNotes: character.CreatorNotes,
|
||||||
SystemPrompt: "",
|
SystemPrompt: character.SystemPrompt,
|
||||||
PostHistoryInstructions: "",
|
PostHistoryInstructions: character.PostHistoryInstructions,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
Creator: character.CreatorName,
|
Creator: character.CreatorName,
|
||||||
CharacterVersion: fmt.Sprintf("%d", character.Version),
|
CharacterVersion: fmt.Sprintf("%d", character.Version),
|
||||||
AlternateGreetings: []string{},
|
AlternateGreetings: alternateGreetings,
|
||||||
Extensions: map[string]interface{}{},
|
CharacterBook: characterBook,
|
||||||
|
Extensions: extensions,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -673,17 +773,38 @@ func createDefaultAvatar() image.Image {
|
|||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saveAvatarFromBytes 将头像原始字节数据保存到本地文件系统
|
||||||
|
func saveAvatarFromBytes(data []byte, name string, ext string) (string, error) {
|
||||||
|
// 生成唯一文件名:MD5(name) + 时间戳
|
||||||
|
hash := md5.Sum([]byte(name))
|
||||||
|
hashStr := hex.EncodeToString(hash[:])
|
||||||
|
filename := hashStr + "_" + time.Now().Format("20060102150405") + ext
|
||||||
|
|
||||||
|
storePath := global.GVA_CONFIG.Local.StorePath
|
||||||
|
if err := os.MkdirAll(storePath, os.ModePerm); err != nil {
|
||||||
|
return "", fmt.Errorf("创建存储目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := storePath + "/" + filename
|
||||||
|
if err := os.WriteFile(filePath, data, 0644); err != nil {
|
||||||
|
return "", fmt.Errorf("写入文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回访问路径
|
||||||
|
return global.GVA_CONFIG.Local.Path + "/" + filename, nil
|
||||||
|
}
|
||||||
|
|
||||||
// calculateTokenCount 计算角色卡的 Token 数量(简单估算)
|
// calculateTokenCount 计算角色卡的 Token 数量(简单估算)
|
||||||
func calculateTokenCount(req interface{}) int {
|
func calculateTokenCount(req interface{}) int {
|
||||||
var text string
|
var text string
|
||||||
switch v := req.(type) {
|
switch v := req.(type) {
|
||||||
case request.CreateCharacterRequest:
|
case request.CreateCharacterRequest:
|
||||||
text = v.Name + v.Description + v.Personality + v.Scenario + v.FirstMessage
|
text = v.Name + v.Description + v.Personality + v.Scenario + v.FirstMessage + v.SystemPrompt + v.PostHistoryInstructions
|
||||||
for _, msg := range v.ExampleMessages {
|
for _, msg := range v.ExampleMessages {
|
||||||
text += msg
|
text += msg
|
||||||
}
|
}
|
||||||
case request.UpdateCharacterRequest:
|
case request.UpdateCharacterRequest:
|
||||||
text = v.Name + v.Description + v.Personality + v.Scenario + v.FirstMessage
|
text = v.Name + v.Description + v.Personality + v.Scenario + v.FirstMessage + v.SystemPrompt + v.PostHistoryInstructions
|
||||||
for _, msg := range v.ExampleMessages {
|
for _, msg := range v.ExampleMessages {
|
||||||
text += msg
|
text += msg
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type CharacterCardV2Data struct {
|
|||||||
Creator string `json:"creator"`
|
Creator string `json:"creator"`
|
||||||
CharacterVersion string `json:"character_version"`
|
CharacterVersion string `json:"character_version"`
|
||||||
AlternateGreetings []string `json:"alternate_greetings"`
|
AlternateGreetings []string `json:"alternate_greetings"`
|
||||||
|
CharacterBook map[string]interface{} `json:"character_book,omitempty"`
|
||||||
Extensions map[string]interface{} `json:"extensions"`
|
Extensions map[string]interface{} `json:"extensions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
web-app-vue/src/components.d.ts
vendored
4
web-app-vue/src/components.d.ts
vendored
@@ -16,6 +16,8 @@ declare module 'vue' {
|
|||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
|
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||||
|
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
@@ -34,6 +36,8 @@ declare module 'vue' {
|
|||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
|
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||||
|
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||||
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
||||||
|
|||||||
12
web-app-vue/src/types/character.d.ts
vendored
12
web-app-vue/src/types/character.d.ts
vendored
@@ -18,6 +18,11 @@ export interface Character {
|
|||||||
version: number
|
version: number
|
||||||
firstMessage: string
|
firstMessage: string
|
||||||
exampleMessages: string[]
|
exampleMessages: string[]
|
||||||
|
systemPrompt: string
|
||||||
|
postHistoryInstructions: string
|
||||||
|
alternateGreetings: string[]
|
||||||
|
characterBook: any
|
||||||
|
extensions: Record<string, any>
|
||||||
totalChats: number
|
totalChats: number
|
||||||
totalLikes: number
|
totalLikes: number
|
||||||
usageCount: number
|
usageCount: number
|
||||||
@@ -58,6 +63,11 @@ export interface CreateCharacterRequest {
|
|||||||
exampleMessages?: string[]
|
exampleMessages?: string[]
|
||||||
tags?: string[]
|
tags?: string[]
|
||||||
isPublic: boolean
|
isPublic: boolean
|
||||||
|
systemPrompt?: string
|
||||||
|
postHistoryInstructions?: string
|
||||||
|
alternateGreetings?: string[]
|
||||||
|
characterBook?: any
|
||||||
|
extensions?: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新角色卡请求
|
// 更新角色卡请求
|
||||||
@@ -82,6 +92,8 @@ export interface CharacterExportData {
|
|||||||
tags: string[]
|
tags: string[]
|
||||||
creator: string
|
creator: string
|
||||||
character_version: number
|
character_version: number
|
||||||
|
alternate_greetings: string[]
|
||||||
|
character_book?: any
|
||||||
extensions: Record<string, any>
|
extensions: Record<string, any>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
{{ character.isFavorited ? '已收藏' : '收藏' }}
|
{{ character.isFavorited ? '已收藏' : '收藏' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
:icon="Like"
|
:icon="Top"
|
||||||
@click="handleLike"
|
@click="handleLike"
|
||||||
>
|
>
|
||||||
点赞 {{ character.totalLikes }}
|
点赞 {{ character.totalLikes }}
|
||||||
@@ -107,6 +107,16 @@
|
|||||||
<h3>场景设定</h3>
|
<h3>场景设定</h3>
|
||||||
<p class="detail-text">{{ character.scenario || '暂无场景设定' }}</p>
|
<p class="detail-text">{{ character.scenario || '暂无场景设定' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="character.systemPrompt" class="detail-section">
|
||||||
|
<h3>系统提示词</h3>
|
||||||
|
<div class="message-box">{{ character.systemPrompt }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="character.postHistoryInstructions" class="detail-section">
|
||||||
|
<h3>后置历史指令</h3>
|
||||||
|
<div class="message-box">{{ character.postHistoryInstructions }}</div>
|
||||||
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane label="对话示例">
|
<el-tab-pane label="对话示例">
|
||||||
@@ -129,6 +139,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane v-if="character.alternateGreetings && character.alternateGreetings.length > 0" label="备用问候语">
|
||||||
|
<div class="detail-section">
|
||||||
|
<div
|
||||||
|
v-for="(greeting, index) in character.alternateGreetings"
|
||||||
|
:key="index"
|
||||||
|
class="message-box"
|
||||||
|
>
|
||||||
|
<div class="greeting-label">问候语 {{ index + 1 }}</div>
|
||||||
|
{{ greeting }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane label="统计信息">
|
<el-tab-pane label="统计信息">
|
||||||
<el-descriptions :column="2" border>
|
<el-descriptions :column="2" border>
|
||||||
<el-descriptions-item label="对话总数">
|
<el-descriptions-item label="对话总数">
|
||||||
@@ -155,6 +178,12 @@
|
|||||||
<el-descriptions-item label="更新时间">
|
<el-descriptions-item label="更新时间">
|
||||||
{{ formatDateTime(character.updatedAt) }}
|
{{ formatDateTime(character.updatedAt) }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item v-if="character.extensions && Object.keys(character.extensions).length > 0" label="扩展数据">
|
||||||
|
{{ Object.keys(character.extensions).join(', ') }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item v-if="character.characterBook" label="角色书">
|
||||||
|
已配置
|
||||||
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
@@ -181,7 +210,7 @@ import {
|
|||||||
ChatLineSquare,
|
ChatLineSquare,
|
||||||
Star,
|
Star,
|
||||||
StarFilled,
|
StarFilled,
|
||||||
Like,
|
Top,
|
||||||
Download,
|
Download,
|
||||||
Edit,
|
Edit,
|
||||||
Delete,
|
Delete,
|
||||||
@@ -422,5 +451,12 @@ onMounted(async () => {
|
|||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.greeting-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -131,6 +131,82 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="系统提示词">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.systemPrompt"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="系统提示词(System Prompt),用于设定AI的行为和规则..."
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="后置历史指令">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.postHistoryInstructions"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="后置历史指令(Post History Instructions),会被放在对话历史之后..."
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="备用问候语">
|
||||||
|
<div class="example-messages">
|
||||||
|
<div
|
||||||
|
v-for="(msg, index) in formData.alternateGreetings"
|
||||||
|
:key="'ag-' + index"
|
||||||
|
class="example-message-item"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="formData.alternateGreetings[index]"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="输入备用问候语..."
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
:icon="Delete"
|
||||||
|
circle
|
||||||
|
@click="removeAlternateGreeting(index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:icon="Plus"
|
||||||
|
plain
|
||||||
|
@click="addAlternateGreeting"
|
||||||
|
>
|
||||||
|
添加备用问候语
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="form-section">
|
||||||
|
<template #header>
|
||||||
|
<span>高级设置</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-form-item label="角色书">
|
||||||
|
<el-input
|
||||||
|
v-model="characterBookText"
|
||||||
|
type="textarea"
|
||||||
|
:rows="6"
|
||||||
|
placeholder="角色书/世界信息(JSON 格式),通常通过导入角色卡获取..."
|
||||||
|
/>
|
||||||
|
<span class="form-tip">角色书数据为 JSON 格式,通常通过导入角色卡自动填入</span>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="扩展数据">
|
||||||
|
<el-input
|
||||||
|
v-model="extensionsText"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="扩展数据(JSON 格式),如 depth_prompt 等..."
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<span class="form-tip">扩展数据为只读字段,保留原版酒馆的扩展信息</span>
|
||||||
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="form-section">
|
<el-card class="form-section">
|
||||||
@@ -199,7 +275,36 @@ const formData = reactive<CreateCharacterRequest>({
|
|||||||
firstMessage: '',
|
firstMessage: '',
|
||||||
exampleMessages: [],
|
exampleMessages: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
isPublic: false
|
isPublic: false,
|
||||||
|
systemPrompt: '',
|
||||||
|
postHistoryInstructions: '',
|
||||||
|
alternateGreetings: [],
|
||||||
|
characterBook: null,
|
||||||
|
extensions: null
|
||||||
|
})
|
||||||
|
|
||||||
|
// 角色书和扩展数据的文本表示(用于 JSON 编辑)
|
||||||
|
const characterBookText = computed({
|
||||||
|
get: () => {
|
||||||
|
if (!formData.characterBook) return ''
|
||||||
|
return JSON.stringify(formData.characterBook, null, 2)
|
||||||
|
},
|
||||||
|
set: (val: string) => {
|
||||||
|
if (!val.trim()) {
|
||||||
|
formData.characterBook = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
formData.characterBook = JSON.parse(val)
|
||||||
|
} catch {
|
||||||
|
// 解析失败时不更新,保留原值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const extensionsText = computed(() => {
|
||||||
|
if (!formData.extensions) return ''
|
||||||
|
return JSON.stringify(formData.extensions, null, 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 常用标签
|
// 常用标签
|
||||||
@@ -238,6 +343,16 @@ function removeExampleMessage(index: number) {
|
|||||||
formData.exampleMessages?.splice(index, 1)
|
formData.exampleMessages?.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加备用问候语
|
||||||
|
function addAlternateGreeting() {
|
||||||
|
formData.alternateGreetings?.push('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除备用问候语
|
||||||
|
function removeAlternateGreeting(index: number) {
|
||||||
|
formData.alternateGreetings?.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
// 返回
|
// 返回
|
||||||
function goBack() {
|
function goBack() {
|
||||||
router.back()
|
router.back()
|
||||||
@@ -296,7 +411,12 @@ onMounted(async () => {
|
|||||||
firstMessage: character.firstMessage,
|
firstMessage: character.firstMessage,
|
||||||
exampleMessages: [...(character.exampleMessages || [])],
|
exampleMessages: [...(character.exampleMessages || [])],
|
||||||
tags: [...(character.tags || [])],
|
tags: [...(character.tags || [])],
|
||||||
isPublic: character.isPublic
|
isPublic: character.isPublic,
|
||||||
|
systemPrompt: character.systemPrompt || '',
|
||||||
|
postHistoryInstructions: character.postHistoryInstructions || '',
|
||||||
|
alternateGreetings: [...(character.alternateGreetings || [])],
|
||||||
|
characterBook: character.characterBook || null,
|
||||||
|
extensions: character.extensions || null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user