更新bug
This commit is contained in:
+111
-5
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user