diff --git a/src/core/ui/generate-toolbar.ts b/src/core/ui/generate-toolbar.ts index 501d9a4f..1c91bd56 100644 --- a/src/core/ui/generate-toolbar.ts +++ b/src/core/ui/generate-toolbar.ts @@ -257,6 +257,16 @@ export class GenerateToolbar extends BaseToolbar { const rawAsset = record(this.edit.getDocumentClip(this.selectedTrackIdx, this.selectedClipIdx)?.asset); const resolvedAsset = record(this.edit.getResolvedClip(this.selectedTrackIdx, this.selectedClipIdx)?.asset); const options = reconcileGenerationOptions(model, record(rawAsset["options"]), record(resolvedAsset["options"])); + if ( + this.edit.generationSettings && + model.optionNames.includes("imageUrls") && + rawAsset["type"] === "image" && + typeof rawAsset["src"] === "string" && + rawAsset["src"].trim() && + (options["imageUrls"] === undefined || (Array.isArray(options["imageUrls"]) && options["imageUrls"].length === 0)) + ) { + options["imageUrls"] = [rawAsset["src"]]; + } this.edit.updateClip(this.selectedTrackIdx, this.selectedClipIdx, { asset: { model: model.model, options } } as never); this.closeAllPopups(); } diff --git a/tests/generate-toolbar.test.ts b/tests/generate-toolbar.test.ts index 3461d11b..e8080827 100644 --- a/tests/generate-toolbar.test.ts +++ b/tests/generate-toolbar.test.ts @@ -582,6 +582,42 @@ describe("GenerateToolbar", () => { toolbar.dispose(); }); + describe("source images on model selection", () => { + it.each([undefined, [], ["https://cdn/reference.png", "{{REFERENCE}}"]])( + "seeds only empty reference lists and preserves placeholders (%j)", + imageUrls => { + const edit = createMockEdit({ type: "image", prompt: "edit this", src: "https://cdn/resolved.png", options: { imageUrls } }); + edit.getDocumentClip.mockReturnValue({ asset: { type: "image", src: "{{IMAGE}}", options: { imageUrls } } }); + Object.assign(edit, { generationSettings: jest.fn() }); + edit.getGenerationModels.mockReturnValue([model("image-edit", "image", [], [{ name: "imageUrls", title: "Source images" }])]); + const { toolbar, container } = mountToolbar(edit); + container.querySelector("[data-model-value='image-edit']")!.click(); + expect(edit.updateClip).toHaveBeenCalledWith(0, 0, { + asset: { model: "image-edit", options: { imageUrls: imageUrls?.length ? imageUrls : ["{{IMAGE}}"] } } + }); + toolbar.dispose(); + } + ); + + it.each([ + { settings: false, src: "https://cdn/source.png", type: "image", acceptsImages: true }, + { settings: true, src: undefined, type: "image", acceptsImages: true }, + { settings: true, src: " ", type: "image", acceptsImages: true }, + { settings: true, src: "https://cdn/source.mp4", type: "video", acceptsImages: true }, + { settings: true, src: "https://cdn/source.png", type: "image", acceptsImages: false } + ])("does not invent source inputs when unavailable or not enabled (%j)", ({ settings, src, type, acceptsImages }) => { + const edit = createMockEdit({ type, src, prompt: "a cat" }); + if (settings) Object.assign(edit, { generationSettings: jest.fn() }); + edit.getGenerationModels.mockReturnValue([ + model("target", type as GenerationAssetType, [], acceptsImages ? [{ name: "imageUrls", title: "Source images" }] : []) + ]); + const { toolbar, container } = mountToolbar(edit); + container.querySelector("[data-model-value='target']")!.click(); + expect(edit.updateClip).toHaveBeenCalledWith(0, 0, { asset: { model: "target", options: {} } }); + toolbar.dispose(); + }); + }); + describe("host status", () => { it("opens the host action from a status button without generating", () => { const edit = createMockEdit();