feat([issue-4188]): mood board items gain persisted prompt-from-media analysis - #4234
Merged
Conversation
Board items backed by gallery media gain an Analyze with AI action that runs the video page's Prompt-from-media flow and persists the result on the item as an optional analysis field (prompt, negative, rationale, provider/model, analyzedAt) via the item PATCH — additive on the wire, so no moodBoards gate bump (whole-record LWW preserves unknown keys, same precedent as the pinterest sub-object). The prompt-from-media endpoint now also resolves a gallery video by on-disk filename, matching the video:<filename> refs board items carry (the gallery flow's history-id path is unchanged).
…n't reset the analyzer panel
… gallery-URL normalization A filename-based video analysis now derives its extraction id from the clip's filename stem, so re-analyzing the same board item overwrites its sampled frames under data/video-thumbnails instead of accumulating a new five-frame set per run (matching the history-id path's stable pfm-<id> key). The client analysis-source resolver now mirrors the server's imageUrlToAppAsset contract: legacy bare gallery refs and query/hash-suffixed /data/images URLs reduce to their on-disk basename instead of being unanalyzable or sending the query string as part of the filename.
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 3 of #4188 (mood boards as a universe style-building tool): board items backed by gallery media can now be analyzed with the Prompt-from-media flow, and the result persists on the item.
image:<file>media-key, a gallery-shapedimageUrl, or avideo:<filename>media-key) gain aScanEyeaction that opensPromptFromMediaModalseeded with the item's media. The analyzer is unchanged for its existing hosts; it gains an optionalonResultcallback (threaded through the modal along withkindDefault/children) so a host can persist a successful run.analysison the item — a run auto-saves{ prompt, negativePrompt, rationale, providerId, model, analyzedAt }onto the item via the item PATCH (moodBoardItemAnalysisSchema, media items only,nullclears;analyzedAtstamped server-side when absent). Additive on the wire — whole-record LWW spreads unknown keys and older peers preserve them (same precedent as thepinterestsub-object), so nomoodBoardsgate bump. The card shows an accent-highlighted analyzed state; the modal shows the saved analysis with copy/remove.POST /api/media-jobs/prompt-from-mediaacceptssourceKind:'video'with afilenamein place of the historyvideoId(board video items store the on-disk filename, not an id). The extraction id is stable per clip (pfm-vf-<stem>), so re-analysis overwrites its sampled frames instead of accumulating sets underdata/video-thumbnails. The gallery flow's id path is unchanged.moodBoardItemAnalysisSource) mirrors the server'simageUrlToAppAssetcontract: legacy bare gallery refs and query/hash-suffixed/data/imagesURLs normalize to the on-disk basename; external URLs, non-gallery paths, text items, and legacyvideo:<id>pins on image items are not analyzable (no button).Analysis is per-item and user-triggered with an explicit vision provider/model picker — no batch auto-analysis (AI Provider Usage Policy).
A codex review pass surfaced two P2s, fixed in the third commit: the stable frame-extraction id (was a fresh UUID per run → unbounded thumbnail accumulation) and the legacy/query-string gallery-URL normalization (those items previously got no analyze action, or sent the query string as part of the filename).
Remaining phase of #4188 (board → universe style synthesis) is not in this PR — see the reconciliation comment on the issue.
Refs #4188
Test plan
server/lib/moodBoardValidation.test.js— analysis PATCH cases: full/minimal accepted,nullclears, rejects missing prompt / unknown keys / non-ISOanalyzedAt.server/services/moodBoard/logic.test.js— normalized persistence (explicit nulls + stampedanalyzedAt), client-providedanalyzedAtkept, other patches leave it untouched,nullclears, ignored on text items.server/services/mediaPromptFromMedia.test.js— filename-based video resolution with the stablepfm-vf-<stem>extraction id and no history lookup; extension-less filename rejected before any ffmpeg work.server/routes/mediaJobs.test.js— video source with neithervideoIdnorfilename400s.client/src/lib/moodBoardItemSrc.test.js— analysis-source resolution: video-by-filename with poster, image by media-key/app path (decoded), legacy bare refs, query/hash stripping before basename, and the null cases.client/src/components/media/PromptFromMedia.test.jsx—onResultfires with the payload; a filename-only clip sends novideoId.client/src/pages/MoodBoardDetail.test.jsx— analyze action only on gallery-backed items, persist PATCH payload and analyzed-state flip, saved-analysis view, remove viaanalysis: null.biome checkclean. Full-suite runs were degraded by extreme machine load (load average ~280) — every failing file also fails identically onmainand none are touched by this change; CI is the authoritative gate.