From 67cd45fa2563231d2d7a9f04cb0a3f79bc91b70a Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 9 Nov 2026 06:57:28 +0000 Subject: [PATCH] fix(chat): attach /music cover art that lands after the song The song can finish before its cover when the image workers are busy. Tag the song message with its music job id and keep looking for the cover for 8 minutes, attaching it to that message when it arrives. Co-Authored-By: Claude Opus 5.5 --- apps/web/src/app/chat/chat-app.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/web/src/app/chat/chat-app.tsx b/apps/web/src/app/chat/chat-app.tsx index 9184584..780451e 100644 --- a/apps/web/src/app/chat/chat-app.tsx +++ b/apps/web/src/app/chat/chat-app.tsx @@ -15,6 +15,8 @@ type Msg = { image?: string; audio?: string; audioType?: string; + /** The music job a song message came from, so late cover art finds it. */ + musicJob?: string; }; type Convo = { id: string; title: string; model: string; messages: Msg[]; updated: number }; type Balance = { spendable_usd: string; earned_spendable_usd: string; prepaid_available_usd: string }; @@ -312,7 +314,24 @@ export function ChatApp({ error }: { error: string | null }) { content: `${written?.title ? `"${written.title}" ` : ""}[music: ${(written?.caption ?? music.prompt).slice(0, 300)}${written?.lyrics || music.lyrics ? " (with lyrics)" : ", instrumental"}] (${Math.round((Date.now() - started) / 1000)}s on a c0mpute worker)${written?.lyrics ? `\n\n${written.lyrics}` : ""}`, audio: url, audioType: blob.type || "audio/mpeg", + musicJob: start.id, }); + // The song can beat its cover (image workers queue separately): keep + // looking for the cover and attach it to this song's message. + if (!coverUrl) { + const jobId: string = start.id; + void (async () => { + const until = Date.now() + 8 * 60_000; + while (Date.now() < until) { + await new Promise((r) => setTimeout(r, 6000)); + const c = await fetch(`${start.poll}/cover`, { cache: "no-store" }).catch(() => null); + if (!c?.ok) continue; + const late = URL.createObjectURL(await c.blob()); + update(id, (cv) => ({ ...cv, messages: cv.messages.map((m) => (m.musicJob === jobId ? { ...m, image: late } : m)) })); + return; + } + })(); + } } catch (e) { if (!(e instanceof DOMException && e.name === "AbortError")) { setFailure(e instanceof Error ? e.message : String(e));