Skip to content

Commit 22586ef

Browse files
committed
Release v1.9.2: 编辑器增强、检测优化、Web服务器修复
新功能: - 编辑器进度条显示 - 气泡框多选功能 优化: - 混合检测替换逻辑优化(支持YOLO框替换多个主检测器框) - 极端长宽比图片面积过滤优化(限制参考块长宽比≤3:1) - 详细日志支持改进 Bug修复: - 修复删除文本框后导出时仍包含已删除文本框的问题 - 修复Web服务器管理员配置保存问题 - 修复初始设置流程
1 parent 62c6888 commit 22586ef

22 files changed

Lines changed: 617 additions & 221 deletions

.github/workflows/build-and-release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ jobs:
188188
cp -r doc dist/manga-translator-cpu/_internal/
189189
cp -r desktop_qt_ui/styles dist/manga-translator-cpu/_internal/desktop_qt_ui/
190190
cp -r desktop_qt_ui/locales dist/manga-translator-cpu/_internal/desktop_qt_ui/
191+
cp -r desktop_qt_ui/resources dist/manga-translator-cpu/_internal/desktop_qt_ui/
191192
cp packaging/VERSION dist/manga-translator-cpu/_internal/
192193
193194
cp -r examples dist/manga-translator-gpu/_internal/
@@ -197,6 +198,7 @@ jobs:
197198
cp -r doc dist/manga-translator-gpu/_internal/
198199
cp -r desktop_qt_ui/styles dist/manga-translator-gpu/_internal/desktop_qt_ui/
199200
cp -r desktop_qt_ui/locales dist/manga-translator-gpu/_internal/desktop_qt_ui/
201+
cp -r desktop_qt_ui/resources dist/manga-translator-gpu/_internal/desktop_qt_ui/
200202
cp packaging/VERSION dist/manga-translator-gpu/_internal/
201203
202204
- name: Create Release Archives using 7-Zip with Splitting

desktop_qt_ui/app_logic.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ def get_display_mapping(self, key: str) -> Optional[Dict[str, str]]:
516516
"OPENAI_API_KEY": self._t("label_OPENAI_API_KEY"),
517517
"OPENAI_MODEL": self._t("label_OPENAI_MODEL"),
518518
"OPENAI_API_BASE": self._t("label_OPENAI_API_BASE"),
519-
"OPENAI_HTTP_PROXY": self._t("label_OPENAI_HTTP_PROXY"),
519+
# "OPENAI_HTTP_PROXY": self._t("label_OPENAI_HTTP_PROXY"), # 已废弃,不在UI中显示
520520
"OPENAI_GLOSSARY_PATH": self._t("label_OPENAI_GLOSSARY_PATH"),
521521
"DEEPSEEK_API_KEY": self._t("label_DEEPSEEK_API_KEY"),
522522
"DEEPSEEK_API_BASE": self._t("label_DEEPSEEK_API_BASE"),
@@ -1241,6 +1241,19 @@ def on_task_finished(self, results):
12411241
try:
12421242
self.state_manager.set_translating(False)
12431243
self.state_manager.set_status_message(f"任务完成,成功处理 {self.saved_files_count} 个文件。")
1244+
1245+
# 重置主视图的进度条
1246+
if hasattr(self, 'main_view') and self.main_view:
1247+
self.main_view.reset_progress()
1248+
1249+
# 播放系统提示音
1250+
try:
1251+
from PyQt6.QtWidgets import QApplication
1252+
QApplication.beep()
1253+
self.logger.info("播放系统提示音")
1254+
except Exception as sound_error:
1255+
self.logger.warning(f"播放提示音失败: {sound_error}")
1256+
12441257
self.task_completed.emit(saved_files)
12451258
except Exception as e:
12461259
self.logger.error(f"完成任务状态更新或信号发射时发生致命错误: {e}", exc_info=True)
@@ -1265,6 +1278,10 @@ def on_task_error(self, error_message):
12651278
self.state_manager.set_translating(False)
12661279
self.state_manager.set_status_message(f"任务失败: {error_message}")
12671280

1281+
# 重置主视图的进度条
1282+
if hasattr(self, 'main_view') and self.main_view:
1283+
self.main_view.reset_progress()
1284+
12681285
# 清理线程
12691286
if self.thread and self.thread.isRunning():
12701287
self.logger.warning("错误发生但线程仍在运行,请求退出...")
@@ -1281,6 +1298,10 @@ def on_task_progress(self, current, total, message):
12811298
percentage = (current / total) * 100 if total > 0 else 0
12821299
self.state_manager.set_translation_progress(percentage)
12831300
self.state_manager.set_status_message(f"[{current}/{total}] {message}")
1301+
1302+
# 更新主视图的进度条
1303+
if hasattr(self, 'main_view') and self.main_view:
1304+
self.main_view.update_progress(current, total, message)
12841305

12851306
def stop_task(self) -> bool:
12861307
"""停止翻译任务(优雅停止,不使用 terminate)"""
@@ -1529,10 +1550,7 @@ def _build_friendly_error_message(self, error_message: str, error_traceback: str
15291550
friendly_msg += "💡 解决方案:\n"
15301551
friendly_msg += " 1. 检查网络连接\n"
15311552
friendly_msg += " - 确认电脑可以正常访问互联网\n\n"
1532-
friendly_msg += " 2. 配置代理(如果需要)\n"
1533-
friendly_msg += " - 位置:翻译设置 → 环境变量 → OPENAI_HTTP_PROXY\n"
1534-
friendly_msg += " - 格式:http://127.0.0.1:7890 或 socks5://127.0.0.1:7890\n\n"
1535-
friendly_msg += " 3. 检查API地址是否正确\n"
1553+
friendly_msg += " 2. 检查API地址是否正确\n"
15361554
friendly_msg += " - 位置:翻译设置 → 环境变量 → API_BASE\n"
15371555
friendly_msg += " - 默认值:https://api.openai.com/v1\n\n"
15381556

@@ -1560,9 +1578,7 @@ def _build_friendly_error_message(self, error_message: str, error_traceback: str
15601578
friendly_msg += " - 确认API密钥有访问该服务的权限\n\n"
15611579
friendly_msg += " 2. 检查账户状态\n"
15621580
friendly_msg += " - 确认账户未被封禁或限制\n\n"
1563-
friendly_msg += " 3. 配置代理\n"
1564-
friendly_msg += " - 某些API在特定地区被限制,需要使用代理\n"
1565-
friendly_msg += " - 位置:翻译设置 → 环境变量 → OPENAI_HTTP_PROXY\n\n"
1581+
15661582

15671583
# 检查是否是404未找到错误
15681584
elif "404" in error_message or "not found" in error_message.lower():
@@ -1718,6 +1734,10 @@ async def _do_processing(self):
17181734
translator_params = self.config_dict.get('cli', {})
17191735
translator_params.update(self.config_dict)
17201736

1737+
# 根据 verbose 设置设置日志级别
1738+
verbose = translator_params.get('verbose', False)
1739+
if hasattr(self, 'log_service') and self.log_service:
1740+
self.log_service.set_console_log_level(verbose)
17211741

17221742
font_filename = self.config_dict.get('render', {}).get('font_path')
17231743
if font_filename:

desktop_qt_ui/editor/commands.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ def execute(self):
117117
"""执行删除操作"""
118118
if 0 <= self._index < len(self._model._regions):
119119
self._model._regions.pop(self._index)
120+
121+
# 【关键修复】同时更新resource_manager中的regions
122+
# 由于ResourceManager使用字典存储,需要重新同步整个列表
123+
from services import get_resource_manager
124+
resource_manager = get_resource_manager()
125+
resource_manager.clear_regions()
126+
for region_data in self._model._regions:
127+
resource_manager.add_region(region_data)
128+
120129
# 删除操作会改变后续区域的索引,必须触发完全更新
121130
# 通过直接调用 regions_changed 信号(而不是 region_style_updated)来确保完全更新
122131
self._model.regions_changed.emit(self._model._regions)
@@ -127,6 +136,15 @@ def undo(self):
127136
"""撤销删除操作:在原位置插入回区域"""
128137
if 0 <= self._index <= len(self._model._regions):
129138
self._model._regions.insert(self._index, copy.deepcopy(self._deleted_data))
139+
140+
# 【关键修复】同时更新resource_manager中的regions
141+
# 由于ResourceManager使用字典存储,需要重新同步整个列表
142+
from services import get_resource_manager
143+
resource_manager = get_resource_manager()
144+
resource_manager.clear_regions()
145+
for region_data in self._model._regions:
146+
resource_manager.add_region(region_data)
147+
130148
# 插入操作会改变后续区域的索引,必须触发完全更新
131149
self._model.regions_changed.emit(self._model._regions)
132150
# 恢复选择到被恢复的区域

desktop_qt_ui/editor/editor_controller.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ def __init__(self, model: EditorModel, parent=None):
6565
# 缓存键常量
6666
self.CACHE_LAST_INPAINTED = "last_inpainted_image"
6767
self.CACHE_LAST_MASK = "last_processed_mask"
68+
69+
# 用户透明度调整标志
70+
self._user_adjusted_alpha = False
6871

6972
# Connect internal signals for thread-safe updates
7073
self._update_refined_mask.connect(self.model.set_refined_mask)
@@ -383,9 +386,11 @@ def load_image_and_regions(self, image_path: str):
383386
self.logger.info(f"Detected translated image, loading as viewer only: {image_path}")
384387
self.model.set_source_image_path(image_path)
385388

386-
# 先设置透明度,再设置图片
387-
# 翻译后的图片本身就是最终结果,所以原图alpha应该是1.0(完全显示)
388-
self.model.set_original_image_alpha(1.0) # 完全不透明,显示翻译后的图片
389+
# 只在首次加载或用户未手动调整时设置透明度
390+
# 如果用户已经调整过透明度,保持当前值
391+
if not hasattr(self, '_user_adjusted_alpha') or not self._user_adjusted_alpha:
392+
# 翻译后的图片本身就是最终结果,所以原图alpha应该是1.0(完全显示)
393+
self.model.set_original_image_alpha(1.0) # 完全不透明,显示翻译后的图片
389394

390395
# 同步Model状态(View仍然监听Model的信号)
391396
self.model.set_image(image)
@@ -410,9 +415,11 @@ def load_image_and_regions(self, image_path: str):
410415
# Update model with the data from the service
411416
self.model.set_source_image_path(image_path) # Save the path
412417

413-
# 先设置透明度,再设置图片(因为set_image会触发on_image_changed,需要正确的透明度值)
414-
# 原图alpha = 0.0 -> 原图完全透明(不可见)-> 显示inpainted
415-
self.model.set_original_image_alpha(0.0) # 完全透明,显示inpainted图片
418+
# 只在首次加载或用户未手动调整时设置透明度
419+
# 如果用户已经调整过透明度,保持当前值
420+
if not hasattr(self, '_user_adjusted_alpha') or not self._user_adjusted_alpha:
421+
# 原图alpha = 0.0 -> 原图完全透明(不可见)-> 显示inpainted
422+
self.model.set_original_image_alpha(0.0) # 完全透明,显示inpainted图片
416423

417424
# 同步Model状态(View仍然监听Model的信号)
418425
self.model.set_image(image)
@@ -1716,6 +1723,8 @@ def set_original_image_alpha(self, alpha: int):
17161723
# slider = 100 -> alpha = 1.0(完全不透明,显示原图)
17171724
alpha_float = alpha / 100.0
17181725
self.model.set_original_image_alpha(alpha_float)
1726+
# 标记用户已手动调整透明度
1727+
self._user_adjusted_alpha = True
17191728

17201729
@pyqtSlot(int)
17211730
def set_preview_alpha(self, alpha: int):

desktop_qt_ui/editor/graphics_items.py

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ def __init__(self, region_data, region_index, geometry_callback, parent=None):
8282
# 设置 text_item 的 Z-order 为负数,让它在父 item 的绘制内容(绿框、白框)之下
8383
self.text_item.setZValue(-1)
8484

85-
self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
85+
# 保留 ItemIsSelectable 以支持 setSelected(),但在 mousePressEvent 中手动控制选择
86+
# ItemIsMovable 会在需要时动态设置
87+
self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
8688
self.setAcceptHoverEvents(True)
8789
self._interaction_mode = 'none'
8890
self._is_dragging = False # 拖动状态标志
@@ -529,7 +531,60 @@ def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
529531
local_pos = event.pos()
530532

531533
if event.button() == Qt.MouseButton.LeftButton:
532-
if self.isSelected():
534+
# 获取 view 和 model
535+
view = self.scene().views()[0] if self.scene() and self.scene().views() else None
536+
if not view or not hasattr(view, 'model'):
537+
# 没有 view/model,使用默认行为
538+
super().mousePressEvent(event)
539+
return
540+
541+
# 检查 Ctrl 键
542+
ctrl_pressed = bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier)
543+
current_selection = view.model.get_selection()
544+
is_selected = self.region_index in current_selection
545+
546+
# === 处理选择逻辑 ===
547+
# 临时禁用 Qt 的自动选择,避免与我们的手动选择冲突
548+
was_selectable = bool(self.flags() & QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
549+
if was_selectable:
550+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
551+
552+
if ctrl_pressed:
553+
# Ctrl+点击:切换选择
554+
if is_selected:
555+
# 取消选择
556+
new_selection = [idx for idx in current_selection if idx != self.region_index]
557+
else:
558+
# 添加到选择
559+
new_selection = current_selection + [self.region_index]
560+
view.model.set_selection(new_selection)
561+
562+
# 恢复 selectable 标志
563+
if was_selectable:
564+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
565+
566+
event.accept()
567+
return # Ctrl+点击只处理选择,不进入拖动模式
568+
else:
569+
# 普通点击(无 Ctrl)
570+
if not is_selected:
571+
# 点击未选中的:单选
572+
view.model.set_selection([self.region_index])
573+
574+
# 恢复 selectable 标志
575+
if was_selectable:
576+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
577+
578+
event.accept()
579+
return
580+
# 点击已选中的:继续处理拖动/编辑(不改变选择)
581+
# 恢复 selectable 标志
582+
if was_selectable:
583+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
584+
585+
# === 处理拖动/编辑逻辑(仅当已选中时) ===
586+
if is_selected:
587+
533588
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
534589

535590
# --- CRITICAL: Snapshot all initial state for the drag operation ---
@@ -573,23 +628,15 @@ def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
573628
# 点击多边形时进入移动模式
574629
self._interaction_mode = 'move'
575630
self._is_dragging = True
576-
631+
632+
# 处理拖动,但禁用 Qt 的选择行为避免干扰
633+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
577634
super().mousePressEvent(event)
635+
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
578636
event.accept()
579637
return
580-
else:
581-
# 未选中状态:直接通过 model 设置选中(避免依赖 GraphicsView 的空间索引)
582-
# 获取 view 和 model
583-
if self.scene() and self.scene().views():
584-
view = self.scene().views()[0]
585-
if hasattr(view, 'model'):
586-
view.model.set_selection([self.region_index])
587-
event.accept() # Accept 事件,阻止传播到 GraphicsView
588-
return
589-
# 如果无法获取 model,fallback 到原来的行为
590-
super().mousePressEvent(event)
591-
event.ignore()
592-
return
638+
639+
# 其他按钮的默认行为
593640
super().mousePressEvent(event)
594641

595642
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):

0 commit comments

Comments
 (0)