diff --git a/applogic/app.go b/applogic/app.go index 8cf56ad..6885999 100644 --- a/applogic/app.go +++ b/applogic/app.go @@ -204,6 +204,32 @@ func (app *App) EnsureCustomTableExist() error { return nil } +func (app *App) EnsureCustomTableExistSP() error { + createTableSQL := ` + CREATE TABLE IF NOT EXISTS custom_table ( + user_id TEXT PRIMARY KEY, + promptstr TEXT NOT NULL, + promptstr_stat INTEGER, + str1 TEXT, + str2 TEXT, + str3 TEXT, + str4 TEXT, + str5 TEXT, + str6 TEXT, + str7 TEXT, + str8 TEXT, + str9 TEXT, + str10 TEXT + );` + + _, err := app.DB.Exec(createTableSQL) + if err != nil { + return fmt.Errorf("error creating custom_table: %w", err) + } + + return nil +} + func (app *App) EnsureUserContextTableExists() error { createTableSQL := ` CREATE TABLE IF NOT EXISTS user_context ( @@ -237,6 +263,39 @@ func (app *App) EnsureUserContextTableExists() error { return nil } +func (app *App) EnsureUserContextTableExistsSP() error { + createTableSQL := ` + CREATE TABLE IF NOT EXISTS user_context ( + user_id TEXT PRIMARY KEY, + conversation_id TEXT NOT NULL, + parent_message_id TEXT + );` + + _, err := app.DB.Exec(createTableSQL) + if err != nil { + return fmt.Errorf("error creating user_context table: %w", err) + } + + // 为 conversation_id 创建索引 + createConvIDIndexSQL := `CREATE INDEX IF NOT EXISTS idx_user_context_conversation_id ON user_context(conversation_id);` + + _, err = app.DB.Exec(createConvIDIndexSQL) + if err != nil { + return fmt.Errorf("error creating index on user_context(conversation_id): %w", err) + } + + // 为 parent_message_id 创建索引 + // 只有当您需要根据 parent_message_id 进行查询时才添加此索引 + createParentMsgIDIndexSQL := `CREATE INDEX IF NOT EXISTS idx_user_context_parent_message_id ON user_context(parent_message_id);` + + _, err = app.DB.Exec(createParentMsgIDIndexSQL) + if err != nil { + return fmt.Errorf("error creating index on user_context(parent_message_id): %w", err) + } + + return nil +} + func (app *App) handleUserContext(userID int64) (string, string, error) { var conversationID, parentMessageID string @@ -265,6 +324,34 @@ func (app *App) handleUserContext(userID int64) (string, string, error) { return conversationID, parentMessageID, nil } +func (app *App) handleUserContextSP(userID string) (string, string, error) { + var conversationID, parentMessageID string + + // 检查用户上下文是否存在 + query := `SELECT conversation_id, parent_message_id FROM user_context WHERE user_id = ?` + err := app.DB.QueryRow(query, userID).Scan(&conversationID, &parentMessageID) + if err != nil { + if err == sql.ErrNoRows { + // 用户上下文不存在,创建新的 + conversationID = utils.GenerateUUID() // 假设generateUUID()是一个生成UUID的函数 + parentMessageID = "" + + // 插入新的用户上下文 + insertQuery := `INSERT INTO user_context (user_id, conversation_id, parent_message_id) VALUES (?, ?, ?)` + _, err = app.DB.Exec(insertQuery, userID, conversationID, parentMessageID) + if err != nil { + return "", "", err + } + } else { + // 查询过程中出现了其他错误 + return "", "", err + } + } + + // 返回conversationID和parentMessageID + return conversationID, parentMessageID, nil +} + func (app *App) migrateUserToNewContext(userID int64) error { // 生成新的conversationID newConversationID := utils.GenerateUUID() // 假设generateUUID()是一个生成UUID的函数 @@ -279,6 +366,20 @@ func (app *App) migrateUserToNewContext(userID int64) error { return nil } +func (app *App) migrateUserToNewContextSP(userID string) error { + // 生成新的conversationID + newConversationID := utils.GenerateUUID() // 假设GenerateUUID()是一个生成UUID的函数 + + // 更新用户上下文 + updateQuery := `UPDATE user_context SET conversation_id = ?, parent_message_id = '' WHERE user_id = ?` + _, err := app.DB.Exec(updateQuery, newConversationID, userID) + if err != nil { + return err + } + + return nil +} + func (app *App) updateUserContext(userID int64, parentMessageID string) error { updateQuery := `UPDATE user_context SET parent_message_id = ? WHERE user_id = ?` _, err := app.DB.Exec(updateQuery, parentMessageID, userID) @@ -288,6 +389,15 @@ func (app *App) updateUserContext(userID int64, parentMessageID string) error { return nil } +func (app *App) updateUserContextSP(userID string, parentMessageID string) error { + updateQuery := `UPDATE user_context SET parent_message_id = ? WHERE user_id = ?` + _, err := app.DB.Exec(updateQuery, parentMessageID, userID) + if err != nil { + return err + } + return nil +} + func (app *App) updateUserContextPro(userID int64, conversationID, parentMessageID string) error { updateQuery := ` UPDATE user_context @@ -300,6 +410,18 @@ func (app *App) updateUserContextPro(userID int64, conversationID, parentMessage return nil } +func (app *App) updateUserContextProSP(userID string, conversationID, parentMessageID string) error { + updateQuery := ` + UPDATE user_context + SET conversation_id = ?, parent_message_id = ? + WHERE user_id = ?;` + _, err := app.DB.Exec(updateQuery, conversationID, parentMessageID, userID) + if err != nil { + return fmt.Errorf("error updating user context: %w", err) + } + return nil +} + func (app *App) getHistory(conversationID, parentMessageID string) ([]structs.Message, error) { // 如果不开启上下文 if config.GetNoContext() { @@ -385,6 +507,20 @@ func (app *App) AddUserMemory(userID int64, conversationID, parentMessageID, con return app.ensureMemoryLimit(userID) } +func (app *App) AddUserMemorySP(userID string, conversationID, parentMessageID, conversationTitle string) error { + // 插入新的记忆 + insertMemorySQL := ` + INSERT INTO user_memories (user_id, conversation_id, parent_message_id, conversation_title) + VALUES (?, ?, ?, ?);` + _, err := app.DB.Exec(insertMemorySQL, userID, conversationID, parentMessageID, conversationTitle) + if err != nil { + return fmt.Errorf("error inserting new memory: %w", err) + } + + // 检查并保持记忆数量不超过10条 + return app.ensureMemoryLimitSP(userID) +} + func (app *App) updateConversationTitle(userID int64, conversationID, parentMessageID, newTitle string) error { // 定义SQL更新语句 updateQuery := ` @@ -401,6 +537,22 @@ func (app *App) updateConversationTitle(userID int64, conversationID, parentMess return nil } +func (app *App) updateConversationTitleSP(userID string, conversationID, parentMessageID, newTitle string) error { + // 定义SQL更新语句 + updateQuery := ` + UPDATE user_memories + SET conversation_title = ? + WHERE user_id = ? AND conversation_id = ? AND parent_message_id = ?;` + + // 执行SQL更新操作 + _, err := app.DB.Exec(updateQuery, newTitle, userID, conversationID, parentMessageID) + if err != nil { + return fmt.Errorf("error updating conversation title: %w", err) + } + + return nil +} + func (app *App) ensureMemoryLimit(userID int64) error { // 查询当前记忆总数 countQuerySQL := `SELECT COUNT(*) FROM user_memories WHERE user_id = ?;` @@ -430,6 +582,35 @@ func (app *App) ensureMemoryLimit(userID int64) error { return nil } +func (app *App) ensureMemoryLimitSP(userID string) error { + // 查询当前记忆总数 + countQuerySQL := `SELECT COUNT(*) FROM user_memories WHERE user_id = ?;` + var count int + row := app.DB.QueryRow(countQuerySQL, userID) + err := row.Scan(&count) + if err != nil { + return fmt.Errorf("error counting memories: %w", err) + } + + // 如果记忆超过5条,则删除最旧的记忆 + if count > 5 { + deleteOldestMemorySQL := ` + DELETE FROM user_memories + WHERE memory_id IN ( + SELECT memory_id FROM user_memories + WHERE user_id = ? + ORDER BY memory_id ASC + LIMIT ? + );` + _, err := app.DB.Exec(deleteOldestMemorySQL, userID, count-5) + if err != nil { + return fmt.Errorf("error deleting old memories: %w", err) + } + } + + return nil +} + func (app *App) GetUserMemories(userID int64) ([]structs.Memory, error) { // 定义查询SQL,获取所有相关的记忆 querySQL := ` @@ -458,3 +639,32 @@ func (app *App) GetUserMemories(userID int64) ([]structs.Memory, error) { return memories, nil } + +func (app *App) GetUserMemoriesSP(userID string) ([]structs.Memory, error) { + // 定义查询SQL,获取所有相关的记忆 + querySQL := ` + SELECT conversation_id, parent_message_id, conversation_title + FROM user_memories + WHERE user_id = ?; + ` + rows, err := app.DB.Query(querySQL, userID) + if err != nil { + return nil, fmt.Errorf("error querying user memories: %w", err) + } + defer rows.Close() // 确保关闭rows以释放数据库资源 + + var memories []structs.Memory + for rows.Next() { + var m structs.Memory + if err := rows.Scan(&m.ConversationID, &m.ParentMessageID, &m.ConversationTitle); err != nil { + return nil, fmt.Errorf("error scanning memory: %w", err) + } + memories = append(memories, m) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error during rows iteration: %w", err) + } + + return memories, nil +} diff --git a/applogic/gensokyo_sp.go b/applogic/gensokyo_sp.go new file mode 100644 index 0000000..e7184a4 --- /dev/null +++ b/applogic/gensokyo_sp.go @@ -0,0 +1,1016 @@ +package applogic + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "math/rand" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + + "github.com/hoshinonyaruko/gensokyo-llm/acnode" + "github.com/hoshinonyaruko/gensokyo-llm/config" + "github.com/hoshinonyaruko/gensokyo-llm/fmtf" + "github.com/hoshinonyaruko/gensokyo-llm/prompt" + "github.com/hoshinonyaruko/gensokyo-llm/promptkb" + "github.com/hoshinonyaruko/gensokyo-llm/structs" + "github.com/hoshinonyaruko/gensokyo-llm/utils" +) + +// UserInfoSP 结构体用于储存用户信息 +type UserInfoSP struct { + UserID string + GroupID string + RealMessageType string + MessageType string +} + +// globalMapSP 用于存储conversationID与UserInfoSP的映射 +var globalMapSP sync.Map + +func (app *App) GensokyoHandlerSP(w http.ResponseWriter, r *http.Request) { + // 只处理POST请求 + if r.Method != http.MethodPost { + http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) + return + } + + // 获取访问者的IP地址 + ip := r.RemoteAddr // 注意:这可能包含端口号 + ip = strings.Split(ip, ":")[0] // 去除端口号,仅保留IP地址 + + // 获取IP白名单 + whiteList := config.IPWhiteList() + + // 检查IP是否在白名单中 + if !utils.Contains(whiteList, ip) { + // 尝试获取URL中的access_token + accessToken := r.URL.Query().Get("access_token") + if accessToken == "" || accessToken != config.GetAccessKey() { + http.Error(w, "Access denied", http.StatusForbidden) // 使用403 Forbidden作为更合适的HTTP状态码 + return + } + } + + // 读取请求体 + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error reading request body", http.StatusInternalServerError) + return + } + defer r.Body.Close() + + // 解析请求体到OnebotGroupMessage结构体 + var message structs.OnebotGroupMessageS + + err = json.Unmarshal(body, &message) + if err != nil { + fmtf.Printf("Error parsing request body: %+v\n", string(body)) + http.Error(w, "Error parsing request body", http.StatusInternalServerError) + return + } + + // 打印日志信息,包括prompt参数 + fmtf.Printf("收到onebotv11信息: %+v\n", string(body)) + + // 打印消息和其他相关信息 + fmtf.Printf("Received message: %v\n", message.Message) + fmtf.Printf("Full message details: %+v\n", message) + + // 进行array转换 + // 检查并解析消息类型 + if _, ok := message.Message.(string); !ok { + // 如果不是字符串,处理消息以转换为字符串,强制转换 + message.Message = ParseMessageContent(message.Message) + } + + var promptstr string + // 读取URL参数 "prompt" + promptstr = r.URL.Query().Get("prompt") + if promptstr != "" { + // 使用 prompt 变量进行后续处理 + fmtf.Printf("收到prompt参数: %s\n", promptstr) + } + + var lockPrompt bool + // 读取URL参数 "lock_prompt" + lockPromptValue := r.URL.Query().Get("lock_prompt") + if lockPromptValue != "" { + // 转换lockPromptValue从字符串到bool + lockPrompt, err = strconv.ParseBool(lockPromptValue) + if err != nil { + // 如果转换失败,可能是因为参数不存在或参数不是有效的布尔字符串 ("true", "false") + fmtf.Printf("错误:无法解析lock_prompt参数: %s\n", err) + } else { + // 使用 lockPrompt 变量进行后续处理 + if lockPrompt { + fmtf.Println("lock_prompt已激活") + } + } + } + + var nomemory bool + // 读取URL参数 "lock_prompt" + nomemoryValue := r.URL.Query().Get("no_memory") + if nomemoryValue != "" { + // 转换lockPromptValue从字符串到bool + nomemory, err = strconv.ParseBool(nomemoryValue) + if err != nil { + // 如果转换失败,可能是因为参数不存在或参数不是有效的布尔字符串 ("true", "false") + fmtf.Printf("错误:无法解析no_memory参数: %s\n", err) + } else { + // 使用 lockPrompt 变量进行后续处理 + if nomemory { + fmtf.Println("no_memory已激活") + } + } + } + + // 判断是否是群聊,然后检查触发词 + if message.RealMessageType != "group_private" && message.MessageType != "private" { + // 去除含2个[[]]的内容 + checkstr := utils.RemoveBracketsContent(message.RawMessage) + if !checkMessageForHints(checkstr, message.SelfID, promptstr) { + // 获取概率值 + chance := config.GetGroupHintChance(promptstr) + + // 生成0-100之间的随机数 + randomValue := rand.Intn(100) + + // 比较随机值与配置中的概率 + if randomValue >= chance { + w.WriteHeader(http.StatusOK) + w.Write([]byte("Group message not hint words.")) + return + } else { + // 记录日志,表明概率检查通过 + fmt.Printf("Probability check passed: %d%% chance, random value: %d\n", chance, randomValue) + } + } else { + fmt.Printf("checkMessageForHints check passed") + } + } + + // 直接从ob11事件获取selfid + selfid := strconv.FormatInt(message.SelfID, 10) + + // 读取URL参数 "api" + api := r.URL.Query().Get("api") + if api != "" { + // 使用 prompt 变量进行后续处理 + fmtf.Printf("收到api参数: %s\n", api) + } + + // 从URL查询参数中获取skip_lang_check + skipLangCheckStr := r.URL.Query().Get("skip_lang_check") + + // 默认skipLangCheck为false + skipLangCheck := false + + if skipLangCheckStr != "" { + // 尝试将获取的字符串转换为布尔值 + var err error + skipLangCheck, err = strconv.ParseBool(skipLangCheckStr) + if err != nil { + // 如果转换出错,向客户端返回错误消息 + fmt.Fprintf(w, "Invalid skip_lang_check value: %s", skipLangCheckStr) + return + } + fmt.Printf("收到 skip_lang_check 参数: %v\n", skipLangCheck) + } + + // 判断message.Message的类型 + switch msg := message.Message.(type) { + case string: + // message.Message是一个string + fmtf.Printf("userid:[%v]Received string message: %s\n", message.UserID, msg) + + //是否过滤群信息 + if !config.GetGroupmessage() { + fmtf.Printf("你设置了不响应群信息:%v", message) + return + } + + // 从GetRestoreCommand获取重置指令的列表 + restoreCommands := config.GetRestoreCommand() + + checkResetCommand := msg + if config.GetIgnoreExtraTips() { + checkResetCommand = utils.RemoveBracketsContent(checkResetCommand) + } + + // 检查checkResetCommand是否在restoreCommands列表中 + isResetCommand := false + for _, command := range restoreCommands { + if checkResetCommand == command { + isResetCommand = true + break + } + } + + if utils.BlacklistInterceptSP(message, selfid, promptstr) { + fmtf.Printf("userid:[%v]groupid:[%v]这位用户或群在黑名单中,被拦截", message.UserID, message.GroupID) + return + } + + //处理重置指令 + if isResetCommand { + fmtf.Println("处理重置操作") + if config.GetGroupContext() == 2 && message.MessageType != "private" { + app.migrateUserToNewContextSP(message.GroupID) + } else { + app.migrateUserToNewContextSP(message.UserID) + } + RestoreResponse := config.GetRandomRestoreResponses() + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if !config.GetUsePrivateSSE() { + utils.SendPrivateMessageSP(message.UserID, RestoreResponse, selfid, promptstr) + } else { + utils.SendSSEPrivateRestoreMessageSP(message.UserID, RestoreResponse, promptstr, selfid) + } + } else { + utils.SendGroupMessageSP(message.GroupID, message.UserID, RestoreResponse, selfid, promptstr) + } + // 处理故事情节的重置 + if config.GetGroupContext() == 2 && message.MessageType != "private" { + app.deleteCustomRecordSP(message.GroupID) + } else { + app.deleteCustomRecordSP(message.UserID) + } + return + } + + withdrawCommand := config.GetWithdrawCommand() + + // 检查checkResetCommand是否在WithdrawCommand列表中 + iswithdrawCommand := false + for _, command := range withdrawCommand { + if checkResetCommand == command { + iswithdrawCommand = true + break + } + } + + // 处理撤回信息 + if iswithdrawCommand { + handleWithdrawMessageSP(message) + return + } + + // newmsg 是一个用于缓存和安全判断的临时量 + newmsg := message.Message.(string) + // 去除注入的提示词 + if config.GetIgnoreExtraTips() { + newmsg = utils.RemoveBracketsContent(newmsg) + } + + var ( + vector []float64 + lastSelectedVectorID int // 用于存储最后选取的相似文本的ID + ) + + // 进行字数拦截 + if config.GetQuestionMaxLenth() != 0 { + if utils.LengthInterceptSP(newmsg, message, selfid, promptstr) { + fmtf.Printf("字数过长,可在questionMaxLenth配置项修改,Q: %v", newmsg) + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("question too long")) + return + } + } + + // 进行语言判断拦截 skipLangCheck为false时 + if len(config.GetAllowedLanguages()) > 0 && !skipLangCheck { + if utils.LanguageInterceptSP(newmsg, message, selfid, promptstr) { + fmtf.Printf("不安全!不支持的语言,可在config.yml设置允许的语言,allowedLanguages配置项,Q: %v", newmsg) + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("language not support")) + return + } + } + + // 如果使用向量缓存 或者使用 向量安全词 + if config.GetUseCache(promptstr) == 2 || config.GetVectorSensitiveFilter() { + if config.GetPrintHanming() { + fmtf.Printf("计算向量的文本: %v", newmsg) + } + // 计算文本向量 + vector, err = app.CalculateTextEmbedding(newmsg) + if err != nil { + fmtf.Printf("Error calculating text embedding: %v", err) + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("Error calculating text embedding")) + return + } + } + + // 缓存省钱部分 + if config.GetUseCache(promptstr) == 2 { + //fmtf.Printf("计算向量: %v", vector) + cacheThreshold := config.GetCacheThreshold() + // 搜索相似文本和对应的ID + similarTexts, ids, err := app.searchForSingleVector(vector, cacheThreshold) + if err != nil { + fmtf.Printf("Error searching for similar texts: %v", err) + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("Error searching for similar texts")) + return + } + + if len(similarTexts) > 0 { + // 总是获取最相似的文本的ID,不管是否最终使用 + lastSelectedVectorID = ids[0] + + chance := rand.Intn(100) + // 检查是否满足设定的概率 + if chance < config.GetCacheChance() { + // 使用最相似的文本的答案 + fmtf.Printf("读取表:%v\n", similarTexts[0]) + responseText, err := app.GetRandomAnswer(similarTexts[0]) + if err == nil { + fmtf.Printf("缓存命中,Q:%v,A:%v\n", newmsg, responseText) + //加入上下文 + if app.AddSingleContextSP(message, responseText) { + fmtf.Printf("缓存加入上下文成功") + } + // 发送响应消息 + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if !config.GetUsePrivateSSE() { + utils.SendPrivateMessageSP(message.UserID, responseText, selfid, promptstr) + } else { + utils.SendSSEPrivateMessageSP(message.UserID, responseText, promptstr, selfid) + } + } else { + utils.SendGroupMessageSP(message.GroupID, message.UserID, responseText, selfid, promptstr) + } + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("Request received and use cache")) + return // 成功使用缓存答案,提前退出 + } else { + fmtf.Printf("Error getting random answer: %v", err) + + } + } else { + fmtf.Printf("缓存命中,但没有符合概率,继续执行后续代码\n") + // 注意:这里不需要再生成 lastSelectedVectorID,因为上面已经生成 + } + } else { + // 没有找到相似文本,存储新的文本及其向量 + newVectorID, err := app.insertVectorData(newmsg, vector) + if err != nil { + fmtf.Printf("Error inserting new vector data: %v", err) + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("Error inserting new vector data")) + return + } + lastSelectedVectorID = int(newVectorID) // 存储新插入向量的ID + fmtf.Printf("没找到缓存,准备储存了lastSelectedVectorID: %v\n", lastSelectedVectorID) + } + + // 这里继续执行您的逻辑,比如生成新的答案等 + // 注意:根据实际情况调整后续逻辑 + } + + //提示词安全部分 + if config.GetAntiPromptAttackPath() != "" { + if checkResponseThreshold(newmsg) { + fmtf.Printf("提示词不安全,过滤:%v", message) + saveresponse := config.GetRandomSaveResponse() + if saveresponse != "" { + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if !config.GetUsePrivateSSE() { + utils.SendPrivateMessageSP(message.UserID, saveresponse, selfid, promptstr) + } else { + utils.SendSSEPrivateSafeMessageSP(message.UserID, saveresponse, promptstr, selfid) + } + } else { + utils.SendGroupMessageSP(message.GroupID, message.UserID, saveresponse, selfid, promptstr) + } + } + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("Request received and not safe")) + return + } + } + + var conversationID, parentMessageID string + // 请求conversation api 增加当前群/用户上下文 + if config.GetGroupContext() == 2 && message.MessageType != "private" { + conversationID, parentMessageID, err = app.handleUserContextSP(message.GroupID) + } else { + conversationID, parentMessageID, err = app.handleUserContextSP(message.UserID) + } + + // 使用map映射conversationID和uid gid的关系 + StoreUserInfoSP(conversationID, message.UserID, message.GroupID, message.RealMessageType, message.MessageType) + + // 保存记忆 + memoryCommand := config.GetMemoryCommand() + + // 检查checkResetCommand是否在memoryCommand列表中 + ismemoryCommand := false + for _, command := range memoryCommand { + if checkResetCommand == command { + ismemoryCommand = true + break + } + } + + // 处理保存记忆 + if ismemoryCommand { + app.handleSaveMemorySP(message, conversationID, parentMessageID, promptstr) // 适配群 + return + } + + // 记忆列表 + memoryLoadCommand := config.GetMemoryLoadCommand() + + // 检查checkResetCommand是否在memoryLoadCommand列表中或以其为前缀 + ismemoryLoadCommand := false + isPrefixedMemoryLoadCommand := false // 新增变量用于检测前缀匹配 + for _, command := range memoryLoadCommand { + if checkResetCommand == command { + ismemoryLoadCommand = true + break + } + if strings.HasPrefix(checkResetCommand, command) { // 检查前缀 + isPrefixedMemoryLoadCommand = true + } + } + + // 处理记忆列表 + if ismemoryLoadCommand { + app.handleMemoryListSP(message, promptstr) // 适配群 + return + } + + // 新增处理载入记忆的逻辑 + if isPrefixedMemoryLoadCommand { + app.handleLoadMemorySP(message, checkResetCommand, promptstr) // 适配群 + return + } + + // 新对话 + newConversationCommand := config.GetNewConversationCommand() + + // 检查checkResetCommand是否在newConversationCommand列表中 + isnewConversationCommand := false + for _, command := range newConversationCommand { + if checkResetCommand == command { + isnewConversationCommand = true + break + } + } + + // 处理新对话 + if isnewConversationCommand { + app.handleNewConversationSP(message, conversationID, parentMessageID, promptstr) // 适配群 + return + } + + //每句话清空上一句话的messageBuilder + ClearMessage(conversationID) + fmtf.Printf("conversationID: %s,parentMessageID%s\n", conversationID, parentMessageID) + if err != nil { + fmtf.Printf("Error handling user context: %v\n", err) + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("Error handling user context")) + return + } + + // 请求模型使用原文请求,并应用安全策略 + requestmsg := message.Message.(string) + + if config.GetPrintHanming() { + fmtf.Printf("消息进入替换前:%v", requestmsg) + } + + // 繁体转换简体 安全策略 + requestmsg, err = utils.ConvertTraditionalToSimplified(requestmsg) + if err != nil { + fmtf.Printf("繁体转换简体失败:%v", err) + } + + // 替换in替换词规则 + if config.GetSensitiveMode() { + requestmsg = acnode.CheckWordIN(requestmsg) + } + + // 按提示词区分的细化替换 这里主要不是为了安全和敏感词,而是细化效果,也就没有使用acnode提高效率 + requestmsg = utils.ReplaceTextIn(requestmsg, promptstr) + + if config.GetGroupContext() == 2 && message.MessageType != "private" { + fmtf.Printf("实际请求conversation端点内容:[%v]%v\n", message.GroupID, requestmsg) + } else { + fmtf.Printf("实际请求conversation端点内容:[%v]%v\n", message.UserID, requestmsg) + } + + // 本次指令不受到记忆的影响,例外.用于玩法类,不需要上下文的场景. + if nomemory { + parentMessageID = "" + } + + requestBody, err := json.Marshal(map[string]interface{}{ + "message": requestmsg, + "conversationId": conversationID, + "parentMessageId": parentMessageID, + "user_id": message.UserID, + }) + + if err != nil { + fmtf.Printf("Error marshalling request: %v\n", err) + return + } + + // 构建URL并发送请求到conversation接口 + port := config.GetPort() + portStr := fmt.Sprintf(":%d", port) + + // 初始化URL,根据api参数动态调整路径 + basePath := "/conversation" + + //MARK:能定义每个yml自己要调用的conversation端点 + newPath := config.GetConversationPath(promptstr) + // 允许覆盖请求不同的conversation + if newPath != "/conversation" && newPath != "" { + fmtf.Printf("覆盖api参数: %s\n", newPath) + basePath = newPath // 动态替换conversation部分为ConversationPath,这个配置是包含了/的 + } + + if api != "" { + fmtf.Printf("收到api参数: %s\n", api) + basePath = "/" + api // 动态替换conversation部分为api参数值 + } + + var baseURL string + + if config.GetLotus(promptstr) == "" { + baseURL = "http://127.0.0.1" + portStr + basePath + } else { + baseURL = config.GetLotus(promptstr) + basePath + } + + // 在加入prompt之前 判断promptstr.yml是否存在 + if !prompt.CheckPromptExistence(promptstr) { + fmtf.Printf("该请求内容所对应yml文件不存在:[%v]:[%v]\n", requestmsg, promptstr) + promptstr = "" + } + + // 使用net/url包来构建和编码URL + urlParams := url.Values{} + if promptstr != "" { + urlParams.Add("prompt", promptstr) + } + + // 元器和glm会根据userid参数来自动封禁用户 + if config.GetApiType() == 5 || basePath == "/conversation_glm" || config.GetApiType() == 6 || basePath == "/conversation_yq" { + urlParams.Add("userid", message.UserID) + } + + // 将查询参数编码后附加到基本URL上 + fullURL := baseURL + if len(urlParams) > 0 { + fullURL += "?" + urlParams.Encode() + } + + fmtf.Printf("Generated URL:%v\n", fullURL) + + resp, err := http.Post(fullURL, "application/json", bytes.NewBuffer(requestBody)) + if err != nil { + fmtf.Printf("Error sending request to conversation interface: %v\n", err) + return + } + + defer resp.Body.Close() + + var lastMessageID string + var response string + + if config.GetuseSse(promptstr) == 2 { + // 处理SSE流式响应 + reader := bufio.NewReader(resp.Body) + for { + line, err := reader.ReadBytes('\n') + if err != nil { + if err == io.EOF { + break // 流结束 + } + fmtf.Printf("Error reading SSE response: %v\n", err) + return + } + + // 忽略空行 + if string(line) == "\n" { + continue + } + + // 处理接收到的数据 + if !config.GetHideExtraLogs() { + fmtf.Printf("Received SSE data: %s", string(line)) + } + + // 去除"data: "前缀后进行JSON解析 + jsonData := strings.TrimPrefix(string(line), "data: ") + var responseData map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &responseData); err == nil { + //接收到最后一条信息 + if id, ok := responseData["messageId"].(string); ok { + + conversationid := responseData["conversationId"].(string) + // 从conversation对应的sync map取出对应的用户和群号,避免高并发内容发送错乱 + userinfo, _ := GetUserInfoSP(conversationid) + + lastMessageID = id // 更新lastMessageID + // 检查是否有未发送的消息部分 + key := utils.GetKeySP(userinfo.GroupID, userinfo.UserID) + accumulatedMessageInterface, exists := groupUserMessages.Load(key) + var accumulatedMessage string + if exists { + accumulatedMessage = accumulatedMessageInterface.(string) + } + + // 提取response字段 + if response, ok = responseData["response"].(string); ok { + + // 如果accumulatedMessage是response的子串,则提取新的部分并发送 + if exists && strings.HasPrefix(response, accumulatedMessage) { + newPart := response[len(accumulatedMessage):] + if newPart != "" { + fmtf.Printf("A完整信息: %s,已发送信息:%s 新部分:%s\n", response, accumulatedMessage, newPart) + // 判断消息类型,如果是私人消息或私有群消息,发送私人消息;否则,根据配置决定是否发送群消息 + if userinfo.RealMessageType == "group_private" || userinfo.MessageType == "private" { + if !config.GetUsePrivateSSE() { + utils.SendPrivateMessageSP(userinfo.UserID, newPart, selfid, promptstr) + } else { + //判断是否最后一条 + state := 11 //继续 + messageSSE := structs.InterfaceBody{ + Content: newPart, + State: state, + } + utils.SendPrivateMessageSSESP(userinfo.UserID, messageSSE, promptstr, selfid) + } + } else { + // 这里发送的是newPart api最后补充的部分 + if !config.GetMdPromptKeyboardAtGroup() { + + utils.SendGroupMessageSP(userinfo.GroupID, userinfo.UserID, newPart, selfid, promptstr) + + } else { + go utils.SendGroupMessageMdPromptKeyboardSP(userinfo.GroupID, userinfo.UserID, newPart, selfid, newmsg, response, promptstr) + + } + } + } else { + // 流的最后一次是完整结束的 + fmtf.Printf("A完整信息: %s(sse完整结束)\n", response) + } + + } else if response != "" { + // 如果accumulatedMessage不存在或不是子串,print + fmtf.Printf("B完整信息: %s,已发送信息:%s", response, accumulatedMessage) + if accumulatedMessage == "" { + // 判断消息类型,如果是私人消息或私有群消息,发送私人消息;否则,根据配置决定是否发送群消息 + if userinfo.RealMessageType == "group_private" || userinfo.MessageType == "private" { + if !config.GetUsePrivateSSE() { + utils.SendPrivateMessageSP(userinfo.UserID, response, selfid, promptstr) + } else { + //判断是否最后一条 + state := 11 //下一个是11 由末尾补充负责 + messageSSE := structs.InterfaceBody{ + Content: response, + State: state, + } + utils.SendPrivateMessageSSESP(userinfo.UserID, messageSSE, promptstr, selfid) + } + } else { + if !config.GetMdPromptKeyboardAtGroup() { + utils.SendGroupMessageSP(userinfo.GroupID, userinfo.UserID, response, selfid, promptstr) + + } else { + + go utils.SendGroupMessageMdPromptKeyboardSP(userinfo.GroupID, userinfo.UserID, response, selfid, newmsg, response, promptstr) + + } + + } + } + } + + // 缓存省钱部分 这里默认不被覆盖,如果主配置开了缓存,始终缓存. + if config.GetUseCache() == 2 { + if response != "" { + fmtf.Printf("缓存了Q:%v,A:%v,向量ID:%v", newmsg, response, lastSelectedVectorID) + app.InsertQAEntry(newmsg, response, lastSelectedVectorID) + } else { + fmtf.Printf("缓存Q:%v时遇到问题,A为空,检查api是否存在问题", newmsg) + } + } + + // 清空key的值 + groupUserMessages.Store(key, "") + } + } else { + //发送信息 + if !config.GetHideExtraLogs() { + fmtf.Printf("收到流数据,切割并发送信息: %s", string(line)) + } + splitAndSendMessagesSP(string(line), newmsg, selfid, promptstr) + } + } + } + + // 在SSE流结束后更新用户上下文 在这里调用gensokyo流式接口的最后一步 插推荐气泡 + if lastMessageID != "" { + fmtf.Printf("lastMessageID: %s\n", lastMessageID) + if config.GetGroupContext() == 2 && message.MessageType != "private" { + err := app.updateUserContextSP(message.GroupID, lastMessageID) + if err != nil { + fmtf.Printf("Error updating user context: %v\n", err) + } + } else { + err := app.updateUserContextSP(message.UserID, lastMessageID) + if err != nil { + fmtf.Printf("Error updating user context: %v\n", err) + } + } + + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if config.GetUsePrivateSSE() { + + // 发气泡和按钮 + var promptkeyboard []string + if !config.GetUseAIPromptkeyboard() { + promptkeyboard = config.GetPromptkeyboard() + } else { + fmtf.Printf("ai生成气泡:%v", "Q"+newmsg+"A"+response) + promptkeyboard = promptkb.GetPromptKeyboardAI("Q"+newmsg+"A"+response, promptstr) + } + + // 使用acnode.CheckWordOUT()过滤promptkeyboard中的每个字符串 + for i, item := range promptkeyboard { + promptkeyboard[i] = acnode.CheckWordOUT(item) + } + + // 添加第四个气泡 + if config.GetNo4Promptkeyboard() { + // 合并所有命令到一个数组 + var allCommands []string + + // 获取并添加RestoreResponses + RestoreResponses := config.GetRestoreCommand() + allCommands = append(allCommands, RestoreResponses...) + + // 获取并添加memoryLoadCommand + memoryLoadCommand := config.GetMemoryLoadCommand() + allCommands = append(allCommands, memoryLoadCommand...) + + // 获取并添加memoryCommand + memoryCommand := config.GetMemoryCommand() + allCommands = append(allCommands, memoryCommand...) + + // 获取并添加newConversationCommand + newConversationCommand := config.GetNewConversationCommand() + allCommands = append(allCommands, newConversationCommand...) + + // 检查合并后的命令数组长度 + if len(allCommands) > 0 { + // 随机选择一个命令 + selectedCommand := allCommands[rand.Intn(len(allCommands))] + + // 在promptkeyboard的末尾添加选中的命令 + if len(promptkeyboard) > 0 { + promptkeyboard = append(promptkeyboard, selectedCommand) + } else { + // 如果promptkeyboard为空,我们也应当初始化它,并添加选中的命令 + promptkeyboard = []string{selectedCommand} + } + } + } + + //最后一条了 + messageSSE := structs.InterfaceBody{ + Content: " ", + State: 20, + PromptKeyboard: promptkeyboard, + } + utils.SendPrivateMessageSSESP(message.UserID, messageSSE, promptstr, selfid) + ResetIndex(newmsg) + } + } + } + } else { + // 处理常规响应 + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + fmtf.Printf("Error reading response body: %v\n", err) + return + } + fmtf.Printf("Response from conversation interface: %s\n", string(responseBody)) + + // 使用map解析响应数据以获取response字段和messageId + var responseData map[string]interface{} + if err := json.Unmarshal(responseBody, &responseData); err != nil { + fmtf.Printf("Error unmarshalling response data: %v\n", err) + return + } + var ok bool + // 使用提取的response内容发送消息 + if response, ok = responseData["response"].(string); ok && response != "" { + // 判断消息类型,如果是私人消息或私有群消息,发送私人消息;否则,根据配置决定是否发送群消息 + if message.RealMessageType == "group_private" || message.MessageType == "private" { + utils.SendPrivateMessageSP(message.UserID, response, selfid, promptstr) + } else { + utils.SendGroupMessageSP(message.GroupID, message.UserID, response, selfid, promptstr) + } + } + + // 更新用户上下文 + if messageId, ok := responseData["messageId"].(string); ok { + if config.GetGroupContext() == 2 && message.MessageType != "private" { + err := app.updateUserContextSP(message.GroupID, messageId) + if err != nil { + fmtf.Printf("Error updating user context: %v\n", err) + } + } else { + err := app.updateUserContextSP(message.UserID, messageId) + if err != nil { + fmtf.Printf("Error updating user context: %v\n", err) + } + } + + } + } + + // OUT规则不仅对实际发送api生效,也对http结果生效 + if config.GetSensitiveModeType() == 1 { + response = acnode.CheckWordOUT(response) + } + + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("Request received and processed Q:" + newmsg + " A:" + response)) + + if response == "" { + return + } + + case map[string]interface{}: + // message.Message是一个map[string]interface{} + // 理论上不应该执行到这里,因为我们已确保它是字符串 + fmtf.Println("Received map message, handling not implemented yet") + // 处理map类型消息的逻辑(TODO) + + default: + // message.Message是一个未知类型 + // 理论上不应该执行到这里,因为我们已确保它是字符串 + fmtf.Printf("Received message of unexpected type: %T\n", msg) + return + } + +} + +// StoreUserInfoSP 用于存储用户信息到全局 map +func StoreUserInfoSP(conversationID string, userID string, groupID string, realMessageType string, messageType string) { + userInfo := UserInfoSP{ + UserID: userID, + GroupID: groupID, + RealMessageType: realMessageType, + MessageType: messageType, + } + globalMapSP.Store(conversationID, userInfo) +} + +// GetUserInfoSP 根据conversationID获取用户信息 +func GetUserInfoSP(conversationID string) (UserInfoSP, bool) { + value, ok := globalMapSP.Load(conversationID) + if ok { + return value.(UserInfoSP), true + } + return UserInfoSP{}, false +} + +// 处理撤回信息的函数 +func handleWithdrawMessageSP(message structs.OnebotGroupMessageS) { + fmtf.Println("处理撤回操作") + var id string + + // 根据消息类型决定使用哪个ID + switch message.RealMessageType { + case "group_private", "guild_private": + id = message.UserID + case "group", "guild": + id = message.GroupID + case "interaction": + id = message.GroupID + default: + fmt.Println("Unsupported message type for withdrawal:", message.RealMessageType) + return + } + + selfidstr := strconv.FormatInt(message.SelfID, 10) + // 调用DeleteLatestMessage函数 + err := utils.DeleteLatestMessageSP(message.RealMessageType, id, message.UserID, selfidstr) + if err != nil { + fmt.Println("Error deleting latest message:", err) + return + } +} + +func splitAndSendMessagesSP(line string, newmesssage string, selfid string, promptstr string) { + // 提取JSON部分 + dataPrefix := "data: " + jsonStr := strings.TrimPrefix(line, dataPrefix) + + // 解析JSON数据 + var sseData struct { + Response string `json:"response"` + ConversationId string `json:"conversationId"` + } + err := json.Unmarshal([]byte(jsonStr), &sseData) + if err != nil { + fmtf.Printf("Error unmarshalling SSE data: %v\n", err) + return + } + + if sseData.Response != "\n\n" { + // 处理提取出的信息 + processMessageSP(sseData.Response, sseData.ConversationId, newmesssage, selfid, promptstr) + } else { + fmtf.Printf("忽略llm末尾的换行符") + } +} + +func processMessageSP(response string, conversationid string, newmesssage string, selfid string, promptstr string) { + // 从conversation对应的sync map取出对应的用户和群号,避免高并发内容发送错乱 + userinfo, _ := GetUserInfoSP(conversationid) + key := utils.GetKeySP(userinfo.GroupID, userinfo.UserID) + + // 定义中文全角和英文标点符号 + punctuations := []rune{'。', '!', '?', ',', ',', '.', '!', '?', '~'} + + for _, char := range response { + AppendRune(conversationid, char) + if utils.ContainsRuneSP(punctuations, char, userinfo.GroupID, userinfo.UserID, promptstr) { + // 达到标点符号,发送累积的整个消息 + if GetMessageLength(conversationid) > 0 { + accumulatedMessage, _ := GetCurrentMessage(conversationid) + // 锁定 + processMessageMu.Lock() + // 从sync.map读取当前的value + valueInterface, _ := groupUserMessages.Load(key) + value, _ := valueInterface.(string) + // 添加当前messageBuilder中的新内容 + value += accumulatedMessage + // 储存新的内容到sync.map + groupUserMessages.Store(key, value) + processMessageMu.Unlock() // 完成更新后时解锁 + + // 判断消息类型,如果是私人消息或私有群消息,发送私人消息;否则,根据配置决定是否发送群消息 + if userinfo.RealMessageType == "group_private" || userinfo.MessageType == "private" { + if !config.GetUsePrivateSSE() { + utils.SendPrivateMessageSP(userinfo.UserID, accumulatedMessage, selfid, promptstr) + } else { + if IncrementIndex(newmesssage) == 1 { + //第一条信息 + //取出当前信息作为按钮回调 + //CallbackData := GetStringById(lastMessageID) + uerid := userinfo.UserID + messageSSE := structs.InterfaceBody{ + Content: accumulatedMessage, + State: 1, + ActionButton: 10, + CallbackData: uerid, + } + utils.SendPrivateMessageSSESP(userinfo.UserID, messageSSE, promptstr, selfid) + } else { + //SSE的前半部分 + messageSSE := structs.InterfaceBody{ + Content: accumulatedMessage, + State: 1, + } + utils.SendPrivateMessageSSESP(userinfo.UserID, messageSSE, promptstr, selfid) + } + } + } else { + utils.SendGroupMessageSP(userinfo.GroupID, userinfo.UserID, accumulatedMessage, selfid, promptstr) + } + + ClearMessage(conversationid) + } + } + } +} diff --git a/applogic/memory.go b/applogic/memory.go index 29d696e..f162f59 100644 --- a/applogic/memory.go +++ b/applogic/memory.go @@ -129,6 +129,53 @@ func (app *App) handleSaveMemory(msg structs.OnebotGroupMessage, ConversationID app.sendMemoryResponseWithkeyBoard(msg, saveMemoryResponse, keyboard, promptstr) } +// 保存记忆 +func (app *App) handleSaveMemorySP(msg structs.OnebotGroupMessageS, ConversationID string, ParentMessageID string, promptstr string) { + conversationTitle := "2024-5-19/18:26" // 默认标题,根据实际需求可能需要调整为动态生成的时间戳 + + userid := msg.UserID + if config.GetGroupContext() == 2 && msg.MessageType != "private" { + userid = msg.GroupID + } + + // 添加用户记忆 + err := app.AddUserMemorySP(userid, ConversationID, ParentMessageID, conversationTitle) + if err != nil { + log.Printf("Error saving memory: %s", err) + return + } + + // 启动Go routine进行历史信息处理和标题更新 + go func() { + userHistory, err := app.getHistory(ConversationID, ParentMessageID) + if err != nil { + log.Printf("Error retrieving history: %s", err) + return + } + + // 处理历史信息为特定格式的字符串 + memoryTitle := formatHistory(userHistory) + newTitle := GetMemoryTitle(memoryTitle) // 获取最终的记忆标题 + + // 更新记忆标题 + err = app.updateConversationTitleSP(userid, ConversationID, ParentMessageID, newTitle) + if err != nil { + log.Printf("Error updating conversation title: %s", err) + } + }() + + var keyboard []string // 准备一个空的键盘数组 + // 获取记忆载入命令 + memoryLoadCommands := config.GetMemoryLoadCommand() + if len(memoryLoadCommands) > 0 { + keyboard = append(keyboard, memoryLoadCommands[0]) // 添加第一个命令到键盘数组 + } + + // 发送保存成功的响应 + saveMemoryResponse := "记忆保存成功!" + app.sendMemoryResponseWithkeyBoardSP(msg, saveMemoryResponse, keyboard, promptstr) +} + // 获取记忆列表 func (app *App) handleMemoryList(msg structs.OnebotGroupMessage, promptstr string) { @@ -188,6 +235,65 @@ func (app *App) handleMemoryList(msg structs.OnebotGroupMessage, promptstr strin app.sendMemoryResponseByline(msg, responseBuilder.String(), keyboard, promptstr) } +// 获取记忆列表 +func (app *App) handleMemoryListSP(msg structs.OnebotGroupMessageS, promptstr string) { + + userid := msg.UserID + if config.GetGroupContext() == 2 && msg.MessageType != "private" { + userid = msg.GroupID + } + + memories, err := app.GetUserMemoriesSP(userid) + if err != nil { + log.Printf("Error retrieving memories: %s", err) + return + } + + // 组合格式化的文本 + var responseBuilder strings.Builder + responseBuilder.WriteString("当前记忆列表:\n") + + // 准备键盘数组,最多包含4个标题 + var keyboard []string + var loadMemoryCommand string + // 获取载入记忆指令 + memoryLoadCommands := config.GetMemoryLoadCommand() + if len(memoryLoadCommands) > 0 { + loadMemoryCommand = memoryLoadCommands[0] + } else { + loadMemoryCommand = "未设置载入指令" + } + + for _, memory := range memories { + // 使用acnode.CheckWordOUT()过滤 + memory.ConversationTitle = acnode.CheckWordOUT(memory.ConversationTitle) + + if config.GetMemoryListMD() == 0 { + responseBuilder.WriteString(memory.ConversationTitle + "\n") + } + keyboard = append(keyboard, loadMemoryCommand+" "+memory.ConversationTitle) // 添加新的标题 + } + + var exampleTitle string + if len(memories) > 0 { + exampleTitle = string([]rune(memories[0].ConversationTitle)[:3]) + } + + if config.GetMemoryListMD() == 0 { + responseBuilder.WriteString(fmt.Sprintf("提示:发送 %s 任意标题开头的前n字即可载入记忆\n如:%s %s", loadMemoryCommand, loadMemoryCommand, exampleTitle)) + } else { + if len(keyboard) == 0 { + responseBuilder.WriteString("目前还没有对话记忆...点按记忆按钮来保存新的记忆吧") + } else { + responseBuilder.WriteString("点击蓝色文字载入记忆") + } + + } + + // 发送组合后的信息,包括键盘数组 + app.sendMemoryResponseBylineSP(msg, responseBuilder.String(), keyboard, promptstr) +} + // 载入记忆 func (app *App) handleLoadMemory(msg structs.OnebotGroupMessage, checkResetCommand string, promptstr string) { @@ -245,6 +351,63 @@ func (app *App) handleLoadMemory(msg structs.OnebotGroupMessage, checkResetComma app.sendMemoryResponse(msg, responseMessage, promptstr) } +// 载入记忆 +func (app *App) handleLoadMemorySP(msg structs.OnebotGroupMessageS, checkResetCommand string, promptstr string) { + + userid := msg.UserID + if config.GetGroupContext() == 2 && msg.MessageType != "private" { + userid = msg.GroupID + } + + // 从配置获取载入记忆指令 + memoryLoadCommands := config.GetMemoryLoadCommand() + + // 移除所有载入记忆指令部分 + for _, command := range memoryLoadCommands { + checkResetCommand = strings.Replace(checkResetCommand, command, "", -1) + } + + // 移除空格得到匹配词 + matchTerm := strings.TrimSpace(checkResetCommand) + + // 判断处理过的字符串是否以"+"开头 移除+号 + matchTerm = strings.TrimPrefix(matchTerm, "+") + + // 获取用户记忆 + memories, err := app.GetUserMemoriesSP(userid) + if err != nil { + log.Printf("Error retrieving memories: %s", err) + app.sendMemoryResponseSP(msg, "获取记忆失败", promptstr) + return + } + + // 查找匹配的记忆 + var matchedMemory *structs.Memory + for _, memory := range memories { + if strings.HasPrefix(memory.ConversationTitle, matchTerm) { + matchedMemory = &memory + break + } + } + + if matchedMemory == nil { + app.sendMemoryResponseSP(msg, "未找到匹配的记忆", promptstr) + return + } + + // 载入记忆 + err = app.updateUserContextProSP(userid, matchedMemory.ConversationID, matchedMemory.ParentMessageID) + if err != nil { + log.Printf("Error adding memory: %s", err) + app.sendMemoryResponseSP(msg, "载入记忆失败", promptstr) + return + } + + // 组合回复信息 + responseMessage := fmt.Sprintf("成功载入了标题为 '%s' 的记忆", matchedMemory.ConversationTitle) + app.sendMemoryResponseSP(msg, responseMessage, promptstr) +} + func (app *App) sendMemoryResponseWithkeyBoard(msg structs.OnebotGroupMessage, response string, keyboard []string, promptstr string) { strSelfID := strconv.FormatInt(msg.SelfID, 10) if msg.RealMessageType == "group_private" || msg.MessageType == "private" { @@ -258,6 +421,19 @@ func (app *App) sendMemoryResponseWithkeyBoard(msg structs.OnebotGroupMessage, r } } +func (app *App) sendMemoryResponseWithkeyBoardSP(msg structs.OnebotGroupMessageS, response string, keyboard []string, promptstr string) { + strSelfID := strconv.FormatInt(msg.SelfID, 10) + if msg.RealMessageType == "group_private" || msg.MessageType == "private" { + if !config.GetUsePrivateSSE() { + utils.SendPrivateMessageSP(msg.UserID, response, strSelfID, promptstr) + } else { + utils.SendSSEPrivateMessageWithKeyboardSP(msg.UserID, response, keyboard, promptstr, strSelfID) + } + } else { + utils.SendGroupMessageSP(msg.GroupID, msg.UserID, response, strSelfID, promptstr) + } +} + func (app *App) sendMemoryResponse(msg structs.OnebotGroupMessage, response string, promptstr string) { strSelfID := strconv.FormatInt(msg.SelfID, 10) if msg.RealMessageType == "group_private" || msg.MessageType == "private" { @@ -271,6 +447,19 @@ func (app *App) sendMemoryResponse(msg structs.OnebotGroupMessage, response stri } } +func (app *App) sendMemoryResponseSP(msg structs.OnebotGroupMessageS, response string, promptstr string) { + strSelfID := strconv.FormatInt(msg.SelfID, 10) + if msg.RealMessageType == "group_private" || msg.MessageType == "private" { + if !config.GetUsePrivateSSE() { + utils.SendPrivateMessageSP(msg.UserID, response, strSelfID, promptstr) + } else { + utils.SendSSEPrivateMessageSP(msg.UserID, response, promptstr, strSelfID) + } + } else { + utils.SendGroupMessageSP(msg.GroupID, msg.UserID, response, strSelfID, promptstr) + } +} + func (app *App) sendMemoryResponseByline(msg structs.OnebotGroupMessage, response string, keyboard []string, promptstr string) { strSelfID := strconv.FormatInt(msg.SelfID, 10) if msg.RealMessageType == "group_private" || msg.MessageType == "private" { @@ -296,6 +485,31 @@ func (app *App) sendMemoryResponseByline(msg structs.OnebotGroupMessage, respons } } +func (app *App) sendMemoryResponseBylineSP(msg structs.OnebotGroupMessageS, response string, keyboard []string, promptstr string) { + strSelfID := strconv.FormatInt(msg.SelfID, 10) + if msg.RealMessageType == "group_private" || msg.MessageType == "private" { + if !config.GetUsePrivateSSE() { + utils.SendPrivateMessageSP(msg.UserID, response, strSelfID, promptstr) + } else { + // 更新键盘数组,确保最多只有三个元素 + if len(keyboard) >= 3 { + keyboard = keyboard[:3] + } + utils.SendSSEPrivateMessageByLineSP(msg.UserID, response, keyboard, promptstr, strSelfID) + } + } else { + if config.GetMemoryListMD() == 0 { + utils.SendGroupMessageSP(msg.GroupID, msg.UserID, response, strSelfID, promptstr) + } else { + // 更新键盘数组,确保最多只有五个元素 + if len(keyboard) >= 5 { + keyboard = keyboard[:5] + } + utils.SendGroupMessageMdPromptKeyboardV2SP(msg.GroupID, msg.UserID, response, strSelfID, promptstr, keyboard) + } + } +} + func formatHistory(history []structs.Message) string { var result string for _, message := range history { @@ -354,3 +568,50 @@ func (app *App) handleNewConversation(msg structs.OnebotGroupMessage, conversati app.sendMemoryResponse(msg, saveMemoryResponse, promotstr) } } + +func (app *App) handleNewConversationSP(msg structs.OnebotGroupMessageS, conversationID string, parentMessageID string, promotstr string) { + // 使用预定义的时间戳作为会话标题 + conversationTitle := "2024-5-19/18:26" // 实际应用中应使用动态生成的时间戳 + userid := msg.UserID + + if config.GetGroupContext() == 2 && msg.MessageType != "private" { + userid = msg.GroupID + } + + // 添加用户记忆 + err := app.AddUserMemorySP(userid, conversationID, parentMessageID, conversationTitle) + if err != nil { + log.Printf("Error saving memory: %s", err) + return + } + + // 启动Go routine进行历史信息处理和标题更新 + go func() { + userHistory, err := app.getHistory(conversationID, parentMessageID) + if err != nil { + log.Printf("Error retrieving history: %s", err) + return + } + + // 处理历史信息为特定格式的字符串 + memoryTitle := formatHistory(userHistory) + newTitle := GetMemoryTitle(memoryTitle) // 获取最终的记忆标题 + + // 更新记忆标题 + err = app.updateConversationTitleSP(userid, conversationID, parentMessageID, newTitle) + if err != nil { + log.Printf("Error updating conversation title: %s", err) + } + }() + + // 迁移用户到新的上下文 + app.migrateUserToNewContextSP(userid) + + // 获取并使用配置中指定的加载记忆指令 + loadCommand := config.GetMemoryLoadCommand() + if len(loadCommand) > 0 { + loadMemoryCommand := loadCommand[0] // 使用数组中的第一个指令 + saveMemoryResponse := fmt.Sprintf("旧的对话已经保存,可发送 %s 来查看,可以开始新的对话了!", loadMemoryCommand) + app.sendMemoryResponseSP(msg, saveMemoryResponse, promotstr) + } +} diff --git a/applogic/promptstr.go b/applogic/promptstr.go index 70cad43..0343253 100644 --- a/applogic/promptstr.go +++ b/applogic/promptstr.go @@ -106,6 +106,17 @@ func (app *App) deleteCustomRecord(userID int64) error { return nil } +func (app *App) deleteCustomRecordSP(userID string) error { + deleteSQL := `DELETE FROM custom_table WHERE user_id = ?;` + + _, err := app.DB.Exec(deleteSQL, userID) + if err != nil { + return fmt.Errorf("error deleting record from custom_table: %w", err) + } + + return nil +} + // Helper function to get index from field name func fieldIndex(field string) int { if strings.HasPrefix(field, "str") && len(field) > 3 { diff --git a/applogic/singlecontext.go b/applogic/singlecontext.go index 7064aed..0bc3bb4 100644 --- a/applogic/singlecontext.go +++ b/applogic/singlecontext.go @@ -48,3 +48,45 @@ func (app *App) AddSingleContext(message structs.OnebotGroupMessage, responseTex return true } + +// 直接根据缓存来储存上下文 +// 其实向量缓存是一个单轮的QA缓存,因为这个项目很初步,很显然无法应对上下文场景的缓存 +// 通过这种方式,将每次缓存的内容也加入上下文,可能会有一个初步的效果提升. +func (app *App) AddSingleContextSP(message structs.OnebotGroupMessageS, responseText string) bool { + // 请求conversation api 增加当前用户上下文 + conversationID, parentMessageID, err := app.handleUserContextSP(message.UserID) + if err != nil { + fmtf.Printf("error in AddSingleContext app.handleUserContex :%v", err) + return false + } + + // 构造用户消息并添加到上下文 + userMessage := structs.Message{ + ConversationID: conversationID, + ParentMessageID: parentMessageID, + Text: message.Message.(string), + Role: "user", + CreatedAt: time.Now().Format(time.RFC3339), + } + userMessageID, err := app.addMessage(userMessage) + if err != nil { + fmtf.Printf("error in AddSingleContext app.addMessage(userMessage) :%v", err) + return false + } + + // 构造助理消息并添加到上下文 + assistantMessage := structs.Message{ + ConversationID: conversationID, + ParentMessageID: userMessageID, + Text: responseText, + Role: "assistant", + CreatedAt: time.Now().Format(time.RFC3339), + } + _, err = app.addMessage(assistantMessage) + if err != nil { + fmtf.Printf("error in AddSingleContext app.addMessage(assistantMessage) :%v", err) + return false + } + + return true +} diff --git a/config/config.go b/config/config.go index 6fcc4d9..fbe7fb6 100644 --- a/config/config.go +++ b/config/config.go @@ -3518,3 +3518,13 @@ func getMdPromptKeyboardAtGroupCmdsInternal(options ...string) []string { return envContents } + +// 获取Stringob11 +func GetStringob11() bool { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.Stringob11 + } + return false +} diff --git a/main.go b/main.go index 53aff04..0154cfd 100644 --- a/main.go +++ b/main.go @@ -95,10 +95,19 @@ func main() { if err != nil { log.Fatalf("Failed to ensure database tables exist: %v", err) } - // 确保user_context表存在 - err = app.EnsureUserContextTableExists() - if err != nil { - log.Fatalf("Failed to ensure user_context table exists: %v", err) + + if !config.GetStringob11() { + // 确保user_context表存在 + err = app.EnsureUserContextTableExists() + if err != nil { + log.Fatalf("Failed to ensure user_context table exists: %v", err) + } + } else { + // 确保user_context表存在 + err = app.EnsureUserContextTableExistsSP() + if err != nil { + log.Fatalf("Failed to ensure user_context table exists: %v", err) + } } // 确保向量表存在 @@ -119,10 +128,18 @@ func main() { log.Fatalf("Failed to ensure SensitiveWordsTable table exists: %v", err) } - // 故事模式存档 - err = app.EnsureCustomTableExist() - if err != nil { - log.Fatalf("Failed to ensure CustomTableExist table exists: %v", err) + if !config.GetStringob11() { + // 故事模式存档 + err = app.EnsureCustomTableExist() + if err != nil { + log.Fatalf("Failed to ensure CustomTableExist table exists: %v", err) + } + } else { + // 故事模式存档 + err = app.EnsureCustomTableExistSP() + if err != nil { + log.Fatalf("Failed to ensure CustomTableExist table exists: %v", err) + } } // 用户多个记忆表 @@ -253,7 +270,12 @@ func main() { } // 设置路由 - http.HandleFunc("/gensokyo", app.GensokyoHandler) + if !config.GetStringob11() { + http.HandleFunc("/gensokyo", app.GensokyoHandler) + } else { + http.HandleFunc("/gensokyo", app.GensokyoHandlerSP) + } + var wspath string if conf.Settings.WSPath == "nil" { wspath = "/" diff --git a/structs/struct.go b/structs/struct.go index 56ef671..301b2a0 100644 --- a/structs/struct.go +++ b/structs/struct.go @@ -52,6 +52,29 @@ type OnebotGroupMessage struct { IsBindedUserId bool `json:"is_binded_user_id,omitempty"` //当前用户号号是否是binded后的 } +type OnebotGroupMessageS struct { + RawMessage string `json:"raw_message"` + MessageID string `json:"message_id"` + GroupID string `json:"group_id"` // Can be either string or int depending on p.Settings.CompleteFields + MessageType string `json:"message_type"` + PostType string `json:"post_type"` + SelfID int64 `json:"self_id"` // Can be either string or int + Sender Sender `json:"sender"` + SubType string `json:"sub_type"` + Time int64 `json:"time"` + Avatar string `json:"avatar,omitempty"` + Echo string `json:"echo,omitempty"` + Message interface{} `json:"message"` // For array format + MessageSeq int `json:"message_seq"` + Font int `json:"font"` + UserID string `json:"user_id"` + RealMessageType string `json:"real_message_type,omitempty"` //当前信息的真实类型 group group_private guild guild_private + RealUserID string `json:"real_user_id,omitempty"` //当前真实uid + RealGroupID string `json:"real_group_id,omitempty"` //当前真实gid + IsBindedGroupId bool `json:"is_binded_group_id,omitempty"` //当前群号是否是binded后的 + IsBindedUserId bool `json:"is_binded_user_id,omitempty"` //当前用户号号是否是binded后的 +} + type Sender struct { Nickname string `json:"nickname"` TinyID string `json:"tiny_id"` @@ -291,6 +314,8 @@ type Settings struct { SpecialNameToQ []ReplacementNamePair `yaml:"specialNameToQ"` NoEmoji int `yaml:"noEmoji"` // 0 false 1 false 2 true + Stringob11 bool `yaml:"stringob11"` + HunyuanType int `yaml:"hunyuanType"` MaxTokensHunyuan int `yaml:"maxTokensHunyuan"` HunyuanStreamModeration bool `yaml:"hunyuanStreamModeration"` diff --git a/template/config_template.go b/template/config_template.go index f2976cc..bc65e88 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -15,6 +15,7 @@ settings: lotus : "" #当填写另一个gensokyo-llm的http地址时,将请求另一个的conversation端点,实现多个llm不需要多次配置,简化配置,单独使用请忽略留空.例:http://192.168.0.1:12345(包含http头和端口) pathToken : "" #gensokyo正向http-api的access_token(是onebotv11标准的) apiType : 0 #0=混元 1=文心(文心平台包含了N种模型...) 2=gpt 3=rwkv 4=通义千问 5=智谱AI 6=腾讯元器 + stringob11 : false #兼容string模式ob11 oneApi : false #内置了一个简化版的oneApi oneApiPort : 50052 #内置简化版oneApi所监听的地址 :50052/v1 diff --git a/utils/blacklist.go b/utils/blacklist.go index bd06a09..df39dbb 100644 --- a/utils/blacklist.go +++ b/utils/blacklist.go @@ -136,3 +136,48 @@ func BlacklistIntercept(message structs.OnebotGroupMessage, selfid string, promp return false // 用户ID不在黑名单中,不拦截 } + +// BlacklistIntercept 检查用户ID是否在黑名单中,如果在,则发送预设消息 +func BlacklistInterceptSP(message structs.OnebotGroupMessageS, selfid string, promptstr string) bool { + // 检查群ID是否在黑名单中 + if IsInBlacklist(message.GroupID) { + // 获取黑名单响应消息 + responseMessage := config.GetBlacklistResponseMessages() + + // 根据消息类型发送响应 + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if !config.GetUsePrivateSSE() { + SendPrivateMessageSP(message.UserID, responseMessage, selfid, promptstr) + } else { + SendSSEPrivateMessageSP(message.UserID, responseMessage, promptstr, selfid) + } + } else { + SendGroupMessageSP(message.GroupID, message.UserID, responseMessage, selfid, promptstr) + } + + fmt.Printf("groupid:[%v]这个群在黑名单中,被拦截\n", message.GroupID) + return true // 拦截 + } + + // 检查用户ID是否在黑名单中 + if IsInBlacklist(message.UserID) { + // 获取黑名单响应消息 + responseMessage := config.GetBlacklistResponseMessages() + + // 根据消息类型发送响应 + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if !config.GetUsePrivateSSE() { + SendPrivateMessageSP(message.UserID, responseMessage, selfid, promptstr) + } else { + SendSSEPrivateMessageSP(message.UserID, responseMessage, promptstr, selfid) + } + } else { + SendGroupMessageSP(message.GroupID, message.UserID, responseMessage, selfid, promptstr) + } + + fmt.Printf("userid:[%v]这位用户在黑名单中,被拦截\n", message.UserID) + return true // 拦截 + } + + return false // 用户ID不在黑名单中,不拦截 +} diff --git a/utils/utils.go b/utils/utils.go index 17582f7..0dd96a2 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -35,16 +35,33 @@ type ResponseData struct { } `json:"data"` } +// ResponseData 是用于解析HTTP响应的结构体 +type ResponseDataSP struct { + Data struct { + MessageID string `json:"message_id"` + } `json:"data"` +} + // MessageIDInfo 代表消息ID及其到期时间 type MessageIDInfo struct { MessageID int64 // 消息ID Expires time.Time // 到期时间 } +// MessageIDInfo 代表消息ID及其到期时间 +type MessageIDInfoSP struct { + MessageID string // 消息ID + Expires time.Time // 到期时间 +} + // UserIDMessageIDs 存储每个用户ID对应的消息ID数组及其有效期 var UserIDMessageIDs = make(map[int64][]MessageIDInfo) var muUserIDMessageIDs sync.RWMutex // 用于UserIDMessageIDs的读写锁 +// UserIDMessageIDs 存储每个用户ID对应的消息ID数组及其有效期 +var UserIDMessageIDsSP = make(map[string][]MessageIDInfoSP) +var muUserIDMessageIDsSP sync.RWMutex // 用于UserIDMessageIDs的读写锁 + var ( baseURLMap = make(map[string]string) baseURLMapMu sync.Mutex @@ -148,6 +165,11 @@ func GetKey(groupid int64, userid int64) string { return fmt.Sprintf("%d.%d", groupid, userid) } +// 获取复合键 +func GetKeySP(groupid string, userid string) string { + return fmt.Sprintf("%s.%s", groupid, userid) +} + // 随机的分布发送 func ContainsRune(slice []rune, value rune, groupid int64, userid int64, promptstr string) bool { var probability int @@ -172,6 +194,30 @@ func ContainsRune(slice []rune, value rune, groupid int64, userid int64, prompts return false } +// 随机的分布发送 +func ContainsRuneSP(slice []rune, value rune, groupid string, userid string, promptstr string) bool { + var probability int + if groupid == userid { + // 获取私聊百分比 + probability = config.GetSplitByPuntuations(promptstr) + } else { + // 获取群聊百分比 + probability = config.GetSplitByPuntuationsGroup(promptstr) + } + + for _, item := range slice { + if item == value { + // 将概率转换为0到1之间的浮点数 + probabilityPercentage := float64(probability) / 100.0 + // 生成一个0到1之间的随机浮点数 + randomValue := rand.Float64() + // 如果随机数小于或等于概率,则返回true + return randomValue <= probabilityPercentage + } + } + return false +} + // 取出ai回答 func ExtractEventDetails(eventData map[string]interface{}) (string, structs.UsageInfo) { var responseTextBuilder strings.Builder @@ -318,6 +364,116 @@ func SendGroupMessage(groupID int64, userID int64, message string, selfid string return nil } +func SendGroupMessageSP(groupID string, userID string, message string, selfid string, promptstr string) error { + //TODO: 用userid作为了echo,在ws收到回调信息的时候,加入到全局撤回数组,AddMessageID,实现撤回 + if server.IsSelfIDExists(selfid) { + // 创建消息结构体 + msg := map[string]interface{}{ + "action": "send_group_msg", + "params": map[string]interface{}{ + "group_id": groupID, + "user_id": userID, + "message": message, + }, + "echo": userID, + } + + // 发送消息 + return server.SendMessageBySelfID(selfid, msg) + } + var baseURL string + if len(config.GetHttpPaths()) > 0 { + baseURL, _ = GetBaseURLByUserID(selfid) + } else { + // 获取基础URL + baseURL = config.GetHttpPath() // 假设config.getHttpPath()返回基础URL + } + + // 构建完整的URL + baseURL = baseURL + "/send_group_msg" + + // 获取PathToken并检查其是否为空 + pathToken := config.GetPathToken() + // 使用net/url包构建URL + u, err := url.Parse(baseURL) + if err != nil { + panic("URL parsing failed: " + err.Error()) + } + + // 添加access_token参数 + query := u.Query() + if pathToken != "" { + query.Set("access_token", pathToken) + } + u.RawQuery = query.Encode() + + if config.GetSensitiveModeType() == 1 { + message = acnode.CheckWordOUT(message) + } + + // 是否不显示Emoji + if config.GetNoEmoji(promptstr) == 2 { + message = RemoveEmojis(message) + } + + //精细化替换 每个yml配置文件都可以具有一个非全局的文本替换规则 + message = ReplaceTextOut(message, promptstr) + + // 去除末尾的换行符 不去除会导致不好看 + message = removeTrailingCRLFs(message) + + // 繁体转换简体 安全策略 防止用户诱导ai发繁体绕过替换规则 + message, err = ConvertTraditionalToSimplified(message) + if err != nil { + fmtf.Printf("繁体转换简体失败:%v", err) + } + + // 构造请求体 + requestBody, err := json.Marshal(map[string]interface{}{ + "group_id": groupID, + "user_id": userID, + "message": message, + }) + fmtf.Printf("发群信息请求:%v", string(requestBody)) + fmtf.Printf("实际发送信息:%v", message) + if err != nil { + return fmtf.Errorf("failed to marshal request body: %w", err) + } + + // 发送POST请求 + resp, err := http.Post(u.String(), "application/json", bytes.NewBuffer(requestBody)) + if err != nil { + return fmtf.Errorf("failed to send POST request: %w", err) + } + defer resp.Body.Close() + + // 检查响应状态 + if resp.StatusCode != http.StatusOK { + return fmtf.Errorf("received non-OK response status: %s", resp.Status) + } + + // 读取响应体 + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + // 解析响应体以获取message_id + var responseData ResponseDataSP + if err := json.Unmarshal(bodyBytes, &responseData); err != nil { + return fmt.Errorf("failed to unmarshal response data: %w", err) + } + messageID := responseData.Data.MessageID + + // 添加messageID到全局变量 + AddMessageIDSP(userID, messageID) + + // 输出响应体,这一步是可选的 + fmt.Println("Response Body:", string(bodyBytes)) + + return nil +} + func SendGroupMessageMdPromptKeyboard(groupID int64, userID int64, message string, selfid string, newmsg string, response string, promptstr string) error { //TODO: 用userid作为了echo,在ws收到回调信息的时候,加入到全局撤回数组,AddMessageID,实现反向ws连接时候的撤回 if server.IsSelfIDExists(selfid) { @@ -578,7 +734,7 @@ func SendGroupMessageMdPromptKeyboard(groupID int64, userID int64, message strin return nil } -func SendGroupMessageMdPromptKeyboardV2(groupID int64, userID int64, message string, selfid string, promptstr string, promptkeyboard []string) error { +func SendGroupMessageMdPromptKeyboardSP(groupID string, userID string, message string, selfid string, newmsg string, response string, promptstr string) error { //TODO: 用userid作为了echo,在ws收到回调信息的时候,加入到全局撤回数组,AddMessageID,实现反向ws连接时候的撤回 if server.IsSelfIDExists(selfid) { // 创建消息结构体 @@ -641,6 +797,26 @@ func SendGroupMessageMdPromptKeyboardV2(groupID int64, userID int64, message str if err != nil { fmtf.Printf("繁体转换简体失败:%v", err) } + + // 首先获取当前的keyboard + var promptkeyboard []string + if !config.GetUseAIPromptkeyboard() { + promptkeyboard = config.GetPromptkeyboard() + } else { + mdCMDs := config.GetMdPromptKeyboardAtGroupCmds(promptstr) + if len(mdCMDs) > 0 { + promptkeyboard = mdCMDs + } else { + fmtf.Printf("ai生成气泡:%v", "Q"+newmsg+"A"+response) + promptkeyboard = promptkb.GetPromptKeyboardAI("Q"+newmsg+"A"+response, promptstr) + } + } + + // 使用acnode.CheckWordOUT()过滤promptkeyboard中的每个字符串 + for i, item := range promptkeyboard { + promptkeyboard[i] = acnode.CheckWordOUT(item) + } + var mdContent string // 这里把message构造成一个cq,md码 if config.GetMemoryListMD() == 2 { @@ -668,36 +844,68 @@ func SendGroupMessageMdPromptKeyboardV2(groupID int64, userID int64, message str } fmt.Println(mdContent) - var promptKeyboardMd structs.PromptKeyboardMarkdown - if config.GetMemoryListMD() == 1 { - // 构建Buttons - buttons := []structs.Button{} - // 添加promptkeyboard的按钮,每个按钮一行 - for i, label := range promptkeyboard { - buttons = append(buttons, structs.Button{ - ID: fmt.Sprintf("%d", i+1), - RenderData: structs.RenderData{ - Label: label, - VisitedLabel: "已载入", - Style: 1, + + // 构建Buttons + buttons := []structs.Button{} + // 添加promptkeyboard的按钮,每个按钮一行 + for i, label := range promptkeyboard { + buttons = append(buttons, structs.Button{ + ID: fmt.Sprintf("%d", i+1), + RenderData: structs.RenderData{ + Label: label, + VisitedLabel: label, + Style: 1, + }, + Action: structs.Action{ + Type: 2, + Permission: structs.Permission{ + Type: 2, + SpecifyRoleIDs: []string{"1", "2", "3"}, }, - Action: structs.Action{ - Type: 2, - Permission: structs.Permission{ - Type: 2, - SpecifyRoleIDs: []string{"1", "2", "3"}, - }, - Data: label, - UnsupportTips: "请升级新版手机QQ", - Enter: true, - Reply: true, + Data: label, + UnsupportTips: "请升级新版手机QQ", + Enter: true, + Reply: true, + }, + }) + } + + // 添加"重置", "撤回", "重发"按钮,它们在一个单独的行 + rowWithThreeButtons := []structs.Button{} + labels := []string{"重置", "忽略", "记忆", "载入"} + + for i, label := range labels { + actionType := 1 + if label == "载入" { + actionType = 2 // 设置特定的 ActionType + } + + button := structs.Button{ + ID: fmt.Sprintf("%d", i+4), // 确保ID不重复 + RenderData: structs.RenderData{ + Label: label, + VisitedLabel: label, + Style: 1, + }, + Action: structs.Action{ + Type: actionType, // 使用条件变量设置的 actionType + Permission: structs.Permission{ + Type: 2, + SpecifyRoleIDs: []string{"1", "2", "3"}, }, - }) + Data: label, + UnsupportTips: "请升级新版手机QQ", + }, } - // 构建完整的PromptKeyboardMarkdown对象 - var rows []structs.Row // 初始化一个空切片来存放行 + rowWithThreeButtons = append(rowWithThreeButtons, button) + } + + // 构建完整的PromptKeyboardMarkdown对象 + var rows []structs.Row // 初始化一个空切片来存放行 + // GetMemoryListMD==1 将buttons添加到rows + if config.GetMemoryListMD() == 1 { // 遍历所有按钮,并每个按钮创建一行 for _, button := range buttons { row := structs.Row{ @@ -705,15 +913,223 @@ func SendGroupMessageMdPromptKeyboardV2(groupID int64, userID int64, message str } rows = append(rows, row) // 将新行添加到行切片中 } + } - // 构建 PromptKeyboardMarkdown 结构体 - promptKeyboardMd = structs.PromptKeyboardMarkdown{ - Markdown: structs.Markdown{ - Content: mdContent, - }, - Keyboard: structs.Keyboard{ - Content: structs.KeyboardContent{ - Rows: rows, // 使用动态创建的行数组 + // 添加特定的 rowWithThreeButtons 至 rows 数组的末尾 + row := structs.Row{ + Buttons: rowWithThreeButtons, // 将当前三个按钮放入 + } + rows = append(rows, row) + + // 构建 PromptKeyboardMarkdown 结构体 + promptKeyboardMd := structs.PromptKeyboardMarkdown{ + Markdown: structs.Markdown{ + Content: mdContent, + }, + Keyboard: structs.Keyboard{ + Content: structs.KeyboardContent{ + Rows: rows, // 使用动态创建的行数组 + }, + }, + Content: "keyboard", + MsgID: "123", + Timestamp: fmt.Sprintf("%d", time.Now().Unix()), + MsgType: 2, + } + + // 序列化成JSON + mdContentBytes, err := json.Marshal(promptKeyboardMd) + if err != nil { + fmt.Printf("Error marshaling to JSON: %v", err) + return nil + } + + // 编码成Base64 + encoded := base64.StdEncoding.EncodeToString(mdContentBytes) + segmentContent := "[CQ:markdown,data=base64://" + encoded + "]" + + // 构造请求体 + requestBody, err := json.Marshal(map[string]interface{}{ + "group_id": groupID, + "user_id": userID, + "message": segmentContent, + }) + fmtf.Printf("发群信息请求:%v", string(requestBody)) + fmtf.Printf("实际发送信息:%v", message) + if err != nil { + return fmtf.Errorf("failed to marshal request body: %w", err) + } + + // 发送POST请求 + resp, err := http.Post(u.String(), "application/json", bytes.NewBuffer(requestBody)) + if err != nil { + return fmtf.Errorf("failed to send POST request: %w", err) + } + defer resp.Body.Close() + + // 检查响应状态 + if resp.StatusCode != http.StatusOK { + return fmtf.Errorf("received non-OK response status: %s", resp.Status) + } + + // 读取响应体 + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + // 解析响应体以获取message_id + var responseData ResponseDataSP + if err := json.Unmarshal(bodyBytes, &responseData); err != nil { + return fmt.Errorf("failed to unmarshal response data: %w", err) + } + messageID := responseData.Data.MessageID + + // 添加messageID到全局变量 + AddMessageIDSP(userID, messageID) + + // 输出响应体,这一步是可选的 + fmt.Println("Response Body:", string(bodyBytes)) + + return nil +} + +func SendGroupMessageMdPromptKeyboardV2(groupID int64, userID int64, message string, selfid string, promptstr string, promptkeyboard []string) error { + //TODO: 用userid作为了echo,在ws收到回调信息的时候,加入到全局撤回数组,AddMessageID,实现反向ws连接时候的撤回 + if server.IsSelfIDExists(selfid) { + // 创建消息结构体 + msg := map[string]interface{}{ + "action": "send_group_msg", + "params": map[string]interface{}{ + "group_id": groupID, + "user_id": userID, + "message": message, + }, + "echo": userID, + } + + // 发送消息 + return server.SendMessageBySelfID(selfid, msg) + } + var baseURL string + if len(config.GetHttpPaths()) > 0 { + baseURL, _ = GetBaseURLByUserID(selfid) + } else { + // 获取基础URL + baseURL = config.GetHttpPath() // 假设config.getHttpPath()返回基础URL + } + + // 构建完整的URL + baseURL = baseURL + "/send_group_msg" + + // 获取PathToken并检查其是否为空 + pathToken := config.GetPathToken() + // 使用net/url包构建URL + u, err := url.Parse(baseURL) + if err != nil { + panic("URL parsing failed: " + err.Error()) + } + + // 添加access_token参数 + query := u.Query() + if pathToken != "" { + query.Set("access_token", pathToken) + } + u.RawQuery = query.Encode() + + if config.GetSensitiveModeType() == 1 { + message = acnode.CheckWordOUT(message) + } + + // 是否不显示Emoji + if config.GetNoEmoji(promptstr) == 2 { + message = RemoveEmojis(message) + } + + //精细化替换 每个yml配置文件都可以具有一个非全局的文本替换规则 + message = ReplaceTextOut(message, promptstr) + + // 去除末尾的换行符 不去除会导致不好看 + message = removeTrailingCRLFs(message) + + // 繁体转换简体 安全策略 防止用户诱导ai发繁体绕过替换规则 + message, err = ConvertTraditionalToSimplified(message) + if err != nil { + fmtf.Printf("繁体转换简体失败:%v", err) + } + var mdContent string + // 这里把message构造成一个cq,md码 + if config.GetMemoryListMD() == 2 { + //构建Markdown内容,对promptkeyboard的内容进行URL编码 + var sb strings.Builder + // 添加初始消息 + sb.WriteString(message) + sb.WriteString("\r") + lastIndex := len(promptkeyboard) - 1 // 获取最后一个索引 + // 遍历promptkeyboard数组,为每个元素生成一个标签 + for i, cmd := range promptkeyboard { + // 对每个命令进行URL编码 + encodedCmd := url.QueryEscape(cmd) + // 构建并添加qqbot-cmd-input标签 + if i == lastIndex { + // 如果是最后一个元素,则不添加 \r + sb.WriteString(fmt.Sprintf("", encodedCmd, encodedCmd)) + } else { + sb.WriteString(fmt.Sprintf("\r", encodedCmd, encodedCmd)) + } + } + mdContent = sb.String() + } else { + mdContent = message + } + + fmt.Println(mdContent) + var promptKeyboardMd structs.PromptKeyboardMarkdown + if config.GetMemoryListMD() == 1 { + // 构建Buttons + buttons := []structs.Button{} + // 添加promptkeyboard的按钮,每个按钮一行 + for i, label := range promptkeyboard { + buttons = append(buttons, structs.Button{ + ID: fmt.Sprintf("%d", i+1), + RenderData: structs.RenderData{ + Label: label, + VisitedLabel: "已载入", + Style: 1, + }, + Action: structs.Action{ + Type: 2, + Permission: structs.Permission{ + Type: 2, + SpecifyRoleIDs: []string{"1", "2", "3"}, + }, + Data: label, + UnsupportTips: "请升级新版手机QQ", + Enter: true, + Reply: true, + }, + }) + } + + // 构建完整的PromptKeyboardMarkdown对象 + var rows []structs.Row // 初始化一个空切片来存放行 + + // 遍历所有按钮,并每个按钮创建一行 + for _, button := range buttons { + row := structs.Row{ + Buttons: []structs.Button{button}, // 将当前按钮加入到新行中 + } + rows = append(rows, row) // 将新行添加到行切片中 + } + + // 构建 PromptKeyboardMarkdown 结构体 + promptKeyboardMd = structs.PromptKeyboardMarkdown{ + Markdown: structs.Markdown{ + Content: mdContent, + }, + Keyboard: structs.Keyboard{ + Content: structs.KeyboardContent{ + Rows: rows, // 使用动态创建的行数组 }, }, Content: "keyboard", @@ -791,7 +1207,434 @@ func SendGroupMessageMdPromptKeyboardV2(groupID int64, userID int64, message str return nil } -func SendPrivateMessage(UserID int64, message string, selfid string, promptstr string) error { +func SendGroupMessageMdPromptKeyboardV2SP(groupID string, userID string, message string, selfid string, promptstr string, promptkeyboard []string) error { + //TODO: 用userid作为了echo,在ws收到回调信息的时候,加入到全局撤回数组,AddMessageID,实现反向ws连接时候的撤回 + if server.IsSelfIDExists(selfid) { + // 创建消息结构体 + msg := map[string]interface{}{ + "action": "send_group_msg", + "params": map[string]interface{}{ + "group_id": groupID, + "user_id": userID, + "message": message, + }, + "echo": userID, + } + + // 发送消息 + return server.SendMessageBySelfID(selfid, msg) + } + var baseURL string + if len(config.GetHttpPaths()) > 0 { + baseURL, _ = GetBaseURLByUserID(selfid) + } else { + // 获取基础URL + baseURL = config.GetHttpPath() // 假设config.getHttpPath()返回基础URL + } + + // 构建完整的URL + baseURL = baseURL + "/send_group_msg" + + // 获取PathToken并检查其是否为空 + pathToken := config.GetPathToken() + // 使用net/url包构建URL + u, err := url.Parse(baseURL) + if err != nil { + panic("URL parsing failed: " + err.Error()) + } + + // 添加access_token参数 + query := u.Query() + if pathToken != "" { + query.Set("access_token", pathToken) + } + u.RawQuery = query.Encode() + + if config.GetSensitiveModeType() == 1 { + message = acnode.CheckWordOUT(message) + } + + // 是否不显示Emoji + if config.GetNoEmoji(promptstr) == 2 { + message = RemoveEmojis(message) + } + + //精细化替换 每个yml配置文件都可以具有一个非全局的文本替换规则 + message = ReplaceTextOut(message, promptstr) + + // 去除末尾的换行符 不去除会导致不好看 + message = removeTrailingCRLFs(message) + + // 繁体转换简体 安全策略 防止用户诱导ai发繁体绕过替换规则 + message, err = ConvertTraditionalToSimplified(message) + if err != nil { + fmtf.Printf("繁体转换简体失败:%v", err) + } + var mdContent string + // 这里把message构造成一个cq,md码 + if config.GetMemoryListMD() == 2 { + //构建Markdown内容,对promptkeyboard的内容进行URL编码 + var sb strings.Builder + // 添加初始消息 + sb.WriteString(message) + sb.WriteString("\r") + lastIndex := len(promptkeyboard) - 1 // 获取最后一个索引 + // 遍历promptkeyboard数组,为每个元素生成一个标签 + for i, cmd := range promptkeyboard { + // 对每个命令进行URL编码 + encodedCmd := url.QueryEscape(cmd) + // 构建并添加qqbot-cmd-input标签 + if i == lastIndex { + // 如果是最后一个元素,则不添加 \r + sb.WriteString(fmt.Sprintf("", encodedCmd, encodedCmd)) + } else { + sb.WriteString(fmt.Sprintf("\r", encodedCmd, encodedCmd)) + } + } + mdContent = sb.String() + } else { + mdContent = message + } + + fmt.Println(mdContent) + var promptKeyboardMd structs.PromptKeyboardMarkdown + if config.GetMemoryListMD() == 1 { + // 构建Buttons + buttons := []structs.Button{} + // 添加promptkeyboard的按钮,每个按钮一行 + for i, label := range promptkeyboard { + buttons = append(buttons, structs.Button{ + ID: fmt.Sprintf("%d", i+1), + RenderData: structs.RenderData{ + Label: label, + VisitedLabel: "已载入", + Style: 1, + }, + Action: structs.Action{ + Type: 2, + Permission: structs.Permission{ + Type: 2, + SpecifyRoleIDs: []string{"1", "2", "3"}, + }, + Data: label, + UnsupportTips: "请升级新版手机QQ", + Enter: true, + Reply: true, + }, + }) + } + + // 构建完整的PromptKeyboardMarkdown对象 + var rows []structs.Row // 初始化一个空切片来存放行 + + // 遍历所有按钮,并每个按钮创建一行 + for _, button := range buttons { + row := structs.Row{ + Buttons: []structs.Button{button}, // 将当前按钮加入到新行中 + } + rows = append(rows, row) // 将新行添加到行切片中 + } + + // 构建 PromptKeyboardMarkdown 结构体 + promptKeyboardMd = structs.PromptKeyboardMarkdown{ + Markdown: structs.Markdown{ + Content: mdContent, + }, + Keyboard: structs.Keyboard{ + Content: structs.KeyboardContent{ + Rows: rows, // 使用动态创建的行数组 + }, + }, + Content: "keyboard", + MsgID: "123", + Timestamp: fmt.Sprintf("%d", time.Now().Unix()), + MsgType: 2, + } + } else { + // 构建 PromptKeyboardMarkdown 结构体 + promptKeyboardMd = structs.PromptKeyboardMarkdown{ + Markdown: structs.Markdown{ + Content: mdContent, + }, + Content: "keyboard", + MsgID: "123", + Timestamp: fmt.Sprintf("%d", time.Now().Unix()), + MsgType: 2, + } + } + + // 序列化成JSON + mdContentBytes, err := json.Marshal(promptKeyboardMd) + if err != nil { + fmt.Printf("Error marshaling to JSON: %v", err) + return nil + } + + // 编码成Base64 + encoded := base64.StdEncoding.EncodeToString(mdContentBytes) + segmentContent := "[CQ:markdown,data=base64://" + encoded + "]" + + // 构造请求体 + requestBody, err := json.Marshal(map[string]interface{}{ + "group_id": groupID, + "user_id": userID, + "message": segmentContent, + }) + fmtf.Printf("发群信息请求:%v", string(requestBody)) + fmtf.Printf("实际发送信息:%v", message) + if err != nil { + return fmtf.Errorf("failed to marshal request body: %w", err) + } + + // 发送POST请求 + resp, err := http.Post(u.String(), "application/json", bytes.NewBuffer(requestBody)) + if err != nil { + return fmtf.Errorf("failed to send POST request: %w", err) + } + defer resp.Body.Close() + + // 检查响应状态 + if resp.StatusCode != http.StatusOK { + return fmtf.Errorf("received non-OK response status: %s", resp.Status) + } + + // 读取响应体 + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + // 解析响应体以获取message_id + var responseData ResponseDataSP + if err := json.Unmarshal(bodyBytes, &responseData); err != nil { + return fmt.Errorf("failed to unmarshal response data: %w", err) + } + messageID := responseData.Data.MessageID + + // 添加messageID到全局变量 + AddMessageIDSP(userID, messageID) + + // 输出响应体,这一步是可选的 + fmt.Println("Response Body:", string(bodyBytes)) + + return nil +} + +func SendPrivateMessage(UserID int64, message string, selfid string, promptstr string) error { + if server.IsSelfIDExists(selfid) { + // 创建消息结构体 + msg := map[string]interface{}{ + "action": "send_private_msg", + "params": map[string]interface{}{ + "user_id": UserID, + "message": message, + }, + "echo": UserID, + } + + // 发送消息 + return server.SendMessageBySelfID(selfid, msg) + } + var baseURL string + if len(config.GetHttpPaths()) > 0 { + baseURL, _ = GetBaseURLByUserID(selfid) + } else { + // 获取基础URL + baseURL = config.GetHttpPath() // 假设config.getHttpPath()返回基础URL + } + + // 构建完整的URL + baseURL = baseURL + "/send_private_msg" + + // 获取PathToken并检查其是否为空 + pathToken := config.GetPathToken() + // 使用net/url包构建URL + u, err := url.Parse(baseURL) + if err != nil { + panic("URL parsing failed: " + err.Error()) + } + + // 添加access_token参数 + query := u.Query() + if pathToken != "" { + query.Set("access_token", pathToken) + } + u.RawQuery = query.Encode() + + if config.GetSensitiveModeType() == 1 { + message = acnode.CheckWordOUT(message) + } + + // 是否不显示Emoji + if config.GetNoEmoji(promptstr) == 2 { + message = RemoveEmojis(message) + } + + //精细化替换 每个yml配置文件都可以具有一个非全局的文本替换规则 + message = ReplaceTextOut(message, promptstr) + + // 去除末尾的换行符 不去除会导致不好看 + message = removeTrailingCRLFs(message) + + // 繁体转换简体 安全策略 防止用户诱导ai发繁体绕过替换规则 + message, err = ConvertTraditionalToSimplified(message) + if err != nil { + fmtf.Printf("繁体转换简体失败:%v", err) + } + + // 构造请求体 + requestBody, err := json.Marshal(map[string]interface{}{ + "user_id": UserID, + "message": message, + }) + + if err != nil { + return fmtf.Errorf("failed to marshal request body: %w", err) + } + fmtf.Printf("实际发送信息:%v", message) + + // 发送POST请求 + resp, err := http.Post(u.String(), "application/json", bytes.NewBuffer(requestBody)) + if err != nil { + return fmtf.Errorf("failed to send POST request: %w", err) + } + defer resp.Body.Close() + + // 检查响应状态 + if resp.StatusCode != http.StatusOK { + return fmtf.Errorf("received non-OK response status: %s", resp.Status) + } + + // 读取响应体 + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + // 解析响应体以获取message_id + var responseData ResponseData + if err := json.Unmarshal(bodyBytes, &responseData); err != nil { + return fmt.Errorf("failed to unmarshal response data: %w", err) + } + messageID := responseData.Data.MessageID + + // 添加messageID到全局变量 + AddMessageID(UserID, messageID) + + // 输出响应体,这一步是可选的 + fmt.Println("Response Body:", string(bodyBytes)) + + return nil +} + +func SendPrivateMessageSP(UserID string, message string, selfid string, promptstr string) error { + if server.IsSelfIDExists(selfid) { + // 创建消息结构体 + msg := map[string]interface{}{ + "action": "send_private_msg", + "params": map[string]interface{}{ + "user_id": UserID, + "message": message, + }, + "echo": UserID, + } + + // 发送消息 + return server.SendMessageBySelfID(selfid, msg) + } + var baseURL string + if len(config.GetHttpPaths()) > 0 { + baseURL, _ = GetBaseURLByUserID(selfid) + } else { + // 获取基础URL + baseURL = config.GetHttpPath() // 假设config.getHttpPath()返回基础URL + } + + // 构建完整的URL + baseURL = baseURL + "/send_private_msg" + + // 获取PathToken并检查其是否为空 + pathToken := config.GetPathToken() + // 使用net/url包构建URL + u, err := url.Parse(baseURL) + if err != nil { + panic("URL parsing failed: " + err.Error()) + } + + // 添加access_token参数 + query := u.Query() + if pathToken != "" { + query.Set("access_token", pathToken) + } + u.RawQuery = query.Encode() + + if config.GetSensitiveModeType() == 1 { + message = acnode.CheckWordOUT(message) + } + + // 是否不显示Emoji + if config.GetNoEmoji(promptstr) == 2 { + message = RemoveEmojis(message) + } + + //精细化替换 每个yml配置文件都可以具有一个非全局的文本替换规则 + message = ReplaceTextOut(message, promptstr) + + // 去除末尾的换行符 不去除会导致不好看 + message = removeTrailingCRLFs(message) + + // 繁体转换简体 安全策略 防止用户诱导ai发繁体绕过替换规则 + message, err = ConvertTraditionalToSimplified(message) + if err != nil { + fmtf.Printf("繁体转换简体失败:%v", err) + } + + // 构造请求体 + requestBody, err := json.Marshal(map[string]interface{}{ + "user_id": UserID, + "message": message, + }) + + if err != nil { + return fmtf.Errorf("failed to marshal request body: %w", err) + } + fmtf.Printf("实际发送信息:%v", message) + + // 发送POST请求 + resp, err := http.Post(u.String(), "application/json", bytes.NewBuffer(requestBody)) + if err != nil { + return fmtf.Errorf("failed to send POST request: %w", err) + } + defer resp.Body.Close() + + // 检查响应状态 + if resp.StatusCode != http.StatusOK { + return fmtf.Errorf("received non-OK response status: %s", resp.Status) + } + + // 读取响应体 + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + // 解析响应体以获取message_id + var responseData ResponseDataSP + if err := json.Unmarshal(bodyBytes, &responseData); err != nil { + return fmt.Errorf("failed to unmarshal response data: %w", err) + } + messageID := responseData.Data.MessageID + + // 添加messageID到全局变量 + AddMessageIDSP(UserID, messageID) + + // 输出响应体,这一步是可选的 + fmt.Println("Response Body:", string(bodyBytes)) + + return nil +} + +func SendPrivateMessageRaw(UserID int64, message string, selfid string) error { if server.IsSelfIDExists(selfid) { // 创建消息结构体 msg := map[string]interface{}{ @@ -832,27 +1675,6 @@ func SendPrivateMessage(UserID int64, message string, selfid string, promptstr s } u.RawQuery = query.Encode() - if config.GetSensitiveModeType() == 1 { - message = acnode.CheckWordOUT(message) - } - - // 是否不显示Emoji - if config.GetNoEmoji(promptstr) == 2 { - message = RemoveEmojis(message) - } - - //精细化替换 每个yml配置文件都可以具有一个非全局的文本替换规则 - message = ReplaceTextOut(message, promptstr) - - // 去除末尾的换行符 不去除会导致不好看 - message = removeTrailingCRLFs(message) - - // 繁体转换简体 安全策略 防止用户诱导ai发繁体绕过替换规则 - message, err = ConvertTraditionalToSimplified(message) - if err != nil { - fmtf.Printf("繁体转换简体失败:%v", err) - } - // 构造请求体 requestBody, err := json.Marshal(map[string]interface{}{ "user_id": UserID, @@ -862,7 +1684,6 @@ func SendPrivateMessage(UserID int64, message string, selfid string, promptstr s if err != nil { return fmtf.Errorf("failed to marshal request body: %w", err) } - fmtf.Printf("实际发送信息:%v", message) // 发送POST请求 resp, err := http.Post(u.String(), "application/json", bytes.NewBuffer(requestBody)) @@ -898,21 +1719,7 @@ func SendPrivateMessage(UserID int64, message string, selfid string, promptstr s return nil } -func SendPrivateMessageRaw(UserID int64, message string, selfid string) error { - if server.IsSelfIDExists(selfid) { - // 创建消息结构体 - msg := map[string]interface{}{ - "action": "send_private_msg", - "params": map[string]interface{}{ - "user_id": UserID, - "message": message, - }, - "echo": UserID, - } - - // 发送消息 - return server.SendMessageBySelfID(selfid, msg) - } +func SendPrivateMessageSSE(UserID int64, message structs.InterfaceBody, promptstr string, selfid string) error { var baseURL string if len(config.GetHttpPaths()) > 0 { baseURL, _ = GetBaseURLByUserID(selfid) @@ -922,7 +1729,7 @@ func SendPrivateMessageRaw(UserID int64, message string, selfid string) error { } // 构建完整的URL - baseURL = baseURL + "/send_private_msg" + baseURL = baseURL + "/send_private_msg_sse" // 获取PathToken并检查其是否为空 pathToken := config.GetPathToken() @@ -939,12 +1746,51 @@ func SendPrivateMessageRaw(UserID int64, message string, selfid string) error { } u.RawQuery = query.Encode() - // 构造请求体 + // 调试用的 + if config.GetPrintHanming() { + fmtf.Printf("流式信息替换前:%v", message.Content) + } + + // 检查是否需要启用敏感词过滤 + if config.GetSensitiveModeType() == 1 && message.Content != "" { + message.Content = acnode.CheckWordOUT(message.Content) + } + + // 是否不显示Emoji + if config.GetNoEmoji(promptstr) == 2 { + message.Content = RemoveEmojis(message.Content) + } + + //精细化替换 每个yml配置文件都可以具有一个非全局的文本替换规则 + message.Content = ReplaceTextOut(message.Content, promptstr) + + // 调试用的 + if config.GetPrintHanming() { + fmtf.Printf("流式信息替换后:%v", message.Content) + } + + // 去除末尾的换行符 不去除会导致sse接口始终等待 + message.Content = removeTrailingCRLFs(message.Content) + + // 繁体转换简体 安全策略 防止用户诱导ai发繁体绕过替换规则 + message.Content, err = ConvertTraditionalToSimplified(message.Content) + if err != nil { + fmtf.Printf("繁体转换简体失败:%v", err) + } + + if message.Content == "" { + message.Content = " " + fmtf.Printf("过滤空SendPrivateMessageSSE,可能是llm api只发了换行符.") + return nil + } + + fmtf.Printf("实际发送信息:%v", message.Content) + + // 构造请求体,包括InterfaceBody requestBody, err := json.Marshal(map[string]interface{}{ "user_id": UserID, "message": message, }) - if err != nil { return fmtf.Errorf("failed to marshal request body: %w", err) } @@ -983,7 +1829,7 @@ func SendPrivateMessageRaw(UserID int64, message string, selfid string) error { return nil } -func SendPrivateMessageSSE(UserID int64, message structs.InterfaceBody, promptstr string, selfid string) error { +func SendPrivateMessageSSESP(UserID string, message structs.InterfaceBody, promptstr string, selfid string) error { var baseURL string if len(config.GetHttpPaths()) > 0 { baseURL, _ = GetBaseURLByUserID(selfid) @@ -1059,6 +1905,16 @@ func SendPrivateMessageSSE(UserID int64, message structs.InterfaceBody, promptst return fmtf.Errorf("failed to marshal request body: %w", err) } + // 输出响应体,这一步是可选的 + fmt.Println("Response Body:", string(requestBody)) + fmt.Println("Response Body:", string(requestBody)) + fmt.Println("Response Body:", string(requestBody)) + fmt.Println("Response Body:", string(requestBody)) + fmt.Println("Response Body:", string(requestBody)) + fmt.Println("Response Body:", string(requestBody)) + fmt.Println("Response path:", string(baseURL)) + fmt.Println("Response path:", string(baseURL)) + // 发送POST请求 resp, err := http.Post(u.String(), "application/json", bytes.NewBuffer(requestBody)) if err != nil { @@ -1077,15 +1933,17 @@ func SendPrivateMessageSSE(UserID int64, message structs.InterfaceBody, promptst return fmt.Errorf("failed to read response body: %w", err) } + fmt.Println("返回 返回返回返回返回返回Body:", string(bodyBytes)) + // 解析响应体以获取message_id - var responseData ResponseData + var responseData ResponseDataSP if err := json.Unmarshal(bodyBytes, &responseData); err != nil { return fmt.Errorf("failed to unmarshal response data: %w", err) } messageID := responseData.Data.MessageID // 添加messageID到全局变量 - AddMessageID(UserID, messageID) + AddMessageIDSP(UserID, messageID) // 输出响应体,这一步是可选的 fmt.Println("Response Body:", string(bodyBytes)) @@ -1428,6 +2286,63 @@ func SendSSEPrivateMessage(userID int64, content string, promptstr string, selfi } } +// SendSSEPrivateMessage 分割并发送消息的核心逻辑,直接遍历字符串 +func SendSSEPrivateMessageSP(userID string, content string, promptstr string, selfid string) { + punctuations := []rune{'。', '!', '?', ',', ',', '.', '!', '?', '~'} + splitProbability := config.GetSplitByPuntuations() + + var parts []string + var currentPart strings.Builder + + for _, runeValue := range content { + currentPart.WriteRune(runeValue) + if strings.ContainsRune(string(punctuations), runeValue) { + // 根据概率决定是否分割 + if rand.Intn(100) < splitProbability { + parts = append(parts, currentPart.String()) + currentPart.Reset() + } + } + } + // 添加最后一部分(如果有的话) + if currentPart.Len() > 0 { + parts = append(parts, currentPart.String()) + } + + // 根据parts长度处理状态 + for i, part := range parts { + state := 1 + if i == len(parts)-2 { // 倒数第二部分 + state = 11 + } else if i == len(parts)-1 { // 最后一部分 + state = 20 + } + + // 构造消息体并发送 + messageSSE := structs.InterfaceBody{ + Content: part, + State: state, + } + + if state == 20 { // 对最后一部分特殊处理 + MemoryLoadCommand := config.GetMemoryLoadCommand() + promptKeyboard := config.GetPromptkeyboard() + + if len(MemoryLoadCommand) > 0 { + selectedRestoreResponse := MemoryLoadCommand[rand.Intn(len(MemoryLoadCommand))] + if len(promptKeyboard) > 0 { + promptKeyboard[0] = selectedRestoreResponse + } + } + + messageSSE.PromptKeyboard = promptKeyboard + } + + // 发送SSE消息函数 + SendPrivateMessageSSESP(userID, messageSSE, promptstr, selfid) + } +} + // SendSSEPrivateMessageWithKeyboard 分割并发送消息的核心逻辑,直接遍历字符串 func SendSSEPrivateMessageWithKeyboard(userID int64, content string, keyboard []string, promptstr string, selfid string) { punctuations := []rune{'。', '!', '?', ',', ',', '.', '!', '?', '~'} @@ -1451,8 +2366,118 @@ func SendSSEPrivateMessageWithKeyboard(userID int64, content string, keyboard [] parts = append(parts, currentPart.String()) } - // 根据parts长度处理状态 - for i, part := range parts { + // 根据parts长度处理状态 + for i, part := range parts { + state := 1 + if i == len(parts)-2 { // 倒数第二部分 + state = 11 + } else if i == len(parts)-1 { // 最后一部分 + state = 20 + } + + // 构造消息体并发送 + messageSSE := structs.InterfaceBody{ + Content: part, + State: state, + } + + if state == 20 { // 对最后一部分特殊处理 + var promptKeyboard []string + if len(keyboard) == 0 { + RestoreResponses := config.GetRestoreCommand() + promptKeyboard = config.GetPromptkeyboard() + + if len(RestoreResponses) > 0 { + selectedRestoreResponse := RestoreResponses[rand.Intn(len(RestoreResponses))] + if len(promptKeyboard) > 0 { + promptKeyboard[0] = selectedRestoreResponse + } + } + } else { + promptKeyboard = keyboard + } + + messageSSE.PromptKeyboard = promptKeyboard + } + + // 发送SSE消息函数 + SendPrivateMessageSSE(userID, messageSSE, promptstr, selfid) + } +} + +// SendSSEPrivateMessageWithKeyboard 分割并发送消息的核心逻辑,直接遍历字符串 +func SendSSEPrivateMessageWithKeyboardSP(userID string, content string, keyboard []string, promptstr string, selfid string) { + punctuations := []rune{'。', '!', '?', ',', ',', '.', '!', '?', '~'} + splitProbability := config.GetSplitByPuntuations() + + var parts []string + var currentPart strings.Builder + + for _, runeValue := range content { + currentPart.WriteRune(runeValue) + if strings.ContainsRune(string(punctuations), runeValue) { + // 根据概率决定是否分割 + if rand.Intn(100) < splitProbability { + parts = append(parts, currentPart.String()) + currentPart.Reset() + } + } + } + // 添加最后一部分(如果有的话) + if currentPart.Len() > 0 { + parts = append(parts, currentPart.String()) + } + + // 根据parts长度处理状态 + for i, part := range parts { + state := 1 + if i == len(parts)-2 { // 倒数第二部分 + state = 11 + } else if i == len(parts)-1 { // 最后一部分 + state = 20 + } + + // 构造消息体并发送 + messageSSE := structs.InterfaceBody{ + Content: part, + State: state, + } + + if state == 20 { // 对最后一部分特殊处理 + var promptKeyboard []string + if len(keyboard) == 0 { + RestoreResponses := config.GetRestoreCommand() + promptKeyboard = config.GetPromptkeyboard() + + if len(RestoreResponses) > 0 { + selectedRestoreResponse := RestoreResponses[rand.Intn(len(RestoreResponses))] + if len(promptKeyboard) > 0 { + promptKeyboard[0] = selectedRestoreResponse + } + } + } else { + promptKeyboard = keyboard + } + + messageSSE.PromptKeyboard = promptKeyboard + } + + // 发送SSE消息函数 + SendPrivateMessageSSESP(userID, messageSSE, promptstr, selfid) + } +} + +// SendSSEPrivateMessageByline 分割并发送消息的核心逻辑,直接遍历字符串 +func SendSSEPrivateMessageByLine(userID int64, content string, keyboard []string, promptstr string, selfid string) { + // 直接使用 strings.Split 按行分割字符串 + parts := strings.Split(content, "\n") + + // 根据parts长度处理状态 + for i, part := range parts { + if part == "" { + continue // 跳过空行 + } + state := 1 if i == len(parts)-2 { // 倒数第二部分 state = 11 @@ -1462,7 +2487,7 @@ func SendSSEPrivateMessageWithKeyboard(userID int64, content string, keyboard [] // 构造消息体并发送 messageSSE := structs.InterfaceBody{ - Content: part, + Content: part + "\n", State: state, } @@ -1491,7 +2516,7 @@ func SendSSEPrivateMessageWithKeyboard(userID int64, content string, keyboard [] } // SendSSEPrivateMessageByline 分割并发送消息的核心逻辑,直接遍历字符串 -func SendSSEPrivateMessageByLine(userID int64, content string, keyboard []string, promptstr string, selfid string) { +func SendSSEPrivateMessageByLineSP(userID string, content string, keyboard []string, promptstr string, selfid string) { // 直接使用 strings.Split 按行分割字符串 parts := strings.Split(content, "\n") @@ -1534,7 +2559,7 @@ func SendSSEPrivateMessageByLine(userID int64, content string, keyboard []string } // 发送SSE消息函数 - SendPrivateMessageSSE(userID, messageSSE, promptstr, selfid) + SendPrivateMessageSSESP(userID, messageSSE, promptstr, selfid) } } @@ -1604,6 +2629,72 @@ func SendSSEPrivateSafeMessage(userID int64, saveresponse string, promptstr stri SendPrivateMessageSSE(userID, messageSSE, promptstr, selfid) } +// SendSSEPrivateSafeMessage 分割并发送安全消息的核心逻辑,直接遍历字符串 +func SendSSEPrivateSafeMessageSP(userID string, saveresponse string, promptstr string, selfid string) { + // 将字符串转换为rune切片,以正确处理多字节字符 + runes := []rune(saveresponse) + + // 计算每部分应该包含的rune数量 + partLength := len(runes) / 3 + + // 初始化用于存储分割结果的切片 + parts := make([]string, 3) + + // 按字符分割字符串 + for i := 0; i < 3; i++ { + if i < 2 { // 前两部分 + start := i * partLength + end := start + partLength + parts[i] = string(runes[start:end]) + } else { // 最后一部分,包含所有剩余的字符 + start := i * partLength + parts[i] = string(runes[start:]) + } + } + // 开头 + messageSSE := structs.InterfaceBody{ + Content: parts[0], + State: 1, + } + + SendPrivateMessageSSESP(userID, messageSSE, promptstr, selfid) + + // 中间 + messageSSE = structs.InterfaceBody{ + Content: parts[1], + State: 11, + } + SendPrivateMessageSSESP(userID, messageSSE, promptstr, selfid) + + // 从配置中获取恢复响应数组 + RestoreResponses := config.GetRestoreCommand() + + var selectedRestoreResponse string + // 如果RestoreResponses至少有一个成员,则随机选择一个 + if len(RestoreResponses) > 0 { + selectedRestoreResponse = RestoreResponses[rand.Intn(len(RestoreResponses))] + } + + // 从配置中获取promptkeyboard + promptkeyboard := config.GetPromptkeyboard() + + // 确保promptkeyboard至少有一个成员 + if len(promptkeyboard) > 0 { + // 使用随机选中的RestoreResponse替换promptkeyboard的第一个成员 + promptkeyboard[0] = selectedRestoreResponse + } + + // 创建InterfaceBody结构体实例 + messageSSE = structs.InterfaceBody{ + Content: parts[2], // 假设空格字符串是期望的内容 + State: 20, // 假设的状态码 + PromptKeyboard: promptkeyboard, // 使用更新后的promptkeyboard + } + + // 发送SSE私人消息 + SendPrivateMessageSSESP(userID, messageSSE, promptstr, selfid) +} + // SendSSEPrivateRestoreMessage 分割并发送重置消息的核心逻辑,直接遍历字符串 func SendSSEPrivateRestoreMessage(userID int64, RestoreResponse string, promptstr string, selfid string) { // 将字符串转换为rune切片,以正确处理多字节字符 @@ -1656,6 +2747,58 @@ func SendSSEPrivateRestoreMessage(userID int64, RestoreResponse string, promptst SendPrivateMessageSSE(userID, messageSSE, promptstr, selfid) } +// SendSSEPrivateRestoreMessage 分割并发送重置消息的核心逻辑,直接遍历字符串 +func SendSSEPrivateRestoreMessageSP(userID string, RestoreResponse string, promptstr string, selfid string) { + // 将字符串转换为rune切片,以正确处理多字节字符 + runes := []rune(RestoreResponse) + + // 计算每部分应该包含的rune数量 + partLength := len(runes) / 3 + + // 初始化用于存储分割结果的切片 + parts := make([]string, 3) + + // 按字符分割字符串 + for i := 0; i < 3; i++ { + if i < 2 { // 前两部分 + start := i * partLength + end := start + partLength + parts[i] = string(runes[start:end]) + } else { // 最后一部分,包含所有剩余的字符 + start := i * partLength + parts[i] = string(runes[start:]) + } + } + + // 开头 + messageSSE := structs.InterfaceBody{ + Content: parts[0], + State: 1, + } + + SendPrivateMessageSSESP(userID, messageSSE, promptstr, selfid) + + //中间 + messageSSE = structs.InterfaceBody{ + Content: parts[1], + State: 11, + } + SendPrivateMessageSSESP(userID, messageSSE, promptstr, selfid) + + // 从配置中获取promptkeyboard + promptkeyboard := config.GetPromptkeyboard() + + // 创建InterfaceBody结构体实例 + messageSSE = structs.InterfaceBody{ + Content: parts[2], // 假设空格字符串是期望的内容 + State: 20, // 假设的状态码 + PromptKeyboard: promptkeyboard, // 使用更新后的promptkeyboard + } + + // 发送SSE私人消息 + SendPrivateMessageSSESP(userID, messageSSE, promptstr, selfid) +} + // LanguageIntercept 检查文本语言,如果不在允许列表中,则返回 true 并发送消息 func LanguageIntercept(text string, message structs.OnebotGroupMessage, selfid string, promptstr string) bool { hintWords := config.GetGroupHintWords() @@ -1693,6 +2836,43 @@ func LanguageIntercept(text string, message structs.OnebotGroupMessage, selfid s return true // 拦截 } +// LanguageIntercept 检查文本语言,如果不在允许列表中,则返回 true 并发送消息 +func LanguageInterceptSP(text string, message structs.OnebotGroupMessageS, selfid string, promptstr string) bool { + hintWords := config.GetGroupHintWords() + // 遍历所有触发词,将其从文本中剔除 + for _, word := range hintWords { + text = strings.Replace(text, word, "", -1) + } + info := whatlanggo.Detect(text) + lang := whatlanggo.LangToString(info.Lang) + fmtf.Printf("LanguageIntercept:%v\n", lang) + + allowedLanguages := config.GetAllowedLanguages() + for _, allowed := range allowedLanguages { + if strings.Contains(allowed, lang) { + return false // 语言允许,不拦截 + } + } + + // 语言不允许,进行拦截 + responseMessage := config.GetLanguagesResponseMessages() + friendlyName := FriendlyLanguageNameCN(info.Lang) + responseMessage = strings.Replace(responseMessage, "**", friendlyName, -1) + + // 发送响应消息 + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if !config.GetUsePrivateSSE() { + SendPrivateMessageSP(message.UserID, responseMessage, selfid, promptstr) + } else { + SendSSEPrivateMessageSP(message.UserID, responseMessage, promptstr, selfid) + } + } else { + SendGroupMessageSP(message.GroupID, message.UserID, responseMessage, selfid, promptstr) + } + + return true // 拦截 +} + // FriendlyLanguageNameCN 将语言代码映射为中文名称 func FriendlyLanguageNameCN(lang whatlanggo.Lang) string { langMapCN := map[whatlanggo.Lang]string{ @@ -1752,6 +2932,29 @@ func LengthIntercept(text string, message structs.OnebotGroupMessage, selfid str return false // 长度符合要求,不拦截 } +// LengthIntercept 检查文本长度,如果超过最大长度,则返回 true 并发送消息 +func LengthInterceptSP(text string, message structs.OnebotGroupMessageS, selfid string, promptstr string) bool { + maxLen := config.GetQuestionMaxLenth() + if len(text) > maxLen { + // 长度超出限制,获取并发送响应消息 + responseMessage := config.GetQmlResponseMessages() + + // 根据消息类型发送响应 + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if !config.GetUsePrivateSSE() { + SendPrivateMessageSP(message.UserID, responseMessage, selfid, promptstr) + } else { + SendSSEPrivateMessageSP(message.UserID, responseMessage, promptstr, selfid) + } + } else { + SendGroupMessageSP(message.GroupID, message.UserID, responseMessage, selfid, promptstr) + } + + return true // 拦截 + } + return false // 长度符合要求,不拦截 +} + // AddMessageID 为指定user_id添加新的消息ID func AddMessageID(userID int64, messageID int64) { muUserIDMessageIDs.Lock() @@ -1768,6 +2971,22 @@ func AddMessageID(userID int64, messageID int64) { UserIDMessageIDs[userID] = append(UserIDMessageIDs[userID], messageInfo) } +// AddMessageID 为指定user_id添加新的消息ID +func AddMessageIDSP(userID string, messageID string) { + muUserIDMessageIDs.Lock() + defer muUserIDMessageIDs.Unlock() + + // 消息ID的有效期是120秒 + expiration := time.Now().Add(120 * time.Second) + messageInfo := MessageIDInfoSP{MessageID: messageID, Expires: expiration} + + // 清理已过期的消息ID + cleanExpiredMessageIDsSP(userID) + + // 添加新的消息ID + UserIDMessageIDsSP[userID] = append(UserIDMessageIDsSP[userID], messageInfo) +} + // cleanExpiredMessageIDs 清理指定user_id的已过期消息ID func cleanExpiredMessageIDs(userID int64) { validMessageIDs := []MessageIDInfo{} @@ -1779,6 +2998,17 @@ func cleanExpiredMessageIDs(userID int64) { UserIDMessageIDs[userID] = validMessageIDs } +// cleanExpiredMessageIDs 清理指定user_id的已过期消息ID +func cleanExpiredMessageIDsSP(userID string) { + validMessageIDs := []MessageIDInfoSP{} + for _, messageInfo := range UserIDMessageIDsSP[userID] { + if messageInfo.Expires.After(time.Now()) { + validMessageIDs = append(validMessageIDs, messageInfo) + } + } + UserIDMessageIDsSP[userID] = validMessageIDs +} + // GetLatestValidMessageID 获取指定user_id当前有效的最新消息ID func GetLatestValidMessageID(userID int64) (int64, bool) { muUserIDMessageIDs.RLock() @@ -1795,6 +3025,22 @@ func GetLatestValidMessageID(userID int64) (int64, bool) { return 0, false } +// GetLatestValidMessageID 获取指定user_id当前有效的最新消息ID +func GetLatestValidMessageIDSP(userID string) (string, bool) { + muUserIDMessageIDsSP.RLock() + defer muUserIDMessageIDsSP.RUnlock() + + // 确保已过期的消息ID被清理 + cleanExpiredMessageIDsSP(userID) + + // 获取最新的消息ID + if len(UserIDMessageIDsSP[userID]) > 0 { + latestMessageInfo := UserIDMessageIDsSP[userID][len(UserIDMessageIDsSP[userID])-1] + return latestMessageInfo.MessageID, true + } + return "", false +} + // sendDeleteRequest 发送删除消息的请求,并输出响应内容 func sendDeleteRequest(url string, requestBody []byte) error { // 发送POST请求 @@ -1885,6 +3131,69 @@ func DeleteLatestMessage(messageType string, id int64, userid int64, selfid stri return sendDeleteRequest(u.String(), requestBodyBytes) } +func DeleteLatestMessageSP(messageType string, id string, userid string, selfid string) error { + var baseURL string + if len(config.GetHttpPaths()) > 0 { + baseURL, _ = GetBaseURLByUserID(selfid) + } else { + // 获取基础URL + baseURL = config.GetHttpPath() // 假设config.getHttpPath()返回基础URL + } + + // 构建完整的URL + baseURL = baseURL + "/delete_msg" + + // 获取PathToken并检查其是否为空 + pathToken := config.GetPathToken() + // 使用net/url包构建URL + u, err := url.Parse(baseURL) + if err != nil { + panic("URL parsing failed: " + err.Error()) + } + + // 添加access_token参数 + query := u.Query() + if pathToken != "" { + query.Set("access_token", pathToken) + } + u.RawQuery = query.Encode() + + // 获取最新的有效消息ID + messageID, valid := GetLatestValidMessageIDSP(userid) + if !valid { + return fmt.Errorf("no valid message ID found for user/group/guild ID: %d", id) + } + + // 构造请求体 + requestBody := make(map[string]interface{}) + requestBody["message_id"] = messageID + + // 根据type填充相应的ID字段 + switch messageType { + case "group_private": + requestBody["user_id"] = id + case "group": + requestBody["group_id"] = id + case "guild": + requestBody["channel_id"] = id + case "guild_private": + requestBody["guild_id"] = id + case "interaction": + requestBody["group_id"] = id + default: + return fmt.Errorf("unsupported message type: %s", messageType) + } + + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + return fmt.Errorf("failed to marshal request body: %w", err) + } + fmtf.Printf("发送撤回请求:%v", string(requestBodyBytes)) + + // 发送删除消息请求 + return sendDeleteRequest(u.String(), requestBodyBytes) +} + // MakeAlternating ensures that roles alternate between "user" and "assistant". func MakeAlternating(messages []structs.MessageContent) []structs.MessageContent { if len(messages) < 2 {