From 5ac40ed1cd82f7525ef5bf5eb273b67247b2db77 Mon Sep 17 00:00:00 2001
From: chakcy <947105045@qq.com>
Date: Tue, 30 Sep 2025 15:53:47 +0800
Subject: [PATCH] =?UTF-8?q?add=20markdown=20=E7=BC=96=E8=BE=91=E5=99=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/view/__init__.py | 3 +
app/view/markdown/__init__.py | 0
app/view/markdown/markdown_interface.py | 775 ++++++++++++++++++++++++
app/view/video/video_interface.py | 2 +-
pyproject.toml | 2 +
uv.lock | 97 +++
6 files changed, 878 insertions(+), 1 deletion(-)
create mode 100644 app/view/markdown/__init__.py
create mode 100644 app/view/markdown/markdown_interface.py
diff --git a/app/view/__init__.py b/app/view/__init__.py
index 9ce3925..dd1847d 100644
--- a/app/view/__init__.py
+++ b/app/view/__init__.py
@@ -18,6 +18,7 @@ from .students.student_interface import StudentInterface
from .video.video_interface import VideoInterface
from .camera.camera_interface import CameraInterface
from .web_view.web_view_interface import WebViewInterface
+from .markdown.markdown_interface import MarkdownInterface
def resource_path(relative_path):
@@ -35,6 +36,7 @@ class Window(SplitFluentWindow):
self.video_interface = VideoInterface()
self.camera_interface = CameraInterface()
self.web_view_interface = WebViewInterface(self)
+ self.markdown_interface = MarkdownInterface()
self.initNavigation()
self.resize(900, 700)
@@ -61,6 +63,7 @@ class Window(SplitFluentWindow):
self.addSubInterface(self.video_interface, FIF.VIDEO, "Video")
self.addSubInterface(self.camera_interface, FIF.CAMERA, "Camera")
self.addSubInterface(self.web_view_interface, FIF.FIT_PAGE, "Web View")
+ self.addSubInterface(self.markdown_interface, FIF.DOCUMENT, "Markdown Editor")
# 使用资源路径函数
self.navigationInterface.addWidget(
diff --git a/app/view/markdown/__init__.py b/app/view/markdown/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/view/markdown/markdown_interface.py b/app/view/markdown/markdown_interface.py
new file mode 100644
index 0000000..5830b1a
--- /dev/null
+++ b/app/view/markdown/markdown_interface.py
@@ -0,0 +1,775 @@
+import os
+import sys
+import markdown
+from pathlib import Path
+
+from PySide6.QtCore import Qt, QTimer, Slot
+from PySide6.QtWidgets import (
+ QWidget,
+ QVBoxLayout,
+ QHBoxLayout,
+ QSplitter,
+ QFileDialog,
+ QMessageBox,
+)
+from PySide6.QtGui import (
+ QFont,
+ QTextCursor,
+ QSyntaxHighlighter,
+ QKeySequence,
+ QShortcut,
+ QTextCharFormat,
+)
+from qfluentwidgets import (
+ CardWidget,
+ PushButton,
+ TextEdit,
+ ScrollArea,
+ setTheme,
+ Theme,
+ InfoBar,
+ InfoBarPosition,
+ StrongBodyLabel,
+ BodyLabel,
+ ToolButton,
+)
+from qfluentwidgets import FluentIcon as FIF
+from PySide6.QtWebEngineWidgets import QWebEngineView
+from PySide6.QtWebChannel import QWebChannel
+
+
+def resource_path(relative_path):
+ """获取资源的绝对路径"""
+ if hasattr(sys, "_MEIPASS"):
+ return os.path.join(sys._MEIPASS, relative_path) # type: ignore
+ return os.path.join(os.path.abspath("."), relative_path)
+
+
+class MarkdownHighlighter(QSyntaxHighlighter):
+ """Markdown语法高亮"""
+
+ def __init__(self, parent=None):
+ super().__init__(parent) # type: ignore
+ self.highlighting_rules = []
+
+ # 标题规则
+ header_format = QTextCharFormat()
+ header_format.setForeground(Qt.GlobalColor.darkBlue)
+ header_format.setFontWeight(QFont.Weight.Bold)
+
+ # 不同级别的标题
+ for pattern in [
+ r"^# .+$",
+ r"^## .+$",
+ r"^### .+$",
+ r"^#### .+$",
+ r"^##### .+$",
+ r"^###### .+$",
+ ]:
+ self.highlighting_rules.append((pattern, header_format))
+
+ # 粗体
+ bold_format = QTextCharFormat()
+ bold_format.setFontWeight(QFont.Weight.Bold)
+ self.highlighting_rules.append((r"\*\*.+?\*\*", bold_format))
+ self.highlighting_rules.append((r"__.+?__", bold_format))
+
+ # 斜体
+ italic_format = QTextCharFormat()
+ italic_format.setFontItalic(True)
+ self.highlighting_rules.append((r"\*.+?\*", italic_format))
+ self.highlighting_rules.append((r"_.+?_", italic_format))
+
+ # 代码
+ code_format = QTextCharFormat()
+ code_format.setForeground(Qt.GlobalColor.darkGreen)
+ code_format.setFontFamily("Consolas")
+ self.highlighting_rules.append((r"`.+?`", code_format))
+
+ # 链接
+ link_format = QTextCharFormat()
+ link_format.setForeground(Qt.GlobalColor.blue)
+ link_format.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SingleUnderline)
+ self.highlighting_rules.append((r"\[.+?\]\(.+?\)", link_format))
+
+ # 列表
+ list_format = QTextCharFormat()
+ list_format.setForeground(Qt.GlobalColor.darkMagenta)
+ self.highlighting_rules.append((r"^[\*\-\+] .+$", list_format))
+ self.highlighting_rules.append((r"^\d+\. .+$", list_format))
+
+ def highlightBlock(self, text):
+ for pattern, format in self.highlighting_rules:
+ import re
+
+ expression = re.compile(pattern)
+ for match in expression.finditer(text):
+ start, end = match.span()
+ self.setFormat(start, end - start, format)
+
+
+class MarkdownPreview(ScrollArea):
+ """Markdown预览组件"""
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setObjectName("markdownPreview")
+ self.setMinimumWidth(300)
+ self.parent = parent # type: ignore
+ self.current_scroll_percent = 0
+
+ # 创建预览容器
+ self.preview_widget = QWidget()
+ self.preview_layout = QVBoxLayout(self.preview_widget)
+
+ # 预览内容区域
+ self.web_view = QWebEngineView()
+ self.web_view.setHtml(self.get_preview_html(""))
+
+ self.channel = QWebChannel()
+ self.web_view.page().setWebChannel(self.channel)
+ self.channel.registerObject("preview", self)
+
+ self.preview_layout.addWidget(self.web_view)
+ self.setWidget(self.preview_widget)
+ self.setWidgetResizable(True)
+
+ # 设置样式
+ self.setStyleSheet(
+ """
+ MarkdownPreview {
+ background-color: white;
+ border: 1px solid #e1e5e9;
+ border-radius: 8px;
+ }
+ """
+ )
+ self.web_view.loadFinished.connect(self.on_load_finished)
+
+ @Slot(bool)
+ def on_load_finished(self):
+ """页面加载完成后注入JavaScript代码"""
+ js_code = """
+ // 监听预览区域的滚动事件
+ let isScrolling = false;
+ window.addEventListener('scroll', function() {
+ if (isScrolling) return;
+
+ var scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
+ if (window.pyHandler && window.pyHandler.onPreviewScroll) {
+ window.pyHandler.onPreviewScroll(scrollPercent);
+ }
+ });
+
+ // 用于从Python端设置滚动位置的函数
+ window.scrollToPercent = function(percent) {
+ isScrolling = true;
+ var scrollY = (percent / 100) * (document.documentElement.scrollHeight - window.innerHeight);
+ window.scrollTo(0, scrollY);
+ setTimeout(() => { isScrolling = false; }, 100);
+ };
+
+ // 保存当前滚动位置
+ window.saveScrollPosition = function() {
+ return (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
+ };
+
+ // 恢复滚动位置
+ window.restoreScrollPosition = function(percent) {
+ window.scrollToPercent(percent);
+ };
+ """
+ self.web_view.page().runJavaScript(js_code)
+
+ # 加载完成后恢复滚动位置
+ if self.current_scroll_percent > 0:
+ QTimer.singleShot(
+ 100, lambda: self.scroll_to_percent(self.current_scroll_percent)
+ )
+
+ @Slot(float)
+ def onPreviewScroll(self, percent):
+ """从JavaScript接收预览区域的滚动位置"""
+ self.current_scroll_percent = percent
+ if self.parent and hasattr(self.parent, "sync_editor_to_preview"):
+ self.parent.sync_editor_to_preview(percent) # type: ignore
+
+ def scroll_to_percent(self, percent):
+ """滚动预览到指定百分比位置"""
+ js_code = f"if (window.scrollToPercent) window.scrollToPercent({percent});"
+ self.web_view.page().runJavaScript(js_code)
+
+ def _on_save_scroll_position(self, result):
+ """保存滚动位置的回调"""
+ if result is not None:
+ self.current_scroll_percent = int(result)
+
+ def restore_scroll_position(self):
+ """恢复保存的滚动位置"""
+ if self.current_scroll_percent > 0:
+ self.scroll_to_percent(self.current_scroll_percent)
+
+ def save_scroll_position(self):
+ """保存当前滚动位置"""
+ js_code = "if (window.saveScrollPosition) window.saveScrollPosition();"
+ self.web_view.page().runJavaScript(js_code, self._on_save_scroll_position)
+
+ def get_preview_html(self, markdown_text):
+ """使用markdown库生成预览HTML"""
+ try:
+ # 使用markdown库转换Markdown
+ html_content = markdown.markdown(
+ markdown_text,
+ extensions=[
+ "extra", # 支持表格、缩写等
+ "codehilite", # 代码高亮
+ "toc", # 目录
+ "nl2br", # 换行转
+ "sane_lists", # 更合理的列表
+ "smarty", # 智能引号等
+ ],
+ extension_configs={
+ "codehilite": {"css_class": "highlight", "linenums": False}
+ },
+ )
+ except Exception as e:
+ html_content = f"
Markdown解析错误: {str(e)}
"
+
+ return f"""
+
+
+
+
+
+
+
+
+ {html_content}
+
+
+
+ """
+
+ def update_preview(self, markdown_text):
+ """更新预览内容"""
+ # 保存当前滚动位置
+ self.save_scroll_position()
+ # 更新HTML内容
+ self.web_view.setHtml(self.get_preview_html(markdown_text))
+
+
+class MarkdownEditor(TextEdit):
+ """增强的Markdown编辑器"""
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setPlaceholderText("在此输入Markdown内容...")
+ self.setFont(QFont("Consolas", 11))
+
+ # 启用语法高亮
+ self.highlighter = MarkdownHighlighter(self.document())
+
+ # 设置制表符宽度
+ self.setTabStopDistance(40)
+
+
+class MarkdownInterface(QWidget):
+ """Markdown编辑器主界面"""
+
+ def __init__(self):
+ super().__init__()
+ self.setObjectName("markdown_interface")
+ self.current_file = None
+ # self.editor = None
+ self.is_syncing = False
+ self.text_change_timer = QTimer() # 用于延迟更新预览
+ self.text_change_timer.setSingleShot(True)
+ self.text_change_timer.timeout.connect(self.delayed_preview_update)
+
+ self.setup_ui()
+ self.setup_shortcuts()
+
+ def setup_ui(self):
+ """设置界面布局"""
+ main_layout = QVBoxLayout(self)
+ main_layout.setContentsMargins(20, 50, 20, 20)
+ main_layout.setSpacing(15)
+
+ # 标题
+ title_label = StrongBodyLabel("Markdown编辑器")
+ title_label.setStyleSheet("font-size: 24px; padding: 10px 0;")
+ main_layout.addWidget(title_label)
+
+ # 编辑器和预览器
+ editor_preview_layout = QHBoxLayout()
+
+ # 编辑器
+ self.editor = MarkdownEditor()
+ self.editor.textChanged.connect(self.on_text_changed)
+
+ # 连接编辑器的滚动事件
+ self.editor.verticalScrollBar().valueChanged.connect(self.on_editor_scroll)
+
+ # 工具栏
+ toolbar = self.create_toolbar()
+ main_layout.addWidget(toolbar)
+
+ # 预览器
+ self.preview = MarkdownPreview(self)
+
+ # 分割器
+ splitter = QSplitter(Qt.Orientation.Horizontal)
+ splitter.addWidget(self.editor)
+ splitter.addWidget(self.preview)
+ splitter.setSizes([400, 400])
+
+ editor_preview_layout.addWidget(splitter)
+ main_layout.addLayout(editor_preview_layout, 1)
+
+ # 状态栏
+ self.status_label = BodyLabel("就绪")
+ self.status_label.setStyleSheet("color: #666; font-size: 12px;")
+ main_layout.addWidget(self.status_label)
+
+ def create_toolbar(self):
+ """创建工具栏"""
+ toolbar_card = CardWidget()
+ toolbar_layout = QHBoxLayout(toolbar_card)
+
+ # 文件操作按钮
+ self.new_btn = PushButton(FIF.ADD, "新建", self)
+ self.new_btn.clicked.connect(self.new_file)
+
+ self.open_btn = PushButton(FIF.FOLDER, "打开", self)
+ self.open_btn.clicked.connect(self.open_file)
+
+ self.save_btn = PushButton(FIF.SAVE, "保存", self)
+ self.save_btn.clicked.connect(self.save_file)
+
+ self.save_as_btn = PushButton(FIF.SAVE_AS, "另存为", self)
+ self.save_as_btn.clicked.connect(self.save_as_file)
+
+ # 编辑操作按钮
+ self.undo_btn = PushButton(FIF.LEFT_ARROW, "撤销", self)
+ self.undo_btn.clicked.connect(self.editor.undo)
+
+ self.redo_btn = PushButton(FIF.RIGHT_ARROW, "反撤销", self)
+ self.redo_btn.clicked.connect(self.editor.redo)
+
+ # 格式化按钮
+ self.bold_btn = ToolButton(self)
+ self.bold_btn.setText("B")
+ self.bold_btn.setToolTip("粗体")
+ self.bold_btn.clicked.connect(self.insert_bold)
+
+ self.italic_btn = ToolButton(self)
+ self.italic_btn.setText("I")
+ self.italic_btn.setToolTip("斜体")
+ self.italic_btn.clicked.connect(self.insert_italic)
+
+ self.code_btn = ToolButton(self)
+ self.code_btn.setText(">")
+ self.code_btn.setToolTip("代码")
+ self.code_btn.clicked.connect(self.insert_code)
+
+ # 添加按钮到工具栏
+ toolbar_layout.addWidget(self.new_btn)
+ toolbar_layout.addWidget(self.open_btn)
+ toolbar_layout.addWidget(self.save_btn)
+ toolbar_layout.addWidget(self.save_as_btn)
+ toolbar_layout.addWidget(self.undo_btn)
+ toolbar_layout.addWidget(self.redo_btn)
+ toolbar_layout.addWidget(self.bold_btn)
+ toolbar_layout.addWidget(self.italic_btn)
+ toolbar_layout.addWidget(self.code_btn)
+ toolbar_layout.addStretch()
+
+ return toolbar_card
+
+ def setup_shortcuts(self):
+ """设置快捷键"""
+ # 文件操作
+ QShortcut(QKeySequence("Ctrl+S"), self).activated.connect(self.save_file)
+ QShortcut(QKeySequence("Ctrl+O"), self).activated.connect(self.open_file)
+ QShortcut(QKeySequence("Ctrl+N"), self).activated.connect(self.new_file)
+
+ # 编辑操作
+ QShortcut(QKeySequence("Ctrl+Z"), self).activated.connect(self.editor.undo)
+ QShortcut(QKeySequence("Ctrl+Y"), self).activated.connect(self.editor.redo)
+ QShortcut(QKeySequence("Ctrl+Shift+Z"), self).activated.connect(
+ self.editor.redo
+ )
+
+ # 格式化快捷键
+ QShortcut(QKeySequence("Ctrl+B"), self).activated.connect(self.insert_bold)
+ QShortcut(QKeySequence("Ctrl+I"), self).activated.connect(self.insert_italic)
+ QShortcut(QKeySequence("Ctrl+K"), self).activated.connect(self.insert_code)
+
+ def on_editor_scroll(self):
+ """编辑器滚动时同步预览"""
+ if self.is_syncing:
+ return
+
+ self.is_syncing = True
+
+ # 计算编辑器的滚动百分比
+ scroll_bar = self.editor.verticalScrollBar()
+ max_scroll = scroll_bar.maximum()
+ if max_scroll > 0:
+ scroll_percent = (scroll_bar.value() / max_scroll) * 100
+ # 同步预览滚动
+ self.preview.scroll_to_percent(scroll_percent)
+
+ self.is_syncing = False
+
+ def sync_editor_to_preview(self, preview_percent):
+ """从预览同步到编辑器"""
+ if self.is_syncing:
+ return
+
+ self.is_syncing = True
+
+ # 同步编辑器滚动
+ scroll_bar = self.editor.verticalScrollBar()
+ max_scroll = scroll_bar.maximum()
+ if max_scroll > 0:
+ scroll_value = (preview_percent / 100) * max_scroll
+ scroll_bar.setValue(int(scroll_value))
+
+ self.is_syncing = False
+
+ def insert_bold(self):
+ """插入粗体"""
+ cursor = self.editor.textCursor()
+ selected_text = cursor.selectedText()
+ if selected_text:
+ cursor.insertText(f"**{selected_text}**")
+ else:
+ cursor.insertText("****")
+ cursor.movePosition(
+ QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 2
+ )
+ self.editor.setTextCursor(cursor)
+
+ def insert_italic(self):
+ """插入斜体"""
+ cursor = self.editor.textCursor()
+ selected_text = cursor.selectedText()
+ if selected_text:
+ cursor.insertText(f"*{selected_text}*")
+ else:
+ cursor.insertText("**")
+ cursor.movePosition(
+ QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 1
+ )
+ self.editor.setTextCursor(cursor)
+
+ def insert_code(self):
+ """插入代码"""
+ cursor = self.editor.textCursor()
+ selected_text = cursor.selectedText()
+ if selected_text:
+ cursor.insertText(f"`{selected_text}`")
+ else:
+ cursor.insertText("``")
+ cursor.movePosition(
+ QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 1
+ )
+ self.editor.setTextCursor(cursor)
+
+ def on_text_changed(self):
+ """文本变化时延迟更新预览"""
+ # 停止之前的计时器
+ self.text_change_timer.stop()
+
+ # 重新启动计时器(延迟300毫秒更新预览)
+ self.text_change_timer.start(300)
+
+ # 更新状态
+ text = self.editor.toPlainText()
+ self.update_status("已修改" if text else "就绪")
+
+ def delayed_preview_update(self):
+ """延迟更新预览,避免频繁刷新"""
+ text = self.editor.toPlainText()
+ self.preview.update_preview(text)
+
+ def new_file(self):
+ """新建文件"""
+ if self.current_file or self.editor.toPlainText():
+ reply = QMessageBox.question(
+ self,
+ "确认",
+ "是否保存当前文件的更改?",
+ QMessageBox.StandardButton.Yes
+ | QMessageBox.StandardButton.No
+ | QMessageBox.StandardButton.Cancel,
+ )
+ if reply == QMessageBox.StandardButton.Yes:
+ if not self.save_file():
+ return
+ elif reply == QMessageBox.StandardButton.Cancel:
+ return
+
+ if self.editor:
+ self.editor.clear()
+ self.current_file = None
+ self.update_status("新建文件")
+ self.show_info("已创建新文件", "success")
+
+ def open_file(self):
+ """打开文件"""
+ file_path, _ = QFileDialog.getOpenFileName(
+ self,
+ "打开Markdown文件",
+ "",
+ "Markdown文件 (*.md *.markdown);;所有文件 (*.*)",
+ )
+
+ if file_path:
+ try:
+ with open(file_path, "r", encoding="utf-8") as file:
+ content = file.read()
+ self.editor.setPlainText(content)
+ self.current_file = file_path
+ self.update_status(f"已打开: {Path(file_path).name}")
+ self.show_info(f"已打开文件: {Path(file_path).name}", "success")
+ except Exception as e:
+ self.show_info(f"打开文件失败: {str(e)}", "error")
+
+ def save_file(self):
+ """保存文件"""
+ if self.current_file:
+ return self._save_to_file(self.current_file)
+ else:
+ return self.save_as_file()
+
+ def save_as_file(self):
+ """另存为文件"""
+ file_path, _ = QFileDialog.getSaveFileName(
+ self,
+ "保存Markdown文件",
+ self.current_file or "未命名文档.md",
+ "Markdown文件 (*.md *.markdown);;所有文件 (*.*)",
+ )
+
+ if file_path:
+ return self._save_to_file(file_path)
+ return False
+
+ def _save_to_file(self, file_path):
+ """保存内容到文件"""
+ try:
+ with open(file_path, "w", encoding="utf-8") as file:
+ file.write(self.editor.toPlainText())
+ self.current_file = file_path
+ self.update_status(f"已保存: {Path(file_path).name}")
+ self.show_info(f"文件已保存: {Path(file_path).name}", "success")
+ return True
+ except Exception as e:
+ self.show_info(f"保存文件失败: {str(e)}", "error")
+ return False
+
+ def update_status(self, message):
+ """更新状态栏"""
+ self.status_label.setText(message)
+
+ def show_info(self, content, type="info"):
+ """显示信息条"""
+ if type == "success":
+ InfoBar.success(
+ "", content, parent=self, duration=2000, position=InfoBarPosition.TOP
+ )
+ elif type == "warning":
+ InfoBar.warning(
+ "", content, parent=self, duration=2000, position=InfoBarPosition.TOP
+ )
+ elif type == "error":
+ InfoBar.error(
+ "", content, parent=self, duration=3000, position=InfoBarPosition.TOP
+ )
+ else:
+ InfoBar.info(
+ "", content, parent=self, duration=2000, position=InfoBarPosition.TOP
+ )
+
+
+if __name__ == "__main__":
+ import sys
+ from PySide6.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+ setTheme(Theme.LIGHT)
+
+ window = MarkdownInterface()
+ window.resize(1000, 700)
+ window.show()
+
+ sys.exit(app.exec())
diff --git a/app/view/video/video_interface.py b/app/view/video/video_interface.py
index 2657f5d..204270b 100644
--- a/app/view/video/video_interface.py
+++ b/app/view/video/video_interface.py
@@ -1,7 +1,7 @@
from PySide6.QtCore import QUrl, Qt
from PySide6.QtGui import QFont # 导入QFont
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QFileDialog, QHBoxLayout
-from qfluentwidgets import setTheme, Theme, FluentIcon, PushButton, PrimaryPushButton
+from qfluentwidgets import FluentIcon, PushButton
from qfluentwidgets.multimedia import VideoWidget
diff --git a/pyproject.toml b/pyproject.toml
index e67abee..0bc40aa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,11 +5,13 @@ description = "A PySide6 application that uses queue-sqlite to manage tasks"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
+ "markdown>=3.9",
"opencv-python>=4.12.0.88",
"opencv-python-headless>=4.12.0.88",
"pyside6>=6.9.1",
"pyside6-fluent-widgets>=1.8.6",
"queue-sqlite>=0.2.0",
+ "requests>=2.32.5",
"toml>=0.10.2",
]
# 是否是生产环境
diff --git a/uv.lock b/uv.lock
index 54901ef..c1a1c12 100644
--- a/uv.lock
+++ b/uv.lock
@@ -11,6 +11,15 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff" },
]
+[[package]]
+name = "certifi"
+version = "2025.8.3"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407" }
+wheels = [
+ { url = "https://mirrors.aliyun.com/pypi/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" },
+]
+
[[package]]
name = "cffi"
version = "1.17.1"
@@ -44,6 +53,48 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a" },
]
+[[package]]
+name = "charset-normalizer"
+version = "3.4.3"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14" }
+wheels = [
+ { url = "https://mirrors.aliyun.com/pypi/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c" },
+ { url = "https://mirrors.aliyun.com/pypi/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a" },
+]
+
[[package]]
name = "darkdetect"
version = "0.8.0"
@@ -53,6 +104,15 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl", hash = "sha256:a7509ccf517eaad92b31c214f593dbcf138ea8a43b2935406bbd565e15527a85" },
]
+[[package]]
+name = "idna"
+version = "3.10"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9" }
+wheels = [
+ { url = "https://mirrors.aliyun.com/pypi/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" },
+]
+
[[package]]
name = "macholib"
version = "1.16.3"
@@ -65,6 +125,15 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c" },
]
+[[package]]
+name = "markdown"
+version = "3.9"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a" }
+wheels = [
+ { url = "https://mirrors.aliyun.com/pypi/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280" },
+]
+
[[package]]
name = "maturin"
version = "1.9.4"
@@ -2908,11 +2977,13 @@ name = "pyside-queue-sqlite"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
+ { name = "markdown" },
{ name = "opencv-python" },
{ name = "opencv-python-headless" },
{ name = "pyside6" },
{ name = "pyside6-fluent-widgets" },
{ name = "queue-sqlite" },
+ { name = "requests" },
{ name = "toml" },
]
@@ -2925,11 +2996,13 @@ dev = [
[package.metadata]
requires-dist = [
+ { name = "markdown", specifier = ">=3.9" },
{ name = "opencv-python", specifier = ">=4.12.0.88" },
{ name = "opencv-python-headless", specifier = ">=4.12.0.88" },
{ name = "pyside6", specifier = ">=6.9.1" },
{ name = "pyside6-fluent-widgets", specifier = ">=1.8.6" },
{ name = "queue-sqlite", specifier = ">=0.2.0" },
+ { name = "requests", specifier = ">=2.32.5" },
{ name = "toml", specifier = ">=0.10.2" },
]
@@ -3065,6 +3138,21 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/21/bb/2abf6dbd3d08f0d5f7aac576168be78b957e283d9d8461bf2afcad75b86c/queue_sqlite_core-0.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a9d19840a9363bc29568b7c0e735c1ffe610b9618641c298d9647816aad8306b" },
]
+[[package]]
+name = "requests"
+version = "2.32.5"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" }
+wheels = [
+ { url = "https://mirrors.aliyun.com/pypi/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6" },
+]
+
[[package]]
name = "setuptools"
version = "80.9.0"
@@ -3095,6 +3183,15 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" },
]
+[[package]]
+name = "urllib3"
+version = "2.5.0"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760" }
+wheels = [
+ { url = "https://mirrors.aliyun.com/pypi/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" },
+]
+
[[package]]
name = "zstandard"
version = "0.23.0"