This commit is contained in:
minecraft1024a
2025-11-02 10:50:22 +08:00
29 changed files with 626 additions and 493 deletions

View File

@@ -14,6 +14,9 @@ from src.config.config import global_config
logger = get_logger("plan_executor")
# 全局背景任务集合
_background_tasks = set()
class ChatterPlanExecutor:
"""
@@ -89,7 +92,9 @@ class ChatterPlanExecutor:
# 将其他动作放入后台任务执行,避免阻塞主流程
if other_actions:
asyncio.create_task(self._execute_other_actions(other_actions, plan))
task = asyncio.create_task(self._execute_other_actions(other_actions, plan))
_background_tasks.add(task)
task.add_done_callback(_background_tasks.discard)
logger.info(f"已将 {len(other_actions)} 个其他动作放入后台任务执行。")
# 注意:后台任务的结果不会立即计入本次返回的统计数据

View File

@@ -254,15 +254,7 @@ class ChatterPlanFilter:
plan
)
actions_before_now = await get_actions_by_timestamp_with_chat(
chat_id=plan.chat_id,
timestamp_start=time.time() - 3600,
timestamp_end=time.time(),
limit=5,
)
actions_before_now_block = build_readable_actions(actions=actions_before_now)
actions_before_now_block = f"你刚刚选择并执行过的action是\n{actions_before_now_block}"
actions_before_now_block = ""
self.last_obs_time_mark = time.time()
@@ -285,6 +277,7 @@ class ChatterPlanFilter:
动作描述:不进行回复,等待合适的回复时机
- 当你刚刚发送了消息没有人回复时选择no_reply
- 当你一次发送了太多消息为了避免打扰聊天节奏选择no_reply
- 在认为对方话没有讲完的时候选择这个
{{
"action": "no_reply",
"reason":"不回复的原因"

View File

@@ -168,6 +168,9 @@ class ChatterActionPlanner:
action_modifier = ActionModifier(self.action_manager, self.chat_id)
await action_modifier.modify_actions()
# 在生成初始计划前,刷新缓存消息到未读列表
await self._flush_cached_messages_to_unread(context)
initial_plan = await self.generator.generate(chat_mode)
# 确保Plan中包含所有当前可用的动作
@@ -258,6 +261,9 @@ class ChatterActionPlanner:
# 重新运行主规划流程这次将正确使用Focus模式
return await self._enhanced_plan_flow(context)
try:
# Normal模式开始时刷新缓存消息到未读列表
await self._flush_cached_messages_to_unread(context)
unread_messages = context.get_unread_messages() if context else []
if not unread_messages:
@@ -459,6 +465,45 @@ class ChatterActionPlanner:
except Exception as e:
logger.warning(f"同步chat_mode到ChatStream失败: {e}")
async def _flush_cached_messages_to_unread(self, context: "StreamContext | None") -> list:
"""在planner开始时将缓存消息刷新到未读消息列表
此方法在动作修改器执行后、生成初始计划前调用,确保计划阶段能看到所有积累的消息。
Args:
context: 流上下文
Returns:
list: 刷新的消息列表
"""
if not context:
return []
try:
from src.chat.message_manager.message_manager import message_manager
stream_id = context.stream_id
if message_manager.is_running and message_manager.has_cached_messages(stream_id):
# 获取缓存消息
cached_messages = message_manager.flush_cached_messages(stream_id)
if cached_messages:
# 直接添加到上下文的未读消息列表
for message in cached_messages:
context.unread_messages.append(message)
logger.info(f"Planner开始前刷新缓存消息到未读列表: stream={stream_id}, 数量={len(cached_messages)}")
return cached_messages
return []
except ImportError:
logger.debug("MessageManager不可用跳过缓存刷新")
return []
except Exception as e:
logger.warning(f"Planner刷新缓存消息失败: error={e}")
return []
def _update_stats_from_execution_result(self, execution_result: dict[str, Any]):
"""根据执行结果更新规划器统计"""
if not execution_result:

View File

@@ -11,6 +11,9 @@ from src.plugin_system import BasePlugin, ComponentInfo, register_plugin
from src.plugin_system.base.component_types import PermissionNodeField
from src.plugin_system.base.config_types import ConfigField
# 全局背景任务集合
_background_tasks = set()
from .actions.read_feed_action import ReadFeedAction
from .actions.send_feed_action import SendFeedAction
from .commands.send_feed_command import SendFeedCommand
@@ -117,8 +120,14 @@ class MaiZoneRefactoredPlugin(BasePlugin):
logger.info("MaiZone重构版插件服务已注册。")
# --- 启动后台任务 ---
asyncio.create_task(scheduler_service.start())
asyncio.create_task(monitor_service.start())
task1 = asyncio.create_task(scheduler_service.start())
_background_tasks.add(task1)
task1.add_done_callback(_background_tasks.discard)
task2 = asyncio.create_task(monitor_service.start())
_background_tasks.add(task2)
task2.add_done_callback(_background_tasks.discard)
logger.info("MaiZone后台监控和定时任务已启动。")
def get_plugin_components(self) -> list[tuple[ComponentInfo, type]]:

View File

@@ -7,6 +7,7 @@ import base64
from collections.abc import Callable
from pathlib import Path
import aiofiles
import aiohttp
from src.common.logger import get_logger
@@ -86,8 +87,8 @@ class ImageService:
if b64_json:
image_bytes = base64.b64decode(b64_json)
file_path = Path(image_dir) / f"image_{i + 1}.png"
with open(file_path, "wb") as f:
f.write(image_bytes)
async with aiofiles.open(file_path, "wb") as f:
await f.write(image_bytes)
logger.info(f"成功保存AI图片到: {file_path}")
return True
else:

View File

@@ -12,6 +12,7 @@ from collections.abc import Callable
from pathlib import Path
from typing import Any
import aiofiles
import aiohttp
import bs4
import json5
@@ -397,8 +398,8 @@ class QZoneService:
}
# 成功获取后,异步写入本地文件作为备份
try:
with open(cookie_file_path, "wb") as f:
f.write(orjson.dumps(parsed_cookies))
async with aiofiles.open(cookie_file_path, "wb") as f:
await f.write(orjson.dumps(parsed_cookies))
logger.info(f"通过Napcat服务成功更新Cookie并已保存至: {cookie_file_path}")
except Exception as e:
logger.warning(f"保存Cookie到文件时出错: {e}")
@@ -413,8 +414,9 @@ class QZoneService:
logger.info("尝试从本地Cookie文件加载...")
if cookie_file_path.exists():
try:
with open(cookie_file_path, "rb") as f:
cookies = orjson.loads(f.read())
async with aiofiles.open(cookie_file_path, "rb") as f:
content = await f.read()
cookies = orjson.loads(content)
logger.info(f"成功从本地文件加载Cookie: {cookie_file_path}")
return cookies
except Exception as e:

View File

@@ -13,6 +13,8 @@ logger = get_logger("stt_whisper_plugin")
# 全局变量来缓存模型,避免重复加载
_whisper_model = None
_is_loading = False
_model_ready_event = asyncio.Event()
_background_tasks = set() # 背景任务集合
class LocalASRTool(BaseTool):
"""
@@ -29,7 +31,7 @@ class LocalASRTool(BaseTool):
"""
一个类方法,用于在插件加载时触发一次模型加载。
"""
global _whisper_model, _is_loading
global _whisper_model, _is_loading, _model_ready_event
if _whisper_model is None and not _is_loading:
_is_loading = True
try:
@@ -47,6 +49,7 @@ class LocalASRTool(BaseTool):
_whisper_model = None
finally:
_is_loading = False
_model_ready_event.set() # 通知等待的任务
async def execute(self, function_args: dict) -> str:
audio_path = function_args.get("audio_path")
@@ -55,9 +58,9 @@ class LocalASRTool(BaseTool):
return "错误:缺少 audio_path 参数。"
global _whisper_model
# 增强的等待逻辑:只要模型还没准备好,就一直等待后台加载任务完成
while _is_loading:
await asyncio.sleep(0.2)
# 使用 Event 等待模型加载完成
if _is_loading:
await _model_ready_event.wait()
if _whisper_model is None:
return "Whisper 模型加载失败,无法识别语音。"
@@ -90,7 +93,9 @@ class STTWhisperPlugin(BasePlugin):
from src.config.config import global_config
if global_config.voice.asr_provider == "local":
# 使用 create_task 在后台开始加载,不阻塞主流程
asyncio.create_task(LocalASRTool.load_model_once(self.config or {}))
task = asyncio.create_task(LocalASRTool.load_model_once(self.config or {}))
_background_tasks.add(task)
task.add_done_callback(_background_tasks.discard)
except Exception as e:
logger.error(f"触发 Whisper 模型预加载时出错: {e}")