Tiny Desk AI 如何構建相片上載表單
Building the photo upload form for Tiny Desk AI
Tiny Desk AI 的相片上載表單按主唱、色士風手和鍵盤手角色保存照片,供生成按固定次序呈現的短篇 AI 桌面音樂會影片。Solo、Duo 或 Trio 決定顯示哪些座位;切換人數不會刪除隱藏照片,成功上載的照片亦可在再次提交時重用其上載 ID。草稿從 IndexedDB 非同步還原,但只有在用戶尚未選新照片時才套用;圖片檢查可及早提示問題,伺服器仍須驗證上載。
The upload form in Tiny Desk AI has three possible seats: lead singer, sax player, and keyboard player. You choose Solo, Duo, or Trio, then add one photo per person. The output is a short AI-generated desk-concert clip with a fixed sequence.
I'm working on this project. This post is about the photo form, because even with only three inputs, there are a few ways to lose someone's choices or assign a photo to the wrong seat.
Give each photo a role
The form stores photos by role:
const CAST_ROLES = ['lead', 'sax', 'keys'] as const;
type CastRole = (typeof CAST_ROLES)[number];
type Photos = Partial<Record<CastRole, File>>;
The selected cast size controls which roles are visible:
const seats = CAST_ROLES.slice(0, cast);
That leaves one ordering to use for rendering the inputs and building the upload list. The singer stays the singer when someone switches from Trio to Solo and back.
Switching the cast size doesn't delete the hidden photos. Removing a photo is a separate action. Someone can compare the options without having to pick the same files again.
There is a limit to this design: each cast is a prefix of the same list. It works for this product because Duo always means lead plus sax. If we added a lead-and-keyboard option, a count alone would no longer describe the selection.
A thumbnail isn't the upload
Each selected file gets an object URL for its preview. The component revokes that URL when the file changes or the component unmounts.
The form also checks that the browser can decode the image, then checks its dimensions and aspect ratio. A file picker accepting JPEG, PNG, and WebP only helps with selection; it doesn't prove that the file is usable. These browser checks give earlier feedback. Server-side validation still has to protect the upload endpoint.
After a successful upload, the form remembers both the File object and the returned upload ID for that role. On another submission attempt, an unchanged file can reuse its ID. Choosing a replacement clears the cached upload for that seat.
That distinction matters when one upload succeeds and a later one fails. There is no reason to send the first photo again while the page still has its successful upload recorded.
Restoring a draft can race with the user
Selected files are saved in IndexedDB. Session storage holds a reference for each role, and the saved records have a 24-hour validity window. This is local recovery in the current browser session, with no cross-device promise.
Loading those files is asynchronous. A person can select a new photo before restoration finishes. If the old draft then overwrites the new selection, the form has undone their work.
The component keeps a choice counter. Restoration only applies while that counter is still zero. Once someone starts choosing photos, their current choices take precedence.
The storage helper also waits for the IndexedDB transaction to complete before resolving a save. A successful put request alone isn't enough: the transaction can still abort.
What the form can't promise
A clear preview and a correct role assignment don't guarantee a good generated video. Faces can drift, especially through motion. The homepage shows the input photos alongside sample results and explains which parts of the clip are fixed, so people can judge the format before paying.
The frontend has a narrower job: keep the selected photos in the right seats, explain invalid inputs, and preserve work where it can. For this form, those details deserve as much attention as the generate button.
來源:DEV Community · dev.to