From d3c206b4074ee86f39e1055a3c1e3489b3dc2d0d Mon Sep 17 00:00:00 2001 From: Ford <3014117377@qq.com> Date: Tue, 29 Oct 2024 20:09:38 +0800 Subject: [PATCH] 添加了用户画像的创建和查询相关世界的用户画像 --- config.json | 2 +- src/controllers/NpcController.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------ src/main.go | 9 ++++----- src/models/NPCRole.go | 23 ++++++++++++++++++++++- src/service/WorldChat_HighConcurrency.go | 34 +++++++++++++++++++++++++++++++--- src/service/WorldChat_HighConcurrency.go_bak | 1278 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1395 insertions(+), 64 deletions(-) create mode 100644 src/service/WorldChat_HighConcurrency.go_bak diff --git a/config.json b/config.json index 559d4b0..d98362e 100644 --- a/config.json +++ b/config.json @@ -211,7 +211,7 @@ }, "npc_role_detail_translations": { - "roleName": "角色名称", + "roleName": "角色名", "introduction": "简介", "consumptionExpectations": "消费心理预期", "psychologicalTraits": "心理特征", diff --git a/src/controllers/NpcController.go b/src/controllers/NpcController.go index b134e29..7dc52a4 100644 --- a/src/controllers/NpcController.go +++ b/src/controllers/NpcController.go @@ -1,10 +1,12 @@ package controllers import ( + "WorldEpcho/src/config" "WorldEpcho/src/config/e" database "WorldEpcho/src/datasource" "WorldEpcho/src/models" "WorldEpcho/src/service" + "WorldEpcho/src/utils" "encoding/json" "fmt" "github.com/gin-gonic/gin" @@ -33,38 +35,17 @@ import ( */ //创建角色画像的请求体 type NpcRoleRequest struct { - Name string `json:"name"` // 名称 - Details Details `json:"details"` //详情 - Gender string `json:"gender"` //性别 - Tags string `json:"tags"` //世界标签集 - RelatedWorlds string `json:"relatedWorlds"` //相关世界 - UpdateTime int64 `json:"updateTime"` //更新时间 - CreateTime int64 `json:"createTime"` //构造时间 - ServiceProviderId string `json:"authId,omitempty"` // ServiceProviderId + Name string `json:"name"` // 名称 + Details models.Details `json:"details"` //详情 + Gender string `json:"gender"` //性别 + Tags string `json:"tags"` //世界标签集 + RelatedWorlds string `json:"relatedWorlds"` //相关世界 + UpdateTime int64 `json:"updateTime"` //更新时间 + CreateTime int64 `json:"createTime"` //构造时间 + ServiceProviderId string `json:"authId,omitempty"` // ServiceProviderId } -// Detail 用于解析详细信息中的 JSON 字符串 -type Details struct { - RoleName string `json:"roleName"` //角色名称 - Introduction Introduction `json:"introduction"` //简介 - ConsumptionExpectations []string `json:"consumptionExpectations"` //消费心理预期 - PsychologicalTraits []string `json:"psychologicalTraits"` //心理特征 - Appearance string `json:"appearance"` //外观 - Personality string `json:"personality"` //性格 - DecisionMakingStyle string `json:"decisionMakingStyle"` //决策风格 - RiskPreference string `json:"riskPreference"` //风险偏好 - AttitudeTowardsUsers string `json:"attitudeTowardsUsers"` //对用户的态度 -} - -//个人传记 Personal profile -type Introduction struct { - EducationLevel string `json:"educationLevel"` //教育水平 - ProfessionalBackground string `json:"professionalBackground"` //职业背景 - Purpose string `json:"purpose"` //目的性 - KnownInformation string `json:"knownInformation"` //已知信息 -} - //创建Npc用户画像的接口 func CreateNpcUserPortrait(ctx *gin.Context) { @@ -138,24 +119,22 @@ func CreateNpcUserPortrait(ctx *gin.Context) { /* configData 英文键转换为中文 */ - /* - var data map[string]interface{} - if err := json.Unmarshal([]byte(string(detailsJSON)), &data); err != nil { - ctx.JSON(http.StatusOK, gin.H{"code": e.ParamParseError, "data": nil, "message": "解析用户画像详细信息数据出错"}) - return - } - // 转换键名 => 英文转中文 - NpcRoleDetailTranslations := config.Conf.NpcRoleDetailTranslations - translatedData := utils.TranslateKeysEnglishToChinese(NpcRoleDetailTranslations, data) - - translatedJSON, err := json.MarshalIndent(translatedData, "", " ") - if err != nil { - fmt.Println("Error marshalling JSON:", err) - ctx.JSON(http.StatusOK, gin.H{"code": e.ParamParseError, "data": nil, "message": "解析用户画像详细信息数据出错"}) - return - } - fmt.Println(" Details translatedJSON ==> ", string(translatedJSON)) - */ + var data map[string]interface{} + if err := json.Unmarshal([]byte(string(detailsJSON)), &data); err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": e.ParamParseError, "data": nil, "message": "解析用户画像详细信息数据出错"}) + return + } + // 转换键名 => 英文转中文 + NpcRoleDetailTranslations := config.Conf.NpcRoleDetailTranslations + translatedData := utils.TranslateKeysEnglishToChinese(NpcRoleDetailTranslations, data) + + translatedJSON, err := json.MarshalIndent(translatedData, "", " ") + if err != nil { + fmt.Println("Error marshalling JSON:", err) + ctx.JSON(http.StatusOK, gin.H{"code": e.ParamParseError, "data": nil, "message": "解析用户画像详细信息数据出错"}) + return + } + fmt.Println(" Details translatedJSON ==> ", string(translatedJSON)) // 创建一个空切片存储相关世界 relatedWorldIds := []int64{} @@ -178,16 +157,18 @@ func CreateNpcUserPortrait(ctx *gin.Context) { } //用户画像详情信息 - // 将Details结构体转换为 JSON - jsonData, err := json.Marshal(NpcRequest.Details) - if err != nil { - fmt.Println("Error:", err) - return - } + /* + // 将Details结构体转换为 JSON + jsonData, err := json.Marshal(NpcRequest.Details) + if err != nil { + fmt.Println("Error:", err) + return + } + */ npcUserRole := &models.NpcRole{ Name: NpcRequest.Name, - Details: string(jsonData), + Details: string(translatedJSON), Gender: NpcRequest.Gender, Tags: matchingTags, RelatedWorlds: relatedWorldIds, @@ -296,6 +277,30 @@ func QueryNpcRoleInfo(ctx *gin.Context) { fmt.Println("根据相关世界ID查询用户画像信息出错") return } + /* + 转换世界配置信息中的英文字段 + */ + var data map[string]interface{} + + for i, npcRole := range npcRoles { + if err := json.Unmarshal([]byte(npcRole.Details), &data); err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": e.ParamParseError, "data": nil, "message": "解析用户画像详情信息数据出错"}) + return + } + // 转换键名 + // 中文转英文 + ReverseTranslations := config.Conf.NpcRoleDetailReverseTranslations + translatedData := utils.TranslateKeysChineseToEnglish(ReverseTranslations, data) + translatedJSON, err := json.MarshalIndent(translatedData, "", " ") + if err != nil { + fmt.Println("Error marshalling JSON:", err) + ctx.JSON(http.StatusOK, gin.H{"code": e.ParamParseError, "data": nil, "message": "解析用户画像详情信息数据出错"}) + return + } + npcRoles[i].Details = string(translatedJSON) + fmt.Println(" configData translatedJSON ==> ", string(translatedJSON)) + } + //提交事务 err = session.Commit() if err != nil { diff --git a/src/main.go b/src/main.go index 7a149b0..6d315ab 100644 --- a/src/main.go +++ b/src/main.go @@ -4,7 +4,6 @@ import ( "WorldEpcho/src/config" "WorldEpcho/src/datasource" "WorldEpcho/src/https_auth" - "WorldEpcho/src/models" "WorldEpcho/src/routers" "WorldEpcho/src/service" "WorldEpcho/src/utils" @@ -25,10 +24,10 @@ func main() { config.RandInit() datasource.InitMysql() // 同步结构体与数据库表 - err := datasource.Engine.Sync2(new(models.NpcRole)) - if err != nil { - fmt.Printf("创建表失败:", err) - } + //err := datasource.Engine.Sync2(new(models.NpcRole)) + //if err != nil { + // fmt.Printf("创建表失败:", err) + //} //生成一个9位的邀请码 //println("生成一个9位的邀请码: ", utils.GenerateInviteCode(1)) diff --git a/src/models/NPCRole.go b/src/models/NPCRole.go index 459e34b..79eb455 100644 --- a/src/models/NPCRole.go +++ b/src/models/NPCRole.go @@ -20,6 +20,27 @@ type NpcRole struct { ServiceProviderId string `xorm:"-" json:"authId,omitempty"` // ServiceProviderId 在数据库中忽略 } +// Detail 用于解析详细信息中的 JSON 字符串 +type Details struct { + RoleName string `json:"roleName"` //角色名称 + Introduction Introduction `json:"introduction"` //简介 + ConsumptionExpectations []string `json:"consumptionExpectations"` //消费心理预期 + PsychologicalTraits []string `json:"psychologicalTraits"` //心理特征 + Appearance string `json:"appearance"` //外观 + Personality string `json:"personality"` //性格 + DecisionMakingStyle string `json:"decisionMakingStyle"` //决策风格 + RiskPreference string `json:"riskPreference"` //风险偏好 + AttitudeTowardsUsers string `json:"attitudeTowardsUsers"` //对用户的态度 +} + +//个人传记 Personal profile +type Introduction struct { + EducationLevel string `json:"educationLevel"` //教育水平 + ProfessionalBackground string `json:"professionalBackground"` //职业背景 + Purpose string `json:"purpose"` //目的性 + KnownInformation string `json:"knownInformation"` //已知信息 +} + // 增加一个新的 NPC 角色 func CreateNpcRole(session *xorm.Session, npcRole *NpcRole) (*NpcRole, error) { _, err := session.Insert(npcRole) @@ -62,7 +83,7 @@ func UpdateNpcRole(npcRole *NpcRole) error { } // 根据 ID 查询单个 NPC 角色 -func GetNpcRoleByID(id int) (*NpcRole, error) { +func GetNpcRoleByID(id int64) (*NpcRole, error) { npcRole := new(NpcRole) has, err := datasource.Engine.ID(id).Get(npcRole) if err != nil { diff --git a/src/service/WorldChat_HighConcurrency.go b/src/service/WorldChat_HighConcurrency.go index b9ef183..2f27b02 100644 --- a/src/service/WorldChat_HighConcurrency.go +++ b/src/service/WorldChat_HighConcurrency.go @@ -126,7 +126,8 @@ type WorldErrorMessage struct { // Wrapper 结构体仅包含一个字符串字段,用于存储 JSON 字符串 type Wrapper struct { - ConfigData map[string]interface{} `json:"ConfigData"` + ConfigData map[string]interface{} `json:"ConfigData"` + EmbeddingNpcs []map[string]interface{} `json:"EmbeddingNpcs" ` } //世界websocket控制器 @@ -245,6 +246,7 @@ func WorldWsHandler(ctx *gin.Context) { world_Id := ctx.Query("worldId") var world *models.WorldInfo var userInfo string + var roleInfo *models.NpcRole if worldName != "" { world, err = models.GetWorldInfoByName(worldName) @@ -279,6 +281,20 @@ func WorldWsHandler(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"code": e.NotFound, "data": nil, "message": "世界名称不存在"}) return } + NpcRoleId := ctx.Query("NpcRoleId") + if NpcRoleId != "" { + NpcRole_Id, err := strconv.ParseInt(NpcRoleId, 10, 64) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "用户画像ID类型转换出错"}) + return + } + roleInfo, err = models.GetNpcRoleByID(NpcRole_Id) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "根据用户画像ID查询画像信息出错"}) + return + } + + } if b || SP != nil { fmt.Println("ServiceProvider ===> ", SP) @@ -287,6 +303,7 @@ func WorldWsHandler(ctx *gin.Context) { if user_info != "" { userInfo = user_info } + //拼接会话主题 //ConversationTitle = "用户Id为" + user_Id + "的用户和数字人" + strings.Join(DpNames, "和") + "开始会话" //fmt.Println("ConversationTitle: ", ConversationTitle) @@ -295,10 +312,22 @@ func WorldWsHandler(ctx *gin.Context) { BgInfo = world.Background } if user_info == "" { - // 创建 Wrapper 实例并将 JSON 字符串封装 + //var details models.Details + //若用户画像存在 + // 创建 Wrapper 实例并将 JSON 字符串封装 wrapper := Wrapper{ConfigData: make(map[string]interface{})} + fmt.Println("world.ConfigData ==> ", world.ConfigData) + if roleInfo != nil { + var npcMap map[string]interface{} + err = json.Unmarshal([]byte(roleInfo.Details), &npcMap) + if err != nil { + log.Fatalf("Error unmarshalling JSON: %v", err) + } + wrapper.EmbeddingNpcs = append(wrapper.EmbeddingNpcs, npcMap) + } // 将 JSON 字符串解析为 map[string]interface{} + //err := json.Unmarshal([]byte(world.ConfigData), &wrapper.ConfigData) err := json.Unmarshal([]byte(world.ConfigData), &wrapper.ConfigData) if err != nil { log.Printf("Error parsing JSON: %v", err) @@ -307,7 +336,6 @@ func WorldWsHandler(ctx *gin.Context) { // 打印解析后的数据 //fmt.Printf("Wrapper ConfigData: %+v\n", wrapper.ConfigData) - // 序列化 Wrapper 实例以生成所需的最终 JSON 输出 finalJSON, err := json.Marshal(wrapper) if err != nil { diff --git a/src/service/WorldChat_HighConcurrency.go_bak b/src/service/WorldChat_HighConcurrency.go_bak new file mode 100644 index 0000000..05cf8eb --- /dev/null +++ b/src/service/WorldChat_HighConcurrency.go_bak @@ -0,0 +1,1278 @@ +package service + +import ( + "WorldEpcho/src/config" + "WorldEpcho/src/config/e" + "WorldEpcho/src/datasource" + "WorldEpcho/src/models" + "WorldEpcho/src/utils" + "github.com/gin-contrib/sessions" + "os" + "strconv" + "strings" + "time" + + //"time" + + //"chat/cache" + //"chat/conf" + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "log" + "net/http" + //"strconv" + //"time" +) + +//const month = 60 * 60 * 24 * 30 // 按照30天算一个月 + +// 发送消息的类型 +type WorldSendMsg struct { + Type int `json:"type"` + Content string `json:"content"` +} + +// 回复的消息 +/* 这个要注释掉 */ +type WorldReplyMsg struct { + From string `json:"from"` + Code int `json:"code"` + Content string `json:"content"` + StatusCode int `json:"statusCode,omitempty"` +} + +// 转发AI接口意识流响应给前端 +type WorldSoulReplyMsg struct { + Code int `json:"code"` + WObj map[string]interface{} `json:"WObj"` + ISLIU string `json:"ISLIU"` + WorldName string `json:"WorldName"` + IsLIUId *string `json:"IsLIUId,omitempty"` // 使用指针类型,并添加 omitempty 标签,可选字段 + MessageStatusType string `json:"messageStatusType,omitempty"` // Optional field to indicate message type + ErrorMessage string `json:"ErrorMessage,omitempty"` //可选字段 +} + +/* +构建Client表 +*/ +type WorldClient struct { + //DpConversations *models.DpConversations + WorldConversations *models.WorldConversation + Socket *websocket.Conn + //Send chan *WorldEchoResponse + Send chan *WorldSoulReplyMsg + WorldName string + AuthId string + UserSource string +} + +// 用户类 +/* +type Client struct { + Id string + SendID string + Socket *websocket.Conn + Send chan []byte +} +*/ + +// 广播类,包括广播内容和源用户 +/* +type Broadcast struct { + Client *Client + Message []byte + Type int +} +*/ + +// 用户管理 +type WorldClientManager struct { + Client map[string]*WorldClient + //Broadcast chan *Broadcast + Reply chan *WorldClient + Register chan *WorldClient + Unregister chan *WorldClient +} + +// Message 信息转JSON (包括:发送者、接收者、内容) +type WorldMessage struct { + Sender string `json:"sender,omitempty"` + Recipient string `json:"recipient,omitempty"` + Content string `json:"content,omitempty"` +} + +var WorldManager = WorldClientManager{ + Client: make(map[string]*WorldClient), // 参与连接的用户,出于性能的考虑,需要设置最大连接数 + //Broadcast: make(chan *Broadcast), + Register: make(chan *WorldClient), + Reply: make(chan *WorldClient), + Unregister: make(chan *WorldClient), +} + +//错误消息响应结构体 +type WorldErrorMessage struct { + Code int `json:"code"` + Message string `json:"message"` +} + +/* + type WorldEchoResponseAndErrorMsg struct { + SoulReplyMsg WorldSoulReplyMsg //WorldSoulReplyMsg + ErrorMessage WorldErrorMessage `json:"ErrorMessage,omitempty"` + } +*/ + +// Wrapper 结构体仅包含一个字符串字段,用于存储 JSON 字符串 +type Wrapper struct { + ConfigData map[string]interface{} `json:"ConfigData"` +} + +//世界websocket控制器 +func WorldWsHandler(ctx *gin.Context) { + /* + http协议升级为webSocket协议 + */ + conn, err := (&websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { // CheckOrigin解决跨域问题 + return true + }}).Upgrade(ctx.Writer, ctx.Request, nil) // 升级成ws协议 + + if err != nil { + http.NotFound(ctx.Writer, ctx.Request) + return + } + /* + 从session里获取用户id + */ + session := sessions.Default(ctx) + userId := session.Get("WorldUserID") + if userId == nil { + // 处理session中未找到值的情况,返回错误信息 + /*这里是双保险为了测试用*/ + userId = ctx.Query("uid") + if userId == nil { + ctx.JSON(http.StatusOK, gin.H{"code": e.NotLoginUser, "data": nil, "message": "Session中未找到用户ID,用户未登录"}) + fmt.Println(config.ColorYellow, "Session中未找到用户ID,用户未登录", config.ColorReset) + return + } + } + fmt.Println("world chat session UserID:", userId) + var SP *models.ServiceProvider + // 服务商id获取 + //从配置文件读取咪咕服务商ID + miGuAuthId := config.Conf.MiGuAuthId + + ServiceProviderId := ctx.Query("AuthId") + if ServiceProviderId != "" || ServiceProviderId == miGuAuthId { + // 从请求头中获取 JWT token + tokenString := ctx.GetHeader("Token") + if tokenString == "" { + fmt.Printf("no Authorization token provided") + ctx.JSON(http.StatusOK, gin.H{"code": e.EmptyParamsError, "data": nil, "message": "请求头中无token信息!"}) + return + } + isValid, err := IsValidMiGuToken(tokenString) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": e.TokenAuthError, "data": nil, "message": "解析token出错!"}) + return + } + if !isValid { + ctx.JSON(http.StatusOK, gin.H{"code": e.InvalidToken, "data": nil, "message": "无效的token"}) + return + } + + SP, err = models.GetServiceProviderById(ServiceProviderId) + if err != nil { + fmt.Println("根据服务商id查询出错:", err) + ctx.JSON(http.StatusOK, gin.H{"code": 0, "data": nil, "message": "根据服务商id查询服务商出错"}) + return + } + if SP == nil { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "data": nil, "message": "非法的服务商"}) + return + } + } + user_Id, ok := userId.(string) + if !ok { + // 类型断言失败,处理类型不匹配的情况 + ctx.JSON(http.StatusOK, gin.H{"code": 0, "data": nil, "message": "用户ID类型错误"}) + return + } + //userId作为string类型的值进行后续操作 + _, b := config.WorldLoginCacheCode.Get(user_Id) + + uid, err := strconv.ParseInt(user_Id, 10, 64) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "data": nil, "message": "数字人id类型转换出错"}) + //continue + return + } + if user_Id == "" && ServiceProviderId == "" { + fmt.Println(config.ColorPurple, "用户id或者服务商id为空", config.ColorReset) + ctx.JSON(http.StatusOK, gin.H{"code": 0, "data": nil, "message": "用户id或者服务商id为空"}) + return + } + if SP != nil { + if SP.InspirationValue == 0 { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "data": nil, "message": "服务商的灵感值余额不足,无法进行数字人聊天"}) + return + } + } + + //校验用户是不是服务商那边的用户 + if user_Id != "" && SP != nil { + user, err := models.GetUserByID(uid) + if err != nil { + fmt.Println(config.ColorPurple, "根据用户ID查询用户信息出错", config.ColorReset) + ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "根据用户ID查询用户信息出错"}) + return + } + if user.RegisteredSource != SP.Name { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": " 该用户不是服务商的用户不能连接! "}) + fmt.Println("该用户不是服务商的用户不能连接!") + return + } + + } + /* + 判断用户是否登录 + */ + //定义会话主题 + //var ConversationTitle string + worldName := ctx.Query("world_name") + world_Id := ctx.Query("worldId") + var world *models.WorldInfo + var userInfo string + + if worldName != "" { + world, err = models.GetWorldInfoByName(worldName) + if err != nil { + log.Println("根据世界名称,查询世界信息失败") + ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "根据世界名称,查询世界信息失败"}) + return + } + } + if world_Id != "" { + worldId, err := strconv.ParseInt(world_Id, 10, 64) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "世界ID类型转换出错"}) + + return + } + world, _, err = models.GetWorldInfoById(worldId) + if err != nil { + log.Println("根据世界名称,查询世界信息失败") + ctx.JSON(http.StatusOK, gin.H{"code": e.ErrorDatabase, "data": nil, "message": "根据世界名称,查询世界信息失败"}) + return + } + if world == nil { + log.Println("世界信息不存在") + ctx.JSON(http.StatusOK, gin.H{"code": e.NotFound, "data": nil, "message": "世界信息不存在"}) + return + } + } + + if world == nil { + log.Println("世界名称不存在") + ctx.JSON(http.StatusOK, gin.H{"code": e.NotFound, "data": nil, "message": "世界名称不存在"}) + return + } + + if b || SP != nil { + fmt.Println("ServiceProvider ===> ", SP) + BgInfo := ctx.Query("BgInfo") + user_info := ctx.Query("userInfo") + if user_info != "" { + userInfo = user_info + } + //拼接会话主题 + //ConversationTitle = "用户Id为" + user_Id + "的用户和数字人" + strings.Join(DpNames, "和") + "开始会话" + //fmt.Println("ConversationTitle: ", ConversationTitle) + if SP != nil && world != nil { + if BgInfo == "" { + BgInfo = world.Background + } + if user_info == "" { + // 创建 Wrapper 实例并将 JSON 字符串封装 + + wrapper := Wrapper{ConfigData: make(map[string]interface{})} + // 将 JSON 字符串解析为 map[string]interface{} + err := json.Unmarshal([]byte(world.ConfigData), &wrapper.ConfigData) + if err != nil { + log.Printf("Error parsing JSON: %v", err) + return + } + // 打印解析后的数据 + //fmt.Printf("Wrapper ConfigData: %+v\n", wrapper.ConfigData) + // 序列化 Wrapper 实例以生成所需的最终 JSON 输出 + finalJSON, err := json.Marshal(wrapper) + if err != nil { + fmt.Println("Error marshaling final JSON: ", err) + ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "业务经理考核系统配置信息解析失败"}) + return + } + + fmt.Println("ConfigData ===>", string(finalJSON)) + //userInfo = string(finalJSON) + userInfo = string(finalJSON) + } + + } + + worldConversation, _, err := models.GetWorldConversationByUidAndWorldName(uid, world.Name) + if err != nil { + log.Println("查询世界信息失败") + ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "查询世界信息失败"}) + return + } + + ISULU_ID := "" + var world_response WorldCreateResponse + var isNewHuaSoul bool + if worldConversation == nil { + //世界意识流不存在调用new接口创建 + world, new_conversation, err := CreateVirtualWorldHandler(uid, world.Name, BgInfo, userInfo) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "content": "创建世界失败!"}) + return + } + // 把新创建的世界会话赋值给会话对象 + worldConversation = new_conversation + isNewHuaSoul = true + ISULU_ID = world.ISLIUid + world_response.Code = world.Code + world_response.ISLIUid = world.ISLIUid + world_response.WObj = world.WObj + world_response.ISLIU = world.ISLIU + //这里加了一个消息状态类型 + world_response.MessageStatusType = world.MessageStatusType + } else { + ISULU_ID = worldConversation.IsLiuId + isNewHuaSoul = false + // 如果查询到记录,调用查询意识流Id接口判断意识流id是否有效,若无效则修改意识流ID为新的意识流ID + flag, err := FindWorldISLIU(uid, world.Name) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "content": "查询世界意识流id失败!"}) + return + } + fmt.Println("查询世界意识流Id, flag: ", flag) + //没找到 + if !flag { + //调用new接口创建世界 + world, new_conversation, err := CreateVirtualWorldHandler(uid, world.Name, BgInfo, userInfo) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "content": "创建世界失败!"}) + return + } + // 把新创建的世界会话赋值给会话对象 + worldConversation = new_conversation + isNewHuaSoul = true + ISULU_ID = world.ISLIUid + world_response.Code = world.Code + world_response.ISLIUid = world.ISLIUid + world_response.WObj = world.WObj + world_response.ISLIU = world.ISLIU + //这里加了一个消息状态类型 + world_response.MessageStatusType = world.MessageStatusType + } + + } + //根据uid查询用户来源是否为咪咕 + user, err := models.GetUserByID(worldConversation.Uid) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "根据用户id查询用户出错!"}) + return + } + // 创建一个用户客户端会话实例 + newClient := &WorldClient{ + WorldConversations: worldConversation, + WorldName: world.Name, + Socket: conn, + //Send: nil, + + Send: make(chan *WorldSoulReplyMsg, 1024), // 创建一个有缓冲的管道 + AuthId: ServiceProviderId, + } + + if user != nil && user.RegisteredSource != "" { + newClient.UserSource = user.RegisteredSource + } + + go newClient.WorldWrite() // 为每个客户端启动写消息协程 + + // 用户会话注册到用户管理上 + //WorldManager.Register <- newClient + WorldManager.Register <- newClient // 注册客户端 + + if isNewHuaSoul { + // 注册成功后,把世界信息开场白写入Socket + if world_response.ISLIU == "" || world_response.WObj == nil { + log.Println("世界开场白,意识流为空...") + // 向前端返回数据格式不正确的状态码和消息 + errorMsg := WorldErrorMessage{ + Code: -1, + Message: "世界开场白,意识流为空...", + } + errmsg, _ := json.Marshal(errorMsg) + _ = newClient.Socket.WriteMessage(websocket.TextMessage, errmsg) + fmt.Println("返回世界开场白信息失败: ", errmsg) + return + + } + /* 创建响应体 WorldEchoRespones */ + HSReplyMsg := WorldSoulReplyMsg{ + Code: world_response.Code, + WObj: world_response.WObj, + ISLIU: world_response.ISLIU, + WorldName: world.Name, + //加了一个消息状态类型 + IsLIUId: &world_response.ISLIUid, + MessageStatusType: world_response.MessageStatusType, + } + + //向管道写入开场白消息 + newClient.Send <- &HSReplyMsg + + /* + msg, _ := json.Marshal(HSReplyMsg) + write_err := newClient.Socket.WriteMessage(websocket.TextMessage, msg) + if write_err != nil { + log.Println("返回意识流给客户端时出错! ") + WorldDestroyWs(newClient) + return + }*/ + + /* 保存世界的独白到数据库 */ + senderType := "world" + // 拼接会话Id + conversation_Id := utils.Strval(newClient.WorldConversations.Uid) + "_" + utils.Strval(newClient.WorldConversations.WorldId) + _, chatDB_err := datasource.SaveWorldChatRecordMongoDB(conversation_Id, newClient.WorldConversations.WorldId, newClient.WorldConversations.WorldName, world_response.ISLIU, world_response.WObj, senderType) + // 错误处理 + if chatDB_err != nil { + log.Println(config.ColorRed, "保存数字人聊天记录至数据库出错!", config.ColorReset) + errorMsg := WorldSoulReplyMsg{ + Code: -1, + ErrorMessage: "保存数字人聊天记录至数据库出错!", + } + /* errorMsg := WorldErrorMessage{ + Code: -1, + Message: "保存数字人聊天记录至数据库出错!", + } + errmsg, _ := json.Marshal(errorMsg) + _ = newClient.Socket.WriteMessage(websocket.TextMessage, errmsg)*/ + newClient.Send <- &errorMsg + return + } + + } + + // ------------------上传意识流---------------------- // + + url := config.Conf.SoulUrl + "/world/neural" + + fileName := "./neural/" + ISULU_ID + ".CFN" + // 获取文件信息 + _, err = os.Stat(fileName) + if err == nil { + fmt.Println("文件存在") + // 构造请求参数 + request := SoulNeuralRequest{ + Auth: config.Conf.SoulAuth, + Type: 1, // 1表示上载 + ISLIUid: ISULU_ID, + } + // 调用函数 + _, err := SoulNeuralFileUpload(url, request, fileName) + if err != nil { + log.Printf("Error during soul neural transfer: %v", err) + } + // 处理响应 + //fmt.Println("Response received: ", response) + + } else if os.IsNotExist(err) { + fmt.Println("文件不存在") + } else { + fmt.Println("获取文件信息出错:", err) + } + + // ---------------------------------------- // + //fmt.Println("调用socket的Read方法前:", ISULU_ID) + go newClient.WorldRead() + //go newClient.WorldWrite() + + } else { + ctx.JSON(http.StatusOK, gin.H{"code": e.NotLoginUser, "data": nil, "message": "用户未登录"}) + return + } + +} + +func WorldDestroyWs(c *WorldClient) { // 避免忘记关闭,所以要加上close + + //关闭websocket之前,下载关于数字人的神经网络记忆文件 + url := config.Conf.SoulUrl + "/world/neural" + fileName := "./neural/" + c.WorldConversations.IsLiuId + ".CFN" + + // 构造请求参数 + request := SoulNeuralRequest{ + Auth: config.Conf.SoulAuth, + Type: 0, // 0表示下载 + ISLIUid: c.WorldConversations.IsLiuId, + } + // 调用函数 + err1 := SoulNeuralFileDownload(url, request, fileName) + if err1 != nil { + log.Printf("Error during soul neural transfer: %v", err1) + } + //注销websocket的客户端 + WorldManager.Unregister <- c + _ = c.Socket.Close() + c.Socket = nil + +} + +/* + 从websocket读取客户端用户的消息,然后服务器回应前端一个消息 +*/ +func (c *WorldClient) WorldRead() { + //从配置文件读取咪咕服务商ID + miGuAuthId := config.Conf.MiGuAuthId + //延迟关闭websocket + defer WorldDestroyWs(c) + /* + 死循环执行websocket消息接收和发送 + */ + for { + c.Socket.PongHandler() + sendMsg := new(WorldSendMsg) + err := c.Socket.ReadJSON(&sendMsg) + if err != nil { + log.Println("数据格式不正确", err) + + /* // 向前端返回数据格式不正确的状态码和消息 + errorMsg := WorldErrorMessage{ + Code: -1, + Message: "数据格式不正确", + } + errmsg, errMarshal := json.Marshal(errorMsg) + if errMarshal != nil { + // 如果JSON编码失败,则打印错误消息 + log.Printf("JSON编码错误: %s\n", errMarshal) + + } else { + // 发送错误消息 + errWrite := c.Socket.WriteMessage(websocket.TextMessage, errmsg) + if errWrite != nil { + // 如果WebSocket发送失败,则打印错误消息 + log.Printf("WebSocket发送错误: %s\n", errWrite) + + } + } + */ + errorMsg := WorldSoulReplyMsg{ + Code: -1, + ErrorMessage: "客户端发送的数据格式不正确!", + } + + c.Send <- &errorMsg + + //fmt.Println("errmsg: ", errmsg) + + //剔除注册的客户端用户 + WorldManager.Unregister <- c + fmt.Println(" Socket.Close() ... ") + //关闭webSocket + _ = c.Socket.Close() + + break + } + //打印前端发送过来的消息 + sendMsgMarshal, err := json.Marshal(sendMsg) + if err != nil { + // 打印错误信息的字符串表示 + log.Printf("Error marshaling sendMsg to JSON: %s", err.Error()) + // 在这里还可以做其它错误处理,例如返回一个错误响应等 + } else { + // 打印 sendMsg JSON 字符串 + fmt.Printf("【==== sendMsg ===】 : %s\n", string(sendMsgMarshal)) + + // 在这里可以继续处理 sendMsg JSON 字符串,比如发送它 + } + + /* + + */ + go func() { + /* + 响应类型0 交互 + */ + // 拼接会话Id + conversation_Id := utils.Strval(c.WorldConversations.Uid) + "_" + utils.Strval(c.WorldConversations.WorldId) + /* dpIdsStr := utils.Int64SliceToStringSlice(c.DpConversations.DpIds) + conversation_Id := utils.Strval(c.DpConversations.Uid) + "_" + strings.Join(dpIdsStr, ",") + "_" + utils.Strval(c.DpConversations.AppId)*/ + + // 调用AI的会话接口 + echoResponse, chat_err := c.WorldEchoAPI(sendMsg) + if c.Socket == nil { + return + } + if chat_err != nil { + log.Println("会话失败") + // 向前端返回数据格式不正确的状态码和消息 + /*errorMsg := WorldErrorMessage{ + Code: -1, + Message: "会话失败", + } + + // 将错误消息对象转换为 JSON 字符串 + errmsg, errMarshal := json.Marshal(errorMsg) + if errMarshal != nil { + // 如果 JSON 编码失败,则打印错误 + log.Printf("WorldEchoAPI error: %v", errMarshal) + } else { + // 发送错误消息 + errWrite := c.Socket.WriteMessage(websocket.TextMessage, errmsg) + if errWrite != nil { + //如果发送 WebSocket消息失败,则打印错误 + log.Printf("Error writing websocket message: %v", errWrite) + } + }*/ + errorMsg := WorldSoulReplyMsg{ + Code: -1, + ErrorMessage: "请求AI模型会话失败!", + } + + c.Send <- &errorMsg + + // 删除会话记录 + /* if chat_err.Error() == "调用会话接口失败无法获取意识流" { + dropConversationErr := models.DeleteConversation() + if dropConversationErr != nil { + log.Println("删除会话失败") + + } + + } + */ + return + } /*else { + c.Send <- echoResponse // 将回应消息放入 `Send` 频道 + }*/ + + resCode := echoResponse.Code + Wobj := echoResponse.WObj + ISLIU := echoResponse.ISLIU + + if ISLIU == "" || Wobj == nil { + log.Println("会话意识流为空...") + // 向前端返回数据格式不正确的状态码和消息 + /*errorMsg := WorldErrorMessage{ + Code: -1, + Message: "会话意识流为空...", + } + errmsg, _ := json.Marshal(errorMsg) + _ = c.Socket.WriteMessage(websocket.TextMessage, errmsg)*/ + + fmt.Println("AI echo error: 会话意识流为空... ") + + errorMsg := WorldSoulReplyMsg{ + Code: -1, + ErrorMessage: "AI模型会话意识流为空.", + } + + c.Send <- &errorMsg + + return + + } + // 读取前端发送过来的消息并打印 + fmt.Println("客户端消息: ", sendMsg) + + if sendMsg.Type == 0 { + /* + 收到前端发送过来的消息,保存至数据库 + */ + senderType := "user" + user_status := make(map[string]interface{}) + _, chatDB_err := datasource.SaveWorldChatRecordMongoDB(conversation_Id, c.WorldConversations.Uid, c.WorldConversations.WorldName, sendMsg.Content, user_status, senderType) + // 错误处理 + if chatDB_err != nil { + log.Println("保存用户聊天记录至数据库出错!") + + /* + errorMsg := ErrorMessage{ + Code: -1, + Message: "保存用户聊天记录至数据库出错!", + } + errmsg, _ := json.Marshal(errorMsg) + _ = c.Socket.WriteMessage(websocket.TextMessage, errmsg) + */ + errorMsg := WorldSoulReplyMsg{ + Code: -1, + ErrorMessage: "保存用户聊天记录至数据库出错!", + } + + c.Send <- &errorMsg + return + } + /* + 调用AI模型接口回复前端用户的消息 + */ + HSReplyMsg := WorldSoulReplyMsg{ + Code: resCode, + WObj: Wobj, + ISLIU: ISLIU, + WorldName: c.WorldName, + IsLIUId: &c.WorldConversations.IsLiuId, + } + + //判断是不是咪咕服务商 + if c.AuthId == miGuAuthId { + reformatHSReplyMsg := ParseEndStrAndReformat(&HSReplyMsg) + /* msg, _ := json.Marshal(reformatHSReplyMsg) + write_err := c.Socket.WriteMessage(websocket.TextMessage, msg) + if write_err != nil { + log.Println("返回意识流给客户端时出错! ") + WorldDestroyWs(c) + return + }*/ + // 将处理后的消息发送到Write协程 + c.Send <- reformatHSReplyMsg + + } else { + /* + msg, _ := json.Marshal(HSReplyMsg) + write_err := c.Socket.WriteMessage(websocket.TextMessage, msg) + if write_err != nil { + log.Println("返回意识流给客户端时出错! ") + WorldDestroyWs(c) + return + } + */ + + c.Send <- &HSReplyMsg + } + + /* + mongoDB保存数字人发送给前端用户的聊天记录 + */ + //最后一次聊天的数字人 + //c.LastMsg = ISLIU + //LastDpId := c.DpConversations.DpIds[c.LastIndex] + senderType2 := "world" + maxTimeStamp, chatDB_err2 := datasource.SaveWorldChatRecordMongoDB(conversation_Id, c.WorldConversations.WorldId, c.WorldConversations.WorldName, ISLIU, Wobj, senderType2) + // 错误处理 + if chatDB_err2 != nil { + log.Println(config.ColorRed, "保存数字人聊天记录至数据库出错!", config.ColorReset) + + /* + errorMsg := ErrorMessage{ + Code: -1, + Message: "保存数字人聊天记录至数据库出错!", + } + errmsg, _ := json.Marshal(errorMsg) + _ = c.Socket.WriteMessage(websocket.TextMessage, errmsg) + */ + errorMsg := WorldSoulReplyMsg{ + Code: -1, + ErrorMessage: "保存数字人聊天记录至数据库出错!", + } + + c.Send <- &errorMsg + return + } + + if c.AuthId == miGuAuthId { + + if endStr, ok := echoResponse.WObj["EndStr"]; ok { + if endStr != "" { + fmt.Println(" endStr ====> :", endStr) + + score, err := ExtractRating(*echoResponse) + if err != nil { + fmt.Println("错误提取评分: ", err) + return + } + fmt.Println("推演到结局,保存结局内容...") + // 创建 OutcomeContent 对象 + outcome := OutcomeContent{ + WObj: echoResponse.WObj, + ISLIU: echoResponse.ISLIU, + } + + // 将 OutcomeContent 对象转换成 JSON 字符串 + outcomeContentBytes, err := json.Marshal(outcome) + if err != nil { + fmt.Println("无法将推演结局内容序列化:", err) + return + } + + outcomeContentStr := string(outcomeContentBytes) + + // 创建 WorldDeductionResult 对象 + deductionResult := models.WorldDeductionResult{ + WorldId: c.WorldConversations.WorldId, + UserId: c.WorldConversations.Uid, + WorldName: c.WorldName, + OutcomeTitle: "推演结局", // 使用 EndStr 作为结局标题 + OutcomeContent: outcomeContentStr, + ChatStartTime: maxTimeStamp, + ChatEndTime: time.Now().Unix(), + Score: score, + } + + // 保存到数据库 + _, err = models.AddWorldDeductionResult(&deductionResult) + if err != nil { + fmt.Println("保存推演结局内容到数据库失败:", err) + return + } + + fmt.Println("推演结局内容已成功保存到mysql数据库,推演结果表中。") + + chatRecordArray := datasource.WorldChatRecordArray{ + ConversationId: utils.Strval(c.WorldConversations.Uid) + "_" + utils.Strval(c.WorldConversations.WorldId), + Timestamp: time.Now().Unix(), + //存个空的聊天记录数组 + Records: []datasource.WorldChatRecord{}, + } + err = datasource.SaveWorldChatRecord(chatRecordArray) + if err != nil { + log.Println("MongoDB数据库,新插入一条数据出错,err: ", err) + //ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "重置世界记忆文件,插入数据失败!"}) + return + } + fmt.Println(config.ColorBlue, "用户聊天到结局,MongoDB创建新的聊天数组", config.ColorReset) + + } + } + + } + + /* + 发送消息成功,resCode为1 + */ + + if resCode == 1 { + /* //增加数字人的交互量 + + err := models.IncrementDigitalPersonInteractionCount(c.DpConversations.DpIds[c.LastIndex]) + if err != nil { + log.Println(config.ColorRed, "更新交互量失败:", err, config.ColorReset) + return + } + fmt.Println(config.ColorGreen, "交互量更新成功!", config.ColorReset) + */ + if c.AuthId != "" { + sp, err := models.GetServiceProviderById(c.AuthId) + if err != nil { + fmt.Println("根据服务商id查询出错:", err) + return + } + if sp != nil && c.UserSource == sp.Name { + /* 添加服务商调用AI会话,灵感消耗日志信息 */ + museNum := int64(echoResponse.MuseValue) + // 构建一个InspirationUsageLog实例 + + /* 更新服务商的灵感值 */ + if museNum > sp.InspirationValue { + // 处理错误或返回 + fmt.Println("服务商灵感值不足本次扣除") + return + } + sp.InspirationValue = sp.InspirationValue - museNum + sp.TotalInspiration = sp.TotalInspiration + museNum + err := models.UpdateServiceProvider(sp) + if err != nil { + log.Println(config.ColorRed, "更新服务商灵感消耗值失败:", err, config.ColorReset) + return + } else { + log.Println(config.ColorBlue, "更新服务商灵感消耗值成功,剩余灵感值:", sp.InspirationValue, config.ColorReset) + } + } + + } + + } + } + + /* + type为1,表示用户无回应,数字人进行主动发送信息,进行主动关怀!! + */ + + if sendMsg.Type == 1 { + //响应给客户端 + HSReplyMsg := WorldSoulReplyMsg{ + Code: resCode, + WObj: Wobj, + ISLIU: ISLIU, + WorldName: c.WorldName, + IsLIUId: &c.WorldConversations.IsLiuId, + } + + //var msg []byte + //判断是不是咪咕服务商 + if c.AuthId == miGuAuthId { + reformatHSReplyMsg := ParseEndStrAndReformat(&HSReplyMsg) + //msg, _ = json.Marshal(reformatHSReplyMsg) + // 将处理后的消息发送到Write协程 + c.Send <- reformatHSReplyMsg + } else { + //msg, _ = json.Marshal(HSReplyMsg) + c.Send <- &HSReplyMsg + } + //解析AI接口返回的数据 + //msg, _ := json.Marshal(HSReplyMsg) + //c.LastMsg = ISLIU + + /* + mongoDB保存数字人发送给前端用户的聊天记录 + */ + + senderType := "world" + maxTimeStamp, chatDB_err := datasource.SaveWorldChatRecordMongoDB(conversation_Id, c.WorldConversations.WorldId, c.WorldConversations.WorldName, ISLIU, Wobj, senderType) + // 错误处理 + if chatDB_err != nil { + log.Println(config.ColorRed, "保存数字人聊天记录至数据库出错!", config.ColorReset) + /* errorMsg := ErrorMessage{ + Code: -1, + Message: "保存数字人聊天记录至数据库出错!", + } + errmsg, _ := json.Marshal(errorMsg) + _ = c.Socket.WriteMessage(websocket.TextMessage, errmsg)*/ + errorMsg := WorldSoulReplyMsg{ + Code: -1, + ErrorMessage: "保存数字人聊天记录至数据库出错!", + } + + c.Send <- &errorMsg + return + } + + // 回复数据至前端用户 + /* write_err := c.Socket.WriteMessage(websocket.TextMessage, msg) + if write_err != nil { + log.Println(config.ColorRed, "数字人主动关怀,返回意识流给客户端时出错!", config.ColorReset) + WorldDestroyWs(c) + return + } + */ + c.Send <- &HSReplyMsg + if endStr, ok := echoResponse.WObj["EndStr"]; ok { + if endStr != "" { + + fmt.Println("推演到结局,保存结局内容...") + // 创建 OutcomeContent 对象 + outcome := OutcomeContent{ + WObj: echoResponse.WObj, + ISLIU: echoResponse.ISLIU, + } + + // 将 OutcomeContent 对象转换成 JSON 字符串 + outcomeContentBytes, err := json.Marshal(outcome) + if err != nil { + fmt.Println("无法将推演结局内容序列化:", err) + return + } + + outcomeContentStr := string(outcomeContentBytes) + + // 创建 WorldDeductionResult 对象 + deductionResult := models.WorldDeductionResult{ + WorldId: c.WorldConversations.WorldId, + UserId: c.WorldConversations.Uid, + WorldName: c.WorldName, + OutcomeTitle: "推演结局", // 使用 EndStr 作为结局标题 + OutcomeContent: outcomeContentStr, + ChatStartTime: maxTimeStamp, + ChatEndTime: time.Now().Unix(), + } + + // 保存到数据库 + _, err = models.AddWorldDeductionResult(&deductionResult) + if err != nil { + fmt.Println("保存推演结局内容到数据库失败:", err) + return + } + + fmt.Println("推演结局内容已成功保存到mysql数据库,推演结果表中。") + + chatRecordArray := datasource.WorldChatRecordArray{ + ConversationId: utils.Strval(c.WorldConversations.Uid) + "_" + utils.Strval(c.WorldConversations.WorldId), + Timestamp: time.Now().Unix(), + //存个空的聊天记录数组 + Records: []datasource.WorldChatRecord{}, + } + err = datasource.SaveWorldChatRecord(chatRecordArray) + if err != nil { + log.Println("MongoDB数据库,新插入一条数据出错,err: ", err) + //ctx.JSON(http.StatusOK, gin.H{"code": 0, "message": "重置世界记忆文件,插入数据失败!"}) + return + } + fmt.Println(config.ColorBlue, "用户聊天到结局,MongoDB创建新的聊天数组", config.ColorReset) + + } + } + + /* + 发送消息成功,resCode为1 + */ + if resCode == 1 { + /*//增加数字人的交互量 + err := models.IncrementDigitalPersonInteractionCount(c.DpConversations.DpIds[c.LastIndex]) + if err != nil { + log.Println(config.ColorRed, "更新交互量失败:", err, config.ColorReset) + } + fmt.Println(config.ColorGreen, "交互量更新成功!", config.ColorReset)*/ + if c.AuthId != "" { + sp, err := models.GetServiceProviderById(c.AuthId) + if err != nil { + fmt.Println("根据服务商id查询出错:", err) + return + } + if sp != nil && c.UserSource == sp.Name { + /* 添加服务商调用AI会话,灵感消耗日志信息 */ + museNum := int64(echoResponse.MuseValue) + + /* 更新服务商的灵感值 */ + if museNum > sp.InspirationValue { + // 处理错误或返回 + fmt.Println("服务商灵感值不足本次扣除") + return + } + sp.InspirationValue = sp.InspirationValue - museNum + sp.TotalInspiration = sp.TotalInspiration + museNum + err := models.UpdateServiceProvider(sp) + if err != nil { + log.Println(config.ColorRed, "更新服务商灵感消耗值失败:", err, config.ColorReset) + return + } else { + log.Println(config.ColorBlue, "更新服务商灵感消耗值成功,剩余灵感值:", sp.InspirationValue, config.ColorReset) + } + } + + } + } + } + }() + + } + +} + +// ExtractRating 从响应中提取整体评分 + +func ExtractRating(response WorldEchoResponse) (int, error) { + endStr, ok := response.WObj["EndStr"].(string) + if !ok || endStr == "" { + return 0, nil + } + + lines := strings.Split(endStr, "\n") + for _, line := range lines { + if strings.Contains(line, "【整体评分】") { + scoreStr := strings.TrimSpace(strings.Split(line, ":")[1]) + score, err := strconv.Atoi(scoreStr) + if err != nil { + return 0, fmt.Errorf("评分转换错误: %v", err) + } + return score, nil + } + } + + return 0, fmt.Errorf("未找到评分信息") +} + +func ExtractRating1(response WorldEchoResponse) string { + + endStr, ok := response.WObj["EndStr"].(string) + if !ok { + return "评分信息不可用" + } + + lines := strings.Split(endStr, "\n") + for _, line := range lines { + if strings.Contains(line, "【整体评分】") { + // 假设评分总是单个数字跟在冒号和换行符后面 + return strings.TrimSpace(strings.Split(line, ":")[1]) + } + } + + return "评分信息不可用" +} + +/* ============================================================================================= */ + +// ParseEndStrAndReformat 重新解析 WorldSoulReplyMsg 中的 WObj 并返回一个新的结构体 +func ParseEndStrAndReformat(response *WorldSoulReplyMsg) *WorldSoulReplyMsg { + if response == nil || response.WObj == nil { + return nil // 如果输入是nil,直接返回nil + } + + //newResponse := *response // 创建一个新的响应体对象副本 + + newResponse := *DeepCopy(response) + if len(newResponse.WObj) == 0 && strings.Contains(newResponse.ISLIU, "【任务提示】") { + newResponse.MessageStatusType = "prologue" + } else if endStr, ok := newResponse.WObj["EndStr"].(string); ok && endStr != "" { + newResponse.MessageStatusType = "end" + } else { + newResponse.MessageStatusType = "chatting" + } + + endStr, ok := newResponse.WObj["EndStr"].(string) + if ok { + if endStr != "" { + // 解析 "EndStr" 中的详细字段 + title := strings.Split(endStr, "@")[0] // 提取 '@' 前的标题 + overallScore := extractBetween(endStr, "【整体评分】:", "\n") // 提取整体评分 + objectiveEvaluation := extractBetween(endStr, "【客观评价】:", "## 【整体评分】") // 提取客观评价至字符串末尾 + + // 将 "EndStr" 结构化为 JSON 对象 + endStrObj := map[string]interface{}{ + "title": title, + "overallScore": strings.TrimSpace(overallScore), + "objectiveEvaluation": strings.TrimSpace(objectiveEvaluation), + } + + newResponse.WObj["EndStr"] = endStrObj // 将格式化后的结论字符串对象重新赋值给响应 + } else { + newResponse.WObj["EndStr"] = nil + } + + } + + // 无论 "EndStr" 是否存在,均检查和处理 '地点' 和 '表情' + if address, exists := newResponse.WObj["地点"]; exists { + newResponse.WObj["address"] = address + delete(newResponse.WObj, "地点") + } + if emotion, exists := newResponse.WObj["表情"]; exists { + newResponse.WObj["emotion"] = emotion + delete(newResponse.WObj, "表情") + } + if chatTime, exists := newResponse.WObj["时间"]; exists { + newResponse.WObj["time"] = chatTime + delete(newResponse.WObj, "时间") + } + return &newResponse // 返回修改后的新响应体 +} + +// extractBetween 查找并提取开始和结束分隔符之间的子字符串 +func extractBetween(s, start, end string) string { + startIdx := strings.Index(s, start) + if startIdx == -1 { + return "" + } + startPos := startIdx + len(start) + endIdx := strings.Index(s[startPos:], end) + if endIdx == -1 { + return s[startPos:] + } + return s[startPos : startPos+endIdx] +} + +// extractToEnd 查找并提取从指定开始分隔符到字符串末尾的内容 +func extractToEnd(s, start string) string { + startIdx := strings.Index(s, start) + if startIdx == -1 { + return "" + } + return s[startIdx+len(start):] +} + +//深拷贝对象 +func DeepCopy(msg *WorldSoulReplyMsg) *WorldSoulReplyMsg { + // 创建一个新的 WorldSoulReplyMsg 实例 + newMsg := &WorldSoulReplyMsg{ + Code: msg.Code, + ISLIU: msg.ISLIU, + WorldName: msg.WorldName, + } + + // 深拷贝 WObj map + if msg.WObj != nil { + newMsg.WObj = make(map[string]interface{}) + for k, v := range msg.WObj { + newMsg.WObj[k] = v // 注意这里只是简单拷贝,对于更复杂的对象可能需要更深层次的拷贝 + } + } + + // 复制可选指针类型字段 IsLIUId + if msg.IsLIUId != nil { + newIsLIUId := *msg.IsLIUId // 创建 IsLIUId 的副本 + newMsg.IsLIUId = &newIsLIUId + } + + // 复制其他可选字段 + if msg.MessageStatusType != "" { + newMsg.MessageStatusType = msg.MessageStatusType + } + + return newMsg +} + +/* + 实现WorldWrite方法 + WorldWrite方法需要修改以适应从通道中读取*WorldEchoRespones类型的数据并将其发送到WebSocket +*/ +func (c *WorldClient) WorldWrite() { + defer func() { + _ = c.Socket.Close() + }() + for { + select { + case message, ok := <-c.Send: + if !ok { + // 如果通道被关闭,发送WebSocket关闭消息并退出goroutine + _ = c.Socket.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + // 序列化消息为JSON + msg, err := json.Marshal(message) + if err != nil { + log.Printf("Error marshaling message: %s\n", err) + continue + } + // 发送序列化后的消息到WebSocket连接 + if err := c.Socket.WriteMessage(websocket.TextMessage, msg); err != nil { + // 处理写入错误,例如可以记录日志或关闭连接 + log.Printf("WebSocket write error: %s\n", err) + return + } + } + } +} + +/* ============================================================================================= */ + +func (c *WorldClient) WorldWrite1() { + defer func() { + _ = c.Socket.Close() + }() + for { + select { + + case message, ok := <-c.Send: + if !ok { + _ = c.Socket.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + /* + resCode := message.Code + status := message.Status + ISLIU := message.ISLIU + */ + + log.Println(c.WorldConversations.Uid, "接受消息:", message) + replyMsg := WorldReplyMsg{ + Code: e.WebsocketSuccess, + Content: fmt.Sprintf("%s", message), + } + msg, _ := json.Marshal(replyMsg) + _ = c.Socket.WriteMessage(websocket.TextMessage, msg) + } + } +} -- libgit2 0.26.0