feat(prompt-templates): add dance storyboard and ancient-china MMO HUD templates (#187)

* feat(prompt-templates): add dance storyboard and ancient-china MMO HUD templates

- social-media-post-sensational-girl-dance-storyboard-8-shots: 8-shot storyboard
  prompt set with shared global style tokens, negative prompt, and character
  lock, tuned for GPT-Image-2. Produces a continuous dance choreography as a
  coherent 8-frame sequence.
- game-ui-ancient-china-open-world-mmo-hud: HUD mockup for a Black Myth: Wukong
  style ancient-China open-world MMO, centered on a female swordswoman
  protagonist. Covers character panel, minimap with bagua frame, skill hotbar,
  quest tracker, chat window, and world-space nameplates with Chinese
  typography rules.
- scripts/import-prompt-templates.mjs: preserve hand-authored templates on
  re-run by keeping any JSON whose source.repo is not the upstream CC-BY
  corpus. Previously clearDir wiped the whole directory, which would delete
  first-party curated prompts on the next import.

Both templates carry source attribution to nexu-io/open-design under
Apache-2.0. Categories reuse the existing 'Social Media Post' and 'Game UI'
enum values already present in the gallery.

* feat(prompt-templates): add preview images for dance and MMO HUD templates

- assets/prompt-templates/image/social-media-post-sensational-girl-dance-storyboard-8-shots.jpg:
  an 8-panel storyboard render produced from the template itself, downscaled to
  1024x1536 JPEG for gallery thumbnails.
- assets/prompt-templates/image/game-ui-ancient-china-open-world-mmo-hud.jpg:
  a reference HUD screenshot rendered from the template prompt via gpt-image-2,
  downscaled to 1536x1024 JPEG.
- Both templates now reference these previews via a raw.githubusercontent.com
  URL pointing at nexu-io/open-design main, matching the pattern used by the
  existing YouMind-sourced seeds (cms-assets.youmind.com) so every card in the
  Prompt Gallery carries a thumbnail once the PR merges.

---------

Co-authored-by: Joey <joey@open-design.local>
This commit is contained in:
Joey-nexu
2026-05-01 17:30:02 +08:00
committed by GitHub
parent 9969003aa5
commit 6789dd4c11
5 changed files with 80 additions and 8 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+29 -8
View File
@@ -46,7 +46,7 @@
* All output JSON carries a `source` block so attribution stays intact.
*/
import { mkdir, writeFile, readdir, unlink } from 'node:fs/promises';
import { mkdir, writeFile, readdir, unlink, readFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -317,25 +317,46 @@ function inferTags(title, prompt, surface) {
return Array.from(set).slice(0, lim);
}
async function clearDir(dir) {
// Remove previously generated JSON files. Hand-authored templates (those
// whose `source.repo` is not the upstream CC-BY corpus we import from) are
// preserved so first-party curated prompts aren't wiped on re-run.
async function clearDir(dir, upstreamRepo) {
try {
const files = await readdir(dir);
for (const f of files) {
if (f.endsWith('.json')) {
await unlink(path.join(dir, f));
if (!f.endsWith('.json')) continue;
const filePath = path.join(dir, f);
let keep = false;
try {
const parsed = JSON.parse(await readFile(filePath, 'utf8'));
const repo = parsed?.source?.repo;
if (repo && repo !== upstreamRepo) keep = true;
} catch {
// Unparseable file — treat as generated and remove.
}
if (!keep) await unlink(filePath);
}
} catch {
// missing dir is fine — created below.
}
}
async function writeAll(entries, outDir) {
async function writeAll(entries, outDir, upstreamRepo) {
await mkdir(outDir, { recursive: true });
await clearDir(outDir);
await clearDir(outDir, upstreamRepo);
// De-dup on slug; if two entries collide, keep the first (which is the
// featured one — always parsed before "All Prompts").
// featured one — always parsed before "All Prompts"). Hand-authored
// templates already on disk (preserved by clearDir) also take priority
// so we never overwrite curated first-party prompts.
const seen = new Set();
try {
const existing = await readdir(outDir);
for (const f of existing) {
if (f.endsWith('.json')) seen.add(f.replace(/\.json$/, ''));
}
} catch {
// noop
}
let count = 0;
for (const entry of entries) {
if (seen.has(entry.id)) continue;
@@ -366,7 +387,7 @@ async function main() {
continue;
}
const outDir = ctx.surface === 'image' ? OUT_IMAGE : OUT_VIDEO;
const written = await writeAll(entries, outDir);
const written = await writeAll(entries, outDir, ctx.repo);
if (ctx.surface === 'image') totalImage += written;
else totalVideo += written;
console.log(