Merge branch 'MaiM-with-u:dev' into dev

pull/1001/head
Snowish-in-wind 2025-05-30 10:00:22 +08:00 committed by GitHub
commit 34a7e054c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 650 additions and 140 deletions

View File

@ -1,12 +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 from typing import Any, Dict, List
import threading import threading
import time import time
import sys
class ConfigEditor: class ConfigEditor:
def __init__(self, root): def __init__(self, root):
@ -21,7 +21,10 @@ 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
@ -54,6 +57,15 @@ class ConfigEditor:
self.main_frame.columnconfigure(1, weight=1) self.main_frame.columnconfigure(1, weight=1)
self.main_frame.rowconfigure(1, weight=1) # 修改为1因为第0行是版本号 self.main_frame.rowconfigure(1, weight=1) # 修改为1因为第0行是版本号
# 默认选择快捷设置栏
self.current_section = "quick_settings"
self.create_quick_settings_widgets()
# 选中导航树中的快捷设置项
for item in self.tree.get_children():
if self.tree.item(item)["values"][0] == "quick_settings":
self.tree.selection_set(item)
break
def load_editor_config(self): def load_editor_config(self):
"""加载编辑器配置""" """加载编辑器配置"""
try: try:
@ -92,14 +104,64 @@ class ConfigEditor:
except Exception as e: except Exception as e:
messagebox.showerror("错误", f"加载配置文件失败: {str(e)}") messagebox.showerror("错误", f"加载配置文件失败: {str(e)}")
self.config = {} self.config = {}
# 自动打开配置路径窗口
self.open_path_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,15 +175,16 @@ 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)
@ -131,9 +194,9 @@ 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)
self.editor_content.pack(fill=tk.BOTH, expand=True) self.editor_content.pack(fill=tk.BOTH, expand=True)
@ -168,8 +231,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,7 +245,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 = {
"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 = "" # 默认无描述
@ -193,10 +268,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))
# 星星图标快捷设置(与配置名同一行) # 星星图标快捷设置(与配置名同一行)
@ -214,13 +291,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 hasattr(self, "current_section") and self.current_section and self.current_section != "quick_settings": if parent == self.content_frame:
self.create_section_widgets( # 主界面
parent, self.current_section, self.config[self.current_section], [self.current_section] 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": 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)
@ -234,18 +317,16 @@ 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( desc_label.grid(row=desc_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W, padx=5, pady=(0, 4))
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:
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=("", 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
if isinstance(value, bool): if isinstance(value, bool):
@ -259,8 +340,8 @@ 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
widget_type = "number" widget_type = "number"
@ -299,19 +380,98 @@ class ConfigEditor:
else: else:
# 其他类型(字符串等)使用普通文本框 # 其他类型(字符串等)使用普通文本框
var = tk.StringVar(value=str(value)) 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) # 特殊处理provider字段
var.trace_add("write", lambda *args: self.on_value_changed()) full_path = ".".join(path + [key])
self.widgets[tuple(path + [key])] = var 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)
var.trace_add("write", lambda *args: self.on_value_changed())
self.widgets[tuple(path + [key])] = var
widget_type = "text" widget_type = "text"
def create_section_widgets(self, parent: ttk.Frame, section: str, data: Dict, path=None) -> None: def create_section_widgets(self, parent: ttk.Frame, section: str, data: Dict, path=None) -> None:
"""为配置节创建编辑控件""" """为配置节创建编辑控件"""
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", "")
# 创建节的标签框架 # 创建节的标签框架
@ -319,14 +479,18 @@ 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_label.pack(side=tk.LEFT, padx=5) 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(): for key, value in data.items():
if isinstance(value, dict): if isinstance(value, dict):
@ -354,15 +518,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
# 获取节的中文名称
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()
@ -373,6 +529,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])
@ -393,13 +551,13 @@ class ConfigEditor:
current = current.get(key, {}) current = current.get(key, {})
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=("微软雅黑", 16, "bold"))
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=("微软雅黑", 10))
desc_label.pack(fill=tk.X, padx=5, pady=(0, 2)) desc_label.pack(fill=tk.X, padx=5, pady=(0, 2))
# 根据类型创建不同的控件 # 根据类型创建不同的控件
@ -416,15 +574,15 @@ 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=("微软雅黑", 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=("", 18)) 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":
@ -500,24 +658,75 @@ 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保留所有其他内容
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)
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)}")
@ -649,6 +858,252 @@ 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 "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()
# 重启程序
self.root.quit()
os.execv(sys.executable, ['python'] + sys.argv)
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()

View File

@ -1,11 +1,19 @@
[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
window_height = 800 window_height = 800
save_delay = 1.0 save_delay = 1.0
[[editor.quick_settings.items]]
name = "核心性格"
description = "麦麦的核心性格描述建议50字以内"
path = "personality.personality_core"
type = "text"
[[editor.quick_settings.items]] [[editor.quick_settings.items]]
name = "性格细节" name = "性格细节"
description = "麦麦性格的细节描述条数任意不能为0" description = "麦麦性格的细节描述条数任意不能为0"
@ -31,47 +39,61 @@ path = "chat.chat_mode"
type = "text" type = "text"
[[editor.quick_settings.items]] [[editor.quick_settings.items]]
name = "退出专注阈值" name = "回复频率normal模式"
description = "自动退出专注聊天的阈值,越低越容易退出专注聊天" description = "麦麦回复频率一般为1默认频率下30分钟麦麦回复30条约数"
path = "chat.exit_focus_threshold" path = "normal_chat.talk_frequency"
type = "number" type = "number"
[[editor.quick_settings.items]] [[editor.quick_settings.items]]
name = "偷取表情包" name = "自动专注阈值auto模式"
description = "是否偷取表情包,让麦麦可以发送她保存的这些表情包"
path = "emoji.steal_emoji"
type = "bool"
[[editor.quick_settings.items]]
name = "核心性格"
description = "麦麦的核心性格描述建议50字以内"
path = "personality.personality_core"
type = "text"
[[editor.quick_settings.items]]
name = "自动专注阈值"
description = "自动切换到专注聊天的阈值,越低越容易进入专注聊天" description = "自动切换到专注聊天的阈值,越低越容易进入专注聊天"
path = "chat.auto_focus_threshold" path = "chat.auto_focus_threshold"
type = "number" type = "number"
[[editor.quick_settings.items]] [[editor.quick_settings.items]]
name = "自我识别处理器" name = "退出专注阈值auto模式"
description = "自动退出专注聊天的阈值,越低越容易退出专注聊天"
path = "chat.exit_focus_threshold"
type = "number"
[[editor.quick_settings.items]]
name = "思考间隔focus模式"
description = "思考的时间间隔(秒),可以有效减少消耗"
path = "focus_chat.think_interval"
type = "number"
[[editor.quick_settings.items]]
name = "连续回复能力focus模式"
description = "连续回复能力,值越高,麦麦连续回复的概率越高"
path = "focus_chat.consecutive_replies"
type = "number"
[[editor.quick_settings.items]]
name = "自我识别处理器focus模式"
description = "是否启用自我识别处理器" description = "是否启用自我识别处理器"
path = "focus_chat_processor.self_identify_processor" path = "focus_chat_processor.self_identify_processor"
type = "bool" type = "bool"
[[editor.quick_settings.items]] [[editor.quick_settings.items]]
name = "工具使用处理器" name = "工具使用处理器focus模式"
description = "是否启用工具使用处理器" description = "是否启用工具使用处理器"
path = "focus_chat_processor.tool_use_processor" path = "focus_chat_processor.tool_use_processor"
type = "bool" type = "bool"
[[editor.quick_settings.items]] [[editor.quick_settings.items]]
name = "工作记忆处理器" name = "工作记忆处理器focus模式"
description = "是否启用工作记忆处理器,不稳定,消耗量大" description = "是否启用工作记忆处理器,不稳定,消耗量大"
path = "focus_chat_processor.working_memory_processor" path = "focus_chat_processor.working_memory_processor"
type = "bool" type = "bool"
[[editor.quick_settings.items]]
name = "显示聊天模式debug模式"
description = "是否在回复后显示当前聊天模式"
path = "experimental.debug_show_chat_mode"
type = "bool"
[translations.sections.inner] [translations.sections.inner]
name = "版本" name = "版本"
description = "麦麦的内部配置,包含版本号等信息。此部分仅供显示,不可编辑。" description = "麦麦的内部配置,包含版本号等信息。此部分仅供显示,不可编辑。"
@ -276,6 +298,10 @@ description = "需要降低回复频率的群组列表"
name = "思考间隔" name = "思考间隔"
description = "思考的时间间隔(秒),可以有效减少消耗" description = "思考的时间间隔(秒),可以有效减少消耗"
[translations.items.consecutive_replies]
name = "连续回复能力"
description = "连续回复能力,值越高,麦麦连续回复的概率越高"
[translations.items.observation_context_size] [translations.items.observation_context_size]
name = "观察上下文大小" name = "观察上下文大小"
description = "观察到的最长上下文大小建议15太短太长都会导致脑袋尖尖" description = "观察到的最长上下文大小建议15太短太长都会导致脑袋尖尖"
@ -488,6 +514,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

@ -11,7 +11,7 @@ from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp
logger = get_logger("action_taken") logger = get_logger("action_taken")
# 常量定义 # 常量定义
WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒 WAITING_TIME_THRESHOLD = 1200 # 等待新消息时间阈值,单位秒
@register_action @register_action

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

@ -133,7 +133,7 @@ class ActionModifier:
reply_sequence.append(action_type == "reply") reply_sequence.append(action_type == "reply")
# 检查no_reply比例 # 检查no_reply比例
print(f"no_reply_count: {no_reply_count}, len(recent_cycles): {len(recent_cycles)}") # print(f"no_reply_count: {no_reply_count}, len(recent_cycles): {len(recent_cycles)}")
# print(1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111) # print(1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111)
if len(recent_cycles) >= (5 * global_config.chat.exit_focus_threshold) and ( if len(recent_cycles) >= (5 * global_config.chat.exit_focus_threshold) and (
no_reply_count / len(recent_cycles) no_reply_count / len(recent_cycles)
@ -143,20 +143,45 @@ class ActionModifier:
result["remove"].append("no_reply") result["remove"].append("no_reply")
result["remove"].append("reply") result["remove"].append("reply")
# 获取最近三次的reply状态 # 计算连续回复的相关阈值
last_three = reply_sequence[-3:] if len(reply_sequence) >= 3 else reply_sequence
max_reply_num = int(global_config.focus_chat.consecutive_replies * 3.2)
sec_thres_reply_num = int(global_config.focus_chat.consecutive_replies * 2)
one_thres_reply_num = int(global_config.focus_chat.consecutive_replies * 1.5)
# 获取最近max_reply_num次的reply状态
if len(reply_sequence) >= max_reply_num:
last_max_reply_num = reply_sequence[-max_reply_num:]
else:
last_max_reply_num = reply_sequence[:]
# 详细打印阈值和序列信息,便于调试
logger.debug(
f"连续回复阈值: max={max_reply_num}, sec={sec_thres_reply_num}, one={one_thres_reply_num}"
f"最近reply序列: {last_max_reply_num}"
)
# print(f"consecutive_replies: {consecutive_replies}")
# 根据最近的reply情况决定是否移除reply动作 # 根据最近的reply情况决定是否移除reply动作
if len(last_three) >= 3 and all(last_three): if len(last_max_reply_num) >= max_reply_num and all(last_max_reply_num):
# 如果最近三次都是reply直接移除 # 如果最近max_reply_num次都是reply直接移除
result["remove"].append("reply") result["remove"].append("reply")
elif len(last_three) >= 2 and all(last_three[-2:]): logger.info(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply直接移除")
# 如果最近两次都是reply40%概率移除 elif len(last_max_reply_num) >= sec_thres_reply_num and all(last_max_reply_num[-sec_thres_reply_num:]):
if random.random() < 0.4: # 如果最近sec_thres_reply_num次都是reply40%概率移除
if random.random() < 0.4 / global_config.focus_chat.consecutive_replies:
result["remove"].append("reply") result["remove"].append("reply")
elif last_three and last_three[-1]: 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}概率移除,移除")
# 如果最近一次是reply20%概率移除 else:
if random.random() < 0.2: 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:]):
# 如果最近one_thres_reply_num次都是reply20%概率移除
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}概率移除,移除")
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}概率移除,不移除")
else:
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

View File

@ -239,7 +239,7 @@ class Hippocampus:
# 不再需要 time_info 参数 # 不再需要 time_info 参数
prompt = ( prompt = (
f'这是一段文字:\n{text}\n\n我想让你基于这段文字来概括"{topic}"这个概念,帮我总结成一句自然的话,' f'这是一段文字:\n{text}\n\n我想让你基于这段文字来概括"{topic}"这个概念,帮我总结成一句自然的话,'
f"要求包含对这个概念的定义,内容,知识,可以包含时间和人物。只输出这句话就好" f"要求包含对这个概念的定义,内容,知识,但是这些信息必须来自这段文字,不能添加信息。\n,请包含时间和人物。只输出这句话就好"
) )
return prompt return prompt

View File

@ -885,6 +885,17 @@ API_SERVER_STYLE_CONFIG = {
}, },
} }
ACTION_MANAGER_STYLE_CONFIG = {
"advanced": {
"console_format": "<level>{time:HH:mm:ss}</level> | <fg #FFA01F>动作选择</fg #FFA01F> | <fg #FFA01F>{message}</fg #FFA01F>",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 动作选择 | {message}",
},
"simple": {
"console_format": "<level>{time:HH:mm:ss}</level> | <fg #FFA01F>动作选择</fg #FFA01F> | <fg #FFA01F>{message}</fg #FFA01F>",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 动作选择 | {message}",
},
}
# maim_message 消息服务样式配置 # maim_message 消息服务样式配置
MAIM_MESSAGE_STYLE_CONFIG = { MAIM_MESSAGE_STYLE_CONFIG = {
"advanced": { "advanced": {
@ -909,6 +920,9 @@ EMOJI_STYLE_CONFIG = EMOJI_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else EMOJI_ST
PFC_ACTION_PLANNER_STYLE_CONFIG = ( PFC_ACTION_PLANNER_STYLE_CONFIG = (
PFC_ACTION_PLANNER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PFC_ACTION_PLANNER_STYLE_CONFIG["advanced"] PFC_ACTION_PLANNER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PFC_ACTION_PLANNER_STYLE_CONFIG["advanced"]
) )
ACTION_MANAGER_STYLE_CONFIG = (
ACTION_MANAGER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else ACTION_MANAGER_STYLE_CONFIG["advanced"]
)
REMOTE_STYLE_CONFIG = REMOTE_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else REMOTE_STYLE_CONFIG["advanced"] REMOTE_STYLE_CONFIG = REMOTE_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else REMOTE_STYLE_CONFIG["advanced"]
BASE_TOOL_STYLE_CONFIG = BASE_TOOL_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else BASE_TOOL_STYLE_CONFIG["advanced"] BASE_TOOL_STYLE_CONFIG = BASE_TOOL_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else BASE_TOOL_STYLE_CONFIG["advanced"]
PERSON_INFO_STYLE_CONFIG = PERSON_INFO_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PERSON_INFO_STYLE_CONFIG["advanced"] PERSON_INFO_STYLE_CONFIG = PERSON_INFO_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PERSON_INFO_STYLE_CONFIG["advanced"]

View File

@ -48,6 +48,7 @@ from src.common.logger import (
API_SERVER_STYLE_CONFIG, API_SERVER_STYLE_CONFIG,
NORMAL_CHAT_RESPONSE_STYLE_CONFIG, NORMAL_CHAT_RESPONSE_STYLE_CONFIG,
EXPRESS_STYLE_CONFIG, EXPRESS_STYLE_CONFIG,
ACTION_MANAGER_STYLE_CONFIG,
) )
# 可根据实际需要补充更多模块配置 # 可根据实际需要补充更多模块配置
@ -100,6 +101,7 @@ MODULE_LOGGER_CONFIGS = {
"normal_chat": NORMAL_CHAT_STYLE_CONFIG, # 一般水群 "normal_chat": NORMAL_CHAT_STYLE_CONFIG, # 一般水群
"focus_chat": FOCUS_CHAT_STYLE_CONFIG, # 专注水群 "focus_chat": FOCUS_CHAT_STYLE_CONFIG, # 专注水群
"expressor": EXPRESS_STYLE_CONFIG, # 麦麦表达 "expressor": EXPRESS_STYLE_CONFIG, # 麦麦表达
"action_manager": ACTION_MANAGER_STYLE_CONFIG, # 动作选择
# ...如有更多模块,继续添加... # ...如有更多模块,继续添加...
} }

View File

@ -141,8 +141,11 @@ class FocusChatConfig(ConfigBase):
compress_length_limit: int = 5 compress_length_limit: int = 5
"""最多压缩份数,超过该数值的压缩上下文会被删除""" """最多压缩份数,超过该数值的压缩上下文会被删除"""
think_interval: int = 1 think_interval: float = 1
"""思考间隔(秒)""" """思考间隔(秒)"""
consecutive_replies: float = 1
"""连续回复能力,值越高,麦麦连续回复的概率越高"""
@dataclass @dataclass

View File

@ -34,7 +34,7 @@ class PicAction(PluginAction):
"当有人要求你生成并发送一张图片时使用", "当有人要求你生成并发送一张图片时使用",
"当有人让你画一张图时使用", "当有人让你画一张图时使用",
] ]
default = True default = False
action_config_file_name = "pic_action_config.toml" action_config_file_name = "pic_action_config.toml"
def __init__( def __init__(

View File

@ -1,5 +1,5 @@
[inner] [inner]
version = "2.6.0" version = "2.7.0"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请在修改后将version的值进行变更 #如果你想要修改配置文件请在修改后将version的值进行变更
@ -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 # 学习间隔 单位秒
@ -92,6 +92,8 @@ talk_frequency_down_groups = [] #降低回复频率的群号码
[focus_chat] #专注聊天 [focus_chat] #专注聊天
think_interval = 3 # 思考间隔 单位秒,可以有效减少消耗 think_interval = 3 # 思考间隔 单位秒,可以有效减少消耗
consecutive_replies = 1 # 连续回复能力,值越高,麦麦连续回复的概率越高
observation_context_size = 16 # 观察到的最长上下文大小,建议15太短太长都会导致脑袋尖尖 observation_context_size = 16 # 观察到的最长上下文大小,建议15太短太长都会导致脑袋尖尖
compressed_length = 8 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度超过心流观察到的上下文长度会压缩最短压缩长度为5 compressed_length = 8 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度超过心流观察到的上下文长度会压缩最短压缩长度为5
@ -194,16 +196,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 +215,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 +230,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 +245,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 +262,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 +282,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 +292,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 +316,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 # 暂时无效