更新
@@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""打包与源码共用的启动入口。
|
||||||
|
|
||||||
|
关键点:默认(Qt 界面)路径下绝不 import wechat_gui——那个模块顶层就会加载
|
||||||
|
tkinter/tcl,冷启动平白多出一大截;tk 经典界面只在显式要求或 PySide6 缺失
|
||||||
|
时才加载。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if "--classic-ui" not in sys.argv and os.environ.get("WECOM_RPA_CLASSIC_UI") != "1":
|
||||||
|
try:
|
||||||
|
from wechat_gui_qt import main as qt_main
|
||||||
|
except ImportError as exc:
|
||||||
|
if not (exc.name or "").startswith("PySide6"):
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
qt_main()
|
||||||
|
return
|
||||||
|
|
||||||
|
from wechat_gui import run_classic_ui
|
||||||
|
|
||||||
|
run_classic_ui()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -18,7 +18,9 @@ if (-not $SkipAppBuild) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$appExe = Join-Path $projectRoot "dist\ZhenAI-WeCom-Assistant-v$version.exe"
|
$appName = "ZhenAI-WeCom-Assistant-v$version"
|
||||||
|
$appDir = Join-Path $projectRoot "dist\$appName"
|
||||||
|
$appExe = Join-Path $appDir "$appName.exe"
|
||||||
if (-not (Test-Path -LiteralPath $appExe)) {
|
if (-not (Test-Path -LiteralPath $appExe)) {
|
||||||
throw ("Application EXE was not found: " + $appExe)
|
throw ("Application EXE was not found: " + $appExe)
|
||||||
}
|
}
|
||||||
@@ -74,7 +76,7 @@ $outputDir = Join-Path $projectRoot "dist\installer"
|
|||||||
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
|
||||||
Push-Location $projectRoot
|
Push-Location $projectRoot
|
||||||
try {
|
try {
|
||||||
& $makensis "/V3" "/INPUTCHARSET" "UTF8" ("/DAPP_VERSION=" + $version) ("/DAPP_FILE_VERSION=" + $fileVersion) ("/DSOURCE_EXE=" + $appExe) $nsi
|
& $makensis "/V3" "/INPUTCHARSET" "UTF8" ("/DAPP_VERSION=" + $version) ("/DAPP_FILE_VERSION=" + $fileVersion) ("/DSOURCE_DIR=" + $appDir) $nsi
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
throw "Installer compilation failed"
|
throw "Installer compilation failed"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function Test-BuildPython([string]$Candidate) {
|
|||||||
if (-not $Candidate -or -not (Test-Path -LiteralPath $Candidate)) {
|
if (-not $Candidate -or -not (Test-Path -LiteralPath $Candidate)) {
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
& $Candidate -c "import PyInstaller, PySide6, requests, numpy, PIL, pyautogui, pyperclip, win32gui, mcp" *> $null
|
& $Candidate -c "import PyInstaller, PySide6, requests, numpy, PIL, pyautogui, pyperclip, win32gui, mcp, rapidocr_onnxruntime" *> $null
|
||||||
return $LASTEXITCODE -eq 0
|
return $LASTEXITCODE -eq 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,15 +61,14 @@ finally {
|
|||||||
Pop-Location
|
Pop-Location
|
||||||
}
|
}
|
||||||
|
|
||||||
$exe = Join-Path $projectRoot "dist\ZhenAI-WeCom-Assistant-v$version.exe"
|
$appName = "ZhenAI-WeCom-Assistant-v$version"
|
||||||
|
$appDir = Join-Path $projectRoot "dist\$appName"
|
||||||
|
$exe = Join-Path $appDir "$appName.exe"
|
||||||
if (-not (Test-Path -LiteralPath $exe)) {
|
if (-not (Test-Path -LiteralPath $exe)) {
|
||||||
throw ("Build completed but EXE was not found: " + $exe)
|
throw ("Build completed but EXE was not found: " + $exe)
|
||||||
}
|
}
|
||||||
|
|
||||||
$packageToc = Join-Path $projectRoot "build\wechat_rpa\PKG-00.toc"
|
$internalDir = Join-Path $appDir "_internal"
|
||||||
if (-not (Test-Path -LiteralPath $packageToc)) {
|
|
||||||
throw "PyInstaller package manifest was not found"
|
|
||||||
}
|
|
||||||
$requiredRuntimeFiles = @(
|
$requiredRuntimeFiles = @(
|
||||||
"PySide6\QtWebEngineProcess.exe",
|
"PySide6\QtWebEngineProcess.exe",
|
||||||
"PySide6\resources\qtwebengine_resources.pak",
|
"PySide6\resources\qtwebengine_resources.pak",
|
||||||
@@ -77,9 +76,8 @@ $requiredRuntimeFiles = @(
|
|||||||
"PySide6\translations\qtwebengine_locales\zh-CN.pak",
|
"PySide6\translations\qtwebengine_locales\zh-CN.pak",
|
||||||
"VCRUNTIME140.dll"
|
"VCRUNTIME140.dll"
|
||||||
)
|
)
|
||||||
$packageManifest = (Get-Content -LiteralPath $packageToc -Raw).Replace('\\', '\')
|
|
||||||
foreach ($requiredFile in $requiredRuntimeFiles) {
|
foreach ($requiredFile in $requiredRuntimeFiles) {
|
||||||
if (-not $packageManifest.Contains($requiredFile)) {
|
if (-not (Test-Path -LiteralPath (Join-Path $internalDir $requiredFile))) {
|
||||||
throw ("Required embedded runtime file is missing: " + $requiredFile)
|
throw ("Required embedded runtime file is missing: " + $requiredFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,5 +101,6 @@ finally {
|
|||||||
Write-Output "Packaged Qt WebEngine self-check passed"
|
Write-Output "Packaged Qt WebEngine self-check passed"
|
||||||
|
|
||||||
$hash = (Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash
|
$hash = (Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash
|
||||||
|
Write-Output ("App folder: " + $appDir)
|
||||||
Write-Output ("EXE: " + $exe)
|
Write-Output ("EXE: " + $exe)
|
||||||
Write-Output ("SHA256: " + $hash)
|
Write-Output ("SHA256: " + $hash)
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""GUI 无关的后台运行时:日志转发与机器人轮询线程。
|
||||||
|
|
||||||
|
原先住在 wechat_gui.py(Tk 控制台)里,Qt 控制台 import 它时会连带把整个
|
||||||
|
tkinter/tcl 拖进进程——打包后的 EXE 每次冷启动都白付这笔钱。抽出来之后两个
|
||||||
|
界面共用,谁也不用替对方的依赖买单。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from runtime_paths import application_data_dir
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class LogQueue:
|
||||||
|
"""把后台线程的标准输出转发到界面,同时留一份带时间戳的磁盘副本。
|
||||||
|
|
||||||
|
界面日志随窗口关闭就没了,出问题时无从回溯——真正卡住发送的那一行往往
|
||||||
|
几分钟前就被刷走了。落盘副本让事后还查得到。
|
||||||
|
|
||||||
|
日志目录用的是可长期写入的数据目录:打包成 EXE 后 __file__ 指向随进程
|
||||||
|
销毁的解包目录,往那儿写等于退出即丢。
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOG_DIR = os.path.join(str(application_data_dir()), "logs")
|
||||||
|
KEEP_FILES = 20
|
||||||
|
|
||||||
|
def __init__(self, target_queue):
|
||||||
|
self.target_queue = target_queue
|
||||||
|
self._handle = None
|
||||||
|
self._open_log_file()
|
||||||
|
|
||||||
|
def _open_log_file(self):
|
||||||
|
try:
|
||||||
|
os.makedirs(self.LOG_DIR, exist_ok=True)
|
||||||
|
self._prune_old_logs()
|
||||||
|
stamp = time.strftime("%Y%m%d_%H%M%S")
|
||||||
|
self._handle = open(
|
||||||
|
os.path.join(self.LOG_DIR, f"gui_{stamp}.log"),
|
||||||
|
"a",
|
||||||
|
encoding="utf-8",
|
||||||
|
buffering=1,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
self._handle = None
|
||||||
|
|
||||||
|
def _prune_old_logs(self):
|
||||||
|
try:
|
||||||
|
files = sorted(
|
||||||
|
glob.glob(os.path.join(self.LOG_DIR, "gui_*.log")),
|
||||||
|
key=os.path.getmtime,
|
||||||
|
)
|
||||||
|
for path in files[: max(0, len(files) - self.KEEP_FILES + 1)]:
|
||||||
|
os.unlink(path)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write(self, message):
|
||||||
|
text = str(message).rstrip()
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
self.target_queue.put(("log", text))
|
||||||
|
if self._handle is not None:
|
||||||
|
try:
|
||||||
|
self._handle.write(f"{time.strftime('%H:%M:%S')} {text}\n")
|
||||||
|
except Exception:
|
||||||
|
self._handle = None
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
if self._handle is not None:
|
||||||
|
try:
|
||||||
|
self._handle.flush()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._handle is not None:
|
||||||
|
try:
|
||||||
|
self._handle.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._handle = None
|
||||||
|
|
||||||
|
|
||||||
|
class BotThread(threading.Thread):
|
||||||
|
"""在后台运行企业微信轮询,避免阻塞界面主线程。"""
|
||||||
|
|
||||||
|
def __init__(self, target_queue, reply_text, poll_seconds,
|
||||||
|
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 _report_progress(self, text):
|
||||||
|
"""把机器人此刻在做什么送到界面上那行进度文字。"""
|
||||||
|
try:
|
||||||
|
self.target_queue.put(("progress", str(text or "")))
|
||||||
|
except Exception:
|
||||||
|
# 进度提示纯属好看,永远不该把轮询带下去
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
bot = None
|
||||||
|
failed = False
|
||||||
|
try:
|
||||||
|
import wechat_bot as bot_module
|
||||||
|
|
||||||
|
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
|
||||||
|
bot.progress_cb = self._report_progress
|
||||||
|
|
||||||
|
if not bot.connect(activate=False, wait_if_missing=True):
|
||||||
|
failed = True
|
||||||
|
self.target_queue.put(("status", "error"))
|
||||||
|
return
|
||||||
|
|
||||||
|
print("[+] 窗口激活模式已开启:企业微信未显示时会自动还原到前台")
|
||||||
|
print("[i] 不会设置系统级置顶;仅检测到未读红点后才执行操作")
|
||||||
|
if bot.mouse_idle_enabled:
|
||||||
|
print(
|
||||||
|
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"))
|
||||||
|
self.target_queue.put(("info", {
|
||||||
|
"hwnd": f"0x{bot.hwnd:08X}",
|
||||||
|
"size": f"{bot.R - bot.L} x {bot.B - bot.T}",
|
||||||
|
"input": f"({bot.input_x}, {bot.input_y})",
|
||||||
|
}))
|
||||||
|
|
||||||
|
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
|
||||||
|
self.target_queue.put(("status", "verification"))
|
||||||
|
self.stop_event.set()
|
||||||
|
break
|
||||||
|
ready = bool(bot._window_ready)
|
||||||
|
if ready != last_ready:
|
||||||
|
last_ready = ready
|
||||||
|
self.target_queue.put(("status", "running" if ready else "waiting"))
|
||||||
|
if ready:
|
||||||
|
self.target_queue.put(("info", {
|
||||||
|
"hwnd": f"0x{bot.hwnd:08X}",
|
||||||
|
"size": f"{bot.R - bot.L} x {bot.B - bot.T}",
|
||||||
|
"input": f"({bot.input_x}, {bot.input_y})",
|
||||||
|
}))
|
||||||
|
self.target_queue.put(("stats", {
|
||||||
|
"replied": bot.reply_count,
|
||||||
|
"false_pos": len(bot.false_pos_rows),
|
||||||
|
}))
|
||||||
|
self.stop_event.wait(self.poll_seconds)
|
||||||
|
except Exception as exc:
|
||||||
|
failed = True
|
||||||
|
self.target_queue.put((
|
||||||
|
"log",
|
||||||
|
f"[-] 后台线程异常:{exc}\n{traceback.format_exc()}",
|
||||||
|
))
|
||||||
|
self.target_queue.put(("status", "error"))
|
||||||
|
finally:
|
||||||
|
self.bot = None
|
||||||
|
if not failed:
|
||||||
|
self.target_queue.put(("status", "stopped"))
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.stop_event.set()
|
||||||
@@ -10,13 +10,13 @@ Unicode true
|
|||||||
!define APP_FILE_VERSION "1.0.0.0"
|
!define APP_FILE_VERSION "1.0.0.0"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!ifndef SOURCE_EXE
|
!ifndef SOURCE_DIR
|
||||||
!define SOURCE_EXE "dist\ZhenAI-WeCom-Assistant-v${APP_VERSION}.exe"
|
!define SOURCE_DIR "dist\ZhenAI-WeCom-Assistant-v${APP_VERSION}"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!define APP_NAME "甄AI客服"
|
!define APP_NAME "甄AI客服"
|
||||||
!define APP_PUBLISHER "甄养堂"
|
!define APP_PUBLISHER "甄养堂"
|
||||||
; Qt WebEngine/PyInstaller 单文件程序不能在打包后改名,否则浏览器子进程启动会失败。
|
; Qt WebEngine/PyInstaller 程序不能在打包后改名,否则浏览器子进程启动会失败。
|
||||||
!define APP_EXE "ZhenAI-WeCom-Assistant-v${APP_VERSION}.exe"
|
!define APP_EXE "ZhenAI-WeCom-Assistant-v${APP_VERSION}.exe"
|
||||||
!define APP_DIR_NAME "ZhenAIService"
|
!define APP_DIR_NAME "ZhenAIService"
|
||||||
!define UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\ZhenAIService"
|
!define UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\ZhenAIService"
|
||||||
@@ -63,9 +63,11 @@ Section "安装甄AI客服" MainSection
|
|||||||
SetRegView 64
|
SetRegView 64
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
|
|
||||||
|
; 清掉旧版单文件 EXE 和上一版的运行库目录,避免新旧 DLL 混装。
|
||||||
Delete "$INSTDIR\ZhenAI-WeCom-Assistant-v*.exe"
|
Delete "$INSTDIR\ZhenAI-WeCom-Assistant-v*.exe"
|
||||||
Delete "$INSTDIR\甄AI客服.exe"
|
Delete "$INSTDIR\甄AI客服.exe"
|
||||||
File /oname=${APP_EXE} "${SOURCE_EXE}"
|
RMDir /r "$INSTDIR\_internal"
|
||||||
|
File /r "${SOURCE_DIR}\*.*"
|
||||||
WriteUninstaller "$INSTDIR\卸载甄AI客服.exe"
|
WriteUninstaller "$INSTDIR\卸载甄AI客服.exe"
|
||||||
|
|
||||||
CreateShortCut "$DESKTOP\甄AI客服.lnk" "$INSTDIR\${APP_EXE}" "" "$INSTDIR\${APP_EXE}" 0
|
CreateShortCut "$DESKTOP\甄AI客服.lnk" "$INSTDIR\${APP_EXE}" "" "$INSTDIR\${APP_EXE}" 0
|
||||||
@@ -96,6 +98,7 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\ZhenAI-WeCom-Assistant-v*.exe"
|
Delete "$INSTDIR\ZhenAI-WeCom-Assistant-v*.exe"
|
||||||
Delete "$INSTDIR\甄AI客服.exe"
|
Delete "$INSTDIR\甄AI客服.exe"
|
||||||
Delete "$INSTDIR\卸载甄AI客服.exe"
|
Delete "$INSTDIR\卸载甄AI客服.exe"
|
||||||
|
RMDir /r "$INSTDIR\_internal"
|
||||||
RMDir "$INSTDIR"
|
RMDir "$INSTDIR"
|
||||||
DeleteRegKey HKCU "${UNINSTALL_KEY}"
|
DeleteRegKey HKCU "${UNINSTALL_KEY}"
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
12:10:01 [待回复恢复] 已从磁盘恢复 4 个未完成任务。
|
|
||||||
12:10:01 [*] 正在查找企业微信主窗口...
|
|
||||||
12:10:01 [*] 企业微信存在 2 个同类顶层窗口,已挑选真正渲染了主界面的那一个(其余为子进程空壳窗口)。
|
|
||||||
12:10:01 [+] 检测到系统 DPI 缩放比例: 200.0%,启用自适应几何缩放。
|
|
||||||
12:10:01 [+] 挂载成功: HWND=0x000308E0, ClassName='WeWorkWindow', Title='企业微信', State='可监听'
|
|
||||||
12:10:01 窗口坐标: (459,427) → (2715,1745),尺寸: 2256×1318
|
|
||||||
12:10:01 动态导航宽度: 320px (置信度 1.00)
|
|
||||||
12:10:01 会话列表区域: left=779, top=539, 460×1206px
|
|
||||||
12:10:01 输入框估算坐标: (2154, 1625)
|
|
||||||
12:10:01 聊天区域: 1420×832px
|
|
||||||
12:10:01 [+] 窗口激活模式已开启:企业微信未显示时会自动还原到前台
|
|
||||||
12:10:01 [i] 不会设置系统级置顶;仅检测到未读红点后才执行操作
|
|
||||||
12:10:01 [+] 人机共存已开启:鼠标静止 5 秒后才自动操作
|
|
||||||
12:10:01 [+] 连续消息合并等待:2 秒
|
|
||||||
12:10:06 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:10:06 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:10:06 [会话识别] 检测到未读纯色文字头像候选,将先确认消息页与会话身份再回复。
|
|
||||||
12:10:06
|
|
||||||
[新消息] 正在处理 row0(坐标: 1009, 612)
|
|
||||||
12:10:39 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:10:39 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:10:46 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:10:47 [页面校验] 会话列表发生变化,实际打开对象与目标不一致,已停止后续发送。
|
|
||||||
12:10:49 [页面校准] 消息侧栏 320px→320px,输入面板顶边 1068px→1012px;会话列表、消息区与输入区域已同步重算。
|
|
||||||
12:10:51 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:10:51
|
|
||||||
[待回复恢复] 发现已读但没有回复的客户消息,正在恢复本次回复...
|
|
||||||
12:10:51
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:10:51 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:10:51 [会话守护] 发现客户新消息,先合并连续消息再回复。
|
|
||||||
12:10:51 [消息合并] 开始收集本会话 2 秒内的连续消息…
|
|
||||||
12:10:54 [消息合并] 收集完成(期间检测到 0 次消息画面更新),将只发起 1 次模型请求。
|
|
||||||
12:10:55 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:10:56 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:10:56 [AI] 本次提取的新内容:
|
|
||||||
你好
|
|
||||||
12:10:56 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (7232 bytes)
|
|
||||||
12:10:56 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:10:56 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:10:56 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:10:56 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:10:56 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:11:03 [AI] 回复内容: 你好呀,我是贴心管家,有什么想问的您慢慢说
|
|
||||||
12:11:04 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:11:08 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:11:09 [页面校验] 会话列表发生变化,实际打开对象与目标不一致,已停止后续发送。
|
|
||||||
12:11:12
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:11:13 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:11:13 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:11:14 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:11:14 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:11:14 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6873 bytes)
|
|
||||||
12:11:14 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:11:14 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:11:14 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:11:14 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:11:14 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:11:22 [AI] 回复内容: 在呢,您有什么事就直接说,我这边看着呢
|
|
||||||
12:11:23 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:11:27 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:11:28 [消息合并] 开始收集本会话 2 秒内的连续消息…
|
|
||||||
12:11:30 [消息合并] 收集完成(期间检测到 0 次消息画面更新),将只发起 1 次模型请求。
|
|
||||||
12:11:31 [人手] 检测到鼠标操作,暂停自动回复,还需静止 5s…
|
|
||||||
12:11:32 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (33368 bytes)
|
|
||||||
12:11:32 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:11:32 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:11:32 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:11:32 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:11:32 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:11:39 [AI] 回复内容: 看到了,是打卡异常提醒。您是想问怎么补卡吗?
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
12:25:43 [待回复恢复] 已从磁盘恢复 6 个未完成任务。
|
|
||||||
12:25:43 [*] 正在查找企业微信主窗口...
|
|
||||||
12:25:43 [*] 企业微信存在 2 个同类顶层窗口,已挑选真正渲染了主界面的那一个(其余为子进程空壳窗口)。
|
|
||||||
12:25:43 [+] 检测到系统 DPI 缩放比例: 200.0%,启用自适应几何缩放。
|
|
||||||
12:25:43 [+] 挂载成功: HWND=0x000308E0, ClassName='WeWorkWindow', Title='企业微信', State='可监听'
|
|
||||||
12:25:43 窗口坐标: (459,427) → (2715,1745),尺寸: 2256×1318
|
|
||||||
12:25:43 动态导航宽度: 320px (置信度 1.00)
|
|
||||||
12:25:43 会话列表区域: left=779, top=539, 460×1206px
|
|
||||||
12:25:43 输入框估算坐标: (2154, 1625)
|
|
||||||
12:25:43 聊天区域: 1420×832px
|
|
||||||
12:25:43 [+] 窗口激活模式已开启:企业微信未显示时会自动还原到前台
|
|
||||||
12:25:43 [i] 不会设置系统级置顶;仅检测到未读红点后才执行操作
|
|
||||||
12:25:43 [+] 人机共存已开启:鼠标静止 5 秒后才自动操作
|
|
||||||
12:25:43 [+] 连续消息合并等待:2 秒
|
|
||||||
12:25:47 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:25:47 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:25:57 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:25:57 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:26:17 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:26:17 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:26:17
|
|
||||||
[新消息] 正在处理 row0(坐标: 1009, 612)
|
|
||||||
12:26:18 [页面校验] 会话列表发生变化,实际打开对象与目标不一致,已停止后续发送。
|
|
||||||
12:26:18 [页面校验] 未能可靠打开目标会话,本轮停止,等待下次重新识别。
|
|
||||||
12:26:20 [页面校准] 消息侧栏 320px→320px,输入面板顶边 1068px→1012px;会话列表、消息区与输入区域已同步重算。
|
|
||||||
12:26:21
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:26:22 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:26:22 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:26:23 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:26:23 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:26:23 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:26:23 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:26:23 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:26:23 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:26:24 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:26:24 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:26:34 [AI] 回复内容: 在呢,您有什么事慢慢说,我这边听着呢
|
|
||||||
12:26:35 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:26:41 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:26:41 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:26:44
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:26:45 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:26:45 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:26:46 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:26:46 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:26:46 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:26:46 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:26:46 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:26:46 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:26:46 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:26:46 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:26:56 [AI] 回复内容: 在呢,您有什么事直接跟我说就行,我这边看着呢
|
|
||||||
12:26:57 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:27:02
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:27:04 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:27:04 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:27:04 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:27:05 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:27:05 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:27:05 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:27:05 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:27:05 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:27:05 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:27:05 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:27:11 [AI] 回复内容: 在呢,您说,我这边听着
|
|
||||||
12:27:12 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:27:15
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:27:17 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:27:17 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:27:18 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:27:18 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:27:18 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:27:18 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:27:18 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:27:18 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:27:18 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:27:18 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:27:26 [AI] 回复内容: 在呢,您有什么事直接跟我说就行,我这边看着呢
|
|
||||||
12:27:27 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:27:30 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:27:30 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:27:33
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:27:34 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:27:34 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:27:35 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:27:35 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:27:35 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:27:35 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:27:35 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:27:35 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:27:35 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:27:35 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:27:43 [AI] 回复内容: 在呢,您慢慢说,有什么事需要我帮您处理?
|
|
||||||
12:27:44 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:27:50 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:27:50 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:27:50 [排队] 当前会话已占用 69 秒仍未回完,先放行其他会话,稍后再回来处理它。
|
|
||||||
12:27:53
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:27:54 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:27:54 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:27:55 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:27:55 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:27:55 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:27:55 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:27:55 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:27:55 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:27:55 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:27:55 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:28:02 [AI] 回复内容: 在呢,您慢慢说,想问什么直接跟我讲就行
|
|
||||||
12:28:03 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:28:08
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:28:09 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:28:09 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:28:10 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:28:10 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:28:10 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:28:10 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:28:10 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:28:10 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:28:10 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:28:10 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:28:22 [AI] 回复内容: 在呢,您有什么事直接跟我说,我这边听着
|
|
||||||
12:28:24 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:28:27
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:28:28 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:28:28 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:28:29 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:28:29 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:28:29 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:28:29 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:28:29 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:28:29 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:28:29 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:28:29 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:28:35 [AI] 回复内容: 在呢,您有什么事直接跟我说,我这边看着呢
|
|
||||||
12:28:36 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:28:40
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:28:41 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:28:41 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:28:42 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:28:42 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:28:42 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:28:42 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:28:42 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:28:42 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:28:42 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:28:42 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:28:49 [AI] 回复内容: 在呢,您有什么事直接说就行,我这边听着呢
|
|
||||||
12:28:50 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:28:54
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:28:55 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:28:55 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:28:56 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:28:56 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:28:56 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:28:56 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:28:56 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:28:56 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:28:56 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:28:56 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:29:06 [AI] 回复内容: 在呢,您慢慢说,想问什么我这边听着呢
|
|
||||||
12:29:07 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:29:10
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:29:11 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:29:11 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:29:12 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:29:12 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:29:12 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:29:12 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:29:12 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:29:12 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:29:12 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:29:12 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:29:22 [AI] 回复内容: 在呢,您有什么事就说,我这边看着呢
|
|
||||||
12:29:23 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:29:27
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:29:28 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:29:28 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:29:29 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:29:29 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:29:29 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:29:29 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:29:29 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:29:29 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:29:29 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:29:29 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:29:36 [AI] 回复内容: 在呢,您慢慢说,有什么事需要我帮您处理?
|
|
||||||
12:29:37 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:29:40 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:29:41 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:29:43
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:29:44 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:29:45 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:29:45 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:29:45 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:29:45 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:29:45 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:29:45 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:29:45 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:29:46 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:29:46 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:29:54 [AI] 回复内容: 在呢,我这边看着消息,您有什么事直接说就行
|
|
||||||
12:29:55 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:30:02 [待回复恢复] 找到已读未回复会话,正在按头像和名称复合指纹重新打开。
|
|
||||||
12:30:03 [页面校验] 点击前会话列表已变化,已取消这次点击。
|
|
||||||
12:30:05
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:30:06 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:30:07 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:30:07 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:30:07 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:30:07 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:30:07 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:30:07 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:30:07 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:30:08 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:30:08 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:30:16 [AI] 回复内容: 在呢,您慢慢说,有什么事需要我帮您处理?
|
|
||||||
12:30:17 [发送保护] 检测到人工草稿,已保留原内容并取消自动发送。
|
|
||||||
12:30:22
|
|
||||||
[会话守护] 当前会话仍有已读未回复任务,正在安全重试...
|
|
||||||
12:30:23 [剪贴板] 成功提取 1 行聊天记录(共采集 1 屏 / 去重后 1 行)
|
|
||||||
12:30:23 [会话守护] 复制文字未证明有新客户消息,将用视觉确认是否新增媒体。
|
|
||||||
12:30:24 [档案] 首次遇到该会话,已暂存(1 行可见历史)
|
|
||||||
12:30:24 [AI] 本次提取的新内容:
|
|
||||||
在不在
|
|
||||||
12:30:24 [AI] 媒体/视觉模式,已截取完整聊天消息区域 (6751 bytes)
|
|
||||||
12:30:24 [AI] 使用视觉模式分析聊天截图...
|
|
||||||
12:30:24 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/files/upload
|
|
||||||
12:30:24 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 文件上传(AI 页面守护)","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/files/upload","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:30:24 [开发模式] 模型请求地址: http://ai.zhenyangtang.com.cn/v1/chat-messages
|
|
||||||
12:30:24 [开发模式] 本次模型配置(聊天内容与敏感值未输出): {"服务类型":"dify","调用协议":"Dify 视觉 chat-messages","API 基础地址":"http://ai.zhenyangtang.com.cn/v1","实际请求地址":"http://ai.zhenyangtang.com.cn/v1/chat-messages","模型名称":"gpt-5.6-sol","API Key":"[已配置,值已隐藏]","请求超时(秒)":120,"最大回复 tokens":500,"温度":0.35,"始终视觉模式":true,"媒体消息自动视觉":true,"AI 页面守护":true,"上下文":true,"MCP 工具":false}
|
|
||||||
12:30:35 [AI] 回复内容: 在呢,我这会儿在线,您想咨询什么事情?
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
10:06:46 [*] 正在查找企业微信主窗口...
|
||||||
|
10:06:46 [-] 未找到企业微信!请确认客户端已经登录并正在运行。
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
10:06:48 [*] 正在查找企业微信主窗口...
|
||||||
|
10:06:48 [-] 未找到企业微信!请确认客户端已经登录并正在运行。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
chat-png
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
chat-png
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
chat-png
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
chat-png
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
chat-png
|
||||||
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 82 KiB |
@@ -8,3 +8,5 @@ pywin32>=306
|
|||||||
requests>=2.31.0
|
requests>=2.31.0
|
||||||
mcp[cli]>=1.0.0
|
mcp[cli]>=1.0.0
|
||||||
PySide6>=6.8,<6.11
|
PySide6>=6.8,<6.11
|
||||||
|
# 会话身份靠昵称 OCR;构建环境缺了它,打出来的包会静默失去识别能力
|
||||||
|
rapidocr-onnxruntime>=1.4.4,<1.5
|
||||||
|
|||||||
@@ -131,7 +131,15 @@ class NameReader:
|
|||||||
try:
|
try:
|
||||||
from rapidocr_onnxruntime import RapidOCR
|
from rapidocr_onnxruntime import RapidOCR
|
||||||
|
|
||||||
self._engine = RapidOCR()
|
try:
|
||||||
|
# onnxruntime 默认按核数开满线程池且空转抢 CPU。识别的
|
||||||
|
# 都是几十像素高的小裁片,两个线程绰绰有余;不限的话
|
||||||
|
# 低配机每轮轮询都会被 OCR 抖一下。
|
||||||
|
self._engine = RapidOCR(
|
||||||
|
intra_op_num_threads=2, inter_op_num_threads=1
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
self._engine = RapidOCR()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self._engine_failed = True
|
self._engine_failed = True
|
||||||
print(f" [身份] OCR 引擎不可用,无法读取会话昵称: {exc}")
|
print(f" [身份] OCR 引擎不可用,无法读取会话昵称: {exc}")
|
||||||
|
|||||||
@@ -73,22 +73,6 @@ AUTO_REPLY_TEXT = "在的,您慢慢说,我这边看着呢。"
|
|||||||
POLL_INTERVAL = 2.0
|
POLL_INTERVAL = 2.0
|
||||||
MOUSE_IDLE_ENABLED = True
|
MOUSE_IDLE_ENABLED = True
|
||||||
MOUSE_IDLE_SECONDS = 20.0
|
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"
|
BG = "#F2F6F3"
|
||||||
@@ -210,186 +194,14 @@ PAGES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LogQueue:
|
from gui_runtime import ( # noqa: F401 # 供旧代码与测试通过 wechat_gui 引用
|
||||||
"""把后台线程的标准输出转发到界面,同时留一份带时间戳的磁盘副本。
|
MESSAGE_BATCH_WINDOW_MAX_SECONDS,
|
||||||
|
MESSAGE_BATCH_WINDOW_MIN_SECONDS,
|
||||||
界面日志随窗口关闭就没了,出问题时无从回溯——真正卡住发送的那一行往往
|
MESSAGE_BATCH_WINDOW_SECONDS,
|
||||||
几分钟前就被刷走了。落盘副本让事后还查得到。
|
BotThread,
|
||||||
"""
|
LogQueue,
|
||||||
|
normalize_message_batch_window_seconds,
|
||||||
LOG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs")
|
)
|
||||||
KEEP_FILES = 20
|
|
||||||
|
|
||||||
def __init__(self, target_queue):
|
|
||||||
self.target_queue = target_queue
|
|
||||||
self._handle = None
|
|
||||||
self._open_log_file()
|
|
||||||
|
|
||||||
def _open_log_file(self):
|
|
||||||
try:
|
|
||||||
os.makedirs(self.LOG_DIR, exist_ok=True)
|
|
||||||
self._prune_old_logs()
|
|
||||||
stamp = time.strftime("%Y%m%d_%H%M%S")
|
|
||||||
self._handle = open(
|
|
||||||
os.path.join(self.LOG_DIR, f"gui_{stamp}.log"),
|
|
||||||
"a",
|
|
||||||
encoding="utf-8",
|
|
||||||
buffering=1,
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
self._handle = None
|
|
||||||
|
|
||||||
def _prune_old_logs(self):
|
|
||||||
try:
|
|
||||||
files = sorted(
|
|
||||||
glob.glob(os.path.join(self.LOG_DIR, "gui_*.log")),
|
|
||||||
key=os.path.getmtime,
|
|
||||||
)
|
|
||||||
for path in files[: max(0, len(files) - self.KEEP_FILES + 1)]:
|
|
||||||
os.unlink(path)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def write(self, message):
|
|
||||||
text = str(message).rstrip()
|
|
||||||
if not text:
|
|
||||||
return
|
|
||||||
self.target_queue.put(("log", text))
|
|
||||||
if self._handle is not None:
|
|
||||||
try:
|
|
||||||
self._handle.write(f"{time.strftime('%H:%M:%S')} {text}\n")
|
|
||||||
except Exception:
|
|
||||||
self._handle = None
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
if self._handle is not None:
|
|
||||||
try:
|
|
||||||
self._handle.flush()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self._handle is not None:
|
|
||||||
try:
|
|
||||||
self._handle.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
self._handle = None
|
|
||||||
|
|
||||||
|
|
||||||
class BotThread(threading.Thread):
|
|
||||||
"""在后台运行企业微信轮询,避免阻塞 Tk 主线程。"""
|
|
||||||
|
|
||||||
def __init__(self, target_queue, reply_text, poll_seconds,
|
|
||||||
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 _report_progress(self, text):
|
|
||||||
"""把机器人此刻在做什么送到界面上那行进度文字。"""
|
|
||||||
try:
|
|
||||||
self.target_queue.put(("progress", str(text or "")))
|
|
||||||
except Exception:
|
|
||||||
# 进度提示纯属好看,永远不该把轮询带下去
|
|
||||||
pass
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
bot = None
|
|
||||||
failed = False
|
|
||||||
try:
|
|
||||||
import wechat_bot as bot_module
|
|
||||||
|
|
||||||
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
|
|
||||||
bot.progress_cb = self._report_progress
|
|
||||||
|
|
||||||
if not bot.connect(activate=False, wait_if_missing=True):
|
|
||||||
failed = True
|
|
||||||
self.target_queue.put(("status", "error"))
|
|
||||||
return
|
|
||||||
|
|
||||||
print("[+] 窗口激活模式已开启:企业微信未显示时会自动还原到前台")
|
|
||||||
print("[i] 不会设置系统级置顶;仅检测到未读红点后才执行操作")
|
|
||||||
if bot.mouse_idle_enabled:
|
|
||||||
print(
|
|
||||||
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"))
|
|
||||||
self.target_queue.put(("info", {
|
|
||||||
"hwnd": f"0x{bot.hwnd:08X}",
|
|
||||||
"size": f"{bot.R - bot.L} x {bot.B - bot.T}",
|
|
||||||
"input": f"({bot.input_x}, {bot.input_y})",
|
|
||||||
}))
|
|
||||||
|
|
||||||
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
|
|
||||||
self.target_queue.put(("status", "verification"))
|
|
||||||
self.stop_event.set()
|
|
||||||
break
|
|
||||||
ready = bool(bot._window_ready)
|
|
||||||
if ready != last_ready:
|
|
||||||
last_ready = ready
|
|
||||||
self.target_queue.put(("status", "running" if ready else "waiting"))
|
|
||||||
if ready:
|
|
||||||
self.target_queue.put(("info", {
|
|
||||||
"hwnd": f"0x{bot.hwnd:08X}",
|
|
||||||
"size": f"{bot.R - bot.L} x {bot.B - bot.T}",
|
|
||||||
"input": f"({bot.input_x}, {bot.input_y})",
|
|
||||||
}))
|
|
||||||
self.target_queue.put(("stats", {
|
|
||||||
"replied": bot.reply_count,
|
|
||||||
"false_pos": len(bot.false_pos_rows),
|
|
||||||
}))
|
|
||||||
self.stop_event.wait(self.poll_seconds)
|
|
||||||
except Exception as exc:
|
|
||||||
failed = True
|
|
||||||
self.target_queue.put((
|
|
||||||
"log",
|
|
||||||
f"[-] 后台线程异常:{exc}\n{traceback.format_exc()}",
|
|
||||||
))
|
|
||||||
self.target_queue.put(("status", "error"))
|
|
||||||
finally:
|
|
||||||
self.bot = None
|
|
||||||
if not failed:
|
|
||||||
self.target_queue.put(("status", "stopped"))
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.stop_event.set()
|
|
||||||
|
|
||||||
|
|
||||||
class ActionButton(tk.Button):
|
class ActionButton(tk.Button):
|
||||||
@@ -3918,6 +3730,11 @@ class App(tk.Tk):
|
|||||||
self._log.configure(state="normal")
|
self._log.configure(state="normal")
|
||||||
self._log.insert("end", f"[{timestamp}] ", "dim")
|
self._log.insert("end", f"[{timestamp}] ", "dim")
|
||||||
self._log.insert("end", str(message) + "\n", tag)
|
self._log.insert("end", str(message) + "\n", tag)
|
||||||
|
# 界面日志只留近况,全量在磁盘副本里;不裁的话跑一整天后每次追加
|
||||||
|
# 都要重排几万行文本,界面越用越卡。
|
||||||
|
overflow = int(self._log.index("end-1c").split(".")[0]) - 2000
|
||||||
|
if overflow > 0:
|
||||||
|
self._log.delete("1.0", f"{overflow + 1}.0")
|
||||||
self._log.see("end")
|
self._log.see("end")
|
||||||
self._log.configure(state="disabled")
|
self._log.configure(state="disabled")
|
||||||
|
|
||||||
@@ -4058,6 +3875,11 @@ def main():
|
|||||||
qt_main()
|
qt_main()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
run_classic_ui()
|
||||||
|
|
||||||
|
|
||||||
|
def run_classic_ui():
|
||||||
|
"""Tk 经典界面;仅在显式要求或 PySide6 不可用时才走到这里。"""
|
||||||
startup_result = {}
|
startup_result = {}
|
||||||
try:
|
try:
|
||||||
import backend_client
|
import backend_client
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from pathlib import Path
|
|||||||
from PySide6.QtCore import (
|
from PySide6.QtCore import (
|
||||||
QEasingCurve,
|
QEasingCurve,
|
||||||
QEvent,
|
QEvent,
|
||||||
|
QObject,
|
||||||
QPoint,
|
QPoint,
|
||||||
QPointF,
|
QPointF,
|
||||||
QPropertyAnimation,
|
QPropertyAnimation,
|
||||||
@@ -74,26 +75,21 @@ from app_version import APP_VERSION, release_status
|
|||||||
from runtime_paths import application_data_dir, resource_path
|
from runtime_paths import application_data_dir, resource_path
|
||||||
|
|
||||||
|
|
||||||
try:
|
# 直接用 GUI 无关的共享运行时;过去 import wechat_gui 会连带把整个
|
||||||
import __main__ as _legacy
|
# tkinter/tcl 拖进 Qt 进程,冷启动白付一笔加载费。
|
||||||
|
from gui_runtime import (
|
||||||
if not hasattr(_legacy, "BotThread"):
|
BotThread,
|
||||||
raise ImportError
|
LogQueue,
|
||||||
except ImportError:
|
normalize_message_batch_window_seconds,
|
||||||
import wechat_gui as _legacy
|
|
||||||
|
|
||||||
|
|
||||||
BotThread = _legacy.BotThread
|
|
||||||
LogQueue = _legacy.LogQueue
|
|
||||||
MESSAGE_BATCH_WINDOW_SECONDS = getattr(
|
|
||||||
_legacy, "MESSAGE_BATCH_WINDOW_SECONDS", 20.0
|
|
||||||
)
|
)
|
||||||
MESSAGE_BATCH_WINDOW_MIN_SECONDS = int(getattr(
|
from gui_runtime import (
|
||||||
_legacy, "MESSAGE_BATCH_WINDOW_MIN_SECONDS", 1.0
|
MESSAGE_BATCH_WINDOW_SECONDS,
|
||||||
))
|
MESSAGE_BATCH_WINDOW_MAX_SECONDS as _BATCH_MAX,
|
||||||
MESSAGE_BATCH_WINDOW_MAX_SECONDS = int(getattr(
|
MESSAGE_BATCH_WINDOW_MIN_SECONDS as _BATCH_MIN,
|
||||||
_legacy, "MESSAGE_BATCH_WINDOW_MAX_SECONDS", 120.0
|
)
|
||||||
))
|
|
||||||
|
MESSAGE_BATCH_WINDOW_MIN_SECONDS = int(_BATCH_MIN)
|
||||||
|
MESSAGE_BATCH_WINDOW_MAX_SECONDS = int(_BATCH_MAX)
|
||||||
SCRIPT_DIR = application_data_dir()
|
SCRIPT_DIR = application_data_dir()
|
||||||
APP_SETTINGS_FILE = SCRIPT_DIR / "app_settings.json"
|
APP_SETTINGS_FILE = SCRIPT_DIR / "app_settings.json"
|
||||||
CUSTOMER_SERVICE_URL = "http://kf.zhenyangtang.com.cn/"
|
CUSTOMER_SERVICE_URL = "http://kf.zhenyangtang.com.cn/"
|
||||||
@@ -385,18 +381,37 @@ class PortalWebView(QWebEngineView):
|
|||||||
|
|
||||||
|
|
||||||
class PortalPage(QWidget):
|
class PortalPage(QWidget):
|
||||||
|
"""AI 客服网页页。
|
||||||
|
|
||||||
|
WebEngine 视图按需创建:Chromium 子进程初始化在低配机上要好几秒、常驻
|
||||||
|
两三百 MB 内存,构造期就拉起会拖慢整个窗口的首帧。
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, parent: QWidget | None = None):
|
def __init__(self, parent: QWidget | None = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setObjectName("PageRoot")
|
self.setObjectName("PageRoot")
|
||||||
layout = QVBoxLayout(self)
|
self._layout = QVBoxLayout(self)
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
self._layout.setContentsMargins(0, 0, 0, 0)
|
||||||
layout.setSpacing(0)
|
self._layout.setSpacing(0)
|
||||||
|
|
||||||
self.progress = QProgressBar()
|
self.progress = QProgressBar()
|
||||||
self.progress.setRange(0, 100)
|
self.progress.setRange(0, 100)
|
||||||
self.progress.hide()
|
self.progress.hide()
|
||||||
layout.addWidget(self.progress)
|
self._layout.addWidget(self.progress)
|
||||||
|
|
||||||
|
self._placeholder = QLabel("正在准备 AI 客服页面…")
|
||||||
|
self._placeholder.setAlignment(Qt.AlignCenter)
|
||||||
|
self._placeholder.setStyleSheet("color:#617269;font-size:14px;background:#f4f7f5;")
|
||||||
|
self._layout.addWidget(self._placeholder, 1)
|
||||||
|
|
||||||
|
self.view: PortalWebView | None = None
|
||||||
|
self.page: QWebEnginePage | None = None
|
||||||
|
self.profile: QWebEngineProfile | None = None
|
||||||
|
|
||||||
|
def ensure_view(self) -> None:
|
||||||
|
"""真正创建 WebEngine 视图;重复调用无副作用。"""
|
||||||
|
if self.view is not None:
|
||||||
|
return
|
||||||
self.view = PortalWebView()
|
self.view = PortalWebView()
|
||||||
self.view.setFocusPolicy(Qt.StrongFocus)
|
self.view.setFocusPolicy(Qt.StrongFocus)
|
||||||
self.view.settings().setAttribute(
|
self.view.settings().setAttribute(
|
||||||
@@ -421,7 +436,8 @@ class PortalPage(QWidget):
|
|||||||
self.view.loadStarted.connect(self._load_started)
|
self.view.loadStarted.connect(self._load_started)
|
||||||
self.view.loadProgress.connect(self.progress.setValue)
|
self.view.loadProgress.connect(self.progress.setValue)
|
||||||
self.view.loadFinished.connect(self._load_finished)
|
self.view.loadFinished.connect(self._load_finished)
|
||||||
layout.addWidget(self.view, 1)
|
self._placeholder.hide()
|
||||||
|
self._layout.addWidget(self.view, 1)
|
||||||
|
|
||||||
if os.environ.get("WECOM_RPA_DISABLE_PORTAL") != "1":
|
if os.environ.get("WECOM_RPA_DISABLE_PORTAL") != "1":
|
||||||
self.view.setUrl(QUrl(CUSTOMER_SERVICE_URL))
|
self.view.setUrl(QUrl(CUSTOMER_SERVICE_URL))
|
||||||
@@ -432,6 +448,10 @@ class PortalPage(QWidget):
|
|||||||
"AI 客服网页在测试模式下未加载</div></body></html>"
|
"AI 客服网页在测试模式下未加载</div></body></html>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def focus_view(self) -> None:
|
||||||
|
if self.view is not None:
|
||||||
|
self.view.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
def _install_light_theme(self) -> None:
|
def _install_light_theme(self) -> None:
|
||||||
css_path = resource_path("edge_light_theme", "light-theme.css")
|
css_path = resource_path("edge_light_theme", "light-theme.css")
|
||||||
try:
|
try:
|
||||||
@@ -471,10 +491,14 @@ class PortalPage(QWidget):
|
|||||||
self.view.setFocus(Qt.OtherFocusReason)
|
self.view.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
def reload(self) -> None:
|
def reload(self) -> None:
|
||||||
self.view.reload()
|
if self.view is None:
|
||||||
|
self.ensure_view()
|
||||||
|
else:
|
||||||
|
self.view.reload()
|
||||||
|
|
||||||
def open_external(self) -> None:
|
def open_external(self) -> None:
|
||||||
QDesktopServices.openUrl(self.view.url() or QUrl(CUSTOMER_SERVICE_URL))
|
url = self.view.url() if self.view is not None else QUrl()
|
||||||
|
QDesktopServices.openUrl(url if url and not url.isEmpty() else QUrl(CUSTOMER_SERVICE_URL))
|
||||||
|
|
||||||
|
|
||||||
class MetricCard(QFrame):
|
class MetricCard(QFrame):
|
||||||
@@ -1689,6 +1713,9 @@ class LogPage(QWidget):
|
|||||||
self.editor = QTextEdit()
|
self.editor = QTextEdit()
|
||||||
self.editor.setReadOnly(True)
|
self.editor.setReadOnly(True)
|
||||||
self.editor.setAcceptRichText(True)
|
self.editor.setAcceptRichText(True)
|
||||||
|
# 机器人每轮轮询都在打日志,跑一天就是几万块富文本;不封顶的话文档
|
||||||
|
# 越长每次追加越慢,整个界面跟着卡。磁盘副本是全量的,界面只留近况。
|
||||||
|
self.editor.document().setMaximumBlockCount(2000)
|
||||||
self.editor.setStyleSheet(
|
self.editor.setStyleSheet(
|
||||||
"QTextEdit{font-family:'Cascadia Mono','Microsoft YaHei UI';font-size:13px;line-height:1.5;}"
|
"QTextEdit{font-family:'Cascadia Mono','Microsoft YaHei UI';font-size:13px;line-height:1.5;}"
|
||||||
)
|
)
|
||||||
@@ -2193,7 +2220,7 @@ class MainWindow(QMainWindow):
|
|||||||
settings["poll_interval"] = max(0.2, float(settings["poll_interval"]))
|
settings["poll_interval"] = max(0.2, float(settings["poll_interval"]))
|
||||||
settings["mouse_idle_seconds"] = max(0.0, float(settings["mouse_idle_seconds"]))
|
settings["mouse_idle_seconds"] = max(0.0, float(settings["mouse_idle_seconds"]))
|
||||||
settings["message_batch_window_seconds"] = (
|
settings["message_batch_window_seconds"] = (
|
||||||
_legacy.normalize_message_batch_window_seconds(
|
normalize_message_batch_window_seconds(
|
||||||
settings["message_batch_window_seconds"]
|
settings["message_batch_window_seconds"]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -2234,7 +2261,8 @@ class MainWindow(QMainWindow):
|
|||||||
if index == 5:
|
if index == 5:
|
||||||
self.queue_page.refresh_data()
|
self.queue_page.refresh_data()
|
||||||
if index == 0:
|
if index == 0:
|
||||||
QTimer.singleShot(80, lambda: self.portal_page.view.setFocus(Qt.OtherFocusReason))
|
self.portal_page.ensure_view()
|
||||||
|
QTimer.singleShot(80, self.portal_page.focus_view)
|
||||||
|
|
||||||
def _refresh_queue_page(self) -> None:
|
def _refresh_queue_page(self) -> None:
|
||||||
"""队列页开着的时候,让它跟着机器人一起动。"""
|
"""队列页开着的时候,让它跟着机器人一起动。"""
|
||||||
@@ -2303,9 +2331,11 @@ class MainWindow(QMainWindow):
|
|||||||
previous_state = event.oldState()
|
previous_state = event.oldState()
|
||||||
saved_geometry = self.saveGeometry()
|
saved_geometry = self.saveGeometry()
|
||||||
was_maximized = bool(previous_state & Qt.WindowMaximized)
|
was_maximized = bool(previous_state & Qt.WindowMaximized)
|
||||||
|
portal_view = self.portal_page.view
|
||||||
portal_was_visible = bool(
|
portal_was_visible = bool(
|
||||||
self.stack.currentIndex() == 0
|
self.stack.currentIndex() == 0
|
||||||
and not self.portal_page.view.isHidden()
|
and portal_view is not None
|
||||||
|
and not portal_view.isHidden()
|
||||||
)
|
)
|
||||||
QTimer.singleShot(
|
QTimer.singleShot(
|
||||||
0,
|
0,
|
||||||
@@ -2355,12 +2385,13 @@ class MainWindow(QMainWindow):
|
|||||||
# window while that surface is still visible can leave a large black DWM
|
# window while that surface is still visible can leave a large black DWM
|
||||||
# window above WeCom on some Windows/GPU combinations. Tear down the
|
# window above WeCom on some Windows/GPU combinations. Tear down the
|
||||||
# visible surface first and let Qt flush that state before hiding us.
|
# visible surface first and let Qt flush that state before hiding us.
|
||||||
|
portal_view = self.portal_page.view
|
||||||
self._portal_was_visible = (
|
self._portal_was_visible = (
|
||||||
self.portal_page.view.isVisible()
|
portal_view is not None and portal_view.isVisible()
|
||||||
if portal_was_visible is None
|
if portal_was_visible is None
|
||||||
else bool(portal_was_visible)
|
else bool(portal_was_visible)
|
||||||
)
|
)
|
||||||
if self._portal_was_visible:
|
if self._portal_was_visible and self.portal_page.view is not None:
|
||||||
self.portal_page.view.hide()
|
self.portal_page.view.hide()
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
self.capsule.show_near(self)
|
self.capsule.show_near(self)
|
||||||
@@ -2378,8 +2409,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.activateWindow()
|
self.activateWindow()
|
||||||
if getattr(self, "_portal_was_visible", False) and self.stack.currentIndex() == 0:
|
if getattr(self, "_portal_was_visible", False) and self.stack.currentIndex() == 0:
|
||||||
def restore_portal() -> None:
|
def restore_portal() -> None:
|
||||||
self.portal_page.view.show()
|
if self.portal_page.view is not None:
|
||||||
self.portal_page.view.setFocus(Qt.OtherFocusReason)
|
self.portal_page.view.show()
|
||||||
|
self.portal_page.view.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
QTimer.singleShot(80, restore_portal)
|
QTimer.singleShot(80, restore_portal)
|
||||||
self._portal_was_visible = False
|
self._portal_was_visible = False
|
||||||
@@ -2564,6 +2596,45 @@ def handle_startup_update(release: object) -> bool:
|
|||||||
return not forced
|
return not forced
|
||||||
|
|
||||||
|
|
||||||
|
class _StartupSyncBridge(QObject):
|
||||||
|
finished = Signal(object)
|
||||||
|
|
||||||
|
|
||||||
|
def _start_background_startup_sync(window: "MainWindow") -> None:
|
||||||
|
"""云端配置同步放到后台线程执行。
|
||||||
|
|
||||||
|
过去它在窗口出现之前同步跑,网络一慢冷启动就跟着慢(超时 3 秒起步、
|
||||||
|
DNS 卡住时更久)。现在窗口先出来,结果回来后再补日志和升级提示。
|
||||||
|
"""
|
||||||
|
bridge = _StartupSyncBridge(window)
|
||||||
|
|
||||||
|
def deliver(result: object) -> None:
|
||||||
|
payload = result if isinstance(result, dict) else {}
|
||||||
|
for diagnostic in payload.get("diagnostics") or []:
|
||||||
|
window.append_log(str(diagnostic), "notify")
|
||||||
|
if not handle_startup_update(payload.get("release")):
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
bridge.finished.connect(deliver)
|
||||||
|
|
||||||
|
def worker() -> None:
|
||||||
|
try:
|
||||||
|
import backend_client
|
||||||
|
|
||||||
|
result = backend_client.startup_sync_config(timeout=3.0)
|
||||||
|
except Exception:
|
||||||
|
# 云端暂时不可用时仍执行上次成功同步的强制升级策略。
|
||||||
|
try:
|
||||||
|
import backend_client
|
||||||
|
|
||||||
|
result = {"release": backend_client.cached_release_status()}
|
||||||
|
except Exception:
|
||||||
|
result = {}
|
||||||
|
bridge.finished.emit(result or {})
|
||||||
|
|
||||||
|
threading.Thread(target=worker, daemon=True, name="startup-sync").start()
|
||||||
|
|
||||||
|
|
||||||
def run_packaging_self_check(app: QApplication) -> int:
|
def run_packaging_self_check(app: QApplication) -> int:
|
||||||
"""离线验证随 EXE 打包的 Qt WebEngine 能否真正创建并加载页面。"""
|
"""离线验证随 EXE 打包的 Qt WebEngine 能否真正创建并加载页面。"""
|
||||||
probe = QWebEngineView()
|
probe = QWebEngineView()
|
||||||
@@ -2609,23 +2680,14 @@ def main() -> None:
|
|||||||
if "--packaging-self-check" in sys.argv:
|
if "--packaging-self-check" in sys.argv:
|
||||||
raise SystemExit(run_packaging_self_check(app))
|
raise SystemExit(run_packaging_self_check(app))
|
||||||
|
|
||||||
startup_result = {}
|
|
||||||
try:
|
|
||||||
import backend_client
|
|
||||||
|
|
||||||
startup_result = backend_client.startup_sync_config(timeout=3.0)
|
|
||||||
except Exception:
|
|
||||||
# 云端暂时不可用时仍执行上次成功同步的强制升级策略。
|
|
||||||
startup_result = {"release": backend_client.cached_release_status()}
|
|
||||||
|
|
||||||
if not handle_startup_update(startup_result.get("release")):
|
|
||||||
return
|
|
||||||
|
|
||||||
window = MainWindow()
|
window = MainWindow()
|
||||||
for diagnostic in startup_result.get("diagnostics") or []:
|
|
||||||
window.append_log(str(diagnostic), "notify")
|
|
||||||
console_shutdown = install_console_shutdown_handler(app, window)
|
console_shutdown = install_console_shutdown_handler(app, window)
|
||||||
window.show()
|
window.show()
|
||||||
|
# WebEngine(Chromium 子进程)挪到首帧之后再拉起:低配机上它初始化要
|
||||||
|
# 好几秒,放在构造期会让用户对着白屏等。
|
||||||
|
QTimer.singleShot(120, window.portal_page.ensure_view)
|
||||||
|
if "--qt-smoke-test" not in sys.argv:
|
||||||
|
_start_background_startup_sync(window)
|
||||||
|
|
||||||
if "--qt-smoke-test" in sys.argv:
|
if "--qt-smoke-test" in sys.argv:
|
||||||
for index in range(window.stack.count()):
|
for index in range(window.stack.count()):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PyInstaller.utils.hooks import collect_submodules
|
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
|
||||||
|
|
||||||
|
|
||||||
project_root = Path(SPECPATH)
|
project_root = Path(SPECPATH)
|
||||||
@@ -14,9 +14,11 @@ hidden_imports = [
|
|||||||
"app_version",
|
"app_version",
|
||||||
"backend_client",
|
"backend_client",
|
||||||
"conversation_store",
|
"conversation_store",
|
||||||
|
"gui_runtime",
|
||||||
"mcp_bridge",
|
"mcp_bridge",
|
||||||
"registration_store",
|
"registration_store",
|
||||||
"runtime_paths",
|
"runtime_paths",
|
||||||
|
"wechat_gui",
|
||||||
"wechat_gui_qt",
|
"wechat_gui_qt",
|
||||||
"PySide6.QtWebEngineCore",
|
"PySide6.QtWebEngineCore",
|
||||||
"PySide6.QtWebEngineWidgets",
|
"PySide6.QtWebEngineWidgets",
|
||||||
@@ -24,29 +26,35 @@ hidden_imports = [
|
|||||||
hidden_imports += collect_submodules("mcp")
|
hidden_imports += collect_submodules("mcp")
|
||||||
|
|
||||||
a = Analysis(
|
a = Analysis(
|
||||||
[str(project_root / "wechat_gui.py")],
|
[str(project_root / "app_main.py")],
|
||||||
pathex=[str(project_root)],
|
pathex=[str(project_root)],
|
||||||
binaries=[],
|
binaries=[],
|
||||||
datas=[
|
datas=[
|
||||||
(str(project_root / "edge_light_theme"), "edge_light_theme"),
|
(str(project_root / "edge_light_theme"), "edge_light_theme"),
|
||||||
(str(project_root / "assets" / "brand"), "assets/brand"),
|
(str(project_root / "assets" / "brand"), "assets/brand"),
|
||||||
],
|
]
|
||||||
|
# 昵称 OCR 的模型与配置:只收代码不收这些文件的话,打包后 RapidOCR
|
||||||
|
# 初始化直接失败,会话身份静默退化。
|
||||||
|
+ collect_data_files("rapidocr_onnxruntime"),
|
||||||
hiddenimports=hidden_imports,
|
hiddenimports=hidden_imports,
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
hooksconfig={},
|
hooksconfig={},
|
||||||
runtime_hooks=[],
|
runtime_hooks=[],
|
||||||
excludes=["PyQt5", "PyQt6", "PySide2"],
|
excludes=["PyQt5", "PyQt6", "PySide2"],
|
||||||
noarchive=False,
|
noarchive=False,
|
||||||
optimize=0,
|
# 去掉 assert 与 __debug__ 分支;不动 docstring(有库在运行期读它)。
|
||||||
|
optimize=1,
|
||||||
)
|
)
|
||||||
pyz = PYZ(a.pure)
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
# 目录形态(onedir):单文件 EXE 每次冷启动都要把几百 MB 的 Qt WebEngine
|
||||||
|
# 解压进临时目录再被杀毒软件逐个扫一遍,低配硬盘上一等就是几十秒。
|
||||||
|
# 目录形态零解压,冷启动只剩正常的模块加载。
|
||||||
exe = EXE(
|
exe = EXE(
|
||||||
pyz,
|
pyz,
|
||||||
a.scripts,
|
a.scripts,
|
||||||
a.binaries,
|
|
||||||
a.datas,
|
|
||||||
[],
|
[],
|
||||||
|
exclude_binaries=True,
|
||||||
name=app_name,
|
name=app_name,
|
||||||
debug=False,
|
debug=False,
|
||||||
bootloader_ignore_signals=False,
|
bootloader_ignore_signals=False,
|
||||||
@@ -60,3 +68,12 @@ exe = EXE(
|
|||||||
entitlements_file=None,
|
entitlements_file=None,
|
||||||
icon=str(project_root / "assets" / "brand" / "zhenyangtang-icon.ico"),
|
icon=str(project_root / "assets" / "brand" / "zhenyangtang-icon.ico"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
coll = COLLECT(
|
||||||
|
exe,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
strip=False,
|
||||||
|
upx=False,
|
||||||
|
name=app_name,
|
||||||
|
)
|
||||||
|
|||||||