This commit is contained in:
BuildTools
2025-09-12 20:34:41 +08:00
10 changed files with 111 additions and 104 deletions

9
bot.py
View File

@@ -7,17 +7,9 @@ import time
import platform import platform
import traceback import traceback
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv
from rich.traceback import install from rich.traceback import install
from colorama import init, Fore from colorama import init, Fore
if os.path.exists(".env"):
load_dotenv(".env", override=True)
print("成功加载环境变量配置")
else:
print("未找到.env文件请确保程序所需的环境变量被正确设置")
raise FileNotFoundError(".env 文件不存在,请创建并配置所需的环境变量")
# maim_message imports for console input # maim_message imports for console input
# 最早期初始化日志系统,确保所有后续模块都使用正确的日志格式 # 最早期初始化日志系统,确保所有后续模块都使用正确的日志格式
@@ -45,7 +37,6 @@ logger.info(f"已设置工作目录为: {script_dir}")
confirm_logger = get_logger("confirm") confirm_logger = get_logger("confirm")
# 获取没有加载env时的环境变量 # 获取没有加载env时的环境变量
env_mask = {key: os.getenv(key) for key in os.environ}
uvicorn_server = None uvicorn_server = None
driver = None driver = None

View File

@@ -136,7 +136,7 @@ def init_prompt():
4. 不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号()表情包at或 @等 )。 4. 不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号()表情包at或 @等 )。
最终请输出一条简短、完整且口语化的回复。 最终请输出一条简短、完整且口语化的回复。
-------------------------------- --------------------------------
{time_block} {time_block}
注意不要复读你前面发过的内容,意思相近也不行。 注意不要复读你前面发过的内容,意思相近也不行。
@@ -1028,6 +1028,23 @@ class DefaultReplyer:
prompt = Prompt(template=template_prompt.template, parameters=prompt_parameters) prompt = Prompt(template=template_prompt.template, parameters=prompt_parameters)
prompt_text = await prompt.build() prompt_text = await prompt.build()
# --- 动态添加分割指令 ---
if global_config.response_splitter.enable and global_config.response_splitter.split_mode == "llm":
split_instruction = """
## 消息分段艺术
为了模仿真实人类的聊天节奏,你可以在需要时将一条回复分成几段发送。
**核心原则**: 只有当分段能**增强表达效果**或**控制信息节奏**时,才在断句处使用 `[SPLIT]` 标记。
**参考场景**:
- 当你想表达一个转折或停顿时。
- 当你想先说结论,再补充说明时。
**任务**: 请结合你的智慧和人设,自然地决定是否需要分段。如果需要,请在最恰当的位置插入 `[SPLIT]` 标记。
"""
# 将分段指令添加到提示词顶部
prompt_text = f"{split_instruction}\n{prompt_text}"
return prompt_text return prompt_text
async def build_prompt_rewrite_context( async def build_prompt_rewrite_context(

View File

@@ -331,8 +331,23 @@ def process_llm_response(text: str, enable_splitter: bool = True, enable_chinese
) )
if global_config.response_splitter.enable and enable_splitter: if global_config.response_splitter.enable and enable_splitter:
split_sentences = split_into_sentences_w_remove_punctuation(cleaned_text) logger.info(f"回复分割器已启用,模式: {global_config.response_splitter.split_mode}")
split_mode = global_config.response_splitter.split_mode
if split_mode == "llm" and "[SPLIT]" in cleaned_text:
logger.debug("检测到 [SPLIT] 标记,使用 LLM 自定义分割。")
split_sentences_raw = cleaned_text.split("[SPLIT]")
split_sentences = [s.strip() for s in split_sentences_raw if s.strip()]
else:
if split_mode == "llm":
logger.debug("未检测到 [SPLIT] 标记,本次不进行分割。")
split_sentences = [cleaned_text]
else: # mode == "punctuation"
logger.debug("使用基于标点的传统模式进行分割。")
split_sentences = split_into_sentences_w_remove_punctuation(cleaned_text)
else: else:
logger.debug("回复分割器已禁用。")
split_sentences = [cleaned_text] split_sentences = [cleaned_text]
sentences = [] sentences = []

View File

@@ -24,8 +24,8 @@ def get_global_api() -> MessageServer: # sourcery skip: extract-method
# 设置基本参数 # 设置基本参数
kwargs = { kwargs = {
"host": os.environ["HOST"], "host": global_config.server.host,
"port": int(os.environ["PORT"]), "port": int(global_config.server.port),
"app": get_global_server().get_app(), "app": get_global_server().get_app(),
} }

View File

@@ -2,7 +2,7 @@ from fastapi import FastAPI, APIRouter
from fastapi.middleware.cors import CORSMiddleware # 新增导入 from fastapi.middleware.cors import CORSMiddleware # 新增导入
from typing import Optional from typing import Optional
from uvicorn import Config, Server as UvicornServer from uvicorn import Config, Server as UvicornServer
import os from src.config.config import global_config
from rich.traceback import install from rich.traceback import install
install(extra_lines=3) install(extra_lines=3)
@@ -98,5 +98,5 @@ def get_global_server() -> Server:
"""获取全局服务器实例""" """获取全局服务器实例"""
global global_server global global_server
if global_server is None: if global_server is None:
global_server = Server(host=os.environ["HOST"], port=int(os.environ["PORT"])) global_server = Server(host=global_config.server.host,port=int(global_config.server.port),)
return global_server return global_server

View File

@@ -43,8 +43,8 @@ from src.config.official_configs import (
CrossContextConfig, CrossContextConfig,
PermissionConfig, PermissionConfig,
CommandConfig, CommandConfig,
MaizoneIntercomConfig,
PlanningSystemConfig, PlanningSystemConfig,
ServerConfig,
) )
from .api_ada_configs import ( from .api_ada_configs import (
@@ -399,9 +399,7 @@ class Config(ValidatedConfigBase):
cross_context: CrossContextConfig = Field( cross_context: CrossContextConfig = Field(
default_factory=lambda: CrossContextConfig(), description="跨群聊上下文共享配置" default_factory=lambda: CrossContextConfig(), description="跨群聊上下文共享配置"
) )
maizone_intercom: MaizoneIntercomConfig = Field( server: ServerConfig = Field(default_factory=lambda: ServerConfig(), description="主服务器配置")
default_factory=lambda: MaizoneIntercomConfig(), description="Maizone互通组配置"
)
class APIAdapterConfig(ValidatedConfigBase): class APIAdapterConfig(ValidatedConfigBase):

View File

@@ -385,6 +385,7 @@ class EmojiConfig(ValidatedConfigBase):
content_filtration: bool = Field(default=False, description="内容过滤") content_filtration: bool = Field(default=False, description="内容过滤")
filtration_prompt: str = Field(default="符合公序良俗", description="过滤提示") filtration_prompt: str = Field(default="符合公序良俗", description="过滤提示")
enable_emotion_analysis: bool = Field(default=True, description="启用情感分析") enable_emotion_analysis: bool = Field(default=True, description="启用情感分析")
max_context_emojis: int = Field(default=30, description="每次随机传递给LLM的表情包最大数量0为全部")
class MemoryConfig(ValidatedConfigBase): class MemoryConfig(ValidatedConfigBase):
@@ -475,6 +476,7 @@ class ResponseSplitterConfig(ValidatedConfigBase):
"""回复分割器配置类""" """回复分割器配置类"""
enable: bool = Field(default=True, description="启用") enable: bool = Field(default=True, description="启用")
split_mode: str = Field(default="llm", description="分割模式: 'llm''punctuation'")
max_length: int = Field(default=256, description="最大长度") max_length: int = Field(default=256, description="最大长度")
max_sentence_num: int = Field(default=3, description="最大句子数") max_sentence_num: int = Field(default=3, description="最大句子数")
enable_kaomoji_protection: bool = Field(default=False, description="启用颜文字保护") enable_kaomoji_protection: bool = Field(default=False, description="启用颜文字保护")
@@ -492,6 +494,13 @@ class ExperimentalConfig(ValidatedConfigBase):
pfc_chatting: bool = Field(default=False, description="启用PFC聊天") pfc_chatting: bool = Field(default=False, description="启用PFC聊天")
class ServerConfig(ValidatedConfigBase):
"""主服务器配置类"""
host: str = Field(default="127.0.0.1", description="主服务器监听地址")
port: int = Field(default=8080, description="主服务器监听端口")
class MaimMessageConfig(ValidatedConfigBase): class MaimMessageConfig(ValidatedConfigBase):
"""maim_message配置类""" """maim_message配置类"""
@@ -674,15 +683,6 @@ class CrossContextConfig(ValidatedConfigBase):
enable: bool = Field(default=False, description="是否启用跨群聊上下文共享功能") enable: bool = Field(default=False, description="是否启用跨群聊上下文共享功能")
groups: List[ContextGroup] = Field(default_factory=list, description="上下文共享组列表") groups: List[ContextGroup] = Field(default_factory=list, description="上下文共享组列表")
class MaizoneIntercomConfig(ValidatedConfigBase):
"""Maizone互通组配置"""
enable: bool = Field(default=False, description="是否启用Maizone互通组功能")
groups: List[ContextGroup] = Field(default_factory=list, description="Maizone互通组列表")
class CommandConfig(ValidatedConfigBase): class CommandConfig(ValidatedConfigBase):
"""命令系统配置类""" """命令系统配置类"""

View File

@@ -28,9 +28,9 @@ class AtAction(BaseAction):
# === 功能描述(必须填写)=== # === 功能描述(必须填写)===
action_parameters = {"user_name": "需要艾特用户的名字", "at_message": "艾特用户时要发送的消息"} action_parameters = {"user_name": "需要艾特用户的名字", "at_message": "艾特用户时要发送的消息"}
action_require = [ action_require = [
"需要艾特某个用户时使用", "当用户明确要求你去'''''提醒''艾特'某人时使用",
"当你需要提醒特定用户查看消息时使用", "当你判断,为了让特定的人看到消息,需要代表用户去呼叫他/她时使用",
"在回复中需要明确指向某个用户时使用", "例如:'你去叫一下张三''提醒一下李四开会'",
] ]
llm_judge_prompt = """ llm_judge_prompt = """
判定是否需要使用艾特用户动作的条件: 判定是否需要使用艾特用户动作的条件:
@@ -150,11 +150,16 @@ class AtAction(BaseAction):
logger.error("回复器生成回复失败") logger.error("回复器生成回复失败")
return False, "回复生成失败" return False, "回复生成失败"
final_message = llm_response.get("content", "") final_message_raw = llm_response.get("content", "")
if not final_message: if not final_message_raw:
logger.warning("回复器生成了空内容") logger.warning("回复器生成了空内容")
return False, "回复内容为空" return False, "回复内容为空"
# 对LLM生成的内容进行后处理解析[SPLIT]标记并将分段消息合并
from src.chat.utils.utils import process_llm_response
final_message_segments = process_llm_response(final_message_raw, enable_splitter=True, enable_chinese_typo=False)
final_message = " ".join(final_message_segments)
await self.send_command( await self.send_command(
"SEND_AT_MESSAGE", "SEND_AT_MESSAGE",
args={"group_id": self.chat_stream.group_info.group_id, "qq_id": user_id, "text": final_message}, args={"group_id": self.chat_stream.group_info.group_id, "qq_id": user_id, "text": final_message},

View File

@@ -1,6 +1,5 @@
import random import random
from typing import Tuple from typing import Tuple
from collections import deque
# 导入新插件系统 # 导入新插件系统
from src.plugin_system import BaseAction, ActionActivationType, ChatMode from src.plugin_system import BaseAction, ActionActivationType, ChatMode
@@ -10,7 +9,7 @@ from src.common.logger import get_logger
# 导入API模块 - 标准Python包方式 # 导入API模块 - 标准Python包方式
from src.plugin_system.apis import llm_api, message_api from src.plugin_system.apis import llm_api, message_api
from src.chat.emoji_system.emoji_manager import get_emoji_manager from src.chat.emoji_system.emoji_manager import get_emoji_manager, MaiEmoji
from src.chat.utils.utils_image import image_path_to_base64 from src.chat.utils.utils_image import image_path_to_base64
from src.config.config import global_config from src.config.config import global_config
@@ -21,7 +20,6 @@ logger = get_logger("emoji")
class EmojiAction(BaseAction): class EmojiAction(BaseAction):
"""表情动作 - 发送表情包""" """表情动作 - 发送表情包"""
# --- 类级别属性 ---
# 激活设置 # 激活设置
if global_config.emoji.emoji_activate_type == "llm": if global_config.emoji.emoji_activate_type == "llm":
activation_type = ActionActivationType.LLM_JUDGE activation_type = ActionActivationType.LLM_JUDGE
@@ -35,9 +33,6 @@ class EmojiAction(BaseAction):
# 动作基本信息 # 动作基本信息
action_name = "emoji" action_name = "emoji"
action_description = "发送表情包辅助表达情绪" action_description = "发送表情包辅助表达情绪"
# 最近发送表情的历史记录
_sent_emoji_history = deque(maxlen=4)
# LLM判断提示词 # LLM判断提示词
llm_judge_prompt = """ llm_judge_prompt = """
@@ -72,12 +67,12 @@ class EmojiAction(BaseAction):
reason = self.action_data.get("reason", "表达当前情绪") reason = self.action_data.get("reason", "表达当前情绪")
logger.info(f"{self.log_prefix} 发送表情原因: {reason}") logger.info(f"{self.log_prefix} 发送表情原因: {reason}")
# 2. 获取所有表情包 # 2. 获取所有有效的表情包对象
emoji_manager = get_emoji_manager() emoji_manager = get_emoji_manager()
all_emojis_obj = [e for e in emoji_manager.emoji_objects if not e.is_deleted] all_emojis_obj: list[MaiEmoji] = [e for e in emoji_manager.emoji_objects if not e.is_deleted and e.description]
if not all_emojis_obj: if not all_emojis_obj:
logger.warning(f"{self.log_prefix} 无法获取任何表情包") logger.warning(f"{self.log_prefix} 无法获取任何带有描述的有效表情包")
return False, "无法获取任何表情包" return False, "无法获取任何带有描述的有效表情包"
# 3. 准备情感数据和后备列表 # 3. 准备情感数据和后备列表
emotion_map = {} emotion_map = {}
@@ -90,29 +85,23 @@ class EmojiAction(BaseAction):
desc = emoji.description desc = emoji.description
emotions = emoji.emotion emotions = emoji.emotion
# 使用 emoji 对象的 hash 作为唯一标识符 all_emojis_data.append((b64, desc))
all_emojis_data.append((b64, desc, emoji.hash))
for emo in emotions: for emo in emotions:
if emo not in emotion_map: if emo not in emotion_map:
emotion_map[emo] = [] emotion_map[emo] = []
emotion_map[emo].append((b64, desc, emoji.hash)) emotion_map[emo].append((b64, desc))
if not all_emojis_data: if not all_emojis_data:
logger.warning(f"{self.log_prefix} 无法加载任何有效的表情包数据") logger.warning(f"{self.log_prefix} 无法加载任何有效的表情包数据")
return False, "无法加载任何有效的表情包数据" return False, "无法加载任何有效的表情包数据"
available_emotions = list(emotion_map.keys()) available_emotions = list(emotion_map.keys())
emoji_base64, emoji_description = "", ""
chosen_emoji_b64, chosen_emoji_desc, chosen_emoji_hash = None, None, None
if not available_emotions: if not available_emotions:
logger.warning(f"{self.log_prefix} 获取到的表情包均无情感标签, 将随机发送") logger.warning(f"{self.log_prefix} 获取到的表情包均无情感标签, 将随机发送")
# 随机选择一个不在历史记录中的表情 emoji_base64, emoji_description = random.choice(all_emojis_data)
selectable_emojis = [e for e in all_emojis_data if e[2] not in self._sent_emoji_history]
if not selectable_emojis: # 如果都发过了,就从全部里面随机选
selectable_emojis = all_emojis_data
chosen_emoji_b64, chosen_emoji_desc, chosen_emoji_hash = random.choice(selectable_emojis)
else: else:
# 获取最近的5条消息内容用于判断 # 获取最近的5条消息内容用于判断
recent_messages = message_api.get_recent_messages(chat_id=self.chat_id, limit=5) recent_messages = message_api.get_recent_messages(chat_id=self.chat_id, limit=5)
@@ -125,75 +114,62 @@ class EmojiAction(BaseAction):
show_actions=False, show_actions=False,
) )
# 4. 构建prompt让LLM选择多个情感 # 4. 构建prompt让LLM选择情感
prompt = f""" prompt = f"""
你是一个正在进行聊天的网友,你需要根据一个理由和最近的聊天记录,从一个情感标签列表中选择最匹配的 **3个** 情感标签,并按匹配度从高到低排序 你是一个正在进行聊天的网友,你需要根据一个理由和最近的聊天记录,从一个情感标签列表中选择最匹配的一个
这是最近的聊天记录: 这是最近的聊天记录:
{messages_text} {messages_text}
这是理由:“{reason} 这是理由:“{reason}
这里是可用的情感标签:{available_emotions} 这里是可用的情感标签:{available_emotions}
请直接返回一个包含3个最匹配情感标签的有序列表例如["开心", "激动", "有趣"],不要进行任何解释或添加其他多余的文字。 请直接返回最匹配的那个情感标签,不要进行任何解释或添加其他多余的文字。
""" """
if global_config.debug.show_prompt:
logger.info(f"{self.log_prefix} 生成的LLM Prompt: {prompt}")
else:
logger.debug(f"{self.log_prefix} 生成的LLM Prompt: {prompt}")
# 5. 调用LLM # 5. 调用LLM
models = llm_api.get_available_models() models = llm_api.get_available_models()
chat_model_config = models.get("planner") chat_model_config = models.get("planner")
if not chat_model_config: if not chat_model_config:
logger.error(f"{self.log_prefix} 未找到 'planner' 模型配置无法调用LLM") logger.error(f"{self.log_prefix} 未找到'utils_small'模型配置无法调用LLM")
return False, "未找到 'planner' 模型配置" return False, "未找到'utils_small'模型配置"
success, chosen_emotions_str, _, _ = await llm_api.generate_with_model( success, chosen_emotion, _, _ = await llm_api.generate_with_model(
prompt, model_config=chat_model_config, request_type="emoji_selection" prompt, model_config=chat_model_config, request_type="emoji"
) )
selected_emoji_info = None if not success:
if success: logger.warning(f"{self.log_prefix} LLM调用失败: {chosen_emotion}, 将随机选择一个表情包")
try: emoji_base64, emoji_description = random.choice(all_emojis_data)
# 解析LLM返回的列表
import json
chosen_emotions = json.loads(chosen_emotions_str)
if isinstance(chosen_emotions, list):
logger.info(f"{self.log_prefix} LLM选择的情感候选项: {chosen_emotions}")
# 遍历候选项,找到第一个不在历史记录中的表情
for emotion in chosen_emotions:
matched_key = next((key for key in emotion_map if emotion in key), None)
if matched_key:
# 从匹配到的表情中,随机选一个不在历史记录的
candidate_emojis = [e for e in emotion_map[matched_key] if e[2] not in self._sent_emoji_history]
if candidate_emojis:
selected_emoji_info = random.choice(candidate_emojis)
break # 找到后立即跳出循环
else:
logger.warning(f"{self.log_prefix} LLM返回的不是一个列表: {chosen_emotions_str}")
except (json.JSONDecodeError, TypeError):
logger.warning(f"{self.log_prefix} 解析LLM返回的情感列表失败: {chosen_emotions_str}")
if selected_emoji_info:
chosen_emoji_b64, chosen_emoji_desc, chosen_emoji_hash = selected_emoji_info
logger.info(f"{self.log_prefix} 从候选项中选择表情: {chosen_emoji_desc}")
else: else:
if not success: chosen_emotion = chosen_emotion.strip().replace('"', "").replace("'", "")
logger.warning(f"{self.log_prefix} LLM调用失败, 将随机选择一个表情包") logger.info(f"{self.log_prefix} LLM选择的情感: {chosen_emotion}")
else:
logger.warning(f"{self.log_prefix} 所有候选项均在最近发送历史中, 将随机选择") # 使用模糊匹配来查找最相关的情感标签
matched_key = next((key for key in emotion_map if chosen_emotion in key), None)
selectable_emojis = [e for e in all_emojis_data if e[2] not in self._sent_emoji_history] if matched_key:
if not selectable_emojis: emoji_base64, emoji_description = random.choice(emotion_map[matched_key])
selectable_emojis = all_emojis_data logger.info(f"{self.log_prefix} 找到匹配情感 '{chosen_emotion}' (匹配到: '{matched_key}') 的表情包: {emoji_description}")
chosen_emoji_b64, chosen_emoji_desc, chosen_emoji_hash = random.choice(selectable_emojis) else:
logger.warning(
f"{self.log_prefix} LLM选择的情感 '{chosen_emotion}' 不在可用列表中, 将随机选择一个表情包"
)
emoji_base64, emoji_description = random.choice(all_emojis_data)
# 7. 发送表情包并更新历史记录 # 7. 发送表情包
if chosen_emoji_b64 and chosen_emoji_hash: success = await self.send_emoji(emoji_base64)
success = await self.send_emoji(chosen_emoji_b64)
if success:
self._sent_emoji_history.append(chosen_emoji_hash)
logger.info(f"{self.log_prefix} 表情包发送成功: {chosen_emoji_desc}")
logger.debug(f"{self.log_prefix} 最近表情历史: {list(self._sent_emoji_history)}")
return True, f"发送表情包: {chosen_emoji_desc}"
logger.error(f"{self.log_prefix} 表情包发送失败") if not success:
return False, "表情包发送失败" logger.error(f"{self.log_prefix} 表情包发送失败")
await self.store_action_info(action_build_into_prompt = True,action_prompt_display =f"发送了一个{chosen_emotion}的表情包,但失败了",action_done= False)
return False, "表情包发送失败"
await self.store_action_info(action_build_into_prompt = True,action_prompt_display =f"发送了一个{chosen_emotion}的表情包",action_done= True)
return True, f"发送表情包: {emoji_description}"
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} 表情动作执行失败: {e}", exc_info=True) logger.error(f"{self.log_prefix} 表情动作执行失败: {e}", exc_info=True)

View File

@@ -1,5 +1,5 @@
[inner] [inner]
version = "6.8.2" version = "6.8.5"
#----以下是给开发人员阅读的如果你只是部署了MoFox-Bot不需要阅读---- #----以下是给开发人员阅读的如果你只是部署了MoFox-Bot不需要阅读----
#如果你想要修改配置文件请递增version的值 #如果你想要修改配置文件请递增version的值
@@ -246,6 +246,7 @@ steal_emoji = true # 是否偷取表情包让MoFox-Bot可以将一些表情
content_filtration = false # 是否启用表情包过滤,只有符合该要求的表情包才会被保存 content_filtration = false # 是否启用表情包过滤,只有符合该要求的表情包才会被保存
filtration_prompt = "符合公序良俗" # 表情包过滤要求,只有符合该要求的表情包才会被保存 filtration_prompt = "符合公序良俗" # 表情包过滤要求,只有符合该要求的表情包才会被保存
enable_emotion_analysis = false # 是否启用表情包感情关键词二次识别,启用后表情包在第一次识别完毕后将送入第二次大模型识别来总结感情关键词,并构建进回复和决策器的上下文消息中 enable_emotion_analysis = false # 是否启用表情包感情关键词二次识别,启用后表情包在第一次识别完毕后将送入第二次大模型识别来总结感情关键词,并构建进回复和决策器的上下文消息中
max_context_emojis = 30 # 每次随机传递给LLM的表情包详细描述的最大数量0为全部
[memory] [memory]
enable_memory = true # 是否启用记忆系统 enable_memory = true # 是否启用记忆系统
@@ -321,6 +322,7 @@ word_replace_rate=0.006 # 整词替换概率
[response_splitter] [response_splitter]
enable = true # 是否启用回复分割器 enable = true # 是否启用回复分割器
split_mode = "punctuation" # 分割模式: "llm" - 由语言模型决定, "punctuation" - 基于标点符号
max_length = 512 # 回复允许的最大长度 max_length = 512 # 回复允许的最大长度
max_sentence_num = 8 # 回复允许的最大句子数 max_sentence_num = 8 # 回复允许的最大句子数
enable_kaomoji_protection = false # 是否启用颜文字保护 enable_kaomoji_protection = false # 是否启用颜文字保护
@@ -479,6 +481,9 @@ insomnia_duration_minutes = [30, 60] # 单次失眠状态的持续时间范围
# 入睡后,经过一段延迟后触发失眠判定的延迟时间(分钟),设置为范围以增加随机性 # 入睡后,经过一段延迟后触发失眠判定的延迟时间(分钟),设置为范围以增加随机性
insomnia_trigger_delay_minutes = [15, 45] insomnia_trigger_delay_minutes = [15, 45]
[server]
host = "127.0.0.1"
port = 8080
[cross_context] # 跨群聊/私聊上下文共享配置 [cross_context] # 跨群聊/私聊上下文共享配置
# 这是总开关,用于一键启用或禁用此功能 # 这是总开关,用于一键启用或禁用此功能