From 803fdc0fe93c6b6f84d852c535293fda3108cc82 Mon Sep 17 00:00:00 2001 From: Zylan Date: Sun, 6 Apr 2025 16:12:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=BA=94=E7=94=A8=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E4=BB=A5=E6=98=BE=E7=A4=BA=E7=B3=BB=E7=BB=9F=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E7=AA=97=E5=8F=A3=EF=BC=8C=E8=B0=83=E6=95=B4=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0IP=E5=9C=B0=E5=9D=80=E8=BF=9E=E6=8E=A5=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E5=BA=93=E4=BB=A5=E6=94=AF=E6=8C=81=E4=BA=8C?= =?UTF-8?q?=E7=BB=B4=E7=A0=81=E7=94=9F=E6=88=90=E5=92=8CPyQt5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.ico | Bin 4142 -> 67646 bytes app.py | 11 +- notification_window.py | 512 +++++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 +- 4 files changed, 524 insertions(+), 3 deletions(-) create mode 100644 notification_window.py diff --git a/app.ico b/app.ico index 434cad28199da39b586b572723ab35f9dd7d8480..169af8cf2b88299c982573e191e6a49338ba7752 100644 GIT binary patch literal 67646 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 literal 4142 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 diff --git a/app.py b/app.py index c0d6084..198ceb3 100644 --- a/app.py +++ b/app.py @@ -16,6 +16,9 @@ import traceback import requests from datetime import datetime import sys +import webbrowser +from functools import partial +from notification_window import create_notification_window app = Flask(__name__) socketio = SocketIO( @@ -878,8 +881,12 @@ def remove_prompt(prompt_id): if __name__ == '__main__': local_ip = get_local_ip() + port = 5000 print(f"Local IP Address: {local_ip}") - print(f"Connect from your mobile device using: {local_ip}:5000") + print(f"Connect from your mobile device using: {local_ip}:{port}") + + # 显示系统通知窗口 - 使用独立模块的函数 + create_notification_window(local_ip, port) # 加载模型配置 model_config = load_model_config() @@ -888,4 +895,4 @@ if __name__ == '__main__': print("已加载模型配置信息") # Run Flask in the main thread without debug mode - socketio.run(app, host='0.0.0.0', port=5000, allow_unsafe_werkzeug=True) + socketio.run(app, host='0.0.0.0', port=port, allow_unsafe_werkzeug=True) diff --git a/notification_window.py b/notification_window.py new file mode 100644 index 0000000..f7f4ed8 --- /dev/null +++ b/notification_window.py @@ -0,0 +1,512 @@ +import os +import sys +import threading +import traceback +import webbrowser +from threading import Thread +from PIL import Image +import qrcode +from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QPushButton, + QVBoxLayout, QHBoxLayout, QFrame, QWidget, QLineEdit, + QSystemTrayIcon, QMenu, QAction) +from PyQt5.QtGui import QPixmap, QIcon, QFont +from PyQt5.QtCore import Qt, QTimer, QPropertyAnimation + +def create_notification_window(ip_address, port): + """创建精美的系统通知弹窗,使用PyQt5实现""" + # 创建一个新线程运行PyQt窗口,避免阻塞主程序 + def show_window(): + try: + # 防止应用程序实例已经存在 + app = QApplication.instance() + if app is None: + app = QApplication(sys.argv) + + # 创建主窗口 + class NotificationWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Snap Solver AI") + + # 设置窗口大小 - 显著增大窗口尺寸 + self.setFixedSize(600, 1100) + screen_geometry = app.desktop().screenGeometry() + x = (screen_geometry.width() - self.width()) // 2 + y = (screen_geometry.height() - self.height()) // 2 + self.move(x, y) + + # 设置窗口图标 + self.app_icon = None + if os.path.exists("app.ico"): + self.app_icon = QIcon("app.ico") + self.setWindowIcon(self.app_icon) + + # 构建完整的访问URL + self.access_url = f"http://{ip_address}:{port}" + + # 设置窗口属性 + self.setWindowFlags(Qt.WindowStaysOnTopHint) + + # 初始化系统托盘 + self.tray_icon = None + + # 初始化界面 + self.init_ui() + + # 创建系统托盘 + self.create_tray_icon() + + def init_ui(self): + # 创建中央部件和布局 + central_widget = QWidget() + central_widget.setStyleSheet(""" + background-color: white; + """) + + main_layout = QVBoxLayout(central_widget) + main_layout.setContentsMargins(40, 40, 40, 40) + main_layout.setSpacing(30) + + # 标题区域 + header_layout = QHBoxLayout() + + # 应用标题 - 增大字体 + app_title = QLabel("Snap Solver AI") + app_title.setStyleSheet(""" + font-size: 36px; + font-weight: bold; + color: #333333; + """) + header_layout.addWidget(app_title, 1) + + # 状态标签 - 增大 + status_label = QLabel("已启动") + status_label.setStyleSheet(""" + background-color: #4CAF50; + color: white; + font-size: 20px; + font-weight: bold; + border-radius: 15px; + padding: 8px 20px; + """) + header_layout.addWidget(status_label, 0, Qt.AlignRight) + + main_layout.addLayout(header_layout) + + # 服务信息提示 - 增大字体 + service_info = QLabel("服务已启动,您可以通过以下方式访问:") + service_info.setStyleSheet(""" + font-size: 22px; + color: #555555; + margin-top: 10px; + """) + main_layout.addWidget(service_info) + + # 二维码区域 - 确保正方形显示区域 + qr_container = QFrame() + qr_container.setFixedWidth(400) # 设置固定宽度 + qr_container.setStyleSheet(""" + background-color: white; + border: none; + """) + qr_container_layout = QVBoxLayout(qr_container) + qr_container_layout.setContentsMargins(0, 0, 0, 0) + qr_container_layout.setAlignment(Qt.AlignCenter) + + qr_frame = QFrame() + qr_frame.setFixedSize(400, 400) # 设置固定正方形尺寸 + qr_frame.setStyleSheet(""" + background-color: white; + border: 3px solid #e0e0e0; + border-radius: 15px; + """) + qr_layout = QVBoxLayout(qr_frame) + qr_layout.setContentsMargins(20, 20, 20, 20) + qr_layout.setAlignment(Qt.AlignCenter) + + # 生成二维码 + try: + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_M, + box_size=10, + border=4, + ) + qr.add_data(self.access_url) + qr.make(fit=True) + + # 转换为PIL图像 + qr_img = qr.make_image(fill_color="black", back_color="white") + + # 确保二维码尺寸合适 + qr_img = qr_img.resize((350, 350), Image.LANCZOS) + temp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp_qr.png') + qr_img.save(temp_path) + + # 创建QR码标签 - 确保是正方形 + qr_label = QLabel() + qr_label.setFixedSize(350, 350) + qr_label.setPixmap(QPixmap(temp_path)) + qr_label.setAlignment(Qt.AlignCenter) + qr_label.setStyleSheet("border: none;") + qr_layout.addWidget(qr_label, 0, Qt.AlignCenter) + + # 删除临时文件 + try: + os.remove(temp_path) + except: + pass + except Exception as e: + print(f"生成二维码失败: {e}") + # 失败时显示提示文本 + qr_label = QLabel("二维码生成失败") + qr_label.setFixedSize(350, 350) + qr_label.setAlignment(Qt.AlignCenter) + qr_label.setStyleSheet(""" + border: 1px solid #e0e0e0; + color: #666666; + font-size: 22px; + """) + qr_layout.addWidget(qr_label, 0, Qt.AlignCenter) + + qr_container_layout.addWidget(qr_frame, 0, Qt.AlignCenter) + + # 扫码提示 - 增大字体 + scan_label = QLabel("扫描二维码访问") + scan_label.setStyleSheet(""" + color: #555555; + font-size: 22px; + margin-top: 15px; + """) + scan_label.setAlignment(Qt.AlignCenter) + qr_container_layout.addWidget(scan_label) + + main_layout.addWidget(qr_container, 0, Qt.AlignCenter) + + # 地址和复制按钮区域 + url_frame = QFrame() + url_frame.setStyleSheet(""" + background-color: #f5f5f5; + border-radius: 12px; + padding: 8px; + """) + url_layout = QHBoxLayout(url_frame) + url_layout.setContentsMargins(20, 15, 20, 15) + url_layout.setSpacing(15) + + # 地址文本框 - 增大字体 + url_entry = QLineEdit(self.access_url) + url_entry.setReadOnly(True) + url_entry.setStyleSheet(""" + QLineEdit { + background-color: #f5f5f5; + border: none; + font-size: 22px; + color: #333333; + padding: 10px; + } + """) + url_entry.setMinimumWidth(350) + url_layout.addWidget(url_entry) + + # 复制按钮 - 增大 + copy_btn = QPushButton("复制") + copy_btn.setStyleSheet(""" + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 8px; + padding: 10px 25px; + font-size: 20px; + font-weight: bold; + } + QPushButton:hover { + background-color: #1976D2; + } + QPushButton:pressed { + background-color: #0D47A1; + } + """) + copy_btn.setCursor(Qt.PointingHandCursor) + copy_btn.clicked.connect(self.copy_url) + self.copy_btn = copy_btn + url_layout.addWidget(copy_btn) + + main_layout.addWidget(url_frame) + + # IP信息 + ip_layout = QHBoxLayout() + ip_layout.setContentsMargins(10, 5, 10, 5) + + ip_icon = QLabel("🖥️") + ip_icon.setStyleSheet("font-size: 22px;") + ip_layout.addWidget(ip_icon) + + # IP信息标签 - 增大字体 + ip_info = QLabel(f"本地IP: {ip_address}") + ip_info.setStyleSheet(""" + color: #666666; + font-size: 20px; + """) + ip_layout.addWidget(ip_info) + ip_layout.addStretch() + + main_layout.addLayout(ip_layout) + + # 操作按钮区域 + button_layout = QHBoxLayout() + button_layout.setSpacing(20) + + # 在浏览器中打开按钮 - 增大 + open_browser_btn = QPushButton("在浏览器中打开") + open_browser_btn.setStyleSheet(""" + QPushButton { + background-color: #f5f5f5; + color: #333333; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 15px 25px; + font-size: 20px; + } + QPushButton:hover { + background-color: #e0e0e0; + } + """) + open_browser_btn.setCursor(Qt.PointingHandCursor) + open_browser_btn.clicked.connect(self.open_in_browser) + button_layout.addWidget(open_browser_btn) + + # 关闭窗口按钮 - 增大 + close_btn = QPushButton("关闭窗口") + close_btn.setStyleSheet(""" + QPushButton { + background-color: #f5f5f5; + color: #333333; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 15px 25px; + font-size: 20px; + } + QPushButton:hover { + background-color: #e0e0e0; + } + """) + close_btn.setCursor(Qt.PointingHandCursor) + close_btn.clicked.connect(self.hide) # 改为隐藏窗口而不是关闭 + button_layout.addWidget(close_btn) + + main_layout.addLayout(button_layout) + + # 提示信息 - 增大字体 + tip_frame = QFrame() + tip_frame.setStyleSheet(""" + background-color: #FFF8E1; + border-radius: 10px; + border: 1px solid #FFE082; + padding: 5px; + """) + tip_layout = QHBoxLayout(tip_frame) + tip_layout.setContentsMargins(15, 12, 15, 12) + + tip_icon = QLabel("💡") + tip_icon.setStyleSheet("font-size: 22px;") + tip_layout.addWidget(tip_icon) + + tip_text = QLabel("确保手机与电脑连接到同一网络才能访问") + tip_text.setStyleSheet(""" + color: #FF8F00; + font-size: 20px; + """) + tip_layout.addWidget(tip_text) + + # 添加托盘提示 + tray_info = QLabel("关闭窗口后,程序将在系统托盘中运行") + tray_info.setStyleSheet(""" + color: #333333; + font-size: 16px; + font-style: italic; + """) + tray_info.setAlignment(Qt.AlignCenter) + main_layout.addWidget(tray_info) + + main_layout.addWidget(tip_frame) + + # 设置中央部件 + self.setCentralWidget(central_widget) + + # 设置淡入效果 + self.setWindowOpacity(0) + self.fade_in() + + def create_tray_icon(self): + """创建系统托盘图标和菜单""" + # 创建托盘图标 + self.tray_icon = QSystemTrayIcon(self) + + # 设置图标 + if self.app_icon: + self.tray_icon.setIcon(self.app_icon) + else: + # 如果没有自定义图标,使用默认图标 + self.tray_icon.setIcon(QApplication.style().standardIcon(QApplication.style().SP_ComputerIcon)) + + # 设置托盘提示文字 + self.tray_icon.setToolTip("Snap Solver AI - 正在运行") + + # 创建托盘菜单 + tray_menu = QMenu() + + # 添加复制链接选项 + copy_action = QAction("复制链接", self) + copy_action.triggered.connect(self.copy_url) + tray_menu.addAction(copy_action) + + # 添加显示主窗口选项 + show_action = QAction("显示主窗口", self) + show_action.triggered.connect(self.show) + tray_menu.addAction(show_action) + + # 添加分隔线 + tray_menu.addSeparator() + + # 添加彻底隐藏托盘选项 + hide_tray_action = QAction("彻底隐藏托盘", self) + hide_tray_action.triggered.connect(self.hide_tray) + tray_menu.addAction(hide_tray_action) + + # 添加分隔线 + tray_menu.addSeparator() + + # 添加退出选项 + quit_action = QAction("关闭程序", self) + quit_action.triggered.connect(self.quit_application) + tray_menu.addAction(quit_action) + + # 设置托盘菜单 + self.tray_icon.setContextMenu(tray_menu) + + # 设置托盘图标点击行为 + self.tray_icon.activated.connect(self.tray_icon_activated) + + # 显示托盘图标 + self.tray_icon.show() + + def tray_icon_activated(self, reason): + """处理托盘图标激活事件""" + if reason == QSystemTrayIcon.DoubleClick: + # 双击显示主窗口 + self.show() + + def closeEvent(self, event): + """重写关闭事件,当关闭窗口时,最小化到系统托盘""" + if self.tray_icon and self.tray_icon.isVisible(): + self.hide() + event.ignore() + else: + event.accept() + + def hide_tray(self): + """彻底隐藏托盘图标,程序继续在后台运行""" + if self.tray_icon: + self.tray_icon.hide() + + def quit_application(self): + """彻底退出应用程序""" + QApplication.quit() + + def fade_in(self): + self.animation = QPropertyAnimation(self, b"windowOpacity") + self.animation.setDuration(250) + self.animation.setStartValue(0) + self.animation.setEndValue(1) + self.animation.start() + + def copy_url(self): + try: + # 将链接复制到剪贴板 + clipboard = QApplication.clipboard() + clipboard.setText(self.access_url) + + # 如果当前按钮可见,则更新其状态 + if hasattr(self, 'copy_btn') and self.copy_btn.isVisible(): + # 显示复制成功 + self.copy_btn.setText("已复制 ✓") + self.copy_btn.setStyleSheet(""" + QPushButton { + background-color: #4CAF50; + color: white; + border: none; + border-radius: 8px; + padding: 10px 25px; + font-size: 20px; + font-weight: bold; + } + """) + QTimer.singleShot(2000, self.reset_copy_button) + + # 如果托盘可见,通过托盘图标显示通知 + if self.tray_icon and self.tray_icon.isVisible(): + self.tray_icon.showMessage("复制成功", + f"已复制链接: {self.access_url}", + QSystemTrayIcon.Information, + 2000) + except Exception as e: + print(f"复制链接失败: {e}") + + def reset_copy_button(self): + if hasattr(self, 'copy_btn') and self.copy_btn.isVisible(): + self.copy_btn.setText("复制") + self.copy_btn.setStyleSheet(""" + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 8px; + padding: 10px 25px; + font-size: 20px; + font-weight: bold; + } + QPushButton:hover { + background-color: #1976D2; + } + QPushButton:pressed { + background-color: #0D47A1; + } + """) + + def open_in_browser(self): + try: + webbrowser.open(self.access_url) + except Exception as e: + print(f"打开浏览器失败: {e}") + + def keyPressEvent(self, event): + # 按ESC键关闭窗口 + if event.key() == Qt.Key_Escape: + self.hide() + else: + super().keyPressEvent(event) + + # 创建并显示窗口 + window = NotificationWindow() + window.show() + + # 执行应用程序 + app.exec_() + + except Exception as e: + print(f"创建通知窗口时出错: {e}") + traceback.print_exc() + + # 创建并启动线程 + window_thread = Thread(target=show_window) + window_thread.daemon = True # 设置为守护线程,这样主程序退出时线程也会退出 + window_thread.start() + +# 如果直接运行此文件,则显示一个测试窗口 +if __name__ == "__main__": + create_notification_window("127.0.0.1", 5000) + # 等待足够长的时间以便查看窗口 + import time + time.sleep(300) # 5分钟 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5f770b0..9d7a318 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,6 @@ flask-socketio==5.5.1 python-engineio==4.11.2 python-socketio==5.12.1 requests==2.32.3 -openai==1.61.0 \ No newline at end of file +openai==1.61.0 +qrcode==7.4.2 +PyQt5==5.15.9 \ No newline at end of file