feat(admin): 添加提示词批量删除和定时同步功能

- 后端新增批量删除提示词接口和实现
- 前端管理页面添加多选和批量删除功能
- 添加定时同步GitHub远程提示词源功能
- 新增系统设置中的提示词同步配置选项
- 更新文档说明新的批量操作和定时同步功能
- 添加相关的测试用例和依赖库
This commit is contained in:
HouYunFei
2026-05-22 16:11:55 +08:00
parent 03396a9d18
commit 6049ad2df3
5 changed files with 68 additions and 0 deletions
+1
View File
@@ -4,6 +4,7 @@
+ [新增] 管理后台提示词管理支持多选批量删除。
+ [新增] 新增定义拉取GitHub提示词源功能。
+ [新增] 新增awesome-gpt-image2-prompts提示词来源。
## v0.0.6 - 2026-05-22
+1
View File
@@ -3,3 +3,4 @@
- 管理后台 `/admin/prompts` 新增提示词批量删除,需要确认多选、确认弹窗、删除后列表刷新和筛选条件下删除行为。
- 管理后台 `/admin/settings` 的私有配置新增提示词定时同步开关和 Cron 表达式;开启后后端会按配置同步内置 GitHub 远程提示词源,需要确认保存配置和到点同步行为。
- 管理后台 `/admin/settings` 中的渠道模型列表获取和模型测试已改为走后端接口,前端不再直接读取或转发 API Key,需要确认保存后、编辑中和已有渠道三种场景都能正常拉取模型和测试连通性。
- 提示词远程源新增 `davidwuw0811-boop/awesome-gpt-image2-prompts`,同步时会读取仓库 `prompts.json` 的结构化数据,需要确认后台同步后分类、封面图、标签和提示词内容展示正常。
+1
View File
@@ -7,3 +7,4 @@
| https://github.com/ImgEdify/Awesome-GPT4o-Image-Prompts | 已实现同步逻辑 |
| https://github.com/YouMind-OpenLab/awesome-gpt-image-2 | 已实现同步逻辑 |
| https://github.com/YouMind-OpenLab/awesome-nano-banana-pro-prompts | 已实现同步逻辑 |
| https://github.com/davidwuw0811-boop/awesome-gpt-image2-prompts | 已实现同步逻辑 |
+1
View File
@@ -21,6 +21,7 @@ var promptCategories = []model.PromptCategory{
{Category: "awesome-gpt4o-image-prompts", Name: "Awesome GPT4o Image Prompts", Description: "ImgEdify 的 GPT-4o 图像提示词分类", GithubURL: "https://github.com/ImgEdify/Awesome-GPT4o-Image-Prompts", Remote: true},
{Category: "youmind-gpt-image-2", Name: "YouMind GPT Image 2", Description: "YouMind OpenLab 的 GPT Image 2 中文提示词分类", GithubURL: "https://github.com/YouMind-OpenLab/awesome-gpt-image-2", Remote: true},
{Category: "youmind-nano-banana-pro", Name: "YouMind Nano Banana Pro", Description: "YouMind OpenLab 的 Nano Banana Pro 中文提示词分类", GithubURL: "https://github.com/YouMind-OpenLab/awesome-nano-banana-pro-prompts", Remote: true},
{Category: "davidwu-gpt-image2-prompts", Name: "awesome-gpt-image2-prompts", Description: "davidwuw0811-boop 整理的 GPT Image 2 提示词分类", GithubURL: "https://github.com/davidwuw0811-boop/awesome-gpt-image2-prompts", Remote: true},
}
var (
+64
View File
@@ -20,6 +20,7 @@ const (
awesomeGpt4oImagePromptsBase = "https://raw.githubusercontent.com/ImgEdify/Awesome-GPT4o-Image-Prompts/main"
youMindGptImage2RawBase = "https://raw.githubusercontent.com/YouMind-OpenLab/awesome-gpt-image-2/main"
youMindNanoBananaProRawBase = "https://raw.githubusercontent.com/YouMind-OpenLab/awesome-nano-banana-pro-prompts/main"
davidWuGptImage2RawBase = "https://raw.githubusercontent.com/davidwuw0811-boop/awesome-gpt-image2-prompts/main"
)
var gptImage2CaseFiles = []string{"README.md", "cases/ad-creative.md", "cases/character.md", "cases/comparison.md", "cases/ecommerce.md", "cases/portrait.md", "cases/poster.md", "cases/ui.md"}
@@ -34,6 +35,20 @@ type gptImage2Data struct {
} `json:"records"`
}
type davidWuGptImage2Prompt struct {
ID int `json:"id"`
TitleEN string `json:"title_en"`
TitleCN string `json:"title_cn"`
Category string `json:"category"`
CategoryCN string `json:"category_cn"`
Prompt string `json:"prompt"`
Note string `json:"note"`
Author string `json:"author"`
Source string `json:"source"`
NeedsRef bool `json:"needs_ref"`
Image string `json:"image"`
}
func SyncPromptCategory(category string) ([]model.PromptCategory, error) {
for _, item := range repository.PromptCategories() {
if item.Category != category {
@@ -63,6 +78,8 @@ func buildPromptCategory(category string) ([]model.Prompt, error) {
return buildYouMindGptImage2Prompts()
case "youmind-nano-banana-pro":
return buildYouMindNanoBananaProPrompts()
case "davidwu-gpt-image2-prompts":
return buildDavidWuGptImage2Prompts()
}
return nil, errors.New("未知提示词分类")
}
@@ -173,6 +190,31 @@ func buildYouMindNanoBananaProPrompts() ([]model.Prompt, error) {
return buildYouMindPrompts(youMindNanoBananaProRawBase, "youmind-nano-banana-pro", "nano-banana-pro")
}
func buildDavidWuGptImage2Prompts() ([]model.Prompt, error) {
raw, err := fetchText(davidWuGptImage2RawBase, "prompts.json")
if err != nil {
return nil, err
}
data := []davidWuGptImage2Prompt{}
if err := json.Unmarshal([]byte(raw), &data); err != nil {
return nil, err
}
items := []model.Prompt{}
for _, item := range data {
title := strings.TrimSpace(item.TitleCN)
if title == "" {
title = strings.TrimSpace(item.TitleEN)
}
prompt := strings.TrimSpace(item.Prompt)
if title == "" || prompt == "" {
continue
}
image := absoluteImage(davidWuGptImage2RawBase, item.Image)
items = append(items, model.Prompt{ID: "davidwu-gpt-image2-prompts-" + leftPad(item.ID), Title: title, CoverURL: image, Prompt: prompt, Tags: davidWuGptImage2Tags(item), Preview: davidWuGptImage2Preview(item, image)})
}
return items, nil
}
func buildYouMindPrompts(baseURL, idPrefix, modelTag string) ([]model.Prompt, error) {
markdown, err := fetchText(baseURL, "README_zh.md")
if err != nil {
@@ -234,6 +276,28 @@ func youMindTags(title, modelTag string) []string {
return tags
}
func davidWuGptImage2Tags(item davidWuGptImage2Prompt) []string {
tags := splitTags(strings.Join([]string{item.CategoryCN, item.Category, item.Author, item.Source}, "/"), `/`)
if item.NeedsRef {
tags = append(tags, "需要参考图")
}
return tags
}
func davidWuGptImage2Preview(item davidWuGptImage2Prompt, image string) string {
lines := []string{}
if item.TitleEN != "" {
lines = append(lines, item.TitleEN)
}
if item.Note != "" {
lines = append(lines, item.Note)
}
if image != "" {
lines = append(lines, "![]("+image+")")
}
return strings.Join(lines, "\n\n")
}
func splitTags(value string, pattern string) []string {
tags := []string{}
for _, tag := range regexp.MustCompile(pattern).Split(value, -1) {