diff --git a/scripts/070configexe.py b/scripts/070configexe.py index a6cd5b51d..0f67cdb87 100644 --- a/scripts/070configexe.py +++ b/scripts/070configexe.py @@ -1,11 +1,12 @@ import tkinter as tk -from tkinter import ttk, messagebox +from tkinter import ttk, messagebox, filedialog import tomli import tomli_w import os from typing import Any, Dict, List, Union import threading import time +import re class ConfigEditor: def __init__(self, root): @@ -21,6 +22,9 @@ class ConfigEditor: # 加载配置 self.load_config() + # 加载环境变量 + self.load_env_vars() + # 自动保存相关 self.last_save_time = time.time() self.save_timer = None @@ -92,13 +96,61 @@ class ConfigEditor: messagebox.showerror("错误", f"加载配置文件失败: {str(e)}") 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): """创建版本号显示标签""" version = self.config.get("inner", {}).get("version", "未知版本") 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_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) def create_navbar(self): @@ -113,14 +165,15 @@ class ConfigEditor: # 添加快捷设置节 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: - 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_name = section_trans.get("name", section) self.tree.insert("", "end", text=section_name, values=(section,)) - # 绑定选择事件 self.tree.bind("<>", 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_title = ttk.Label(self.editor_frame, text="") - self.editor_title.pack(fill=tk.X) + # self.editor_title = ttk.Label(self.editor_frame, text="") + # self.editor_title.pack(fill=tk.X) # 创建编辑区内容 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.refresh_button = ttk.Button(self.button_frame, text="刷新", command=self.refresh_config) - self.refresh_button.pack(side=tk.RIGHT, padx=5) + # self.refresh_button = ttk.Button(self.button_frame, text="刷新", command=self.refresh_config) + # 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: """为不同类型的值创建对应的编辑控件""" @@ -178,6 +235,14 @@ class ConfigEditor: # --- 修改开始: 改进翻译查找逻辑 --- 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_desc_to_display = "" # 默认无描述 @@ -192,10 +257,12 @@ class ConfigEditor: if generic_translation and generic_translation.get("name"): item_name_to_display = generic_translation.get("name") 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)) # 星星图标快捷设置(与配置名同一行) @@ -210,11 +277,19 @@ class ConfigEditor: for widget in parent.winfo_children(): widget.destroy() self.widgets.clear() - # 重新渲染本分组 - 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]) - elif hasattr(self, 'current_section') and self.current_section == "quick_settings": - self.create_quick_settings_widgets() # 如果当前是快捷设置,也刷新它 + # 判断parent是不是self.content_frame + if parent == self.content_frame: + # 主界面 + 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]) + elif hasattr(self, 'current_section') and self.current_section == "quick_settings": + 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.grid(row=0, column=content_col_offset_for_star, sticky=tk.W, padx=5) @@ -228,7 +303,7 @@ class ConfigEditor: # 配置项描述(第二行) desc_row = 1 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)) widget_row = desc_row + 1 # 内容控件在描述下方 else: @@ -236,7 +311,7 @@ class ConfigEditor: # 配置内容控件(第三行或第二行) 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) return @@ -251,7 +326,7 @@ class ConfigEditor: elif isinstance(value, (int, float)): # 数字使用数字输入框 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) var.trace_add("write", lambda *args: self.on_value_changed()) self.widgets[tuple(path + [key])] = var @@ -287,19 +362,98 @@ class ConfigEditor: else: # 其他类型(字符串等)使用普通文本框 var = tk.StringVar(value=str(value)) - entry = ttk.Entry(frame, textvariable=var, font=("", 20)) - 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 + + # 特殊处理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("<>", 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) + var.trace_add("write", lambda *args: self.on_value_changed()) + self.widgets[tuple(path + [key])] = var widget_type = "text" def create_section_widgets(self, parent: ttk.Frame, section: str, data: Dict, path=None) -> None: """为配置节创建编辑控件""" if path is None: path = [section] + # section完整路径 + full_section_path = ".".join(path) # 获取节的中文名称和描述 - section_trans = self.translations.get("sections", {}).get(section, {}) - section_name = section_trans.get("name", section) + 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": "自我识别模型" + } + 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", "") # 创建节的标签框架 @@ -307,13 +461,17 @@ class ConfigEditor: 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) # 创建节的描述标签 - if section_desc: - desc_label = ttk.Label(section_frame, text=f"({section_desc})", foreground="gray") - desc_label.pack(side=tk.LEFT, padx=5) + if isinstance(section_trans.get("description"), dict): + # 如果是多语言描述,优先取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) # 为每个配置项创建对应的控件 for key, value in data.items(): @@ -343,14 +501,6 @@ class ConfigEditor: section = self.tree.item(selection[0])["values"][0] # 使用values中的原始节名 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(): widget.destroy() @@ -361,6 +511,8 @@ class ConfigEditor: # 创建编辑控件 if section == "quick_settings": self.create_quick_settings_widgets() + elif section == "env_vars": + self.create_env_vars_section(self.content_frame) elif section in self.config: self.create_section_widgets(self.content_frame, section, self.config[section]) @@ -382,12 +534,12 @@ class ConfigEditor: 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)) # 创建描述标签 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)) # 根据类型创建不同的控件 @@ -404,14 +556,14 @@ class ConfigEditor: elif setting_type == "text": value = str(value) if value is not None else "" 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)) var.trace_add("write", lambda *args, p=path, v=var: self.on_quick_setting_changed(p, v)) elif setting_type == "number": value = str(value) if value is not None else "0" 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)) var.trace_add("write", lambda *args, p=path, v=var: self.on_quick_setting_changed(p, v)) @@ -483,22 +635,78 @@ class ConfigEditor: try: # 获取所有控件的值 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) - # 更新配置 current = self.config for key in path[:-1]: current = current[key] - final_key = path[-1] # 直接用最后一个key + final_key = path[-1] current[final_key] = value - - # 保存到文件 + + # === 只保存 TOML,不包含 env_vars === + env_vars = self.config.pop('env_vars', None) with open(self.config_path, "wb") as 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.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: messagebox.showerror("错误", f"保存配置失败: {str(e)}") @@ -629,6 +837,254 @@ class ConfigEditor: self.widgets.clear() 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(): root = tk.Tk() app = ConfigEditor(root) diff --git a/scripts/configexe.toml b/scripts/configexe.toml index de9665195..244fac36e 100644 --- a/scripts/configexe.toml +++ b/scripts/configexe.toml @@ -1,5 +1,7 @@ [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] window_width = 1000 @@ -72,6 +74,12 @@ description = "是否启用工作记忆处理器,不稳定,消耗量大" path = "focus_chat_processor.working_memory_processor" type = "bool" +[[editor.quick_settings.items]] +name = "显示聊天模式" +description = "是否在回复后显示当前聊天模式" +path = "experimental.debug_show_chat_mode" +type = "bool" + [translations.sections.inner] name = "版本" description = "麦麦的内部配置,包含版本号等信息。此部分仅供显示,不可编辑。" @@ -488,6 +496,10 @@ description = "暂时无效" name = "启用分割器" description = "是否启用回复分割器" +[translations.items."telemetry.enable"] +name = "启用遥测" +description = "是否发送统计信息,主要是看全球有多少只麦麦" + [translations.items."chinese_typo.enable"] name = "启用错别字" description = "是否启用中文错别字生成器" diff --git a/src/chat/focus_chat/planners/actions/reply_action.py b/src/chat/focus_chat/planners/actions/reply_action.py index df3413398..349038dc8 100644 --- a/src/chat/focus_chat/planners/actions/reply_action.py +++ b/src/chat/focus_chat/planners/actions/reply_action.py @@ -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.heart_flow.observation.chatting_observation import ChattingObservation from src.chat.focus_chat.hfc_utils import create_empty_anchor_message +from src.config.config import global_config logger = get_logger("action_taken") @@ -34,7 +35,7 @@ class ReplyAction(BaseAction): "一次只回复一个人,一次只回复一个话题,突出重点", "如果是自己发的消息想继续,需自然衔接", "避免重复或评价自己的发言,不要和自己聊天", - "注意:回复尽量简短一些。可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,平淡一些。不要有额外的符号,尽量简单简短", + f"注意你的回复要求:{global_config.expression.expression_style}", ] associated_types: list[str] = ["text", "emoji"] diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 100e0c6d8..5f7b1c286 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -40,7 +40,7 @@ identity_detail = [ [expression] # 表达方式 -expression_style = "描述麦麦说话的表达风格,表达习惯" +expression_style = "描述麦麦说话的表达风格,表达习惯,例如:(回复尽量简短一些。可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,平淡一些。不要有额外的符号,尽量简单简短)" enable_expression_learning = true # 是否启用表达学习,麦麦会学习人类说话风格 learning_interval = 600 # 学习间隔 单位秒 @@ -194,16 +194,18 @@ temp = 0.2 #模型的温度,新V3建议0.1-0.3 # 强烈建议使用免费的小模型 name = "Qwen/Qwen3-8B" provider = "SILICONFLOW" -enable_thinking = false # 是否启用思考 pri_in = 0 pri_out = 0 +temp = 0.7 +enable_thinking = false # 是否启用思考 [model.memory_summary] # 记忆的概括模型 name = "Qwen/Qwen3-30B-A3B" provider = "SILICONFLOW" -enable_thinking = false # 是否启用思考 pri_in = 0.7 pri_out = 2.8 +temp = 0.7 +enable_thinking = false # 是否启用思考 [model.vlm] # 图像识别模型 name = "Pro/Qwen/Qwen2.5-VL-7B-Instruct" @@ -211,6 +213,7 @@ provider = "SILICONFLOW" pri_in = 0.35 pri_out = 0.35 + #嵌入模型 [model.embedding] name = "BAAI/bge-m3" @@ -225,6 +228,7 @@ name = "Pro/deepseek-ai/DeepSeek-R1" provider = "SILICONFLOW" pri_in = 4.0 #模型的输入价格(非必填,可以记录消耗) pri_out = 16.0 #模型的输出价格(非必填,可以记录消耗) +temp = 0.7 [model.normal_chat_2] # 一般聊天模式的次要回复模型,推荐使用 非推理模型 name = "Pro/deepseek-ai/DeepSeek-V3" @@ -239,9 +243,10 @@ temp = 0.2 #模型的温度,新V3建议0.1-0.3 [model.focus_working_memory] #工作记忆模型 name = "Qwen/Qwen3-30B-A3B" provider = "SILICONFLOW" -enable_thinking = false # 是否启用思考 +enable_thinking = false # 是否启用思考(qwen3 only) pri_in = 0.7 pri_out = 2.8 +temp = 0.7 [model.focus_chat_mind] #聊天规划:认真聊天时,生成麦麦对聊天的规划想法 name = "Pro/deepseek-ai/DeepSeek-V3" @@ -255,15 +260,16 @@ temp = 0.3 [model.focus_tool_use] #工具调用模型,需要使用支持工具调用的模型 name = "Qwen/Qwen3-14B" provider = "SILICONFLOW" -enable_thinking = false # 是否启用思考 pri_in = 0.5 pri_out = 2 +temp = 0.7 +enable_thinking = false # 是否启用思考(qwen3 only) [model.focus_planner] #决策:认真聊天时,负责决定麦麦该做什么 name = "Pro/deepseek-ai/DeepSeek-V3" # name = "Qwen/Qwen3-30B-A3B" provider = "SILICONFLOW" -# enable_thinking = false # 是否启用思考 +# enable_thinking = false # 是否启用思考(qwen3 only) pri_in = 2 pri_out = 8 temp = 0.3 @@ -274,7 +280,7 @@ temp = 0.3 name = "Pro/deepseek-ai/DeepSeek-V3" # name = "Qwen/Qwen3-30B-A3B" provider = "SILICONFLOW" -# enable_thinking = false # 是否启用思考 +# enable_thinking = false # 是否启用思考(qwen3 only) pri_in = 2 pri_out = 8 temp = 0.3 @@ -284,37 +290,10 @@ temp = 0.3 # name = "Pro/deepseek-ai/DeepSeek-V3" name = "Qwen/Qwen3-30B-A3B" provider = "SILICONFLOW" -enable_thinking = false # 是否启用思考 pri_in = 0.7 pri_out = 2.8 temp = 0.7 - - - -#私聊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 +enable_thinking = false # 是否启用思考(qwen3 only) @@ -335,7 +314,6 @@ enable = true [experimental] #实验性功能 debug_show_chat_mode = false # 是否在回复后显示当前聊天模式 enable_friend_chat = false # 是否启用好友聊天 -pfc_chatting = false # 暂时无效