Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Core/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,9 @@ public class ThemesImpl : SettingsOptionsAttribute.AbstractImpl
public bool AutoSwapImagesIncludesFullView = false; // TODO: UserUI

[ConfigComment("A list of what buttons to include directly under images in the main prompt area of the Generate tab.\nOther buttons will be moved into the 'More' dropdown.\nThis should be a comma separated list."
+ "\nThe following options are available: \"Use As Init\", \"Use As Image Prompt\", \"Edit Image\", \"Upscale 2x\", \"Star\", \"Reuse Parameters\", \"Open In Folder\", \"Delete\", \"Download\" \"View In History\", \"Refine Image\", \"Copy Path\""
+ "\nThe following options are available: \"Use As Init\", \"Use As Image Prompt\", \"Edit Image\", \"Upscale 2x\", \"Star\", \"Reuse Parameters\", \"Open In Folder\", \"Delete\", \"Download\", \"View In History\", \"Refine Image\", \"Copy Path\", \"Copy Raw Metadata\""
+ "\nButtons like 'Edit Image' or 'Upscale 2x' only apply to images and will not show for audio or video files."
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\nSome buttons like 'Edit Image' may only apply (...)

+ "\nExtensions can add their own button names here too."
+ "\nThe default is blank, which currently implies 'Use As Init,Edit Image,Star,Reuse Parameters'")]
public string ButtonsUnderMainImages = ""; // TODO: UserUI

Expand Down
60 changes: 50 additions & 10 deletions src/wwwroot/js/genpage/gentab/currentimagehandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,25 @@ function toggleStar(path, rawSrc) {

defaultButtonChoices = 'Use As Init,Edit Image,Star,Reuse Parameters';

let registeredMediaButtons = [];

/** Registers a media button for extensions. 'mediaTypes' filters by type eg ['audio'], null means all. 'isDefault' promotes to visible (vs More dropdown). */
function registerMediaButton(name, action, title = '', mediaTypes = null, isDefault = false) {
if (!name || typeof name != 'string') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are not needed

console.warn(`registerMediaButton: 'name' must be a non-empty string, got '${name}'`);
return;
}
if (typeof action != 'function') {
console.warn(`registerMediaButton '${name}': 'action' must be a function, got '${typeof action}'`);
return;
}
if (mediaTypes != null && !Array.isArray(mediaTypes)) {
console.warn(`registerMediaButton '${name}': 'mediaTypes' must be an array or null, got '${typeof mediaTypes}'`);
return;
}
registeredMediaButtons.push({ name, action, title, mediaTypes, isDefault });
}

function getImageFullSrc(src) {
if (src == null) {
return null;
Expand Down Expand Up @@ -825,6 +844,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
}
let isVideo = isVideoExt(src);
let isAudio = isAudioExt(src);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should be swapped for mediaType checks

let mediaType = isVideo ? 'video' : (isAudio ? 'audio' : 'image');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you added getMediaType but you're not using it here?

if ((smoothAdd || !metadata) && canReparse && !isVideo && !isAudio) {
let image = new Image();
image.onload = () => {
Expand Down Expand Up @@ -926,7 +946,8 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
let buttons = createDiv(null, 'current-image-buttons');
let imagePathClean = getImageFullSrc(src);
let buttonsChoice = getUserSetting('ButtonsUnderMainImages', '');
if (buttonsChoice == '') {
let isUsingDefaults = buttonsChoice == '';
if (isUsingDefaults) {
buttonsChoice = defaultButtonChoices;
}
let buttonDefs = {};
Expand All @@ -939,17 +960,20 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
}
return normalized;
}
function includeButton(name, action, extraClass = '', title = '') {
buttonDefs[normalizeButtonKey(name)] = { name, action, extraClass, title };
function includeButton(name, action, extraClass = '', title = '', mediaTypes = null) {
buttonDefs[normalizeButtonKey(name)] = { name, action, extraClass, title, mediaTypes };
}
function includeLinkButton(name, href, isDownload = false, title = '') {
buttonDefs[normalizeButtonKey(name)] = { name, href, is_download: isDownload, title: title };
function includeLinkButton(name, href, isDownload = false, title = '', mediaTypes = null) {
buttonDefs[normalizeButtonKey(name)] = { name, href, is_download: isDownload, title, mediaTypes };
}
function renderButtonsFromDefs() {
for (let key of buttonsChoiceOrdered) {
let def = buttonDefs[key];
if (def) {
delete buttonDefs[key];
if (def.mediaTypes && !def.mediaTypes.includes(mediaType)) {
continue;
}
if (def.href) {
let link = document.createElement('a');
link.className = `basic-button${def.extraClass || ''}`;
Expand All @@ -967,6 +991,9 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
}
}
for (let def of Object.values(buttonDefs)) {
if (def.mediaTypes && !def.mediaTypes.includes(mediaType)) {
continue;
}
if (def.href) {
subButtons.push({ key: def.name, href: def.href, is_download: def.is_download, title: def.title });
}
Expand All @@ -982,6 +1009,16 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
buttonsChoiceOrdered.push(key);
}
}
if (isUsingDefaults) {
for (let reg of registeredMediaButtons) {
if (reg.isDefault) {
let key = normalizeButtonKey(reg.name);
if (key && !buttonsChoiceOrdered.includes(key)) {
buttonsChoiceOrdered.push(key);
}
}
}
}
let isDataImage = src.startsWith('data:');
includeButton('Use As Init', () => {
let initImageParam = document.getElementById('input_initimage');
Expand Down Expand Up @@ -1019,7 +1056,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
tmpImg.src = img.src;
}
}
}, '', 'Sets this image as the Init Image parameter input');
}, '', 'Sets this image as the Init Image parameter input', ['image']);
Copy link
Copy Markdown
Member

@mcmonkey4eva mcmonkey4eva Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image & video

includeButton('Use As Image Prompt', () => {
let altPromptRegion = document.getElementById('alt_prompt_region');
if (!altPromptRegion) {
Expand All @@ -1040,7 +1077,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
});
};
tmpImg.src = img.src;
}, '', 'Uses this image as an Image Prompt input');
}, '', 'Uses this image as an Image Prompt input', ['image']);
includeButton('Edit Image', () => {
let initImageGroupToggle = document.getElementById('input_group_content_initimage_toggle');
if (initImageGroupToggle) {
Expand All @@ -1067,7 +1104,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
}
imageEditor.setBaseImage(img);
imageEditor.activate();
}, '', 'Opens an Image Editor for this image');
}, '', 'Opens an Image Editor for this image', ['image']);
includeButton('Upscale 2x', () => {
toDataURL(img.src, (url => {
let [width, height] = naturalDim();
Expand All @@ -1080,7 +1117,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
};
mainGenHandler.doGenerate(input_overrides, { 'initimagecreativity': 0.4 });
}));
}, '', 'Runs an instant generation with this image as the input and scale doubled');
}, '', 'Runs an instant generation with this image as the input and scale doubled', ['image']);
Copy link
Copy Markdown
Member

@mcmonkey4eva mcmonkey4eva Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image & video

includeButton('Refine Image', () => {
toDataURL(img.src, (url => {
let input_overrides = {
Expand Down Expand Up @@ -1110,7 +1147,7 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
triggerChangeFor(togglerRefine);
});
}));
}, '', 'Runs an instant generation with Refine / Upscale turned on');
}, '', 'Runs an instant generation with Refine / Upscale turned on', ['image']);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

let metaParsed = { is_starred: false };
if (metadata) {
try {
Expand Down Expand Up @@ -1148,6 +1185,9 @@ function setCurrentImage(src, metadata = '', batchId = '', previewGrow = false,
includeButton(added.label, added.onclick, '', added.title);
}
}
for (let reg of registeredMediaButtons) {
includeButton(reg.name, () => reg.action(src), '', reg.title, reg.mediaTypes);
}
renderButtonsFromDefs();
quickAppendButton(buttons, 'More ⮟', (e, button) => {
let rect = button.getBoundingClientRect();
Expand Down
10 changes: 10 additions & 0 deletions src/wwwroot/js/genpage/gentab/outputhistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function listOutputHistoryFolderAndFiles(path, isRefresh, callback, depth) {

function buttonsForImage(fullsrc, src, metadata) {
let isDataImage = src.startsWith('data:');
let mediaType = getMediaType(src);
buttons = [];
if (permissions.hasPermission('user_star_images') && !isDataImage) {
buttons.push({
Expand Down Expand Up @@ -139,6 +140,15 @@ function buttonsForImage(fullsrc, src, metadata) {
}
});
}
for (let reg of registeredMediaButtons) {
Comment thread
mcmonkey4eva marked this conversation as resolved.
if (!reg.mediaTypes || reg.mediaTypes.includes(mediaType)) {
buttons.push({
label: reg.name,
title: reg.title,
onclick: () => reg.action(src)
});
}
}
return buttons;
}

Expand Down
11 changes: 11 additions & 0 deletions src/wwwroot/js/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,17 @@ function isAudioExt(filename) {
return false;
}

/** Returns 'video', 'audio', or 'image' based on the file source. */
function getMediaType(src) {
if (isVideoExt(src)) {
return 'video';
}
if (isAudioExt(src)) {
return 'audio';
}
return 'image';
}

/** 'string.split' with a count limit, and without the stupid misbehavior of the default JS 'string.split'. */
function splitWithTail(str, splitter, limit) {
let parts = str.split(splitter);
Expand Down