Merge branch 'dev' of https://github.com/MaiM-with-u/MaiBot into dev
This commit is contained in:
@@ -8,6 +8,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class ConfigEditor:
|
class ConfigEditor:
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
self.root = root
|
self.root = root
|
||||||
@@ -21,10 +22,10 @@ class ConfigEditor:
|
|||||||
|
|
||||||
# 加载配置
|
# 加载配置
|
||||||
self.load_config()
|
self.load_config()
|
||||||
|
|
||||||
# 加载环境变量
|
# 加载环境变量
|
||||||
self.load_env_vars()
|
self.load_env_vars()
|
||||||
|
|
||||||
# 自动保存相关
|
# 自动保存相关
|
||||||
self.last_save_time = time.time()
|
self.last_save_time = time.time()
|
||||||
self.save_timer = None
|
self.save_timer = None
|
||||||
@@ -114,40 +115,40 @@ class ConfigEditor:
|
|||||||
env_path = self.config.get("inner", {}).get("env_file", ".env")
|
env_path = self.config.get("inner", {}).get("env_file", ".env")
|
||||||
if not os.path.isabs(env_path):
|
if not os.path.isabs(env_path):
|
||||||
env_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), env_path)
|
env_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), env_path)
|
||||||
|
|
||||||
if not os.path.exists(env_path):
|
if not os.path.exists(env_path):
|
||||||
print(f"环境文件不存在: {env_path}")
|
print(f"环境文件不存在: {env_path}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 读取环境文件
|
# 读取环境文件
|
||||||
with open(env_path, 'r', encoding='utf-8') as f:
|
with open(env_path, "r", encoding="utf-8") as f:
|
||||||
env_content = f.read()
|
env_content = f.read()
|
||||||
|
|
||||||
# 解析环境变量
|
# 解析环境变量
|
||||||
env_vars = {}
|
env_vars = {}
|
||||||
for line in env_content.split('\n'):
|
for line in env_content.split("\n"):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line or line.startswith('#'):
|
if not line or line.startswith("#"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if '=' in line:
|
if "=" in line:
|
||||||
key, value = line.split('=', 1)
|
key, value = line.split("=", 1)
|
||||||
key = key.strip()
|
key = key.strip()
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
|
|
||||||
# 检查是否是目标变量
|
# 检查是否是目标变量
|
||||||
if key.endswith('_BASE_URL') or key.endswith('_KEY'):
|
if key.endswith("_BASE_URL") or key.endswith("_KEY"):
|
||||||
# 提取前缀(去掉_BASE_URL或_KEY)
|
# 提取前缀(去掉_BASE_URL或_KEY)
|
||||||
prefix = key[:-9] if key.endswith('_BASE_URL') else key[:-4]
|
prefix = key[:-9] if key.endswith("_BASE_URL") else key[:-4]
|
||||||
if prefix not in env_vars:
|
if prefix not in env_vars:
|
||||||
env_vars[prefix] = {}
|
env_vars[prefix] = {}
|
||||||
env_vars[prefix][key] = value
|
env_vars[prefix][key] = value
|
||||||
|
|
||||||
# 将解析的环境变量添加到配置中
|
# 将解析的环境变量添加到配置中
|
||||||
if 'env_vars' not in self.config:
|
if "env_vars" not in self.config:
|
||||||
self.config['env_vars'] = {}
|
self.config["env_vars"] = {}
|
||||||
self.config['env_vars'].update(env_vars)
|
self.config["env_vars"].update(env_vars)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"加载环境变量失败: {str(e)}")
|
print(f"加载环境变量失败: {str(e)}")
|
||||||
|
|
||||||
@@ -156,11 +157,11 @@ class ConfigEditor:
|
|||||||
version = self.config.get("inner", {}).get("version", "未知版本")
|
version = self.config.get("inner", {}).get("version", "未知版本")
|
||||||
version_frame = ttk.Frame(self.main_frame)
|
version_frame = ttk.Frame(self.main_frame)
|
||||||
version_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
|
version_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
|
||||||
|
|
||||||
# 添加配置按钮
|
# 添加配置按钮
|
||||||
config_button = ttk.Button(version_frame, text="配置路径", command=self.open_path_config)
|
config_button = ttk.Button(version_frame, text="配置路径", command=self.open_path_config)
|
||||||
config_button.pack(side=tk.LEFT, padx=5)
|
config_button.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
version_label = ttk.Label(version_frame, text=f"麦麦版本:{version}", font=("微软雅黑", 10, "bold"))
|
version_label = ttk.Label(version_frame, text=f"麦麦版本:{version}", font=("微软雅黑", 10, "bold"))
|
||||||
version_label.pack(side=tk.LEFT, padx=5)
|
version_label.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
@@ -175,13 +176,22 @@ class ConfigEditor:
|
|||||||
|
|
||||||
# 添加快捷设置节
|
# 添加快捷设置节
|
||||||
self.tree.insert("", "end", text="快捷设置", values=("quick_settings",))
|
self.tree.insert("", "end", text="快捷设置", values=("quick_settings",))
|
||||||
|
|
||||||
# 添加env_vars节,显示为"配置你的模型APIKEY"
|
# 添加env_vars节,显示为"配置你的模型APIKEY"
|
||||||
self.tree.insert("", "end", text="配置你的模型APIKEY", values=("env_vars",))
|
self.tree.insert("", "end", text="配置你的模型APIKEY", values=("env_vars",))
|
||||||
|
|
||||||
# 只显示bot_config.toml实际存在的section
|
# 只显示bot_config.toml实际存在的section
|
||||||
for section in self.config:
|
for section in self.config:
|
||||||
if section not in ("inner", "env_vars", "telemetry", "experimental", "maim_message", "keyword_reaction", "message_receive", "relationship"):
|
if section not in (
|
||||||
|
"inner",
|
||||||
|
"env_vars",
|
||||||
|
"telemetry",
|
||||||
|
"experimental",
|
||||||
|
"maim_message",
|
||||||
|
"keyword_reaction",
|
||||||
|
"message_receive",
|
||||||
|
"relationship",
|
||||||
|
):
|
||||||
section_trans = self.translations.get("sections", {}).get(section, {})
|
section_trans = self.translations.get("sections", {}).get(section, {})
|
||||||
section_name = section_trans.get("name", section)
|
section_name = section_trans.get("name", section)
|
||||||
self.tree.insert("", "end", text=section_name, values=(section,))
|
self.tree.insert("", "end", text=section_name, values=(section,))
|
||||||
@@ -196,7 +206,7 @@ class ConfigEditor:
|
|||||||
# 创建编辑区标题
|
# 创建编辑区标题
|
||||||
# self.editor_title = ttk.Label(self.editor_frame, text="")
|
# self.editor_title = ttk.Label(self.editor_frame, text="")
|
||||||
# self.editor_title.pack(fill=tk.X)
|
# self.editor_title.pack(fill=tk.X)
|
||||||
|
|
||||||
# 创建编辑区内容
|
# 创建编辑区内容
|
||||||
self.editor_content = ttk.Frame(self.editor_frame)
|
self.editor_content = ttk.Frame(self.editor_frame)
|
||||||
self.editor_content.pack(fill=tk.BOTH, expand=True)
|
self.editor_content.pack(fill=tk.BOTH, expand=True)
|
||||||
@@ -245,15 +255,15 @@ class ConfigEditor:
|
|||||||
|
|
||||||
# --- 修改开始: 改进翻译查找逻辑 ---
|
# --- 修改开始: 改进翻译查找逻辑 ---
|
||||||
full_config_path_key = ".".join(path + [key]) # 例如 "chinese_typo.enable"
|
full_config_path_key = ".".join(path + [key]) # 例如 "chinese_typo.enable"
|
||||||
|
|
||||||
model_item_translations = {
|
model_item_translations = {
|
||||||
"name": ("模型名称", "模型的唯一标识或名称"),
|
"name": ("模型名称", "模型的唯一标识或名称"),
|
||||||
"provider": ("模型提供商", "模型API的提供商"),
|
"provider": ("模型提供商", "模型API的提供商"),
|
||||||
"pri_in": ("输入价格", "模型输入的价格/消耗"),
|
"pri_in": ("输入价格", "模型输入的价格/消耗"),
|
||||||
"pri_out": ("输出价格", "模型输出的价格/消耗"),
|
"pri_out": ("输出价格", "模型输出的价格/消耗"),
|
||||||
"temp": ("模型温度", "控制模型输出的多样性")
|
"temp": ("模型温度", "控制模型输出的多样性"),
|
||||||
}
|
}
|
||||||
|
|
||||||
item_name_to_display = key # 默认显示原始键名
|
item_name_to_display = key # 默认显示原始键名
|
||||||
item_desc_to_display = "" # 默认无描述
|
item_desc_to_display = "" # 默认无描述
|
||||||
|
|
||||||
@@ -294,9 +304,15 @@ class ConfigEditor:
|
|||||||
# 判断parent是不是self.content_frame
|
# 判断parent是不是self.content_frame
|
||||||
if parent == self.content_frame:
|
if parent == self.content_frame:
|
||||||
# 主界面
|
# 主界面
|
||||||
if hasattr(self, 'current_section') and self.current_section and self.current_section != "quick_settings":
|
if (
|
||||||
self.create_section_widgets(parent, self.current_section, self.config[self.current_section], [self.current_section])
|
hasattr(self, "current_section")
|
||||||
elif hasattr(self, 'current_section') and self.current_section == "quick_settings":
|
and self.current_section
|
||||||
|
and self.current_section != "quick_settings"
|
||||||
|
):
|
||||||
|
self.create_section_widgets(
|
||||||
|
parent, self.current_section, self.config[self.current_section], [self.current_section]
|
||||||
|
)
|
||||||
|
elif hasattr(self, "current_section") and self.current_section == "quick_settings":
|
||||||
self.create_quick_settings_widgets()
|
self.create_quick_settings_widgets()
|
||||||
else:
|
else:
|
||||||
# 弹窗Tab
|
# 弹窗Tab
|
||||||
@@ -318,15 +334,17 @@ class ConfigEditor:
|
|||||||
desc_row = 1
|
desc_row = 1
|
||||||
if item_desc_to_display:
|
if item_desc_to_display:
|
||||||
desc_label = ttk.Label(frame, text=item_desc_to_display, foreground="gray", font=("微软雅黑", 10))
|
desc_label = ttk.Label(frame, text=item_desc_to_display, foreground="gray", font=("微软雅黑", 10))
|
||||||
desc_label.grid(row=desc_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W, padx=5, pady=(0, 4))
|
desc_label.grid(
|
||||||
widget_row = desc_row + 1 # 内容控件在描述下方
|
row=desc_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W, padx=5, pady=(0, 4)
|
||||||
|
)
|
||||||
|
widget_row = desc_row + 1 # 内容控件在描述下方
|
||||||
else:
|
else:
|
||||||
widget_row = desc_row # 内容控件直接在第二行
|
widget_row = desc_row # 内容控件直接在第二行
|
||||||
|
|
||||||
# 配置内容控件(第三行或第二行)
|
# 配置内容控件(第三行或第二行)
|
||||||
if path[0] == "inner":
|
if path[0] == "inner":
|
||||||
value_label = ttk.Label(frame, text=str(value), font=("微软雅黑", 16))
|
value_label = ttk.Label(frame, text=str(value), font=("微软雅黑", 16))
|
||||||
value_label.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W, padx=5)
|
value_label.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W, padx=5)
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
@@ -341,7 +359,7 @@ class ConfigEditor:
|
|||||||
# 数字使用数字输入框
|
# 数字使用数字输入框
|
||||||
var = tk.StringVar(value=str(value))
|
var = tk.StringVar(value=str(value))
|
||||||
entry = ttk.Entry(frame, textvariable=var, font=("微软雅黑", 16))
|
entry = ttk.Entry(frame, textvariable=var, font=("微软雅黑", 16))
|
||||||
entry.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W+tk.E, padx=5)
|
entry.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W + tk.E, padx=5)
|
||||||
var.trace_add("write", lambda *args: self.on_value_changed())
|
var.trace_add("write", lambda *args: self.on_value_changed())
|
||||||
self.widgets[tuple(path + [key])] = var
|
self.widgets[tuple(path + [key])] = var
|
||||||
widget_type = "number"
|
widget_type = "number"
|
||||||
@@ -380,7 +398,7 @@ class ConfigEditor:
|
|||||||
else:
|
else:
|
||||||
# 其他类型(字符串等)使用普通文本框
|
# 其他类型(字符串等)使用普通文本框
|
||||||
var = tk.StringVar(value=str(value))
|
var = tk.StringVar(value=str(value))
|
||||||
|
|
||||||
# 特殊处理provider字段
|
# 特殊处理provider字段
|
||||||
full_path = ".".join(path + [key])
|
full_path = ".".join(path + [key])
|
||||||
if key == "provider" and full_path.startswith("model."):
|
if key == "provider" and full_path.startswith("model."):
|
||||||
@@ -397,37 +415,45 @@ class ConfigEditor:
|
|||||||
if f"{prefix}_BASE_URL" in values and f"{prefix}_KEY" in values:
|
if f"{prefix}_BASE_URL" in values and f"{prefix}_KEY" in values:
|
||||||
providers.append(prefix)
|
providers.append(prefix)
|
||||||
# print(f"添加provider: {prefix}")
|
# print(f"添加provider: {prefix}")
|
||||||
|
|
||||||
# print(f"最终providers列表: {providers}")
|
# print(f"最终providers列表: {providers}")
|
||||||
if providers:
|
if providers:
|
||||||
# 创建模型名称标签(大字体)
|
# 创建模型名称标签(大字体)
|
||||||
model_name = var.get() if var.get() else providers[0]
|
# model_name = var.get() if var.get() else providers[0]
|
||||||
section_translations = {
|
# section_translations = {
|
||||||
"model.utils": "麦麦组件模型",
|
# "model.utils": "麦麦组件模型",
|
||||||
"model.utils_small": "小型麦麦组件模型",
|
# "model.utils_small": "小型麦麦组件模型",
|
||||||
"model.memory_summary": "记忆概括模型",
|
# "model.memory_summary": "记忆概括模型",
|
||||||
"model.vlm": "图像识别模型",
|
# "model.vlm": "图像识别模型",
|
||||||
"model.embedding": "嵌入模型",
|
# "model.embedding": "嵌入模型",
|
||||||
"model.normal_chat_1": "普通聊天:主要聊天模型",
|
# "model.normal_chat_1": "普通聊天:主要聊天模型",
|
||||||
"model.normal_chat_2": "普通聊天:次要聊天模型",
|
# "model.normal_chat_2": "普通聊天:次要聊天模型",
|
||||||
"model.focus_working_memory": "专注模式:工作记忆模型",
|
# "model.focus_working_memory": "专注模式:工作记忆模型",
|
||||||
"model.focus_chat_mind": "专注模式:聊天思考模型",
|
# "model.focus_chat_mind": "专注模式:聊天思考模型",
|
||||||
"model.focus_tool_use": "专注模式:工具调用模型",
|
# "model.focus_tool_use": "专注模式:工具调用模型",
|
||||||
"model.focus_planner": "专注模式:决策模型",
|
# "model.focus_planner": "专注模式:决策模型",
|
||||||
"model.focus_expressor": "专注模式:表达器模型",
|
# "model.focus_expressor": "专注模式:表达器模型",
|
||||||
"model.focus_self_recognize": "专注模式:自我识别模型"
|
# "model.focus_self_recognize": "专注模式:自我识别模型"
|
||||||
}
|
# }
|
||||||
# 获取当前节的名称
|
# 获取当前节的名称
|
||||||
# current_section = ".".join(path[:-1]) # 去掉最后一个key
|
# current_section = ".".join(path[:-1]) # 去掉最后一个key
|
||||||
# section_name = section_translations.get(current_section, current_section)
|
# section_name = section_translations.get(current_section, current_section)
|
||||||
|
|
||||||
# 创建节名称标签(大字体)
|
# 创建节名称标签(大字体)
|
||||||
# section_label = ttk.Label(frame, text="11", font=("微软雅黑", 24, "bold"))
|
# section_label = ttk.Label(frame, text="11", font=("微软雅黑", 24, "bold"))
|
||||||
# section_label.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W, padx=5, pady=(0, 5))
|
# section_label.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W, padx=5, pady=(0, 5))
|
||||||
|
|
||||||
# 创建下拉菜单(小字体)
|
# 创建下拉菜单(小字体)
|
||||||
combo = ttk.Combobox(frame, textvariable=var, values=providers, font=("微软雅黑", 12), state="readonly")
|
combo = ttk.Combobox(
|
||||||
combo.grid(row=widget_row + 1, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W+tk.E, padx=5)
|
frame, textvariable=var, values=providers, font=("微软雅黑", 12), state="readonly"
|
||||||
|
)
|
||||||
|
combo.grid(
|
||||||
|
row=widget_row + 1,
|
||||||
|
column=0,
|
||||||
|
columnspan=content_col_offset_for_star + 1,
|
||||||
|
sticky=tk.W + tk.E,
|
||||||
|
padx=5,
|
||||||
|
)
|
||||||
combo.bind("<<ComboboxSelected>>", lambda e: self.on_value_changed())
|
combo.bind("<<ComboboxSelected>>", lambda e: self.on_value_changed())
|
||||||
self.widgets[tuple(path + [key])] = var
|
self.widgets[tuple(path + [key])] = var
|
||||||
widget_type = "provider"
|
widget_type = "provider"
|
||||||
@@ -436,14 +462,18 @@ class ConfigEditor:
|
|||||||
# 如果没有可用的provider,使用普通文本框
|
# 如果没有可用的provider,使用普通文本框
|
||||||
# print(f"没有可用的provider,使用普通文本框")
|
# print(f"没有可用的provider,使用普通文本框")
|
||||||
entry = ttk.Entry(frame, textvariable=var, font=("微软雅黑", 16))
|
entry = ttk.Entry(frame, textvariable=var, font=("微软雅黑", 16))
|
||||||
entry.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W+tk.E, padx=5)
|
entry.grid(
|
||||||
|
row=widget_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W + tk.E, padx=5
|
||||||
|
)
|
||||||
var.trace_add("write", lambda *args: self.on_value_changed())
|
var.trace_add("write", lambda *args: self.on_value_changed())
|
||||||
self.widgets[tuple(path + [key])] = var
|
self.widgets[tuple(path + [key])] = var
|
||||||
widget_type = "text"
|
widget_type = "text"
|
||||||
else:
|
else:
|
||||||
# 普通文本框
|
# 普通文本框
|
||||||
entry = ttk.Entry(frame, textvariable=var, font=("微软雅黑", 16))
|
entry = ttk.Entry(frame, textvariable=var, font=("微软雅黑", 16))
|
||||||
entry.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W+tk.E, padx=5)
|
entry.grid(
|
||||||
|
row=widget_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W + tk.E, padx=5
|
||||||
|
)
|
||||||
var.trace_add("write", lambda *args: self.on_value_changed())
|
var.trace_add("write", lambda *args: self.on_value_changed())
|
||||||
self.widgets[tuple(path + [key])] = var
|
self.widgets[tuple(path + [key])] = var
|
||||||
widget_type = "text"
|
widget_type = "text"
|
||||||
@@ -468,7 +498,7 @@ class ConfigEditor:
|
|||||||
"model.focus_tool_use": "工具调用模型",
|
"model.focus_tool_use": "工具调用模型",
|
||||||
"model.focus_planner": "决策模型",
|
"model.focus_planner": "决策模型",
|
||||||
"model.focus_expressor": "表达器模型",
|
"model.focus_expressor": "表达器模型",
|
||||||
"model.focus_self_recognize": "自我识别模型"
|
"model.focus_self_recognize": "自我识别模型",
|
||||||
}
|
}
|
||||||
section_trans = self.translations.get("sections", {}).get(full_section_path, {})
|
section_trans = self.translations.get("sections", {}).get(full_section_path, {})
|
||||||
section_name = section_trans.get("name") or section_translations.get(full_section_path) or section
|
section_name = section_trans.get("name") or section_translations.get(full_section_path) or section
|
||||||
@@ -490,7 +520,7 @@ class ConfigEditor:
|
|||||||
else:
|
else:
|
||||||
desc_label = ttk.Label(section_frame, text=section_desc, foreground="gray", font=("微软雅黑", 10))
|
desc_label = ttk.Label(section_frame, text=section_desc, foreground="gray", font=("微软雅黑", 10))
|
||||||
desc_label.pack(side=tk.LEFT, padx=5)
|
desc_label.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
# 为每个配置项创建对应的控件
|
# 为每个配置项创建对应的控件
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
@@ -518,7 +548,7 @@ class ConfigEditor:
|
|||||||
|
|
||||||
section = self.tree.item(selection[0])["values"][0] # 使用values中的原始节名
|
section = self.tree.item(selection[0])["values"][0] # 使用values中的原始节名
|
||||||
self.current_section = section
|
self.current_section = section
|
||||||
|
|
||||||
# 清空编辑器
|
# 清空编辑器
|
||||||
for widget in self.content_frame.winfo_children():
|
for widget in self.content_frame.winfo_children():
|
||||||
widget.destroy()
|
widget.destroy()
|
||||||
@@ -557,7 +587,7 @@ class ConfigEditor:
|
|||||||
|
|
||||||
# 创建描述标签
|
# 创建描述标签
|
||||||
if setting.get("description"):
|
if setting.get("description"):
|
||||||
desc_label = ttk.Label(frame, text=setting['description'], foreground="gray", font=("微软雅黑", 10))
|
desc_label = ttk.Label(frame, text=setting["description"], foreground="gray", font=("微软雅黑", 10))
|
||||||
desc_label.pack(fill=tk.X, padx=5, pady=(0, 2))
|
desc_label.pack(fill=tk.X, padx=5, pady=(0, 2))
|
||||||
|
|
||||||
# 根据类型创建不同的控件
|
# 根据类型创建不同的控件
|
||||||
@@ -575,14 +605,14 @@ class ConfigEditor:
|
|||||||
value = str(value) if value is not None else ""
|
value = str(value) if value is not None else ""
|
||||||
var = tk.StringVar(value=value)
|
var = tk.StringVar(value=value)
|
||||||
entry = ttk.Entry(frame, textvariable=var, width=40, font=("微软雅黑", 12))
|
entry = ttk.Entry(frame, textvariable=var, width=40, font=("微软雅黑", 12))
|
||||||
entry.pack(fill=tk.X, padx=5, pady=(0,5))
|
entry.pack(fill=tk.X, padx=5, pady=(0, 5))
|
||||||
var.trace_add("write", lambda *args, p=path, v=var: self.on_quick_setting_changed(p, v))
|
var.trace_add("write", lambda *args, p=path, v=var: self.on_quick_setting_changed(p, v))
|
||||||
|
|
||||||
elif setting_type == "number":
|
elif setting_type == "number":
|
||||||
value = str(value) if value is not None else "0"
|
value = str(value) if value is not None else "0"
|
||||||
var = tk.StringVar(value=value)
|
var = tk.StringVar(value=value)
|
||||||
entry = ttk.Entry(frame, textvariable=var, width=10, font=("微软雅黑", 12))
|
entry = ttk.Entry(frame, textvariable=var, width=10, font=("微软雅黑", 12))
|
||||||
entry.pack(fill=tk.X, padx=5, pady=(0,5))
|
entry.pack(fill=tk.X, padx=5, pady=(0, 5))
|
||||||
var.trace_add("write", lambda *args, p=path, v=var: self.on_quick_setting_changed(p, v))
|
var.trace_add("write", lambda *args, p=path, v=var: self.on_quick_setting_changed(p, v))
|
||||||
|
|
||||||
elif setting_type == "list":
|
elif setting_type == "list":
|
||||||
@@ -659,7 +689,7 @@ class ConfigEditor:
|
|||||||
# 获取所有控件的值
|
# 获取所有控件的值
|
||||||
for path, widget in self.widgets.items():
|
for path, widget in self.widgets.items():
|
||||||
# 跳过 env_vars 的控件赋值(只用于.env,不写回config)
|
# 跳过 env_vars 的控件赋值(只用于.env,不写回config)
|
||||||
if len(path) >= 2 and path[0] == 'env_vars':
|
if len(path) >= 2 and path[0] == "env_vars":
|
||||||
continue
|
continue
|
||||||
value = self.get_widget_value(widget)
|
value = self.get_widget_value(widget)
|
||||||
current = self.config
|
current = self.config
|
||||||
@@ -669,11 +699,11 @@ class ConfigEditor:
|
|||||||
current[final_key] = value
|
current[final_key] = value
|
||||||
|
|
||||||
# === 只保存 TOML,不包含 env_vars ===
|
# === 只保存 TOML,不包含 env_vars ===
|
||||||
env_vars = self.config.pop('env_vars', None)
|
env_vars = self.config.pop("env_vars", None)
|
||||||
with open(self.config_path, "wb") as f:
|
with open(self.config_path, "wb") as f:
|
||||||
tomli_w.dump(self.config, f)
|
tomli_w.dump(self.config, f)
|
||||||
if env_vars is not None:
|
if env_vars is not None:
|
||||||
self.config['env_vars'] = env_vars
|
self.config["env_vars"] = env_vars
|
||||||
|
|
||||||
# === 保存 env_vars 到 .env 文件(只覆盖特定key,其他内容保留) ===
|
# === 保存 env_vars 到 .env 文件(只覆盖特定key,其他内容保留) ===
|
||||||
env_path = self.editor_config["config"].get("env_file", ".env")
|
env_path = self.editor_config["config"].get("env_file", ".env")
|
||||||
@@ -687,7 +717,7 @@ class ConfigEditor:
|
|||||||
# 2. 收集所有目标key的新值(直接从widgets取)
|
# 2. 收集所有目标key的新值(直接从widgets取)
|
||||||
new_env_dict = {}
|
new_env_dict = {}
|
||||||
for path, widget in self.widgets.items():
|
for path, widget in self.widgets.items():
|
||||||
if len(path) == 2 and path[0] == 'env_vars':
|
if len(path) == 2 and path[0] == "env_vars":
|
||||||
k = path[1]
|
k = path[1]
|
||||||
if k.endswith("_BASE_URL") or k.endswith("_KEY"):
|
if k.endswith("_BASE_URL") or k.endswith("_KEY"):
|
||||||
new_env_dict[k] = self.get_widget_value(widget)
|
new_env_dict[k] = self.get_widget_value(widget)
|
||||||
@@ -715,15 +745,15 @@ class ConfigEditor:
|
|||||||
|
|
||||||
# === 保存完 .env 后,同步 widgets 的值回 self.config['env_vars'] ===
|
# === 保存完 .env 后,同步 widgets 的值回 self.config['env_vars'] ===
|
||||||
for path, widget in self.widgets.items():
|
for path, widget in self.widgets.items():
|
||||||
if len(path) == 2 and path[0] == 'env_vars':
|
if len(path) == 2 and path[0] == "env_vars":
|
||||||
prefix_key = path[1]
|
prefix_key = path[1]
|
||||||
if prefix_key.endswith("_BASE_URL") or prefix_key.endswith("_KEY"):
|
if prefix_key.endswith("_BASE_URL") or prefix_key.endswith("_KEY"):
|
||||||
prefix = prefix_key[:-9] if prefix_key.endswith("_BASE_URL") else prefix_key[:-4]
|
prefix = prefix_key[:-9] if prefix_key.endswith("_BASE_URL") else prefix_key[:-4]
|
||||||
if 'env_vars' not in self.config:
|
if "env_vars" not in self.config:
|
||||||
self.config['env_vars'] = {}
|
self.config["env_vars"] = {}
|
||||||
if prefix not in self.config['env_vars']:
|
if prefix not in self.config["env_vars"]:
|
||||||
self.config['env_vars'][prefix] = {}
|
self.config["env_vars"][prefix] = {}
|
||||||
self.config['env_vars'][prefix][prefix_key] = self.get_widget_value(widget)
|
self.config["env_vars"][prefix][prefix_key] = self.get_widget_value(widget)
|
||||||
|
|
||||||
self.last_save_time = time.time()
|
self.last_save_time = time.time()
|
||||||
self.pending_save = False
|
self.pending_save = False
|
||||||
@@ -862,62 +892,60 @@ class ConfigEditor:
|
|||||||
"""创建环境变量组"""
|
"""创建环境变量组"""
|
||||||
frame = ttk.Frame(parent)
|
frame = ttk.Frame(parent)
|
||||||
frame.pack(fill=tk.X, padx=5, pady=2)
|
frame.pack(fill=tk.X, padx=5, pady=2)
|
||||||
|
|
||||||
# 创建组标题
|
# 创建组标题
|
||||||
title_frame = ttk.Frame(frame)
|
title_frame = ttk.Frame(frame)
|
||||||
title_frame.pack(fill=tk.X, pady=(5, 0))
|
title_frame.pack(fill=tk.X, pady=(5, 0))
|
||||||
|
|
||||||
title_label = ttk.Label(title_frame, text=f"API配置组: {prefix}", font=("微软雅黑", 16, "bold"))
|
title_label = ttk.Label(title_frame, text=f"API配置组: {prefix}", font=("微软雅黑", 16, "bold"))
|
||||||
title_label.pack(side=tk.LEFT, padx=5)
|
title_label.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
# 删除按钮
|
# 删除按钮
|
||||||
del_button = ttk.Button(title_frame, text="删除组",
|
del_button = ttk.Button(title_frame, text="删除组", command=lambda: self.delete_env_var_group(prefix))
|
||||||
command=lambda: self.delete_env_var_group(prefix))
|
|
||||||
del_button.pack(side=tk.RIGHT, padx=5)
|
del_button.pack(side=tk.RIGHT, padx=5)
|
||||||
|
|
||||||
# 创建BASE_URL输入框
|
# 创建BASE_URL输入框
|
||||||
base_url_frame = ttk.Frame(frame)
|
base_url_frame = ttk.Frame(frame)
|
||||||
base_url_frame.pack(fill=tk.X, padx=5, pady=2)
|
base_url_frame.pack(fill=tk.X, padx=5, pady=2)
|
||||||
|
|
||||||
base_url_label = ttk.Label(base_url_frame, text="BASE_URL:", font=("微软雅黑", 12))
|
base_url_label = ttk.Label(base_url_frame, text="BASE_URL:", font=("微软雅黑", 12))
|
||||||
base_url_label.pack(side=tk.LEFT, padx=5)
|
base_url_label.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
base_url_var = tk.StringVar(value=values.get(f"{prefix}_BASE_URL", ""))
|
base_url_var = tk.StringVar(value=values.get(f"{prefix}_BASE_URL", ""))
|
||||||
base_url_entry = ttk.Entry(base_url_frame, textvariable=base_url_var, font=("微软雅黑", 12))
|
base_url_entry = ttk.Entry(base_url_frame, textvariable=base_url_var, font=("微软雅黑", 12))
|
||||||
base_url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
base_url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
||||||
base_url_var.trace_add("write", lambda *args: self.on_value_changed())
|
base_url_var.trace_add("write", lambda *args: self.on_value_changed())
|
||||||
|
|
||||||
# 创建KEY输入框
|
# 创建KEY输入框
|
||||||
key_frame = ttk.Frame(frame)
|
key_frame = ttk.Frame(frame)
|
||||||
key_frame.pack(fill=tk.X, padx=5, pady=2)
|
key_frame.pack(fill=tk.X, padx=5, pady=2)
|
||||||
|
|
||||||
key_label = ttk.Label(key_frame, text="API KEY:", font=("微软雅黑", 12))
|
key_label = ttk.Label(key_frame, text="API KEY:", font=("微软雅黑", 12))
|
||||||
key_label.pack(side=tk.LEFT, padx=5)
|
key_label.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
key_var = tk.StringVar(value=values.get(f"{prefix}_KEY", ""))
|
key_var = tk.StringVar(value=values.get(f"{prefix}_KEY", ""))
|
||||||
key_entry = ttk.Entry(key_frame, textvariable=key_var, font=("微软雅黑", 12))
|
key_entry = ttk.Entry(key_frame, textvariable=key_var, font=("微软雅黑", 12))
|
||||||
key_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
key_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
||||||
key_var.trace_add("write", lambda *args: self.on_value_changed())
|
key_var.trace_add("write", lambda *args: self.on_value_changed())
|
||||||
|
|
||||||
# 存储变量引用
|
# 存储变量引用
|
||||||
self.widgets[tuple(path + [f"{prefix}_BASE_URL"])] = base_url_var
|
self.widgets[tuple(path + [f"{prefix}_BASE_URL"])] = base_url_var
|
||||||
self.widgets[tuple(path + [f"{prefix}_KEY"])] = key_var
|
self.widgets[tuple(path + [f"{prefix}_KEY"])] = key_var
|
||||||
|
|
||||||
# 添加分隔线
|
# 添加分隔线
|
||||||
separator = ttk.Separator(frame, orient='horizontal')
|
separator = ttk.Separator(frame, orient="horizontal")
|
||||||
separator.pack(fill=tk.X, pady=5)
|
separator.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
def create_env_vars_section(self, parent: ttk.Frame) -> None:
|
def create_env_vars_section(self, parent: ttk.Frame) -> None:
|
||||||
"""创建环境变量编辑区"""
|
"""创建环境变量编辑区"""
|
||||||
# 创建添加新组的按钮
|
# 创建添加新组的按钮
|
||||||
add_button = ttk.Button(parent, text="添加新的API配置组",
|
add_button = ttk.Button(parent, text="添加新的API配置组", command=self.add_new_env_var_group)
|
||||||
command=self.add_new_env_var_group)
|
|
||||||
add_button.pack(pady=10)
|
add_button.pack(pady=10)
|
||||||
|
|
||||||
# 创建现有组的编辑区
|
# 创建现有组的编辑区
|
||||||
if 'env_vars' in self.config:
|
if "env_vars" in self.config:
|
||||||
for prefix, values in self.config['env_vars'].items():
|
for prefix, values in self.config["env_vars"].items():
|
||||||
self.create_env_var_group(parent, prefix, values, ['env_vars'])
|
self.create_env_var_group(parent, prefix, values, ["env_vars"])
|
||||||
|
|
||||||
def add_new_env_var_group(self):
|
def add_new_env_var_group(self):
|
||||||
"""添加新的环境变量组"""
|
"""添加新的环境变量组"""
|
||||||
@@ -925,42 +953,39 @@ class ConfigEditor:
|
|||||||
dialog = tk.Toplevel(self.root)
|
dialog = tk.Toplevel(self.root)
|
||||||
dialog.title("添加新的API配置组")
|
dialog.title("添加新的API配置组")
|
||||||
dialog.geometry("400x200")
|
dialog.geometry("400x200")
|
||||||
|
|
||||||
# 创建输入框架
|
# 创建输入框架
|
||||||
frame = ttk.Frame(dialog, padding="10")
|
frame = ttk.Frame(dialog, padding="10")
|
||||||
frame.pack(fill=tk.BOTH, expand=True)
|
frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
# 前缀输入
|
# 前缀输入
|
||||||
prefix_label = ttk.Label(frame, text="API前缀名称:", font=("微软雅黑", 12))
|
prefix_label = ttk.Label(frame, text="API前缀名称:", font=("微软雅黑", 12))
|
||||||
prefix_label.pack(pady=5)
|
prefix_label.pack(pady=5)
|
||||||
|
|
||||||
prefix_var = tk.StringVar()
|
prefix_var = tk.StringVar()
|
||||||
prefix_entry = ttk.Entry(frame, textvariable=prefix_var, font=("微软雅黑", 12))
|
prefix_entry = ttk.Entry(frame, textvariable=prefix_var, font=("微软雅黑", 12))
|
||||||
prefix_entry.pack(fill=tk.X, pady=5)
|
prefix_entry.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
# 确认按钮
|
# 确认按钮
|
||||||
def on_confirm():
|
def on_confirm():
|
||||||
prefix = prefix_var.get().strip()
|
prefix = prefix_var.get().strip()
|
||||||
if prefix:
|
if prefix:
|
||||||
if 'env_vars' not in self.config:
|
if "env_vars" not in self.config:
|
||||||
self.config['env_vars'] = {}
|
self.config["env_vars"] = {}
|
||||||
self.config['env_vars'][prefix] = {
|
self.config["env_vars"][prefix] = {f"{prefix}_BASE_URL": "", f"{prefix}_KEY": ""}
|
||||||
f"{prefix}_BASE_URL": "",
|
|
||||||
f"{prefix}_KEY": ""
|
|
||||||
}
|
|
||||||
# 刷新显示
|
# 刷新显示
|
||||||
self.refresh_env_vars_section()
|
self.refresh_env_vars_section()
|
||||||
self.on_value_changed()
|
self.on_value_changed()
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
|
|
||||||
confirm_button = ttk.Button(frame, text="确认", command=on_confirm)
|
confirm_button = ttk.Button(frame, text="确认", command=on_confirm)
|
||||||
confirm_button.pack(pady=10)
|
confirm_button.pack(pady=10)
|
||||||
|
|
||||||
def delete_env_var_group(self, prefix: str):
|
def delete_env_var_group(self, prefix: str):
|
||||||
"""删除环境变量组"""
|
"""删除环境变量组"""
|
||||||
if messagebox.askyesno("确认", f"确定要删除 {prefix} 配置组吗?"):
|
if messagebox.askyesno("确认", f"确定要删除 {prefix} 配置组吗?"):
|
||||||
if 'env_vars' in self.config:
|
if "env_vars" in self.config:
|
||||||
del self.config['env_vars'][prefix]
|
del self.config["env_vars"][prefix]
|
||||||
# 刷新显示
|
# 刷新显示
|
||||||
self.refresh_env_vars_section()
|
self.refresh_env_vars_section()
|
||||||
self.on_value_changed()
|
self.on_value_changed()
|
||||||
@@ -971,7 +996,7 @@ class ConfigEditor:
|
|||||||
for widget in self.content_frame.winfo_children():
|
for widget in self.content_frame.winfo_children():
|
||||||
widget.destroy()
|
widget.destroy()
|
||||||
self.widgets.clear()
|
self.widgets.clear()
|
||||||
|
|
||||||
# 重新创建编辑区
|
# 重新创建编辑区
|
||||||
self.create_env_vars_section(self.content_frame)
|
self.create_env_vars_section(self.content_frame)
|
||||||
|
|
||||||
@@ -980,10 +1005,10 @@ class ConfigEditor:
|
|||||||
dialog = tk.Toplevel(self.root)
|
dialog = tk.Toplevel(self.root)
|
||||||
dialog.title("高级选项")
|
dialog.title("高级选项")
|
||||||
dialog.geometry("700x800")
|
dialog.geometry("700x800")
|
||||||
|
|
||||||
notebook = ttk.Notebook(dialog)
|
notebook = ttk.Notebook(dialog)
|
||||||
notebook.pack(fill=tk.BOTH, expand=True)
|
notebook.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
# 遥测栏
|
# 遥测栏
|
||||||
if "telemetry" in self.config:
|
if "telemetry" in self.config:
|
||||||
telemetry_frame = ttk.Frame(notebook)
|
telemetry_frame = ttk.Frame(notebook)
|
||||||
@@ -1003,7 +1028,9 @@ class ConfigEditor:
|
|||||||
if "message_receive" in self.config:
|
if "message_receive" in self.config:
|
||||||
recv_frame = ttk.Frame(notebook)
|
recv_frame = ttk.Frame(notebook)
|
||||||
notebook.add(recv_frame, text="消息接收")
|
notebook.add(recv_frame, text="消息接收")
|
||||||
self.create_section_widgets(recv_frame, "message_receive", self.config["message_receive"], ["message_receive"])
|
self.create_section_widgets(
|
||||||
|
recv_frame, "message_receive", self.config["message_receive"], ["message_receive"]
|
||||||
|
)
|
||||||
# 关系栏
|
# 关系栏
|
||||||
if "relationship" in self.config:
|
if "relationship" in self.config:
|
||||||
rel_frame = ttk.Frame(notebook)
|
rel_frame = ttk.Frame(notebook)
|
||||||
@@ -1015,96 +1042,95 @@ class ConfigEditor:
|
|||||||
dialog = tk.Toplevel(self.root)
|
dialog = tk.Toplevel(self.root)
|
||||||
dialog.title("配置路径")
|
dialog.title("配置路径")
|
||||||
dialog.geometry("600x200")
|
dialog.geometry("600x200")
|
||||||
|
|
||||||
# 创建输入框架
|
# 创建输入框架
|
||||||
frame = ttk.Frame(dialog, padding="10")
|
frame = ttk.Frame(dialog, padding="10")
|
||||||
frame.pack(fill=tk.BOTH, expand=True)
|
frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
# bot_config.toml路径配置
|
# bot_config.toml路径配置
|
||||||
bot_config_frame = ttk.Frame(frame)
|
bot_config_frame = ttk.Frame(frame)
|
||||||
bot_config_frame.pack(fill=tk.X, pady=5)
|
bot_config_frame.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
bot_config_label = ttk.Label(bot_config_frame, text="bot_config.toml路径:", font=("微软雅黑", 12))
|
bot_config_label = ttk.Label(bot_config_frame, text="bot_config.toml路径:", font=("微软雅黑", 12))
|
||||||
bot_config_label.pack(side=tk.LEFT, padx=5)
|
bot_config_label.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
bot_config_var = tk.StringVar(value=self.config_path)
|
bot_config_var = tk.StringVar(value=self.config_path)
|
||||||
bot_config_entry = ttk.Entry(bot_config_frame, textvariable=bot_config_var, font=("微软雅黑", 12))
|
bot_config_entry = ttk.Entry(bot_config_frame, textvariable=bot_config_var, font=("微软雅黑", 12))
|
||||||
bot_config_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
bot_config_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
||||||
|
|
||||||
def apply_config():
|
def apply_config():
|
||||||
new_bot_config_path = bot_config_var.get().strip()
|
new_bot_config_path = bot_config_var.get().strip()
|
||||||
new_env_path = env_var.get().strip()
|
new_env_path = env_var.get().strip()
|
||||||
|
|
||||||
if not new_bot_config_path or not new_env_path:
|
if not new_bot_config_path or not new_env_path:
|
||||||
messagebox.showerror("错误", "路径不能为空")
|
messagebox.showerror("错误", "路径不能为空")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not os.path.exists(new_bot_config_path):
|
if not os.path.exists(new_bot_config_path):
|
||||||
messagebox.showerror("错误", "bot_config.toml文件不存在")
|
messagebox.showerror("错误", "bot_config.toml文件不存在")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 更新配置
|
# 更新配置
|
||||||
self.config_path = new_bot_config_path
|
self.config_path = new_bot_config_path
|
||||||
self.editor_config["config"]["bot_config_path"] = new_bot_config_path
|
self.editor_config["config"]["bot_config_path"] = new_bot_config_path
|
||||||
self.editor_config["config"]["env_file"] = new_env_path
|
self.editor_config["config"]["env_file"] = new_env_path
|
||||||
|
|
||||||
# 保存编辑器配置
|
# 保存编辑器配置
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "configexe.toml")
|
config_path = os.path.join(os.path.dirname(__file__), "configexe.toml")
|
||||||
with open(config_path, "wb") as f:
|
with open(config_path, "wb") as f:
|
||||||
tomli_w.dump(self.editor_config, f)
|
tomli_w.dump(self.editor_config, f)
|
||||||
|
|
||||||
# 重新加载配置
|
# 重新加载配置
|
||||||
self.load_config()
|
self.load_config()
|
||||||
self.load_env_vars()
|
self.load_env_vars()
|
||||||
|
|
||||||
# 刷新显示
|
# 刷新显示
|
||||||
self.refresh_config()
|
self.refresh_config()
|
||||||
|
|
||||||
messagebox.showinfo("成功", "路径配置已更新,程序将重新启动")
|
messagebox.showinfo("成功", "路径配置已更新,程序将重新启动")
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
|
|
||||||
# 重启程序
|
# 重启程序
|
||||||
self.root.quit()
|
self.root.quit()
|
||||||
os.execv(sys.executable, ['python'] + sys.argv)
|
os.execv(sys.executable, ["python"] + sys.argv)
|
||||||
|
|
||||||
def browse_bot_config():
|
def browse_bot_config():
|
||||||
file_path = filedialog.askopenfilename(
|
file_path = filedialog.askopenfilename(
|
||||||
title="选择bot_config.toml文件",
|
title="选择bot_config.toml文件", filetypes=[("TOML文件", "*.toml"), ("所有文件", "*.*")]
|
||||||
filetypes=[("TOML文件", "*.toml"), ("所有文件", "*.*")]
|
|
||||||
)
|
)
|
||||||
if file_path:
|
if file_path:
|
||||||
bot_config_var.set(file_path)
|
bot_config_var.set(file_path)
|
||||||
apply_config()
|
apply_config()
|
||||||
|
|
||||||
browse_bot_config_btn = ttk.Button(bot_config_frame, text="浏览", command=browse_bot_config)
|
browse_bot_config_btn = ttk.Button(bot_config_frame, text="浏览", command=browse_bot_config)
|
||||||
browse_bot_config_btn.pack(side=tk.LEFT, padx=5)
|
browse_bot_config_btn.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
# .env路径配置
|
# .env路径配置
|
||||||
env_frame = ttk.Frame(frame)
|
env_frame = ttk.Frame(frame)
|
||||||
env_frame.pack(fill=tk.X, pady=5)
|
env_frame.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
env_label = ttk.Label(env_frame, text=".env路径:", font=("微软雅黑", 12))
|
env_label = ttk.Label(env_frame, text=".env路径:", font=("微软雅黑", 12))
|
||||||
env_label.pack(side=tk.LEFT, padx=5)
|
env_label.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
env_path = self.editor_config["config"].get("env_file", ".env")
|
env_path = self.editor_config["config"].get("env_file", ".env")
|
||||||
if not os.path.isabs(env_path):
|
if not os.path.isabs(env_path):
|
||||||
env_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), env_path)
|
env_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), env_path)
|
||||||
env_var = tk.StringVar(value=env_path)
|
env_var = tk.StringVar(value=env_path)
|
||||||
env_entry = ttk.Entry(env_frame, textvariable=env_var, font=("微软雅黑", 12))
|
env_entry = ttk.Entry(env_frame, textvariable=env_var, font=("微软雅黑", 12))
|
||||||
env_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
env_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
||||||
|
|
||||||
def browse_env():
|
def browse_env():
|
||||||
file_path = filedialog.askopenfilename(
|
file_path = filedialog.askopenfilename(
|
||||||
title="选择.env文件",
|
title="选择.env文件", filetypes=[("环境变量文件", "*.env"), ("所有文件", "*.*")]
|
||||||
filetypes=[("环境变量文件", "*.env"), ("所有文件", "*.*")]
|
|
||||||
)
|
)
|
||||||
if file_path:
|
if file_path:
|
||||||
env_var.set(file_path)
|
env_var.set(file_path)
|
||||||
apply_config()
|
apply_config()
|
||||||
|
|
||||||
browse_env_btn = ttk.Button(env_frame, text="浏览", command=browse_env)
|
browse_env_btn = ttk.Button(env_frame, text="浏览", command=browse_env)
|
||||||
browse_env_btn.pack(side=tk.LEFT, padx=5)
|
browse_env_btn.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
_app = ConfigEditor(root)
|
_app = ConfigEditor(root)
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
from typing import List, Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
log_dir = "log/log_cycle_debug/"
|
log_dir = "log/log_cycle_debug/"
|
||||||
|
|
||||||
|
|
||||||
class CycleDetail:
|
class CycleDetail:
|
||||||
"""循环信息记录类"""
|
"""循环信息记录类"""
|
||||||
|
|
||||||
@@ -23,35 +24,40 @@ class CycleDetail:
|
|||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
"""将循环信息转换为字典格式"""
|
"""将循环信息转换为字典格式"""
|
||||||
|
|
||||||
def convert_to_serializable(obj, depth=0, seen=None):
|
def convert_to_serializable(obj, depth=0, seen=None):
|
||||||
if seen is None:
|
if seen is None:
|
||||||
seen = set()
|
seen = set()
|
||||||
|
|
||||||
# 防止递归过深
|
# 防止递归过深
|
||||||
if depth > 5: # 降低递归深度限制
|
if depth > 5: # 降低递归深度限制
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
|
||||||
# 防止循环引用
|
# 防止循环引用
|
||||||
obj_id = id(obj)
|
obj_id = id(obj)
|
||||||
if obj_id in seen:
|
if obj_id in seen:
|
||||||
return str(obj)
|
return str(obj)
|
||||||
seen.add(obj_id)
|
seen.add(obj_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if hasattr(obj, 'to_dict'):
|
if hasattr(obj, "to_dict"):
|
||||||
# 对于有to_dict方法的对象,直接调用其to_dict方法
|
# 对于有to_dict方法的对象,直接调用其to_dict方法
|
||||||
return obj.to_dict()
|
return obj.to_dict()
|
||||||
elif isinstance(obj, dict):
|
elif isinstance(obj, dict):
|
||||||
# 对于字典,只保留基本类型和可序列化的值
|
# 对于字典,只保留基本类型和可序列化的值
|
||||||
return {k: convert_to_serializable(v, depth + 1, seen)
|
return {
|
||||||
for k, v in obj.items()
|
k: convert_to_serializable(v, depth + 1, seen)
|
||||||
if isinstance(k, (str, int, float, bool))}
|
for k, v in obj.items()
|
||||||
|
if isinstance(k, (str, int, float, bool))
|
||||||
|
}
|
||||||
elif isinstance(obj, (list, tuple)):
|
elif isinstance(obj, (list, tuple)):
|
||||||
# 对于列表和元组,只保留可序列化的元素
|
# 对于列表和元组,只保留可序列化的元素
|
||||||
return [convert_to_serializable(item, depth + 1, seen)
|
return [
|
||||||
for item in obj
|
convert_to_serializable(item, depth + 1, seen)
|
||||||
if not isinstance(item, (dict, list, tuple)) or
|
for item in obj
|
||||||
isinstance(item, (str, int, float, bool, type(None)))]
|
if not isinstance(item, (dict, list, tuple))
|
||||||
|
or isinstance(item, (str, int, float, bool, type(None)))
|
||||||
|
]
|
||||||
elif isinstance(obj, (str, int, float, bool, type(None))):
|
elif isinstance(obj, (str, int, float, bool, type(None))):
|
||||||
return obj
|
return obj
|
||||||
else:
|
else:
|
||||||
@@ -74,19 +80,19 @@ class CycleDetail:
|
|||||||
def complete_cycle(self):
|
def complete_cycle(self):
|
||||||
"""完成循环,记录结束时间"""
|
"""完成循环,记录结束时间"""
|
||||||
self.end_time = time.time()
|
self.end_time = time.time()
|
||||||
|
|
||||||
# 处理 prefix,只保留中英文字符
|
# 处理 prefix,只保留中英文字符
|
||||||
if not self.prefix:
|
if not self.prefix:
|
||||||
self.prefix = "group"
|
self.prefix = "group"
|
||||||
else:
|
else:
|
||||||
# 只保留中文和英文字符
|
# 只保留中文和英文字符
|
||||||
self.prefix = ''.join(char for char in self.prefix if '\u4e00' <= char <= '\u9fff' or char.isascii())
|
self.prefix = "".join(char for char in self.prefix if "\u4e00" <= char <= "\u9fff" or char.isascii())
|
||||||
if not self.prefix:
|
if not self.prefix:
|
||||||
self.prefix = "group"
|
self.prefix = "group"
|
||||||
|
|
||||||
current_time_minute = time.strftime("%Y%m%d_%H%M", time.localtime())
|
current_time_minute = time.strftime("%Y%m%d_%H%M", time.localtime())
|
||||||
self.log_cycle_to_file(log_dir + self.prefix + f"/{current_time_minute}_cycle_" + str(self.cycle_id) + ".json")
|
self.log_cycle_to_file(log_dir + self.prefix + f"/{current_time_minute}_cycle_" + str(self.cycle_id) + ".json")
|
||||||
|
|
||||||
def log_cycle_to_file(self, file_path: str):
|
def log_cycle_to_file(self, file_path: str):
|
||||||
"""将循环信息写入文件"""
|
"""将循环信息写入文件"""
|
||||||
# 如果目录不存在,则创建目录
|
# 如果目录不存在,则创建目录
|
||||||
@@ -95,6 +101,7 @@ class CycleDetail:
|
|||||||
os.makedirs(dir_name, exist_ok=True)
|
os.makedirs(dir_name, exist_ok=True)
|
||||||
# 写入文件
|
# 写入文件
|
||||||
import json
|
import json
|
||||||
|
|
||||||
with open(file_path, "a", encoding="utf-8") as f:
|
with open(file_path, "a", encoding="utf-8") as f:
|
||||||
f.write(json.dumps(self.to_dict(), ensure_ascii=False) + "\n")
|
f.write(json.dumps(self.to_dict(), ensure_ascii=False) + "\n")
|
||||||
|
|
||||||
|
|||||||
@@ -418,7 +418,9 @@ class HeartFChatting:
|
|||||||
# 记录耗时
|
# 记录耗时
|
||||||
processor_time_costs[processor_name] = duration_since_parallel_start
|
processor_time_costs[processor_name] = duration_since_parallel_start
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.info(f"{self.log_prefix} 处理器 {processor_name} 超时(>{global_config.focus_chat.processor_max_time}s),已跳过")
|
logger.info(
|
||||||
|
f"{self.log_prefix} 处理器 {processor_name} 超时(>{global_config.focus_chat.processor_max_time}s),已跳过"
|
||||||
|
)
|
||||||
processor_time_costs[processor_name] = global_config.focus_chat.processor_max_time
|
processor_time_costs[processor_name] = global_config.focus_chat.processor_max_time
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -462,7 +464,7 @@ class HeartFChatting:
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.all_observations = observations
|
self.all_observations = observations
|
||||||
|
|
||||||
with Timer("调整动作", cycle_timers):
|
with Timer("调整动作", cycle_timers):
|
||||||
# 处理特殊的观察
|
# 处理特殊的观察
|
||||||
await self.action_modifier.modify_actions(observations=observations)
|
await self.action_modifier.modify_actions(observations=observations)
|
||||||
@@ -476,26 +478,24 @@ class HeartFChatting:
|
|||||||
with Timer("并行回忆和处理", cycle_timers):
|
with Timer("并行回忆和处理", cycle_timers):
|
||||||
memory_task = asyncio.create_task(self.memory_activator.activate_memory(observations))
|
memory_task = asyncio.create_task(self.memory_activator.activate_memory(observations))
|
||||||
processor_task = asyncio.create_task(self._process_processors(observations, []))
|
processor_task = asyncio.create_task(self._process_processors(observations, []))
|
||||||
|
|
||||||
# 等待两个任务完成
|
# 等待两个任务完成
|
||||||
running_memorys, (all_plan_info, processor_time_costs) = await asyncio.gather(memory_task, processor_task)
|
running_memorys, (all_plan_info, processor_time_costs) = await asyncio.gather(
|
||||||
|
memory_task, processor_task
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# 串行执行
|
# 串行执行
|
||||||
with Timer("回忆", cycle_timers):
|
with Timer("回忆", cycle_timers):
|
||||||
running_memorys = await self.memory_activator.activate_memory(observations)
|
running_memorys = await self.memory_activator.activate_memory(observations)
|
||||||
|
|
||||||
with Timer("执行 信息处理器", cycle_timers):
|
with Timer("执行 信息处理器", cycle_timers):
|
||||||
all_plan_info, processor_time_costs = await self._process_processors(
|
all_plan_info, processor_time_costs = await self._process_processors(observations, running_memorys)
|
||||||
observations, running_memorys
|
|
||||||
)
|
|
||||||
|
|
||||||
loop_processor_info = {
|
loop_processor_info = {
|
||||||
"all_plan_info": all_plan_info,
|
"all_plan_info": all_plan_info,
|
||||||
"processor_time_costs": processor_time_costs,
|
"processor_time_costs": processor_time_costs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
with Timer("规划器", cycle_timers):
|
with Timer("规划器", cycle_timers):
|
||||||
plan_result = await self.action_planner.plan(all_plan_info, running_memorys)
|
plan_result = await self.action_planner.plan(all_plan_info, running_memorys)
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class ActionModifier:
|
|||||||
observations: Optional[List[Observation]] = None,
|
observations: Optional[List[Observation]] = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
):
|
):
|
||||||
|
|
||||||
# 处理Observation对象
|
# 处理Observation对象
|
||||||
if observations:
|
if observations:
|
||||||
# action_info = ActionInfo()
|
# action_info = ActionInfo()
|
||||||
@@ -163,22 +162,34 @@ class ActionModifier:
|
|||||||
if len(last_max_reply_num) >= max_reply_num and all(last_max_reply_num):
|
if len(last_max_reply_num) >= max_reply_num and all(last_max_reply_num):
|
||||||
# 如果最近max_reply_num次都是reply,直接移除
|
# 如果最近max_reply_num次都是reply,直接移除
|
||||||
result["remove"].append("reply")
|
result["remove"].append("reply")
|
||||||
logger.info(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,直接移除")
|
logger.info(
|
||||||
|
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,直接移除"
|
||||||
|
)
|
||||||
elif len(last_max_reply_num) >= sec_thres_reply_num and all(last_max_reply_num[-sec_thres_reply_num:]):
|
elif len(last_max_reply_num) >= sec_thres_reply_num and all(last_max_reply_num[-sec_thres_reply_num:]):
|
||||||
# 如果最近sec_thres_reply_num次都是reply,40%概率移除
|
# 如果最近sec_thres_reply_num次都是reply,40%概率移除
|
||||||
if random.random() < 0.4 / global_config.focus_chat.consecutive_replies:
|
if random.random() < 0.4 / global_config.focus_chat.consecutive_replies:
|
||||||
result["remove"].append("reply")
|
result["remove"].append("reply")
|
||||||
logger.info(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,{0.4 / global_config.focus_chat.consecutive_replies}概率移除,移除")
|
logger.info(
|
||||||
|
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,{0.4 / global_config.focus_chat.consecutive_replies}概率移除,移除"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,{0.4 / global_config.focus_chat.consecutive_replies}概率移除,不移除")
|
logger.debug(
|
||||||
|
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,{0.4 / global_config.focus_chat.consecutive_replies}概率移除,不移除"
|
||||||
|
)
|
||||||
elif len(last_max_reply_num) >= one_thres_reply_num and all(last_max_reply_num[-one_thres_reply_num:]):
|
elif len(last_max_reply_num) >= one_thres_reply_num and all(last_max_reply_num[-one_thres_reply_num:]):
|
||||||
# 如果最近one_thres_reply_num次都是reply,20%概率移除
|
# 如果最近one_thres_reply_num次都是reply,20%概率移除
|
||||||
if random.random() < 0.2 / global_config.focus_chat.consecutive_replies:
|
if random.random() < 0.2 / global_config.focus_chat.consecutive_replies:
|
||||||
result["remove"].append("reply")
|
result["remove"].append("reply")
|
||||||
logger.info(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,{0.2 / global_config.focus_chat.consecutive_replies}概率移除,移除")
|
logger.info(
|
||||||
|
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,{0.2 / global_config.focus_chat.consecutive_replies}概率移除,移除"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,{0.2 / global_config.focus_chat.consecutive_replies}概率移除,不移除")
|
logger.debug(
|
||||||
|
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,{0.2 / global_config.focus_chat.consecutive_replies}概率移除,不移除"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,无需移除")
|
logger.debug(
|
||||||
|
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply,{len(last_max_reply_num) - no_reply_count}次reply,无需移除"
|
||||||
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -29,14 +29,6 @@ def init_prompt():
|
|||||||
{self_info_block}
|
{self_info_block}
|
||||||
{extra_info_block}
|
{extra_info_block}
|
||||||
{memory_str}
|
{memory_str}
|
||||||
你需要基于以下信息决定如何参与对话
|
|
||||||
这些信息可能会有冲突,请你整合这些信息,并选择一个最合适的action:
|
|
||||||
{chat_content_block}
|
|
||||||
|
|
||||||
{mind_info_block}
|
|
||||||
{cycle_info_block}
|
|
||||||
|
|
||||||
请综合分析聊天内容和你看到的新消息,参考聊天规划,选择合适的action:
|
|
||||||
注意,除了下面动作选项之外,你在群聊里不能做其他任何事情,这是你能力的边界,现在请你选择合适的action:
|
注意,除了下面动作选项之外,你在群聊里不能做其他任何事情,这是你能力的边界,现在请你选择合适的action:
|
||||||
|
|
||||||
{action_options_text}
|
{action_options_text}
|
||||||
@@ -46,6 +38,15 @@ def init_prompt():
|
|||||||
|
|
||||||
{moderation_prompt}
|
{moderation_prompt}
|
||||||
|
|
||||||
|
你需要基于以下信息决定如何参与对话
|
||||||
|
这些信息可能会有冲突,请你整合这些信息,并选择一个最合适的action:
|
||||||
|
{chat_content_block}
|
||||||
|
|
||||||
|
{mind_info_block}
|
||||||
|
{cycle_info_block}
|
||||||
|
|
||||||
|
请综合分析聊天内容和你看到的新消息,参考聊天规划,选择合适的action:
|
||||||
|
|
||||||
请你以下面格式输出你选择的action:
|
请你以下面格式输出你选择的action:
|
||||||
{{
|
{{
|
||||||
"action": "action_name",
|
"action": "action_name",
|
||||||
@@ -270,7 +271,6 @@ class ActionPlanner:
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""构建 Planner LLM 的提示词 (获取模板并填充数据)"""
|
"""构建 Planner LLM 的提示词 (获取模板并填充数据)"""
|
||||||
try:
|
try:
|
||||||
|
|
||||||
memory_str = ""
|
memory_str = ""
|
||||||
if global_config.focus_chat.parallel_processing:
|
if global_config.focus_chat.parallel_processing:
|
||||||
memory_str = ""
|
memory_str = ""
|
||||||
@@ -278,10 +278,7 @@ class ActionPlanner:
|
|||||||
memory_str = "以下是当前在聊天中,你回忆起的记忆:\n"
|
memory_str = "以下是当前在聊天中,你回忆起的记忆:\n"
|
||||||
for running_memory in running_memorys:
|
for running_memory in running_memorys:
|
||||||
memory_str += f"{running_memory['topic']}: {running_memory['content']}\n"
|
memory_str += f"{running_memory['topic']}: {running_memory['content']}\n"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
chat_context_description = "你现在正在一个群聊中"
|
chat_context_description = "你现在正在一个群聊中"
|
||||||
chat_target_name = None # Only relevant for private
|
chat_target_name = None # Only relevant for private
|
||||||
if not is_group_chat and chat_target_info:
|
if not is_group_chat and chat_target_info:
|
||||||
|
|||||||
@@ -42,5 +42,5 @@ class ActionObservation:
|
|||||||
"observe_id": self.observe_id,
|
"observe_id": self.observe_id,
|
||||||
"last_observe_time": self.last_observe_time,
|
"last_observe_time": self.last_observe_time,
|
||||||
"all_actions": self.all_actions,
|
"all_actions": self.all_actions,
|
||||||
"all_using_actions": self.all_using_actions
|
"all_using_actions": self.all_using_actions,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class ChattingObservation(Observation):
|
|||||||
"person_list": self.person_list,
|
"person_list": self.person_list,
|
||||||
"oldest_messages_str": self.oldest_messages_str,
|
"oldest_messages_str": self.oldest_messages_str,
|
||||||
"compressor_prompt": self.compressor_prompt,
|
"compressor_prompt": self.compressor_prompt,
|
||||||
"last_observe_time": self.last_observe_time
|
"last_observe_time": self.last_observe_time,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
@@ -89,6 +89,8 @@ class ChattingObservation(Observation):
|
|||||||
logger.debug(f"初始化observation: self.is_group_chat: {self.is_group_chat}")
|
logger.debug(f"初始化observation: self.is_group_chat: {self.is_group_chat}")
|
||||||
logger.debug(f"初始化observation: self.chat_target_info: {self.chat_target_info}")
|
logger.debug(f"初始化observation: self.chat_target_info: {self.chat_target_info}")
|
||||||
initial_messages = get_raw_msg_before_timestamp_with_chat(self.chat_id, self.last_observe_time, 10)
|
initial_messages = get_raw_msg_before_timestamp_with_chat(self.chat_id, self.last_observe_time, 10)
|
||||||
|
self.last_observe_time = initial_messages[-1]["time"] if initial_messages else self.last_observe_time
|
||||||
|
# logger.error(f"初始化observation: initial_messages: {initial_messages}\n\n\n\n{self.last_observe_time}")
|
||||||
self.talking_message = initial_messages
|
self.talking_message = initial_messages
|
||||||
self.talking_message_str = await build_readable_messages(self.talking_message)
|
self.talking_message_str = await build_readable_messages(self.talking_message)
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class HFCloopObservation:
|
|||||||
responses_for_prompt = []
|
responses_for_prompt = []
|
||||||
|
|
||||||
cycle_last_reason = ""
|
cycle_last_reason = ""
|
||||||
|
|
||||||
# 检查这最近的活动循环中有多少是连续的文本回复 (从最近的开始看)
|
# 检查这最近的活动循环中有多少是连续的文本回复 (从最近的开始看)
|
||||||
for cycle in recent_active_cycles:
|
for cycle in recent_active_cycles:
|
||||||
action_type = cycle.loop_plan_info["action_result"]["action_type"]
|
action_type = cycle.loop_plan_info["action_result"]["action_type"]
|
||||||
@@ -57,29 +57,33 @@ class HFCloopObservation:
|
|||||||
action_reasoning_str = f"你选择这个action的原因是:{action_reasoning}"
|
action_reasoning_str = f"你选择这个action的原因是:{action_reasoning}"
|
||||||
else:
|
else:
|
||||||
action_reasoning_str = ""
|
action_reasoning_str = ""
|
||||||
|
|
||||||
if action_type == "reply":
|
if action_type == "reply":
|
||||||
consecutive_text_replies += 1
|
consecutive_text_replies += 1
|
||||||
response_text = cycle.loop_plan_info["action_result"]["action_data"].get("text", "[空回复]")
|
response_text = cycle.loop_plan_info["action_result"]["action_data"].get("text", "[空回复]")
|
||||||
responses_for_prompt.append(response_text)
|
responses_for_prompt.append(response_text)
|
||||||
|
|
||||||
if is_taken:
|
if is_taken:
|
||||||
action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}')。{action_reasoning_str}\n"
|
action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}')。{action_reasoning_str}\n"
|
||||||
else:
|
else:
|
||||||
action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}'),但是动作失败了。{action_reasoning_str}\n"
|
action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}'),但是动作失败了。{action_reasoning_str}\n"
|
||||||
elif action_type == "no_reply":
|
elif action_type == "no_reply":
|
||||||
action_detailed_str += f"{action_taken_time_str}时,你选择不回复(action:{action_type}),{action_reasoning_str}\n"
|
action_detailed_str += (
|
||||||
|
f"{action_taken_time_str}时,你选择不回复(action:{action_type}),{action_reasoning_str}\n"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if is_taken:
|
if is_taken:
|
||||||
action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}),{action_reasoning_str}\n"
|
action_detailed_str += (
|
||||||
|
f"{action_taken_time_str}时,你选择执行了(action:{action_type}),{action_reasoning_str}\n"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}),但是动作失败了。{action_reasoning_str}\n"
|
action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}),但是动作失败了。{action_reasoning_str}\n"
|
||||||
|
|
||||||
if action_detailed_str:
|
if action_detailed_str:
|
||||||
cycle_info_block = f"\n你最近做的事:\n{action_detailed_str}\n"
|
cycle_info_block = f"\n你最近做的事:\n{action_detailed_str}\n"
|
||||||
else:
|
else:
|
||||||
cycle_info_block = "\n"
|
cycle_info_block = "\n"
|
||||||
|
|
||||||
# 根据连续文本回复的数量构建提示信息
|
# 根据连续文本回复的数量构建提示信息
|
||||||
if consecutive_text_replies >= 3: # 如果最近的三个活动都是文本回复
|
if consecutive_text_replies >= 3: # 如果最近的三个活动都是文本回复
|
||||||
cycle_info_block = f'你已经连续回复了三条消息(最近: "{responses_for_prompt[0]}",第二近: "{responses_for_prompt[1]}",第三近: "{responses_for_prompt[2]}")。你回复的有点多了,请注意'
|
cycle_info_block = f'你已经连续回复了三条消息(最近: "{responses_for_prompt[0]}",第二近: "{responses_for_prompt[1]}",第三近: "{responses_for_prompt[2]}")。你回复的有点多了,请注意'
|
||||||
@@ -116,5 +120,5 @@ class HFCloopObservation:
|
|||||||
"observe_id": self.observe_id,
|
"observe_id": self.observe_id,
|
||||||
"last_observe_time": self.last_observe_time,
|
"last_observe_time": self.last_observe_time,
|
||||||
# 不序列化history_loop,避免循环引用
|
# 不序列化history_loop,避免循环引用
|
||||||
"history_loop_count": len(self.history_loop)
|
"history_loop_count": len(self.history_loop),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class Observation:
|
|||||||
return {
|
return {
|
||||||
"observe_info": self.observe_info,
|
"observe_info": self.observe_info,
|
||||||
"observe_id": self.observe_id,
|
"observe_id": self.observe_id,
|
||||||
"last_observe_time": self.last_observe_time
|
"last_observe_time": self.last_observe_time,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def observe(self):
|
async def observe(self):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class StructureObservation:
|
|||||||
"observe_id": self.observe_id,
|
"observe_id": self.observe_id,
|
||||||
"last_observe_time": self.last_observe_time,
|
"last_observe_time": self.last_observe_time,
|
||||||
"history_loop": self.history_loop,
|
"history_loop": self.history_loop,
|
||||||
"structured_info": self.structured_info
|
"structured_info": self.structured_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_observe_info(self):
|
def get_observe_info(self):
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ class WorkingMemoryObservation:
|
|||||||
"observe_info": self.observe_info,
|
"observe_info": self.observe_info,
|
||||||
"observe_id": self.observe_id,
|
"observe_id": self.observe_id,
|
||||||
"last_observe_time": self.last_observe_time,
|
"last_observe_time": self.last_observe_time,
|
||||||
"working_memory": self.working_memory.to_dict() if hasattr(self.working_memory, 'to_dict') else str(self.working_memory),
|
"working_memory": self.working_memory.to_dict()
|
||||||
"retrieved_working_memory": [item.to_dict() if hasattr(item, 'to_dict') else str(item) for item in self.retrieved_working_memory]
|
if hasattr(self.working_memory, "to_dict")
|
||||||
|
else str(self.working_memory),
|
||||||
|
"retrieved_working_memory": [
|
||||||
|
item.to_dict() if hasattr(item, "to_dict") else str(item) for item in self.retrieved_working_memory
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,10 +146,10 @@ class FocusChatConfig(ConfigBase):
|
|||||||
|
|
||||||
consecutive_replies: float = 1
|
consecutive_replies: float = 1
|
||||||
"""连续回复能力,值越高,麦麦连续回复的概率越高"""
|
"""连续回复能力,值越高,麦麦连续回复的概率越高"""
|
||||||
|
|
||||||
parallel_processing: bool = False
|
parallel_processing: bool = False
|
||||||
"""是否允许处理器阶段和回忆阶段并行执行"""
|
"""是否允许处理器阶段和回忆阶段并行执行"""
|
||||||
|
|
||||||
processor_max_time: int = 25
|
processor_max_time: int = 25
|
||||||
"""处理器最大时间,单位秒,如果超过这个时间,处理器会自动停止"""
|
"""处理器最大时间,单位秒,如果超过这个时间,处理器会自动停止"""
|
||||||
|
|
||||||
@@ -166,13 +166,11 @@ class FocusChatProcessorConfig(ConfigBase):
|
|||||||
|
|
||||||
working_memory_processor: bool = True
|
working_memory_processor: bool = True
|
||||||
"""是否启用工作记忆处理器"""
|
"""是否启用工作记忆处理器"""
|
||||||
|
|
||||||
lite_chat_mind_processor: bool = False
|
lite_chat_mind_processor: bool = False
|
||||||
"""是否启用轻量级聊天思维处理器,可以节省token消耗和时间"""
|
"""是否启用轻量级聊天思维处理器,可以节省token消耗和时间"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ExpressionConfig(ConfigBase):
|
class ExpressionConfig(ConfigBase):
|
||||||
"""表达配置类"""
|
"""表达配置类"""
|
||||||
|
|||||||
@@ -753,7 +753,7 @@ class LLMRequest:
|
|||||||
|
|
||||||
response = await self._execute_request(endpoint="/chat/completions", payload=data, prompt=prompt)
|
response = await self._execute_request(endpoint="/chat/completions", payload=data, prompt=prompt)
|
||||||
# 原样返回响应,不做处理
|
# 原样返回响应,不做处理
|
||||||
|
|
||||||
if len(response) == 3:
|
if len(response) == 3:
|
||||||
content, reasoning_content, tool_calls = response
|
content, reasoning_content, tool_calls = response
|
||||||
return content, (reasoning_content, self.model_name, tool_calls)
|
return content, (reasoning_content, self.model_name, tool_calls)
|
||||||
|
|||||||
0
src/plugins/vtb_action/__init__.py
Normal file
0
src/plugins/vtb_action/__init__.py
Normal file
1
src/plugins/vtb_action/actions/__init__.py
Normal file
1
src/plugins/vtb_action/actions/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import vtb_action # noqa
|
||||||
74
src/plugins/vtb_action/actions/vtb_action.py
Normal file
74
src/plugins/vtb_action/actions/vtb_action.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
from src.common.logger_manager import get_logger
|
||||||
|
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
logger = get_logger("vtb_action")
|
||||||
|
|
||||||
|
|
||||||
|
@register_action
|
||||||
|
class VTBAction(PluginAction):
|
||||||
|
"""VTB虚拟主播动作处理类"""
|
||||||
|
|
||||||
|
action_name = "vtb_action"
|
||||||
|
action_description = "使用虚拟主播预设动作表达心情或感觉,适用于需要生动表达情感的场景"
|
||||||
|
action_parameters = {
|
||||||
|
"text": "描述想要表达的心情或感觉的文本内容,必填,应当是对情感状态的自然描述",
|
||||||
|
}
|
||||||
|
action_require = [
|
||||||
|
"当需要表达特定情感或心情时使用",
|
||||||
|
"当用户明确要求使用虚拟主播动作时使用",
|
||||||
|
"当回应内容需要更生动的情感表达时使用",
|
||||||
|
"当想要通过预设动作增强互动体验时使用",
|
||||||
|
]
|
||||||
|
default = True # 设为默认动作
|
||||||
|
associated_types = ["vtb_text"]
|
||||||
|
|
||||||
|
async def process(self) -> Tuple[bool, str]:
|
||||||
|
"""处理VTB虚拟主播动作"""
|
||||||
|
logger.info(f"{self.log_prefix} 执行VTB动作: {self.reasoning}")
|
||||||
|
|
||||||
|
# 获取要表达的心情或感觉文本
|
||||||
|
text = self.action_data.get("text")
|
||||||
|
|
||||||
|
if not text:
|
||||||
|
logger.error(f"{self.log_prefix} 执行VTB动作时未提供文本内容")
|
||||||
|
return False, "执行VTB动作失败:未提供文本内容"
|
||||||
|
|
||||||
|
# 处理文本使其更适合VTB动作表达
|
||||||
|
processed_text = self._process_text_for_vtb(text)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送VTB动作消息
|
||||||
|
await self.send_message(type="vtb_text", data=processed_text)
|
||||||
|
|
||||||
|
logger.info(f"{self.log_prefix} VTB动作执行成功,文本内容: {processed_text}")
|
||||||
|
return True, "VTB动作执行成功"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{self.log_prefix} 执行VTB动作时出错: {e}")
|
||||||
|
return False, f"执行VTB动作时出错: {e}"
|
||||||
|
|
||||||
|
def _process_text_for_vtb(self, text: str) -> str:
|
||||||
|
"""
|
||||||
|
处理文本使其更适合VTB动作表达
|
||||||
|
- 优化情感表达的准确性
|
||||||
|
- 规范化心情描述格式
|
||||||
|
- 确保文本适合虚拟主播动作系统理解
|
||||||
|
"""
|
||||||
|
# 简单示例实现
|
||||||
|
processed_text = text.strip()
|
||||||
|
|
||||||
|
# 移除多余的空格和换行
|
||||||
|
import re
|
||||||
|
|
||||||
|
processed_text = re.sub(r"\s+", " ", processed_text)
|
||||||
|
|
||||||
|
# 确保文本长度适中,避免过长的描述
|
||||||
|
if len(processed_text) > 100:
|
||||||
|
processed_text = processed_text[:100] + "..."
|
||||||
|
|
||||||
|
# 如果文本为空,提供默认的情感描述
|
||||||
|
if not processed_text:
|
||||||
|
processed_text = "平静"
|
||||||
|
|
||||||
|
return processed_text
|
||||||
Reference in New Issue
Block a user