feat(ai-studio): add prompt template storage

This commit is contained in:
chengma
2026-07-11 12:35:42 +08:00
parent 1267e0a727
commit 3ebefab91e
6 changed files with 81 additions and 4 deletions
+4
View File
@@ -253,6 +253,10 @@ def cover_prompts_dir(config=None) -> str:
return data_path("prompts", "cover", config=config)
def image_studio_prompts_dir(config=None) -> str:
return data_path("prompts", "image_studio", config=config)
def diagnostic_log_dir(config=None) -> str:
return data_path("logs", config=config)
+31
View File
@@ -10,6 +10,7 @@ from . import appconfig
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()
TEMPLATE_EXT = ".txt"
INVALID_NAME_CHARS = set('\\/:*?"<>|')
DEFAULT_PROMPTS_PACKAGE = "app.default_prompts"
@@ -151,6 +152,36 @@ def delete_title_template(name, directory=TITLE_TEMPLATES_DIR) -> None:
delete_template(name, directory)
def list_image_studio_templates(directory=IMAGE_STUDIO_PROMPTS_DIR):
"""Return AI studio prompt template names sorted by display name."""
return list_templates(directory)
def load_image_studio_template(name, directory=IMAGE_STUDIO_PROMPTS_DIR) -> str:
"""Load one AI studio prompt template as raw text."""
return load_template(name, directory)
def save_image_studio_template(name, text, directory=IMAGE_STUDIO_PROMPTS_DIR) -> None:
"""Save one AI studio prompt template as raw UTF-8 text."""
save_template(name, text, directory)
def rename_image_studio_template(old, new, directory=IMAGE_STUDIO_PROMPTS_DIR) -> None:
"""Rename an AI studio prompt template with duplicate-name protection."""
rename_template(old, new, directory)
def delete_image_studio_template(name, directory=IMAGE_STUDIO_PROMPTS_DIR) -> None:
"""Delete one AI studio prompt template."""
delete_template(name, directory)
def list_cover_templates(directory=COVER_PROMPTS_DIR):
"""Return cover template names sorted by display name."""
+3 -2
View File
@@ -80,7 +80,7 @@ imported → collected → generated → applied
- cmhub 网关 Key → `data/config/cmhub.json`,schema `{ "api_key": "..." }`;`config.json` 只保存 Base URL、别名和超时,不保存 Key。
- 业务数据(账号、任务、各阶段结果)→ SQLite `data/cmshopee.db`。
- 图片(采集的旧封面、AI 生成的新封面)→ `data/images/`(路径记在 DB)。
- 提示词 → 标题当前工作文本存单文件 `data/title_prompt.txt`;标题命名模板存 `data/prompts/title/<名称>.txt`;封面命名模板存 `data/prompts/cover/<名称>.txt`。
- 提示词 → 标题当前工作文本存单文件 `data/title_prompt.txt`;标题命名模板存 `data/prompts/title/<名称>.txt`;封面命名模板存 `data/prompts/cover/<名称>.txt`;AI工场完整提示词模板存 `data/prompts/image_studio/<名称>.txt`。
- 登录态 → 各账号 `data/chrome_user_data_dir/<slug>/`。
T-538 后统一数据根为 `data/`:打包版默认 `<exe目录>/data`,源码运行默认项目根 `data/`。`config.json` 中 `user_data_root`、`image_dir`、`db_path` 默认仍保存为 `chrome_user_data_dir`、`images`、`cmshopee.db` 等相对值,运行时由 `appconfig` 解析到 `data/` 下;绝对路径作为高级自定义仍按原值使用。启动时会迁移 T-524 旧包的 exe 顶层数据到 `data/`,并检测 `data/` 可写。
@@ -504,7 +504,8 @@ cmshopee/
│ ├── title_prompt.txt # 标题当前工作文本(启动回显)
│ └── prompts/
│ ├── title/<名称>.txt # 标题提示词命名模板
│ └── cover/<名称>.txt # 封面提示词命名模板
│ ├── cover/<名称>.txt # 封面提示词命名模板
│ └── image_studio/<名称>.txt # AI工场完整提示词模板
└── prototypes/ # 已验证原型/探查脚本(demo/set_*/get_title/cookies/inspect_images/grab/1.py)
# 逻辑待并入 app/editor.py 后清理;见 prototypes/README.md
```
+8 -2
View File
@@ -3,7 +3,7 @@ id: T-589
title: AI工场独立提示词模板数据层与完整 CRUD
phase: 7
deps: [T-583]
status: TODO
status: DONE
created: 2026-07-11
---
@@ -34,4 +34,10 @@ AI工场只有一个完整提示词框,不再区分“主提示词/每张动
## 执行记录
(完成后记录模板路径、复用点和测试结果。)
- 2026-07-11:完成 AI工场独立提示词模板数据层。
- `app/appconfig.py` 新增 `image_studio_prompts_dir(config=None)`,固定解析到 `data/prompts/image_studio/`。
- `app/prompts.py` 复用 T-583 通用 `list/load/save/rename/delete_template`,新增 `list/load/save/rename/delete_image_studio_template` 薄封装;模板内容按原始完整文本保存,不解析、不拆行。
- `tests/test_prompts.py` 覆盖 AI工场模板 CRUD、空名/非法名/重名/不存在错误、原始多行文本保持,以及与标题/封面模板目录隔离;`tests/test_appconfig.py` 覆盖目录路径。
- `docs/04-architecture.md` 已同步 `data/prompts/image_studio/<名称>.txt` 存储口径。
- 当前工作树运行 `py -3.10 -m unittest tests.test_prompts tests.test_appconfig` 仍因无关未提交默认封面模板改名失败(`papa1` 变为「默认」);T-589 本身在 clean worktree 验证通过。
- 验证:在只套用 T-589 diff 的 clean worktree 中运行 `py -3.10 -m unittest tests.test_prompts tests.test_appconfig`(25 tests)、`python -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`py -3.10 -m unittest discover -s tests`(356 tests)和 `git diff --check`,全部通过。
+4
View File
@@ -241,6 +241,10 @@ class AppConfigTests(TempDirMixin, unittest.TestCase):
self.assertEqual(os.path.join(data_root, "title_prompt.txt"), appconfig.title_prompt_path(cfg))
self.assertEqual(os.path.join(data_root, "prompts", "title"), appconfig.title_templates_dir(cfg))
self.assertEqual(os.path.join(data_root, "prompts", "cover"), appconfig.cover_prompts_dir(cfg))
self.assertEqual(
os.path.join(data_root, "prompts", "image_studio"),
appconfig.image_studio_prompts_dir(cfg),
)
self.assertEqual(os.path.join(data_root, "logs"), appconfig.diagnostic_log_dir(cfg))
portable = dict(cfg)
+31
View File
@@ -74,6 +74,37 @@ class PromptTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_image_studio_template_crud_isolated_and_preserves_raw_text(self):
with self.make_temp_dir() as temp_dir:
studio_dir = os.path.join(temp_dir, "prompts", "image_studio")
title_dir = os.path.join(temp_dir, "prompts", "title")
cover_dir = os.path.join(temp_dir, "prompts", "cover")
raw_text = "完整提示词第一行\n第二行:不要拆分\n{商品id}"
prompts.save_image_studio_template("工场模板", raw_text, studio_dir)
prompts.save_title_template("标题模板", "标题", title_dir)
prompts.save_cover_template("封面模板", "封面", cover_dir)
self.assertEqual(["工场模板"], prompts.list_image_studio_templates(studio_dir))
self.assertEqual(raw_text, prompts.load_image_studio_template("工场模板", studio_dir))
self.assertEqual(["标题模板"], prompts.list_title_templates(title_dir))
self.assertEqual(["封面模板"], prompts.list_cover_templates(cover_dir))
prompts.rename_image_studio_template("工场模板", "工场模板2", studio_dir)
self.assertEqual(["工场模板2"], prompts.list_image_studio_templates(studio_dir))
with self.assertRaisesRegex(prompts.PromptError, "提示词模板已存在"):
prompts.rename_image_studio_template("工场模板2", "工场模板2", studio_dir)
with self.assertRaisesRegex(prompts.PromptError, "提示词模板名不能为空"):
prompts.save_image_studio_template("", "x", studio_dir)
with self.assertRaisesRegex(prompts.PromptError, "提示词模板名非法"):
prompts.save_image_studio_template("../bad", "x", studio_dir)
prompts.delete_image_studio_template("工场模板2", studio_dir)
self.assertEqual([], prompts.list_image_studio_templates(studio_dir))
with self.assertRaisesRegex(prompts.PromptError, "提示词模板不存在"):
prompts.load_image_studio_template("工场模板2", studio_dir)
self.assert_removed(temp_dir)
def test_ensure_default_prompts_seeds_empty_user_prompt_files(self):
with self.make_temp_dir() as temp_dir:
title_path = os.path.join(temp_dir, "title_prompt.txt")