🤖 自动格式化代码 [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),
"version": ConfigField(type=str, default="2.0.0", 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_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_echo": ConfigField(type=bool, default=True, description="是否启用'/echo'命令"),
"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": {
"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_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成个性化问候语")
"enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成个性化问候语"),
},
"helpful": {
"enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成帮助内容"),
"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": {
"show_extended_help": ConfigField(type=bool, default=True, description="是否显示扩展帮助信息"),
"include_action_info": ConfigField(type=bool, default=True, description="帮助信息中是否包含Action的信息"),
"include_config_info": ConfigField(type=bool, default=True, description="帮助信息中是否包含配置相关信息"),
"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": {
"max_message_length": ConfigField(type=int, default=500, 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": {
"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": {
"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": {
"show_detailed_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": {
"level": ConfigField(type=str, default="INFO", description="日志级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]),
"prefix": ConfigField(type=str, default="[ExampleComprehensive]", description="日志前缀")
}
"level": ConfigField(
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]]:

View File

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

View File

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

View File

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

View File

@@ -47,7 +47,9 @@ class HFCloopObservation:
action_reasoning = action_result.get("reasoning", "未提供理由")
is_taken = cycle.loop_action_info.get("action_taken", False)
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_reasoning)
# print(is_taken)

View File

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

View File

@@ -210,7 +210,7 @@ class RelationshipManager:
if not readable_messages:
return
for original_name, mapped_name in name_mapping.items():
# print(f"original_name: {original_name}, mapped_name: {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):
toml_str += f'{field_name} = "{value}"\n'
elif isinstance(value, bool):
toml_str += f'{field_name} = {str(value).lower()}\n'
toml_str += f"{field_name} = {str(value).lower()}\n"
else:
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:
self.config = toml.load(f) or {}
logger.debug(f"{self.log_prefix} 配置已从 {config_file_path} 加载")
# 从配置中更新 enable_plugin
if "plugin" in self.config and "enabled" in self.config["plugin"]:
self.enable_plugin = self.config["plugin"]["enabled"]

View File

@@ -15,4 +15,4 @@ class ConfigField:
description: str # 字段描述
example: Optional[str] = None # 示例值
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
plugin_instance = plugin_class(plugin_dir=plugin_dir)
# 检查插件是否启用
if not plugin_instance.enable_plugin:
logger.info(f"插件 {plugin_name} 已禁用,跳过加载")
continue
if plugin_instance.register_plugin():
total_registered += 1
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),
"version": ConfigField(type=str, default="1.0.0", 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_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_exit_focus": ConfigField(type=bool, default=True, description="是否启用'退出专注模式'动作"),
"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": {
"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_2_wait": ConfigField(type=int, default=60, description="第2次连续不回复的等待时间"),
"stage_3_wait": ConfigField(type=int, default=600, description="第3次连续不回复的等待时间"),
},
"emoji": {
"random_probability": ConfigField(
type=float,
default=0.1,
description="Normal模式下随机发送表情的概率0.0到1.0",
example=0.15
type=float, default=0.1, description="Normal模式下随机发送表情的概率0.0到1.0", example=0.15
)
}
},
}
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):
components.append((ChangeToFocusChatAction.get_action_info(), ChangeToFocusChatAction))
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):
components.append((LogCommand.get_command_info(name="log", description="记录消息到日志,不拦截后续处理"), LogCommand))
components.append(
(LogCommand.get_command_info(name="log", description="记录消息到日志,不拦截后续处理"), LogCommand)
)
return components

View File

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

View File

@@ -164,7 +164,7 @@ class MuteAction(BaseAction):
success = await self.send_command(
command_name="GROUP_BAN",
args={"qq_id": str(user_id), "duration": str(duration_int)},
display_message=f"发送禁言命令",
display_message="发送禁言命令",
)
if success:
@@ -180,8 +180,8 @@ class MuteAction(BaseAction):
"user_id": user_id,
"duration": duration_int,
"duration_str": time_str,
"reason": reason
}
"reason": reason,
},
)
return True, f"成功禁言 {target},时长 {time_str}"
else:
@@ -389,7 +389,7 @@ class MutePlugin(BasePlugin):
"mute": "核心禁言功能配置",
"smart_mute": "智能禁言Action的专属配置",
"mute_command": "禁言命令Command的专属配置",
"logging": "日志记录相关配置"
"logging": "日志记录相关配置",
}
# 配置Schema定义
@@ -398,17 +398,21 @@ class MutePlugin(BasePlugin):
"name": ConfigField(type=str, default="mute_plugin", description="插件名称", required=True),
"version": ConfigField(type=str, default="2.0.0", 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": {
"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": {
"min_duration": ConfigField(type=int, default=60, description="最短禁言时长(秒)"),
"max_duration": ConfigField(type=int, default=2592000, description="最长禁言时长默认30天"),
"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="是否记录禁言历史(未来功能)"),
"templates": ConfigField(
type=list,
@@ -418,9 +422,9 @@ class MutePlugin(BasePlugin):
"明白了,禁言 {target} {duration},原因是{reason}",
"哇哈哈哈哈哈,已禁言 {target} {duration},理由:{reason}",
"哎呦我去,对 {target} 执行禁言 {duration},因为{reason}",
"{target},你完蛋了,我要禁言你 {duration} 秒,原因:{reason}"
"{target},你完蛋了,我要禁言你 {duration} 秒,原因:{reason}",
],
description="成功禁言后发送的随机消息模板"
description="成功禁言后发送的随机消息模板",
),
"error_messages": ConfigField(
type=list,
@@ -430,26 +434,30 @@ class MutePlugin(BasePlugin):
"禁言时长必须是正数哦~",
"禁言时长必须是数字哦~",
"找不到 {target} 这个人呢~",
"查找用户信息时出现问题~"
"查找用户信息时出现问题~",
],
description="执行禁言过程中发生错误时发送的随机消息模板"
)
description="执行禁言过程中发生错误时发送的随机消息模板",
),
},
"smart_mute": {
"strict_mode": ConfigField(type=bool, default=True, description="LLM判定的严格模式"),
"keyword_sensitivity": ConfigField(type=str, default="normal", description="关键词激活的敏感度", choices=["low", "normal", "high"]),
"allow_parallel": ConfigField(type=bool, default=False, description="是否允许并行执行(暂未启用)")
"keyword_sensitivity": ConfigField(
type=str, default="normal", description="关键词激活的敏感度", choices=["low", "normal", "high"]
),
"allow_parallel": ConfigField(type=bool, default=False, description="是否允许并行执行(暂未启用)"),
},
"mute_command": {
"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": {
"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="日志记录前缀"),
"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]]:

View File

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

View File

@@ -128,25 +128,22 @@ class VTBPlugin(BasePlugin):
"name": ConfigField(type=str, default="vtb_plugin", description="插件名称", required=True),
"version": ConfigField(type=str, default="0.1.0", description="插件版本号"),
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
"description": ConfigField(type=str, default="虚拟主播情感表达插件", description="插件描述", required=True)
},
"components": {
"enable_vtb": ConfigField(type=bool, default=True, description="是否启用VTB动作")
"description": ConfigField(type=str, default="虚拟主播情感表达插件", description="插件描述", required=True),
},
"components": {"enable_vtb": ConfigField(type=bool, default=True, description="是否启用VTB动作")},
"vtb_action": {
"random_activation_probability": ConfigField(
type=float,
default=0.08,
description="Normal模式下随机触发VTB动作的概率0.0到1.0",
example=0.1
type=float, default=0.08, description="Normal模式下随机触发VTB动作的概率0.0到1.0", example=0.1
),
"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": {
"level": ConfigField(type=str, default="INFO", description="日志级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"]),
"prefix": ConfigField(type=str, default="[VTB]", description="日志记录前缀")
}
"level": ConfigField(
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]]: