🤖 自动格式化代码 [skip ci]

This commit is contained in:
github-actions[bot]
2025-06-16 14:10:49 +00:00
parent f6215cd560
commit ee005456ea
16 changed files with 461 additions and 383 deletions

View File

@@ -668,7 +668,9 @@ class ExampleComprehensivePlugin(BasePlugin):
"name": ConfigField(type=str, default="example_plugin", description="插件名称", required=True), "name": ConfigField(type=str, default="example_plugin", description="插件名称", required=True),
"version": ConfigField(type=str, default="2.0.0", description="插件版本号"), "version": ConfigField(type=str, default="2.0.0", description="插件版本号"),
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"), "enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
"description": ConfigField(type=str, default="综合示例插件,展示新插件系统的完整功能", description="插件描述", required=True) "description": ConfigField(
type=str, default="综合示例插件,展示新插件系统的完整功能", description="插件描述", required=True
),
}, },
"components": { "components": {
"enable_greeting": ConfigField(type=bool, default=True, description="是否启用'智能问候'Action"), "enable_greeting": ConfigField(type=bool, default=True, description="是否启用'智能问候'Action"),
@@ -677,47 +679,53 @@ class ExampleComprehensivePlugin(BasePlugin):
"enable_send": ConfigField(type=bool, default=True, description="是否启用'/send'命令"), "enable_send": ConfigField(type=bool, default=True, description="是否启用'/send'命令"),
"enable_echo": ConfigField(type=bool, default=True, description="是否启用'/echo'命令"), "enable_echo": ConfigField(type=bool, default=True, description="是否启用'/echo'命令"),
"enable_info": ConfigField(type=bool, default=True, description="是否启用'/info'命令"), "enable_info": ConfigField(type=bool, default=True, description="是否启用'/info'命令"),
"enable_dice": ConfigField(type=bool, default=True, description="是否启用'!dice'命令") "enable_dice": ConfigField(type=bool, default=True, description="是否启用'!dice'命令"),
}, },
"greeting": { "greeting": {
"template": ConfigField(type=str, default="你好,{username}欢迎使用MaiBot综合插件系统", description="问候消息模板"), "template": ConfigField(
type=str, default="你好,{username}欢迎使用MaiBot综合插件系统", description="问候消息模板"
),
"enable_emoji": ConfigField(type=bool, default=True, description="问候时是否附带表情"), "enable_emoji": ConfigField(type=bool, default=True, description="问候时是否附带表情"),
"enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成个性化问候语") "enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成个性化问候语"),
}, },
"helpful": { "helpful": {
"enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成帮助内容"), "enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成帮助内容"),
"enable_emoji": ConfigField(type=bool, default=True, description="提供帮助时是否附带表情"), "enable_emoji": ConfigField(type=bool, default=True, description="提供帮助时是否附带表情"),
"random_activation_probability": ConfigField(type=float, default=0.15, description="Normal模式下随机触发帮助的概率") "random_activation_probability": ConfigField(
type=float, default=0.15, description="Normal模式下随机触发帮助的概率"
),
}, },
"help": { "help": {
"show_extended_help": ConfigField(type=bool, default=True, description="是否显示扩展帮助信息"), "show_extended_help": ConfigField(type=bool, default=True, description="是否显示扩展帮助信息"),
"include_action_info": ConfigField(type=bool, default=True, description="帮助信息中是否包含Action的信息"), "include_action_info": ConfigField(type=bool, default=True, description="帮助信息中是否包含Action的信息"),
"include_config_info": ConfigField(type=bool, default=True, description="帮助信息中是否包含配置相关信息"), "include_config_info": ConfigField(type=bool, default=True, description="帮助信息中是否包含配置相关信息"),
"enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成帮助摘要"), "enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成帮助摘要"),
"enable_emoji": ConfigField(type=bool, default=True, description="帮助信息中是否使用表情符号") "enable_emoji": ConfigField(type=bool, default=True, description="帮助信息中是否使用表情符号"),
}, },
"send": { "send": {
"max_message_length": ConfigField(type=int, default=500, description="发送消息的最大长度限制"), "max_message_length": ConfigField(type=int, default=500, description="发送消息的最大长度限制"),
"enable_length_check": ConfigField(type=bool, default=True, description="是否启用消息长度检查"), "enable_length_check": ConfigField(type=bool, default=True, description="是否启用消息长度检查"),
"default_platform": ConfigField(type=str, default="qq", description="默认发送平台") "default_platform": ConfigField(type=str, default="qq", description="默认发送平台"),
}, },
"echo": { "echo": {
"max_length": ConfigField(type=int, default=200, description="回声消息的最大长度"), "max_length": ConfigField(type=int, default=200, description="回声消息的最大长度"),
"enable_formatting": ConfigField(type=bool, default=True, description="是否为回声消息添加'🔊 回声: '前缀") "enable_formatting": ConfigField(type=bool, default=True, description="是否为回声消息添加'🔊 回声: '前缀"),
}, },
"dice": { "dice": {
"enable_dice": ConfigField(type=bool, default=True, description="是否启用骰子功能"), "enable_dice": ConfigField(type=bool, default=True, description="是否启用骰子功能"),
"max_dice_count": ConfigField(type=int, default=10, description="一次最多可以掷的骰子数量") "max_dice_count": ConfigField(type=int, default=10, description="一次最多可以掷的骰子数量"),
}, },
"info": { "info": {
"show_detailed_info": ConfigField(type=bool, default=True, description="是否显示详细信息"), "show_detailed_info": ConfigField(type=bool, default=True, description="是否显示详细信息"),
"include_stream_info": ConfigField(type=bool, default=True, description="是否包含聊天流信息"), "include_stream_info": ConfigField(type=bool, default=True, description="是否包含聊天流信息"),
"max_content_preview": ConfigField(type=int, default=100, description="消息内容预览的最大长度") "max_content_preview": ConfigField(type=int, default=100, description="消息内容预览的最大长度"),
}, },
"logging": { "logging": {
"level": ConfigField(type=str, default="INFO", description="日志级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]), "level": ConfigField(
"prefix": ConfigField(type=str, default="[ExampleComprehensive]", description="日志前缀") type=str, default="INFO", description="日志级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]
} ),
"prefix": ConfigField(type=str, default="[ExampleComprehensive]", description="日志前缀"),
},
} }
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:

View File

@@ -1183,4 +1183,3 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -1,62 +1,59 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk, colorchooser, messagebox, filedialog from tkinter import ttk, messagebox, filedialog
import json import json
from pathlib import Path from pathlib import Path
import threading import threading
import queue
import time
import toml import toml
from datetime import datetime from datetime import datetime
import bisect
from collections import defaultdict from collections import defaultdict
import os import os
class LogIndex: class LogIndex:
"""日志索引,用于快速检索和过滤""" """日志索引,用于快速检索和过滤"""
def __init__(self): def __init__(self):
self.entries = [] # 所有日志条目 self.entries = [] # 所有日志条目
self.module_index = defaultdict(list) # 按模块索引 self.module_index = defaultdict(list) # 按模块索引
self.level_index = defaultdict(list) # 按级别索引 self.level_index = defaultdict(list) # 按级别索引
self.filtered_indices = [] # 当前过滤结果的索引 self.filtered_indices = [] # 当前过滤结果的索引
self.total_entries = 0 self.total_entries = 0
def add_entry(self, index, entry): def add_entry(self, index, entry):
"""添加日志条目到索引""" """添加日志条目到索引"""
if index >= len(self.entries): if index >= len(self.entries):
self.entries.extend([None] * (index - len(self.entries) + 1)) self.entries.extend([None] * (index - len(self.entries) + 1))
self.entries[index] = entry self.entries[index] = entry
self.total_entries = max(self.total_entries, index + 1) self.total_entries = max(self.total_entries, index + 1)
# 更新各种索引 # 更新各种索引
logger_name = entry.get("logger_name", "") logger_name = entry.get("logger_name", "")
level = entry.get("level", "") level = entry.get("level", "")
self.module_index[logger_name].append(index) self.module_index[logger_name].append(index)
self.level_index[level].append(index) self.level_index[level].append(index)
def filter_entries(self, modules=None, level=None, search_text=None): def filter_entries(self, modules=None, level=None, search_text=None):
"""根据条件过滤日志条目""" """根据条件过滤日志条目"""
if not modules and not level and not search_text: if not modules and not level and not search_text:
self.filtered_indices = list(range(self.total_entries)) self.filtered_indices = list(range(self.total_entries))
return self.filtered_indices return self.filtered_indices
candidate_indices = set(range(self.total_entries)) candidate_indices = set(range(self.total_entries))
# 模块过滤 # 模块过滤
if modules and "全部" not in modules: if modules and "全部" not in modules:
module_indices = set() module_indices = set()
for module in modules: for module in modules:
module_indices.update(self.module_index.get(module, [])) module_indices.update(self.module_index.get(module, []))
candidate_indices &= module_indices candidate_indices &= module_indices
# 级别过滤 # 级别过滤
if level and level != "全部": if level and level != "全部":
level_indices = set(self.level_index.get(level, [])) level_indices = set(self.level_index.get(level, []))
candidate_indices &= level_indices candidate_indices &= level_indices
# 文本搜索过滤 # 文本搜索过滤
if search_text: if search_text:
search_text = search_text.lower() search_text = search_text.lower()
@@ -68,14 +65,14 @@ class LogIndex:
if search_text in text_content: if search_text in text_content:
text_indices.add(i) text_indices.add(i)
candidate_indices &= text_indices candidate_indices &= text_indices
self.filtered_indices = sorted(list(candidate_indices)) self.filtered_indices = sorted(list(candidate_indices))
return self.filtered_indices return self.filtered_indices
def get_filtered_count(self): def get_filtered_count(self):
"""获取过滤后的条目数量""" """获取过滤后的条目数量"""
return len(self.filtered_indices) return len(self.filtered_indices)
def get_entry_at_filtered_position(self, position): def get_entry_at_filtered_position(self, position):
"""获取过滤结果中指定位置的条目""" """获取过滤结果中指定位置的条目"""
if 0 <= position < len(self.filtered_indices): if 0 <= position < len(self.filtered_indices):
@@ -242,16 +239,16 @@ class LogFormatter:
class VirtualLogDisplay: class VirtualLogDisplay:
"""虚拟滚动日志显示组件""" """虚拟滚动日志显示组件"""
def __init__(self, parent, formatter): def __init__(self, parent, formatter):
self.parent = parent self.parent = parent
self.formatter = formatter self.formatter = formatter
self.line_height = 20 # 每行高度(像素) self.line_height = 20 # 每行高度(像素)
self.visible_lines = 30 # 可见行数 self.visible_lines = 30 # 可见行数
# 创建主框架 # 创建主框架
self.main_frame = ttk.Frame(parent) self.main_frame = ttk.Frame(parent)
# 创建文本框和滚动条 # 创建文本框和滚动条
self.scrollbar = ttk.Scrollbar(self.main_frame) self.scrollbar = ttk.Scrollbar(self.main_frame)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
@@ -264,24 +261,24 @@ class VirtualLogDisplay:
foreground="#ffffff", foreground="#ffffff",
insertbackground="#ffffff", insertbackground="#ffffff",
selectbackground="#404040", selectbackground="#404040",
font=("Consolas", 10) font=("Consolas", 10),
) )
self.text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar.config(command=self.text_widget.yview) self.scrollbar.config(command=self.text_widget.yview)
# 配置文本标签样式 # 配置文本标签样式
self.configure_text_tags() self.configure_text_tags()
# 数据源 # 数据源
self.log_index = None self.log_index = None
self.current_page = 0 self.current_page = 0
self.page_size = 500 # 每页显示条数 self.page_size = 500 # 每页显示条数
self.max_display_lines = 2000 # 最大显示行数 self.max_display_lines = 2000 # 最大显示行数
def pack(self, **kwargs): def pack(self, **kwargs):
"""包装pack方法""" """包装pack方法"""
self.main_frame.pack(**kwargs) self.main_frame.pack(**kwargs)
def configure_text_tags(self): def configure_text_tags(self):
"""配置文本标签样式""" """配置文本标签样式"""
# 基础标签 # 基础标签
@@ -289,78 +286,84 @@ class VirtualLogDisplay:
self.text_widget.tag_configure("level", foreground="#808080") self.text_widget.tag_configure("level", foreground="#808080")
self.text_widget.tag_configure("module", foreground="#808080") self.text_widget.tag_configure("module", foreground="#808080")
self.text_widget.tag_configure("message", foreground="#ffffff") self.text_widget.tag_configure("message", foreground="#ffffff")
# 日志级别颜色标签 # 日志级别颜色标签
for level, color in self.formatter.level_colors.items(): for level, color in self.formatter.level_colors.items():
self.text_widget.tag_configure(f"level_{level}", foreground=color) self.text_widget.tag_configure(f"level_{level}", foreground=color)
# 模块颜色标签 # 模块颜色标签
for module, color in self.formatter.module_colors.items(): for module, color in self.formatter.module_colors.items():
self.text_widget.tag_configure(f"module_{module}", foreground=color) self.text_widget.tag_configure(f"module_{module}", foreground=color)
def set_log_index(self, log_index): def set_log_index(self, log_index):
"""设置日志索引数据源""" """设置日志索引数据源"""
self.log_index = log_index self.log_index = log_index
self.current_page = 0 self.current_page = 0
self.refresh_display() self.refresh_display()
def refresh_display(self): def refresh_display(self):
"""刷新显示""" """刷新显示"""
if not self.log_index: if not self.log_index:
self.text_widget.delete(1.0, tk.END) self.text_widget.delete(1.0, tk.END)
return return
# 清空显示 # 清空显示
self.text_widget.delete(1.0, tk.END) self.text_widget.delete(1.0, tk.END)
# 批量加载和显示日志 # 批量加载和显示日志
total_count = self.log_index.get_filtered_count() total_count = self.log_index.get_filtered_count()
if total_count == 0: if total_count == 0:
self.text_widget.insert(tk.END, "没有符合条件的日志记录\n") self.text_widget.insert(tk.END, "没有符合条件的日志记录\n")
return return
# 计算显示范围 # 计算显示范围
start_index = 0 start_index = 0
end_index = min(total_count, self.max_display_lines) end_index = min(total_count, self.max_display_lines)
# 批量处理和显示 # 批量处理和显示
batch_size = 100 batch_size = 100
for batch_start in range(start_index, end_index, batch_size): for batch_start in range(start_index, end_index, batch_size):
batch_end = min(batch_start + batch_size, end_index) batch_end = min(batch_start + batch_size, end_index)
self.display_batch(batch_start, batch_end) self.display_batch(batch_start, batch_end)
# 让UI有机会响应 # 让UI有机会响应
self.parent.update_idletasks() self.parent.update_idletasks()
# 滚动到底部(如果需要) # 滚动到底部(如果需要)
self.text_widget.see(tk.END) self.text_widget.see(tk.END)
def display_batch(self, start_index, end_index): def display_batch(self, start_index, end_index):
"""批量显示日志条目""" """批量显示日志条目"""
batch_text = [] batch_text = []
batch_tags = [] batch_tags = []
for i in range(start_index, end_index): for i in range(start_index, end_index):
log_entry = self.log_index.get_entry_at_filtered_position(i) log_entry = self.log_index.get_entry_at_filtered_position(i)
if log_entry: if log_entry:
parts, tags = self.formatter.format_log_entry(log_entry) parts, tags = self.formatter.format_log_entry(log_entry)
# 合并部分为单行文本 # 合并部分为单行文本
line_text = " ".join(parts) + "\n" line_text = " ".join(parts) + "\n"
batch_text.append(line_text) batch_text.append(line_text)
# 记录标签信息(简化处理) # 记录标签信息(简化处理)
if tags and self.formatter.enable_level_colors: if tags and self.formatter.enable_level_colors:
level = log_entry.get("level", "info") level = log_entry.get("level", "info")
batch_tags.append(("line", len("".join(batch_text)) - len(line_text), batch_tags.append(
len("".join(batch_text)) - 1, f"level_{level}")) (
"line",
len("".join(batch_text)) - len(line_text),
len("".join(batch_text)) - 1,
f"level_{level}",
)
)
# 一次性插入所有文本 # 一次性插入所有文本
if batch_text: if batch_text:
start_pos = self.text_widget.index(tk.END) start_pos = self.text_widget.index(tk.END)
all_text = "".join(batch_text) all_text = "".join(batch_text)
self.text_widget.insert(tk.END, all_text) self.text_widget.insert(tk.END, all_text)
# 应用标签(可选,为了性能可以考虑简化) # 应用标签(可选,为了性能可以考虑简化)
for tag_info in batch_tags: for tag_info in batch_tags:
try: try:
@@ -372,35 +375,35 @@ class VirtualLogDisplay:
class AsyncLogLoader: class AsyncLogLoader:
"""异步日志加载器""" """异步日志加载器"""
def __init__(self, callback): def __init__(self, callback):
self.callback = callback self.callback = callback
self.loading = False self.loading = False
self.should_stop = False self.should_stop = False
def load_file_async(self, file_path, progress_callback=None): def load_file_async(self, file_path, progress_callback=None):
"""异步加载日志文件""" """异步加载日志文件"""
if self.loading: if self.loading:
return return
self.loading = True self.loading = True
self.should_stop = False self.should_stop = False
def load_worker(): def load_worker():
try: try:
log_index = LogIndex() log_index = LogIndex()
if not os.path.exists(file_path): if not os.path.exists(file_path):
self.callback(log_index, "文件不存在") self.callback(log_index, "文件不存在")
return return
file_size = os.path.getsize(file_path) file_size = os.path.getsize(file_path)
processed_size = 0 processed_size = 0
with open(file_path, "r", encoding="utf-8") as f: with open(file_path, "r", encoding="utf-8") as f:
line_count = 0 line_count = 0
batch_size = 1000 # 批量处理 batch_size = 1000 # 批量处理
while not self.should_stop: while not self.should_stop:
lines = [] lines = []
for _ in range(batch_size): for _ in range(batch_size):
@@ -408,11 +411,11 @@ class AsyncLogLoader:
if not line: if not line:
break break
lines.append(line) lines.append(line)
processed_size += len(line.encode('utf-8')) processed_size += len(line.encode("utf-8"))
if not lines: if not lines:
break break
# 处理这批数据 # 处理这批数据
for line in lines: for line in lines:
try: try:
@@ -421,24 +424,24 @@ class AsyncLogLoader:
line_count += 1 line_count += 1
except json.JSONDecodeError: except json.JSONDecodeError:
continue continue
# 更新进度 # 更新进度
if progress_callback: if progress_callback:
progress = min(100, (processed_size / file_size) * 100) progress = min(100, (processed_size / file_size) * 100)
progress_callback(progress, line_count) progress_callback(progress, line_count)
if not self.should_stop: if not self.should_stop:
self.callback(log_index, None) self.callback(log_index, None)
except Exception as e: except Exception as e:
self.callback(None, str(e)) self.callback(None, str(e))
finally: finally:
self.loading = False self.loading = False
thread = threading.Thread(target=load_worker) thread = threading.Thread(target=load_worker)
thread.daemon = True thread.daemon = True
thread.start() thread.start()
def stop_loading(self): def stop_loading(self):
"""停止加载""" """停止加载"""
self.should_stop = True self.should_stop = True
@@ -462,7 +465,7 @@ class LogViewer:
# 初始化异步加载器 # 初始化异步加载器
self.async_loader = AsyncLogLoader(self.on_file_loaded) self.async_loader = AsyncLogLoader(self.on_file_loaded)
# 初始化日志索引 # 初始化日志索引
self.log_index = LogIndex() self.log_index = LogIndex()
@@ -472,7 +475,7 @@ class LogViewer:
# 创建控制面板 # 创建控制面板
self.create_control_panel() self.create_control_panel()
# 创建虚拟滚动日志显示区域 # 创建虚拟滚动日志显示区域
self.log_display = VirtualLogDisplay(self.main_frame, self.formatter) self.log_display = VirtualLogDisplay(self.main_frame, self.formatter)
self.log_display.pack(fill=tk.BOTH, expand=True) self.log_display.pack(fill=tk.BOTH, expand=True)
@@ -503,9 +506,9 @@ class LogViewer:
self.default_config = { self.default_config = {
"log": {"date_style": "m-d H:i:s", "log_level_style": "lite", "color_text": "full"}, "log": {"date_style": "m-d H:i:s", "log_level_style": "lite", "color_text": "full"},
} }
self.log_config = self.default_config["log"].copy() self.log_config = self.default_config["log"].copy()
config_path = Path("config/bot_config.toml") config_path = Path("config/bot_config.toml")
try: try:
if config_path.exists(): if config_path.exists():
@@ -576,20 +579,20 @@ class LogViewer:
def on_file_loaded(self, log_index, error): def on_file_loaded(self, log_index, error):
"""文件加载完成回调""" """文件加载完成回调"""
self.progress_bar.pack_forget() self.progress_bar.pack_forget()
if error: if error:
self.status_var.set(f"加载失败: {error}") self.status_var.set(f"加载失败: {error}")
messagebox.showerror("错误", f"加载日志文件失败: {error}") messagebox.showerror("错误", f"加载日志文件失败: {error}")
return return
self.log_index = log_index self.log_index = log_index
self.status_var.set(f"已加载 {log_index.total_entries} 条日志") self.status_var.set(f"已加载 {log_index.total_entries} 条日志")
# 更新模块列表 # 更新模块列表
self.modules = set(log_index.module_index.keys()) self.modules = set(log_index.module_index.keys())
module_values = ["全部"] + sorted(list(self.modules)) module_values = ["全部"] + sorted(list(self.modules))
self.module_combo["values"] = module_values self.module_combo["values"] = module_values
# 应用过滤并显示 # 应用过滤并显示
self.filter_logs() self.filter_logs()
@@ -607,22 +610,19 @@ class LogViewer:
if not self.current_log_file.exists(): if not self.current_log_file.exists():
self.status_var.set("文件不存在") self.status_var.set("文件不存在")
return return
# 显示进度条 # 显示进度条
self.progress_bar.pack(side=tk.LEFT, padx=5, pady=2, before=self.status_label) self.progress_bar.pack(side=tk.LEFT, padx=5, pady=2, before=self.status_label)
self.progress_var.set(0) self.progress_var.set(0)
self.status_var.set("正在加载...") self.status_var.set("正在加载...")
# 清空当前数据 # 清空当前数据
self.log_index = LogIndex() self.log_index = LogIndex()
self.modules.clear() self.modules.clear()
self.selected_modules.clear() self.selected_modules.clear()
# 开始异步加载 # 开始异步加载
self.async_loader.load_file_async( self.async_loader.load_file_async(str(self.current_log_file), self.on_loading_progress)
str(self.current_log_file),
self.on_loading_progress
)
def on_module_selected(self, event=None): def on_module_selected(self, event=None):
"""模块选择事件""" """模块选择事件"""
@@ -637,18 +637,18 @@ class LogViewer:
"""过滤日志""" """过滤日志"""
if not self.log_index: if not self.log_index:
return return
# 获取过滤条件 # 获取过滤条件
selected_modules = self.selected_modules if self.selected_modules else None selected_modules = self.selected_modules if self.selected_modules else None
level = self.level_var.get() if self.level_var.get() != "全部" else None level = self.level_var.get() if self.level_var.get() != "全部" else None
search_text = self.search_var.get().strip() if self.search_var.get().strip() else None search_text = self.search_var.get().strip() if self.search_var.get().strip() else None
# 应用过滤 # 应用过滤
self.log_index.filter_entries(selected_modules, level, search_text) self.log_index.filter_entries(selected_modules, level, search_text)
# 更新显示 # 更新显示
self.log_display.set_log_index(self.log_index) self.log_display.set_log_index(self.log_index)
# 更新状态 # 更新状态
filtered_count = self.log_index.get_filtered_count() filtered_count = self.log_index.get_filtered_count()
total_count = self.log_index.total_entries total_count = self.log_index.total_entries
@@ -683,4 +683,4 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -341,7 +341,12 @@ class HeartFChatting:
}, },
"observed_messages": "", "observed_messages": "",
}, },
"loop_action_info": {"action_taken": False, "reply_text": "", "command": "", "taken_time": time.time()}, "loop_action_info": {
"action_taken": False,
"reply_text": "",
"command": "",
"taken_time": time.time(),
},
} }
self._current_cycle_detail.set_loop_info(error_loop_info) self._current_cycle_detail.set_loop_info(error_loop_info)
self._current_cycle_detail.complete_cycle() self._current_cycle_detail.complete_cycle()
@@ -420,7 +425,12 @@ class HeartFChatting:
}, },
"observed_messages": "", "observed_messages": "",
}, },
"loop_action_info": {"action_taken": False, "reply_text": "", "command": "", "taken_time": time.time()}, "loop_action_info": {
"action_taken": False,
"reply_text": "",
"command": "",
"taken_time": time.time(),
},
} }
try: try:
self._current_cycle_detail.set_loop_info(error_loop_info) self._current_cycle_detail.set_loop_info(error_loop_info)

View File

@@ -1,4 +1,3 @@
from reportportal_client import current
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.heart_flow.observation.observation import Observation from src.chat.heart_flow.observation.observation import Observation
from src.llm_models.utils_model import LLMRequest from src.llm_models.utils_model import LLMRequest
@@ -18,7 +17,12 @@ from json_repair import repair_json
from src.person_info.person_info import get_person_info_manager from src.person_info.person_info import get_person_info_manager
import json import json
import asyncio import asyncio
from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat, get_raw_msg_by_timestamp_with_chat_inclusive, get_raw_msg_before_timestamp_with_chat, num_new_messages_since from src.chat.utils.chat_message_builder import (
get_raw_msg_by_timestamp_with_chat,
get_raw_msg_by_timestamp_with_chat_inclusive,
get_raw_msg_before_timestamp_with_chat,
num_new_messages_since,
)
import os import os
import pickle import pickle
@@ -106,18 +110,18 @@ class RelationshipProcessor(BaseProcessor):
self.info_fetched_cache: Dict[ self.info_fetched_cache: Dict[
str, Dict[str, any] str, Dict[str, any]
] = {} # {person_id: {"info": str, "ttl": int, "start_time": float}} ] = {} # {person_id: {"info": str, "ttl": int, "start_time": float}}
# 新的消息段缓存结构: # 新的消息段缓存结构:
# {person_id: [{"start_time": float, "end_time": float, "last_msg_time": float, "message_count": int}, ...]} # {person_id: [{"start_time": float, "end_time": float, "last_msg_time": float, "message_count": int}, ...]}
self.person_engaged_cache: Dict[str, List[Dict[str, any]]] = {} self.person_engaged_cache: Dict[str, List[Dict[str, any]]] = {}
# 持久化存储文件路径 # 持久化存储文件路径
self.cache_file_path = os.path.join("data", f"relationship_cache_{self.subheartflow_id}.pkl") self.cache_file_path = os.path.join("data", f"relationship_cache_{self.subheartflow_id}.pkl")
# 最后处理的消息时间,避免重复处理相同消息 # 最后处理的消息时间,避免重复处理相同消息
current_time = time.time() current_time = time.time()
self.last_processed_message_time = current_time self.last_processed_message_time = current_time
# 最后清理时间,用于定期清理老消息段 # 最后清理时间,用于定期清理老消息段
self.last_cleanup_time = 0.0 self.last_cleanup_time = 0.0
@@ -135,7 +139,7 @@ class RelationshipProcessor(BaseProcessor):
name = get_chat_manager().get_stream_name(self.subheartflow_id) name = get_chat_manager().get_stream_name(self.subheartflow_id)
self.log_prefix = f"[{name}] " self.log_prefix = f"[{name}] "
# 加载持久化的缓存 # 加载持久化的缓存
self._load_cache() self._load_cache()
@@ -143,19 +147,21 @@ class RelationshipProcessor(BaseProcessor):
# 缓存管理模块 # 缓存管理模块
# 负责持久化存储、状态管理、缓存读写 # 负责持久化存储、状态管理、缓存读写
# ================================ # ================================
def _load_cache(self): def _load_cache(self):
"""从文件加载持久化的缓存""" """从文件加载持久化的缓存"""
if os.path.exists(self.cache_file_path): if os.path.exists(self.cache_file_path):
try: try:
with open(self.cache_file_path, 'rb') as f: with open(self.cache_file_path, "rb") as f:
cache_data = pickle.load(f) cache_data = pickle.load(f)
# 新格式:包含额外信息的缓存 # 新格式:包含额外信息的缓存
self.person_engaged_cache = cache_data.get('person_engaged_cache', {}) self.person_engaged_cache = cache_data.get("person_engaged_cache", {})
self.last_processed_message_time = cache_data.get('last_processed_message_time', 0.0) self.last_processed_message_time = cache_data.get("last_processed_message_time", 0.0)
self.last_cleanup_time = cache_data.get('last_cleanup_time', 0.0) self.last_cleanup_time = cache_data.get("last_cleanup_time", 0.0)
logger.info(f"{self.log_prefix} 成功加载关系缓存,包含 {len(self.person_engaged_cache)} 个用户,最后处理时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_processed_message_time)) if self.last_processed_message_time > 0 else '未设置'}") logger.info(
f"{self.log_prefix} 成功加载关系缓存,包含 {len(self.person_engaged_cache)} 个用户,最后处理时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_processed_message_time)) if self.last_processed_message_time > 0 else '未设置'}"
)
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} 加载关系缓存失败: {e}") logger.error(f"{self.log_prefix} 加载关系缓存失败: {e}")
self.person_engaged_cache = {} self.person_engaged_cache = {}
@@ -168,63 +174,65 @@ class RelationshipProcessor(BaseProcessor):
try: try:
os.makedirs(os.path.dirname(self.cache_file_path), exist_ok=True) os.makedirs(os.path.dirname(self.cache_file_path), exist_ok=True)
cache_data = { cache_data = {
'person_engaged_cache': self.person_engaged_cache, "person_engaged_cache": self.person_engaged_cache,
'last_processed_message_time': self.last_processed_message_time, "last_processed_message_time": self.last_processed_message_time,
'last_cleanup_time': self.last_cleanup_time "last_cleanup_time": self.last_cleanup_time,
} }
with open(self.cache_file_path, 'wb') as f: with open(self.cache_file_path, "wb") as f:
pickle.dump(cache_data, f) pickle.dump(cache_data, f)
logger.debug(f"{self.log_prefix} 成功保存关系缓存") logger.debug(f"{self.log_prefix} 成功保存关系缓存")
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} 保存关系缓存失败: {e}") logger.error(f"{self.log_prefix} 保存关系缓存失败: {e}")
# ================================ # ================================
# 消息段管理模块 # 消息段管理模块
# 负责跟踪用户消息活动、管理消息段、清理过期数据 # 负责跟踪用户消息活动、管理消息段、清理过期数据
# ================================ # ================================
def _update_message_segments(self, person_id: str, message_time: float): def _update_message_segments(self, person_id: str, message_time: float):
"""更新用户的消息段 """更新用户的消息段
Args: Args:
person_id: 用户ID person_id: 用户ID
message_time: 消息时间戳 message_time: 消息时间戳
""" """
if person_id not in self.person_engaged_cache: if person_id not in self.person_engaged_cache:
self.person_engaged_cache[person_id] = [] self.person_engaged_cache[person_id] = []
segments = self.person_engaged_cache[person_id] segments = self.person_engaged_cache[person_id]
current_time = time.time() current_time = time.time()
# 获取该消息前5条消息的时间作为潜在的开始时间 # 获取该消息前5条消息的时间作为潜在的开始时间
before_messages = get_raw_msg_before_timestamp_with_chat(self.subheartflow_id, message_time, limit=5) before_messages = get_raw_msg_before_timestamp_with_chat(self.subheartflow_id, message_time, limit=5)
if before_messages: if before_messages:
# 由于get_raw_msg_before_timestamp_with_chat返回按时间升序排序的消息最后一个是最接近message_time的 # 由于get_raw_msg_before_timestamp_with_chat返回按时间升序排序的消息最后一个是最接近message_time的
# 我们需要第一个消息作为开始时间但应该确保至少包含5条消息或该用户之前的消息 # 我们需要第一个消息作为开始时间但应该确保至少包含5条消息或该用户之前的消息
potential_start_time = before_messages[0]['time'] potential_start_time = before_messages[0]["time"]
else: else:
# 如果没有前面的消息,就从当前消息开始 # 如果没有前面的消息,就从当前消息开始
potential_start_time = message_time potential_start_time = message_time
# 如果没有现有消息段,创建新的 # 如果没有现有消息段,创建新的
if not segments: if not segments:
new_segment = { new_segment = {
"start_time": potential_start_time, "start_time": potential_start_time,
"end_time": message_time, "end_time": message_time,
"last_msg_time": message_time, "last_msg_time": message_time,
"message_count": self._count_messages_in_timerange(potential_start_time, message_time) "message_count": self._count_messages_in_timerange(potential_start_time, message_time),
} }
segments.append(new_segment) segments.append(new_segment)
logger.info(f"{self.log_prefix} 为用户 {person_id} 创建新消息段: 时间范围 {time.strftime('%H:%M:%S', time.localtime(potential_start_time))} - {time.strftime('%H:%M:%S', time.localtime(message_time))}, 消息数: {new_segment['message_count']}") logger.info(
f"{self.log_prefix} 为用户 {person_id} 创建新消息段: 时间范围 {time.strftime('%H:%M:%S', time.localtime(potential_start_time))} - {time.strftime('%H:%M:%S', time.localtime(message_time))}, 消息数: {new_segment['message_count']}"
)
self._save_cache() self._save_cache()
return return
# 获取最后一个消息段 # 获取最后一个消息段
last_segment = segments[-1] last_segment = segments[-1]
# 计算从最后一条消息到当前消息之间的消息数量(不包含边界) # 计算从最后一条消息到当前消息之间的消息数量(不包含边界)
messages_between = self._count_messages_between(last_segment["last_msg_time"], message_time) messages_between = self._count_messages_between(last_segment["last_msg_time"], message_time)
if messages_between <= 10: if messages_between <= 10:
# 在10条消息内延伸当前消息段 # 在10条消息内延伸当前消息段
last_segment["end_time"] = message_time last_segment["end_time"] = message_time
@@ -242,82 +250,82 @@ class RelationshipProcessor(BaseProcessor):
) )
if after_messages and len(after_messages) >= 5: if after_messages and len(after_messages) >= 5:
# 如果有足够的后续消息使用第5条消息的时间作为结束时间 # 如果有足够的后续消息使用第5条消息的时间作为结束时间
last_segment["end_time"] = after_messages[4]['time'] last_segment["end_time"] = after_messages[4]["time"]
else: else:
# 如果没有足够的后续消息,保持原有的结束时间 # 如果没有足够的后续消息,保持原有的结束时间
pass pass
# 重新计算当前消息段的消息数量 # 重新计算当前消息段的消息数量
last_segment["message_count"] = self._count_messages_in_timerange( last_segment["message_count"] = self._count_messages_in_timerange(
last_segment["start_time"], last_segment["end_time"] last_segment["start_time"], last_segment["end_time"]
) )
# 创建新的消息段 # 创建新的消息段
new_segment = { new_segment = {
"start_time": potential_start_time, "start_time": potential_start_time,
"end_time": message_time, "end_time": message_time,
"last_msg_time": message_time, "last_msg_time": message_time,
"message_count": self._count_messages_in_timerange(potential_start_time, message_time) "message_count": self._count_messages_in_timerange(potential_start_time, message_time),
} }
segments.append(new_segment) segments.append(new_segment)
logger.info(f"{self.log_prefix} 为用户 {person_id} 创建新消息段超过10条消息间隔: {new_segment}") logger.info(f"{self.log_prefix} 为用户 {person_id} 创建新消息段超过10条消息间隔: {new_segment}")
self._save_cache() self._save_cache()
def _count_messages_in_timerange(self, start_time: float, end_time: float) -> int: def _count_messages_in_timerange(self, start_time: float, end_time: float) -> int:
"""计算指定时间范围内的消息数量(包含边界)""" """计算指定时间范围内的消息数量(包含边界)"""
messages = get_raw_msg_by_timestamp_with_chat_inclusive(self.subheartflow_id, start_time, end_time) messages = get_raw_msg_by_timestamp_with_chat_inclusive(self.subheartflow_id, start_time, end_time)
return len(messages) return len(messages)
def _count_messages_between(self, start_time: float, end_time: float) -> int: def _count_messages_between(self, start_time: float, end_time: float) -> int:
"""计算两个时间点之间的消息数量(不包含边界),用于间隔检查""" """计算两个时间点之间的消息数量(不包含边界),用于间隔检查"""
return num_new_messages_since(self.subheartflow_id, start_time, end_time) return num_new_messages_since(self.subheartflow_id, start_time, end_time)
def _get_total_message_count(self, person_id: str) -> int: def _get_total_message_count(self, person_id: str) -> int:
"""获取用户所有消息段的总消息数量""" """获取用户所有消息段的总消息数量"""
if person_id not in self.person_engaged_cache: if person_id not in self.person_engaged_cache:
return 0 return 0
total_count = 0 total_count = 0
for segment in self.person_engaged_cache[person_id]: for segment in self.person_engaged_cache[person_id]:
total_count += segment["message_count"] total_count += segment["message_count"]
return total_count return total_count
def _cleanup_old_segments(self) -> bool: def _cleanup_old_segments(self) -> bool:
"""清理老旧的消息段 """清理老旧的消息段
Returns: Returns:
bool: 是否执行了清理操作 bool: 是否执行了清理操作
""" """
if not SEGMENT_CLEANUP_CONFIG["enable_cleanup"]: if not SEGMENT_CLEANUP_CONFIG["enable_cleanup"]:
return False return False
current_time = time.time() current_time = time.time()
# 检查是否需要执行清理(基于时间间隔) # 检查是否需要执行清理(基于时间间隔)
cleanup_interval_seconds = SEGMENT_CLEANUP_CONFIG["cleanup_interval_hours"] * 3600 cleanup_interval_seconds = SEGMENT_CLEANUP_CONFIG["cleanup_interval_hours"] * 3600
if current_time - self.last_cleanup_time < cleanup_interval_seconds: if current_time - self.last_cleanup_time < cleanup_interval_seconds:
return False return False
logger.info(f"{self.log_prefix} 开始执行老消息段清理...") logger.info(f"{self.log_prefix} 开始执行老消息段清理...")
cleanup_stats = { cleanup_stats = {
"users_cleaned": 0, "users_cleaned": 0,
"segments_removed": 0, "segments_removed": 0,
"total_segments_before": 0, "total_segments_before": 0,
"total_segments_after": 0 "total_segments_after": 0,
} }
max_age_seconds = SEGMENT_CLEANUP_CONFIG["max_segment_age_days"] * 24 * 3600 max_age_seconds = SEGMENT_CLEANUP_CONFIG["max_segment_age_days"] * 24 * 3600
max_segments_per_user = SEGMENT_CLEANUP_CONFIG["max_segments_per_user"] max_segments_per_user = SEGMENT_CLEANUP_CONFIG["max_segments_per_user"]
users_to_remove = [] users_to_remove = []
for person_id, segments in self.person_engaged_cache.items(): for person_id, segments in self.person_engaged_cache.items():
cleanup_stats["total_segments_before"] += len(segments) cleanup_stats["total_segments_before"] += len(segments)
original_segment_count = len(segments) original_segment_count = len(segments)
# 1. 按时间清理:移除过期的消息段 # 1. 按时间清理:移除过期的消息段
segments_after_age_cleanup = [] segments_after_age_cleanup = []
for segment in segments: for segment in segments:
@@ -326,8 +334,10 @@ class RelationshipProcessor(BaseProcessor):
segments_after_age_cleanup.append(segment) segments_after_age_cleanup.append(segment)
else: else:
cleanup_stats["segments_removed"] += 1 cleanup_stats["segments_removed"] += 1
logger.debug(f"{self.log_prefix} 移除用户 {person_id} 的过期消息段: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['start_time']))} - {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['end_time']))}") logger.debug(
f"{self.log_prefix} 移除用户 {person_id} 的过期消息段: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['start_time']))} - {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['end_time']))}"
)
# 2. 按数量清理:如果消息段数量仍然过多,保留最新的 # 2. 按数量清理:如果消息段数量仍然过多,保留最新的
if len(segments_after_age_cleanup) > max_segments_per_user: if len(segments_after_age_cleanup) > max_segments_per_user:
# 按end_time排序保留最新的 # 按end_time排序保留最新的
@@ -335,10 +345,12 @@ class RelationshipProcessor(BaseProcessor):
segments_removed_count = len(segments_after_age_cleanup) - max_segments_per_user segments_removed_count = len(segments_after_age_cleanup) - max_segments_per_user
cleanup_stats["segments_removed"] += segments_removed_count cleanup_stats["segments_removed"] += segments_removed_count
segments_after_age_cleanup = segments_after_age_cleanup[:max_segments_per_user] segments_after_age_cleanup = segments_after_age_cleanup[:max_segments_per_user]
logger.debug(f"{self.log_prefix} 用户 {person_id} 消息段数量过多,移除 {segments_removed_count} 个最老的消息段") logger.debug(
f"{self.log_prefix} 用户 {person_id} 消息段数量过多,移除 {segments_removed_count} 个最老的消息段"
)
# 使用清理后的消息段 # 使用清理后的消息段
# 更新缓存 # 更新缓存
if len(segments_after_age_cleanup) == 0: if len(segments_after_age_cleanup) == 0:
# 如果没有剩余消息段,标记用户为待移除 # 如果没有剩余消息段,标记用户为待移除
@@ -346,34 +358,38 @@ class RelationshipProcessor(BaseProcessor):
else: else:
self.person_engaged_cache[person_id] = segments_after_age_cleanup self.person_engaged_cache[person_id] = segments_after_age_cleanup
cleanup_stats["total_segments_after"] += len(segments_after_age_cleanup) cleanup_stats["total_segments_after"] += len(segments_after_age_cleanup)
if original_segment_count != len(segments_after_age_cleanup): if original_segment_count != len(segments_after_age_cleanup):
cleanup_stats["users_cleaned"] += 1 cleanup_stats["users_cleaned"] += 1
# 移除没有消息段的用户 # 移除没有消息段的用户
for person_id in users_to_remove: for person_id in users_to_remove:
del self.person_engaged_cache[person_id] del self.person_engaged_cache[person_id]
logger.debug(f"{self.log_prefix} 移除用户 {person_id}:没有剩余消息段") logger.debug(f"{self.log_prefix} 移除用户 {person_id}:没有剩余消息段")
# 更新最后清理时间 # 更新最后清理时间
self.last_cleanup_time = current_time self.last_cleanup_time = current_time
# 保存缓存 # 保存缓存
if cleanup_stats["segments_removed"] > 0 or len(users_to_remove) > 0: if cleanup_stats["segments_removed"] > 0 or len(users_to_remove) > 0:
self._save_cache() self._save_cache()
logger.info(f"{self.log_prefix} 清理完成 - 影响用户: {cleanup_stats['users_cleaned']}, 移除消息段: {cleanup_stats['segments_removed']}, 移除用户: {len(users_to_remove)}") logger.info(
logger.info(f"{self.log_prefix} 消息段统计 - 清理前: {cleanup_stats['total_segments_before']}, 清理后: {cleanup_stats['total_segments_after']}") f"{self.log_prefix} 清理完成 - 影响用户: {cleanup_stats['users_cleaned']}, 移除消息段: {cleanup_stats['segments_removed']}, 移除用户: {len(users_to_remove)}"
)
logger.info(
f"{self.log_prefix} 消息段统计 - 清理前: {cleanup_stats['total_segments_before']}, 清理后: {cleanup_stats['total_segments_after']}"
)
else: else:
logger.debug(f"{self.log_prefix} 清理完成 - 无需清理任何内容") logger.debug(f"{self.log_prefix} 清理完成 - 无需清理任何内容")
return cleanup_stats["segments_removed"] > 0 or len(users_to_remove) > 0 return cleanup_stats["segments_removed"] > 0 or len(users_to_remove) > 0
def force_cleanup_user_segments(self, person_id: str) -> bool: def force_cleanup_user_segments(self, person_id: str) -> bool:
"""强制清理指定用户的所有消息段 """强制清理指定用户的所有消息段
Args: Args:
person_id: 用户ID person_id: 用户ID
Returns: Returns:
bool: 是否成功清理 bool: 是否成功清理
""" """
@@ -389,34 +405,42 @@ class RelationshipProcessor(BaseProcessor):
"""获取缓存状态信息,用于调试和监控""" """获取缓存状态信息,用于调试和监控"""
if not self.person_engaged_cache: if not self.person_engaged_cache:
return f"{self.log_prefix} 关系缓存为空" return f"{self.log_prefix} 关系缓存为空"
status_lines = [f"{self.log_prefix} 关系缓存状态:"] status_lines = [f"{self.log_prefix} 关系缓存状态:"]
status_lines.append(f"最后处理消息时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_processed_message_time)) if self.last_processed_message_time > 0 else '未设置'}") status_lines.append(
status_lines.append(f"最后清理时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_cleanup_time)) if self.last_cleanup_time > 0 else '执行'}") f"最后处理消息时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_processed_message_time)) if self.last_processed_message_time > 0 else '设置'}"
)
status_lines.append(
f"最后清理时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_cleanup_time)) if self.last_cleanup_time > 0 else '未执行'}"
)
status_lines.append(f"总用户数:{len(self.person_engaged_cache)}") status_lines.append(f"总用户数:{len(self.person_engaged_cache)}")
status_lines.append(f"清理配置:{'启用' if SEGMENT_CLEANUP_CONFIG['enable_cleanup'] else '禁用'} (最大保存{SEGMENT_CLEANUP_CONFIG['max_segment_age_days']}天, 每用户最多{SEGMENT_CLEANUP_CONFIG['max_segments_per_user']}段)") status_lines.append(
f"清理配置:{'启用' if SEGMENT_CLEANUP_CONFIG['enable_cleanup'] else '禁用'} (最大保存{SEGMENT_CLEANUP_CONFIG['max_segment_age_days']}天, 每用户最多{SEGMENT_CLEANUP_CONFIG['max_segments_per_user']}段)"
)
status_lines.append("") status_lines.append("")
for person_id, segments in self.person_engaged_cache.items(): for person_id, segments in self.person_engaged_cache.items():
total_count = self._get_total_message_count(person_id) total_count = self._get_total_message_count(person_id)
status_lines.append(f"用户 {person_id}:") status_lines.append(f"用户 {person_id}:")
status_lines.append(f" 总消息数:{total_count} ({total_count}/45)") status_lines.append(f" 总消息数:{total_count} ({total_count}/45)")
status_lines.append(f" 消息段数:{len(segments)}") status_lines.append(f" 消息段数:{len(segments)}")
for i, segment in enumerate(segments): for i, segment in enumerate(segments):
start_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['start_time'])) start_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(segment["start_time"]))
end_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['end_time'])) end_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(segment["end_time"]))
last_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['last_msg_time'])) last_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(segment["last_msg_time"]))
status_lines.append(f"{i+1}: {start_str} -> {end_str} (最后消息: {last_str}, 消息数: {segment['message_count']})") status_lines.append(
f"{i + 1}: {start_str} -> {end_str} (最后消息: {last_str}, 消息数: {segment['message_count']})"
)
status_lines.append("") status_lines.append("")
return "\n".join(status_lines) return "\n".join(status_lines)
# ================================ # ================================
# 主要处理流程 # 主要处理流程
# 统筹各模块协作、对外提供服务接口 # 统筹各模块协作、对外提供服务接口
# ================================ # ================================
async def process_info(self, observations: List[Observation] = None, *infos) -> List[InfoBase]: async def process_info(self, observations: List[Observation] = None, *infos) -> List[InfoBase]:
"""处理信息对象 """处理信息对象
@@ -446,7 +470,7 @@ class RelationshipProcessor(BaseProcessor):
""" """
# 0. 执行定期清理 # 0. 执行定期清理
self._cleanup_old_segments() self._cleanup_old_segments()
# 1. 从观察信息中提取所需数据 # 1. 从观察信息中提取所需数据
# 需要兼容私聊 # 需要兼容私聊
@@ -456,24 +480,35 @@ class RelationshipProcessor(BaseProcessor):
for observation in observations: for observation in observations:
if isinstance(observation, ChattingObservation): if isinstance(observation, ChattingObservation):
chat_observe_info = observation.get_observe_info() chat_observe_info = observation.get_observe_info()
# 从聊天观察中提取用户信息并更新消息段 # 从聊天观察中提取用户信息并更新消息段
# 获取最新的非bot消息来更新消息段 # 获取最新的非bot消息来更新消息段
latest_messages = get_raw_msg_by_timestamp_with_chat( latest_messages = get_raw_msg_by_timestamp_with_chat(
self.subheartflow_id, self.last_processed_message_time, current_time, limit=50 # 获取自上次处理后的消息 self.subheartflow_id,
self.last_processed_message_time,
current_time,
limit=50, # 获取自上次处理后的消息
) )
if latest_messages: if latest_messages:
# 处理所有新的非bot消息 # 处理所有新的非bot消息
for latest_msg in latest_messages: for latest_msg in latest_messages:
user_id = latest_msg.get('user_id') user_id = latest_msg.get("user_id")
platform = latest_msg.get('user_platform') or latest_msg.get('chat_info_platform') platform = latest_msg.get("user_platform") or latest_msg.get("chat_info_platform")
msg_time = latest_msg.get('time', 0) msg_time = latest_msg.get("time", 0)
if user_id and platform and user_id != global_config.bot.qq_account and msg_time > self.last_processed_message_time: if (
user_id
and platform
and user_id != global_config.bot.qq_account
and msg_time > self.last_processed_message_time
):
from src.person_info.person_info import PersonInfoManager from src.person_info.person_info import PersonInfoManager
person_id = PersonInfoManager.get_person_id(platform, user_id) person_id = PersonInfoManager.get_person_id(platform, user_id)
self._update_message_segments(person_id, msg_time) self._update_message_segments(person_id, msg_time)
logger.debug(f"{self.log_prefix} 更新用户 {person_id} 的消息段,消息时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(msg_time))}") logger.debug(
f"{self.log_prefix} 更新用户 {person_id} 的消息段,消息时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(msg_time))}"
)
self.last_processed_message_time = max(self.last_processed_message_time, msg_time) self.last_processed_message_time = max(self.last_processed_message_time, msg_time)
break break
@@ -496,9 +531,7 @@ class RelationshipProcessor(BaseProcessor):
for person_id in users_to_build_relationship: for person_id in users_to_build_relationship:
segments = self.person_engaged_cache[person_id] segments = self.person_engaged_cache[person_id]
# 异步执行关系构建 # 异步执行关系构建
asyncio.create_task( asyncio.create_task(self.update_impression_on_segments(person_id, self.subheartflow_id, segments))
self.update_impression_on_segments(person_id, self.subheartflow_id, segments)
)
# 移除已处理的用户缓存 # 移除已处理的用户缓存
del self.person_engaged_cache[person_id] del self.person_engaged_cache[person_id]
self._save_cache() self._save_cache()
@@ -659,11 +692,11 @@ class RelationshipProcessor(BaseProcessor):
# 关系构建模块 # 关系构建模块
# 负责触发关系构建、整合消息段、更新用户印象 # 负责触发关系构建、整合消息段、更新用户印象
# ================================ # ================================
async def update_impression_on_segments(self, person_id: str, chat_id: str, segments: List[Dict[str, any]]): async def update_impression_on_segments(self, person_id: str, chat_id: str, segments: List[Dict[str, any]]):
""" """
基于消息段更新用户印象 基于消息段更新用户印象
Args: Args:
person_id: 用户ID person_id: 用户ID
chat_id: 聊天ID chat_id: 聊天ID
@@ -672,17 +705,21 @@ class RelationshipProcessor(BaseProcessor):
logger.info(f"开始为 {person_id} 基于 {len(segments)} 个消息段更新印象") logger.info(f"开始为 {person_id} 基于 {len(segments)} 个消息段更新印象")
try: try:
processed_messages = [] processed_messages = []
for i, segment in enumerate(segments): for i, segment in enumerate(segments):
start_time = segment["start_time"] start_time = segment["start_time"]
end_time = segment["end_time"] end_time = segment["end_time"]
message_count = segment["message_count"] segment["message_count"]
start_date = time.strftime('%Y-%m-%d %H:%M', time.localtime(start_time)) start_date = time.strftime("%Y-%m-%d %H:%M", time.localtime(start_time))
# 获取该段的消息(包含边界) # 获取该段的消息(包含边界)
segment_messages = get_raw_msg_by_timestamp_with_chat_inclusive(self.subheartflow_id, start_time, end_time) segment_messages = get_raw_msg_by_timestamp_with_chat_inclusive(
logger.info(f"消息段 {i+1}: {start_date} - {time.strftime('%Y-%m-%d %H:%M', time.localtime(end_time))}, 消息数: {len(segment_messages)}") self.subheartflow_id, start_time, end_time
)
logger.info(
f"消息段 {i + 1}: {start_date} - {time.strftime('%Y-%m-%d %H:%M', time.localtime(end_time))}, 消息数: {len(segment_messages)}"
)
if segment_messages: if segment_messages:
# 如果不是第一个消息段,在消息列表前添加间隔标识 # 如果不是第一个消息段,在消息列表前添加间隔标识
if i > 0: if i > 0:
@@ -690,31 +727,29 @@ class RelationshipProcessor(BaseProcessor):
gap_message = { gap_message = {
"time": start_time - 0.1, # 稍微早于段开始时间 "time": start_time - 0.1, # 稍微早于段开始时间
"user_id": "system", "user_id": "system",
"user_platform": "system", "user_platform": "system",
"user_nickname": "系统", "user_nickname": "系统",
"user_cardname": "", "user_cardname": "",
"display_message": f"...(中间省略一些消息){start_date} 之后的消息如下...", "display_message": f"...(中间省略一些消息){start_date} 之后的消息如下...",
"is_action_record": True, "is_action_record": True,
"chat_info_platform": segment_messages[0].get("chat_info_platform", ""), "chat_info_platform": segment_messages[0].get("chat_info_platform", ""),
"chat_id": chat_id "chat_id": chat_id,
} }
processed_messages.append(gap_message) processed_messages.append(gap_message)
# 添加该段的所有消息 # 添加该段的所有消息
processed_messages.extend(segment_messages) processed_messages.extend(segment_messages)
if processed_messages: if processed_messages:
# 按时间排序所有消息(包括间隔标识) # 按时间排序所有消息(包括间隔标识)
processed_messages.sort(key=lambda x: x['time']) processed_messages.sort(key=lambda x: x["time"])
logger.info(f"{person_id} 获取到总共 {len(processed_messages)} 条消息(包含间隔标识)用于印象更新") logger.info(f"{person_id} 获取到总共 {len(processed_messages)} 条消息(包含间隔标识)用于印象更新")
relationship_manager = get_relationship_manager() relationship_manager = get_relationship_manager()
# 调用原有的更新方法 # 调用原有的更新方法
await relationship_manager.update_person_impression( await relationship_manager.update_person_impression(
person_id=person_id, person_id=person_id, timestamp=time.time(), bot_engaged_messages=processed_messages
timestamp=time.time(),
bot_engaged_messages=processed_messages
) )
else: else:
logger.info(f"没有找到 {person_id} 的消息段对应的消息,不更新印象") logger.info(f"没有找到 {person_id} 的消息段对应的消息,不更新印象")
@@ -727,7 +762,7 @@ class RelationshipProcessor(BaseProcessor):
# 信息调取模块 # 信息调取模块
# 负责实时分析对话需求、提取用户信息、管理信息缓存 # 负责实时分析对话需求、提取用户信息、管理信息缓存
# ================================ # ================================
async def _execute_instant_extraction_batch(self, instant_tasks: list): async def _execute_instant_extraction_batch(self, instant_tasks: list):
""" """
批量执行即时提取任务 批量执行即时提取任务
@@ -919,6 +954,4 @@ class RelationshipProcessor(BaseProcessor):
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
init_prompt() init_prompt()

View File

@@ -47,7 +47,9 @@ class HFCloopObservation:
action_reasoning = action_result.get("reasoning", "未提供理由") action_reasoning = action_result.get("reasoning", "未提供理由")
is_taken = cycle.loop_action_info.get("action_taken", False) is_taken = cycle.loop_action_info.get("action_taken", False)
action_taken_time = cycle.loop_action_info.get("taken_time", 0) action_taken_time = cycle.loop_action_info.get("taken_time", 0)
action_taken_time_str = datetime.fromtimestamp(action_taken_time).strftime("%H:%M:%S") if action_taken_time > 0 else "未知时间" action_taken_time_str = (
datetime.fromtimestamp(action_taken_time).strftime("%H:%M:%S") if action_taken_time > 0 else "未知时间"
)
# print(action_type) # print(action_type)
# print(action_reasoning) # print(action_reasoning)
# print(is_taken) # print(is_taken)

View File

@@ -2,7 +2,7 @@ import asyncio
import time import time
import traceback import traceback
from random import random from random import random
from typing import List, Optional, Dict, Any # 导入类型提示 from typing import List, Optional, Dict # 导入类型提示
import os import os
import pickle import pickle
from maim_message import UserInfo, Seg from maim_message import UserInfo, Seg
@@ -24,7 +24,12 @@ from src.chat.normal_chat.normal_chat_action_modifier import NormalChatActionMod
from src.chat.normal_chat.normal_chat_expressor import NormalChatExpressor from src.chat.normal_chat.normal_chat_expressor import NormalChatExpressor
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
from src.person_info.person_info import PersonInfoManager from src.person_info.person_info import PersonInfoManager
from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat, get_raw_msg_by_timestamp_with_chat_inclusive, get_raw_msg_before_timestamp_with_chat, num_new_messages_since from src.chat.utils.chat_message_builder import (
get_raw_msg_by_timestamp_with_chat,
get_raw_msg_by_timestamp_with_chat_inclusive,
get_raw_msg_before_timestamp_with_chat,
num_new_messages_since,
)
from src.person_info.relationship_manager import get_relationship_manager from src.person_info.relationship_manager import get_relationship_manager
willing_manager = get_willing_manager() willing_manager = get_willing_manager()
@@ -80,13 +85,13 @@ class NormalChat:
# 新的消息段缓存结构: # 新的消息段缓存结构:
# {person_id: [{"start_time": float, "end_time": float, "last_msg_time": float, "message_count": int}, ...]} # {person_id: [{"start_time": float, "end_time": float, "last_msg_time": float, "message_count": int}, ...]}
self.person_engaged_cache: Dict[str, List[Dict[str, any]]] = {} self.person_engaged_cache: Dict[str, List[Dict[str, any]]] = {}
# 持久化存储文件路径 # 持久化存储文件路径
self.cache_file_path = os.path.join("data", f"relationship_cache_{self.stream_id}.pkl") self.cache_file_path = os.path.join("data", f"relationship_cache_{self.stream_id}.pkl")
# 最后处理的消息时间,避免重复处理相同消息 # 最后处理的消息时间,避免重复处理相同消息
self.last_processed_message_time = 0.0 self.last_processed_message_time = 0.0
# 最后清理时间,用于定期清理老消息段 # 最后清理时间,用于定期清理老消息段
self.last_cleanup_time = 0.0 self.last_cleanup_time = 0.0
@@ -104,19 +109,21 @@ class NormalChat:
# 缓存管理模块 # 缓存管理模块
# 负责持久化存储、状态管理、缓存读写 # 负责持久化存储、状态管理、缓存读写
# ================================ # ================================
def _load_cache(self): def _load_cache(self):
"""从文件加载持久化的缓存""" """从文件加载持久化的缓存"""
if os.path.exists(self.cache_file_path): if os.path.exists(self.cache_file_path):
try: try:
with open(self.cache_file_path, 'rb') as f: with open(self.cache_file_path, "rb") as f:
cache_data = pickle.load(f) cache_data = pickle.load(f)
# 新格式:包含额外信息的缓存 # 新格式:包含额外信息的缓存
self.person_engaged_cache = cache_data.get('person_engaged_cache', {}) self.person_engaged_cache = cache_data.get("person_engaged_cache", {})
self.last_processed_message_time = cache_data.get('last_processed_message_time', 0.0) self.last_processed_message_time = cache_data.get("last_processed_message_time", 0.0)
self.last_cleanup_time = cache_data.get('last_cleanup_time', 0.0) self.last_cleanup_time = cache_data.get("last_cleanup_time", 0.0)
logger.info(f"[{self.stream_name}] 成功加载关系缓存,包含 {len(self.person_engaged_cache)} 个用户,最后处理时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_processed_message_time)) if self.last_processed_message_time > 0 else '未设置'}") logger.info(
f"[{self.stream_name}] 成功加载关系缓存,包含 {len(self.person_engaged_cache)} 个用户,最后处理时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_processed_message_time)) if self.last_processed_message_time > 0 else '未设置'}"
)
except Exception as e: except Exception as e:
logger.error(f"[{self.stream_name}] 加载关系缓存失败: {e}") logger.error(f"[{self.stream_name}] 加载关系缓存失败: {e}")
self.person_engaged_cache = {} self.person_engaged_cache = {}
@@ -129,63 +136,65 @@ class NormalChat:
try: try:
os.makedirs(os.path.dirname(self.cache_file_path), exist_ok=True) os.makedirs(os.path.dirname(self.cache_file_path), exist_ok=True)
cache_data = { cache_data = {
'person_engaged_cache': self.person_engaged_cache, "person_engaged_cache": self.person_engaged_cache,
'last_processed_message_time': self.last_processed_message_time, "last_processed_message_time": self.last_processed_message_time,
'last_cleanup_time': self.last_cleanup_time "last_cleanup_time": self.last_cleanup_time,
} }
with open(self.cache_file_path, 'wb') as f: with open(self.cache_file_path, "wb") as f:
pickle.dump(cache_data, f) pickle.dump(cache_data, f)
logger.debug(f"[{self.stream_name}] 成功保存关系缓存") logger.debug(f"[{self.stream_name}] 成功保存关系缓存")
except Exception as e: except Exception as e:
logger.error(f"[{self.stream_name}] 保存关系缓存失败: {e}") logger.error(f"[{self.stream_name}] 保存关系缓存失败: {e}")
# ================================ # ================================
# 消息段管理模块 # 消息段管理模块
# 负责跟踪用户消息活动、管理消息段、清理过期数据 # 负责跟踪用户消息活动、管理消息段、清理过期数据
# ================================ # ================================
def _update_message_segments(self, person_id: str, message_time: float): def _update_message_segments(self, person_id: str, message_time: float):
"""更新用户的消息段 """更新用户的消息段
Args: Args:
person_id: 用户ID person_id: 用户ID
message_time: 消息时间戳 message_time: 消息时间戳
""" """
if person_id not in self.person_engaged_cache: if person_id not in self.person_engaged_cache:
self.person_engaged_cache[person_id] = [] self.person_engaged_cache[person_id] = []
segments = self.person_engaged_cache[person_id] segments = self.person_engaged_cache[person_id]
current_time = time.time() current_time = time.time()
# 获取该消息前5条消息的时间作为潜在的开始时间 # 获取该消息前5条消息的时间作为潜在的开始时间
before_messages = get_raw_msg_before_timestamp_with_chat(self.stream_id, message_time, limit=5) before_messages = get_raw_msg_before_timestamp_with_chat(self.stream_id, message_time, limit=5)
if before_messages: if before_messages:
# 由于get_raw_msg_before_timestamp_with_chat返回按时间升序排序的消息最后一个是最接近message_time的 # 由于get_raw_msg_before_timestamp_with_chat返回按时间升序排序的消息最后一个是最接近message_time的
# 我们需要第一个消息作为开始时间但应该确保至少包含5条消息或该用户之前的消息 # 我们需要第一个消息作为开始时间但应该确保至少包含5条消息或该用户之前的消息
potential_start_time = before_messages[0]['time'] potential_start_time = before_messages[0]["time"]
else: else:
# 如果没有前面的消息,就从当前消息开始 # 如果没有前面的消息,就从当前消息开始
potential_start_time = message_time potential_start_time = message_time
# 如果没有现有消息段,创建新的 # 如果没有现有消息段,创建新的
if not segments: if not segments:
new_segment = { new_segment = {
"start_time": potential_start_time, "start_time": potential_start_time,
"end_time": message_time, "end_time": message_time,
"last_msg_time": message_time, "last_msg_time": message_time,
"message_count": self._count_messages_in_timerange(potential_start_time, message_time) "message_count": self._count_messages_in_timerange(potential_start_time, message_time),
} }
segments.append(new_segment) segments.append(new_segment)
logger.info(f"[{self.stream_name}] 为用户 {person_id} 创建新消息段: 时间范围 {time.strftime('%H:%M:%S', time.localtime(potential_start_time))} - {time.strftime('%H:%M:%S', time.localtime(message_time))}, 消息数: {new_segment['message_count']}") logger.info(
f"[{self.stream_name}] 为用户 {person_id} 创建新消息段: 时间范围 {time.strftime('%H:%M:%S', time.localtime(potential_start_time))} - {time.strftime('%H:%M:%S', time.localtime(message_time))}, 消息数: {new_segment['message_count']}"
)
self._save_cache() self._save_cache()
return return
# 获取最后一个消息段 # 获取最后一个消息段
last_segment = segments[-1] last_segment = segments[-1]
# 计算从最后一条消息到当前消息之间的消息数量(不包含边界) # 计算从最后一条消息到当前消息之间的消息数量(不包含边界)
messages_between = self._count_messages_between(last_segment["last_msg_time"], message_time) messages_between = self._count_messages_between(last_segment["last_msg_time"], message_time)
if messages_between <= 10: if messages_between <= 10:
# 在10条消息内延伸当前消息段 # 在10条消息内延伸当前消息段
last_segment["end_time"] = message_time last_segment["end_time"] = message_time
@@ -203,82 +212,82 @@ class NormalChat:
) )
if after_messages and len(after_messages) >= 5: if after_messages and len(after_messages) >= 5:
# 如果有足够的后续消息使用第5条消息的时间作为结束时间 # 如果有足够的后续消息使用第5条消息的时间作为结束时间
last_segment["end_time"] = after_messages[4]['time'] last_segment["end_time"] = after_messages[4]["time"]
else: else:
# 如果没有足够的后续消息,保持原有的结束时间 # 如果没有足够的后续消息,保持原有的结束时间
pass pass
# 重新计算当前消息段的消息数量 # 重新计算当前消息段的消息数量
last_segment["message_count"] = self._count_messages_in_timerange( last_segment["message_count"] = self._count_messages_in_timerange(
last_segment["start_time"], last_segment["end_time"] last_segment["start_time"], last_segment["end_time"]
) )
# 创建新的消息段 # 创建新的消息段
new_segment = { new_segment = {
"start_time": potential_start_time, "start_time": potential_start_time,
"end_time": message_time, "end_time": message_time,
"last_msg_time": message_time, "last_msg_time": message_time,
"message_count": self._count_messages_in_timerange(potential_start_time, message_time) "message_count": self._count_messages_in_timerange(potential_start_time, message_time),
} }
segments.append(new_segment) segments.append(new_segment)
logger.info(f"[{self.stream_name}] 为用户 {person_id} 创建新消息段超过10条消息间隔: {new_segment}") logger.info(f"[{self.stream_name}] 为用户 {person_id} 创建新消息段超过10条消息间隔: {new_segment}")
self._save_cache() self._save_cache()
def _count_messages_in_timerange(self, start_time: float, end_time: float) -> int: def _count_messages_in_timerange(self, start_time: float, end_time: float) -> int:
"""计算指定时间范围内的消息数量(包含边界)""" """计算指定时间范围内的消息数量(包含边界)"""
messages = get_raw_msg_by_timestamp_with_chat_inclusive(self.stream_id, start_time, end_time) messages = get_raw_msg_by_timestamp_with_chat_inclusive(self.stream_id, start_time, end_time)
return len(messages) return len(messages)
def _count_messages_between(self, start_time: float, end_time: float) -> int: def _count_messages_between(self, start_time: float, end_time: float) -> int:
"""计算两个时间点之间的消息数量(不包含边界),用于间隔检查""" """计算两个时间点之间的消息数量(不包含边界),用于间隔检查"""
return num_new_messages_since(self.stream_id, start_time, end_time) return num_new_messages_since(self.stream_id, start_time, end_time)
def _get_total_message_count(self, person_id: str) -> int: def _get_total_message_count(self, person_id: str) -> int:
"""获取用户所有消息段的总消息数量""" """获取用户所有消息段的总消息数量"""
if person_id not in self.person_engaged_cache: if person_id not in self.person_engaged_cache:
return 0 return 0
total_count = 0 total_count = 0
for segment in self.person_engaged_cache[person_id]: for segment in self.person_engaged_cache[person_id]:
total_count += segment["message_count"] total_count += segment["message_count"]
return total_count return total_count
def _cleanup_old_segments(self) -> bool: def _cleanup_old_segments(self) -> bool:
"""清理老旧的消息段 """清理老旧的消息段
Returns: Returns:
bool: 是否执行了清理操作 bool: 是否执行了清理操作
""" """
if not SEGMENT_CLEANUP_CONFIG["enable_cleanup"]: if not SEGMENT_CLEANUP_CONFIG["enable_cleanup"]:
return False return False
current_time = time.time() current_time = time.time()
# 检查是否需要执行清理(基于时间间隔) # 检查是否需要执行清理(基于时间间隔)
cleanup_interval_seconds = SEGMENT_CLEANUP_CONFIG["cleanup_interval_hours"] * 3600 cleanup_interval_seconds = SEGMENT_CLEANUP_CONFIG["cleanup_interval_hours"] * 3600
if current_time - self.last_cleanup_time < cleanup_interval_seconds: if current_time - self.last_cleanup_time < cleanup_interval_seconds:
return False return False
logger.info(f"[{self.stream_name}] 开始执行老消息段清理...") logger.info(f"[{self.stream_name}] 开始执行老消息段清理...")
cleanup_stats = { cleanup_stats = {
"users_cleaned": 0, "users_cleaned": 0,
"segments_removed": 0, "segments_removed": 0,
"total_segments_before": 0, "total_segments_before": 0,
"total_segments_after": 0 "total_segments_after": 0,
} }
max_age_seconds = SEGMENT_CLEANUP_CONFIG["max_segment_age_days"] * 24 * 3600 max_age_seconds = SEGMENT_CLEANUP_CONFIG["max_segment_age_days"] * 24 * 3600
max_segments_per_user = SEGMENT_CLEANUP_CONFIG["max_segments_per_user"] max_segments_per_user = SEGMENT_CLEANUP_CONFIG["max_segments_per_user"]
users_to_remove = [] users_to_remove = []
for person_id, segments in self.person_engaged_cache.items(): for person_id, segments in self.person_engaged_cache.items():
cleanup_stats["total_segments_before"] += len(segments) cleanup_stats["total_segments_before"] += len(segments)
original_segment_count = len(segments) original_segment_count = len(segments)
# 1. 按时间清理:移除过期的消息段 # 1. 按时间清理:移除过期的消息段
segments_after_age_cleanup = [] segments_after_age_cleanup = []
for segment in segments: for segment in segments:
@@ -287,8 +296,10 @@ class NormalChat:
segments_after_age_cleanup.append(segment) segments_after_age_cleanup.append(segment)
else: else:
cleanup_stats["segments_removed"] += 1 cleanup_stats["segments_removed"] += 1
logger.debug(f"[{self.stream_name}] 移除用户 {person_id} 的过期消息段: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['start_time']))} - {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['end_time']))}") logger.debug(
f"[{self.stream_name}] 移除用户 {person_id} 的过期消息段: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['start_time']))} - {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['end_time']))}"
)
# 2. 按数量清理:如果消息段数量仍然过多,保留最新的 # 2. 按数量清理:如果消息段数量仍然过多,保留最新的
if len(segments_after_age_cleanup) > max_segments_per_user: if len(segments_after_age_cleanup) > max_segments_per_user:
# 按end_time排序保留最新的 # 按end_time排序保留最新的
@@ -296,10 +307,12 @@ class NormalChat:
segments_removed_count = len(segments_after_age_cleanup) - max_segments_per_user segments_removed_count = len(segments_after_age_cleanup) - max_segments_per_user
cleanup_stats["segments_removed"] += segments_removed_count cleanup_stats["segments_removed"] += segments_removed_count
segments_after_age_cleanup = segments_after_age_cleanup[:max_segments_per_user] segments_after_age_cleanup = segments_after_age_cleanup[:max_segments_per_user]
logger.debug(f"[{self.stream_name}] 用户 {person_id} 消息段数量过多,移除 {segments_removed_count} 个最老的消息段") logger.debug(
f"[{self.stream_name}] 用户 {person_id} 消息段数量过多,移除 {segments_removed_count} 个最老的消息段"
)
# 使用清理后的消息段 # 使用清理后的消息段
# 更新缓存 # 更新缓存
if len(segments_after_age_cleanup) == 0: if len(segments_after_age_cleanup) == 0:
# 如果没有剩余消息段,标记用户为待移除 # 如果没有剩余消息段,标记用户为待移除
@@ -307,76 +320,90 @@ class NormalChat:
else: else:
self.person_engaged_cache[person_id] = segments_after_age_cleanup self.person_engaged_cache[person_id] = segments_after_age_cleanup
cleanup_stats["total_segments_after"] += len(segments_after_age_cleanup) cleanup_stats["total_segments_after"] += len(segments_after_age_cleanup)
if original_segment_count != len(segments_after_age_cleanup): if original_segment_count != len(segments_after_age_cleanup):
cleanup_stats["users_cleaned"] += 1 cleanup_stats["users_cleaned"] += 1
# 移除没有消息段的用户 # 移除没有消息段的用户
for person_id in users_to_remove: for person_id in users_to_remove:
del self.person_engaged_cache[person_id] del self.person_engaged_cache[person_id]
logger.debug(f"[{self.stream_name}] 移除用户 {person_id}:没有剩余消息段") logger.debug(f"[{self.stream_name}] 移除用户 {person_id}:没有剩余消息段")
# 更新最后清理时间 # 更新最后清理时间
self.last_cleanup_time = current_time self.last_cleanup_time = current_time
# 保存缓存 # 保存缓存
if cleanup_stats["segments_removed"] > 0 or len(users_to_remove) > 0: if cleanup_stats["segments_removed"] > 0 or len(users_to_remove) > 0:
self._save_cache() self._save_cache()
logger.info(f"[{self.stream_name}] 清理完成 - 影响用户: {cleanup_stats['users_cleaned']}, 移除消息段: {cleanup_stats['segments_removed']}, 移除用户: {len(users_to_remove)}") logger.info(
logger.info(f"[{self.stream_name}] 消息段统计 - 清理前: {cleanup_stats['total_segments_before']}, 清理后: {cleanup_stats['total_segments_after']}") f"[{self.stream_name}] 清理完成 - 影响用户: {cleanup_stats['users_cleaned']}, 移除消息段: {cleanup_stats['segments_removed']}, 移除用户: {len(users_to_remove)}"
)
logger.info(
f"[{self.stream_name}] 消息段统计 - 清理前: {cleanup_stats['total_segments_before']}, 清理后: {cleanup_stats['total_segments_after']}"
)
else: else:
logger.debug(f"[{self.stream_name}] 清理完成 - 无需清理任何内容") logger.debug(f"[{self.stream_name}] 清理完成 - 无需清理任何内容")
return cleanup_stats["segments_removed"] > 0 or len(users_to_remove) > 0 return cleanup_stats["segments_removed"] > 0 or len(users_to_remove) > 0
def get_cache_status(self) -> str: def get_cache_status(self) -> str:
"""获取缓存状态信息,用于调试和监控""" """获取缓存状态信息,用于调试和监控"""
if not self.person_engaged_cache: if not self.person_engaged_cache:
return f"[{self.stream_name}] 关系缓存为空" return f"[{self.stream_name}] 关系缓存为空"
status_lines = [f"[{self.stream_name}] 关系缓存状态:"] status_lines = [f"[{self.stream_name}] 关系缓存状态:"]
status_lines.append(f"最后处理消息时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_processed_message_time)) if self.last_processed_message_time > 0 else '未设置'}") status_lines.append(
status_lines.append(f"最后清理时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_cleanup_time)) if self.last_cleanup_time > 0 else '执行'}") f"最后处理消息时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_processed_message_time)) if self.last_processed_message_time > 0 else '设置'}"
)
status_lines.append(
f"最后清理时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_cleanup_time)) if self.last_cleanup_time > 0 else '未执行'}"
)
status_lines.append(f"总用户数:{len(self.person_engaged_cache)}") status_lines.append(f"总用户数:{len(self.person_engaged_cache)}")
status_lines.append(f"清理配置:{'启用' if SEGMENT_CLEANUP_CONFIG['enable_cleanup'] else '禁用'} (最大保存{SEGMENT_CLEANUP_CONFIG['max_segment_age_days']}天, 每用户最多{SEGMENT_CLEANUP_CONFIG['max_segments_per_user']}段)") status_lines.append(
f"清理配置:{'启用' if SEGMENT_CLEANUP_CONFIG['enable_cleanup'] else '禁用'} (最大保存{SEGMENT_CLEANUP_CONFIG['max_segment_age_days']}天, 每用户最多{SEGMENT_CLEANUP_CONFIG['max_segments_per_user']}段)"
)
status_lines.append("") status_lines.append("")
for person_id, segments in self.person_engaged_cache.items(): for person_id, segments in self.person_engaged_cache.items():
total_count = self._get_total_message_count(person_id) total_count = self._get_total_message_count(person_id)
status_lines.append(f"用户 {person_id}:") status_lines.append(f"用户 {person_id}:")
status_lines.append(f" 总消息数:{total_count} ({total_count}/45)") status_lines.append(f" 总消息数:{total_count} ({total_count}/45)")
status_lines.append(f" 消息段数:{len(segments)}") status_lines.append(f" 消息段数:{len(segments)}")
for i, segment in enumerate(segments): for i, segment in enumerate(segments):
start_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['start_time'])) start_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(segment["start_time"]))
end_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['end_time'])) end_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(segment["end_time"]))
last_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(segment['last_msg_time'])) last_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(segment["last_msg_time"]))
status_lines.append(f"{i+1}: {start_str} -> {end_str} (最后消息: {last_str}, 消息数: {segment['message_count']})") status_lines.append(
f"{i + 1}: {start_str} -> {end_str} (最后消息: {last_str}, 消息数: {segment['message_count']})"
)
status_lines.append("") status_lines.append("")
return "\n".join(status_lines) return "\n".join(status_lines)
def _update_user_message_segments(self, message: MessageRecv): def _update_user_message_segments(self, message: MessageRecv):
"""更新用户消息段信息""" """更新用户消息段信息"""
current_time = time.time() time.time()
user_id = message.message_info.user_info.user_id user_id = message.message_info.user_info.user_id
platform = message.message_info.platform platform = message.message_info.platform
msg_time = message.message_info.time msg_time = message.message_info.time
# 跳过机器人自己的消息 # 跳过机器人自己的消息
if user_id == global_config.bot.qq_account: if user_id == global_config.bot.qq_account:
return return
# 只处理新消息(避免重复处理) # 只处理新消息(避免重复处理)
if msg_time <= self.last_processed_message_time: if msg_time <= self.last_processed_message_time:
return return
person_id = PersonInfoManager.get_person_id(platform, user_id) person_id = PersonInfoManager.get_person_id(platform, user_id)
self._update_message_segments(person_id, msg_time) self._update_message_segments(person_id, msg_time)
# 更新最后处理时间 # 更新最后处理时间
self.last_processed_message_time = max(self.last_processed_message_time, msg_time) self.last_processed_message_time = max(self.last_processed_message_time, msg_time)
logger.debug(f"[{self.stream_name}] 更新用户 {person_id} 的消息段,消息时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(msg_time))}") logger.debug(
f"[{self.stream_name}] 更新用户 {person_id} 的消息段,消息时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(msg_time))}"
)
# 改为实例方法 # 改为实例方法
async def _create_thinking_message(self, message: MessageRecv, timestamp: Optional[float] = None) -> str: async def _create_thinking_message(self, message: MessageRecv, timestamp: Optional[float] = None) -> str:
@@ -585,7 +612,7 @@ class NormalChat:
# 执行定期清理 # 执行定期清理
self._cleanup_old_segments() self._cleanup_old_segments()
# 更新消息段信息 # 更新消息段信息
self._update_user_message_segments(message) self._update_user_message_segments(message)
@@ -1072,12 +1099,10 @@ class NormalChat:
"""获取动作管理器实例""" """获取动作管理器实例"""
return self.action_manager return self.action_manager
async def _check_relation_building_conditions(self): async def _check_relation_building_conditions(self):
"""检查person_engaged_cache中是否有满足关系构建条件的用户""" """检查person_engaged_cache中是否有满足关系构建条件的用户"""
users_to_build_relationship = [] users_to_build_relationship = []
for person_id, segments in list(self.person_engaged_cache.items()): for person_id, segments in list(self.person_engaged_cache.items()):
total_message_count = self._get_total_message_count(person_id) total_message_count = self._get_total_message_count(person_id)
if total_message_count >= 45: if total_message_count >= 45:
@@ -1095,9 +1120,7 @@ class NormalChat:
for person_id in users_to_build_relationship: for person_id in users_to_build_relationship:
segments = self.person_engaged_cache[person_id] segments = self.person_engaged_cache[person_id]
# 异步执行关系构建 # 异步执行关系构建
asyncio.create_task( asyncio.create_task(self._build_relation_for_person_segments(person_id, segments))
self._build_relation_for_person_segments(person_id, segments)
)
# 移除已处理的用户缓存 # 移除已处理的用户缓存
del self.person_engaged_cache[person_id] del self.person_engaged_cache[person_id]
self._save_cache() self._save_cache()
@@ -1108,17 +1131,19 @@ class NormalChat:
logger.info(f"[{self.stream_name}] 开始为 {person_id} 基于 {len(segments)} 个消息段更新印象") logger.info(f"[{self.stream_name}] 开始为 {person_id} 基于 {len(segments)} 个消息段更新印象")
try: try:
processed_messages = [] processed_messages = []
for i, segment in enumerate(segments): for i, segment in enumerate(segments):
start_time = segment["start_time"] start_time = segment["start_time"]
end_time = segment["end_time"] end_time = segment["end_time"]
message_count = segment["message_count"] segment["message_count"]
start_date = time.strftime('%Y-%m-%d %H:%M', time.localtime(start_time)) start_date = time.strftime("%Y-%m-%d %H:%M", time.localtime(start_time))
# 获取该段的消息(包含边界) # 获取该段的消息(包含边界)
segment_messages = get_raw_msg_by_timestamp_with_chat_inclusive(self.stream_id, start_time, end_time) segment_messages = get_raw_msg_by_timestamp_with_chat_inclusive(self.stream_id, start_time, end_time)
logger.info(f"[{self.stream_name}] 消息段 {i+1}: {start_date} - {time.strftime('%Y-%m-%d %H:%M', time.localtime(end_time))}, 消息数: {len(segment_messages)}") logger.info(
f"[{self.stream_name}] 消息段 {i + 1}: {start_date} - {time.strftime('%Y-%m-%d %H:%M', time.localtime(end_time))}, 消息数: {len(segment_messages)}"
)
if segment_messages: if segment_messages:
# 如果不是第一个消息段,在消息列表前添加间隔标识 # 如果不是第一个消息段,在消息列表前添加间隔标识
if i > 0: if i > 0:
@@ -1126,33 +1151,33 @@ class NormalChat:
gap_message = { gap_message = {
"time": start_time - 0.1, # 稍微早于段开始时间 "time": start_time - 0.1, # 稍微早于段开始时间
"user_id": "system", "user_id": "system",
"user_platform": "system", "user_platform": "system",
"user_nickname": "系统", "user_nickname": "系统",
"user_cardname": "", "user_cardname": "",
"display_message": f"...(中间省略一些消息){start_date} 之后的消息如下...", "display_message": f"...(中间省略一些消息){start_date} 之后的消息如下...",
"is_action_record": True, "is_action_record": True,
"chat_info_platform": segment_messages[0].get("chat_info_platform", ""), "chat_info_platform": segment_messages[0].get("chat_info_platform", ""),
"chat_id": self.stream_id "chat_id": self.stream_id,
} }
processed_messages.append(gap_message) processed_messages.append(gap_message)
# 添加该段的所有消息 # 添加该段的所有消息
processed_messages.extend(segment_messages) processed_messages.extend(segment_messages)
if processed_messages: if processed_messages:
# 按时间排序所有消息(包括间隔标识) # 按时间排序所有消息(包括间隔标识)
processed_messages.sort(key=lambda x: x['time']) processed_messages.sort(key=lambda x: x["time"])
logger.info(f"[{self.stream_name}] 为 {person_id} 获取到总共 {len(processed_messages)} 条消息(包含间隔标识)用于印象更新") logger.info(
f"[{self.stream_name}] 为 {person_id} 获取到总共 {len(processed_messages)} 条消息(包含间隔标识)用于印象更新"
)
relationship_manager = get_relationship_manager() relationship_manager = get_relationship_manager()
# 调用原有的更新方法 # 调用原有的更新方法
await relationship_manager.update_person_impression( await relationship_manager.update_person_impression(
person_id=person_id, person_id=person_id, timestamp=time.time(), bot_engaged_messages=processed_messages
timestamp=time.time(),
bot_engaged_messages=processed_messages
) )
logger.info(f"[{self.stream_name}] 用户 {person_id} 关系构建完成") logger.info(f"[{self.stream_name}] 用户 {person_id} 关系构建完成")
else: else:
logger.warning(f"[{self.stream_name}] 没有找到 {person_id} 的消息段对应的消息,不更新印象") logger.warning(f"[{self.stream_name}] 没有找到 {person_id} 的消息段对应的消息,不更新印象")

View File

@@ -210,7 +210,7 @@ class RelationshipManager:
if not readable_messages: if not readable_messages:
return return
for original_name, mapped_name in name_mapping.items(): for original_name, mapped_name in name_mapping.items():
# print(f"original_name: {original_name}, mapped_name: {mapped_name}") # print(f"original_name: {original_name}, mapped_name: {mapped_name}")
readable_messages = readable_messages.replace(f"{original_name}", f"{mapped_name}") readable_messages = readable_messages.replace(f"{original_name}", f"{mapped_name}")

View File

@@ -120,7 +120,7 @@ class BasePlugin(ABC):
if isinstance(value, str): if isinstance(value, str):
toml_str += f'{field_name} = "{value}"\n' toml_str += f'{field_name} = "{value}"\n'
elif isinstance(value, bool): elif isinstance(value, bool):
toml_str += f'{field_name} = {str(value).lower()}\n' toml_str += f"{field_name} = {str(value).lower()}\n"
else: else:
toml_str += f"{field_name} = {value}\n" toml_str += f"{field_name} = {value}\n"
@@ -173,7 +173,7 @@ class BasePlugin(ABC):
with open(config_file_path, "r", encoding="utf-8") as f: with open(config_file_path, "r", encoding="utf-8") as f:
self.config = toml.load(f) or {} self.config = toml.load(f) or {}
logger.debug(f"{self.log_prefix} 配置已从 {config_file_path} 加载") logger.debug(f"{self.log_prefix} 配置已从 {config_file_path} 加载")
# 从配置中更新 enable_plugin # 从配置中更新 enable_plugin
if "plugin" in self.config and "enabled" in self.config["plugin"]: if "plugin" in self.config and "enabled" in self.config["plugin"]:
self.enable_plugin = self.config["plugin"]["enabled"] self.enable_plugin = self.config["plugin"]["enabled"]

View File

@@ -15,4 +15,4 @@ class ConfigField:
description: str # 字段描述 description: str # 字段描述
example: Optional[str] = None # 示例值 example: Optional[str] = None # 示例值
required: bool = False # 是否必需 required: bool = False # 是否必需
choices: Optional[List[Any]] = field(default_factory=list) # 可选值列表 choices: Optional[List[Any]] = field(default_factory=list) # 可选值列表

View File

@@ -93,12 +93,12 @@ class PluginManager:
self.plugin_paths[plugin_name] = plugin_dir self.plugin_paths[plugin_name] = plugin_dir
plugin_instance = plugin_class(plugin_dir=plugin_dir) plugin_instance = plugin_class(plugin_dir=plugin_dir)
# 检查插件是否启用 # 检查插件是否启用
if not plugin_instance.enable_plugin: if not plugin_instance.enable_plugin:
logger.info(f"插件 {plugin_name} 已禁用,跳过加载") logger.info(f"插件 {plugin_name} 已禁用,跳过加载")
continue continue
if plugin_instance.register_plugin(): if plugin_instance.register_plugin():
total_registered += 1 total_registered += 1
self.loaded_plugins[plugin_name] = plugin_instance self.loaded_plugins[plugin_name] = plugin_instance

View File

@@ -427,7 +427,9 @@ class CoreActionsPlugin(BasePlugin):
"name": ConfigField(type=str, default="core_actions", description="插件名称", required=True), "name": ConfigField(type=str, default="core_actions", description="插件名称", required=True),
"version": ConfigField(type=str, default="1.0.0", description="插件版本号"), "version": ConfigField(type=str, default="1.0.0", description="插件版本号"),
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"), "enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
"description": ConfigField(type=str, default="系统核心动作插件,提供基础聊天交互功能", description="插件描述", required=True) "description": ConfigField(
type=str, default="系统核心动作插件,提供基础聊天交互功能", description="插件描述", required=True
),
}, },
"components": { "components": {
"enable_reply": ConfigField(type=bool, default=True, description="是否启用'回复'动作"), "enable_reply": ConfigField(type=bool, default=True, description="是否启用'回复'动作"),
@@ -436,22 +438,21 @@ class CoreActionsPlugin(BasePlugin):
"enable_change_to_focus": ConfigField(type=bool, default=True, description="是否启用'切换到专注模式'动作"), "enable_change_to_focus": ConfigField(type=bool, default=True, description="是否启用'切换到专注模式'动作"),
"enable_exit_focus": ConfigField(type=bool, default=True, description="是否启用'退出专注模式'动作"), "enable_exit_focus": ConfigField(type=bool, default=True, description="是否启用'退出专注模式'动作"),
"enable_ping_command": ConfigField(type=bool, default=True, description="是否启用'/ping'测试命令"), "enable_ping_command": ConfigField(type=bool, default=True, description="是否启用'/ping'测试命令"),
"enable_log_command": ConfigField(type=bool, default=True, description="是否启用'/log'日志命令") "enable_log_command": ConfigField(type=bool, default=True, description="是否启用'/log'日志命令"),
}, },
"no_reply": { "no_reply": {
"waiting_timeout": ConfigField(type=int, default=1200, description="连续不回复时,最长的等待超时时间(秒)"), "waiting_timeout": ConfigField(
type=int, default=1200, description="连续不回复时,最长的等待超时时间(秒)"
),
"stage_1_wait": ConfigField(type=int, default=10, description="第1次连续不回复的等待时间"), "stage_1_wait": ConfigField(type=int, default=10, description="第1次连续不回复的等待时间"),
"stage_2_wait": ConfigField(type=int, default=60, description="第2次连续不回复的等待时间"), "stage_2_wait": ConfigField(type=int, default=60, description="第2次连续不回复的等待时间"),
"stage_3_wait": ConfigField(type=int, default=600, description="第3次连续不回复的等待时间"), "stage_3_wait": ConfigField(type=int, default=600, description="第3次连续不回复的等待时间"),
}, },
"emoji": { "emoji": {
"random_probability": ConfigField( "random_probability": ConfigField(
type=float, type=float, default=0.1, description="Normal模式下随机发送表情的概率0.0到1.0", example=0.15
default=0.1,
description="Normal模式下随机发送表情的概率0.0到1.0",
example=0.15
) )
} },
} }
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
@@ -482,9 +483,13 @@ class CoreActionsPlugin(BasePlugin):
if self.get_config("components.enable_change_to_focus", True): if self.get_config("components.enable_change_to_focus", True):
components.append((ChangeToFocusChatAction.get_action_info(), ChangeToFocusChatAction)) components.append((ChangeToFocusChatAction.get_action_info(), ChangeToFocusChatAction))
if self.get_config("components.enable_ping_command", True): if self.get_config("components.enable_ping_command", True):
components.append((PingCommand.get_command_info(name="ping", description="测试机器人响应,拦截后续处理"), PingCommand)) components.append(
(PingCommand.get_command_info(name="ping", description="测试机器人响应,拦截后续处理"), PingCommand)
)
if self.get_config("components.enable_log_command", True): if self.get_config("components.enable_log_command", True):
components.append((LogCommand.get_command_info(name="log", description="记录消息到日志,不拦截后续处理"), LogCommand)) components.append(
(LogCommand.get_command_info(name="log", description="记录消息到日志,不拦截后续处理"), LogCommand)
)
return components return components

View File

@@ -412,7 +412,7 @@ class DoubaoImagePlugin(BasePlugin):
"api": "API相关配置包含火山引擎API的访问信息", "api": "API相关配置包含火山引擎API的访问信息",
"generation": "图片生成参数配置,控制生成图片的各种参数", "generation": "图片生成参数配置,控制生成图片的各种参数",
"cache": "结果缓存配置", "cache": "结果缓存配置",
"components": "组件启用配置" "components": "组件启用配置",
} }
# 配置Schema定义 # 配置Schema定义
@@ -422,56 +422,47 @@ class DoubaoImagePlugin(BasePlugin):
"version": ConfigField(type=str, default="2.0.0", description="插件版本号"), "version": ConfigField(type=str, default="2.0.0", description="插件版本号"),
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"), "enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
"description": ConfigField( "description": ConfigField(
type=str, type=str, default="基于火山引擎豆包模型的AI图片生成插件", description="插件描述", required=True
default="基于火山引擎豆包模型的AI图片生成插件", ),
description="插件描述",
required=True
)
}, },
"api": { "api": {
"base_url": ConfigField( "base_url": ConfigField(
type=str, type=str,
default="https://ark.cn-beijing.volces.com/api/v3", default="https://ark.cn-beijing.volces.com/api/v3",
description="API基础URL", description="API基础URL",
example="https://api.example.com/v1" example="https://api.example.com/v1",
), ),
"volcano_generate_api_key": ConfigField( "volcano_generate_api_key": ConfigField(
type=str, type=str, default="YOUR_DOUBAO_API_KEY_HERE", description="火山引擎豆包API密钥", required=True
default="YOUR_DOUBAO_API_KEY_HERE", ),
description="火山引擎豆包API密钥",
required=True
)
}, },
"generation": { "generation": {
"default_model": ConfigField( "default_model": ConfigField(
type=str, type=str,
default="doubao-seedream-3-0-t2i-250415", default="doubao-seedream-3-0-t2i-250415",
description="默认使用的文生图模型", description="默认使用的文生图模型",
choices=["doubao-seedream-3-0-t2i-250415", "doubao-seedream-2-0-t2i"] choices=["doubao-seedream-3-0-t2i-250415", "doubao-seedream-2-0-t2i"],
), ),
"default_size": ConfigField( "default_size": ConfigField(
type=str, type=str,
default="1024x1024", default="1024x1024",
description="默认图片尺寸", description="默认图片尺寸",
example="1024x1024", example="1024x1024",
choices=["1024x1024", "1024x1280", "1280x1024", "1024x1536", "1536x1024"] choices=["1024x1024", "1024x1280", "1280x1024", "1024x1536", "1536x1024"],
), ),
"default_watermark": ConfigField(type=bool, default=True, description="是否默认添加水印"), "default_watermark": ConfigField(type=bool, default=True, description="是否默认添加水印"),
"default_guidance_scale": ConfigField( "default_guidance_scale": ConfigField(
type=float, type=float, default=2.5, description="模型指导强度,影响图片与提示的关联性", example="2.0"
default=2.5,
description="模型指导强度,影响图片与提示的关联性",
example="2.0"
), ),
"default_seed": ConfigField(type=int, default=42, description="随机种子,用于复现图片") "default_seed": ConfigField(type=int, default=42, description="随机种子,用于复现图片"),
}, },
"cache": { "cache": {
"enabled": ConfigField(type=bool, default=True, description="是否启用请求缓存"), "enabled": ConfigField(type=bool, default=True, description="是否启用请求缓存"),
"max_size": ConfigField(type=int, default=10, description="最大缓存数量") "max_size": ConfigField(type=int, default=10, description="最大缓存数量"),
}, },
"components": { "components": {
"enable_image_generation": ConfigField(type=bool, default=True, description="是否启用图片生成Action") "enable_image_generation": ConfigField(type=bool, default=True, description="是否启用图片生成Action")
} },
} }
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:

View File

@@ -164,7 +164,7 @@ class MuteAction(BaseAction):
success = await self.send_command( success = await self.send_command(
command_name="GROUP_BAN", command_name="GROUP_BAN",
args={"qq_id": str(user_id), "duration": str(duration_int)}, args={"qq_id": str(user_id), "duration": str(duration_int)},
display_message=f"发送禁言命令", display_message="发送禁言命令",
) )
if success: if success:
@@ -180,8 +180,8 @@ class MuteAction(BaseAction):
"user_id": user_id, "user_id": user_id,
"duration": duration_int, "duration": duration_int,
"duration_str": time_str, "duration_str": time_str,
"reason": reason "reason": reason,
} },
) )
return True, f"成功禁言 {target},时长 {time_str}" return True, f"成功禁言 {target},时长 {time_str}"
else: else:
@@ -389,7 +389,7 @@ class MutePlugin(BasePlugin):
"mute": "核心禁言功能配置", "mute": "核心禁言功能配置",
"smart_mute": "智能禁言Action的专属配置", "smart_mute": "智能禁言Action的专属配置",
"mute_command": "禁言命令Command的专属配置", "mute_command": "禁言命令Command的专属配置",
"logging": "日志记录相关配置" "logging": "日志记录相关配置",
} }
# 配置Schema定义 # 配置Schema定义
@@ -398,17 +398,21 @@ class MutePlugin(BasePlugin):
"name": ConfigField(type=str, default="mute_plugin", description="插件名称", required=True), "name": ConfigField(type=str, default="mute_plugin", description="插件名称", required=True),
"version": ConfigField(type=str, default="2.0.0", description="插件版本号"), "version": ConfigField(type=str, default="2.0.0", description="插件版本号"),
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"), "enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
"description": ConfigField(type=str, default="群聊禁言管理插件,提供智能禁言功能", description="插件描述", required=True) "description": ConfigField(
type=str, default="群聊禁言管理插件,提供智能禁言功能", description="插件描述", required=True
),
}, },
"components": { "components": {
"enable_smart_mute": ConfigField(type=bool, default=True, description="是否启用智能禁言Action"), "enable_smart_mute": ConfigField(type=bool, default=True, description="是否启用智能禁言Action"),
"enable_mute_command": ConfigField(type=bool, default=False, description="是否启用禁言命令Command") "enable_mute_command": ConfigField(type=bool, default=False, description="是否启用禁言命令Command"),
}, },
"mute": { "mute": {
"min_duration": ConfigField(type=int, default=60, description="最短禁言时长(秒)"), "min_duration": ConfigField(type=int, default=60, description="最短禁言时长(秒)"),
"max_duration": ConfigField(type=int, default=2592000, description="最长禁言时长默认30天"), "max_duration": ConfigField(type=int, default=2592000, description="最长禁言时长默认30天"),
"default_duration": ConfigField(type=int, default=300, description="默认禁言时长默认5分钟"), "default_duration": ConfigField(type=int, default=300, description="默认禁言时长默认5分钟"),
"enable_duration_formatting": ConfigField(type=bool, default=True, description="是否启用人性化的时长显示(如 '5分钟' 而非 '300秒'"), "enable_duration_formatting": ConfigField(
type=bool, default=True, description="是否启用人性化的时长显示(如 '5分钟' 而非 '300秒'"
),
"log_mute_history": ConfigField(type=bool, default=True, description="是否记录禁言历史(未来功能)"), "log_mute_history": ConfigField(type=bool, default=True, description="是否记录禁言历史(未来功能)"),
"templates": ConfigField( "templates": ConfigField(
type=list, type=list,
@@ -418,9 +422,9 @@ class MutePlugin(BasePlugin):
"明白了,禁言 {target} {duration},原因是{reason}", "明白了,禁言 {target} {duration},原因是{reason}",
"哇哈哈哈哈哈,已禁言 {target} {duration},理由:{reason}", "哇哈哈哈哈哈,已禁言 {target} {duration},理由:{reason}",
"哎呦我去,对 {target} 执行禁言 {duration},因为{reason}", "哎呦我去,对 {target} 执行禁言 {duration},因为{reason}",
"{target},你完蛋了,我要禁言你 {duration} 秒,原因:{reason}" "{target},你完蛋了,我要禁言你 {duration} 秒,原因:{reason}",
], ],
description="成功禁言后发送的随机消息模板" description="成功禁言后发送的随机消息模板",
), ),
"error_messages": ConfigField( "error_messages": ConfigField(
type=list, type=list,
@@ -430,26 +434,30 @@ class MutePlugin(BasePlugin):
"禁言时长必须是正数哦~", "禁言时长必须是正数哦~",
"禁言时长必须是数字哦~", "禁言时长必须是数字哦~",
"找不到 {target} 这个人呢~", "找不到 {target} 这个人呢~",
"查找用户信息时出现问题~" "查找用户信息时出现问题~",
], ],
description="执行禁言过程中发生错误时发送的随机消息模板" description="执行禁言过程中发生错误时发送的随机消息模板",
) ),
}, },
"smart_mute": { "smart_mute": {
"strict_mode": ConfigField(type=bool, default=True, description="LLM判定的严格模式"), "strict_mode": ConfigField(type=bool, default=True, description="LLM判定的严格模式"),
"keyword_sensitivity": ConfigField(type=str, default="normal", description="关键词激活的敏感度", choices=["low", "normal", "high"]), "keyword_sensitivity": ConfigField(
"allow_parallel": ConfigField(type=bool, default=False, description="是否允许并行执行(暂未启用)") type=str, default="normal", description="关键词激活的敏感度", choices=["low", "normal", "high"]
),
"allow_parallel": ConfigField(type=bool, default=False, description="是否允许并行执行(暂未启用)"),
}, },
"mute_command": { "mute_command": {
"max_batch_size": ConfigField(type=int, default=5, description="最大批量禁言数量(未来功能)"), "max_batch_size": ConfigField(type=int, default=5, description="最大批量禁言数量(未来功能)"),
"cooldown_seconds": ConfigField(type=int, default=3, description="命令冷却时间(秒)") "cooldown_seconds": ConfigField(type=int, default=3, description="命令冷却时间(秒)"),
}, },
"logging": { "logging": {
"level": ConfigField(type=str, default="INFO", description="日志记录级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]), "level": ConfigField(
type=str, default="INFO", description="日志记录级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]
),
"prefix": ConfigField(type=str, default="[MutePlugin]", description="日志记录前缀"), "prefix": ConfigField(type=str, default="[MutePlugin]", description="日志记录前缀"),
"include_user_info": ConfigField(type=bool, default=True, description="日志中是否包含用户信息"), "include_user_info": ConfigField(type=bool, default=True, description="日志中是否包含用户信息"),
"include_duration_info": ConfigField(type=bool, default=True, description="日志中是否包含禁言时长信息") "include_duration_info": ConfigField(type=bool, default=True, description="日志中是否包含禁言时长信息"),
} },
} }
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:

View File

@@ -112,7 +112,7 @@ class TTSPlugin(BasePlugin):
config_section_descriptions = { config_section_descriptions = {
"plugin": "插件基本信息配置", "plugin": "插件基本信息配置",
"components": "组件启用控制", "components": "组件启用控制",
"logging": "日志记录相关配置" "logging": "日志记录相关配置",
} }
# 配置Schema定义 # 配置Schema定义
@@ -121,15 +121,15 @@ class TTSPlugin(BasePlugin):
"name": ConfigField(type=str, default="tts_plugin", description="插件名称", required=True), "name": ConfigField(type=str, default="tts_plugin", description="插件名称", required=True),
"version": ConfigField(type=str, default="0.1.0", description="插件版本号"), "version": ConfigField(type=str, default="0.1.0", description="插件版本号"),
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"), "enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
"description": ConfigField(type=str, default="文字转语音插件", description="插件描述", required=True) "description": ConfigField(type=str, default="文字转语音插件", description="插件描述", required=True),
},
"components": {
"enable_tts": ConfigField(type=bool, default=True, description="是否启用TTS Action")
}, },
"components": {"enable_tts": ConfigField(type=bool, default=True, description="是否启用TTS Action")},
"logging": { "logging": {
"level": ConfigField(type=str, default="INFO", description="日志记录级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]), "level": ConfigField(
"prefix": ConfigField(type=str, default="[TTS]", description="日志记录前缀") type=str, default="INFO", description="日志记录级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]
} ),
"prefix": ConfigField(type=str, default="[TTS]", description="日志记录前缀"),
},
} }
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:

View File

@@ -128,25 +128,22 @@ class VTBPlugin(BasePlugin):
"name": ConfigField(type=str, default="vtb_plugin", description="插件名称", required=True), "name": ConfigField(type=str, default="vtb_plugin", description="插件名称", required=True),
"version": ConfigField(type=str, default="0.1.0", description="插件版本号"), "version": ConfigField(type=str, default="0.1.0", description="插件版本号"),
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"), "enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
"description": ConfigField(type=str, default="虚拟主播情感表达插件", description="插件描述", required=True) "description": ConfigField(type=str, default="虚拟主播情感表达插件", description="插件描述", required=True),
},
"components": {
"enable_vtb": ConfigField(type=bool, default=True, description="是否启用VTB动作")
}, },
"components": {"enable_vtb": ConfigField(type=bool, default=True, description="是否启用VTB动作")},
"vtb_action": { "vtb_action": {
"random_activation_probability": ConfigField( "random_activation_probability": ConfigField(
type=float, type=float, default=0.08, description="Normal模式下随机触发VTB动作的概率0.0到1.0", example=0.1
default=0.08,
description="Normal模式下随机触发VTB动作的概率0.0到1.0",
example=0.1
), ),
"max_text_length": ConfigField(type=int, default=100, description="用于VTB动作的情感描述文本的最大长度"), "max_text_length": ConfigField(type=int, default=100, description="用于VTB动作的情感描述文本的最大长度"),
"default_emotion": ConfigField(type=str, default="平静", description="当没有有效输入时,默认表达的情感") "default_emotion": ConfigField(type=str, default="平静", description="当没有有效输入时,默认表达的情感"),
}, },
"logging": { "logging": {
"level": ConfigField(type=str, default="INFO", description="日志级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]), "level": ConfigField(
"prefix": ConfigField(type=str, default="[VTB]", description="日志记录前缀") type=str, default="INFO", description="日志级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]
} ),
"prefix": ConfigField(type=str, default="[VTB]", description="日志记录前缀"),
},
} }
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: