更新bug

This commit is contained in:
Your Name
2026-07-31 11:48:16 +08:00
parent f913a57529
commit f22cc1a70d
109 changed files with 37586 additions and 927 deletions
+111 -5
View File
@@ -72,6 +72,22 @@ AUTO_REPLY_TEXT = "在的,您慢慢说,我这边看着呢。"
POLL_INTERVAL = 2.0
MOUSE_IDLE_ENABLED = True
MOUSE_IDLE_SECONDS = 20.0
MESSAGE_BATCH_WINDOW_SECONDS = 20.0
MESSAGE_BATCH_WINDOW_MIN_SECONDS = 1.0
MESSAGE_BATCH_WINDOW_MAX_SECONDS = 120.0
def normalize_message_batch_window_seconds(value, default=MESSAGE_BATCH_WINDOW_SECONDS):
"""读取本地设置时安全归一化消息合并等待时间。"""
if isinstance(value, bool):
return float(default)
try:
seconds = float(value)
except (TypeError, ValueError):
return float(default)
if not MESSAGE_BATCH_WINDOW_MIN_SECONDS <= seconds <= MESSAGE_BATCH_WINDOW_MAX_SECONDS:
return float(default)
return seconds
# 暖白与医疗绿组成的浅色主题,保持长时间使用时的清晰度与舒适度。
BG = "#F2F6F3"
@@ -212,15 +228,27 @@ class BotThread(threading.Thread):
"""在后台运行企业微信轮询,避免阻塞 Tk 主线程。"""
def __init__(self, target_queue, reply_text, poll_seconds,
mouse_idle_enabled=True, mouse_idle_seconds=20.0):
mouse_idle_enabled=True, mouse_idle_seconds=20.0,
message_batch_window_seconds=MESSAGE_BATCH_WINDOW_SECONDS):
super().__init__(daemon=True)
self.target_queue = target_queue
self.reply_text = reply_text
self.poll_seconds = poll_seconds
self.mouse_idle_enabled = mouse_idle_enabled
self.mouse_idle_seconds = mouse_idle_seconds
self.bot = None
self.message_batch_window_seconds = MESSAGE_BATCH_WINDOW_SECONDS
self.set_message_batch_window_seconds(message_batch_window_seconds)
self.stop_event = threading.Event()
def set_message_batch_window_seconds(self, value):
"""更新下一次消息合并窗口;不会改变已经开始等待的窗口快照。"""
seconds = normalize_message_batch_window_seconds(value)
self.message_batch_window_seconds = seconds
bot = getattr(self, "bot", None)
if bot is not None:
bot.message_batch_window_seconds = seconds
def run(self):
bot = None
failed = False
@@ -229,8 +257,10 @@ class BotThread(threading.Thread):
bot_module.AUTO_REPLY_TEXT = self.reply_text
bot = bot_module.WeChatBot()
self.bot = bot
bot.mouse_idle_enabled = self.mouse_idle_enabled
bot.mouse_idle_seconds = self.mouse_idle_seconds
bot.message_batch_window_seconds = self.message_batch_window_seconds
bot._stop_check = self.stop_event
bot.safe_window_mode = True
bot.auto_activate_window = True
@@ -247,6 +277,10 @@ class BotThread(threading.Thread):
f"[+] 人机共存已开启:鼠标静止 "
f"{bot.mouse_idle_seconds:.0f} 秒后才自动操作"
)
print(
f"[+] 连续消息合并等待:"
f"{bot.message_batch_window_seconds:.0f}"
)
last_ready = bool(bot._window_ready)
self.target_queue.put(("status", "running" if last_ready else "waiting"))
@@ -257,6 +291,8 @@ class BotThread(threading.Thread):
}))
while not self.stop_event.is_set():
# GUI 保存后只改变尚未开始的下一轮合并窗口;当前窗口不会被截断。
bot.message_batch_window_seconds = self.message_batch_window_seconds
bot._poll_once()
if bot.security_verification_required:
failed = True
@@ -286,6 +322,7 @@ class BotThread(threading.Thread):
))
self.target_queue.put(("status", "error"))
finally:
self.bot = None
if not failed:
self.target_queue.put(("status", "stopped"))
@@ -576,6 +613,7 @@ class App(tk.Tk):
"poll_interval": POLL_INTERVAL,
"mouse_idle_enabled": MOUSE_IDLE_ENABLED,
"mouse_idle_seconds": MOUSE_IDLE_SECONDS,
"message_batch_window_seconds": MESSAGE_BATCH_WINDOW_SECONDS,
}
def _load_runtime_settings(self):
@@ -591,6 +629,12 @@ class App(tk.Tk):
reply = str(saved.get("auto_reply_text", settings["auto_reply_text"])).strip()
poll = float(saved.get("poll_interval", settings["poll_interval"]))
idle = float(saved.get("mouse_idle_seconds", settings["mouse_idle_seconds"]))
batch_window = normalize_message_batch_window_seconds(
saved.get(
"message_batch_window_seconds",
settings["message_batch_window_seconds"],
)
)
if poll <= 0 or idle < 0:
raise ValueError
settings.update({
@@ -600,6 +644,7 @@ class App(tk.Tk):
saved.get("mouse_idle_enabled", settings["mouse_idle_enabled"])
),
"mouse_idle_seconds": idle,
"message_batch_window_seconds": batch_window,
})
except (TypeError, ValueError):
return self._runtime_defaults()
@@ -625,13 +670,21 @@ class App(tk.Tk):
"_reply_var",
"_poll_var",
"_idle_seconds_var",
"_batch_window_var",
"_mouse_idle_var",
)):
return False
try:
poll = float(self._poll_var.get().strip())
idle = float(self._idle_seconds_var.get().strip())
if poll <= 0 or idle < 0:
batch_window = float(self._batch_window_var.get().strip())
if (
poll <= 0
or idle < 0
or not MESSAGE_BATCH_WINDOW_MIN_SECONDS
<= batch_window
<= MESSAGE_BATCH_WINDOW_MAX_SECONDS
):
raise ValueError
except ValueError:
if not silent and hasattr(self, "_runtime_save_status"):
@@ -646,6 +699,7 @@ class App(tk.Tk):
"poll_interval": poll,
"mouse_idle_enabled": bool(self._mouse_idle_var.get()),
"mouse_idle_seconds": idle,
"message_batch_window_seconds": batch_window,
}
if settings != self._last_runtime_settings:
tmp_path = APP_SETTINGS_FILE + ".tmp"
@@ -661,6 +715,9 @@ class App(tk.Tk):
)
return False
self._last_runtime_settings = dict(settings)
thread = getattr(self, "_thread", None)
if thread is not None and thread.is_alive():
thread.set_message_batch_window_seconds(batch_window)
if not silent and hasattr(self, "_runtime_save_status"):
self._runtime_save_status.configure(
text=f"已自动保存 {time.strftime('%H:%M:%S')}",
@@ -2590,14 +2647,31 @@ class App(tk.Tk):
number_row.pack(fill="x")
number_row.grid_columnconfigure(0, weight=1)
number_row.grid_columnconfigure(1, weight=1)
number_row.grid_columnconfigure(2, weight=1)
self._poll_var = tk.StringVar(
value=str(self._runtime_settings["poll_interval"]),
)
self._idle_seconds_var = tk.StringVar(
value=str(self._runtime_settings["mouse_idle_seconds"]),
)
self._batch_window_var = tk.StringVar(
value=str(self._runtime_settings["message_batch_window_seconds"]),
)
self._field(number_row, "轮询间隔(秒)", self._poll_var, column=0)
self._field(number_row, "鼠标静止等待(秒)", self._idle_seconds_var, column=1)
self._field(
number_row,
"连续消息合并等待(秒)",
self._batch_window_var,
column=2,
)
tk.Label(
settings.body,
text="同一客户连续发送多条消息时,等待后合并为一次回复(可设置 1–120 秒,默认 20 秒)。",
bg=PANEL,
fg=TEXT_FAINT,
font=TYPE_SMALL,
).pack(anchor="w", pady=(10, 0))
self._runtime_save_status = tk.Label(
settings.body,
text="已加载保存配置" if os.path.exists(APP_SETTINGS_FILE) else "修改后自动保存",
@@ -2626,6 +2700,7 @@ class App(tk.Tk):
self._reply_var,
self._poll_var,
self._idle_seconds_var,
self._batch_window_var,
self._mouse_idle_var,
):
variable.trace_add("write", self._schedule_runtime_settings_save)
@@ -3375,9 +3450,18 @@ class App(tk.Tk):
Toggle(
body,
vision_var,
"用视觉模式(需要多模态模型",
"始终使用视觉模式(图片/表情会自动启用",
).grid(row=len(fields), column=0, columnspan=2, sticky="w", pady=(10, 0))
ui_guard_var = tk.BooleanVar(
value=bool(getattr(ai_config, "AI_UI_GUARD_ENABLED", True))
)
Toggle(
body,
ui_guard_var,
"启用 AI 页面守护(仅在页面异常时调用视觉模型)",
).grid(row=len(fields) + 1, column=0, columnspan=2, sticky="w", pady=(8, 0))
status = tk.Label(
body,
text="",
@@ -3386,10 +3470,10 @@ class App(tk.Tk):
font=TYPE_SMALL,
anchor="w",
)
status.grid(row=len(fields) + 1, column=0, columnspan=2, sticky="ew", pady=(10, 0))
status.grid(row=len(fields) + 2, column=0, columnspan=2, sticky="ew", pady=(10, 0))
buttons = tk.Frame(body, bg=PANEL)
buttons.grid(row=len(fields) + 2, column=0, columnspan=2, sticky="e", pady=(14, 0))
buttons.grid(row=len(fields) + 3, column=0, columnspan=2, sticky="e", pady=(14, 0))
def save_config():
for label, key, value_type, _secret in fields:
@@ -3404,6 +3488,7 @@ class App(tk.Tk):
return
setattr(ai_config, key, value)
ai_config.AI_USE_VISION = vision_var.get()
ai_config.AI_UI_GUARD_ENABLED = ui_guard_var.get()
ai_config.AI_ENABLED = self._ai_var.get()
ai_config.AI_CONTEXT_ENABLED = self._context_var.get()
ai_config.AI_COUNTER_INSULT_ENABLED = self._counter_insult_var.get()
@@ -3653,6 +3738,22 @@ class App(tk.Tk):
messagebox.showerror("参数错误", "鼠标静止等待必须是大于或等于 0 的数字。", parent=self)
self._show_page("通用设置")
return
try:
batch_window_seconds = float(self._batch_window_var.get().strip())
if not (
MESSAGE_BATCH_WINDOW_MIN_SECONDS
<= batch_window_seconds
<= MESSAGE_BATCH_WINDOW_MAX_SECONDS
):
raise ValueError
except ValueError:
messagebox.showerror(
"参数错误",
"连续消息合并等待必须是 1–120 秒之间的数字。",
parent=self,
)
self._show_page("通用设置")
return
self._save_ai_switches()
self._stdout_proxy = LogQueue(self._queue)
@@ -3663,6 +3764,7 @@ class App(tk.Tk):
poll_seconds,
mouse_idle_enabled=self._mouse_idle_var.get(),
mouse_idle_seconds=idle_seconds,
message_batch_window_seconds=batch_window_seconds,
)
self._running = True
self._start_time = time.time()
@@ -3813,6 +3915,8 @@ class App(tk.Tk):
except Exception:
pass
self._append(str(data.get("message") or "后台配置同步完成"), "ok")
for diagnostic in data.get("diagnostics") or []:
self._append(str(diagnostic), "notify")
except queue.Empty:
pass
@@ -3901,6 +4005,8 @@ def main():
if not handle_startup_update(startup_result.get("release")):
return
app = App()
for diagnostic in startup_result.get("diagnostics") or []:
app._append(str(diagnostic), "notify")
app.protocol("WM_DELETE_WINDOW", app.on_close)
app.mainloop()