feat(ai-studio): add prompt template storage
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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
@@ -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`,全部通过。
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user