-
Extends the 跳舞直播间 (dance room) system to one independent live room per character, with an operator playback-management/blacklist layer (T39), a "我的作品" page for paid users (T38), a standalone 人衣图生成 admin tool, and a new admin-dance-library.html page for curating the per-character dance clip pool with AI QC. On top of that, dance clips now have an explicit published (上架/下架) status: passing AI QC (or a manual admin override to "correct") auto- publishes a clip, failing it auto-unpublishes it, and only published clips are eligible for a room's random-fill pool — unreviewed/rejected clips stay in the library but no longer autoplay for viewers. Existing already-QC'd "correct" clips are backfilled to published on first boot so the room doesn't go quiet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MvkJsRcQ2qEuaFea31rBY8
-
…k, stale-task recovery, pagination Person reference photos (any subset of full-body/face x front/side/back) + one garment image -> async background task generating front/side/view composites of that person wearing the garment, via either GPT Image 2 (万界方舟) or wavespeed.ai's Seedream v4. Split into three pages (admin-dressgen.html create / admin-dressgenlist.html list / admin-dressgendetail.html detail) since the combined page's operations column was easy to lose off-screen; the list page's action column is now sticky and every thumbnail opens a click-to-zoom preview. Default provider (image2) auto-falls-back to wavespeed per view on any failure — an explicit, later product decision overriding this session's earlier "no auto-switching around moderation" stance, recorded in the code comment above WAVESPEED_API_KEY. A crashed/restarted process no longer leaves a task stuck in "generating" forever (resumeDressgenTasks(), same idea as T8's resumeDressupGenerations()). GET /api/admin/dressgen is now paginated (?page=&pageSize=, defaults 20) instead of returning the whole file as a bare array. 19 tests in dressgen-generate.test.js, including a real crash-and-restart scenario (SIGKILL against a hanging mock, second process reading the same on-disk store) and the auto-fallback path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Px7XHxSdWfd5b3vfNMwQjd
-
New blacklist store (dressup-store.js) — explicitly distinct from T32's automated face-blacklist model and T26's garment moderation, per M.11's own warning. isBlacklisted(targetType, targetId) is checked at every submission entry point (/api/dance/pick, both paid upload endpoints — rejected before any spend, matching M.11's "不进流水线、不扣积分") and filters GET /api/dance/wardrobe and pickNextDanceClip's random-fill pool (extending T36's expired-character filter to also drop blacklisted garments/characters/generated_videos) — records are never deleted, just excluded from future selection, same "记录保留" spirit as T36. Admin CRUD: GET/POST /api/admin/blacklist, DELETE /api/admin/blacklist/:id — creation attributed via adminActorUserId (T30), same null-over-the-legacy-ADMIN_PASSWORD-channel behavior as credit/adjust. Playback management (需求文档 6.5 节): POST /api/admin/dance/schedule/pin inserts an existing clip into the very next slot (factored T34's priorityInbox-insert logic into a shared insertClipNext() so both call sites move in lockstep); POST .../schedule/skip advances past whatever's currently playing; POST /api/admin/dance/pause freezes only the random-fill loop (paid_boost content still drains immediately even while paused — 需求文档 6.5 节 frames "暂停自动填充" as filler-specific, not a full stop). dance-admin-ops.test.js covers blacklist CRUD/attribution, entry-point rejection for both a blacklisted garment (pick) and a blacklisted user (upload), pin/skip changing the schedule, and pause/resume against a deliberately drained-to-stable-size schedule (comparing exact lengths — comparing clip ids/count directly was flaky since a small mock pool can legitimately re-pick the same clip after a skip). This completes dev-plan-5.md (T31-T39) — 需求文档 9 节's fifth and final planned phase. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DUiYs6UjxDMkfvnjtsKFo
-
Both are pluggable seams (dev-plan-4.md §1.2 items 1-2), same shape as T20's sendVerificationEmail: a fixed env var gates a real vendor call that isn't implemented yet (none has been picked), and the no-vendor path is what actually runs today. describeGarmentImage() — mode 2 has no admin around to hand-write garments.appearanceDescriptor the way admin_seed garments do, so it needs *something* non-empty to fill that field; today that's a fixed placeholder string, swapped for a real vision-model call once a vendor's picked. moderateGarmentImage() — the requirements doc flags image-content moderation as vendor-TBD but never actually lists it as its own task; this fills that gap. Every call writes a moderation_log row (new dressup-store.js export, M.9 schema) regardless of which branch runs, so the audit trail exists before a real vendor does. Default-permissive when unconfigured — T2's upload endpoint already enforces the real trust-boundary checks (magic-byte type check, 8MB cap, EXIF-stripped re-encode); this only covers the *content* check, which nothing here can perform without a vendor. Neither is unit-tested standalone (server.js doesn't export internal functions, matching sendVerificationEmail's existing precedent) — T27 integration-tests both through the actual upload endpoint. Also adds pricing.js to the tmpdir file-copy list in every test that spawns server.js in isolation — server.js now requires it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DUiYs6UjxDMkfvnjtsKFo
-
generateDanceVideo() takes a queued generationJobs record through prompt build -> forward character/garment/dance-ref uploads to H3 Console's /v1/assets -> POST /v1/videos -> poll -> push a traced clip into library.json (characterId/garmentId/refDanceVideoId). Mirrors generateForPrompt's submit/poll/persist shape but keyed off the new generationJobs store so a restart resumes any in-flight job by its h3JobId, same as resumeGenerations() does for the live queue. POST /api/admin/generate accepts characterId/garmentId (+ optional refDanceVideoId, else a random active dance-ref is picked) and skips straight to the cached clip if that character x garment combo was already generated (M.7 dedupe). GET /api/admin/generate/:id polls job status. Tests spin up a mock H3 Console over HTTP so the full submit/poll/land path runs offline. Also fixes two existing test fixtures (dressup-crud, admin-pages-flow) that spawn server.js without copying prompt-template.js — worked until T8 added the first require() of it into server.js, at which point the spawned server silently failed to boot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Generic createStore(filePath) shared by characters.json/garments.json/ dance-refs.json, same full-read/full-write pattern as library.json. Not yet wired into server.js routes — that's T3-T5.