🐛 fix(config): 修复 Windows 下配置文件保存时的权限错误

- 在 `ConfigManager.save()` 方法中增加重试逻辑,当 `os.replace` 因目标文件被占用而抛出 `PermissionError` 时,最多重试 3 次,每次间隔 0.1 秒

♻️ refactor(import): 修正模块导入路径

- 在 `LLMService.get_sd_prompt_guide` 方法中,将 `from sd_service import ...` 修正为 `from services.sd_service import ...`,以修复因相对导入路径错误导致的模块未找到问题

🔧 chore(ui): 优化 UI 组件交互性与初始化参数

- 在 `_fn_batch_generate` 函数中,为 `PublishQueue` 实例化明确指定工作空间参数 `"xhs_workspace"`
- 在 `tab_create.py` 的封面图策略 `gr.Radio` 组件中,添加 `interactive=True` 参数以启用用户交互
This commit is contained in:
zhoujie 2026-02-28 21:19:40 +08:00
parent 1ec520b47e
commit 1889e9a222
4 changed files with 13 additions and 3 deletions

View File

@ -95,13 +95,22 @@ class ConfigManager:
def save(self): def save(self):
"""原子写:临时文件 + os.replace防止写中断导致数据损坏""" """原子写:临时文件 + os.replace防止写中断导致数据损坏"""
import time
config_dir = os.path.dirname(os.path.abspath(CONFIG_FILE)) or "." config_dir = os.path.dirname(os.path.abspath(CONFIG_FILE)) or "."
try: try:
fd, tmp_path = tempfile.mkstemp(dir=config_dir, suffix=".tmp", prefix="config_") fd, tmp_path = tempfile.mkstemp(dir=config_dir, suffix=".tmp", prefix="config_")
try: try:
with os.fdopen(fd, "w", encoding="utf-8") as f: with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(self._config, f, indent=4, ensure_ascii=False) json.dump(self._config, f, indent=4, ensure_ascii=False)
# Windows 下目标文件被占用时 os.replace 会抛 PermissionError(WinError 5),重试最多 3 次
for attempt in range(3):
try:
os.replace(tmp_path, CONFIG_FILE) os.replace(tmp_path, CONFIG_FILE)
break
except PermissionError:
if attempt == 2:
raise
time.sleep(0.1)
except Exception: except Exception:
try: try:
os.remove(tmp_path) os.remove(tmp_path)

View File

@ -370,7 +370,7 @@ class LLMService:
@staticmethod @staticmethod
def get_sd_prompt_guide(sd_model_name: str = None, persona: str = None) -> str: def get_sd_prompt_guide(sd_model_name: str = None, persona: str = None) -> str:
"""根据当前 SD 模型 + 人设 生成 LLM 使用的绘图 Prompt 指南(含反 AI 检测指导 + 人设视觉风格)""" """根据当前 SD 模型 + 人设 生成 LLM 使用的绘图 Prompt 指南(含反 AI 检测指导 + 人设视觉风格)"""
from sd_service import SD_MODEL_PROFILES, detect_model_profile, get_persona_sd_profile from services.sd_service import SD_MODEL_PROFILES, detect_model_profile, get_persona_sd_profile
key = detect_model_profile(sd_model_name) if sd_model_name else "juggernautXL" key = detect_model_profile(sd_model_name) if sd_model_name else "juggernautXL"
profile = SD_MODEL_PROFILES.get(key, SD_MODEL_PROFILES["juggernautXL"]) profile = SD_MODEL_PROFILES.get(key, SD_MODEL_PROFILES["juggernautXL"])

View File

@ -70,7 +70,7 @@ def _fn_topic_recommend(model_name):
def _fn_batch_generate(model_name, topics, style, sd_model_name, persona_text, template_name): def _fn_batch_generate(model_name, topics, style, sd_model_name, persona_text, template_name):
"""批量生成文案并入草稿队列""" """批量生成文案并入草稿队列"""
pq = PublishQueue() pq = PublishQueue("xhs_workspace")
return batch_generate_copy( return batch_generate_copy(
model=model_name, model=model_name,
topics=topics, topics=topics,

View File

@ -83,6 +83,7 @@ def build_tab(
label="封面图策略", label="封面图策略",
value="人物特写", value="人物特写",
info="影响 SD 构图和尺寸", info="影响 SD 构图和尺寸",
interactive=True,
) )
quality_mode = gr.Radio( quality_mode = gr.Radio(
sd_preset_names, sd_preset_names,