Skip to content

Commit 9b61857

Browse files
committed
Release v1.8.0
1 parent b3cb897 commit 9b61857

10 files changed

Lines changed: 859 additions & 60 deletions

File tree

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,33 @@ jobs:
6666
with:
6767
fetch-depth: 0
6868

69-
- name: Download and Extract Models
69+
- name: Download and Extract GPU Models
7070
env:
7171
GH_TOKEN: ${{ github.token }}
7272
run: |
73-
echo "Downloading models from release assets..."
73+
echo "Downloading GPU models from release assets..."
7474
gh release download v1.6.0 -p "models.7z"
7575
76-
echo "Decompressing models..."
77-
7z x "models.7z" -o./extracted_models
76+
echo "Decompressing GPU models..."
77+
7z x "models.7z" -o./extracted_models_gpu
7878
79-
echo "Replacing models directory..."
80-
rm -rf ./models
81-
mv ./extracted_models/models ./models
82-
echo "Models replaced successfully."
79+
echo "Setting up GPU models directory..."
80+
rm -rf ./models_gpu
81+
mv ./extracted_models_gpu/models ./models_gpu
82+
echo "GPU models ready."
83+
84+
- name: Download and Extract CPU Models
85+
run: |
86+
echo "Downloading CPU models from v1.7.9..."
87+
curl -L -o models_cpu.7z https://github.com/hgmzhn/manga-translator-ui/releases/download/v1.7.9/models.7z
88+
89+
echo "Decompressing CPU models..."
90+
7z x "models_cpu.7z" -o./extracted_models_cpu
91+
92+
echo "Setting up CPU models directory..."
93+
rm -rf ./models_cpu
94+
mv ./extracted_models_cpu/models ./models_cpu
95+
echo "CPU models ready."
8396
8497
# - name: Restore Previous TUF Assets
8598
# env:
@@ -170,13 +183,13 @@ jobs:
170183
171184
cp -r examples dist/manga-translator-cpu/_internal/
172185
cp -r fonts dist/manga-translator-cpu/_internal/
173-
cp -r models dist/manga-translator-cpu/_internal/
186+
cp -r models_cpu dist/manga-translator-cpu/_internal/models
174187
cp -r dict dist/manga-translator-cpu/_internal/
175188
cp packaging/VERSION dist/manga-translator-cpu/_internal/
176189
177190
cp -r examples dist/manga-translator-gpu/_internal/
178191
cp -r fonts dist/manga-translator-gpu/_internal/
179-
cp -r models dist/manga-translator-gpu/_internal/
192+
cp -r models_gpu dist/manga-translator-gpu/_internal/models
180193
cp -r dict dist/manga-translator-gpu/_internal/
181194
cp packaging/VERSION dist/manga-translator-gpu/_internal/
182195

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ venv/
77
.venv_cpu/
88
.venv_gpu/
99

10+
# Rust project directory (reference only)
11+
manga-image-translator-rust-master/
12+
1013
# Environment files (contain sensitive information)
1114
.env
1215
.env.*
1316

17+
# User configuration file (contains user-specific settings)
18+
examples/config.json
19+
1420
# Claude settings
1521
.claude/
1622

@@ -78,7 +84,7 @@ gemini_key_validator_ui.py30cadb668d2efc5c432fd2939130df0d_translations.json
7884
新建文件夹/
7985
manga-translator-ui-1.5.1/
8086
manga-translator-ui-1.7.6/
81-
BallonsTranslator-dev/*
87+
BallonsTranslator-dev/
8288
# Added by Gemini to ignore untracked files
8389
.cunzhi-memory/
8490
logs/

desktop_qt_ui/app_logic.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ def update_config(self, config_updates: Dict[str, Any]) -> bool:
301301
return False
302302

303303
def update_single_config(self, full_key: str, value: Any):
304-
self.logger.info(f"--- DIAGNOSTIC_KEY: update_single_config called with key: '{full_key}', value: '{value}'")
304+
self.logger.debug(f"update_single_config: '{full_key}' = '{value}'")
305305
try:
306306
config_obj = self.config_service.get_config()
307307
keys = full_key.split('.')
@@ -311,16 +311,16 @@ def update_single_config(self, full_key: str, value: Any):
311311
setattr(parent_obj, keys[-1], value)
312312
self.config_service.set_config(config_obj)
313313
self.config_service.save_config_file()
314-
self.logger.info(f"Saved '{full_key}' = '{value}' to config.")
314+
self.logger.debug(f"配置已保存: '{full_key}' = '{value}'")
315315

316316
# 当翻译器设置被更改时,直接更新翻译服务的内部状态
317317
if full_key == 'translator.translator':
318-
self.logger.info(f"Translator has been changed to '{value}'. Updating translation service state.")
318+
self.logger.debug(f"翻译器已切换: '{value}'")
319319
self.translation_service.set_translator(value)
320320

321321
# 当渲染设置被更改时,通知编辑器刷新
322322
if full_key.startswith('render.'):
323-
self.logger.info(f"Render setting '{full_key}' changed. Emitting signal.")
323+
self.logger.debug(f"渲染设置已更改: '{full_key}'")
324324
self.render_setting_changed.emit()
325325

326326
except Exception as e:

desktop_qt_ui/services/config_service.py

Lines changed: 164 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,23 @@ def __init__(self, root_dir: str):
4747
# Use get_default_config_path() for PyInstaller compatibility
4848
# Temporarily set a placeholder, will be properly set after initialization
4949
self.default_config_path = None
50+
self.user_config_path = None
5051

5152
self.config_path = None # This will hold the path of a loaded file
5253
self.current_config: AppSettings = AppSettings()
5354

5455
# Set the correct default config path
5556
self.default_config_path = self.get_default_config_path()
56-
self.logger.info(f"默认配置文件路径: {self.default_config_path}")
57-
self.logger.info(f"配置文件是否存在: {os.path.exists(self.default_config_path)}")
57+
self.user_config_path = self.get_user_config_path()
58+
self.logger.debug(f"默认配置: {os.path.basename(self.default_config_path)}")
59+
self.logger.debug(f"用户配置: {os.path.basename(self.user_config_path)}")
60+
self.logger.debug(f"默认配置存在: {os.path.exists(self.default_config_path)}")
61+
self.logger.debug(f"用户配置存在: {os.path.exists(self.user_config_path)}")
5862
if hasattr(sys, '_MEIPASS'):
59-
self.logger.info(f"打包环境,sys._MEIPASS = {sys._MEIPASS}")
63+
self.logger.debug(f"打包环境,sys._MEIPASS = {sys._MEIPASS}")
6064

61-
# Try to load the default config on startup
62-
if os.path.exists(self.default_config_path):
63-
self.logger.info(f"正在加载配置文件: {self.default_config_path}")
64-
success = self.load_config_file(self.default_config_path)
65-
if success:
66-
self.logger.info("配置文件加载成功")
67-
else:
68-
self.logger.error("配置文件加载失败")
69-
else:
70-
self.logger.warning(f"Default config file not found at: {self.default_config_path}")
65+
# 加载配置:优先级 用户配置 > 默认配置 > 代码默认值
66+
self._load_configs_with_priority()
7167

7268
self._translator_configs = None
7369
self._env_cache = None
@@ -159,7 +155,7 @@ def deep_update(target, source):
159155
self.current_config = AppSettings.parse_obj(new_config_dict)
160156

161157
self.config_path = config_path
162-
self.logger.info(f"成功加载配置文件: {config_path}")
158+
self.logger.debug(f"加载配置: {os.path.basename(config_path)}")
163159
self.config_changed.emit(self.current_config.dict())
164160
return True
165161

@@ -168,21 +164,58 @@ def deep_update(target, source):
168164
return False
169165

170166
def save_config_file(self, config_path: Optional[str] = None) -> bool:
171-
"""保存JSON配置文件"""
167+
"""
168+
保存JSON配置文件
169+
默认同时保存到用户配置和模板配置
170+
- 模板配置:临时UI状态强制设为默认值
171+
- 用户配置:包含所有配置(保留实际值)
172+
"""
172173
try:
173-
save_path = config_path or self.config_path or self.default_config_path
174-
if not save_path:
175-
self.logger.error("没有指定保存路径,且无默认路径")
176-
return False
177-
178-
os.makedirs(os.path.dirname(save_path), exist_ok=True)
174+
if config_path:
175+
# 如果指定了路径,只保存到指定路径
176+
save_paths = [config_path]
177+
else:
178+
# 默认同时保存到两个文件
179+
save_paths = [self.user_config_path, self.default_config_path]
179180

180-
with open(save_path, 'w', encoding='utf-8') as f:
181-
json.dump(self.current_config.dict(), f, indent=2, ensure_ascii=False)
181+
success_count = 0
182+
for save_path in save_paths:
183+
if not save_path:
184+
continue
182185

183-
self.config_path = save_path
184-
self.logger.info(f"成功保存配置文件: {save_path}")
185-
return True
186+
# 获取当前配置
187+
config_dict = self.current_config.dict()
188+
189+
# 只有保存到模板配置时才重置临时状态
190+
is_default_config = save_path == self.default_config_path
191+
if is_default_config:
192+
# 重置临时UI状态为默认值
193+
if 'app' not in config_dict:
194+
config_dict['app'] = {}
195+
config_dict['app']['last_open_dir'] = '.'
196+
config_dict['app']['last_output_path'] = ''
197+
198+
if 'cli' in config_dict:
199+
config_dict['cli']['verbose'] = False
200+
201+
try:
202+
os.makedirs(os.path.dirname(save_path), exist_ok=True)
203+
204+
with open(save_path, 'w', encoding='utf-8') as f:
205+
json.dump(config_dict, f, indent=2, ensure_ascii=False)
206+
207+
filename = os.path.basename(save_path)
208+
self.logger.debug(f"保存配置: {filename}")
209+
success_count += 1
210+
except Exception as e:
211+
self.logger.error(f"保存配置失败 ({os.path.basename(save_path)}): {e}")
212+
213+
if success_count > 0:
214+
self.config_path = self.user_config_path
215+
return True
216+
else:
217+
self.logger.error("所有配置文件保存失败")
218+
return False
186219

187220
except Exception as e:
188221
self.logger.error(f"保存配置文件失败: {e}")
@@ -202,10 +235,8 @@ def reload_config(self):
202235
# 2. 重新创建 AppSettings 对象 (用于UI设置)
203236
self.current_config = AppSettings()
204237

205-
# 3. 在新创建的 AppSettings 对象之上,重新应用 JSON 配置文件中的设置
206-
config_file_to_load = self.config_path or self.default_config_path
207-
if config_file_to_load and os.path.exists(config_file_to_load):
208-
self.load_config_file(config_file_to_load)
238+
# 3. 按优先级重新加载配置文件
239+
self._load_configs_with_priority()
209240

210241
# 4. 通知所有监听者配置已更改
211242
self.config_changed.emit(self.current_config.dict())
@@ -216,7 +247,7 @@ def reload_from_disk(self):
216247
强制从当前设置的 config_path 重新加载配置, 并通知所有监听者。
217248
"""
218249
if self.config_path and os.path.exists(self.config_path):
219-
self.logger.info(f"正在从 {self.config_path} 强制重载配置...")
250+
self.logger.debug(f"从磁盘重载配置: {os.path.basename(self.config_path)}")
220251
self.load_config_file(self.config_path)
221252
else:
222253
self.logger.warning("无法重载配置:config_path 未设置或文件不存在。")
@@ -232,7 +263,7 @@ def get_config_reference(self) -> AppSettings:
232263
def set_config(self, config: AppSettings) -> None:
233264
"""设置配置并通知监听者"""
234265
self.current_config = config.copy(deep=True)
235-
self.logger.info("配置已更新,正在通知监听者...")
266+
self.logger.debug("配置已更新,正在通知监听者...")
236267
self.config_changed.emit(self.current_config.dict())
237268

238269
def update_config(self, updates: Dict[str, Any]) -> None:
@@ -249,7 +280,7 @@ def deep_update(target, source):
249280
deep_update(new_config_dict, updates)
250281

251282
self.current_config = AppSettings.parse_obj(new_config_dict)
252-
self.logger.info("配置已更新,正在通知监听者...")
283+
self.logger.debug("配置已更新,正在通知监听者...")
253284
self.config_changed.emit(self.current_config.dict())
254285

255286
def load_env_vars(self) -> Dict[str, str]:
@@ -354,6 +385,106 @@ def get_default_config_path(self) -> str:
354385
# 开发环境
355386
return os.path.join(self.root_dir, "examples", "config-example.json")
356387

388+
def get_user_config_path(self) -> str:
389+
"""
390+
获取用户配置文件路径
391+
392+
打包后和开发时都在examples目录
393+
"""
394+
if hasattr(sys, '_MEIPASS'):
395+
# 打包环境:用户配置在_internal/examples目录
396+
return os.path.join(sys._MEIPASS, 'examples', 'config.json')
397+
else:
398+
# 开发环境:用户配置在项目根目录的examples目录
399+
return os.path.join(self.root_dir, "examples", "config.json")
400+
401+
def _load_configs_with_priority(self):
402+
"""
403+
按优先级加载配置文件
404+
优先级:用户配置 > 默认配置 > 代码默认值
405+
"""
406+
# 1. 先加载默认配置(如果存在)
407+
if os.path.exists(self.default_config_path):
408+
self.logger.debug(f"加载默认配置: {os.path.basename(self.default_config_path)}")
409+
self.load_config_file(self.default_config_path)
410+
else:
411+
self.logger.warning(f"默认配置不存在: {os.path.basename(self.default_config_path)}")
412+
413+
# 2. 再加载用户配置(如果存在),覆盖默认配置
414+
if os.path.exists(self.user_config_path):
415+
self.logger.debug(f"加载用户配置: {os.path.basename(self.user_config_path)}")
416+
self.load_config_file(self.user_config_path)
417+
self.config_path = self.user_config_path
418+
else:
419+
self.logger.debug(f"用户配置不存在,使用默认配置")
420+
# 如果用户配置不存在,使用默认配置路径作为保存目标
421+
if os.path.exists(self.default_config_path):
422+
self.config_path = self.default_config_path
423+
424+
# 3. 同步用户配置(添加新字段、删除旧字段)
425+
self._sync_user_config()
426+
427+
def _sync_user_config(self):
428+
"""
429+
同步用户配置文件
430+
- 如果默认配置新增字段 → 添加到用户配置
431+
- 如果默认配置删除字段 → 从用户配置删除
432+
- 保持用户修改的值不变
433+
"""
434+
if not os.path.exists(self.default_config_path):
435+
self.logger.warning("默认配置不存在,跳过同步")
436+
return
437+
438+
try:
439+
# 读取默认配置(作为模板)
440+
with open(self.default_config_path, 'r', encoding='utf-8') as f:
441+
default_data = json.load(f)
442+
443+
# 如果用户配置存在,读取并同步
444+
if os.path.exists(self.user_config_path):
445+
with open(self.user_config_path, 'r', encoding='utf-8') as f:
446+
user_data = json.load(f)
447+
448+
# 同步配置(递归处理嵌套字典)
449+
synced_data = self._sync_dict(default_data, user_data)
450+
451+
# 如果有变化,保存回用户配置
452+
if synced_data != user_data:
453+
self.logger.info("检测到配置结构变化,正在同步用户配置")
454+
with open(self.user_config_path, 'w', encoding='utf-8') as f:
455+
json.dump(synced_data, f, indent=2, ensure_ascii=False)
456+
self.logger.info("用户配置同步完成")
457+
else:
458+
# 用户配置不存在,创建一个空的(只包含用户修改的值)
459+
self.logger.info("用户配置不存在,将在首次保存时创建")
460+
461+
except Exception as e:
462+
self.logger.error(f"同步用户配置失败: {e}")
463+
464+
def _sync_dict(self, template: dict, user: dict) -> dict:
465+
"""
466+
递归同步字典
467+
- 保留模板中存在的键
468+
- 删除模板中不存在的键
469+
- 保持用户设置的值
470+
"""
471+
result = {}
472+
473+
for key in template.keys():
474+
if key in user:
475+
# 用户配置有这个键
476+
if isinstance(template[key], dict) and isinstance(user[key], dict):
477+
# 递归处理嵌套字典
478+
result[key] = self._sync_dict(template[key], user[key])
479+
else:
480+
# 使用用户的值
481+
result[key] = user[key]
482+
else:
483+
# 用户配置没有这个键,使用模板的值
484+
result[key] = template[key]
485+
486+
return result
487+
357488
def load_default_config(self) -> bool:
358489
"""加载默认配置"""
359490
default_path = self.get_default_config_path()

doc/CHANGELOG_v1.8.0.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Changelog v1.8.0
2+
3+
## 修复与改进
4+
5+
- 修复了cpu版内存占用异常的问题
6+
- 添加了用户配置保存

0 commit comments

Comments
 (0)