@@ -19,12 +19,13 @@ type Conversation struct {
|
||||
Title string `gorm:"type:varchar(200)" json:"title"` // 对话标题
|
||||
|
||||
// 对话配置
|
||||
PresetID *uint `gorm:"index" json:"presetId"` // 使用的预设ID
|
||||
WorldbookID *uint `gorm:"index" json:"worldbookId"` // 使用的世界书ID
|
||||
AIProvider string `gorm:"type:varchar(50)" json:"aiProvider"` // AI提供商 (openai/anthropic)
|
||||
Model string `gorm:"type:varchar(100)" json:"model"` // 使用的模型
|
||||
Settings datatypes.JSON `gorm:"type:jsonb" json:"settings"` // 对话设置 (temperature等)
|
||||
WorldbookEnabled bool `gorm:"default:false" json:"worldbookEnabled"` // 是否启用世界书
|
||||
PresetID *uint `gorm:"index" json:"presetId"` // 使用的预设ID
|
||||
WorldbookID *uint `gorm:"index" json:"worldbookId"` // 使用的世界书ID
|
||||
AIProvider string `gorm:"type:varchar(50)" json:"aiProvider"` // AI提供商 (openai/anthropic)
|
||||
Model string `gorm:"type:varchar(100)" json:"model"` // 使用的模型
|
||||
Settings datatypes.JSON `gorm:"type:jsonb" json:"settings"` // 对话设置 (temperature等)
|
||||
WorldbookEnabled bool `gorm:"default:false" json:"worldbookEnabled"` // 是否启用世界书
|
||||
Variables datatypes.JSON `gorm:"type:jsonb;default:'{}'" json:"variables"` // 变量存储 ({{setvar::}}/{{getvar::}})
|
||||
|
||||
// 统计信息
|
||||
MessageCount int `gorm:"default:0" json:"messageCount"` // 消息数量
|
||||
|
||||
@@ -101,14 +101,10 @@ func (s *ConversationService) CreateConversation(userID uint, req *request.Creat
|
||||
userName = user.NickName
|
||||
}
|
||||
|
||||
// 应用输出阶段正则脚本处理开场白
|
||||
// 【重要】不再应用正则脚本处理开场白,保留原始内容
|
||||
// 让前端来处理 <Status_block> 和 <maintext> 的渲染
|
||||
processedFirstMes := character.FirstMes
|
||||
var regexService RegexScriptService
|
||||
outputScripts, err := regexService.GetScriptsForPlacement(userID, 1, &character.ID, nil)
|
||||
if err == nil && len(outputScripts) > 0 {
|
||||
processedFirstMes = regexService.ExecuteScripts(outputScripts, processedFirstMes, userName, character.Name)
|
||||
global.GVA_LOG.Info(fmt.Sprintf("开场白应用正则脚本: 原始长度=%d, 处理后长度=%d", len(character.FirstMes), len(processedFirstMes)))
|
||||
}
|
||||
global.GVA_LOG.Info(fmt.Sprintf("[开场白] 保留原始内容,长度=%d", len(processedFirstMes)))
|
||||
|
||||
firstMessage := app.Message{
|
||||
ConversationID: conversation.ID,
|
||||
@@ -418,16 +414,75 @@ func (s *ConversationService) SendMessage(userID, conversationID uint, req *requ
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 应用显示阶段的正则脚本 (Placement 3)
|
||||
displayContent := assistantMessage.Content
|
||||
displayScripts, err := regexService.GetScriptsForPlacement(userID, 3, &conversation.CharacterID, nil)
|
||||
if err == nil && len(displayScripts) > 0 {
|
||||
displayContent = regexService.ExecuteScripts(displayScripts, displayContent, userName, character.Name)
|
||||
global.GVA_LOG.Info(fmt.Sprintf("应用了 %d 个显示阶段正则脚本", len(displayScripts)))
|
||||
// 提取并保存变量 (从 AI 回复中提取 {{setvar::key::value}})
|
||||
newVars, cleanedContent := regexService.ExtractSetVars(assistantMessage.Content)
|
||||
if len(newVars) > 0 {
|
||||
// 加载现有变量
|
||||
var existingVars map[string]string
|
||||
if len(conversation.Variables) > 0 {
|
||||
json.Unmarshal(conversation.Variables, &existingVars)
|
||||
}
|
||||
if existingVars == nil {
|
||||
existingVars = make(map[string]string)
|
||||
}
|
||||
|
||||
// 合并新变量
|
||||
for k, v := range newVars {
|
||||
existingVars[k] = v
|
||||
}
|
||||
|
||||
// 保存回数据库
|
||||
varsJSON, _ := json.Marshal(existingVars)
|
||||
global.GVA_DB.Model(&conversation).Update("variables", datatypes.JSON(varsJSON))
|
||||
global.GVA_LOG.Info(fmt.Sprintf("提取并保存了 %d 个变量: %v", len(newVars), newVars))
|
||||
}
|
||||
|
||||
// 先替换 {{getvar::}} 为实际变量值(在应用正则脚本之前)
|
||||
var currentVars map[string]string
|
||||
if len(conversation.Variables) > 0 {
|
||||
json.Unmarshal(conversation.Variables, ¤tVars)
|
||||
}
|
||||
displayContent := cleanedContent // 使用清理后的内容(移除了 {{setvar::}})
|
||||
if currentVars != nil {
|
||||
displayContent = regexService.SubstituteGetVars(displayContent, currentVars)
|
||||
global.GVA_LOG.Info(fmt.Sprintf("替换了 {{getvar::}} 变量"))
|
||||
}
|
||||
|
||||
// 提取 <Status_block> 和 <maintext>,保护它们不被正则脚本修改
|
||||
statusBlock, contentWithoutStatus := regexService.ExtractStatusBlock(displayContent)
|
||||
maintext, contentWithoutMaintext := regexService.ExtractMaintext(contentWithoutStatus)
|
||||
|
||||
global.GVA_LOG.Info(fmt.Sprintf("[状态栏] 提取到 Status_block 长度: %d, maintext 长度: %d", len(statusBlock), len(maintext)))
|
||||
if len(statusBlock) > 0 {
|
||||
previewLen := len(statusBlock)
|
||||
if previewLen > 100 {
|
||||
previewLen = 100
|
||||
}
|
||||
global.GVA_LOG.Info(fmt.Sprintf("[状态栏] Status_block 内容预览: %s", statusBlock[:previewLen]))
|
||||
}
|
||||
|
||||
// 应用显示阶段的正则脚本 (Placement 3) - 只处理剩余内容
|
||||
finalProcessedContent := contentWithoutMaintext
|
||||
displayScripts, err2 := regexService.GetScriptsForPlacement(userID, 3, &conversation.CharacterID, nil)
|
||||
if err2 == nil && len(displayScripts) > 0 {
|
||||
global.GVA_LOG.Info(fmt.Sprintf("[状态栏] 应用正则脚本前的内容长度: %d", len(finalProcessedContent)))
|
||||
finalProcessedContent = regexService.ExecuteScripts(displayScripts, finalProcessedContent, userName, character.Name)
|
||||
global.GVA_LOG.Info(fmt.Sprintf("[状态栏] 应用了 %d 个显示阶段正则脚本,处理后内容长度: %d", len(displayScripts), len(finalProcessedContent)))
|
||||
}
|
||||
|
||||
// 重新组装内容:maintext + Status_block + 处理后的内容
|
||||
finalContent := finalProcessedContent
|
||||
if maintext != "" {
|
||||
finalContent = "<maintext>" + maintext + "</maintext>\n\n" + finalContent
|
||||
}
|
||||
if statusBlock != "" {
|
||||
finalContent = finalContent + "\n\n<Status_block>\n" + statusBlock + "\n</Status_block>"
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info(fmt.Sprintf("[状态栏] 最终返回内容长度: %d", len(finalContent)))
|
||||
|
||||
resp := response.ToMessageResponse(&assistantMessage)
|
||||
resp.Content = displayContent // 使用处理后的显示内容
|
||||
resp.Content = finalContent // 使用处理后的显示内容
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
@@ -577,31 +632,10 @@ func (s *ConversationService) callAIService(conversation app.Conversation, chara
|
||||
}
|
||||
global.GVA_LOG.Info(fmt.Sprintf("========== AI返回的完整内容 ==========\n%s\n==========================================", aiResponse))
|
||||
|
||||
// 应用输出阶段的正则脚本 (Placement 1)
|
||||
var regexService RegexScriptService
|
||||
global.GVA_LOG.Info(fmt.Sprintf("查询输出阶段正则脚本: userID=%d, placement=1, charID=%d", conversation.UserID, conversation.CharacterID))
|
||||
outputScripts, err := regexService.GetScriptsForPlacement(conversation.UserID, 1, &conversation.CharacterID, nil)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error(fmt.Sprintf("查询输出阶段正则脚本失败: %v", err))
|
||||
} else {
|
||||
global.GVA_LOG.Info(fmt.Sprintf("找到 %d 个输出阶段正则脚本", len(outputScripts)))
|
||||
if len(outputScripts) > 0 {
|
||||
// 获取用户信息
|
||||
var user app.AppUser
|
||||
err = global.GVA_DB.Where("id = ?", conversation.UserID).First(&user).Error
|
||||
userName := ""
|
||||
if err == nil {
|
||||
userName = user.Username
|
||||
if userName == "" {
|
||||
userName = user.NickName
|
||||
}
|
||||
}
|
||||
|
||||
originalResponse := aiResponse
|
||||
aiResponse = regexService.ExecuteScripts(outputScripts, aiResponse, userName, character.Name)
|
||||
global.GVA_LOG.Info(fmt.Sprintf("应用了 %d 个输出阶段正则脚本,原文: %s, 处理后: %s", len(outputScripts), originalResponse, aiResponse))
|
||||
}
|
||||
}
|
||||
// 【重要】不再应用 Placement 1 正则脚本,保留 AI 原始回复
|
||||
// 让 SendMessage 函数来提取和保护 <Status_block> 和 <maintext>
|
||||
// 前端会负责渲染这些标签
|
||||
global.GVA_LOG.Info("[AI回复] 保留原始内容,不应用输出阶段正则脚本")
|
||||
|
||||
return aiResponse, nil
|
||||
}
|
||||
|
||||
@@ -311,6 +311,78 @@ func (s *RegexScriptService) substituteMacros(text string, userName string, char
|
||||
return result
|
||||
}
|
||||
|
||||
// ExtractSetVars 从文本中提取 {{setvar::key::value}} 并返回变量映射和清理后的文本
|
||||
func (s *RegexScriptService) ExtractSetVars(text string) (map[string]string, string) {
|
||||
vars := make(map[string]string)
|
||||
|
||||
// 匹配 {{setvar::key::value}}
|
||||
re := regexp.MustCompile(`\{\{setvar::([^:]+)::([^}]*)\}\}`)
|
||||
matches := re.FindAllStringSubmatch(text, -1)
|
||||
|
||||
for _, match := range matches {
|
||||
if len(match) == 3 {
|
||||
key := strings.TrimSpace(match[1])
|
||||
value := match[2]
|
||||
vars[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// 从文本中移除所有 {{setvar::}} 标记
|
||||
cleanText := re.ReplaceAllString(text, "")
|
||||
|
||||
return vars, cleanText
|
||||
}
|
||||
|
||||
// SubstituteGetVars 替换文本中的 {{getvar::key}} 为实际变量值
|
||||
func (s *RegexScriptService) SubstituteGetVars(text string, variables map[string]string) string {
|
||||
result := text
|
||||
|
||||
// 匹配 {{getvar::key}}
|
||||
re := regexp.MustCompile(`\{\{getvar::([^}]+)\}\}`)
|
||||
result = re.ReplaceAllStringFunc(result, func(match string) string {
|
||||
matches := re.FindStringSubmatch(match)
|
||||
if len(matches) == 2 {
|
||||
key := strings.TrimSpace(matches[1])
|
||||
if value, ok := variables[key]; ok {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return "" // 如果变量不存在,返回空字符串
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ExtractStatusBlock 提取 <Status_block> 中的 YAML 数据
|
||||
func (s *RegexScriptService) ExtractStatusBlock(text string) (string, string) {
|
||||
// 匹配 <Status_block>...</Status_block>
|
||||
re := regexp.MustCompile(`(?s)<Status_block>\s*(.*?)\s*</Status_block>`)
|
||||
matches := re.FindStringSubmatch(text)
|
||||
|
||||
if len(matches) == 2 {
|
||||
statusBlock := strings.TrimSpace(matches[1])
|
||||
cleanText := re.ReplaceAllString(text, "")
|
||||
return statusBlock, cleanText
|
||||
}
|
||||
|
||||
return "", text
|
||||
}
|
||||
|
||||
// ExtractMaintext 提取 <maintext> 中的内容
|
||||
func (s *RegexScriptService) ExtractMaintext(text string) (string, string) {
|
||||
// 匹配 <maintext>...</maintext>
|
||||
re := regexp.MustCompile(`(?s)<maintext>\s*(.*?)\s*</maintext>`)
|
||||
matches := re.FindStringSubmatch(text)
|
||||
|
||||
if len(matches) == 2 {
|
||||
maintext := strings.TrimSpace(matches[1])
|
||||
cleanText := re.ReplaceAllString(text, "")
|
||||
return maintext, cleanText
|
||||
}
|
||||
|
||||
return "", text
|
||||
}
|
||||
|
||||
// GetScriptsForPlacement 获取指定阶段的脚本
|
||||
func (s *RegexScriptService) GetScriptsForPlacement(userID uint, placement int, charID *uint, presetID *uint) ([]app.RegexScript, error) {
|
||||
var scripts []app.RegexScript
|
||||
|
||||
Reference in New Issue
Block a user