feat(product-suite): add prompt template settings

This commit is contained in:
chengma
2026-07-16 16:18:11 +08:00
parent 613950c9f3
commit eea3fdd0a9
14 changed files with 1017 additions and 35 deletions
+112
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import os
import tempfile
from importlib import resources
from . import appconfig
@@ -11,10 +12,14 @@ TITLE_PROMPT_PATH = appconfig.title_prompt_path()
TITLE_TEMPLATES_DIR = appconfig.title_templates_dir()
COVER_PROMPTS_DIR = appconfig.cover_prompts_dir()
IMAGE_STUDIO_PROMPTS_DIR = appconfig.image_studio_prompts_dir()
PRODUCT_SUITE_PROMPT_PATH = appconfig.product_suite_prompt_path()
TEMPLATE_EXT = ".txt"
INVALID_NAME_CHARS = set('\\/:*?"<>|')
DEFAULT_PROMPTS_PACKAGE = "app.default_prompts"
DEFAULT_COVER_PROMPTS_PACKAGE = "app.default_prompts.cover"
DEFAULT_PRODUCT_SUITE_PROMPTS_PACKAGE = "app.default_prompts.product_suite"
DEFAULT_PRODUCT_SUITE_PROMPT_NAME = "base.txt"
PRODUCT_SUITE_DEFAULT_ERROR = "内置套图提示词模板无效,请重新安装软件或联系技术支持。"
class PromptError(RuntimeError):
@@ -69,6 +74,74 @@ def ensure_default_prompts(
save_cover_template(name, text, cover_prompts_dir)
def read_product_suite_prompt(path=PRODUCT_SUITE_PROMPT_PATH) -> str:
"""Read a product-suite template without hiding invalid user content."""
if not os.path.exists(path):
return ""
try:
with open(path, "r", encoding="utf-8") as fh:
return fh.read()
except (OSError, UnicodeError) as exc:
raise PromptError("套图提示词模板读取失败:%s" % exc) from exc
def load_product_suite_prompt(path=PRODUCT_SUITE_PROMPT_PATH) -> str:
"""Load and validate the current user product-suite template."""
text = read_product_suite_prompt(path)
if not text.strip():
raise PromptError("套图提示词模板不存在或为空")
_validate_product_suite_prompt(text, "套图提示词模板无效")
return text
def load_default_product_suite_prompt() -> str:
"""Load and validate the packaged product-suite template."""
try:
text = (
resources.files(DEFAULT_PRODUCT_SUITE_PROMPTS_PACKAGE)
.joinpath(DEFAULT_PRODUCT_SUITE_PROMPT_NAME)
.read_text(encoding="utf-8")
)
except (FileNotFoundError, ModuleNotFoundError, OSError, UnicodeError) as exc:
raise PromptError(PRODUCT_SUITE_DEFAULT_ERROR) from exc
try:
_validate_product_suite_prompt(text, "内置套图提示词模板无效")
except PromptError as exc:
raise PromptError(PRODUCT_SUITE_DEFAULT_ERROR) from exc
return text
def save_product_suite_prompt(text, path=PRODUCT_SUITE_PROMPT_PATH) -> None:
"""Validate and atomically save the user product-suite template."""
_validate_product_suite_prompt(text, "套图提示词模板无效")
_atomic_write_text(path, str(text))
def ensure_default_product_suite_prompt(path=PRODUCT_SUITE_PROMPT_PATH) -> str:
"""Seed a validated packaged template without overwriting user content."""
if os.path.exists(path):
current = read_product_suite_prompt(path)
if current.strip():
_validate_product_suite_prompt(current, "套图提示词模板无效")
return current
default_text = load_default_product_suite_prompt()
save_product_suite_prompt(default_text, path)
return default_text
def restore_default_product_suite_prompt(path=PRODUCT_SUITE_PROMPT_PATH) -> str:
"""Validate and atomically restore the packaged product-suite template."""
default_text = load_default_product_suite_prompt()
save_product_suite_prompt(default_text, path)
return default_text
def list_templates(directory):
"""Return prompt template names sorted by display name."""
@@ -273,6 +346,45 @@ def _has_non_empty_file(path) -> bool:
return False
def _validate_product_suite_prompt(text, prefix) -> None:
from . import product_suite
try:
product_suite.validate_product_suite_prompt(text)
except product_suite.ProductSuitePromptError as exc:
raise PromptError("%s:%s" % (prefix, exc)) from exc
def _atomic_write_text(path, text) -> None:
target = os.path.abspath(path)
directory = os.path.dirname(target)
if directory:
os.makedirs(directory, exist_ok=True)
temporary_path = ""
try:
with tempfile.NamedTemporaryFile(
mode="w",
encoding="utf-8",
dir=directory or None,
prefix=".prompt-",
suffix=".tmp",
delete=False,
) as handle:
temporary_path = handle.name
handle.write(str(text))
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary_path, target)
except OSError as exc:
raise PromptError("套图提示词模板保存失败:%s" % exc) from exc
finally:
if temporary_path and os.path.exists(temporary_path):
try:
os.remove(temporary_path)
except OSError:
pass
def _read_default_prompt_text(filename) -> str:
try:
return (