-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathdesktop.py
More file actions
163 lines (142 loc) · 5.33 KB
/
Copy pathdesktop.py
File metadata and controls
163 lines (142 loc) · 5.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
"""
Desktop application entry point.
Uses Edge WebView2 (built into Windows 10/11) for a native window.
Falls back to browser if WebView2 is unavailable.
Usage:
python desktop.py
webot.exe (packaged version)
"""
import os
import sys
import threading
import time
import webbrowser
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent
if getattr(sys, "frozen", False):
PROJECT_ROOT = Path(sys.executable).resolve().parent
def _write_crash_log(exc_info: str) -> None:
"""Write crash details to a file for windowed-mode debugging."""
try:
crash_dir = PROJECT_ROOT / "data"
crash_dir.mkdir(parents=True, exist_ok=True)
crash_path = crash_dir / "crash.log"
with open(crash_path, "a", encoding="utf-8") as f:
f.write(f"\n{'='*60}\n")
f.write(f"Crash at {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(exc_info)
f.write(f"\n{'='*60}\n\n")
except Exception:
pass # last resort — can't even write crash log
def start_bot():
"""Start bot in background thread (signal-safe)."""
import traceback
sys.path.insert(0, str(PROJECT_ROOT))
from src.web.server import (
start_web_server, update_status,
register_bot, _bot_exited,
)
web_thread = start_web_server()
try:
from src.config import load_config
config = load_config()
update_status(
wechat_backend=config.wechat_backend,
ai_backend=config.ai_backend,
)
from src.bot import Bot
bot = Bot(config)
# Bot.run() calls _register_backend() during init — no patch needed
register_bot(thread=threading.current_thread(), backend=None)
bot.run()
# Bot exited normally (e.g., no groups found)
update_status(running=False)
except SystemExit:
update_status(running=False)
except Exception as e:
update_status(running=False, error=str(e))
exc_info = traceback.format_exc()
_write_crash_log(exc_info)
finally:
# Always reset bot control state so the user can restart
# via the web UI (or auto-restart will work next launch)
_bot_exited()
def main():
# ── Set CWD to app home directory ──────────────────────────────
# Regardless of how the app is launched (double-click EXE / CLI /
# shortcut), fix the current working directory to the application
# directory so all relative paths (data/, .env, etc.) resolve
# correctly and data survives across sessions.
if getattr(sys, "frozen", False):
os.chdir(str(Path(sys.executable).resolve().parent))
else:
os.chdir(str(PROJECT_ROOT))
# Check if onboarding is needed
from src.config import is_onboarding_done
onboarding_needed = not is_onboarding_done()
# Always start web server (needed for both onboarding and dashboard)
from src.web.server import start_web_server
web_thread = start_web_server()
# Bot starts STOPPED — user must click "启动机器人" in the UI.
# This prevents auto-startup races with WeChat login / key availability.
# Wait for web server to be HTTP-ready (not just TCP socket bound).
# A raw TCP check only confirms the port is bound, but the HTTP handler
# may not have entered serve_forever() yet — causing "无法连接服务器"
# when the webview loads too early. We poll /api/status instead.
import urllib.request as _urllib_request
ready = False
for i in range(60):
try:
req = _urllib_request.urlopen(
"http://127.0.0.1:7327/api/status", timeout=2
)
req.close()
ready = True
break
except Exception:
time.sleep(0.25)
if not ready:
_write_crash_log("Web server HTTP readiness timeout (60 attempts)")
try:
import ctypes
ctypes.windll.user32.MessageBoxW(
0,
"Web 服务器启动超时,请检查端口 7327 是否被占用。\n\n"
"详情见 data/crash.log",
"webot — 启动失败",
0x10,
)
except Exception:
pass
return
title = "webot — 初始设置" if onboarding_needed else "webot — Dashboard"
# Try native WebView2, fall back to browser
try:
import webview
window = webview.create_window(
title=title,
url="http://127.0.0.1:7327",
width=1200,
height=800,
min_size=(900, 600),
)
webview.start(gui="edgechromium")
except Exception as e:
logger_available = False
try:
from src.web.server import logger
logger.warning("WebView2 不可用,正在使用浏览器: %s", e)
logger_available = True
except Exception:
pass
if not logger_available:
_write_crash_log(f"WebView2 unavailable: {e}\nFalling back to browser.")
webbrowser.open("http://127.0.0.1:7327")
print("webot 已在浏览器中打开。按 Ctrl+C 退出。", flush=True)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n正在退出...", flush=True)
if __name__ == "__main__":
main()