升级配置文件读取器

This commit is contained in:
SengokuCola
2025-05-29 21:31:07 +08:00
parent aa75eccea6
commit 1bed1b5335
4 changed files with 533 additions and 86 deletions

View File

@@ -1,11 +1,12 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk, messagebox from tkinter import ttk, messagebox, filedialog
import tomli import tomli
import tomli_w import tomli_w
import os import os
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union
import threading import threading
import time import time
import re
class ConfigEditor: class ConfigEditor:
def __init__(self, root): def __init__(self, root):
@@ -21,6 +22,9 @@ class ConfigEditor:
# 加载配置 # 加载配置
self.load_config() self.load_config()
# 加载环境变量
self.load_env_vars()
# 自动保存相关 # 自动保存相关
self.last_save_time = time.time() self.last_save_time = time.time()
self.save_timer = None self.save_timer = None
@@ -92,13 +96,61 @@ class ConfigEditor:
messagebox.showerror("错误", f"加载配置文件失败: {str(e)}") messagebox.showerror("错误", f"加载配置文件失败: {str(e)}")
self.config = {} self.config = {}
def load_env_vars(self):
"""加载并解析环境变量文件"""
try:
# 从配置中获取环境文件路径
env_path = self.config.get("inner", {}).get("env_file", ".env")
if not os.path.isabs(env_path):
env_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), env_path)
if not os.path.exists(env_path):
print(f"环境文件不存在: {env_path}")
return
# 读取环境文件
with open(env_path, 'r', encoding='utf-8') as f:
env_content = f.read()
# 解析环境变量
env_vars = {}
for line in env_content.split('\n'):
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
# 检查是否是目标变量
if key.endswith('_BASE_URL') or key.endswith('_KEY'):
# 提取前缀去掉_BASE_URL或_KEY
prefix = key[:-9] if key.endswith('_BASE_URL') else key[:-4]
if prefix not in env_vars:
env_vars[prefix] = {}
env_vars[prefix][key] = value
# 将解析的环境变量添加到配置中
if 'env_vars' not in self.config:
self.config['env_vars'] = {}
self.config['env_vars'].update(env_vars)
except Exception as e:
print(f"加载环境变量失败: {str(e)}")
def create_version_label(self): def create_version_label(self):
"""创建版本号显示标签""" """创建版本号显示标签"""
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))
version_label = ttk.Label(version_frame, text=f"麦麦版本:{version}", font=("", 10, "bold")) # 添加配置按钮
config_button = ttk.Button(version_frame, text="配置路径", command=self.open_path_config)
config_button.pack(side=tk.LEFT, padx=5)
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)
def create_navbar(self): def create_navbar(self):
@@ -113,14 +165,15 @@ class ConfigEditor:
# 添加快捷设置节 # 添加快捷设置节
self.tree.insert("", "end", text="快捷设置", values=("quick_settings",)) self.tree.insert("", "end", text="快捷设置", values=("quick_settings",))
# 添加配置项到树 # 添加env_vars节显示为"配置你的模型APIKEY"
self.tree.insert("", "end", text="配置你的模型APIKEY", values=("env_vars",))
# 只显示bot_config.toml实际存在的section
for section in self.config: for section in self.config:
if section != "inner": # 跳过inner部分 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,))
# 绑定选择事件 # 绑定选择事件
self.tree.bind("<<TreeviewSelect>>", self.on_section_select) self.tree.bind("<<TreeviewSelect>>", self.on_section_select)
@@ -130,8 +183,8 @@ class ConfigEditor:
self.editor_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S)) self.editor_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
# 创建编辑区标题 # 创建编辑区标题
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)
@@ -167,8 +220,12 @@ class ConfigEditor:
self.button_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E)) self.button_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E))
# 刷新按钮 # 刷新按钮
self.refresh_button = ttk.Button(self.button_frame, text="刷新", command=self.refresh_config) # self.refresh_button = ttk.Button(self.button_frame, text="刷新", command=self.refresh_config)
self.refresh_button.pack(side=tk.RIGHT, padx=5) # self.refresh_button.pack(side=tk.RIGHT, padx=5)
# 高级选项按钮(左下角)
self.advanced_button = ttk.Button(self.button_frame, text="高级选项", command=self.open_advanced_options)
self.advanced_button.pack(side=tk.LEFT, padx=5)
def create_widget_for_value(self, parent: ttk.Frame, key: str, value: Any, path: List[str]) -> None: def create_widget_for_value(self, parent: ttk.Frame, key: str, value: Any, path: List[str]) -> None:
"""为不同类型的值创建对应的编辑控件""" """为不同类型的值创建对应的编辑控件"""
@@ -178,6 +235,14 @@ 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 = {
"name": ("模型名称", "模型的唯一标识或名称"),
"provider": ("模型提供商", "模型API的提供商"),
"pri_in": ("输入价格", "模型输入的价格/消耗"),
"pri_out": ("输出价格", "模型输出的价格/消耗"),
"temp": ("模型温度", "控制模型输出的多样性")
}
item_name_to_display = key # 默认显示原始键名 item_name_to_display = key # 默认显示原始键名
item_desc_to_display = "" # 默认无描述 item_desc_to_display = "" # 默认无描述
@@ -192,10 +257,12 @@ class ConfigEditor:
if generic_translation and generic_translation.get("name"): if generic_translation and generic_translation.get("name"):
item_name_to_display = generic_translation.get("name") item_name_to_display = generic_translation.get("name")
item_desc_to_display = generic_translation.get("description", "") item_desc_to_display = generic_translation.get("description", "")
elif key in model_item_translations:
item_name_to_display, item_desc_to_display = model_item_translations[key]
# --- 修改结束 --- # --- 修改结束 ---
# 配置名(大号字体) # 配置名(大号字体)
label = ttk.Label(frame, text=item_name_to_display, font=("", 20, "bold")) label = ttk.Label(frame, text=item_name_to_display, font=("微软雅黑", 16, "bold"))
label.grid(row=0, column=0, sticky=tk.W, padx=5, pady=(0, 0)) label.grid(row=0, column=0, sticky=tk.W, padx=5, pady=(0, 0))
# 星星图标快捷设置(与配置名同一行) # 星星图标快捷设置(与配置名同一行)
@@ -210,11 +277,19 @@ class ConfigEditor:
for widget in parent.winfo_children(): for widget in parent.winfo_children():
widget.destroy() widget.destroy()
self.widgets.clear() self.widgets.clear()
# 重新渲染本分组 # 判断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 hasattr(self, 'current_section') 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]) 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": elif hasattr(self, 'current_section') and self.current_section == "quick_settings":
self.create_quick_settings_widgets() # 如果当前是快捷设置,也刷新它 self.create_quick_settings_widgets()
else:
# 弹窗Tab
# 重新渲染当前Tab的内容
if path:
section = path[0]
self.create_section_widgets(parent, section, self.config[section], path)
pin_btn = ttk.Button(frame, text=icon, width=2, command=on_star_click) pin_btn = ttk.Button(frame, text=icon, width=2, command=on_star_click)
pin_btn.grid(row=0, column=content_col_offset_for_star, sticky=tk.W, padx=5) pin_btn.grid(row=0, column=content_col_offset_for_star, sticky=tk.W, padx=5)
@@ -228,7 +303,7 @@ 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=("", 16)) 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(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 # 内容控件在描述下方 widget_row = desc_row + 1 # 内容控件在描述下方
else: else:
@@ -236,7 +311,7 @@ class ConfigEditor:
# 配置内容控件(第三行或第二行) # 配置内容控件(第三行或第二行)
if path[0] == "inner": if path[0] == "inner":
value_label = ttk.Label(frame, text=str(value), font=("", 20)) 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
@@ -251,7 +326,7 @@ class ConfigEditor:
elif isinstance(value, (int, float)): elif isinstance(value, (int, float)):
# 数字使用数字输入框 # 数字使用数字输入框
var = tk.StringVar(value=str(value)) var = tk.StringVar(value=str(value))
entry = ttk.Entry(frame, textvariable=var, font=("", 20)) 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
@@ -287,7 +362,69 @@ class ConfigEditor:
else: else:
# 其他类型(字符串等)使用普通文本框 # 其他类型(字符串等)使用普通文本框
var = tk.StringVar(value=str(value)) var = tk.StringVar(value=str(value))
entry = ttk.Entry(frame, textvariable=var, font=("", 20))
# 特殊处理provider字段
full_path = ".".join(path + [key])
if key == "provider" and full_path.startswith("model."):
# print(f"处理provider字段完整路径: {full_path}")
# print(f"当前config中的env_vars: {self.config.get('env_vars', {})}")
# 获取所有可用的provider选项
providers = []
if "env_vars" in self.config:
# print(f"找到env_vars节内容: {self.config['env_vars']}")
# 遍历env_vars中的所有配置对
for prefix, values in self.config["env_vars"].items():
# print(f"检查配置对 {prefix}: {values}")
# 检查是否同时有BASE_URL和KEY
if f"{prefix}_BASE_URL" in values and f"{prefix}_KEY" in values:
providers.append(prefix)
# print(f"添加provider: {prefix}")
print(f"最终providers列表: {providers}")
if providers:
# 创建模型名称标签(大字体)
model_name = var.get() if var.get() else providers[0]
section_translations = {
"model.utils": "工具模型",
"model.utils_small": "小型工具模型",
"model.memory_summary": "记忆概括模型",
"model.vlm": "图像识别模型",
"model.embedding": "嵌入模型",
"model.normal_chat_1": "普通聊天:主要聊天模型",
"model.normal_chat_2": "普通聊天:次要聊天模型",
"model.focus_working_memory": "专注模式:工作记忆模型",
"model.focus_chat_mind": "专注模式:聊天规划模型",
"model.focus_tool_use": "专注模式:工具调用模型",
"model.focus_planner": "专注模式:决策模型",
"model.focus_expressor": "专注模式:表达器模型",
"model.focus_self_recognize": "专注模式:自我识别模型"
}
# 获取当前节的名称
# current_section = ".".join(path[:-1]) # 去掉最后一个key
# section_name = section_translations.get(current_section, current_section)
# 创建节名称标签(大字体)
# 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))
# 创建下拉菜单(小字体)
combo = ttk.Combobox(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())
self.widgets[tuple(path + [key])] = var
widget_type = "provider"
# print(f"创建了下拉菜单,选项: {providers}")
else:
# 如果没有可用的provider使用普通文本框
# print(f"没有可用的provider使用普通文本框")
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)
var.trace_add("write", lambda *args: self.on_value_changed())
self.widgets[tuple(path + [key])] = var
widget_type = "text"
else:
# 普通文本框
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
@@ -297,9 +434,26 @@ class ConfigEditor:
"""为配置节创建编辑控件""" """为配置节创建编辑控件"""
if path is None: if path is None:
path = [section] path = [section]
# section完整路径
full_section_path = ".".join(path)
# 获取节的中文名称和描述 # 获取节的中文名称和描述
section_trans = self.translations.get("sections", {}).get(section, {}) section_translations = {
section_name = section_trans.get("name", section) "model.utils": "工具模型",
"model.utils_small": "小型工具模型",
"model.memory_summary": "记忆概括模型",
"model.vlm": "图像识别模型",
"model.embedding": "嵌入模型",
"model.normal_chat_1": "主要聊天模型",
"model.normal_chat_2": "次要聊天模型",
"model.focus_working_memory": "工作记忆模型",
"model.focus_chat_mind": "聊天规划模型",
"model.focus_tool_use": "工具调用模型",
"model.focus_planner": "决策模型",
"model.focus_expressor": "表达器模型",
"model.focus_self_recognize": "自我识别模型"
}
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_desc = section_trans.get("description", "") section_desc = section_trans.get("description", "")
# 创建节的标签框架 # 创建节的标签框架
@@ -307,12 +461,16 @@ class ConfigEditor:
section_frame.pack(fill=tk.X, padx=5, pady=10) section_frame.pack(fill=tk.X, padx=5, pady=10)
# 创建节的名称标签 # 创建节的名称标签
section_label = ttk.Label(section_frame, text=f"[{section_name}]", font=("", 12, "bold")) section_label = ttk.Label(section_frame, text=f"[{section_name}]", font=("微软雅黑", 18, "bold"))
section_label.pack(side=tk.LEFT, padx=5) section_label.pack(side=tk.LEFT, padx=5)
# 创建节的描述标签 # 创建节的描述标签
if section_desc: if isinstance(section_trans.get("description"), dict):
desc_label = ttk.Label(section_frame, text=f"({section_desc})", foreground="gray") # 如果是多语言描述优先取en否则取第一个
desc_en = section_trans["description"].get("en") or next(iter(section_trans["description"].values()), "")
desc_label = ttk.Label(section_frame, text=desc_en, foreground="gray", font=("微软雅黑", 10))
else:
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)
# 为每个配置项创建对应的控件 # 为每个配置项创建对应的控件
@@ -343,14 +501,6 @@ 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
# 获取节的中文名称
if section == "quick_settings":
section_name = "快捷设置"
else:
section_trans = self.translations.get("sections", {}).get(section, {})
section_name = section_trans.get("name", section)
self.editor_title.config(text=f"编辑 {section_name}")
# 清空编辑器 # 清空编辑器
for widget in self.content_frame.winfo_children(): for widget in self.content_frame.winfo_children():
widget.destroy() widget.destroy()
@@ -361,6 +511,8 @@ class ConfigEditor:
# 创建编辑控件 # 创建编辑控件
if section == "quick_settings": if section == "quick_settings":
self.create_quick_settings_widgets() self.create_quick_settings_widgets()
elif section == "env_vars":
self.create_env_vars_section(self.content_frame)
elif section in self.config: elif section in self.config:
self.create_section_widgets(self.content_frame, section, self.config[section]) self.create_section_widgets(self.content_frame, section, self.config[section])
@@ -382,12 +534,12 @@ class ConfigEditor:
value = current.get(path[-1]) # 获取最后一个键的值 value = current.get(path[-1]) # 获取最后一个键的值
# 创建名称标签 # 创建名称标签
name_label = ttk.Label(frame, text=setting["name"], font=("", 18)) name_label = ttk.Label(frame, text=setting["name"], font=("微软雅黑", 18))
name_label.pack(fill=tk.X, padx=5, pady=(2, 0)) name_label.pack(fill=tk.X, padx=5, pady=(2, 0))
# 创建描述标签 # 创建描述标签
if setting.get("description"): if setting.get("description"):
desc_label = ttk.Label(frame, text=setting['description'], foreground="gray", font=("", 16)) desc_label = ttk.Label(frame, text=setting['description'], foreground="gray", font=("微软雅黑", 16))
desc_label.pack(fill=tk.X, padx=5, pady=(0, 2)) desc_label.pack(fill=tk.X, padx=5, pady=(0, 2))
# 根据类型创建不同的控件 # 根据类型创建不同的控件
@@ -404,14 +556,14 @@ class ConfigEditor:
elif setting_type == "text": elif setting_type == "text":
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=("", 18)) entry = ttk.Entry(frame, textvariable=var, width=40, font=("微软雅黑", 18))
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=("", 18)) entry = ttk.Entry(frame, textvariable=var, width=10, font=("微软雅黑", 18))
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))
@@ -483,22 +635,78 @@ class ConfigEditor:
try: try:
# 获取所有控件的值 # 获取所有控件的值
for path, widget in self.widgets.items(): for path, widget in self.widgets.items():
# 跳过 env_vars 的控件赋值(只用于.env不写回config
if len(path) >= 2 and path[0] == 'env_vars':
continue
value = self.get_widget_value(widget) value = self.get_widget_value(widget)
# 更新配置
current = self.config current = self.config
for key in path[:-1]: for key in path[:-1]:
current = current[key] current = current[key]
final_key = path[-1] # 直接用最后一个key final_key = path[-1]
current[final_key] = value current[final_key] = value
# 保存到文件 # === 只保存 TOML不包含 env_vars ===
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:
self.config['env_vars'] = env_vars
# === 保存 env_vars 到 .env 文件只覆盖特定key其他内容保留 ===
env_path = self.editor_config["config"].get("env_file", ".env")
if not os.path.isabs(env_path):
env_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), env_path)
# 1. 读取原有.env内容
old_lines = []
if os.path.exists(env_path):
with open(env_path, "r", encoding="utf-8") as f:
old_lines = f.readlines()
# 2. 收集所有目标key的新值直接从widgets取
new_env_dict = {}
for path, widget in self.widgets.items():
if len(path) == 2 and path[0] == 'env_vars':
k = path[1]
if k.endswith("_BASE_URL") or k.endswith("_KEY"):
new_env_dict[k] = self.get_widget_value(widget)
# 3. 遍历原有行替换目标key并且只保留当前界面有的key
result_lines = []
found_keys = set()
for line in old_lines:
if "=" in line and not line.strip().startswith("#"):
k = line.split("=", 1)[0].strip()
if k in new_env_dict:
result_lines.append(f"{k}={new_env_dict[k]}\n")
found_keys.add(k)
elif k.endswith("_BASE_URL") or k.endswith("_KEY"):
# 跳过界面上已删除的key不保留
continue
else:
result_lines.append(line)
else:
result_lines.append(line)
# 4. 新key如果原.env没有则追加
for k, v in new_env_dict.items():
if k not in found_keys:
result_lines.append(f"{k}={v}\n")
# 5. 写回.env
with open(env_path, "w", encoding="utf-8") as f:
f.writelines(result_lines)
# === 结束 ===
# === 保存完 .env 后,同步 widgets 的值回 self.config['env_vars'] ===
for path, widget in self.widgets.items():
if len(path) == 2 and path[0] == 'env_vars':
prefix_key = path[1]
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]
if 'env_vars' not in self.config:
self.config['env_vars'] = {}
if prefix not in self.config['env_vars']:
self.config['env_vars'][prefix] = {}
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
self.editor_title.config(text=f"{self.editor_title.cget('text')} (已保存)")
self.root.after(2000, lambda: self.editor_title.config(text=self.editor_title.cget('text').replace(" (已保存)", "")))
except Exception as e: except Exception as e:
messagebox.showerror("错误", f"保存配置失败: {str(e)}") messagebox.showerror("错误", f"保存配置失败: {str(e)}")
@@ -629,6 +837,254 @@ class ConfigEditor:
self.widgets.clear() self.widgets.clear()
self.create_quick_settings_widgets() self.create_quick_settings_widgets()
def create_env_var_group(self, parent: ttk.Frame, prefix: str, values: Dict[str, str], path: List[str]) -> None:
"""创建环境变量组"""
frame = ttk.Frame(parent)
frame.pack(fill=tk.X, padx=5, pady=2)
# 创建组标题
title_frame = ttk.Frame(frame)
title_frame.pack(fill=tk.X, pady=(5, 0))
title_label = ttk.Label(title_frame, text=f"API配置组: {prefix}", font=("微软雅黑", 16, "bold"))
title_label.pack(side=tk.LEFT, padx=5)
# 删除按钮
del_button = ttk.Button(title_frame, text="删除组",
command=lambda: self.delete_env_var_group(prefix))
del_button.pack(side=tk.RIGHT, padx=5)
# 创建BASE_URL输入框
base_url_frame = ttk.Frame(frame)
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.pack(side=tk.LEFT, padx=5)
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.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
base_url_var.trace_add("write", lambda *args: self.on_value_changed())
# 创建KEY输入框
key_frame = ttk.Frame(frame)
key_frame.pack(fill=tk.X, padx=5, pady=2)
key_label = ttk.Label(key_frame, text="API KEY:", font=("微软雅黑", 12))
key_label.pack(side=tk.LEFT, padx=5)
key_var = tk.StringVar(value=values.get(f"{prefix}_KEY", ""))
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_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}_KEY"])] = key_var
# 添加分隔线
separator = ttk.Separator(frame, orient='horizontal')
separator.pack(fill=tk.X, pady=5)
def create_env_vars_section(self, parent: ttk.Frame) -> None:
"""创建环境变量编辑区"""
# 创建添加新组的按钮
add_button = ttk.Button(parent, text="添加新的API配置组",
command=self.add_new_env_var_group)
add_button.pack(pady=10)
# 创建现有组的编辑区
if 'env_vars' in self.config:
for prefix, values in self.config['env_vars'].items():
self.create_env_var_group(parent, prefix, values, ['env_vars'])
def add_new_env_var_group(self):
"""添加新的环境变量组"""
# 创建新窗口
dialog = tk.Toplevel(self.root)
dialog.title("添加新的API配置组")
dialog.geometry("400x200")
# 创建输入框架
frame = ttk.Frame(dialog, padding="10")
frame.pack(fill=tk.BOTH, expand=True)
# 前缀输入
prefix_label = ttk.Label(frame, text="API前缀名称:", font=("微软雅黑", 12))
prefix_label.pack(pady=5)
prefix_var = tk.StringVar()
prefix_entry = ttk.Entry(frame, textvariable=prefix_var, font=("微软雅黑", 12))
prefix_entry.pack(fill=tk.X, pady=5)
# 确认按钮
def on_confirm():
prefix = prefix_var.get().strip()
if prefix:
if 'env_vars' not in self.config:
self.config['env_vars'] = {}
self.config['env_vars'][prefix] = {
f"{prefix}_BASE_URL": "",
f"{prefix}_KEY": ""
}
# 刷新显示
self.refresh_env_vars_section()
self.on_value_changed()
dialog.destroy()
confirm_button = ttk.Button(frame, text="确认", command=on_confirm)
confirm_button.pack(pady=10)
def delete_env_var_group(self, prefix: str):
"""删除环境变量组"""
if messagebox.askyesno("确认", f"确定要删除 {prefix} 配置组吗?"):
if 'env_vars' in self.config:
del self.config['env_vars'][prefix]
# 刷新显示
self.refresh_env_vars_section()
self.on_value_changed()
def refresh_env_vars_section(self):
"""刷新环境变量编辑区"""
# 清空当前显示
for widget in self.content_frame.winfo_children():
widget.destroy()
self.widgets.clear()
# 重新创建编辑区
self.create_env_vars_section(self.content_frame)
def open_advanced_options(self):
"""弹窗显示高级配置"""
dialog = tk.Toplevel(self.root)
dialog.title("高级选项")
dialog.geometry("700x800")
notebook = ttk.Notebook(dialog)
notebook.pack(fill=tk.BOTH, expand=True)
# 遥测栏
if "telemetry" in self.config:
telemetry_frame = ttk.Frame(notebook)
notebook.add(telemetry_frame, text="遥测")
self.create_section_widgets(telemetry_frame, "telemetry", self.config["telemetry"], ["telemetry"])
# 实验性功能栏
if "experimental" in self.config:
exp_frame = ttk.Frame(notebook)
notebook.add(exp_frame, text="实验性功能")
self.create_section_widgets(exp_frame, "experimental", self.config["experimental"], ["experimental"])
# 消息服务栏
if "maim_message" in self.config:
msg_frame = ttk.Frame(notebook)
notebook.add(msg_frame, text="消息服务")
self.create_section_widgets(msg_frame, "maim_message", self.config["maim_message"], ["maim_message"])
# 关键词反应栏
if "keyword_reaction" in self.config:
kw_frame = ttk.Frame(notebook)
notebook.add(kw_frame, text="关键词反应")
self.create_section_widgets(kw_frame, "keyword_reaction", self.config["keyword_reaction"], ["keyword_reaction"])
# 消息接收栏
if "message_receive" in self.config:
recv_frame = ttk.Frame(notebook)
notebook.add(recv_frame, text="消息接收")
self.create_section_widgets(recv_frame, "message_receive", self.config["message_receive"], ["message_receive"])
# 关系栏
if "relationship" in self.config:
rel_frame = ttk.Frame(notebook)
notebook.add(rel_frame, text="关系")
self.create_section_widgets(rel_frame, "relationship", self.config["relationship"], ["relationship"])
def open_path_config(self):
"""打开路径配置对话框"""
dialog = tk.Toplevel(self.root)
dialog.title("配置路径")
dialog.geometry("600x200")
# 创建输入框架
frame = ttk.Frame(dialog, padding="10")
frame.pack(fill=tk.BOTH, expand=True)
# bot_config.toml路径配置
bot_config_frame = ttk.Frame(frame)
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.pack(side=tk.LEFT, padx=5)
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.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
def apply_config():
new_bot_config_path = bot_config_var.get().strip()
new_env_path = env_var.get().strip()
if not new_bot_config_path or not new_env_path:
messagebox.showerror("错误", "路径不能为空")
return
if not os.path.exists(new_bot_config_path):
messagebox.showerror("错误", "bot_config.toml文件不存在")
return
# 更新配置
self.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
# 保存编辑器配置
config_path = os.path.join(os.path.dirname(__file__), "configexe.toml")
with open(config_path, "wb") as f:
tomli_w.dump(self.editor_config, f)
# 重新加载配置
self.load_config()
self.load_env_vars()
# 刷新显示
self.refresh_config()
messagebox.showinfo("成功", "路径配置已更新")
dialog.destroy()
def browse_bot_config():
file_path = filedialog.askopenfilename(
title="选择bot_config.toml文件",
filetypes=[("TOML文件", "*.toml"), ("所有文件", "*.*")]
)
if file_path:
bot_config_var.set(file_path)
apply_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)
# .env路径配置
env_frame = ttk.Frame(frame)
env_frame.pack(fill=tk.X, pady=5)
env_label = ttk.Label(env_frame, text=".env路径:", font=("微软雅黑", 12))
env_label.pack(side=tk.LEFT, padx=5)
env_path = self.editor_config["config"].get("env_file", ".env")
if not os.path.isabs(env_path):
env_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), env_path)
env_var = tk.StringVar(value=env_path)
env_entry = ttk.Entry(env_frame, textvariable=env_var, font=("微软雅黑", 12))
env_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
def browse_env():
file_path = filedialog.askopenfilename(
title="选择.env文件",
filetypes=[("环境变量文件", "*.env"), ("所有文件", "*.*")]
)
if file_path:
env_var.set(file_path)
apply_config()
browse_env_btn = ttk.Button(env_frame, text="浏览", command=browse_env)
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)

View File

@@ -1,5 +1,7 @@
[config] [config]
bot_config_path = "config/bot_config.toml" bot_config_path = "C:/GitHub/MaiBot-Core/config/bot_config.toml"
env_path = "env.toml"
env_file = "c:\\GitHub\\MaiBot-Core\\.env"
[editor] [editor]
window_width = 1000 window_width = 1000
@@ -72,6 +74,12 @@ description = "是否启用工作记忆处理器,不稳定,消耗量大"
path = "focus_chat_processor.working_memory_processor" path = "focus_chat_processor.working_memory_processor"
type = "bool" type = "bool"
[[editor.quick_settings.items]]
name = "显示聊天模式"
description = "是否在回复后显示当前聊天模式"
path = "experimental.debug_show_chat_mode"
type = "bool"
[translations.sections.inner] [translations.sections.inner]
name = "版本" name = "版本"
description = "麦麦的内部配置,包含版本号等信息。此部分仅供显示,不可编辑。" description = "麦麦的内部配置,包含版本号等信息。此部分仅供显示,不可编辑。"
@@ -488,6 +496,10 @@ description = "暂时无效"
name = "启用分割器" name = "启用分割器"
description = "是否启用回复分割器" description = "是否启用回复分割器"
[translations.items."telemetry.enable"]
name = "启用遥测"
description = "是否发送统计信息,主要是看全球有多少只麦麦"
[translations.items."chinese_typo.enable"] [translations.items."chinese_typo.enable"]
name = "启用错别字" name = "启用错别字"
description = "是否启用中文错别字生成器" description = "是否启用中文错别字生成器"

View File

@@ -8,6 +8,7 @@ from src.chat.focus_chat.expressors.default_expressor import DefaultExpressor
from src.chat.message_receive.chat_stream import ChatStream from src.chat.message_receive.chat_stream import ChatStream
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.focus_chat.hfc_utils import create_empty_anchor_message from src.chat.focus_chat.hfc_utils import create_empty_anchor_message
from src.config.config import global_config
logger = get_logger("action_taken") logger = get_logger("action_taken")
@@ -34,7 +35,7 @@ class ReplyAction(BaseAction):
"一次只回复一个人,一次只回复一个话题,突出重点", "一次只回复一个人,一次只回复一个话题,突出重点",
"如果是自己发的消息想继续,需自然衔接", "如果是自己发的消息想继续,需自然衔接",
"避免重复或评价自己的发言,不要和自己聊天", "避免重复或评价自己的发言,不要和自己聊天",
"注意:回复尽量简短一些。可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,平淡一些。不要有额外的符号,尽量简单简短", f"注意你的回复要求:{global_config.expression.expression_style}",
] ]
associated_types: list[str] = ["text", "emoji"] associated_types: list[str] = ["text", "emoji"]

View File

@@ -40,7 +40,7 @@ identity_detail = [
[expression] [expression]
# 表达方式 # 表达方式
expression_style = "描述麦麦说话的表达风格,表达习惯" expression_style = "描述麦麦说话的表达风格,表达习惯,例如:(回复尽量简短一些。可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,平淡一些。不要有额外的符号,尽量简单简短)"
enable_expression_learning = true # 是否启用表达学习,麦麦会学习人类说话风格 enable_expression_learning = true # 是否启用表达学习,麦麦会学习人类说话风格
learning_interval = 600 # 学习间隔 单位秒 learning_interval = 600 # 学习间隔 单位秒
@@ -194,16 +194,18 @@ temp = 0.2 #模型的温度新V3建议0.1-0.3
# 强烈建议使用免费的小模型 # 强烈建议使用免费的小模型
name = "Qwen/Qwen3-8B" name = "Qwen/Qwen3-8B"
provider = "SILICONFLOW" provider = "SILICONFLOW"
enable_thinking = false # 是否启用思考
pri_in = 0 pri_in = 0
pri_out = 0 pri_out = 0
temp = 0.7
enable_thinking = false # 是否启用思考
[model.memory_summary] # 记忆的概括模型 [model.memory_summary] # 记忆的概括模型
name = "Qwen/Qwen3-30B-A3B" name = "Qwen/Qwen3-30B-A3B"
provider = "SILICONFLOW" provider = "SILICONFLOW"
enable_thinking = false # 是否启用思考
pri_in = 0.7 pri_in = 0.7
pri_out = 2.8 pri_out = 2.8
temp = 0.7
enable_thinking = false # 是否启用思考
[model.vlm] # 图像识别模型 [model.vlm] # 图像识别模型
name = "Pro/Qwen/Qwen2.5-VL-7B-Instruct" name = "Pro/Qwen/Qwen2.5-VL-7B-Instruct"
@@ -211,6 +213,7 @@ provider = "SILICONFLOW"
pri_in = 0.35 pri_in = 0.35
pri_out = 0.35 pri_out = 0.35
#嵌入模型 #嵌入模型
[model.embedding] [model.embedding]
name = "BAAI/bge-m3" name = "BAAI/bge-m3"
@@ -225,6 +228,7 @@ name = "Pro/deepseek-ai/DeepSeek-R1"
provider = "SILICONFLOW" provider = "SILICONFLOW"
pri_in = 4.0 #模型的输入价格(非必填,可以记录消耗) pri_in = 4.0 #模型的输入价格(非必填,可以记录消耗)
pri_out = 16.0 #模型的输出价格(非必填,可以记录消耗) pri_out = 16.0 #模型的输出价格(非必填,可以记录消耗)
temp = 0.7
[model.normal_chat_2] # 一般聊天模式的次要回复模型,推荐使用 非推理模型 [model.normal_chat_2] # 一般聊天模式的次要回复模型,推荐使用 非推理模型
name = "Pro/deepseek-ai/DeepSeek-V3" name = "Pro/deepseek-ai/DeepSeek-V3"
@@ -239,9 +243,10 @@ temp = 0.2 #模型的温度新V3建议0.1-0.3
[model.focus_working_memory] #工作记忆模型 [model.focus_working_memory] #工作记忆模型
name = "Qwen/Qwen3-30B-A3B" name = "Qwen/Qwen3-30B-A3B"
provider = "SILICONFLOW" provider = "SILICONFLOW"
enable_thinking = false # 是否启用思考 enable_thinking = false # 是否启用思考(qwen3 only)
pri_in = 0.7 pri_in = 0.7
pri_out = 2.8 pri_out = 2.8
temp = 0.7
[model.focus_chat_mind] #聊天规划:认真聊天时,生成麦麦对聊天的规划想法 [model.focus_chat_mind] #聊天规划:认真聊天时,生成麦麦对聊天的规划想法
name = "Pro/deepseek-ai/DeepSeek-V3" name = "Pro/deepseek-ai/DeepSeek-V3"
@@ -255,15 +260,16 @@ temp = 0.3
[model.focus_tool_use] #工具调用模型,需要使用支持工具调用的模型 [model.focus_tool_use] #工具调用模型,需要使用支持工具调用的模型
name = "Qwen/Qwen3-14B" name = "Qwen/Qwen3-14B"
provider = "SILICONFLOW" provider = "SILICONFLOW"
enable_thinking = false # 是否启用思考
pri_in = 0.5 pri_in = 0.5
pri_out = 2 pri_out = 2
temp = 0.7
enable_thinking = false # 是否启用思考qwen3 only
[model.focus_planner] #决策:认真聊天时,负责决定麦麦该做什么 [model.focus_planner] #决策:认真聊天时,负责决定麦麦该做什么
name = "Pro/deepseek-ai/DeepSeek-V3" name = "Pro/deepseek-ai/DeepSeek-V3"
# name = "Qwen/Qwen3-30B-A3B" # name = "Qwen/Qwen3-30B-A3B"
provider = "SILICONFLOW" provider = "SILICONFLOW"
# enable_thinking = false # 是否启用思考 # enable_thinking = false # 是否启用思考(qwen3 only)
pri_in = 2 pri_in = 2
pri_out = 8 pri_out = 8
temp = 0.3 temp = 0.3
@@ -274,7 +280,7 @@ temp = 0.3
name = "Pro/deepseek-ai/DeepSeek-V3" name = "Pro/deepseek-ai/DeepSeek-V3"
# name = "Qwen/Qwen3-30B-A3B" # name = "Qwen/Qwen3-30B-A3B"
provider = "SILICONFLOW" provider = "SILICONFLOW"
# enable_thinking = false # 是否启用思考 # enable_thinking = false # 是否启用思考(qwen3 only)
pri_in = 2 pri_in = 2
pri_out = 8 pri_out = 8
temp = 0.3 temp = 0.3
@@ -284,37 +290,10 @@ temp = 0.3
# name = "Pro/deepseek-ai/DeepSeek-V3" # name = "Pro/deepseek-ai/DeepSeek-V3"
name = "Qwen/Qwen3-30B-A3B" name = "Qwen/Qwen3-30B-A3B"
provider = "SILICONFLOW" provider = "SILICONFLOW"
enable_thinking = false # 是否启用思考
pri_in = 0.7 pri_in = 0.7
pri_out = 2.8 pri_out = 2.8
temp = 0.7 temp = 0.7
enable_thinking = false # 是否启用思考(qwen3 only)
#私聊PFC需要开启PFC功能默认三个模型均为硅基流动v3如果需要支持多人同时私聊或频繁调用建议把其中的一个或两个换成官方v3或其它模型以免撞到429
#PFC决策模型
[model.pfc_action_planner]
name = "Pro/deepseek-ai/DeepSeek-V3"
provider = "SILICONFLOW"
temp = 0.3
pri_in = 2
pri_out = 8
#PFC聊天模型
[model.pfc_chat]
name = "Pro/deepseek-ai/DeepSeek-V3"
provider = "SILICONFLOW"
temp = 0.3
pri_in = 2
pri_out = 8
#PFC检查模型
[model.pfc_reply_checker]
name = "Pro/deepseek-ai/DeepSeek-V3"
provider = "SILICONFLOW"
pri_in = 2
pri_out = 8
@@ -335,7 +314,6 @@ enable = true
[experimental] #实验性功能 [experimental] #实验性功能
debug_show_chat_mode = false # 是否在回复后显示当前聊天模式 debug_show_chat_mode = false # 是否在回复后显示当前聊天模式
enable_friend_chat = false # 是否启用好友聊天 enable_friend_chat = false # 是否启用好友聊天
pfc_chatting = false # 暂时无效