This commit is contained in:
墨梓柒
2025-05-06 21:02:16 +08:00
6 changed files with 19 additions and 310 deletions

5
bot.py
View File

@@ -17,6 +17,11 @@ from rich.traceback import install
install(extra_lines=3)
# 设置工作目录为脚本所在目录
script_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(script_dir)
print(f"已设置工作目录为: {script_dir}")
logger = get_logger("main")
confirm_logger = get_logger("confirm")

View File

@@ -1,302 +0,0 @@
import flet as ft
import os
import atexit
import psutil # Keep for initial PID checks maybe, though state should handle it
# import asyncio # <--- 如果不再需要其他异步任务,可以考虑移除
# --- Import refactored modules --- #
from src.MaiGoi.state import AppState
from src.MaiGoi.process_manager import (
start_bot_and_show_console,
output_processor_loop, # Needed for restarting on navigate
cleanup_on_exit,
handle_disconnect,
)
from src.MaiGoi.ui_views import (
create_main_view,
create_console_view,
create_adapters_view,
create_process_output_view,
)
from src.MaiGoi.config_manager import load_config
# --- Import the new settings view --- #
from src.MaiGoi.ui_settings_view import create_settings_view
# --- Global AppState instance --- #
# This holds all the state previously scattered as globals
app_state = AppState()
# --- File Picker Result Handler Placeholder ---
# We need a placeholder function or logic to handle the result here if needed
# For now, the result will be handled within the adapters view itself.
# def handle_file_picker_result(e: ft.FilePickerResultEvent):
# print("File picker result in launcher (should be handled in view):", e.files)
# --- atexit Cleanup Registration --- #
# Register the cleanup function from the process manager module
# It needs access to the app_state
atexit.register(cleanup_on_exit, app_state)
print("[Main Script] atexit cleanup handler from process_manager registered.", flush=True)
# --- Routing Logic --- #
def route_change(route: ft.RouteChangeEvent):
"""Handles Flet route changes, creating and appending views."""
page = route.page
target_route = route.route
# --- 移除异步显示弹窗的辅助函数 ---
# async def show_python_path_dialog():
# ...
# Clear existing views before adding new ones
page.views.clear()
# Always add the main view
main_view = create_main_view(page, app_state)
page.views.append(main_view)
# --- Handle Specific Routes --- #
if target_route == "/console":
# 清理:移除之前添加的 is_python_dialog_opening 标志(如果愿意)
# app_state.is_python_dialog_opening = False # 可选清理
console_view = create_console_view(page, app_state)
page.views.append(console_view)
# --- 仅设置标志 ---
print(f"[Route Change /console] Checking python_path: '{app_state.python_path}'")
if not app_state.python_path:
print("[Route Change /console] python_path is empty, setting flag.")
app_state.needs_python_path_dialog = True
# *** 不再在这里打开弹窗 ***
# Check process status and potentially restart processor loop if needed
is_running = app_state.bot_pid is not None and psutil.pid_exists(app_state.bot_pid)
print(
f"[Route Change /console] Checking status: PID={app_state.bot_pid}, is_running={is_running}, stop_event={app_state.stop_event.is_set()}",
flush=True,
)
if is_running:
print("[Route Change /console] Process is running.", flush=True)
# If the processor loop was stopped (e.g., by navigating away or stop button),
# but the process is still running, restart the loop.
if app_state.stop_event.is_set():
print("[Route Change /console] Stop event was set, clearing and restarting processor loop.", flush=True)
app_state.stop_event.clear()
# Make sure output_list_view is available before starting loop
if not app_state.output_list_view:
print("[Route Change /console] Warning: output_list_view is None when restarting loop. Creating.")
app_state.output_list_view = ft.ListView(
expand=True, spacing=2, auto_scroll=app_state.is_auto_scroll_enabled, padding=5
)
console_view.controls[1].controls[0].content = app_state.output_list_view # Update content in view
page.run_task(output_processor_loop, page, app_state)
else:
print("[Route Change /console] Process is not running.", flush=True)
# Ensure console view shows the 'not running' state if needed
if app_state.output_list_view:
# Check if already has the message? Might add duplicates.
# Simple approach: just add it if the list is empty or last msg isn't it.
add_not_running_msg = True
if app_state.output_list_view.controls:
last_control = app_state.output_list_view.controls[-1]
# Check if it's Text and value is not None before checking content
if (
isinstance(last_control, ft.Text)
and last_control.value is not None
and "Bot 进程未运行" in last_control.value
):
add_not_running_msg = False
if add_not_running_msg:
app_state.output_list_view.controls.append(ft.Text("--- Bot 进程未运行 ---", italic=True))
else:
# If list view doesn't exist here, create it and add the message
print("[Route Change /console] Creating ListView to show 'not running' message.")
app_state.output_list_view = ft.ListView(
expand=True, spacing=2, auto_scroll=app_state.is_auto_scroll_enabled, padding=5
)
app_state.output_list_view.controls.append(ft.Text("--- Bot 进程未运行 ---", italic=True))
# Update the console view container's content
console_view.controls[1].controls[0].content = app_state.output_list_view
elif target_route == "/adapters":
adapters_view = create_adapters_view(page, app_state)
page.views.append(adapters_view)
elif target_route == "/settings":
# Call the new settings view function
settings_view = create_settings_view(page, app_state)
page.views.append(settings_view)
# --- Handle Dynamic Adapter Output Route --- #
# Check if the route matches the pattern /adapters/<something>
elif target_route.startswith("/adapters/") and len(target_route.split("/")) == 3:
parts = target_route.split("/")
process_id = parts[2] # Extract the process ID (which is the script path for now)
print(f"[Route Change] Detected adapter output route for ID: {process_id}")
adapter_output_view = create_process_output_view(page, app_state, process_id)
if adapter_output_view:
page.views.append(adapter_output_view)
else:
# If view creation failed (e.g., process state not found), show error and stay on previous view?
# Or redirect back to /adapters? Let's go back to adapters list.
print(f"[Route Change] Failed to create output view for {process_id}. Redirecting to /adapters.")
# Avoid infinite loop if /adapters also fails
if len(page.views) > 1: # Ensure we don't pop the main view
page.views.pop() # Pop the failed view attempt
# Find the adapters view if it exists, otherwise just update
adapters_view_index = -1
for i, view in enumerate(page.views):
if view.route == "/adapters":
adapters_view_index = i
break
if adapters_view_index == -1: # Adapters view wasn't in stack? Add it.
adapters_view = create_adapters_view(page, app_state)
page.views.append(adapters_view)
# Go back to the adapters list route to rebuild the view stack correctly
page.go("/adapters")
return # Prevent page.update() below
# Update the page to show the correct view(s)
page.update()
def view_pop(e: ft.ViewPopEvent):
"""Handles view popping (e.g., back navigation)."""
page = e.page
# Remove the top view
page.views.pop()
if page.views:
top_view = page.views[-1]
# Go to the route of the view now at the top of the stack
# This will trigger route_change again to rebuild the view stack correctly
page.go(top_view.route)
# else: print("Warning: Popped the last view.")
# --- Main Application Setup --- #
def main(page: ft.Page):
# Load initial config and store in state
if os.path.exists("logs/interest/interest_history.log"):
os.remove("logs/interest/interest_history.log")
loaded_config = load_config()
app_state.gui_config = loaded_config
app_state.adapter_paths = loaded_config.get("adapters", []).copy()
app_state.bot_script_path = loaded_config.get("bot_script_path", "bot.py") # Load bot script path
# 加载用户自定义的 Python 路径
if "python_path" in loaded_config and os.path.exists(loaded_config["python_path"]):
app_state.python_path = loaded_config["python_path"]
print(f"[Main] 从配置加载 Python 路径: {app_state.python_path}")
print(f"[Main] Initial adapters loaded: {app_state.adapter_paths}")
# Set script_dir in AppState early
app_state.script_dir = os.path.dirname(os.path.abspath(__file__))
print(f"[Main] Script directory set in state: {app_state.script_dir}", flush=True)
# --- Setup File Picker --- #
# Create the FilePicker instance
# The on_result handler will be set dynamically in the view that uses it
app_state.file_picker = ft.FilePicker()
# Add the FilePicker to the page's overlay controls
page.overlay.append(app_state.file_picker)
print("[Main] FilePicker created and added to page overlay.")
page.title = "MaiBot 启动器"
page.window.width = 1400
page.window.height = 1000 # Increased height slightly for monitor
page.vertical_alignment = ft.MainAxisAlignment.START
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
# --- Apply Theme from Config --- #
saved_theme = app_state.gui_config.get("theme", "System").upper()
try:
page.theme_mode = ft.ThemeMode[saved_theme]
print(f"[Main] Applied theme from config: {page.theme_mode}")
except KeyError:
print(f"[Main] Warning: Invalid theme '{saved_theme}' in config. Falling back to System.")
page.theme_mode = ft.ThemeMode.SYSTEM
# --- 自定义主题颜色 --- #
# 创建深色主题,使橙色变得更暗
dark_theme = ft.Theme(
color_scheme_seed=ft.colors.ORANGE,
primary_color=ft.colors.ORANGE_700, # 使用更暗的橙色
color_scheme=ft.ColorScheme(
primary=ft.colors.ORANGE_700,
primary_container=ft.colors.ORANGE_800,
),
)
# 创建亮色主题
light_theme = ft.Theme(
color_scheme_seed=ft.colors.ORANGE,
)
# 设置自定义主题
page.theme = light_theme
page.dark_theme = dark_theme
page.padding = 0 # <-- 将页面 padding 设置为 0
# --- Create the main 'Start Bot' button and store in state --- #
# This button needs to exist before the first route_change call
app_state.start_bot_button = ft.FilledButton(
"启动 MaiBot 主程序 (bot.py)",
icon=ft.icons.SMART_TOY_OUTLINED,
# The click handler now calls the function from process_manager
on_click=lambda _: start_bot_and_show_console(page, app_state),
expand=True,
tooltip="启动主程序并在新视图中显示控制台输出",
)
print("[Main] Start Bot Button created and stored in state.", flush=True)
# --- Routing Setup --- #
page.on_route_change = route_change
page.on_view_pop = view_pop
# --- Disconnect Handler --- #
# Pass app_state to the disconnect handler
page.on_disconnect = lambda e: handle_disconnect(page, app_state, e)
print("[Main] Registered page.on_disconnect handler.", flush=True)
# Prevent immediate close to allow cleanup
page.window_prevent_close = True
# --- Hide Native Title Bar --- #
# page.window_title_bar_hidden = True
# page.window.frameless = True
# --- Initial Navigation --- #
# Trigger the initial route change to build the first view
page.go(page.route if page.route else "/")
# --- Run Flet App --- #
if __name__ == "__main__":
# No need to initialize globals here anymore, AppState handles it.
ft.app(target=main)
# This print will appear *after* the Flet window closes,
# but *before* the atexit handler runs.
print("[Main Script] Flet app exited. atexit handler should run next.", flush=True)
# --- Removed Code Sections (Previously Globals and Functions) ---
# (Keep this comment block or similar for reference if desired)
# Removed: bot_process, bot_pid, output_queue, stop_event, interest_monitor_control,
# output_list_view, start_bot_button (now in AppState),
# is_auto_scroll_enabled (now in AppState)
# Removed: ansi_converter
# Removed: cleanup_on_exit (moved to process_manager)
# Removed: update_page_safe (moved to utils)
# Removed: show_snackbar (moved to utils)
# Removed: run_script (moved to utils)
# Removed: handle_disconnect (moved to process_manager)
# Removed: stop_bot_process (moved to process_manager)
# Removed: read_process_output (moved to process_manager)
# Removed: output_processor_loop (moved to process_manager)
# Removed: start_bot_and_show_console (moved to process_manager)
# Removed: create_console_view (moved to ui_views)
# (Main view creation logic also moved to ui_views within create_main_view)

View File

@@ -8,14 +8,16 @@ logger = get_logger("rename_person_tool")
class RenamePersonTool(BaseTool):
name = "rename_person"
description = "这个工具可以改变用户的昵称。你可以选择改变对他人的称呼。"
description = (
"这个工具可以改变用户的昵称。你可以选择改变对他人的称呼。你想给人改名,叫别人别的称呼,需要调用这个工具。"
)
parameters = {
"type": "object",
"properties": {
"person_name": {"type": "string", "description": "需要重新取名的用户的当前昵称"},
"message_content": {
"type": "string",
"description": "可选的。当前的聊天内容或特定要求,用于提供取名建议的上下文。",
"description": "当前的聊天内容或特定要求,用于提供取名建议的上下文,尽可能详细",
},
},
"required": ["person_name"],

View File

@@ -672,12 +672,12 @@ class SubHeartflowManager:
"""处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)"""
# 注意:这里不需要再获取锁,因为 sbhf_focus_into_absent 内部会处理锁
logger.debug(f"[管理器 HFC 处理器] 接收到来自 {subheartflow_id} 的 HFC 无回复信号")
await self.sbhf_focus_into_absent(subheartflow_id)
await self.sbhf_focus_into_absent_or_chat(subheartflow_id)
# --- 结束新增 --- #
# --- 新增:处理来自 HeartFChatting 的状态转换请求 --- #
async def sbhf_focus_into_absent(self, subflow_id: Any):
async def sbhf_focus_into_absent_or_chat(self, subflow_id: Any):
"""
接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 ABSENT 或 CHAT。
通常在连续多次 "no_reply" 后被调用。
@@ -729,6 +729,8 @@ class SubHeartflowManager:
f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})"
)
try:
# 从HFC到CHAT时清空兴趣字典
subflow.clear_interest_dict()
await subflow.change_chat_state(target_state)
final_state = subflow.chat_state.chat_status
if final_state == target_state:

View File

@@ -215,9 +215,11 @@ class PersonInfoManager:
if old_name:
qv_name_prompt += f"你之前叫他{old_name},是因为{old_reason}"
qv_name_prompt += f"\n其他取名的要求是:{request}"
qv_name_prompt += f"\n其他取名的要求是:{request},不要太浮夸"
qv_name_prompt += "\n请根据以上用户信息想想你叫他什么比较好请最好使用用户的qq昵称可以稍作修改"
qv_name_prompt += (
"\n请根据以上用户信息想想你叫他什么比较好不要太浮夸请最好使用用户的qq昵称可以稍作修改"
)
if existing_names:
qv_name_prompt += f"\n请注意,以下名称已被使用,不要使用以下昵称:{existing_names}\n"
qv_name_prompt += "请用json给出你的想法并给出理由示例如下"

View File

@@ -69,8 +69,8 @@ nonebot-qq="http://127.0.0.1:18002/api/message"
allow_focus_mode = true # 是否允许专注聊天状态
# 是否启用heart_flowC(HFC)模式
# 启用后麦麦会自主选择进入heart_flowC模式(持续一段时间进行主动的观察和回复并给出回复比较消耗token
base_normal_chat_num = 3 # 最多允许多少个群进行普通聊天
base_focused_chat_num = 2 # 最多允许多少个群进行专注聊天
base_normal_chat_num = 8 # 最多允许多少个群进行普通聊天
base_focused_chat_num = 5 # 最多允许多少个群进行专注聊天
observation_context_size = 15 # 观察到的最长上下文大小,建议15太短太长都会导致脑袋尖尖
message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟